Skip to content

Commit

Permalink
Merge pull request #690 from techmatters/CHI-2878-fix_custom_channel_…
Browse files Browse the repository at this point in the history
…deactivate

Chi 2878 fix custom channel deactivate
  • Loading branch information
stephenhand authored Sep 18, 2024
2 parents d59761a + 331a924 commit 3bb787b
Show file tree
Hide file tree
Showing 4 changed files with 101 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export const handleEvent = async (context: Context<EnvVars>, event: EventFields)
.addCustomerExternalId as AddCustomerExternalId;
await addCustomerExternalId(context, event);

if (taskAttributes.channelType === 'web') {
if ((taskAttributes.customChannelType || taskAttributes.channelType) === 'web') {
// Add task sid to tasksSids channel attr so we can end the chat from webchat client (see endChat function)
const addTaskHandlerPath =
Runtime.getFunctions()['helpers/addTaskSidToChannelAttributes'].path;
Expand Down
65 changes: 51 additions & 14 deletions functions/taskrouterListeners/janitorListener.private.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {
TASK_COMPLETED,
} from '@tech-matters/serverless-helpers/taskrouter';

import { Twilio } from 'twilio';
import type { ChatChannelJanitor } from '../helpers/chatChannelJanitor.private';
import type { ChannelToFlex } from '../helpers/customChannels/customChannelToFlex.private';
import type { ChannelCaptureHandlers } from '../channelCapture/channelCaptureHandlers.private';
Expand All @@ -44,6 +45,7 @@ export const eventTypes: EventType[] = [
];

type EnvVars = {
TWILIO_WORKSPACE_SID: string;
CHAT_SERVICE_SID: string;
FLEX_PROXY_SERVICE_SID: string;
SYNC_SERVICE_SID: string;
Expand All @@ -65,7 +67,9 @@ const isCleanupBotCapture = (
return channelCaptureHandlers.isChatCaptureControlTask(taskAttributes);
};

const isHandledByOtherListener = (
const isHandledByOtherListener = async (
client: Twilio,
workspaceSid: string,
taskSid: string,
taskAttributes: {
channelType?: string;
Expand All @@ -77,58 +81,75 @@ const isHandledByOtherListener = (
].path) as ChannelCaptureHandlers;

if (channelCaptureHandlers.isChatCaptureControlTask(taskAttributes)) {
console.debug('isHandledByOtherListener? - Yes, isChatCaptureControl');
return true;
}

const transferHelpers = require(Runtime.getFunctions()['transfer/helpers']
.path) as TransferHelpers;

if (!transferHelpers.hasTaskControl(taskSid, taskAttributes)) {
return true;
const res = !(await transferHelpers.hasTaskControl(
client,
workspaceSid,
taskSid,
taskAttributes,
));
if (res) {
console.debug('isHandledByOtherListener? - Yes, does not have task control', taskAttributes);
} else {
console.debug('isHandledByOtherListener? - No, not handled by other listener');
}

return false;
return res;
};

const isCleanupCustomChannel = (
const isCleanupCustomChannel = async (
eventType: EventType,
client: Twilio,
workspaceSid: string,
taskSid: string,
taskAttributes: {
channelType?: string;
customChannelType?: string;
isChatCaptureControl?: boolean;
} & ChatTransferTaskAttributes,
) => {
if (![TASK_DELETED, TASK_SYSTEM_DELETED, TASK_CANCELED].includes(eventType)) {
return false;
}

if (isHandledByOtherListener(taskSid, taskAttributes)) {
if (await isHandledByOtherListener(client, workspaceSid, taskSid, taskAttributes)) {
return false;
}

const channelToFlex = require(Runtime.getFunctions()['helpers/customChannels/customChannelToFlex']
.path) as ChannelToFlex;

return channelToFlex.isAseloCustomChannel(taskAttributes.channelType);
return channelToFlex.isAseloCustomChannel(
taskAttributes.customChannelType || taskAttributes.channelType,
);
};

const isDeactivateConversationOrchestration = (
const isDeactivateConversationOrchestration = async (
eventType: EventType,
client: Twilio,
workspaceSid: string,
taskSid: string,
taskAttributes: {
channelType?: string;
isChatCaptureControl?: boolean;
} & ChatTransferTaskAttributes,
) => {
console.debug('isDeactivateConversationOrchestration?');
if (
![TASK_WRAPUP, TASK_COMPLETED, TASK_DELETED, TASK_SYSTEM_DELETED, TASK_CANCELED].includes(
eventType,
)
) {
console.debug('isDeactivateConversationOrchestration? - No, wrong event type:', eventType);
return false;
}

if (isHandledByOtherListener(taskSid, taskAttributes)) {
if (await isHandledByOtherListener(client, workspaceSid, taskSid, taskAttributes)) {
console.debug('isDeactivateConversationOrchestration? - No, handled by other listener');
return false;
}

Expand All @@ -154,6 +175,7 @@ export const handleEvent = async (context: Context<EnvVars>, event: EventFields)
TaskSid: taskSid,
TaskChannelUniqueName: taskChannelUniqueName,
} = event;
const client = context.getTwilioClient();

// The janitor is only be executed for chat based tasks
if (!['chat', 'survey'].includes(taskChannelUniqueName)) return;
Expand Down Expand Up @@ -183,7 +205,15 @@ export const handleEvent = async (context: Context<EnvVars>, event: EventFields)
return;
}

if (isCleanupCustomChannel(eventType, taskSid, taskAttributes)) {
if (
await isCleanupCustomChannel(
eventType,
client,
context.TWILIO_WORKSPACE_SID,
taskSid,
taskAttributes,
)
) {
console.log('Handling clean up custom channel...');

const chatChannelJanitor = require(Runtime.getFunctions()['helpers/chatChannelJanitor'].path)
Expand All @@ -194,9 +224,16 @@ export const handleEvent = async (context: Context<EnvVars>, event: EventFields)
return;
}

if (isDeactivateConversationOrchestration(eventType, taskSid, taskAttributes)) {
if (
await isDeactivateConversationOrchestration(
eventType,
client,
context.TWILIO_WORKSPACE_SID,
taskSid,
taskAttributes,
)
) {
// This task has reached a point where the channel should be deactivated, unless post survey is enabled
const client = context.getTwilioClient();
const serviceConfig = await client.flexApi.configuration.get().fetch();
const { feature_flags: featureFlags } = serviceConfig.attributes;

Expand Down
28 changes: 25 additions & 3 deletions functions/transfer/helpers.private.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
import { Twilio } from 'twilio';

export type TransferMeta = {
mode: 'COLD' | 'WARM';
Expand All @@ -28,9 +29,30 @@ export type ChatTransferTaskAttributes = {
const hasTransferStarted = (taskAttributes: ChatTransferTaskAttributes) =>
Boolean(taskAttributes && taskAttributes.transferMeta);

export const hasTaskControl = (taskSid: string, taskAttributes: ChatTransferTaskAttributes) =>
!hasTransferStarted(taskAttributes) ||
taskAttributes.transferMeta?.sidWithTaskControl === taskSid;
export const hasTaskControl = async (
client: Twilio,
workspaceSid: string,
taskSid: string,
taskAttributes: ChatTransferTaskAttributes,
) => {
if (!hasTransferStarted(taskAttributes)) {
console.debug('hasTaskControl? Yes - Transfer has not started');
return true;
}
const reservations = await client.taskrouter.v1.workspaces
.get(workspaceSid)
.tasks.get(taskSid)
.reservations.list();
const res = Boolean(
reservations.find((r) => r.sid === taskAttributes.transferMeta?.sidWithTaskControl),
);
console.debug(
`hasTaskControl? ${res ? 'Yes' : 'No'} - ${
taskAttributes.transferMeta?.sidWithTaskControl
} (taskAttributes.transferMeta?.sidWithTaskControl) IN (${reservations.map((r) => r.sid)})`,
);
return res;
};

export type TransferHelpers = {
hasTaskControl: typeof hasTaskControl;
Expand Down
28 changes: 24 additions & 4 deletions tests/taskrouterListeners/janitorListener.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,10 @@ import { Context } from '@twilio-labs/serverless-runtime-types/types';
import { mock } from 'jest-mock-extended';

import each from 'jest-each';
import { Twilio } from 'twilio';
import * as janitorListener from '../../functions/taskrouterListeners/janitorListener.private';
import { AseloCustomChannels } from '../../functions/helpers/customChannels/customChannelToFlex.private';
import helpers from '../helpers';
import helpers, { RecursivePartial } from '../helpers';

const mockChannelJanitor = jest.fn();
jest.mock('../../functions/helpers/chatChannelJanitor.private', () => ({
Expand Down Expand Up @@ -59,6 +60,7 @@ const nonCustomChannelTaskAttributes = {
type EnvVars = {
CHAT_SERVICE_SID: string;
FLEX_PROXY_SERVICE_SID: string;
TWILIO_WORKSPACE_SID: string;
};

const mockFetchFlexApiConfig = jest.fn(() => ({
Expand All @@ -68,11 +70,29 @@ const mockFetchFlexApiConfig = jest.fn(() => ({
},
},
}));

const mockClient: RecursivePartial<Twilio> = {
flexApi: { configuration: { get: () => ({ fetch: mockFetchFlexApiConfig }) } },
taskrouter: {
v1: {
workspaces: {
get: () => ({
tasks: {
get: () => ({
reservations: {
list: () => Promise.resolve([{ sid: 'WRxxx' }]),
},
}),
},
}),
},
},
},
};

const context = {
...mock<Context<EnvVars>>(),
getTwilioClient: (): any => ({
flexApi: { configuration: { get: () => ({ fetch: mockFetchFlexApiConfig }) } },
}),
getTwilioClient: (): Twilio => mockClient as Twilio,
CHAT_SERVICE_SID: 'CHxxx',
FLEX_PROXY_SERVICE_SID: 'KCxxx',
SYNC_SERVICE_SID: 'xxx',
Expand Down

0 comments on commit 3bb787b

Please sign in to comment.