diff --git a/components/cloze/.gitignore b/components/cloze/.gitignore deleted file mode 100644 index ec761ccab7595..0000000000000 --- a/components/cloze/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -*.js -*.mjs -dist \ No newline at end of file diff --git a/components/cloze/actions/create-note/create-note.mjs b/components/cloze/actions/create-note/create-note.mjs new file mode 100644 index 0000000000000..71e6b7059366e --- /dev/null +++ b/components/cloze/actions/create-note/create-note.mjs @@ -0,0 +1,81 @@ +import app from "../../cloze.app.mjs"; + +export default { + key: "cloze-create-note", + name: "Create Note", + description: "Creates a note in Cloze. [See the documentation](https://api.cloze.com/api-docs/#!/Content/post_v1_createcontent).", + version: "0.0.1", + type: "action", + props: { + app, + uniqueId: { + type: "string", + label: "Unique ID", + description: "A unique identifier for this content record. This will often be the unique Id in an external system so that updates can be matched up with the record in Cloze.", + }, + source: { + type: "string", + label: "Source", + description: "The source that this content record originally came from (Eg. `todoist.com`). Must be a valid domain.", + }, + date: { + type: "string", + label: "Date", + description: "When the content should show up in the timeline. Can be a string or a UTC timestamp in ms since the epoch. Eg. `2021-01-01` or `1609459200000`.", + optional: true, + }, + from: { + type: "string", + label: "From", + description: "From address for this content record (the address of the person created the record). This can be an email address, phone number, social handle or app link (Eg. `na16.salesforce.com:006j000000Pkp1d`)", + optional: true, + }, + subject: { + type: "string", + label: "Subject", + description: "Subject of the communication record.", + optional: true, + }, + body: { + type: "string", + label: "Body", + description: "Body text of the communication record.", + }, + additionalData: { + type: "object", + label: "Additional Data", + description: "Additional details for the note in JSON format. [See the documentation](https://api.cloze.com/api-docs/#!/Content/post_v1_createcontent).", + optional: true, + }, + }, + async run({ $ }) { + const { + app, + uniqueId, + date, + from, + source, + subject, + body, + additionalData, + } = this; + + const response = await app.addContentRecord({ + $, + data: { + uniqueid: uniqueId, + date, + style: "note", + from, + source, + subject, + body, + ...additionalData, + }, + }); + + $.export("$summary", "Successfully created note."); + + return response; + }, +}; diff --git a/components/cloze/actions/create-update-company/create-update-company.mjs b/components/cloze/actions/create-update-company/create-update-company.mjs new file mode 100644 index 0000000000000..06ed206a4bdfd --- /dev/null +++ b/components/cloze/actions/create-update-company/create-update-company.mjs @@ -0,0 +1,148 @@ +import app from "../../cloze.app.mjs"; +import utils from "../../common/utils.mjs"; + +export default { + key: "cloze-create-update-company", + name: "Create Or Update Company", + description: "Create a new company or enhance an existing company within Cloze. Companies can be created with just a domain name or both a name and another unique identifier such as a phone number and email address. [See the documentation](https://api.cloze.com/api-docs/#!/Relations_-_Companies/post_v1_companies_create).", + version: "0.0.1", + type: "action", + props: { + app, + name: { + type: "string", + label: "Company Name", + description: "The name of the company.", + optional: true, + }, + emails: { + type: "string[]", + label: "Emails", + description: "The emails of the company. Each email should be a JSON object with `value` key. [See the documentation](https://api.cloze.com/api-docs/#!/Relations_-_Companies/post_v1_companies_create).", + optional: true, + }, + phones: { + type: "string[]", + label: "Phones", + description: "The phones of the company. Each phone should be a JSON object with `value` key. [See the documentation](https://api.cloze.com/api-docs/#!/Relations_-_Companies/post_v1_companies_create).", + optional: true, + }, + domains: { + type: "string[]", + label: "Domains", + description: "The domains of the company.", + optional: true, + }, + segment: { + type: "string", + label: "Segment", + description: "The segment of the company.", + optional: true, + options: [ + "customer", + "partner", + "supplier", + "investor", + "advisor", + "competitor", + "custom1", + "custom2", + "custom3", + "custom4", + "custom5", + "coworker", + "family", + "friend", + "network", + "personal1", + "personal2", + ], + }, + step: { + type: "string", + label: "Step", + description: "Unique Id of Next Step", + optional: true, + }, + stage: { + type: "string", + label: "Stage", + description: "The stage of the company.", + optional: true, + options: [ + { + label: "Lead Stage", + value: "lead", + }, + { + label: "Potential Stage", + value: "future", + }, + { + label: "Active Stage", + value: "current", + }, + { + label: "Inactive Stage", + value: "past", + }, + { + label: "Lost Stage", + value: "out", + }, + ], + }, + assignTo: { + type: "string", + label: "Assign To", + description: "Assign this company to this team member.", + optional: true, + }, + additionalData: { + type: "object", + label: "Additional Data", + description: "Additional details for the company in JSON format. [See the documentation](https://api.cloze.com/api-docs/#!/Relations_-_Companies/post_v1_companies_create).", + optional: true, + }, + }, + methods: { + createCompany(args = {}) { + return this.app.post({ + path: "/companies/create", + ...args, + }); + }, + }, + async run({ $ }) { + const { + createCompany, + name, + emails, + phones, + domains, + segment, + step, + stage, + assignTo, + additionalData, + } = this; + + const response = await createCompany({ + $, + data: { + name, + emails: utils.parseArray(emails), + phones: utils.parseArray(phones), + domains, + segment, + step, + stage, + assignTo, + ...additionalData, + }, + }); + + $.export("$summary", "Successfully created/updated company."); + return response; + }, +}; diff --git a/components/cloze/actions/create-update-project/create-update-project.mjs b/components/cloze/actions/create-update-project/create-update-project.mjs new file mode 100644 index 0000000000000..ab25a8ea2cc7c --- /dev/null +++ b/components/cloze/actions/create-update-project/create-update-project.mjs @@ -0,0 +1,115 @@ +import app from "../../cloze.app.mjs"; +import utils from "../../common/utils.mjs"; + +export default { + key: "cloze-create-update-project", + name: "Create Or Update Project", + description: "Create a new project or merge updates into an existing one. [See the documentation](https://api.cloze.com/api-docs/#!/Relations_-_Projects/post_v1_projects_create).", + version: "0.0.1", + type: "action", + props: { + app, + name: { + type: "string", + label: "Project Name", + description: "The name of the project.", + }, + appLinks: { + type: "string[]", + label: "App Links", + description: "The app links of the project. Each app link should be a JSON object with at least `source` and `uniqueid` keys. [See the documentation](https://api.cloze.com/api-docs/#!/Relations_-_Projects/post_v1_projects_create).", + optional: true, + default: [ + JSON.stringify({ + source: "na16.salesforce.com", + uniqueid: "sdf234v", + }), + ], + }, + summary: { + type: "string", + label: "Project Summary", + description: "The summary of the project.", + optional: true, + }, + stage: { + type: "string", + label: "Stage", + description: "The stage of the project.", + optional: true, + options: [ + { + label: "Potential Stage", + value: "future", + }, + { + label: "Active Stage", + value: "current", + }, + { + label: "Won or Done stage", + value: "won", + }, + { + label: "Lost Stage", + value: "lost", + }, + ], + }, + segment: { + type: "string", + label: "Segment", + description: "The segment of the project.", + optional: true, + options: [ + "project", + "project1", + "project2", + "project3", + "project4", + "project5", + ], + }, + additionalData: { + type: "object", + label: "Additional Data", + description: "Additional details for the project in JSON format. [See the documentation](https://api.cloze.com/api-docs/#!/Relations_-_Projects/post_v1_projects_create).", + optional: true, + }, + }, + methods: { + createProject(args = {}) { + return this.app.post({ + path: "/projects/create", + ...args, + }); + }, + }, + async run({ $ }) { + const { + createProject, + name, + appLinks, + summary, + stage, + segment, + additionalData, + } = this; + + const response = await createProject({ + $, + data: { + name, + appLinks: utils.parseArray(appLinks), + summary, + stage, + segment, + ...additionalData, + }, + }); + + $.export("$summary", "Successfully created/updated project."); + + return response; + }, +}; diff --git a/components/cloze/app/cloze.app.ts b/components/cloze/app/cloze.app.ts deleted file mode 100644 index d510120638d18..0000000000000 --- a/components/cloze/app/cloze.app.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { defineApp } from "@pipedream/types"; - -export default defineApp({ - type: "app", - app: "cloze", - propDefinitions: {}, - methods: { - // this.$auth contains connected account data - authKeys() { - console.log(Object.keys(this.$auth)); - }, - }, -}); \ No newline at end of file diff --git a/components/cloze/cloze.app.mjs b/components/cloze/cloze.app.mjs new file mode 100644 index 0000000000000..cb6a97dfde6d8 --- /dev/null +++ b/components/cloze/cloze.app.mjs @@ -0,0 +1,45 @@ +import { axios } from "@pipedream/platform"; +import constants from "./common/constants.mjs"; + +export default { + type: "app", + app: "cloze", + methods: { + getUrl(path) { + return `${constants.BASE_URL}${constants.VERSION_PATH}${path}`; + }, + getHeaders(headers) { + return { + Authorization: `Bearer ${this.$auth.oauth_access_token}`, + ...headers, + }; + }, + async _makeRequest({ + $ = this, path, headers, ...args + } = {}) { + const response = await axios($, { + ...args, + url: this.getUrl(path), + headers: this.getHeaders(headers), + }); + + if (response.errorcode) { + throw new Error(JSON.stringify(response, null, 2)); + } + + return response; + }, + post(args = {}) { + return this._makeRequest({ + method: "POST", + ...args, + }); + }, + addContentRecord(args = {}) { + return this.post({ + path: "/createcontent", + ...args, + }); + }, + }, +}; diff --git a/components/cloze/common/constants.mjs b/components/cloze/common/constants.mjs new file mode 100644 index 0000000000000..24ae1690ba99f --- /dev/null +++ b/components/cloze/common/constants.mjs @@ -0,0 +1,9 @@ +const BASE_URL = "https://api.cloze.com"; +const VERSION_PATH = "/v1"; +const WEBHOOK_ID = "webhookId"; + +export default { + BASE_URL, + VERSION_PATH, + WEBHOOK_ID, +}; diff --git a/components/cloze/common/utils.mjs b/components/cloze/common/utils.mjs new file mode 100644 index 0000000000000..650af25ed3a8e --- /dev/null +++ b/components/cloze/common/utils.mjs @@ -0,0 +1,51 @@ +import { ConfigurationError } from "@pipedream/platform"; + +const parseJson = (input) => { + const parse = (value) => { + if (typeof(value) === "string") { + try { + return parseJson(JSON.parse(value)); + } catch (e) { + return value; + } + } else if (typeof(value) === "object" && value !== null) { + return Object.entries(value) + .reduce((acc, [ + key, + val, + ]) => Object.assign(acc, { + [key]: parse(val), + }), {}); + } + return value; + }; + + return parse(input); +}; + +function parseArray(value) { + try { + if (!value) { + return; + } + + if (Array.isArray(value)) { + return value; + } + + const parsedValue = JSON.parse(value); + + if (!Array.isArray(parsedValue)) { + throw new Error("Not an array"); + } + + return parsedValue; + + } catch (e) { + throw new ConfigurationError("Make sure the custom expression contains a valid array object"); + } +} + +export default { + parseArray: (value) => parseArray(value)?.map(parseJson), +}; diff --git a/components/cloze/package.json b/components/cloze/package.json index 82ec92999670b..6c94bb0443f38 100644 --- a/components/cloze/package.json +++ b/components/cloze/package.json @@ -1,16 +1,18 @@ { "name": "@pipedream/cloze", - "version": "0.0.1", + "version": "0.1.0", "description": "Pipedream Cloze Components", - "main": "dist/app/cloze.app.mjs", + "main": "cloze.app.mjs", "keywords": [ "pipedream", "cloze" ], - "files": ["dist"], "homepage": "https://pipedream.com/apps/cloze", "author": "Pipedream (https://pipedream.com/)", "publishConfig": { "access": "public" + }, + "dependencies": { + "@pipedream/platform": "3.0.3" } -} \ No newline at end of file +} diff --git a/components/cloze/sources/common/events.mjs b/components/cloze/sources/common/events.mjs new file mode 100644 index 0000000000000..4b7a4d3f4a0c8 --- /dev/null +++ b/components/cloze/sources/common/events.mjs @@ -0,0 +1,8 @@ +export default { + PERSON_CHANGE: "person.change", + PROJECT_CHANGE: "project.change", + COMPANY_CHANGE: "company.change", + PERSON_AUDIT_CHANGE: "person.audit.change", + PROJECT_AUDIT_CHANGE: "project.audit.change", + COMPANY_AUDIT_CHANGE: "company.audit.change", +}; diff --git a/components/cloze/sources/common/webhook.mjs b/components/cloze/sources/common/webhook.mjs new file mode 100644 index 0000000000000..5ff0146bcff6f --- /dev/null +++ b/components/cloze/sources/common/webhook.mjs @@ -0,0 +1,118 @@ +import { ConfigurationError } from "@pipedream/platform"; +import app from "../../cloze.app.mjs"; +import constants from "../../common/constants.mjs"; + +export default { + props: { + app, + db: "$.service.db", + http: { + type: "$.interface.http", + customResponse: true, + }, + scope: { + type: "string", + label: "Scope", + description: "Scope of subscription, changes to the user's local person, project, and company may be monitored, or team relations may be monitored, or team hierarchies can be monitored. Can be `local`, `team`, `hierarchy:/X/Y/Z` or `hierarchy:/X/Y/Z/*`", + options: [ + "local", + "team", + ], + default: "local", + }, + }, + hooks: { + async activate() { + const { + createWebhook, + setWebhookId, + http: { endpoint: targetUrl }, + getEventName, + scope, + } = this; + + const response = + await createWebhook({ + data: { + event: getEventName(), + target_url: targetUrl, + scope, + }, + }); + + setWebhookId(response.uniqueid); + }, + async deactivate() { + const { + getWebhookId, + deleteWebhook, + getEventName, + } = this; + + const webhookId = getWebhookId(); + if (webhookId) { + await deleteWebhook({ + data: { + uniqueid: webhookId, + event: getEventName(), + }, + }); + } + }, + }, + methods: { + setWebhookId(value) { + this.db.set(constants.WEBHOOK_ID, value); + }, + getWebhookId() { + return this.db.get(constants.WEBHOOK_ID); + }, + generateMeta() { + throw new ConfigurationError("generateMeta is not implemented"); + }, + getEventName() { + throw new ConfigurationError("getEventName is not implemented"); + }, + processResource(events) { + events.forEach((event) => { + this.$emit(event, this.generateMeta(event)); + }); + }, + createWebhook(args = {}) { + return this.app.post({ + debug: true, + path: "/subscribe", + ...args, + }); + }, + deleteWebhook(args = {}) { + return this.app.post({ + debug: true, + path: "/unsubscribe", + ...args, + }); + }, + }, + async run({ + body, headers, + }) { + const { + getWebhookId, + http, + } = this; + + if (headers["x-cloze-subscription-id"] !== getWebhookId()) { + return console.log("Webhook ID does not match with Cloze subscription ID"); + } + + http.respond({ + status: 200, + body: "OK", + headers: { + "content-type": "text/plain", + }, + }); + + this.processResource(body); + }, +}; diff --git a/components/cloze/sources/company-change-instant/company-change-instant.mjs b/components/cloze/sources/company-change-instant/company-change-instant.mjs new file mode 100644 index 0000000000000..416e3fa5c8f4b --- /dev/null +++ b/components/cloze/sources/company-change-instant/company-change-instant.mjs @@ -0,0 +1,27 @@ +import common from "../common/webhook.mjs"; +import events from "../common/events.mjs"; +import sampleEmit from "./test-event.mjs"; + +export default { + ...common, + key: "cloze-company-change-instant", + name: "Company Change (Instant)", + description: "Emit new event when significant changes regarding a company are detected. [See the documentation](https://api.cloze.com/api-docs/#!/Webhooks/post_v1_subscribe).", + version: "0.0.1", + type: "source", + dedupe: "unique", + methods: { + ...common.methods, + getEventName() { + return events.COMPANY_CHANGE; + }, + generateMeta(event) { + return { + id: event?.company.syncKey, + summary: "New Company Change", + ts: event?.company.lastChanged, + }; + }, + }, + sampleEmit, +}; diff --git a/components/cloze/sources/company-change-instant/test-event.mjs b/components/cloze/sources/company-change-instant/test-event.mjs new file mode 100644 index 0000000000000..1342a328c51bd --- /dev/null +++ b/components/cloze/sources/company-change-instant/test-event.mjs @@ -0,0 +1,22 @@ +export default { + "company": { + "syncKey": "nL8WlFFbFuVvlZfLWFEH3tSsTYZqUQ50qNyfMUi8zFA", + "name": "test 1 upd 3", + "visibility": "visible", + "views": [ + "my", + "defined" + ], + "firstSeen": 1731443475576, + "lastChanged": 1731453402556, + "domains": [ + "test.com" + ], + "segment": "none", + "stage": "none", + "step": "none", + "assignee": "test@test.com" + }, + "changes": {}, + "event": "company.change" +}; diff --git a/components/cloze/sources/person-change-instant/person-change-instant.mjs b/components/cloze/sources/person-change-instant/person-change-instant.mjs new file mode 100644 index 0000000000000..7f7d88d0f0f05 --- /dev/null +++ b/components/cloze/sources/person-change-instant/person-change-instant.mjs @@ -0,0 +1,27 @@ +import common from "../common/webhook.mjs"; +import events from "../common/events.mjs"; +import sampleEmit from "./test-event.mjs"; + +export default { + ...common, + key: "cloze-person-change-instant", + name: "Person Change (Instant)", + description: "Emit new event when significant changes happen to a person. [See the documentation](https://api.cloze.com/api-docs/#!/Webhooks/post_v1_subscribe).", + version: "0.0.1", + type: "source", + dedupe: "unique", + methods: { + ...common.methods, + getEventName() { + return events.PERSON_CHANGE; + }, + generateMeta(event) { + return { + id: event?.person.syncKey, + summary: "New Person Change", + ts: event?.person.lastChanged, + }; + }, + }, + sampleEmit, +}; diff --git a/components/cloze/sources/person-change-instant/test-event.mjs b/components/cloze/sources/person-change-instant/test-event.mjs new file mode 100644 index 0000000000000..97b22de9bee08 --- /dev/null +++ b/components/cloze/sources/person-change-instant/test-event.mjs @@ -0,0 +1,22 @@ +export default { + "person": { + "syncKey": "nL8WlasdFbFuVvlZfLWFEH3tSsTYZqUQ50qNyfMUi8zFA", + "name": "test 1 upd 3", + "visibility": "visible", + "views": [ + "my", + "defined" + ], + "firstSeen": 1731443475576, + "lastChanged": 1731453402556, + "domains": [ + "test.com" + ], + "segment": "none", + "stage": "none", + "step": "none", + "assignee": "test@test.com", + }, + "changes": {}, + "event": "person.change" +}; diff --git a/components/cloze/sources/project-change-instant/project-change-instant.mjs b/components/cloze/sources/project-change-instant/project-change-instant.mjs new file mode 100644 index 0000000000000..d787091b2e2b9 --- /dev/null +++ b/components/cloze/sources/project-change-instant/project-change-instant.mjs @@ -0,0 +1,27 @@ +import common from "../common/webhook.mjs"; +import events from "../common/events.mjs"; +import sampleEmit from "./test-event.mjs"; + +export default { + ...common, + key: "cloze-project-change-instant", + name: "Project Change (Instant)", + description: "Emit new event when a significant change occurs in a project. [See the documentation](https://api.cloze.com/api-docs/#!/Webhooks/post_v1_subscribe).", + version: "0.0.1", + type: "source", + dedupe: "unique", + methods: { + ...common.methods, + getEventName() { + return events.PROJECT_CHANGE; + }, + generateMeta(event) { + return { + id: event?.project.syncKey, + summary: "New Project Change", + ts: event?.project.lastChanged, + }; + }, + }, + sampleEmit, +}; diff --git a/components/cloze/sources/project-change-instant/test-event.mjs b/components/cloze/sources/project-change-instant/test-event.mjs new file mode 100644 index 0000000000000..579c6f86eedde --- /dev/null +++ b/components/cloze/sources/project-change-instant/test-event.mjs @@ -0,0 +1,22 @@ +export default { + "project": { + "syncKey": "nL8WlFFbFuVvlZfLWFEH3tSsTYZqUQ50qNyfMUi8zFA", + "name": "test 1 upd 3", + "visibility": "visible", + "views": [ + "my", + "defined" + ], + "firstSeen": 1731443475576, + "lastChanged": 1731453402556, + "domains": [ + "test.com" + ], + "segment": "none", + "stage": "none", + "step": "none", + "assignee": "test@test.com", + }, + "changes": {}, + "event": "project.change" +}; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 26f7b569291b0..3bf190a6811bd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1955,7 +1955,10 @@ importers: specifiers: {} components/cloze: - specifiers: {} + specifiers: + '@pipedream/platform': 3.0.3 + dependencies: + '@pipedream/platform': 3.0.3 components/clubworx: specifiers: {}