diff --git a/components/posthog/actions/capture-event/capture-event.mjs b/components/posthog/actions/capture-event/capture-event.mjs index d32abef3cfc92..858fed867817d 100644 --- a/components/posthog/actions/capture-event/capture-event.mjs +++ b/components/posthog/actions/capture-event/capture-event.mjs @@ -4,7 +4,7 @@ export default { key: "posthog-capture-event", name: "Capture Event", description: "Captures a given event within the PostHog system. [See the documentation](https://posthog.com/docs/api/post-only-endpoints#single-event)", - version: "0.0.5", + version: "0.0.6", annotations: { destructiveHint: false, openWorldHint: true, diff --git a/components/posthog/actions/create-project-insight/create-project-insight.mjs b/components/posthog/actions/create-project-insight/create-project-insight.mjs new file mode 100644 index 0000000000000..02fa0c1e7eef7 --- /dev/null +++ b/components/posthog/actions/create-project-insight/create-project-insight.mjs @@ -0,0 +1,108 @@ +import posthog from "../../posthog.app.mjs"; +import utils from "../../common/utils.mjs"; + +export default { + key: "posthog-create-project-insight", + name: "Create Project Insight", + description: "Create a new insight in a project. [See the documentation](https://posthog.com/docs/api/insights#post-api-projects-project_id-insights)", + version: "0.0.1", + annotations: { + destructiveHint: false, + openWorldHint: true, + readOnlyHint: false, + idempotentHint: false, + }, + type: "action", + props: { + posthog, + organizationId: { + propDefinition: [ + posthog, + "organizationId", + ], + }, + projectId: { + propDefinition: [ + posthog, + "projectId", + (c) => ({ + organizationId: c.organizationId, + }), + ], + }, + name: { + type: "string", + label: "Name", + description: "Name of the insight", + optional: true, + }, + derivedName: { + type: "string", + label: "Derived Name", + description: "Derived name of the insight", + optional: true, + }, + query: { + propDefinition: [ + posthog, + "query", + ], + }, + description: { + type: "string", + label: "Description", + description: "Description of the insight", + optional: true, + }, + tags: { + type: "string[]", + label: "Tags", + description: "Tags for the insight", + optional: true, + }, + favorited: { + type: "boolean", + label: "Favorited", + description: "Whether the insight is favorited", + optional: true, + }, + deleted: { + type: "boolean", + label: "Deleted", + description: "Whether the insight is deleted", + optional: true, + }, + }, + async run({ $ }) { + const { + posthog, + projectId, + name, + derivedName, + query, + description, + tags, + favorited, + deleted, + } = this; + + const insight = await posthog.createInsight({ + $, + projectId, + data: { + name, + derived_name: derivedName, + query: utils.parseJson(query), + description, + tags: utils.parseJson(tags), + favorited, + deleted, + }, + }); + + $.export("$summary", `Successfully created insight${name + ? ` "${name}"` + : ""} with ID ${insight.id}`); + return insight; + }, +}; diff --git a/components/posthog/actions/create-query/create-query.mjs b/components/posthog/actions/create-query/create-query.mjs index 083cd061bbe51..6c5b7b91a732c 100644 --- a/components/posthog/actions/create-query/create-query.mjs +++ b/components/posthog/actions/create-query/create-query.mjs @@ -4,7 +4,7 @@ export default { key: "posthog-create-query", name: "Create Query", description: "Create a HogQLQuery and return the results. [See the documentation](https://posthog.com/docs/api/queries#creating-a-query)", - version: "0.0.4", + version: "0.0.5", annotations: { destructiveHint: false, openWorldHint: true, diff --git a/components/posthog/actions/get-cohorts/get-cohorts.mjs b/components/posthog/actions/get-cohorts/get-cohorts.mjs index ec8178b69cfc4..932b4095cc1bb 100644 --- a/components/posthog/actions/get-cohorts/get-cohorts.mjs +++ b/components/posthog/actions/get-cohorts/get-cohorts.mjs @@ -4,7 +4,7 @@ export default { key: "posthog-get-cohorts", name: "Get Cohorts", description: "Retrieve a list of cohorts. [See the documentation](https://posthog.com/docs/api/cohorts#get-api-projects-project_id-cohorts)", - version: "0.0.4", + version: "0.0.5", annotations: { destructiveHint: false, openWorldHint: true, diff --git a/components/posthog/actions/get-persons/get-persons.mjs b/components/posthog/actions/get-persons/get-persons.mjs index fdb90afd55067..855ac336544ab 100644 --- a/components/posthog/actions/get-persons/get-persons.mjs +++ b/components/posthog/actions/get-persons/get-persons.mjs @@ -4,7 +4,7 @@ export default { key: "posthog-get-persons", name: "Get Persons", description: "Retrieve a list of persons. [See the documentation](https://posthog.com/docs/api/persons#get-api-projects-project_id-persons)", - version: "0.0.4", + version: "0.0.5", annotations: { destructiveHint: false, openWorldHint: true, diff --git a/components/posthog/actions/get-project-insight/get-project-insight.mjs b/components/posthog/actions/get-project-insight/get-project-insight.mjs new file mode 100644 index 0000000000000..ac33ca853798b --- /dev/null +++ b/components/posthog/actions/get-project-insight/get-project-insight.mjs @@ -0,0 +1,85 @@ +import posthog from "../../posthog.app.mjs"; + +export default { + key: "posthog-get-project-insight", + name: "Get Project Insight", + description: "Retrieve a specific insight from a project. [See the documentation](https://posthog.com/docs/api/insights#get-api-projects-project_id-insights-id)", + version: "0.0.1", + annotations: { + destructiveHint: false, + openWorldHint: true, + readOnlyHint: true, + idempotentHint: true, + }, + type: "action", + props: { + posthog, + organizationId: { + propDefinition: [ + posthog, + "organizationId", + ], + }, + projectId: { + propDefinition: [ + posthog, + "projectId", + (c) => ({ + organizationId: c.organizationId, + }), + ], + }, + insightId: { + propDefinition: [ + posthog, + "insightId", + (c) => ({ + projectId: c.projectId, + }), + ], + }, + fromDashboard: { + type: "string", + label: "From Dashboard", + description: "Only if loading an insight in the context of a dashboard: The relevant dashboard's ID. When set, the specified dashboard's filters and date range override will be applied.", + optional: true, + }, + refresh: { + type: "string", + label: "Refresh", + description: "Whether to refresh the insight. Options: `force_cache`, `blocking`, `async`, `lazy_async`, `force_blocking`, `force_async`", + optional: true, + default: "force_cache", + options: [ + "force_cache", + "blocking", + "async", + "lazy_async", + "force_blocking", + "force_async", + ], + }, + }, + async run({ $ }) { + const { + posthog, + projectId, + insightId, + fromDashboard, + refresh, + } = this; + + const insight = await posthog.getInsight({ + $, + projectId, + insightId, + params: { + from_dashboard: fromDashboard, + refresh, + }, + }); + + $.export("$summary", `Successfully retrieved insight with ID ${insightId}`); + return insight; + }, +}; diff --git a/components/posthog/actions/get-surveys/get-surveys.mjs b/components/posthog/actions/get-surveys/get-surveys.mjs index d4b4bfed56c26..ffdb6a96e05b7 100644 --- a/components/posthog/actions/get-surveys/get-surveys.mjs +++ b/components/posthog/actions/get-surveys/get-surveys.mjs @@ -4,7 +4,7 @@ export default { key: "posthog-get-surveys", name: "Get Surveys", description: "Retrieve a list of surveys. [See the documentation](https://posthog.com/docs/api/surveys#get-api-projects-project_id-surveys)", - version: "0.0.4", + version: "0.0.5", annotations: { destructiveHint: false, openWorldHint: true, diff --git a/components/posthog/actions/list-project-insights/list-project-insights.mjs b/components/posthog/actions/list-project-insights/list-project-insights.mjs new file mode 100644 index 0000000000000..ae948c61ebe23 --- /dev/null +++ b/components/posthog/actions/list-project-insights/list-project-insights.mjs @@ -0,0 +1,81 @@ +import posthog from "../../posthog.app.mjs"; +import constants from "../../common/constants.mjs"; + +export default { + key: "posthog-list-project-insights", + name: "List Project Insights", + description: "Retrieve a list of insights for a project. [See the documentation](https://posthog.com/docs/api/insights#get-api-projects-project_id-insights)", + version: "0.0.1", + annotations: { + destructiveHint: false, + openWorldHint: true, + readOnlyHint: true, + idempotentHint: true, + }, + type: "action", + props: { + posthog, + organizationId: { + propDefinition: [ + posthog, + "organizationId", + ], + }, + projectId: { + propDefinition: [ + posthog, + "projectId", + (c) => ({ + organizationId: c.organizationId, + }), + ], + }, + basic: { + type: "boolean", + label: "Basic", + description: "Return basic insight metadata only (no results, faster).", + optional: true, + }, + refresh: { + type: "string", + label: "Refresh", + description: "Whether to refresh the retrieved insights. Options: `force_cache`, `blocking`, `async`, `lazy_async`, `force_blocking`, `force_async`. Default: `force_cache`", + optional: true, + default: "force_cache", + options: constants.REFRESH_OPTIONS, + }, + maxResults: { + propDefinition: [ + posthog, + "maxResults", + ], + }, + }, + async run({ $ }) { + const { + posthog, + projectId, + basic, + refresh, + maxResults, + } = this; + + const insights = await posthog.iterateResults({ + fn: posthog.listInsights, + args: { + $, + projectId, + params: { + basic, + refresh, + }, + }, + max: maxResults, + }); + + $.export("$summary", `Successfully retrieved ${insights.length} insight${insights.length === 1 + ? "" + : "s"}`); + return insights; + }, +}; diff --git a/components/posthog/actions/update-project-insight/update-project-insight.mjs b/components/posthog/actions/update-project-insight/update-project-insight.mjs new file mode 100644 index 0000000000000..9600c042c1311 --- /dev/null +++ b/components/posthog/actions/update-project-insight/update-project-insight.mjs @@ -0,0 +1,117 @@ +import posthog from "../../posthog.app.mjs"; +import utils from "../../common/utils.mjs"; + +export default { + key: "posthog-update-project-insight", + name: "Update Project Insight", + description: "Update an existing insight in a project. [See the documentation](https://posthog.com/docs/api/insights#patch-api-projects-project_id-insights-id)", + version: "0.0.1", + annotations: { + destructiveHint: false, + openWorldHint: true, + readOnlyHint: false, + idempotentHint: true, + }, + type: "action", + props: { + posthog, + organizationId: { + propDefinition: [ + posthog, + "organizationId", + ], + }, + projectId: { + propDefinition: [ + posthog, + "projectId", + (c) => ({ + organizationId: c.organizationId, + }), + ], + }, + insightId: { + propDefinition: [ + posthog, + "insightId", + (c) => ({ + projectId: c.projectId, + }), + ], + }, + name: { + type: "string", + label: "Name", + description: "Name of the insight", + optional: true, + }, + derivedName: { + type: "string", + label: "Derived Name", + description: "Derived name of the insight", + optional: true, + }, + query: { + propDefinition: [ + posthog, + "query", + ], + }, + description: { + type: "string", + label: "Description", + description: "Description of the insight", + optional: true, + }, + tags: { + type: "string[]", + label: "Tags", + description: "Tags for the insight", + optional: true, + }, + favorited: { + type: "boolean", + label: "Favorited", + description: "Whether the insight is favorited", + optional: true, + }, + deleted: { + type: "boolean", + label: "Deleted", + description: "Whether the insight is deleted", + optional: true, + }, + }, + async run({ $ }) { + const { + posthog, + projectId, + insightId, + name, + derivedName, + query, + description, + tags, + favorited, + deleted, + } = this; + + const insight = await posthog.updateInsight({ + $, + projectId, + insightId, + data: { + name, + derived_name: derivedName, + query: utils.parseJson(query), + description, + tags: utils.parseJson(tags), + favorited, + deleted, + }, + }); + + $.export("$summary", `Successfully updated insight with ID ${insightId}`); + return insight; + }, +}; diff --git a/components/posthog/common/constants.mjs b/components/posthog/common/constants.mjs index 6414d992bb568..4c0033de96db1 100644 --- a/components/posthog/common/constants.mjs +++ b/components/posthog/common/constants.mjs @@ -1,5 +1,14 @@ const DEFAULT_LIMIT = 50; +const REFRESH_OPTIONS = [ + "force_cache", + "blocking", + "async", + "lazy_async", + "force_blocking", + "force_async", +]; export default { DEFAULT_LIMIT, + REFRESH_OPTIONS, }; diff --git a/components/posthog/common/utils.mjs b/components/posthog/common/utils.mjs new file mode 100644 index 0000000000000..2adf04343104f --- /dev/null +++ b/components/posthog/common/utils.mjs @@ -0,0 +1,44 @@ +const parseJson = (input, maxDepth = 100) => { + const seen = new WeakSet(); + const parse = (value) => { + if (maxDepth <= 0) { + return value; + } + if (typeof(value) === "string") { + // Only parse if the string looks like a JSON object or array + const trimmed = value.trim(); + if ( + (trimmed.startsWith("{") && trimmed.endsWith("}")) || + (trimmed.startsWith("[") && trimmed.endsWith("]")) + ) { + try { + return parseJson(JSON.parse(value), maxDepth - 1); + } catch (e) { + return value; + } + } + return value; + } else if (typeof(value) === "object" && value !== null && !Array.isArray(value)) { + if (seen.has(value)) { + return value; + } + seen.add(value); + return Object.entries(value) + .reduce((acc, [ + key, + val, + ]) => Object.assign(acc, { + [key]: parse(val), + }), {}); + } else if (Array.isArray(value)) { + return value.map((item) => parse(item)); + } + return value; + }; + + return parse(input); +}; + +export default { + parseJson, +}; diff --git a/components/posthog/package.json b/components/posthog/package.json index 73212d81a0326..c9aec231d768b 100644 --- a/components/posthog/package.json +++ b/components/posthog/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/posthog", - "version": "0.2.1", + "version": "0.3.0", "description": "Pipedream PostHog Components", "main": "posthog.app.mjs", "keywords": [ diff --git a/components/posthog/posthog.app.mjs b/components/posthog/posthog.app.mjs index cc4f207e8c48a..dbdbcdf4346e5 100644 --- a/components/posthog/posthog.app.mjs +++ b/components/posthog/posthog.app.mjs @@ -67,6 +67,44 @@ export default { default: 100, optional: true, }, + insightId: { + type: "string", + label: "Insight ID", + description: "Identifier of an insight", + async options({ + projectId, page, + }) { + const limit = constants.DEFAULT_LIMIT; + const { results } = await this.listInsights({ + projectId, + params: { + limit, + offset: page * limit, + }, + }); + return results?.map(({ + id: value, name, derived_name, + }) => ({ + value, + label: name || derived_name || `Insight ${value}`, + })) || []; + }, + }, + query: { + type: "object", + label: "Query", + description: `Query object defining the insight. Must be a valid PostHog query object with format: \`{"kind": "InsightVizNode", "source": {...}}\`. + +**Common query types:** +- **TrendsQuery**: Time-series trend analysis. Example: \`{"kind": "InsightVizNode", "source": {"kind": "TrendsQuery", "series": [{"kind": "EventsNode", "event": "pageview", "name": "pageview", "math": "total"}], "trendsFilter": {}}}\` +- **FunnelsQuery**: Conversion funnel analysis. Example: \`{"kind": "InsightVizNode", "source": {"kind": "FunnelsQuery", "series": [{"kind": "EventsNode", "event": "signup_started"}, {"kind": "EventsNode", "event": "signup_completed"}], "funnelsFilter": {}}}\` +- **RetentionQuery**: User retention analysis +- **PathsQuery**: User path exploration +- **LifecycleQuery**: User lifecycle tracking + +See [PostHog Query API documentation](https://posthog.com/docs/api/queries) for complete query specifications.`, + optional: true, + }, }, methods: { _baseUrl(publicUrl = false) { @@ -138,6 +176,40 @@ export default { ...opts, }); }, + listInsights({ + projectId, ...opts + }) { + return this._makeRequest({ + path: `/api/projects/${projectId}/insights`, + ...opts, + }); + }, + getInsight({ + projectId, insightId, ...opts + }) { + return this._makeRequest({ + path: `/api/projects/${projectId}/insights/${insightId}`, + ...opts, + }); + }, + createInsight({ + projectId, ...opts + }) { + return this._makeRequest({ + method: "POST", + path: `/api/projects/${projectId}/insights`, + ...opts, + }); + }, + updateInsight({ + projectId, insightId, ...opts + }) { + return this._makeRequest({ + method: "PATCH", + path: `/api/projects/${projectId}/insights/${insightId}`, + ...opts, + }); + }, createQuery({ projectId, ...opts }) { diff --git a/components/posthog/sources/new-action-performed/new-action-performed.mjs b/components/posthog/sources/new-action-performed/new-action-performed.mjs index aa4dca4d86bcd..80189f1c4afd0 100644 --- a/components/posthog/sources/new-action-performed/new-action-performed.mjs +++ b/components/posthog/sources/new-action-performed/new-action-performed.mjs @@ -5,7 +5,7 @@ export default { key: "posthog-new-action-performed", name: "New Action Performed", description: "Emit new event when an action is performed in a project. [See the documentation](https://posthog.com/docs/api/query#post-api-projects-project_id-query)", - version: "0.0.3", + version: "0.0.4", type: "source", dedupe: "unique", props: {