From 4a35174fb6f82f953e9bb3ffe45c69a5c4d80dc3 Mon Sep 17 00:00:00 2001 From: Marius Andra Date: Sat, 26 Oct 2024 12:28:07 +0200 Subject: [PATCH] feat(messaging): test sending e-mails (#25825) --- hogvm/typescript/package.json | 2 +- hogvm/typescript/src/execute.ts | 5 +- package.json | 2 +- plugin-server/package.json | 2 +- plugin-server/pnpm-lock.yaml | 8 +- plugin-server/src/cdp/cdp-api.ts | 4 +- plugin-server/src/cdp/hog-executor.ts | 51 ++++- plugin-server/src/cdp/hog-function-manager.ts | 4 + plugin-server/src/cdp/types.ts | 1 + plugin-server/src/cdp/utils.ts | 48 ++++- plugin-server/tests/cdp/cdp-api.test.ts | 38 ++++ plugin-server/tests/cdp/cdp-e2e.test.ts | 2 +- .../cdp/cdp-processed-events-consumer.test.ts | 2 +- plugin-server/tests/cdp/examples.ts | 117 +++++++++- plugin-server/tests/cdp/fixtures.ts | 1 + plugin-server/tests/cdp/hog-executor.test.ts | 77 ++++++- .../tests/cdp/hog-function-manager.test.ts | 3 + pnpm-lock.yaml | 11 +- .../cdp/templates/mailjet/template_mailjet.py | 8 +- .../test/__snapshots__/test_in_cohort.ambr | 8 +- .../test/__snapshots__/test_trends.ambr | 204 +++++++++++++++--- 21 files changed, 529 insertions(+), 69 deletions(-) diff --git a/hogvm/typescript/package.json b/hogvm/typescript/package.json index cdb4b64b03240..44aafe8789766 100644 --- a/hogvm/typescript/package.json +++ b/hogvm/typescript/package.json @@ -1,6 +1,6 @@ { "name": "@posthog/hogvm", - "version": "1.0.55", + "version": "1.0.56", "description": "PostHog Hog Virtual Machine", "types": "dist/index.d.ts", "source": "src/index.ts", diff --git a/hogvm/typescript/src/execute.ts b/hogvm/typescript/src/execute.ts index 2083de268e723..43016f3702602 100644 --- a/hogvm/typescript/src/execute.ts +++ b/hogvm/typescript/src/execute.ts @@ -122,7 +122,10 @@ export function exec(input: any[] | VMState | Bytecodes, options?: ExecOptions): let ops = vmState ? vmState.ops : 0 const timeout = options?.timeout ?? DEFAULT_TIMEOUT_MS const maxAsyncSteps = options?.maxAsyncSteps ?? DEFAULT_MAX_ASYNC_STEPS - const rootGlobals: Record = options?.globals ?? {} + const rootGlobals: Record = + bytecodes.root?.globals && options?.globals + ? { ...bytecodes.root.globals, ...options.globals } + : bytecodes.root?.globals ?? options?.globals ?? {} if (callStack.length === 0) { callStack.push({ diff --git a/package.json b/package.json index a95fbb9641686..bad9179f56473 100644 --- a/package.json +++ b/package.json @@ -76,7 +76,7 @@ "@medv/finder": "^3.1.0", "@microlink/react-json-view": "^1.21.3", "@monaco-editor/react": "4.6.0", - "@posthog/hogvm": "^1.0.55", + "@posthog/hogvm": "^1.0.56", "@posthog/icons": "0.8.5", "@posthog/plugin-scaffold": "^1.4.4", "@react-hook/size": "^2.1.2", diff --git a/plugin-server/package.json b/plugin-server/package.json index d3b9711b6f963..60fb25342d14e 100644 --- a/plugin-server/package.json +++ b/plugin-server/package.json @@ -54,7 +54,7 @@ "@maxmind/geoip2-node": "^3.4.0", "@posthog/clickhouse": "^1.7.0", "@posthog/cyclotron": "file:../rust/cyclotron-node", - "@posthog/hogvm": "^1.0.55", + "@posthog/hogvm": "^1.0.56", "@posthog/plugin-scaffold": "1.4.4", "@sentry/node": "^7.49.0", "@sentry/profiling-node": "^0.3.0", diff --git a/plugin-server/pnpm-lock.yaml b/plugin-server/pnpm-lock.yaml index d6150f2962bd9..36a8662a19aac 100644 --- a/plugin-server/pnpm-lock.yaml +++ b/plugin-server/pnpm-lock.yaml @@ -47,8 +47,8 @@ dependencies: specifier: file:../rust/cyclotron-node version: file:../rust/cyclotron-node '@posthog/hogvm': - specifier: ^1.0.55 - version: 1.0.55(luxon@3.4.4) + specifier: ^1.0.56 + version: 1.0.56(luxon@3.4.4) '@posthog/plugin-scaffold': specifier: 1.4.4 version: 1.4.4 @@ -3119,8 +3119,8 @@ packages: engines: {node: '>=12'} dev: false - /@posthog/hogvm@1.0.55(luxon@3.4.4): - resolution: {integrity: sha512-cjF3lrA62aaqoERiVJHpkkRBS6QJ1rH4xYwiOMOs8ZQKNNRofeqth4NJzhJReXA0Wlf99l8hDt2lgFgLaDBI1w==} + /@posthog/hogvm@1.0.56(luxon@3.4.4): + resolution: {integrity: sha512-28d2E3j2f4QJBJk/a3YTwFLEUodJ16s2hGhGVX4XsZ0qekkkich9vGsSYM3A8D6aAg/aZxAf78PtlakGyHyx1Q==} peerDependencies: luxon: ^3.4.4 dependencies: diff --git a/plugin-server/src/cdp/cdp-api.ts b/plugin-server/src/cdp/cdp-api.ts index 4d33df3bca9f3..0f4ea02b96814 100644 --- a/plugin-server/src/cdp/cdp-api.ts +++ b/plugin-server/src/cdp/cdp-api.ts @@ -138,7 +138,9 @@ export class CdpApi { url: `${this.hub.SITE_URL ?? 'http://localhost:8000'}/project/${team.id}`, }, }, - compoundConfiguration + compoundConfiguration, + // The "email" hog functions export a "sendEmail" function that we must explicitly call + hogFunction.type === 'email' ? ['sendEmail', [globals.email]] : undefined ) if (invocation.queue === 'fetch') { diff --git a/plugin-server/src/cdp/hog-executor.ts b/plugin-server/src/cdp/hog-executor.ts index 801c958c52305..c57d954460f66 100644 --- a/plugin-server/src/cdp/hog-executor.ts +++ b/plugin-server/src/cdp/hog-executor.ts @@ -16,7 +16,7 @@ import { HogFunctionQueueParametersFetchResponse, HogFunctionType, } from './types' -import { convertToHogFunctionFilterGlobal } from './utils' +import { buildExportedFunctionInvoker, convertToHogFunctionFilterGlobal } from './utils' export const MAX_ASYNC_STEPS = 5 export const MAX_HOG_LOGS = 25 @@ -278,16 +278,59 @@ export class HogExecutor { } const sensitiveValues = this.getSensitiveValues(invocation.hogFunction, globals.inputs) + const invocationInput = + invocation.vmState ?? + (invocation.functionToExecute + ? buildExportedFunctionInvoker( + invocation.hogFunction.bytecode, + globals, + invocation.functionToExecute[0], // name + invocation.functionToExecute[1] // args + ) + : invocation.hogFunction.bytecode) try { let hogLogs = 0 - execRes = execHog(invocation.vmState ?? invocation.hogFunction.bytecode, { - globals, + execRes = execHog(invocationInput, { + globals: invocation.functionToExecute ? undefined : globals, maxAsyncSteps: MAX_ASYNC_STEPS, // NOTE: This will likely be configurable in the future asyncFunctions: { // We need to pass these in but they don't actually do anything as it is a sync exec fetch: async () => Promise.resolve(), }, + importBytecode: (module) => { + // TODO: more than one hardcoded module + if (module === 'provider/email') { + const provider = this.hogFunctionManager.getTeamHogEmailProvider(invocation.teamId) + if (!provider) { + throw new Error('No email provider configured') + } + try { + const providerGlobals = this.buildHogFunctionGlobals({ + id: '', + teamId: invocation.teamId, + hogFunction: provider, + globals: {} as any, + queue: 'hog', + timings: [], + priority: 0, + } satisfies HogFunctionInvocation) + + return { + bytecode: provider.bytecode, + globals: providerGlobals, + } + } catch (e) { + result.logs.push({ + level: 'error', + timestamp: DateTime.now(), + message: `Error building inputs: ${e}`, + }) + throw e + } + } + throw new Error(`Can't import unknown module: ${module}`) + }, functions: { print: (...args) => { hogLogs++ @@ -453,7 +496,7 @@ export class HogExecutor { result.finished = true // Explicitly set to true to prevent infinite loops status.error( '🦔', - `[HogExecutor] Error executing function ${invocation.hogFunction.id} - ${invocation.hogFunction.name}. Event: '${invocation.globals.event.url}'`, + `[HogExecutor] Error executing function ${invocation.hogFunction.id} - ${invocation.hogFunction.name}. Event: '${invocation.globals.event?.url}'`, err ) } diff --git a/plugin-server/src/cdp/hog-function-manager.ts b/plugin-server/src/cdp/hog-function-manager.ts index a54be7d96a058..c53ff71952ec2 100644 --- a/plugin-server/src/cdp/hog-function-manager.ts +++ b/plugin-server/src/cdp/hog-function-manager.ts @@ -100,6 +100,10 @@ export class HogFunctionManager { return this.getTeamHogFunctions(teamId).filter((x) => x.type === 'destination' || !x.type) } + public getTeamHogEmailProvider(teamId: Team['id']): HogFunctionType | undefined { + return this.getTeamHogFunctions(teamId).find((x) => x.type === 'email') + } + public getHogFunction(id: HogFunctionType['id']): HogFunctionType | undefined { if (!this.ready) { throw new Error('HogFunctionManager is not ready! Run HogFunctionManager.start() before this') diff --git a/plugin-server/src/cdp/types.ts b/plugin-server/src/cdp/types.ts index 19cce8157a075..037e08e7a9a54 100644 --- a/plugin-server/src/cdp/types.ts +++ b/plugin-server/src/cdp/types.ts @@ -196,6 +196,7 @@ export type HogFunctionInvocation = { // The current vmstate (set if the invocation is paused) vmState?: VMState timings: HogFunctionTiming[] + functionToExecute?: [string, any[]] } export type HogFunctionAsyncFunctionRequest = { diff --git a/plugin-server/src/cdp/utils.ts b/plugin-server/src/cdp/utils.ts index 64679d9743cd2..f8031a043a1bc 100644 --- a/plugin-server/src/cdp/utils.ts +++ b/plugin-server/src/cdp/utils.ts @@ -1,6 +1,7 @@ // NOTE: PostIngestionEvent is our context event - it should never be sent directly to an output, but rather transformed into a lightweight schema import { CyclotronJob, CyclotronJobUpdate } from '@posthog/cyclotron' +import { Bytecodes } from '@posthog/hogvm' import { captureException } from '@sentry/node' import { DateTime } from 'luxon' import RE2 from 're2' @@ -273,7 +274,8 @@ export const prepareLogEntriesForClickhouse = ( export function createInvocation( globals: HogFunctionInvocationGlobals, - hogFunction: HogFunctionType + hogFunction: HogFunctionType, + functionToExecute?: [string, any[]] ): HogFunctionInvocation { // Add the source of the trigger to the globals const modifiedGlobals: HogFunctionInvocationGlobals = { @@ -292,6 +294,7 @@ export function createInvocation( queue: 'hog', priority: 1, timings: [], + functionToExecute, } } @@ -378,3 +381,46 @@ export function cyclotronJobToInvocation(job: CyclotronJob, hogFunction: HogFunc timings: parsedState.timings, } } + +/** Build bytecode that calls a function in another imported bytecode */ +export function buildExportedFunctionInvoker( + exportBytecode: any[], + exportGlobals: any, + functionName: string, + args: any[] +): Bytecodes { + let argBytecodes: any[] = [] + for (let i = 0; i < args.length; i++) { + argBytecodes = [ + ...argBytecodes, + 33, // integer + i + 1, // (index in args array) + 32, // string + '__args', + 1, // get global + 2, // (chain length) + ] + } + const bytecode = [ + '_H', + 1, + ...argBytecodes, + 32, // string + 'x', + 2, // call global + 'import', + 1, // (arg count) + 32, // string + functionName, + 45, // get property + 54, // call local + args.length, + 35, // pop + ] + return { + bytecodes: { + x: { bytecode: exportBytecode, globals: exportGlobals }, + root: { bytecode, globals: { __args: args } }, + }, + } +} diff --git a/plugin-server/tests/cdp/cdp-api.test.ts b/plugin-server/tests/cdp/cdp-api.test.ts index aff207f099a4b..94ccf642230a9 100644 --- a/plugin-server/tests/cdp/cdp-api.test.ts +++ b/plugin-server/tests/cdp/cdp-api.test.ts @@ -239,5 +239,43 @@ describe('CDP API', () => { ], }) }) + + it('call exported sendEmail for email provider functions', async () => { + hogFunction = await insertHogFunction({ + ...HOG_EXAMPLES.export_send_email, + ...HOG_INPUTS_EXAMPLES.simple_fetch, + ...HOG_FILTERS_EXAMPLES.no_filters, + }) + + mockFetch.mockImplementationOnce(() => + Promise.resolve({ + status: 201, + text: () => Promise.resolve(JSON.stringify({ real: true })), + }) + ) + const res = await supertest(app) + .post(`/api/projects/${hogFunction.team_id}/hog_functions/${hogFunction.id}/invocations`) + .send({ globals: { ...globals, email: { from: 'me@mycompany.com' } }, mock_async_functions: false }) + + expect(res.status).toEqual(200) + expect(res.body).toMatchObject({ + status: 'success', + error: 'undefined', + logs: [ + { + level: 'debug', + message: 'Executing function', + }, + { + level: 'info', + message: '{"from":"me@mycompany.com"}', + }, + { + level: 'debug', + message: expect.stringContaining('Function completed in'), + }, + ], + }) + }) }) }) diff --git a/plugin-server/tests/cdp/cdp-e2e.test.ts b/plugin-server/tests/cdp/cdp-e2e.test.ts index e40c98c903edd..db170f7ea8e1f 100644 --- a/plugin-server/tests/cdp/cdp-e2e.test.ts +++ b/plugin-server/tests/cdp/cdp-e2e.test.ts @@ -131,7 +131,7 @@ describe('CDP E2E', () => { Array [ "https://example.com/posthog-webhook", Object { - "body": "{\\"event\\":{\\"uuid\\":\\"b3a1fe86-b10c-43cc-acaf-d208977608d0\\",\\"event\\":\\"$pageview\\",\\"elements_chain\\":\\"\\",\\"distinct_id\\":\\"distinct_id\\",\\"url\\":\\"http://localhost:8000/events/1\\",\\"properties\\":{\\"$current_url\\":\\"https://posthog.com\\",\\"$lib_version\\":\\"1.0.0\\"},\\"timestamp\\":\\"2024-09-03T09:00:00Z\\"},\\"groups\\":{},\\"nested\\":{\\"foo\\":\\"http://localhost:8000/events/1\\"},\\"person\\":{\\"id\\":\\"uuid\\",\\"name\\":\\"test\\",\\"url\\":\\"http://localhost:8000/persons/1\\",\\"properties\\":{\\"email\\":\\"test@posthog.com\\"}},\\"event_url\\":\\"http://localhost:8000/events/1-test\\"}", + "body": "{\\"event\\":{\\"uuid\\":\\"b3a1fe86-b10c-43cc-acaf-d208977608d0\\",\\"event\\":\\"$pageview\\",\\"elements_chain\\":\\"\\",\\"distinct_id\\":\\"distinct_id\\",\\"url\\":\\"http://localhost:8000/events/1\\",\\"properties\\":{\\"$current_url\\":\\"https://posthog.com\\",\\"$lib_version\\":\\"1.0.0\\"},\\"timestamp\\":\\"2024-09-03T09:00:00Z\\"},\\"groups\\":{},\\"nested\\":{\\"foo\\":\\"http://localhost:8000/events/1\\"},\\"person\\":{\\"id\\":\\"uuid\\",\\"name\\":\\"test\\",\\"url\\":\\"http://localhost:8000/persons/1\\",\\"properties\\":{\\"email\\":\\"test@posthog.com\\",\\"first_name\\":\\"Pumpkin\\"}},\\"event_url\\":\\"http://localhost:8000/events/1-test\\"}", "headers": Object { "version": "v=1.0.0", }, diff --git a/plugin-server/tests/cdp/cdp-processed-events-consumer.test.ts b/plugin-server/tests/cdp/cdp-processed-events-consumer.test.ts index 633f408101802..f86cec16a0bc2 100644 --- a/plugin-server/tests/cdp/cdp-processed-events-consumer.test.ts +++ b/plugin-server/tests/cdp/cdp-processed-events-consumer.test.ts @@ -172,7 +172,7 @@ describe('CDP Processed Events Consumer', () => { { topic: 'log_entries_test', value: { - message: "Suspending function due to async function call 'fetch'. Payload: 2002 bytes", + message: "Suspending function due to async function call 'fetch'. Payload: 2035 bytes", log_source_id: fnFetchNoFilters.id, }, }, diff --git a/plugin-server/tests/cdp/examples.ts b/plugin-server/tests/cdp/examples.ts index d948f85e67424..fafafce472e3d 100644 --- a/plugin-server/tests/cdp/examples.ts +++ b/plugin-server/tests/cdp/examples.ts @@ -5,8 +5,9 @@ import { HogFunctionType } from '../../src/cdp/types' * As such we have a bunch of prebuilt examples here for usage in tests. */ -export const HOG_EXAMPLES: Record> = { +export const HOG_EXAMPLES: Record> = { simple_fetch: { + type: 'destination', hog: "let res := fetch(inputs.url, {\n 'headers': inputs.headers,\n 'body': inputs.body,\n 'method': inputs.method\n});\n\nprint('Fetch response:', res)", bytecode: [ '_h', @@ -57,6 +58,7 @@ export const HOG_EXAMPLES: Record> = { @@ -366,6 +427,60 @@ export const HOG_INPUTS_EXAMPLES: Record', + design: [], + subject: 'Hello {person.properties.email}', + }, + bytecode: { + to: ['_H', 1, 32, 'email', 32, 'properties', 32, 'person', 1, 3], + body: [ + '_H', + 1, + 32, + 'Hello ', + 32, + 'first_name', + 32, + 'properties', + 32, + 'person', + 1, + 3, + 32, + ' ', + 32, + 'last_name', + 32, + 'properties', + 32, + 'person', + 1, + 3, + 32, + '!\n\nThis is a broadcast', + 2, + 'concat', + 5, + ], + from: ['_H', 1, 32, 'info@posthog.com'], + html: ['_H', 1, 32, ''], + subject: ['_H', 1, 32, 'Hello ', 32, 'email', 32, 'properties', 32, 'person', 1, 3, 2, 'concat', 2], + }, + }, + }, + }, } export const HOG_FILTERS_EXAMPLES: Record> = { diff --git a/plugin-server/tests/cdp/fixtures.ts b/plugin-server/tests/cdp/fixtures.ts index efbf3d2a06f83..e34920fdd981e 100644 --- a/plugin-server/tests/cdp/fixtures.ts +++ b/plugin-server/tests/cdp/fixtures.ts @@ -121,6 +121,7 @@ export const createHogExecutionGlobals = ( url: 'http://localhost:8000/persons/1', properties: { email: 'test@posthog.com', + first_name: 'Pumpkin', }, ...(data.person ?? {}), }, diff --git a/plugin-server/tests/cdp/hog-executor.test.ts b/plugin-server/tests/cdp/hog-executor.test.ts index 7f10b5a9103be..94608b2f5bea1 100644 --- a/plugin-server/tests/cdp/hog-executor.test.ts +++ b/plugin-server/tests/cdp/hog-executor.test.ts @@ -45,6 +45,7 @@ describe('Hog Executor', () => { reloadAllHogFunctions: jest.fn(), getTeamHogDestinations: jest.fn(), getTeamHogFunction: jest.fn(), + getTeamHogEmailProvider: jest.fn(), } beforeEach(async () => { @@ -106,7 +107,7 @@ describe('Hog Executor', () => { { timestamp: expect.any(DateTime), level: 'debug', - message: "Suspending function due to async function call 'fetch'. Payload: 1918 bytes", + message: "Suspending function due to async function call 'fetch'. Payload: 1951 bytes", }, ]) }) @@ -162,7 +163,7 @@ describe('Hog Executor', () => { id: 'uuid', name: 'test', url: 'http://localhost:8000/persons/1', - properties: { email: 'test@posthog.com' }, + properties: { email: 'test@posthog.com', first_name: 'Pumpkin' }, }, event_url: 'http://localhost:8000/events/1-test', }) @@ -187,10 +188,10 @@ describe('Hog Executor', () => { expect(logs.map((log) => log.message)).toMatchInlineSnapshot(` Array [ "Executing function", - "Suspending function due to async function call 'fetch'. Payload: 1918 bytes", + "Suspending function due to async function call 'fetch'. Payload: 1951 bytes", "Resuming function", "Fetch response:, {\\"status\\":200,\\"body\\":\\"success\\"}", - "Function completed in 100ms. Sync: 0ms. Mem: 779 bytes. Ops: 22. Event: 'http://localhost:8000/events/1'", + "Function completed in 100ms. Sync: 0ms. Mem: 812 bytes. Ops: 22. Event: 'http://localhost:8000/events/1'", ] `) }) @@ -206,15 +207,79 @@ describe('Hog Executor', () => { expect(logs.map((log) => log.message)).toMatchInlineSnapshot(` Array [ "Executing function", - "Suspending function due to async function call 'fetch'. Payload: 1918 bytes", + "Suspending function due to async function call 'fetch'. Payload: 1951 bytes", "Resuming function", "Fetch response:, {\\"status\\":200,\\"body\\":{\\"foo\\":\\"bar\\"}}", - "Function completed in 100ms. Sync: 0ms. Mem: 779 bytes. Ops: 22. Event: 'http://localhost:8000/events/1'", + "Function completed in 100ms. Sync: 0ms. Mem: 812 bytes. Ops: 22. Event: 'http://localhost:8000/events/1'", ] `) }) }) + describe('email provider functions', () => { + let hogFunction: HogFunctionType + let providerFunction: HogFunctionType + beforeEach(() => { + providerFunction = createHogFunction({ + name: 'Test hog function', + ...HOG_EXAMPLES.export_send_email, + ...HOG_INPUTS_EXAMPLES.none, + ...HOG_FILTERS_EXAMPLES.no_filters, + }) + hogFunction = createHogFunction({ + name: 'Test hog function', + ...HOG_EXAMPLES.import_send_email, + ...HOG_INPUTS_EXAMPLES.email, + ...HOG_FILTERS_EXAMPLES.no_filters, + }) + mockFunctionManager.getTeamHogDestinations.mockReturnValue([hogFunction, providerFunction]) + mockFunctionManager.getTeamHogFunction.mockReturnValue(hogFunction) + mockFunctionManager.getTeamHogEmailProvider.mockReturnValue(providerFunction) + }) + + it('can execute an invocation', () => { + const invocation = createInvocation(hogFunction) + const result = executor.execute(invocation) + expect(result).toEqual({ + capturedPostHogEvents: [], + invocation: { + id: expect.any(String), + teamId: 1, + priority: 0, + globals: invocation.globals, + hogFunction: invocation.hogFunction, + queue: 'hog', + timings: [ + { + kind: 'hog', + duration_ms: 0, + }, + ], + vmState: expect.any(Object), + }, + finished: true, + logs: [ + { + level: 'debug', + message: 'Executing function', + timestamp: expect.any(Object), + }, + { + level: 'info', + message: + '{"to":"test@posthog.com","body":"Hello Pumpkin !\\n\\nThis is a broadcast","from":"info@posthog.com","html":"","subject":"Hello test@posthog.com"}', + timestamp: expect.any(Object), + }, + { + level: 'debug', + message: expect.stringContaining('Function completed in'), + timestamp: expect.any(Object), + }, + ], + }) + }) + }) + describe('filtering', () => { it('can filters incoming messages correctly', () => { const fn = createHogFunction({ diff --git a/plugin-server/tests/cdp/hog-function-manager.test.ts b/plugin-server/tests/cdp/hog-function-manager.test.ts index 9ae3e2c0e6e10..d5d5b575dd3ec 100644 --- a/plugin-server/tests/cdp/hog-function-manager.test.ts +++ b/plugin-server/tests/cdp/hog-function-manager.test.ts @@ -146,6 +146,9 @@ describe('HogFunctionManager', () => { expect(allFunctions.length).toEqual(2) expect(allFunctions.map((f) => f.type).sort()).toEqual(['destination', 'email']) + const emailProvider = manager.getTeamHogEmailProvider(teamId1) + expect(emailProvider.type).toEqual('email') + await hub.db.postgres.query( PostgresUse.COMMON_WRITE, `UPDATE posthog_hogfunction SET name='Test Hog Function team 1 updated' WHERE id = $1`, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 213db7def9748..ccb44bdecac07 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -50,8 +50,8 @@ dependencies: specifier: 4.6.0 version: 4.6.0(monaco-editor@0.49.0)(react-dom@18.2.0)(react@18.2.0) '@posthog/hogvm': - specifier: ^1.0.55 - version: 1.0.55(luxon@3.5.0) + specifier: ^1.0.56 + version: 1.0.56(luxon@3.5.0) '@posthog/icons': specifier: 0.8.5 version: 0.8.5(react-dom@18.2.0)(react@18.2.0) @@ -389,7 +389,7 @@ dependencies: optionalDependencies: fsevents: specifier: ^2.3.2 - version: 2.3.3 + version: 2.3.2 devDependencies: '@babel/core': @@ -5415,8 +5415,8 @@ packages: resolution: {integrity: sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==} dev: false - /@posthog/hogvm@1.0.55(luxon@3.5.0): - resolution: {integrity: sha512-cjF3lrA62aaqoERiVJHpkkRBS6QJ1rH4xYwiOMOs8ZQKNNRofeqth4NJzhJReXA0Wlf99l8hDt2lgFgLaDBI1w==} + /@posthog/hogvm@1.0.56(luxon@3.5.0): + resolution: {integrity: sha512-28d2E3j2f4QJBJk/a3YTwFLEUodJ16s2hGhGVX4XsZ0qekkkich9vGsSYM3A8D6aAg/aZxAf78PtlakGyHyx1Q==} peerDependencies: luxon: ^3.4.4 dependencies: @@ -13134,7 +13134,6 @@ packages: engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] requiresBuild: true - dev: true optional: true /fsevents@2.3.3: diff --git a/posthog/cdp/templates/mailjet/template_mailjet.py b/posthog/cdp/templates/mailjet/template_mailjet.py index 23cf5e07da0f4..e0fa8863d0a0e 100644 --- a/posthog/cdp/templates/mailjet/template_mailjet.py +++ b/posthog/cdp/templates/mailjet/template_mailjet.py @@ -170,17 +170,17 @@ 'Messages': [ { 'From': { - 'Email': inputs.email.from, + 'Email': email.from, 'Name': '' }, 'To': [ { - 'Email': inputs.email.to, + 'Email': email.to, 'Name': '' } ], - 'Subject': inputs.email.subject, - 'HTMLPart': inputs.email.html + 'Subject': email.subject, + 'HTMLPart': email.html } ] } diff --git a/posthog/hogql/transforms/test/__snapshots__/test_in_cohort.ambr b/posthog/hogql/transforms/test/__snapshots__/test_in_cohort.ambr index 06e46e0ca30b0..f018e96ef067a 100644 --- a/posthog/hogql/transforms/test/__snapshots__/test_in_cohort.ambr +++ b/posthog/hogql/transforms/test/__snapshots__/test_in_cohort.ambr @@ -31,7 +31,7 @@ FROM events LEFT JOIN ( SELECT person_static_cohort.person_id AS cohort_person_id, 1 AS matched, person_static_cohort.cohort_id AS cohort_id FROM person_static_cohort - WHERE and(equals(person_static_cohort.team_id, 420), in(person_static_cohort.cohort_id, [6]))) AS __in_cohort ON equals(__in_cohort.cohort_person_id, events.person_id) + WHERE and(equals(person_static_cohort.team_id, 420), in(person_static_cohort.cohort_id, [4]))) AS __in_cohort ON equals(__in_cohort.cohort_person_id, events.person_id) WHERE and(equals(events.team_id, 420), 1, ifNull(equals(__in_cohort.matched, 1), 0)) LIMIT 100 SETTINGS readonly=2, max_execution_time=60, allow_experimental_object_type=1, format_csv_allow_double_quotes=0, max_ast_elements=4000000, max_expanded_ast_elements=4000000, max_bytes_before_external_group_by=0 @@ -42,7 +42,7 @@ FROM events LEFT JOIN ( SELECT person_id AS cohort_person_id, 1 AS matched, cohort_id FROM static_cohort_people - WHERE in(cohort_id, [6])) AS __in_cohort ON equals(__in_cohort.cohort_person_id, person_id) + WHERE in(cohort_id, [4])) AS __in_cohort ON equals(__in_cohort.cohort_person_id, person_id) WHERE and(1, equals(__in_cohort.matched, 1)) LIMIT 100 ''' @@ -55,7 +55,7 @@ FROM events LEFT JOIN ( SELECT person_static_cohort.person_id AS cohort_person_id, 1 AS matched, person_static_cohort.cohort_id AS cohort_id FROM person_static_cohort - WHERE and(equals(person_static_cohort.team_id, 420), in(person_static_cohort.cohort_id, [7]))) AS __in_cohort ON equals(__in_cohort.cohort_person_id, events.person_id) + WHERE and(equals(person_static_cohort.team_id, 420), in(person_static_cohort.cohort_id, [5]))) AS __in_cohort ON equals(__in_cohort.cohort_person_id, events.person_id) WHERE and(equals(events.team_id, 420), 1, ifNull(equals(__in_cohort.matched, 1), 0)) LIMIT 100 SETTINGS readonly=2, max_execution_time=60, allow_experimental_object_type=1, format_csv_allow_double_quotes=0, max_ast_elements=4000000, max_expanded_ast_elements=4000000, max_bytes_before_external_group_by=0 @@ -66,7 +66,7 @@ FROM events LEFT JOIN ( SELECT person_id AS cohort_person_id, 1 AS matched, cohort_id FROM static_cohort_people - WHERE in(cohort_id, [7])) AS __in_cohort ON equals(__in_cohort.cohort_person_id, person_id) + WHERE in(cohort_id, [5])) AS __in_cohort ON equals(__in_cohort.cohort_person_id, person_id) WHERE and(1, equals(__in_cohort.matched, 1)) LIMIT 100 ''' diff --git a/posthog/hogql_queries/insights/trends/test/__snapshots__/test_trends.ambr b/posthog/hogql_queries/insights/trends/test/__snapshots__/test_trends.ambr index 6027f7ca7bb42..4ae57feb8cb96 100644 --- a/posthog/hogql_queries/insights/trends/test/__snapshots__/test_trends.ambr +++ b/posthog/hogql_queries/insights/trends/test/__snapshots__/test_trends.ambr @@ -851,14 +851,49 @@ # --- # name: TestTrends.test_dau_with_breakdown_filtering_with_sampling.1 ''' - /* celery:posthog.tasks.tasks.sync_insight_caching_state */ - SELECT team_id, - date_diff('second', max(timestamp), now()) AS age - FROM events - WHERE timestamp > date_sub(DAY, 3, now()) - AND timestamp < now() - GROUP BY team_id - ORDER BY age; + SELECT groupArray(1)(date)[1] AS date, + arrayFold((acc, x) -> arrayMap(i -> plus(acc[i], x[i]), range(1, plus(length(date), 1))), groupArray(ifNull(total, 0)), arrayWithConstant(length(date), reinterpretAsFloat64(0))) AS total, + if(ifNull(ifNull(greaterOrEquals(row_number, 25), 0), 0), '$$_posthog_breakdown_other_$$', breakdown_value) AS breakdown_value + FROM + (SELECT arrayMap(number -> plus(toStartOfDay(assumeNotNull(parseDateTime64BestEffortOrNull('2019-12-28 00:00:00', 6, 'UTC'))), toIntervalDay(number)), range(0, plus(coalesce(dateDiff('day', toStartOfDay(assumeNotNull(parseDateTime64BestEffortOrNull('2019-12-28 00:00:00', 6, 'UTC'))), toStartOfDay(assumeNotNull(parseDateTime64BestEffortOrNull('2020-01-04 23:59:59', 6, 'UTC'))))), 1))) AS date, + arrayMap(_match_date -> arraySum(arraySlice(groupArray(ifNull(count, 0)), indexOf(groupArray(day_start) AS _days_for_count, _match_date) AS _index, plus(minus(arrayLastIndex(x -> ifNull(equals(x, _match_date), isNull(x) + and isNull(_match_date)), _days_for_count), _index), 1))), date) AS total, + breakdown_value AS breakdown_value, + rowNumberInAllBlocks() AS row_number + FROM + (SELECT sum(total) AS count, + day_start AS day_start, + breakdown_value AS breakdown_value + FROM + (SELECT count(DISTINCT if(not(empty(e__override.distinct_id)), e__override.person_id, e.person_id)) AS total, + toStartOfDay(toTimeZone(e.timestamp, 'UTC')) AS day_start, + ifNull(nullIf(toString(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(e.properties, '$some_property'), ''), 'null'), '^"|"$', '')), ''), '$$_posthog_breakdown_null_$$') AS breakdown_value + FROM events AS e SAMPLE 1.0 + LEFT OUTER JOIN + (SELECT argMax(person_distinct_id_overrides.person_id, person_distinct_id_overrides.version) AS person_id, + person_distinct_id_overrides.distinct_id AS distinct_id + FROM person_distinct_id_overrides + WHERE equals(person_distinct_id_overrides.team_id, 2) + GROUP BY person_distinct_id_overrides.distinct_id + HAVING ifNull(equals(argMax(person_distinct_id_overrides.is_deleted, person_distinct_id_overrides.version), 0), 0) SETTINGS optimize_aggregation_in_order=1) AS e__override ON equals(e.distinct_id, e__override.distinct_id) + WHERE and(equals(e.team_id, 2), greaterOrEquals(toTimeZone(e.timestamp, 'UTC'), toStartOfDay(assumeNotNull(parseDateTime64BestEffortOrNull('2019-12-28 00:00:00', 6, 'UTC')))), lessOrEquals(toTimeZone(e.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2020-01-04 23:59:59', 6, 'UTC'))), equals(e.event, 'sign up')) + GROUP BY day_start, + breakdown_value) + GROUP BY day_start, + breakdown_value + ORDER BY day_start ASC, breakdown_value ASC) + GROUP BY breakdown_value + ORDER BY if(ifNull(equals(breakdown_value, '$$_posthog_breakdown_other_$$'), 0), 2, if(ifNull(equals(breakdown_value, '$$_posthog_breakdown_null_$$'), 0), 1, 0)) ASC, arraySum(total) DESC, breakdown_value ASC) + WHERE isNotNull(breakdown_value) + GROUP BY breakdown_value + ORDER BY if(ifNull(equals(breakdown_value, '$$_posthog_breakdown_other_$$'), 0), 2, if(ifNull(equals(breakdown_value, '$$_posthog_breakdown_null_$$'), 0), 1, 0)) ASC, arraySum(total) DESC, breakdown_value ASC + LIMIT 50000 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0 ''' # --- # name: TestTrends.test_dau_with_breakdown_filtering_with_sampling.10 @@ -1075,38 +1110,143 @@ # --- # name: TestTrends.test_dau_with_breakdown_filtering_with_sampling.2 ''' - /* celery:posthog.tasks.tasks.sync_insight_caching_state */ - SELECT team_id, - date_diff('second', max(timestamp), now()) AS age - FROM events - WHERE timestamp > date_sub(DAY, 3, now()) - AND timestamp < now() - GROUP BY team_id - ORDER BY age; + SELECT groupArray(1)(date)[1] AS date, + arrayFold((acc, x) -> arrayMap(i -> plus(acc[i], x[i]), range(1, plus(length(date), 1))), groupArray(ifNull(total, 0)), arrayWithConstant(length(date), reinterpretAsFloat64(0))) AS total, + if(ifNull(ifNull(greaterOrEquals(row_number, 25), 0), 0), '$$_posthog_breakdown_other_$$', breakdown_value) AS breakdown_value + FROM + (SELECT arrayMap(number -> plus(toStartOfDay(assumeNotNull(parseDateTime64BestEffortOrNull('2019-12-28 00:00:00', 6, 'UTC'))), toIntervalDay(number)), range(0, plus(coalesce(dateDiff('day', toStartOfDay(assumeNotNull(parseDateTime64BestEffortOrNull('2019-12-28 00:00:00', 6, 'UTC'))), toStartOfDay(assumeNotNull(parseDateTime64BestEffortOrNull('2020-01-04 23:59:59', 6, 'UTC'))))), 1))) AS date, + arrayMap(_match_date -> arraySum(arraySlice(groupArray(ifNull(count, 0)), indexOf(groupArray(day_start) AS _days_for_count, _match_date) AS _index, plus(minus(arrayLastIndex(x -> ifNull(equals(x, _match_date), isNull(x) + and isNull(_match_date)), _days_for_count), _index), 1))), date) AS total, + breakdown_value AS breakdown_value, + rowNumberInAllBlocks() AS row_number + FROM + (SELECT sum(total) AS count, + day_start AS day_start, + breakdown_value AS breakdown_value + FROM + (SELECT count(DISTINCT if(not(empty(e__override.distinct_id)), e__override.person_id, e.person_id)) AS total, + toStartOfDay(toTimeZone(e.timestamp, 'UTC')) AS day_start, + ifNull(nullIf(toString(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(e.properties, '$some_property'), ''), 'null'), '^"|"$', '')), ''), '$$_posthog_breakdown_null_$$') AS breakdown_value + FROM events AS e SAMPLE 1.0 + LEFT OUTER JOIN + (SELECT argMax(person_distinct_id_overrides.person_id, person_distinct_id_overrides.version) AS person_id, + person_distinct_id_overrides.distinct_id AS distinct_id + FROM person_distinct_id_overrides + WHERE equals(person_distinct_id_overrides.team_id, 2) + GROUP BY person_distinct_id_overrides.distinct_id + HAVING ifNull(equals(argMax(person_distinct_id_overrides.is_deleted, person_distinct_id_overrides.version), 0), 0) SETTINGS optimize_aggregation_in_order=1) AS e__override ON equals(e.distinct_id, e__override.distinct_id) + WHERE and(equals(e.team_id, 2), greaterOrEquals(toTimeZone(e.timestamp, 'UTC'), toStartOfDay(assumeNotNull(parseDateTime64BestEffortOrNull('2019-12-28 00:00:00', 6, 'UTC')))), lessOrEquals(toTimeZone(e.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2020-01-04 23:59:59', 6, 'UTC'))), equals(e.event, 'sign up')) + GROUP BY day_start, + breakdown_value) + GROUP BY day_start, + breakdown_value + ORDER BY day_start ASC, breakdown_value ASC) + GROUP BY breakdown_value + ORDER BY if(ifNull(equals(breakdown_value, '$$_posthog_breakdown_other_$$'), 0), 2, if(ifNull(equals(breakdown_value, '$$_posthog_breakdown_null_$$'), 0), 1, 0)) ASC, arraySum(total) DESC, breakdown_value ASC) + WHERE isNotNull(breakdown_value) + GROUP BY breakdown_value + ORDER BY if(ifNull(equals(breakdown_value, '$$_posthog_breakdown_other_$$'), 0), 2, if(ifNull(equals(breakdown_value, '$$_posthog_breakdown_null_$$'), 0), 1, 0)) ASC, arraySum(total) DESC, breakdown_value ASC + LIMIT 50000 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0 ''' # --- # name: TestTrends.test_dau_with_breakdown_filtering_with_sampling.3 ''' - /* celery:posthog.tasks.tasks.sync_insight_caching_state */ - SELECT team_id, - date_diff('second', max(timestamp), now()) AS age - FROM events - WHERE timestamp > date_sub(DAY, 3, now()) - AND timestamp < now() - GROUP BY team_id - ORDER BY age; + SELECT groupArray(1)(date)[1] AS date, + arrayFold((acc, x) -> arrayMap(i -> plus(acc[i], x[i]), range(1, plus(length(date), 1))), groupArray(ifNull(total, 0)), arrayWithConstant(length(date), reinterpretAsFloat64(0))) AS total, + arrayMap(i -> if(ifNull(ifNull(greaterOrEquals(row_number, 25), 0), 0), '$$_posthog_breakdown_other_$$', i), breakdown_value) AS breakdown_value + FROM + (SELECT arrayMap(number -> plus(toStartOfDay(assumeNotNull(parseDateTime64BestEffortOrNull('2019-12-28 00:00:00', 6, 'UTC'))), toIntervalDay(number)), range(0, plus(coalesce(dateDiff('day', toStartOfDay(assumeNotNull(parseDateTime64BestEffortOrNull('2019-12-28 00:00:00', 6, 'UTC'))), toStartOfDay(assumeNotNull(parseDateTime64BestEffortOrNull('2020-01-04 23:59:59', 6, 'UTC'))))), 1))) AS date, + arrayMap(_match_date -> arraySum(arraySlice(groupArray(ifNull(count, 0)), indexOf(groupArray(day_start) AS _days_for_count, _match_date) AS _index, plus(minus(arrayLastIndex(x -> ifNull(equals(x, _match_date), isNull(x) + and isNull(_match_date)), _days_for_count), _index), 1))), date) AS total, + breakdown_value AS breakdown_value, + rowNumberInAllBlocks() AS row_number + FROM + (SELECT sum(total) AS count, + day_start AS day_start, + [ifNull(toString(breakdown_value_1), '$$_posthog_breakdown_null_$$')] AS breakdown_value + FROM + (SELECT count(DISTINCT if(not(empty(e__override.distinct_id)), e__override.person_id, e.person_id)) AS total, + toStartOfDay(toTimeZone(e.timestamp, 'UTC')) AS day_start, + ifNull(nullIf(toString(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(e.properties, '$some_property'), ''), 'null'), '^"|"$', '')), ''), '$$_posthog_breakdown_null_$$') AS breakdown_value_1 + FROM events AS e SAMPLE 1.0 + LEFT OUTER JOIN + (SELECT argMax(person_distinct_id_overrides.person_id, person_distinct_id_overrides.version) AS person_id, + person_distinct_id_overrides.distinct_id AS distinct_id + FROM person_distinct_id_overrides + WHERE equals(person_distinct_id_overrides.team_id, 2) + GROUP BY person_distinct_id_overrides.distinct_id + HAVING ifNull(equals(argMax(person_distinct_id_overrides.is_deleted, person_distinct_id_overrides.version), 0), 0) SETTINGS optimize_aggregation_in_order=1) AS e__override ON equals(e.distinct_id, e__override.distinct_id) + WHERE and(equals(e.team_id, 2), greaterOrEquals(toTimeZone(e.timestamp, 'UTC'), toStartOfDay(assumeNotNull(parseDateTime64BestEffortOrNull('2019-12-28 00:00:00', 6, 'UTC')))), lessOrEquals(toTimeZone(e.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2020-01-04 23:59:59', 6, 'UTC'))), equals(e.event, 'sign up')) + GROUP BY day_start, + breakdown_value_1) + GROUP BY day_start, + breakdown_value_1 + ORDER BY day_start ASC, breakdown_value ASC) + GROUP BY breakdown_value + ORDER BY if(has(breakdown_value, '$$_posthog_breakdown_other_$$'), 2, if(has(breakdown_value, '$$_posthog_breakdown_null_$$'), 1, 0)) ASC, arraySum(total) DESC, breakdown_value ASC) + WHERE arrayExists(x -> isNotNull(x), breakdown_value) + GROUP BY breakdown_value + ORDER BY if(has(breakdown_value, '$$_posthog_breakdown_other_$$'), 2, if(has(breakdown_value, '$$_posthog_breakdown_null_$$'), 1, 0)) ASC, arraySum(total) DESC, breakdown_value ASC + LIMIT 50000 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0 ''' # --- # name: TestTrends.test_dau_with_breakdown_filtering_with_sampling.4 ''' - /* celery:posthog.tasks.tasks.sync_insight_caching_state */ - SELECT team_id, - date_diff('second', max(timestamp), now()) AS age - FROM events - WHERE timestamp > date_sub(DAY, 3, now()) - AND timestamp < now() - GROUP BY team_id - ORDER BY age; + SELECT groupArray(1)(date)[1] AS date, + arrayFold((acc, x) -> arrayMap(i -> plus(acc[i], x[i]), range(1, plus(length(date), 1))), groupArray(ifNull(total, 0)), arrayWithConstant(length(date), reinterpretAsFloat64(0))) AS total, + arrayMap(i -> if(ifNull(ifNull(greaterOrEquals(row_number, 25), 0), 0), '$$_posthog_breakdown_other_$$', i), breakdown_value) AS breakdown_value + FROM + (SELECT arrayMap(number -> plus(toStartOfDay(assumeNotNull(parseDateTime64BestEffortOrNull('2019-12-28 00:00:00', 6, 'UTC'))), toIntervalDay(number)), range(0, plus(coalesce(dateDiff('day', toStartOfDay(assumeNotNull(parseDateTime64BestEffortOrNull('2019-12-28 00:00:00', 6, 'UTC'))), toStartOfDay(assumeNotNull(parseDateTime64BestEffortOrNull('2020-01-04 23:59:59', 6, 'UTC'))))), 1))) AS date, + arrayMap(_match_date -> arraySum(arraySlice(groupArray(ifNull(count, 0)), indexOf(groupArray(day_start) AS _days_for_count, _match_date) AS _index, plus(minus(arrayLastIndex(x -> ifNull(equals(x, _match_date), isNull(x) + and isNull(_match_date)), _days_for_count), _index), 1))), date) AS total, + breakdown_value AS breakdown_value, + rowNumberInAllBlocks() AS row_number + FROM + (SELECT sum(total) AS count, + day_start AS day_start, + [ifNull(toString(breakdown_value_1), '$$_posthog_breakdown_null_$$')] AS breakdown_value + FROM + (SELECT count(DISTINCT if(not(empty(e__override.distinct_id)), e__override.person_id, e.person_id)) AS total, + toStartOfDay(toTimeZone(e.timestamp, 'UTC')) AS day_start, + ifNull(nullIf(toString(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(e.properties, '$some_property'), ''), 'null'), '^"|"$', '')), ''), '$$_posthog_breakdown_null_$$') AS breakdown_value_1 + FROM events AS e SAMPLE 1.0 + LEFT OUTER JOIN + (SELECT argMax(person_distinct_id_overrides.person_id, person_distinct_id_overrides.version) AS person_id, + person_distinct_id_overrides.distinct_id AS distinct_id + FROM person_distinct_id_overrides + WHERE equals(person_distinct_id_overrides.team_id, 2) + GROUP BY person_distinct_id_overrides.distinct_id + HAVING ifNull(equals(argMax(person_distinct_id_overrides.is_deleted, person_distinct_id_overrides.version), 0), 0) SETTINGS optimize_aggregation_in_order=1) AS e__override ON equals(e.distinct_id, e__override.distinct_id) + WHERE and(equals(e.team_id, 2), greaterOrEquals(toTimeZone(e.timestamp, 'UTC'), toStartOfDay(assumeNotNull(parseDateTime64BestEffortOrNull('2019-12-28 00:00:00', 6, 'UTC')))), lessOrEquals(toTimeZone(e.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2020-01-04 23:59:59', 6, 'UTC'))), equals(e.event, 'sign up')) + GROUP BY day_start, + breakdown_value_1) + GROUP BY day_start, + breakdown_value_1 + ORDER BY day_start ASC, breakdown_value ASC) + GROUP BY breakdown_value + ORDER BY if(has(breakdown_value, '$$_posthog_breakdown_other_$$'), 2, if(has(breakdown_value, '$$_posthog_breakdown_null_$$'), 1, 0)) ASC, arraySum(total) DESC, breakdown_value ASC) + WHERE arrayExists(x -> isNotNull(x), breakdown_value) + GROUP BY breakdown_value + ORDER BY if(has(breakdown_value, '$$_posthog_breakdown_other_$$'), 2, if(has(breakdown_value, '$$_posthog_breakdown_null_$$'), 1, 0)) ASC, arraySum(total) DESC, breakdown_value ASC + LIMIT 50000 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0 ''' # --- # name: TestTrends.test_dau_with_breakdown_filtering_with_sampling.5