Skip to content
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

[FEATURE]: Call communication execute with queue #375

Merged
merged 34 commits into from
Aug 11, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
9b682c0
chore: add variables environment for slack
mourabraz May 6, 2022
a8d1e4a
feat: add communication module
mourabraz May 6, 2022
fc55266
chore: add variables environment for slack
mourabraz May 6, 2022
ca58e3e
feat: add communication module
mourabraz May 6, 2022
50c434d
fix: imports
mourabraz Jul 19, 2022
908067e
Merge branch '153-feat-create-slack-channels' of github.com:xgeekshq/…
mourabraz Jul 19, 2022
3c2240c
fix: remove unnecessary if clause
mourabraz Jul 22, 2022
9be0ff2
chore: add redis configuration
mourabraz Jul 22, 2022
d6a64b3
chore: add package.json script queue command
mourabraz Jul 22, 2022
55b4e49
chore: add queue module
mourabraz Jul 22, 2022
7ce9075
chore: run queues in another process
mourabraz Jul 23, 2022
7923477
chore: remove unnecessary consumer
mourabraz Jul 23, 2022
5d56574
refactor: clean and reorganize imports
mourabraz Jul 23, 2022
9b7a477
refactor: reorganize imports
mourabraz Jul 23, 2022
e26545a
chore: implement queue producer and consumer
mourabraz Jul 24, 2022
fad142a
chore: remove innecessary files
mourabraz Jul 24, 2022
a2b58c7
chore: improve log information
mourabraz Jul 24, 2022
2400084
test: fix errors after add queues
mourabraz Jul 24, 2022
4b523a5
chore: move consumer and producer to communication module
mourabraz Jul 25, 2022
6e2e440
chore: remove test outside src folder
mourabraz Jul 25, 2022
43145b8
Merge branch 'main' into feat-call-communication-execute-with-queue
mourabraz Jul 26, 2022
714e70a
chore: move queue module to module folder
mourabraz Jul 26, 2022
7e60ab0
chore: remove unnecessary test
mourabraz Jul 26, 2022
de42dbb
fix: errors after move queue module
mourabraz Jul 26, 2022
5d09d25
chore: add unit tests
mourabraz Jul 26, 2022
afb1ff8
chore: fix typo and remove console.log
mourabraz Jul 26, 2022
bb29aed
chore: add tls configuration for redis on queue module
mourabraz Aug 1, 2022
0a598a5
fix: pr conflicts
mourabraz Aug 2, 2022
56e1fe4
chore: remove unnecessary console.log
mourabraz Aug 2, 2022
9499cab
chore: move queue registration to the interested module
mourabraz Aug 2, 2022
1abd2a0
fix: slack communication consumer path
mourabraz Aug 2, 2022
d3a9e1b
refactor: use assign by destructuring
mourabraz Aug 2, 2022
8604597
Merge branch 'main' into feat-call-communication-execute-with-queue
mourabraz Aug 11, 2022
55e066f
fix: merge conflicts
mourabraz Aug 11, 2022
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
1,490 changes: 1,119 additions & 371 deletions backend/package-lock.json

Large diffs are not rendered by default.

