@agent-native/core 0.77.15 → 0.77.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/corpus/README.md +1 -1
- package/corpus/core/CHANGELOG.md +19 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/production-agent.ts +46 -33
- package/corpus/core/src/client/AgentPanel.tsx +4 -0
- package/corpus/core/src/client/org/OrgSwitcher.tsx +4 -0
- package/corpus/core/src/client/use-db-sync.ts +19 -2
- package/corpus/core/src/server/auth-marketing.ts +15 -0
- package/corpus/core/src/server/onboarding-html.ts +47 -1
- package/corpus/templates/analytics/.agents/skills/dashboard-management/SKILL.md +76 -0
- package/corpus/templates/analytics/actions/update-dashboard.ts +1 -0
- package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +13 -2
- package/corpus/templates/analytics/app/i18n-data.ts +10 -0
- package/corpus/templates/analytics/app/lib/sql-query.ts +97 -21
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +22 -1
- package/corpus/templates/analytics/app/root.tsx +15 -6
- package/corpus/templates/analytics/changelog/2026-06-25-dashboard-charts-no-longer-refresh-in-the-background-during-.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-25-first-party-dashboards-use-indexed-event-dates-for-faster-da.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-25-retention-and-active-user-dashboard-panels-now-count-account.md +6 -0
- package/corpus/templates/analytics/seeds/dashboards/agent-native-templates-first-party.json +58 -58
- package/corpus/templates/analytics/server/db/schema.ts +2 -0
- package/corpus/templates/analytics/server/handlers/sql-query.ts +1 -1
- package/corpus/templates/analytics/server/lib/dashboard-catalog.ts +1 -1
- package/corpus/templates/analytics/server/lib/first-party-analytics.ts +16 -6
- package/corpus/templates/analytics/server/lib/first-party-metric-catalog.ts +88 -56
- package/corpus/templates/analytics/server/plugins/db.ts +73 -0
- package/corpus/templates/clips/AGENTS.md +5 -0
- package/corpus/templates/clips/changelog/2026-06-25-github-issue-and-pull-request-pages-can-now-preview-playable.md +6 -0
- package/corpus/templates/clips/chrome-extension/PERMISSIONS.md +8 -2
- package/corpus/templates/clips/chrome-extension/public/manifest.json +14 -2
- package/corpus/templates/clips/chrome-extension/src/github-preview-content.ts +233 -0
- package/corpus/templates/clips/chrome-extension/src/github-preview.html +12 -0
- package/corpus/templates/clips/chrome-extension/src/github-preview.ts +414 -0
- package/corpus/templates/clips/chrome-extension/vite.config.ts +5 -0
- package/corpus/templates/mail/actions/archive-email.ts +13 -3
- package/corpus/templates/mail/app/hooks/use-emails.ts +5 -2
- package/corpus/templates/mail/changelog/2026-06-25-archive-failures-now-explain-when-gmail-needs-reconnecting-p.md +6 -0
- package/corpus/templates/mail/shared/archive-errors.ts +137 -0
- package/corpus/templates/plan/changelog/2026-06-25-hosted-signup-now-shows-how-to-switch-visual-plan-to-local-files.md +6 -0
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +17 -12
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/client/AgentPanel.d.ts.map +1 -1
- package/dist/client/AgentPanel.js +2 -1
- package/dist/client/AgentPanel.js.map +1 -1
- package/dist/client/org/OrgSwitcher.d.ts.map +1 -1
- package/dist/client/org/OrgSwitcher.js +3 -1
- package/dist/client/org/OrgSwitcher.js.map +1 -1
- package/dist/client/use-db-sync.d.ts +9 -0
- package/dist/client/use-db-sync.d.ts.map +1 -1
- package/dist/client/use-db-sync.js +8 -1
- package/dist/client/use-db-sync.js.map +1 -1
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/awareness.d.ts.map +1 -1
- package/dist/observability/routes.d.ts +5 -5
- package/dist/server/agent-engine-api-key-route.d.ts +2 -2
- package/dist/server/auth-marketing.d.ts +4 -0
- package/dist/server/auth-marketing.d.ts.map +1 -1
- package/dist/server/auth-marketing.js +9 -0
- package/dist/server/auth-marketing.js.map +1 -1
- package/dist/server/onboarding-html.d.ts.map +1 -1
- package/dist/server/onboarding-html.js +46 -1
- package/dist/server/onboarding-html.js.map +1 -1
- package/package.json +1 -1
|
@@ -120,8 +120,10 @@ export const analyticsEvents = table("analytics_events", {
|
|
|
120
120
|
eventName: text("event_name").notNull(),
|
|
121
121
|
userId: text("user_id"),
|
|
122
122
|
anonymousId: text("anonymous_id"),
|
|
123
|
+
userKey: text("user_key"),
|
|
123
124
|
sessionId: text("session_id"),
|
|
124
125
|
timestamp: text("timestamp").notNull(),
|
|
126
|
+
eventDate: text("event_date"),
|
|
125
127
|
receivedAt: text("received_at").notNull().default(now()),
|
|
126
128
|
url: text("url"),
|
|
127
129
|
path: text("path"),
|
|
@@ -369,7 +369,7 @@ export const handleSqlQuery = defineEventHandler(async (event) => {
|
|
|
369
369
|
} catch (error: any) {
|
|
370
370
|
const message = error?.message || String(error);
|
|
371
371
|
console.error(`SQL query error (${source}):`, message);
|
|
372
|
-
setResponseStatus(event, 400);
|
|
372
|
+
setResponseStatus(event, /DB query timed out/i.test(message) ? 504 : 400);
|
|
373
373
|
return { error: message };
|
|
374
374
|
}
|
|
375
375
|
});
|
|
@@ -937,7 +937,7 @@ export const dashboardCatalogEntries: DashboardCatalogEntry[] = [
|
|
|
937
937
|
"referrals",
|
|
938
938
|
"virality",
|
|
939
939
|
],
|
|
940
|
-
panelCount:
|
|
940
|
+
panelCount: 34,
|
|
941
941
|
version: CATALOG_VERSION,
|
|
942
942
|
recommended: true,
|
|
943
943
|
buildConfig: () => seedConfig("agent-native-templates-first-party"),
|
|
@@ -185,6 +185,10 @@ function normalizeTimestamp(value: unknown): string {
|
|
|
185
185
|
return nowIso();
|
|
186
186
|
}
|
|
187
187
|
|
|
188
|
+
function eventDateFromTimestamp(timestamp: string): string {
|
|
189
|
+
return timestamp.slice(0, 10);
|
|
190
|
+
}
|
|
191
|
+
|
|
188
192
|
function urlParts(url: string | null): {
|
|
189
193
|
url: string | null;
|
|
190
194
|
path: string | null;
|
|
@@ -320,18 +324,24 @@ export async function recordAnalyticsEvents(
|
|
|
320
324
|
asString((properties as any).signedIn) ||
|
|
321
325
|
asString((context as any).signed_in) ||
|
|
322
326
|
asString((context as any).signedIn);
|
|
327
|
+
const userId = event.userId ?? asString((properties as any).userId);
|
|
328
|
+
const anonymousId =
|
|
329
|
+
event.anonymousId ??
|
|
330
|
+
asString((properties as any).anonymousId) ??
|
|
331
|
+
asString((properties as any).distinctId);
|
|
332
|
+
const userKey = userId || anonymousId;
|
|
333
|
+
const timestamp = normalizeTimestamp(event.timestamp);
|
|
323
334
|
|
|
324
335
|
return {
|
|
325
336
|
id: id("evt"),
|
|
326
337
|
publicKeyId: key.id,
|
|
327
338
|
eventName: event.event,
|
|
328
|
-
userId
|
|
329
|
-
anonymousId
|
|
330
|
-
|
|
331
|
-
asString((properties as any).anonymousId) ??
|
|
332
|
-
asString((properties as any).distinctId),
|
|
339
|
+
userId,
|
|
340
|
+
anonymousId,
|
|
341
|
+
userKey,
|
|
333
342
|
sessionId: event.sessionId ?? asString((properties as any).sessionId),
|
|
334
|
-
timestamp
|
|
343
|
+
timestamp,
|
|
344
|
+
eventDate: eventDateFromTimestamp(timestamp),
|
|
335
345
|
receivedAt,
|
|
336
346
|
url: parts.url,
|
|
337
347
|
path: parts.path ?? asString(properties.path),
|
|
@@ -11,10 +11,11 @@
|
|
|
11
11
|
* already ships (and is exercised) in the
|
|
12
12
|
* `agent-native-templates-first-party` seed. See `compose-dashboard.ts`.
|
|
13
13
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
14
|
+
* Default seed panels with matching catalog keys are generated from this file.
|
|
15
|
+
* The seed can also include layout-only section panels, and the catalog can keep
|
|
16
|
+
* extra reusable panels that are not on the default dashboard. Windowed metrics
|
|
17
|
+
* swap the `interval 'N days'` literal for the requested window via
|
|
18
|
+
* `buildSql(window)`.
|
|
18
19
|
*
|
|
19
20
|
* Distinct from the `<data-dictionary>` (the user/org-scoped catalog of business
|
|
20
21
|
* metric definitions the agent consults before writing ad-hoc SQL). That is a
|
|
@@ -62,14 +63,28 @@ const WINDOW_DAYS: Record<Exclude<MetricWindow, "all">, number> = {
|
|
|
62
63
|
* Apply a window to SQL that contains `interval 'N days'` clauses.
|
|
63
64
|
*
|
|
64
65
|
* - "30d" / "90d": replace every `interval '<n> days'` with the requested days.
|
|
65
|
-
* - "all": strip the
|
|
66
|
-
*
|
|
67
|
-
* `>= now() - interval` window clause is removed; any other interval usage is
|
|
68
|
-
* left intact.)
|
|
66
|
+
* - "all": strip the standard date-window clause so the metric covers all
|
|
67
|
+
* time. (Other interval usage is left intact.)
|
|
69
68
|
*/
|
|
70
69
|
function applyWindow(sql: string, window: MetricWindow): string {
|
|
71
70
|
if (window === "all") {
|
|
72
71
|
return sql
|
|
72
|
+
.replace(
|
|
73
|
+
/\s+AND\s+substr\s*\(\s*timestamp\s*,\s*1\s*,\s*10\s*\)\s*>=\s*to_char\s*\(\s*CURRENT_DATE\s*-\s*INTERVAL\s*'\d+\s*days?'\s*,\s*'YYYY-MM-DD'\s*\)/gi,
|
|
74
|
+
"",
|
|
75
|
+
)
|
|
76
|
+
.replace(
|
|
77
|
+
/\s+WHERE\s+substr\s*\(\s*timestamp\s*,\s*1\s*,\s*10\s*\)\s*>=\s*to_char\s*\(\s*CURRENT_DATE\s*-\s*INTERVAL\s*'\d+\s*days?'\s*,\s*'YYYY-MM-DD'\s*\)\s+AND\s+/gi,
|
|
78
|
+
" WHERE ",
|
|
79
|
+
)
|
|
80
|
+
.replace(
|
|
81
|
+
/\s+AND\s+event_date\s*>=\s*to_char\s*\(\s*CURRENT_DATE\s*-\s*INTERVAL\s*'\d+\s*days?'\s*,\s*'YYYY-MM-DD'\s*\)/gi,
|
|
82
|
+
"",
|
|
83
|
+
)
|
|
84
|
+
.replace(
|
|
85
|
+
/\s+WHERE\s+event_date\s*>=\s*to_char\s*\(\s*CURRENT_DATE\s*-\s*INTERVAL\s*'\d+\s*days?'\s*,\s*'YYYY-MM-DD'\s*\)\s+AND\s+/gi,
|
|
86
|
+
" WHERE ",
|
|
87
|
+
)
|
|
73
88
|
.replace(
|
|
74
89
|
/\s+AND\s+timestamp::timestamptz\s*>=\s*now\(\)\s*-\s*interval\s*'\d+\s*days?'/gi,
|
|
75
90
|
"",
|
|
@@ -99,19 +114,33 @@ function fixed(sql: string): (window?: MetricWindow) => string {
|
|
|
99
114
|
const TEMPLATE_EXPR =
|
|
100
115
|
"COALESCE(NULLIF(template, ''), NULLIF(properties::jsonb ->> 'templateId', ''), NULLIF(properties::jsonb ->> 'agent_native_template', ''), NULLIF(properties::jsonb ->> 'agentNativeTemplate', ''), NULLIF(app, ''), NULLIF(properties::jsonb ->> 'agent_native_app', ''), NULLIF(properties::jsonb ->> 'agentNativeApp', ''), 'unknown')";
|
|
101
116
|
const KNOWN_TEMPLATE_FILTER = `${TEMPLATE_EXPR} <> 'unknown'`;
|
|
102
|
-
const
|
|
117
|
+
const PRODUCT_ACTIVITY_TEMPLATE_FILTER = `${TEMPLATE_EXPR} <> 'docs'`;
|
|
118
|
+
const KNOWN_PRODUCT_ACTIVITY_TEMPLATE_FILTER = `${KNOWN_TEMPLATE_FILTER} AND ${PRODUCT_ACTIVITY_TEMPLATE_FILTER}`;
|
|
119
|
+
const EVENT_DATE_SQL = "event_date";
|
|
120
|
+
const EVENT_DATE_FILTER_SQL = EVENT_DATE_SQL;
|
|
121
|
+
const USER_KEY_SQL = "NULLIF(user_key, '')";
|
|
122
|
+
const RETENTION_ROLLING_DAYS = 7;
|
|
123
|
+
const RETENTION_MIN_COHORT_SIZE = 5;
|
|
103
124
|
|
|
104
125
|
function daysAgoSql(days: number): string {
|
|
105
126
|
const unit = days === 1 ? "day" : "days";
|
|
106
127
|
return `to_char(CURRENT_DATE - INTERVAL '${days} ${unit}', 'YYYY-MM-DD')`;
|
|
107
128
|
}
|
|
108
129
|
|
|
109
|
-
function
|
|
130
|
+
function windowStartFilter(days: number): string {
|
|
131
|
+
return `${EVENT_DATE_SQL} >= ${daysAgoSql(days)}`;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function rollingWindowStartSql(anchorExpr = "a.date"): string {
|
|
135
|
+
return `to_char(${anchorExpr}::date - INTERVAL '${RETENTION_ROLLING_DAYS - 1} days', 'YYYY-MM-DD')`;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function dashboardTimeRangeFilter(dateExpr = EVENT_DATE_FILTER_SQL): string {
|
|
110
139
|
return `('{{timeRange}}' IN ('', 'all') OR ('{{timeRange}}' = '7d' AND ${dateExpr} >= ${daysAgoSql(7)}) OR ('{{timeRange}}' = '30d' AND ${dateExpr} >= ${daysAgoSql(30)}) OR ('{{timeRange}}' = '90d' AND ${dateExpr} >= ${daysAgoSql(90)}) OR ('{{timeRange}}' = '180d' AND ${dateExpr} >= ${daysAgoSql(180)}) OR ('{{timeRange}}' = '365d' AND ${dateExpr} >= ${daysAgoSql(365)}))`;
|
|
111
140
|
}
|
|
112
141
|
|
|
113
142
|
function dashboardLookbackTimeRangeFilter(
|
|
114
|
-
dateExpr =
|
|
143
|
+
dateExpr = EVENT_DATE_FILTER_SQL,
|
|
115
144
|
lookbackDays = 0,
|
|
116
145
|
): string {
|
|
117
146
|
return `('{{timeRange}}' IN ('', 'all') OR ('{{timeRange}}' = '7d' AND ${dateExpr} >= ${daysAgoSql(7 + lookbackDays)}) OR ('{{timeRange}}' = '30d' AND ${dateExpr} >= ${daysAgoSql(30 + lookbackDays)}) OR ('{{timeRange}}' = '90d' AND ${dateExpr} >= ${daysAgoSql(90 + lookbackDays)}) OR ('{{timeRange}}' = '180d' AND ${dateExpr} >= ${daysAgoSql(180 + lookbackDays)}) OR ('{{timeRange}}' = '365d' AND ${dateExpr} >= ${daysAgoSql(365 + lookbackDays)}))`;
|
|
@@ -121,11 +150,14 @@ const DASHBOARD_TIME_RANGE_FILTER = dashboardTimeRangeFilter();
|
|
|
121
150
|
const DASHBOARD_EVENT_DATE_RANGE_FILTER =
|
|
122
151
|
dashboardTimeRangeFilter("event_date");
|
|
123
152
|
const DASHBOARD_WAU_BASE_RANGE_FILTER = dashboardLookbackTimeRangeFilter(
|
|
124
|
-
|
|
153
|
+
EVENT_DATE_FILTER_SQL,
|
|
125
154
|
6,
|
|
126
155
|
);
|
|
127
156
|
const DASHBOARD_EMAIL_FILTER =
|
|
128
157
|
"('{{emailFilter}}' IN ('', 'all') OR ('{{emailFilter}}' = 'exclude_builder' AND lower(coalesce(user_id, '')) NOT LIKE '%@builder.io') OR ('{{emailFilter}}' = 'only_builder' AND lower(coalesce(user_id, '')) LIKE '%@builder.io'))";
|
|
158
|
+
const SIGNED_IN_ACTIVITY_KEY_SQL = USER_KEY_SQL;
|
|
159
|
+
const SIGNED_IN_ACTIVITY_FILTER = `event_name = 'session status' AND signed_in = 'true' AND ${SIGNED_IN_ACTIVITY_KEY_SQL} IS NOT NULL`;
|
|
160
|
+
const SIGNED_IN_PRODUCT_ACTIVITY_FILTER = `${SIGNED_IN_ACTIVITY_FILTER} AND ${PRODUCT_ACTIVITY_TEMPLATE_FILTER}`;
|
|
129
161
|
export const FIRST_PARTY_DASHBOARD_FILTERS: FirstPartyDashboardFilter[] = [
|
|
130
162
|
{
|
|
131
163
|
id: "timeRange",
|
|
@@ -166,13 +198,13 @@ export function usesFirstPartyDashboardFilters(sql: string): boolean {
|
|
|
166
198
|
}
|
|
167
199
|
|
|
168
200
|
const TOTAL_SIGNUPS_SQL = `SELECT COUNT(*) AS signups FROM analytics_events WHERE event_name = 'signup' AND ${DASHBOARD_TIME_RANGE_FILTER} AND ${DASHBOARD_EMAIL_FILTER}`;
|
|
169
|
-
const SIGNUPS_OVER_TIME_SQL = `WITH offsets AS (SELECT (ROW_NUMBER() OVER (ORDER BY
|
|
170
|
-
const RETENTION_OVER_TIME_SQL = `WITH base AS (SELECT
|
|
171
|
-
const ONE_DAY_RETENTION_BY_TEMPLATE_SQL = `WITH base AS (SELECT
|
|
172
|
-
const SEVEN_DAY_RETENTION_BY_TEMPLATE_SQL = `WITH base AS (SELECT
|
|
201
|
+
const SIGNUPS_OVER_TIME_SQL = `WITH offsets AS (SELECT (ROW_NUMBER() OVER (ORDER BY ${EVENT_DATE_SQL}) - 1)::int AS n FROM analytics_events LIMIT 800), signup_events AS (SELECT ${EVENT_DATE_SQL} AS date, ${TEMPLATE_EXPR} AS template FROM analytics_events WHERE event_name = 'signup' AND ${DASHBOARD_TIME_RANGE_FILTER} AND ${DASHBOARD_EMAIL_FILTER}), bounds AS (SELECT MIN(date::date) AS start_date, MAX(date::date) AS end_date FROM signup_events), dates AS (SELECT to_char(bounds.start_date + offsets.n, 'YYYY-MM-DD') AS date FROM bounds CROSS JOIN offsets WHERE bounds.start_date IS NOT NULL AND bounds.start_date + offsets.n <= bounds.end_date), templates AS (SELECT DISTINCT template FROM signup_events), daily AS (SELECT date, template, COUNT(*) AS count FROM signup_events GROUP BY date, template) SELECT dates.date, templates.template, COALESCE(daily.count, 0) AS count FROM dates CROSS JOIN templates LEFT JOIN daily ON daily.date = dates.date AND daily.template = templates.template ORDER BY dates.date, templates.template`;
|
|
202
|
+
const RETENTION_OVER_TIME_SQL = `WITH base AS (SELECT ${SIGNED_IN_ACTIVITY_KEY_SQL} AS user_key, ${EVENT_DATE_SQL} AS event_date, user_id FROM analytics_events WHERE ${SIGNED_IN_PRODUCT_ACTIVITY_FILTER} AND ${DASHBOARD_EMAIL_FILTER}), first_seen AS (SELECT user_key, MIN(event_date) AS cohort_date FROM base GROUP BY user_key), anchor_dates AS (SELECT DISTINCT cohort_date AS date FROM first_seen WHERE cohort_date <= ${daysAgoSql(7)} AND ${dashboardTimeRangeFilter("cohort_date")}), cohort_windows AS (SELECT a.date, f.user_key, f.cohort_date FROM anchor_dates a JOIN first_seen f ON f.cohort_date >= ${rollingWindowStartSql()} AND f.cohort_date <= a.date), cohort_sizes AS (SELECT date, COUNT(DISTINCT user_key) AS users FROM cohort_windows GROUP BY date), periods AS (SELECT '1d retention' AS period UNION ALL SELECT '7d retention' AS period), retained AS (SELECT cw.date, '1d retention' AS period, COUNT(DISTINCT cw.user_key) AS retained FROM cohort_windows cw JOIN base b ON b.user_key = cw.user_key AND b.event_date = to_char(cw.cohort_date::date + INTERVAL '1 day', 'YYYY-MM-DD') GROUP BY cw.date UNION ALL SELECT cw.date, '7d retention' AS period, COUNT(DISTINCT cw.user_key) AS retained FROM cohort_windows cw JOIN base b ON b.user_key = cw.user_key AND b.event_date = to_char(cw.cohort_date::date + INTERVAL '7 days', 'YYYY-MM-DD') GROUP BY cw.date) SELECT cs.date, p.period, COALESCE(r.retained, 0) AS retained_users, cs.users AS cohort_users, COALESCE(r.retained::float / NULLIF(cs.users, 0), 0) AS rate FROM cohort_sizes cs CROSS JOIN periods p LEFT JOIN retained r ON r.date = cs.date AND r.period = p.period WHERE cs.users >= ${RETENTION_MIN_COHORT_SIZE} ORDER BY cs.date, p.period`;
|
|
203
|
+
const ONE_DAY_RETENTION_BY_TEMPLATE_SQL = `WITH base AS (SELECT ${SIGNED_IN_ACTIVITY_KEY_SQL} AS user_key, ${TEMPLATE_EXPR} AS template, ${EVENT_DATE_SQL} AS event_date, user_id FROM analytics_events WHERE ${SIGNED_IN_ACTIVITY_FILTER} AND ${DASHBOARD_EMAIL_FILTER} AND ${KNOWN_PRODUCT_ACTIVITY_TEMPLATE_FILTER}), first_seen AS (SELECT user_key, template, MIN(event_date) AS cohort_date FROM base GROUP BY user_key, template), anchor_dates AS (SELECT DISTINCT cohort_date AS date, template FROM first_seen WHERE cohort_date <= ${daysAgoSql(1)} AND ${dashboardTimeRangeFilter("cohort_date")}), cohort_windows AS (SELECT a.date, a.template, f.user_key, f.cohort_date FROM anchor_dates a JOIN first_seen f ON f.template = a.template AND f.cohort_date >= ${rollingWindowStartSql()} AND f.cohort_date <= a.date), cohort_sizes AS (SELECT date, template, COUNT(DISTINCT user_key) AS users FROM cohort_windows GROUP BY date, template), retained AS (SELECT cw.date, cw.template, COUNT(DISTINCT cw.user_key) AS retained FROM cohort_windows cw JOIN base b ON b.user_key = cw.user_key AND b.template = cw.template AND b.event_date = to_char(cw.cohort_date::date + INTERVAL '1 day', 'YYYY-MM-DD') GROUP BY cw.date, cw.template) SELECT cs.date, cs.template, COALESCE(r.retained, 0) AS retained_users, cs.users AS cohort_users, COALESCE(r.retained::float / NULLIF(cs.users, 0), 0) AS rate FROM cohort_sizes cs LEFT JOIN retained r ON r.date = cs.date AND r.template = cs.template WHERE cs.users >= ${RETENTION_MIN_COHORT_SIZE} ORDER BY cs.date, cs.template`;
|
|
204
|
+
const SEVEN_DAY_RETENTION_BY_TEMPLATE_SQL = `WITH base AS (SELECT ${SIGNED_IN_ACTIVITY_KEY_SQL} AS user_key, ${TEMPLATE_EXPR} AS template, ${EVENT_DATE_SQL} AS event_date, user_id FROM analytics_events WHERE ${SIGNED_IN_ACTIVITY_FILTER} AND ${DASHBOARD_EMAIL_FILTER} AND ${KNOWN_PRODUCT_ACTIVITY_TEMPLATE_FILTER}), first_seen AS (SELECT user_key, template, MIN(event_date) AS cohort_date FROM base GROUP BY user_key, template), anchor_dates AS (SELECT DISTINCT cohort_date AS date, template FROM first_seen WHERE cohort_date <= ${daysAgoSql(7)} AND ${dashboardTimeRangeFilter("cohort_date")}), cohort_windows AS (SELECT a.date, a.template, f.user_key, f.cohort_date FROM anchor_dates a JOIN first_seen f ON f.template = a.template AND f.cohort_date >= ${rollingWindowStartSql()} AND f.cohort_date <= a.date), cohort_sizes AS (SELECT date, template, COUNT(DISTINCT user_key) AS users FROM cohort_windows GROUP BY date, template), retained AS (SELECT cw.date, cw.template, COUNT(DISTINCT cw.user_key) AS retained FROM cohort_windows cw JOIN base b ON b.user_key = cw.user_key AND b.template = cw.template AND b.event_date = to_char(cw.cohort_date::date + INTERVAL '7 days', 'YYYY-MM-DD') GROUP BY cw.date, cw.template) SELECT cs.date, cs.template, COALESCE(r.retained, 0) AS retained_users, cs.users AS cohort_users, COALESCE(r.retained::float / NULLIF(cs.users, 0), 0) AS rate FROM cohort_sizes cs LEFT JOIN retained r ON r.date = cs.date AND r.template = cs.template WHERE cs.users >= ${RETENTION_MIN_COHORT_SIZE} ORDER BY cs.date, cs.template`;
|
|
173
205
|
const SIGNUPS_BY_TEMPLATE_SQL = `SELECT ${TEMPLATE_EXPR} AS template, COUNT(*) AS count FROM analytics_events WHERE event_name = 'signup' AND ${DASHBOARD_TIME_RANGE_FILTER} AND ${DASHBOARD_EMAIL_FILTER} GROUP BY ${TEMPLATE_EXPR} ORDER BY count DESC`;
|
|
174
|
-
const DAU_BY_TEMPLATE_SQL = `SELECT
|
|
175
|
-
const WAU_BY_TEMPLATE_SQL = `WITH base AS (SELECT
|
|
206
|
+
const DAU_BY_TEMPLATE_SQL = `SELECT ${EVENT_DATE_SQL} AS date, ${TEMPLATE_EXPR} AS template, COUNT(DISTINCT ${SIGNED_IN_ACTIVITY_KEY_SQL}) AS visitors FROM analytics_events WHERE ${SIGNED_IN_ACTIVITY_FILTER} AND ${DASHBOARD_TIME_RANGE_FILTER} AND ${DASHBOARD_EMAIL_FILTER} AND ${KNOWN_PRODUCT_ACTIVITY_TEMPLATE_FILTER} GROUP BY ${EVENT_DATE_SQL}, ${TEMPLATE_EXPR} ORDER BY date, template`;
|
|
207
|
+
const WAU_BY_TEMPLATE_SQL = `WITH base AS (SELECT ${SIGNED_IN_ACTIVITY_KEY_SQL} AS visitor_key, ${TEMPLATE_EXPR} AS template, ${EVENT_DATE_SQL} AS event_date, user_id FROM analytics_events WHERE ${SIGNED_IN_ACTIVITY_FILTER} AND ${DASHBOARD_EMAIL_FILTER} AND ${KNOWN_PRODUCT_ACTIVITY_TEMPLATE_FILTER} AND ${DASHBOARD_WAU_BASE_RANGE_FILTER}), days AS (SELECT DISTINCT event_date AS date FROM base WHERE ${DASHBOARD_EVENT_DATE_RANGE_FILTER}) SELECT d.date, b.template, COUNT(DISTINCT b.visitor_key) AS visitors FROM days d JOIN base b ON b.event_date >= to_char(d.date::date - INTERVAL '6 days', 'YYYY-MM-DD') AND b.event_date <= d.date GROUP BY d.date, b.template ORDER BY d.date, b.template`;
|
|
176
208
|
|
|
177
209
|
/**
|
|
178
210
|
* Catalog entries. Order here is the default panel order when a caller passes
|
|
@@ -259,7 +291,7 @@ const ENTRIES: FirstPartyMetric[] = [
|
|
|
259
291
|
width: 1,
|
|
260
292
|
windowed: false,
|
|
261
293
|
buildSql: fixed(
|
|
262
|
-
|
|
294
|
+
`SELECT ${EVENT_DATE_SQL} AS date, COUNT(*) AS count FROM analytics_events WHERE event_name = 'session status' GROUP BY ${EVENT_DATE_SQL} ORDER BY date`,
|
|
263
295
|
),
|
|
264
296
|
config: { xKey: "date", yKey: "count", color: "#10b981" },
|
|
265
297
|
},
|
|
@@ -339,7 +371,7 @@ const ENTRIES: FirstPartyMetric[] = [
|
|
|
339
371
|
width: 2,
|
|
340
372
|
windowed: false,
|
|
341
373
|
buildSql: fixed(
|
|
342
|
-
`SELECT
|
|
374
|
+
`SELECT ${EVENT_DATE_SQL} AS date, COALESCE(NULLIF(template, ''), 'unknown') AS template, COUNT(*) AS count FROM analytics_events WHERE event_name = 'click template' AND ${DASHBOARD_TIME_RANGE_FILTER} AND ${DASHBOARD_EMAIL_FILTER} GROUP BY ${EVENT_DATE_SQL}, COALESCE(NULLIF(template, ''), 'unknown') ORDER BY date, template`,
|
|
343
375
|
),
|
|
344
376
|
config: {
|
|
345
377
|
xKey: "date",
|
|
@@ -361,7 +393,7 @@ const ENTRIES: FirstPartyMetric[] = [
|
|
|
361
393
|
width: 2,
|
|
362
394
|
windowed: false,
|
|
363
395
|
buildSql: fixed(
|
|
364
|
-
`SELECT
|
|
396
|
+
`SELECT ${EVENT_DATE_SQL} AS date, COALESCE(NULLIF(template, ''), 'unknown') AS template, COUNT(*) AS count FROM analytics_events WHERE event_name = 'click template' AND ${DASHBOARD_TIME_RANGE_FILTER} AND ${DASHBOARD_EMAIL_FILTER} GROUP BY ${EVENT_DATE_SQL}, COALESCE(NULLIF(template, ''), 'unknown') ORDER BY date, template`,
|
|
365
397
|
),
|
|
366
398
|
config: {
|
|
367
399
|
xKey: "date",
|
|
@@ -376,14 +408,14 @@ const ENTRIES: FirstPartyMetric[] = [
|
|
|
376
408
|
},
|
|
377
409
|
},
|
|
378
410
|
{
|
|
379
|
-
key: "demo-clicks-
|
|
411
|
+
key: "demo-clicks-over-time",
|
|
380
412
|
title: "Try-Demo Clicks Over Time",
|
|
381
413
|
chartType: "area",
|
|
382
414
|
source: "first-party",
|
|
383
415
|
width: 2,
|
|
384
416
|
windowed: false,
|
|
385
417
|
buildSql: fixed(
|
|
386
|
-
`SELECT
|
|
418
|
+
`SELECT ${EVENT_DATE_SQL} AS date, COALESCE(NULLIF(template, ''), 'unknown') AS template, COUNT(*) AS count FROM analytics_events WHERE event_name = 'click try demo' AND ${DASHBOARD_TIME_RANGE_FILTER} AND ${DASHBOARD_EMAIL_FILTER} GROUP BY ${EVENT_DATE_SQL}, COALESCE(NULLIF(template, ''), 'unknown') ORDER BY date, template`,
|
|
387
419
|
),
|
|
388
420
|
config: {
|
|
389
421
|
xKey: "date",
|
|
@@ -405,7 +437,7 @@ const ENTRIES: FirstPartyMetric[] = [
|
|
|
405
437
|
width: 2,
|
|
406
438
|
windowed: false,
|
|
407
439
|
buildSql: fixed(
|
|
408
|
-
`SELECT
|
|
440
|
+
`SELECT ${EVENT_DATE_SQL} AS date, COALESCE(NULLIF(template, ''), 'unknown') AS template, COUNT(*) AS count FROM analytics_events WHERE event_name = 'copy cli command' AND ${DASHBOARD_TIME_RANGE_FILTER} AND ${DASHBOARD_EMAIL_FILTER} GROUP BY ${EVENT_DATE_SQL}, COALESCE(NULLIF(template, ''), 'unknown') ORDER BY date, template`,
|
|
409
441
|
),
|
|
410
442
|
config: {
|
|
411
443
|
xKey: "date",
|
|
@@ -427,7 +459,7 @@ const ENTRIES: FirstPartyMetric[] = [
|
|
|
427
459
|
width: 2,
|
|
428
460
|
windowed: false,
|
|
429
461
|
buildSql: fixed(
|
|
430
|
-
`SELECT
|
|
462
|
+
`SELECT ${EVENT_DATE_SQL} AS date, COALESCE(NULLIF(template, ''), 'unknown') AS template, COUNT(*) AS count FROM analytics_events WHERE event_name = 'copy cli command' AND ${DASHBOARD_TIME_RANGE_FILTER} AND ${DASHBOARD_EMAIL_FILTER} GROUP BY ${EVENT_DATE_SQL}, COALESCE(NULLIF(template, ''), 'unknown') ORDER BY date, template`,
|
|
431
463
|
),
|
|
432
464
|
config: {
|
|
433
465
|
xKey: "date",
|
|
@@ -453,7 +485,7 @@ const ENTRIES: FirstPartyMetric[] = [
|
|
|
453
485
|
// Browser telemetry emits explicit `pageview` events with URL context, so
|
|
454
486
|
// keep pageview panels scoped to that event instead of all tracked events.
|
|
455
487
|
buildSql: fixed(
|
|
456
|
-
`SELECT
|
|
488
|
+
`SELECT ${EVENT_DATE_SQL} AS date, COALESCE(NULLIF(template, ''), NULLIF(app, ''), 'unknown') AS template, COUNT(*) AS count FROM analytics_events WHERE event_name = 'pageview' AND ${DASHBOARD_TIME_RANGE_FILTER} AND ${DASHBOARD_EMAIL_FILTER} GROUP BY ${EVENT_DATE_SQL}, COALESCE(NULLIF(template, ''), NULLIF(app, ''), 'unknown') ORDER BY date, template`,
|
|
457
489
|
),
|
|
458
490
|
config: {
|
|
459
491
|
xKey: "date",
|
|
@@ -476,7 +508,7 @@ const ENTRIES: FirstPartyMetric[] = [
|
|
|
476
508
|
width: 2,
|
|
477
509
|
windowed: false,
|
|
478
510
|
buildSql: fixed(
|
|
479
|
-
`WITH pageviews AS (SELECT COALESCE(CASE WHEN NULLIF(hostname, '') IS NOT NULL THEN 'https://' || hostname || COALESCE(NULLIF(path, ''), '/') END, NULLIF(url, ''), NULLIF(path, ''), 'unknown') AS url, CASE WHEN NULLIF(hostname, '') IS NOT NULL THEN 'https://' || hostname || COALESCE(NULLIF(path, ''), '/') WHEN lower(COALESCE(NULLIF(url, ''), '')) LIKE 'http://%' OR lower(COALESCE(NULLIF(url, ''), '')) LIKE 'https://%' THEN url WHEN NULLIF(path, '') IS NOT NULL AND substr(path, 1, 1) = '/' AND substr(path, 1, 2) != '//' THEN path ELSE '' END AS href, COUNT(*) AS views, COUNT(DISTINCT
|
|
511
|
+
`WITH pageviews AS (SELECT COALESCE(CASE WHEN NULLIF(hostname, '') IS NOT NULL THEN 'https://' || hostname || COALESCE(NULLIF(path, ''), '/') END, NULLIF(url, ''), NULLIF(path, ''), 'unknown') AS url, CASE WHEN NULLIF(hostname, '') IS NOT NULL THEN 'https://' || hostname || COALESCE(NULLIF(path, ''), '/') WHEN lower(COALESCE(NULLIF(url, ''), '')) LIKE 'http://%' OR lower(COALESCE(NULLIF(url, ''), '')) LIKE 'https://%' THEN url WHEN NULLIF(path, '') IS NOT NULL AND substr(path, 1, 1) = '/' AND substr(path, 1, 2) != '//' THEN path ELSE '' END AS href, COUNT(*) AS views, COUNT(DISTINCT ${USER_KEY_SQL}) AS users, MAX(${EVENT_DATE_SQL}) AS last_seen FROM analytics_events WHERE event_name = 'pageview' AND ${DASHBOARD_TIME_RANGE_FILTER} AND ${DASHBOARD_EMAIL_FILTER} GROUP BY 1, 2) SELECT url, views, users, last_seen, href FROM pageviews ORDER BY views DESC LIMIT 25`,
|
|
480
512
|
),
|
|
481
513
|
config: {
|
|
482
514
|
description:
|
|
@@ -500,7 +532,7 @@ const ENTRIES: FirstPartyMetric[] = [
|
|
|
500
532
|
width: 2,
|
|
501
533
|
windowed: false,
|
|
502
534
|
buildSql: fixed(
|
|
503
|
-
`WITH raw_referrers AS (SELECT COALESCE(NULLIF(referrer, ''), NULLIF(properties::jsonb ->> 'referrer', ''), NULLIF(properties::jsonb ->> 'landing_referrer', '')) AS raw_referrer, NULLIF(hostname, '') AS page_host,
|
|
535
|
+
`WITH raw_referrers AS (SELECT COALESCE(NULLIF(referrer, ''), NULLIF(properties::jsonb ->> 'referrer', ''), NULLIF(properties::jsonb ->> 'landing_referrer', '')) AS raw_referrer, NULLIF(hostname, '') AS page_host, ${USER_KEY_SQL} AS user_key FROM analytics_events WHERE event_name = 'pageview' AND ${DASHBOARD_TIME_RANGE_FILTER} AND ${DASHBOARD_EMAIL_FILTER}), referrer_domains AS (SELECT lower(CASE WHEN lower(raw_referrer) LIKE 'http://%' OR lower(raw_referrer) LIKE 'https://%' THEN split_part(split_part(split_part(raw_referrer, '://', 2), '/', 1), chr(63), 1) WHEN raw_referrer LIKE '//%' THEN split_part(split_part(substr(raw_referrer, 3), '/', 1), chr(63), 1) ELSE split_part(split_part(raw_referrer, '/', 1), chr(63), 1) END) AS referrer_domain, lower(page_host) AS page_host, user_key FROM raw_referrers WHERE raw_referrer IS NOT NULL) SELECT referrer_domain, COUNT(*) AS visits, COUNT(DISTINCT user_key) AS users FROM referrer_domains WHERE referrer_domain <> '' AND (page_host IS NULL OR referrer_domain <> page_host) GROUP BY referrer_domain ORDER BY visits DESC LIMIT 20`,
|
|
504
536
|
),
|
|
505
537
|
config: {
|
|
506
538
|
description:
|
|
@@ -522,7 +554,7 @@ const ENTRIES: FirstPartyMetric[] = [
|
|
|
522
554
|
width: 2,
|
|
523
555
|
windowed: false,
|
|
524
556
|
buildSql: fixed(
|
|
525
|
-
`WITH clip_events AS (SELECT COALESCE(NULLIF(properties::jsonb ->> 'recording_id', ''), NULLIF(path, ''), NULLIF(url, ''), 'unknown') AS clip_key, CASE WHEN lower(COALESCE(NULLIF(url, ''), '')) LIKE 'http://%' OR lower(COALESCE(NULLIF(url, ''), '')) LIKE 'https://%' THEN url WHEN NULLIF(hostname, '') IS NOT NULL THEN 'https://' || hostname || COALESCE(NULLIF(path, ''), '/') WHEN NULLIF(path, '') LIKE '/%' THEN 'https://clips.agent-native.com' || path ELSE '' END AS href,
|
|
557
|
+
`WITH clip_events AS (SELECT COALESCE(NULLIF(properties::jsonb ->> 'recording_id', ''), NULLIF(path, ''), NULLIF(url, ''), 'unknown') AS clip_key, CASE WHEN lower(COALESCE(NULLIF(url, ''), '')) LIKE 'http://%' OR lower(COALESCE(NULLIF(url, ''), '')) LIKE 'https://%' THEN url WHEN NULLIF(hostname, '') IS NOT NULL THEN 'https://' || hostname || COALESCE(NULLIF(path, ''), '/') WHEN NULLIF(path, '') LIKE '/%' THEN 'https://clips.agent-native.com' || path ELSE '' END AS href, ${USER_KEY_SQL} AS user_key, ${EVENT_DATE_SQL} AS event_date FROM analytics_events WHERE event_name = 'share_view' AND ${DASHBOARD_TIME_RANGE_FILTER} AND ${DASHBOARD_EMAIL_FILTER} AND COALESCE(NULLIF(properties::jsonb ->> 'surface', ''), 'clip') = 'clip'), clip_views AS (SELECT clip_key, COALESCE(MAX(NULLIF(href, '')), clip_key) AS href, COUNT(*) AS views, COUNT(DISTINCT user_key) AS users, MAX(event_date) AS last_seen FROM clip_events GROUP BY clip_key) SELECT CASE WHEN lower(COALESCE(href, '')) LIKE 'http://%' OR lower(COALESCE(href, '')) LIKE 'https://%' THEN href ELSE clip_key END AS clip, views, users, last_seen, href FROM clip_views ORDER BY views DESC LIMIT 25`,
|
|
526
558
|
),
|
|
527
559
|
config: {
|
|
528
560
|
description: "Most-viewed shared Clips pages in the selected time range.",
|
|
@@ -539,24 +571,24 @@ const ENTRIES: FirstPartyMetric[] = [
|
|
|
539
571
|
},
|
|
540
572
|
{
|
|
541
573
|
key: "repeat-users",
|
|
542
|
-
title: "Repeat
|
|
574
|
+
title: "Repeat Signed-In Visitors",
|
|
543
575
|
chartType: "metric",
|
|
544
576
|
source: "first-party",
|
|
545
577
|
width: 1,
|
|
546
578
|
windowed: false,
|
|
547
579
|
buildSql: fixed(
|
|
548
|
-
`WITH user_days AS (SELECT
|
|
580
|
+
`WITH user_days AS (SELECT ${SIGNED_IN_ACTIVITY_KEY_SQL} AS user_key, COUNT(DISTINCT ${EVENT_DATE_SQL}) AS active_days FROM analytics_events WHERE ${SIGNED_IN_PRODUCT_ACTIVITY_FILTER} AND ${DASHBOARD_TIME_RANGE_FILTER} AND ${DASHBOARD_EMAIL_FILTER} GROUP BY ${SIGNED_IN_ACTIVITY_KEY_SQL}) SELECT COUNT(*) AS count FROM user_days WHERE active_days >= 2`,
|
|
549
581
|
),
|
|
550
582
|
config: {
|
|
551
583
|
yKey: "count",
|
|
552
584
|
yFormatter: "number",
|
|
553
585
|
description:
|
|
554
|
-
"
|
|
586
|
+
"Signed-in browser identities with non-docs app session activity on at least two days in the selected time range.",
|
|
555
587
|
},
|
|
556
588
|
},
|
|
557
589
|
{
|
|
558
590
|
key: "retention-over-time",
|
|
559
|
-
title: "
|
|
591
|
+
title: "7d Rolling Signed-In Retention",
|
|
560
592
|
chartType: "line",
|
|
561
593
|
source: "first-party",
|
|
562
594
|
width: 3,
|
|
@@ -573,12 +605,12 @@ const ENTRIES: FirstPartyMetric[] = [
|
|
|
573
605
|
},
|
|
574
606
|
colors: ["#10b981", "#8b5cf6"],
|
|
575
607
|
description:
|
|
576
|
-
"
|
|
608
|
+
"Trailing 7-day first-seen signed-in app session cohorts, keyed by browser identity. Docs traffic is excluded; windows under 5 identities are hidden.",
|
|
577
609
|
},
|
|
578
610
|
},
|
|
579
611
|
{
|
|
580
612
|
key: "one-day-retention-by-template",
|
|
581
|
-
title: "1d Retention by Template",
|
|
613
|
+
title: "7d Rolling 1d Signed-In Retention by Template",
|
|
582
614
|
chartType: "line",
|
|
583
615
|
source: "first-party",
|
|
584
616
|
width: 2,
|
|
@@ -594,12 +626,12 @@ const ENTRIES: FirstPartyMetric[] = [
|
|
|
594
626
|
valueKey: "rate",
|
|
595
627
|
},
|
|
596
628
|
description:
|
|
597
|
-
"
|
|
629
|
+
"Trailing 7-day per-template signed-in app session cohorts. Docs traffic is excluded; a browser identity is retained when it returns signed in to the same template the next day. Windows under 5 identities are hidden.",
|
|
598
630
|
},
|
|
599
631
|
},
|
|
600
632
|
{
|
|
601
633
|
key: "seven-day-retention-by-template",
|
|
602
|
-
title: "7d Retention by Template",
|
|
634
|
+
title: "7d Rolling 7d Signed-In Retention by Template",
|
|
603
635
|
chartType: "line",
|
|
604
636
|
source: "first-party",
|
|
605
637
|
width: 2,
|
|
@@ -615,12 +647,12 @@ const ENTRIES: FirstPartyMetric[] = [
|
|
|
615
647
|
valueKey: "rate",
|
|
616
648
|
},
|
|
617
649
|
description:
|
|
618
|
-
"
|
|
650
|
+
"Trailing 7-day per-template signed-in app session cohorts. Docs traffic is excluded; a browser identity is retained when it returns signed in to the same template seven days later. Windows under 5 identities are hidden.",
|
|
619
651
|
},
|
|
620
652
|
},
|
|
621
653
|
{
|
|
622
654
|
key: "dau-over-time",
|
|
623
|
-
title: "
|
|
655
|
+
title: "Signed-In Daily Active Visitors by Template",
|
|
624
656
|
chartType: "area",
|
|
625
657
|
source: "first-party",
|
|
626
658
|
width: 2,
|
|
@@ -628,21 +660,21 @@ const ENTRIES: FirstPartyMetric[] = [
|
|
|
628
660
|
buildSql: fixed(DAU_BY_TEMPLATE_SQL),
|
|
629
661
|
config: {
|
|
630
662
|
xKey: "date",
|
|
631
|
-
yKey: "
|
|
663
|
+
yKey: "visitors",
|
|
632
664
|
yFormatter: "number",
|
|
633
665
|
pivot: {
|
|
634
666
|
xKey: "date",
|
|
635
667
|
seriesKey: "template",
|
|
636
|
-
valueKey: "
|
|
668
|
+
valueKey: "visitors",
|
|
637
669
|
},
|
|
638
670
|
stacked: true,
|
|
639
671
|
description:
|
|
640
|
-
"Distinct
|
|
672
|
+
"Distinct signed-in browser identities per day, stacked by inferred template/app. Docs traffic is excluded. This is signed-in activity, not true account-level DAU, until session telemetry includes account identity.",
|
|
641
673
|
},
|
|
642
674
|
},
|
|
643
675
|
{
|
|
644
676
|
key: "wau-over-time",
|
|
645
|
-
title: "
|
|
677
|
+
title: "Signed-In Weekly Active Visitors by Template",
|
|
646
678
|
chartType: "area",
|
|
647
679
|
source: "first-party",
|
|
648
680
|
width: 2,
|
|
@@ -650,16 +682,16 @@ const ENTRIES: FirstPartyMetric[] = [
|
|
|
650
682
|
buildSql: fixed(WAU_BY_TEMPLATE_SQL),
|
|
651
683
|
config: {
|
|
652
684
|
xKey: "date",
|
|
653
|
-
yKey: "
|
|
685
|
+
yKey: "visitors",
|
|
654
686
|
yFormatter: "number",
|
|
655
687
|
pivot: {
|
|
656
688
|
xKey: "date",
|
|
657
689
|
seriesKey: "template",
|
|
658
|
-
valueKey: "
|
|
690
|
+
valueKey: "visitors",
|
|
659
691
|
},
|
|
660
692
|
stacked: true,
|
|
661
693
|
description:
|
|
662
|
-
"Trailing 7-day distinct
|
|
694
|
+
"Trailing 7-day distinct signed-in browser identities for each active date, stacked by inferred template/app. Docs traffic is excluded. This is signed-in activity, not true account-level WAU, until session telemetry includes account identity.",
|
|
663
695
|
},
|
|
664
696
|
},
|
|
665
697
|
|
|
@@ -672,7 +704,7 @@ const ENTRIES: FirstPartyMetric[] = [
|
|
|
672
704
|
width: 1,
|
|
673
705
|
windowed: true,
|
|
674
706
|
buildSql: windowed(
|
|
675
|
-
|
|
707
|
+
`SELECT COUNT(*) AS count FROM analytics_events WHERE event_name = 'signup' AND ${windowStartFilter(30)} AND properties::jsonb ->> 'referral_source' IS NOT NULL AND properties::jsonb ->> 'referral_source' <> '' AND properties::jsonb ->> 'referral_source' <> 'direct'`,
|
|
676
708
|
),
|
|
677
709
|
config: {
|
|
678
710
|
yKey: "count",
|
|
@@ -689,7 +721,7 @@ const ENTRIES: FirstPartyMetric[] = [
|
|
|
689
721
|
width: 1,
|
|
690
722
|
windowed: true,
|
|
691
723
|
buildSql: windowed(
|
|
692
|
-
|
|
724
|
+
`SELECT CASE WHEN COUNT(*) = 0 THEN 0 ELSE 1.0 * COUNT(*) FILTER (WHERE properties::jsonb ->> 'referral_source' IS NOT NULL AND properties::jsonb ->> 'referral_source' <> '' AND properties::jsonb ->> 'referral_source' <> 'direct') / COUNT(*) END AS rate FROM analytics_events WHERE event_name = 'signup' AND ${windowStartFilter(30)}`,
|
|
693
725
|
),
|
|
694
726
|
config: {
|
|
695
727
|
yKey: "rate",
|
|
@@ -706,7 +738,7 @@ const ENTRIES: FirstPartyMetric[] = [
|
|
|
706
738
|
width: 1,
|
|
707
739
|
windowed: true,
|
|
708
740
|
buildSql: windowed(
|
|
709
|
-
|
|
741
|
+
`SELECT COUNT(*) AS count FROM analytics_events WHERE event_name = 'signup' AND ${windowStartFilter(30)} AND properties::jsonb ->> 'referral_source' = 'clip_share'`,
|
|
710
742
|
),
|
|
711
743
|
config: {
|
|
712
744
|
yKey: "count",
|
|
@@ -722,7 +754,7 @@ const ENTRIES: FirstPartyMetric[] = [
|
|
|
722
754
|
width: 1,
|
|
723
755
|
windowed: true,
|
|
724
756
|
buildSql: windowed(
|
|
725
|
-
|
|
757
|
+
`SELECT COALESCE(NULLIF(properties::jsonb ->> 'referral_source', ''), 'direct') AS referral_source, COUNT(*) AS count FROM analytics_events WHERE event_name = 'signup' AND ${windowStartFilter(90)} GROUP BY COALESCE(NULLIF(properties::jsonb ->> 'referral_source', ''), 'direct') ORDER BY count DESC LIMIT 20`,
|
|
726
758
|
),
|
|
727
759
|
config: {
|
|
728
760
|
xKey: "referral_source",
|
|
@@ -740,7 +772,7 @@ const ENTRIES: FirstPartyMetric[] = [
|
|
|
740
772
|
width: 2,
|
|
741
773
|
windowed: true,
|
|
742
774
|
buildSql: windowed(
|
|
743
|
-
|
|
775
|
+
`SELECT ${EVENT_DATE_SQL} AS date, COUNT(*) AS count FROM analytics_events WHERE event_name = 'signup' AND ${windowStartFilter(90)} AND properties::jsonb ->> 'referral_source' IS NOT NULL AND properties::jsonb ->> 'referral_source' <> '' AND properties::jsonb ->> 'referral_source' <> 'direct' GROUP BY ${EVENT_DATE_SQL} ORDER BY date`,
|
|
744
776
|
),
|
|
745
777
|
config: {
|
|
746
778
|
xKey: "date",
|
|
@@ -757,7 +789,7 @@ const ENTRIES: FirstPartyMetric[] = [
|
|
|
757
789
|
width: 1,
|
|
758
790
|
windowed: true,
|
|
759
791
|
buildSql: windowed(
|
|
760
|
-
|
|
792
|
+
`SELECT properties::jsonb ->> 'referrer_user' AS referrer_user, COUNT(*) AS signups FROM analytics_events WHERE event_name = 'signup' AND ${windowStartFilter(90)} AND properties::jsonb ->> 'referrer_user' IS NOT NULL AND properties::jsonb ->> 'referrer_user' <> '' GROUP BY properties::jsonb ->> 'referrer_user' ORDER BY signups DESC LIMIT 20`,
|
|
761
793
|
),
|
|
762
794
|
config: {
|
|
763
795
|
description:
|
|
@@ -776,7 +808,7 @@ const ENTRIES: FirstPartyMetric[] = [
|
|
|
776
808
|
width: 2,
|
|
777
809
|
windowed: true,
|
|
778
810
|
buildSql: windowed(
|
|
779
|
-
|
|
811
|
+
`SELECT 'Share views' AS stage, COUNT(*) AS count FROM analytics_events WHERE event_name = 'share_view' AND ${windowStartFilter(30)} UNION ALL SELECT 'CTA clicks' AS stage, COUNT(*) AS count FROM analytics_events WHERE event_name = 'share_cta_click' AND ${windowStartFilter(30)} UNION ALL SELECT 'Clip-share signups' AS stage, COUNT(*) AS count FROM analytics_events WHERE event_name = 'signup' AND ${windowStartFilter(30)} AND properties::jsonb ->> 'referral_source' = 'clip_share'`,
|
|
780
812
|
),
|
|
781
813
|
config: {
|
|
782
814
|
xKey: "stage",
|
|
@@ -794,7 +826,7 @@ const ENTRIES: FirstPartyMetric[] = [
|
|
|
794
826
|
width: 1,
|
|
795
827
|
windowed: true,
|
|
796
828
|
buildSql: windowed(
|
|
797
|
-
|
|
829
|
+
`SELECT COALESCE(COUNT(*) FILTER (WHERE recent.auth_user_id IN (SELECT properties::jsonb ->> 'referrer_user' FROM analytics_events WHERE event_name = 'signup' AND properties::jsonb ->> 'referrer_user' IS NOT NULL AND properties::jsonb ->> 'referrer_user' <> ''))::float / NULLIF(COUNT(*), 0), 0) AS rate FROM (SELECT DISTINCT properties::jsonb ->> 'auth_user_id' AS auth_user_id FROM analytics_events WHERE event_name = 'signup' AND ${windowStartFilter(90)} AND properties::jsonb ->> 'auth_user_id' IS NOT NULL AND properties::jsonb ->> 'auth_user_id' <> '') AS recent`,
|
|
798
830
|
),
|
|
799
831
|
config: {
|
|
800
832
|
yKey: "rate",
|
|
@@ -811,7 +843,7 @@ const ENTRIES: FirstPartyMetric[] = [
|
|
|
811
843
|
width: 1,
|
|
812
844
|
windowed: true,
|
|
813
845
|
buildSql: windowed(
|
|
814
|
-
|
|
846
|
+
`SELECT COALESCE(COUNT(*) FILTER (WHERE properties::jsonb ->> 'referrer_user' IS NOT NULL AND properties::jsonb ->> 'referrer_user' <> '')::float / NULLIF(COUNT(DISTINCT properties::jsonb ->> 'auth_user_id'), 0), 0) AS k FROM analytics_events WHERE event_name = 'signup' AND ${windowStartFilter(90)}`,
|
|
815
847
|
),
|
|
816
848
|
config: {
|
|
817
849
|
yKey: "k",
|
|
@@ -828,7 +860,7 @@ const ENTRIES: FirstPartyMetric[] = [
|
|
|
828
860
|
width: 1,
|
|
829
861
|
windowed: true,
|
|
830
862
|
buildSql: windowed(
|
|
831
|
-
|
|
863
|
+
`SELECT COUNT(DISTINCT properties::jsonb ->> 'referrer_user') AS count FROM analytics_events WHERE event_name = 'signup' AND ${windowStartFilter(90)} AND properties::jsonb ->> 'referrer_user' IS NOT NULL AND properties::jsonb ->> 'referrer_user' <> ''`,
|
|
832
864
|
),
|
|
833
865
|
config: {
|
|
834
866
|
yKey: "count",
|