Skip to content
Open
Show file tree
Hide file tree
Changes from 4 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
5 changes: 4 additions & 1 deletion .env.dist
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,7 @@ TSC_NONPOLLING_WATCHER=1
TEMPO_TOKEN=
TEMPO_URL=
CALAMARI_TOKEN=
CALAMARI_URL=
CALAMARI_URL=
SLACK_API_TOKEN=
SLACK_NOTIFICATIONS_CHANNEL=worklog-monitor
NODE_ICU_DATA=/home/node/app/node_modules/node-icu
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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",
Expand Down
1 change: 1 addition & 0 deletions src/aggregator/aggregator.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,6 @@ import { AggregatorController } from './aggregator.controller';
imports: [TempoModule, CalamariModule, MappedUsersModule],
providers: [AggregatorService],
controllers: [AggregatorController],
exports: [AggregatorService],
})
export class AggregatorModule {}
2 changes: 2 additions & 0 deletions src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -17,6 +18,7 @@ import { AggregatorModule } from './aggregator/aggregator.module';
TempoModule,
CalamariModule,
AggregatorModule,
NotificationsModule,
],
controllers: [AppController],
providers: [AggregatorService],
Expand Down
4 changes: 4 additions & 0 deletions src/config/slack.ts
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,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
channel: process.env.SLACK_NOTIFICATIONS_CHANNEL,
channel: process.env.SLACK_NOTIFICATIONS_CHANNEL || '',

};
19 changes: 19 additions & 0 deletions src/notifications/notifications.controller.spec.ts
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();
});
});
34 changes: 34 additions & 0 deletions src/notifications/notifications.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
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')
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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.
Maybe 204 is better?

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);

lazyUsers.forEach(workLogResult => this.notificationsService.sendToUser(workLogResult, lastWorkingDate));
}

@Post('/slack/to-channel')
public async sendNotificationToChannel(@Query('date') date: string) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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.
Maybe 204 is better?

const lastWorkingDate = new Date(date); // todo get last working date
const users: UserWorklogResult[] = await this.aggregatorService.aggregate(lastWorkingDate);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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);

this.notificationsService.sendToChannel(lazyUsers, lastWorkingDate);
}
}
15 changes: 15 additions & 0 deletions src/notifications/notifications.module.ts
Original file line number Diff line number Diff line change
@@ -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 {
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can be moved to the same line, but it's nothing special :)

19 changes: 19 additions & 0 deletions src/notifications/notifications.service.spec.ts
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();
});
});
36 changes: 36 additions & 0 deletions src/notifications/notifications.service.ts
Original file line number Diff line number Diff line change
@@ -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 = `: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, 'worklog-monitor-int');
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hardcoded channel.

}
}
19 changes: 19 additions & 0 deletions src/notifications/slack/slack.service.spec.ts
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();
});
});
49 changes: 49 additions & 0 deletions src/notifications/slack/slack.service.ts
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}`);
}
}
}
Loading