diff --git a/Common/Server/Utils/Workspace/Slack/Actions/Auth.ts b/Common/Server/Utils/Workspace/Slack/Actions/Auth.ts index ef3d819641..87512ba28a 100644 --- a/Common/Server/Utils/Workspace/Slack/Actions/Auth.ts +++ b/Common/Server/Utils/Workspace/Slack/Actions/Auth.ts @@ -30,6 +30,7 @@ export interface SlackRequest { slackUserId?: string | undefined; slackUsername?: string | undefined; actions?: SlackAction[] | undefined; + triggerId?: string | undefined; } export default class SlackAuthAction { @@ -89,6 +90,9 @@ export default class SlackAuthAction { (payload as JSONObject)["user"] as JSONObject )["username"] as string; + + const triggerId: string | undefined = payload["trigger_id"] as string; + const projectAuth: WorkspaceProjectAuthToken | null = await WorkspaceProjectAuthTokenService.findOneBy({ query: { @@ -175,6 +179,7 @@ export default class SlackAuthAction { slackMessageId: slackMessageId, slackUserFullName: slackUserFullName, actions: actions, + triggerId: triggerId, }; } } diff --git a/Common/Server/Utils/Workspace/Slack/Actions/Incident.ts b/Common/Server/Utils/Workspace/Slack/Actions/Incident.ts index bdd07506f8..5d6ea88ab9 100644 --- a/Common/Server/Utils/Workspace/Slack/Actions/Incident.ts +++ b/Common/Server/Utils/Workspace/Slack/Actions/Incident.ts @@ -8,10 +8,10 @@ import { SlackAction, SlackRequest } from "./Auth"; import Response from "../../../Response"; import { WorkspaceDropdownBlock, + WorkspaceModalBlock, WorkspacePayloadMarkdown, WorkspaceTextAreaBlock, } from "../../../../../Types/Workspace/WorkspaceMessagePayload"; -import { JSONObject } from "../../../../../Types/JSON"; import WorkspaceNotificationRuleService from "../../../../Services/WorkspaceNotificationRuleService"; import NotificationRuleEventType from "../../../../../Types/Workspace/NotificationRules/EventType"; import WorkspaceType from "../../../../../Types/Workspace/WorkspaceType"; @@ -319,6 +319,11 @@ export default class SlackIncidentActions { ); } + // We send this early let slack know we're ok. We'll do the rest in the background. + Response.sendJsonObjectResponse(req, res, { + response_action: "clear", + }); + const incidentId: ObjectID = new ObjectID(actionValue); // send a modal with a dropdown that says "Public Note" or "Private Note" and a text area to add the note. @@ -347,19 +352,25 @@ export default class SlackIncidentActions { placeholder: "Note", }; - const modal: JSONObject = SlackUtil.getModalBlock({ - payloadModalBlock: { - _type: "WorkspaceModalBlock", - title: "Add Note", - submitButtonTitle: "Submit", - submitButtonActionId: SlackActionType.SubmitIncidentNote, - cancelButtonTitle: "Cancel", - submitButtonValue: incidentId.toString(), - blocks: [notePickerDropdown, noteTextArea], - }, - }); + const modalBlock: WorkspaceModalBlock = { + _type: "WorkspaceModalBlock", + title: "Add Note", + submitButtonTitle: "Submit", + submitButtonActionId: SlackActionType.SubmitIncidentNote, + cancelButtonTitle: "Cancel", + callbackId: "add_incident_note", + submitButtonValue: incidentId.toString(), + blocks: [notePickerDropdown, noteTextArea], + }; + + + await SlackUtil.showModalToUser({ + authToken: data.slackRequest.projectAuthToken!, + modalBlock: modalBlock, + triggerId: data.slackRequest.triggerId!, + }) + - return Response.sendJsonObjectResponse(req, res, modal); } public static async handleIncidentAction(data: { diff --git a/Common/Server/Utils/Workspace/Slack/Slack.ts b/Common/Server/Utils/Workspace/Slack/Slack.ts index e0da18f28e..9b0d75bb9a 100644 --- a/Common/Server/Utils/Workspace/Slack/Slack.ts +++ b/Common/Server/Utils/Workspace/Slack/Slack.ts @@ -28,6 +28,59 @@ import SlackifyMarkdown from "slackify-markdown"; import { DropdownOption } from "../../../../UI/Components/Dropdown/Dropdown"; export default class SlackUtil extends WorkspaceBase { + + public static override async showModalToUser(data: { + authToken: string; + triggerId: string; + modalBlock: WorkspaceModalBlock; + }): Promise { + + logger.debug("Showing modal to user with data:"); + logger.debug(data); + + // Show modal to user + const blocks: Array = this.getBlocksFromWorkspaceMessagePayload( + { + messageBlocks: [data.modalBlock], + }, + ); + + // use view.open API to show modal + const result: HTTPErrorResponse | HTTPResponse = await API.post( + URL.fromString("https://slack.com/api/views.open"), + { + trigger_id: data.triggerId, + view: { + type: "modal", + callback_id: data.modalBlock.callbackId, + title: { + type: "plain_text", + text: data.modalBlock.title, + }, + blocks: blocks, + }, + }, + { + Authorization: `Bearer ${data.authToken}`, + ["Content-Type"]: "application/json", + }, + ); + + if (result instanceof HTTPErrorResponse) { + logger.error("Error response from Slack API:"); + logger.error(result); + throw result; + } + + if ((result.jsonData as JSONObject)?.["ok"] !== true) { + logger.error("Invalid response from Slack API:"); + logger.error(result.jsonData); + throw new BadRequestException("Invalid response"); + } + + logger.debug("Modal shown to user successfully."); + } + public static override async sendDirectMessageToUser(data: { authToken: string; workspaceUserId: string; diff --git a/Common/Server/Utils/Workspace/WorkspaceBase.ts b/Common/Server/Utils/Workspace/WorkspaceBase.ts index b5605753b7..e0dcbcf43d 100644 --- a/Common/Server/Utils/Workspace/WorkspaceBase.ts +++ b/Common/Server/Utils/Workspace/WorkspaceBase.ts @@ -36,6 +36,16 @@ export interface WorkspaceChannel { } export default class WorkspaceBase { + + public static async showModalToUser(_data: { + authToken: string; + triggerId: string; + modalBlock: WorkspaceModalBlock; + }): Promise { + + throw new NotImplementedException(); + } + public static sendDirectMessageToUser(_data: { authToken: string; workspaceUserId: string; diff --git a/Common/Types/Workspace/WorkspaceMessagePayload.ts b/Common/Types/Workspace/WorkspaceMessagePayload.ts index 9c4ea7ac57..b27085f8c5 100644 --- a/Common/Types/Workspace/WorkspaceMessagePayload.ts +++ b/Common/Types/Workspace/WorkspaceMessagePayload.ts @@ -53,6 +53,7 @@ export interface WorkspaceDropdownBlock extends WorkspaceMessageBlock { export interface WorkspaceModalBlock extends WorkspaceMessageBlock { _type: "WorkspaceModalBlock"; title: string; + callbackId: string; submitButtonTitle: string; cancelButtonTitle: string; submitButtonActionId?: string | undefined;