feat(virtualq): add actions to manage waiters

This commit is contained in:
Ali BARIN
2024-12-20 13:18:25 +00:00
parent 28da91b19a
commit b0e186cfbd
17 changed files with 571 additions and 0 deletions

View File

@@ -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 });
},
});

View File

@@ -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 } });
},
});

View File

@@ -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];

View File

@@ -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 });
},
});

View File

@@ -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 });
},
});

View File

@@ -0,0 +1,35 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 229.2 57.3">
<defs>
<style>
.cls-1 { fill: #2E3D59; } .cls-2 { fill: #f5d05e; }
</style>
</defs>
<g>
<g id="Ebene_1">
<a>
<path id="fullLogo" class="cls-1" d="M16.3,40.4c-.2.5-.6.8-1.1.8h-4.3c-.5,0-1-.2-1.2-.8L0,17.9c-.2-.4.1-.8.5-.8h4.3c.5,0,1,.2,1.1.8l6.9,16.6,6.9-16.6c.2-.5.6-.8,1.1-.8h4.3c.4,0,.7.4.5.8l-9.6,22.5h0Z"/>
</a>
<path id="fullLogo-2" class="cls-1" d="M219.7,19.2c-5.3,0-9.6-4.3-9.6-9.6S214.4,0,219.7,0s9.6,4.3,9.6,9.6-4.3,9.6-9.6,9.6ZM219.7,1c-4.7,0-8.6,3.8-8.6,8.6s3.8,8.6,8.6,8.6,8.6-3.8,8.6-8.6-3.8-8.6-8.6-8.6Z"/>
<path id="fullLogo-3" class="cls-2" d="M185.7,35.9c-7.8,0-14.2-6.4-14.2-14.2s6.4-14.2,14.2-14.2,14.2,6.4,14.2,14.2-.8,5.3-2.2,7.5l-2.2-2.1c-1.4-1.3-3.7-1.3-5,.1-1.3,1.4-1.3,3.7.1,5l2,1.8c-2,1.1-4.4,1.8-6.9,1.8M202.9,34c2.5-3.5,4-7.7,4-12.3,0-11.7-9.5-21.2-21.2-21.2s-21.2,9.5-21.2,21.2,9.5,21.2,21.2,21.2,8.7-1.4,12.1-3.8l3.1,2.9c.7.6,1.6,1,2.4,1s1.9-.4,2.6-1.1c1.3-1.4,1.3-3.7-.1-5l-2.9-2.7h0Z"/>
<path id="fullLogo-4" class="cls-1" d="M37.8,40.4c0,.5-.3.8-.8.8h-3.9c-.5,0-.8-.3-.8-.8v-22.4c0-.5.3-.8.8-.8h3.9c.5,0,.8.3.8.8v22.4ZM35,4.2c1.9,0,3.4,1.5,3.4,3.4s-1.5,3.4-3.4,3.4-3.4-1.5-3.4-3.4,1.5-3.4,3.4-3.4"/>
<a>
<path id="fullLogo-5" class="cls-1" d="M106.8,29.9v-11.9c0-.5.3-.8.8-.8h3.9c.5,0,.8.3.8.8v11.9c0,8-5.1,12-11.8,12s-11.8-4-11.8-12v-11.9c0-.5.3-.8.8-.8h3.9c.5,0,.8.3.8.8v11.9c0,5.5,3.6,6.7,6.3,6.7s6.3-1.2,6.3-6.7"/>
</a>
<path id="fullLogo-6" class="cls-1" d="M131.7,21.7c-3.1,0-7.2,1.8-7.2,7.5s3.4,7.5,6.4,7.6c3,.2,7.7-1.5,7.7-7.5s-3.9-7.6-7-7.6M144.3,40.4c0,.5-.3.8-.8.8h-3.9c-.5,0-.8-.3-.8-.8v-2.4c-2,3.2-5.8,3.9-8.2,3.9-7.5,0-11.6-6-11.6-12.5s5.7-12.9,12.8-12.9,12.5,4.3,12.5,13v11h0Z"/>
<a>
<path id="fullLogo-7" class="cls-1" d="M156.8,4.2h-3.9c-.5,0-.8.3-.8.8v35.3c0,.5.3.8.8.8h3.9c.5,0,.8-.3.8-.8V5c0-.5-.3-.8-.8-.8"/>
</a>
<a>
<path id="fullLogo-8" class="cls-1" d="M83.1,17.1h-6.4V5c0-.5-.3-.8-.8-.8h-3.9c-.5,0-.8.3-.8.8v12.1h-6.4c-.5,0-.8.3-.8.8v3.6c0,.5.3.8.8.8h6.4v17.9c0,.5.3.8.8.8h3.9c.5,0,.8-.3.8-.8v-17.9h6.4c.5,0,.8-.3.8-.8v-3.6c0-.5-.3-.8-.8-.8"/>
</a>
<a>
<path id="fullLogo-9" class="cls-1" d="M58.4,17.1h-6.4c-4.5,0-7.9,3.4-7.9,10v13.2c0,.5.3.8.8.8h3.9c.5,0,.8-.3.8-.8v-.9s0,0,0,0v-12.3c0-3,1.8-4.7,3.9-4.7h4.9c.5,0,.8-.3.8-.8v-3.6c0-.5-.3-.8-.8-.8"/>
</a>
<a>
<path id="fullLogo-39" class="cls-1" d="M218.8,6.9c0-.1-.1-.2-.3-.2h-1c-.1,0-.2,0-.3.2l-2.3,5.3c0,0,0,.2.1.2h1c.1,0,.2,0,.3-.2l1.6-3.9,1.6,3.9c0,.1.1.2.3.2h1c0,0,.2,0,.1-.2l-2.3-5.3h0Z"/>
</a>
<path id="fullLogo-40" class="cls-1" d="M223.8,12.2c0,.1,0,.2-.2.2h-.9c-.1,0-.2,0-.2-.2v-5.3c0-.1,0-.2.2-.2h.9c.1,0,.2,0,.2.2v5.3Z"/>
<path id="fullLogo-41" class="cls-1" d="M219.7,10.2c0,0,.1,0,.1.2v.9c0,.1,0,.2-.1.2h-3.4c0,0-.1,0-.1-.2v-.9c0-.1,0-.2.1-.2h3.4Z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@@ -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,
};

