diff --git a/components/charthop/actions/create-employee/create-employee.mjs b/components/charthop/actions/create-employee/create-employee.mjs new file mode 100644 index 0000000000000..cecf98acb3cb5 --- /dev/null +++ b/components/charthop/actions/create-employee/create-employee.mjs @@ -0,0 +1,47 @@ +import charthop from "../../charthop.app.mjs"; +import { parseObject } from "../../common/utils.mjs"; + +export default { + key: "charthop-create-employee", + name: "Create Employee", + description: "Adds a new employee to the system. [See the documentation](https://api.charthop.com/swagger#/person/createPerson)", + version: "0.0.1", + type: "action", + props: { + charthop, + orgId: { + propDefinition: [ + charthop, + "orgId", + ], + }, + name: { + type: "string", + label: "Name", + description: "Name of the employee", + }, + additionalProperties: { + type: "object", + label: "Additional Properties", + description: "Additional properties to add to the employee", + optional: true, + }, + }, + async run({ $ }) { + const additionalProperties = this.additionalProperties + ? parseObject(this.additionalProperties) + : {}; + + const response = await this.charthop.createPerson({ + $, + orgId: this.orgId, + data: { + name: this.name, + ...additionalProperties, + }, + }); + + $.export("$summary", `Successfully created employee with ID: ${response.id}`); + return response; + }, +}; diff --git a/components/charthop/actions/search-organization/search-organization.mjs b/components/charthop/actions/search-organization/search-organization.mjs new file mode 100644 index 0000000000000..e5758c381121f --- /dev/null +++ b/components/charthop/actions/search-organization/search-organization.mjs @@ -0,0 +1,36 @@ +import charthop from "../../charthop.app.mjs"; + +export default { + key: "charthop-search-organization", + name: "Search Organization", + description: "Return people, job, group, and field data for a particular org that match a provided search string. [See the documentation](https://api.charthop.com/swagger#/search/searchOrgData)", + version: "0.0.1", + type: "action", + props: { + charthop, + orgId: { + propDefinition: [ + charthop, + "orgId", + ], + }, + q: { + type: "string", + label: "Query", + description: "The search query", + }, + }, + async run({ $ }) { + const response = await this.charthop.searchOrganization({ + $, + orgId: this.orgId, + params: { + q: this.q, + includeFormer: true, + }, + }); + + $.export("$summary", "Successfully completed search query"); + return response; + }, +}; diff --git a/components/charthop/actions/update-employee-details/update-employee-details.mjs b/components/charthop/actions/update-employee-details/update-employee-details.mjs new file mode 100644 index 0000000000000..356e17917b530 --- /dev/null +++ b/components/charthop/actions/update-employee-details/update-employee-details.mjs @@ -0,0 +1,83 @@ +import charthop from "../../charthop.app.mjs"; + +export default { + key: "charthop-update-employee-details", + name: "Update Employee Details", + description: "Updates an existing employee's details. [See the documentation](https://api.charthop.com/swagger#/user/updateUser)", + version: "0.0.1", + type: "action", + props: { + charthop, + orgId: { + propDefinition: [ + charthop, + "orgId", + ], + }, + employeeId: { + propDefinition: [ + charthop, + "employeeId", + (c) => ({ + orgId: c.orgId, + }), + ], + reloadProps: true, + }, + }, + async additionalProps() { + const props = {}; + if (!this.employeeId || !this.orgId) { + return props; + } + + const employee = await this.charthop.getPerson({ + orgId: this.orgId, + personId: this.employeeId, + }); + + for (const [ + key, + value, + ] of Object.entries(employee)) { + if (key === "id") { + continue; + } + props[key] = { + type: "string", + label: `${key}`, + default: key === "name" + ? (`${value?.first} ${value?.last}`).trim() + : `${value}`, + }; + } + + return props; + }, + async run({ $ }) { + const { + charthop, + orgId, + employeeId, + ...fields + } = this; + + await charthop.updatePerson({ + $, + orgId, + personId: employeeId, + data: { + ...fields, + }, + }); + + const response = await charthop.getPerson({ + $, + orgId, + personId: employeeId, + }); + + $.export("$summary", `Successfully updated employee with ID ${employeeId}`); + return response; + }, +}; diff --git a/components/charthop/charthop.app.mjs b/components/charthop/charthop.app.mjs index 746957318d73f..7d3993b5f2343 100644 --- a/components/charthop/charthop.app.mjs +++ b/components/charthop/charthop.app.mjs @@ -1,11 +1,214 @@ +import { axios } from "@pipedream/platform"; + export default { type: "app", app: "charthop", - propDefinitions: {}, + propDefinitions: { + orgId: { + type: "string", + label: "Organization ID", + description: "The identifier of an organization", + async options({ prevContext }) { + const params = prevContext?.from + ? { + from: prevContext.from, + } + : {}; + const { data } = await this.listOrgs({ + params, + }); + const options = data?.map(({ + id: value, name: label, + }) => ({ + value, + label, + })) || []; + return { + options, + context: { + from: options[options.length - 1].id, + }, + }; + }, + }, + employeeId: { + type: "string", + label: "Employee ID", + description: "The identifier of an employee", + async options({ + orgId, prevContext, + }) { + const params = { + includeAll: true, + }; + if (prevContext?.from) { + params.from = prevContext.from; + } + const { data } = await this.listPersons({ + orgId, + params: { + ...params, + }, + }); + const options = data?.map(({ + id: value, name, + }) => ({ + value, + label: (`${name?.first} ${name?.last}`).trim(), + })) || []; + return { + options, + context: { + from: data[data.length - 1].id, + }, + }; + }, + }, + groupTypeId: { + type: "string", + label: "Group Type ID", + description: "The identifier of a group type", + async options({ + orgId, prevContext, + }) { + const params = { + includeAll: true, + }; + if (prevContext?.from) { + params.from = prevContext.from; + } + const { data } = await this.listGroupTypes({ + orgId, + params: { + ...params, + }, + }); + const options = data?.map(({ + id: value, name: label, + }) => ({ + value, + label, + })) || []; + return { + options, + context: { + from: data[data.length - 1].id, + }, + }; + }, + }, + }, methods: { - // this.$auth contains connected account data - authKeys() { - console.log(Object.keys(this.$auth)); + _baseUrl() { + return "https://api.charthop.com"; + }, + _makeRequest({ + $ = this, + path, + ...otherOpts + }) { + return axios($, { + ...otherOpts, + url: `${this._baseUrl()}${path}`, + headers: { + "Authorization": `Bearer ${this.$auth.api_token}`, + "Content-Type": "application/json", + }, + }); + }, + listOrgs(opts = {}) { + return this._makeRequest({ + path: "/v1/org", + ...opts, + }); + }, + listPersons({ + orgId, ...opts + }) { + return this._makeRequest({ + path: `/v2/org/${orgId}/person`, + ...opts, + }); + }, + listJobs({ + orgId, ...opts + }) { + return this._makeRequest({ + path: `/v2/org/${orgId}/job`, + ...opts, + }); + }, + listGroupTypes({ + orgId, ...opts + }) { + return this._makeRequest({ + path: `/v1/org/${orgId}/group-type`, + ...opts, + }); + }, + listGroups({ + orgId, type, ...opts + }) { + return this._makeRequest({ + path: `/v2/org/${orgId}/group/${type}`, + ...opts, + }); + }, + getPerson({ + orgId, personId, ...opts + }) { + return this._makeRequest({ + path: `/v2/org/${orgId}/person/${personId}`, + ...opts, + }); + }, + createPerson({ + orgId, ...opts + }) { + return this._makeRequest({ + method: "POST", + path: `/v2/org/${orgId}/person`, + ...opts, + }); + }, + updatePerson({ + orgId, personId, ...opts + }) { + return this._makeRequest({ + method: "PATCH", + path: `/v2/org/${orgId}/person/${personId}`, + ...opts, + }); + }, + searchOrganization({ + orgId, ...opts + }) { + return this._makeRequest({ + path: `/v1/org/${orgId}/search`, + ...opts, + }); + }, + async *paginate({ + resourceFn, + args, + max, + }) { + let count = 0; + do { + const { + data, next, + } = await resourceFn(args); + for (const item of data) { + yield item; + if (max && ++count >= max) { + return; + } + args.params = { + ...args.params, + from: next, + }; + } + } while (args.params?.from); }, }, }; diff --git a/components/charthop/common/utils.mjs b/components/charthop/common/utils.mjs new file mode 100644 index 0000000000000..6489741fadd51 --- /dev/null +++ b/components/charthop/common/utils.mjs @@ -0,0 +1,17 @@ +function parseObject(obj) { + if (!obj) { + return undefined; + } + if (typeof obj === "string") { + try { + return JSON.parse(obj); + } catch { + return obj; + } + } + return obj; +} + +export { + parseObject, +}; diff --git a/components/charthop/package.json b/components/charthop/package.json index f4cef0f7edc4e..5c52feeacd438 100644 --- a/components/charthop/package.json +++ b/components/charthop/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/charthop", - "version": "0.0.1", + "version": "0.1.0", "description": "Pipedream ChartHop Components", "main": "charthop.app.mjs", "keywords": [ @@ -11,5 +11,8 @@ "author": "Pipedream (https://pipedream.com/)", "publishConfig": { "access": "public" + }, + "dependencies": { + "@pipedream/platform": "^3.0.3" } -} \ No newline at end of file +} diff --git a/components/charthop/sources/common/base.mjs b/components/charthop/sources/common/base.mjs new file mode 100644 index 0000000000000..5969897db2045 --- /dev/null +++ b/components/charthop/sources/common/base.mjs @@ -0,0 +1,93 @@ +import charthop from "../../charthop.app.mjs"; +import { + ConfigurationError, DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, +} from "@pipedream/platform"; + +export default { + props: { + charthop, + db: "$.service.db", + timer: { + type: "$.interface.timer", + default: { + intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, + }, + }, + orgId: { + propDefinition: [ + charthop, + "orgId", + ], + }, + }, + methods: { + _getLastId() { + return this.db.get("lastId"); + }, + _setLastId(lastId) { + this.db.set("lastId", lastId); + }, + generateMeta(item) { + return { + id: item.id, + summary: this.getSummary(item), + ts: Date.now(), + }; + }, + emitEvents(items) { + items.forEach((item) => { + const meta = this.generateMeta(item); + this.$emit(item, meta); + }); + }, + getArgs() { + return { + orgId: this.orgId, + }; + }, + async getResults() { + const lastId = this._getLastId(); + const resourceFn = this.getResourceFn(); + const args = this.getArgs(); + + if (lastId) { + args.params = { + ...args.params, + from: lastId, + }; + } + + const items = this.charthop.paginate({ + resourceFn, + args, + }); + + const results = []; + for await (const item of items) { + results.push(item); + } + + if (results.length) { + this._setLastId(results[results.length - 1].id); + } + + return results; + }, + getResourceFn() { + throw new ConfigurationError("getResourceFn is not implemented"); + }, + getSummary() { + throw new ConfigurationError("getSummary is not implemented"); + }, + }, + hooks: { + async deploy() { + const results = await this.getResults(); + this.emitEvents(results.slice(-25)); + }, + }, + async run() { + const results = await this.getResults(); + this.emitEvents(results); + }, +}; diff --git a/components/charthop/sources/new-employee-created/new-employee-created.mjs b/components/charthop/sources/new-employee-created/new-employee-created.mjs new file mode 100644 index 0000000000000..95a5ec6ddf9dc --- /dev/null +++ b/components/charthop/sources/new-employee-created/new-employee-created.mjs @@ -0,0 +1,28 @@ +import common from "../common/base.mjs"; + +export default { + ...common, + key: "charthop-new-employee-created", + name: "New Employee Created", + description: "Emit new event when a new employee is added to the organization. [See the documentation](https://api.charthop.com/swagger#/person/findPersons)", + version: "0.0.1", + type: "source", + dedupe: "unique", + methods: { + ...common.methods, + getResourceFn() { + return this.charthop.listPersons; + }, + getArgs() { + return { + orgId: this.orgId, + params: { + includeAll: true, + }, + }; + }, + getSummary(item) { + return `New Employee: ${item.id}`; + }, + }, +}; diff --git a/components/charthop/sources/new-group-created/new-group-created.mjs b/components/charthop/sources/new-group-created/new-group-created.mjs new file mode 100644 index 0000000000000..3efac1ee46655 --- /dev/null +++ b/components/charthop/sources/new-group-created/new-group-created.mjs @@ -0,0 +1,38 @@ +import common from "../common/base.mjs"; + +export default { + ...common, + key: "charthop-new-group-created", + name: "New Group Created", + description: "Emit new event when a new group is added to the organization. [See the documentation](https://api.charthop.com/swagger#/group/findGroups)", + version: "0.0.1", + type: "source", + dedupe: "unique", + props: { + ...common.props, + groupTypeId: { + propDefinition: [ + common.props.charthop, + "groupTypeId", + (c) => ({ + orgId: c.orgId, + }), + ], + }, + }, + methods: { + ...common.methods, + getResourceFn() { + return this.charthop.listGroups; + }, + getArgs() { + return { + orgId: this.orgId, + type: this.groupTypeId, + }; + }, + getSummary(item) { + return `New Group: ${item.id}`; + }, + }, +}; diff --git a/components/charthop/sources/new-job-created/new-job-created.mjs b/components/charthop/sources/new-job-created/new-job-created.mjs new file mode 100644 index 0000000000000..d99bc1e3e8c89 --- /dev/null +++ b/components/charthop/sources/new-job-created/new-job-created.mjs @@ -0,0 +1,20 @@ +import common from "../common/base.mjs"; + +export default { + ...common, + key: "charthop-new-job-created", + name: "New Job Created", + description: "Emit new event when a new job is added to the organization. [See the documentation](https://api.charthop.com/swagger#/job/findJobs)", + version: "0.0.1", + type: "source", + dedupe: "unique", + methods: { + ...common.methods, + getResourceFn() { + return this.charthop.listJobs; + }, + getSummary(item) { + return `New Job: ${item.id}`; + }, + }, +}; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3183cb0fdb21a..7897d0b82a193 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1713,7 +1713,11 @@ importers: specifier: ^3.0.3 version: 3.0.3 - components/charthop: {} + components/charthop: + dependencies: + '@pipedream/platform': + specifier: ^3.0.3 + version: 3.0.3 components/chartmogul: dependencies: @@ -9279,8 +9283,7 @@ importers: specifier: 3.0.1 version: 3.0.1 - components/seen: - specifiers: {} + components/seen: {} components/segment: dependencies: @@ -14103,12 +14106,12 @@ packages: '@dabh/diagnostics@2.0.3': resolution: {integrity: sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==} - '@definitelytyped/header-parser@0.2.16': - resolution: {integrity: sha512-UFsgPft5bhZn07UNGz/9ck4AhdKgLFEOmi2DNr7gXcGL89zbe3u5oVafKUT8j1HOtSBjT8ZEQsXHKlbq+wwF/Q==} + '@definitelytyped/header-parser@0.2.17': + resolution: {integrity: sha512-U0juKFkTOcbkSfO83WSzMEJHYDwoBFiq0tf/JszulL3+7UoSiqunpGmxXS54bm3eGqy7GWjV8AqPQHdeoEaWBQ==} engines: {node: '>=18.18.0'} - '@definitelytyped/typescript-versions@0.1.6': - resolution: {integrity: sha512-gQpXFteIKrOw4ldmBZQfBrD3WobaIG1SwOr/3alXWkcYbkOWa2NRxQbiaYQ2IvYTGaZK26miJw0UOAFiuIs4gA==} + '@definitelytyped/typescript-versions@0.1.7': + resolution: {integrity: sha512-sBzBi1SBn79OkSr8V0H+FzR7QumHk23syPyRxod/VRBrSkgN9rCliIe+nqLoWRAKN8EeKbp00ketnJNLZhucdA==} engines: {node: '>=18.18.0'} '@definitelytyped/utils@0.1.8': @@ -22145,6 +22148,7 @@ packages: lodash.get@4.4.2: resolution: {integrity: sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==} + deprecated: This package is deprecated. Use the optional chaining (?.) operator instead. lodash.has@4.5.2: resolution: {integrity: sha512-rnYUdIo6xRCJnQmbVFEwcxF144erlD+M3YcJUVesflU9paQaE8p+fJDcIQrlMYbxoANFL+AB9hZrzSBBk5PL+g==} @@ -29555,13 +29559,13 @@ snapshots: enabled: 2.0.0 kuler: 2.0.0 - '@definitelytyped/header-parser@0.2.16': + '@definitelytyped/header-parser@0.2.17': dependencies: - '@definitelytyped/typescript-versions': 0.1.6 + '@definitelytyped/typescript-versions': 0.1.7 '@definitelytyped/utils': 0.1.8 semver: 7.6.3 - '@definitelytyped/typescript-versions@0.1.6': {} + '@definitelytyped/typescript-versions@0.1.7': {} '@definitelytyped/utils@0.1.8': dependencies: @@ -36076,7 +36080,7 @@ snapshots: dts-critic@3.3.11(typescript@5.7.2): dependencies: - '@definitelytyped/header-parser': 0.2.16 + '@definitelytyped/header-parser': 0.2.17 command-exists: 1.2.9 rimraf: 3.0.2 semver: 6.3.1 @@ -36086,8 +36090,8 @@ snapshots: dtslint@4.2.1(typescript@5.7.2): dependencies: - '@definitelytyped/header-parser': 0.2.16 - '@definitelytyped/typescript-versions': 0.1.6 + '@definitelytyped/header-parser': 0.2.17 + '@definitelytyped/typescript-versions': 0.1.7 '@definitelytyped/utils': 0.1.8 dts-critic: 3.3.11(typescript@5.7.2) fs-extra: 6.0.1