From 9779bfbae40e39e49c97eab0a507b137aa4ef40c Mon Sep 17 00:00:00 2001 From: Luan Cazarine Date: Tue, 4 Feb 2025 17:17:27 -0300 Subject: [PATCH 1/7] refiner init --- .../actions/identify-user/identify-user.mjs | 34 ++++++ .../actions/track-event/track-event.mjs | 47 ++++++++ components/refiner/package.json | 2 +- components/refiner/refiner.app.mjs | 101 ++++++++++++++++- .../new-segment-entry/new-segment-entry.mjs | 102 ++++++++++++++++++ .../new-survey-completion.mjs | 98 +++++++++++++++++ 6 files changed, 382 insertions(+), 2 deletions(-) create mode 100644 components/refiner/actions/identify-user/identify-user.mjs create mode 100644 components/refiner/actions/track-event/track-event.mjs create mode 100644 components/refiner/sources/new-segment-entry/new-segment-entry.mjs create mode 100644 components/refiner/sources/new-survey-completion/new-survey-completion.mjs 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..eb97dcd2791bf --- /dev/null +++ b/components/refiner/actions/identify-user/identify-user.mjs @@ -0,0 +1,34 @@ +import refiner from "../../refiner.app.mjs"; +import { axios } from "@pipedream/platform"; + +export default { + key: "refiner-identify-user", + name: "Identify User", + description: "Creates or updates a user profile in Refiner. [See the documentation]()", + version: "0.0.{{ts}}", + type: "action", + props: { + refiner, + userId: { + propDefinition: [ + refiner, + "userId", + ], + }, + email: { + propDefinition: [ + refiner, + "email", + ], + }, + }, + async run({ $ }) { + const response = await this.refiner.identifyUser({ + userId: 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..e50b1b034ed8e --- /dev/null +++ b/components/refiner/actions/track-event/track-event.mjs @@ -0,0 +1,47 @@ +import refiner from "../../refiner.app.mjs"; +import { axios } from "@pipedream/platform"; + +export default { + key: "refiner-track-event", + name: "Track Event", + description: "Tracks a user event in Refiner. [See the documentation](https://refiner.io/docs/api/)", + version: "0.0.{{ts}}", + type: "action", + props: { + refiner, + eventName: { + propDefinition: [ + refiner, + "eventName", + ], + }, + userId: { + propDefinition: [ + refiner, + "userId", + ], + optional: true, + }, + email: { + propDefinition: [ + refiner, + "email", + ], + optional: true, + }, + }, + async run({ $ }) { + if (!this.userId && !this.email) { + throw new Error("Either userId or email must be provided to track the event."); + } + + const response = await this.refiner.trackEvent({ + eventName: this.eventName, + userId: 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..9951c2276715e 100644 --- a/components/refiner/package.json +++ b/components/refiner/package.json @@ -12,4 +12,4 @@ "publishConfig": { "access": "public" } -} \ No newline at end of file +} diff --git a/components/refiner/refiner.app.mjs b/components/refiner/refiner.app.mjs index 459526d9fd6b1..6d8f943d77848 100644 --- a/components/refiner/refiner.app.mjs +++ b/components/refiner/refiner.app.mjs @@ -1,11 +1,110 @@ +import { axios } from "@pipedream/platform"; + export default { type: "app", app: "refiner", - propDefinitions: {}, + version: "0.0.{{ts}}", + propDefinitions: { + userId: { + type: "string", + label: "User ID", + description: "The ID of the user to identify or track events for.", + optional: true, + }, + email: { + type: "string", + label: "Email", + description: "The email address of the user to identify or track events for.", + optional: true, + }, + eventName: { + type: "string", + label: "Event Name", + description: "The name of the event or signal being tracked.", + }, + }, methods: { // this.$auth contains connected account data authKeys() { console.log(Object.keys(this.$auth)); }, + _baseUrl() { + return "https://api.refiner.io/v1"; + }, + async _makeRequest(opts = {}) { + const { + $ = this, method = "GET", path = "/", headers, ...otherOpts + } = opts; + return axios($, { + method, + url: this._baseUrl() + path, + headers: { + ...headers, + "Authorization": `Bearer ${this.$auth.api_key}`, + "user-agent": "@PipedreamHQ/pipedream v0.1", + "Content-Type": "application/json", + }, + ...otherOpts, + }); + }, + async identifyUser(opts = {}) { + const { + userId, email, ...attributes + } = opts; + if (!userId && !email) { + throw new Error("Either userId or email must be provided to identify the user."); + } + const data = { + ...(userId && { + id: userId, + }), + ...(email && { + email, + }), + ...attributes, + }; + return this._makeRequest({ + method: "POST", + path: "/identify-user", + data, + }); + }, + async trackEvent(opts = {}) { + const { + eventName, userId, email, + } = opts; + if (!eventName) { + throw new Error("eventName is required to track an event."); + } + if (!userId && !email) { + throw new Error("Either userId or email must be provided to track the event."); + } + const data = { + event: eventName, + ...(userId && { + id: userId, + }), + ...(email && { + email, + }), + }; + return this._makeRequest({ + method: "POST", + path: "/track-event", + data, + }); + }, + async emitSurveyCompleted() { + return this._makeRequest({ + method: "POST", + path: "/emit-survey-completed", + }); + }, + async emitSegmentEntered() { + return this._makeRequest({ + method: "POST", + path: "/emit-segment-entered", + }); + }, }, }; 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..3f00b559f9465 --- /dev/null +++ b/components/refiner/sources/new-segment-entry/new-segment-entry.mjs @@ -0,0 +1,102 @@ +import { + axios, DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, +} from "@pipedream/platform"; +import refiner from "../../refiner.app.mjs"; + +export default { + key: "refiner-new-segment-entry", + name: "New Segment Entry", + description: "Emits a new event whenever a user enters a segment in Refiner. [See the documentation]()", + version: "0.0.{{ts}}", + type: "source", + dedupe: "unique", + props: { + refiner: { + type: "app", + app: "refiner", + }, + db: { + type: "$.service.db", + }, + timer: { + type: "$.interface.timer", + default: { + intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, + }, + }, + }, + hooks: { + async deploy() { + const MAX_EVENTS = 50; + const lastTs = await this.db.get("lastTs") || 0; + + const entries = await this.refiner._makeRequest({ + method: "GET", + path: "/segment-entries", + params: { + since: lastTs, + limit: MAX_EVENTS, + sort: "desc", + }, + }); + + let newLastTs = lastTs; + + for (const entry of entries) { + const ts = entry.timestamp + ? Date.parse(entry.timestamp) + : Date.now(); + this.$emit(entry, { + id: entry.id ?? ts, + summary: `User ${entry.userId} entered segment ${entry.segmentId}`, + ts, + }); + + if (ts > newLastTs) { + newLastTs = ts; + } + + if (--MAX_EVENTS <= 0) break; + } + + await this.db.set("lastTs", newLastTs); + }, + async activate() { + // Activation logic if any + }, + async deactivate() { + // Deactivation logic if any + }, + }, + async run() { + const lastTs = await this.db.get("lastTs") || 0; + + const entries = await this.refiner._makeRequest({ + method: "GET", + path: "/segment-entries", + params: { + since: lastTs, + sort: "asc", + }, + }); + + let newLastTs = lastTs; + + for (const entry of entries) { + const ts = entry.timestamp + ? Date.parse(entry.timestamp) + : Date.now(); + this.$emit(entry, { + id: entry.id ?? ts, + summary: `User ${entry.userId} entered segment ${entry.segmentId}`, + ts, + }); + + if (ts > newLastTs) { + newLastTs = ts; + } + } + + await this.db.set("lastTs", newLastTs); + }, +}; 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..f22f55abf2e27 --- /dev/null +++ b/components/refiner/sources/new-survey-completion/new-survey-completion.mjs @@ -0,0 +1,98 @@ +import { + axios, DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, +} from "@pipedream/platform"; +import refiner from "../../refiner.app.mjs"; + +export default { + key: "refiner-new-survey-completion", + name: "New Survey Completion", + description: "Emits a new event whenever a user completes a survey in Refiner. [See the documentation]()", + version: "0.0.{{ts}}", + type: "source", + dedupe: "unique", + props: { + refiner: { + type: "app", + app: "refiner", + }, + db: "$.service.db", + timer: { + type: "$.interface.timer", + default: { + intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, + }, + }, + }, + methods: { + async _fetchSurveyCompletions(since) { + return this.refiner._makeRequest({ + method: "GET", + path: "/survey-completions", + params: { + since, + }, + }); + }, + async _getLastRunAt() { + return this.db.get("lastRunAt") || new Date(0).toISOString(); + }, + async _setLastRunAt(timestamp) { + await this.db.set("lastRunAt", timestamp); + }, + }, + hooks: { + async deploy() { + let lastRunAt = await this._getLastRunAt(); + const currentRunAt = new Date().toISOString(); + + const surveyCompletions = await this._fetchSurveyCompletions(lastRunAt); + const sortedCompletions = surveyCompletions.sort((a, b) => new Date(b.completed_at) - new Date(a.completed_at)); + + const eventsToEmit = sortedCompletions.slice(0, 50); + + for (const survey of eventsToEmit) { + const ts = Date.parse(survey.completed_at) || Date.now(); + this.$emit(survey, { + id: survey.id || ts, + summary: `Survey completed by user ${survey.user_id || "unknown"}`, + ts, + }); + } + + if (eventsToEmit.length > 0) { + const latestRunAt = eventsToEmit[0].completed_at; + await this._setLastRunAt(latestRunAt); + } else { + await this._setLastRunAt(currentRunAt); + } + }, + async activate() { + // No webhook setup required for polling source + }, + async deactivate() { + // No webhook cleanup required for polling source + }, + }, + async run() { + const lastRunAt = await this._getLastRunAt(); + const currentRunAt = new Date().toISOString(); + + const surveyCompletions = await this._fetchSurveyCompletions(lastRunAt); + + let latestRunAt = lastRunAt; + + for (const survey of surveyCompletions) { + const ts = Date.parse(survey.completed_at) || Date.now(); + this.$emit(survey, { + id: survey.id || ts, + summary: `Survey completed by user ${survey.user_id || "unknown"}`, + ts, + }); + if (survey.completed_at > latestRunAt) { + latestRunAt = survey.completed_at; + } + } + + await this._setLastRunAt(latestRunAt || currentRunAt); + }, +}; From 720ad6757e3c6486cd6488e5154db666a2c32d5b Mon Sep 17 00:00:00 2001 From: Luan Cazarine Date: Wed, 5 Feb 2025 11:48:45 -0300 Subject: [PATCH 2/7] [Components] tinyurl #15135 Sources - New Survey Completion - New Segment Entry Actions - Identify User - Track Event --- .../actions/identify-user/identify-user.mjs | 19 ++- .../actions/track-event/track-event.mjs | 21 +-- components/refiner/package.json | 5 +- components/refiner/refiner.app.mjs | 155 ++++++++++-------- components/refiner/sources/common/base.mjs | 66 ++++++++ .../new-segment-entry/new-segment-entry.mjs | 114 +++---------- .../sources/new-segment-entry/test-event.mjs | 32 ++++ .../new-survey-completion.mjs | 102 ++---------- .../new-survey-completion/test-event.mjs | 31 ++++ 9 files changed, 290 insertions(+), 255 deletions(-) create mode 100644 components/refiner/sources/common/base.mjs create mode 100644 components/refiner/sources/new-segment-entry/test-event.mjs create mode 100644 components/refiner/sources/new-survey-completion/test-event.mjs diff --git a/components/refiner/actions/identify-user/identify-user.mjs b/components/refiner/actions/identify-user/identify-user.mjs index eb97dcd2791bf..856a79a01dcd3 100644 --- a/components/refiner/actions/identify-user/identify-user.mjs +++ b/components/refiner/actions/identify-user/identify-user.mjs @@ -1,11 +1,11 @@ +import { ConfigurationError } from "@pipedream/platform"; import refiner from "../../refiner.app.mjs"; -import { axios } from "@pipedream/platform"; export default { key: "refiner-identify-user", name: "Identify User", - description: "Creates or updates a user profile in Refiner. [See the documentation]()", - version: "0.0.{{ts}}", + description: "Creates or updates a user profile in Refiner. [See the documentation](https://refiner.io/docs/api/#identify-user)", + version: "0.0.1", type: "action", props: { refiner, @@ -14,18 +14,27 @@ export default { refiner, "userId", ], + optional: true, }, email: { propDefinition: [ refiner, "email", ], + optional: true, }, }, async run({ $ }) { + if (!this.userId && !this.email) { + throw new ConfigurationError("Either User Id or E mail must be provided to identify the user."); + } + const response = await this.refiner.identifyUser({ - userId: this.userId, - email: this.email, + $, + data: { + id: this.userId, + email: this.email, + }, }); $.export("$summary", `User identified successfully. Contact UUID: ${response.contact_uuid}`); diff --git a/components/refiner/actions/track-event/track-event.mjs b/components/refiner/actions/track-event/track-event.mjs index e50b1b034ed8e..d8e2c2932f5b2 100644 --- a/components/refiner/actions/track-event/track-event.mjs +++ b/components/refiner/actions/track-event/track-event.mjs @@ -1,19 +1,17 @@ import refiner from "../../refiner.app.mjs"; -import { axios } from "@pipedream/platform"; export default { key: "refiner-track-event", name: "Track Event", - description: "Tracks a user event in Refiner. [See the documentation](https://refiner.io/docs/api/)", - version: "0.0.{{ts}}", + 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: { - propDefinition: [ - refiner, - "eventName", - ], + type: "string", + label: "Event Name", + description: "The name of the event or signal being tracked.", }, userId: { propDefinition: [ @@ -36,9 +34,12 @@ export default { } const response = await this.refiner.trackEvent({ - eventName: this.eventName, - userId: this.userId, - email: this.email, + $, + data: { + event: this.eventName, + id: this.userId, + email: this.email, + }, }); $.export("$summary", `Tracked event "${this.eventName}" successfully.`); diff --git a/components/refiner/package.json b/components/refiner/package.json index 9951c2276715e..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" } } diff --git a/components/refiner/refiner.app.mjs b/components/refiner/refiner.app.mjs index 6d8f943d77848..cf10d14125339 100644 --- a/components/refiner/refiner.app.mjs +++ b/components/refiner/refiner.app.mjs @@ -3,13 +3,25 @@ import { axios } from "@pipedream/platform"; export default { type: "app", app: "refiner", - version: "0.0.{{ts}}", propDefinitions: { userId: { type: "string", label: "User ID", description: "The ID of the user to identify or track events for.", - optional: true, + async options({ page }) { + const { items } = await this.listContacts({ + params: { + page: page + 1, + }, + }); + + return items.map(({ + uuid: value, email: label, + }) => ({ + label, + value, + })); + }, }, email: { type: "string", @@ -17,94 +29,105 @@ export default { description: "The email address of the user to identify or track events for.", optional: true, }, - eventName: { + segmentId: { type: "string", - label: "Event Name", - description: "The name of the event or signal being tracked.", + 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"; }, - async _makeRequest(opts = {}) { - const { - $ = this, method = "GET", path = "/", headers, ...otherOpts - } = opts; + _headers() { + return { + "Authorization": `Bearer ${this.$auth.api_key}`, + }; + }, + _makeRequest({ + $ = this, path, ...opts + }) { return axios($, { - method, url: this._baseUrl() + path, - headers: { - ...headers, - "Authorization": `Bearer ${this.$auth.api_key}`, - "user-agent": "@PipedreamHQ/pipedream v0.1", - "Content-Type": "application/json", - }, - ...otherOpts, + headers: this._headers(), + ...opts, }); }, - async identifyUser(opts = {}) { - const { - userId, email, ...attributes - } = opts; - if (!userId && !email) { - throw new Error("Either userId or email must be provided to identify the user."); - } - const data = { - ...(userId && { - id: userId, - }), - ...(email && { - email, - }), - ...attributes, - }; + listContacts(opts = {}) { return this._makeRequest({ - method: "POST", - path: "/identify-user", - data, + path: "/contacts", + ...opts, }); }, - async trackEvent(opts = {}) { - const { - eventName, userId, email, - } = opts; - if (!eventName) { - throw new Error("eventName is required to track an event."); - } - if (!userId && !email) { - throw new Error("Either userId or email must be provided to track the event."); - } - const data = { - event: eventName, - ...(userId && { - id: userId, - }), - ...(email && { - email, - }), - }; + listResponses(opts = {}) { return this._makeRequest({ - method: "POST", - path: "/track-event", - data, + path: "/responses", + ...opts, + }); + }, + listSegments(opts = {}) { + return this._makeRequest({ + path: "/segments", + ...opts, }); }, - async emitSurveyCompleted() { + identifyUser(opts = {}) { return this._makeRequest({ method: "POST", - path: "/emit-survey-completed", + path: "/identify-user", + ...opts, }); }, - async emitSegmentEntered() { + trackEvent(opts = {}) { return this._makeRequest({ method: "POST", - path: "/emit-segment-entered", + 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..1ae58fc7ba9bc --- /dev/null +++ b/components/refiner/sources/common/base.mjs @@ -0,0 +1,66 @@ +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 (!itemDate.length) continue; + 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 index 3f00b559f9465..76afff9737067 100644 --- a/components/refiner/sources/new-segment-entry/new-segment-entry.mjs +++ b/components/refiner/sources/new-segment-entry/new-segment-entry.mjs @@ -1,102 +1,40 @@ -import { - axios, DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, -} from "@pipedream/platform"; -import refiner from "../../refiner.app.mjs"; +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: "Emits a new event whenever a user enters a segment in Refiner. [See the documentation]()", - version: "0.0.{{ts}}", + description: "Emit new event whenever a user enters a segment in Refiner.", + version: "0.0.1", type: "source", dedupe: "unique", props: { - refiner: { - type: "app", - app: "refiner", - }, - db: { - type: "$.service.db", - }, - timer: { - type: "$.interface.timer", - default: { - intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, - }, + ...common.props, + segmentId: { + propDefinition: [ + common.props.refiner, + "segmentId", + ], }, }, - hooks: { - async deploy() { - const MAX_EVENTS = 50; - const lastTs = await this.db.get("lastTs") || 0; - - const entries = await this.refiner._makeRequest({ - method: "GET", - path: "/segment-entries", - params: { - since: lastTs, - limit: MAX_EVENTS, - sort: "desc", - }, - }); - - let newLastTs = lastTs; - - for (const entry of entries) { - const ts = entry.timestamp - ? Date.parse(entry.timestamp) - : Date.now(); - this.$emit(entry, { - id: entry.id ?? ts, - summary: `User ${entry.userId} entered segment ${entry.segmentId}`, - ts, - }); - - if (ts > newLastTs) { - newLastTs = ts; - } - - if (--MAX_EVENTS <= 0) break; - } - - await this.db.set("lastTs", newLastTs); + methods: { + ...common.methods, + getFunction() { + return this.refiner.listContacts; }, - async activate() { - // Activation logic if any + getParams() { + return { + regment_uuid: this.segmentId, + }; }, - async deactivate() { - // Deactivation logic if any + getSummary(item) { + return `User ${item.email} entered segment ${this.segmentId}`; + }, + getItemDate(item) { + return item.segments + .filter(({ uuid }) => uuid === this.segmentId)[0].created_at; }, }, - async run() { - const lastTs = await this.db.get("lastTs") || 0; - - const entries = await this.refiner._makeRequest({ - method: "GET", - path: "/segment-entries", - params: { - since: lastTs, - sort: "asc", - }, - }); - - let newLastTs = lastTs; - - for (const entry of entries) { - const ts = entry.timestamp - ? Date.parse(entry.timestamp) - : Date.now(); - this.$emit(entry, { - id: entry.id ?? ts, - summary: `User ${entry.userId} entered segment ${entry.segmentId}`, - ts, - }); - - if (ts > newLastTs) { - newLastTs = ts; - } - } - - await this.db.set("lastTs", newLastTs); - }, + 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 index f22f55abf2e27..42f2176a33f1c 100644 --- a/components/refiner/sources/new-survey-completion/new-survey-completion.mjs +++ b/components/refiner/sources/new-survey-completion/new-survey-completion.mjs @@ -1,98 +1,30 @@ -import { - axios, DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, -} from "@pipedream/platform"; -import refiner from "../../refiner.app.mjs"; +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: "Emits a new event whenever a user completes a survey in Refiner. [See the documentation]()", - version: "0.0.{{ts}}", + description: "Emit new event whenever a user completes a survey in Refiner.", + version: "0.0.1", type: "source", dedupe: "unique", - props: { - refiner: { - type: "app", - app: "refiner", - }, - db: "$.service.db", - timer: { - type: "$.interface.timer", - default: { - intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, - }, - }, - }, methods: { - async _fetchSurveyCompletions(since) { - return this.refiner._makeRequest({ - method: "GET", - path: "/survey-completions", - params: { - since, - }, - }); + ...common.methods, + getFunction() { + return this.refiner.listResponses; }, - async _getLastRunAt() { - return this.db.get("lastRunAt") || new Date(0).toISOString(); + getParams() { + return { + include: "completed", + }; }, - async _setLastRunAt(timestamp) { - await this.db.set("lastRunAt", timestamp); + getSummary(item) { + return `Survey (${item.form.uuid}) completed by user ${item.contact.uuid}`; }, - }, - hooks: { - async deploy() { - let lastRunAt = await this._getLastRunAt(); - const currentRunAt = new Date().toISOString(); - - const surveyCompletions = await this._fetchSurveyCompletions(lastRunAt); - const sortedCompletions = surveyCompletions.sort((a, b) => new Date(b.completed_at) - new Date(a.completed_at)); - - const eventsToEmit = sortedCompletions.slice(0, 50); - - for (const survey of eventsToEmit) { - const ts = Date.parse(survey.completed_at) || Date.now(); - this.$emit(survey, { - id: survey.id || ts, - summary: `Survey completed by user ${survey.user_id || "unknown"}`, - ts, - }); - } - - if (eventsToEmit.length > 0) { - const latestRunAt = eventsToEmit[0].completed_at; - await this._setLastRunAt(latestRunAt); - } else { - await this._setLastRunAt(currentRunAt); - } + getItemDate(item) { + return item.completed_at; }, - async activate() { - // No webhook setup required for polling source - }, - async deactivate() { - // No webhook cleanup required for polling source - }, - }, - async run() { - const lastRunAt = await this._getLastRunAt(); - const currentRunAt = new Date().toISOString(); - - const surveyCompletions = await this._fetchSurveyCompletions(lastRunAt); - - let latestRunAt = lastRunAt; - - for (const survey of surveyCompletions) { - const ts = Date.parse(survey.completed_at) || Date.now(); - this.$emit(survey, { - id: survey.id || ts, - summary: `Survey completed by user ${survey.user_id || "unknown"}`, - ts, - }); - if (survey.completed_at > latestRunAt) { - latestRunAt = survey.completed_at; - } - } - - await this._setLastRunAt(latestRunAt || currentRunAt); }, + 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 From b228acd3c62e32999fc5c2a6c1170b0c70174ba2 Mon Sep 17 00:00:00 2001 From: Luan Cazarine Date: Wed, 5 Feb 2025 11:51:48 -0300 Subject: [PATCH 3/7] pnpm update --- pnpm-lock.yaml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) 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: From f5d2b271f407e2e19659fcf82ecfeb9b72f4d05f Mon Sep 17 00:00:00 2001 From: Luan Cazarine Date: Wed, 5 Feb 2025 15:10:20 -0300 Subject: [PATCH 4/7] Update components/refiner/sources/new-segment-entry/new-segment-entry.mjs Co-authored-by: michelle0927 --- .../refiner/sources/new-segment-entry/new-segment-entry.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/refiner/sources/new-segment-entry/new-segment-entry.mjs b/components/refiner/sources/new-segment-entry/new-segment-entry.mjs index 76afff9737067..043d508f18faf 100644 --- a/components/refiner/sources/new-segment-entry/new-segment-entry.mjs +++ b/components/refiner/sources/new-segment-entry/new-segment-entry.mjs @@ -25,7 +25,7 @@ export default { }, getParams() { return { - regment_uuid: this.segmentId, + segment_uuid: this.segmentId, }; }, getSummary(item) { From 8f0c54005d863badef4c348f02693aaa243281a2 Mon Sep 17 00:00:00 2001 From: Luan Cazarine Date: Wed, 5 Feb 2025 15:13:38 -0300 Subject: [PATCH 5/7] some adjusts --- components/refiner/actions/identify-user/identify-user.mjs | 2 +- components/refiner/actions/track-event/track-event.mjs | 3 ++- components/refiner/sources/common/base.mjs | 2 -- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/components/refiner/actions/identify-user/identify-user.mjs b/components/refiner/actions/identify-user/identify-user.mjs index 856a79a01dcd3..5444bd9da2a4d 100644 --- a/components/refiner/actions/identify-user/identify-user.mjs +++ b/components/refiner/actions/identify-user/identify-user.mjs @@ -26,7 +26,7 @@ export default { }, async run({ $ }) { if (!this.userId && !this.email) { - throw new ConfigurationError("Either User Id or E mail must be provided to identify the user."); + throw new ConfigurationError("Either User ID or Email must be provided to identify the user."); } const response = await this.refiner.identifyUser({ diff --git a/components/refiner/actions/track-event/track-event.mjs b/components/refiner/actions/track-event/track-event.mjs index d8e2c2932f5b2..b52b79acc1fea 100644 --- a/components/refiner/actions/track-event/track-event.mjs +++ b/components/refiner/actions/track-event/track-event.mjs @@ -1,3 +1,4 @@ +import { ConfigurationError } from "@pipedream/platform/dist"; import refiner from "../../refiner.app.mjs"; export default { @@ -30,7 +31,7 @@ export default { }, async run({ $ }) { if (!this.userId && !this.email) { - throw new Error("Either userId or email must be provided to track the event."); + throw new ConfigurationError("Either User ID or Email must be provided to track the event."); } const response = await this.refiner.trackEvent({ diff --git a/components/refiner/sources/common/base.mjs b/components/refiner/sources/common/base.mjs index 1ae58fc7ba9bc..212cf5432a699 100644 --- a/components/refiner/sources/common/base.mjs +++ b/components/refiner/sources/common/base.mjs @@ -30,8 +30,6 @@ export default { let responseArray = []; for await (const item of response) { const itemDate = this.getItemDate(item); - - if (!itemDate.length) continue; if (Date.parse(itemDate) <= lastDate) break; responseArray.push(item); } From 62a1b74259b303b0e91e7811d4958592d5536969 Mon Sep 17 00:00:00 2001 From: Luan Cazarine Date: Wed, 5 Feb 2025 15:17:54 -0300 Subject: [PATCH 6/7] fix import --- components/refiner/actions/track-event/track-event.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/refiner/actions/track-event/track-event.mjs b/components/refiner/actions/track-event/track-event.mjs index b52b79acc1fea..50816625d77e8 100644 --- a/components/refiner/actions/track-event/track-event.mjs +++ b/components/refiner/actions/track-event/track-event.mjs @@ -1,4 +1,4 @@ -import { ConfigurationError } from "@pipedream/platform/dist"; +import { ConfigurationError } from "@pipedream/platform"; import refiner from "../../refiner.app.mjs"; export default { From 07b649b9397b3d9f1ab3d012ea55c9796a4ae0c2 Mon Sep 17 00:00:00 2001 From: Luan Cazarine Date: Thu, 6 Feb 2025 09:48:23 -0300 Subject: [PATCH 7/7] some adjusts --- components/refiner/actions/identify-user/identify-user.mjs | 2 +- .../refiner/sources/new-segment-entry/new-segment-entry.mjs | 2 +- .../sources/new-survey-completion/new-survey-completion.mjs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/components/refiner/actions/identify-user/identify-user.mjs b/components/refiner/actions/identify-user/identify-user.mjs index 5444bd9da2a4d..cfacb13522162 100644 --- a/components/refiner/actions/identify-user/identify-user.mjs +++ b/components/refiner/actions/identify-user/identify-user.mjs @@ -4,7 +4,7 @@ import refiner from "../../refiner.app.mjs"; export default { key: "refiner-identify-user", name: "Identify User", - description: "Creates or updates a user profile in Refiner. [See the documentation](https://refiner.io/docs/api/#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: { diff --git a/components/refiner/sources/new-segment-entry/new-segment-entry.mjs b/components/refiner/sources/new-segment-entry/new-segment-entry.mjs index 043d508f18faf..d8cc44f2f32e0 100644 --- a/components/refiner/sources/new-segment-entry/new-segment-entry.mjs +++ b/components/refiner/sources/new-segment-entry/new-segment-entry.mjs @@ -5,7 +5,7 @@ export default { ...common, key: "refiner-new-segment-entry", name: "New Segment Entry", - description: "Emit new event whenever a user enters a segment in Refiner.", + 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", diff --git a/components/refiner/sources/new-survey-completion/new-survey-completion.mjs b/components/refiner/sources/new-survey-completion/new-survey-completion.mjs index 42f2176a33f1c..5ed361b3494e5 100644 --- a/components/refiner/sources/new-survey-completion/new-survey-completion.mjs +++ b/components/refiner/sources/new-survey-completion/new-survey-completion.mjs @@ -5,7 +5,7 @@ export default { ...common, key: "refiner-new-survey-completion", name: "New Survey Completion", - description: "Emit new event whenever a user completes a survey in Refiner.", + 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",