diff --git a/components/refiner/actions/identify-user/identify-user.mjs b/components/refiner/actions/identify-user/identify-user.mjs new file mode 100644 index 0000000000000..cfacb13522162 --- /dev/null +++ b/components/refiner/actions/identify-user/identify-user.mjs @@ -0,0 +1,43 @@ +import { ConfigurationError } from "@pipedream/platform"; +import refiner from "../../refiner.app.mjs"; + +export default { + key: "refiner-identify-user", + name: "Identify User", + description: "Identify a user with user ID or email. If the user does not exist, a new one will be created. [See the documentation](https://refiner.io/docs/api/#identify-user)", + version: "0.0.1", + type: "action", + props: { + refiner, + userId: { + propDefinition: [ + refiner, + "userId", + ], + optional: true, + }, + email: { + propDefinition: [ + refiner, + "email", + ], + optional: true, + }, + }, + async run({ $ }) { + if (!this.userId && !this.email) { + throw new ConfigurationError("Either User ID or Email must be provided to identify the user."); + } + + const response = await this.refiner.identifyUser({ + $, + data: { + id: this.userId, + email: this.email, + }, + }); + + $.export("$summary", `User identified successfully. Contact UUID: ${response.contact_uuid}`); + return response; + }, +}; diff --git a/components/refiner/actions/track-event/track-event.mjs b/components/refiner/actions/track-event/track-event.mjs new file mode 100644 index 0000000000000..50816625d77e8 --- /dev/null +++ b/components/refiner/actions/track-event/track-event.mjs @@ -0,0 +1,49 @@ +import { ConfigurationError } from "@pipedream/platform"; +import refiner from "../../refiner.app.mjs"; + +export default { + key: "refiner-track-event", + name: "Track Event", + description: "Tracks a user event in Refiner. [See the documentation](https://refiner.io/docs/api/#track-event)", + version: "0.0.1", + type: "action", + props: { + refiner, + eventName: { + type: "string", + label: "Event Name", + description: "The name of the event or signal being tracked.", + }, + userId: { + propDefinition: [ + refiner, + "userId", + ], + optional: true, + }, + email: { + propDefinition: [ + refiner, + "email", + ], + optional: true, + }, + }, + async run({ $ }) { + if (!this.userId && !this.email) { + throw new ConfigurationError("Either User ID or Email must be provided to track the event."); + } + + const response = await this.refiner.trackEvent({ + $, + data: { + event: this.eventName, + id: this.userId, + email: this.email, + }, + }); + + $.export("$summary", `Tracked event "${this.eventName}" successfully.`); + return response; + }, +}; diff --git a/components/refiner/package.json b/components/refiner/package.json index 2422cf687683f..a941ecdb5faa6 100644 --- a/components/refiner/package.json +++ b/components/refiner/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/refiner", - "version": "0.0.1", + "version": "0.1.0", "description": "Pipedream Refiner Components", "main": "refiner.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/refiner/refiner.app.mjs b/components/refiner/refiner.app.mjs index 459526d9fd6b1..cf10d14125339 100644 --- a/components/refiner/refiner.app.mjs +++ b/components/refiner/refiner.app.mjs @@ -1,11 +1,133 @@ +import { axios } from "@pipedream/platform"; + export default { type: "app", app: "refiner", - propDefinitions: {}, + propDefinitions: { + userId: { + type: "string", + label: "User ID", + description: "The ID of the user to identify or track events for.", + async options({ page }) { + const { items } = await this.listContacts({ + params: { + page: page + 1, + }, + }); + + return items.map(({ + uuid: value, email: label, + }) => ({ + label, + value, + })); + }, + }, + email: { + type: "string", + label: "Email", + description: "The email address of the user to identify or track events for.", + optional: true, + }, + segmentId: { + type: "string", + label: "Segment ID", + description: "The ID of the segment to emit events for.", + async options({ page }) { + const { items } = await this.listSegments({ + params: { + page: page + 1, + }, + }); + + return items.map(({ + uuid: value, name: label, + }) => ({ + label, + value, + })); + }, + }, + }, methods: { - // this.$auth contains connected account data - authKeys() { - console.log(Object.keys(this.$auth)); + _baseUrl() { + return "https://api.refiner.io/v1"; + }, + _headers() { + return { + "Authorization": `Bearer ${this.$auth.api_key}`, + }; + }, + _makeRequest({ + $ = this, path, ...opts + }) { + return axios($, { + url: this._baseUrl() + path, + headers: this._headers(), + ...opts, + }); + }, + listContacts(opts = {}) { + return this._makeRequest({ + path: "/contacts", + ...opts, + }); + }, + listResponses(opts = {}) { + return this._makeRequest({ + path: "/responses", + ...opts, + }); + }, + listSegments(opts = {}) { + return this._makeRequest({ + path: "/segments", + ...opts, + }); + }, + identifyUser(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/identify-user", + ...opts, + }); + }, + trackEvent(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/track-event", + ...opts, + }); + }, + async *paginate({ + fn, params = {}, maxResults = null, ...opts + }) { + let hasMore = false; + let count = 0; + let page = 0; + + do { + params.page = ++page; + const { + items, + pagination: { + current_page, last_page, + }, + } = await fn({ + params, + ...opts, + }); + for (const d of items) { + yield d; + + if (maxResults && ++count === maxResults) { + return count; + } + } + + hasMore = current_page < last_page; + + } while (hasMore); }, }, }; diff --git a/components/refiner/sources/common/base.mjs b/components/refiner/sources/common/base.mjs new file mode 100644 index 0000000000000..212cf5432a699 --- /dev/null +++ b/components/refiner/sources/common/base.mjs @@ -0,0 +1,64 @@ +import { DEFAULT_POLLING_SOURCE_TIMER_INTERVAL } from "@pipedream/platform"; +import refiner from "../../refiner.app.mjs"; + +export default { + props: { + refiner, + db: "$.service.db", + timer: { + type: "$.interface.timer", + default: { + intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, + }, + }, + }, + methods: { + _getLastDate() { + return this.db.get("lastDate") || 0; + }, + _setLastDate(lastDate) { + this.db.set("lastDate", lastDate); + }, + async emitEvent(maxResults = false) { + const lastDate = this._getLastDate(); + + const response = this.refiner.paginate({ + fn: this.getFunction(), + params: this.getParams(), + }); + + let responseArray = []; + for await (const item of response) { + const itemDate = this.getItemDate(item); + if (Date.parse(itemDate) <= lastDate) break; + responseArray.push(item); + } + + if (responseArray.length) { + if (maxResults && (responseArray.length > maxResults)) { + responseArray.length = maxResults; + } + const itemDate = this.getItemDate(responseArray[0]); + this._setLastDate(itemDate); + } + + for (const item of responseArray.reverse()) { + const itemDate = this.getItemDate(item); + + this.$emit(item, { + id: item.uuid, + summary: this.getSummary(item), + ts: Date.parse(itemDate), + }); + } + }, + }, + hooks: { + async deploy() { + await this.emitEvent(25); + }, + }, + async run() { + await this.emitEvent(); + }, +}; diff --git a/components/refiner/sources/new-segment-entry/new-segment-entry.mjs b/components/refiner/sources/new-segment-entry/new-segment-entry.mjs new file mode 100644 index 0000000000000..d8cc44f2f32e0 --- /dev/null +++ b/components/refiner/sources/new-segment-entry/new-segment-entry.mjs @@ -0,0 +1,40 @@ +import common from "../common/base.mjs"; +import sampleEmit from "./test-event.mjs"; + +export default { + ...common, + key: "refiner-new-segment-entry", + name: "New Segment Entry", + description: "Emit new event whenever a user enters a segment in Refiner. [See the documentation](https://refiner.io/docs/api/#get-contacts)", + version: "0.0.1", + type: "source", + dedupe: "unique", + props: { + ...common.props, + segmentId: { + propDefinition: [ + common.props.refiner, + "segmentId", + ], + }, + }, + methods: { + ...common.methods, + getFunction() { + return this.refiner.listContacts; + }, + getParams() { + return { + segment_uuid: this.segmentId, + }; + }, + getSummary(item) { + return `User ${item.email} entered segment ${this.segmentId}`; + }, + getItemDate(item) { + return item.segments + .filter(({ uuid }) => uuid === this.segmentId)[0].created_at; + }, + }, + sampleEmit, +}; diff --git a/components/refiner/sources/new-segment-entry/test-event.mjs b/components/refiner/sources/new-segment-entry/test-event.mjs new file mode 100644 index 0000000000000..3c347997c7c09 --- /dev/null +++ b/components/refiner/sources/new-segment-entry/test-event.mjs @@ -0,0 +1,32 @@ +export default { + "uuid": "15cce3d0-ed5d-11ea-aaef-e58a2a43e996", + "remote_id": "Your-Contact-ID", + "email": "jane@awesome.com", + "display_name": "Jane Doe", + "first_seen_at": "2020-09-02T20:44:24.000000Z", + "last_seen_at": "2020-09-02T21:57:50.000000Z", + "attributes": { + "a_user_attribute": "Manager", + "another_one": "Marketing", + "a_survey_answer": "9", + "another_answer": "ABC", + }, + "segments": [ + { + "uuid": "0ff87720-9ae5-11ea-bce5-65a395204572", + "name": "Power Users Segment" + }, + ], + "account": { + "uuid": "15d08cc0-ed5d-11ea-b2ce-c1b46bd4b7c4", + "remote_id": "Your-Account-Id", + "domain": "awesome.com", + "display_name": "Awesome Inc.", + "first_seen_at": "2020-09-02T20:44:24.000000Z", + "last_seen_at": "2020-09-02T21:57:50.000000Z", + "attributes": { + "an_account_attribute": "Computer Software", + "another_one": "2020", + } + } +} \ No newline at end of file diff --git a/components/refiner/sources/new-survey-completion/new-survey-completion.mjs b/components/refiner/sources/new-survey-completion/new-survey-completion.mjs new file mode 100644 index 0000000000000..5ed361b3494e5 --- /dev/null +++ b/components/refiner/sources/new-survey-completion/new-survey-completion.mjs @@ -0,0 +1,30 @@ +import common from "../common/base.mjs"; +import sampleEmit from "./test-event.mjs"; + +export default { + ...common, + key: "refiner-new-survey-completion", + name: "New Survey Completion", + description: "Emit new event whenever a user completes a survey in Refiner. [See the documentation](https://refiner.io/docs/api/#get-responses)", + version: "0.0.1", + type: "source", + dedupe: "unique", + methods: { + ...common.methods, + getFunction() { + return this.refiner.listResponses; + }, + getParams() { + return { + include: "completed", + }; + }, + getSummary(item) { + return `Survey (${item.form.uuid}) completed by user ${item.contact.uuid}`; + }, + getItemDate(item) { + return item.completed_at; + }, + }, + sampleEmit, +}; diff --git a/components/refiner/sources/new-survey-completion/test-event.mjs b/components/refiner/sources/new-survey-completion/test-event.mjs new file mode 100644 index 0000000000000..894542ed21802 --- /dev/null +++ b/components/refiner/sources/new-survey-completion/test-event.mjs @@ -0,0 +1,31 @@ +export default { + "uuid": "cb47c260-ed64-11ea-8187-a7fc8351fba4", + "first_shown_at": "2020-09-02 21:53:33", + "last_shown_at": "2020-09-02 21:53:33", + "show_counter": 1, + "first_data_reception_at": "2020-09-02 21:53:33", + "last_data_reception_at": "2020-09-02 21:53:33", + "completed_at": "2020-09-02 21:53:33", + "received_at": "2020-09-02 21:53:33", + "dismissed_at": null, + "form": { + "uuid": "3894dc20-8fe9-11ea-892e-d13af52e06ae", + "name": "My first survey" + }, + "data": { + "first_question": "First Reply", + "second_question": "Second Reply" + }, + "contact": { + "uuid": "e5365340-ed47-11ea-9a73-61d23f380055", + "remote_id": "YOUR-USER-ID-123", + "email": "jane@awesome.com", + "display_name": "Jane Doe", + "account": { + "uuid": "5ab55ab0-ebf3-11ea-a442-4fa2c72e1cf7", + "remote_id": "Your account ID", + "display_name": "Awesome Inc.", + "domain": null + } + } +} \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a64aa7c24e9c3..8cfba55144bde 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8726,7 +8726,11 @@ importers: specifier: ^1.3.0 version: 1.6.6 - components/refiner: {} + components/refiner: + dependencies: + '@pipedream/platform': + specifier: ^3.0.3 + version: 3.0.3 components/reflect: dependencies: