From 79b0d083e6a77cf18e4355f97464b4c78bb016e1 Mon Sep 17 00:00:00 2001 From: Krzysztof DK <106629516+krzysztof-dk@users.noreply.github.com> Date: Thu, 16 Jan 2025 17:28:53 +0100 Subject: [PATCH] feat: add freescout app with new event trigger (#2287) * added FreeScout plugin * docs: add freescout in sidebar * docs(freescout): add missing app icon * refactor(freescout): rename filter as eventType --------- Co-authored-by: Ali BARIN --- .../src/apps/freescout/assets/favicon.svg | 25 ++++++++ .../backend/src/apps/freescout/auth/index.js | 44 +++++++++++++ .../apps/freescout/auth/is-still-verified.js | 6 ++ .../apps/freescout/auth/verify-credentials.js | 5 ++ .../apps/freescout/common/add-auth-header.js | 9 +++ .../src/apps/freescout/common/set-base-url.js | 6 ++ .../apps/freescout/common/webhook-filters.js | 52 ++++++++++++++++ packages/backend/src/apps/freescout/index.js | 18 ++++++ .../src/apps/freescout/triggers/index.js | 3 + .../freescout/triggers/new-event/index.js | 61 +++++++++++++++++++ .../src/models/__snapshots__/app.test.js.snap | 1 + packages/docs/pages/.vitepress/config.js | 9 +++ .../docs/pages/apps/freescout/connection.md | 13 ++++ .../docs/pages/apps/freescout/triggers.md | 13 ++++ .../docs/pages/public/favicons/freescout.svg | 24 ++++++++ 15 files changed, 289 insertions(+) create mode 100644 packages/backend/src/apps/freescout/assets/favicon.svg create mode 100644 packages/backend/src/apps/freescout/auth/index.js create mode 100644 packages/backend/src/apps/freescout/auth/is-still-verified.js create mode 100644 packages/backend/src/apps/freescout/auth/verify-credentials.js create mode 100644 packages/backend/src/apps/freescout/common/add-auth-header.js create mode 100644 packages/backend/src/apps/freescout/common/set-base-url.js create mode 100644 packages/backend/src/apps/freescout/common/webhook-filters.js create mode 100644 packages/backend/src/apps/freescout/index.js create mode 100644 packages/backend/src/apps/freescout/triggers/index.js create mode 100644 packages/backend/src/apps/freescout/triggers/new-event/index.js create mode 100644 packages/docs/pages/apps/freescout/connection.md create mode 100644 packages/docs/pages/apps/freescout/triggers.md create mode 100644 packages/docs/pages/public/favicons/freescout.svg diff --git a/packages/backend/src/apps/freescout/assets/favicon.svg b/packages/backend/src/apps/freescout/assets/favicon.svg new file mode 100644 index 00000000..db766fa8 --- /dev/null +++ b/packages/backend/src/apps/freescout/assets/favicon.svg @@ -0,0 +1,25 @@ + + + + + + + + diff --git a/packages/backend/src/apps/freescout/auth/index.js b/packages/backend/src/apps/freescout/auth/index.js new file mode 100644 index 00000000..f6c8aedb --- /dev/null +++ b/packages/backend/src/apps/freescout/auth/index.js @@ -0,0 +1,44 @@ +import verifyCredentials from './verify-credentials.js'; +import isStillVerified from './is-still-verified.js'; + +export default { + fields: [ + { + key: 'screenName', + label: 'Screen Name', + type: 'string', + required: true, + readOnly: false, + value: null, + placeholder: null, + description: + 'Screen name of your connection to be used on Automatisch UI.', + clickToCopy: false, + }, + { + key: 'instanceUrl', + label: 'FreeScout instance URL', + type: 'string', + required: true, + readOnly: false, + description: 'Your FreeScout instance URL.', + docUrl: 'https://automatisch.io/docs/freescout#instance-url', + clickToCopy: false, + }, + { + key: 'apiKey', + label: 'API Key', + type: 'string', + required: true, + readOnly: false, + value: null, + placeholder: null, + description: 'FreeScout API key of your account.', + docUrl: 'https://automatisch.io/docs/freescout#api-key', + clickToCopy: false, + }, + ], + + verifyCredentials, + isStillVerified, +}; diff --git a/packages/backend/src/apps/freescout/auth/is-still-verified.js b/packages/backend/src/apps/freescout/auth/is-still-verified.js new file mode 100644 index 00000000..c5514911 --- /dev/null +++ b/packages/backend/src/apps/freescout/auth/is-still-verified.js @@ -0,0 +1,6 @@ +const isStillVerified = async ($) => { + await $.http.get('/api/mailboxes'); + return true; +}; + +export default isStillVerified; diff --git a/packages/backend/src/apps/freescout/auth/verify-credentials.js b/packages/backend/src/apps/freescout/auth/verify-credentials.js new file mode 100644 index 00000000..1ac858af --- /dev/null +++ b/packages/backend/src/apps/freescout/auth/verify-credentials.js @@ -0,0 +1,5 @@ +const verifyCredentials = async ($) => { + await $.http.get('/api/mailboxes'); +}; + +export default verifyCredentials; diff --git a/packages/backend/src/apps/freescout/common/add-auth-header.js b/packages/backend/src/apps/freescout/common/add-auth-header.js new file mode 100644 index 00000000..e599ae4d --- /dev/null +++ b/packages/backend/src/apps/freescout/common/add-auth-header.js @@ -0,0 +1,9 @@ +const addAuthHeader = ($, requestConfig) => { + if ($.auth.data?.apiKey) { + requestConfig.headers['X-FreeScout-API-Key'] = $.auth.data.apiKey; + } + + return requestConfig; +}; + +export default addAuthHeader; diff --git a/packages/backend/src/apps/freescout/common/set-base-url.js b/packages/backend/src/apps/freescout/common/set-base-url.js new file mode 100644 index 00000000..8df6a36f --- /dev/null +++ b/packages/backend/src/apps/freescout/common/set-base-url.js @@ -0,0 +1,6 @@ +const setBaseUrl = ($, requestConfig) => { + requestConfig.baseURL = $.auth.data.instanceUrl; + return requestConfig; +}; + +export default setBaseUrl; diff --git a/packages/backend/src/apps/freescout/common/webhook-filters.js b/packages/backend/src/apps/freescout/common/webhook-filters.js new file mode 100644 index 00000000..00602d9f --- /dev/null +++ b/packages/backend/src/apps/freescout/common/webhook-filters.js @@ -0,0 +1,52 @@ +const webhookFilters = [ + { + value: 'convo.assigned', + label: 'Conversation assigned', + }, + { + value: 'convo.created', + label: 'Conversation created', + }, + { + value: 'convo.deleted', + label: 'Conversation deleted', + }, + { + value: 'convo.deleted_forever', + label: 'Conversation deleted forever', + }, + { + value: 'convo.restored', + label: 'Conversation restored from Deleted folder', + }, + { + value: 'convo.moved', + label: 'Conversation moved', + }, + { + value: 'convo.status', + label: 'Conversation status updated', + }, + { + value: 'convo.customer.reply.created', + label: 'Customer replied', + }, + { + value: 'convo.agent.reply.created', + label: 'Agent replied', + }, + { + value: 'convo.note.created', + label: 'Note added', + }, + { + value: 'customer.created', + label: 'Customer create', + }, + { + value: 'customer.updated', + label: 'Customer update', + }, +]; + +export default webhookFilters; diff --git a/packages/backend/src/apps/freescout/index.js b/packages/backend/src/apps/freescout/index.js new file mode 100644 index 00000000..4044522a --- /dev/null +++ b/packages/backend/src/apps/freescout/index.js @@ -0,0 +1,18 @@ +import defineApp from '../../helpers/define-app.js'; +import addAuthHeader from './common/add-auth-header.js'; +import auth from './auth/index.js'; +import triggers from './triggers/index.js'; +import setBaseUrl from './common/set-base-url.js'; + +export default defineApp({ + name: 'FreeScout', + key: 'freescout', + iconUrl: '{BASE_URL}/apps/freescout/assets/favicon.svg', + supportsConnections: true, + baseUrl: 'https://freescout.net', + primaryColor: '#F5D05E', + authDocUrl: '{DOCS_URL}/apps/freescout/connection', + beforeRequest: [setBaseUrl, addAuthHeader], + auth, + triggers, +}); diff --git a/packages/backend/src/apps/freescout/triggers/index.js b/packages/backend/src/apps/freescout/triggers/index.js new file mode 100644 index 00000000..0a362fd4 --- /dev/null +++ b/packages/backend/src/apps/freescout/triggers/index.js @@ -0,0 +1,3 @@ +import newEvent from './new-event/index.js'; + +export default [newEvent]; diff --git a/packages/backend/src/apps/freescout/triggers/new-event/index.js b/packages/backend/src/apps/freescout/triggers/new-event/index.js new file mode 100644 index 00000000..caae12e9 --- /dev/null +++ b/packages/backend/src/apps/freescout/triggers/new-event/index.js @@ -0,0 +1,61 @@ +import Crypto from 'crypto'; +import isEmpty from 'lodash/isEmpty.js'; +import defineTrigger from '../../../../helpers/define-trigger.js'; +import webhookFilters from '../../common/webhook-filters.js'; + +export default defineTrigger({ + name: 'New event', + key: 'newEvent', + type: 'webhook', + description: 'Triggers when a new event occurs.', + arguments: [ + { + label: 'Event type', + key: 'eventType', + type: 'dropdown', + required: true, + description: 'Pick an event type to receive events for.', + variables: false, + options: webhookFilters, + }, + ], + + async run($) { + const dataItem = { + raw: $.request.body, + meta: { + internalId: Crypto.randomUUID(), + }, + }; + + $.pushTriggerItem(dataItem); + }, + + async testRun($) { + const lastExecutionStep = await $.getLastExecutionStep(); + + if (!isEmpty(lastExecutionStep?.dataOut)) { + $.pushTriggerItem({ + raw: lastExecutionStep.dataOut, + meta: { + internalId: '', + }, + }); + } + }, + + async registerHook($) { + const payload = { + url: $.webhookUrl, + events: [$.step.parameters.eventType], + }; + + const response = await $.http.post('/api/webhooks', payload); + + await $.flow.setRemoteWebhookId(response.data?.id?.toString()); + }, + + async unregisterHook($) { + await $.http.delete(`/api/webhooks/${$.flow.remoteWebhookId}`); + }, +}); diff --git a/packages/backend/src/models/__snapshots__/app.test.js.snap b/packages/backend/src/models/__snapshots__/app.test.js.snap index 0ada15b4..feab84c0 100644 --- a/packages/backend/src/models/__snapshots__/app.test.js.snap +++ b/packages/backend/src/models/__snapshots__/app.test.js.snap @@ -19,6 +19,7 @@ exports[`App model > list should have list of applications keys 1`] = ` "flickr", "flowers-software", "formatter", + "freescout", "ghost", "github", "gitlab", diff --git a/packages/docs/pages/.vitepress/config.js b/packages/docs/pages/.vitepress/config.js index 8011303f..0d0cc410 100644 --- a/packages/docs/pages/.vitepress/config.js +++ b/packages/docs/pages/.vitepress/config.js @@ -159,6 +159,15 @@ export default defineConfig({ { text: 'Connection', link: '/apps/formatter/connection' }, ], }, + { + text: 'Freescout', + collapsible: true, + collapsed: true, + items: [ + { text: 'Triggers', link: '/apps/freescout/triggers' }, + { text: 'Connection', link: '/apps/freescout/connection' }, + ], + }, { text: 'Ghost', collapsible: true, diff --git a/packages/docs/pages/apps/freescout/connection.md b/packages/docs/pages/apps/freescout/connection.md new file mode 100644 index 00000000..b0ae8867 --- /dev/null +++ b/packages/docs/pages/apps/freescout/connection.md @@ -0,0 +1,13 @@ +# FreeScout + +:::info +This page explains the steps you need to follow to set up the FreeScout +connection in Automatisch. If any of the steps are outdated, please let us know! +::: + +1. Go to your FreeScout instance. +2. Go to the Manage > Settings > API & Webhooks page. +3. Generate **API Key**. +4. Copy the **API Key** value to the `API Key` field on Automatisch. +5. Click **Submit** button on Automatisch. +6. Congrats! Start using your new FreeScout connection within the flows. diff --git a/packages/docs/pages/apps/freescout/triggers.md b/packages/docs/pages/apps/freescout/triggers.md new file mode 100644 index 00000000..9bdeda5e --- /dev/null +++ b/packages/docs/pages/apps/freescout/triggers.md @@ -0,0 +1,13 @@ +--- +favicon: /favicons/freescout.svg +items: + - name: New event + desc: Triggers when a new event is created. The supported events are conversation created, conversation assigned, conversation status updated, conversation moved, conversation deleted, conversation deleted forever, conversation restored from deleted folder, customer replied, agent replied, note added, customer created, customer updated. + +--- + + + + diff --git a/packages/docs/pages/public/favicons/freescout.svg b/packages/docs/pages/public/favicons/freescout.svg new file mode 100644 index 00000000..b2fe6412 --- /dev/null +++ b/packages/docs/pages/public/favicons/freescout.svg @@ -0,0 +1,24 @@ + + + + + + +