7 changes: 5 additions & 2 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,9 @@
"pre-commit": "lint-staged"
},
"dependencies": {
"@faker-js/faker": "^7.3.0",
"@nestjs-modules/mailer": "^1.8.1",
"@faker-js/faker": "^6.1.2",
"@nestjs-modules/mailer": "^1.6.1",
"@nestjs/bull": "^0.6.0",
"@nestjs/common": "^9.0.0",
"@nestjs/config": "^2.0.0",
"@nestjs/core": "^9.0.0",
Expand All @@ -40,6 +41,7 @@
"@slack/web-api": "^6.7.1",
"axios": "^0.27.2",
"bcrypt": "^5.0.1",
"bull": "^4.8.4",
"class-transformer": "^0.5.1",
"class-validator": "^0.13.2",
"cron": "2.1.0",
Expand Down Expand Up @@ -70,6 +72,7 @@
"@nestjs/schematics": "^9.0.1",
"@nestjs/testing": "^9.0.0",
"@types/bcrypt": "^5.0.0",
"@types/bull": "^3.15.8",
"@types/express": "^4.17.13",
"@types/jest": "^28.1.6",
"@types/node": "^17.0.23",
Expand Down
6 changes: 3 additions & 3 deletions backend/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { CardsModule } from 'modules/cards/cards.module';
import { CommentsModule } from 'modules/comments/comments.module';
import { CommunicationModule } from 'modules/communication/communication.module';
import EmailModule from 'modules/mailer/mailer.module';
import { QueueModule } from 'modules/queue/queue.module';
import SocketModule from 'modules/socket/socket.module';
import TeamsModule from 'modules/teams/teams.module';
import UsersModule from 'modules/users/users.module';
Expand Down Expand Up @@ -41,10 +42,9 @@ if (configuration().azure.enabled) {
if (configuration().smtp.enabled) {
imports.push(EmailModule);
}

if (configuration().slack.enable) {
imports.push(CommunicationModule);
}
if (configuration().slack.enable) {
imports.push(QueueModule);
imports.push(CommunicationModule);
}

Expand Down
6 changes: 5 additions & 1 deletion backend/src/infrastructure/config/config.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,11 @@ const NODE_ENV = process.env.NODE_ENV;
is: 'true',
then: Joi.required(),
otherwise: Joi.optional()
})
}),
REDIS_USER: Joi.string(),
REDIS_PASSWORD: Joi.string(),
REDIS_HOST: Joi.string().required(),
REDIS_PORT: Joi.number().required()
})
})
],
Expand Down
6 changes: 6 additions & 0 deletions backend/src/infrastructure/config/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@ export const configuration = (): Configuration => {
botToken: process.env.SLACK_API_BOT_TOKEN as string,
masterChannelId: process.env.SLACK_MASTER_CHANNEL_ID as string,
channelPrefix: process.env.SLACK_CHANNEL_PREFIX as string
},
redis: {
user: process.env.REDIS_USER as string,
password: process.env.REDIS_PASSWORD as string,
host: process.env.REDIS_HOST as string,
port: parseInt(process.env.REDIS_PORT as string, 10)
}
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,10 @@ export interface Configuration extends AzureConfiguration {
masterChannelId: string;
channelPrefix: string;
};
redis: {
user?: string;
password?: string;
host: string;
port: number;
};
}
20 changes: 19 additions & 1 deletion backend/src/libs/test-utils/mocks/configService.mock.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,32 @@
import { SLACK_CHANNEL_PREFIX, SLACK_MASTER_CHANNEL_ID } from '../../constants/slack';
import { FRONTEND_URL } from 'libs/constants/frontend';

import {
SLACK_API_BOT_TOKEN,
SLACK_CHANNEL_PREFIX,
SLACK_MASTER_CHANNEL_ID
} from '../../constants/slack';

