Skip to content

Commit

Permalink
feat: send board phase to slack (#1156)
Browse files Browse the repository at this point in the history
  • Loading branch information
GoncaloCanteiro authored Feb 28, 2023
1 parent 02a1da9 commit 3d16fb8
Show file tree
Hide file tree
Showing 19 changed files with 263 additions and 17 deletions.
2 changes: 2 additions & 0 deletions backend/src/libs/constants/slack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@ export const SLACK_API_BOT_TOKEN = 'slack.botToken';
export const SLACK_CHANNEL_PREFIX = 'slack.channelPrefix';

export const SLACK_MASTER_CHANNEL_ID = 'slack.masterChannelId';

export const SLACK_ENABLE = 'slack.enable';
12 changes: 12 additions & 0 deletions backend/src/modules/boards/controller/boards.controller.spec.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { ConfigService } from '@nestjs/config';
import configService from 'src/libs/test-utils/mocks/configService.mock';
import { EventEmitterModule } from '@nestjs/event-emitter';
import { getModelToken } from '@nestjs/mongoose';
import { SchedulerRegistry } from '@nestjs/schedule';
Expand Down Expand Up @@ -84,6 +86,10 @@ describe('BoardsController', () => {
provide: getModelToken('TeamUser'),
useValue: {}
},
{
provide: ConfigService,
useValue: configService
},
{
provide: getModelToken('Schedules'),
useValue: {
Expand All @@ -101,6 +107,12 @@ describe('BoardsController', () => {
useValue: {
execute: jest.fn()
}
},
{
provide: CommunicationsType.TYPES.services.SlackSendMessageService,
useValue: {
execute: jest.fn()
}
}
]
}).compile();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ import Board from 'src/modules/boards/entities/board.schema';

export interface BoardRepositoryInterface extends BaseInterfaceRepository<Board> {
getBoard(boardId: string): Promise<Board>;
updatePhase(boardId, phase): Promise<Board>;
}
15 changes: 15 additions & 0 deletions backend/src/modules/boards/repositories/board.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,19 @@ export class BoardRepository
getBoard(boardId: string): Promise<Board> {
return this.findOneById(boardId);
}
updatePhase(boardId, phase): Promise<Board> {
return this.findOneByFieldAndUpdate(
{
_id: boardId
},
{
phase
},
{ new: true },
{
path: 'team',
select: 'name -_id'
}
);
}
}
61 changes: 50 additions & 11 deletions backend/src/modules/boards/services/update.board.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ import { BOARD_PHASE_SERVER_UPDATED } from 'src/libs/constants/phase';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { BoardPhaseDto } from 'src/libs/dto/board-phase.dto';
import PhaseChangeEvent from 'src/modules/socket/events/user-updated-phase.event';
import { SendMessageServiceInterface } from 'src/modules/communication/interfaces/send-message.service.interface';
import { SlackMessageDto } from 'src/modules/communication/dto/slack.message.dto';
import { SLACK_ENABLE, SLACK_MASTER_CHANNEL_ID } from 'src/libs/constants/slack';
import { ConfigService } from '@nestjs/config';
import { BoardPhases } from 'src/libs/enum/board.phases';
import Team from 'src/modules/teams/entities/teams.schema';

@Injectable()
export default class UpdateBoardServiceImpl implements UpdateBoardServiceInterface {
Expand All @@ -43,14 +49,18 @@ export default class UpdateBoardServiceImpl implements UpdateBoardServiceInterfa
private getTeamService: GetTeamServiceInterface,
@Inject(CommunicationsType.TYPES.services.SlackCommunicationService)
private slackCommunicationService: CommunicationServiceInterface,
@Inject(CommunicationsType.TYPES.services.SlackSendMessageService)
private slackSendMessageService: SendMessageServiceInterface,

@InjectModel(BoardUser.name)
private boardUserModel: Model<BoardUserDocument>,
private socketService: SocketGateway,
@Inject(Cards.TYPES.services.DeleteCardService)
private deleteCardService: DeleteCardService,
@Inject(Boards.TYPES.repositories.BoardRepository)
private readonly boardRepository: BoardRepositoryInterface,
private eventEmitter: EventEmitter2
private eventEmitter: EventEmitter2,
private configService: ConfigService
) {}

/**
Expand Down Expand Up @@ -483,23 +493,52 @@ export default class UpdateBoardServiceImpl implements UpdateBoardServiceInterfa
async updatePhase(boardPhaseDto: BoardPhaseDto) {
try {
const { boardId, phase } = boardPhaseDto;
await this.boardModel
.findOneAndUpdate(
{
_id: boardId
},
{
phase
}
)
.exec();
const {
slackEnable,
phase: currentPhase,
team
} = await this.boardRepository.updatePhase(boardId, phase);

this.eventEmitter.emit(BOARD_PHASE_SERVER_UPDATED, new PhaseChangeEvent(boardPhaseDto));

//Sends message to SLACK
if (
(team as Team).name === 'xgeeks' &&
slackEnable === true &&
currentPhase !== BoardPhases.ADDCARDS &&
this.configService.getOrThrow(SLACK_ENABLE)
) {
const message = this.generateMessage(currentPhase, boardId);
const slackMessageDto = new SlackMessageDto(
this.configService.getOrThrow(SLACK_MASTER_CHANNEL_ID),
message
);
this.slackSendMessageService.execute(slackMessageDto);
}
} catch (err) {
throw new BadRequestException(UPDATE_FAILED);
}
}

private generateMessage(phase: string, boardId: string): string {
const today = new Date();

if (phase === BoardPhases.VOTINGPHASE) {
return `Hello team, <https://split.kigroup.de/boards/${boardId}|here> is the ${today.toLocaleString(
'default',
{
month: 'long'
}
)} retro board \n\n <https://split.kigroup.de/boards/${boardId}> \n\n Take a look and please add your votes. \n\nThank you for your collaboration! :ok_hand: Keep rocking :rocket:`;
}

if (phase == BoardPhases.SUBMITED) {
return `Hello team, the ${today.toLocaleString('default', {
month: 'long'
})} retro board was submited \n\nThank you for your collaboration! :ok_hand: Keep rocking :rocket:`;
}
}

private async addBoardUsers(boardUsers: BoardUserDto[]) {
const createdBoardUsers = await this.boardUserModel.insertMany(boardUsers);

Expand Down
12 changes: 12 additions & 0 deletions backend/src/modules/columns/controller/columns.controller.spec.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { ConfigService } from '@nestjs/config';
import configService from 'src/libs/test-utils/mocks/configService.mock';
import { EventEmitterModule } from '@nestjs/event-emitter';
import { getModelToken } from '@nestjs/mongoose';
import { SchedulerRegistry } from '@nestjs/schedule';
Expand Down Expand Up @@ -84,6 +86,10 @@ describe('ColumnsController', () => {
provide: getModelToken('TeamUser'),
useValue: {}
},
{
provide: ConfigService,
useValue: configService
},
{
provide: getModelToken('Schedules'),
useValue: {
Expand All @@ -101,6 +107,12 @@ describe('ColumnsController', () => {
useValue: {
execute: jest.fn()
}
},
{
provide: CommunicationsType.TYPES.services.SlackSendMessageService,
useValue: {
execute: jest.fn()
}
}
]
}).compile();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { ChatHandlerInterface } from 'src/modules/communication/interfaces/chat.handler.interface';
import { SlackMessageDto } from '../dto/slack.message.dto';
import { SendMessageApplicationInterface } from '../interfaces/SendMessageApplication.interface';

export class SlackSendMessageApplication implements SendMessageApplicationInterface {
constructor(private readonly chatHandler: ChatHandlerInterface) {}

public async execute(data: SlackMessageDto): Promise<void> {
await this.postMessageOnChannel(data);
}

private async postMessageOnChannel(data: SlackMessageDto): Promise<void> {
this.chatHandler.postMessage(data.slackChannelId, data.message);
}
}
21 changes: 20 additions & 1 deletion backend/src/modules/communication/communication.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import {
ConversationsHandler,
MergeBoardApplication,
ResponsibleApplication,
SendMessageApplication,
SendMessageService,
UsersHandler
} from 'src/modules/communication/communication.providers';
import { SlackArchiveChannelConsumer } from 'src/modules/communication/consumers/slack-archive-channel.consumer';
Expand All @@ -22,9 +24,11 @@ import { SlackCommunicationProducer } from 'src/modules/communication/producers/
import { SlackAddUserToChannelConsumer } from './consumers/slack-add-user-channel.consummer';
import { SlackMergeBoardConsumer } from './consumers/slack-merge-board.consumer';
import { SlackResponsibleConsumer } from './consumers/slack-responsible.consumer';
import { SlackSendMessageConsumer } from './consumers/slack-send-message.consumer';
import { SlackAddUserToChannelProducer } from './producers/slack-add-user-channel.producer';
import { SlackMergeBoardProducer } from './producers/slack-merge-board.producer';
import { SlackResponsibleProducer } from './producers/slack-responsible.producer';
import { SlackSendMessageProducer } from './producers/slack-send-message-channel.producer';

@Module({
imports: [
Expand Down Expand Up @@ -85,13 +89,25 @@ import { SlackResponsibleProducer } from './producers/slack-responsible.producer
removeOnComplete: SlackAddUserToChannelProducer.REMOVE_ON_COMPLETE,
priority: SlackAddUserToChannelProducer.PRIORITY
}
}),
BullModule.registerQueue({
name: SlackSendMessageProducer.QUEUE_NAME,
defaultJobOptions: {
attempts: SlackSendMessageProducer.ATTEMPTS,
backoff: SlackSendMessageProducer.BACKOFF,
delay: SlackSendMessageProducer.DELAY,
removeOnFail: SlackSendMessageProducer.REMOVE_ON_FAIL,
removeOnComplete: SlackSendMessageProducer.REMOVE_ON_COMPLETE,
priority: SlackSendMessageProducer.PRIORITY
}
})
]
: [])
],
providers: [
CommunicationService,
ArchiveChannelService,
SendMessageService,
...(configuration().slack.enable
? [
CommunicationGateAdapter,
Expand All @@ -103,6 +119,9 @@ import { SlackResponsibleProducer } from './producers/slack-responsible.producer
ResponsibleApplication,
MergeBoardApplication,
AddUserIntoChannelApplication,
SendMessageApplication,
SlackSendMessageConsumer,
SlackSendMessageProducer,
SlackCommunicationProducer,
SlackCommunicationConsumer,
SlackResponsibleProducer,
Expand All @@ -116,6 +135,6 @@ import { SlackResponsibleProducer } from './producers/slack-responsible.producer
]
: [])
],
exports: [CommunicationService, ArchiveChannelService]
exports: [CommunicationService, ArchiveChannelService, SendMessageService]
})
export class CommunicationModule {}
17 changes: 17 additions & 0 deletions backend/src/modules/communication/communication.providers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ import { SlackArchiveChannelService } from 'src/modules/communication/services/s
import { SlackCommunicationService } from 'src/modules/communication/services/slack-communication.service';
import { SlackDisabledCommunicationService } from 'src/modules/communication/services/slack-disabled-communication.service';
import { SlackAddUserIntoChannelApplication } from './applications/slack-add-user-channel.application';
import { SlackSendMessageApplication } from './applications/slack-send-message-channel.application';
import { UsersSlackHandler } from './handlers/users-slack.handler';
import { SlackSendMessageService } from './services/slack-send-messages.service';

export const CommunicationGateAdapter = {
provide: SlackCommunicationGateAdapter,
Expand Down Expand Up @@ -84,6 +86,14 @@ export const CommunicationApplication = {
inject: [ConfigService, ConversationsSlackHandler, UsersSlackHandler, ChatSlackHandler]
};

export const SendMessageApplication = {
provide: TYPES.application.SlackSendMessageApplication,
useFactory: (chatHandler: ChatHandlerInterface) => {
return new SlackSendMessageApplication(chatHandler);
},
inject: [ChatSlackHandler]
};

export const AddUserIntoChannelApplication = {
provide: TYPES.application.SlackAddUserIntoChannelApplication,
useFactory: (
Expand Down Expand Up @@ -146,3 +156,10 @@ export const ArchiveChannelService = {
? SlackArchiveChannelService
: SlackDisabledCommunicationService
};

export const SendMessageService = {
provide: TYPES.services.SlackSendMessageService,
useClass: configuration().slack.enable
? SlackSendMessageService
: SlackDisabledCommunicationService
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { Process, Processor } from '@nestjs/bull';
import { SlackSendMessageProducer } from '../producers/slack-send-message-channel.producer';
import { SlackMessageType } from 'src/modules/communication/dto/types';
import { TYPES } from 'src/modules/communication/interfaces/types';
import { Job } from 'bull';
import { Inject, Logger } from '@nestjs/common';
import { SlackCommunicationEventListeners } from './slack-communication-event-listeners';
import { SendMessageApplicationInterface } from '../interfaces/SendMessageApplication.interface';

@Processor(SlackSendMessageProducer.QUEUE_NAME)
export class SlackSendMessageConsumer extends SlackCommunicationEventListeners<
SlackMessageType,
SlackMessageType
> {
constructor(
@Inject(TYPES.application.SlackSendMessageApplication)
private readonly application: SendMessageApplicationInterface
) {
const logger = new Logger(SlackSendMessageProducer.name);
super(logger);
}

@Process()
override async communication(job: Job<SlackMessageType>) {
const { slackChannelId } = job.data;

this.logger.verbose(
`execute communication for board with id: "${slackChannelId}" and Job id: "${job.id}" (pid ${process.pid})`
);

this.application.execute(job.data);
}
}
9 changes: 9 additions & 0 deletions backend/src/modules/communication/dto/slack.message.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export class SlackMessageDto {
slackChannelId!: string;
message!: string;

constructor(slackChannelId: string, message: string) {
this.slackChannelId = slackChannelId;
this.message = message;
}
}
5 changes: 5 additions & 0 deletions backend/src/modules/communication/dto/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,3 +84,8 @@ export type ArchiveChannelData = {
export type AddUserMainChannelType = {
email: string;
};

export type SlackMessageType = {
slackChannelId: string;
message: string;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { SlackMessageType } from '../dto/types';

export interface SendMessageApplicationInterface {
execute(data: SlackMessageType): Promise<void>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { SlackMessageType } from '../dto/types';

export interface SendMessageServiceInterface {
execute(data: SlackMessageType): Promise<void>;
}
6 changes: 4 additions & 2 deletions backend/src/modules/communication/interfaces/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ export const TYPES = {
SlackMergeBoardApplication: 'SlackMergeBoardApplication',
SlackResponsibleApplication: 'SlackResponsibleApplication',
SlackArchiveChannelApplication: 'SlackArchiveChannelApplication',
SlackAddUserIntoChannelApplication: 'SlackAddUserIntoChannelApplication'
SlackAddUserIntoChannelApplication: 'SlackAddUserIntoChannelApplication',
SlackSendMessageApplication: 'SlackSendMessageApplication'
},
services: {
SlackCommunicationService: 'SlackCommunicationService',
SlackArchiveChannelService: 'SlackArchiveChannelService'
SlackArchiveChannelService: 'SlackArchiveChannelService',
SlackSendMessageService: 'SlackSendMessageService'
}
};
Loading

0 comments on commit 3d16fb8

Please sign in to comment.