View File

@@ -0,0 +1,8 @@
import verifyCredentials from './verify-credentials.js';
const isStillVerified = async ($) => {
await verifyCredentials($);
return true;
};
export default isStillVerified;

View File

@@ -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;

View File

@@ -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;

View File

@@ -0,0 +1,4 @@
import listLines from './list-lines/index.js';
import listWaiters from './list-waiters/index.js';
export default [listLines, listWaiters];

View File

@@ -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 };
},
};

View File

@@ -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 };
},
};

View File

@@ -0,0 +1,3 @@
import listAppointmentFields from './list-appointment-fields/index.js';
export default [listAppointmentFields];

View File

@@ -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.',
},
];
}
},
};

View File

@@ -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,
});

View File

@@ -0,0 +1,35 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 229.2 57.3">
<defs>
<style>
.cls-1 { fill: #2E3D59; } .cls-2 { fill: #f5d05e; }
</style>
</defs>
<g>
<g id="Ebene_1">
<a>
<path id="fullLogo" class="cls-1" d="M16.3,40.4c-.2.5-.6.8-1.1.8h-4.3c-.5,0-1-.2-1.2-.8L0,17.9c-.2-.4.1-.8.5-.8h4.3c.5,0,1,.2,1.1.8l6.9,16.6,6.9-16.6c.2-.5.6-.8,1.1-.8h4.3c.4,0,.7.4.5.8l-9.6,22.5h0Z"/>
</a>
<path id="fullLogo-2" class="cls-1" d="M219.7,19.2c-5.3,0-9.6-4.3-9.6-9.6S214.4,0,219.7,0s9.6,4.3,9.6,9.6-4.3,9.6-9.6,9.6ZM219.7,1c-4.7,0-8.6,3.8-8.6,8.6s3.8,8.6,8.6,8.6,8.6-3.8,8.6-8.6-3.8-8.6-8.6-8.6Z"/>
<path id="fullLogo-3" class="cls-2" d="M185.7,35.9c-7.8,0-14.2-6.4-14.2-14.2s6.4-14.2,14.2-14.2,14.2,6.4,14.2,14.2-.8,5.3-2.2,7.5l-2.2-2.1c-1.4-1.3-3.7-1.3-5,.1-1.3,1.4-1.3,3.7.1,5l2,1.8c-2,1.1-4.4,1.8-6.9,1.8M202.9,34c2.5-3.5,4-7.7,4-12.3,0-11.7-9.5-21.2-21.2-21.2s-21.2,9.5-21.2,21.2,9.5,21.2,21.2,21.2,8.7-1.4,12.1-3.8l3.1,2.9c.7.6,1.6,1,2.4,1s1.9-.4,2.6-1.1c1.3-1.4,1.3-3.7-.1-5l-2.9-2.7h0Z"/>
<path id="fullLogo-4" class="cls-1" d="M37.8,40.4c0,.5-.3.8-.8.8h-3.9c-.5,0-.8-.3-.8-.8v-22.4c0-.5.3-.8.8-.8h3.9c.5,0,.8.3.8.8v22.4ZM35,4.2c1.9,0,3.4,1.5,3.4,3.4s-1.5,3.4-3.4,3.4-3.4-1.5-3.4-3.4,1.5-3.4,3.4-3.4"/>
<a>
<path id="fullLogo-5" class="cls-1" d="M106.8,29.9v-11.9c0-.5.3-.8.8-.8h3.9c.5,0,.8.3.8.8v11.9c0,8-5.1,12-11.8,12s-11.8-4-11.8-12v-11.9c0-.5.3-.8.8-.8h3.9c.5,0,.8.3.8.8v11.9c0,5.5,3.6,6.7,6.3,6.7s6.3-1.2,6.3-6.7"/>
</a>
<path id="fullLogo-6" class="cls-1" d="M131.7,21.7c-3.1,0-7.2,1.8-7.2,7.5s3.4,7.5,6.4,7.6c3,.2,7.7-1.5,7.7-7.5s-3.9-7.6-7-7.6M144.3,40.4c0,.5-.3.8-.8.8h-3.9c-.5,0-.8-.3-.8-.8v-2.4c-2,3.2-5.8,3.9-8.2,3.9-7.5,0-11.6-6-11.6-12.5s5.7-12.9,12.8-12.9,12.5,4.3,12.5,13v11h0Z"/>
<a>
<path id="fullLogo-7" class="cls-1" d="M156.8,4.2h-3.9c-.5,0-.8.3-.8.8v35.3c0,.5.3.8.8.8h3.9c.5,0,.8-.3.8-.8V5c0-.5-.3-.8-.8-.8"/>
</a>
<a>
<path id="fullLogo-8" class="cls-1" d="M83.1,17.1h-6.4V5c0-.5-.3-.8-.8-.8h-3.9c-.5,0-.8.3-.8.8v12.1h-6.4c-.5,0-.8.3-.8.8v3.6c0,.5.3.8.8.8h6.4v17.9c0,.5.3.8.8.8h3.9c.5,0,.8-.3.8-.8v-17.9h6.4c.5,0,.8-.3.8-.8v-3.6c0-.5-.3-.8-.8-.8"/>
</a>
<a>
<path id="fullLogo-9" class="cls-1" d="M58.4,17.1h-6.4c-4.5,0-7.9,3.4-7.9,10v13.2c0,.5.3.8.8.8h3.9c.5,0,.8-.3.8-.8v-.9s0,0,0,0v-12.3c0-3,1.8-4.7,3.9-4.7h4.9c.5,0,.8-.3.8-.8v-3.6c0-.5-.3-.8-.8-.8"/>
</a>
<a>
<path id="fullLogo-39" class="cls-1" d="M218.8,6.9c0-.1-.1-.2-.3-.2h-1c-.1,0-.2,0-.3.2l-2.3,5.3c0,0,0,.2.1.2h1c.1,0,.2,0,.3-.2l1.6-3.9,1.6,3.9c0,.1.1.2.3.2h1c0,0,.2,0,.1-.2l-2.3-5.3h0Z"/>
</a>
<path id="fullLogo-40" class="cls-1" d="M223.8,12.2c0,.1,0,.2-.2.2h-.9c-.1,0-.2,0-.2-.2v-5.3c0-.1,0-.2.2-.2h.9c.1,0,.2,0,.2.2v5.3Z"/>
<path id="fullLogo-41" class="cls-1" d="M219.7,10.2c0,0,.1,0,.1.2v.9c0,.1,0,.2-.1.2h-3.4c0,0-.1,0-.1-.2v-.9c0-.1,0-.2.1-.2h3.4Z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.9 KiB