diff --git a/plugins/PostHog/v1/configValidation.json b/plugins/PostHog/v1/configValidation.json new file mode 100644 index 00000000..d7228d9f --- /dev/null +++ b/plugins/PostHog/v1/configValidation.json @@ -0,0 +1,13 @@ +{ + "steps": [ + { + "displayName": "Authenticate", + "dataStream": { + "name": "currentUser" + }, + "required": true, + "error": "Could not authenticate with PostHog. Check that your personal API key is valid, has not expired, and that the correct region is selected.", + "success": "Connected to PostHog successfully." + } + ] +} \ No newline at end of file diff --git a/plugins/PostHog/v1/custom_types.json b/plugins/PostHog/v1/custom_types.json new file mode 100644 index 00000000..2eefe5ed --- /dev/null +++ b/plugins/PostHog/v1/custom_types.json @@ -0,0 +1,30 @@ +[ + { + "name": "PostHog Organization", + "sourceType": "PostHog Organization", + "icon": "building", + "singular": "Organization", + "plural": "Organizations" + }, + { + "name": "PostHog Project", + "sourceType": "PostHog Project", + "icon": "cube", + "singular": "Project", + "plural": "Projects" + }, + { + "name": "PostHog Member", + "sourceType": "PostHog Member", + "icon": "user", + "singular": "Member", + "plural": "Members" + }, + { + "name": "PostHog Feature Flag", + "sourceType": "PostHog Feature Flag", + "icon": "flag", + "singular": "Feature Flag", + "plural": "Feature Flags" + } +] \ No newline at end of file diff --git a/plugins/PostHog/v1/dataStreams/activeHours.json b/plugins/PostHog/v1/dataStreams/activeHours.json new file mode 100644 index 00000000..e4eb774f --- /dev/null +++ b/plugins/PostHog/v1/dataStreams/activeHours.json @@ -0,0 +1,68 @@ +{ + "name": "activeHours", + "displayName": "Active Hours", + "description": "Event activity by day of week and hour of day for a project", + "tags": [ + "Web Analytics" + ], + "baseDataSourceName": "httpRequestScopedSingle", + "config": { + "httpMethod": "post", + "endpointPath": "api/projects/{{object.rawId}}/query/", + "postBody": { + "query": { + "kind": "HogQLQuery", + "query": "SELECT toDayOfWeek(timestamp) AS day_of_week, toHour(timestamp) AS hour_of_day, count() AS events FROM events WHERE event = '$pageview' AND timestamp >= toDateTime('{{timeframe.start}}') AND timestamp < toDateTime('{{timeframe.end}}') GROUP BY day_of_week, hour_of_day ORDER BY day_of_week, hour_of_day" + } + }, + "postRequestScript": "activeHours.js" + }, + "matches": { + "sourceType": { + "type": "oneOf", + "values": [ + "PostHog Project" + ] + } + }, + "metadata": [ + { + "name": "day_name", + "displayName": "Day", + "shape": "string", + "role": "label" + }, + { + "name": "day_of_week", + "displayName": "Day of Week", + "shape": "number", + "visible": false + }, + { + "name": "hour_of_day", + "displayName": "Hour", + "shape": "number" + }, + { + "name": "events", + "displayName": "Events", + "shape": "number", + "role": "value" + } + ], + "timeframes": true, + "defaultShaping": { + "sort": { + "by": [ + [ + "day_of_week", + "asc" + ], + [ + "hour_of_day", + "asc" + ] + ] + } + } +} \ No newline at end of file diff --git a/plugins/PostHog/v1/dataStreams/activeUsers.json b/plugins/PostHog/v1/dataStreams/activeUsers.json new file mode 100644 index 00000000..23d2302d --- /dev/null +++ b/plugins/PostHog/v1/dataStreams/activeUsers.json @@ -0,0 +1,53 @@ +{ + "name": "activeUsers", + "displayName": "Active Users", + "description": "Number of unique active users per day for a project", + "tags": [ + "Users", + "Analytics" + ], + "baseDataSourceName": "httpRequestScopedSingle", + "config": { + "httpMethod": "post", + "endpointPath": "api/projects/{{object.rawId}}/query/", + "postBody": { + "query": { + "kind": "HogQLQuery", + "query": "SELECT toStartOfDay(timestamp) AS day, count(DISTINCT person_id) AS active_users FROM events WHERE timestamp >= toDateTime('{{timeframe.start}}') AND timestamp < toDateTime('{{timeframe.end}}') GROUP BY day ORDER BY day" + } + }, + "postRequestScript": "activeUsers.js" + }, + "matches": { + "sourceType": { + "type": "oneOf", + "values": [ + "PostHog Project" + ] + } + }, + "metadata": [ + { + "name": "day", + "displayName": "Day", + "shape": "date", + "role": "timestamp" + }, + { + "name": "active_users", + "displayName": "Active Users", + "shape": "number", + "role": "value" + } + ], + "timeframes": [ + "last7days", + "last30days", + "thisMonth", + "lastMonth", + "thisQuarter", + "thisYear", + "lastQuarter", + "lastYear" + ] +} \ No newline at end of file diff --git a/plugins/PostHog/v1/dataStreams/currentUser.json b/plugins/PostHog/v1/dataStreams/currentUser.json new file mode 100644 index 00000000..3840618f --- /dev/null +++ b/plugins/PostHog/v1/dataStreams/currentUser.json @@ -0,0 +1,40 @@ +{ + "name": "currentUser", + "displayName": "Current User", + "description": "Returns the authenticated PostHog user", + "tags": [ + "User" + ], + "baseDataSourceName": "httpRequestUnscoped", + "config": { + "httpMethod": "get", + "endpointPath": "api/users/@me/", + "postRequestScript": "currentUser.js" + }, + "matches": "none", + "metadata": [ + { + "name": "uuid", + "displayName": "ID", + "visible": false + }, + { + "name": "email", + "displayName": "Email", + "role": "label" + }, + { + "name": "first_name", + "displayName": "First name" + }, + { + "name": "distinct_id", + "displayName": "Distinct ID", + "visible": false + } + ], + "timeframes": false, + "visibility": { + "type": "hidden" + } +} \ No newline at end of file diff --git a/plugins/PostHog/v1/dataStreams/eventBreakdown.json b/plugins/PostHog/v1/dataStreams/eventBreakdown.json new file mode 100644 index 00000000..80413a83 --- /dev/null +++ b/plugins/PostHog/v1/dataStreams/eventBreakdown.json @@ -0,0 +1,55 @@ +{ + "name": "eventBreakdown", + "displayName": "Event Breakdown", + "description": "Top events by count within the selected timeframe for a project", + "tags": [ + "Events", + "Analytics" + ], + "baseDataSourceName": "httpRequestScopedSingle", + "config": { + "httpMethod": "post", + "endpointPath": "api/projects/{{object.rawId}}/query/", + "postBody": { + "query": { + "kind": "HogQLQuery", + "query": "SELECT event AS event_name, count() AS count FROM events WHERE timestamp >= toDateTime('{{timeframe.start}}') AND timestamp < toDateTime('{{timeframe.end}}') GROUP BY event ORDER BY count DESC LIMIT 25" + } + }, + "postRequestScript": "eventBreakdown.js" + }, + "matches": { + "sourceType": { + "type": "oneOf", + "values": [ + "PostHog Project" + ] + } + }, + "metadata": [ + { + "name": "event_name", + "displayName": "Event", + "shape": "string", + "role": "label" + }, + { + "name": "count", + "displayName": "Count", + "shape": "number", + "role": "value" + } + ], + "timeframes": true, + "defaultTimeframe": "dashboard", + "defaultShaping": { + "sort": { + "by": [ + [ + "count", + "desc" + ] + ] + } + } +} \ No newline at end of file diff --git a/plugins/PostHog/v1/dataStreams/eventVolume.json b/plugins/PostHog/v1/dataStreams/eventVolume.json new file mode 100644 index 00000000..53838e34 --- /dev/null +++ b/plugins/PostHog/v1/dataStreams/eventVolume.json @@ -0,0 +1,58 @@ +{ + "name": "eventVolume", + "displayName": "Event Volume", + "description": "Event count over time for a project, optionally filtered to a single event name", + "tags": [ + "Events", + "Analytics" + ], + "baseDataSourceName": "httpRequestScopedSingle", + "config": { + "httpMethod": "post", + "endpointPath": "api/projects/{{object.rawId}}/query/", + "postBody": { + "query": { + "kind": "HogQLQuery", + "query": "SELECT toStartOfHour(timestamp) AS time, count() AS events FROM events WHERE timestamp >= toDateTime('{{timeframe.start}}') AND timestamp < toDateTime('{{timeframe.end}}') {{event ? \"AND event = '\" + event.split(\"'\").join(\"''\") + \"'\" : ''}} GROUP BY time ORDER BY time" + } + }, + "postRequestScript": "eventVolume.js" + }, + "matches": { + "sourceType": { + "type": "oneOf", + "values": [ + "PostHog Project" + ] + } + }, + "ui": [ + { + "type": "text", + "name": "event", + "label": "Event name (optional)", + "help": "Filter to a specific event name, e.g. $pageview. Leave blank to count all events." + } + ], + "metadata": [ + { + "name": "time", + "displayName": "Time", + "shape": "date", + "role": "timestamp" + }, + { + "name": "events", + "displayName": "Events", + "shape": "number", + "role": "value" + } + ], + "timeframes": [ + "last1hour", + "last12hours", + "last24hours", + "last7days", + "last30days" + ] +} diff --git a/plugins/PostHog/v1/dataStreams/featureFlags.json b/plugins/PostHog/v1/dataStreams/featureFlags.json new file mode 100644 index 00000000..1febacb9 --- /dev/null +++ b/plugins/PostHog/v1/dataStreams/featureFlags.json @@ -0,0 +1,72 @@ +{ + "name": "featureFlags", + "displayName": "Feature Flags", + "description": "Current state of feature flags in a PostHog project", + "tags": [ + "Feature Flags" + ], + "baseDataSourceName": "httpRequestScopedSingle", + "config": { + "httpMethod": "get", + "endpointPath": "api/projects/{{object.rawId}}/feature_flags/", + "paging": { + "mode": "nextUrl", + "pageSize": { + "realm": "queryArg", + "path": "limit", + "value": "100" + }, + "in": { + "realm": "webLink", + "path": "next" + } + }, + "pathToData": "results", + "postRequestScript": "featureFlags.js" + }, + "matches": { + "sourceType": { + "type": "oneOf", + "values": [ + "PostHog Project" + ] + } + }, + "metadata": [ + { + "name": "id", + "displayName": "ID", + "visible": false + }, + { + "name": "key", + "displayName": "Key", + "role": "label" + }, + { + "name": "description", + "displayName": "Description" + }, + { + "name": "active", + "displayName": "Active", + "shape": "boolean" + }, + { + "name": "created_at", + "displayName": "Created", + "shape": "date" + }, + { + "name": "last_called_at", + "displayName": "Last evaluated", + "shape": "date" + }, + { + "name": "project", + "displayName": "Project ID", + "visible": false + } + ], + "timeframes": false +} diff --git a/plugins/PostHog/v1/dataStreams/flagEvaluations.json b/plugins/PostHog/v1/dataStreams/flagEvaluations.json new file mode 100644 index 00000000..958a74b8 --- /dev/null +++ b/plugins/PostHog/v1/dataStreams/flagEvaluations.json @@ -0,0 +1,53 @@ +{ + "name": "flagEvaluations", + "displayName": "Flag Evaluations", + "description": "Evaluation volume over time for a feature flag", + "tags": [ + "Feature Flags", + "Analytics" + ], + "baseDataSourceName": "httpRequestScopedSingle", + "config": { + "httpMethod": "post", + "endpointPath": "api/projects/{{object.project}}/query/", + "postBody": { + "query": { + "kind": "HogQLQuery", + "query": "SELECT toStartOfDay(timestamp) AS day, count() AS evaluations FROM events WHERE event = '$feature_flag_called' AND properties.$feature_flag = '{{object.name.split(\"'\").join(\"''\")}}' AND timestamp >= toDateTime('{{timeframe.start}}') AND timestamp < toDateTime('{{timeframe.end}}') GROUP BY day ORDER BY day" + } + }, + "postRequestScript": "flagEvaluations.js" + }, + "matches": { + "sourceType": { + "type": "oneOf", + "values": [ + "PostHog Feature Flag" + ] + } + }, + "metadata": [ + { + "name": "day", + "displayName": "Day", + "shape": "date", + "role": "timestamp" + }, + { + "name": "evaluations", + "displayName": "Evaluations", + "shape": "number", + "role": "value" + } + ], + "timeframes": [ + "last7days", + "last30days", + "thisMonth", + "lastMonth", + "thisQuarter", + "thisYear", + "lastQuarter", + "lastYear" + ] +} diff --git a/plugins/PostHog/v1/dataStreams/flagVariants.json b/plugins/PostHog/v1/dataStreams/flagVariants.json new file mode 100644 index 00000000..b753b2c0 --- /dev/null +++ b/plugins/PostHog/v1/dataStreams/flagVariants.json @@ -0,0 +1,54 @@ +{ + "name": "flagVariants", + "displayName": "Flag Variants", + "description": "Distribution of variants served for a feature flag", + "tags": [ + "Feature Flags", + "Analytics" + ], + "baseDataSourceName": "httpRequestScopedSingle", + "config": { + "httpMethod": "post", + "endpointPath": "api/projects/{{object.project}}/query/", + "postBody": { + "query": { + "kind": "HogQLQuery", + "query": "SELECT properties.$feature_flag_response AS variant, count() AS evaluations FROM events WHERE event = '$feature_flag_called' AND properties.$feature_flag = '{{object.name.split(\"'\").join(\"''\")}}' AND timestamp >= toDateTime('{{timeframe.start}}') AND timestamp < toDateTime('{{timeframe.end}}') GROUP BY variant ORDER BY evaluations DESC" + } + }, + "postRequestScript": "flagVariants.js" + }, + "matches": { + "sourceType": { + "type": "oneOf", + "values": [ + "PostHog Feature Flag" + ] + } + }, + "metadata": [ + { + "name": "variant", + "displayName": "Variant", + "shape": "string", + "role": "label" + }, + { + "name": "evaluations", + "displayName": "Evaluations", + "shape": "number", + "role": "value" + } + ], + "timeframes": true, + "defaultShaping": { + "sort": { + "by": [ + [ + "evaluations", + "desc" + ] + ] + } + } +} diff --git a/plugins/PostHog/v1/dataStreams/frustratingPages.json b/plugins/PostHog/v1/dataStreams/frustratingPages.json new file mode 100644 index 00000000..5941ea3f --- /dev/null +++ b/plugins/PostHog/v1/dataStreams/frustratingPages.json @@ -0,0 +1,63 @@ +{ + "name": "frustratingPages", + "displayName": "Frustrating Pages", + "description": "Per-page counts of rage clicks, dead clicks and errors for a project", + "tags": [ + "Web Analytics" + ], + "baseDataSourceName": "httpRequestScopedSingle", + "config": { + "httpMethod": "post", + "endpointPath": "api/projects/{{object.rawId}}/query/", + "postBody": { + "query": { + "kind": "HogQLQuery", + "query": "SELECT properties.$pathname AS path, countIf(event = '$rageclick') AS rage_clicks, countIf(event = '$dead_click') AS dead_clicks, countIf(event = '$exception') AS errors FROM events WHERE event IN ('$rageclick', '$dead_click', '$exception') AND timestamp >= toDateTime('{{timeframe.start}}') AND timestamp < toDateTime('{{timeframe.end}}') GROUP BY path ORDER BY rage_clicks DESC LIMIT 50" + } + }, + "postRequestScript": "frustratingPages.js" + }, + "matches": { + "sourceType": { + "type": "oneOf", + "values": [ + "PostHog Project" + ] + } + }, + "metadata": [ + { + "name": "path", + "displayName": "Path", + "shape": "string", + "role": "label" + }, + { + "name": "rage_clicks", + "displayName": "Rage Clicks", + "shape": "number", + "role": "value" + }, + { + "name": "dead_clicks", + "displayName": "Dead Clicks", + "shape": "number" + }, + { + "name": "errors", + "displayName": "Errors", + "shape": "number" + } + ], + "timeframes": true, + "defaultShaping": { + "sort": { + "by": [ + [ + "rage_clicks", + "desc" + ] + ] + } + } +} \ No newline at end of file diff --git a/plugins/PostHog/v1/dataStreams/hogqlQuery.json b/plugins/PostHog/v1/dataStreams/hogqlQuery.json new file mode 100644 index 00000000..15f4f41a --- /dev/null +++ b/plugins/PostHog/v1/dataStreams/hogqlQuery.json @@ -0,0 +1,51 @@ +{ + "name": "hogqlQuery", + "displayName": "HogQL Query", + "description": "Result rows of an arbitrary user-supplied HogQL query run against a project", + "tags": [ + "HogQL", + "Analytics" + ], + "baseDataSourceName": "httpRequestScopedSingle", + "config": { + "httpMethod": "post", + "endpointPath": "api/projects/{{object.rawId}}/query/", + "postBody": { + "query": { + "kind": "HogQLQuery", + "query": "{{query}}", + "filters": { + "dateRange": { + "date_from": "{{timeframe.start}}", + "date_to": "{{timeframe.end}}" + } + } + } + }, + "postRequestScript": "hogqlQuery.js" + }, + "matches": { + "sourceType": { + "type": "oneOf", + "values": [ + "PostHog Project" + ] + } + }, + "manualConfigApply": true, + "ui": [ + { + "type": "code", + "name": "query", + "label": "HogQL query", + "language": "sql", + "help": "A HogQL SELECT query. Optionally include the literal `{filters}` token where you want the tile's timeframe applied, e.g. `SELECT count() FROM events WHERE {filters}`." + } + ], + "metadata": [ + { + "pattern": ".*" + } + ], + "timeframes": true +} \ No newline at end of file diff --git a/plugins/PostHog/v1/dataStreams/members.json b/plugins/PostHog/v1/dataStreams/members.json new file mode 100644 index 00000000..d7137aa4 --- /dev/null +++ b/plugins/PostHog/v1/dataStreams/members.json @@ -0,0 +1,72 @@ +{ + "name": "members", + "displayName": "Members", + "description": "The roster of members for a PostHog organization", + "tags": [ + "User" + ], + "baseDataSourceName": "httpRequestScopedSingle", + "config": { + "httpMethod": "get", + "endpointPath": "api/organizations/{{object.rawId}}/members/", + "paging": { + "mode": "nextUrl", + "pageSize": { + "realm": "queryArg", + "path": "limit", + "value": "100" + }, + "in": { + "realm": "webLink", + "path": "next" + } + }, + "pathToData": "results", + "postRequestScript": "members.js" + }, + "matches": { + "sourceType": { + "type": "oneOf", + "values": [ + "PostHog Organization" + ] + } + }, + "metadata": [ + { + "name": "id", + "displayName": "ID", + "visible": false + }, + { + "name": "name", + "displayName": "Name", + "role": "label" + }, + { + "name": "email", + "displayName": "Email" + }, + { + "name": "role", + "displayName": "Role", + "role": "value" + }, + { + "name": "last_login", + "displayName": "Last Login", + "shape": "date" + }, + { + "name": "is_2fa_enabled", + "displayName": "2FA", + "shape": "boolean" + }, + { + "name": "organization", + "displayName": "Organization ID", + "visible": false + } + ], + "timeframes": false +} diff --git a/plugins/PostHog/v1/dataStreams/organizations.json b/plugins/PostHog/v1/dataStreams/organizations.json new file mode 100644 index 00000000..65eb2488 --- /dev/null +++ b/plugins/PostHog/v1/dataStreams/organizations.json @@ -0,0 +1,53 @@ +{ + "name": "organizations", + "displayName": "Organizations", + "description": "Lists PostHog organizations accessible to the API key", + "tags": [ + "Organization" + ], + "baseDataSourceName": "httpRequestUnscoped", + "config": { + "httpMethod": "get", + "endpointPath": "api/organizations/", + "pathToData": "results", + "paging": { + "mode": "nextUrl", + "pageSize": { + "realm": "queryArg", + "path": "limit", + "value": "100" + }, + "in": { + "realm": "payload", + "path": "next" + } + } + }, + "matches": "none", + "metadata": [ + { + "name": "id", + "displayName": "ID", + "visible": false + }, + { + "name": "name", + "displayName": "Name", + "role": "label" + }, + { + "name": "member_count", + "displayName": "Members", + "shape": "number" + }, + { + "name": "created_at", + "displayName": "Created", + "shape": "date" + } + ], + "timeframes": false, + "visibility": { + "type": "hidden" + } +} \ No newline at end of file diff --git a/plugins/PostHog/v1/dataStreams/projects.json b/plugins/PostHog/v1/dataStreams/projects.json new file mode 100644 index 00000000..c6542be1 --- /dev/null +++ b/plugins/PostHog/v1/dataStreams/projects.json @@ -0,0 +1,49 @@ +{ + "name": "projects", + "displayName": "Projects", + "description": "Lists the projects belonging to a PostHog organization", + "tags": [ + "Project" + ], + "baseDataSourceName": "httpRequestScopedSingle", + "config": { + "httpMethod": "get", + "endpointPath": "api/organizations/{{object.rawId}}/projects/", + "pathToData": "results" + }, + "matches": { + "sourceType": { + "type": "oneOf", + "values": [ + "PostHog Organization" + ] + } + }, + "metadata": [ + { + "name": "id", + "displayName": "ID", + "visible": false + }, + { + "name": "name", + "displayName": "Name", + "role": "label" + }, + { + "name": "timezone", + "displayName": "Timezone" + }, + { + "name": "organization", + "displayName": "Organization ID", + "visible": false + }, + { + "name": "ingested_event", + "displayName": "Events ingested", + "shape": "boolean" + } + ], + "timeframes": false +} \ No newline at end of file diff --git a/plugins/PostHog/v1/dataStreams/scripts/activeHours.js b/plugins/PostHog/v1/dataStreams/scripts/activeHours.js new file mode 100644 index 00000000..cd3fb100 --- /dev/null +++ b/plugins/PostHog/v1/dataStreams/scripts/activeHours.js @@ -0,0 +1,26 @@ +// api/projects/{id}/query/ returns COLUMNAR JSON for HogQLQuery results: +// { results: [[v1,v2,v3],...], columns: ["day_of_week","hour_of_day","events"], types: [...] } +// This is not an object array, so it must be zipped into row objects here. +const DAY_NAMES = { + 1: "Mon", + 2: "Tue", + 3: "Wed", + 4: "Thu", + 5: "Fri", + 6: "Sat", + 7: "Sun", +}; + +const cols = data.columns || []; +result = (data.results || []).map((r) => { + const row = Object.fromEntries( + cols.map((c, i) => [ + c, + ["day_of_week", "hour_of_day", "events"].includes(c) + ? Number(r[i]) + : r[i], + ]), + ); + row.day_name = DAY_NAMES[row.day_of_week] || String(row.day_of_week); + return row; +}); diff --git a/plugins/PostHog/v1/dataStreams/scripts/activeUsers.js b/plugins/PostHog/v1/dataStreams/scripts/activeUsers.js new file mode 100644 index 00000000..013bcddc --- /dev/null +++ b/plugins/PostHog/v1/dataStreams/scripts/activeUsers.js @@ -0,0 +1,9 @@ +// api/projects/{id}/query/ returns COLUMNAR JSON for HogQLQuery results: +// { results: [[v1,v2],...], columns: ["day","active_users"], types: [...] } +// This is not an object array, so it must be zipped into row objects here. +const cols = data.columns || []; +result = (data.results || []).map((r) => + Object.fromEntries( + cols.map((c, i) => [c, c === "active_users" ? Number(r[i]) : r[i]]), + ), +); diff --git a/plugins/PostHog/v1/dataStreams/scripts/currentUser.js b/plugins/PostHog/v1/dataStreams/scripts/currentUser.js new file mode 100644 index 00000000..aee9ca56 --- /dev/null +++ b/plugins/PostHog/v1/dataStreams/scripts/currentUser.js @@ -0,0 +1,3 @@ +// api/users/@me/ returns the authenticated user object at the root of the +// response body. Wrap it in an array so it becomes a single row. +result = data ? [data] : []; diff --git a/plugins/PostHog/v1/dataStreams/scripts/eventBreakdown.js b/plugins/PostHog/v1/dataStreams/scripts/eventBreakdown.js new file mode 100644 index 00000000..cc15a7d1 --- /dev/null +++ b/plugins/PostHog/v1/dataStreams/scripts/eventBreakdown.js @@ -0,0 +1,9 @@ +// api/projects/{id}/query/ returns COLUMNAR JSON for HogQLQuery results: +// { results: [[v1,v2],...], columns: ["event_name","count"], types: [...] } +// This is not an object array, so it must be zipped into row objects here. +const cols = data.columns || []; +result = (data.results || []).map((r) => + Object.fromEntries( + cols.map((c, i) => [c, c === "count" ? Number(r[i]) : r[i]]), + ), +); diff --git a/plugins/PostHog/v1/dataStreams/scripts/eventVolume.js b/plugins/PostHog/v1/dataStreams/scripts/eventVolume.js new file mode 100644 index 00000000..d6069ab2 --- /dev/null +++ b/plugins/PostHog/v1/dataStreams/scripts/eventVolume.js @@ -0,0 +1,9 @@ +// api/projects/{id}/query/ returns COLUMNAR JSON for HogQLQuery results: +// { results: [[v1,v2],...], columns: ["time","events"], types: [...] } +// This is not an object array, so it must be zipped into row objects here. +const cols = data.columns || []; +result = (data.results || []).map((r) => + Object.fromEntries( + cols.map((c, i) => [c, c === "events" ? Number(r[i]) : r[i]]), + ), +); diff --git a/plugins/PostHog/v1/dataStreams/scripts/featureFlags.js b/plugins/PostHog/v1/dataStreams/scripts/featureFlags.js new file mode 100644 index 00000000..83229e30 --- /dev/null +++ b/plugins/PostHog/v1/dataStreams/scripts/featureFlags.js @@ -0,0 +1,19 @@ +// api/projects/{id}/feature_flags/ → { count, next, previous, results: [...] }. +// Keep live flags only (drop deleted and archived), and carry the scoping +// project's id onto each row so flag objects link to their project and +// flag-scoped streams know which project to query. +const unwrap = (v) => (Array.isArray(v) ? v[0] : v); +const projectId = unwrap( + context.objects && context.objects[0] && context.objects[0].rawId, +); +result = (data.results || []) + .filter((f) => !f.deleted && !f.archived) + .map((f) => ({ + id: f.id, + key: f.key, + description: f.name || "", + active: !!f.active, + created_at: f.created_at, + last_called_at: f.last_called_at, + project: projectId, + })); diff --git a/plugins/PostHog/v1/dataStreams/scripts/flagEvaluations.js b/plugins/PostHog/v1/dataStreams/scripts/flagEvaluations.js new file mode 100644 index 00000000..e84daa9f --- /dev/null +++ b/plugins/PostHog/v1/dataStreams/scripts/flagEvaluations.js @@ -0,0 +1,9 @@ +// api/projects/{id}/query/ returns COLUMNAR JSON for HogQLQuery results: +// { results: [[v1,v2],...], columns: ["day","evaluations"], types: [...] } +// This is not an object array, so it must be zipped into row objects here. +const cols = data.columns || []; +result = (data.results || []).map((r) => + Object.fromEntries( + cols.map((c, i) => [c, c === "evaluations" ? Number(r[i]) : r[i]]), + ), +); diff --git a/plugins/PostHog/v1/dataStreams/scripts/flagVariants.js b/plugins/PostHog/v1/dataStreams/scripts/flagVariants.js new file mode 100644 index 00000000..299a672b --- /dev/null +++ b/plugins/PostHog/v1/dataStreams/scripts/flagVariants.js @@ -0,0 +1,9 @@ +// api/projects/{id}/query/ returns COLUMNAR JSON for HogQLQuery results: +// { results: [[v1,v2],...], columns: ["variant","evaluations"], types: [...] } +// This is not an object array, so it must be zipped into row objects here. +const cols = data.columns || []; +result = (data.results || []).map((r) => + Object.fromEntries( + cols.map((c, i) => [c, c === "evaluations" ? Number(r[i]) : r[i]]), + ), +); diff --git a/plugins/PostHog/v1/dataStreams/scripts/frustratingPages.js b/plugins/PostHog/v1/dataStreams/scripts/frustratingPages.js new file mode 100644 index 00000000..185d5a65 --- /dev/null +++ b/plugins/PostHog/v1/dataStreams/scripts/frustratingPages.js @@ -0,0 +1,10 @@ +// api/projects/{id}/query/ returns COLUMNAR JSON for HogQLQuery results: +// { results: [[v1,v2,v3,v4],...], columns: ["path","rage_clicks","dead_clicks","errors"], types: [...] } +// This is not an object array, so it must be zipped into row objects here. +const cols = data.columns || []; +const numericCols = new Set(["rage_clicks", "dead_clicks", "errors"]); +result = (data.results || []).map((r) => + Object.fromEntries( + cols.map((c, i) => [c, numericCols.has(c) ? Number(r[i]) : r[i]]), + ), +); diff --git a/plugins/PostHog/v1/dataStreams/scripts/hogqlQuery.js b/plugins/PostHog/v1/dataStreams/scripts/hogqlQuery.js new file mode 100644 index 00000000..e7ea8572 --- /dev/null +++ b/plugins/PostHog/v1/dataStreams/scripts/hogqlQuery.js @@ -0,0 +1,21 @@ +// api/projects/{id}/query/ returns COLUMNAR JSON for HogQLQuery results: +// { results: [[v1,v2],...], columns: [...], types: [...] } +// Columns are arbitrary/user-defined (query is user-supplied), so they must be +// zipped into row objects dynamically here rather than declared statically. +const cols = data.columns || []; +// HogQL is user-supplied, so it can return duplicate column names (e.g. +// `SELECT count(), count()`). Object.fromEntries would silently drop all but +// the last, so disambiguate repeats before zipping cells into row objects. +const seen = new Set(); +const uniqueCols = cols.map((col) => { + let name = col; + let suffix = 2; + while (seen.has(name)) { + name = `${col}_${suffix++}`; + } + seen.add(name); + return name; +}); +result = (data.results || []).map((r) => + Object.fromEntries(uniqueCols.map((c, i) => [c, r[i]])), +); diff --git a/plugins/PostHog/v1/dataStreams/scripts/members.js b/plugins/PostHog/v1/dataStreams/scripts/members.js new file mode 100644 index 00000000..d5b71eae --- /dev/null +++ b/plugins/PostHog/v1/dataStreams/scripts/members.js @@ -0,0 +1,24 @@ +// api/organizations/{id}/members/ returns { count, next, previous, results: [...] }. +// Each result nests the user under `user` and level under `level` — flatten, +// compose a display name, and map the numeric level to a role label. +// The endpoint doesn't return the parent org id, but this stream is always +// scoped to one organization — carry that org's id onto each row so the import +// step can link members to their organization. +const ROLE = { 1: "Member", 8: "Admin", 15: "Owner" }; +const unwrap = (v) => (Array.isArray(v) ? v[0] : v); +const orgId = unwrap( + context.objects && context.objects[0] && context.objects[0].rawId, +); +result = (data.results || []).map((m) => { + const u = m.user || {}; + const full = [u.first_name, u.last_name].filter(Boolean).join(" ").trim(); + return { + id: m.id, + name: full || u.email, + email: u.email, + role: ROLE[m.level] || "Member", + last_login: m.last_login, + is_2fa_enabled: !!m.is_2fa_enabled, + organization: orgId, + }; +}); diff --git a/plugins/PostHog/v1/dataStreams/scripts/webBreakdown.js b/plugins/PostHog/v1/dataStreams/scripts/webBreakdown.js new file mode 100644 index 00000000..87198a5a --- /dev/null +++ b/plugins/PostHog/v1/dataStreams/scripts/webBreakdown.js @@ -0,0 +1,12 @@ +// api/projects/{id}/query/ returns COLUMNAR JSON for HogQLQuery results: +// { results: [[v1,v2,v3],...], columns: ["dimension","visitors","views"], types: [...] } +// This is not an object array, so it must be zipped into row objects here. +const cols = data.columns || []; +result = (data.results || []).map((r) => + Object.fromEntries( + cols.map((c, i) => [ + c, + c === "visitors" || c === "views" ? Number(r[i]) : r[i], + ]), + ), +); diff --git a/plugins/PostHog/v1/dataStreams/scripts/webPaths.js b/plugins/PostHog/v1/dataStreams/scripts/webPaths.js new file mode 100644 index 00000000..4a801898 --- /dev/null +++ b/plugins/PostHog/v1/dataStreams/scripts/webPaths.js @@ -0,0 +1,12 @@ +// api/projects/{id}/query/ returns COLUMNAR JSON for HogQLQuery results: +// { results: [[v1,v2,v3],...], columns: ["path","visitors","views"], types: [...] } +// This is not an object array, so it must be zipped into row objects here. +const cols = data.columns || []; +result = (data.results || []).map((r) => + Object.fromEntries( + cols.map((c, i) => [ + c, + c === "visitors" || c === "views" ? Number(r[i]) : r[i], + ]), + ), +); diff --git a/plugins/PostHog/v1/dataStreams/scripts/webVisitors.js b/plugins/PostHog/v1/dataStreams/scripts/webVisitors.js new file mode 100644 index 00000000..4ab1b26e --- /dev/null +++ b/plugins/PostHog/v1/dataStreams/scripts/webVisitors.js @@ -0,0 +1,12 @@ +// api/projects/{id}/query/ returns COLUMNAR JSON for HogQLQuery results: +// { results: [[v1,v2,v3],...], columns: ["day","visitors","views"], types: [...] } +// This is not an object array, so it must be zipped into row objects here. +const cols = data.columns || []; +result = (data.results || []).map((r) => + Object.fromEntries( + cols.map((c, i) => [ + c, + c === "visitors" || c === "views" ? Number(r[i]) : r[i], + ]), + ), +); diff --git a/plugins/PostHog/v1/dataStreams/webBreakdown.json b/plugins/PostHog/v1/dataStreams/webBreakdown.json new file mode 100644 index 00000000..b7640103 --- /dev/null +++ b/plugins/PostHog/v1/dataStreams/webBreakdown.json @@ -0,0 +1,91 @@ +{ + "name": "webBreakdown", + "displayName": "Web Breakdown", + "description": "Unique visitors and pageviews broken down by a chosen dimension for a project", + "tags": [ + "Web Analytics" + ], + "baseDataSourceName": "httpRequestScopedSingle", + "config": { + "httpMethod": "post", + "endpointPath": "api/projects/{{object.rawId}}/query/", + "postBody": { + "query": { + "kind": "HogQLQuery", + "query": "SELECT properties.{{dimension}} AS dimension, count(DISTINCT person_id) AS visitors, count() AS views FROM events WHERE event = '$pageview' AND timestamp >= toDateTime('{{timeframe.start}}') AND timestamp < toDateTime('{{timeframe.end}}') GROUP BY dimension ORDER BY visitors DESC LIMIT 50" + } + }, + "postRequestScript": "webBreakdown.js" + }, + "matches": { + "sourceType": { + "type": "oneOf", + "values": [ + "PostHog Project" + ] + } + }, + "ui": [ + { + "type": "choiceChips", + "name": "dimension", + "label": "Dimension", + "defaultValue": "$device_type", + "validation": { + "required": true + }, + "options": [ + { + "value": "$device_type", + "label": "Device type" + }, + { + "value": "$browser", + "label": "Browser" + }, + { + "value": "$os", + "label": "OS" + }, + { + "value": "$geoip_country_code", + "label": "Country" + }, + { + "value": "$referring_domain", + "label": "Referrer" + } + ] + } + ], + "metadata": [ + { + "name": "dimension", + "displayName": "Dimension", + "shape": "string", + "role": "label" + }, + { + "name": "visitors", + "displayName": "Visitors", + "shape": "number", + "role": "value" + }, + { + "name": "views", + "displayName": "Views", + "shape": "number" + } + ], + "timeframes": true, + "defaultShaping": { + "sort": { + "by": [ + [ + "visitors", + "desc" + ] + ] + } + } +} \ No newline at end of file diff --git a/plugins/PostHog/v1/dataStreams/webPaths.json b/plugins/PostHog/v1/dataStreams/webPaths.json new file mode 100644 index 00000000..146c056c --- /dev/null +++ b/plugins/PostHog/v1/dataStreams/webPaths.json @@ -0,0 +1,58 @@ +{ + "name": "webPaths", + "displayName": "Web Paths", + "description": "Top pages by visitors and pageviews for a project", + "tags": [ + "Web Analytics" + ], + "baseDataSourceName": "httpRequestScopedSingle", + "config": { + "httpMethod": "post", + "endpointPath": "api/projects/{{object.rawId}}/query/", + "postBody": { + "query": { + "kind": "HogQLQuery", + "query": "SELECT properties.$pathname AS path, count(DISTINCT person_id) AS visitors, count() AS views FROM events WHERE event = '$pageview' AND timestamp >= toDateTime('{{timeframe.start}}') AND timestamp < toDateTime('{{timeframe.end}}') GROUP BY path ORDER BY views DESC LIMIT 100" + } + }, + "postRequestScript": "webPaths.js" + }, + "matches": { + "sourceType": { + "type": "oneOf", + "values": [ + "PostHog Project" + ] + } + }, + "metadata": [ + { + "name": "path", + "displayName": "Path", + "shape": "string", + "role": "label" + }, + { + "name": "visitors", + "displayName": "Visitors", + "shape": "number", + "role": "value" + }, + { + "name": "views", + "displayName": "Views", + "shape": "number" + } + ], + "timeframes": true, + "defaultShaping": { + "sort": { + "by": [ + [ + "views", + "desc" + ] + ] + } + } +} \ No newline at end of file diff --git a/plugins/PostHog/v1/dataStreams/webVisitors.json b/plugins/PostHog/v1/dataStreams/webVisitors.json new file mode 100644 index 00000000..ba1d40db --- /dev/null +++ b/plugins/PostHog/v1/dataStreams/webVisitors.json @@ -0,0 +1,57 @@ +{ + "name": "webVisitors", + "displayName": "Web Visitors", + "description": "Unique visitors and pageviews per day for a project", + "tags": [ + "Web Analytics" + ], + "baseDataSourceName": "httpRequestScopedSingle", + "config": { + "httpMethod": "post", + "endpointPath": "api/projects/{{object.rawId}}/query/", + "postBody": { + "query": { + "kind": "HogQLQuery", + "query": "SELECT toStartOfDay(timestamp) AS day, count(DISTINCT person_id) AS visitors, count() AS views FROM events WHERE event = '$pageview' AND timestamp >= toDateTime('{{timeframe.start}}') AND timestamp < toDateTime('{{timeframe.end}}') GROUP BY day ORDER BY day" + } + }, + "postRequestScript": "webVisitors.js" + }, + "matches": { + "sourceType": { + "type": "oneOf", + "values": [ + "PostHog Project" + ] + } + }, + "metadata": [ + { + "name": "day", + "displayName": "Day", + "shape": "date", + "role": "timestamp" + }, + { + "name": "visitors", + "displayName": "Visitors", + "shape": "number", + "role": "value" + }, + { + "name": "views", + "displayName": "Views", + "shape": "number" + } + ], + "timeframes": [ + "last7days", + "last30days", + "thisMonth", + "lastMonth", + "thisQuarter", + "thisYear", + "lastQuarter", + "lastYear" + ] +} \ No newline at end of file diff --git a/plugins/PostHog/v1/defaultContent/Project/manifest.json b/plugins/PostHog/v1/defaultContent/Project/manifest.json new file mode 100644 index 00000000..df20a104 --- /dev/null +++ b/plugins/PostHog/v1/defaultContent/Project/manifest.json @@ -0,0 +1,12 @@ +{ + "items": [ + { + "name": "overview", + "type": "dashboard" + }, + { + "name": "webAnalytics", + "type": "dashboard" + } + ] +} \ No newline at end of file diff --git a/plugins/PostHog/v1/defaultContent/Project/overview.dash.json b/plugins/PostHog/v1/defaultContent/Project/overview.dash.json new file mode 100644 index 00000000..1e48af8b --- /dev/null +++ b/plugins/PostHog/v1/defaultContent/Project/overview.dash.json @@ -0,0 +1,288 @@ +{ + "name": "Overview", + "schemaVersion": "1.5", + "timeframe": "last7days", + "variables": [ + "{{variables.[PostHog Project]}}" + ], + "dashboard": { + "_type": "layout/grid", + "columns": 4, + "version": 1, + "contents": [ + { + "i": "efab64bb-b61c-4c6e-9d3e-ad197a469730", + "x": 0, + "y": 0, + "w": 1, + "h": 4, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Properties", + "description": "", + "timeframe": "none", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "datastream-properties", + "name": "properties", + "pluginConfigId": "{{configId}}" + }, + "scope": { + "scope": "{{scopes.[PostHog Projects]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[PostHog Project]}}" + }, + "variables": [ + "{{variables.[PostHog Project]}}" + ], + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "transpose": true, + "columnOrder": [ + "name", + "sourceId", + "sourceType" + ], + "hiddenColumns": [ + "id", + "links", + "type", + "label" + ] + } + } + } + } + }, + { + "i": "328cb9b1-2859-44c8-9e3a-f2aea7367d22", + "x": 1, + "y": 0, + "w": 3, + "h": 4, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Event Volume", + "description": "", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[eventVolume]}}", + "name": "eventVolume", + "pluginConfigId": "{{configId}}" + }, + "scope": { + "scope": "{{scopes.[PostHog Projects]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[PostHog Project]}}" + }, + "variables": [ + "{{variables.[PostHog Project]}}" + ], + "visualisation": { + "type": "data-stream-line-graph", + "config": { + "data-stream-line-graph": { + "xAxisColumn": "time", + "yAxisColumn": [ + "events" + ], + "seriesColumn": "none", + "showLegend": false, + "legendPosition": "bottom", + "yAxisLabel": "Events", + "showYAxisLabel": true, + "showTrendLine": false + } + } + } + } + }, + { + "i": "ba271cd7-4ab0-48c9-b8ae-ade58deefa11", + "x": 0, + "y": 4, + "w": 2, + "h": 4, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Active Users", + "description": "", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[activeUsers]}}", + "name": "activeUsers", + "pluginConfigId": "{{configId}}" + }, + "scope": { + "scope": "{{scopes.[PostHog Projects]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[PostHog Project]}}" + }, + "variables": [ + "{{variables.[PostHog Project]}}" + ], + "visualisation": { + "type": "data-stream-line-graph", + "config": { + "data-stream-line-graph": { + "xAxisColumn": "day", + "yAxisColumn": [ + "active_users" + ], + "seriesColumn": "none", + "showLegend": false, + "legendPosition": "bottom", + "yAxisLabel": "Active users", + "showYAxisLabel": true, + "showTrendLine": false + } + } + } + } + }, + { + "i": "a69b6919-165e-4484-9b7e-b70e012cb90f", + "x": 2, + "y": 4, + "w": 2, + "h": 4, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Top Events", + "description": "", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[eventBreakdown]}}", + "name": "eventBreakdown", + "pluginConfigId": "{{configId}}", + "sort": { + "by": [ + [ + "count", + "desc" + ] + ], + "top": 10 + } + }, + "scope": { + "scope": "{{scopes.[PostHog Projects]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[PostHog Project]}}" + }, + "variables": [ + "{{variables.[PostHog Project]}}" + ], + "visualisation": { + "type": "data-stream-bar-chart", + "config": { + "data-stream-bar-chart": { + "xAxisData": "event_name", + "yAxisData": [ + "count" + ], + "xAxisGroup": "none", + "xAxisLabel": "", + "yAxisLabel": "", + "showXAxisLabel": true, + "showYAxisLabel": true, + "showLegend": false, + "legendPosition": "bottom", + "showGrid": true, + "horizontalLayout": "vertical", + "displayMode": "actual", + "showTotals": false, + "showValue": false, + "grouping": false, + "range": { + "type": "auto" + } + } + } + } + } + }, + { + "i": "a3ba1b49-720c-46fa-97d3-ec0270bae82d", + "x": 0, + "y": 8, + "w": 4, + "h": 4, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Feature Flags", + "description": "", + "timeframe": "none", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[featureFlags]}}", + "name": "featureFlags", + "pluginConfigId": "{{configId}}", + "sort": { + "by": [ + [ + "created_at", + "desc" + ] + ] + } + }, + "scope": { + "scope": "{{scopes.[PostHog Projects]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[PostHog Project]}}" + }, + "variables": [ + "{{variables.[PostHog Project]}}" + ], + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "transpose": false, + "columnOrder": [ + "key", + "description", + "active", + "last_called_at", + "created_at" + ], + "hiddenColumns": [ + "id", + "project" + ] + } + } + } + } + } + ] + } +} \ No newline at end of file diff --git a/plugins/PostHog/v1/defaultContent/Project/webAnalytics.dash.json b/plugins/PostHog/v1/defaultContent/Project/webAnalytics.dash.json new file mode 100644 index 00000000..98314472 --- /dev/null +++ b/plugins/PostHog/v1/defaultContent/Project/webAnalytics.dash.json @@ -0,0 +1,491 @@ +{ + "name": "Web Analytics", + "schemaVersion": "1.5", + "timeframe": "last30days", + "variables": [ + "{{variables.[PostHog Project]}}" + ], + "dashboard": { + "_type": "layout/grid", + "columns": 4, + "version": 1, + "contents": [ + { + "i": "22644a39-7a7f-42df-8912-d192e775a38a", + "x": 0, + "y": 0, + "w": 4, + "h": 4, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Unique Visitors", + "description": "", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[webVisitors]}}", + "name": "webVisitors", + "pluginConfigId": "{{configId}}" + }, + "scope": { + "scope": "{{scopes.[PostHog Projects]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[PostHog Project]}}" + }, + "variables": [ + "{{variables.[PostHog Project]}}" + ], + "visualisation": { + "type": "data-stream-line-graph", + "config": { + "data-stream-line-graph": { + "xAxisColumn": "day", + "yAxisColumn": [ + "visitors", + "views" + ], + "seriesColumn": "none", + "showLegend": true, + "legendPosition": "bottom", + "yAxisLabel": "Visitors", + "showYAxisLabel": true, + "showTrendLine": false + } + } + } + } + }, + { + "i": "ff863d32-7b4d-4493-a749-69da018ed039", + "x": 0, + "y": 4, + "w": 1, + "h": 4, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Device Type", + "description": "", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[webBreakdown]}}", + "name": "webBreakdown", + "pluginConfigId": "{{configId}}", + "dataSourceConfig": { + "dimension": "$device_type" + }, + "sort": { + "by": [ + [ + "visitors", + "desc" + ] + ], + "top": 10 + } + }, + "scope": { + "scope": "{{scopes.[PostHog Projects]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[PostHog Project]}}" + }, + "variables": [ + "{{variables.[PostHog Project]}}" + ], + "visualisation": { + "type": "data-stream-bar-chart", + "config": { + "data-stream-bar-chart": { + "xAxisData": "dimension", + "yAxisData": [ + "visitors" + ], + "xAxisGroup": "none", + "xAxisLabel": "", + "yAxisLabel": "", + "showXAxisLabel": true, + "showYAxisLabel": true, + "showLegend": false, + "legendPosition": "bottom", + "showGrid": true, + "horizontalLayout": "vertical", + "displayMode": "actual", + "showTotals": false, + "showValue": false, + "grouping": false, + "range": { + "type": "auto" + } + } + } + } + } + }, + { + "i": "a1dd46f4-aece-49d5-8c67-4e4bbf4c6571", + "x": 1, + "y": 4, + "w": 1, + "h": 4, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Top Countries", + "description": "", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[webBreakdown]}}", + "name": "webBreakdown", + "pluginConfigId": "{{configId}}", + "dataSourceConfig": { + "dimension": "$geoip_country_code" + }, + "sort": { + "by": [ + [ + "visitors", + "desc" + ] + ], + "top": 10 + } + }, + "scope": { + "scope": "{{scopes.[PostHog Projects]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[PostHog Project]}}" + }, + "variables": [ + "{{variables.[PostHog Project]}}" + ], + "visualisation": { + "type": "data-stream-bar-chart", + "config": { + "data-stream-bar-chart": { + "xAxisData": "dimension", + "yAxisData": [ + "visitors" + ], + "xAxisGroup": "none", + "xAxisLabel": "", + "yAxisLabel": "", + "showXAxisLabel": true, + "showYAxisLabel": true, + "showLegend": false, + "legendPosition": "bottom", + "showGrid": true, + "horizontalLayout": "vertical", + "displayMode": "actual", + "showTotals": false, + "showValue": false, + "grouping": false, + "range": { + "type": "auto" + } + } + } + } + } + }, + { + "i": "8f3b0d3f-1fd5-41d5-a79c-143e3bfc0d15", + "x": 2, + "y": 4, + "w": 1, + "h": 4, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Top Referrers", + "description": "", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[webBreakdown]}}", + "name": "webBreakdown", + "pluginConfigId": "{{configId}}", + "dataSourceConfig": { + "dimension": "$referring_domain" + }, + "sort": { + "by": [ + [ + "visitors", + "desc" + ] + ], + "top": 10 + } + }, + "scope": { + "scope": "{{scopes.[PostHog Projects]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[PostHog Project]}}" + }, + "variables": [ + "{{variables.[PostHog Project]}}" + ], + "visualisation": { + "type": "data-stream-bar-chart", + "config": { + "data-stream-bar-chart": { + "xAxisData": "dimension", + "yAxisData": [ + "visitors" + ], + "xAxisGroup": "none", + "xAxisLabel": "", + "yAxisLabel": "", + "showXAxisLabel": true, + "showYAxisLabel": true, + "showLegend": false, + "legendPosition": "bottom", + "showGrid": true, + "horizontalLayout": "vertical", + "displayMode": "actual", + "showTotals": false, + "showValue": false, + "grouping": false, + "range": { + "type": "auto" + } + } + } + } + } + }, + { + "i": "8fb4e2e5-5423-4faf-8b2b-9499b6aaec2e", + "x": 3, + "y": 4, + "w": 1, + "h": 4, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Browsers", + "description": "", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[webBreakdown]}}", + "name": "webBreakdown", + "pluginConfigId": "{{configId}}", + "dataSourceConfig": { + "dimension": "$browser" + }, + "sort": { + "by": [ + [ + "visitors", + "desc" + ] + ], + "top": 10 + } + }, + "scope": { + "scope": "{{scopes.[PostHog Projects]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[PostHog Project]}}" + }, + "variables": [ + "{{variables.[PostHog Project]}}" + ], + "visualisation": { + "type": "data-stream-bar-chart", + "config": { + "data-stream-bar-chart": { + "xAxisData": "dimension", + "yAxisData": [ + "visitors" + ], + "xAxisGroup": "none", + "xAxisLabel": "", + "yAxisLabel": "", + "showXAxisLabel": true, + "showYAxisLabel": true, + "showLegend": false, + "legendPosition": "bottom", + "showGrid": true, + "horizontalLayout": "vertical", + "displayMode": "actual", + "showTotals": false, + "showValue": false, + "grouping": false, + "range": { + "type": "auto" + } + } + } + } + } + }, + { + "i": "95ccd2c1-10f0-4ba1-ab72-d93cda136beb", + "x": 0, + "y": 8, + "w": 2, + "h": 4, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Top Pages", + "description": "", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[webPaths]}}", + "name": "webPaths", + "pluginConfigId": "{{configId}}" + }, + "scope": { + "scope": "{{scopes.[PostHog Projects]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[PostHog Project]}}" + }, + "variables": [ + "{{variables.[PostHog Project]}}" + ], + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "transpose": false, + "columnOrder": [ + "path", + "visitors", + "views" + ] + } + } + } + } + }, + { + "i": "616e3400-f700-4951-9f4d-2e7cc26707be", + "x": 2, + "y": 8, + "w": 2, + "h": 4, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Frustrating Pages", + "description": "", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[frustratingPages]}}", + "name": "frustratingPages", + "pluginConfigId": "{{configId}}" + }, + "scope": { + "scope": "{{scopes.[PostHog Projects]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[PostHog Project]}}" + }, + "variables": [ + "{{variables.[PostHog Project]}}" + ], + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "transpose": false, + "columnOrder": [ + "path", + "rage_clicks", + "dead_clicks", + "errors" + ] + } + } + } + } + }, + { + "i": "34a3e236-f0f4-4aa1-bdef-7026d5ca04e4", + "x": 0, + "y": 12, + "w": 4, + "h": 4, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Active Hours", + "description": "", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[activeHours]}}", + "name": "activeHours", + "pluginConfigId": "{{configId}}" + }, + "scope": { + "scope": "{{scopes.[PostHog Projects]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[PostHog Project]}}" + }, + "variables": [ + "{{variables.[PostHog Project]}}" + ], + "visualisation": { + "type": "data-stream-bar-chart", + "config": { + "data-stream-bar-chart": { + "xAxisData": "hour_of_day", + "yAxisData": [ + "events" + ], + "xAxisGroup": "day_name", + "xAxisLabel": "Hour of day", + "yAxisLabel": "Events", + "showXAxisLabel": true, + "showYAxisLabel": true, + "showLegend": true, + "legendPosition": "right", + "showGrid": true, + "horizontalLayout": "vertical", + "displayMode": "actual", + "showTotals": false, + "showValue": false, + "grouping": false, + "range": { + "type": "auto" + } + } + } + } + } + } + ] + } +} \ No newline at end of file diff --git a/plugins/PostHog/v1/defaultContent/account.dash.json b/plugins/PostHog/v1/defaultContent/account.dash.json new file mode 100644 index 00000000..f0cd6291 --- /dev/null +++ b/plugins/PostHog/v1/defaultContent/account.dash.json @@ -0,0 +1,204 @@ +{ + "name": "Account", + "schemaVersion": "1.5", + "timeframe": "last7days", + "dashboard": { + "_type": "layout/grid", + "columns": 4, + "version": 1, + "contents": [ + { + "i": "74a7e88a-2a3c-468f-a45b-806ac717ddf5", + "x": 0, + "y": 0, + "w": 1, + "h": 3, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Organizations", + "description": "", + "timeframe": "none", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[organizations]}}", + "name": "organizations", + "pluginConfigId": "{{configId}}", + "group": { + "by": [], + "aggregate": [ + { + "type": "count" + } + ] + } + }, + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": "count", + "comparisonColumn": "none", + "label": "Organizations" + } + } + } + } + }, + { + "i": "3a649fbb-ae7e-46af-a646-4555b92d8457", + "x": 0, + "y": 3, + "w": 1, + "h": 3, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Projects", + "description": "", + "timeframe": "none", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[projects]}}", + "name": "projects", + "pluginConfigId": "{{configId}}", + "group": { + "by": [], + "aggregate": [ + { + "type": "count" + } + ] + } + }, + "scope": { + "query": "g.V().order().by('__name').hasNot('__canonicalType').has(\"__configId\", within(\"{{configId}}\")).or(__.has(\"sourceType\", \"PostHog Organization\")).limit(500)", + "bindings": {}, + "queryDetail": {} + }, + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": "count", + "comparisonColumn": "none", + "label": "Projects" + } + } + } + } + }, + { + "i": "82892cca-edb0-4b8a-ace5-1b4777a197f9", + "x": 1, + "y": 0, + "w": 3, + "h": 3, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Organizations", + "description": "", + "timeframe": "none", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[organizations]}}", + "name": "organizations", + "pluginConfigId": "{{configId}}", + "sort": { + "by": [ + [ + "name", + "asc" + ] + ] + } + }, + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "transpose": false, + "columnOrder": [ + "name", + "slug", + "member_count", + "created_at" + ], + "hiddenColumns": [ + "id" + ] + } + } + } + } + }, + { + "i": "17c64ea1-2216-4ad8-9c86-0e529e98f499", + "x": 1, + "y": 3, + "w": 3, + "h": 3, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Projects", + "description": "", + "timeframe": "none", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[projects]}}", + "name": "projects", + "pluginConfigId": "{{configId}}", + "sort": { + "by": [ + [ + "name", + "asc" + ] + ] + } + }, + "scope": { + "query": "g.V().order().by('__name').hasNot('__canonicalType').has(\"__configId\", within(\"{{configId}}\")).or(__.has(\"sourceType\", \"PostHog Organization\")).limit(500)", + "bindings": {}, + "queryDetail": {} + }, + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "transpose": false, + "columnOrder": [ + "name", + "timezone", + "ingested_event" + ], + "hiddenColumns": [ + "id", + "organization" + ] + } + } + } + } + } + ] + } +} \ No newline at end of file diff --git a/plugins/PostHog/v1/defaultContent/featureFlag.dash.json b/plugins/PostHog/v1/defaultContent/featureFlag.dash.json new file mode 100644 index 00000000..71aae1aa --- /dev/null +++ b/plugins/PostHog/v1/defaultContent/featureFlag.dash.json @@ -0,0 +1,171 @@ +{ + "name": "Feature Flag", + "schemaVersion": "1.5", + "timeframe": "last30days", + "variables": [ + "{{variables.[PostHog Feature Flag]}}" + ], + "dashboard": { + "_type": "layout/grid", + "columns": 4, + "version": 1, + "contents": [ + { + "w": 4, + "h": 1, + "x": 0, + "y": 0, + "z": 0, + "i": "7235a3fd-88b6-4e2f-88bf-fb9bbfc1b050", + "moved": false, + "static": false, + "config": { + "_type": "tile/data-stream", + "timeframe": "none", + "variables": [ + "{{variables.[PostHog Feature Flag]}}" + ], + "dataStream": { + "name": "properties", + "pluginConfigId": "{{configId}}", + "id": "datastream-properties" + }, + "scope": { + "variable": "{{variables.[PostHog Feature Flag]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[PostHog Feature Flags]}}" + }, + "description": "", + "activePluginConfigIds": [ + "{{configId}}" + ], + "title": "", + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": "description", + "comparisonColumn": "none", + "formatted": true + } + } + } + } + }, + { + "w": 4, + "h": 3, + "x": 0, + "y": 1, + "z": 0, + "i": "5564c229-c4f4-4e6d-9997-ebb1a1b7660a", + "moved": false, + "static": false, + "config": { + "variables": [ + "{{variables.[PostHog Feature Flag]}}" + ], + "dataStream": { + "name": "flagEvaluations", + "pluginConfigId": "{{configId}}", + "id": "{{dataStreams.[flagEvaluations]}}" + }, + "scope": { + "variable": "{{variables.[PostHog Feature Flag]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[PostHog Feature Flags]}}" + }, + "_type": "tile/data-stream", + "description": "", + "activePluginConfigIds": [ + "{{configId}}" + ], + "title": "Evaluations Over Time", + "visualisation": { + "type": "data-stream-line-graph", + "config": { + "data-stream-line-graph": { + "yAxisLabel": "Evaluations", + "xAxisColumn": "day", + "showLegend": false, + "showYAxisLabel": true, + "seriesColumn": "none", + "showTrendLine": false, + "legendPosition": "bottom", + "yAxisColumn": [ + "evaluations" + ] + } + } + } + } + }, + { + "w": 4, + "h": 3, + "x": 0, + "y": 4, + "z": 0, + "i": "17171d35-c365-45e5-8e63-a920a20bd6de", + "moved": false, + "static": false, + "config": { + "variables": [ + "{{variables.[PostHog Feature Flag]}}" + ], + "dataStream": { + "name": "flagVariants", + "pluginConfigId": "{{configId}}", + "id": "{{dataStreams.[flagVariants]}}", + "sort": { + "by": [ + [ + "evaluations", + "desc" + ] + ] + } + }, + "scope": { + "variable": "{{variables.[PostHog Feature Flag]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[PostHog Feature Flags]}}" + }, + "_type": "tile/data-stream", + "description": "", + "activePluginConfigIds": [ + "{{configId}}" + ], + "title": "Variant Split", + "visualisation": { + "type": "data-stream-bar-chart", + "config": { + "data-stream-bar-chart": { + "xAxisGroup": "none", + "showLegend": false, + "range": { + "type": "auto" + }, + "showGrid": true, + "grouping": false, + "xAxisData": "variant", + "displayMode": "actual", + "showTotals": false, + "yAxisLabel": "", + "horizontalLayout": "vertical", + "showValue": false, + "yAxisData": [ + "evaluations" + ], + "showYAxisLabel": true, + "xAxisLabel": "", + "legendPosition": "bottom", + "showXAxisLabel": true + } + } + } + } + } + ] + } +} \ No newline at end of file diff --git a/plugins/PostHog/v1/defaultContent/manifest.json b/plugins/PostHog/v1/defaultContent/manifest.json new file mode 100644 index 00000000..41d192f2 --- /dev/null +++ b/plugins/PostHog/v1/defaultContent/manifest.json @@ -0,0 +1,20 @@ +{ + "items": [ + { + "name": "account", + "type": "dashboard" + }, + { + "name": "organization", + "type": "dashboard" + }, + { + "name": "featureFlag", + "type": "dashboard" + }, + { + "name": "Project", + "type": "folder" + } + ] +} \ No newline at end of file diff --git a/plugins/PostHog/v1/defaultContent/organization.dash.json b/plugins/PostHog/v1/defaultContent/organization.dash.json new file mode 100644 index 00000000..b45e6d64 --- /dev/null +++ b/plugins/PostHog/v1/defaultContent/organization.dash.json @@ -0,0 +1,231 @@ +{ + "name": "Organization", + "schemaVersion": "1.5", + "timeframe": "last7days", + "variables": [ + "{{variables.[PostHog Organization]}}" + ], + "dashboard": { + "_type": "layout/grid", + "columns": 4, + "version": 1, + "contents": [ + { + "i": "c7f1a2b3-4d5e-6f70-8192-a3b4c5d6e7f8", + "x": 0, + "y": 0, + "w": 1, + "h": 3, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Projects", + "description": "", + "timeframe": "none", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[projects]}}", + "name": "projects", + "pluginConfigId": "{{configId}}", + "group": { + "by": [], + "aggregate": [ + { + "type": "count" + } + ] + } + }, + "scope": { + "scope": "{{scopes.[PostHog Organizations]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[PostHog Organization]}}" + }, + "variables": [ + "{{variables.[PostHog Organization]}}" + ], + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": "count", + "comparisonColumn": "none", + "label": "Projects" + } + } + } + } + }, + { + "i": "869ad81c-9381-49c8-b5f7-12fc17becef4", + "x": 1, + "y": 0, + "w": 3, + "h": 3, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Projects", + "description": "Projects in the current PostHog organization", + "timeframe": "none", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[projects]}}", + "name": "projects", + "pluginConfigId": "{{configId}}", + "sort": { + "by": [ + [ + "name", + "asc" + ] + ] + } + }, + "scope": { + "scope": "{{scopes.[PostHog Organizations]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[PostHog Organization]}}" + }, + "variables": [ + "{{variables.[PostHog Organization]}}" + ], + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "transpose": false, + "columnOrder": [ + "name", + "timezone", + "ingested_event" + ], + "hiddenColumns": [ + "id", + "organization" + ] + } + } + } + } + }, + { + "i": "a26a8df6-12be-4a62-91f2-536512722d9b", + "x": 0, + "y": 3, + "w": 1, + "h": 3, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Members", + "description": "", + "timeframe": "none", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[members]}}", + "name": "members", + "pluginConfigId": "{{configId}}", + "group": { + "by": [], + "aggregate": [ + { + "type": "count" + } + ] + } + }, + "scope": { + "scope": "{{scopes.[PostHog Organizations]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[PostHog Organization]}}" + }, + "variables": [ + "{{variables.[PostHog Organization]}}" + ], + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": "count", + "comparisonColumn": "none", + "label": "Members" + } + } + } + } + }, + { + "i": "d99af3a1-a887-4b4f-9fdd-5877df54e151", + "x": 1, + "y": 3, + "w": 3, + "h": 3, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Members", + "description": "Members of the current PostHog organization", + "timeframe": "none", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[members]}}", + "name": "members", + "pluginConfigId": "{{configId}}", + "sort": { + "by": [ + [ + "name", + "asc" + ] + ] + } + }, + "scope": { + "scope": "{{scopes.[PostHog Organizations]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[PostHog Organization]}}" + }, + "variables": [ + "{{variables.[PostHog Organization]}}" + ], + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "transpose": false, + "columnOrder": [ + "name", + "email", + "role", + "last_login", + "is_2fa_enabled" + ], + "hiddenColumns": [ + "id", + "organization" + ] + } + } + } + } + } + ] + } +} \ No newline at end of file diff --git a/plugins/PostHog/v1/defaultContent/scopes.json b/plugins/PostHog/v1/defaultContent/scopes.json new file mode 100644 index 00000000..d6064a82 --- /dev/null +++ b/plugins/PostHog/v1/defaultContent/scopes.json @@ -0,0 +1,53 @@ +[ + { + "name": "PostHog Projects", + "matches": { + "sourceType": { + "type": "oneOf", + "values": [ + "PostHog Project" + ] + } + }, + "variable": { + "name": "PostHog Project", + "allowMultipleSelection": false, + "default": "none", + "type": "object" + } + }, + { + "name": "PostHog Organizations", + "matches": { + "sourceType": { + "type": "oneOf", + "values": [ + "PostHog Organization" + ] + } + }, + "variable": { + "name": "PostHog Organization", + "allowMultipleSelection": false, + "default": "none", + "type": "object" + } + }, + { + "name": "PostHog Feature Flags", + "matches": { + "sourceType": { + "type": "oneOf", + "values": [ + "PostHog Feature Flag" + ] + } + }, + "variable": { + "name": "PostHog Feature Flag", + "allowMultipleSelection": false, + "default": "none", + "type": "object" + } + } +] \ No newline at end of file diff --git a/plugins/PostHog/v1/docs/README.md b/plugins/PostHog/v1/docs/README.md new file mode 100644 index 00000000..a0a95a6b --- /dev/null +++ b/plugins/PostHog/v1/docs/README.md @@ -0,0 +1,50 @@ +# PostHog + +Monitor your [PostHog](https://posthog.com) product analytics in SquaredUp. This plugin imports your PostHog organizations and projects into the SquaredUp graph, and provides data streams for event volume, active users, top events, feature flags, and ad-hoc HogQL queries. + +## What this plugin monitors + +- **Organizations** and **Projects** are imported as objects you can scope dashboards to, search for, and drill into. +- Per-project dashboards show **event volume over time**, **active (unique) users**, the **top events** being captured, and the current state of your **feature flags**. +- A **Web Analytics** perspective mirrors PostHog's built-in web view: **unique visitors** over time, breakdowns by **device / country / referrer / browser**, **top pages**, **frustrating pages** (rage clicks, dead clicks, errors), and an **active-hours** view. +- A flexible **HogQL query** stream lets you run any SQL-style query against a project's data and chart the result. + +## Prerequisites — getting a personal API key + +1. Sign in to PostHog and go to **Settings → Personal API keys** (`https://us.posthog.com/settings/user-api-keys`, or the `eu.` equivalent). +2. Click **Create personal API key** and give it a name (e.g. "SquaredUp"). +3. Under **Scopes**, grant **read** access to at least: + - **Organization** (`organization:read`) + - **Project** (`project:read`) + - **Feature flag** (`feature_flag:read`) + - **Query** (`query:read`) + - **User** (`user:read`) +4. Under **Organization & project access**, allow the key to access the organizations and projects you want to monitor (or grant access to all). +5. Copy the key (it starts with `phx_`) — you will not be able to see it again. + +## Configuration fields + +| Field | What it is | Where to find it | Required | +| -------------------- | ------------------------------------------------------------------- | ------------------------------------------- | -------- | +| **Region** | The PostHog Cloud region your account is hosted in (US or EU). | The domain you sign in at (`us.` or `eu.`). | Yes | +| **Personal API key** | A PostHog personal API key used as a bearer token on every request. | Settings → Personal API keys (see above). | Yes | + +Self-hosted PostHog instances are not supported in this version. + +## What gets indexed + +| Object type | Represents | +| ------------------------ | ---------------------------------------------------------------------- | +| **PostHog Organization** | A PostHog organization (top-level account / billing entity). | +| **PostHog Project** | A project within an organization — the container for events and flags. | +| **PostHog Member** | A person who belongs to an organization, with their role. | +| **PostHog Feature Flag** | A feature flag within a project, with its key, description and state. | + +All organizations your API key can access are imported, and their projects, members and feature flags are imported per-organization / per-project (so multi-organization accounts are fully covered). + +## Known limitations + +- **Rate limits.** PostHog enforces per-key rate limits (typically a few hundred requests/minute, and lower limits on the analytics/query endpoints). Very frequent refreshes of many HogQL tiles may be throttled. +- **Query scopes.** The event-volume, active-users, top-events and HogQL streams use the `/query/` endpoint, which requires the `query:read` scope. Without it those streams return an authorization error while imports still succeed. +- **Billing data is unavailable.** PostHog's billing API cannot be accessed with a personal API key, so spend/usage cost data is not surfaced. +- **HogQL result size.** Very large query results are capped (~6MB per request); narrow the timeframe or aggregate in the query if a stream reports a size error. diff --git a/plugins/PostHog/v1/icon.svg b/plugins/PostHog/v1/icon.svg new file mode 100644 index 00000000..f5b6bf88 --- /dev/null +++ b/plugins/PostHog/v1/icon.svg @@ -0,0 +1 @@ + diff --git a/plugins/PostHog/v1/indexDefinitions/default.json b/plugins/PostHog/v1/indexDefinitions/default.json new file mode 100644 index 00000000..0b80e7d3 --- /dev/null +++ b/plugins/PostHog/v1/indexDefinitions/default.json @@ -0,0 +1,96 @@ +{ + "steps": [ + { + "name": "organizations", + "dataStream": { + "name": "organizations" + }, + "timeframe": "none", + "objectMapping": { + "id": "id", + "name": "name", + "type": { + "value": "PostHog Organization" + } + } + }, + { + "name": "projects", + "dataStream": { + "name": "projects" + }, + "scope": { + "query": "g.V().has(\"sourceType\", \"PostHog Organization\")" + }, + "dependsOn": [ + "organizations" + ], + "timeframe": "none", + "objectMapping": { + "id": "id", + "name": "name", + "type": { + "value": "PostHog Project" + }, + "properties": [ + "timezone", + "organization" + ] + } + }, + { + "name": "members", + "dataStream": { + "name": "members" + }, + "scope": { + "query": "g.V().has(\"sourceType\", \"PostHog Organization\")" + }, + "dependsOn": [ + "organizations" + ], + "timeframe": "none", + "optional": true, + "objectMapping": { + "id": "id", + "name": "name", + "type": { + "value": "PostHog Member" + }, + "properties": [ + "email", + "role", + "last_login", + "organization" + ] + } + }, + { + "name": "featureFlags", + "dataStream": { + "name": "featureFlags" + }, + "scope": { + "query": "g.V().has(\"sourceType\", \"PostHog Project\")" + }, + "dependsOn": [ + "projects" + ], + "timeframe": "none", + "optional": true, + "objectMapping": { + "id": "id", + "name": "key", + "type": { + "value": "PostHog Feature Flag" + }, + "properties": [ + "description", + "active", + "last_called_at", + "project" + ] + } + } + ] +} \ No newline at end of file diff --git a/plugins/PostHog/v1/metadata.json b/plugins/PostHog/v1/metadata.json new file mode 100644 index 00000000..2ec4b9c2 --- /dev/null +++ b/plugins/PostHog/v1/metadata.json @@ -0,0 +1,56 @@ +{ + "name": "posthog", + "displayName": "PostHog", + "version": "1.0.0", + "author": { + "name": "@adamkinniburgh", + "type": "community" + }, + "description": "Monitor your PostHog organizations and projects in SquaredUp - track event volume, active users, top events, feature flags, and run ad-hoc HogQL queries.", + "category": "Analytics", + "type": "hybrid", + "schemaVersion": "2.1", + "importNotSupported": false, + "restrictedToPlatforms": [], + "keywords": [ + "posthog", + "analytics", + "product analytics", + "events", + "feature flags", + "hogql", + "active users" + ], + "objectTypes": [ + "PostHog Organization", + "PostHog Project", + "PostHog Member", + "PostHog Feature Flag" + ], + "links": [ + { + "category": "documentation", + "url": "https://github.com/squaredup/plugins/blob/main/plugins/PostHog/v1/docs/README.md", + "label": "Help adding this plugin" + }, + { + "category": "source", + "url": "https://github.com/squaredup/plugins/tree/main/plugins/PostHog", + "label": "Repository" + } + ], + "base": { + "plugin": "WebAPI", + "majorVersion": "1", + "config": { + "baseUrl": "https://{{region === 'eu' ? 'eu' : 'us'}}.posthog.com", + "authMode": "none", + "headers": [ + { + "key": "Authorization", + "value": "Bearer {{personalApiKey}}" + } + ] + } + } +} \ No newline at end of file diff --git a/plugins/PostHog/v1/ui.json b/plugins/PostHog/v1/ui.json new file mode 100644 index 00000000..44243be8 --- /dev/null +++ b/plugins/PostHog/v1/ui.json @@ -0,0 +1,31 @@ +[ + { + "type": "radio", + "name": "region", + "label": "Region", + "help": "The PostHog Cloud region your account is hosted in. Choose EU if you sign in at eu.posthog.com, otherwise US.", + "defaultValue": "us", + "options": [ + { + "value": "us", + "label": "US Cloud (us.posthog.com)" + }, + { + "value": "eu", + "label": "EU Cloud (eu.posthog.com)" + } + ], + "validation": { + "required": true + } + }, + { + "type": "password", + "name": "personalApiKey", + "label": "Personal API key", + "help": "A PostHog personal API key (starts with `phx_`). Create one at **Settings → Personal API keys**. Grant read scopes for Organization, Project, Feature flag, Query, and User so all data streams work.", + "validation": { + "required": true + } + } +] \ No newline at end of file