diff --git a/components/rippling/actions/get-user/get-user.mjs b/components/rippling/actions/get-user/get-user.mjs new file mode 100644 index 0000000000000..129d27421b6d7 --- /dev/null +++ b/components/rippling/actions/get-user/get-user.mjs @@ -0,0 +1,27 @@ +import rippling from "../../rippling.app.mjs"; + +export default { + key: "rippling-get-user", + name: "Get User", + description: "Retrieves a specific user from Rippling. [See the documentation](https://developer.rippling.com/documentation/rest-api/reference/get-users)", + version: "0.0.1", + type: "action", + props: { + rippling, + userId: { + propDefinition: [ + rippling, + "userId", + ], + }, + }, + async run({ $ }) { + const response = await this.rippling.getUser({ + $, + userId: this.userId, + }); + + $.export("$summary", `Successfully retrieved user with ID: ${this.userId}`); + return response; + }, +}; diff --git a/components/rippling/actions/list-companies/list-companies.mjs b/components/rippling/actions/list-companies/list-companies.mjs new file mode 100644 index 0000000000000..bd4b10384dd58 --- /dev/null +++ b/components/rippling/actions/list-companies/list-companies.mjs @@ -0,0 +1,54 @@ +import rippling from "../../rippling.app.mjs"; + +export default { + key: "rippling-list-companies", + name: "List Companies", + description: "Retrieves a list of all companies from Rippling. [See the documentation](https://developer.rippling.com/documentation/rest-api/reference/list-companies)", + version: "0.0.1", + type: "action", + props: { + rippling, + expand: { + propDefinition: [ + rippling, + "expandCompanies", + ], + }, + orderBy: { + propDefinition: [ + rippling, + "orderBy", + ], + }, + orderDirection: { + propDefinition: [ + rippling, + "orderDirection", + ], + }, + maxResults: { + propDefinition: [ + rippling, + "maxResults", + ], + }, + }, + async run({ $ }) { + const response = await this.rippling.getPaginatedResources({ + fn: this.rippling.listCompanies, + args: { + $, + params: { + order_by: `${this.orderBy} ${this.orderDirection}`, + ...(this.expand && { + expand: this.expand.join(","), + }), + }, + }, + max: this.maxResults, + }); + + $.export("$summary", `Successfully retrieved ${response?.length || 0} companies`); + return response; + }, +}; diff --git a/components/rippling/actions/list-teams/list-teams.mjs b/components/rippling/actions/list-teams/list-teams.mjs new file mode 100644 index 0000000000000..505d368b1e05f --- /dev/null +++ b/components/rippling/actions/list-teams/list-teams.mjs @@ -0,0 +1,48 @@ +import rippling from "../../rippling.app.mjs"; + +export default { + key: "rippling-list-teams", + name: "List Teams", + description: "Retrieves a list of all teams from Rippling. [See the documentation](https://developer.rippling.com/documentation/rest-api/reference/list-teams)", + version: "0.0.1", + type: "action", + props: { + rippling, + expand: { + propDefinition: [ + rippling, + "expandTeams", + ], + }, + orderBy: { + propDefinition: [ + rippling, + "orderBy", + ], + }, + maxResults: { + propDefinition: [ + rippling, + "maxResults", + ], + }, + }, + async run({ $ }) { + const response = await this.rippling.getPaginatedResources({ + fn: this.rippling.listTeams, + args: { + $, + params: { + order_by: this.orderBy, + ...(this.expand && { + expand: this.expand.join(","), + }), + }, + }, + max: this.maxResults, + }); + + $.export("$summary", `Successfully retrieved ${response?.length || 0} teams`); + return response; + }, +}; diff --git a/components/rippling/actions/list-workers/list-workers.mjs b/components/rippling/actions/list-workers/list-workers.mjs new file mode 100644 index 0000000000000..35614f16372cf --- /dev/null +++ b/components/rippling/actions/list-workers/list-workers.mjs @@ -0,0 +1,63 @@ +import rippling from "../../rippling.app.mjs"; + +export default { + key: "rippling-list-workers", + name: "List Workers", + description: "Retrieves a list of all workers from Rippling. [See the documentation](https://developer.rippling.com/documentation/rest-api/reference/list-workers)", + version: "0.0.1", + type: "action", + props: { + rippling, + filter: { + propDefinition: [ + rippling, + "filterWorkers", + ], + }, + expand: { + propDefinition: [ + rippling, + "expandWorkers", + ], + }, + orderBy: { + propDefinition: [ + rippling, + "orderBy", + ], + }, + orderDirection: { + propDefinition: [ + rippling, + "orderDirection", + ], + }, + maxResults: { + propDefinition: [ + rippling, + "maxResults", + ], + }, + }, + async run({ $ }) { + const response = await this.rippling.getPaginatedResources({ + fn: this.rippling.listWorkers, + args: { + $, + params: { + order_by: `${this.orderBy} ${this.orderDirection}`, + ...(this.filter && { + filter: this.filter, + }), + ...(this.expand && { + expand: this.expand.join(","), + }), + }, + }, + max: this.maxResults, + }); + + $.export("$summary", `Successfully retrieved ${response?.length || 0} workers`); + return response; + }, +}; diff --git a/components/rippling/package.json b/components/rippling/package.json index eac12cee074d9..bb7634f5c50bc 100644 --- a/components/rippling/package.json +++ b/components/rippling/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/rippling", - "version": "0.0.1", + "version": "0.1.0", "description": "Pipedream Rippling Components", "main": "rippling.app.mjs", "keywords": [ @@ -11,5 +11,8 @@ "author": "Pipedream (https://pipedream.com/)", "publishConfig": { "access": "public" + }, + "dependencies": { + "@pipedream/platform": "^3.1.0" } -} \ No newline at end of file +} diff --git a/components/rippling/rippling.app.mjs b/components/rippling/rippling.app.mjs index d9b3ee64585f2..8446177b31425 100644 --- a/components/rippling/rippling.app.mjs +++ b/components/rippling/rippling.app.mjs @@ -1,11 +1,266 @@ +import { axios } from "@pipedream/platform"; + export default { type: "app", app: "rippling", - propDefinitions: {}, + propDefinitions: { + workerId: { + type: "string", + label: "Worker ID", + description: "The ID of the worker", + async options({ prevContext }) { + const args = prevContext?.next + ? { + url: prevContext?.next, + } + : {}; + const { + results, next_link: next, + } = await this.listWorkers(args); + return { + options: results?.map(({ + id: value, personal_info, + }) => ({ + label: `${personal_info.first_name} ${personal_info.last_name}`, + value, + })) || [], + context: { + next, + }, + }; + }, + }, + teamId: { + type: "string", + label: "Team ID", + description: "The ID of the team", + async options({ prevContext }) { + const args = prevContext?.next + ? { + url: prevContext?.next, + } + : {}; + const { + results, next_link: next, + } = await this.listTeams(args); + return { + options: results?.map(({ + id: value, name: label, + }) => ({ + label, + value, + })) || [], + context: { + next, + }, + }; + }, + }, + companyId: { + type: "string", + label: "Company ID", + description: "The ID of the company", + async options({ prevContext }) { + const args = prevContext?.next + ? { + url: prevContext?.next, + } + : {}; + const { + results, next_link: next, + } = await this.listCompanies(args); + return { + options: results?.map(({ + id: value, name: label, + }) => ({ + label, + value, + })) || [], + context: { + next, + }, + }; + }, + }, + userId: { + type: "string", + label: "User ID", + description: "The ID of the user", + async options({ prevContext }) { + const args = prevContext?.next + ? { + url: prevContext?.next, + } + : {}; + const { + results, next_link: next, + } = await this.listUsers(args); + return { + options: results?.map(({ + id: value, display_name: label, + }) => ({ + label, + value, + })) || [], + context: { + next, + }, + }; + }, + }, + filterWorkers: { + type: "string", + label: "Filter", + description: "Filter workers by field. Filterable fields: `status`, `work_email`, `user_id`, `created_at`, `updated_at`. Example: `status+eq+'ACTIVE'` [See the documentation](https://developer.rippling.com/documentation/rest-api/guides/query-parameters#filter) for more information.", + optional: true, + }, + expandWorkers: { + type: "string[]", + label: "Expand", + description: "Expand fields", + options: [ + "user", + "manager", + "legal_entity", + "employment_type", + "compensation", + "department", + "teams", + "level", + "custom_fields", + "business_partners", + ], + optional: true, + }, + expandCompanies: { + type: "string[]", + label: "Expand", + description: "Expand fields", + options: [ + "parent_legal_entity", + "legal_entities", + ], + optional: true, + }, + expandTeams: { + type: "string[]", + label: "Expand", + description: "Expand fields", + options: [ + "parent", + ], + optional: true, + }, + orderBy: { + type: "string", + label: "Order By", + description: "The field to order the results by", + options: [ + "id", + "created_at", + "updated_at", + ], + default: "id", + optional: true, + }, + orderDirection: { + type: "string", + label: "Order Direction", + description: "The direction to order the results by", + options: [ + "asc", + "desc", + ], + default: "asc", + optional: true, + }, + maxResults: { + type: "integer", + label: "Max Results", + description: "The maximum number of results to return", + optional: true, + default: 100, + }, + }, methods: { - // this.$auth contains connected account data - authKeys() { - console.log(Object.keys(this.$auth)); + _baseUrl() { + return "https://rest.ripplingapis.com"; + }, + _headers(headers = {}) { + return { + ...headers, + Authorization: `Bearer ${this.$auth.api_key}`, + }; + }, + async _makeRequest({ + $ = this, url, path, headers, ...opts + }) { + return axios($, { + url: url || `${this._baseUrl()}${path}`, + headers: this._headers(headers), + ...opts, + }); + }, + getUser({ + userId, ...opts + }) { + return this._makeRequest({ + path: `/users/${userId}`, + ...opts, + }); + }, + listUsers(opts = {}) { + return this._makeRequest({ + path: "/users", + ...opts, + }); + }, + listTeams(opts = {}) { + return this._makeRequest({ + path: "/teams", + ...opts, + }); + }, + listWorkers(opts = {}) { + return this._makeRequest({ + path: "/workers", + ...opts, + }); + }, + listCompanies(opts = {}) { + return this._makeRequest({ + path: "/companies", + ...opts, + }); + }, + async *paginate({ + fn, args = {}, max, + }) { + let hasMore, count = 0; + do { + const { + results, next_link: next, + } = await fn(args); + if (!results?.length) { + return; + } + for (const item of results) { + yield item; + if (max && ++count >= max) { + return; + } + } + hasMore = next; + args.url = next; + } while (hasMore); + }, + async getPaginatedResources(opts) { + const results = []; + const resources = this.paginate(opts); + for await (const resource of resources) { + results.push(resource); + } + return results; }, }, -}; \ No newline at end of file +}; diff --git a/components/rippling/sources/new-worker-created/new-worker-created.mjs b/components/rippling/sources/new-worker-created/new-worker-created.mjs new file mode 100644 index 0000000000000..dc2cbdfe2285c --- /dev/null +++ b/components/rippling/sources/new-worker-created/new-worker-created.mjs @@ -0,0 +1,87 @@ +import { DEFAULT_POLLING_SOURCE_TIMER_INTERVAL } from "@pipedream/platform"; +import rippling from "../../rippling.app.mjs"; + +export default { + key: "rippling-new-worker-created", + name: "New Worker Created", + description: "Emit new event when a new worker is created in Rippling. [See the documentation](https://developer.rippling.com/documentation/rest-api/reference/list-workers)", + version: "0.0.1", + type: "source", + dedupe: "unique", + props: { + rippling, + db: "$.service.db", + timer: { + type: "$.interface.timer", + default: { + intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, + }, + }, + filter: { + propDefinition: [ + rippling, + "filterWorkers", + ], + }, + expand: { + propDefinition: [ + rippling, + "expandWorkers", + ], + }, + }, + methods: { + _getLastCreatedAt() { + return this.db.get("lastCreatedAt") || 0; + }, + _setLastCreatedAt(createdAt) { + this.db.set("lastCreatedAt", createdAt); + }, + generateMeta(worker) { + return { + id: worker.id, + summary: `New Worker: ${worker?.user?.display_name || worker.id}`, + ts: Date.parse(worker.created_at), + }; + }, + async processEvents(max) { + const lastCreatedAt = this._getLastCreatedAt(); + let maxCreatedAt = lastCreatedAt; + const workers = await this.rippling.paginate({ + fn: this.rippling.listWorkers, + args: { + params: { + order_by: "created_at desc", + ...(this.filter && { + filter: this.filter, + }), + ...(this.expand && { + expand: this.expand.join(","), + }), + }, + }, + max, + }); + for await (const worker of workers) { + const ts = Date.parse(worker.created_at); + if (ts > lastCreatedAt) { + this.$emit(worker, this.generateMeta(worker)); + if (ts > maxCreatedAt) { + maxCreatedAt = ts; + } + } else { + break; + } + } + this._setLastCreatedAt(maxCreatedAt); + }, + }, + hooks: { + async deploy() { + await this.processEvents(25); + }, + }, + async run() { + await this.processEvents(); + }, +}; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 558f172a496ec..f49cd566868b1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11268,7 +11268,11 @@ importers: specifier: ^1.5.1 version: 1.6.6 - components/rippling: {} + components/rippling: + dependencies: + '@pipedream/platform': + specifier: ^3.1.0 + version: 3.1.0 components/rise: dependencies: @@ -15645,14 +15649,6 @@ importers: specifier: ^6.0.0 version: 6.2.0 - modelcontextprotocol/node_modules2/@modelcontextprotocol/sdk/dist/cjs: {} - - modelcontextprotocol/node_modules2/@modelcontextprotocol/sdk/dist/esm: {} - - modelcontextprotocol/node_modules2/zod-to-json-schema/dist/cjs: {} - - modelcontextprotocol/node_modules2/zod-to-json-schema/dist/esm: {} - packages/ai: dependencies: '@pipedream/sdk':