const configService = {
get(key: string) {
switch (key) {
case 'JWT_ACCESS_TOKEN_EXPIRATION_TIME':
return '3600';
default:
return 'UNKNOWN';
}
},
getOrThrow(key: string) {
switch (key) {
case 'JWT_ACCESS_TOKEN_EXPIRATION_TIME':
return '3600';
case SLACK_API_BOT_TOKEN:
return 'ANY_SLACK_API_BOT_TOKEN';
case SLACK_MASTER_CHANNEL_ID:
return 'ANY_SLACK_CHANNEL_ID';
case SLACK_CHANNEL_PREFIX:
return 'ANY_PREFIX_';
case FRONTEND_URL:
return 'ANY_FRONTEND_URL';
default:
return 'UNKNOWN';
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { faker } from '@faker-js/faker';
import { Logger } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import {
ChatMeMessageArguments,
ConversationsCreateArguments,
Expand All @@ -10,6 +9,12 @@ import {
} from '@slack/web-api';
import * as WebClientSlackApi from '@slack/web-api';

import { FRONTEND_URL } from 'libs/constants/frontend';
import {
SLACK_API_BOT_TOKEN,
SLACK_CHANNEL_PREFIX,
SLACK_MASTER_CHANNEL_ID
} from 'libs/constants/slack';
import configService from 'libs/test-utils/mocks/configService.mock';
import { CreateChannelError } from 'modules/communication/errors/create-channel.error';
import { GetProfileError } from 'modules/communication/errors/get-profile.error';
Expand Down Expand Up @@ -125,6 +130,13 @@ jest.mock('@slack/web-api', () => ({
}
}));

const getConfiguration = () => ({
slackApiBotToken: configService.get(SLACK_API_BOT_TOKEN),
slackMasterChannelId: configService.get(SLACK_MASTER_CHANNEL_ID),
slackChannelPrefix: configService.get(SLACK_CHANNEL_PREFIX),
frontendUrl: configService.get(FRONTEND_URL)
});

describe('SlackCommunicationGateAdapter', () => {
let adapter: SlackCommunicationGateAdapter;

Expand All @@ -133,9 +145,7 @@ describe('SlackCommunicationGateAdapter', () => {
jest.spyOn(Logger.prototype, 'warn').mockImplementation(jest.fn);
jest.spyOn(Logger.prototype, 'verbose').mockImplementation(jest.fn);

adapter = new SlackCommunicationGateAdapter(
configService as unknown as ConfigService<Record<string, unknown>, false>
);
adapter = new SlackCommunicationGateAdapter(getConfiguration());
});

it('should be defined', () => {
Expand Down Expand Up @@ -248,9 +258,7 @@ describe('SlackCommunicationGateAdapter', () => {
jest.spyOn(Logger.prototype, 'error').mockImplementation(LoggerErrorMock);
jest.spyOn(Logger.prototype, 'verbose').mockImplementation(jest.fn);

adapterWithErrors = new SlackCommunicationGateAdapter(
configService as unknown as ConfigService<Record<string, unknown>, false>
);
adapterWithErrors = new SlackCommunicationGateAdapter(getConfiguration());
});

afterEach(() => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { Logger } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { WebClient } from '@slack/web-api';

import { SLACK_API_BOT_TOKEN } from 'libs/constants/slack';
import { ConfigurationType } from 'modules/communication/dto/types';
import { CreateChannelError } from 'modules/communication/errors/create-channel.error';
import { GetProfileError } from 'modules/communication/errors/get-profile.error';
import { GetUsersFromChannelError } from 'modules/communication/errors/get-users-from-channel.error';
Expand All @@ -17,8 +16,8 @@ export class SlackCommunicationGateAdapter implements CommunicationGateInterface

private client: WebClient;

constructor(private readonly configService: ConfigService) {
this.client = new WebClient(this.configService.get(SLACK_API_BOT_TOKEN));
constructor(private readonly config: ConfigurationType) {
this.client = new WebClient(this.config.slackApiBotToken);

this.logger.verbose('@slack/web-api client created');
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { faker } from '@faker-js/faker';
import { Logger } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import {
ChatMeMessageArguments,
ConversationsCreateArguments,
Expand All @@ -9,7 +8,12 @@ import {
UsersProfileGetArguments
} from '@slack/web-api';

// import * as WebClientSlackApi from '@slack/web-api';
import { FRONTEND_URL } from 'libs/constants/frontend';
import {
SLACK_API_BOT_TOKEN,
SLACK_CHANNEL_PREFIX,
SLACK_MASTER_CHANNEL_ID
} from 'libs/constants/slack';
import configService from 'libs/test-utils/mocks/configService.mock';
import {
fillDividedBoardsUsersWithTeamUsers,
Expand All @@ -20,7 +24,6 @@ import { SlackExecuteCommunication } from 'modules/communication/applications/sl
import { ChatSlackHandler } from 'modules/communication/handlers/chat-slack.handler';
import { ConversationsSlackHandler } from 'modules/communication/handlers/conversations-slack.handler';
import { UsersSlackHandler } from 'modules/communication/handlers/users-slack.handler';
import { SlackExecuteCommunicationService } from 'modules/communication/services/slack-execute-communication.service';

const slackUsersIds = [
'U023BECGF',
Expand Down Expand Up @@ -140,31 +143,31 @@ jest.spyOn(Logger.prototype, 'error').mockImplementation(jest.fn);
jest.spyOn(Logger.prototype, 'warn').mockImplementation(jest.fn);
jest.spyOn(Logger.prototype, 'verbose').mockImplementation(jest.fn);

function MakeSlackCommunicationGateAdapterStub() {
return new SlackCommunicationGateAdapter(configService as unknown as ConfigService);
}
const getConfiguration = () => ({
slackApiBotToken: configService.getOrThrow(SLACK_API_BOT_TOKEN),
slackMasterChannelId: configService.getOrThrow(SLACK_MASTER_CHANNEL_ID),
slackChannelPrefix: configService.getOrThrow(SLACK_CHANNEL_PREFIX),
frontendUrl: configService.getOrThrow(FRONTEND_URL)
});

describe('SlackExecuteCommunication', () => {
let service: SlackExecuteCommunicationService;

const communicationGateAdapterMocked = MakeSlackCommunicationGateAdapterStub();
let application: SlackExecuteCommunication;
const communicationGateAdapterMocked = new SlackCommunicationGateAdapter(getConfiguration());

beforeAll(async () => {
const application = new SlackExecuteCommunication(
configService as unknown as ConfigService,
application = new SlackExecuteCommunication(
getConfiguration(),
new ConversationsSlackHandler(communicationGateAdapterMocked),
new UsersSlackHandler(communicationGateAdapterMocked),
new ChatSlackHandler(communicationGateAdapterMocked)
);

service = new SlackExecuteCommunicationService(application);
});

it('should be defined', () => {
expect(service).toBeDefined();
expect(application).toBeDefined();
});

it('shoult create channels, invite users, post messages into slack platfomr and returns all teams created', async () => {
it('shoult create channels, invite users, post messages into slack platform and returns all teams created', async () => {
let givenBoard: any = {
_id: 'main-board',
title: 'Main Board',
Expand Down Expand Up @@ -251,7 +254,7 @@ describe('SlackExecuteCommunication', () => {
}
})),
{
role: 'stackholder',
role: 'stakeholder',
user: {
_id: 'any_id',
firstName: 'any_first_name',
Expand All @@ -266,7 +269,7 @@ describe('SlackExecuteCommunication', () => {
givenBoard = translateBoard(givenBoard);
givenBoard = fillDividedBoardsUsersWithTeamUsers(givenBoard);

const result = await service.execute(givenBoard);
const result = await application.execute(givenBoard);

const expected = [
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
import { Logger } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';

import { FRONTEND_URL } from 'libs/constants/frontend';
import { SLACK_CHANNEL_PREFIX, SLACK_MASTER_CHANNEL_ID } from 'libs/constants/slack';
import { TeamDto } from 'modules/communication/dto/team.dto';
import { BoardRoles, BoardType } from 'modules/communication/dto/types';
import { BoardRoles, BoardType, ConfigurationType } from 'modules/communication/dto/types';
import { UserDto } from 'modules/communication/dto/user.dto';
import { BoardNotValidError } from 'modules/communication/errors/board-not-valid.error';
import { ChatHandlerInterface } from 'modules/communication/interfaces/chat.handler.interface';
Expand All @@ -16,25 +13,18 @@ export class SlackExecuteCommunication implements ExecuteCommunicationInterface
private logger = new Logger(SlackExecuteCommunication.name);

constructor(
private readonly configService: ConfigService,
private readonly config: ConfigurationType,
private readonly conversationsHandler: ConversationsHandlerInterface,
private readonly usersHandler: UsersHandlerInterface,
private readonly chatHandler: ChatHandlerInterface
) {}

public async execute(board: BoardType): Promise<TeamDto[]> {
let teams = this.makeTeams(board);

teams = await this.addSlackIdOnTeams(teams);

// create all channels
teams = await this.createAllChannels(teams);

// invite memebers for each channel
teams = await this.inviteAllMembers(teams);

await this.postMessageOnEachChannel(teams);

await this.postMessageOnMasterChannel(teams);

return teams;
Expand Down Expand Up @@ -68,23 +58,20 @@ export class SlackExecuteCommunication implements ExecuteCommunicationInterface
(Note: currently, retrobot does not check if the chosen responsibles joined xgeeks less than 3 months ago, so, if that happens, you have to decide who will take that role in the team. In the future, retrobot will automatically validate this rule.)\n\n
Talent wins games, but teamwork and intelligence wins championships. :fire: :muscle:`;

await this.chatHandler.postMessage(
this.configService.get(SLACK_MASTER_CHANNEL_ID) as string,
generalText
);
await this.chatHandler.postMessage(this.config.slackMasterChannelId, generalText);
}

private async postMessageOnEachChannel(teams: TeamDto[]): Promise<void> {
const generalText = {
member: (
boardId: string
) => `<!channel> In order to proceed with the retro of this month, here is the board link: \n\n
${this.configService.get(FRONTEND_URL)}/boards/${boardId}
${this.config.frontendUrl}/boards/${boardId}
`,
responsible: (
boardId: string
) => `<!channel> In order to proceed with the retro of this month, here is the main board link: \n\n
${this.configService.get(FRONTEND_URL)}/boards/${boardId}
${this.config.frontendUrl}/boards/${boardId}
`
};

Expand Down Expand Up @@ -149,7 +136,7 @@ export class SlackExecuteCommunication implements ExecuteCommunicationInterface

private async addSlackIdOnTeams(teams: TeamDto[]): Promise<TeamDto[]> {
const usersIdsOnSlack = await this.conversationsHandler.getUsersFromChannelSlowly(
this.configService.get(SLACK_MASTER_CHANNEL_ID) as string
this.config.slackMasterChannelId as string
);
const usersProfiles = await this.usersHandler.getProfilesByIds(usersIdsOnSlack);

Expand Down Expand Up @@ -180,9 +167,9 @@ export class SlackExecuteCommunication implements ExecuteCommunicationInterface

const normalizeName = (name: string) => {
// only contain lowercase letters, numbers, hyphens, and underscores, and must be 80 characters or less
const fullName = `${
process.env.NODE_ENV === 'dev' ? new Date().getTime() : ''
}${this.configService.get(SLACK_CHANNEL_PREFIX)}${name}`;
const fullName = `${process.env.NODE_ENV === 'dev' ? new Date().getTime() : ''}${
this.config.slackChannelPrefix
}${name}`;
return fullName
.replace(/\s/, '_')
.replace(/[^a-zA-Z0-9-_]/g, '')
Expand Down
Loading