diff --git a/.github/actions/main-action/action.yml b/.github/actions/main-action/action.yml index 7e649bc1..4e3e533f 100644 --- a/.github/actions/main-action/action.yml +++ b/.github/actions/main-action/action.yml @@ -67,8 +67,11 @@ inputs: s3-bucket: description: 'S3 bucket name where Aselo documents are stored' required: true - helpline-name: - description: 'The identifier in the format "-" used for this helpline' + helpline-code: + description: 'The short (usually 2 character) upper case code used to identify the helpline internally, e.g. ZA, IN, BR.' + required: true + environment-code: + description: 'The short upper case code used to identify the environment internally, e.g. STG, PROD, DEV' required: true send-slack-message: description: 'Specifies if should send a Slack message at the end of successful run. Defaults to true' @@ -81,6 +84,9 @@ inputs: runs: using: 'composite' steps: + - name: Set helpline-name + run: echo "helpline-name=${{ inputs.helpline-code }}_${{ inputs.environment-code }}" >> $GITHUB_ENV + shell: bash # Set any env vars needed from Parameter Store here # Slack env - name: Set GITHUB_ACTIONS_SLACK_BOT_TOKEN @@ -100,6 +106,8 @@ runs: - name: Fill .env run: | cat <> .env + HELPLINE_CODE=${{ inputs.helpline-code }} + ENVIRONMENT_CODE=${{ inputs.environment-code }} TWILIO_WORKSPACE_SID=${{ inputs.workspace-sid }} TWILIO_CHAT_TRANSFER_WORKFLOW_SID=${{ inputs.transfer-workflow-sid }} SYNC_SERVICE_API_KEY=${{ inputs.sync-service-api-key }} @@ -131,7 +139,7 @@ runs: - name: Execute custom action (if any) uses: ./.github/actions/custom-actions with: - helpline-name: ${{ inputs.helpline-name }} + helpline-name: ${{ env.helpline-name }} account-sid: ${{ inputs.account-sid }} # Install dependencies for the twilio functions - name: Install dependencies for the twilio functions @@ -170,6 +178,6 @@ runs: if: ${{ inputs.send-slack-message != 'false' }} with: channel-id: ${{ env.ASELO_DEPLOYS_CHANNEL_ID }} - slack-message: "`[Serverless]` Deployment to `${{ inputs.helpline-name }}` from ${{ github.ref_type }} `${{ github.ref_name }}` requested by `${{ github.triggering_actor }}` completed using workflow '${{ github.workflow }}' with SHA ${{ github.sha }} :rocket:." + slack-message: "`[Serverless]` Deployment to `${{ env.helpline-name }}` from ${{ github.ref_type }} `${{ github.ref_name }}` requested by `${{ github.triggering_actor }}` completed using workflow '${{ github.workflow }}' with SHA ${{ github.sha }} :rocket:." env: SLACK_BOT_TOKEN: ${{ env.GITHUB_ACTIONS_SLACK_BOT_TOKEN }} diff --git a/.github/workflows/custom_helpline.yml b/.github/workflows/custom_helpline.yml index e15a1ae2..91991a61 100644 --- a/.github/workflows/custom_helpline.yml +++ b/.github/workflows/custom_helpline.yml @@ -191,7 +191,8 @@ jobs: aselo-app-secret-key: $ASELO_APP_SECRET_KEY aws-region: $HELPLINE_AWS_REGION s3-bucket: $S3_BUCKET - helpline-name: ${{inputs.helpline_code}}_${{inputs.environment_code}} + helpline-code: ${{inputs.helpline_code}} + environment-code: ${{inputs.environment_code}} # Set 'false' if the target environment is production OR the force_enable_operating_hours override option is checked - otherwise 'true' disable-operating-hours: ${{ (inputs.force_enable_operating_hours || inputs.environment_code == 'PROD') && 'false' || 'true' }} send-slack-message: ${{ inputs.send-slack-message }} diff --git a/README.md b/README.md index 41698931..bcfeb5e1 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,8 @@ For help on twilio-run commands run: | `S3_ENDPOINT` | _local transcripts only_ http://localhost:4566 | | `ASELO_APP_ACCESS_KEY` | AWS_ACCESS_KEY_ID with access to s3 bucket (can be any string for localstack) | | `ASELO_APP_SECRET_KEY` | AWS_SECRET_ACCESS_KEY for ASELO_APP_ACCESS_KEY (can be any string for localstack | +| `HELPLINE_CODE` | The short (usually 2 character) upper case code used to identify the helpline internally, e.g. ZA, IN, BR. | +| `ENVIRONMENT_CODE` | The short upper case code used to identify the environment internally, e.g. STG, PROD, DEV | ## Deployment diff --git a/functions/captureChannelWithBot.protected.ts b/functions/captureChannelWithBot.protected.ts index 00ce288b..503a0822 100644 --- a/functions/captureChannelWithBot.protected.ts +++ b/functions/captureChannelWithBot.protected.ts @@ -26,9 +26,11 @@ import { error500, success, } from '@tech-matters/serverless-helpers'; -import { LexClient } from './helpers/lexClient.private'; +import { LexClient, BotType } from './helpers/lexClient.private'; type EnvVars = { + HELPLINE_CODE: string; + ENVIRONMENT_CODE: string; CHAT_SERVICE_SID: string; ASELO_APP_ACCESS_KEY: string; ASELO_APP_SECRET_KEY: string; @@ -42,7 +44,8 @@ export type Body = { message: string; // (in Studio Flow, trigger.message.Body) The triggering message fromServiceUser: string; // (in Studio Flow, trigger.message.From) The service user unique name studioFlowSid: string; // (in Studio Flow, flow.flow_sid) The Studio Flow sid. Needed to trigger an API type execution once the channel is released. - botName: string; + language: string; + type: BotType; }; export const handler = async ( @@ -55,7 +58,7 @@ export const handler = async ( const resolve = bindResolve(callback)(response); try { - const { channelSid, message, fromServiceUser, studioFlowSid, botName } = event; + const { channelSid, message, fromServiceUser, studioFlowSid, language, type } = event; if (!channelSid) { resolve(error400('channelSid')); @@ -73,8 +76,8 @@ export const handler = async ( resolve(error400('studioFlowSid')); return; } - if (!botName) { - resolve(error400('botName')); + if (!type) { + resolve(error400('type')); return; } @@ -101,6 +104,10 @@ export const handler = async ( }), ); + const { ENVIRONMENT_CODE, HELPLINE_CODE } = context; + const languageSuffix = (language || 'en-US').replace('-', '_'); + const botName = `${ENVIRONMENT_CODE}_${HELPLINE_CODE}_${type}_${languageSuffix}`; + const chatbotCallbackWebhook = await channel.webhooks().create({ type: 'webhook', configuration: { @@ -126,7 +133,6 @@ export const handler = async ( const updatedChannelAttributes = JSON.parse(updated.attributes); - // Cleanup task for captured channel by the bot await context .getTwilioClient() .taskrouter.workspaces(context.TWILIO_WORKSPACE_SID) diff --git a/functions/helpers/lexClient.private.ts b/functions/helpers/lexClient.private.ts index 328bba73..c2269987 100644 --- a/functions/helpers/lexClient.private.ts +++ b/functions/helpers/lexClient.private.ts @@ -21,6 +21,8 @@ type AWSCredentials = { AWS_REGION: string; }; +export type BotType = 'pre_survey' | 'post_survey'; + export const postText = async ( credentials: AWSCredentials, { @@ -35,12 +37,13 @@ export const postText = async ( userId: string; }, ) => { + const { ASELO_APP_ACCESS_KEY, ASELO_APP_SECRET_KEY, AWS_REGION } = credentials; AWS.config.update({ credentials: { - accessKeyId: credentials.ASELO_APP_ACCESS_KEY, - secretAccessKey: credentials.ASELO_APP_SECRET_KEY, + accessKeyId: ASELO_APP_ACCESS_KEY, + secretAccessKey: ASELO_APP_SECRET_KEY, }, - region: credentials.AWS_REGION, + region: AWS_REGION, }); const Lex = new AWS.LexRuntime(); diff --git a/functions/webhooks/chatbotCallback.protected.ts b/functions/webhooks/chatbotCallback.protected.ts index d707903a..6b70c7d5 100644 --- a/functions/webhooks/chatbotCallback.protected.ts +++ b/functions/webhooks/chatbotCallback.protected.ts @@ -88,27 +88,20 @@ export const handler = async ( userId: channel.sid, }); - await channel.messages().create({ - body: lexResponse.message, - from: 'Bot', - xTwilioWebhookEnabled: 'true', - }); - // If the session ended, we should unlock the channel to continue the Studio Flow if (lexClient.isEndOfDialog(lexResponse.dialogState)) { const releasedChannelAttributes = { ...omit(channelAttributes, 'channelCapturedByBot'), memory: lexResponse.slots, + preSurveyComplete: true, }; // TODO: This is now only assuming pre-survey bot. We should have a way to specify what's the next step after the bot execution is ended - const nextAction = client.studio.v2 - .flows(channelAttributes.channelCapturedByBot.studioFlowSid) - .executions.create({ - from: ChannelSid, - to: ChannelSid, - parameters: { - ChannelAttributes: releasedChannelAttributes, + const nextAction = () => + channel.webhooks().create({ + type: 'studio', + configuration: { + flowSid: channelAttributes.channelCapturedByBot.studioFlowSid, }, }); @@ -124,22 +117,34 @@ export const handler = async ( attributes: JSON.stringify(releasedChannelAttributes), }), // Move control task to complete state - client.taskrouter.v1 - .workspaces(context.TWILIO_WORKSPACE_SID) - .tasks(channelAttributes.controlTaskSid) - .update({ assignmentStatus: 'completed' }), + (async () => { + try { + await client.taskrouter.v1 + .workspaces(context.TWILIO_WORKSPACE_SID) + .tasks(channelAttributes.controlTaskSid) + .update({ assignmentStatus: 'completed' }); + } catch (err) { + console.log(err); + } + })(), // Remove this webhook from the channel channel .webhooks() .get(channelAttributes.channelCapturedByBot.chatbotCallbackWebhookSid) .remove(), // Trigger the next step once the channel is released - nextAction, + nextAction(), ]); console.log('Channel unblocked and bot session deleted'); } + await channel.messages().create({ + body: lexResponse.message, + from: 'Bot', + xTwilioWebhookEnabled: 'true', + }); + resolve(success('All messages sent :)')); return; } diff --git a/tests/captureChannelWithBot.test.ts b/tests/captureChannelWithBot.test.ts index 65047ac9..5968be43 100644 --- a/tests/captureChannelWithBot.test.ts +++ b/tests/captureChannelWithBot.test.ts @@ -93,6 +93,8 @@ const mockContext = { AWS_REGION: 'us-east-1', TWILIO_WORKSPACE_SID: 'WE23xxx0orre', SURVEY_WORKFLOW_SID: 'AZexxx903esd', + HELPLINE_CODE: 'AS', + ENVIRONMENT_CODE: 'DEV', }; const mockEvent: Body = { @@ -100,7 +102,8 @@ const mockEvent: Body = { message: 'Message sent', fromServiceUser: 'Test User', studioFlowSid: 'FL0123xxdew', - botName: 'C6HUSTIFBR', + language: 'en_US', + type: 'pre_survey', }; const mockCallback = jest.fn(); @@ -139,7 +142,8 @@ describe('captureChannelWithBot', () => { message: 'Message sent', fromServiceUser: 'Test User', studioFlowSid: 'FL0123xxdew', - botName: 'C6HUSTIFBR', + language: 'en_US', + type: 'pre_survey', }; await captureChannelWithBot(mockContext, event, mockCallback); diff --git a/tests/webhooks/chatbotCallback.test.ts b/tests/webhooks/chatbotCallback.test.ts index 2c4fea47..45494bf7 100644 --- a/tests/webhooks/chatbotCallback.test.ts +++ b/tests/webhooks/chatbotCallback.test.ts @@ -69,6 +69,7 @@ const context = { get: jest.fn().mockReturnValue({ remove: jest.fn().mockReturnValue({}), }), + create: jest.fn(), }), }), messages: jest.fn().mockReturnValue({