diff --git a/templates/analytics/app/components/layout/Layout.spec.ts b/templates/analytics/app/components/layout/Layout.spec.ts index 8f091b266b9..22b055f82e7 100644 --- a/templates/analytics/app/components/layout/Layout.spec.ts +++ b/templates/analytics/app/components/layout/Layout.spec.ts @@ -36,11 +36,11 @@ describe("Analytics layout sidebar route policy", () => { expect(source).toContain( 'className="min-h-0 min-w-0 flex flex-1 flex-col space-y-0.5 overflow-x-hidden overflow-y-auto px-2 py-3"', ); - expect(source).toContain(", ): { config: Record; changed: boolean } { @@ -86,7 +96,8 @@ export function repairFirstPartyBigQueryDashboardQueries( panel.id !== "wau-over-time" || panel.source !== "bigquery" || typeof panel.sql !== "string" || - panel.sql.trim() !== "" + (panel.sql.trim() !== "" && + !isMalformedFirstPartyBigQueryWauSql(panel.sql)) ) { return rawPanel; } diff --git a/templates/analytics/server/lib/first-party-dashboard-repair.spec.ts b/templates/analytics/server/lib/first-party-dashboard-repair.spec.ts index 6b19d897f3d..d2e6d57dcdf 100644 --- a/templates/analytics/server/lib/first-party-dashboard-repair.spec.ts +++ b/templates/analytics/server/lib/first-party-dashboard-repair.spec.ts @@ -474,6 +474,95 @@ describe("repairPersistedFirstPartyDashboardQueries", () => { expect(panels[2].sql).toBe(""); }); + it("repairs the malformed non-empty BigQuery wau query", async () => { + const weekly = requiredFirstPartyPanel("wau-over-time"); + const malformedSql = FIRST_PARTY_BIGQUERY_WAU_SQL.replace( + "WHEN '{{timeRange}}' = '7d'", + "WHEN '{{timeRange}}' = '{{timeRange}}'", + ); + const row = legacyRow({ + id: FIRST_PARTY_BIGQUERY_DASHBOARD_ID, + config: JSON.stringify({ + panels: [ + { + ...weekly, + source: "bigquery", + sql: malformedSql, + }, + ], + }), + }); + const mocks = createDb(row); + dbMocks.getDb.mockReturnValue(mocks.db); + + await expect(repairPersistedFirstPartyDashboardQueries()).resolves.toBe( + true, + ); + + const updateCalls = mocks.updateSet.mock.calls as unknown as Array< + [{ config: string }] + >; + expect(JSON.parse(updateCalls[0]![0].config).panels[0].sql).toBe( + FIRST_PARTY_BIGQUERY_WAU_SQL, + ); + }); + + it("preserves a customized malformed-looking BigQuery wau query", async () => { + const weekly = requiredFirstPartyPanel("wau-over-time"); + const customizedSql = FIRST_PARTY_BIGQUERY_WAU_SQL.replace( + "WHEN '{{timeRange}}' = '7d'", + "WHEN '{{timeRange}}' = '{{timeRange}}'", + ).replace("ORDER BY date, template", "ORDER BY template, date"); + const row = legacyRow({ + id: FIRST_PARTY_BIGQUERY_DASHBOARD_ID, + config: JSON.stringify({ + panels: [ + { + ...weekly, + source: "bigquery", + sql: customizedSql, + }, + ], + }), + }); + const mocks = createDb(row); + dbMocks.getDb.mockReturnValue(mocks.db); + + await expect(repairPersistedFirstPartyDashboardQueries()).resolves.toBe( + false, + ); + + expect(mocks.update).not.toHaveBeenCalled(); + }); + + it("preserves a malformed-looking query when a SQL literal changes", async () => { + const weekly = requiredFirstPartyPanel("wau-over-time"); + const customizedSql = FIRST_PARTY_BIGQUERY_WAU_SQL.replace( + "WHEN '{{timeRange}}' = '7d'", + "WHEN '{{timeRange}}' = '{{timeRange}}'", + ).replace("'session status'", "'session status'"); + const row = legacyRow({ + id: FIRST_PARTY_BIGQUERY_DASHBOARD_ID, + config: JSON.stringify({ + panels: [ + { + ...weekly, + source: "bigquery", + sql: customizedSql, + }, + ], + }), + }); + const mocks = createDb(row); + dbMocks.getDb.mockReturnValue(mocks.db); + + await expect(repairPersistedFirstPartyDashboardQueries()).resolves.toBe( + false, + ); + + expect(mocks.update).not.toHaveBeenCalled(); + }); + it("repairs the deployed materialized one-day retention panel during startup", async () => { const retention = requiredFirstPartyPanel("one-day-retention-by-template"); const row = legacyRow({