From be610c7fa9da21ae91f089751f495c29c9828595 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C4=B1dvan=20Akca?= Date: Mon, 30 Oct 2023 17:16:56 +0300 Subject: [PATCH] feat(zendesk): add create ticket action --- .../zendesk/actions/create-ticket/fields.ts | 301 ++++++++++++++++++ .../zendesk/actions/create-ticket/index.ts | 101 ++++++ .../backend/src/apps/zendesk/actions/index.ts | 3 + .../src/apps/zendesk/dynamic-data/index.ts | 13 + .../zendesk/dynamic-data/list-brands/index.ts | 38 +++ .../zendesk/dynamic-data/list-groups/index.ts | 38 +++ .../list-sharing-agreements/index.ts | 40 +++ .../dynamic-data/list-ticket-forms/index.ts | 38 +++ .../zendesk/dynamic-data/list-users/index.ts | 43 +++ packages/backend/src/apps/zendesk/index.ts | 4 + packages/docs/pages/.vitepress/config.js | 5 +- packages/docs/pages/apps/zendesk/actions.md | 12 + packages/docs/pages/guide/available-apps.md | 1 + 13 files changed, 636 insertions(+), 1 deletion(-) create mode 100644 packages/backend/src/apps/zendesk/actions/create-ticket/fields.ts create mode 100644 packages/backend/src/apps/zendesk/actions/create-ticket/index.ts create mode 100644 packages/backend/src/apps/zendesk/actions/index.ts create mode 100644 packages/backend/src/apps/zendesk/dynamic-data/index.ts create mode 100644 packages/backend/src/apps/zendesk/dynamic-data/list-brands/index.ts create mode 100644 packages/backend/src/apps/zendesk/dynamic-data/list-groups/index.ts create mode 100644 packages/backend/src/apps/zendesk/dynamic-data/list-sharing-agreements/index.ts create mode 100644 packages/backend/src/apps/zendesk/dynamic-data/list-ticket-forms/index.ts create mode 100644 packages/backend/src/apps/zendesk/dynamic-data/list-users/index.ts create mode 100644 packages/docs/pages/apps/zendesk/actions.md diff --git a/packages/backend/src/apps/zendesk/actions/create-ticket/fields.ts b/packages/backend/src/apps/zendesk/actions/create-ticket/fields.ts new file mode 100644 index 00000000..390f5f3e --- /dev/null +++ b/packages/backend/src/apps/zendesk/actions/create-ticket/fields.ts @@ -0,0 +1,301 @@ +export const fields = [ + { + label: 'Subject', + key: 'subject', + type: 'string' as const, + required: true, + variables: true, + description: '', + }, + { + label: 'Assignee', + key: 'assigneeId', + type: 'dropdown' as const, + required: false, + variables: true, + description: + 'Note: An error occurs if the assignee is not in the default group (or the specific group chosen below).', + source: { + type: 'query', + name: 'getDynamicData', + arguments: [ + { + name: 'key', + value: 'listUsers', + }, + { + name: 'parameters.showUserRole', + value: 'true', + }, + { + name: 'parameters.includeAdmins', + value: 'true', + }, + ], + }, + }, + { + label: 'Collaborators', + key: 'collaborators', + type: 'dynamic' as const, + required: false, + description: '', + fields: [ + { + label: 'Collaborator', + key: 'collaborator', + type: 'dropdown' as const, + required: false, + variables: true, + description: '', + source: { + type: 'query', + name: 'getDynamicData', + arguments: [ + { + name: 'key', + value: 'listUsers', + }, + { + name: 'parameters.includeAdmins', + value: 'true', + }, + ], + }, + }, + ], + }, + { + label: 'Collaborator Emails', + key: 'collaboratorEmails', + type: 'dynamic' as const, + required: false, + description: + 'You have the option to include individuals who are not Zendesk users as Collaborators by adding their email addresses here.', + fields: [ + { + label: 'Collaborator Email', + key: 'collaboratorEmail', + type: 'string' as const, + required: false, + variables: true, + description: '', + }, + ], + }, + { + label: 'Group', + key: 'groupId', + type: 'dropdown' as const, + required: false, + variables: true, + description: 'Allocate this ticket to a specific group.', + source: { + type: 'query', + name: 'getDynamicData', + arguments: [ + { + name: 'key', + value: 'listGroups', + }, + ], + }, + }, + { + label: 'Requester Name', + key: 'requesterName', + type: 'string' as const, + required: false, + variables: true, + description: + 'To specify the Requester, you need to fill in the Requester Name in this field and provide the Requestor Email in the next field.', + }, + { + label: 'Requester Email', + key: 'requesterEmail', + type: 'string' as const, + required: false, + variables: true, + description: + 'To specify the Requester, you need to fill in the Requester Email in this field and provide the Requestor Name in the previous field.', + }, + { + label: 'First Comment/Description Format', + key: 'format', + type: 'dropdown' as const, + required: false, + variables: true, + description: '', + options: [ + { label: 'Plain Text', value: 'Plain Text' }, + { label: 'HTML', value: 'HTML' }, + ], + }, + { + label: 'First Comment/Description', + key: 'comment', + type: 'string' as const, + required: true, + variables: true, + description: '', + }, + { + label: 'Should the first comment be public?', + key: 'publicOrNot', + type: 'dropdown' as const, + required: false, + variables: true, + description: '', + options: [ + { label: 'Yes', value: 'yes' }, + { label: 'No', value: 'no' }, + ], + }, + { + label: 'Tags', + key: 'tags', + type: 'string' as const, + required: false, + variables: true, + description: 'A comma separated list of tags.', + }, + { + label: 'Status', + key: 'status', + type: 'dropdown' as const, + required: false, + variables: true, + description: '', + options: [ + { label: 'New', value: 'new' }, + { label: 'Open', value: 'open' }, + { label: 'Pending', value: 'pending' }, + { label: 'Hold', value: 'hold' }, + { label: 'Solved', value: 'solved' }, + { label: 'Closed', value: 'closed' }, + ], + }, + { + label: 'Type', + key: 'type', + type: 'dropdown' as const, + required: false, + variables: true, + description: '', + options: [ + { label: 'Problem', value: 'problem' }, + { label: 'Incident', value: 'incident' }, + { label: 'Question', value: 'question' }, + { label: 'Task', value: 'task' }, + ], + }, + { + label: 'Due At', + key: 'dueAt', + type: 'string' as const, + required: false, + variables: true, + description: 'Limited to tickets typed as "task".', + }, + { + label: 'Priority', + key: 'priority', + type: 'dropdown' as const, + required: false, + variables: true, + description: '', + options: [ + { label: 'Urgent', value: 'urgent' }, + { label: 'High', value: 'high' }, + { label: 'Normal', value: 'normal' }, + { label: 'Low', value: 'low' }, + ], + }, + { + label: 'Submitter', + key: 'submitterId', + type: 'dropdown' as const, + required: false, + variables: true, + description: '', + source: { + type: 'query', + name: 'getDynamicData', + arguments: [ + { + name: 'key', + value: 'listUsers', + }, + { + name: 'parameters.includeAdmins', + value: 'false', + }, + ], + }, + }, + { + label: 'Ticket Form', + key: 'ticketForm', + type: 'dropdown' as const, + required: false, + variables: true, + description: + 'When chosen, this will configure the form displayed for this ticket. Note: This field is solely relevant for Zendesk enterprise accounts.', + source: { + type: 'query', + name: 'getDynamicData', + arguments: [ + { + name: 'key', + value: 'listTicketForms', + }, + ], + }, + }, + { + label: 'Sharing Agreements', + key: 'sharingAgreements', + type: 'dynamic' as const, + required: false, + description: '', + fields: [ + { + label: 'Sharing Agreement', + key: 'sharingAgreement', + type: 'dropdown' as const, + required: false, + variables: true, + description: '', + source: { + type: 'query', + name: 'getDynamicData', + arguments: [ + { + name: 'key', + value: 'listSharingAgreements', + }, + ], + }, + }, + ], + }, + { + label: 'Brand', + key: 'brandId', + type: 'dropdown' as const, + required: false, + variables: true, + description: + 'This applies exclusively to Zendesk customers subscribed to plans that include multi-brand support.', + source: { + type: 'query', + name: 'getDynamicData', + arguments: [ + { + name: 'key', + value: 'listBrands', + }, + ], + }, + }, +]; diff --git a/packages/backend/src/apps/zendesk/actions/create-ticket/index.ts b/packages/backend/src/apps/zendesk/actions/create-ticket/index.ts new file mode 100644 index 00000000..942b54a5 --- /dev/null +++ b/packages/backend/src/apps/zendesk/actions/create-ticket/index.ts @@ -0,0 +1,101 @@ +import { IJSONArray, IJSONObject } from '@automatisch/types'; +import defineAction from '../../../../helpers/define-action'; +import { fields } from './fields'; +import isEmpty from 'lodash/isEmpty'; + +type Payload = { + ticket: IJSONObject; +}; + +export default defineAction({ + name: 'Create ticket', + key: 'createTicket', + description: 'Creates a new ticket', + arguments: fields, + + async run($) { + const { + subject, + assigneeId, + groupId, + requesterName, + requesterEmail, + format, + comment, + publicOrNot, + status, + type, + dueAt, + priority, + submitterId, + ticketForm, + brandId, + } = $.step.parameters; + + const collaborators = $.step.parameters.collaborators as IJSONArray; + const collaboratorIds = collaborators?.map( + (collaborator: IJSONObject) => collaborator.collaborator + ); + + const collaboratorEmails = $.step.parameters + .collaboratorEmails as IJSONArray; + const formattedCollaboratorEmails = collaboratorEmails?.map( + (collaboratorEmail: IJSONObject) => collaboratorEmail.collaboratorEmail + ); + + const formattedCollaborators = [ + ...collaboratorIds, + ...formattedCollaboratorEmails, + ]; + + const sharingAgreements = $.step.parameters.sharingAgreements as IJSONArray; + const sharingAgreementIds = sharingAgreements + ?.filter(isEmpty) + .map((sharingAgreement: IJSONObject) => + Number(sharingAgreement.sharingAgreement) + ); + + const tags = $.step.parameters.tags as string; + const formattedTags = tags.split(','); + + const payload: Payload = { + ticket: { + subject, + assignee_id: assigneeId, + collaborators: formattedCollaborators, + group_id: groupId, + is_public: publicOrNot, + tags: formattedTags, + status, + type, + due_at: dueAt, + priority, + submitter_id: submitterId, + ticket_form_id: ticketForm, + sharing_agreement_ids: sharingAgreementIds, + brand_id: brandId, + }, + }; + + if (requesterName && requesterEmail) { + payload.ticket.requester = { + name: requesterName, + email: requesterEmail, + }; + } + + if (format === 'HTML') { + payload.ticket.comment = { + html_body: comment, + }; + } else { + payload.ticket.comment = { + body: comment, + }; + } + + const response = await $.http.post('/api/v2/tickets', payload); + + $.setActionItem({ raw: response.data }); + }, +}); diff --git a/packages/backend/src/apps/zendesk/actions/index.ts b/packages/backend/src/apps/zendesk/actions/index.ts new file mode 100644 index 00000000..fa3cea56 --- /dev/null +++ b/packages/backend/src/apps/zendesk/actions/index.ts @@ -0,0 +1,3 @@ +import createTicket from './create-ticket'; + +export default [createTicket]; diff --git a/packages/backend/src/apps/zendesk/dynamic-data/index.ts b/packages/backend/src/apps/zendesk/dynamic-data/index.ts new file mode 100644 index 00000000..4cd089e7 --- /dev/null +++ b/packages/backend/src/apps/zendesk/dynamic-data/index.ts @@ -0,0 +1,13 @@ +import listUsers from './list-users'; +import listBrands from './list-brands'; +import listGroups from './list-groups'; +import listSharingAgreements from './list-sharing-agreements'; +import listTicketForms from './list-ticket-forms'; + +export default [ + listUsers, + listBrands, + listGroups, + listSharingAgreements, + listTicketForms, +]; diff --git a/packages/backend/src/apps/zendesk/dynamic-data/list-brands/index.ts b/packages/backend/src/apps/zendesk/dynamic-data/list-brands/index.ts new file mode 100644 index 00000000..3b6f43d8 --- /dev/null +++ b/packages/backend/src/apps/zendesk/dynamic-data/list-brands/index.ts @@ -0,0 +1,38 @@ +import { IGlobalVariable, IJSONObject } from '@automatisch/types'; + +export default { + name: 'List brands', + key: 'listBrands', + + async run($: IGlobalVariable) { + const brands: { + data: IJSONObject[]; + } = { + data: [], + }; + + const params = { + page: 1, + per_page: 100, + }; + + let nextPage; + do { + const response = await $.http.get('/api/v2/brands', { params }); + const allBrands = response?.data?.brands; + nextPage = response.data.next_page; + params.page = params.page + 1; + + if (allBrands?.length) { + for (const brand of allBrands) { + brands.data.push({ + value: brand.id, + name: brand.name, + }); + } + } + } while (nextPage); + + return brands; + }, +}; diff --git a/packages/backend/src/apps/zendesk/dynamic-data/list-groups/index.ts b/packages/backend/src/apps/zendesk/dynamic-data/list-groups/index.ts new file mode 100644 index 00000000..02db70fb --- /dev/null +++ b/packages/backend/src/apps/zendesk/dynamic-data/list-groups/index.ts @@ -0,0 +1,38 @@ +import { IGlobalVariable, IJSONObject } from '@automatisch/types'; + +export default { + name: 'List groups', + key: 'listGroups', + + async run($: IGlobalVariable) { + const groups: { + data: IJSONObject[]; + } = { + data: [], + }; + let hasMore; + + const params = { + 'page[size]': 100, + 'page[after]': undefined as unknown as string, + }; + + do { + const response = await $.http.get('/api/v2/groups', { params }); + const allGroups = response?.data?.groups; + hasMore = response?.data?.meta?.has_more; + params['page[after]'] = response.data.links?.after_cursor; + + if (allGroups?.length) { + for (const group of allGroups) { + groups.data.push({ + value: group.id, + name: group.name, + }); + } + } + } while (hasMore); + + return groups; + }, +}; diff --git a/packages/backend/src/apps/zendesk/dynamic-data/list-sharing-agreements/index.ts b/packages/backend/src/apps/zendesk/dynamic-data/list-sharing-agreements/index.ts new file mode 100644 index 00000000..81c49835 --- /dev/null +++ b/packages/backend/src/apps/zendesk/dynamic-data/list-sharing-agreements/index.ts @@ -0,0 +1,40 @@ +import { IGlobalVariable, IJSONObject } from '@automatisch/types'; + +export default { + name: 'List sharing agreements', + key: 'listSharingAgreements', + + async run($: IGlobalVariable) { + const sharingAgreements: { + data: IJSONObject[]; + } = { + data: [], + }; + + const params = { + page: 1, + per_page: 100, + }; + + let nextPage; + do { + const response = await $.http.get('/api/v2/sharing_agreements', { + params, + }); + const allSharingAgreements = response?.data?.sharing_agreements; + nextPage = response.data.next_page; + params.page = params.page + 1; + + if (allSharingAgreements?.length) { + for (const sharingAgreement of allSharingAgreements) { + sharingAgreements.data.push({ + value: sharingAgreement.id, + name: sharingAgreement.name, + }); + } + } + } while (nextPage); + + return sharingAgreements; + }, +}; diff --git a/packages/backend/src/apps/zendesk/dynamic-data/list-ticket-forms/index.ts b/packages/backend/src/apps/zendesk/dynamic-data/list-ticket-forms/index.ts new file mode 100644 index 00000000..1ec08ddf --- /dev/null +++ b/packages/backend/src/apps/zendesk/dynamic-data/list-ticket-forms/index.ts @@ -0,0 +1,38 @@ +import { IGlobalVariable, IJSONObject } from '@automatisch/types'; + +export default { + name: 'List ticket forms', + key: 'listTicketForms', + + async run($: IGlobalVariable) { + const ticketForms: { + data: IJSONObject[]; + } = { + data: [], + }; + + const params = { + page: 1, + per_page: 100, + }; + + let nextPage; + do { + const response = await $.http.get('/api/v2/ticket_forms', { params }); + const allTicketForms = response?.data?.ticket_forms; + nextPage = response.data.next_page; + params.page = params.page + 1; + + if (allTicketForms?.length) { + for (const ticketForm of allTicketForms) { + ticketForms.data.push({ + value: ticketForm.id, + name: ticketForm.name, + }); + } + } + } while (nextPage); + + return ticketForms; + }, +}; diff --git a/packages/backend/src/apps/zendesk/dynamic-data/list-users/index.ts b/packages/backend/src/apps/zendesk/dynamic-data/list-users/index.ts new file mode 100644 index 00000000..46b3bd89 --- /dev/null +++ b/packages/backend/src/apps/zendesk/dynamic-data/list-users/index.ts @@ -0,0 +1,43 @@ +import { IGlobalVariable, IJSONObject } from '@automatisch/types'; + +export default { + name: 'List users', + key: 'listUsers', + + async run($: IGlobalVariable) { + const users: { + data: IJSONObject[]; + } = { + data: [], + }; + let hasMore; + const showUserRole = $.step.parameters.showUserRole === 'true'; + const includeAdmins = $.step.parameters.includeAdmins === 'true'; + const role = includeAdmins ? ['admin', 'agent'] : ['agent']; + + const params = { + 'page[size]': 100, + role, + 'page[after]': undefined as unknown as string, + }; + + do { + const response = await $.http.get('/api/v2/users', { params }); + const allUsers = response?.data?.users; + hasMore = response?.data?.meta?.has_more; + params['page[after]'] = response.data.links?.after_cursor; + + if (allUsers?.length) { + for (const user of allUsers) { + const name = showUserRole ? `${user.name} ${user.role}` : user.name; + users.data.push({ + value: user.id, + name, + }); + } + } + } while (hasMore); + + return users; + }, +}; diff --git a/packages/backend/src/apps/zendesk/index.ts b/packages/backend/src/apps/zendesk/index.ts index 64655b35..54810011 100644 --- a/packages/backend/src/apps/zendesk/index.ts +++ b/packages/backend/src/apps/zendesk/index.ts @@ -1,6 +1,8 @@ import defineApp from '../../helpers/define-app'; import addAuthHeader from './common/add-auth-headers'; import auth from './auth'; +import actions from './actions'; +import dynamicData from './dynamic-data'; export default defineApp({ name: 'Zendesk', @@ -13,4 +15,6 @@ export default defineApp({ supportsConnections: true, beforeRequest: [addAuthHeader], auth, + actions, + dynamicData, }); diff --git a/packages/docs/pages/.vitepress/config.js b/packages/docs/pages/.vitepress/config.js index 15e92517..f8c5db37 100644 --- a/packages/docs/pages/.vitepress/config.js +++ b/packages/docs/pages/.vitepress/config.js @@ -442,7 +442,10 @@ export default defineConfig({ text: 'Zendesk', collapsible: true, collapsed: true, - items: [{ text: 'Connection', link: '/apps/zendesk/connection' }], + items: [ + { text: 'Actions', link: '/apps/zendesk/actions' }, + { text: 'Connection', link: '/apps/zendesk/connection' }, + ], }, ], '/': [ diff --git a/packages/docs/pages/apps/zendesk/actions.md b/packages/docs/pages/apps/zendesk/actions.md new file mode 100644 index 00000000..f989c244 --- /dev/null +++ b/packages/docs/pages/apps/zendesk/actions.md @@ -0,0 +1,12 @@ +--- +favicon: /favicons/zendesk.svg +items: + - name: Create ticket + desc: Creates a new ticket. +--- + + + + diff --git a/packages/docs/pages/guide/available-apps.md b/packages/docs/pages/guide/available-apps.md index cda867be..e2b6b154 100644 --- a/packages/docs/pages/guide/available-apps.md +++ b/packages/docs/pages/guide/available-apps.md @@ -46,3 +46,4 @@ The following integrations are currently supported by Automatisch. - [Webhooks](/apps/webhooks/triggers) - [WordPress](/apps/wordpress/triggers) - [Youtube](/apps/youtube/triggers) +- [Zendesk](/apps/zendesk/actions)