From 8af65ffe76048504004938d06cab083f85983a37 Mon Sep 17 00:00:00 2001 From: leonel Date: Thu, 7 Mar 2024 12:16:40 -0300 Subject: [PATCH] Add ability for bot to send a cheatsheet for each submission --- src/commands/__test__/tarea.test.ts | 68 +++++++++- src/commands/tarea/tarea.ts | 60 ++------- .../__test__/handleRobotFaceReaction.spec.ts | 98 +++++++++++++- .../reaction/handleRobotFaceReaction.ts | 123 ++---------------- src/utils/assignmentCheatsheet.ts | 3 + 5 files changed, 188 insertions(+), 164 deletions(-) create mode 100644 src/utils/assignmentCheatsheet.ts diff --git a/src/commands/__test__/tarea.test.ts b/src/commands/__test__/tarea.test.ts index 19eaa23..abe083e 100644 --- a/src/commands/__test__/tarea.test.ts +++ b/src/commands/__test__/tarea.test.ts @@ -17,6 +17,7 @@ import { multipleBlocksOfCodeDetected } from '../../blocks/multipleBlocksOfCodeD import { multipleGitHubLinksDetected } from '../../blocks/multipleGitHubLinksDetected'; import { IThreadResponse } from '../../api/marketplace/thread/IThreadResponse'; import { unknownCommandBlock } from '../../blocks/unknownCommandBlock'; +import { assignmentCheatsheet } from '../../utils/assignmentCheatsheet'; jest.mock('../tarea/uploadTarea'); jest.mock('../../api/marketplace/user/userApi'); @@ -107,7 +108,72 @@ describe('tareaCommandFunction', () => { }); expect(commandMock.say).toHaveBeenCalledWith( - `Tarea subida con éxito <@${usersInfoResponse.user.id}>!\n\nTarea:\n${fullMessage}\n\n*Para agregar correcciones responder en este hilo.*` + `Tarea subida con éxito <@${usersInfoResponse.user.id}>!\n\nTarea:\n${fullMessage}\n\nTe recomendamos leer las siguientes <${assignmentCheatsheet['1']} | notas> que pueden ayudarte a solucionar errores comunes. \n\n*Para agregar correcciones responder en este hilo (no en el mensaje original).*` + ); + + expect(threadApi.create).toHaveBeenCalledTimes(1); + expect(threadApi.create).toHaveBeenCalledWith({ + authorId: env.BOT_ID, + text: chatPostMessageResponse.message.text, + timestamp: chatPostMessageResponse.ts, + }); + }); + + it("should not send a cheatsheet as part of the response if the lesson doesn't have one", async () => { + const typeCodeText = 'console.log("Hello World!!!")'; + const fullMessage = + 'Hola, aca dejo la tarea\n\n```console.log("Hello World!!!")```'; + + commandMock.command = { + text: fullMessage, + channel_name: 'clase-3', + user_id: 'mockId', + }; + + const submissionResponseMock = { + completed: false, + delivery: typeCodeText, + fkStudentId: 1, + fkTaskId: 3, + id: 1, + isActive: true, + viewer: undefined, + } as unknown as ISubmissionResponse; + + commandMock.client.users.info.mockResolvedValueOnce( + // @ts-ignore + usersInfoResponse + ); + + uploadTareaMock.mockResolvedValueOnce(submissionResponseMock); + + commandMock.say.mockResolvedValueOnce({ + ok: true, + ts: chatPostMessageResponse.ts, + message: { + text: chatPostMessageResponse.message.text, + }, + }); + + threadApiCreateMock.mockResolvedValue({} as unknown as IThreadResponse); + + await tareaCommandFunction( + // @ts-ignore + commandMock + ); + + expect(uploadTareaMock).toBeCalledTimes(1); + expect(uploadTareaMock).toHaveBeenCalledWith({ + classNumber: '3', + delivery: typeCodeText, + slackId: usersInfoResponse.user.id, + firstName: usersInfoResponse.user.profile.first_name, + lastName: usersInfoResponse.user.profile.last_name, + email: usersInfoResponse.user.profile.email, + }); + + expect(commandMock.say).toHaveBeenCalledWith( + `Tarea subida con éxito <@${usersInfoResponse.user.id}>!\n\nTarea:\n${fullMessage}\n\n*Para agregar correcciones responder en este hilo (no en el mensaje original).*` ); expect(threadApi.create).toHaveBeenCalledTimes(1); diff --git a/src/commands/tarea/tarea.ts b/src/commands/tarea/tarea.ts index b8b6bb4..4469a2f 100644 --- a/src/commands/tarea/tarea.ts +++ b/src/commands/tarea/tarea.ts @@ -22,6 +22,7 @@ import { multipleBlocksOfCodeDetected } from '../../blocks/multipleBlocksOfCodeD import { multipleSubmissionFormatsDetected } from '../../blocks/multipleSubmissionsDetected'; import { wrongFormatSubmission } from '../../blocks/wrongFormatSubmission'; import { multipleGitHubLinksDetected } from '../../blocks/multipleGitHubLinksDetected'; +import { assignmentCheatsheet } from '../../utils/assignmentCheatsheet'; dotenv.config(); @@ -46,28 +47,19 @@ export const tareaCommandFunction = async ({ respond, client, }: Command): Promise => { - console.log('HERE START TAREA COMMAND FUNCTION'); - console.log('command: ', command); - console.log('client: ', client); - await ack(); + try { - console.log('HERE ENTERS IN TRY BLOCK'); const { user } = (await client.users.info({ user: command.user_id, })) as IUserClient; - console.log('user: ', user); - console.log('client.users.info property "user": ', command.user_id); - if (!user) { throw new Error('Slack-api Error: User not found'); } const classNumber = validateChannelName(command.channel_name); - console.log('classNumber: ', classNumber); - if (!classNumber) { await respond(unknownCommandBlock()); return; @@ -75,8 +67,6 @@ export const tareaCommandFunction = async ({ const submissionHasValidFormat = validateSubmissionFormat(command.text); - console.log('submissionHasValidFormat: ', submissionHasValidFormat); - if (!submissionHasValidFormat) { await respond(wrongFormatSubmission()); return; @@ -86,8 +76,6 @@ export const tareaCommandFunction = async ({ command.text ); - console.log('submissionHasSingleFormat: ', submissionHasSingleFormat); - if (!submissionHasSingleFormat) { await respond(multipleSubmissionFormatsDetected()); return; @@ -97,11 +85,6 @@ export const tareaCommandFunction = async ({ command.text ); - console.log( - 'validateIsNotMultipleSubmissions: ', - validateIsNotMultipleSubmissions - ); - if (!validateIsNotMultipleSubmissions && command.text.includes('```')) { await respond(multipleBlocksOfCodeDetected()); return; @@ -120,8 +103,6 @@ export const tareaCommandFunction = async ({ delivery: command.text, }); - console.log('validSubmissionFormat: ', validSubmissionFormat); - if (!validSubmissionFormat) { await respond(wrongFormatBlock()); return; @@ -129,8 +110,6 @@ export const tareaCommandFunction = async ({ const onlySubmissionContent = extractOnlySubmission(command.text); - console.log('onlySubmissionContent: ', onlySubmissionContent); - if (command.text && validSubmissionFormat) { const tarea = await uploadTarea({ classNumber, @@ -141,22 +120,18 @@ export const tareaCommandFunction = async ({ email: user.profile.email as string, }); - console.log('tarea: ', tarea); - console.log('uploadTarea property "classNumber": ', classNumber); - console.log('uploadTarea property "slackId": ', user.id); - console.log('uploadTarea property "delivery": ', onlySubmissionContent); - console.log( - 'uploadTarea property "firstName": ', - user.profile.first_name - ); - console.log('uploadTarea property "lastName": ', user.profile.last_name); - console.log('uploadTarea property "email": ', user.profile.email); - - const messageResponse = await say( - `Tarea subida con éxito <@${ - user.id - }>!\n\nTarea:\n${command.text.trim()}\n\n*Para agregar correcciones responder en este hilo.*` - ); + const cheatsheet = + assignmentCheatsheet[classNumber as keyof typeof assignmentCheatsheet]; + + const submissionResponse = cheatsheet + ? `Tarea subida con éxito <@${ + user.id + }>!\n\nTarea:\n${command.text.trim()}\n\nTe recomendamos leer las siguientes <${cheatsheet} | notas> que pueden ayudarte a solucionar errores comunes. \n\n*Para agregar correcciones responder en este hilo (no en el mensaje original).*` + : `Tarea subida con éxito <@${ + user.id + }>!\n\nTarea:\n${command.text.trim()}\n\n*Para agregar correcciones responder en este hilo (no en el mensaje original).*`; + + const messageResponse = await say(submissionResponse); console.log('messageResponse: ', messageResponse); @@ -168,13 +143,6 @@ export const tareaCommandFunction = async ({ taskId: tarea.taskId, }; - console.log('thread: ', thread); - console.log('thread property "authorId": ', process.env.BOT_ID!); - console.log('thread property "studentId": ', tarea.studentId); - console.log('thread property "text": ', messageResponse.message?.text); - console.log('thread property "timestamp": ', messageResponse.ts); - console.log('thread property "taskId": ', tarea.taskId); - await threadApi.create(thread); } } catch (error) { diff --git a/src/events/reaction/__test__/handleRobotFaceReaction.spec.ts b/src/events/reaction/__test__/handleRobotFaceReaction.spec.ts index 43295ba..cea9159 100644 --- a/src/events/reaction/__test__/handleRobotFaceReaction.spec.ts +++ b/src/events/reaction/__test__/handleRobotFaceReaction.spec.ts @@ -18,6 +18,7 @@ import { import { ISubmissionResponse } from '../../../api/marketplace/submission/ISubmissionResponse'; import threadApi from '../../../api/marketplace/thread/threadApi'; import env from '../../../config/env.config'; +import { assignmentCheatsheet } from '../../../utils/assignmentCheatsheet'; jest.mock('../../../commands/tarea/uploadTarea'); jest.mock('../../../api/marketplace/user/userApi'); @@ -113,7 +114,7 @@ describe('handleRobotFaceReaction', () => { expect(clientMock.chat.postMessage).toBeCalledTimes(2); expect(clientMock.chat.postMessage).toHaveBeenNthCalledWith(1, { channel: messageAuthorEvent.item.channel, - text: `Tarea subida con éxito <@${messageAuthorEvent.item_user}>! \n\nAcá está el <${chatGetPermalinkResponse.permalink}|Link> al mensaje original.\n\n*Para agregar correcciones responder en este hilo (no en el mensaje original).*`, + text: `Tarea subida con éxito <@${messageAuthorEvent.item_user}>! \n\nAcá está el <${chatGetPermalinkResponse.permalink}|Link> al mensaje original. Te recomendamos leer las siguientes <${assignmentCheatsheet['1']} | notas> que pueden ayudarte a solucionar errores comunes. \n\n*Para agregar correcciones responder en este hilo (no en el mensaje original).*`, }); expect(clientMock.chat.postMessage).toHaveBeenNthCalledWith(2, { channel: messageAuthorEvent.item.channel, @@ -214,7 +215,7 @@ describe('handleRobotFaceReaction', () => { expect(clientMock.chat.postMessage).toBeCalledTimes(2); expect(clientMock.chat.postMessage).toHaveBeenNthCalledWith(1, { channel: randomUserEvent.item.channel, - text: `Tarea subida con éxito <@${randomUserEvent.item_user}>! \n\nAcá está el <${chatGetPermalinkResponse.permalink}|Link> al mensaje original.\n\n*Para agregar correcciones responder en este hilo (no en el mensaje original).*`, + text: `Tarea subida con éxito <@${messageAuthorEvent.item_user}>! \n\nAcá está el <${chatGetPermalinkResponse.permalink}|Link> al mensaje original. Te recomendamos leer las siguientes <${assignmentCheatsheet['1']} | notas> que pueden ayudarte a solucionar errores comunes. \n\n*Para agregar correcciones responder en este hilo (no en el mensaje original).*`, }); expect(clientMock.chat.postMessage).toHaveBeenNthCalledWith(2, { channel: randomUserEvent.item.channel, @@ -274,6 +275,99 @@ describe('handleRobotFaceReaction', () => { expect(clientMock.reactions.add).toBeCalledTimes(0); expect(loggerMock.error).toBeCalledTimes(0); }); + + it("should not send a cheatsheet as part of the response if the lesson doesn't have one", async () => { + const typeCodeText = 'console.log("Hello World!!!")'; + + const submissionResponseMock = { + completed: false, + delivery: typeCodeText, + studentId: 1, + taskId: 3, + id: 1, + isActive: true, + viewer: undefined, + } as ISubmissionResponse; + + clientMock.conversations.history.mockResolvedValueOnce( + // @ts-ignore + conversationsHistoryResponse + ); + + clientMock.users.info.mockResolvedValueOnce( + // @ts-ignore + usersInfoResponse + ); + + clientMock.conversations.info.mockResolvedValueOnce( + // @ts-ignore + { + channel: { + ...conversationsInfoResponse, + name: 'clase-3', + name_normalized: 'clase-3', + }, + } + ); + + uploadTareaMock.mockResolvedValueOnce(submissionResponseMock); + + clientMock.chat.getPermalink.mockResolvedValueOnce( + // @ts-ignore + chatGetPermalinkResponse + ); + + clientMock.chat.postMessage.mockResolvedValueOnce( + // @ts-ignore + chatPostMessageResponse + ); + + await handleRobotFaceReaction({ + client: clientMock, + // @ts-ignore + event: messageAuthorEvent, + logger: loggerMock, + }); + + expect(uploadTareaMock).toBeCalledTimes(1); + expect(uploadTareaMock).toHaveBeenCalledWith({ + classNumber: '3', + delivery: typeCodeText, + slackId: messageAuthorEvent.item_user, + firstName: usersInfoResponse.user.profile.first_name, + lastName: usersInfoResponse.user.profile.last_name, + email: usersInfoResponse.user.profile.email, + }); + + expect(clientMock.chat.postMessage).toBeCalledTimes(2); + expect(clientMock.chat.postMessage).toHaveBeenNthCalledWith(1, { + channel: messageAuthorEvent.item.channel, + text: `Tarea subida con éxito <@${messageAuthorEvent.item_user}>! \n\nAcá está el <${chatGetPermalinkResponse.permalink}|Link> al mensaje original.\n\n*Para agregar correcciones responder en este hilo (no en el mensaje original).*`, + }); + expect(clientMock.chat.postMessage).toHaveBeenNthCalledWith(2, { + channel: messageAuthorEvent.item.channel, + text: 'Si querés agregar una corrección a esta tarea hacelo como una respuesta al mensaje que mandó el bot.', + thread_ts: messageAuthorEvent.item.ts, + }); + + expect(threadApi.create).toHaveBeenCalledTimes(1); + expect(threadApi.create).toHaveBeenCalledWith({ + authorId: env.BOT_ID, + studentId: submissionResponseMock.studentId, + taskId: submissionResponseMock.taskId, + text: chatPostMessageResponse.message.text, + timestamp: chatPostMessageResponse.ts, + }); + + expect(clientMock.reactions.add).toBeCalledTimes(1); + expect(clientMock.reactions.add).toBeCalledWith({ + channel: messageAuthorEvent.item.channel, + name: 'white_check_mark', + timestamp: messageAuthorEvent.item.ts, + }); + + expect(loggerMock.error).toBeCalledTimes(0); + }); }); describe('Submission types', () => { diff --git a/src/events/reaction/handleRobotFaceReaction.ts b/src/events/reaction/handleRobotFaceReaction.ts index 4ee0e43..9a01507 100644 --- a/src/events/reaction/handleRobotFaceReaction.ts +++ b/src/events/reaction/handleRobotFaceReaction.ts @@ -14,27 +14,18 @@ import { extractOnlySubmission } from '../../utils/extractOnlySubmission'; import { validateSubmissionSingleFormat } from '../../utils/validateSubmissionSingleFormat'; import { validateSubmissionFormat } from '../../utils/validateSubmissionFormat'; import { validateNotMultipleSubmissions } from '../../utils/validateNotMultipleSubmissions'; +import { assignmentCheatsheet } from '../../utils/assignmentCheatsheet'; export const handleRobotFaceReaction: Middleware< SlackEventMiddlewareArgs<'reaction_added'>, StringIndexed > = async ({ client, event, logger }): Promise => { - console.log('HERE START HANDLE ROBOT FACE REACTION'); - console.log('client: ', client); - console.log('event: ', event); - if ( event.type === 'reaction_added' && event.reaction === 'robot_face' && event.item.type === 'message' ) { - console.log('Event data: ', event); - console.log('Event type: ', event.type); - console.log('Event reaction: ', event.reaction); - console.log('Event item type: ', event.item.type); - try { - console.log('HERE ENTERS IN TRY BLOCK'); const { messages: messagesFromChannel } = await client.conversations.history({ latest: event.item.ts, @@ -43,16 +34,6 @@ export const handleRobotFaceReaction: Middleware< inclusive: true, }); - console.log('messages from channel: ', messagesFromChannel); - console.log( - 'client.conversations.history property "latest": ', - event.item.ts - ); - console.log( - 'client.conversations.history property "channel": ', - event.item.channel - ); - if (!messagesFromChannel) { throw new Error( `Message with ID ${event.item.ts} not found in channel ${event.item.channel}.` @@ -61,8 +42,6 @@ export const handleRobotFaceReaction: Middleware< const reactedMessage = messagesFromChannel[0]; - console.log('reactedMessage: ', reactedMessage); - if (!reactedMessage.reactions) { throw new Error( `Message with ID ${event.item.ts} in channel ${event.item.channel} has no reactions.` @@ -73,8 +52,6 @@ export const handleRobotFaceReaction: Middleware< reactedMessage.reactions ); - console.log('botAlreadyReacted: ', botAlreadyReacted); - if (botAlreadyReacted) { logger.info( `Robotina already processed message with ID ${event.item.ts} in channel ${event.item.channel}, exiting...` @@ -115,17 +92,12 @@ export const handleRobotFaceReaction: Middleware< return; } - console.log('reactorIsValid: ', reactorIsValid); - logger.info('Processing submission...'); const { user: slackUser } = await client.users.info({ user: event.item_user, }); - console.log('slackUser: ', slackUser); - console.log('client.users.info property "user": ', event.item_user); - if (!slackUser) { throw new Error(`User with ID ${event.item_user} not found.`); } @@ -134,21 +106,12 @@ export const handleRobotFaceReaction: Middleware< channel: event.item.channel, }); - console.log('channel: ', channel); - console.log( - 'client.conversations.info property "channel": ', - event.item.channel - ); - if (!channel) { throw new Error(`Channel ${event.item.channel} not found.`); } const lessonId = validateChannelName(channel.name!); - console.log('lessonId: ', lessonId); - console.log('validateChannelName property "name": ', channel.name); - if (!lessonId) { throw new Error( 'Channel name must be in the format "clase-" or "clase-react-".' @@ -159,8 +122,6 @@ export const handleRobotFaceReaction: Middleware< reactedMessage.text! ); - console.log('submissionHasValidFormat: ', submissionHasValidFormat); - if (!submissionHasValidFormat) { await client.chat.postMessage({ channel: event.item.channel, @@ -174,8 +135,6 @@ export const handleRobotFaceReaction: Middleware< reactedMessage.text! ); - console.log('submissionHasSingleFormat: ', submissionHasSingleFormat); - if (!submissionHasSingleFormat) { await client.chat.postMessage({ channel: event.item.channel, @@ -189,11 +148,6 @@ export const handleRobotFaceReaction: Middleware< reactedMessage.text! ); - console.log( - 'validateIsNotMultipleSubmissions: ', - validateIsNotMultipleSubmissions - ); - if ( !validateIsNotMultipleSubmissions && reactedMessage.text!.includes('```') @@ -223,8 +177,6 @@ export const handleRobotFaceReaction: Middleware< delivery: reactedMessage.text!, }); - console.log('validSubmissionFormat: ', validSubmissionFormat); - if (!validSubmissionFormat) { await client.chat.postMessage({ channel: event.item.channel, @@ -236,8 +188,6 @@ export const handleRobotFaceReaction: Middleware< const onlySubmissionContent = extractOnlySubmission(reactedMessage.text!); - console.log('onlySubmissionContent: ', onlySubmissionContent); - const tarea = await uploadTarea({ classNumber: lessonId, slackId: slackUser.id!, @@ -247,69 +197,29 @@ export const handleRobotFaceReaction: Middleware< email: slackUser.profile!.email!, }); - console.log('tarea: ', tarea); - console.log('uploadTarea property "classNumber": ', lessonId); - console.log('uploadTarea property "slackId": ', slackUser.id); - console.log('uploadTarea property "delivery": ', onlySubmissionContent); - console.log( - 'uploadTarea property "firstName": ', - slackUser.profile!.first_name - ); - console.log( - 'uploadTarea property "lastName": ', - slackUser.profile!.last_name - ); - console.log('uploadTarea property "email": ', slackUser.profile!.email); - const { permalink } = await client.chat.getPermalink({ channel: event.item.channel, message_ts: event.item.ts, }); - console.log('permalink: ', permalink); - console.log( - 'client.chat.getPermalink property "channel": ', - event.item.channel - ); - console.log( - 'client.chat.getPermalink property "message_ts": ', - event.item.ts - ); + const cheatsheet = + assignmentCheatsheet[lessonId as keyof typeof assignmentCheatsheet]; + + const submissionResponse = cheatsheet + ? `Tarea subida con éxito <@${slackUser.id}>! \n\nAcá está el <${permalink}|Link> al mensaje original. Te recomendamos leer las siguientes <${cheatsheet} | notas> que pueden ayudarte a solucionar errores comunes. \n\n*Para agregar correcciones responder en este hilo (no en el mensaje original).*` + : `Tarea subida con éxito <@${slackUser.id}>! \n\nAcá está el <${permalink}|Link> al mensaje original.\n\n*Para agregar correcciones responder en este hilo (no en el mensaje original).*`; const botMessage = await client.chat.postMessage({ channel: event.item.channel, - text: `Tarea subida con éxito <@${slackUser.id}>! \n\nAcá está el <${permalink}|Link> al mensaje original.\n\n*Para agregar correcciones responder en este hilo (no en el mensaje original).*`, + text: submissionResponse, }); - console.log('botMessage: ', botMessage); - console.log( - 'client.chat.postMessage property "channel": ', - event.item.channel - ); - console.log( - 'client.chat.postMessage property "text": ', - `Tarea subida con éxito <@${slackUser.id}>! \n\nAcá está el <${permalink}|Link> al mensaje original.\n\n*Para agregar correcciones responder en este hilo (no en el mensaje original).*` - ); - await client.chat.postMessage({ channel: event.item.channel, text: 'Si querés agregar una corrección a esta tarea hacelo como una respuesta al mensaje que mandó el bot.', thread_ts: event.item.ts, }); - console.log( - 'client.chat.postMessage property "channel": ', - event.item.channel - ); - console.log( - 'client.chat.postMessage property "text": ', - 'Si querés agregar una corrección a esta tarea hacelo como una respuesta al mensaje que mandó el bot.' - ); - console.log( - 'client.chat.postMessage property "thread_ts": ', - event.item.ts - ); - const createThreadDto: ICreateThreadDto = { authorId: env.BOT_ID!, studentId: tarea.studentId, @@ -318,16 +228,6 @@ export const handleRobotFaceReaction: Middleware< taskId: tarea.taskId, }; - console.log('createThreadDto: ', createThreadDto); - console.log('createThreadDto property "authorId": ', env.BOT_ID); - console.log('createThreadDto property "studentId": ', tarea.studentId); - console.log( - 'createThreadDto property "text": ', - botMessage.message!.text! - ); - console.log('createThreadDto property "timestamp": ', botMessage.ts); - console.log('createThreadDto property "taskId": ', tarea.taskId); - await threadApi.create(createThreadDto); await client.reactions.add({ @@ -336,13 +236,6 @@ export const handleRobotFaceReaction: Middleware< timestamp: event.item.ts, }); - console.log( - 'client.reactions.add property "channel": ', - event.item.channel - ); - console.log('client.reactions.add property "name": ', 'white_check_mark'); - console.log('client.reactions.add property "timestamp": ', event.item.ts); - logger.info('Submission processed successfully!'); } catch (error) { logger.error( diff --git a/src/utils/assignmentCheatsheet.ts b/src/utils/assignmentCheatsheet.ts new file mode 100644 index 0000000..997f5a4 --- /dev/null +++ b/src/utils/assignmentCheatsheet.ts @@ -0,0 +1,3 @@ +export const assignmentCheatsheet = { + '1': 'https://docs.google.com/document/d/1Yj8xDa8-5K2um3qJotn6-s_XM5_q5OwZKP7yHf4mW2c', +};