diff --git a/src/appmixer/hubspot/artifacts/test-flows/test-flow-make-api-call.json b/src/appmixer/hubspot/artifacts/test-flows/test-flow-make-api-call.json new file mode 100644 index 0000000000..bf9dfc91f1 --- /dev/null +++ b/src/appmixer/hubspot/artifacts/test-flows/test-flow-make-api-call.json @@ -0,0 +1,195 @@ +{ + "name": "E2E Hubspot - Make API Call", + "description": "End-to-end test for Hubspot connector - tests MakeApiCall component by performing a GET request to the HubSpot CRM contacts endpoint and validating the response.", + "flow": { + "on-start": { + "type": "appmixer.utils.controls.OnStart", + "x": 64, + "y": 16, + "source": {}, + "version": "1.0.0", + "config": {} + }, + "make-api-call": { + "type": "appmixer.hubspot.crm.MakeApiCall", + "x": 256, + "y": 16, + "version": "1.0.0", + "source": { + "in": { + "on-start": [ + "out" + ] + } + }, + "config": { + "transform": { + "in": { + "on-start": { + "out": { + "type": "json2new", + "modifiers": { + "url": {}, + "method": {} + }, + "lambda": { + "url": "https://api.hubapi.com/crm/v3/objects/contacts", + "method": "GET" + } + } + } + } + } + } + }, + "assert-status": { + "type": "appmixer.utils.test.Assert", + "x": 448, + "y": 16, + "version": "1.0.0", + "source": { + "in": { + "make-api-call": [ + "out" + ] + } + }, + "config": { + "transform": { + "in": { + "make-api-call": { + "out": { + "type": "json2new", + "modifiers": { + "expression": { + "status-var": { + "variable": "$.make-api-call.out.status", + "functions": [] + } + } + }, + "lambda": { + "expression": { + "AND": [ + { + "field": "{{{status-var}}}", + "assertion": "equal", + "expected": "200" + } + ] + } + } + } + } + } + } + } + }, + "assert-body": { + "type": "appmixer.utils.test.Assert", + "x": 448, + "y": 144, + "version": "1.0.0", + "source": { + "in": { + "make-api-call": [ + "out" + ] + } + }, + "config": { + "transform": { + "in": { + "make-api-call": { + "out": { + "type": "json2new", + "modifiers": { + "expression": { + "body-var": { + "variable": "$.make-api-call.out.body", + "functions": [] + } + } + }, + "lambda": { + "expression": { + "AND": [ + { + "field": "{{{body-var}}}", + "assertion": "notEmpty" + } + ] + } + } + } + } + } + } + } + }, + "after-all": { + "type": "appmixer.utils.test.AfterAll", + "x": 640, + "y": 80, + "version": "1.0.0", + "source": { + "in": { + "assert-status": [ + "out" + ], + "assert-body": [ + "out" + ] + } + }, + "config": { + "properties": { + "timeout": 30 + } + } + }, + "process-results": { + "type": "appmixer.utils.test.ProcessE2EResults", + "x": 832, + "y": 80, + "version": "1.0.0", + "source": { + "in": { + "after-all": [ + "out" + ] + } + }, + "config": { + "properties": { + "successStoreId": "64f6f1f9193228000754082f", + "failedStoreId": "64f6f1f0193228000754082e" + }, + "transform": { + "in": { + "after-all": { + "out": { + "type": "json2new", + "modifiers": { + "recipients": {}, + "testCase": {}, + "result": { + "result-var": { + "variable": "$.after-all.out", + "functions": [] + } + } + }, + "lambda": { + "recipients": "jirka@client.io", + "testCase": "E2E Hubspot - Make API Call", + "result": "{{{result-var}}}" + } + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/src/appmixer/hubspot/bundle.json b/src/appmixer/hubspot/bundle.json index 922d9e424d..0a4a9f91c9 100644 --- a/src/appmixer/hubspot/bundle.json +++ b/src/appmixer/hubspot/bundle.json @@ -1,6 +1,6 @@ { "name": "appmixer.hubspot", - "version": "4.4.1", + "version": "4.4.2", "engine": ">=6.0.0", "changelog": { "1.0.0": [ @@ -87,6 +87,9 @@ "Added optional Pipeline filter to NewDeal trigger.", "Added Pipeline and Stage filter inputs to ListDeals action using CRM Search API filterGroups.", "Replaced hard-coded deal stages in UpdateDeal action with dynamic ListPipelineStages cascade." + ], + "4.4.2": [ + "Added MakeApiCall component." ] } -} +} \ No newline at end of file diff --git a/src/appmixer/hubspot/crm/MakeApiCall/MakeApiCall.js b/src/appmixer/hubspot/crm/MakeApiCall/MakeApiCall.js new file mode 100644 index 0000000000..6e768e4591 --- /dev/null +++ b/src/appmixer/hubspot/crm/MakeApiCall/MakeApiCall.js @@ -0,0 +1,43 @@ +'use strict'; + +module.exports = { + + async receive(context) { + + const { url, method, headers, parameters, body } = context.messages.in.content; + + const requestOptions = { + method, + url, + headers: { + 'Authorization': `Bearer ${context.auth.accessToken}`, + 'Content-Type': 'application/json' + } + }; + + if (headers && headers.length > 0) { + headers.forEach(({ key, value }) => { + if (key) requestOptions.headers[key] = value; + }); + } + + if (parameters && parameters.length > 0) { + requestOptions.params = parameters.reduce((acc, { key, value }) => { + if (key) acc[key] = value; + return acc; + }, {}); + } + + if (body) { + requestOptions.data = JSON.parse(body); + } + + const response = await context.httpRequest(requestOptions); + + return context.sendJson({ + status: response.status, + headers: response.headers, + body: response.data + }, 'out'); + } +}; diff --git a/src/appmixer/hubspot/crm/MakeApiCall/component.json b/src/appmixer/hubspot/crm/MakeApiCall/component.json new file mode 100644 index 0000000000..3895f838ee --- /dev/null +++ b/src/appmixer/hubspot/crm/MakeApiCall/component.json @@ -0,0 +1,210 @@ +{ + "name": "appmixer.hubspot.crm.MakeApiCall", + "author": "Appmixer ", + "description": "Performs an arbitrary authorized API call to the HubSpot API.", + "private": false, + "auth": { + "service": "appmixer:hubspot", + "scope": [ + "oauth", + "tickets", + "e-commerce", + "timeline", + "media_bridge.read", + "crm.lists.read", + "crm.lists.write", + "crm.objects.companies.write", + "crm.objects.companies.read", + "crm.objects.contacts.read", + "crm.objects.contacts.write", + "crm.objects.deals.read", + "crm.objects.deals.write", + "crm.objects.owners.read", + "crm.schemas.companies.read", + "crm.schemas.companies.write", + "crm.schemas.contacts.read", + "crm.schemas.contacts.write", + "crm.schemas.custom.read", + "crm.schemas.deals.read", + "crm.schemas.deals.write", + "sales-email-read" + ] + }, + "quota": { + "manager": "appmixer:hubspot", + "resources": "requests", + "scope": { + "userId": "{{userId}}" + } + }, + "inPorts": [ + { + "name": "in", + "schema": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "method": { + "type": "string", + "enum": [ + "GET", + "POST", + "PUT", + "PATCH", + "DELETE" + ], + "default": "GET" + }, + "headers": { + "type": "array", + "items": { + "type": "object", + "properties": { + "key": { + "type": "string" + }, + "value": { + "type": "string" + } + } + } + }, + "parameters": { + "type": "array", + "items": { + "type": "object", + "properties": { + "key": { + "type": "string" + }, + "value": { + "type": "string" + } + } + } + }, + "body": { + "type": "string" + } + }, + "required": [ + "url", + "method" + ] + }, + "inspector": { + "inputs": { + "url": { + "type": "text", + "index": 1, + "label": "API Endpoint URL", + "tooltip": "Enter the full API endpoint URL. For example: https://api.hubapi.com/crm/v3/objects/contacts." + }, + "method": { + "type": "select", + "index": 2, + "label": "HTTP Method", + "defaultValue": "GET", + "tooltip": "Select the HTTP method for the API call.", + "options": [ + { + "label": "GET", + "value": "GET" + }, + { + "label": "POST", + "value": "POST" + }, + { + "label": "PUT", + "value": "PUT" + }, + { + "label": "PATCH", + "value": "PATCH" + }, + { + "label": "DELETE", + "value": "DELETE" + } + ] + }, + "headers": { + "type": "expression", + "index": 3, + "label": "Headers", + "tooltip": "Enter key-value pairs to be sent as additional request headers.", + "levels": [ + "ADD" + ], + "fields": { + "key": { + "type": "text", + "label": "Key", + "index": 1 + }, + "value": { + "type": "text", + "label": "Value", + "index": 2 + } + } + }, + "parameters": { + "type": "expression", + "index": 4, + "label": "Query Parameters", + "tooltip": "Enter key-value pairs to be sent as query parameters.", + "levels": [ + "ADD" + ], + "fields": { + "key": { + "type": "text", + "label": "Key", + "index": 1 + }, + "value": { + "type": "text", + "label": "Value", + "index": 2 + } + } + }, + "body": { + "type": "textarea", + "index": 5, + "label": "Request Body", + "tooltip": "Enter the request body in JSON format (for POST, PUT, PATCH requests)." + } + } + } + } + ], + "outPorts": [ + { + "name": "out", + "options": [ + { + "label": "Status Code", + "value": "status" + }, + { + "label": "Response Headers", + "value": "headers" + }, + { + "label": "Response Body", + "value": "body", + "schema": { + "type": "object", + "properties": {} + } + } + ] + } + ], + "icon": "data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9IjI1MDAiIHZpZXdCb3g9IjYuMjA4NTYyODMgLjY0NDk4ODI0IDI0NC4yNjk0MzcxNyAyNTEuMjQ3MDExNzYiIHdpZHRoPSIyNTAwIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxwYXRoIGQ9Im0xOTEuMzg1IDg1LjY5NHYtMjkuNTA2YTIyLjcyMiAyMi43MjIgMCAwIDAgMTMuMTAxLTIwLjQ4di0uNjc3YzAtMTIuNTQ5LTEwLjE3My0yMi43MjItMjIuNzIxLTIyLjcyMmgtLjY3OGMtMTIuNTQ5IDAtMjIuNzIyIDEwLjE3My0yMi43MjIgMjIuNzIydi42NzdhMjIuNzIyIDIyLjcyMiAwIDAgMCAxMy4xMDEgMjAuNDh2MjkuNTA2YTY0LjM0MiA2NC4zNDIgMCAwIDAgLTMwLjU5NCAxMy40N2wtODAuOTIyLTYzLjAzYy41NzctMi4wODMuODc4LTQuMjI1LjkxMi02LjM3NWEyNS42IDI1LjYgMCAxIDAgLTI1LjYzMyAyNS41NSAyNS4zMjMgMjUuMzIzIDAgMCAwIDEyLjYwNy0zLjQzbDc5LjY4NSA2Mi4wMDdjLTE0LjY1IDIyLjEzMS0xNC4yNTggNTAuOTc0Ljk4NyA3Mi43bC0yNC4yMzYgMjQuMjQzYy0xLjk2LS42MjYtNC0uOTU5LTYuMDU3LS45ODctMTEuNjA3LjAxLTIxLjAxIDkuNDIzLTIxLjAwNyAyMS4wMy4wMDMgMTEuNjA2IDkuNDEyIDIxLjAxNCAyMS4wMTggMjEuMDE3IDExLjYwNy4wMDMgMjEuMDItOS40IDIxLjAzLTIxLjAwN2EyMC43NDcgMjAuNzQ3IDAgMCAwIC0uOTg4LTYuMDU2bDIzLjk3Ni0yMy45ODVjMjEuNDIzIDE2LjQ5MiA1MC44NDYgMTcuOTEzIDczLjc1OSAzLjU2MiAyMi45MTItMTQuMzUyIDM0LjQ3NS00MS40NDYgMjguOTg1LTY3LjkxOC01LjQ5LTI2LjQ3My0yNi44NzMtNDYuNzM0LTUzLjYwMy01MC43OTJtLTkuOTM4IDk3LjA0NGEzMy4xNyAzMy4xNyAwIDEgMSAwLTY2LjMxNmMxNy44NS42MjUgMzIgMTUuMjcyIDMyLjAxIDMzLjEzNC4wMDggMTcuODYtMTQuMTI3IDMyLjUyMi0zMS45NzcgMzMuMTY1IiBmaWxsPSIjZmY3YTU5Ii8+PC9zdmc+" +} \ No newline at end of file