From 68d0d6bd420d75cd0f16a0ac2dd0e542dd69706e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Czy=C5=BC?= Date: Sat, 28 Sep 2019 00:45:57 +0200 Subject: [PATCH 1/7] feat: added slack integration --- .env.dist | 5 +- package.json | 2 + src/aggregator/aggregator.module.ts | 1 + src/app.module.ts | 2 + .../notifications.controller.spec.ts | 18 ++ src/notifications/notifications.controller.ts | 36 ++++ src/notifications/notifications.module.ts | 15 ++ .../notifications.service.spec.ts | 19 ++ src/notifications/notifications.service.ts | 36 ++++ src/notifications/slack/slack.service.spec.ts | 19 ++ src/notifications/slack/slack.service.ts | 49 +++++ yarn.lock | 194 +++++++++++++----- 12 files changed, 346 insertions(+), 50 deletions(-) create mode 100644 src/notifications/notifications.controller.spec.ts create mode 100644 src/notifications/notifications.controller.ts create mode 100644 src/notifications/notifications.module.ts create mode 100644 src/notifications/notifications.service.spec.ts create mode 100644 src/notifications/notifications.service.ts create mode 100644 src/notifications/slack/slack.service.spec.ts create mode 100644 src/notifications/slack/slack.service.ts diff --git a/.env.dist b/.env.dist index e09f96e..5552cdb 100644 --- a/.env.dist +++ b/.env.dist @@ -7,4 +7,7 @@ TSC_NONPOLLING_WATCHER=1 TEMPO_TOKEN= TEMPO_URL= CALAMARI_TOKEN= -CALAMARI_URL= \ No newline at end of file +CALAMARI_URL= +SLACK_API_TOKEN= +SLACK_NOTIFICATIONS_CHANNEL=worklog-monitor +NODE_ICU_DATA=/home/node/app/node_modules/node-icu diff --git a/package.json b/package.json index c70847d..b731507 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "@nestjs/core": "^6.7.2", "@nestjs/platform-express": "^6.7.2", "@nestjs/swagger": "^3.1.0", + "@slack/web-api": "^5.2.0", "@typescript-eslint/eslint-plugin": "^2.3.1", "@typescript-eslint/eslint-plugin-tslint": "^2.3.1", "@typescript-eslint/parser": "^2.3.1", @@ -33,6 +34,7 @@ "jsonfile": "^5.0.0", "moment": "^2.24.0", "nestjs-config": "^1.4.4", + "node-icu": "^1.1.0", "reflect-metadata": "^0.1.13", "rimraf": "^3.0.0", "rxjs": "^6.5.3", diff --git a/src/aggregator/aggregator.module.ts b/src/aggregator/aggregator.module.ts index 2f8e73a..d021e09 100644 --- a/src/aggregator/aggregator.module.ts +++ b/src/aggregator/aggregator.module.ts @@ -11,5 +11,6 @@ import { AggregatorController } from './aggregator.controller'; imports: [TempoModule, CalamariModule, MappedUsersModule], providers: [AggregatorService], controllers: [AggregatorController], + exports: [AggregatorService], }) export class AggregatorModule {} diff --git a/src/app.module.ts b/src/app.module.ts index 1d7626f..d31ff84 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -3,6 +3,7 @@ import * as path from 'path'; import { Module } from '@nestjs/common'; import { ConfigModule } from 'nestjs-config'; +import { NotificationsModule } from './notifications/notifications.module'; import { AppController } from './app.controller'; import { MappedUsersModule } from './mapped-users/mapped-users.module'; import { TempoModule } from './tempo/tempo.module'; @@ -17,6 +18,7 @@ import { AggregatorModule } from './aggregator/aggregator.module'; TempoModule, CalamariModule, AggregatorModule, + NotificationsModule, ], controllers: [AppController], providers: [AggregatorService], diff --git a/src/notifications/notifications.controller.spec.ts b/src/notifications/notifications.controller.spec.ts new file mode 100644 index 0000000..ab78ea3 --- /dev/null +++ b/src/notifications/notifications.controller.spec.ts @@ -0,0 +1,18 @@ +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); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); +}); diff --git a/src/notifications/notifications.controller.ts b/src/notifications/notifications.controller.ts new file mode 100644 index 0000000..422099c --- /dev/null +++ b/src/notifications/notifications.controller.ts @@ -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 { NotificationsService } from './notifications.service'; + +@Controller('notifications') +export class NotificationsController { + constructor( + private readonly aggregatorService: AggregatorService, + private readonly notificationsService: NotificationsService, + ) { + } + + @Post('/slack/to-users') + public async sendNotificationsToUsers(@Query('date') date: string) { + const lastWorkingDate = new Date(date); // todo get last working date + const users: UserWorklogResult[] = await this.aggregatorService.aggregate(lastWorkingDate); + const lazyUsers = users.filter(user => !user.worklogs.length); + + for (const workLogResult of lazyUsers) { + this.notificationsService.sendToUser(workLogResult, lastWorkingDate); + } + } + + @Post('/slack/to-channel') + public async sendNotificationToChannel(@Query('date') date: string) { + const lastWorkingDate = new Date(date); // todo get last working date + const users: UserWorklogResult[] = await this.aggregatorService.aggregate(lastWorkingDate); + + const lazyUsers = users.filter(user => !user.worklogs.length); + + this.notificationsService.sendToChannel(lazyUsers, lastWorkingDate); + } +} diff --git a/src/notifications/notifications.module.ts b/src/notifications/notifications.module.ts new file mode 100644 index 0000000..6076395 --- /dev/null +++ b/src/notifications/notifications.module.ts @@ -0,0 +1,15 @@ +import { Module } from '@nestjs/common'; + +import { AggregatorModule } from '../aggregator/aggregator.module'; + +import { NotificationsController } from './notifications.controller'; +import { NotificationsService } from './notifications.service'; +import { SlackService } from './slack/slack.service'; + +@Module({ + imports: [AggregatorModule], + controllers: [NotificationsController], + providers: [NotificationsService, SlackService], +}) +export class NotificationsModule { +} diff --git a/src/notifications/notifications.service.spec.ts b/src/notifications/notifications.service.spec.ts new file mode 100644 index 0000000..6993d6e --- /dev/null +++ b/src/notifications/notifications.service.spec.ts @@ -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); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/src/notifications/notifications.service.ts b/src/notifications/notifications.service.ts new file mode 100644 index 0000000..62fe3b4 --- /dev/null +++ b/src/notifications/notifications.service.ts @@ -0,0 +1,36 @@ +import { Injectable } from '@nestjs/common'; + +import { UserWorklogResult } from '../aggregator/interfaces/user-worklog-result.interface'; + +import { SlackService } from './slack/slack.service'; + +@Injectable() +export class NotificationsService { + constructor(private readonly slackService: SlackService) { + } + + 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 = `:warning: Brakujące work logi za *${dateString}*: \n${workLogResults + .map(result => [`:pisiorek: ${result.firstName} ${result.lastName}`]) + .join('\n')}`; + + return this.slackService.sendToChannel(message, 'worklog-monitor-int'); + } +} diff --git a/src/notifications/slack/slack.service.spec.ts b/src/notifications/slack/slack.service.spec.ts new file mode 100644 index 0000000..1ef86e3 --- /dev/null +++ b/src/notifications/slack/slack.service.spec.ts @@ -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); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/src/notifications/slack/slack.service.ts b/src/notifications/slack/slack.service.ts new file mode 100644 index 0000000..e17ab44 --- /dev/null +++ b/src/notifications/slack/slack.service.ts @@ -0,0 +1,49 @@ +import { BadRequestException, Injectable } from '@nestjs/common'; +import { WebAPICallResult, WebClient } from '@slack/web-api'; + +@Injectable() +export class SlackService { + private readonly webClient: WebClient; + + private readonly channel: string; + + constructor() { + this.webClient = new WebClient(process.env.SLACK_API_TOKEN); + this.channel = process.env.SLACK_NOTIFICATIONS_CHANNEL; + } + + public async sendToChannel(message: string, channel = null): Promise { + try { + return this.webClient.chat.postMessage({ + channel: channel || this.channel, + text: message, + }); + } catch (e) { + console.error(e); + throw new BadRequestException(`Unable to post a message to channel ${channel}`); + } + } + + public async sendToUser(message: string, email: string): Promise { + let user; + try { + const userResponse = await this.webClient.users.lookupByEmail({ + email, + }); + user = userResponse.user; + } catch (e) { + console.error(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) { + console.error(e); + throw new BadRequestException(`Unable to post a message to ${email}`); + } + } +} diff --git a/yarn.lock b/yarn.lock index cbd2418..754ff3d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,10 +2,10 @@ # yarn lockfile v1 -"@angular-devkit/core@8.3.5": - version "8.3.5" - resolved "https://registry.yarnpkg.com/@angular-devkit/core/-/core-8.3.5.tgz#d1b58df73a379e29f3ddc1cb36cf29d8e476786e" - integrity sha512-ag7Nr94wQUqCFtZjw+rMET+djGBmLk989Id5lLWViW99g4XFeS+e45mJv3JYRzF218+6EdicZz0DGQRYHekVeg== +"@angular-devkit/core@8.3.6": + version "8.3.6" + resolved "https://registry.yarnpkg.com/@angular-devkit/core/-/core-8.3.6.tgz#6ad4787e3cb8b03234a194dd53e12cf054a4169c" + integrity sha512-kf4ViwjxERlyAnnrbenaUzPr0muixCyupzyiJ2RIuenK3ob9t1fnAsaugZt+Gfo54i3NgfBMKu1xNwnTR7HnAw== dependencies: ajv "6.10.2" fast-json-stable-stringify "2.0.0" @@ -13,25 +13,25 @@ rxjs "6.4.0" source-map "0.7.3" -"@angular-devkit/schematics-cli@0.803.5": - version "0.803.5" - resolved "https://registry.yarnpkg.com/@angular-devkit/schematics-cli/-/schematics-cli-0.803.5.tgz#e468a5c847fea4c7fea799c82876b795b79cf587" - integrity sha512-URF0js+5DVc4FPRJanLhtDHcNhQ1Ed60+Zjh4/i1kbMs9Y8oq0YpuW2DiLlpW/Y9XTaNNe2I0ROhklAxVKIQ/A== +"@angular-devkit/schematics-cli@0.803.6": + version "0.803.6" + resolved "https://registry.yarnpkg.com/@angular-devkit/schematics-cli/-/schematics-cli-0.803.6.tgz#edf16a8c3b8a733f9e38d2a0b28b9e158b7a679e" + integrity sha512-1EeUwp6fU6Cz0XrCkoS/kYvKGAzNafCBJk0VexmPKgaHBdxjsUQNmc0F1Kk33cEf5bd/gkQ43HIovqZOFcsYXA== dependencies: - "@angular-devkit/core" "8.3.5" - "@angular-devkit/schematics" "8.3.5" - "@schematics/schematics" "0.803.5" + "@angular-devkit/core" "8.3.6" + "@angular-devkit/schematics" "8.3.6" + "@schematics/schematics" "0.803.6" inquirer "6.5.1" minimist "1.2.0" rxjs "6.4.0" symbol-observable "1.2.0" -"@angular-devkit/schematics@8.3.5": - version "8.3.5" - resolved "https://registry.yarnpkg.com/@angular-devkit/schematics/-/schematics-8.3.5.tgz#d2db54c7226a338fa101ab2eb09ea0231285c87d" - integrity sha512-RMtM10kS+Docg90jzFMa4HQ+UzX95Gi5rCT/kSydEkBhp+Jeu/B0K2y67Fm2/qTdVNRCujrCpEmtiRcp1qsOQg== +"@angular-devkit/schematics@8.3.6": + version "8.3.6" + resolved "https://registry.yarnpkg.com/@angular-devkit/schematics/-/schematics-8.3.6.tgz#9a21a090208398a70e87d24a66e8d147f14713e0" + integrity sha512-5I4WDIMHw8zuajhXdy2xjtJLglMWE2Bo1Ri4wFR8cmj8nXUQ1fdPMWg3CqiepcNls2c8xXXMBMHZb/FhC32sBw== dependencies: - "@angular-devkit/core" "8.3.5" + "@angular-devkit/core" "8.3.6" rxjs "6.4.0" "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.5.5": @@ -366,18 +366,18 @@ "@types/yargs" "^13.0.0" "@nestjs/cli@^6.9.0": - version "6.9.0" - resolved "https://registry.yarnpkg.com/@nestjs/cli/-/cli-6.9.0.tgz#13a18d645a5482237f0b17e12140da6837a21eea" - integrity sha512-VpxEsOIr3dJUW64tLBNMcHx90Ply/rRptTiGIq59xLmSdso0QyPDFjfWJ9Dd8sCeDitTMAX0e07n74EggbuHxg== + version "6.9.1" + resolved "https://registry.yarnpkg.com/@nestjs/cli/-/cli-6.9.1.tgz#655d5ac10909cb354935222f75a1dfc64968dc97" + integrity sha512-9+cbhOpZHL/BG/xfNX4umESHAxg5Ib8k1Nsmoiiklf32c82yMNYF4g6K96dxYxRdu91j2LMZfubS46SmV0dZrQ== dependencies: - "@angular-devkit/core" "8.3.5" - "@angular-devkit/schematics" "8.3.5" - "@angular-devkit/schematics-cli" "0.803.5" + "@angular-devkit/core" "8.3.6" + "@angular-devkit/schematics" "8.3.6" + "@angular-devkit/schematics-cli" "0.803.6" "@nestjs/schematics" "^6.6.3" "@types/webpack" "4.39.1" chalk "2.4.2" cli-table3 "0.5.1" - commander "3.0.1" + commander "3.0.2" fork-ts-checker-webpack-plugin "^1.5.0" inquirer "7.0.0" node-emoji "1.10.0" @@ -421,12 +421,12 @@ multer "1.4.2" "@nestjs/schematics@^6.6.3": - version "6.6.7" - resolved "https://registry.yarnpkg.com/@nestjs/schematics/-/schematics-6.6.7.tgz#31a4c331cfc815ea5c1c54839a5ad1cc25ed38d1" - integrity sha512-PkrWFgYXfypDMKADFaWsNYcICdgWhvKEMm0UGUtYAmnsmf0xofoRcd63rGXpHmp28WwRxHnDx3iEUJ5G6gdYOw== + version "6.7.0" + resolved "https://registry.yarnpkg.com/@nestjs/schematics/-/schematics-6.7.0.tgz#2da95fc5f1abb4a5e130bb276f74ca5321b249d2" + integrity sha512-571DvTcYFLS6+Vz3sZ/kptwOe4xMLtwi4BrkJNWBHWagyGKe37U6D6GeGxKLFStd842BUvdqD3Q+fJa7rVumKA== dependencies: - "@angular-devkit/core" "8.3.5" - "@angular-devkit/schematics" "8.3.5" + "@angular-devkit/core" "8.3.6" + "@angular-devkit/schematics" "8.3.6" fs-extra "^8.1.0" ts-morph "^4.0.0" typescript "3.4.5" @@ -476,13 +476,42 @@ consola "^2.3.0" node-fetch "^2.3.0" -"@schematics/schematics@0.803.5": - version "0.803.5" - resolved "https://registry.yarnpkg.com/@schematics/schematics/-/schematics-0.803.5.tgz#2f4a3589ace9d24fd56a13cb3b80c51d1562a2aa" - integrity sha512-OpC5LkjPMKJyZOg5dX7NbboHMF89h9OArCBXZKQ6+r3tNxyE8GOw+Of91C0C5QpMP7czC8XKEo1IjxtpQxf6Gw== +"@schematics/schematics@0.803.6": + version "0.803.6" + resolved "https://registry.yarnpkg.com/@schematics/schematics/-/schematics-0.803.6.tgz#628fe91dfe596945cc7f50c14b4d2f6a7dc125dd" + integrity sha512-HfUEdfl2VITO0gl0unAEyqbg3EWKdOeHodUTRVRI6yrOPcQeySLkffyviR7cUG4VSHX6slTPSUcRmii+vRgdfQ== + dependencies: + "@angular-devkit/core" "8.3.6" + "@angular-devkit/schematics" "8.3.6" + +"@slack/logger@^1.0.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@slack/logger/-/logger-1.1.0.tgz#563a97e38670109cba5a1ec1c4f3098bc0d344c6" + integrity sha512-D3tabyLoUrsFy0w3koxaCVv+5ZJfIy+j0QW3PUq0XO3UiVuF5rtpAbqngAYVpeKnxPpqBjeth4XJ3tllKoW3aA== dependencies: - "@angular-devkit/core" "8.3.5" - "@angular-devkit/schematics" "8.3.5" + "@types/node" ">=8.9.0" + +"@slack/types@^1.1.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@slack/types/-/types-1.2.0.tgz#edad17244ccc335afb31c1089a0a86ae480373f5" + integrity sha512-XsksAErF2CN9jqNzKrm3tVkGke0wT6xsIdw80h9PIKaYJzmpt5soweaFGbV3j74VuwIOOq4P5sHjrSZaP0Du0w== + +"@slack/web-api@^5.2.0": + version "5.2.0" + resolved "https://registry.yarnpkg.com/@slack/web-api/-/web-api-5.2.0.tgz#50f282b80809cd00fffee31a5fcc13c77c867c9c" + integrity sha512-z47Q0PVP15/Z6ZQ8NBK5QLVw64ToohTv5lHhbu6D7qs7jgiLNsyxhjJRD/LIvnw5CGXBmN6qV7Vk/5Y9xmcZxA== + dependencies: + "@slack/logger" "^1.0.0" + "@slack/types" "^1.1.0" + "@types/is-stream" "^1.1.0" + "@types/node" ">=8.9.0" + "@types/p-queue" "^2.3.2" + axios "^0.18.0" + eventemitter3 "^3.1.0" + form-data "^2.5.0" + is-stream "^1.1.0" + p-queue "^2.4.2" + p-retry "^4.0.0" "@types/anymatch@*": version "1.3.1" @@ -578,6 +607,13 @@ "@types/minimatch" "*" "@types/node" "*" +"@types/is-stream@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@types/is-stream/-/is-stream-1.1.0.tgz#b84d7bb207a210f2af9bed431dc0fbe9c4143be1" + integrity sha512-jkZatu4QVbR60mpIzjINmtS1ZF4a/FqdTUTBeQDVOQ2PYyidtwFKr0B5G6ERukKwliq+7mIXvxyppwzG5EgRYg== + dependencies: + "@types/node" "*" + "@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0": version "2.0.1" resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz#42995b446db9a48a11a07ec083499a860e9138ff" @@ -637,16 +673,26 @@ resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA== -"@types/node@*", "@types/node@^12.7.8": +"@types/node@*", "@types/node@>=8.9.0", "@types/node@^12.7.8": version "12.7.8" resolved "https://registry.yarnpkg.com/@types/node/-/node-12.7.8.tgz#cb1bf6800238898bc2ff6ffa5702c3cadd350708" integrity sha512-FMdVn84tJJdV+xe+53sYiZS4R5yn1mAIxfj+DVoNiQjTYz1+OYmjwEZr1ev9nU0axXwda0QDbYl06QHanRVH3A== +"@types/p-queue@^2.3.2": + version "2.3.2" + resolved "https://registry.yarnpkg.com/@types/p-queue/-/p-queue-2.3.2.tgz#16bc5fece69ef85efaf2bce8b13f3ebe39c5a1c8" + integrity sha512-eKAv5Ql6k78dh3ULCsSBxX6bFNuGjTmof5Q/T6PiECDq0Yf8IIn46jCyp3RJvCi8owaEmm3DZH1PEImjBMd/vQ== + "@types/range-parser@*": version "1.2.3" resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.3.tgz#7ee330ba7caafb98090bece86a5ee44115904c2c" integrity sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA== +"@types/retry@^0.12.0": + version "0.12.0" + resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.0.tgz#2b35eccfcee7d38cd72ad99232fbd58bffb3c84d" + integrity sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA== + "@types/serve-static@*": version "1.13.3" resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.13.3.tgz#eb7e1c41c4468272557e897e9171ded5e2ded9d1" @@ -1227,6 +1273,14 @@ axios@0.19.0: follow-redirects "1.5.10" is-buffer "^2.0.2" +axios@^0.18.0: + version "0.18.1" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.18.1.tgz#ff3f0de2e7b5d180e757ad98000f1081b87bcea3" + integrity sha512-0BfJq4NSfQXd+SkFdrvFbG7addhYSBA2mQwISr46pD6E5iqkWg02RAs8vyTT/j0RTnoYmeXauBuSv1qKwR179g== + dependencies: + follow-redirects "1.5.10" + is-buffer "^2.0.2" + axobject-query@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-2.0.2.tgz#ea187abe5b9002b377f925d8bf7d1c561adf38f9" @@ -1635,7 +1689,7 @@ chokidar@^2.0.2, chokidar@^2.0.4, chokidar@^2.1.5: optionalDependencies: fsevents "^1.2.7" -chownr@^1.1.1: +chownr@^1.0.1, chownr@^1.1.1: version "1.1.3" resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.3.tgz#42d837d5239688d55f303003a508230fa6727142" integrity sha512-i70fVHhmV3DtTl6nqvZOnIjbY0Pe4kAUjwHj8z0zAdgBtYrJyYwLKCCuRBQ5ppkyL0AkN7HKRnETdmdp1zqNXw== @@ -1796,10 +1850,10 @@ combined-stream@^1.0.6, combined-stream@~1.0.6: dependencies: delayed-stream "~1.0.0" -commander@3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/commander/-/commander-3.0.1.tgz#4595aec3530525e671fb6f85fb173df8ff8bf57a" - integrity sha512-UNgvDd+csKdc9GD4zjtkHKQbT8Aspt2jCBqNSPp53vAS0L1tS9sXB2TCEOPHJ7kt9bN/niWkYj8T3RQSoMXdSQ== +commander@3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/commander/-/commander-3.0.2.tgz#6837c3fb677ad9933d1cfba42dd14d5117d6b39e" + integrity sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow== commander@^2.11.0, commander@^2.12.1, commander@^2.20.0, commander@~2.20.0: version "2.20.0" @@ -2688,6 +2742,11 @@ event-emitter@^0.3.5: d "1" es5-ext "~0.10.14" +eventemitter3@^3.1.0: + version "3.1.2" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-3.1.2.tgz#2d3d48f9c346698fce83a85d7d664e98535df6e7" + integrity sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q== + events@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/events/-/events-3.0.0.tgz#9a0a0dfaf62893d92b875b8f2698ca4114973e88" @@ -3029,7 +3088,7 @@ fork-ts-checker-webpack-plugin@^1.5.0: tapable "^1.0.0" worker-rpc "^0.1.0" -form-data@^2.3.1: +form-data@^2.3.1, form-data@^2.5.0: version "2.5.1" resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.5.1.tgz#f2cbec57b5e59e23716e128fe44d4e5dd23895f4" integrity sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA== @@ -3260,9 +3319,9 @@ growly@^1.3.0: integrity sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE= handlebars@^4.1.2: - version "4.3.1" - resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.3.1.tgz#6febc1890851f62a8932d495cc88d29390fa850d" - integrity sha512-c0HoNHzDiHpBt4Kqe99N8tdLPKAnGCQ73gYMPWtAYM4PwGnf7xl8PBUHJqh9ijlzt2uQKaSRxbXRt+rZ7M2/kA== + version "4.3.3" + resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.3.3.tgz#56dd05fe33d6bd8a7d797351c39a0cdcfd576be5" + integrity sha512-VupOxR91xcGojfINrzMqrvlyYbBs39sXIrWa7YdaQWeBudOlvKEGvCczMfJPgnuwHE/zyH1M6J+IUP6cgDVyxg== dependencies: neo-async "^2.6.0" optimist "^0.6.1" @@ -4851,7 +4910,7 @@ minimist@~0.0.1: resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf" integrity sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8= -minipass@^2.6.0, minipass@^2.8.6: +minipass@^2.0.2, minipass@^2.6.0, minipass@^2.8.6: version "2.8.6" resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.8.6.tgz#620d889ace26356391d010ecb9458749df9b6db5" integrity sha512-lFG7d6g3+/UaFDCOtqPiKAC9zngWWsQZl1g5q6gaONqrjq61SX2xFqXMleQiFVyDpYwa018E9hmlAFY22PCb+A== @@ -4867,7 +4926,7 @@ minipass@^2.9.0: safe-buffer "^5.1.2" yallist "^3.0.0" -minizlib@^1.2.1: +minizlib@^1.0.3, minizlib@^1.2.1: version "1.3.2" resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.3.2.tgz#5d24764998f98112586f7e566bd4c0999769dad4" integrity sha512-lsNFqSHdJ21EwKzCp12HHJGxSMtHkCW1EMA9cceG3MkMNARjuWotZnMe3NKNshAvFXpm4loZqmYsCmRwhS2JMw== @@ -5052,6 +5111,14 @@ node-fetch@^2.3.0: resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.0.tgz#e633456386d4aa55863f676a7ab0daa8fdecb0fd" integrity sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA== +node-icu@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/node-icu/-/node-icu-1.1.0.tgz#2bef30f37f8574649ac08c1cedbe2442cd1072e4" + integrity sha1-K+8w83+FdGSawIwc7b4kQs0QcuQ= + dependencies: + rimraf "^2.6.1" + tar "^3.1.3" + node-int64@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" @@ -5463,11 +5530,24 @@ p-locate@^3.0.0: dependencies: p-limit "^2.0.0" +p-queue@^2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/p-queue/-/p-queue-2.4.2.tgz#03609826682b743be9a22dba25051bd46724fc34" + integrity sha512-n8/y+yDJwBjoLQe1GSJbbaYQLTI7QHNZI2+rpmCDbe++WLf9HC3gf6iqj5yfPAV71W4UF3ql5W1+UBPXoXTxng== + p-reduce@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-reduce/-/p-reduce-1.0.0.tgz#18c2b0dd936a4690a529f8231f58a0fdb6a47dfa" integrity sha1-GMKw3ZNqRpClKfgjH1ig/bakffo= +p-retry@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/p-retry/-/p-retry-4.1.0.tgz#9ce7cef2069e84bf590df3b8ec18d740109338d6" + integrity sha512-oepllyG9gX1qH4Sm20YAKxg1GA7L7puhvGnTfimi31P07zSIj7SDV6YtuAx9nbJF51DES+2CIIRkXs8GKqWJxA== + dependencies: + "@types/retry" "^0.12.0" + retry "^0.12.0" + p-try@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3" @@ -5890,9 +5970,9 @@ rc@^1.0.1, rc@^1.1.6, rc@^1.2.7: strip-json-comments "~2.0.1" react-is@^16.8.1, react-is@^16.8.4: - version "16.9.0" - resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.9.0.tgz#21ca9561399aad0ff1a7701c01683e8ca981edcb" - integrity sha512-tJBzzzIgnnRfEm046qRcURvwQnZVXmuCbscxUO5RWrGTXpon2d4c8mI0D8WE6ydVIm29JiLB6+RslkIvym9Rjw== + version "16.10.0" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.10.0.tgz#3d6a031e57fff73c3cfa0347feb3e8f40c5141e5" + integrity sha512-WRki2sBb7MTpYp7FtDEmSeGKX2vamYyq3rc9o7fKUG+/DHVyJu69NnvJsiSwwhh2Tt8XN40MQHkDBEXwyfxncQ== read-pkg-up@^2.0.0: version "2.0.0" @@ -6141,6 +6221,11 @@ ret@~0.1.10: resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== +retry@^0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" + integrity sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs= + reusify@^1.0.0: version "1.0.4" resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" @@ -6809,6 +6894,17 @@ tapable@^1.0.0, tapable@^1.1.3: resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2" integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA== +tar@^3.1.3: + version "3.2.1" + resolved "https://registry.yarnpkg.com/tar/-/tar-3.2.1.tgz#9aa8e41c88f09e76c166075bc71f93d5166e61b1" + integrity sha512-ZSzds1E0IqutvMU8HxjMaU8eB7urw2fGwTq88ukDOVuUIh0656l7/P7LiVPxhO5kS4flcRJQk8USG+cghQbTUQ== + dependencies: + chownr "^1.0.1" + minipass "^2.0.2" + minizlib "^1.0.3" + mkdirp "^0.5.0" + yallist "^3.0.2" + tar@^4: version "4.4.13" resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.13.tgz#43b364bc52888d555298637b10d60790254ab525" From d521835a1fb0571f55e613a101afdc74474933ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Czy=C5=BC?= Date: Sat, 28 Sep 2019 00:48:16 +0200 Subject: [PATCH 2/7] chore: updated alert information --- src/notifications/notifications.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/notifications/notifications.service.ts b/src/notifications/notifications.service.ts index 62fe3b4..ed63a22 100644 --- a/src/notifications/notifications.service.ts +++ b/src/notifications/notifications.service.ts @@ -27,7 +27,7 @@ export class NotificationsService { month: 'long', day: 'numeric', }); - const message = `:warning: Brakujące work logi za *${dateString}*: \n${workLogResults + const message = `:alert: Brakujące work logi za *${dateString}* :alert: \n\n${workLogResults .map(result => [`:pisiorek: ${result.firstName} ${result.lastName}`]) .join('\n')}`; From e2719647022c0da6a58cd34e1ed80042164c63e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Czy=C5=BC?= Date: Sat, 28 Sep 2019 00:53:33 +0200 Subject: [PATCH 3/7] lint: fixed linter errors --- src/notifications/notifications.controller.spec.ts | 1 + src/notifications/notifications.controller.ts | 4 +--- src/notifications/slack/slack.service.ts | 3 --- 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/notifications/notifications.controller.spec.ts b/src/notifications/notifications.controller.spec.ts index ab78ea3..8d1a37a 100644 --- a/src/notifications/notifications.controller.spec.ts +++ b/src/notifications/notifications.controller.spec.ts @@ -1,4 +1,5 @@ import { Test, TestingModule } from '@nestjs/testing'; + import { NotificationsController } from './notifications.controller'; describe('Notifications Controller', () => { diff --git a/src/notifications/notifications.controller.ts b/src/notifications/notifications.controller.ts index 422099c..4c14685 100644 --- a/src/notifications/notifications.controller.ts +++ b/src/notifications/notifications.controller.ts @@ -19,9 +19,7 @@ export class NotificationsController { const users: UserWorklogResult[] = await this.aggregatorService.aggregate(lastWorkingDate); const lazyUsers = users.filter(user => !user.worklogs.length); - for (const workLogResult of lazyUsers) { - this.notificationsService.sendToUser(workLogResult, lastWorkingDate); - } + lazyUsers.forEach(workLogResult => this.notificationsService.sendToUser(workLogResult, lastWorkingDate)); } @Post('/slack/to-channel') diff --git a/src/notifications/slack/slack.service.ts b/src/notifications/slack/slack.service.ts index e17ab44..c3381e4 100644 --- a/src/notifications/slack/slack.service.ts +++ b/src/notifications/slack/slack.service.ts @@ -19,7 +19,6 @@ export class SlackService { text: message, }); } catch (e) { - console.error(e); throw new BadRequestException(`Unable to post a message to channel ${channel}`); } } @@ -32,7 +31,6 @@ export class SlackService { }); user = userResponse.user; } catch (e) { - console.error(e); throw new BadRequestException(`Unable to lookup user by email '${email}'`); } @@ -42,7 +40,6 @@ export class SlackService { text: message, }); } catch (e) { - console.error(e); throw new BadRequestException(`Unable to post a message to ${email}`); } } From ad9b1ffb02ca6096a3cabbb94b11eebe7cb13c39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Czy=C5=BC?= Date: Sat, 28 Sep 2019 01:01:47 +0200 Subject: [PATCH 4/7] refactor: using config --- src/config/slack.ts | 4 ++++ src/notifications/slack/slack.service.ts | 9 ++++++--- 2 files changed, 10 insertions(+), 3 deletions(-) create mode 100644 src/config/slack.ts diff --git a/src/config/slack.ts b/src/config/slack.ts new file mode 100644 index 0000000..103edcd --- /dev/null +++ b/src/config/slack.ts @@ -0,0 +1,4 @@ +export default { + apiToken: process.env.SLACK_API_TOKEN, + channel: process.env.SLACK_NOTIFICATIONS_CHANNEL, +}; diff --git a/src/notifications/slack/slack.service.ts b/src/notifications/slack/slack.service.ts index c3381e4..8b2b48e 100644 --- a/src/notifications/slack/slack.service.ts +++ b/src/notifications/slack/slack.service.ts @@ -1,5 +1,6 @@ import { BadRequestException, Injectable } from '@nestjs/common'; import { WebAPICallResult, WebClient } from '@slack/web-api'; +import { ConfigService } from 'nestjs-config'; @Injectable() export class SlackService { @@ -7,9 +8,11 @@ export class SlackService { private readonly channel: string; - constructor() { - this.webClient = new WebClient(process.env.SLACK_API_TOKEN); - this.channel = process.env.SLACK_NOTIFICATIONS_CHANNEL; + 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 { From 4dc84ed3b1e0b9bdb19897564b03889ab90d07ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Czy=C5=BC?= Date: Sat, 28 Sep 2019 01:13:21 +0200 Subject: [PATCH 5/7] Update src/config/slack.ts Co-Authored-By: SimonB407 <51948193+SimonB407@users.noreply.github.com> --- src/config/slack.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config/slack.ts b/src/config/slack.ts index 103edcd..67463a8 100644 --- a/src/config/slack.ts +++ b/src/config/slack.ts @@ -1,4 +1,4 @@ export default { - apiToken: process.env.SLACK_API_TOKEN, + apiToken: process.env.SLACK_API_TOKEN || '', channel: process.env.SLACK_NOTIFICATIONS_CHANNEL, }; From dbae959e77007385411a3534f9ca2f99f80520c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Czy=C5=BC?= Date: Sat, 28 Sep 2019 01:17:06 +0200 Subject: [PATCH 6/7] fix: using channel name from config --- src/notifications/notifications.service.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/notifications/notifications.service.ts b/src/notifications/notifications.service.ts index ed63a22..44e8d03 100644 --- a/src/notifications/notifications.service.ts +++ b/src/notifications/notifications.service.ts @@ -1,4 +1,5 @@ import { Injectable } from '@nestjs/common'; +import { ConfigService } from 'nestjs-config'; import { UserWorklogResult } from '../aggregator/interfaces/user-worklog-result.interface'; @@ -6,7 +7,10 @@ import { SlackService } from './slack/slack.service'; @Injectable() export class NotificationsService { - constructor(private readonly slackService: SlackService) { + constructor( + private readonly slackService: SlackService, + private readonly config: ConfigService, + ) { } public sendToUser(workLogResult: UserWorklogResult, date: Date) { @@ -31,6 +35,6 @@ export class NotificationsService { .map(result => [`:pisiorek: ${result.firstName} ${result.lastName}`]) .join('\n')}`; - return this.slackService.sendToChannel(message, 'worklog-monitor-int'); + return this.slackService.sendToChannel(message, this.config.get('slack.channel')); } } From 60eaf02aff04d670ded84ed45bba0d2a65148bbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Czy=C5=BC?= Date: Sat, 28 Sep 2019 01:46:13 +0200 Subject: [PATCH 7/7] feat: using calamariService to get previousWorkingDay --- src/notifications/notifications.controller.ts | 8 +++++--- src/notifications/notifications.module.ts | 3 ++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/notifications/notifications.controller.ts b/src/notifications/notifications.controller.ts index 4c14685..58bbac8 100644 --- a/src/notifications/notifications.controller.ts +++ b/src/notifications/notifications.controller.ts @@ -2,6 +2,7 @@ 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'; @@ -9,13 +10,14 @@ import { NotificationsService } from './notifications.service'; export class NotificationsController { constructor( private readonly aggregatorService: AggregatorService, + private readonly calamariService: CalamariService, private readonly notificationsService: NotificationsService, ) { } @Post('/slack/to-users') public async sendNotificationsToUsers(@Query('date') date: string) { - const lastWorkingDate = new Date(date); // todo get last working date + 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); @@ -24,11 +26,11 @@ export class NotificationsController { @Post('/slack/to-channel') public async sendNotificationToChannel(@Query('date') date: string) { - const lastWorkingDate = new Date(date); // todo get last working date + 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); - this.notificationsService.sendToChannel(lazyUsers, lastWorkingDate); + await this.notificationsService.sendToChannel(lazyUsers, lastWorkingDate); } } diff --git a/src/notifications/notifications.module.ts b/src/notifications/notifications.module.ts index 6076395..3327490 100644 --- a/src/notifications/notifications.module.ts +++ b/src/notifications/notifications.module.ts @@ -1,13 +1,14 @@ 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], + imports: [AggregatorModule, CalamariModule], controllers: [NotificationsController], providers: [NotificationsService, SlackService], })