From b0e186cfbdc93ee5b86d5183f63427fd01028b41 Mon Sep 17 00:00:00 2001 From: Ali BARIN Date: Fri, 20 Dec 2024 13:18:25 +0000 Subject: [PATCH] feat(virtualq): add actions to manage waiters --- .../virtualq/actions/create-waiter/index.js | 134 +++++++++++++++ .../virtualq/actions/delete-waiter/index.js | 34 ++++ .../src/apps/virtualq/actions/index.js | 6 + .../virtualq/actions/show-waiter/index.js | 33 ++++ .../virtualq/actions/update-waiter/index.js | 157 ++++++++++++++++++ .../src/apps/virtualq/assets/favicon.svg | 35 ++++ .../backend/src/apps/virtualq/auth/index.js | 21 +++ .../apps/virtualq/auth/is-still-verified.js | 8 + .../apps/virtualq/auth/verify-credentials.js | 14 ++ .../apps/virtualq/common/add-auth-header.js | 15 ++ .../src/apps/virtualq/dynamic-data/index.js | 4 + .../virtualq/dynamic-data/list-lines/index.js | 15 ++ .../dynamic-data/list-waiters/index.js | 15 ++ .../src/apps/virtualq/dynamic-fields/index.js | 3 + .../list-appointment-fields/index.js | 20 +++ packages/backend/src/apps/virtualq/index.js | 22 +++ .../docs/pages/public/favicons/virtualq.svg | 35 ++++ 17 files changed, 571 insertions(+) create mode 100644 packages/backend/src/apps/virtualq/actions/create-waiter/index.js create mode 100644 packages/backend/src/apps/virtualq/actions/delete-waiter/index.js create mode 100644 packages/backend/src/apps/virtualq/actions/index.js create mode 100644 packages/backend/src/apps/virtualq/actions/show-waiter/index.js create mode 100644 packages/backend/src/apps/virtualq/actions/update-waiter/index.js create mode 100644 packages/backend/src/apps/virtualq/assets/favicon.svg create mode 100644 packages/backend/src/apps/virtualq/auth/index.js create mode 100644 packages/backend/src/apps/virtualq/auth/is-still-verified.js create mode 100644 packages/backend/src/apps/virtualq/auth/verify-credentials.js create mode 100644 packages/backend/src/apps/virtualq/common/add-auth-header.js create mode 100644 packages/backend/src/apps/virtualq/dynamic-data/index.js create mode 100644 packages/backend/src/apps/virtualq/dynamic-data/list-lines/index.js create mode 100644 packages/backend/src/apps/virtualq/dynamic-data/list-waiters/index.js create mode 100644 packages/backend/src/apps/virtualq/dynamic-fields/index.js create mode 100644 packages/backend/src/apps/virtualq/dynamic-fields/list-appointment-fields/index.js create mode 100644 packages/backend/src/apps/virtualq/index.js create mode 100644 packages/docs/pages/public/favicons/virtualq.svg diff --git a/packages/backend/src/apps/virtualq/actions/create-waiter/index.js b/packages/backend/src/apps/virtualq/actions/create-waiter/index.js new file mode 100644 index 00000000..af22d2a5 --- /dev/null +++ b/packages/backend/src/apps/virtualq/actions/create-waiter/index.js @@ -0,0 +1,134 @@ +import defineAction from '../../../../helpers/define-action.js'; + +export default defineAction({ + name: 'Create waiter', + key: 'createWaiter', + description: 'Enqueues a waiter to the line with the selected line.', + arguments: [ + { + label: 'Line', + key: 'lineId', + type: 'dropdown', + required: true, + variables: true, + description: 'The line to join', + source: { + type: 'query', + name: 'getDynamicData', + arguments: [ + { + name: 'key', + value: 'listLines', + }, + ], + }, + }, + { + label: 'Phone', + key: 'phone', + type: 'string', + required: true, + variables: true, + description: + "The caller's phone number including country code (for example +4017111112233)", + }, + { + label: 'Channel', + key: 'channel', + type: 'dropdown', + description: + 'Option describing if the waiter expects a callback or will receive a text message', + required: true, + variables: true, + options: [ + { label: 'Call back', value: 'CallBack' }, + { label: 'Call in', value: 'CallIn' }, + ], + }, + { + label: 'Source', + key: 'source', + type: 'dropdown', + description: 'Option describing the source where the caller came from', + required: true, + variables: true, + options: [ + { label: 'Widget', value: 'Widget' }, + { label: 'Phone', value: 'Phone' }, + { label: 'Mobile', value: 'Mobile' }, + { label: 'App', value: 'App' }, + { label: 'Other', value: 'Other' }, + ], + }, + { + label: 'Appointment', + key: 'appointment', + type: 'dropdown', + required: true, + variables: true, + value: false, + description: + 'If set to true, then this marks this as an appointment. If appointment_time is set, this is automatically set to true.', + options: [ + { label: 'Yes', value: true }, + { label: 'No', value: false }, + ], + additionalFields: { + type: 'query', + name: 'getDynamicFields', + arguments: [ + { + name: 'key', + value: 'listAppointmentFields', + }, + { + name: 'parameters.appointment', + value: '{parameters.appointment}', + }, + ], + }, + }, + { + label: 'Service phone to call', + key: 'servicePhoneToCall', + type: 'string', + description: + "If set, callback uses this number instead of the line's service phone number", + required: false, + variables: true, + }, + ], + async run($) { + const { + lineId, + phone, + channel, + source, + appointment, + appointmentTime, + servicePhoneToCall, + } = $.step.parameters; + + const body = { + data: { + type: 'waiters', + attributes: { + line_id: lineId, + phone, + channel, + source, + appointment, + servicePhoneToCall, + }, + }, + }; + + if (appointment) { + body.data.attributes.appointmentTime = appointmentTime; + } + + const { data } = await $.http.post('/v2/waiters', body); + + $.setActionItem({ raw: data }); + }, +}); diff --git a/packages/backend/src/apps/virtualq/actions/delete-waiter/index.js b/packages/backend/src/apps/virtualq/actions/delete-waiter/index.js new file mode 100644 index 00000000..3922046d --- /dev/null +++ b/packages/backend/src/apps/virtualq/actions/delete-waiter/index.js @@ -0,0 +1,34 @@ +import defineAction from '../../../../helpers/define-action.js'; + +export default defineAction({ + name: 'Delete waiter', + key: 'deleteWaiter', + description: + 'Cancels waiting. The provided waiter will be removed from the queue.', + arguments: [ + { + label: 'Waiter', + key: 'waiterId', + type: 'dropdown', + required: true, + variables: true, + source: { + type: 'query', + name: 'getDynamicData', + arguments: [ + { + name: 'key', + value: 'listWaiters', + }, + ], + }, + }, + ], + async run($) { + const waiterId = $.step.parameters.waiterId; + + const { data } = await $.http.delete(`/v2/waiters/${waiterId}`); + + $.setActionItem({ raw: { output: data } }); + }, +}); diff --git a/packages/backend/src/apps/virtualq/actions/index.js b/packages/backend/src/apps/virtualq/actions/index.js new file mode 100644 index 00000000..96dd545e --- /dev/null +++ b/packages/backend/src/apps/virtualq/actions/index.js @@ -0,0 +1,6 @@ +import createWaiter from './create-waiter/index.js'; +import deleteWaiter from './delete-waiter/index.js'; +import showWaiter from './show-waiter/index.js'; +import updateWaiter from './update-waiter/index.js'; + +export default [createWaiter, deleteWaiter, showWaiter, updateWaiter]; diff --git a/packages/backend/src/apps/virtualq/actions/show-waiter/index.js b/packages/backend/src/apps/virtualq/actions/show-waiter/index.js new file mode 100644 index 00000000..1166a183 --- /dev/null +++ b/packages/backend/src/apps/virtualq/actions/show-waiter/index.js @@ -0,0 +1,33 @@ +import defineAction from '../../../../helpers/define-action.js'; + +export default defineAction({ + name: 'Show waiter', + key: 'showWaiter', + description: 'Returns the complete waiter information.', + arguments: [ + { + label: 'Waiter', + key: 'waiterId', + type: 'dropdown', + required: true, + variables: true, + source: { + type: 'query', + name: 'getDynamicData', + arguments: [ + { + name: 'key', + value: 'listWaiters', + }, + ], + }, + }, + ], + async run($) { + const waiterId = $.step.parameters.waiterId; + + const { data } = await $.http.get(`/v2/waiters/${waiterId}`); + + $.setActionItem({ raw: data }); + }, +}); diff --git a/packages/backend/src/apps/virtualq/actions/update-waiter/index.js b/packages/backend/src/apps/virtualq/actions/update-waiter/index.js new file mode 100644 index 00000000..51f9d8b4 --- /dev/null +++ b/packages/backend/src/apps/virtualq/actions/update-waiter/index.js @@ -0,0 +1,157 @@ +import defineAction from '../../../../helpers/define-action.js'; + +export default defineAction({ + name: 'Update waiter', + key: 'updateWaiter', + description: 'Updates a waiter to the line with the selected line.', + arguments: [ + { + label: 'Waiter', + key: 'waiterId', + type: 'dropdown', + required: true, + variables: true, + source: { + type: 'query', + name: 'getDynamicData', + arguments: [ + { + name: 'key', + value: 'listWaiters', + }, + ], + }, + }, + { + label: 'Line', + key: 'lineId', + type: 'dropdown', + required: false, + variables: true, + description: 'Used to find caller if 0 is used for waiter field', + source: { + type: 'query', + name: 'getDynamicData', + arguments: [ + { + name: 'key', + value: 'listLines', + }, + ], + }, + }, + { + label: 'Phone', + key: 'phone', + type: 'string', + required: false, + variables: true, + description: 'Used to find caller if 0 is used for waiter field', + }, + { + label: 'EWT', + key: 'serviceWaiterEwt', + type: 'string', + description: 'EWT as calculated by the service', + required: false, + variables: true, + }, + { + label: 'State', + key: 'serviceWaiterState', + type: 'dropdown', + description: 'State of caller in the call center', + required: false, + variables: true, + options: [ + { label: 'Waiting', value: 'Waiting' }, + { label: 'Connected', value: 'Connected' }, + { label: 'Transferred', value: 'Transferred' }, + { label: 'Timeout', value: 'Timeout' }, + { label: 'Canceled', value: 'Canceled' }, + ], + }, + { + label: 'Wait time', + key: 'waitTimeWhenUp', + type: 'string', + description: 'Wait time in seconds before being transferred to agent', + required: false, + variables: true, + }, + { + label: 'Talk time', + key: 'talkTime', + type: 'string', + description: 'Time in seconds spent talking with Agent', + required: false, + variables: true, + }, + { + label: 'Agent', + key: 'agentId', + type: 'string', + description: 'Agent where call was transferred to', + required: false, + variables: true, + }, + { + label: 'Service phone to call', + key: 'servicePhoneToCall', + type: 'string', + description: + "If set, callback uses this number instead of the line's service phone number", + required: false, + variables: true, + }, + ], + + async run($) { + const { + waiterId, + lineId, + phone, + serviceWaiterEwt, + serviceWaiterState, + waitTimeWhenUp, + talkTime, + agentId, + servicePhoneToCall, + } = $.step.parameters; + + const body = { + data: { + type: 'waiters', + attributes: { + line_id: lineId, + phone, + service_phone_to_call: servicePhoneToCall, + }, + }, + }; + + if (serviceWaiterEwt) { + body.data.attributes.service_waiter_ewt = serviceWaiterEwt; + } + + if (serviceWaiterState) { + body.data.attributes.service_waiter_state = serviceWaiterState; + } + + if (talkTime) { + body.data.attributes.talk_time = talkTime; + } + + if (agentId) { + body.data.attributes.agent_id = agentId; + } + + if (waitTimeWhenUp) { + body.data.attributes.wait_time_when_up = waitTimeWhenUp; + } + + const { data } = await $.http.put(`/v2/waiters/${waiterId}`, body); + + $.setActionItem({ raw: data }); + }, +}); diff --git a/packages/backend/src/apps/virtualq/assets/favicon.svg b/packages/backend/src/apps/virtualq/assets/favicon.svg new file mode 100644 index 00000000..41162b4d --- /dev/null +++ b/packages/backend/src/apps/virtualq/assets/favicon.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/backend/src/apps/virtualq/auth/index.js b/packages/backend/src/apps/virtualq/auth/index.js new file mode 100644 index 00000000..41659238 --- /dev/null +++ b/packages/backend/src/apps/virtualq/auth/index.js @@ -0,0 +1,21 @@ +import verifyCredentials from './verify-credentials.js'; +import isStillVerified from './is-still-verified.js'; + +export default { + fields: [ + { + key: 'apiKey', + label: 'API Key', + type: 'string', + required: true, + readOnly: false, + value: null, + placeholder: null, + description: 'API key of the VirtualQ API service.', + clickToCopy: false, + }, + ], + + verifyCredentials, + isStillVerified, +}; diff --git a/packages/backend/src/apps/virtualq/auth/is-still-verified.js b/packages/backend/src/apps/virtualq/auth/is-still-verified.js new file mode 100644 index 00000000..6663679a --- /dev/null +++ b/packages/backend/src/apps/virtualq/auth/is-still-verified.js @@ -0,0 +1,8 @@ +import verifyCredentials from './verify-credentials.js'; + +const isStillVerified = async ($) => { + await verifyCredentials($); + return true; +}; + +export default isStillVerified; diff --git a/packages/backend/src/apps/virtualq/auth/verify-credentials.js b/packages/backend/src/apps/virtualq/auth/verify-credentials.js new file mode 100644 index 00000000..1899f9e9 --- /dev/null +++ b/packages/backend/src/apps/virtualq/auth/verify-credentials.js @@ -0,0 +1,14 @@ +const verifyCredentials = async ($) => { + const response = await $.http.get('/v2/call_centers'); + + const callCenterNames = response.data.data + .map((callCenter) => callCenter.attributes.name) + .join(' - '); + + await $.auth.set({ + screenName: callCenterNames, + apiKey: $.auth.data.apiKey, + }); +}; + +export default verifyCredentials; diff --git a/packages/backend/src/apps/virtualq/common/add-auth-header.js b/packages/backend/src/apps/virtualq/common/add-auth-header.js new file mode 100644 index 00000000..91d34fe8 --- /dev/null +++ b/packages/backend/src/apps/virtualq/common/add-auth-header.js @@ -0,0 +1,15 @@ +const addAuthHeader = ($, requestConfig) => { + if ($.auth.data?.apiKey) { + requestConfig.headers['X-API-Key'] = $.auth.data.apiKey; + } + + if (requestConfig.method === 'post' || requestConfig.method === 'put') { + requestConfig.headers['Content-Type'] = 'application/vnd.api+json'; + } + + requestConfig.headers['X-Keys-Format'] = 'underscore'; + + return requestConfig; +}; + +export default addAuthHeader; diff --git a/packages/backend/src/apps/virtualq/dynamic-data/index.js b/packages/backend/src/apps/virtualq/dynamic-data/index.js new file mode 100644 index 00000000..e8aed5d3 --- /dev/null +++ b/packages/backend/src/apps/virtualq/dynamic-data/index.js @@ -0,0 +1,4 @@ +import listLines from './list-lines/index.js'; +import listWaiters from './list-waiters/index.js'; + +export default [listLines, listWaiters]; diff --git a/packages/backend/src/apps/virtualq/dynamic-data/list-lines/index.js b/packages/backend/src/apps/virtualq/dynamic-data/list-lines/index.js new file mode 100644 index 00000000..de2cf230 --- /dev/null +++ b/packages/backend/src/apps/virtualq/dynamic-data/list-lines/index.js @@ -0,0 +1,15 @@ +export default { + name: 'List lines', + key: 'listLines', + + async run($) { + const response = await $.http.get('/v2/lines'); + + const lines = response.data.data.map((line) => ({ + value: line.id, + name: line.attributes.name, + })); + + return { data: lines }; + }, +}; diff --git a/packages/backend/src/apps/virtualq/dynamic-data/list-waiters/index.js b/packages/backend/src/apps/virtualq/dynamic-data/list-waiters/index.js new file mode 100644 index 00000000..d7264cc2 --- /dev/null +++ b/packages/backend/src/apps/virtualq/dynamic-data/list-waiters/index.js @@ -0,0 +1,15 @@ +export default { + name: 'List waiters', + key: 'listWaiters', + + async run($) { + const response = await $.http.get('/v2/waiters'); + + const lines = response.data.data.map((line) => ({ + value: line.id, + name: line.attributes.name, + })); + + return { data: lines }; + }, +}; diff --git a/packages/backend/src/apps/virtualq/dynamic-fields/index.js b/packages/backend/src/apps/virtualq/dynamic-fields/index.js new file mode 100644 index 00000000..9473451b --- /dev/null +++ b/packages/backend/src/apps/virtualq/dynamic-fields/index.js @@ -0,0 +1,3 @@ +import listAppointmentFields from './list-appointment-fields/index.js'; + +export default [listAppointmentFields]; diff --git a/packages/backend/src/apps/virtualq/dynamic-fields/list-appointment-fields/index.js b/packages/backend/src/apps/virtualq/dynamic-fields/list-appointment-fields/index.js new file mode 100644 index 00000000..8ce2a3ca --- /dev/null +++ b/packages/backend/src/apps/virtualq/dynamic-fields/list-appointment-fields/index.js @@ -0,0 +1,20 @@ +export default { + name: 'List appointment fields', + key: 'listAppointmentFields', + + async run($) { + if ($.step.parameters.appointment) { + return [ + { + label: 'Appointment Time', + key: 'appointmentTime', + type: 'string', + required: true, + variables: true, + description: + 'Overrides the estimated up time with this time. Specify number of seconds since 1970 UTC.', + }, + ]; + } + }, +}; diff --git a/packages/backend/src/apps/virtualq/index.js b/packages/backend/src/apps/virtualq/index.js new file mode 100644 index 00000000..09ae740a --- /dev/null +++ b/packages/backend/src/apps/virtualq/index.js @@ -0,0 +1,22 @@ +import defineApp from '../../helpers/define-app.js'; +import addAuthHeader from './common/add-auth-header.js'; +import auth from './auth/index.js'; +import actions from './actions/index.js'; +import dynamicData from './dynamic-data/index.js'; +import dynamicFields from './dynamic-fields/index.js'; + +export default defineApp({ + name: 'VirtualQ', + key: 'virtualq', + iconUrl: '{BASE_URL}/apps/virtualq/assets/favicon.svg', + authDocUrl: '{DOCS_URL}/apps/virtualq/connection', + supportsConnections: true, + baseUrl: 'https://www.virtualq.io', + apiBaseUrl: 'https://api.virtualq.io/api/', + primaryColor: '#2E3D59', + beforeRequest: [addAuthHeader], + auth, + actions, + dynamicData, + dynamicFields, +}); diff --git a/packages/docs/pages/public/favicons/virtualq.svg b/packages/docs/pages/public/favicons/virtualq.svg new file mode 100644 index 00000000..41162b4d --- /dev/null +++ b/packages/docs/pages/public/favicons/virtualq.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +