From 7e02fc890d1f9f2344997ea48c3bef5d2d3575d8 Mon Sep 17 00:00:00 2001 From: Ben White Date: Thu, 23 Jan 2025 16:13:42 +0100 Subject: [PATCH 001/163] added initial work --- .../cdp-cyclotron-plugins-worker.consumer.ts | 110 ++++++++++++++++++ .../cdp-cyclotron-worker.consumer.ts | 27 +++-- .../src/cdp/legacy-plugins/manager.ts | 5 + plugin-server/src/cdp/legacy-plugins/types.ts | 9 ++ .../services/hog-function-manager.service.ts | 1 + plugin-server/src/cdp/types.ts | 3 +- 6 files changed, 140 insertions(+), 15 deletions(-) create mode 100644 plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.ts create mode 100644 plugin-server/src/cdp/legacy-plugins/manager.ts create mode 100644 plugin-server/src/cdp/legacy-plugins/types.ts diff --git a/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.ts b/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.ts new file mode 100644 index 0000000000000..dfb8eca718f0c --- /dev/null +++ b/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.ts @@ -0,0 +1,110 @@ +import { Meta, ProcessedPluginEvent } from '@posthog/plugin-scaffold' +import { DateTime } from 'luxon' + +import { PLUGINS_BY_ID } from '../legacy-plugins/manager' +import { HogFunctionInvocation, HogFunctionInvocationResult, HogFunctionTypeType } from '../types' +import { CdpCyclotronWorker } from './cdp-cyclotron-worker.consumer' + +type PluginState = { + setupPromise: Promise + meta: Meta +} + +const PLUGIN_STATE: Record = {} + +/** + * NOTE: This is a consumer to take care of legacy plugins. + */ +export class CdpCyclotronWorkerPlugins extends CdpCyclotronWorker { + protected name = 'CdpCyclotronWorkerPlugins' + protected queue = 'plugins' as const + protected hogTypes: HogFunctionTypeType[] = ['destination'] + + public async processInvocations(invocations: HogFunctionInvocation[]): Promise { + return await this.runManyWithHeartbeat(invocations, (item) => this.executePluginInvocation(item)) + } + + private async executePluginInvocation(invocation: HogFunctionInvocation): Promise { + const result: HogFunctionInvocationResult = { + invocation, + finished: true, + capturedPostHogEvents: [], + logs: [], + } + + const pluginId = invocation.hogFunction.template_id?.startsWith('plugin-') + ? invocation.hogFunction.template_id + : `plugin-${invocation.hogFunction.template_id}` + + result.logs.push({ + level: 'debug', + timestamp: DateTime.now(), + message: `Executing plugin ${pluginId}`, + }) + const plugin = PLUGINS_BY_ID[pluginId] + + if (!plugin) { + result.error = new Error(`Plugin ${pluginId} not found`) + result.logs.push({ + level: 'error', + timestamp: DateTime.now(), + message: `Plugin ${pluginId} not found`, + }) + return result + } + + // Convert the invocation into the right interface for the plugin + + const inputs = invocation.globals.inputs + + const event: ProcessedPluginEvent = { + distinct_id: invocation.globals.event.distinct_id, + ip: invocation.globals.event.properties.$ip, + team_id: invocation.hogFunction.team_id, + event: invocation.globals.event.event, + properties: invocation.globals.event.properties, + timestamp: invocation.globals.event.timestamp, + $set: invocation.globals.event.properties.$set, + $set_once: invocation.globals.event.properties.$set_once, + uuid: invocation.globals.event.uuid, + person: invocation.globals.person + ? { + uuid: invocation.globals.person.id, + team_id: invocation.hogFunction.team_id, + properties: invocation.globals.person.properties, + created_at: '', // NOTE: We don't have this anymore - see if any plugin uses it... + } + : undefined, + } + + let state = PLUGIN_STATE[pluginId] + + if (!state) { + const meta: Meta = { + global: inputs, + attachments: {}, + config: {}, + jobs: {}, + metrics: {}, + cache: {} as any, + storage: {} as any, // NOTE: Figuree out what to do about storage as that is used... + geoip: {} as any, + utils: {} as any, + } + + state = PLUGIN_STATE[pluginId] = { + setupPromise: plugin.setupPlugin?.(meta) ?? Promise.resolve(), + meta, + } + } + + await state.setupPromise + + await plugin.onEvent?.(event, { + ...state.meta, + global: inputs, + }) + + return result + } +} diff --git a/plugin-server/src/cdp/consumers/cdp-cyclotron-worker.consumer.ts b/plugin-server/src/cdp/consumers/cdp-cyclotron-worker.consumer.ts index 89e752d9cb7f3..5fd0d1981dc45 100644 --- a/plugin-server/src/cdp/consumers/cdp-cyclotron-worker.consumer.ts +++ b/plugin-server/src/cdp/consumers/cdp-cyclotron-worker.consumer.ts @@ -13,9 +13,13 @@ export class CdpCyclotronWorker extends CdpConsumerBase { protected name = 'CdpCyclotronWorker' private cyclotronWorker?: CyclotronWorker private runningWorker: Promise | undefined - protected queue: 'hog' | 'fetch' = 'hog' + protected queue: 'hog' | 'fetch' | 'plugins' = 'hog' protected hogTypes: HogFunctionTypeType[] = ['destination', 'internal_destination'] + public async processInvocations(invocations: HogFunctionInvocation[]): Promise { + return await this.runManyWithHeartbeat(invocations, (item) => this.hogExecutor.execute(item)) + } + public async processBatch(invocations: HogFunctionInvocation[]): Promise { if (!invocations.length) { return @@ -23,19 +27,7 @@ export class CdpCyclotronWorker extends CdpConsumerBase { const invocationResults = await runInstrumentedFunction({ statsKey: `cdpConsumer.handleEachBatch.executeInvocations`, - func: async () => { - // NOTE: this service will never do fetching (unless we decide we want to do it in node at some point, its only used for e2e testing) - // fetchExecutor would use rusty-hook to send a fetch request but thats no longer the case - // we are currentyl going to execute the fetch locally for testing purposes - // as nothing should ever land on the deprecated fetch queue this should be safe. - const fetchQueue = invocations.filter((item) => item.queue === 'fetch') - const fetchResults = await this.runManyWithHeartbeat(fetchQueue, (item) => - this.fetchExecutor.execute(item) - ) - const hogQueue = invocations.filter((item) => item.queue === 'hog') - const hogResults = await this.runManyWithHeartbeat(hogQueue, (item) => this.hogExecutor.execute(item)) - return [...hogResults, ...(fetchResults.filter(Boolean) as HogFunctionInvocationResult[])] - }, + func: async () => await this.processInvocations(invocations), }) await this.processInvocationResults(invocationResults) @@ -140,4 +132,11 @@ export class CdpCyclotronWorker extends CdpConsumerBase { export class CdpCyclotronWorkerFetch extends CdpCyclotronWorker { protected name = 'CdpCyclotronWorkerFetch' protected queue = 'fetch' as const + + public async processInvocations(invocations: HogFunctionInvocation[]): Promise { + // NOTE: this service will never do fetching (unless we decide we want to do it in node at some point, its only used for e2e testing) + return (await this.runManyWithHeartbeat(invocations, (item) => this.fetchExecutor.execute(item))).filter( + Boolean + ) as HogFunctionInvocationResult[] + } } diff --git a/plugin-server/src/cdp/legacy-plugins/manager.ts b/plugin-server/src/cdp/legacy-plugins/manager.ts new file mode 100644 index 0000000000000..f6de65c90608b --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/manager.ts @@ -0,0 +1,5 @@ +import { customerioPlugin } from './customerio' + +export const PLUGINS_BY_ID = { + [customerioPlugin.id]: customerioPlugin, +} diff --git a/plugin-server/src/cdp/legacy-plugins/types.ts b/plugin-server/src/cdp/legacy-plugins/types.ts new file mode 100644 index 0000000000000..cde9ed0529317 --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/types.ts @@ -0,0 +1,9 @@ +import { Plugin } from '@posthog/plugin-scaffold' + +export type LegacyPlugin = { + id: string + name: string + description: string + onEvent: Plugin['onEvent'] + setupPlugin: Plugin['setupPlugin'] +} diff --git a/plugin-server/src/cdp/services/hog-function-manager.service.ts b/plugin-server/src/cdp/services/hog-function-manager.service.ts index 0c4a693b22f46..0c3b6d04052c1 100644 --- a/plugin-server/src/cdp/services/hog-function-manager.service.ts +++ b/plugin-server/src/cdp/services/hog-function-manager.service.ts @@ -25,6 +25,7 @@ const HOG_FUNCTION_FIELDS = [ 'bytecode', 'masking', 'type', + 'template_id', ] export class HogFunctionManagerService { diff --git a/plugin-server/src/cdp/types.ts b/plugin-server/src/cdp/types.ts index bd154a2c2d29c..1a0cd607c7a36 100644 --- a/plugin-server/src/cdp/types.ts +++ b/plugin-server/src/cdp/types.ts @@ -212,7 +212,7 @@ export type HogFunctionInvocation = { teamId: Team['id'] hogFunction: HogFunctionType priority: number - queue: 'hog' | 'fetch' + queue: 'hog' | 'fetch' | 'plugins' queueParameters?: HogFunctionInvocationQueueParameters // The current vmstate (set if the invocation is paused) vmState?: VMState @@ -305,6 +305,7 @@ export type HogFunctionType = { mappings?: HogFunctionMappingType[] | null masking?: HogFunctionMasking | null depends_on_integration_ids?: Set + template_id?: string } export type HogFunctionInputType = { From 99e5b7274cb8d1becf0ded204c578c9195624420 Mon Sep 17 00:00:00 2001 From: Ben White Date: Thu, 23 Jan 2025 16:50:27 +0100 Subject: [PATCH 002/163] Fix up new plugins --- ...-cyclotron-plugins-worker.consumer.test.ts | 160 ++++++++++++++++++ .../cdp-cyclotron-plugins-worker.consumer.ts | 89 ++++++---- .../src/cdp/legacy-plugins/manager.ts | 3 +- plugin-server/src/cdp/legacy-plugins/types.ts | 13 +- 4 files changed, 229 insertions(+), 36 deletions(-) create mode 100644 plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.test.ts diff --git a/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.test.ts b/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.test.ts new file mode 100644 index 0000000000000..c45d2eedc521f --- /dev/null +++ b/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.test.ts @@ -0,0 +1,160 @@ +import { DateTime } from 'luxon' + +import { + createHogExecutionGlobals, + createInvocation, + insertHogFunction as _insertHogFunction, +} from '~/tests/cdp/fixtures' +import { forSnapshot } from '~/tests/helpers/snapshots' +import { getFirstTeam, resetTestDatabase } from '~/tests/helpers/sql' + +import { Hub, Team } from '../../types' +import { closeHub, createHub } from '../../utils/db/hub' +import { PLUGINS_BY_ID } from '../legacy-plugins/manager' +import { HogFunctionInvocationGlobalsWithInputs, HogFunctionType } from '../types' +import { CdpCyclotronWorkerPlugins } from './cdp-cyclotron-plugins-worker.consumer' + +jest.mock('../../../src/utils/fetch', () => { + return { + trackedFetch: jest.fn(() => + Promise.resolve({ + status: 200, + text: () => Promise.resolve(JSON.stringify({ success: true })), + json: () => Promise.resolve({ success: true }), + }) + ), + } +}) + +const mockFetch: jest.Mock = require('../../../src/utils/fetch').trackedFetch + +jest.setTimeout(1000) + +/** + * NOTE: The internal and normal events consumers are very similar so we can test them together + */ +describe('CdpCyclotronWorkerPlugins', () => { + let processor: CdpCyclotronWorkerPlugins + let hub: Hub + let team: Team + let fn: HogFunctionType + let globals: HogFunctionInvocationGlobalsWithInputs + + const insertHogFunction = async (hogFunction: Partial) => { + const item = await _insertHogFunction(hub.postgres, team.id, { + ...hogFunction, + type: 'destination', + }) + // Trigger the reload that django would do + await processor.hogFunctionManager.reloadAllHogFunctions() + return item + } + + beforeEach(async () => { + await resetTestDatabase() + hub = await createHub() + + team = await getFirstTeam(hub) + + processor = new CdpCyclotronWorkerPlugins(hub) + await processor.start() + + mockFetch.mockClear() + + const fixedTime = DateTime.fromObject({ year: 2025, month: 1, day: 1 }, { zone: 'UTC' }) + jest.spyOn(Date, 'now').mockReturnValue(fixedTime.toMillis()) + + fn = await insertHogFunction({ + name: 'Plugin test', + template_id: 'plugin-intercom', + }) + globals = { + ...createHogExecutionGlobals({ + project: { + id: team.id, + } as any, + event: { + uuid: 'b3a1fe86-b10c-43cc-acaf-d208977608d0', + event: '$pageview', + properties: { + $current_url: 'https://posthog.com', + $lib_version: '1.0.0', + }, + } as any, + }), + inputs: { + intercomApiKey: '1234567890', + triggeringEvents: '$identify,mycustomevent', + ignoredEmailDomains: 'posthog.com,dev.posthog.com', + useEuropeanDataStorage: 'No', + }, + } + }) + + afterEach(async () => { + jest.setTimeout(10000) + await processor.stop() + await closeHub(hub) + }) + + afterAll(() => { + jest.useRealTimers() + }) + + describe('setupPlugin', () => { + it('should setup a plugin on first call', async () => { + jest.spyOn(PLUGINS_BY_ID['intercom'], 'setupPlugin') + + const results = [] + + results.push(processor.executePluginInvocation(createInvocation(fn, globals))) + results.push(processor.executePluginInvocation(createInvocation(fn, globals))) + results.push(processor.executePluginInvocation(createInvocation(fn, globals))) + + expect(await Promise.all(results)).toMatchObject([ + { finished: true }, + { finished: true }, + { finished: true }, + ]) + + expect(PLUGINS_BY_ID['intercom'].setupPlugin).toHaveBeenCalledTimes(1) + expect(jest.mocked(PLUGINS_BY_ID['intercom'].setupPlugin!).mock.calls[0][0]).toMatchInlineSnapshot(` + { + "attachments": {}, + "cache": {}, + "config": {}, + "fetch": [Function], + "geoip": {}, + "global": { + "ignoredEmailDomains": "posthog.com,dev.posthog.com", + "intercomApiKey": "1234567890", + "triggeringEvents": "$identify,mycustomevent", + "useEuropeanDataStorage": "No", + }, + "jobs": {}, + "metrics": {}, + "storage": {}, + "utils": {}, + } + `) + }) + }) + + describe('onEvent', () => { + it('should call the plugin onEvent method', async () => { + jest.spyOn(PLUGINS_BY_ID['intercom'], 'onEvent') + + const results = [] + + results.push(processor.executePluginInvocation(createInvocation(fn, globals))) + results.push(processor.executePluginInvocation(createInvocation(fn, globals))) + results.push(processor.executePluginInvocation(createInvocation(fn, globals))) + + expect(await Promise.all(results)).toMatchObject([ + { finished: true }, + { finished: true }, + { finished: true }, + ]) + }) + }) +}) diff --git a/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.ts b/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.ts index dfb8eca718f0c..9ec7216dce02c 100644 --- a/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.ts +++ b/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.ts @@ -2,16 +2,16 @@ import { Meta, ProcessedPluginEvent } from '@posthog/plugin-scaffold' import { DateTime } from 'luxon' import { PLUGINS_BY_ID } from '../legacy-plugins/manager' +import { FetchType, MetaWithFetch } from '../legacy-plugins/types' import { HogFunctionInvocation, HogFunctionInvocationResult, HogFunctionTypeType } from '../types' import { CdpCyclotronWorker } from './cdp-cyclotron-worker.consumer' type PluginState = { setupPromise: Promise + errored: boolean meta: Meta } -const PLUGIN_STATE: Record = {} - /** * NOTE: This is a consumer to take care of legacy plugins. */ @@ -20,11 +20,18 @@ export class CdpCyclotronWorkerPlugins extends CdpCyclotronWorker { protected queue = 'plugins' as const protected hogTypes: HogFunctionTypeType[] = ['destination'] + private pluginState: Record = {} + public async processInvocations(invocations: HogFunctionInvocation[]): Promise { return await this.runManyWithHeartbeat(invocations, (item) => this.executePluginInvocation(item)) } - private async executePluginInvocation(invocation: HogFunctionInvocation): Promise { + public fetch(...args: Parameters) { + // TOOD: THis better + return this.fetchExecutor.fetch(...args) + } + + public async executePluginInvocation(invocation: HogFunctionInvocation): Promise { const result: HogFunctionInvocationResult = { invocation, finished: true, @@ -33,17 +40,17 @@ export class CdpCyclotronWorkerPlugins extends CdpCyclotronWorker { } const pluginId = invocation.hogFunction.template_id?.startsWith('plugin-') - ? invocation.hogFunction.template_id - : `plugin-${invocation.hogFunction.template_id}` + ? invocation.hogFunction.template_id.replace('plugin-', '') + : null result.logs.push({ level: 'debug', timestamp: DateTime.now(), message: `Executing plugin ${pluginId}`, }) - const plugin = PLUGINS_BY_ID[pluginId] + const plugin = pluginId ? PLUGINS_BY_ID[pluginId] : null - if (!plugin) { + if (!plugin || !pluginId) { result.error = new Error(`Plugin ${pluginId} not found`) result.logs.push({ level: 'error', @@ -53,9 +60,43 @@ export class CdpCyclotronWorkerPlugins extends CdpCyclotronWorker { return result } - // Convert the invocation into the right interface for the plugin + let state = this.pluginState[pluginId] - const inputs = invocation.globals.inputs + if (!state) { + const meta: MetaWithFetch = { + global: invocation.globals.inputs, + attachments: {}, + config: {}, + jobs: {}, + metrics: {}, + cache: {} as any, + storage: {} as any, // NOTE: Figuree out what to do about storage as that is used... + geoip: {} as any, + utils: {} as any, + fetch: this.fetch, + } + + state = this.pluginState[pluginId] = { + setupPromise: plugin.setupPlugin?.(meta) ?? Promise.resolve(), + meta, + errored: false, + } + } + + try { + await state.setupPromise + } catch (e) { + state.errored = true + result.error = e + result.logs.push({ + level: 'error', + timestamp: DateTime.now(), + message: `Plugin ${pluginId} setup failed`, + }) + return result + } + + // Convert the invocation into the right interface for the plugin const event: ProcessedPluginEvent = { distinct_id: invocation.globals.event.distinct_id, @@ -77,32 +118,12 @@ export class CdpCyclotronWorkerPlugins extends CdpCyclotronWorker { : undefined, } - let state = PLUGIN_STATE[pluginId] - - if (!state) { - const meta: Meta = { - global: inputs, - attachments: {}, - config: {}, - jobs: {}, - metrics: {}, - cache: {} as any, - storage: {} as any, // NOTE: Figuree out what to do about storage as that is used... - geoip: {} as any, - utils: {} as any, - } - - state = PLUGIN_STATE[pluginId] = { - setupPromise: plugin.setupPlugin?.(meta) ?? Promise.resolve(), - meta, - } - } - - await state.setupPromise + await plugin.onEvent?.(event, state.meta) - await plugin.onEvent?.(event, { - ...state.meta, - global: inputs, + result.logs.push({ + level: 'debug', + timestamp: DateTime.now(), + message: `Plugin ${pluginId} execution successful`, }) return result diff --git a/plugin-server/src/cdp/legacy-plugins/manager.ts b/plugin-server/src/cdp/legacy-plugins/manager.ts index f6de65c90608b..45861c579c4ff 100644 --- a/plugin-server/src/cdp/legacy-plugins/manager.ts +++ b/plugin-server/src/cdp/legacy-plugins/manager.ts @@ -1,5 +1,6 @@ import { customerioPlugin } from './customerio' - +import { intercomPlugin } from './intercom' export const PLUGINS_BY_ID = { [customerioPlugin.id]: customerioPlugin, + [intercomPlugin.id]: intercomPlugin, } diff --git a/plugin-server/src/cdp/legacy-plugins/types.ts b/plugin-server/src/cdp/legacy-plugins/types.ts index cde9ed0529317..739079116e8b6 100644 --- a/plugin-server/src/cdp/legacy-plugins/types.ts +++ b/plugin-server/src/cdp/legacy-plugins/types.ts @@ -1,4 +1,5 @@ -import { Plugin } from '@posthog/plugin-scaffold' +import { Meta, Plugin } from '@posthog/plugin-scaffold' +import fetch from 'node-fetch' export type LegacyPlugin = { id: string @@ -7,3 +8,13 @@ export type LegacyPlugin = { onEvent: Plugin['onEvent'] setupPlugin: Plugin['setupPlugin'] } + +export type FetchType = typeof fetch + +export type MetaWithFetch = Meta & { + fetch: FetchType +} + +export type PluginWithFetch = Plugin & { + fetch: FetchType +} From 52792bf16ec7c5901c80b54fcb3d9755a2579772 Mon Sep 17 00:00:00 2001 From: Ben White Date: Thu, 23 Jan 2025 17:51:14 +0100 Subject: [PATCH 003/163] Fixes --- ...-cyclotron-plugins-worker.consumer.test.ts | 50 ++++++++++++++----- .../cdp-cyclotron-plugins-worker.consumer.ts | 10 ++-- .../legacy-plugins/{manager.ts => index.ts} | 1 + 3 files changed, 45 insertions(+), 16 deletions(-) rename plugin-server/src/cdp/legacy-plugins/{manager.ts => index.ts} (99%) diff --git a/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.test.ts b/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.test.ts index c45d2eedc521f..08685c32a3ea0 100644 --- a/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.test.ts +++ b/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.test.ts @@ -5,12 +5,11 @@ import { createInvocation, insertHogFunction as _insertHogFunction, } from '~/tests/cdp/fixtures' -import { forSnapshot } from '~/tests/helpers/snapshots' import { getFirstTeam, resetTestDatabase } from '~/tests/helpers/sql' import { Hub, Team } from '../../types' import { closeHub, createHub } from '../../utils/db/hub' -import { PLUGINS_BY_ID } from '../legacy-plugins/manager' +import { PLUGINS_BY_ID } from '../legacy-plugins' import { HogFunctionInvocationGlobalsWithInputs, HogFunctionType } from '../types' import { CdpCyclotronWorkerPlugins } from './cdp-cyclotron-plugins-worker.consumer' @@ -57,6 +56,7 @@ describe('CdpCyclotronWorkerPlugins', () => { team = await getFirstTeam(hub) processor = new CdpCyclotronWorkerPlugins(hub) + await processor.start() mockFetch.mockClear() @@ -79,13 +79,16 @@ describe('CdpCyclotronWorkerPlugins', () => { properties: { $current_url: 'https://posthog.com', $lib_version: '1.0.0', + $set: { + email: 'test@posthog.com', + }, }, } as any, }), inputs: { intercomApiKey: '1234567890', triggeringEvents: '$identify,mycustomevent', - ignoredEmailDomains: 'posthog.com,dev.posthog.com', + ignoredEmailDomains: 'dev.posthog.com', useEuropeanDataStorage: 'No', }, } @@ -144,17 +147,40 @@ describe('CdpCyclotronWorkerPlugins', () => { it('should call the plugin onEvent method', async () => { jest.spyOn(PLUGINS_BY_ID['intercom'], 'onEvent') - const results = [] + const invocation = createInvocation(fn, globals) + invocation.globals.event.event = 'mycustomevent' + invocation.globals.event.properties = { + email: 'test@posthog.com', + } + await processor.executePluginInvocation(invocation) - results.push(processor.executePluginInvocation(createInvocation(fn, globals))) - results.push(processor.executePluginInvocation(createInvocation(fn, globals))) - results.push(processor.executePluginInvocation(createInvocation(fn, globals))) + expect(PLUGINS_BY_ID['intercom'].onEvent).toHaveBeenCalledTimes(1) + expect(jest.mocked(PLUGINS_BY_ID['intercom'].onEvent!).mock.calls[0][0]).toMatchInlineSnapshot(` + { + "$set": undefined, + "$set_once": undefined, + "distinct_id": "distinct_id", + "event": "mycustomevent", + "ip": undefined, + "person": { + "created_at": "", + "properties": { + "email": "test@posthog.com", + "first_name": "Pumpkin", + }, + "team_id": 2, + "uuid": "uuid", + }, + "properties": { + "email": "test@posthog.com", + }, + "team_id": 2, + "timestamp": "2025-01-23T15:59:22.483Z", + "uuid": "b3a1fe86-b10c-43cc-acaf-d208977608d0", + } + `) - expect(await Promise.all(results)).toMatchObject([ - { finished: true }, - { finished: true }, - { finished: true }, - ]) + expect(mockFetch).toHaveBeenCalledTimes(1) }) }) }) diff --git a/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.ts b/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.ts index 9ec7216dce02c..c9d3af0a695f1 100644 --- a/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.ts +++ b/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.ts @@ -1,7 +1,9 @@ import { Meta, ProcessedPluginEvent } from '@posthog/plugin-scaffold' import { DateTime } from 'luxon' -import { PLUGINS_BY_ID } from '../legacy-plugins/manager' +import { trackedFetch } from '~/src/utils/fetch' + +import { PLUGINS_BY_ID } from '../legacy-plugins' import { FetchType, MetaWithFetch } from '../legacy-plugins/types' import { HogFunctionInvocation, HogFunctionInvocationResult, HogFunctionTypeType } from '../types' import { CdpCyclotronWorker } from './cdp-cyclotron-worker.consumer' @@ -28,7 +30,7 @@ export class CdpCyclotronWorkerPlugins extends CdpCyclotronWorker { public fetch(...args: Parameters) { // TOOD: THis better - return this.fetchExecutor.fetch(...args) + return trackedFetch(...args) } public async executePluginInvocation(invocation: HogFunctionInvocation): Promise { @@ -64,9 +66,9 @@ export class CdpCyclotronWorkerPlugins extends CdpCyclotronWorker { if (!state) { const meta: MetaWithFetch = { - global: invocation.globals.inputs, + config: invocation.globals.inputs, attachments: {}, - config: {}, + globalwu: {}, jobs: {}, metrics: {}, cache: {} as any, diff --git a/plugin-server/src/cdp/legacy-plugins/manager.ts b/plugin-server/src/cdp/legacy-plugins/index.ts similarity index 99% rename from plugin-server/src/cdp/legacy-plugins/manager.ts rename to plugin-server/src/cdp/legacy-plugins/index.ts index 45861c579c4ff..f15927da6081b 100644 --- a/plugin-server/src/cdp/legacy-plugins/manager.ts +++ b/plugin-server/src/cdp/legacy-plugins/index.ts @@ -1,5 +1,6 @@ import { customerioPlugin } from './customerio' import { intercomPlugin } from './intercom' + export const PLUGINS_BY_ID = { [customerioPlugin.id]: customerioPlugin, [intercomPlugin.id]: intercomPlugin, From dfb9e0ddab900afab23922bf660f7e1a772e672f Mon Sep 17 00:00:00 2001 From: Ben White Date: Thu, 23 Jan 2025 17:56:03 +0100 Subject: [PATCH 004/163] Fixes --- ...-cyclotron-plugins-worker.consumer.test.ts | 48 ++++++++++++++++--- 1 file changed, 41 insertions(+), 7 deletions(-) diff --git a/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.test.ts b/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.test.ts index 08685c32a3ea0..da5a95800a254 100644 --- a/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.test.ts +++ b/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.test.ts @@ -5,6 +5,7 @@ import { createInvocation, insertHogFunction as _insertHogFunction, } from '~/tests/cdp/fixtures' +import { forSnapshot } from '~/tests/helpers/snapshots' import { getFirstTeam, resetTestDatabase } from '~/tests/helpers/sql' import { Hub, Team } from '../../types' @@ -83,6 +84,7 @@ describe('CdpCyclotronWorkerPlugins', () => { email: 'test@posthog.com', }, }, + timestamp: fixedTime.toISO(), } as any, }), inputs: { @@ -152,16 +154,20 @@ describe('CdpCyclotronWorkerPlugins', () => { invocation.globals.event.properties = { email: 'test@posthog.com', } + + mockFetch.mockResolvedValue({ + status: 200, + json: () => Promise.resolve({ total_count: 1 }), + }) + await processor.executePluginInvocation(invocation) expect(PLUGINS_BY_ID['intercom'].onEvent).toHaveBeenCalledTimes(1) - expect(jest.mocked(PLUGINS_BY_ID['intercom'].onEvent!).mock.calls[0][0]).toMatchInlineSnapshot(` + expect(forSnapshot(jest.mocked(PLUGINS_BY_ID['intercom'].onEvent!).mock.calls[0][0])) + .toMatchInlineSnapshot(` { - "$set": undefined, - "$set_once": undefined, "distinct_id": "distinct_id", "event": "mycustomevent", - "ip": undefined, "person": { "created_at": "", "properties": { @@ -175,12 +181,40 @@ describe('CdpCyclotronWorkerPlugins', () => { "email": "test@posthog.com", }, "team_id": 2, - "timestamp": "2025-01-23T15:59:22.483Z", - "uuid": "b3a1fe86-b10c-43cc-acaf-d208977608d0", + "timestamp": "2025-01-01T00:00:00.000Z", + "uuid": "", } `) - expect(mockFetch).toHaveBeenCalledTimes(1) + expect(mockFetch).toHaveBeenCalledTimes(2) + expect(forSnapshot(mockFetch.mock.calls[0])).toMatchInlineSnapshot(` + [ + "https://api.intercom.io/contacts/search", + { + "body": "{"query":{"field":"email","operator":"=","value":"test@posthog.com"}}", + "headers": { + "Accept": "application/json", + "Authorization": "Bearer 1234567890", + "Content-Type": "application/json", + }, + "method": "POST", + }, + ] + `) + expect(forSnapshot(mockFetch.mock.calls[1])).toMatchInlineSnapshot(` + [ + "https://api.intercom.io/events", + { + "body": "{"event_name":"mycustomevent","created_at":null,"email":"test@posthog.com","id":"distinct_id"}", + "headers": { + "Accept": "application/json", + "Authorization": "Bearer 1234567890", + "Content-Type": "application/json", + }, + "method": "POST", + }, + ] + `) }) }) }) From 6c2575adca1b40cb1221c5ae2f885bdfa18d07f6 Mon Sep 17 00:00:00 2001 From: Ben White Date: Thu, 23 Jan 2025 17:59:40 +0100 Subject: [PATCH 005/163] Fixes --- ...-cyclotron-plugins-worker.consumer.test.ts | 42 ++++++++++++++++--- .../cdp-cyclotron-plugins-worker.consumer.ts | 27 ++++++++---- 2 files changed, 56 insertions(+), 13 deletions(-) diff --git a/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.test.ts b/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.test.ts index da5a95800a254..8cff66d917e7a 100644 --- a/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.test.ts +++ b/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.test.ts @@ -127,15 +127,15 @@ describe('CdpCyclotronWorkerPlugins', () => { { "attachments": {}, "cache": {}, - "config": {}, - "fetch": [Function], - "geoip": {}, - "global": { - "ignoredEmailDomains": "posthog.com,dev.posthog.com", + "config": { + "ignoredEmailDomains": "dev.posthog.com", "intercomApiKey": "1234567890", "triggeringEvents": "$identify,mycustomevent", "useEuropeanDataStorage": "No", }, + "fetch": [Function], + "geoip": {}, + "globalwu": {}, "jobs": {}, "metrics": {}, "storage": {}, @@ -216,5 +216,37 @@ describe('CdpCyclotronWorkerPlugins', () => { ] `) }) + + it('should handle and collect errors', async () => { + jest.spyOn(PLUGINS_BY_ID['intercom'], 'onEvent') + + const invocation = createInvocation(fn, globals) + invocation.globals.event.event = 'mycustomevent' + invocation.globals.event.properties = { + email: 'test@posthog.com', + } + + mockFetch.mockRejectedValue(new Error('Test error')) + + const res = await processor.executePluginInvocation(invocation) + + expect(PLUGINS_BY_ID['intercom'].onEvent).toHaveBeenCalledTimes(1) + + expect(res.error).toBeInstanceOf(Error) + expect(forSnapshot(res.logs)).toMatchInlineSnapshot(` + [ + { + "level": "debug", + "message": "Executing plugin intercom", + "timestamp": "2025-01-01T01:00:00.000+01:00", + }, + { + "level": "error", + "message": "Plugin intercom execution failed", + "timestamp": "2025-01-01T01:00:00.000+01:00", + }, + ] + `) + }) }) }) diff --git a/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.ts b/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.ts index c9d3af0a695f1..fa8c18a43c5e0 100644 --- a/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.ts +++ b/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.ts @@ -1,4 +1,4 @@ -import { Meta, ProcessedPluginEvent } from '@posthog/plugin-scaffold' +import { Meta, ProcessedPluginEvent, RetryError } from '@posthog/plugin-scaffold' import { DateTime } from 'luxon' import { trackedFetch } from '~/src/utils/fetch' @@ -120,13 +120,24 @@ export class CdpCyclotronWorkerPlugins extends CdpCyclotronWorker { : undefined, } - await plugin.onEvent?.(event, state.meta) - - result.logs.push({ - level: 'debug', - timestamp: DateTime.now(), - message: `Plugin ${pluginId} execution successful`, - }) + try { + await plugin.onEvent?.(event, state.meta) + result.logs.push({ + level: 'debug', + timestamp: DateTime.now(), + message: `Plugin ${pluginId} execution successful`, + }) + } catch (e) { + if (e instanceof RetryError) { + // NOTE: Schedule as a retry to cyclotron? + } + result.error = e + result.logs.push({ + level: 'error', + timestamp: DateTime.now(), + message: `Plugin ${pluginId} execution failed`, + }) + } return result } From f7db6e5883c846e87dccbde7209aede8064c4f59 Mon Sep 17 00:00:00 2001 From: Ben White Date: Thu, 23 Jan 2025 18:00:06 +0100 Subject: [PATCH 006/163] Fix --- .../cdp/consumers/cdp-cyclotron-plugins-worker.consumer.test.ts | 2 +- .../src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.test.ts b/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.test.ts index 8cff66d917e7a..d74f12d73705c 100644 --- a/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.test.ts +++ b/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.test.ts @@ -135,7 +135,7 @@ describe('CdpCyclotronWorkerPlugins', () => { }, "fetch": [Function], "geoip": {}, - "globalwu": {}, + "global": {}, "jobs": {}, "metrics": {}, "storage": {}, diff --git a/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.ts b/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.ts index fa8c18a43c5e0..e202d9e2bea30 100644 --- a/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.ts +++ b/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.ts @@ -68,7 +68,7 @@ export class CdpCyclotronWorkerPlugins extends CdpCyclotronWorker { const meta: MetaWithFetch = { config: invocation.globals.inputs, attachments: {}, - globalwu: {}, + global: {}, jobs: {}, metrics: {}, cache: {} as any, From 835d8fb3b77bad0a1b1b01c6a4ae45ff5af988e3 Mon Sep 17 00:00:00 2001 From: Ben White Date: Thu, 23 Jan 2025 18:04:54 +0100 Subject: [PATCH 007/163] Fixes --- .../cdp-processed-events.consumer.ts | 2 +- plugin-server/tests/cdp/cdp-e2e.test.ts | 114 ++++++++++++++++++ 2 files changed, 115 insertions(+), 1 deletion(-) diff --git a/plugin-server/src/cdp/consumers/cdp-processed-events.consumer.ts b/plugin-server/src/cdp/consumers/cdp-processed-events.consumer.ts index 52b6e558c41e6..67b434c9979b2 100644 --- a/plugin-server/src/cdp/consumers/cdp-processed-events.consumer.ts +++ b/plugin-server/src/cdp/consumers/cdp-processed-events.consumer.ts @@ -41,7 +41,7 @@ export class CdpProcessedEventsConsumer extends CdpConsumerBase { return { teamId: item.globals.project.id, functionId: item.hogFunction.id, - queueName: 'hog', + queueName: item.hogFunction.template_id?.startsWith('plugin-') ? 'plugin' : 'hog', priority: item.priority, vmState: serializeHogFunctionInvocation(item), } diff --git a/plugin-server/tests/cdp/cdp-e2e.test.ts b/plugin-server/tests/cdp/cdp-e2e.test.ts index cce755643caa9..50db3eb85f189 100644 --- a/plugin-server/tests/cdp/cdp-e2e.test.ts +++ b/plugin-server/tests/cdp/cdp-e2e.test.ts @@ -3,6 +3,7 @@ import { getProducedKafkaMessages, getProducedKafkaMessagesForTopic } from '../h import { CdpCyclotronWorker, CdpCyclotronWorkerFetch } from '../../src/cdp/consumers/cdp-cyclotron-worker.consumer' import { CdpProcessedEventsConsumer } from '../../src/cdp/consumers/cdp-processed-events.consumer' +import { CdpCyclotronWorkerPlugins } from '../../src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer' import { HogFunctionInvocationGlobals, HogFunctionType } from '../../src/cdp/types' import { KAFKA_APP_METRICS_2, KAFKA_LOG_ENTRIES } from '../../src/config/kafka-topics' import { Hub, Team } from '../../src/types' @@ -34,6 +35,8 @@ describe('CDP Consumer loop', () => { let processedEventsConsumer: CdpProcessedEventsConsumer let cyclotronWorker: CdpCyclotronWorker | undefined let cyclotronFetchWorker: CdpCyclotronWorkerFetch | undefined + let cdpCyclotronWorkerPlugins: CdpCyclotronWorkerPlugins | undefined + let hub: Hub let team: Team let fnFetchNoFilters: HogFunctionType @@ -62,6 +65,8 @@ describe('CDP Consumer loop', () => { cyclotronWorker = new CdpCyclotronWorker(hub) await cyclotronWorker.start() + cdpCyclotronWorkerPlugins = new CdpCyclotronWorkerPlugins(hub) + await cdpCyclotronWorkerPlugins.start() cyclotronFetchWorker = new CdpCyclotronWorkerFetch(hub) await cyclotronFetchWorker.start() @@ -88,6 +93,7 @@ describe('CDP Consumer loop', () => { processedEventsConsumer?.stop().then(() => console.log('Stopped processedEventsConsumer')), cyclotronWorker?.stop().then(() => console.log('Stopped cyclotronWorker')), cyclotronFetchWorker?.stop().then(() => console.log('Stopped cyclotronFetchWorker')), + cdpCyclotronWorkerPlugins?.stop().then(() => console.log('Stopped cdpCyclotronWorkerPlugins')), ] await Promise.all(stoppers) @@ -210,5 +216,113 @@ describe('CDP Consumer loop', () => { }, ]) }) + + it('should invoke a legacy plugin in the worker loop until completed', async () => { + const invocations = await processedEventsConsumer.processBatch([globals]) + expect(invocations).toHaveLength(1) + + await waitForExpect(() => { + expect(getProducedKafkaMessages()).toHaveLength(7) + }, 5000) + + expect(mockFetch).toHaveBeenCalledTimes(1) + + expect(mockFetch.mock.calls[0]).toMatchInlineSnapshot(` + [ + "https://example.com/posthog-webhook", + { + "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": { + "version": "v=1.0.0", + }, + "method": "POST", + "timeout": 10000, + }, + ] + `) + + const logMessages = getProducedKafkaMessagesForTopic(KAFKA_LOG_ENTRIES) + const metricsMessages = getProducedKafkaMessagesForTopic(KAFKA_APP_METRICS_2) + + expect(metricsMessages).toMatchObject([ + { + topic: 'clickhouse_app_metrics2_test', + value: { + app_source: 'hog_function', + app_source_id: fnFetchNoFilters.id.toString(), + count: 1, + metric_kind: 'other', + metric_name: 'fetch', + team_id: 2, + }, + }, + { + topic: 'clickhouse_app_metrics2_test', + value: { + app_source: 'hog_function', + app_source_id: fnFetchNoFilters.id.toString(), + count: 1, + metric_kind: 'success', + metric_name: 'succeeded', + team_id: 2, + }, + }, + ]) + + expect(logMessages).toMatchObject([ + { + topic: 'log_entries_test', + value: { + level: 'debug', + log_source: 'hog_function', + log_source_id: fnFetchNoFilters.id.toString(), + message: 'Executing function', + team_id: 2, + }, + }, + { + topic: 'log_entries_test', + value: { + level: 'debug', + log_source: 'hog_function', + log_source_id: fnFetchNoFilters.id.toString(), + message: expect.stringContaining( + "Suspending function due to async function call 'fetch'. Payload:" + ), + team_id: 2, + }, + }, + { + topic: 'log_entries_test', + value: { + level: 'debug', + log_source: 'hog_function', + log_source_id: fnFetchNoFilters.id.toString(), + message: 'Resuming function', + team_id: 2, + }, + }, + { + topic: 'log_entries_test', + value: { + level: 'info', + log_source: 'hog_function', + log_source_id: fnFetchNoFilters.id.toString(), + message: `Fetch response:, {"status":200,"body":{"success":true}}`, + team_id: 2, + }, + }, + { + topic: 'log_entries_test', + value: { + level: 'debug', + log_source: 'hog_function', + log_source_id: fnFetchNoFilters.id.toString(), + message: expect.stringContaining('Function completed in'), + team_id: 2, + }, + }, + ]) + }) }) }) From dcc8b2fc1faa1b9bde7e6d0a1e3f76e2c84fea7f Mon Sep 17 00:00:00 2001 From: Ben White Date: Thu, 23 Jan 2025 22:21:50 +0100 Subject: [PATCH 008/163] Fixes --- .../consumers/cdp-cyclotron-plugins-worker.consumer.test.ts | 6 +++--- .../cdp/consumers/cdp-cyclotron-plugins-worker.consumer.ts | 2 +- plugin-server/src/utils/fetch.ts | 2 ++ 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.test.ts b/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.test.ts index d74f12d73705c..ebb5fca6c9a9a 100644 --- a/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.test.ts +++ b/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.test.ts @@ -108,7 +108,7 @@ describe('CdpCyclotronWorkerPlugins', () => { describe('setupPlugin', () => { it('should setup a plugin on first call', async () => { - jest.spyOn(PLUGINS_BY_ID['intercom'], 'setupPlugin') + jest.spyOn(PLUGINS_BY_ID['intercom'] as any, 'setupPlugin') const results = [] @@ -147,7 +147,7 @@ describe('CdpCyclotronWorkerPlugins', () => { describe('onEvent', () => { it('should call the plugin onEvent method', async () => { - jest.spyOn(PLUGINS_BY_ID['intercom'], 'onEvent') + jest.spyOn(PLUGINS_BY_ID['intercom'] as any, 'onEvent') const invocation = createInvocation(fn, globals) invocation.globals.event.event = 'mycustomevent' @@ -218,7 +218,7 @@ describe('CdpCyclotronWorkerPlugins', () => { }) it('should handle and collect errors', async () => { - jest.spyOn(PLUGINS_BY_ID['intercom'], 'onEvent') + jest.spyOn(PLUGINS_BY_ID['intercom'] as any, 'onEvent') const invocation = createInvocation(fn, globals) invocation.globals.event.event = 'mycustomevent' diff --git a/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.ts b/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.ts index e202d9e2bea30..6e5626ed408ef 100644 --- a/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.ts +++ b/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.ts @@ -75,7 +75,7 @@ export class CdpCyclotronWorkerPlugins extends CdpCyclotronWorker { storage: {} as any, // NOTE: Figuree out what to do about storage as that is used... geoip: {} as any, utils: {} as any, - fetch: this.fetch, + fetch: this.fetch as any, } state = this.pluginState[pluginId] = { diff --git a/plugin-server/src/utils/fetch.ts b/plugin-server/src/utils/fetch.ts index 96358d8ec2864..0fcd39cd20dc1 100644 --- a/plugin-server/src/utils/fetch.ts +++ b/plugin-server/src/utils/fetch.ts @@ -9,6 +9,8 @@ import net from 'node:net' import fetch, { type RequestInfo, type RequestInit, type Response, FetchError, Request } from 'node-fetch' import { URL } from 'url' +export type { Response } + import { runInSpan } from '../sentry' import { isProdEnv } from './env-utils' From 18ef79b43b6134b4ea3e717d3f55ad957995fc8a Mon Sep 17 00:00:00 2001 From: Ben White Date: Thu, 23 Jan 2025 22:25:43 +0100 Subject: [PATCH 009/163] Fix --- ...rker.consumer.test.ts => cdp-cyclotron-plugins-worker.test.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename plugin-server/src/cdp/consumers/{cdp-cyclotron-plugins-worker.consumer.test.ts => cdp-cyclotron-plugins-worker.test.ts} (100%) diff --git a/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.test.ts b/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.test.ts similarity index 100% rename from plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.test.ts rename to plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.test.ts From 9317b33fd4a1efe59df4850ab29b0328f63e3ee6 Mon Sep 17 00:00:00 2001 From: Ben White Date: Mon, 27 Jan 2025 09:22:07 +0100 Subject: [PATCH 010/163] Fixes --- .../cdp-cyclotron-plugins-worker.consumer.ts | 16 ++++++---------- .../consumers/cdp-cyclotron-worker.consumer.ts | 2 +- plugin-server/src/cdp/legacy-plugins/types.ts | 13 +------------ 3 files changed, 8 insertions(+), 23 deletions(-) diff --git a/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.ts b/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.ts index 6e5626ed408ef..82c22c9da616c 100644 --- a/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.ts +++ b/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.ts @@ -1,10 +1,7 @@ import { Meta, ProcessedPluginEvent, RetryError } from '@posthog/plugin-scaffold' import { DateTime } from 'luxon' -import { trackedFetch } from '~/src/utils/fetch' - import { PLUGINS_BY_ID } from '../legacy-plugins' -import { FetchType, MetaWithFetch } from '../legacy-plugins/types' import { HogFunctionInvocation, HogFunctionInvocationResult, HogFunctionTypeType } from '../types' import { CdpCyclotronWorker } from './cdp-cyclotron-worker.consumer' @@ -25,12 +22,12 @@ export class CdpCyclotronWorkerPlugins extends CdpCyclotronWorker { private pluginState: Record = {} public async processInvocations(invocations: HogFunctionInvocation[]): Promise { - return await this.runManyWithHeartbeat(invocations, (item) => this.executePluginInvocation(item)) - } + const results = await this.runManyWithHeartbeat(invocations, (item) => this.executePluginInvocation(item)) + + await this.processInvocationResults(results) + await this.updateJobs(results) - public fetch(...args: Parameters) { - // TOOD: THis better - return trackedFetch(...args) + return results } public async executePluginInvocation(invocation: HogFunctionInvocation): Promise { @@ -65,7 +62,7 @@ export class CdpCyclotronWorkerPlugins extends CdpCyclotronWorker { let state = this.pluginState[pluginId] if (!state) { - const meta: MetaWithFetch = { + const meta: Meta = { config: invocation.globals.inputs, attachments: {}, global: {}, @@ -75,7 +72,6 @@ export class CdpCyclotronWorkerPlugins extends CdpCyclotronWorker { storage: {} as any, // NOTE: Figuree out what to do about storage as that is used... geoip: {} as any, utils: {} as any, - fetch: this.fetch as any, } state = this.pluginState[pluginId] = { diff --git a/plugin-server/src/cdp/consumers/cdp-cyclotron-worker.consumer.ts b/plugin-server/src/cdp/consumers/cdp-cyclotron-worker.consumer.ts index 5fd0d1981dc45..1d96123162969 100644 --- a/plugin-server/src/cdp/consumers/cdp-cyclotron-worker.consumer.ts +++ b/plugin-server/src/cdp/consumers/cdp-cyclotron-worker.consumer.ts @@ -35,7 +35,7 @@ export class CdpCyclotronWorker extends CdpConsumerBase { await this.produceQueuedMessages() } - private async updateJobs(invocations: HogFunctionInvocationResult[]) { + protected async updateJobs(invocations: HogFunctionInvocationResult[]) { await Promise.all( invocations.map((item) => { if (item.invocation.queue === 'fetch') { diff --git a/plugin-server/src/cdp/legacy-plugins/types.ts b/plugin-server/src/cdp/legacy-plugins/types.ts index 739079116e8b6..cde9ed0529317 100644 --- a/plugin-server/src/cdp/legacy-plugins/types.ts +++ b/plugin-server/src/cdp/legacy-plugins/types.ts @@ -1,5 +1,4 @@ -import { Meta, Plugin } from '@posthog/plugin-scaffold' -import fetch from 'node-fetch' +import { Plugin } from '@posthog/plugin-scaffold' export type LegacyPlugin = { id: string @@ -8,13 +7,3 @@ export type LegacyPlugin = { onEvent: Plugin['onEvent'] setupPlugin: Plugin['setupPlugin'] } - -export type FetchType = typeof fetch - -export type MetaWithFetch = Meta & { - fetch: FetchType -} - -export type PluginWithFetch = Plugin & { - fetch: FetchType -} From e47eb3c8c837152f88cd367d4be53fc9d6163708 Mon Sep 17 00:00:00 2001 From: Ben White Date: Mon, 27 Jan 2025 09:23:42 +0100 Subject: [PATCH 011/163] Fix --- .../src/cdp/consumers/cdp-cyclotron-plugins-worker.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.test.ts b/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.test.ts index ebb5fca6c9a9a..05bf5cadb4b4b 100644 --- a/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.test.ts +++ b/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.test.ts @@ -133,7 +133,6 @@ describe('CdpCyclotronWorkerPlugins', () => { "triggeringEvents": "$identify,mycustomevent", "useEuropeanDataStorage": "No", }, - "fetch": [Function], "geoip": {}, "global": {}, "jobs": {}, From 365c1b898f1120eeb55967e5720a00ace0b92625 Mon Sep 17 00:00:00 2001 From: Ben White Date: Mon, 27 Jan 2025 09:55:33 +0100 Subject: [PATCH 012/163] Added check for metrics production --- .../cdp-cyclotron-plugins-worker.test.ts.snap | 45 +++++++++++++++ .../cdp-cyclotron-plugins-worker.consumer.ts | 1 + .../cdp-cyclotron-plugins-worker.test.ts | 56 ++++++++++--------- 3 files changed, 77 insertions(+), 25 deletions(-) create mode 100644 plugin-server/src/cdp/consumers/__snapshots__/cdp-cyclotron-plugins-worker.test.ts.snap diff --git a/plugin-server/src/cdp/consumers/__snapshots__/cdp-cyclotron-plugins-worker.test.ts.snap b/plugin-server/src/cdp/consumers/__snapshots__/cdp-cyclotron-plugins-worker.test.ts.snap new file mode 100644 index 0000000000000..d509f563624b2 --- /dev/null +++ b/plugin-server/src/cdp/consumers/__snapshots__/cdp-cyclotron-plugins-worker.test.ts.snap @@ -0,0 +1,45 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`CdpCyclotronWorkerPlugins onEvent should handle and collect errors 3`] = ` +[ + { + "key": "", + "topic": "clickhouse_app_metrics2_test", + "value": { + "app_source": "hog_function", + "app_source_id": "", + "count": 1, + "metric_kind": "failure", + "metric_name": "failed", + "team_id": 2, + "timestamp": "2025-01-01 00:00:00.000", + }, + }, + { + "key": "", + "topic": "log_entries_test", + "value": { + "instance_id": "", + "level": "debug", + "log_source": "hog_function", + "log_source_id": "", + "message": "Executing plugin intercom", + "team_id": 2, + "timestamp": "2025-01-01 00:00:00.000", + }, + }, + { + "key": "", + "topic": "log_entries_test", + "value": { + "instance_id": "", + "level": "error", + "log_source": "hog_function", + "log_source_id": "", + "message": "Plugin intercom execution failed", + "team_id": 2, + "timestamp": "2025-01-01 00:00:00.001", + }, + }, +] +`; diff --git a/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.ts b/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.ts index 82c22c9da616c..14f39d509181b 100644 --- a/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.ts +++ b/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.ts @@ -26,6 +26,7 @@ export class CdpCyclotronWorkerPlugins extends CdpCyclotronWorker { await this.processInvocationResults(results) await this.updateJobs(results) + await this.produceQueuedMessages() return results } diff --git a/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.test.ts b/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.test.ts index 05bf5cadb4b4b..1a2e040c7a6cd 100644 --- a/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.test.ts +++ b/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.test.ts @@ -5,6 +5,7 @@ import { createInvocation, insertHogFunction as _insertHogFunction, } from '~/tests/cdp/fixtures' +import { getProducedKafkaMessages } from '~/tests/helpers/mocks/producer.mock' import { forSnapshot } from '~/tests/helpers/snapshots' import { getFirstTeam, resetTestDatabase } from '~/tests/helpers/sql' @@ -55,11 +56,13 @@ describe('CdpCyclotronWorkerPlugins', () => { hub = await createHub() team = await getFirstTeam(hub) - processor = new CdpCyclotronWorkerPlugins(hub) await processor.start() + jest.spyOn(processor['cyclotronWorker']!, 'updateJob').mockImplementation(() => {}) + jest.spyOn(processor['cyclotronWorker']!, 'releaseJob').mockImplementation(() => Promise.resolve()) + mockFetch.mockClear() const fixedTime = DateTime.fromObject({ year: 2025, month: 1, day: 1 }, { zone: 'UTC' }) @@ -110,18 +113,14 @@ describe('CdpCyclotronWorkerPlugins', () => { it('should setup a plugin on first call', async () => { jest.spyOn(PLUGINS_BY_ID['intercom'] as any, 'setupPlugin') - const results = [] - - results.push(processor.executePluginInvocation(createInvocation(fn, globals))) - results.push(processor.executePluginInvocation(createInvocation(fn, globals))) - results.push(processor.executePluginInvocation(createInvocation(fn, globals))) - - expect(await Promise.all(results)).toMatchObject([ - { finished: true }, - { finished: true }, - { finished: true }, + const results = processor.processInvocations([ + createInvocation(fn, globals), + createInvocation(fn, globals), + createInvocation(fn, globals), ]) + expect(await results).toMatchObject([{ finished: true }, { finished: true }, { finished: true }]) + expect(PLUGINS_BY_ID['intercom'].setupPlugin).toHaveBeenCalledTimes(1) expect(jest.mocked(PLUGINS_BY_ID['intercom'].setupPlugin!).mock.calls[0][0]).toMatchInlineSnapshot(` { @@ -159,7 +158,7 @@ describe('CdpCyclotronWorkerPlugins', () => { json: () => Promise.resolve({ total_count: 1 }), }) - await processor.executePluginInvocation(invocation) + await processor.processInvocations([invocation]) expect(PLUGINS_BY_ID['intercom'].onEvent).toHaveBeenCalledTimes(1) expect(forSnapshot(jest.mocked(PLUGINS_BY_ID['intercom'].onEvent!).mock.calls[0][0])) @@ -214,6 +213,15 @@ describe('CdpCyclotronWorkerPlugins', () => { }, ] `) + + expect(forSnapshot(jest.mocked(processor['cyclotronWorker']!.updateJob).mock.calls)).toMatchInlineSnapshot(` + [ + [ + "", + "completed", + ], + ] + `) }) it('should handle and collect errors', async () => { @@ -227,25 +235,23 @@ describe('CdpCyclotronWorkerPlugins', () => { mockFetch.mockRejectedValue(new Error('Test error')) - const res = await processor.executePluginInvocation(invocation) + const res = await processor.processInvocations([invocation]) expect(PLUGINS_BY_ID['intercom'].onEvent).toHaveBeenCalledTimes(1) - expect(res.error).toBeInstanceOf(Error) - expect(forSnapshot(res.logs)).toMatchInlineSnapshot(` + expect(res[0].error).toBeInstanceOf(Error) + expect(forSnapshot(res[0].logs)).toMatchInlineSnapshot(`[]`) + + expect(forSnapshot(jest.mocked(processor['cyclotronWorker']!.updateJob).mock.calls)).toMatchInlineSnapshot(` [ - { - "level": "debug", - "message": "Executing plugin intercom", - "timestamp": "2025-01-01T01:00:00.000+01:00", - }, - { - "level": "error", - "message": "Plugin intercom execution failed", - "timestamp": "2025-01-01T01:00:00.000+01:00", - }, + [ + "", + "failed", + ], ] `) + + expect(forSnapshot(getProducedKafkaMessages())).toMatchSnapshot() }) }) }) From 20a304c15ed1071b2f4e909298a8f489b8aed293 Mon Sep 17 00:00:00 2001 From: Ben White Date: Mon, 27 Jan 2025 10:28:21 +0100 Subject: [PATCH 013/163] fixed frontend --- .../hogfunctions/HogFunctionConfiguration.tsx | 22 +++++++++---- .../filters/HogFunctionFilters.tsx | 4 ++- .../_internal/template_legacy_plugin.py | 31 +++++++++++++++++++ posthog/models/hog_functions/hog_function.py | 4 +++ 4 files changed, 54 insertions(+), 7 deletions(-) create mode 100644 posthog/cdp/templates/_internal/template_legacy_plugin.py diff --git a/frontend/src/scenes/pipeline/hogfunctions/HogFunctionConfiguration.tsx b/frontend/src/scenes/pipeline/hogfunctions/HogFunctionConfiguration.tsx index 6f6a9962a1ed4..86dce22800f2b 100644 --- a/frontend/src/scenes/pipeline/hogfunctions/HogFunctionConfiguration.tsx +++ b/frontend/src/scenes/pipeline/hogfunctions/HogFunctionConfiguration.tsx @@ -100,6 +100,8 @@ export function HogFunctionConfiguration({ return } + const isLegacyPlugin = hogFunction?.template?.id?.startsWith('plugin-') + const headerButtons = ( <> {!templateId && ( @@ -107,9 +109,11 @@ export function HogFunctionConfiguration({ - duplicate()}> - Duplicate - + {!isLegacyPlugin && ( + duplicate()}> + Duplicate + + )} deleteHogFunction()}> Delete @@ -174,11 +178,12 @@ export function HogFunctionConfiguration({ ) const canEditSource = displayOptions.canEditSource ?? - ['destination', 'email', 'site_destination', 'site_app', 'transformation'].includes(type) + (['destination', 'email', 'site_destination', 'site_app', 'transformation'].includes(type) && !isLegacyPlugin) const showPersonsCount = displayOptions.showPersonsCount ?? ['broadcast'].includes(type) const showTesting = displayOptions.showTesting ?? - ['destination', 'internal_destination', 'transformation', 'broadcast', 'email'].includes(type) + (['destination', 'internal_destination', 'transformation', 'broadcast', 'email'].includes(type) && + !isLegacyPlugin) return (
@@ -259,7 +264,12 @@ export function HogFunctionConfiguration({ - {hogFunction?.template && !hogFunction.template.id.startsWith('template-blank-') ? ( + {isLegacyPlugin ? ( + + This destination is one of our legacy plugins. It will be deprecated and you + should instead upgrade + + ) : hogFunction?.template && !hogFunction.template.id.startsWith('template-blank-') ? ( } - const showMasking = type === 'destination' + const isLegacyPlugin = configuration?.template?.id?.startsWith('plugin-') + + const showMasking = type === 'destination' && !isLegacyPlugin const showDropEvents = type === 'transformation' return ( diff --git a/posthog/cdp/templates/_internal/template_legacy_plugin.py b/posthog/cdp/templates/_internal/template_legacy_plugin.py new file mode 100644 index 0000000000000..cef66400e47a7 --- /dev/null +++ b/posthog/cdp/templates/_internal/template_legacy_plugin.py @@ -0,0 +1,31 @@ +from posthog.cdp.templates.hog_function_template import HogFunctionTemplate + +legacy_plugin_template: HogFunctionTemplate = HogFunctionTemplate( + status="alpha", + type="destination", + id="template-legacy-plugin", + name="Legacy plugin", + description="Legacy plugins", + icon_url="/static/hedgehog/builder-hog-01.png", + category=["Custom", "Analytics"], + hog=""" +print('not used') +""".strip(), + inputs_schema=[], +) + + +def create_legacy_plugin_template(template_id: str) -> HogFunctionTemplate: + return HogFunctionTemplate( + status="alpha", + type="destination", + id=f"{template_id}", + name=f"Legacy plugin {template_id}", + description="Legacy plugins", + icon_url="/static/hedgehog/builder-hog-01.png", + category=["Custom"], + hog=""" + print('not used') + """.strip(), + inputs_schema=[], + ) diff --git a/posthog/models/hog_functions/hog_function.py b/posthog/models/hog_functions/hog_function.py index a715f10b86b7b..244f887d3724c 100644 --- a/posthog/models/hog_functions/hog_function.py +++ b/posthog/models/hog_functions/hog_function.py @@ -6,6 +6,7 @@ from django.dispatch.dispatcher import receiver import structlog +from posthog.cdp.templates._internal.template_legacy_plugin import create_legacy_plugin_template from posthog.cdp.templates.hog_function_template import HogFunctionTemplate from posthog.helpers.encrypted_fields import EncryptedJSONStringField from posthog.models.action.action import Action @@ -96,6 +97,9 @@ class Meta: def template(self) -> Optional[HogFunctionTemplate]: from posthog.cdp.templates import ALL_HOG_FUNCTION_TEMPLATES_BY_ID + if self.template_id and self.template_id.startswith("plugin-"): + return create_legacy_plugin_template(self.template_id) + return ALL_HOG_FUNCTION_TEMPLATES_BY_ID.get(self.template_id, None) @property From 693bf59d1eb76adb823fc1485160b00af8432673 Mon Sep 17 00:00:00 2001 From: Ben White Date: Mon, 27 Jan 2025 10:31:05 +0100 Subject: [PATCH 014/163] Fixes --- plugin-server/src/capabilities.ts | 6 ++++++ plugin-server/src/main/pluginsServer.ts | 8 ++++++++ plugin-server/src/types.ts | 2 ++ 3 files changed, 16 insertions(+) diff --git a/plugin-server/src/capabilities.ts b/plugin-server/src/capabilities.ts index bb7f34eb39163..55aa5ed25cc4c 100644 --- a/plugin-server/src/capabilities.ts +++ b/plugin-server/src/capabilities.ts @@ -26,6 +26,7 @@ export function getPluginServerCapabilities(config: PluginsServerConfig): Plugin cdpProcessedEvents: true, cdpInternalEvents: true, cdpCyclotronWorker: true, + cdpCyclotronWorkerPlugins: true, syncInlinePlugins: true, ...sharedCapabilities, } @@ -139,6 +140,11 @@ export function getPluginServerCapabilities(config: PluginsServerConfig): Plugin cdpCyclotronWorker: true, ...sharedCapabilities, } + case PluginServerMode.cdp_cyclotron_worker_plugins: + return { + cdpCyclotronWorkerPlugins: true, + ...sharedCapabilities, + } // This is only for functional tests, which time out if all capabilities are used // ideally we'd run just the specific capability needed per test, but that's not easy to do atm case PluginServerMode.functional_tests: diff --git a/plugin-server/src/main/pluginsServer.ts b/plugin-server/src/main/pluginsServer.ts index d6c8251b294c5..274e3231e35f3 100644 --- a/plugin-server/src/main/pluginsServer.ts +++ b/plugin-server/src/main/pluginsServer.ts @@ -11,6 +11,7 @@ import v8Profiler from 'v8-profiler-next' import { getPluginServerCapabilities } from '../capabilities' import { CdpApi } from '../cdp/cdp-api' +import { CdpCyclotronWorkerPlugins } from '../cdp/consumers/cdp-cyclotron-plugins-worker.consumer' import { CdpCyclotronWorker, CdpCyclotronWorkerFetch } from '../cdp/consumers/cdp-cyclotron-worker.consumer' import { CdpInternalEventsConsumer } from '../cdp/consumers/cdp-internal-event.consumer' import { CdpProcessedEventsConsumer } from '../cdp/consumers/cdp-processed-events.consumer' @@ -547,6 +548,13 @@ export async function startPluginsServer( } } + if (capabilities.cdpCyclotronWorkerPlugins) { + const hub = await setupHub() + const worker = new CdpCyclotronWorkerPlugins(hub) + await worker.start() + services.push(worker.service) + } + if (capabilities.http) { const app = setupCommonRoutes(services) diff --git a/plugin-server/src/types.ts b/plugin-server/src/types.ts index 9cf3d1bdf7f92..5682796f0817c 100644 --- a/plugin-server/src/types.ts +++ b/plugin-server/src/types.ts @@ -90,6 +90,7 @@ export enum PluginServerMode { cdp_processed_events = 'cdp-processed-events', cdp_internal_events = 'cdp-internal-events', cdp_cyclotron_worker = 'cdp-cyclotron-worker', + cdp_cyclotron_worker_plugins = 'cdp-cyclotron-worker-plugins', functional_tests = 'functional-tests', } @@ -386,6 +387,7 @@ export interface PluginServerCapabilities { cdpProcessedEvents?: boolean cdpInternalEvents?: boolean cdpCyclotronWorker?: boolean + cdpCyclotronWorkerPlugins?: boolean appManagementSingleton?: boolean preflightSchedules?: boolean // Used for instance health checks on hobby deploy, not useful on cloud http?: boolean From 4e8267dc38e8cd44485cf6af0a187d93a90eb430 Mon Sep 17 00:00:00 2001 From: Ben White Date: Mon, 27 Jan 2025 10:38:43 +0100 Subject: [PATCH 015/163] Fix up --- .../src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.ts | 2 +- .../src/cdp/consumers/cdp-cyclotron-worker.consumer.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.ts b/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.ts index 14f39d509181b..a416bad2380b9 100644 --- a/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.ts +++ b/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.ts @@ -16,7 +16,7 @@ type PluginState = { */ export class CdpCyclotronWorkerPlugins extends CdpCyclotronWorker { protected name = 'CdpCyclotronWorkerPlugins' - protected queue = 'plugins' as const + protected queue = 'plugin' as const protected hogTypes: HogFunctionTypeType[] = ['destination'] private pluginState: Record = {} diff --git a/plugin-server/src/cdp/consumers/cdp-cyclotron-worker.consumer.ts b/plugin-server/src/cdp/consumers/cdp-cyclotron-worker.consumer.ts index 1d96123162969..185d186a9f2b8 100644 --- a/plugin-server/src/cdp/consumers/cdp-cyclotron-worker.consumer.ts +++ b/plugin-server/src/cdp/consumers/cdp-cyclotron-worker.consumer.ts @@ -13,7 +13,7 @@ export class CdpCyclotronWorker extends CdpConsumerBase { protected name = 'CdpCyclotronWorker' private cyclotronWorker?: CyclotronWorker private runningWorker: Promise | undefined - protected queue: 'hog' | 'fetch' | 'plugins' = 'hog' + protected queue: 'hog' | 'fetch' | 'plugin' = 'hog' protected hogTypes: HogFunctionTypeType[] = ['destination', 'internal_destination'] public async processInvocations(invocations: HogFunctionInvocation[]): Promise { From 8d2f00b918a76554c0bcbe3d5f29f8dd29a547bf Mon Sep 17 00:00:00 2001 From: Ben White Date: Mon, 27 Jan 2025 10:39:58 +0100 Subject: [PATCH 016/163] Fix --- .../src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.ts b/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.ts index a416bad2380b9..7c13606539c7d 100644 --- a/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.ts +++ b/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.ts @@ -90,7 +90,7 @@ export class CdpCyclotronWorkerPlugins extends CdpCyclotronWorker { result.logs.push({ level: 'error', timestamp: DateTime.now(), - message: `Plugin ${pluginId} setup failed`, + message: `Plugin ${pluginId} setup failed: ${e.message}`, }) return result } From e3a1ca9a9f1f7c3e640b325ccc1aba0d06b6f34b Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 27 Jan 2025 09:44:31 +0000 Subject: [PATCH 017/163] Update query snapshots --- .../models/test/__snapshots__/test_cohort.ambr | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ee/clickhouse/models/test/__snapshots__/test_cohort.ambr b/ee/clickhouse/models/test/__snapshots__/test_cohort.ambr index 250798eb5711c..bc44a6b098e26 100644 --- a/ee/clickhouse/models/test/__snapshots__/test_cohort.ambr +++ b/ee/clickhouse/models/test/__snapshots__/test_cohort.ambr @@ -233,7 +233,7 @@ WHERE equals(person_distinct_id_overrides.team_id, 99999) 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, 99999), greaterOrEquals(timestamp, toDateTime64('2023-01-23 00:00:00.000000', 6, 'UTC')), lessOrEquals(timestamp, toDateTime64('2025-01-23 23:59:59.999999', 6, 'UTC')), equals(e.event, '$pageview'))) + WHERE and(equals(e.team_id, 99999), greaterOrEquals(timestamp, toDateTime64('2023-01-27 00:00:00.000000', 6, 'UTC')), lessOrEquals(timestamp, toDateTime64('2025-01-27 23:59:59.999999', 6, 'UTC')), equals(e.event, '$pageview'))) GROUP BY actor_id) AS source ORDER BY source.id ASC LIMIT 100 SETTINGS optimize_aggregation_in_order=1, @@ -374,7 +374,7 @@ actor_id AS id FROM (SELECT min(toTimeZone(e.timestamp, 'UTC')) AS min_timestamp, - minIf(toTimeZone(e.timestamp, 'UTC'), greaterOrEquals(toTimeZone(e.timestamp, 'UTC'), toDateTime64('2025-01-08 00:00:00.000000', 6, 'UTC'))) AS min_timestamp_with_condition, + minIf(toTimeZone(e.timestamp, 'UTC'), greaterOrEquals(toTimeZone(e.timestamp, 'UTC'), toDateTime64('2025-01-12 00:00:00.000000', 6, 'UTC'))) AS min_timestamp_with_condition, if(not(empty(e__override.distinct_id)), e__override.person_id, e.person_id) AS actor_id, argMin(e.uuid, toTimeZone(e.timestamp, 'UTC')) AS uuid, argMin(e.distinct_id, toTimeZone(e.timestamp, 'UTC')) AS distinct_id @@ -386,7 +386,7 @@ WHERE equals(person_distinct_id_overrides.team_id, 99999) 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, 99999), lessOrEquals(toTimeZone(e.timestamp, 'UTC'), toDateTime64('2025-01-23 23:59:59.999999', 6, 'UTC')), equals(e.event, 'signup')) + WHERE and(equals(e.team_id, 99999), lessOrEquals(toTimeZone(e.timestamp, 'UTC'), toDateTime64('2025-01-27 23:59:59.999999', 6, 'UTC')), equals(e.event, 'signup')) GROUP BY if(not(empty(e__override.distinct_id)), e__override.person_id, e.person_id) HAVING ifNull(equals(min_timestamp, min_timestamp_with_condition), isNull(min_timestamp) and isNull(min_timestamp_with_condition))) @@ -474,7 +474,7 @@ WHERE equals(person_distinct_id_overrides.team_id, 99999) 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, 99999), greaterOrEquals(timestamp, toDateTime64('2023-01-23 00:00:00.000000', 6, 'UTC')), lessOrEquals(timestamp, toDateTime64('2025-01-23 23:59:59.999999', 6, 'UTC')), equals(e.event, '$pageview'))) + WHERE and(equals(e.team_id, 99999), greaterOrEquals(timestamp, toDateTime64('2023-01-27 00:00:00.000000', 6, 'UTC')), lessOrEquals(timestamp, toDateTime64('2025-01-27 23:59:59.999999', 6, 'UTC')), equals(e.event, '$pageview'))) GROUP BY actor_id) AS source ORDER BY source.id ASC LIMIT 100 SETTINGS optimize_aggregation_in_order=1, @@ -488,7 +488,7 @@ actor_id AS id FROM (SELECT min(toTimeZone(e.timestamp, 'UTC')) AS min_timestamp, - minIf(toTimeZone(e.timestamp, 'UTC'), greaterOrEquals(toTimeZone(e.timestamp, 'UTC'), toDateTime64('2025-01-08 00:00:00.000000', 6, 'UTC'))) AS min_timestamp_with_condition, + minIf(toTimeZone(e.timestamp, 'UTC'), greaterOrEquals(toTimeZone(e.timestamp, 'UTC'), toDateTime64('2025-01-12 00:00:00.000000', 6, 'UTC'))) AS min_timestamp_with_condition, if(not(empty(e__override.distinct_id)), e__override.person_id, e.person_id) AS actor_id, argMin(e.uuid, toTimeZone(e.timestamp, 'UTC')) AS uuid, argMin(e.distinct_id, toTimeZone(e.timestamp, 'UTC')) AS distinct_id @@ -500,7 +500,7 @@ WHERE equals(person_distinct_id_overrides.team_id, 99999) 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, 99999), lessOrEquals(toTimeZone(e.timestamp, 'UTC'), toDateTime64('2025-01-23 23:59:59.999999', 6, 'UTC')), equals(e.event, 'signup')) + WHERE and(equals(e.team_id, 99999), lessOrEquals(toTimeZone(e.timestamp, 'UTC'), toDateTime64('2025-01-27 23:59:59.999999', 6, 'UTC')), equals(e.event, 'signup')) GROUP BY if(not(empty(e__override.distinct_id)), e__override.person_id, e.person_id) HAVING ifNull(equals(min_timestamp, min_timestamp_with_condition), isNull(min_timestamp) and isNull(min_timestamp_with_condition))) From 9694f077aac475124e20763a51966e8cae248c57 Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 27 Jan 2025 09:44:54 +0000 Subject: [PATCH 018/163] Update UI snapshots for `chromium` (1) --- ...uccess--second-recording-in-list--dark.png | Bin 118489 -> 119098 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/frontend/__snapshots__/replay-player-success--second-recording-in-list--dark.png b/frontend/__snapshots__/replay-player-success--second-recording-in-list--dark.png index e659453aae2dc6dc59adce6f94b5b795efb3effb..369a08aada28d06319c8499cc4dfd0e36d48115d 100644 GIT binary patch literal 119098 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 literal 118489 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 From e33dd64f977fa30cc7a4e0720e1d06b28e5206d5 Mon Sep 17 00:00:00 2001 From: Ben White Date: Mon, 27 Jan 2025 10:47:20 +0100 Subject: [PATCH 019/163] Fix storage --- .../cdp-cyclotron-plugins-worker.test.ts.snap | 2 +- .../cdp-cyclotron-plugins-worker.consumer.ts | 23 +++++++++++++++---- .../cdp-cyclotron-plugins-worker.test.ts | 6 ++++- 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/plugin-server/src/cdp/consumers/__snapshots__/cdp-cyclotron-plugins-worker.test.ts.snap b/plugin-server/src/cdp/consumers/__snapshots__/cdp-cyclotron-plugins-worker.test.ts.snap index d509f563624b2..e8924a331c548 100644 --- a/plugin-server/src/cdp/consumers/__snapshots__/cdp-cyclotron-plugins-worker.test.ts.snap +++ b/plugin-server/src/cdp/consumers/__snapshots__/cdp-cyclotron-plugins-worker.test.ts.snap @@ -36,7 +36,7 @@ exports[`CdpCyclotronWorkerPlugins onEvent should handle and collect errors 3`] "level": "error", "log_source": "hog_function", "log_source_id": "", - "message": "Plugin intercom execution failed", + "message": "Plugin errored: Service is down, retry later", "team_id": 2, "timestamp": "2025-01-01 00:00:00.001", }, diff --git a/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.ts b/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.ts index 7c13606539c7d..b57d9ba535864 100644 --- a/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.ts +++ b/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.ts @@ -1,4 +1,4 @@ -import { Meta, ProcessedPluginEvent, RetryError } from '@posthog/plugin-scaffold' +import { Meta, ProcessedPluginEvent, RetryError, StorageExtension } from '@posthog/plugin-scaffold' import { DateTime } from 'luxon' import { PLUGINS_BY_ID } from '../legacy-plugins' @@ -11,6 +11,21 @@ type PluginState = { meta: Meta } +const createStorage = (): StorageExtension => { + const storage: Record = {} + return { + get: (key: string) => Promise.resolve(storage[key]), + set: (key: string, value: any) => { + storage[key] = value + return Promise.resolve() + }, + del: (key: string) => { + delete storage[key] + return Promise.resolve() + }, + } +} + /** * NOTE: This is a consumer to take care of legacy plugins. */ @@ -70,7 +85,7 @@ export class CdpCyclotronWorkerPlugins extends CdpCyclotronWorker { jobs: {}, metrics: {}, cache: {} as any, - storage: {} as any, // NOTE: Figuree out what to do about storage as that is used... + storage: createStorage(), geoip: {} as any, utils: {} as any, } @@ -122,7 +137,7 @@ export class CdpCyclotronWorkerPlugins extends CdpCyclotronWorker { result.logs.push({ level: 'debug', timestamp: DateTime.now(), - message: `Plugin ${pluginId} execution successful`, + message: `Execution successful`, }) } catch (e) { if (e instanceof RetryError) { @@ -132,7 +147,7 @@ export class CdpCyclotronWorkerPlugins extends CdpCyclotronWorker { result.logs.push({ level: 'error', timestamp: DateTime.now(), - message: `Plugin ${pluginId} execution failed`, + message: `Plugin errored: ${e.message}`, }) } diff --git a/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.test.ts b/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.test.ts index 1a2e040c7a6cd..43ff26e5882f7 100644 --- a/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.test.ts +++ b/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.test.ts @@ -136,7 +136,11 @@ describe('CdpCyclotronWorkerPlugins', () => { "global": {}, "jobs": {}, "metrics": {}, - "storage": {}, + "storage": { + "del": [Function], + "get": [Function], + "set": [Function], + }, "utils": {}, } `) From b7781cfdaac3b13016d9f8cff8ee56d30e1e39d0 Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 27 Jan 2025 09:52:33 +0000 Subject: [PATCH 020/163] Update query snapshots --- posthog/api/test/__snapshots__/test_cohort.ambr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/posthog/api/test/__snapshots__/test_cohort.ambr b/posthog/api/test/__snapshots__/test_cohort.ambr index 26c2244e971da..8a6006ca4cbaa 100644 --- a/posthog/api/test/__snapshots__/test_cohort.ambr +++ b/posthog/api/test/__snapshots__/test_cohort.ambr @@ -174,7 +174,7 @@ WHERE equals(person_distinct_id_overrides.team_id, 99999) 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, 99999), greaterOrEquals(timestamp, toDateTime64('2025-01-22 00:00:00.000000', 6, 'UTC')), lessOrEquals(timestamp, toDateTime64('2025-01-23 23:59:59.999999', 6, 'UTC')), equals(e.event, '$pageview'))) + WHERE and(equals(e.team_id, 99999), greaterOrEquals(timestamp, toDateTime64('2025-01-26 00:00:00.000000', 6, 'UTC')), lessOrEquals(timestamp, toDateTime64('2025-01-27 23:59:59.999999', 6, 'UTC')), equals(e.event, '$pageview'))) GROUP BY actor_id) AS source ORDER BY source.id ASC LIMIT 100 SETTINGS optimize_aggregation_in_order=1, From 9001d928b755d9b4ef4d49bc2008fe1dc3f2ff40 Mon Sep 17 00:00:00 2001 From: Ben White Date: Mon, 27 Jan 2025 11:53:31 +0100 Subject: [PATCH 021/163] Fixes --- .../cdp-cyclotron-plugins-worker.consumer.ts | 44 ++- .../cdp-cyclotron-plugins-worker.test.ts | 39 ++- .../cdp/legacy-plugins/customerio/index.ts | 278 ++++++++++++++++++ .../cdp/legacy-plugins/customerio/plugin.json | 60 ++++ .../src/cdp/legacy-plugins/intercom/index.ts | 197 +++++++++++++ .../cdp/legacy-plugins/intercom/plugin.json | 42 +++ plugin-server/src/cdp/legacy-plugins/types.ts | 22 +- 7 files changed, 659 insertions(+), 23 deletions(-) create mode 100644 plugin-server/src/cdp/legacy-plugins/customerio/index.ts create mode 100644 plugin-server/src/cdp/legacy-plugins/customerio/plugin.json create mode 100644 plugin-server/src/cdp/legacy-plugins/intercom/index.ts create mode 100644 plugin-server/src/cdp/legacy-plugins/intercom/plugin.json diff --git a/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.ts b/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.ts index b57d9ba535864..580212585f761 100644 --- a/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.ts +++ b/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.ts @@ -1,7 +1,11 @@ import { Meta, ProcessedPluginEvent, RetryError, StorageExtension } from '@posthog/plugin-scaffold' import { DateTime } from 'luxon' +import { Response, trackedFetch } from '~/src/utils/fetch' +import { status } from '~/src/utils/status' + import { PLUGINS_BY_ID } from '../legacy-plugins' +import { LegacyPluginLogger, LegacyPluginMeta } from '../legacy-plugins/types' import { HogFunctionInvocation, HogFunctionInvocationResult, HogFunctionTypeType } from '../types' import { CdpCyclotronWorker } from './cdp-cyclotron-worker.consumer' @@ -46,6 +50,10 @@ export class CdpCyclotronWorkerPlugins extends CdpCyclotronWorker { return results } + public async fetch(...args: Parameters): Promise { + return trackedFetch(...args) + } + public async executePluginInvocation(invocation: HogFunctionInvocation): Promise { const result: HogFunctionInvocationResult = { invocation, @@ -75,10 +83,25 @@ export class CdpCyclotronWorkerPlugins extends CdpCyclotronWorker { return result } + const addLog = (level: 'debug' | 'warn' | 'error' | 'info', ...args: any[]) => { + result.logs.push({ + level, + timestamp: DateTime.now(), + message: args.join(' '), + }) + } + + const logger: LegacyPluginLogger = { + debug: (...args: any[]) => addLog('debug', ...args), + warn: (...args: any[]) => addLog('warn', ...args), + log: (...args: any[]) => addLog('info', ...args), + error: (...args: any[]) => addLog('error', ...args), + } + let state = this.pluginState[pluginId] if (!state) { - const meta: Meta = { + const meta: LegacyPluginMeta = { config: invocation.globals.inputs, attachments: {}, global: {}, @@ -88,6 +111,8 @@ export class CdpCyclotronWorkerPlugins extends CdpCyclotronWorker { storage: createStorage(), geoip: {} as any, utils: {} as any, + fetch: (...args) => this.fetch(...args), + logger: logger, } state = this.pluginState[pluginId] = { @@ -133,7 +158,15 @@ export class CdpCyclotronWorkerPlugins extends CdpCyclotronWorker { } try { - await plugin.onEvent?.(event, state.meta) + status.info('⚡️', 'Executing plugin', { + pluginId, + invocationId: invocation.id, + }) + await plugin.onEvent?.(event, { + ...state.meta, + logger, + fetch: this.fetch, + }) result.logs.push({ level: 'debug', timestamp: DateTime.now(), @@ -143,6 +176,13 @@ export class CdpCyclotronWorkerPlugins extends CdpCyclotronWorker { if (e instanceof RetryError) { // NOTE: Schedule as a retry to cyclotron? } + + status.error('💩', 'Plugin errored', { + error: e, + pluginId, + invocationId: invocation.id, + }) + result.error = e result.logs.push({ level: 'error', diff --git a/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.test.ts b/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.test.ts index 43ff26e5882f7..e1acefe03dfce 100644 --- a/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.test.ts +++ b/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.test.ts @@ -15,19 +15,19 @@ import { PLUGINS_BY_ID } from '../legacy-plugins' import { HogFunctionInvocationGlobalsWithInputs, HogFunctionType } from '../types' import { CdpCyclotronWorkerPlugins } from './cdp-cyclotron-plugins-worker.consumer' -jest.mock('../../../src/utils/fetch', () => { - return { - trackedFetch: jest.fn(() => - Promise.resolve({ - status: 200, - text: () => Promise.resolve(JSON.stringify({ success: true })), - json: () => Promise.resolve({ success: true }), - }) - ), - } -}) - -const mockFetch: jest.Mock = require('../../../src/utils/fetch').trackedFetch +// jest.mock('../../../src/utils/fetch', () => { +// return { +// trackedFetch: jest.fn(() => +// Promise.resolve({ +// status: 200, +// text: () => Promise.resolve(JSON.stringify({ success: true })), +// json: () => Promise.resolve({ success: true }), +// }) +// ), +// } +// }) + +// const mockFetch: jest.Mock = require('../../../src/utils/fetch').trackedFetch jest.setTimeout(1000) @@ -40,7 +40,7 @@ describe('CdpCyclotronWorkerPlugins', () => { let team: Team let fn: HogFunctionType let globals: HogFunctionInvocationGlobalsWithInputs - + let mockFetch: jest.Mock const insertHogFunction = async (hogFunction: Partial) => { const item = await _insertHogFunction(hub.postgres, team.id, { ...hogFunction, @@ -60,11 +60,11 @@ describe('CdpCyclotronWorkerPlugins', () => { await processor.start() + processor.fetch = mockFetch = jest.fn(() => Promise.resolve({} as any)) + jest.spyOn(processor['cyclotronWorker']!, 'updateJob').mockImplementation(() => {}) jest.spyOn(processor['cyclotronWorker']!, 'releaseJob').mockImplementation(() => Promise.resolve()) - mockFetch.mockClear() - const fixedTime = DateTime.fromObject({ year: 2025, month: 1, day: 1 }, { zone: 'UTC' }) jest.spyOn(Date, 'now').mockReturnValue(fixedTime.toMillis()) @@ -132,9 +132,16 @@ describe('CdpCyclotronWorkerPlugins', () => { "triggeringEvents": "$identify,mycustomevent", "useEuropeanDataStorage": "No", }, + "fetch": [Function], "geoip": {}, "global": {}, "jobs": {}, + "logger": { + "debug": [Function], + "error": [Function], + "log": [Function], + "warn": [Function], + }, "metrics": {}, "storage": { "del": [Function], diff --git a/plugin-server/src/cdp/legacy-plugins/customerio/index.ts b/plugin-server/src/cdp/legacy-plugins/customerio/index.ts new file mode 100644 index 0000000000000..ed32ef0ea730c --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/customerio/index.ts @@ -0,0 +1,278 @@ +import { PluginInput, ProcessedPluginEvent } from '@posthog/plugin-scaffold' +import { RetryError } from '@posthog/plugin-scaffold' + +import { Response } from '~/src/utils/fetch' + +import { LegacyPlugin, LegacyPluginMeta } from '../types' + +const DEFAULT_HOST = 'track.customer.io' +const DEFAULT_SEND_EVENTS_FROM_ANONYMOUS_USERS = 'Send all events' + +interface CustomerIoPluginInput extends PluginInput { + config: { + customerioSiteId: string + customerioToken: string + host?: 'track.customer.io' | 'track-eu.customer.io' + identifyByEmail?: 'Yes' | 'No' + sendEventsFromAnonymousUsers?: + | 'Send all events' + | 'Only send events from users with emails' + | 'Only send events from users that have been identified' + eventsToSend?: string + } + global: { + authorizationHeader: string + eventNames: string[] + eventsConfig: EventsConfig + identifyByEmail: boolean + } +} + +type CustomerIoMeta = LegacyPluginMeta +enum EventsConfig { + SEND_ALL = '1', + SEND_EMAILS = '2', + SEND_IDENTIFIED = '3', +} + +const EVENTS_CONFIG_MAP = { + 'Send all events': EventsConfig.SEND_ALL, + 'Only send events from users with emails': EventsConfig.SEND_EMAILS, + 'Only send events from users that have been identified': EventsConfig.SEND_IDENTIFIED, +} + +interface Customer { + status: Set<'seen' | 'identified' | 'with_email'> + existsAlready: boolean + email: string | null +} + +async function callCustomerIoApi( + meta: CustomerIoMeta, + method: NonNullable, + host: string, + path: string, + authorization: string, + body?: any +) { + const headers: Record = { 'User-Agent': 'PostHog Customer.io App', Authorization: authorization } + let bodySerialized: string | undefined + if (body != null) { + headers['Content-Type'] = 'application/json' + bodySerialized = JSON.stringify(body) + } + let response: Response + try { + response = await meta.fetch(`https://${host}${path}`, { method, headers, body: bodySerialized }) + } catch (e) { + throw new RetryError(`Cannot reach the Customer.io API. ${e}`) + } + const responseStatusClass = Math.floor(response.status / 100) + if (response.status === 401 || response.status === 403) { + const responseData = await response.json() + throw new Error( + `Customer.io Site ID or API Key invalid! Response ${response.status}: ${JSON.stringify(responseData)}` + ) + } + if (response.status === 408 || response.status === 429 || responseStatusClass === 5) { + const responseData = await response.json() + throw new RetryError( + `Received a potentially intermittent error from the Customer.io API. Response ${ + response.status + }: ${JSON.stringify(responseData)}` + ) + } + if (responseStatusClass !== 2) { + const responseData = await response.json() + throw new Error( + `Received an unexpected error from the Customer.io API. Response ${response.status}: ${JSON.stringify( + responseData + )}` + ) + } + return response +} + +export const setupPlugin = async (meta: CustomerIoMeta) => { + const { config, global, storage, logger } = meta + const customerioBase64AuthToken = Buffer.from(`${config.customerioSiteId}:${config.customerioToken}`).toString( + 'base64' + ) + global.authorizationHeader = `Basic ${customerioBase64AuthToken}` + global.eventNames = config.eventsToSend + ? (config.eventsToSend as string) + .split(',') + .map((name) => name.trim()) + .filter(Boolean) + : [] + global.eventsConfig = + EVENTS_CONFIG_MAP[config.sendEventsFromAnonymousUsers || DEFAULT_SEND_EVENTS_FROM_ANONYMOUS_USERS] + global.identifyByEmail = config.identifyByEmail === 'Yes' + + const credentialsVerifiedPreviously = await storage.get(global.authorizationHeader, false) + + if (credentialsVerifiedPreviously) { + logger.log('Customer.io credentials verified previously. Completing setupPlugin.') + return + } + + // See https://www.customer.io/docs/api/#operation/getCioAllowlist + await callCustomerIoApi(meta, 'GET', 'api.customer.io', '/v1/api/info/ip_addresses', global.authorizationHeader) + await storage.set(global.authorizationHeader, true) + logger.log('Successfully authenticated with Customer.io. Completing setupPlugin.') +} + +export const onEvent = async (event: ProcessedPluginEvent, meta: CustomerIoMeta) => { + const { global, config, logger } = meta + // KLUDGE: This shouldn't even run if setupPlugin failed. Needs to be fixed at the plugin server level + if (!global.eventNames) { + throw new RetryError('Cannot run exportEvents because setupPlugin failed!') + } + + if (global.eventNames.length !== 0 && !global.eventNames.includes(event.event)) { + return + } + if (event.event === '$create_alias') { + return + } + + const customer: Customer = await syncCustomerMetadata(meta, event) + logger.debug(customer) + logger.debug(shouldCustomerBeTracked(customer, global.eventsConfig)) + if (!shouldCustomerBeTracked(customer, global.eventsConfig)) { + return + } + + await exportSingleEvent( + meta, + event, + customer, + global.authorizationHeader, + config.host || DEFAULT_HOST, + global.identifyByEmail + ) +} + +async function syncCustomerMetadata(meta: CustomerIoMeta, event: ProcessedPluginEvent): Promise { + const { storage, logger } = meta + const customerStatusKey = `customer-status/${event.distinct_id}` + const customerStatusArray = (await storage.get(customerStatusKey, [])) as string[] + const customerStatus = new Set(customerStatusArray) as Customer['status'] + const customerExistsAlready = customerStatus.has('seen') + const email = getEmailFromEvent(event) + + logger.debug(email) + + // Update customer status + customerStatus.add('seen') + if (event.event === '$identify') { + customerStatus.add('identified') + } + if (email) { + customerStatus.add('with_email') + } + + if (customerStatus.size > customerStatusArray.length) { + await storage.set(customerStatusKey, Array.from(customerStatus)) + } + + return { + status: customerStatus, + existsAlready: customerExistsAlready, + email, + } +} + +function shouldCustomerBeTracked(customer: Customer, eventsConfig: EventsConfig): boolean { + switch (eventsConfig) { + case EventsConfig.SEND_ALL: + return true + case EventsConfig.SEND_EMAILS: + return customer.status.has('with_email') + case EventsConfig.SEND_IDENTIFIED: + return customer.status.has('identified') + default: + throw new Error(`Unknown eventsConfig: ${eventsConfig}`) + } +} + +async function exportSingleEvent( + meta: CustomerIoMeta, + event: ProcessedPluginEvent, + customer: Customer, + authorizationHeader: string, + host: string, + identifyByEmail: boolean +) { + // Clean up properties + if (event.properties) { + delete event.properties['$set'] + delete event.properties['$set_once'] + } + + const customerPayload: Record = { + ...(event.$set || {}), + _update: customer.existsAlready, + identifier: event.distinct_id, + } + + if ('created_at' in customerPayload) { + // Timestamp must be in seconds since UNIX epoch. + // See: https://customer.io/docs/journeys/faq-timestamps/. + customerPayload.created_at = Date.parse(customerPayload.created_at) / 1000 + } + + let id = event.distinct_id + + if (customer.email) { + customerPayload.email = customer.email + if (identifyByEmail) { + id = customer.email + } + } + // Create or update customer + // See https://www.customer.io/docs/api/#operation/identify + await callCustomerIoApi(meta, 'PUT', host, `/api/v1/customers/${id}`, authorizationHeader, customerPayload) + + const eventType = event.event === '$pageview' ? 'page' : event.event === '$screen' ? 'screen' : 'event' + const eventTimestamp = (event.timestamp ? new Date(event.timestamp).valueOf() : Date.now()) / 1000 + // Track event + // See https://www.customer.io/docs/api/#operation/track + await callCustomerIoApi(meta, 'POST', host, `/api/v1/customers/${id}/events`, authorizationHeader, { + name: event.event, + type: eventType, + timestamp: eventTimestamp, + data: event.properties || {}, + }) +} + +function isEmail(email: string): boolean { + if (typeof email !== 'string') { + return false + } + const re = + /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/ + return re.test(email.toLowerCase()) +} + +function getEmailFromEvent(event: ProcessedPluginEvent): string | null { + const setAttribute = event.$set + if (typeof setAttribute !== 'object' || !setAttribute['email']) { + return null + } + const emailCandidate = setAttribute['email'] + if (isEmail(emailCandidate)) { + return emailCandidate + } + // Use distinct ID as a last resort + if (isEmail(event.distinct_id)) { + return event.distinct_id + } + return null +} + +export const customerioPlugin: LegacyPlugin = { + id: 'customer-io', + setupPlugin, + onEvent, +} diff --git a/plugin-server/src/cdp/legacy-plugins/customerio/plugin.json b/plugin-server/src/cdp/legacy-plugins/customerio/plugin.json new file mode 100644 index 0000000000000..d59671c3d3ab1 --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/customerio/plugin.json @@ -0,0 +1,60 @@ +{ + "name": "Customer.io", + "description": "Send event data and emails into Customer.io.", + "posthogVersion": ">= 1.25.0", + "main": "index.ts", + "config": [ + { + "key": "customerioSiteId", + "hint": "Provided during Customer.io setup.", + "name": "Customer.io Site ID", + "type": "string", + "default": "", + "required": true, + "secret": true + }, + { + "key": "customerioToken", + "hint": "Provided during Customer.io setup.", + "name": "Customer.io API Key", + "type": "string", + "default": "", + "required": true, + "secret": true + }, + { + "key": "host", + "name": "Tracking Endpoint", + "hint": "Use the EU variant if your Customer.io account is based in the EU region.", + "type": "choice", + "default": "track.customer.io", + "choices": ["track.customer.io", "track-eu.customer.io"] + }, + { + "key": "identifyByEmail", + "name": "Identify by email", + "hint": "If enabled, the plugin will identify users by email instead of ID, whenever an email is available.", + "type": "choice", + "default": "No", + "choices": ["Yes", "No"] + }, + { + "key": "sendEventsFromAnonymousUsers", + "name": "Filtering of Anonymous Users", + "type": "choice", + "hint": "Customer.io pricing is based on the number of customers. This is an option to only send events from users that have been identified. Take into consideration that merging after identification won't work (as those previously anonymous events won't be there).", + "default": "Send all events", + "choices": [ + "Send all events", + "Only send events from users that have been identified", + "Only send events from users with emails" + ] + }, + { + "key": "eventsToSend", + "name": "PostHog Event Allowlist", + "type": "string", + "hint": "If this is set, only the specified events (comma-separated) will be sent to Customer.io." + } + ] +} diff --git a/plugin-server/src/cdp/legacy-plugins/intercom/index.ts b/plugin-server/src/cdp/legacy-plugins/intercom/index.ts new file mode 100644 index 0000000000000..bd3568ce65e00 --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/intercom/index.ts @@ -0,0 +1,197 @@ +import { Plugin, ProcessedPluginEvent, RetryError } from '@posthog/plugin-scaffold' + +import { Response } from '~/src/utils/fetch' + +import { LegacyPlugin, LegacyPluginMeta } from '../types' + +type IntercomPlugin = Plugin<{ + global: { + intercomUrl: string + } + config: { + intercomApiKey: string + triggeringEvents: string + ignoredEmailDomains: string + useEuropeanDataStorage: string + } +}> + +type IntercomMeta = LegacyPluginMeta + +async function onEvent(event: ProcessedPluginEvent, meta: IntercomMeta): Promise { + if (!isTriggeringEvent(meta.config.triggeringEvents, event.event)) { + return + } + + const intercomUrl = + meta.config.useEuropeanDataStorage === 'Yes' ? 'https://api.eu.intercom.com' : 'https://api.intercom.io' + + const email = getEmailFromEvent(event) + if (!email) { + meta.logger.warn( + `'${event.event}' will not be sent to Intercom because distinct_id is not an email and no 'email' was found in the event properties.` + ) + meta.logger.debug(`Skipped event with UUID ${event.uuid}`) + return + } + + if (isIgnoredEmailDomain(meta.config.ignoredEmailDomains, email)) { + return + } + + const timestamp = getTimestamp(meta, event) + + const isContactInIntercom = await searchForContactInIntercom(meta, intercomUrl, meta.config.intercomApiKey, email) + if (!isContactInIntercom) { + return + } + await sendEventToIntercom( + meta, + intercomUrl, + meta.config.intercomApiKey, + email, + event.event, + event['distinct_id'], + timestamp + ) +} + +async function searchForContactInIntercom(meta: IntercomMeta, url: string, apiKey: string, email: string) { + const searchContactResponse = await fetchWithRetry( + meta, + `${url}/contacts/search`, + { + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + Authorization: `Bearer ${apiKey}`, + }, + body: JSON.stringify({ + query: { + field: 'email', + operator: '=', + value: email, + }, + }), + }, + 'POST' + ) + const searchContactResponseJson = (await searchContactResponse.json()) as Record + + if (!statusOk(searchContactResponse) || searchContactResponseJson.errors) { + const errorMessage = searchContactResponseJson.errors ? searchContactResponseJson.errors[0].message : '' + meta.logger.error( + `Unable to search contact ${email} in Intercom. Status Code: ${searchContactResponseJson.status}. Error message: ${errorMessage}` + ) + return false + } else { + const found = searchContactResponseJson['total_count'] > 0 + meta.logger.log(`Contact ${email} in Intercom ${found ? 'found' : 'not found'}`) + return found + } +} + +async function sendEventToIntercom( + meta: IntercomMeta, + url: string, + apiKey: string, + email: string, + event: string, + distinct_id: string, + eventSendTime: number +) { + const sendEventResponse = await fetchWithRetry( + meta, + `${url}/events`, + { + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + Authorization: `Bearer ${apiKey}`, + }, + body: JSON.stringify({ + event_name: event, + created_at: eventSendTime, + email, + id: distinct_id, + }), + }, + 'POST' + ) + + if (!statusOk(sendEventResponse)) { + let errorMessage = '' + try { + const sendEventResponseJson = await sendEventResponse.json() + errorMessage = sendEventResponseJson.errors ? sendEventResponseJson.errors[0].message : '' + } catch {} + meta.logger.error( + `Unable to send event ${event} for ${email} to Intercom. Status Code: ${sendEventResponse.status}. Error message: ${errorMessage}` + ) + } else { + meta.logger.log(`Sent event ${event} for ${email} to Intercom`) + } +} + +async function fetchWithRetry(meta: IntercomMeta, url: string, options = {}, method = 'GET'): Promise { + try { + const res = await meta.fetch(url, { method: method, ...options }) + return res + } catch { + throw new RetryError('Service is down, retry later') + } +} + +function statusOk(res: Response) { + return String(res.status)[0] === '2' +} + +function isEmail(email: string): boolean { + const re = + /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/ + return re.test(String(email).toLowerCase()) +} + +function getEmailFromEvent(event: ProcessedPluginEvent): string | null { + if (isEmail(event.distinct_id)) { + return event.distinct_id + } else if (event['$set'] && Object.keys(event['$set']).includes('email')) { + if (isEmail(event['$set']['email'])) { + return event['$set']['email'] + } + } else if (event['properties'] && Object.keys(event['properties']).includes('email')) { + if (isEmail(event['properties']['email'])) { + return event['properties']['email'] + } + } + + return null +} + +function isIgnoredEmailDomain(ignoredEmailDomains: string, email: string): boolean { + const emailDomainsToIgnore = (ignoredEmailDomains || '').split(',').map((e) => e.trim()) + return emailDomainsToIgnore.includes(email.split('@')[1]) +} + +function isTriggeringEvent(triggeringEvents: string, event: string): boolean { + const validEvents = (triggeringEvents || '').split(',').map((e) => e.trim()) + return validEvents.indexOf(event) >= 0 +} + +function getTimestamp(meta: IntercomMeta, event: ProcessedPluginEvent): number { + try { + if (event['timestamp']) { + return Number(event['timestamp']) + } + } catch { + meta.logger.error('Event timestamp cannot be parsed as a number') + } + const date = new Date() + return Math.floor(date.getTime() / 1000) +} + +export const intercomPlugin: LegacyPlugin = { + id: 'intercom', + onEvent, + setupPlugin: () => Promise.resolve(), +} diff --git a/plugin-server/src/cdp/legacy-plugins/intercom/plugin.json b/plugin-server/src/cdp/legacy-plugins/intercom/plugin.json new file mode 100644 index 0000000000000..6de2b2723ffcb --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/intercom/plugin.json @@ -0,0 +1,42 @@ +{ + "name": "Intercom", + "url": "TODO", + "description": "Send event data to Intercom on PostHog events.", + "main": "index.ts", + "config": [ + { + "key": "intercomApiKey", + "hint": "Create an [Intercom app](https://developers.intercom.com/building-apps/), then go to Configure > Authentication to find your key.", + "name": "Intercom API Key", + "type": "string", + "default": "", + "required": true, + "secret": true + }, + { + "key": "triggeringEvents", + "hint": "A comma-separated list of PostHog events you want to send to Intercom (e.g.: '$identify,mycustomevent' ).", + "name": "Triggering events", + "type": "string", + "default": "$identify", + "required": true + }, + { + "key": "ignoredEmailDomains", + "hint": "A comma-separated list of email domains to ignore and not send events for in Intercom (e.g. 'posthog.com,dev.posthog.com' ).", + "name": "Email domains to skip", + "type": "string", + "default": "", + "required": false + }, + { + "key": "useEuropeanDataStorage", + "hint": "Send events to api.eu.intercom.com, if you are using Intercom's European Data Hosting.", + "name": "Send events to European Data Hosting", + "type": "choice", + "default": "No", + "choices": ["Yes", "No"], + "required": false + } + ] +} diff --git a/plugin-server/src/cdp/legacy-plugins/types.ts b/plugin-server/src/cdp/legacy-plugins/types.ts index cde9ed0529317..22acb222c50cc 100644 --- a/plugin-server/src/cdp/legacy-plugins/types.ts +++ b/plugin-server/src/cdp/legacy-plugins/types.ts @@ -1,9 +1,21 @@ -import { Plugin } from '@posthog/plugin-scaffold' +import { Meta, PluginInput, ProcessedPluginEvent } from '@posthog/plugin-scaffold' + +import { Response, trackedFetch } from '~/src/utils/fetch' + +export type LegacyPluginLogger = { + debug: (...args: any[]) => void + warn: (...args: any[]) => void + log: (...args: any[]) => void + error: (...args: any[]) => void +} + +export type LegacyPluginMeta = Meta & { + logger: LegacyPluginLogger + fetch: (...args: Parameters) => Promise +} export type LegacyPlugin = { id: string - name: string - description: string - onEvent: Plugin['onEvent'] - setupPlugin: Plugin['setupPlugin'] + onEvent(event: ProcessedPluginEvent, meta: LegacyPluginMeta): Promise + setupPlugin?: (meta: LegacyPluginMeta) => Promise } From d573b2047f66ac7efd3f55491d01eba08901dd5e Mon Sep 17 00:00:00 2001 From: Ben White Date: Mon, 27 Jan 2025 11:57:59 +0100 Subject: [PATCH 022/163] Fixes --- .../consumers/cdp-cyclotron-plugins-worker.test.ts | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.test.ts b/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.test.ts index e1acefe03dfce..52247ddf7216c 100644 --- a/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.test.ts +++ b/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.test.ts @@ -15,20 +15,6 @@ import { PLUGINS_BY_ID } from '../legacy-plugins' import { HogFunctionInvocationGlobalsWithInputs, HogFunctionType } from '../types' import { CdpCyclotronWorkerPlugins } from './cdp-cyclotron-plugins-worker.consumer' -// jest.mock('../../../src/utils/fetch', () => { -// return { -// trackedFetch: jest.fn(() => -// Promise.resolve({ -// status: 200, -// text: () => Promise.resolve(JSON.stringify({ success: true })), -// json: () => Promise.resolve({ success: true }), -// }) -// ), -// } -// }) - -// const mockFetch: jest.Mock = require('../../../src/utils/fetch').trackedFetch - jest.setTimeout(1000) /** From d0547391f5fb743475650f630c827b9b6bb2a949 Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 27 Jan 2025 11:12:06 +0000 Subject: [PATCH 023/163] Update UI snapshots for `chromium` (1) --- ...uccess--second-recording-in-list--dark.png | Bin 119098 -> 118722 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/frontend/__snapshots__/replay-player-success--second-recording-in-list--dark.png b/frontend/__snapshots__/replay-player-success--second-recording-in-list--dark.png index 369a08aada28d06319c8499cc4dfd0e36d48115d..397bb37bd08f74b083c4f147a14e88d2107cef81 100644 GIT binary patch delta 65556 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 delta 65935 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 z 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 rL&L9Q|M4fl_QU@H8EBUL From 2ab4312e0a228b7a611fa266b5b86cb8e9626d78 Mon Sep 17 00:00:00 2001 From: Ben White Date: Mon, 27 Jan 2025 15:09:13 +0100 Subject: [PATCH 024/163] USe own types --- .../cdp/legacy-plugins/customerio/index.ts | 22 ++++++------------- .../src/cdp/legacy-plugins/intercom/index.ts | 8 +++---- plugin-server/src/cdp/legacy-plugins/types.ts | 12 ++++++---- 3 files changed, 18 insertions(+), 24 deletions(-) diff --git a/plugin-server/src/cdp/legacy-plugins/customerio/index.ts b/plugin-server/src/cdp/legacy-plugins/customerio/index.ts index ed32ef0ea730c..798ee94941c36 100644 --- a/plugin-server/src/cdp/legacy-plugins/customerio/index.ts +++ b/plugin-server/src/cdp/legacy-plugins/customerio/index.ts @@ -1,4 +1,4 @@ -import { PluginInput, ProcessedPluginEvent } from '@posthog/plugin-scaffold' +import { ProcessedPluginEvent } from '@posthog/plugin-scaffold' import { RetryError } from '@posthog/plugin-scaffold' import { Response } from '~/src/utils/fetch' @@ -8,7 +8,7 @@ import { LegacyPlugin, LegacyPluginMeta } from '../types' const DEFAULT_HOST = 'track.customer.io' const DEFAULT_SEND_EVENTS_FROM_ANONYMOUS_USERS = 'Send all events' -interface CustomerIoPluginInput extends PluginInput { +type CustomerIoMeta = LegacyPluginMeta & { config: { customerioSiteId: string customerioToken: string @@ -28,7 +28,6 @@ interface CustomerIoPluginInput extends PluginInput { } } -type CustomerIoMeta = LegacyPluginMeta enum EventsConfig { SEND_ALL = '1', SEND_EMAILS = '2', @@ -94,7 +93,7 @@ async function callCustomerIoApi( } export const setupPlugin = async (meta: CustomerIoMeta) => { - const { config, global, storage, logger } = meta + const { config, global, logger } = meta const customerioBase64AuthToken = Buffer.from(`${config.customerioSiteId}:${config.customerioToken}`).toString( 'base64' ) @@ -109,16 +108,8 @@ export const setupPlugin = async (meta: CustomerIoMeta) => { EVENTS_CONFIG_MAP[config.sendEventsFromAnonymousUsers || DEFAULT_SEND_EVENTS_FROM_ANONYMOUS_USERS] global.identifyByEmail = config.identifyByEmail === 'Yes' - const credentialsVerifiedPreviously = await storage.get(global.authorizationHeader, false) - - if (credentialsVerifiedPreviously) { - logger.log('Customer.io credentials verified previously. Completing setupPlugin.') - return - } - // See https://www.customer.io/docs/api/#operation/getCioAllowlist await callCustomerIoApi(meta, 'GET', 'api.customer.io', '/v1/api/info/ip_addresses', global.authorizationHeader) - await storage.set(global.authorizationHeader, true) logger.log('Successfully authenticated with Customer.io. Completing setupPlugin.') } @@ -154,9 +145,9 @@ export const onEvent = async (event: ProcessedPluginEvent, meta: CustomerIoMeta) } async function syncCustomerMetadata(meta: CustomerIoMeta, event: ProcessedPluginEvent): Promise { - const { storage, logger } = meta + const { logger } = meta const customerStatusKey = `customer-status/${event.distinct_id}` - const customerStatusArray = (await storage.get(customerStatusKey, [])) as string[] + const customerStatusArray = [] as string[] const customerStatus = new Set(customerStatusArray) as Customer['status'] const customerExistsAlready = customerStatus.has('seen') const email = getEmailFromEvent(event) @@ -173,7 +164,8 @@ async function syncCustomerMetadata(meta: CustomerIoMeta, event: ProcessedPlugin } if (customerStatus.size > customerStatusArray.length) { - await storage.set(customerStatusKey, Array.from(customerStatus)) + // TODO: Fix this + // await storage.set(customerStatusKey, Array.from(customerStatus)) } return { diff --git a/plugin-server/src/cdp/legacy-plugins/intercom/index.ts b/plugin-server/src/cdp/legacy-plugins/intercom/index.ts index bd3568ce65e00..bc0fd544a5528 100644 --- a/plugin-server/src/cdp/legacy-plugins/intercom/index.ts +++ b/plugin-server/src/cdp/legacy-plugins/intercom/index.ts @@ -1,10 +1,10 @@ -import { Plugin, ProcessedPluginEvent, RetryError } from '@posthog/plugin-scaffold' +import { ProcessedPluginEvent, RetryError } from '@posthog/plugin-scaffold' import { Response } from '~/src/utils/fetch' import { LegacyPlugin, LegacyPluginMeta } from '../types' -type IntercomPlugin = Plugin<{ +type IntercomMeta = LegacyPluginMeta & { global: { intercomUrl: string } @@ -14,9 +14,7 @@ type IntercomPlugin = Plugin<{ ignoredEmailDomains: string useEuropeanDataStorage: string } -}> - -type IntercomMeta = LegacyPluginMeta +} async function onEvent(event: ProcessedPluginEvent, meta: IntercomMeta): Promise { if (!isTriggeringEvent(meta.config.triggeringEvents, event.event)) { diff --git a/plugin-server/src/cdp/legacy-plugins/types.ts b/plugin-server/src/cdp/legacy-plugins/types.ts index 22acb222c50cc..02f7a54f5ba9e 100644 --- a/plugin-server/src/cdp/legacy-plugins/types.ts +++ b/plugin-server/src/cdp/legacy-plugins/types.ts @@ -1,4 +1,4 @@ -import { Meta, PluginInput, ProcessedPluginEvent } from '@posthog/plugin-scaffold' +import { ProcessedPluginEvent } from '@posthog/plugin-scaffold' import { Response, trackedFetch } from '~/src/utils/fetch' @@ -9,13 +9,17 @@ export type LegacyPluginLogger = { error: (...args: any[]) => void } -export type LegacyPluginMeta = Meta & { +export type LegacyPluginMeta = { + // storage: StorageExtension + config: Record + global: Record + logger: LegacyPluginLogger fetch: (...args: Parameters) => Promise } export type LegacyPlugin = { id: string - onEvent(event: ProcessedPluginEvent, meta: LegacyPluginMeta): Promise - setupPlugin?: (meta: LegacyPluginMeta) => Promise + onEvent(event: ProcessedPluginEvent, meta: LegacyPluginMeta): Promise + setupPlugin?: (meta: LegacyPluginMeta) => Promise } From 3231131d973450db88727636db9a625eaafe99cb Mon Sep 17 00:00:00 2001 From: Ben White Date: Mon, 27 Jan 2025 17:27:33 +0100 Subject: [PATCH 025/163] Fixes --- .../cdp/legacy-plugins/customerio/index.ts | 21 ++++++------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/plugin-server/src/cdp/legacy-plugins/customerio/index.ts b/plugin-server/src/cdp/legacy-plugins/customerio/index.ts index 798ee94941c36..1a596c11452e5 100644 --- a/plugin-server/src/cdp/legacy-plugins/customerio/index.ts +++ b/plugin-server/src/cdp/legacy-plugins/customerio/index.ts @@ -42,7 +42,6 @@ const EVENTS_CONFIG_MAP = { interface Customer { status: Set<'seen' | 'identified' | 'with_email'> - existsAlready: boolean email: string | null } @@ -127,7 +126,7 @@ export const onEvent = async (event: ProcessedPluginEvent, meta: CustomerIoMeta) return } - const customer: Customer = await syncCustomerMetadata(meta, event) + const customer: Customer = syncCustomerMetadata(meta, event) logger.debug(customer) logger.debug(shouldCustomerBeTracked(customer, global.eventsConfig)) if (!shouldCustomerBeTracked(customer, global.eventsConfig)) { @@ -144,14 +143,10 @@ export const onEvent = async (event: ProcessedPluginEvent, meta: CustomerIoMeta) ) } -async function syncCustomerMetadata(meta: CustomerIoMeta, event: ProcessedPluginEvent): Promise { +function syncCustomerMetadata(meta: CustomerIoMeta, event: ProcessedPluginEvent): Customer { const { logger } = meta - const customerStatusKey = `customer-status/${event.distinct_id}` - const customerStatusArray = [] as string[] - const customerStatus = new Set(customerStatusArray) as Customer['status'] - const customerExistsAlready = customerStatus.has('seen') const email = getEmailFromEvent(event) - + const customerStatus = new Set() as Customer['status'] logger.debug(email) // Update customer status @@ -163,14 +158,8 @@ async function syncCustomerMetadata(meta: CustomerIoMeta, event: ProcessedPlugin customerStatus.add('with_email') } - if (customerStatus.size > customerStatusArray.length) { - // TODO: Fix this - // await storage.set(customerStatusKey, Array.from(customerStatus)) - } - return { status: customerStatus, - existsAlready: customerExistsAlready, email, } } @@ -204,7 +193,6 @@ async function exportSingleEvent( const customerPayload: Record = { ...(event.$set || {}), - _update: customer.existsAlready, identifier: event.distinct_id, } @@ -248,6 +236,9 @@ function isEmail(email: string): boolean { } function getEmailFromEvent(event: ProcessedPluginEvent): string | null { + if (event.person?.properties?.email) { + return event.person.properties.email + } const setAttribute = event.$set if (typeof setAttribute !== 'object' || !setAttribute['email']) { return null From 343551ce2d38399d85835bbdae19236d6d3a1fe8 Mon Sep 17 00:00:00 2001 From: Ben White Date: Mon, 27 Jan 2025 17:29:30 +0100 Subject: [PATCH 026/163] fix --- plugin-server/src/cdp/legacy-plugins/customerio/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin-server/src/cdp/legacy-plugins/customerio/index.ts b/plugin-server/src/cdp/legacy-plugins/customerio/index.ts index 1a596c11452e5..4650a58471ec0 100644 --- a/plugin-server/src/cdp/legacy-plugins/customerio/index.ts +++ b/plugin-server/src/cdp/legacy-plugins/customerio/index.ts @@ -256,6 +256,6 @@ function getEmailFromEvent(event: ProcessedPluginEvent): string | null { export const customerioPlugin: LegacyPlugin = { id: 'customer-io', - setupPlugin, + setupPlugin: setupPlugin as any, onEvent, } From c574e3522385c5c948b690ddf2b66d6bbdf04d23 Mon Sep 17 00:00:00 2001 From: Ben White Date: Mon, 27 Jan 2025 17:32:17 +0100 Subject: [PATCH 027/163] Fixes --- .../cdp-cyclotron-plugins-worker.consumer.ts | 29 +++---------------- 1 file changed, 4 insertions(+), 25 deletions(-) diff --git a/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.ts b/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.ts index 580212585f761..ddbbd4c0462e4 100644 --- a/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.ts +++ b/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.ts @@ -1,4 +1,4 @@ -import { Meta, ProcessedPluginEvent, RetryError, StorageExtension } from '@posthog/plugin-scaffold' +import { ProcessedPluginEvent, RetryError } from '@posthog/plugin-scaffold' import { DateTime } from 'luxon' import { Response, trackedFetch } from '~/src/utils/fetch' @@ -12,22 +12,7 @@ import { CdpCyclotronWorker } from './cdp-cyclotron-worker.consumer' type PluginState = { setupPromise: Promise errored: boolean - meta: Meta -} - -const createStorage = (): StorageExtension => { - const storage: Record = {} - return { - get: (key: string) => Promise.resolve(storage[key]), - set: (key: string, value: any) => { - storage[key] = value - return Promise.resolve() - }, - del: (key: string) => { - delete storage[key] - return Promise.resolve() - }, - } + meta: LegacyPluginMeta } /** @@ -101,16 +86,9 @@ export class CdpCyclotronWorkerPlugins extends CdpCyclotronWorker { let state = this.pluginState[pluginId] if (!state) { - const meta: LegacyPluginMeta = { + const meta: LegacyPluginMeta = { config: invocation.globals.inputs, - attachments: {}, global: {}, - jobs: {}, - metrics: {}, - cache: {} as any, - storage: createStorage(), - geoip: {} as any, - utils: {} as any, fetch: (...args) => this.fetch(...args), logger: logger, } @@ -164,6 +142,7 @@ export class CdpCyclotronWorkerPlugins extends CdpCyclotronWorker { }) await plugin.onEvent?.(event, { ...state.meta, + // NOTE: We override logger and fetch here so we can track the calls logger, fetch: this.fetch, }) From e8665e55706a5bcec879db525b055b789b0246cd Mon Sep 17 00:00:00 2001 From: Ben White Date: Mon, 27 Jan 2025 18:20:25 +0100 Subject: [PATCH 028/163] Added migration commands --- posthog/cdp/migrations.py | 96 +++++++++++++++++++ .../migrate_plugins_to_hog_functions.py | 23 +++++ posthog/models/plugin.py | 10 ++ 3 files changed, 129 insertions(+) create mode 100644 posthog/cdp/migrations.py create mode 100644 posthog/management/commands/migrate_plugins_to_hog_functions.py diff --git a/posthog/cdp/migrations.py b/posthog/cdp/migrations.py new file mode 100644 index 0000000000000..f79effda7b42c --- /dev/null +++ b/posthog/cdp/migrations.py @@ -0,0 +1,96 @@ +# Migrate from legacy plugins to new hog function plugins + + +import json +from posthog.api.hog_function import HogFunctionSerializer +from posthog.models.hog_functions.hog_function import HogFunction +from posthog.models.plugin import PluginAttachment, PluginConfig + + +def migrate_legacy_plugins(dry_run=True, team_ids=None, test_mode=False): + # Get all legacy plugin_configs that are active with their attachments and global values + legacy_plugins = PluginConfig.objects.select_related("plugin").filter(enabled=True) + + if team_ids: + legacy_plugins = legacy_plugins.filter(team_id__in=team_ids) + + hog_functions = [] + + for plugin_config in legacy_plugins: + print(plugin_config.plugin.name) + print(plugin_config.config) + print(plugin_config.plugin.config_schema) + + plugin_id = plugin_config.plugin.url.replace("inline://", "").replace("https://github.com/PostHog/", "") + + inputs = {} + inputs_schema = [] + + # Iterate over the plugin config to build the inputs + + for schema in plugin_config.plugin.config_schema: + if not schema.get("key"): + continue + + print("Converting schema", schema) + + # Some hacky stuff to convert the schemas correctly + input_schema = { + "key": schema["key"], + "type": schema["type"], + "label": schema.get("name", schema["key"]), + "description": schema.get("hint", ""), + "secret": schema.get("secret", False), + "required": schema.get("required", False), + "default": schema.get("default", None), + } + + if schema["type"] == "choice": + input_schema["choices"] = [ + { + "label": choice, + "value": choice, + } + for choice in schema["choices"] + ] + input_schema["type"] = "string" + elif schema["type"] == "attachment": + input_schema["type"] = "string" + + inputs_schema.append(input_schema) + + for key, value in plugin_config.config.items(): + inputs[key] = {"value": value} + + # Load all attachments for this plugin config + attachments = PluginAttachment.objects.filter(plugin_config=plugin_config) + + for attachment in attachments: + inputs[attachment.key] = {"value": attachment.parse_contents()} + + serializer_context = {"team": plugin_config.team, "get_team": lambda: plugin_config.team} + + data = { + "template_id": f"plugin-{plugin_id}", + "type": "destination", + "name": plugin_config.plugin.name, + "description": "This is a legacy destination migrated from our old plugin system.", + "filters": {}, + "inputs": inputs, + "inputs_schema": inputs_schema, + "enabled": True, + "icon_url": plugin_config.plugin.icon, + } + + print("Attempting to create hog function", data) + print(json.dumps(data, indent=2)) + + serializer = HogFunctionSerializer( + data=data, + context=serializer_context, + ) + serializer.is_valid(raise_exception=True) + hog_functions.append(HogFunction(**serializer.validated_data)) + + print(hog_functions) + return hog_functions diff --git a/posthog/management/commands/migrate_plugins_to_hog_functions.py b/posthog/management/commands/migrate_plugins_to_hog_functions.py new file mode 100644 index 0000000000000..8aa05aee700ac --- /dev/null +++ b/posthog/management/commands/migrate_plugins_to_hog_functions.py @@ -0,0 +1,23 @@ +from django.core.management.base import BaseCommand + +from posthog.cdp.migrations import migrate_legacy_plugins + + +class Command(BaseCommand): + help = "Migrate plugins to HogFunctions" + + def add_arguments(self, parser): + parser.add_argument( + "--dry-run", + type=bool, + help="If set, will not actually perform the migration, but will print out what would have been done", + ) + parser.add_argument("--team-ids", type=str, help="Comma separated list of team ids to sync") + parser.add_argument("--test-mode", type=str, help="Whether to just copy as a test function rather than migrate") + + def handle(self, *args, **options): + dry_run = options["dry_run"] + team_ids = options["team_ids"] + test_mode = options["test_mode"] + + migrate_legacy_plugins(dry_run=dry_run, team_ids=team_ids, test_mode=test_mode) diff --git a/posthog/models/plugin.py b/posthog/models/plugin.py index d2e204bdf3211..a11295e217bc7 100644 --- a/posthog/models/plugin.py +++ b/posthog/models/plugin.py @@ -1,4 +1,5 @@ import datetime +import json import os import subprocess from dataclasses import dataclass @@ -274,6 +275,15 @@ class PluginAttachment(models.Model): file_size = models.IntegerField() contents = models.BinaryField() + def parse_contents(self) -> str | None: + if self.content_type == "application/json": + return json.loads(self.contents) + + if self.content_type == "text/plain": + return self.contents.decode("utf-8") + + return None + class PluginStorage(models.Model): plugin_config = models.ForeignKey("PluginConfig", on_delete=models.CASCADE) From 04ba772fc45c6c9308d7350b36dc747c3b4be1ea Mon Sep 17 00:00:00 2001 From: Ben White Date: Mon, 27 Jan 2025 18:23:26 +0100 Subject: [PATCH 029/163] Fixes --- posthog/cdp/migrations.py | 35 ++++++++++++++++++++++++++--------- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/posthog/cdp/migrations.py b/posthog/cdp/migrations.py index f79effda7b42c..378e4acb0b8ca 100644 --- a/posthog/cdp/migrations.py +++ b/posthog/cdp/migrations.py @@ -7,7 +7,7 @@ from posthog.models.plugin import PluginAttachment, PluginConfig -def migrate_legacy_plugins(dry_run=True, team_ids=None, test_mode=False): +def migrate_legacy_plugins(dry_run=True, team_ids=None, test_mode=True): # Get all legacy plugin_configs that are active with their attachments and global values legacy_plugins = PluginConfig.objects.select_related("plugin").filter(enabled=True) @@ -17,11 +17,15 @@ def migrate_legacy_plugins(dry_run=True, team_ids=None, test_mode=False): hog_functions = [] for plugin_config in legacy_plugins: - print(plugin_config.plugin.name) - print(plugin_config.config) - print(plugin_config.plugin.config_schema) + print(plugin_config.plugin.name) # noqa: T201 + print(plugin_config.config) # noqa: T201 + print(plugin_config.plugin.config_schema) # noqa: T201 plugin_id = plugin_config.plugin.url.replace("inline://", "").replace("https://github.com/PostHog/", "") + plugin_name = plugin_config.plugin.name + + if test_mode: + plugin_name = f"[CDP-TEST-HIDDEN] {plugin_name}" inputs = {} inputs_schema = [] @@ -32,7 +36,7 @@ def migrate_legacy_plugins(dry_run=True, team_ids=None, test_mode=False): if not schema.get("key"): continue - print("Converting schema", schema) + print("Converting schema", schema) # noqa: T201 # Some hacky stuff to convert the schemas correctly input_schema = { @@ -73,7 +77,7 @@ def migrate_legacy_plugins(dry_run=True, team_ids=None, test_mode=False): data = { "template_id": f"plugin-{plugin_id}", "type": "destination", - "name": plugin_config.plugin.name, + "name": plugin_name, "description": "This is a legacy destination migrated from our old plugin system.", "filters": {}, "inputs": inputs, @@ -82,8 +86,8 @@ def migrate_legacy_plugins(dry_run=True, team_ids=None, test_mode=False): "icon_url": plugin_config.plugin.icon, } - print("Attempting to create hog function", data) - print(json.dumps(data, indent=2)) + print("Attempting to create hog function", data) # noqa: T201 + print(json.dumps(data, indent=2)) # noqa: T201 serializer = HogFunctionSerializer( data=data, @@ -92,5 +96,18 @@ def migrate_legacy_plugins(dry_run=True, team_ids=None, test_mode=False): serializer.is_valid(raise_exception=True) hog_functions.append(HogFunction(**serializer.validated_data)) - print(hog_functions) + print(hog_functions) # noqa: T201 + + if dry_run: + print("Dry run, not creating hog functions") # noqa: T201 + return hog_functions + + print("Creating hog functions") # noqa: T201 + HogFunction.objects.bulk_create(hog_functions) + + # Disable the old plugins + PluginConfig.objects.filter(id__in=[plugin_config.id for plugin_config in legacy_plugins]).update(enabled=False) + + print("Done") # noqa: T201 + return hog_functions From 99987b2b3fd645a76a5ace480a9fba5dd4891ee7 Mon Sep 17 00:00:00 2001 From: Ben White Date: Mon, 27 Jan 2025 18:27:03 +0100 Subject: [PATCH 030/163] Fix --- .../templates/_internal/template_legacy_plugin.py | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/posthog/cdp/templates/_internal/template_legacy_plugin.py b/posthog/cdp/templates/_internal/template_legacy_plugin.py index cef66400e47a7..184251cc6b2c6 100644 --- a/posthog/cdp/templates/_internal/template_legacy_plugin.py +++ b/posthog/cdp/templates/_internal/template_legacy_plugin.py @@ -1,19 +1,5 @@ from posthog.cdp.templates.hog_function_template import HogFunctionTemplate -legacy_plugin_template: HogFunctionTemplate = HogFunctionTemplate( - status="alpha", - type="destination", - id="template-legacy-plugin", - name="Legacy plugin", - description="Legacy plugins", - icon_url="/static/hedgehog/builder-hog-01.png", - category=["Custom", "Analytics"], - hog=""" -print('not used') -""".strip(), - inputs_schema=[], -) - def create_legacy_plugin_template(template_id: str) -> HogFunctionTemplate: return HogFunctionTemplate( From 2ffb989f606d2e460376fa4004b81ae37fdd815f Mon Sep 17 00:00:00 2001 From: Ben White Date: Mon, 27 Jan 2025 18:28:49 +0100 Subject: [PATCH 031/163] Fix --- plugin-server/tests/cdp/cdp-e2e.test.ts | 113 ------------------------ 1 file changed, 113 deletions(-) diff --git a/plugin-server/tests/cdp/cdp-e2e.test.ts b/plugin-server/tests/cdp/cdp-e2e.test.ts index 50db3eb85f189..425eeeb37f6b7 100644 --- a/plugin-server/tests/cdp/cdp-e2e.test.ts +++ b/plugin-server/tests/cdp/cdp-e2e.test.ts @@ -3,7 +3,6 @@ import { getProducedKafkaMessages, getProducedKafkaMessagesForTopic } from '../h import { CdpCyclotronWorker, CdpCyclotronWorkerFetch } from '../../src/cdp/consumers/cdp-cyclotron-worker.consumer' import { CdpProcessedEventsConsumer } from '../../src/cdp/consumers/cdp-processed-events.consumer' -import { CdpCyclotronWorkerPlugins } from '../../src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer' import { HogFunctionInvocationGlobals, HogFunctionType } from '../../src/cdp/types' import { KAFKA_APP_METRICS_2, KAFKA_LOG_ENTRIES } from '../../src/config/kafka-topics' import { Hub, Team } from '../../src/types' @@ -35,7 +34,6 @@ describe('CDP Consumer loop', () => { let processedEventsConsumer: CdpProcessedEventsConsumer let cyclotronWorker: CdpCyclotronWorker | undefined let cyclotronFetchWorker: CdpCyclotronWorkerFetch | undefined - let cdpCyclotronWorkerPlugins: CdpCyclotronWorkerPlugins | undefined let hub: Hub let team: Team @@ -65,8 +63,6 @@ describe('CDP Consumer loop', () => { cyclotronWorker = new CdpCyclotronWorker(hub) await cyclotronWorker.start() - cdpCyclotronWorkerPlugins = new CdpCyclotronWorkerPlugins(hub) - await cdpCyclotronWorkerPlugins.start() cyclotronFetchWorker = new CdpCyclotronWorkerFetch(hub) await cyclotronFetchWorker.start() @@ -93,7 +89,6 @@ describe('CDP Consumer loop', () => { processedEventsConsumer?.stop().then(() => console.log('Stopped processedEventsConsumer')), cyclotronWorker?.stop().then(() => console.log('Stopped cyclotronWorker')), cyclotronFetchWorker?.stop().then(() => console.log('Stopped cyclotronFetchWorker')), - cdpCyclotronWorkerPlugins?.stop().then(() => console.log('Stopped cdpCyclotronWorkerPlugins')), ] await Promise.all(stoppers) @@ -216,113 +211,5 @@ describe('CDP Consumer loop', () => { }, ]) }) - - it('should invoke a legacy plugin in the worker loop until completed', async () => { - const invocations = await processedEventsConsumer.processBatch([globals]) - expect(invocations).toHaveLength(1) - - await waitForExpect(() => { - expect(getProducedKafkaMessages()).toHaveLength(7) - }, 5000) - - expect(mockFetch).toHaveBeenCalledTimes(1) - - expect(mockFetch.mock.calls[0]).toMatchInlineSnapshot(` - [ - "https://example.com/posthog-webhook", - { - "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": { - "version": "v=1.0.0", - }, - "method": "POST", - "timeout": 10000, - }, - ] - `) - - const logMessages = getProducedKafkaMessagesForTopic(KAFKA_LOG_ENTRIES) - const metricsMessages = getProducedKafkaMessagesForTopic(KAFKA_APP_METRICS_2) - - expect(metricsMessages).toMatchObject([ - { - topic: 'clickhouse_app_metrics2_test', - value: { - app_source: 'hog_function', - app_source_id: fnFetchNoFilters.id.toString(), - count: 1, - metric_kind: 'other', - metric_name: 'fetch', - team_id: 2, - }, - }, - { - topic: 'clickhouse_app_metrics2_test', - value: { - app_source: 'hog_function', - app_source_id: fnFetchNoFilters.id.toString(), - count: 1, - metric_kind: 'success', - metric_name: 'succeeded', - team_id: 2, - }, - }, - ]) - - expect(logMessages).toMatchObject([ - { - topic: 'log_entries_test', - value: { - level: 'debug', - log_source: 'hog_function', - log_source_id: fnFetchNoFilters.id.toString(), - message: 'Executing function', - team_id: 2, - }, - }, - { - topic: 'log_entries_test', - value: { - level: 'debug', - log_source: 'hog_function', - log_source_id: fnFetchNoFilters.id.toString(), - message: expect.stringContaining( - "Suspending function due to async function call 'fetch'. Payload:" - ), - team_id: 2, - }, - }, - { - topic: 'log_entries_test', - value: { - level: 'debug', - log_source: 'hog_function', - log_source_id: fnFetchNoFilters.id.toString(), - message: 'Resuming function', - team_id: 2, - }, - }, - { - topic: 'log_entries_test', - value: { - level: 'info', - log_source: 'hog_function', - log_source_id: fnFetchNoFilters.id.toString(), - message: `Fetch response:, {"status":200,"body":{"success":true}}`, - team_id: 2, - }, - }, - { - topic: 'log_entries_test', - value: { - level: 'debug', - log_source: 'hog_function', - log_source_id: fnFetchNoFilters.id.toString(), - message: expect.stringContaining('Function completed in'), - team_id: 2, - }, - }, - ]) - }) }) }) From 406d7de5bd019ecec4f90c4d4f0211c7f7ec6550 Mon Sep 17 00:00:00 2001 From: Ben White Date: Tue, 28 Jan 2025 09:10:53 +0100 Subject: [PATCH 032/163] Fix migration --- posthog/cdp/migrations.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/posthog/cdp/migrations.py b/posthog/cdp/migrations.py index 378e4acb0b8ca..8b260cbea49cf 100644 --- a/posthog/cdp/migrations.py +++ b/posthog/cdp/migrations.py @@ -72,7 +72,7 @@ def migrate_legacy_plugins(dry_run=True, team_ids=None, test_mode=True): for attachment in attachments: inputs[attachment.key] = {"value": attachment.parse_contents()} - serializer_context = {"team": plugin_config.team, "get_team": lambda: plugin_config.team} + serializer_context = {"team": plugin_config.team, "get_team": (lambda config=plugin_config: config.team)} data = { "template_id": f"plugin-{plugin_id}", @@ -105,8 +105,10 @@ def migrate_legacy_plugins(dry_run=True, team_ids=None, test_mode=True): print("Creating hog functions") # noqa: T201 HogFunction.objects.bulk_create(hog_functions) - # Disable the old plugins - PluginConfig.objects.filter(id__in=[plugin_config.id for plugin_config in legacy_plugins]).update(enabled=False) + if not test_mode: + print("Disabling old plugins") # noqa: T201 + # Disable the old plugins + PluginConfig.objects.filter(id__in=[plugin_config.id for plugin_config in legacy_plugins]).update(enabled=False) print("Done") # noqa: T201 From 0460d3a08f1b02510327c7a23ebb5a0617ba7ac6 Mon Sep 17 00:00:00 2001 From: Ben White Date: Tue, 28 Jan 2025 09:15:17 +0100 Subject: [PATCH 033/163] Fixes --- posthog/cdp/migrations.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/posthog/cdp/migrations.py b/posthog/cdp/migrations.py index 8b260cbea49cf..86aa29e7108a7 100644 --- a/posthog/cdp/migrations.py +++ b/posthog/cdp/migrations.py @@ -17,9 +17,13 @@ def migrate_legacy_plugins(dry_run=True, team_ids=None, test_mode=True): hog_functions = [] for plugin_config in legacy_plugins: - print(plugin_config.plugin.name) # noqa: T201 - print(plugin_config.config) # noqa: T201 - print(plugin_config.plugin.config_schema) # noqa: T201 + methods = plugin_config.plugin.capabilities.get("methods", []) + + if "onEvent" not in methods or "composeWebhook" not in methods: + print("Skipping plugin", plugin_config.plugin.name, "as it doesn't have onEvent or composeWebhook") # noqa: T201 + continue + + print("Attempting to migrate plugin", plugin_config) # noqa: T201 plugin_id = plugin_config.plugin.url.replace("inline://", "").replace("https://github.com/PostHog/", "") plugin_name = plugin_config.plugin.name @@ -86,8 +90,7 @@ def migrate_legacy_plugins(dry_run=True, team_ids=None, test_mode=True): "icon_url": plugin_config.plugin.icon, } - print("Attempting to create hog function", data) # noqa: T201 - print(json.dumps(data, indent=2)) # noqa: T201 + print("Attempting to create hog function...") # noqa: T201 serializer = HogFunctionSerializer( data=data, @@ -98,6 +101,10 @@ def migrate_legacy_plugins(dry_run=True, team_ids=None, test_mode=True): print(hog_functions) # noqa: T201 + if not hog_functions: + print("No hog functions to create") # noqa: T201 + return [] + if dry_run: print("Dry run, not creating hog functions") # noqa: T201 return hog_functions From e2450472199a8b656b5b64f1218490a5cf41ff47 Mon Sep 17 00:00:00 2001 From: Ben White Date: Tue, 28 Jan 2025 09:17:18 +0100 Subject: [PATCH 034/163] Fixes --- .../cdp-cyclotron-plugins-worker.consumer.ts | 1 + .../cdp-cyclotron-plugins-worker.test.ts | 11 ---------- .../hog-function-manager.service.test.ts | 20 ++----------------- 3 files changed, 3 insertions(+), 29 deletions(-) diff --git a/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.ts b/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.ts index ddbbd4c0462e4..66094bcbc70dc 100644 --- a/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.ts +++ b/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.ts @@ -86,6 +86,7 @@ export class CdpCyclotronWorkerPlugins extends CdpCyclotronWorker { let state = this.pluginState[pluginId] if (!state) { + // TODO: Modify fetch to be a silent log if it is a test function... const meta: LegacyPluginMeta = { config: invocation.globals.inputs, global: {}, diff --git a/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.test.ts b/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.test.ts index 52247ddf7216c..c5b1faf1558d5 100644 --- a/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.test.ts +++ b/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.test.ts @@ -110,8 +110,6 @@ describe('CdpCyclotronWorkerPlugins', () => { expect(PLUGINS_BY_ID['intercom'].setupPlugin).toHaveBeenCalledTimes(1) expect(jest.mocked(PLUGINS_BY_ID['intercom'].setupPlugin!).mock.calls[0][0]).toMatchInlineSnapshot(` { - "attachments": {}, - "cache": {}, "config": { "ignoredEmailDomains": "dev.posthog.com", "intercomApiKey": "1234567890", @@ -119,22 +117,13 @@ describe('CdpCyclotronWorkerPlugins', () => { "useEuropeanDataStorage": "No", }, "fetch": [Function], - "geoip": {}, "global": {}, - "jobs": {}, "logger": { "debug": [Function], "error": [Function], "log": [Function], "warn": [Function], }, - "metrics": {}, - "storage": { - "del": [Function], - "get": [Function], - "set": [Function], - }, - "utils": {}, } `) }) diff --git a/plugin-server/src/cdp/services/hog-function-manager.service.test.ts b/plugin-server/src/cdp/services/hog-function-manager.service.test.ts index c4530200b8b5c..41c15d8f1c85a 100644 --- a/plugin-server/src/cdp/services/hog-function-manager.service.test.ts +++ b/plugin-server/src/cdp/services/hog-function-manager.service.test.ts @@ -3,6 +3,7 @@ import { Hub } from '~/src/types' import { closeHub, createHub } from '~/src/utils/db/hub' import { PostgresUse } from '~/src/utils/db/postgres' import { insertHogFunction, insertIntegration } from '~/tests/cdp/fixtures' +import { forSnapshot } from '~/tests/helpers/snapshots' import { createTeam, resetTestDatabase } from '~/tests/helpers/sql' import { HogFunctionManagerService } from './hog-function-manager.service' @@ -70,24 +71,6 @@ describe('HogFunctionManager', () => { }) ) - // hogFunctions.push( - // await insertHogFunction(hub.postgres, teamId1, { - // name: 'Email Provider team 1', - // type: 'email', - // inputs_schema: [ - // { - // type: 'email', - // key: 'message', - // }, - // ], - // inputs: { - // email: { - // value: { from: 'me@a.com', to: 'you@b.com', subject: 'subject', html: 'text' }, - // }, - // }, - // }) - // ) - hogFunctions.push( await insertHogFunction(hub.postgres, teamId2, { name: 'Test Hog Function team 2', @@ -149,6 +132,7 @@ describe('HogFunctionManager', () => { encrypted_inputs: null, masking: null, mappings: null, + template_id: null, depends_on_integration_ids: new Set([integrations[0].id]), }, ]) From 4e7ffe0450f4888b8280cb3aefac679ec4a8ceb0 Mon Sep 17 00:00:00 2001 From: Ben White Date: Tue, 28 Jan 2025 09:19:57 +0100 Subject: [PATCH 035/163] Fix --- plugin-server/src/cdp/legacy-plugins/types.ts | 1 - posthog/cdp/migrations.py | 4 ---- 2 files changed, 5 deletions(-) diff --git a/plugin-server/src/cdp/legacy-plugins/types.ts b/plugin-server/src/cdp/legacy-plugins/types.ts index 02f7a54f5ba9e..b65484dfe43a8 100644 --- a/plugin-server/src/cdp/legacy-plugins/types.ts +++ b/plugin-server/src/cdp/legacy-plugins/types.ts @@ -10,7 +10,6 @@ export type LegacyPluginLogger = { } export type LegacyPluginMeta = { - // storage: StorageExtension config: Record global: Record diff --git a/posthog/cdp/migrations.py b/posthog/cdp/migrations.py index 86aa29e7108a7..eee11f0e08011 100644 --- a/posthog/cdp/migrations.py +++ b/posthog/cdp/migrations.py @@ -1,7 +1,3 @@ -# Migrate from legacy plugins to new hog function plugins - - -import json from posthog.api.hog_function import HogFunctionSerializer from posthog.models.hog_functions.hog_function import HogFunction from posthog.models.plugin import PluginAttachment, PluginConfig From 3af5796fc46cd23fe911a25f60f21e76e25a09fd Mon Sep 17 00:00:00 2001 From: Ben White Date: Tue, 28 Jan 2025 09:22:15 +0100 Subject: [PATCH 036/163] Fixes --- .../commands/migrate_plugins_to_hog_functions.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/posthog/management/commands/migrate_plugins_to_hog_functions.py b/posthog/management/commands/migrate_plugins_to_hog_functions.py index 8aa05aee700ac..ccc58b88604ab 100644 --- a/posthog/management/commands/migrate_plugins_to_hog_functions.py +++ b/posthog/management/commands/migrate_plugins_to_hog_functions.py @@ -9,15 +9,19 @@ class Command(BaseCommand): def add_arguments(self, parser): parser.add_argument( "--dry-run", - type=bool, + action="store_true", help="If set, will not actually perform the migration, but will print out what would have been done", ) parser.add_argument("--team-ids", type=str, help="Comma separated list of team ids to sync") - parser.add_argument("--test-mode", type=str, help="Whether to just copy as a test function rather than migrate") + parser.add_argument( + "--test-mode", action="store_true", help="Whether to just copy as a test function rather than migrate" + ) def handle(self, *args, **options): dry_run = options["dry_run"] team_ids = options["team_ids"] test_mode = options["test_mode"] + print("Migrating plugins to hog functions", options) # noqa: T201 + migrate_legacy_plugins(dry_run=dry_run, team_ids=team_ids, test_mode=test_mode) From 91b8ea5877e6b100224cfcf1b7019866b5efd639 Mon Sep 17 00:00:00 2001 From: Ben White Date: Tue, 28 Jan 2025 09:36:24 +0100 Subject: [PATCH 037/163] Fixes --- .../cdp-cyclotron-plugins-worker.consumer.ts | 20 +++++++++++-- .../cdp-cyclotron-plugins-worker.test.ts | 29 ++++++++++++++++++- 2 files changed, 46 insertions(+), 3 deletions(-) diff --git a/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.ts b/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.ts index 66094bcbc70dc..37d0492022330 100644 --- a/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.ts +++ b/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.ts @@ -51,6 +51,8 @@ export class CdpCyclotronWorkerPlugins extends CdpCyclotronWorker { ? invocation.hogFunction.template_id.replace('plugin-', '') : null + const isTestFunction = invocation.hogFunction.name.includes('[CDP-TEST-HIDDEN]') + result.logs.push({ level: 'debug', timestamp: DateTime.now(), @@ -85,12 +87,26 @@ export class CdpCyclotronWorkerPlugins extends CdpCyclotronWorker { let state = this.pluginState[pluginId] + const fetch = (...args: Parameters): Promise => { + if (isTestFunction) { + addLog('info', 'Fetch called but mocked due to test function') + return Promise.resolve({ + status: 500, + json: () => + Promise.resolve({ + message: 'Test function', + }), + } as Response) + } + return this.fetch(...args) + } + if (!state) { // TODO: Modify fetch to be a silent log if it is a test function... const meta: LegacyPluginMeta = { config: invocation.globals.inputs, global: {}, - fetch: (...args) => this.fetch(...args), + fetch, logger: logger, } @@ -145,7 +161,7 @@ export class CdpCyclotronWorkerPlugins extends CdpCyclotronWorker { ...state.meta, // NOTE: We override logger and fetch here so we can track the calls logger, - fetch: this.fetch, + fetch, }) result.logs.push({ level: 'debug', diff --git a/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.test.ts b/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.test.ts index c5b1faf1558d5..3acd044f8ace8 100644 --- a/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.test.ts +++ b/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.test.ts @@ -5,7 +5,7 @@ import { createInvocation, insertHogFunction as _insertHogFunction, } from '~/tests/cdp/fixtures' -import { getProducedKafkaMessages } from '~/tests/helpers/mocks/producer.mock' +import { getProducedKafkaMessages, getProducedKafkaMessagesForTopic } from '~/tests/helpers/mocks/producer.mock' import { forSnapshot } from '~/tests/helpers/snapshots' import { getFirstTeam, resetTestDatabase } from '~/tests/helpers/sql' @@ -210,6 +210,33 @@ describe('CdpCyclotronWorkerPlugins', () => { `) }) + it('should mock out fetch if it is a test function', async () => { + jest.spyOn(PLUGINS_BY_ID['intercom'] as any, 'onEvent') + + const invocation = createInvocation(fn, globals) + invocation.hogFunction.name = 'My function [CDP-TEST-HIDDEN]' + invocation.globals.event.event = 'mycustomevent' + invocation.globals.event.properties = { + email: 'test@posthog.com', + } + + await processor.processInvocations([invocation]) + + expect(mockFetch).toHaveBeenCalledTimes(0) + + expect(PLUGINS_BY_ID['intercom'].onEvent).toHaveBeenCalledTimes(1) + + expect(forSnapshot(getProducedKafkaMessagesForTopic('log_entries_test').map((m) => m.value.message))) + .toMatchInlineSnapshot(` + [ + "Executing plugin intercom", + "Fetch called but mocked due to test function", + "Unable to search contact test@posthog.com in Intercom. Status Code: undefined. Error message: ", + "Execution successful", + ] + `) + }) + it('should handle and collect errors', async () => { jest.spyOn(PLUGINS_BY_ID['intercom'] as any, 'onEvent') From be04729619e663f909cef8fa6f3114ceb6305212 Mon Sep 17 00:00:00 2001 From: Ben White Date: Tue, 28 Jan 2025 09:44:30 +0100 Subject: [PATCH 038/163] fix --- .../src/cdp/services/hog-function-manager.service.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/plugin-server/src/cdp/services/hog-function-manager.service.test.ts b/plugin-server/src/cdp/services/hog-function-manager.service.test.ts index 41c15d8f1c85a..b31892365e316 100644 --- a/plugin-server/src/cdp/services/hog-function-manager.service.test.ts +++ b/plugin-server/src/cdp/services/hog-function-manager.service.test.ts @@ -3,7 +3,6 @@ import { Hub } from '~/src/types' import { closeHub, createHub } from '~/src/utils/db/hub' import { PostgresUse } from '~/src/utils/db/postgres' import { insertHogFunction, insertIntegration } from '~/tests/cdp/fixtures' -import { forSnapshot } from '~/tests/helpers/snapshots' import { createTeam, resetTestDatabase } from '~/tests/helpers/sql' import { HogFunctionManagerService } from './hog-function-manager.service' From a2d445045ec85313ac824118afba3704d5a011c7 Mon Sep 17 00:00:00 2001 From: Ben White Date: Tue, 28 Jan 2025 10:04:17 +0100 Subject: [PATCH 039/163] Fixes --- .../cdp-cyclotron-plugins-worker.test.ts.snap | 46 ++++++++++++++++ .../cdp-cyclotron-plugins-worker.consumer.ts | 3 +- .../cdp-cyclotron-plugins-worker.test.ts | 55 ++++++++++++++++++- .../cdp/legacy-plugins/customerio/index.ts | 5 +- .../src/cdp/legacy-plugins/intercom/index.ts | 1 + plugin-server/src/cdp/legacy-plugins/types.ts | 6 +- .../src/cdp/services/hog-executor.service.ts | 2 +- 7 files changed, 112 insertions(+), 6 deletions(-) diff --git a/plugin-server/src/cdp/consumers/__snapshots__/cdp-cyclotron-plugins-worker.test.ts.snap b/plugin-server/src/cdp/consumers/__snapshots__/cdp-cyclotron-plugins-worker.test.ts.snap index e8924a331c548..4a2036225a8b3 100644 --- a/plugin-server/src/cdp/consumers/__snapshots__/cdp-cyclotron-plugins-worker.test.ts.snap +++ b/plugin-server/src/cdp/consumers/__snapshots__/cdp-cyclotron-plugins-worker.test.ts.snap @@ -43,3 +43,49 @@ exports[`CdpCyclotronWorkerPlugins onEvent should handle and collect errors 3`] }, ] `; + +exports[`CdpCyclotronWorkerPlugins smoke tests should run the plugin: { name: 'customer-io', plugin: [Object] } 1`] = ` +[ + { + "level": "debug", + "message": "Executing plugin customer-io", + }, + { + "level": "info", + "message": "Successfully authenticated with Customer.io. Completing setupPlugin.", + }, + { + "level": "debug", + "message": "test@posthog.com", + }, + { + "level": "debug", + "message": "{"status":{},"email":"test@posthog.com"}", + }, + { + "level": "debug", + "message": "true", + }, + { + "level": "debug", + "message": "Execution successful", + }, +] +`; + +exports[`CdpCyclotronWorkerPlugins smoke tests should run the plugin: { name: 'intercom', plugin: [Object] } 1`] = ` +[ + { + "level": "debug", + "message": "Executing plugin intercom", + }, + { + "level": "info", + "message": "Contact test@posthog.com in Intercom not found", + }, + { + "level": "debug", + "message": "Execution successful", + }, +] +`; diff --git a/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.ts b/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.ts index 37d0492022330..ba4e333d23d6c 100644 --- a/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.ts +++ b/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.ts @@ -6,6 +6,7 @@ import { status } from '~/src/utils/status' import { PLUGINS_BY_ID } from '../legacy-plugins' import { LegacyPluginLogger, LegacyPluginMeta } from '../legacy-plugins/types' +import { sanitizeLogMessage } from '../services/hog-executor.service' import { HogFunctionInvocation, HogFunctionInvocationResult, HogFunctionTypeType } from '../types' import { CdpCyclotronWorker } from './cdp-cyclotron-worker.consumer' @@ -74,7 +75,7 @@ export class CdpCyclotronWorkerPlugins extends CdpCyclotronWorker { result.logs.push({ level, timestamp: DateTime.now(), - message: args.join(' '), + message: sanitizeLogMessage(args), }) } diff --git a/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.test.ts b/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.test.ts index 3acd044f8ace8..fedbe3ebe84db 100644 --- a/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.test.ts +++ b/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.test.ts @@ -46,7 +46,15 @@ describe('CdpCyclotronWorkerPlugins', () => { await processor.start() - processor.fetch = mockFetch = jest.fn(() => Promise.resolve({} as any)) + processor.fetch = mockFetch = jest.fn(() => + Promise.resolve({ + status: 200, + json: () => + Promise.resolve({ + status: 200, + }), + } as any) + ) jest.spyOn(processor['cyclotronWorker']!, 'updateJob').mockImplementation(() => {}) jest.spyOn(processor['cyclotronWorker']!, 'releaseJob').mockImplementation(() => Promise.resolve()) @@ -267,4 +275,49 @@ describe('CdpCyclotronWorkerPlugins', () => { expect(forSnapshot(getProducedKafkaMessages())).toMatchSnapshot() }) }) + + describe('smoke tests', () => { + const testCases = Object.entries(PLUGINS_BY_ID).map(([pluginId, plugin]) => ({ + name: pluginId, + plugin, + })) + + it.each(testCases)('should run the plugin: %s', async ({ name, plugin }) => { + globals.event.event = '$identify' // Many plugins filter for this + const invocation = createInvocation(fn, globals) + + invocation.hogFunction.template_id = `plugin-${plugin.id}` + + const inputs: Record = {} + + for (const input of plugin.metadata.config) { + if (!input.key) { + continue + } + + if (input.default) { + inputs[input.key] = input.default + continue + } + + if (input.type === 'choice') { + inputs[input.key] = input.choices[0] + } else if (input.type === 'string') { + inputs[input.key] = 'test' + } + } + + invocation.hogFunction.name = name + await processor.processInvocations([invocation]) + + expect( + forSnapshot( + getProducedKafkaMessagesForTopic('log_entries_test').map((m) => ({ + message: m.value.message, + level: m.value.level, + })) + ) + ).toMatchSnapshot() + }) + }) }) diff --git a/plugin-server/src/cdp/legacy-plugins/customerio/index.ts b/plugin-server/src/cdp/legacy-plugins/customerio/index.ts index 4650a58471ec0..7ec1e99ec92f5 100644 --- a/plugin-server/src/cdp/legacy-plugins/customerio/index.ts +++ b/plugin-server/src/cdp/legacy-plugins/customerio/index.ts @@ -128,7 +128,7 @@ export const onEvent = async (event: ProcessedPluginEvent, meta: CustomerIoMeta) const customer: Customer = syncCustomerMetadata(meta, event) logger.debug(customer) - logger.debug(shouldCustomerBeTracked(customer, global.eventsConfig)) + logger.debug('Should customer be tracked:', shouldCustomerBeTracked(customer, global.eventsConfig)) if (!shouldCustomerBeTracked(customer, global.eventsConfig)) { return } @@ -147,7 +147,7 @@ function syncCustomerMetadata(meta: CustomerIoMeta, event: ProcessedPluginEvent) const { logger } = meta const email = getEmailFromEvent(event) const customerStatus = new Set() as Customer['status'] - logger.debug(email) + logger.debug('Detected email:', email) // Update customer status customerStatus.add('seen') @@ -256,6 +256,7 @@ function getEmailFromEvent(event: ProcessedPluginEvent): string | null { export const customerioPlugin: LegacyPlugin = { id: 'customer-io', + metadata: require('./plugin.json'), setupPlugin: setupPlugin as any, onEvent, } diff --git a/plugin-server/src/cdp/legacy-plugins/intercom/index.ts b/plugin-server/src/cdp/legacy-plugins/intercom/index.ts index bc0fd544a5528..c01adbb7e46fa 100644 --- a/plugin-server/src/cdp/legacy-plugins/intercom/index.ts +++ b/plugin-server/src/cdp/legacy-plugins/intercom/index.ts @@ -190,6 +190,7 @@ function getTimestamp(meta: IntercomMeta, event: ProcessedPluginEvent): number { export const intercomPlugin: LegacyPlugin = { id: 'intercom', + metadata: require('./plugin.json'), onEvent, setupPlugin: () => Promise.resolve(), } diff --git a/plugin-server/src/cdp/legacy-plugins/types.ts b/plugin-server/src/cdp/legacy-plugins/types.ts index b65484dfe43a8..f9df0a48e7ad1 100644 --- a/plugin-server/src/cdp/legacy-plugins/types.ts +++ b/plugin-server/src/cdp/legacy-plugins/types.ts @@ -1,4 +1,4 @@ -import { ProcessedPluginEvent } from '@posthog/plugin-scaffold' +import { PluginConfigSchema, ProcessedPluginEvent } from '@posthog/plugin-scaffold' import { Response, trackedFetch } from '~/src/utils/fetch' @@ -19,6 +19,10 @@ export type LegacyPluginMeta = { export type LegacyPlugin = { id: string + metadata: { + name: string + config: PluginConfigSchema[] + } onEvent(event: ProcessedPluginEvent, meta: LegacyPluginMeta): Promise setupPlugin?: (meta: LegacyPluginMeta) => Promise } diff --git a/plugin-server/src/cdp/services/hog-executor.service.ts b/plugin-server/src/cdp/services/hog-executor.service.ts index 9962d51e03b24..5514d03206d28 100644 --- a/plugin-server/src/cdp/services/hog-executor.service.ts +++ b/plugin-server/src/cdp/services/hog-executor.service.ts @@ -93,7 +93,7 @@ export const formatInput = (bytecode: any, globals: HogFunctionInvocation['globa } } -const sanitizeLogMessage = (args: any[], sensitiveValues?: string[]): string => { +export const sanitizeLogMessage = (args: any[], sensitiveValues?: string[]): string => { let message = args.map((arg) => (typeof arg !== 'string' ? JSON.stringify(arg) : arg)).join(', ') // Find and replace any sensitive values From b0ba1888e9fb4d8815b538a4c86409aa2d183e04 Mon Sep 17 00:00:00 2001 From: Ben White Date: Tue, 28 Jan 2025 10:05:13 +0100 Subject: [PATCH 040/163] Fixes --- .../__snapshots__/cdp-cyclotron-plugins-worker.test.ts.snap | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugin-server/src/cdp/consumers/__snapshots__/cdp-cyclotron-plugins-worker.test.ts.snap b/plugin-server/src/cdp/consumers/__snapshots__/cdp-cyclotron-plugins-worker.test.ts.snap index 4a2036225a8b3..1211a43ef7591 100644 --- a/plugin-server/src/cdp/consumers/__snapshots__/cdp-cyclotron-plugins-worker.test.ts.snap +++ b/plugin-server/src/cdp/consumers/__snapshots__/cdp-cyclotron-plugins-worker.test.ts.snap @@ -56,7 +56,7 @@ exports[`CdpCyclotronWorkerPlugins smoke tests should run the plugin: { name: 'c }, { "level": "debug", - "message": "test@posthog.com", + "message": "Detected email:, test@posthog.com", }, { "level": "debug", @@ -64,7 +64,7 @@ exports[`CdpCyclotronWorkerPlugins smoke tests should run the plugin: { name: 'c }, { "level": "debug", - "message": "true", + "message": "Should customer be tracked:, true", }, { "level": "debug", From 2de9d40339b8aaf6e54d03448c5eaaf00166db09 Mon Sep 17 00:00:00 2001 From: Ben White Date: Tue, 28 Jan 2025 10:09:01 +0100 Subject: [PATCH 041/163] Fixes --- posthog/cdp/migrations.py | 7 ++++++- posthog/models/plugin.py | 18 ++++++++++++------ 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/posthog/cdp/migrations.py b/posthog/cdp/migrations.py index eee11f0e08011..5156c5e496aac 100644 --- a/posthog/cdp/migrations.py +++ b/posthog/cdp/migrations.py @@ -20,8 +20,13 @@ def migrate_legacy_plugins(dry_run=True, team_ids=None, test_mode=True): continue print("Attempting to migrate plugin", plugin_config) # noqa: T201 + url: str = plugin_config.plugin.url or "" - plugin_id = plugin_config.plugin.url.replace("inline://", "").replace("https://github.com/PostHog/", "") + if not url: + print("Skipping plugin", plugin_config.plugin.name, "as it doesn't have a url") # noqa: T201 + continue + + plugin_id = url.replace("inline://", "").replace("https://github.com/PostHog/", "") plugin_name = plugin_config.plugin.name if test_mode: diff --git a/posthog/models/plugin.py b/posthog/models/plugin.py index a11295e217bc7..a01a7347efb17 100644 --- a/posthog/models/plugin.py +++ b/posthog/models/plugin.py @@ -276,13 +276,19 @@ class PluginAttachment(models.Model): contents = models.BinaryField() def parse_contents(self) -> str | None: - if self.content_type == "application/json": - return json.loads(self.contents) + contents: bytes | None = self.contents + if not contents: + return None - if self.content_type == "text/plain": - return self.contents.decode("utf-8") - - return None + try: + if self.content_type == "application/json": + return json.loads(contents) + + if self.content_type == "text/plain": + return contents.decode("utf-8") + return None + except Exception: + return None class PluginStorage(models.Model): From cde1d59bb96f754d2d0caf3fe0ecdb0e04b8ffe3 Mon Sep 17 00:00:00 2001 From: Ben White Date: Tue, 28 Jan 2025 10:17:38 +0100 Subject: [PATCH 042/163] Fixes --- .../src/cdp/consumers/cdp-function-callback.consumer.ts | 0 .../src/cdp/consumers/cdp-processed-events.consumer.ts | 6 +++--- plugin-server/src/cdp/legacy-plugins/customerio/index.ts | 3 ++- plugin-server/src/cdp/legacy-plugins/intercom/index.ts | 3 ++- 4 files changed, 7 insertions(+), 5 deletions(-) delete mode 100644 plugin-server/src/cdp/consumers/cdp-function-callback.consumer.ts diff --git a/plugin-server/src/cdp/consumers/cdp-function-callback.consumer.ts b/plugin-server/src/cdp/consumers/cdp-function-callback.consumer.ts deleted file mode 100644 index e69de29bb2d1d..0000000000000 diff --git a/plugin-server/src/cdp/consumers/cdp-processed-events.consumer.ts b/plugin-server/src/cdp/consumers/cdp-processed-events.consumer.ts index 67b434c9979b2..5716e1bea0407 100644 --- a/plugin-server/src/cdp/consumers/cdp-processed-events.consumer.ts +++ b/plugin-server/src/cdp/consumers/cdp-processed-events.consumer.ts @@ -1,13 +1,13 @@ import { CyclotronManager } from '@posthog/cyclotron' import { Message } from 'node-rdkafka' -import { Hub, RawClickHouseEvent } from '~/src/types' - import { convertToHogFunctionInvocationGlobals, fixLogDeduplication, serializeHogFunctionInvocation, -} from '../../cdp/utils' +} from '~/src/cdp/utils' +import { Hub, RawClickHouseEvent } from '~/src/types' + import { KAFKA_EVENTS_JSON, KAFKA_LOG_ENTRIES } from '../../config/kafka-topics' import { runInstrumentedFunction } from '../../main/utils' import { status } from '../../utils/status' diff --git a/plugin-server/src/cdp/legacy-plugins/customerio/index.ts b/plugin-server/src/cdp/legacy-plugins/customerio/index.ts index 7ec1e99ec92f5..684deb0c22d5e 100644 --- a/plugin-server/src/cdp/legacy-plugins/customerio/index.ts +++ b/plugin-server/src/cdp/legacy-plugins/customerio/index.ts @@ -4,6 +4,7 @@ import { RetryError } from '@posthog/plugin-scaffold' import { Response } from '~/src/utils/fetch' import { LegacyPlugin, LegacyPluginMeta } from '../types' +import metadata from './plugin.json' const DEFAULT_HOST = 'track.customer.io' const DEFAULT_SEND_EVENTS_FROM_ANONYMOUS_USERS = 'Send all events' @@ -256,7 +257,7 @@ function getEmailFromEvent(event: ProcessedPluginEvent): string | null { export const customerioPlugin: LegacyPlugin = { id: 'customer-io', - metadata: require('./plugin.json'), + metadata: metadata as any, setupPlugin: setupPlugin as any, onEvent, } diff --git a/plugin-server/src/cdp/legacy-plugins/intercom/index.ts b/plugin-server/src/cdp/legacy-plugins/intercom/index.ts index c01adbb7e46fa..c2eb3b5319841 100644 --- a/plugin-server/src/cdp/legacy-plugins/intercom/index.ts +++ b/plugin-server/src/cdp/legacy-plugins/intercom/index.ts @@ -3,6 +3,7 @@ import { ProcessedPluginEvent, RetryError } from '@posthog/plugin-scaffold' import { Response } from '~/src/utils/fetch' import { LegacyPlugin, LegacyPluginMeta } from '../types' +import metadata from './plugin.json' type IntercomMeta = LegacyPluginMeta & { global: { @@ -190,7 +191,7 @@ function getTimestamp(meta: IntercomMeta, event: ProcessedPluginEvent): number { export const intercomPlugin: LegacyPlugin = { id: 'intercom', - metadata: require('./plugin.json'), + metadata: metadata as any, onEvent, setupPlugin: () => Promise.resolve(), } From 1e954b235715db86fd917a81ff366c84da7dd73c Mon Sep 17 00:00:00 2001 From: Ben White Date: Tue, 28 Jan 2025 10:34:13 +0100 Subject: [PATCH 043/163] Fixes --- .../cdp-cyclotron-plugins-worker.consumer.ts | 13 +++---------- .../consumers/cdp-cyclotron-plugins-worker.test.ts | 10 +++++----- .../cdp/consumers/cdp-cyclotron-worker.consumer.ts | 6 ++++-- 3 files changed, 12 insertions(+), 17 deletions(-) diff --git a/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.ts b/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.ts index ba4e333d23d6c..877451bc4417a 100644 --- a/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.ts +++ b/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.ts @@ -1,9 +1,8 @@ import { ProcessedPluginEvent, RetryError } from '@posthog/plugin-scaffold' import { DateTime } from 'luxon' -import { Response, trackedFetch } from '~/src/utils/fetch' -import { status } from '~/src/utils/status' - +import { Response, trackedFetch } from '../../utils/fetch' +import { status } from '../../utils/status' import { PLUGINS_BY_ID } from '../legacy-plugins' import { LegacyPluginLogger, LegacyPluginMeta } from '../legacy-plugins/types' import { sanitizeLogMessage } from '../services/hog-executor.service' @@ -27,13 +26,7 @@ export class CdpCyclotronWorkerPlugins extends CdpCyclotronWorker { private pluginState: Record = {} public async processInvocations(invocations: HogFunctionInvocation[]): Promise { - const results = await this.runManyWithHeartbeat(invocations, (item) => this.executePluginInvocation(item)) - - await this.processInvocationResults(results) - await this.updateJobs(results) - await this.produceQueuedMessages() - - return results + return await this.runManyWithHeartbeat(invocations, (item) => this.executePluginInvocation(item)) } public async fetch(...args: Parameters): Promise { diff --git a/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.test.ts b/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.test.ts index fedbe3ebe84db..8d501a550c182 100644 --- a/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.test.ts +++ b/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.test.ts @@ -107,7 +107,7 @@ describe('CdpCyclotronWorkerPlugins', () => { it('should setup a plugin on first call', async () => { jest.spyOn(PLUGINS_BY_ID['intercom'] as any, 'setupPlugin') - const results = processor.processInvocations([ + const results = processor.processBatch([ createInvocation(fn, globals), createInvocation(fn, globals), createInvocation(fn, globals), @@ -152,7 +152,7 @@ describe('CdpCyclotronWorkerPlugins', () => { json: () => Promise.resolve({ total_count: 1 }), }) - await processor.processInvocations([invocation]) + await processor.processBatch([invocation]) expect(PLUGINS_BY_ID['intercom'].onEvent).toHaveBeenCalledTimes(1) expect(forSnapshot(jest.mocked(PLUGINS_BY_ID['intercom'].onEvent!).mock.calls[0][0])) @@ -228,7 +228,7 @@ describe('CdpCyclotronWorkerPlugins', () => { email: 'test@posthog.com', } - await processor.processInvocations([invocation]) + await processor.processBatch([invocation]) expect(mockFetch).toHaveBeenCalledTimes(0) @@ -256,7 +256,7 @@ describe('CdpCyclotronWorkerPlugins', () => { mockFetch.mockRejectedValue(new Error('Test error')) - const res = await processor.processInvocations([invocation]) + const res = await processor.processBatch([invocation]) expect(PLUGINS_BY_ID['intercom'].onEvent).toHaveBeenCalledTimes(1) @@ -308,7 +308,7 @@ describe('CdpCyclotronWorkerPlugins', () => { } invocation.hogFunction.name = name - await processor.processInvocations([invocation]) + await processor.processBatch([invocation]) expect( forSnapshot( diff --git a/plugin-server/src/cdp/consumers/cdp-cyclotron-worker.consumer.ts b/plugin-server/src/cdp/consumers/cdp-cyclotron-worker.consumer.ts index 185d186a9f2b8..a43930ec547f8 100644 --- a/plugin-server/src/cdp/consumers/cdp-cyclotron-worker.consumer.ts +++ b/plugin-server/src/cdp/consumers/cdp-cyclotron-worker.consumer.ts @@ -20,9 +20,9 @@ export class CdpCyclotronWorker extends CdpConsumerBase { return await this.runManyWithHeartbeat(invocations, (item) => this.hogExecutor.execute(item)) } - public async processBatch(invocations: HogFunctionInvocation[]): Promise { + public async processBatch(invocations: HogFunctionInvocation[]): Promise { if (!invocations.length) { - return + return [] } const invocationResults = await runInstrumentedFunction({ @@ -33,6 +33,8 @@ export class CdpCyclotronWorker extends CdpConsumerBase { await this.processInvocationResults(invocationResults) await this.updateJobs(invocationResults) await this.produceQueuedMessages() + + return invocationResults } protected async updateJobs(invocations: HogFunctionInvocationResult[]) { From da407fd592dedd304e7cd8b7c1ed212d4fc0ac53 Mon Sep 17 00:00:00 2001 From: Ben White Date: Tue, 28 Jan 2025 11:27:47 +0100 Subject: [PATCH 044/163] Revert --- .../src/cdp/consumers/cdp-processed-events.consumer.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugin-server/src/cdp/consumers/cdp-processed-events.consumer.ts b/plugin-server/src/cdp/consumers/cdp-processed-events.consumer.ts index 5716e1bea0407..67b434c9979b2 100644 --- a/plugin-server/src/cdp/consumers/cdp-processed-events.consumer.ts +++ b/plugin-server/src/cdp/consumers/cdp-processed-events.consumer.ts @@ -1,13 +1,13 @@ import { CyclotronManager } from '@posthog/cyclotron' import { Message } from 'node-rdkafka' +import { Hub, RawClickHouseEvent } from '~/src/types' + import { convertToHogFunctionInvocationGlobals, fixLogDeduplication, serializeHogFunctionInvocation, -} from '~/src/cdp/utils' -import { Hub, RawClickHouseEvent } from '~/src/types' - +} from '../../cdp/utils' import { KAFKA_EVENTS_JSON, KAFKA_LOG_ENTRIES } from '../../config/kafka-topics' import { runInstrumentedFunction } from '../../main/utils' import { status } from '../../utils/status' From ab8757bf83b62c25e2ad6240900ec5378253295e Mon Sep 17 00:00:00 2001 From: Ben White Date: Tue, 28 Jan 2025 11:53:44 +0100 Subject: [PATCH 045/163] Fixes --- plugin-server/src/main/pluginsServer.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/plugin-server/src/main/pluginsServer.ts b/plugin-server/src/main/pluginsServer.ts index 274e3231e35f3..6807430d9e931 100644 --- a/plugin-server/src/main/pluginsServer.ts +++ b/plugin-server/src/main/pluginsServer.ts @@ -550,9 +550,13 @@ export async function startPluginsServer( if (capabilities.cdpCyclotronWorkerPlugins) { const hub = await setupHub() - const worker = new CdpCyclotronWorkerPlugins(hub) - await worker.start() - services.push(worker.service) + if (!hub.CYCLOTRON_DATABASE_URL) { + status.error('💥', 'Cyclotron database URL not set.') + } else { + const worker = new CdpCyclotronWorkerPlugins(hub) + await worker.start() + services.push(worker.service) + } } if (capabilities.http) { From 8ebb8a983b1e066a36870e2d439d399b2abfea87 Mon Sep 17 00:00:00 2001 From: Ben White Date: Tue, 28 Jan 2025 12:04:21 +0100 Subject: [PATCH 046/163] Remove old secrets --- .vscode/launch.json | 2 -- plugin-server/src/config/config.ts | 1 - plugin-server/src/types.ts | 1 - 3 files changed, 4 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index dd756e6acc43d..d8b8160204498 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -143,8 +143,6 @@ "WORKER_CONCURRENCY": "2", "OBJECT_STORAGE_ENABLED": "True", "HOG_HOOK_URL": "http://localhost:3300/hoghook", - "CDP_ASYNC_FUNCTIONS_RUSTY_HOOK_TEAMS": "", - "CDP_CYCLOTRON_ENABLED_TEAMS": "*", "PLUGIN_SERVER_MODE": "all-v2" }, "presentation": { diff --git a/plugin-server/src/config/config.ts b/plugin-server/src/config/config.ts index 5de8a99a0c74c..447c921c645ae 100644 --- a/plugin-server/src/config/config.ts +++ b/plugin-server/src/config/config.ts @@ -187,7 +187,6 @@ export function getDefaultConfig(): PluginsServerConfig { CDP_WATCHER_TTL: 60 * 60 * 24, // This is really long as it is essentially only important to make sure the key is eventually deleted CDP_WATCHER_REFILL_RATE: 10, CDP_WATCHER_DISABLED_TEMPORARY_MAX_COUNT: 3, - CDP_ASYNC_FUNCTIONS_RUSTY_HOOK_TEAMS: '', CDP_HOG_FILTERS_TELEMETRY_TEAMS: '', CDP_REDIS_PASSWORD: '', CDP_EVENT_PROCESSOR_EXECUTE_FIRST_STEP: true, diff --git a/plugin-server/src/types.ts b/plugin-server/src/types.ts index 5682796f0817c..ec42460600740 100644 --- a/plugin-server/src/types.ts +++ b/plugin-server/src/types.ts @@ -119,7 +119,6 @@ export type CdpConfig = { CDP_WATCHER_REFILL_RATE: number // The number of tokens to be refilled per second CDP_WATCHER_DISABLED_TEMPORARY_TTL: number // How long a function should be temporarily disabled for CDP_WATCHER_DISABLED_TEMPORARY_MAX_COUNT: number // How many times a function can be disabled before it is disabled permanently - CDP_ASYNC_FUNCTIONS_RUSTY_HOOK_TEAMS: string CDP_HOG_FILTERS_TELEMETRY_TEAMS: string CDP_CYCLOTRON_BATCH_SIZE: number CDP_CYCLOTRON_BATCH_DELAY_MS: number From c7d34249e32ec47ed087103acef5ae9f12e6927e Mon Sep 17 00:00:00 2001 From: Ben White Date: Tue, 28 Jan 2025 12:19:39 +0100 Subject: [PATCH 047/163] Fixes --- .../cdp-cyclotron-plugins-worker.consumer.ts | 178 +--------------- .../cdp-cyclotron-plugins-worker.test.ts | 2 +- .../legacy-plugin-executor.service.ts | 193 ++++++++++++++++++ 3 files changed, 202 insertions(+), 171 deletions(-) create mode 100644 plugin-server/src/cdp/services/legacy-plugin-executor.service.ts diff --git a/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.ts b/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.ts index 877451bc4417a..36edf264ab82b 100644 --- a/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.ts +++ b/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.ts @@ -1,20 +1,9 @@ -import { ProcessedPluginEvent, RetryError } from '@posthog/plugin-scaffold' -import { DateTime } from 'luxon' +import { Hub } from '~/src/types' -import { Response, trackedFetch } from '../../utils/fetch' -import { status } from '../../utils/status' -import { PLUGINS_BY_ID } from '../legacy-plugins' -import { LegacyPluginLogger, LegacyPluginMeta } from '../legacy-plugins/types' -import { sanitizeLogMessage } from '../services/hog-executor.service' +import { LegacyPluginExecutorService } from '../services/legacy-plugin-executor.service' import { HogFunctionInvocation, HogFunctionInvocationResult, HogFunctionTypeType } from '../types' import { CdpCyclotronWorker } from './cdp-cyclotron-worker.consumer' -type PluginState = { - setupPromise: Promise - errored: boolean - meta: LegacyPluginMeta -} - /** * NOTE: This is a consumer to take care of legacy plugins. */ @@ -22,165 +11,14 @@ export class CdpCyclotronWorkerPlugins extends CdpCyclotronWorker { protected name = 'CdpCyclotronWorkerPlugins' protected queue = 'plugin' as const protected hogTypes: HogFunctionTypeType[] = ['destination'] + private pluginExecutor: LegacyPluginExecutorService - private pluginState: Record = {} - - public async processInvocations(invocations: HogFunctionInvocation[]): Promise { - return await this.runManyWithHeartbeat(invocations, (item) => this.executePluginInvocation(item)) - } - - public async fetch(...args: Parameters): Promise { - return trackedFetch(...args) + constructor(hub: Hub) { + super(hub) + this.pluginExecutor = new LegacyPluginExecutorService(hub) } - public async executePluginInvocation(invocation: HogFunctionInvocation): Promise { - const result: HogFunctionInvocationResult = { - invocation, - finished: true, - capturedPostHogEvents: [], - logs: [], - } - - const pluginId = invocation.hogFunction.template_id?.startsWith('plugin-') - ? invocation.hogFunction.template_id.replace('plugin-', '') - : null - - const isTestFunction = invocation.hogFunction.name.includes('[CDP-TEST-HIDDEN]') - - result.logs.push({ - level: 'debug', - timestamp: DateTime.now(), - message: `Executing plugin ${pluginId}`, - }) - const plugin = pluginId ? PLUGINS_BY_ID[pluginId] : null - - if (!plugin || !pluginId) { - result.error = new Error(`Plugin ${pluginId} not found`) - result.logs.push({ - level: 'error', - timestamp: DateTime.now(), - message: `Plugin ${pluginId} not found`, - }) - return result - } - - const addLog = (level: 'debug' | 'warn' | 'error' | 'info', ...args: any[]) => { - result.logs.push({ - level, - timestamp: DateTime.now(), - message: sanitizeLogMessage(args), - }) - } - - const logger: LegacyPluginLogger = { - debug: (...args: any[]) => addLog('debug', ...args), - warn: (...args: any[]) => addLog('warn', ...args), - log: (...args: any[]) => addLog('info', ...args), - error: (...args: any[]) => addLog('error', ...args), - } - - let state = this.pluginState[pluginId] - - const fetch = (...args: Parameters): Promise => { - if (isTestFunction) { - addLog('info', 'Fetch called but mocked due to test function') - return Promise.resolve({ - status: 500, - json: () => - Promise.resolve({ - message: 'Test function', - }), - } as Response) - } - return this.fetch(...args) - } - - if (!state) { - // TODO: Modify fetch to be a silent log if it is a test function... - const meta: LegacyPluginMeta = { - config: invocation.globals.inputs, - global: {}, - fetch, - logger: logger, - } - - state = this.pluginState[pluginId] = { - setupPromise: plugin.setupPlugin?.(meta) ?? Promise.resolve(), - meta, - errored: false, - } - } - - try { - await state.setupPromise - } catch (e) { - state.errored = true - result.error = e - result.logs.push({ - level: 'error', - timestamp: DateTime.now(), - message: `Plugin ${pluginId} setup failed: ${e.message}`, - }) - return result - } - - // Convert the invocation into the right interface for the plugin - - const event: ProcessedPluginEvent = { - distinct_id: invocation.globals.event.distinct_id, - ip: invocation.globals.event.properties.$ip, - team_id: invocation.hogFunction.team_id, - event: invocation.globals.event.event, - properties: invocation.globals.event.properties, - timestamp: invocation.globals.event.timestamp, - $set: invocation.globals.event.properties.$set, - $set_once: invocation.globals.event.properties.$set_once, - uuid: invocation.globals.event.uuid, - person: invocation.globals.person - ? { - uuid: invocation.globals.person.id, - team_id: invocation.hogFunction.team_id, - properties: invocation.globals.person.properties, - created_at: '', // NOTE: We don't have this anymore - see if any plugin uses it... - } - : undefined, - } - - try { - status.info('⚡️', 'Executing plugin', { - pluginId, - invocationId: invocation.id, - }) - await plugin.onEvent?.(event, { - ...state.meta, - // NOTE: We override logger and fetch here so we can track the calls - logger, - fetch, - }) - result.logs.push({ - level: 'debug', - timestamp: DateTime.now(), - message: `Execution successful`, - }) - } catch (e) { - if (e instanceof RetryError) { - // NOTE: Schedule as a retry to cyclotron? - } - - status.error('💩', 'Plugin errored', { - error: e, - pluginId, - invocationId: invocation.id, - }) - - result.error = e - result.logs.push({ - level: 'error', - timestamp: DateTime.now(), - message: `Plugin errored: ${e.message}`, - }) - } - - return result + public async processInvocations(invocations: HogFunctionInvocation[]): Promise { + return await this.runManyWithHeartbeat(invocations, (item) => this.pluginExecutor.execute(item)) } } diff --git a/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.test.ts b/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.test.ts index 8d501a550c182..840a1c1f8f2db 100644 --- a/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.test.ts +++ b/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.test.ts @@ -46,7 +46,7 @@ describe('CdpCyclotronWorkerPlugins', () => { await processor.start() - processor.fetch = mockFetch = jest.fn(() => + processor['pluginExecutor'].fetch = mockFetch = jest.fn(() => Promise.resolve({ status: 200, json: () => diff --git a/plugin-server/src/cdp/services/legacy-plugin-executor.service.ts b/plugin-server/src/cdp/services/legacy-plugin-executor.service.ts new file mode 100644 index 0000000000000..c0d77f8386aed --- /dev/null +++ b/plugin-server/src/cdp/services/legacy-plugin-executor.service.ts @@ -0,0 +1,193 @@ +import { ProcessedPluginEvent, RetryError } from '@posthog/plugin-scaffold' +import { DateTime } from 'luxon' +import { Histogram } from 'prom-client' + +import { Hub } from '../../types' +import { Response, trackedFetch } from '../../utils/fetch' +import { status } from '../../utils/status' +import { PLUGINS_BY_ID } from '../legacy-plugins' +import { LegacyPluginLogger, LegacyPluginMeta } from '../legacy-plugins/types' +import { sanitizeLogMessage } from '../services/hog-executor.service' +import { HogFunctionInvocation, HogFunctionInvocationResult } from '../types' + +const pluginExecutionDuration = new Histogram({ + name: 'cdp_plugin_execution_duration_ms', + help: 'Processing time and success status of plugins', + // We have a timeout so we don't need to worry about much more than that + buckets: [0, 10, 20, 50, 100, 200], +}) + +export type PluginState = { + setupPromise: Promise + errored: boolean + meta: LegacyPluginMeta +} + +/** + * NOTE: This is a consumer to take care of legacy plugins. + */ +export class LegacyPluginExecutorService { + constructor(private hub: Hub) {} + + private pluginState: Record = {} + + public async fetch(...args: Parameters): Promise { + return trackedFetch(...args) + } + + public async execute(invocation: HogFunctionInvocation): Promise { + const result: HogFunctionInvocationResult = { + invocation, + finished: true, + capturedPostHogEvents: [], + logs: [], + } + + const pluginId = invocation.hogFunction.template_id?.startsWith('plugin-') + ? invocation.hogFunction.template_id.replace('plugin-', '') + : null + + const isTestFunction = invocation.hogFunction.name.includes('[CDP-TEST-HIDDEN]') + + result.logs.push({ + level: 'debug', + timestamp: DateTime.now(), + message: `Executing plugin ${pluginId}`, + }) + const plugin = pluginId ? PLUGINS_BY_ID[pluginId] : null + + if (!plugin || !pluginId) { + result.error = new Error(`Plugin ${pluginId} not found`) + result.logs.push({ + level: 'error', + timestamp: DateTime.now(), + message: `Plugin ${pluginId} not found`, + }) + return result + } + + const addLog = (level: 'debug' | 'warn' | 'error' | 'info', ...args: any[]) => { + result.logs.push({ + level, + timestamp: DateTime.now(), + message: sanitizeLogMessage(args), + }) + } + + const logger: LegacyPluginLogger = { + debug: (...args: any[]) => addLog('debug', ...args), + warn: (...args: any[]) => addLog('warn', ...args), + log: (...args: any[]) => addLog('info', ...args), + error: (...args: any[]) => addLog('error', ...args), + } + + let state = this.pluginState[pluginId] + + const fetch = (...args: Parameters): Promise => { + if (isTestFunction) { + addLog('info', 'Fetch called but mocked due to test function') + return Promise.resolve({ + status: 500, + json: () => + Promise.resolve({ + message: 'Test function', + }), + } as Response) + } + return this.fetch(...args) + } + + if (!state) { + // TODO: Modify fetch to be a silent log if it is a test function... + const meta: LegacyPluginMeta = { + config: invocation.globals.inputs, + global: {}, + fetch, + logger: logger, + } + + state = this.pluginState[pluginId] = { + setupPromise: plugin.setupPlugin?.(meta) ?? Promise.resolve(), + meta, + errored: false, + } + } + + try { + await state.setupPromise + } catch (e) { + state.errored = true + result.error = e + result.logs.push({ + level: 'error', + timestamp: DateTime.now(), + message: `Plugin ${pluginId} setup failed: ${e.message}`, + }) + return result + } + + // Convert the invocation into the right interface for the plugin + + const event: ProcessedPluginEvent = { + distinct_id: invocation.globals.event.distinct_id, + ip: invocation.globals.event.properties.$ip, + team_id: invocation.hogFunction.team_id, + event: invocation.globals.event.event, + properties: invocation.globals.event.properties, + timestamp: invocation.globals.event.timestamp, + $set: invocation.globals.event.properties.$set, + $set_once: invocation.globals.event.properties.$set_once, + uuid: invocation.globals.event.uuid, + person: invocation.globals.person + ? { + uuid: invocation.globals.person.id, + team_id: invocation.hogFunction.team_id, + properties: invocation.globals.person.properties, + created_at: '', // NOTE: We don't have this anymore - see if any plugin uses it... + } + : undefined, + } + + const start = performance.now() + + try { + status.info('⚡️', 'Executing plugin', { + pluginId, + invocationId: invocation.id, + }) + + await plugin.onEvent?.(event, { + ...state.meta, + // NOTE: We override logger and fetch here so we can track the calls + logger, + fetch, + }) + result.logs.push({ + level: 'debug', + timestamp: DateTime.now(), + message: `Execution successful`, + }) + } catch (e) { + if (e instanceof RetryError) { + // NOTE: Schedule as a retry to cyclotron? + } + + status.error('💩', 'Plugin errored', { + error: e, + pluginId, + invocationId: invocation.id, + }) + + result.error = e + result.logs.push({ + level: 'error', + timestamp: DateTime.now(), + message: `Plugin errored: ${e.message}`, + }) + } finally { + pluginExecutionDuration.observe(performance.now() - start) + } + + return result + } +} From e229857edfb24daf8a9cafc3b9b7ef99624d28a5 Mon Sep 17 00:00:00 2001 From: Ben White Date: Tue, 28 Jan 2025 12:47:22 +0100 Subject: [PATCH 048/163] Added a transformation --- .../posthog-filter-out-plugin/index.test.ts | 256 ++++++++++++++++++ .../posthog-filter-out-plugin/index.ts | 126 +++++++++ .../posthog-filter-out-plugin/plugin.json | 32 +++ plugin-server/src/cdp/legacy-plugins/types.ts | 10 + 4 files changed, 424 insertions(+) create mode 100644 plugin-server/src/cdp/legacy-plugins/posthog-filter-out-plugin/index.test.ts create mode 100644 plugin-server/src/cdp/legacy-plugins/posthog-filter-out-plugin/index.ts create mode 100644 plugin-server/src/cdp/legacy-plugins/posthog-filter-out-plugin/plugin.json diff --git a/plugin-server/src/cdp/legacy-plugins/posthog-filter-out-plugin/index.test.ts b/plugin-server/src/cdp/legacy-plugins/posthog-filter-out-plugin/index.test.ts new file mode 100644 index 0000000000000..82d4878d59bc5 --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/posthog-filter-out-plugin/index.test.ts @@ -0,0 +1,256 @@ +import { PluginEvent } from '@posthog/plugin-scaffold' + +import { LegacyPluginMeta } from '../types' +import { Filter, processEvent, setupPlugin } from '.' + +const filters: Filter[] = [ + { + property: '$host', + type: 'string', + operator: 'not_contains', + value: 'localhost', + }, + { + property: 'foo', + type: 'number', + operator: 'gt', + value: 10, + }, + { + property: 'bar', + type: 'boolean', + operator: 'is', + value: true, + }, +] + +const createEvent = (event: Partial): PluginEvent => { + return { + uuid: '123', + event: 'test event', + properties: {}, + now: '2025-01-01T00:00:00Z', + distinct_id: '123', + ip: '123', + site_url: '123', + team_id: 123, + ...event, + } +} + +const meta = { + global: { filters, eventsToDrop: ['to_drop_event'] }, +} as unknown as LegacyPluginMeta + +test('Event satisfies all conditions and passes', () => { + const event = createEvent({ + event: 'test event', + properties: { + $host: 'example.com', + foo: 20, + bar: true, + }, + }) as unknown as PluginEvent + const processedEvent = processEvent(event, meta) + expect(processedEvent).toEqual(event) +}) + +test('Event does not satisfy one condition and is dropped', () => { + const event = createEvent({ + event: 'test event', + properties: { + $host: 'localhost:8000', + foo: 20, + bar: true, + }, + }) as unknown as PluginEvent + const processedEvent = processEvent(event, meta) + expect(processedEvent).toBeUndefined() +}) + +test('Event does not satisfy any condition and is dropped', () => { + const event = createEvent({ + event: 'test event', + properties: { + $host: 'localhost:8000', + foo: 5, + bar: false, + }, + }) as unknown as PluginEvent + const processedEvent = processEvent(event, meta) + expect(processedEvent).toBeUndefined() +}) + +test('Event is marked to be dropped is dropped', () => { + const event = createEvent({ + event: 'to_drop_event', + properties: { + $host: 'example.com', + foo: 20, + bar: true, + }, + }) as unknown as PluginEvent + const processedEvent = processEvent(event, meta) + expect(processedEvent).toBeUndefined() +}) + +test('Event is marked to be dropped when a property is undefined', () => { + const event = createEvent({ + event: 'test_event', + properties: { + $host: undefined, + foo: 20, + bar: true, + }, + }) as unknown as PluginEvent + const processedEvent = processEvent(event, meta) + expect(processedEvent).toBeUndefined() +}) + +test('Event is marked to be dropped when a property is undefined but keepUndefinedProperties', () => { + const event = createEvent({ + event: 'test_event', + properties: { + $host: undefined, + foo: 20, + bar: true, + }, + }) as unknown as PluginEvent + const processedEvent = processEvent(event, { + global: { ...meta.global, keepUndefinedProperties: true }, + } as unknown as LegacyPluginMeta) + expect(processedEvent).toEqual(event) +}) + +function setup(config: any) { + const global: any = {} + + setupPlugin({ + global, + config: { + ...config, + filters, + }, + } as any) + + return global +} + +test('setupPlugin() parsing eventsToDrop', () => { + expect(setup({ eventsToDrop: 'foo, bar ' }).eventsToDrop).toEqual(['foo', 'bar']) + expect(setup({ eventsToDrop: '$foo,$bar' }).eventsToDrop).toEqual(['$foo', '$bar']) + expect(setup({}).eventsToDrop).toEqual([]) +}) + +test('setupPlugin() parsing keepUndefinedProperties', () => { + expect(setup({ keepUndefinedProperties: 'Yes' }).keepUndefinedProperties).toEqual(true) + expect(setup({ keepUndefinedProperties: 'No' }).keepUndefinedProperties).toEqual(false) + expect(setup({}).keepUndefinedProperties).toEqual(false) +}) + +describe('empty filters', () => { + const meta_no_filters = { + global: { filters: [], eventsToDrop: ['to_drop_event'] }, + } as unknown as LegacyPluginMeta + + test('Event satisfies all conditions and passes', () => { + const event = createEvent({ + event: 'test event', + properties: { + $host: 'example.com', + foo: 20, + bar: true, + }, + }) as unknown as PluginEvent + const processedEvent = processEvent(event, meta_no_filters) + expect(processedEvent).toEqual(event) + }) + + test('Event is marked to be dropped is dropped', () => { + const event = createEvent({ + event: 'to_drop_event', + properties: { + $host: 'example.com', + foo: 20, + bar: true, + }, + }) as unknown as PluginEvent + const processedEvent = processEvent(event, meta_no_filters) + expect(processedEvent).toBeUndefined() + }) + + test('setupPlugin() without any config works', () => { + const global: any = {} + setupPlugin({ config: {}, global, attachments: { filters: null } } as any) + expect(global.filters).toEqual([]) + expect(global.eventsToDrop).toEqual([]) + expect(global.keepUndefinedProperties).toEqual(false) + }) + + test('setupPlugin() with other config works', () => { + const global: any = {} + setupPlugin({ + config: { eventsToDrop: 'foo,bar', keepUndefinedProperties: 'Yes' }, + global, + attachments: { filters: null }, + } as any) + expect(global.filters).toEqual([]) + expect(global.eventsToDrop).toEqual(['foo', 'bar']) + expect(global.keepUndefinedProperties).toEqual(true) + }) +}) + +const filters_or: Filter[][] = [ + [ + { + property: '$host', + type: 'string', + operator: 'not_contains', + value: 'localhost', + }, + { + property: 'foo', + type: 'number', + operator: 'gt', + value: 10, + }, + ], + [ + { + property: 'bar', + type: 'boolean', + operator: 'is', + value: true, + }, + ], +] + +const meta_or = { + global: { filters: filters_or, eventsToDrop: ['to_drop_event'] }, +} as unknown as LegacyPluginMeta + +test('Event satisfies at least one filter group and passes', () => { + const event = createEvent({ + event: 'test event', + properties: { + $host: 'example.com', + foo: 5, + bar: true, + }, + }) as unknown as PluginEvent + const processedEvent = processEvent(event, meta_or) + expect(processedEvent).toEqual(event) +}) + +test('Event satisfies no filter groups and is dropped', () => { + const event = createEvent({ + event: 'test event', + properties: { + $host: 'localhost:8000', + foo: 5, + bar: false, + }, + }) as unknown as PluginEvent + const processedEvent = processEvent(event, meta_or) + expect(processedEvent).toBeUndefined() +}) diff --git a/plugin-server/src/cdp/legacy-plugins/posthog-filter-out-plugin/index.ts b/plugin-server/src/cdp/legacy-plugins/posthog-filter-out-plugin/index.ts new file mode 100644 index 0000000000000..5a7f4cea3ecbc --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/posthog-filter-out-plugin/index.ts @@ -0,0 +1,126 @@ +import { Meta, PluginAttachment, PluginEvent } from '@posthog/plugin-scaffold' + +import { LegacyPluginMeta } from '../types' + +export interface Filter { + property: string + type: 'string' | 'number' | 'boolean' + operator: string + value: string | number | boolean +} + +export type PluginMeta = Meta<{ + config: { + eventsToDrop?: string + keepUndefinedProperties?: 'Yes' | 'No' + } + global: { + filters: Filter[][] | Filter[] + eventsToDrop: string[] + keepUndefinedProperties?: boolean + } + attachments: { + filters?: PluginAttachment + } +}> + +const operations: Record boolean>> = { + string: { + is: (a, b) => a === b, + is_not: (a, b) => a !== b, + contains: (a, b) => a.includes(b), + not_contains: (a, b) => !a.includes(b), + regex: (a, b) => new RegExp(b).test(a), + not_regex: (a, b) => !new RegExp(b).test(a), + }, + number: { + gt: (a, b) => a > b, + lt: (a, b) => a < b, + gte: (a, b) => a >= b, + lte: (a, b) => a <= b, + eq: (a, b) => a === b, + neq: (a, b) => a !== b, + }, + boolean: { + is: (a, b) => a === b, + is_not: (a, b) => a !== b, + }, +} + +export function setupPlugin({ global, config }: LegacyPluginMeta) { + if (config.filters) { + try { + const filterGroups = parseFiltersAndMigrate(config.filters) + if (!filterGroups) { + throw new Error('No filters found') + } + + // Check if the filters are valid + for (const filters of filterGroups) { + for (const filter of filters) { + if (!operations[filter.type][filter.operator]) { + throw new Error( + `Invalid operator "${filter.operator}" for type "${filter.type}" in filter for "${filter.property}"` + ) + } + } + } + // Save the filters to the global object + global.filters = filterGroups + } catch (err) { + throw new Error('Could not parse filters attachment: ' + err.message) + } + } else { + global.filters = [] + } + global.eventsToDrop = config?.eventsToDrop?.split(',')?.map((event: string) => event.trim()) || [] + + global.keepUndefinedProperties = config.keepUndefinedProperties === 'Yes' +} + +export function processEvent(event: PluginEvent, meta: LegacyPluginMeta): PluginEvent | undefined { + if (!event.properties) { + return event + } + const { eventsToDrop, keepUndefinedProperties } = meta.global + const filters = parseFiltersAndMigrate(meta.global.filters) + + // If the event name matches, we drop the event + if (eventsToDrop.some((e: any) => event.event === e)) { + return undefined + } + + // Check if the event satisfies any of the filter groups (OR logic between groups) + const keepEvent = filters.some((filterGroup) => + // Check if all filters in the group are satisfied (AND logic within group) + filterGroup.every((filter) => { + const value = event.properties?.[filter.property] + if (value === undefined) { + return keepUndefinedProperties + } + + const operation = operations[filter.type][filter.operator] + if (!operation) { + throw new Error(`Invalid operator ${filter.operator}`) + } + + return operation(value, filter.value) + }) + ) + + // If should keep the event, return it, else return undefined + return keepEvent ? event : undefined +} + +const parseFiltersAndMigrate = (filters: Filter[][] | Filter[]): Filter[][] => { + if (!Array.isArray(filters)) { + throw new Error('No filters found') + } + + // Handle legacy format: Convert single filter array to nested array + // to maintain backwards compatibility with older plugin versions that used a single array of filters with "AND" logic + if (filters.length === 0 || !Array.isArray(filters[0])) { + return [filters as Filter[]] + } + return filters as Filter[][] +} diff --git a/plugin-server/src/cdp/legacy-plugins/posthog-filter-out-plugin/plugin.json b/plugin-server/src/cdp/legacy-plugins/posthog-filter-out-plugin/plugin.json new file mode 100644 index 0000000000000..5421131984fd5 --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/posthog-filter-out-plugin/plugin.json @@ -0,0 +1,32 @@ +{ + "name": "Filter Out Plugin", + "url": "https://github.com/plibither8/posthog-filter-out-plugin", + "description": "Filter out events where property values satisfy the given condition", + "main": "src/main.ts", + "config": [ + { + "markdown": "All filters must adhere to the JSON schema specified in the project's [README](https://github.com/plibither8/posthog-filter-out-plugin)." + }, + { + "key": "filters", + "name": "Filters to apply", + "type": "attachment", + "hint": "A JSON file containing an array of filters to apply. See the README for more information.", + "required": false + }, + { + "key": "eventsToDrop", + "name": "Events to filter out", + "type": "string", + "hint": "A comma-separated list of event names to filter out (e.g. $pageview,$autocapture)", + "required": false + }, + { + "key": "keepUndefinedProperties", + "name": "Keep event if any of the filtered properties are undefined?", + "type": "choice", + "choices": ["Yes", "No"], + "default": "No" + } + ] +} diff --git a/plugin-server/src/cdp/legacy-plugins/types.ts b/plugin-server/src/cdp/legacy-plugins/types.ts index f9df0a48e7ad1..3a075c8667f0d 100644 --- a/plugin-server/src/cdp/legacy-plugins/types.ts +++ b/plugin-server/src/cdp/legacy-plugins/types.ts @@ -26,3 +26,13 @@ export type LegacyPlugin = { onEvent(event: ProcessedPluginEvent, meta: LegacyPluginMeta): Promise setupPlugin?: (meta: LegacyPluginMeta) => Promise } + +export type LegacyTransformationPlugin = { + id: string + metadata: { + name: string + config: PluginConfigSchema[] + } + processEvent(event: ProcessedPluginEvent, meta: LegacyPluginMeta): Promise + setupPlugin?: (meta: LegacyPluginMeta) => Promise +} From d2514c27e1550f6d9e16002d1536316855517742 Mon Sep 17 00:00:00 2001 From: Ben White Date: Tue, 28 Jan 2025 13:08:34 +0100 Subject: [PATCH 049/163] Fixes --- .../cdp-cyclotron-plugins-worker.test.ts | 25 ++++++++++--------- plugin-server/src/cdp/legacy-plugins/index.ts | 7 +++++- .../posthog-filter-out-plugin/index.ts | 10 +++++++- plugin-server/src/cdp/legacy-plugins/types.ts | 6 ++--- .../legacy-plugin-executor.service.ts | 4 +-- 5 files changed, 33 insertions(+), 19 deletions(-) diff --git a/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.test.ts b/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.test.ts index 840a1c1f8f2db..f80b61d4cfc0c 100644 --- a/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.test.ts +++ b/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.test.ts @@ -11,7 +11,7 @@ import { getFirstTeam, resetTestDatabase } from '~/tests/helpers/sql' import { Hub, Team } from '../../types' import { closeHub, createHub } from '../../utils/db/hub' -import { PLUGINS_BY_ID } from '../legacy-plugins' +import { DESTINATION_PLUGINS_BY_ID } from '../legacy-plugins' import { HogFunctionInvocationGlobalsWithInputs, HogFunctionType } from '../types' import { CdpCyclotronWorkerPlugins } from './cdp-cyclotron-plugins-worker.consumer' @@ -105,7 +105,7 @@ describe('CdpCyclotronWorkerPlugins', () => { describe('setupPlugin', () => { it('should setup a plugin on first call', async () => { - jest.spyOn(PLUGINS_BY_ID['intercom'] as any, 'setupPlugin') + jest.spyOn(DESTINATION_PLUGINS_BY_ID['intercom'] as any, 'setupPlugin') const results = processor.processBatch([ createInvocation(fn, globals), @@ -115,8 +115,9 @@ describe('CdpCyclotronWorkerPlugins', () => { expect(await results).toMatchObject([{ finished: true }, { finished: true }, { finished: true }]) - expect(PLUGINS_BY_ID['intercom'].setupPlugin).toHaveBeenCalledTimes(1) - expect(jest.mocked(PLUGINS_BY_ID['intercom'].setupPlugin!).mock.calls[0][0]).toMatchInlineSnapshot(` + expect(DESTINATION_PLUGINS_BY_ID['intercom'].setupPlugin).toHaveBeenCalledTimes(1) + expect(jest.mocked(DESTINATION_PLUGINS_BY_ID['intercom'].setupPlugin!).mock.calls[0][0]) + .toMatchInlineSnapshot(` { "config": { "ignoredEmailDomains": "dev.posthog.com", @@ -139,7 +140,7 @@ describe('CdpCyclotronWorkerPlugins', () => { describe('onEvent', () => { it('should call the plugin onEvent method', async () => { - jest.spyOn(PLUGINS_BY_ID['intercom'] as any, 'onEvent') + jest.spyOn(DESTINATION_PLUGINS_BY_ID['intercom'] as any, 'onEvent') const invocation = createInvocation(fn, globals) invocation.globals.event.event = 'mycustomevent' @@ -154,8 +155,8 @@ describe('CdpCyclotronWorkerPlugins', () => { await processor.processBatch([invocation]) - expect(PLUGINS_BY_ID['intercom'].onEvent).toHaveBeenCalledTimes(1) - expect(forSnapshot(jest.mocked(PLUGINS_BY_ID['intercom'].onEvent!).mock.calls[0][0])) + expect(DESTINATION_PLUGINS_BY_ID['intercom'].onEvent).toHaveBeenCalledTimes(1) + expect(forSnapshot(jest.mocked(DESTINATION_PLUGINS_BY_ID['intercom'].onEvent!).mock.calls[0][0])) .toMatchInlineSnapshot(` { "distinct_id": "distinct_id", @@ -219,7 +220,7 @@ describe('CdpCyclotronWorkerPlugins', () => { }) it('should mock out fetch if it is a test function', async () => { - jest.spyOn(PLUGINS_BY_ID['intercom'] as any, 'onEvent') + jest.spyOn(DESTINATION_PLUGINS_BY_ID['intercom'] as any, 'onEvent') const invocation = createInvocation(fn, globals) invocation.hogFunction.name = 'My function [CDP-TEST-HIDDEN]' @@ -232,7 +233,7 @@ describe('CdpCyclotronWorkerPlugins', () => { expect(mockFetch).toHaveBeenCalledTimes(0) - expect(PLUGINS_BY_ID['intercom'].onEvent).toHaveBeenCalledTimes(1) + expect(DESTINATION_PLUGINS_BY_ID['intercom'].onEvent).toHaveBeenCalledTimes(1) expect(forSnapshot(getProducedKafkaMessagesForTopic('log_entries_test').map((m) => m.value.message))) .toMatchInlineSnapshot(` @@ -246,7 +247,7 @@ describe('CdpCyclotronWorkerPlugins', () => { }) it('should handle and collect errors', async () => { - jest.spyOn(PLUGINS_BY_ID['intercom'] as any, 'onEvent') + jest.spyOn(DESTINATION_PLUGINS_BY_ID['intercom'] as any, 'onEvent') const invocation = createInvocation(fn, globals) invocation.globals.event.event = 'mycustomevent' @@ -258,7 +259,7 @@ describe('CdpCyclotronWorkerPlugins', () => { const res = await processor.processBatch([invocation]) - expect(PLUGINS_BY_ID['intercom'].onEvent).toHaveBeenCalledTimes(1) + expect(DESTINATION_PLUGINS_BY_ID['intercom'].onEvent).toHaveBeenCalledTimes(1) expect(res[0].error).toBeInstanceOf(Error) expect(forSnapshot(res[0].logs)).toMatchInlineSnapshot(`[]`) @@ -277,7 +278,7 @@ describe('CdpCyclotronWorkerPlugins', () => { }) describe('smoke tests', () => { - const testCases = Object.entries(PLUGINS_BY_ID).map(([pluginId, plugin]) => ({ + const testCases = Object.entries(DESTINATION_PLUGINS_BY_ID).map(([pluginId, plugin]) => ({ name: pluginId, plugin, })) diff --git a/plugin-server/src/cdp/legacy-plugins/index.ts b/plugin-server/src/cdp/legacy-plugins/index.ts index f15927da6081b..02a3482dc2b38 100644 --- a/plugin-server/src/cdp/legacy-plugins/index.ts +++ b/plugin-server/src/cdp/legacy-plugins/index.ts @@ -1,7 +1,12 @@ import { customerioPlugin } from './customerio' import { intercomPlugin } from './intercom' +import { posthogFilterOutPlugin } from './posthog-filter-out-plugin' -export const PLUGINS_BY_ID = { +export const DESTINATION_PLUGINS_BY_ID = { [customerioPlugin.id]: customerioPlugin, [intercomPlugin.id]: intercomPlugin, } + +export const TRANSFORMATION_PLUGINS_BY_ID = { + [posthogFilterOutPlugin.id]: posthogFilterOutPlugin, +} diff --git a/plugin-server/src/cdp/legacy-plugins/posthog-filter-out-plugin/index.ts b/plugin-server/src/cdp/legacy-plugins/posthog-filter-out-plugin/index.ts index 5a7f4cea3ecbc..3f3b55b1fcb8e 100644 --- a/plugin-server/src/cdp/legacy-plugins/posthog-filter-out-plugin/index.ts +++ b/plugin-server/src/cdp/legacy-plugins/posthog-filter-out-plugin/index.ts @@ -1,6 +1,7 @@ import { Meta, PluginAttachment, PluginEvent } from '@posthog/plugin-scaffold' -import { LegacyPluginMeta } from '../types' +import { LegacyPluginMeta, LegacyTransformationPlugin } from '../types' +import metadata from './plugin.json' export interface Filter { property: string @@ -124,3 +125,10 @@ const parseFiltersAndMigrate = (filters: Filter[][] | Filter[]): Filter[][] => { } return filters as Filter[][] } + +export const posthogFilterOutPlugin: LegacyTransformationPlugin = { + id: 'posthog-filter-out-plugin', + metadata: metadata as any, + processEvent, + setupPlugin, +} diff --git a/plugin-server/src/cdp/legacy-plugins/types.ts b/plugin-server/src/cdp/legacy-plugins/types.ts index 3a075c8667f0d..d5bc54c7c4706 100644 --- a/plugin-server/src/cdp/legacy-plugins/types.ts +++ b/plugin-server/src/cdp/legacy-plugins/types.ts @@ -1,4 +1,4 @@ -import { PluginConfigSchema, ProcessedPluginEvent } from '@posthog/plugin-scaffold' +import { PluginConfigSchema, PluginEvent, ProcessedPluginEvent } from '@posthog/plugin-scaffold' import { Response, trackedFetch } from '~/src/utils/fetch' @@ -33,6 +33,6 @@ export type LegacyTransformationPlugin = { name: string config: PluginConfigSchema[] } - processEvent(event: ProcessedPluginEvent, meta: LegacyPluginMeta): Promise - setupPlugin?: (meta: LegacyPluginMeta) => Promise + processEvent(event: PluginEvent, meta: LegacyPluginMeta): PluginEvent | undefined + setupPlugin?: (meta: LegacyPluginMeta) => void } diff --git a/plugin-server/src/cdp/services/legacy-plugin-executor.service.ts b/plugin-server/src/cdp/services/legacy-plugin-executor.service.ts index c0d77f8386aed..0d43880cfa11f 100644 --- a/plugin-server/src/cdp/services/legacy-plugin-executor.service.ts +++ b/plugin-server/src/cdp/services/legacy-plugin-executor.service.ts @@ -5,7 +5,7 @@ import { Histogram } from 'prom-client' import { Hub } from '../../types' import { Response, trackedFetch } from '../../utils/fetch' import { status } from '../../utils/status' -import { PLUGINS_BY_ID } from '../legacy-plugins' +import { DESTINATION_PLUGINS_BY_ID } from '../legacy-plugins' import { LegacyPluginLogger, LegacyPluginMeta } from '../legacy-plugins/types' import { sanitizeLogMessage } from '../services/hog-executor.service' import { HogFunctionInvocation, HogFunctionInvocationResult } from '../types' @@ -54,7 +54,7 @@ export class LegacyPluginExecutorService { timestamp: DateTime.now(), message: `Executing plugin ${pluginId}`, }) - const plugin = pluginId ? PLUGINS_BY_ID[pluginId] : null + const plugin = pluginId ? DESTINATION_PLUGINS_BY_ID[pluginId] : null if (!plugin || !pluginId) { result.error = new Error(`Plugin ${pluginId} not found`) From c9f3e2a0385cd929e8e48a4c8fe70ca74d7e09a0 Mon Sep 17 00:00:00 2001 From: Ben White Date: Tue, 28 Jan 2025 13:49:40 +0100 Subject: [PATCH 050/163] Fixes --- plugin-server/src/cdp/legacy-plugins/customerio/index.ts | 4 ++-- plugin-server/src/cdp/legacy-plugins/intercom/index.ts | 4 ++-- plugin-server/src/cdp/legacy-plugins/types.ts | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/plugin-server/src/cdp/legacy-plugins/customerio/index.ts b/plugin-server/src/cdp/legacy-plugins/customerio/index.ts index 684deb0c22d5e..63a17618a4cfe 100644 --- a/plugin-server/src/cdp/legacy-plugins/customerio/index.ts +++ b/plugin-server/src/cdp/legacy-plugins/customerio/index.ts @@ -3,7 +3,7 @@ import { RetryError } from '@posthog/plugin-scaffold' import { Response } from '~/src/utils/fetch' -import { LegacyPlugin, LegacyPluginMeta } from '../types' +import { LegacyDestinationPlugin, LegacyPluginMeta } from '../types' import metadata from './plugin.json' const DEFAULT_HOST = 'track.customer.io' @@ -255,7 +255,7 @@ function getEmailFromEvent(event: ProcessedPluginEvent): string | null { return null } -export const customerioPlugin: LegacyPlugin = { +export const customerioPlugin: LegacyDestinationPlugin = { id: 'customer-io', metadata: metadata as any, setupPlugin: setupPlugin as any, diff --git a/plugin-server/src/cdp/legacy-plugins/intercom/index.ts b/plugin-server/src/cdp/legacy-plugins/intercom/index.ts index c2eb3b5319841..358bccb2a63ba 100644 --- a/plugin-server/src/cdp/legacy-plugins/intercom/index.ts +++ b/plugin-server/src/cdp/legacy-plugins/intercom/index.ts @@ -2,7 +2,7 @@ import { ProcessedPluginEvent, RetryError } from '@posthog/plugin-scaffold' import { Response } from '~/src/utils/fetch' -import { LegacyPlugin, LegacyPluginMeta } from '../types' +import { LegacyDestinationPlugin, LegacyPluginMeta } from '../types' import metadata from './plugin.json' type IntercomMeta = LegacyPluginMeta & { @@ -189,7 +189,7 @@ function getTimestamp(meta: IntercomMeta, event: ProcessedPluginEvent): number { return Math.floor(date.getTime() / 1000) } -export const intercomPlugin: LegacyPlugin = { +export const intercomPlugin: LegacyDestinationPlugin = { id: 'intercom', metadata: metadata as any, onEvent, diff --git a/plugin-server/src/cdp/legacy-plugins/types.ts b/plugin-server/src/cdp/legacy-plugins/types.ts index d5bc54c7c4706..f2802adffbb7c 100644 --- a/plugin-server/src/cdp/legacy-plugins/types.ts +++ b/plugin-server/src/cdp/legacy-plugins/types.ts @@ -17,7 +17,7 @@ export type LegacyPluginMeta = { fetch: (...args: Parameters) => Promise } -export type LegacyPlugin = { +export type LegacyDestinationPlugin = { id: string metadata: { name: string From 00b32bc9b065a39108bd0db5ad7786b0612970d3 Mon Sep 17 00:00:00 2001 From: Ben White Date: Tue, 28 Jan 2025 15:38:43 +0100 Subject: [PATCH 051/163] Added a range of transformations --- .../downsampling-plugin/index.test.ts | 78 ++ .../downsampling-plugin/index.ts | 45 ++ .../downsampling-plugin/plugin.json | 29 + .../language-url-splitter-app/index.test.ts | 41 ++ .../language-url-splitter-app/index.ts | 26 + .../language-url-splitter-app/plugin.json | 53 ++ .../index.test.ts | 683 ++++++++++++++++++ .../index.ts | 93 +++ .../plugin.json | 61 ++ .../posthog-filter-out-plugin/index.test.ts | 2 +- .../posthog-filter-out-plugin/index.ts | 5 +- .../posthog-filter-out-plugin/plugin.json | 0 .../index.test.ts | 123 ++++ .../posthog-url-normalizer-plugin/index.ts | 34 + .../posthog-url-normalizer-plugin/plugin.json | 12 + .../property-filter-plugin/index.test.ts | 72 ++ .../property-filter-plugin/index.ts | 42 ++ .../property-filter-plugin/plugin.json | 19 + .../_transformations/taxonomy-plugin/index.ts | 84 +++ .../taxonomy-plugin/plugin.json | 24 + .../timestamp-parser-plugin/index.js | 18 + .../timestamp-parser-plugin/index.tests.ts | 50 ++ .../timestamp-parser-plugin/plugin.json | 6 + plugin-server/src/cdp/legacy-plugins/index.ts | 2 +- plugin-server/src/cdp/legacy-plugins/types.ts | 2 +- 25 files changed, 1599 insertions(+), 5 deletions(-) create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/downsampling-plugin/index.test.ts create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/downsampling-plugin/index.ts create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/downsampling-plugin/plugin.json create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/language-url-splitter-app/index.test.ts create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/language-url-splitter-app/index.ts create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/language-url-splitter-app/plugin.json create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/posthog-app-url-parameters-to-event-properties/index.test.ts create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/posthog-app-url-parameters-to-event-properties/index.ts create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/posthog-app-url-parameters-to-event-properties/plugin.json rename plugin-server/src/cdp/legacy-plugins/{ => _transformations}/posthog-filter-out-plugin/index.test.ts (99%) rename plugin-server/src/cdp/legacy-plugins/{ => _transformations}/posthog-filter-out-plugin/index.ts (95%) rename plugin-server/src/cdp/legacy-plugins/{ => _transformations}/posthog-filter-out-plugin/plugin.json (100%) create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/posthog-url-normalizer-plugin/index.test.ts create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/posthog-url-normalizer-plugin/index.ts create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/posthog-url-normalizer-plugin/plugin.json create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/property-filter-plugin/index.test.ts create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/property-filter-plugin/index.ts create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/property-filter-plugin/plugin.json create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/taxonomy-plugin/index.ts create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/taxonomy-plugin/plugin.json create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/timestamp-parser-plugin/index.js create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/timestamp-parser-plugin/index.tests.ts create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/timestamp-parser-plugin/plugin.json diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/downsampling-plugin/index.test.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/downsampling-plugin/index.test.ts new file mode 100644 index 0000000000000..db38c19ace8ac --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/downsampling-plugin/index.test.ts @@ -0,0 +1,78 @@ +import { PluginEvent } from '@posthog/plugin-scaffold' +import { randomBytes } from 'crypto' + +import { LegacyPluginMeta } from '../../types' +import { processEvent, setupPlugin } from './index' + +let meta: LegacyPluginMeta + +const createEvent = (event: Partial): PluginEvent => + ({ + distinct_id: '1', + event: '$pageview', + properties: { + $current_url: 'http://www.google.com', + ...event.properties, + }, + ...event, + } as unknown as PluginEvent) + +beforeEach(() => { + meta = { + global: {}, + config: { + percentage: '100', + }, + } as unknown as LegacyPluginMeta +}) + +test('processEvent filters event', () => { + // Setup Plugin + setupPlugin(meta) + + for (let i = 0; i < 100; i++) { + const event0 = createEvent({ distinct_id: randomBytes(10).toString('hex') }) + const event1 = processEvent(event0, meta) + expect(event1).toEqual(event0) + } +}) + +test('processEvent filters 0 events at 0 percent', () => { + meta.config.percentage = '0' + + // Setup Plugin + setupPlugin(meta) + + // create a random event + const event0 = createEvent({ event: 'blah' }) + + for (let i = 0; i < 100; i++) { + const event1 = processEvent(event0, meta) + expect(event1).toBeNull() + } +}) + +test('processEvent filters same events at different increasing percent', () => { + // create an event. Hash generates 0.42 + const event0 = createEvent({ distinct_id: '1' }) + + for (let i = 0; i < 5; i++) { + meta.config.percentage = (i * 10).toString() + + // Setup Plugin + setupPlugin(meta) + + const event1 = processEvent(event0, meta) + expect(event1).toBeNull() + } + + for (let i = 5; i <= 10; i++) { + meta.config.percentage = (i * 10).toString() + + // Setup Plugin + setupPlugin(meta) + + const event1 = processEvent(event0, meta) + expect(event1).toEqual(event0) + } +}) diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/downsampling-plugin/index.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/downsampling-plugin/index.ts new file mode 100644 index 0000000000000..d779cdfb6b903 --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/downsampling-plugin/index.ts @@ -0,0 +1,45 @@ +import { PluginEvent } from '@posthog/plugin-scaffold' +import { createHash } from 'crypto' + +import { LegacyPluginMeta, LegacyTransformationPlugin } from '../../types' +import metadata from './plugin.json' + +export function setupPlugin({ config, global }: LegacyPluginMeta) { + const percentage = parseFloat(config.percentage) + if (isNaN(percentage) || percentage > 100 || percentage < 0) { + throw new Error('Percentage must be a number between 0 and 100.') + } + global.percentage = percentage + global.randomSampling = config.samplingMethod === 'Random sampling' +} + +// /* Runs on every event */ +export function processEvent(event: PluginEvent, { global }: LegacyPluginMeta) { + // hash is a sha256 hash of the distinct_id represented in base 16 + // We take the first 15 digits, convert this into an integer, + // dividing by the biggest 15 digit, base 16 number to get a value between 0 and 1. + // This is stable, so a distinct_id that was allowed before will continue to be allowed, + // even if the percentage increases + + let shouldIngestEvent = true + if (global.randomSampling) { + shouldIngestEvent = Math.round(Math.random() * 100) <= global.percentage + } else { + const hash = createHash('sha256').update(event.distinct_id).digest('hex') + // eslint-disable-next-line @typescript-eslint/no-loss-of-precision + const decisionValue = parseInt(hash.substring(0, 15), 16) / 0xfffffffffffffff + shouldIngestEvent = decisionValue <= global.percentage / 100 + } + + if (shouldIngestEvent) { + return event + } + return null +} + +export const downsamplingPlugin: LegacyTransformationPlugin = { + id: 'downsampling-plugin', + metadata: metadata as any, + processEvent, + setupPlugin, +} diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/downsampling-plugin/plugin.json b/plugin-server/src/cdp/legacy-plugins/_transformations/downsampling-plugin/plugin.json new file mode 100644 index 0000000000000..5feab1574bafd --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/downsampling-plugin/plugin.json @@ -0,0 +1,29 @@ +{ + "name": "Downsampling Plugin", + "url": "https://github.com/posthog/downsampling-plugin", + "description": "Reduces event volume coming into PostHog", + "main": "src/index.ts", + "posthogVersion": ">= 1.24.0", + "config": [ + { + "key": "percentage", + "hint": "Reduces events flowing in to the percentage value above", + "name": "% of events to keep", + "type": "string", + "default": "100", + "required": false + }, + { + "key": "samplingMethod", + "hint": "`Distinct ID aware sampling` will sample based on distinct IDs, meaning that a user's events will all be ingested or all be dropped at a given sample percentage.", + "name": "Sampling method", + "type": "choice", + "choices": [ + "Random sampling", + "Distinct ID aware sampling" + ], + "default": "Distinct ID aware sampling", + "required": false + } + ] +} diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/language-url-splitter-app/index.test.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/language-url-splitter-app/index.test.ts new file mode 100644 index 0000000000000..896c8684ad8e5 --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/language-url-splitter-app/index.test.ts @@ -0,0 +1,41 @@ +import { PluginEvent } from '@posthog/plugin-scaffold' + +import { LegacyPluginMeta } from '../../types' +import { processEvent } from './index' +import pluginJson from './plugin.json' + +const globalConfig = Object.fromEntries(pluginJson.config.filter((c) => c.key).map((c) => [c.key, c.default])) +const makeEvent = ($pathname: string) => ({ event: '$pageview', properties: { $pathname } } as unknown as PluginEvent) + +test('changes properties', () => { + const matches: [string, PluginEvent['properties']][] = [ + ['/lol', { $pathname: '/lol' }], + ['/english', { $pathname: '/english' }], + ['/en', { $pathname: '/', locale: 'en' }], + ['/en/', { $pathname: '/', locale: 'en' }], + ['/en/?', { $pathname: '/?', locale: 'en' }], + ['/en#bla', { $pathname: '/#bla', locale: 'en' }], + ['/en?bla', { $pathname: '/?bla', locale: 'en' }], + ['/en/asd', { $pathname: '/asd', locale: 'en' }], + ['/en/en/en', { $pathname: '/en/en', locale: 'en' }], + ] + + for (const [$pathname, properties] of matches) { + expect(processEvent(makeEvent($pathname), { config: globalConfig } as LegacyPluginMeta).properties).toEqual( + properties + ) + } +}) + +test('changes properties if new $pathname', () => { + const config = { ...globalConfig, replaceKey: '$otherPath' } + const matches: [string, PluginEvent['properties']][] = [ + ['/en/asd', { $pathname: '/en/asd', $otherPath: '/asd', locale: 'en' }], + ] + + for (const [$pathname, properties] of matches) { + expect(processEvent(makeEvent($pathname), { config } as unknown as LegacyPluginMeta).properties).toEqual( + properties + ) + } +}) diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/language-url-splitter-app/index.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/language-url-splitter-app/index.ts new file mode 100644 index 0000000000000..751fb8c857b8c --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/language-url-splitter-app/index.ts @@ -0,0 +1,26 @@ +import { PluginEvent } from '@posthog/plugin-scaffold' + +import { LegacyPluginMeta, LegacyTransformationPlugin } from '../../types' +import metadata from './plugin.json' + +export function processEvent(event: PluginEvent, { config }: LegacyPluginMeta) { + const { pattern, matchGroup, property, replacePattern, replaceKey, replaceValue } = config + if (event.properties && typeof event.properties['$pathname'] === 'string') { + const regexp = new RegExp(pattern) + const match = event.properties['$pathname'].match(regexp) + if (match) { + event.properties[property] = match[matchGroup] + if (replacePattern) { + const replaceRegexp = new RegExp(replacePattern) + event.properties[replaceKey] = event.properties['$pathname'].replace(replaceRegexp, replaceValue) + } + } + } + return event +} + +export const languageUrlSplitterPlugin: LegacyTransformationPlugin = { + id: 'language-url-splitter-app', + metadata: metadata as any, + processEvent, +} diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/language-url-splitter-app/plugin.json b/plugin-server/src/cdp/legacy-plugins/_transformations/language-url-splitter-app/plugin.json new file mode 100644 index 0000000000000..ed4bf5a6b717d --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/language-url-splitter-app/plugin.json @@ -0,0 +1,53 @@ +{ + "name": "Language URL stripper", + "config": [ + { + "key": "pattern", + "name": "Pattern", + "type": "string", + "default": "^/([a-z]{2})(?=/|#|\\?|$)", + "hint": "Ininitalized with `const regexp = new RegExp($pattern)`", + "required": true + }, + { + "key": "matchGroup", + "name": "Match group", + "type": "string", + "default": "1", + "hint": "Used in: `const value = regexp.match($pathname)[$matchGroup]`", + "required": true + }, + { + "key": "property", + "name": "Property", + "type": "string", + "default": "locale", + "hint": "Name of the event property we will store the matched value in", + "required": true + }, + { + "key": "replacePattern", + "name": "Replacement pattern", + "type": "string", + "default": "^(/[a-z]{2})(/|(?=/|#|\\?|$))", + "hint": "Initialized with `new RegExp($pattern)`, leave empty to disable path cleanup.", + "required": true + }, + { + "key": "replaceKey", + "name": "Replacement key", + "type": "string", + "default": "$pathname", + "hint": "Where to store the updated path. Keep as `$pathname` to override.", + "required": true + }, + { + "key": "replaceValue", + "name": "Replacement value", + "type": "string", + "default": "/", + "hint": "`properties[key] = $pathname.replace(pattern, value)`", + "required": true + } + ] +} \ No newline at end of file diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-app-url-parameters-to-event-properties/index.test.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-app-url-parameters-to-event-properties/index.test.ts new file mode 100644 index 0000000000000..6d7362d0651a3 --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-app-url-parameters-to-event-properties/index.test.ts @@ -0,0 +1,683 @@ +import { PluginEvent } from '@posthog/plugin-scaffold' + +import { LocalMeta, PluginConfig, processEvent, setupPlugin } from './index' + +/** + * Given a url, construct a page view event. + * + * @param $current_url The current url of the page view + * @returns A new PostHog page view event + */ +function buildPageViewEvent($current_url: string): PluginEvent { + const event: PluginEvent = { + properties: { $current_url }, + distinct_id: 'distinct_id', + ip: '1.2.3.4', + site_url: 'posthog.com', + team_id: 0, + now: '2022-06-17T20:21:31.778000+00:00', + event: '$pageview', + uuid: '01817354-06bb-0000-d31c-2c4eed374100', + } + + return event +} + +const defaultConfig: PluginConfig = { + parameters: 'myUrlParameter', + prefix: '', + suffix: '', + setAsUserProperties: 'false', + setAsInitialUserProperties: 'false', + ignoreCase: 'false', + alwaysJson: 'false', +} + +const pluginJSON = require('./plugin.json') + +function buildMockMeta(partialConfig: Partial = {}): LocalMeta { + const config: PluginConfig = { ...defaultConfig, ...partialConfig } + return { + global: { + ignoreCase: config.ignoreCase === 'true', + setAsInitialUserProperties: config.setAsInitialUserProperties === 'true', + setAsUserProperties: config.setAsUserProperties === 'true', + alwaysJson: config.alwaysJson === 'true', + parameters: new Set( + config.parameters ? config.parameters.split(',').map((parameter) => parameter.trim()) : null + ), + }, + config: config, + } as LocalMeta +} + +describe('ParamsToPropertiesPlugin', () => { + let mockMeta: LocalMeta + + beforeEach(() => { + jest.clearAllMocks() + + mockMeta = buildMockMeta() + }) + + describe('setupPlugin', () => { + it('should set one item to whitelist', () => { + const meta = { + global: { + parameters: new Set(), + }, + config: { + parameters: 'one_item', + prefix: '', + suffix: '', + setAsUserProperties: 'false', + setAsInitialUserProperties: 'false', + ignoreCase: 'false', + alwaysJson: 'false', + }, + } as LocalMeta + + expect(meta.global.parameters.size).toBe(0) + + setupPlugin(meta) + + expect(meta.global.parameters.size).toBe(1) + }) + + it('should set three item to whitelist', () => { + const meta = { + global: { + parameters: new Set(), + }, + config: { + parameters: 'one_item, two_item,three_item', + prefix: '', + suffix: '', + setAsUserProperties: 'false', + setAsInitialUserProperties: 'false', + ignoreCase: 'false', + alwaysJson: 'false', + }, + } as LocalMeta + + expect(meta.global.parameters.size).toBe(0) + + setupPlugin(meta) + + expect(meta.global.parameters.size).toBe(3) + expect(meta.global.parameters.has('one_item')).toBeTruthy() + expect(meta.global.parameters.has('two_item')).toBeTruthy() + expect(meta.global.parameters.has('three_item')).toBeTruthy() + }) + + it('should clear global whitelist when config is missing whitelist', () => { + const meta = { + global: { + parameters: new Set(['one_item']), + }, + config: { + prefix: '', + suffix: '', + setAsUserProperties: 'false', + setAsInitialUserProperties: 'false', + ignoreCase: 'false', + alwaysJson: 'false', + }, + } as LocalMeta + + expect(meta.global.parameters.size).toBe(1) + + setupPlugin(meta) + + expect(meta.global.parameters.size).toBe(0) + }) + }) + + describe('plugin.json', () => { + it('should contain all properties of PluginConfig', () => { + expect(pluginJSON.config).toBeDefined() + if (pluginJSON.config) { + const fields = new Set() + for (const item of pluginJSON.config) { + fields.add(item.key) + } + + expect(fields.has('ignoreCase')).toBeTruthy() + expect(fields.has('prefix')).toBeTruthy() + expect(fields.has('setAsInitialUserProperties')).toBeTruthy() + expect(fields.has('setAsUserProperties')).toBeTruthy() + expect(fields.has('suffix')).toBeTruthy() + expect(fields.has('parameters')).toBeTruthy() + expect(fields.has('alwaysJson')).toBeTruthy() + expect(fields.size).toEqual(7) + } + }) + + it('should match types of all properties of PluginConfig', () => { + expect(pluginJSON.config).toBeDefined() + if (pluginJSON.config) { + const fields = new Map() + for (const item of pluginJSON.config) { + fields.set(item.key, item.type) + } + + expect(fields.get('ignoreCase')).toEqual('choice') + expect(fields.get('prefix')).toEqual('string') + expect(fields.get('setAsInitialUserProperties')).toEqual('choice') + expect(fields.get('setAsUserProperties')).toEqual('choice') + expect(fields.get('suffix')).toEqual('string') + expect(fields.get('parameters')).toEqual('string') + expect(fields.get('alwaysJson')).toEqual('choice') + } + }) + }) + + describe('processEvent', () => { + it("shouldn't change properties count", () => { + const sourceEvent = buildPageViewEvent('https://posthog.com/test?plugin=1') + if (sourceEvent.properties) { + const sourcePropertiesCount = Object.keys(sourceEvent?.properties).length + const processedEvent = processEvent(sourceEvent, mockMeta) + + if (processedEvent.properties) { + expect(Object.keys(processedEvent.properties).length).toEqual(sourcePropertiesCount) + } else { + expect(processedEvent.properties).toBeDefined() + } + } else { + expect(sourceEvent.properties).toBeDefined() + } + }) + + it('should add 1 property', () => { + const sourceEvent = buildPageViewEvent('https://posthog.com/test?plugin=1&myUrlParameter=1') + + if (sourceEvent.properties) { + expect(sourceEvent.properties['myUrlParameter']).not.toBeDefined() + expect(mockMeta.global.alwaysJson).toBeFalsy() + + const sourcePropertiesCount = Object.keys(sourceEvent?.properties).length + const processedEvent = processEvent(sourceEvent, mockMeta) + + if (processedEvent.properties) { + expect(Object.keys(processedEvent.properties).length).toBeGreaterThan(sourcePropertiesCount) + expect(Object.keys(processedEvent.properties).length).toEqual(sourcePropertiesCount + 1) + expect(processedEvent.properties['myUrlParameter']).toBeDefined() + expect(processedEvent.properties.myUrlParameter).toEqual('1') + } else { + expect(processedEvent.properties).toBeDefined() + } + } else { + expect(sourceEvent.properties).toBeDefined() + } + }) + + it('should add 2 property', () => { + const sourceEvent = buildPageViewEvent('https://posthog.com/test?plugin=1&myUrlParameter=1') + + if (sourceEvent.properties) { + expect(sourceEvent.properties['myUrlParameter']).not.toBeDefined() + + const sourcePropertiesCount = Object.keys(sourceEvent?.properties).length + const processedEvent = processEvent( + sourceEvent, + buildMockMeta({ parameters: 'plugin, myUrlParameter' }) + ) + + if (processedEvent.properties) { + expect(Object.keys(processedEvent.properties).length).toBeGreaterThan(sourcePropertiesCount) + expect(Object.keys(processedEvent.properties).length).toEqual(sourcePropertiesCount + 2) + expect(processedEvent.properties['plugin']).toBeDefined() + expect(processedEvent.properties['myUrlParameter']).toBeDefined() + } else { + expect(processedEvent.properties).toBeDefined() + } + } else { + expect(sourceEvent.properties).toBeDefined() + } + }) + + it('should add 1 property and 1 $set property', () => { + const sourceEvent = buildPageViewEvent('https://posthog.com/test?plugin=1&myUrlParameter=1') + + if (sourceEvent.properties) { + expect(sourceEvent.properties['myUrlParameter']).not.toBeDefined() + expect(sourceEvent.properties['$set']).not.toBeDefined() + + const sourcePropertiesCount = Object.keys(sourceEvent?.properties).length + const processedEvent = processEvent(sourceEvent, buildMockMeta({ setAsUserProperties: 'true' })) + + if (processedEvent.properties) { + expect(Object.keys(processedEvent.properties).length).toBeGreaterThan(sourcePropertiesCount) + expect(Object.keys(processedEvent.properties).length).toEqual(sourcePropertiesCount + 2) + expect(processedEvent.properties['myUrlParameter']).toBeDefined() + expect(processedEvent.properties['$set']).toBeDefined() + expect(processedEvent.properties.$set['myUrlParameter']).toBeDefined() + } else { + expect(processedEvent.properties).toBeDefined() + } + } else { + expect(sourceEvent.properties).toBeDefined() + } + }) + + it('should add 1 property and 1 $set_once property', () => { + const sourceEvent = buildPageViewEvent('https://posthog.com/test?plugin=1&myUrlParameter=1') + + if (sourceEvent.properties) { + expect(sourceEvent.properties['myUrlParameter']).not.toBeDefined() + expect(sourceEvent.properties['$set_once']).not.toBeDefined() + + const sourcePropertiesCount = Object.keys(sourceEvent?.properties).length + const processedEvent = processEvent(sourceEvent, buildMockMeta({ setAsInitialUserProperties: 'true' })) + + if (processedEvent.properties) { + expect(Object.keys(processedEvent.properties).length).toBeGreaterThan(sourcePropertiesCount) + expect(Object.keys(processedEvent.properties).length).toEqual(sourcePropertiesCount + 2) + expect(processedEvent.properties['myUrlParameter']).toBeDefined() + expect(processedEvent.properties['$set_once']).toBeDefined() + expect(processedEvent.properties.$set_once['initial_myUrlParameter']).toBeDefined() + } else { + expect(processedEvent.properties).toBeDefined() + } + } else { + expect(sourceEvent.properties).toBeDefined() + } + }) + + it('should add 1 property, 1 $set property and 1 $set_once property', () => { + const sourceEvent = buildPageViewEvent('https://posthog.com/test?plugin=1&myUrlParameter=1') + + if (sourceEvent.properties) { + expect(sourceEvent.properties['myUrlParameter']).not.toBeDefined() + expect(sourceEvent.properties['$set']).not.toBeDefined() + expect(sourceEvent.properties['$set_once']).not.toBeDefined() + + const sourcePropertiesCount = Object.keys(sourceEvent?.properties).length + const processedEvent = processEvent( + sourceEvent, + buildMockMeta({ setAsUserProperties: 'true', setAsInitialUserProperties: 'true' }) + ) + + if (processedEvent.properties) { + expect(Object.keys(processedEvent.properties).length).toBeGreaterThan(sourcePropertiesCount) + expect(Object.keys(processedEvent.properties).length).toEqual(sourcePropertiesCount + 3) + expect(processedEvent.properties['myUrlParameter']).toBeDefined() + expect(processedEvent.properties['$set']).toBeDefined() + expect(processedEvent.properties['$set_once']).toBeDefined() + expect(processedEvent.properties.$set['myUrlParameter']).toBeDefined() + expect(processedEvent.properties.$set_once['initial_myUrlParameter']).toBeDefined() + } else { + expect(processedEvent.properties).toBeDefined() + } + } else { + expect(sourceEvent.properties).toBeDefined() + } + }) + + it('should add 1 property with prefix', () => { + const sourceEvent = buildPageViewEvent('https://posthog.com/test?plugin=1&myUrlParameter=1') + + if (sourceEvent.properties) { + expect(sourceEvent.properties['prefix_myUrlParameter']).not.toBeDefined() + + const sourcePropertiesCount = Object.keys(sourceEvent?.properties).length + const processedEvent = processEvent(sourceEvent, buildMockMeta({ prefix: 'prefix_' })) + + if (processedEvent.properties) { + expect(Object.keys(processedEvent.properties).length).toBeGreaterThan(sourcePropertiesCount) + expect(Object.keys(processedEvent.properties).length).toEqual(sourcePropertiesCount + 1) + expect(processedEvent.properties['prefix_myUrlParameter']).toBeDefined() + } else { + expect(processedEvent.properties).toBeDefined() + } + } else { + expect(sourceEvent.properties).toBeDefined() + } + }) + + it('should add 1 property with suffix', () => { + const sourceEvent = buildPageViewEvent('https://posthog.com/test?plugin=1&myUrlParameter=1') + + if (sourceEvent.properties) { + expect(sourceEvent.properties['myUrlParameter_suffix']).not.toBeDefined() + + const sourcePropertiesCount = Object.keys(sourceEvent?.properties).length + const processedEvent = processEvent(sourceEvent, buildMockMeta({ suffix: '_suffix' })) + + if (processedEvent.properties) { + expect(Object.keys(processedEvent.properties).length).toBeGreaterThan(sourcePropertiesCount) + expect(Object.keys(processedEvent.properties).length).toEqual(sourcePropertiesCount + 1) + expect(processedEvent.properties['myUrlParameter_suffix']).toBeDefined() + } else { + expect(processedEvent.properties).toBeDefined() + } + } else { + expect(sourceEvent.properties).toBeDefined() + } + }) + + it('should add 1 property with prefix and suffix', () => { + const sourceEvent = buildPageViewEvent('https://posthog.com/test?plugin=1&myUrlParameter=1') + + if (sourceEvent.properties) { + expect(sourceEvent.properties['prefix_myUrlParameter_suffix']).not.toBeDefined() + + const sourcePropertiesCount = Object.keys(sourceEvent?.properties).length + const processedEvent = processEvent( + sourceEvent, + buildMockMeta({ prefix: 'prefix_', suffix: '_suffix' }) + ) + + if (processedEvent.properties) { + expect(Object.keys(processedEvent.properties).length).toBeGreaterThan(sourcePropertiesCount) + expect(Object.keys(processedEvent.properties).length).toEqual(sourcePropertiesCount + 1) + expect(processedEvent.properties['prefix_myUrlParameter_suffix']).toBeDefined() + } else { + expect(processedEvent.properties).toBeDefined() + } + } else { + expect(sourceEvent.properties).toBeDefined() + } + }) + + it("shouldn't add properties when $current_url is undefined", () => { + const sourceEvent = { + ...buildPageViewEvent('https://posthog.com/test?plugin=1&myUrlParameter=1'), + ...{ properties: { $current_url: undefined } }, + } + + if (sourceEvent.properties) { + const sourcePropertiesCount = Object.keys(sourceEvent?.properties).length + const processedEvent = processEvent(sourceEvent, mockMeta) + + if (processedEvent.properties) { + expect(Object.keys(processedEvent.properties).length).toEqual(sourcePropertiesCount) + expect(processedEvent.properties['myUrlParameter']).not.toBeDefined() + } else { + expect(processedEvent.properties).toBeDefined() + } + } else { + expect(sourceEvent.properties).toBeDefined() + } + }) + + it("shouldn't add properties when properties is undefined", () => { + const sourceEvent = { + ...buildPageViewEvent('https://posthog.com/test?plugin=1&myUrlParameter=1'), + ...{ properties: undefined }, + } + + expect(sourceEvent.properties).not.toBeDefined() + + const processedEvent = processEvent(sourceEvent, mockMeta) + expect(processedEvent.properties).not.toBeDefined() + }) + + it('should add 1 property regardless of case', () => { + const sourceEvent = buildPageViewEvent('https://posthog.com/test?plugin=1&MyUrlParameter=1') + + if (sourceEvent.properties) { + expect(sourceEvent.properties['myUrlParameter']).not.toBeDefined() + + const sourcePropertiesCount = Object.keys(sourceEvent?.properties).length + const processedEvent = processEvent(sourceEvent, buildMockMeta({ ignoreCase: 'true' })) + + if (processedEvent.properties) { + expect(Object.keys(processedEvent.properties).length).toBeGreaterThan(sourcePropertiesCount) + expect(Object.keys(processedEvent.properties).length).toEqual(sourcePropertiesCount + 1) + expect(processedEvent.properties['myUrlParameter']).toBeDefined() + } else { + expect(processedEvent.properties).toBeDefined() + } + } else { + expect(sourceEvent.properties).toBeDefined() + } + }) + + it("shouldn't add properties respecting case missmatch", () => { + const sourceEvent = buildPageViewEvent('https://posthog.com/test?plugin=1&MyUrlParameter=1') + + if (sourceEvent.properties) { + expect(sourceEvent.properties['myUrlParameter']).not.toBeDefined() + + const sourcePropertiesCount = Object.keys(sourceEvent?.properties).length + const processedEvent = processEvent(sourceEvent, buildMockMeta()) + + if (processedEvent.properties) { + expect(Object.keys(processedEvent.properties).length).toEqual(sourcePropertiesCount) + expect(processedEvent.properties['myUrlParameter']).not.toBeDefined() + } else { + expect(processedEvent.properties).toBeDefined() + } + } else { + expect(sourceEvent.properties).toBeDefined() + } + }) + + it('should add 1 property regardless of case with prefix and suffix', () => { + const sourceEvent = buildPageViewEvent('https://posthog.com/test?plugin=1&MyUrlParameter=1') + + if (sourceEvent.properties) { + expect(sourceEvent.properties['myUrlParameter']).not.toBeDefined() + + const sourcePropertiesCount = Object.keys(sourceEvent?.properties).length + const processedEvent = processEvent( + sourceEvent, + buildMockMeta({ ignoreCase: 'true', prefix: 'prefix_', suffix: '_suffix' }) + ) + + if (processedEvent.properties) { + expect(Object.keys(processedEvent.properties).length).toBeGreaterThan(sourcePropertiesCount) + expect(Object.keys(processedEvent.properties).length).toEqual(sourcePropertiesCount + 1) + expect(processedEvent.properties['prefix_myUrlParameter_suffix']).toBeDefined() + } else { + expect(processedEvent.properties).toBeDefined() + } + } else { + expect(sourceEvent.properties).toBeDefined() + } + }) + + it('should add 1 property, 1 $set property and 1 $set_once property regardless of case with prefix and suffix', () => { + const sourceEvent = buildPageViewEvent('https://posthog.com/test?plugin=1&MyUrlParameter=1') + + if (sourceEvent.properties) { + expect(sourceEvent.properties['myUrlParameter']).not.toBeDefined() + expect(sourceEvent.properties['$set']).not.toBeDefined() + expect(sourceEvent.properties['$set_once']).not.toBeDefined() + + const sourcePropertiesCount = Object.keys(sourceEvent?.properties).length + const processedEvent = processEvent( + sourceEvent, + buildMockMeta({ + ignoreCase: 'true', + prefix: 'prefix_', + suffix: '_suffix', + setAsUserProperties: 'true', + setAsInitialUserProperties: 'true', + }) + ) + + if (processedEvent.properties) { + expect(Object.keys(processedEvent.properties).length).toBeGreaterThan(sourcePropertiesCount) + expect(Object.keys(processedEvent.properties).length).toEqual(sourcePropertiesCount + 3) + expect(processedEvent.properties['prefix_myUrlParameter_suffix']).toBeDefined() + expect(processedEvent.properties['$set']).toBeDefined() + expect(processedEvent.properties.$set['prefix_myUrlParameter_suffix']).toBeDefined() + expect(processedEvent.properties['$set_once']).toBeDefined() + expect(processedEvent.properties.$set_once['initial_prefix_myUrlParameter_suffix']).toBeDefined() + } else { + expect(processedEvent.properties).toBeDefined() + } + } else { + expect(sourceEvent.properties).toBeDefined() + } + }) + ;[ + { + label: '', + ignoreCase: 'false', + prefix: '', + suffix: '', + setAsUserProperties: '', + setAsInitialUserProperties: '', + }, + { + label: 'ignoring case', + ignoreCase: 'true', + prefix: '', + suffix: '', + setAsUserProperties: '', + setAsInitialUserProperties: '', + }, + { + label: 'with a prefix', + ignoreCase: 'false', + prefix: 'prefix_', + suffix: '', + setAsUserProperties: '', + setAsInitialUserProperties: '', + }, + { + label: 'with a suffix', + ignoreCase: 'false', + prefix: '', + suffix: '_suffix', + setAsUserProperties: '', + setAsInitialUserProperties: '', + }, + { + label: 'with a prefix and a suffix', + ignoreCase: 'false', + prefix: 'prefix_', + suffix: '_suffix', + setAsUserProperties: '', + setAsInitialUserProperties: '', + }, + { + label: 'with a $set property', + ignoreCase: 'false', + prefix: '', + suffix: '', + setAsUserProperties: 'true', + setAsInitialUserProperties: '', + }, + { + label: 'with a $set_once property', + ignoreCase: 'false', + prefix: '', + suffix: '', + setAsUserProperties: '', + setAsInitialUserProperties: 'true', + }, + { + label: 'with a $set and a $set_once property', + ignoreCase: 'false', + prefix: '', + suffix: '', + setAsUserProperties: 'true', + setAsInitialUserProperties: 'true', + }, + { + label: 'with a prefix, a suffix, a $set, and a $set_once property', + ignoreCase: 'false', + prefix: 'preefix_', + suffix: '_suffax', + setAsUserProperties: 'true', + setAsInitialUserProperties: 'true', + }, + ].forEach((testOptions) => { + it(`should add 1 multivalue property ${testOptions['label']}`, () => { + const testParameterBase = 'multiValueParam' + const testMockMeta = buildMockMeta({ + ignoreCase: testOptions['ignoreCase'] === 'true' ? 'true' : 'false', + prefix: testOptions['prefix'], + suffix: testOptions['suffix'], + setAsUserProperties: testOptions['setAsUserProperties'] === 'true' ? 'true' : 'false', + setAsInitialUserProperties: testOptions['setAsInitialUserProperties'] === 'true' ? 'true' : 'false', + parameters: testParameterBase, + }) + const testData = JSON.stringify(['1', '2']) + + let testParameter = testParameterBase + + if (testOptions['prefix'].length > 0) { + testParameter = `${testOptions['prefix']}${testParameter}` + } + + if (testOptions['suffix'].length > 0) { + testParameter = `${testParameter}${testOptions['suffix']}` + } + + const eventParameter = + testOptions['ignoreCase'] === 'true' ? testParameterBase.toUpperCase() : testParameterBase + const sourceEvent = buildPageViewEvent( + `https://posthog.com/test?plugin=1&${eventParameter}=1&${eventParameter}=2` + ) + + if (sourceEvent.properties) { + expect(sourceEvent.properties[testParameter]).not.toBeDefined() + + const sourcePropertiesCount = Object.keys(sourceEvent?.properties).length + const addlPropsCount = + 1 + + (testOptions['setAsUserProperties'] === 'true' ? 1 : 0) + + (testOptions['setAsInitialUserProperties'] === 'true' ? 1 : 0) + const processedEvent = processEvent(sourceEvent, testMockMeta) + + if (processedEvent.properties) { + // the setAs options are additive + + if (testOptions['setAsUserProperties'] === 'true') { + expect(Object.keys(processedEvent.properties.$set)).toBeDefined() + expect(Object.keys(processedEvent.properties.$set).length).toEqual(1) + expect(processedEvent.properties.$set[testParameter]).toBeDefined() + expect(processedEvent.properties.$set[testParameter]).toEqual(testData) + } + + if (testOptions['setAsInitialUserProperties'] === 'true') { + expect(Object.keys(processedEvent.properties.$set_once)).toBeDefined() + expect(Object.keys(processedEvent.properties.$set_once).length).toEqual(1) + expect(processedEvent.properties.$set_once[`initial_${testParameter}`]).toBeDefined() + expect(processedEvent.properties.$set_once[`initial_${testParameter}`]).toEqual(testData) + } + + expect(Object.keys(processedEvent.properties).length).toBeGreaterThan(sourcePropertiesCount) + expect(Object.keys(processedEvent.properties).length).toEqual( + sourcePropertiesCount + addlPropsCount + ) + expect(processedEvent.properties[testParameter]).toBeDefined() + expect(processedEvent.properties[testParameter]).toEqual(testData) + } else { + expect(processedEvent.properties).toBeDefined() + } + } else { + expect(sourceEvent.properties).toBeDefined() + } + }) + }) + }) + + it('should add 1 property stored as JSON when alwaysJson = true', () => { + const sourceEvent = buildPageViewEvent('https://posthog.com/test?plugin=1&myUrlParameter=1') + + if (sourceEvent.properties) { + expect(sourceEvent.properties['myUrlParameter']).not.toBeDefined() + + const sourcePropertiesCount = Object.keys(sourceEvent?.properties).length + const processedEvent = processEvent(sourceEvent, buildMockMeta({ alwaysJson: 'true' })) + + if (processedEvent.properties) { + expect(Object.keys(processedEvent.properties).length).toBeGreaterThan(sourcePropertiesCount) + expect(Object.keys(processedEvent.properties).length).toEqual(sourcePropertiesCount + 1) + expect(processedEvent.properties['myUrlParameter']).toBeDefined() + expect(processedEvent.properties.myUrlParameter).toEqual(JSON.stringify(['1'])) + } else { + expect(processedEvent.properties).toBeDefined() + } + } else { + expect(sourceEvent.properties).toBeDefined() + } + }) +}) diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-app-url-parameters-to-event-properties/index.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-app-url-parameters-to-event-properties/index.ts new file mode 100644 index 0000000000000..30b8c2325eecc --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-app-url-parameters-to-event-properties/index.ts @@ -0,0 +1,93 @@ +import { PluginEvent } from '@posthog/plugin-scaffold' +import { URLSearchParams } from 'url' + +import { LegacyPluginMeta, LegacyTransformationPlugin } from '../../types' +import metadata from './plugin.json' + +export type PluginConfig = { + ignoreCase: 'true' | 'false' + prefix: string + setAsInitialUserProperties: 'true' | 'false' + setAsUserProperties: 'true' | 'false' + suffix: string + parameters: string + alwaysJson: 'true' | 'false' +} + +export type LocalMeta = LegacyPluginMeta & { + global: { + ignoreCase: boolean + setAsInitialUserProperties: boolean + setAsUserProperties: boolean + alwaysJson: boolean + parameters: Set + } + config: PluginConfig +} + +function convertSearchParams(params: URLSearchParams): URLSearchParams { + return new URLSearchParams([...params].map(([key, value]) => [key.toLowerCase(), value]) as [string, string][]) +} + +export const setupPlugin = (meta: LocalMeta): void => { + const { global, config } = meta + + global.ignoreCase = config.ignoreCase === 'true' + global.setAsInitialUserProperties = config.setAsInitialUserProperties === 'true' + global.setAsUserProperties = config.setAsUserProperties === 'true' + global.alwaysJson = config.alwaysJson === 'true' + global.parameters = new Set( + config.parameters ? config.parameters.split(',').map((parameter) => parameter.trim()) : null + ) +} + +export const processEvent = (event: PluginEvent, meta: LocalMeta): PluginEvent => { + if (event.properties?.$current_url) { + const url = new URL(event.properties.$current_url) + const params = meta.global.ignoreCase + ? convertSearchParams(new URLSearchParams(url.searchParams)) + : new URLSearchParams(url.searchParams) + + for (const name of meta.global.parameters) { + let value: string | Array = '' + + if (meta.global.ignoreCase) { + for (const key of params.keys()) { + if (key.toLowerCase() === name.toLowerCase()) { + value = params.getAll(key) + } + } + } else { + value = params.getAll(name) + } + + if (value.length > 0) { + const key = `${meta.config.prefix}${name}${meta.config.suffix}` + + // if we've only got one, then just store the first string as a string + // unless we want them all JSON-ified + const storeValue = value.length === 1 && !meta.global.alwaysJson ? value[0] : JSON.stringify(value) + + event.properties[key] = storeValue + if (meta.global.setAsUserProperties) { + event.properties.$set = event.properties.$set || {} + event.properties.$set[key] = storeValue + } + + if (meta.global.setAsInitialUserProperties) { + event.properties.$set_once = event.properties.$set_once || {} + event.properties.$set_once[`initial_${key}`] = storeValue + } + } + } + } + + return event +} + +export const posthogAppUrlParametersToEventPropertiesPlugin: LegacyTransformationPlugin = { + id: 'posthog-app-url-parameters-to-event-properties', + metadata: metadata as any, + processEvent, + setupPlugin: setupPlugin as any, +} diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-app-url-parameters-to-event-properties/plugin.json b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-app-url-parameters-to-event-properties/plugin.json new file mode 100644 index 0000000000000..6e84ab12d58a4 --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-app-url-parameters-to-event-properties/plugin.json @@ -0,0 +1,61 @@ +{ + "name": "URL parameters to event properties", + "url": "https://github.com/PostHog/posthog-app-url-parameters-to-event-properties", + "description": "Converts URL query parameters to event properties", + "main": "index.ts", + "config": [ + { + "key": "parameters", + "name": "URL query parameters to convert", + "type": "string", + "default": "", + "hint": "Comma separated list of URL query parameters to capture. Leaving this blank will capture nothing." + }, + { + "key": "prefix", + "name": "Prefix", + "type": "string", + "default": "", + "hint": "Add a prefix to the property name e.g. set it to 'prefix_' to get followerId -> prefix_followerId" + }, + { + "key": "suffix", + "name": "Suffix", + "type": "string", + "default": "", + "hint": "Add a suffix to the property name e.g. set it to '_suffix' to get followerId -> followerId_suffix" + }, + { + "key": "ignoreCase", + "name": "Ignore the case of URL parameters", + "type": "choice", + "choices": ["true", "false"], + "default": "false", + "hint": "Ignores the case of parameters e.g. when set to true than followerId would match FollowerId, followerID, FoLlOwErId and similar" + }, + { + "key": "setAsUserProperties", + "name": "Add to user properties", + "type": "choice", + "choices": ["true", "false"], + "default": "false", + "hint": "Additionally adds the property to the user properties" + }, + { + "key": "setAsInitialUserProperties", + "name": "Add to user initial properties", + "type": "choice", + "choices": ["true", "false"], + "default": "false", + "hint": "Additionally adds the property to the user initial properties. This will add a prefix of 'initial_' before the already fully composed property e.g. initial_prefix_followerId_suffix" + }, + { + "key": "alwaysJson", + "name": "Always JSON stringify the property data", + "type": "choice", + "choices": ["true", "false"], + "default": "false", + "hint": "If set, always store the resulting data as a JSON array. (Otherwise, single parameters get stored as-is, and multi-value parameters get stored as a JSON array.)" + } + ] +} diff --git a/plugin-server/src/cdp/legacy-plugins/posthog-filter-out-plugin/index.test.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-filter-out-plugin/index.test.ts similarity index 99% rename from plugin-server/src/cdp/legacy-plugins/posthog-filter-out-plugin/index.test.ts rename to plugin-server/src/cdp/legacy-plugins/_transformations/posthog-filter-out-plugin/index.test.ts index 82d4878d59bc5..88348a64c42e6 100644 --- a/plugin-server/src/cdp/legacy-plugins/posthog-filter-out-plugin/index.test.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-filter-out-plugin/index.test.ts @@ -1,6 +1,6 @@ import { PluginEvent } from '@posthog/plugin-scaffold' -import { LegacyPluginMeta } from '../types' +import { LegacyPluginMeta } from '../../types' import { Filter, processEvent, setupPlugin } from '.' const filters: Filter[] = [ diff --git a/plugin-server/src/cdp/legacy-plugins/posthog-filter-out-plugin/index.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-filter-out-plugin/index.ts similarity index 95% rename from plugin-server/src/cdp/legacy-plugins/posthog-filter-out-plugin/index.ts rename to plugin-server/src/cdp/legacy-plugins/_transformations/posthog-filter-out-plugin/index.ts index 3f3b55b1fcb8e..3de9132c76f98 100644 --- a/plugin-server/src/cdp/legacy-plugins/posthog-filter-out-plugin/index.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-filter-out-plugin/index.ts @@ -1,6 +1,6 @@ import { Meta, PluginAttachment, PluginEvent } from '@posthog/plugin-scaffold' -import { LegacyPluginMeta, LegacyTransformationPlugin } from '../types' +import { LegacyPluginMeta, LegacyTransformationPlugin } from '../../types' import metadata from './plugin.json' export interface Filter { @@ -51,7 +51,8 @@ const operations: Record bool export function setupPlugin({ global, config }: LegacyPluginMeta) { if (config.filters) { try { - const filterGroups = parseFiltersAndMigrate(config.filters) + const filters = typeof config.filters === 'string' ? JSON.parse(config.filters) : config.filters + const filterGroups = parseFiltersAndMigrate(filters) if (!filterGroups) { throw new Error('No filters found') } diff --git a/plugin-server/src/cdp/legacy-plugins/posthog-filter-out-plugin/plugin.json b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-filter-out-plugin/plugin.json similarity index 100% rename from plugin-server/src/cdp/legacy-plugins/posthog-filter-out-plugin/plugin.json rename to plugin-server/src/cdp/legacy-plugins/_transformations/posthog-filter-out-plugin/plugin.json diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-url-normalizer-plugin/index.test.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-url-normalizer-plugin/index.test.ts new file mode 100644 index 0000000000000..ef9e330acc540 --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-url-normalizer-plugin/index.test.ts @@ -0,0 +1,123 @@ +import { PluginEvent } from '@posthog/plugin-scaffold' + +import { LegacyPluginMeta } from '../../types' +import { processEvent } from './index' + +/** + * Given a url, construct a page view event. + * + * @param $current_url The current url of the page view + * @returns A new PostHog page view event + */ +function buildPageViewEvent($current_url: string): PluginEvent { + const event: PluginEvent = { + properties: { $current_url }, + distinct_id: 'distinct_id', + ip: '1.2.3.4', + site_url: 'test.com', + team_id: 0, + now: '2022-06-17T20:21:31.778000+00:00', + event: '$pageview', + uuid: '01817354-06bb-0000-d31c-2c4eed374100', + } + + return event +} + +function buildEventWithoutCurrentUrl(): PluginEvent { + const event: PluginEvent = { + properties: {}, + distinct_id: 'distinct_id', + ip: '1.2.3.4', + site_url: 'test.com', + team_id: 0, + now: '2022-06-17T20:21:31.778000+00:00', + event: '$identify', + uuid: '01817354-06bb-0000-d31c-2c4eed374100', + } + + return event +} + +const meta = { + logger: { + debug: jest.fn(), + }, +} as unknown as LegacyPluginMeta + +describe('processEvent', () => { + it("shouldn't change a url that's already lowercase", () => { + const sourceEvent = buildPageViewEvent('http://www.google.com/test') + + const processedEvent = processEvent(sourceEvent, meta) + + expect(processedEvent?.properties?.$current_url).toEqual('http://www.google.com/test') + }) + + it('should convert the current_url to lowercase', () => { + const sourceEvent = buildPageViewEvent('http://www.GoOGle.com/WhatAreYouThinking') + + const processedEvent = processEvent(sourceEvent, meta) + + expect(processedEvent?.properties?.$current_url).toEqual('http://www.google.com/whatareyouthinking') + }) + + it('should remove the trailing slash from the current_url', () => { + const sourceEvent = buildPageViewEvent('http://www.google.com/this_is_a_test/') + + const processedEvent = processEvent(sourceEvent, meta) + + expect(processedEvent?.properties?.$current_url).toEqual('http://www.google.com/this_is_a_test') + }) + + it("should preserve the trailing slash if it's the only character in the path", () => { + const sourceEvent = buildPageViewEvent('http://www.google.com/') + + const processedEvent = processEvent(sourceEvent, meta) + + expect(processedEvent?.properties?.$current_url).toEqual('http://www.google.com/') + }) + + it('should preserve trailing id anchors', () => { + const sourceEvent = buildPageViewEvent('http://www.google.com/this_is_a_test#id_anchor') + + const processedEvent = processEvent(sourceEvent, meta) + + expect(processedEvent?.properties?.$current_url).toEqual('http://www.google.com/this_is_a_test#id_anchor') + }) + + it('should preserve trailing anchors but drop trailing slashes', () => { + const sourceEvent = buildPageViewEvent('http://www.google.com/this_is_a_test_with_trailing_slash/#id_anchor') + + const processedEvent = processEvent(sourceEvent, meta) + + expect(processedEvent?.properties?.$current_url).toEqual( + 'http://www.google.com/this_is_a_test_with_trailing_slash#id_anchor' + ) + }) + + it("shouldn't modify events that don't have a $current_url set", () => { + const sourceEvent = buildEventWithoutCurrentUrl() + + const processedEvent = processEvent(sourceEvent, meta) + + expect(processedEvent).toEqual(sourceEvent) + expect(processedEvent?.properties).toEqual(sourceEvent.properties) + expect(processedEvent?.properties?.$current_url).toBeUndefined() + }) + + it('should raise an error if the $current_url is an invalid url', () => { + const sourceEvent = buildPageViewEvent('invalid url') + + expect(() => processEvent(sourceEvent, meta)).toThrowError(`Unable to normalize invalid URL: "invalid url"`) + }) + + it('should log the normalized_url for debugging', () => { + const sourceEvent = buildPageViewEvent('http://www.GoOGle.com/WhatAreYouThinking') + processEvent(sourceEvent, meta) + + expect(meta.logger.debug).toHaveBeenCalledWith( + 'event.$current_url: "http://www.GoOGle.com/WhatAreYouThinking" normalized to "http://www.google.com/whatareyouthinking"' + ) + }) +}) diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-url-normalizer-plugin/index.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-url-normalizer-plugin/index.ts new file mode 100644 index 0000000000000..b50780f8a84a2 --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-url-normalizer-plugin/index.ts @@ -0,0 +1,34 @@ +import { PluginEvent } from '@posthog/plugin-scaffold' + +import { LegacyPluginMeta } from '../../types' +import { LegacyTransformationPlugin } from '../../types' +import metadata from './plugin.json' + +function normalizeUrl(url: string): string { + try { + const parsedUrl = new URL(url.toLocaleLowerCase()) + parsedUrl.pathname = parsedUrl.pathname.replace(/\/$/, '') + + return parsedUrl.toString() + } catch (err) { + throw `Unable to normalize invalid URL: "${url}"` + } +} + +export function processEvent(event: PluginEvent, { logger }: LegacyPluginMeta) { + const $current_url = event?.properties?.$current_url + if (event?.properties && $current_url) { + const normalized_url = normalizeUrl($current_url) + event.properties.$current_url = normalized_url + + logger.debug(`event.$current_url: "${$current_url}" normalized to "${normalized_url}"`) + } + + return event +} + +export const posthogUrlNormalizerPlugin: LegacyTransformationPlugin = { + id: 'posthog-url-normalizer-plugin', + metadata: metadata as any, + processEvent, +} diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-url-normalizer-plugin/plugin.json b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-url-normalizer-plugin/plugin.json new file mode 100644 index 0000000000000..be643ef352e2d --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-url-normalizer-plugin/plugin.json @@ -0,0 +1,12 @@ +{ + "name": "URL Normalizer", + "url": "https://github.com/MarkBennett/posthog-url-normalizer-plugin", + "description": "A PostHog plugin to normalize the format of urls in your application allowing you to more easily compare them in insights.", + "main": "index.ts", + "posthogVersion": ">= 1.25.0", + "config": [ + { + "markdown": "Normalize the format of urls in your application allowing you to more easily compare them in insights. By default, this converts all urls to lowercase and strips the trailing slash, overrighting the old `current_url` value." + } + ] +} diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/property-filter-plugin/index.test.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/property-filter-plugin/index.test.ts new file mode 100644 index 0000000000000..31bee7b6b21f2 --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/property-filter-plugin/index.test.ts @@ -0,0 +1,72 @@ +const { createEvent } = require('@posthog/plugin-scaffold/test/utils') +const { processEvent } = require('.') + +const global = { + propertiesToFilter: [ + 'gender', + '$set.gender', + '$set.age', + 'foo.bar.baz.one', + 'nonExisting', + '$set.$not_in_props', + 'no-such.with-dot', + ], +} + +const properties = { + properties: { + name: 'Mr. Hog', + gender: 'male', + age: 12, + $set: { + age: 35, + pet: 'dog', + firstName: 'Post', + gender: 'female', + }, + foo: { + bar: { + baz: { + one: 'one', + two: 'two', + }, + }, + }, + }, +} + +test('event properties are filtered', async () => { + const event = await processEvent(createEvent(properties), { global }) + expect(event.properties).not.toHaveProperty('gender') + expect(event.properties.$set).not.toHaveProperty('age') + expect(event.properties.foo.bar.baz).not.toHaveProperty('one') + expect(event.properties).toHaveProperty('name') + expect(event.properties).toHaveProperty('$set') + expect(event.properties).toHaveProperty('foo') + expect(event.properties.$set).toHaveProperty('firstName', 'Post') + expect(event.properties.foo.bar.baz).toHaveProperty('two', 'two') + expect(event.properties).toEqual({ + name: 'Mr. Hog', + age: 12, + $set: { + pet: 'dog', + firstName: 'Post', + }, + foo: { + bar: { + baz: { + two: 'two', + }, + }, + }, + }) +}) + +const emptyProperties = {} + +test('event properties are empty when no properties are given', async () => { + const event = await processEvent(createEvent(emptyProperties), { global }) + + expect(event.properties).not.toHaveProperty('$set') + expect(event.properties).not.toHaveProperty('foo') +}) diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/property-filter-plugin/index.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/property-filter-plugin/index.ts new file mode 100644 index 0000000000000..204f1422a863c --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/property-filter-plugin/index.ts @@ -0,0 +1,42 @@ +import { PluginEvent } from '@posthog/plugin-scaffold' + +import { LegacyPluginMeta, LegacyTransformationPlugin } from '../../types' +import metadata from './plugin.json' + +export function setupPlugin({ config, global }: LegacyPluginMeta) { + global.propertiesToFilter = config.properties.split(',') +} + +function recursiveRemoveFilterObject(properties: Record, propertyToFilterParts: string[]) { + // if we've reached the final filter part, then we can remove the key if it exists + // otherwise recursively go down the properties object with the remaining filter parts + const currentKey = propertyToFilterParts.shift() + if (currentKey != undefined && currentKey in properties) { + if (propertyToFilterParts.length == 0) { + delete properties[currentKey] + } else { + recursiveRemoveFilterObject(properties[currentKey], propertyToFilterParts) + } + } +} + +export function processEvent(event: PluginEvent, { global }: LegacyPluginMeta) { + const propertiesCopy = event.properties ? { ...event.properties } : {} + + for (const propertyToFilter of global.propertiesToFilter) { + if (propertyToFilter === '$ip') { + event.ip = null + } + + recursiveRemoveFilterObject(propertiesCopy, propertyToFilter.split('.')) + } + + return { ...event, properties: propertiesCopy } +} + +export const propertyFilterPlugin: LegacyTransformationPlugin = { + id: 'property-filter-plugin', + metadata: metadata as any, + processEvent, + setupPlugin, +} diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/property-filter-plugin/plugin.json b/plugin-server/src/cdp/legacy-plugins/_transformations/property-filter-plugin/plugin.json new file mode 100644 index 0000000000000..52cf4932f35e8 --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/property-filter-plugin/plugin.json @@ -0,0 +1,19 @@ +{ + "name": "Property Filter", + "url": "https://github.com/witty-works/posthog-property-filter-plugin", + "description": "This plugin will set all configured properties to null inside an ingested event.", + "main": "index.js", + "config": [ + { + "markdown": "\n\n# Important!\nThis plugin will only work on events ingested **after** the plugin was enabled. This means it **will** register events as being the first if there were events that occured **before** it was enabled. To mitigate this, you could consider renaming the relevant events and creating an [action](https://posthog.com/docs/features/actions) that matches both the old event name and the new one.\n" + }, + { + "key": "properties", + "name": "List of properties to filter out:", + "type": "string", + "default": "", + "hint": "Separate properties with commas, without using spaces, like so: `foo,bar,baz`", + "required": true + } + ] +} \ No newline at end of file diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/taxonomy-plugin/index.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/taxonomy-plugin/index.ts new file mode 100644 index 0000000000000..ccb6566b6f70f --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/taxonomy-plugin/index.ts @@ -0,0 +1,84 @@ +import { LegacyTransformationPlugin } from '../../types' +import metadata from './plugin.json' + +const transformations = [ + { + name: 'camelCase', + matchPattern: /[A-Z]/g, + transform: (str: string, matchPattern: RegExp) => + str[0].toLowerCase() + + str.slice(1).replace(matchPattern, (substr: string) => substr[substr.length - 1].toUpperCase()), + }, + { + name: 'PascalCase', + matchPattern: /[A-Z]/g, + transform: (str: string, matchPattern: RegExp) => + str[0].toUpperCase() + + str.slice(1).replace(matchPattern, (substr: string) => substr[substr.length - 1].toUpperCase()), + }, + { + name: 'snake_case', + matchPattern: /([_])([a-z])/g, + transform: (str: string, matchPattern: RegExp) => defaultTransformation(str, matchPattern, '_'), + }, + { + name: 'kebab_case', + matchPattern: /([-])([a-z])/g, + transform: (str: string, matchPattern: RegExp) => defaultTransformation(str, matchPattern, '-'), + }, + { + name: 'spaces', + matchPattern: /([\s])([a-z])/g, + transform: (str: string, matchPattern: RegExp) => defaultTransformation(str, matchPattern, ' '), + }, +] + +const configSelectionMap = { + camelCase: 0, + PascalCase: 1, + snake_case: 2, + 'kebab-case': 3, + 'spaces in between': 4, +} + +const skippedPostHogEvents = ['survey shown', 'survey sent', 'survey dismissed'] + +function processEventBatch(events, { config }) { + for (const event of events) { + if (!event.event.startsWith('$') && !skippedPostHogEvents.includes(event.event)) { + event.event = standardizeName( + event.event, + transformations[configSelectionMap[config.defaultNamingConvention]] + ) + } + } + return events +} + +const defaultTransformation = (str, matchPattern, sep) => { + const parsedStr = str.replace( + matchPattern, + (substr) => sep + (substr.length === 1 ? substr.toLowerCase() : substr[1].toLowerCase()) + ) + if (parsedStr[0] === sep) { + return parsedStr.slice(1) // Handle PascalCase + } + return parsedStr +} + +const standardizeName = (name, desiredPattern) => { + for (const transformation of transformations) { + if (transformation.name === desiredPattern.name || name.search(transformation.matchPattern) < 0) { + continue + } + return desiredPattern.transform(name, transformation.matchPattern) + } + return name +} + +export const taxonomyPlugin: LegacyTransformationPlugin = { + id: 'taxonomy-plugin', + metadata: metadata as any, + processEventBatch, + setupPlugin, +} diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/taxonomy-plugin/plugin.json b/plugin-server/src/cdp/legacy-plugins/_transformations/taxonomy-plugin/plugin.json new file mode 100644 index 0000000000000..88d4d33c07402 --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/taxonomy-plugin/plugin.json @@ -0,0 +1,24 @@ +{ + "name": "Taxonomy Plugin", + "url": "https://github.com/PostHog/taxonomy-plugin", + "description": "Standardize your event names into a single pattern.", + "main": "index.js", + "config": [ + { + "key": "defaultNamingConvention", + "hint": "", + "name": "Select your default naming pattern", + "type": "choice", + "choices": [ + "camelCase", + "PascalCase", + "snake_case", + "kebab-case", + "spaces in between" + ], + "order": 1, + "default": "camelCase", + "required": true + } + ] +} diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/timestamp-parser-plugin/index.js b/plugin-server/src/cdp/legacy-plugins/_transformations/timestamp-parser-plugin/index.js new file mode 100644 index 0000000000000..4e75b04548fa3 --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/timestamp-parser-plugin/index.js @@ -0,0 +1,18 @@ +function processEvent(event) { + if (event.properties && event['timestamp'] && !isNaN(event['timestamp'])) { + const eventDate = new Date(event['timestamp']) + event.properties['day_of_the_week'] = eventDate.toLocaleDateString('en-GB', { weekday: 'long' }) + const date = eventDate.toLocaleDateString('en-GB').split('/') + event.properties['day'] = Number(date[0]) + event.properties['month'] = Number(date[1]) + event.properties['year'] = Number(date[2]) + event.properties['hour'] = eventDate.getHours() + event.properties['minute'] = eventDate.getMinutes() + } + + return event +} + +module.exports = { + processEvent +} diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/timestamp-parser-plugin/index.tests.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/timestamp-parser-plugin/index.tests.ts new file mode 100644 index 0000000000000..6a016816d84de --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/timestamp-parser-plugin/index.tests.ts @@ -0,0 +1,50 @@ +const { + createEvent, + createIdentify, + createPageview, + createCache, + getMeta, + resetMeta, + clone, +} = require('@posthog/plugin-scaffold/test/utils.js') +const { setupPlugin, processEvent } = require('../index') + + +test('processEvent adds the right properties', async () => { + + const event0 = createEvent({ event: 'Monday 27/01/2021', properties: { $time: 1611772203.557, keepMe: 'nothing changes' } }) + + const event1 = await processEvent(clone(event0), getMeta()) + expect(event1).toEqual({ + ...event0, + properties: { + ...event0.properties, + day_of_the_week: 'Wednesday', + day: '27', + month: '01', + year: '2021', + }, + }) + + const event2 = createEvent({ event: 'Monday 25/01/2021', properties: { $time: 1611587425.118, keepMe: 'nothing changes' } }) + + const event3 = await processEvent(clone(event2), getMeta()) + expect(event3).toEqual({ + ...event2, + properties: { + ...event2.properties, + day_of_the_week: 'Monday', + day: '25', + month: '01', + year: '2021', + }, + }) + +}) + +test('processEvent does not crash with identify', async () => { + const event0 = createIdentify() + + const event1 = await processEvent(clone(event0), getMeta()) + expect(event1).toEqual(event0) +}) \ No newline at end of file diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/timestamp-parser-plugin/plugin.json b/plugin-server/src/cdp/legacy-plugins/_transformations/timestamp-parser-plugin/plugin.json new file mode 100644 index 0000000000000..f5075b6013b5a --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/timestamp-parser-plugin/plugin.json @@ -0,0 +1,6 @@ +{ + "name": "Timestamp Parser", + "url": "https://github.com/PostHog/timestamp-parser-plugin", + "description": "Parse your event timestamps into useful date properties.", + "main": "index.js" +} diff --git a/plugin-server/src/cdp/legacy-plugins/index.ts b/plugin-server/src/cdp/legacy-plugins/index.ts index 02a3482dc2b38..2a6854468380a 100644 --- a/plugin-server/src/cdp/legacy-plugins/index.ts +++ b/plugin-server/src/cdp/legacy-plugins/index.ts @@ -1,6 +1,6 @@ +import { posthogFilterOutPlugin } from './_transformations/posthog-filter-out-plugin' import { customerioPlugin } from './customerio' import { intercomPlugin } from './intercom' -import { posthogFilterOutPlugin } from './posthog-filter-out-plugin' export const DESTINATION_PLUGINS_BY_ID = { [customerioPlugin.id]: customerioPlugin, diff --git a/plugin-server/src/cdp/legacy-plugins/types.ts b/plugin-server/src/cdp/legacy-plugins/types.ts index f2802adffbb7c..0c57b7f17f991 100644 --- a/plugin-server/src/cdp/legacy-plugins/types.ts +++ b/plugin-server/src/cdp/legacy-plugins/types.ts @@ -33,6 +33,6 @@ export type LegacyTransformationPlugin = { name: string config: PluginConfigSchema[] } - processEvent(event: PluginEvent, meta: LegacyPluginMeta): PluginEvent | undefined + processEvent(event: PluginEvent, meta: LegacyPluginMeta): PluginEvent | undefined | null setupPlugin?: (meta: LegacyPluginMeta) => void } From 0b38dbfecf8de473bbca0c2b085465a866aeef8a Mon Sep 17 00:00:00 2001 From: Ben White Date: Tue, 28 Jan 2025 16:02:27 +0100 Subject: [PATCH 052/163] Fixes --- .../property-filter-plugin/index.test.ts | 29 +++- .../taxonomy-plugin/index.test.ts | 146 ++++++++++++++++++ .../_transformations/taxonomy-plugin/index.ts | 63 +++----- .../taxonomy-plugin/legacy.ts | 80 ++++++++++ 4 files changed, 270 insertions(+), 48 deletions(-) create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/taxonomy-plugin/index.test.ts create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/taxonomy-plugin/legacy.ts diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/property-filter-plugin/index.test.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/property-filter-plugin/index.test.ts index 31bee7b6b21f2..84cdaa40cc8db 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/property-filter-plugin/index.test.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/property-filter-plugin/index.test.ts @@ -1,5 +1,17 @@ -const { createEvent } = require('@posthog/plugin-scaffold/test/utils') -const { processEvent } = require('.') +import { PluginEvent } from '@posthog/plugin-scaffold' + +import { LegacyPluginMeta } from '../../types' +import { processEvent } from './index' + +const createEvent = (event: Partial): PluginEvent => + ({ + distinct_id: '1', + event: '$pageview', + properties: { + ...event.properties, + }, + ...event, + } as unknown as PluginEvent) const global = { propertiesToFilter: [ @@ -13,6 +25,11 @@ const global = { ], } +const meta: LegacyPluginMeta = { + global, + config: {}, +} as unknown as LegacyPluginMeta + const properties = { properties: { name: 'Mr. Hog', @@ -35,8 +52,8 @@ const properties = { }, } -test('event properties are filtered', async () => { - const event = await processEvent(createEvent(properties), { global }) +test('event properties are filtered', () => { + const event = processEvent(createEvent(properties), meta) expect(event.properties).not.toHaveProperty('gender') expect(event.properties.$set).not.toHaveProperty('age') expect(event.properties.foo.bar.baz).not.toHaveProperty('one') @@ -64,8 +81,8 @@ test('event properties are filtered', async () => { const emptyProperties = {} -test('event properties are empty when no properties are given', async () => { - const event = await processEvent(createEvent(emptyProperties), { global }) +test('event properties are empty when no properties are given', () => { + const event = processEvent(createEvent(emptyProperties), meta) expect(event.properties).not.toHaveProperty('$set') expect(event.properties).not.toHaveProperty('foo') diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/taxonomy-plugin/index.test.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/taxonomy-plugin/index.test.ts new file mode 100644 index 0000000000000..2fe0ff497f265 --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/taxonomy-plugin/index.test.ts @@ -0,0 +1,146 @@ +import { PluginEvent } from '@posthog/plugin-scaffold' + +import { LegacyPluginMeta } from '../../types' +import { processEvent } from './legacy' + +const createEvent = (event: Partial): PluginEvent => + ({ + distinct_id: '1', + event: '$pageview', + properties: { + ...event.properties, + }, + ...event, + } as unknown as PluginEvent) + +describe('taxonomy-plugin', () => { + describe('event name transformations', () => { + it('should transform to camelCase', () => { + const testCases = [ + ['user_signed_up', 'userSignedUp'], + // NOTE: This is how the legacy plugin worked - its a bug + ['User Logged In', 'user Logged In'], + ['checkout-completed', 'checkoutCompleted'], + ] + + for (const [input, expected] of testCases) { + const event = createEvent({ event: input }) + const result = processEvent(event, { + config: { defaultNamingConvention: 'camelCase' }, + global: {}, + } as unknown as LegacyPluginMeta) + expect(result.event).toBe(expected) + } + }) + + it('should transform to PascalCase', () => { + const testCases = [ + ['user_signed_up', 'UserSignedUp'], + ['user logged in', 'UserLoggedIn'], + ['checkout-completed', 'CheckoutCompleted'], + ] + + for (const [input, expected] of testCases) { + const event = createEvent({ event: input }) + const result = processEvent(event, { + config: { defaultNamingConvention: 'PascalCase' }, + global: {}, + } as unknown as LegacyPluginMeta) + expect(result.event).toBe(expected) + } + }) + + it('should transform to snake_case', () => { + const testCases = [ + ['userSignedUp', 'user_signed_up'], + // NOTE: This is how the legacy plugin worked - its a bug + ['User Logged In', 'user _logged _in'], + ['checkout-completed', 'checkout_completed'], + ] + + for (const [input, expected] of testCases) { + const event = createEvent({ event: input }) + const result = processEvent(event, { + config: { defaultNamingConvention: 'snake_case' }, + global: {}, + } as unknown as LegacyPluginMeta) + expect(result.event).toBe(expected) + } + }) + + it('should transform to kebab-case', () => { + const testCases = [ + ['userSignedUp', 'user-signed-up'], + ['User Logged In', 'user -logged -in'], // NOTE: This is how the legacy plugin worked - its a bug + ['checkout_completed', 'checkout-completed'], + ] + + for (const [input, expected] of testCases) { + const event = createEvent({ event: input }) + const result = processEvent(event, { + config: { defaultNamingConvention: 'kebab-case' }, + global: {}, + } as unknown as LegacyPluginMeta) + expect(result.event).toBe(expected) + } + }) + + it('should transform to spaces', () => { + const testCases = [ + ['userSignedUp', 'user signed up'], + ['user-logged-in', 'user logged in'], + ['checkout_completed', 'checkout completed'], + ] + + for (const [input, expected] of testCases) { + const event = createEvent({ event: input }) + const result = processEvent(event, { + config: { defaultNamingConvention: 'spaces in between' }, + global: {}, + } as unknown as LegacyPluginMeta) + expect(result.event).toBe(expected) + } + }) + }) + + describe('special cases', () => { + it('should not transform PostHog system events starting with $', () => { + const testCases = ['$pageview', '$autocapture', '$feature_flag_called'] + + for (const systemEvent of testCases) { + const event = createEvent({ event: systemEvent }) + const result = processEvent(event, { + config: { defaultNamingConvention: 'camelCase' }, + global: {}, + } as unknown as LegacyPluginMeta) + expect(result.event).toBe(systemEvent) + } + }) + + it('should not transform skipped PostHog events', () => { + const testCases = ['survey shown', 'survey sent', 'survey dismissed'] + + for (const surveyEvent of testCases) { + const event = createEvent({ event: surveyEvent }) + const result = processEvent(event, { + config: { defaultNamingConvention: 'camelCase' }, + global: {}, + } as unknown as LegacyPluginMeta) + expect(result.event).toBe(surveyEvent) + } + }) + + it('should preserve other event properties', () => { + const event = createEvent({ event: 'user_signed_up', properties: { foo: 'bar' } }) + const result = processEvent(event, { + config: { defaultNamingConvention: 'camelCase' }, + global: {}, + } as unknown as LegacyPluginMeta) + + expect(result).toEqual({ + ...event, + event: 'userSignedUp', + }) + }) + }) +}) diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/taxonomy-plugin/index.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/taxonomy-plugin/index.ts index ccb6566b6f70f..1f356cfe43788 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/taxonomy-plugin/index.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/taxonomy-plugin/index.ts @@ -1,61 +1,51 @@ -import { LegacyTransformationPlugin } from '../../types' +import { PluginEvent } from '@posthog/plugin-scaffold' + +import { LegacyPluginMeta, LegacyTransformationPlugin } from '../../types' import metadata from './plugin.json' -const transformations = [ - { - name: 'camelCase', +type Transformation = { + matchPattern: RegExp + transform: (str: string, matchPattern: RegExp) => string +} + +const transformations: Record = { + camelCase: { matchPattern: /[A-Z]/g, transform: (str: string, matchPattern: RegExp) => str[0].toLowerCase() + str.slice(1).replace(matchPattern, (substr: string) => substr[substr.length - 1].toUpperCase()), }, - { - name: 'PascalCase', + PascalCase: { matchPattern: /[A-Z]/g, transform: (str: string, matchPattern: RegExp) => str[0].toUpperCase() + str.slice(1).replace(matchPattern, (substr: string) => substr[substr.length - 1].toUpperCase()), }, - { - name: 'snake_case', + snake_case: { matchPattern: /([_])([a-z])/g, transform: (str: string, matchPattern: RegExp) => defaultTransformation(str, matchPattern, '_'), }, - { - name: 'kebab_case', + 'kebab-case': { matchPattern: /([-])([a-z])/g, transform: (str: string, matchPattern: RegExp) => defaultTransformation(str, matchPattern, '-'), }, - { - name: 'spaces', + 'spaces in between': { matchPattern: /([\s])([a-z])/g, transform: (str: string, matchPattern: RegExp) => defaultTransformation(str, matchPattern, ' '), }, -] - -const configSelectionMap = { - camelCase: 0, - PascalCase: 1, - snake_case: 2, - 'kebab-case': 3, - 'spaces in between': 4, } const skippedPostHogEvents = ['survey shown', 'survey sent', 'survey dismissed'] -function processEventBatch(events, { config }) { - for (const event of events) { - if (!event.event.startsWith('$') && !skippedPostHogEvents.includes(event.event)) { - event.event = standardizeName( - event.event, - transformations[configSelectionMap[config.defaultNamingConvention]] - ) - } +export function processEvent(event: PluginEvent, { config }: LegacyPluginMeta) { + if (!event.event.startsWith('$') && !skippedPostHogEvents.includes(event.event)) { + const transformer = transformations[config.defaultNamingConvention] + event.event = transformer.transform(event.event, transformer.matchPattern) } - return events + return event } -const defaultTransformation = (str, matchPattern, sep) => { +const defaultTransformation = (str: string, matchPattern: RegExp, sep: string) => { const parsedStr = str.replace( matchPattern, (substr) => sep + (substr.length === 1 ? substr.toLowerCase() : substr[1].toLowerCase()) @@ -66,19 +56,8 @@ const defaultTransformation = (str, matchPattern, sep) => { return parsedStr } -const standardizeName = (name, desiredPattern) => { - for (const transformation of transformations) { - if (transformation.name === desiredPattern.name || name.search(transformation.matchPattern) < 0) { - continue - } - return desiredPattern.transform(name, transformation.matchPattern) - } - return name -} - export const taxonomyPlugin: LegacyTransformationPlugin = { id: 'taxonomy-plugin', metadata: metadata as any, - processEventBatch, - setupPlugin, + processEvent, } diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/taxonomy-plugin/legacy.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/taxonomy-plugin/legacy.ts new file mode 100644 index 0000000000000..1402ff385dde6 --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/taxonomy-plugin/legacy.ts @@ -0,0 +1,80 @@ +import { PluginEvent } from '@posthog/plugin-scaffold' + +import { LegacyPluginMeta } from '../../types' + +type Transformation = { + name: string + matchPattern: RegExp + transform: (str: string, matchPattern: RegExp) => string +} + +const transformations: Transformation[] = [ + { + name: 'camelCase', + matchPattern: /[A-Z]/g, + transform: (str: string, matchPattern: RegExp) => + str[0].toLowerCase() + + str.slice(1).replace(matchPattern, (substr: string) => substr[substr.length - 1].toUpperCase()), + }, + { + name: 'PascalCase', + matchPattern: /[A-Z]/g, + transform: (str: string, matchPattern: RegExp) => + str[0].toUpperCase() + + str.slice(1).replace(matchPattern, (substr) => substr[substr.length - 1].toUpperCase()), + }, + { + name: 'snake_case', + matchPattern: /([_])([a-z])/g, + transform: (str: string, matchPattern: RegExp) => defaultTransformation(str, matchPattern, '_'), + }, + { + name: 'kebab_case', + matchPattern: /([-])([a-z])/g, + transform: (str: string, matchPattern: RegExp) => defaultTransformation(str, matchPattern, '-'), + }, + { + name: 'spaces', + matchPattern: /([\s])([a-z])/g, + transform: (str: string, matchPattern: RegExp) => defaultTransformation(str, matchPattern, ' '), + }, +] + +const configSelectionMap: Record = { + camelCase: 0, + PascalCase: 1, + snake_case: 2, + 'kebab-case': 3, + 'spaces in between': 4, +} + +const skippedPostHogEvents = ['survey shown', 'survey sent', 'survey dismissed'] + +export function processEvent(event: PluginEvent, { config }: LegacyPluginMeta) { + if (!event.event.startsWith('$') && !skippedPostHogEvents.includes(event.event)) { + const defaultTransformation = configSelectionMap[config.defaultNamingConvention] + event.event = standardizeName(event.event, transformations[defaultTransformation]) + } + return event +} + +const defaultTransformation = (str: string, matchPattern: RegExp, sep: string) => { + const parsedStr = str.replace( + matchPattern, + (substr) => sep + (substr.length === 1 ? substr.toLowerCase() : substr[1].toLowerCase()) + ) + if (parsedStr[0] === sep) { + return parsedStr.slice(1) // Handle PascalCase + } + return parsedStr +} + +const standardizeName = (name: string, desiredPattern: Transformation) => { + for (const transformation of transformations) { + if (transformation.name === desiredPattern.name || name.search(transformation.matchPattern) < 0) { + continue + } + return desiredPattern.transform(name, transformation.matchPattern) + } + return name +} From c4510bebce6472fcce9bb04f9f5c12d35e92b2b9 Mon Sep 17 00:00:00 2001 From: Ben White Date: Tue, 28 Jan 2025 16:14:08 +0100 Subject: [PATCH 053/163] Fixed semver flattener --- .../semver-flattener-plugin/index.test.ts | 111 ++++++++++++++++++ .../semver-flattener-plugin/index.ts | 63 ++++++++++ .../semver-flattener-plugin/plugin.json | 18 +++ 3 files changed, 192 insertions(+) create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/semver-flattener-plugin/index.test.ts create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/semver-flattener-plugin/index.ts create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/semver-flattener-plugin/plugin.json diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/semver-flattener-plugin/index.test.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/semver-flattener-plugin/index.test.ts new file mode 100644 index 0000000000000..b3192f8693272 --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/semver-flattener-plugin/index.test.ts @@ -0,0 +1,111 @@ +import { PluginEvent } from '@posthog/plugin-scaffold' + +import { LegacyPluginMeta } from '../../types' +import { processEvent } from './index' + +interface SemanticVersionTestCase { + versionString: string + expected: any +} + +const createEvent = (event: Partial): PluginEvent => + ({ + distinct_id: '1', + event: '$pageview', + properties: { + ...event.properties, + }, + ...event, + } as unknown as PluginEvent) + +const meta = { + config: { + properties: 'targetted_version, another_targetted_version', + }, + logger: { + log: jest.fn(), + }, +} as unknown as LegacyPluginMeta + +describe('the semver flattener plugin', () => { + test('processEvent adds properties when they match config', async () => { + // Create a random event + const event0: PluginEvent = createEvent({ + uuid: 'the-uuid', + event: 'booking completed', + properties: { + targetted_version: '1.12.20', + not_a_targetted_version: '1.34.53', + another_targetted_version: '1.23.14-pre+build.12345', + }, + }) as PluginEvent + + const processedEvent = processEvent(event0, meta) + + expect(processedEvent?.properties).toEqual({ + targetted_version: '1.12.20', + targetted_version__major: 1, + targetted_version__minor: 12, + targetted_version__patch: 20, + not_a_targetted_version: '1.34.53', + another_targetted_version: '1.23.14-pre+build.12345', + another_targetted_version__major: 1, + another_targetted_version__minor: 23, + another_targetted_version__patch: 14, + another_targetted_version__preRelease: 'pre', + another_targetted_version__build: 'build.12345', + }) + }) + + const versionExamples: SemanticVersionTestCase[] = [ + { + versionString: '1.2.3', + expected: { major: 1, minor: 2, patch: 3, build: undefined }, + }, + { + versionString: '22.7', + expected: { major: 22, minor: 7, preRelease: undefined, build: undefined }, + }, + { + versionString: '22.7-pre-release', + expected: { major: 22, minor: 7, patch: undefined, preRelease: 'pre-release', build: undefined }, + }, + { + versionString: '1.0.0-alpha+001', + expected: { major: 1, minor: 0, patch: 0, preRelease: 'alpha', build: '001' }, + }, + { + versionString: '1.0.0+20130313144700', + expected: { major: 1, minor: 0, patch: 0, build: '20130313144700' }, + }, + { + versionString: '1.2.3-beta+exp.sha.5114f85', + expected: { major: 1, minor: 2, patch: 3, preRelease: 'beta', build: 'exp.sha.5114f85' }, + }, + { + versionString: '1.0.0+21AF26D3—-117B344092BD', + expected: { major: 1, minor: 0, patch: 0, preRelease: undefined, build: '21AF26D3—-117B344092BD' }, + }, + ] + versionExamples.forEach(({ versionString, expected }) => { + test(`can process ${versionString}`, () => { + const event = createEvent({ + properties: { + targetted_version: versionString, + }, + }) + + const processedEvent = processEvent(event, meta) + + console.log(processedEvent?.properties) + expect(processedEvent?.properties).toEqual({ + targetted_version: versionString, + targetted_version__major: expected.major, + targetted_version__minor: expected.minor, + targetted_version__patch: expected.patch, + targetted_version__preRelease: expected.preRelease, + targetted_version__build: expected.build, + }) + }) + }) +}) diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/semver-flattener-plugin/index.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/semver-flattener-plugin/index.ts new file mode 100644 index 0000000000000..d93739ce14236 --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/semver-flattener-plugin/index.ts @@ -0,0 +1,63 @@ +import { PluginEvent } from '@posthog/plugin-scaffold' + +import { LegacyPluginMeta, LegacyTransformationPlugin } from '../../types' +import metadata from './plugin.json' + +interface VersionParts { + major: number + minor: number + patch?: number + preRelease?: string + build?: string +} +//examples from semver spec +//Examples: 1.0.0-alpha+001, 1.0.0+20130313144700, 1.0.0-beta+exp.sha.5114f85, 1.0.0+21AF26D3—-117B344092BD. +//see https://semver.org/#backusnaur-form-grammar-for-valid-semver-versions +const splitVersion = (candidate: string): VersionParts => { + const [head, build] = candidate.split('+') + const [version, ...preRelease] = head.split('-') + const [major, minor, patch] = version.split('.') + return { + major: Number(major), + minor: Number(minor), + patch: patch ? Number(patch) : undefined, + preRelease: preRelease.join('-') || undefined, + build, + } +} + +export function processEvent(event: PluginEvent, meta: LegacyPluginMeta) { + if (!event.properties) { + return + } + + const properties = meta.config.properties as string + const targetProperties = properties.split(',').map((s) => s.trim()) + + for (const target of targetProperties) { + const candidate = event.properties[target] + meta.logger.log('found candidate property: ', target, ' matches ', candidate) + if (candidate) { + const { major, minor, patch, preRelease, build } = splitVersion(candidate) + event.properties[`${target}__major`] = major + event.properties[`${target}__minor`] = minor + if (patch !== undefined) { + event.properties[`${target}__patch`] = patch + } + if (preRelease !== undefined) { + event.properties[`${target}__preRelease`] = preRelease + } + if (build !== undefined) { + event.properties[`${target}__build`] = build + } + } + } + + return event +} + +export const semverFlattenerPlugin: LegacyTransformationPlugin = { + id: 'semver-flattener-plugin', + metadata: metadata as any, + processEvent, +} diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/semver-flattener-plugin/plugin.json b/plugin-server/src/cdp/legacy-plugins/_transformations/semver-flattener-plugin/plugin.json new file mode 100644 index 0000000000000..fe344eb252a77 --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/semver-flattener-plugin/plugin.json @@ -0,0 +1,18 @@ +{ + "name": "posthog-semver-flattener", + "url": "https://github.com/PostHog/posthog-semver-flattener-plugin", + "main": "index.ts", + "config": [ + { + "markdown": "Processes specified properties to flatten sematic versions. Assumes any property contains a string which matches [the SemVer specification](https://semver.org/#backusnaur-form-grammar-for-valid-semver-versions)" + }, + { + "key": "properties", + "name": "comma separated properties to explode version number from", + "type": "string", + "hint": "my_version_number,app_version", + "default": "", + "required": true + } + ] +} From 123af184c01faa97ff0a018d37306a934ee99c0a Mon Sep 17 00:00:00 2001 From: Ben White Date: Tue, 28 Jan 2025 16:20:16 +0100 Subject: [PATCH 054/163] Add remaining transforms --- .../semver-flattener-plugin/index.test.ts | 3 +- .../user-agent-plugin/index.test.ts | 276 ++++++++++++++++++ .../user-agent-plugin/index.ts | 161 ++++++++++ .../user-agent-plugin/plugin.json | 36 +++ 4 files changed, 474 insertions(+), 2 deletions(-) create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/user-agent-plugin/index.test.ts create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/user-agent-plugin/index.ts create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/user-agent-plugin/plugin.json diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/semver-flattener-plugin/index.test.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/semver-flattener-plugin/index.test.ts index b3192f8693272..fb9f9b9bad819 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/semver-flattener-plugin/index.test.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/semver-flattener-plugin/index.test.ts @@ -28,7 +28,7 @@ const meta = { } as unknown as LegacyPluginMeta describe('the semver flattener plugin', () => { - test('processEvent adds properties when they match config', async () => { + test('processEvent adds properties when they match config', () => { // Create a random event const event0: PluginEvent = createEvent({ uuid: 'the-uuid', @@ -97,7 +97,6 @@ describe('the semver flattener plugin', () => { const processedEvent = processEvent(event, meta) - console.log(processedEvent?.properties) expect(processedEvent?.properties).toEqual({ targetted_version: versionString, targetted_version__major: expected.major, diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/user-agent-plugin/index.test.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/user-agent-plugin/index.test.ts new file mode 100644 index 0000000000000..92359d56f3950 --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/user-agent-plugin/index.test.ts @@ -0,0 +1,276 @@ +import { PluginEvent } from '@posthog/plugin-scaffold' + +import { processEvent, UserAgentMeta } from './index' + +function makeMeta(options?: { + enable?: boolean + enableSegmentAnalyticsJs?: boolean + overrideUserAgentDetails?: boolean + debugMode?: boolean +}): UserAgentMeta { + return { + global: { + enabledPlugin: options?.enable ?? true, + enableSegmentAnalyticsJs: options?.enableSegmentAnalyticsJs ?? false, + overrideUserAgentDetails: options?.overrideUserAgentDetails ?? true, + debugMode: options?.debugMode ?? false, + }, + config: { + enable: options?.enable ? 'true' : 'false', + enableSegmentAnalyticsJs: options?.enableSegmentAnalyticsJs ? 'true' : 'false', + overrideUserAgentDetails: options?.overrideUserAgentDetails ? 'true' : 'false', + }, + logger: { + log: jest.fn(), + warn: jest.fn(), + } as any, + } as unknown as UserAgentMeta +} + +describe('useragent-plugin', () => { + test('should not process event when disabled', () => { + const event = { properties: {} } + const processedEvent = processEvent(event as any, makeMeta()) + expect(Object.keys(processedEvent.properties!)).toStrictEqual(Object.keys(event.properties)) + }) + + test('should not process event when $userAgent is missing', () => { + const event = { + properties: { + $lib: 'posthog-node', + }, + } as unknown as PluginEvent + + const processedEvent = processEvent(event, makeMeta()) + expect(Object.keys(processedEvent.properties!)).toStrictEqual(['$lib']) + }) + + test('should not process event when $userAgent is empty', () => { + const event = { + properties: { + $useragent: '', + $lib: 'posthog-node', + }, + } as unknown as PluginEvent + + const processedEvent = processEvent(event, makeMeta()) + expect(Object.keys(processedEvent.properties!)).toStrictEqual(['$lib']) + }) + + test('should add user agent details when $useragent property exists', () => { + const event = { + properties: { + $useragent: + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0 Safari/605.1.15', + $lib: 'posthog-node', + }, + } as unknown as PluginEvent + + const processedEvent = processEvent(event, makeMeta()) + expect(Object.keys(processedEvent.properties!)).toEqual( + expect.arrayContaining([ + '$lib', + '$browser', + '$browser_version', + '$os', + '$device', + '$device_type', + '$browser_type', + ]) + ) + expect(processedEvent.properties!).toStrictEqual( + expect.objectContaining({ + $browser: 'safari', + $browser_version: '14.0.0', + $os: 'Mac OS', + }) + ) + }) + + test('should add user agent details when $user-agent property exists', () => { + const event = { + properties: { + '$user-agent': + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0 Safari/605.1.15', + $lib: 'posthog-node', + }, + } as unknown as PluginEvent + + const processedEvent = processEvent(event, makeMeta()) + expect(Object.keys(processedEvent.properties!)).toEqual( + expect.arrayContaining([ + '$lib', + '$browser', + '$browser_version', + '$os', + '$device', + '$device_type', + '$browser_type', + ]) + ) + expect(processedEvent.properties!).toStrictEqual( + expect.objectContaining({ + $browser: 'safari', + $browser_version: '14.0.0', + $os: 'Mac OS', + $device: '', + $device_type: 'Desktop', + }) + ) + }) + + test('should add user agent details when $user_agent property exists', () => { + const event = { + properties: { + $user_agent: + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0 Safari/605.1.15', + $lib: 'posthog-node', + }, + } as unknown as PluginEvent + + const processedEvent = processEvent(event, makeMeta()) + expect(Object.keys(processedEvent.properties!)).toEqual( + expect.arrayContaining([ + '$lib', + '$browser', + '$browser_version', + '$os', + '$device', + '$device_type', + '$browser_type', + ]) + ) + expect(processedEvent.properties!).toStrictEqual( + expect.objectContaining({ + $browser: 'safari', + $browser_version: '14.0.0', + $os: 'Mac OS', + $device: '', + $device_type: 'Desktop', + }) + ) + }) + + test('should return correct browser properties for given $browser property', () => { + const event = { + id: '017dc2cb-9fe0-0000-ceed-5ef8e328261d', + timestamp: '2021-12-16T10:31:04.234000+00:00', + event: 'check', + distinct_id: '91786645996505845983216505144491686624250709556909346823253562854100595129050', + properties: { + $ip: '31.164.196.102', + $lib: 'posthog-python', + $lib_version: '1.4.4', + $plugins_deferred: [], + $plugins_failed: [], + $plugins_succeeded: ['GeoIP (3347)', 'useragentplugin (3348)'], + $useragent: + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36 Edg/96.0.1054.57', + }, + elements_chain: '', + } as unknown as PluginEvent + + const processedEvent = processEvent(event, makeMeta({ overrideUserAgentDetails: false })) + + expect(Object.keys(processedEvent.properties!)).toEqual( + expect.arrayContaining(['$browser', '$browser_version', '$os', '$device', '$device_type', '$browser_type']) + ) + + expect(processedEvent.properties!).toStrictEqual( + expect.objectContaining({ + $browser: 'edge-chromium', + $browser_version: '96.0.1054', + $os: 'Mac OS', + $device: '', + $device_type: 'Desktop', + }) + ) + }) + + test('should return correct browser properties for an iPhone useragent', () => { + const event = { + id: '017dc2cb-9fe0-0000-ceed-5ef8e328261d', + timestamp: '2021-12-16T10:31:04.234000+00:00', + event: 'check', + distinct_id: '91786645996505845983216505144491686624250709556909346823253562854100595129050', + properties: { + $ip: '31.164.196.102', + $lib: 'posthog-python', + $lib_version: '1.4.4', + $plugins_deferred: [], + $plugins_failed: [], + $plugins_succeeded: ['GeoIP (3347)', 'useragentplugin (3348)'], + $useragent: + 'Mozilla/5.0 (iPhone; CPU iPhone OS 15_4_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.4 Mobile/15E148 Safari/604.1', + }, + elements_chain: '', + } as unknown as PluginEvent + + const processedEvent = processEvent(event, makeMeta({ overrideUserAgentDetails: false })) + + expect(Object.keys(processedEvent.properties!)).toEqual( + expect.arrayContaining(['$browser', '$browser_version', '$os', '$device', '$device_type', '$browser_type']) + ) + + expect(processedEvent.properties!).toStrictEqual( + expect.objectContaining({ + $browser: 'ios', + $browser_version: '15.4.0', + $os: 'iOS', + $device: 'iPhone', + $device_type: 'Mobile', + }) + ) + }) + + test('should not override existing properties when overrideUserAgentDetails is disabled', () => { + const event = { + properties: { + $browser: 'safari', + $browser_version: '14.0', + $os: 'macos', + $device: '', + $device_type: 'Desktop', + $useragent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:82.0) Gecko/20100101 Firefox/82.0', + $lib: 'posthog-node', + }, + } as unknown as PluginEvent + + const processedEvent = processEvent(event, makeMeta({ overrideUserAgentDetails: false })) + expect(processedEvent.properties!).toStrictEqual( + expect.objectContaining({ + $browser: 'safari', + $browser_version: '14.0', + $os: 'macos', + $device: '', + $device_type: 'Desktop', + }) + ) + }) + + describe('enableSegmentAnalyticsJs is true', () => { + test('should add user agent details when segment_userAgent property exists', () => { + const event = { + properties: { + segment_userAgent: + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0 Safari/605.1.15', + $lib: 'posthog-node', + }, + } as unknown as PluginEvent + + const processedEvent = processEvent(event, makeMeta({ enableSegmentAnalyticsJs: true })) + expect(Object.keys(processedEvent.properties!)).toEqual( + expect.arrayContaining(['$lib', '$browser', '$browser_version', '$os', '$browser_type']) + ) + expect(processedEvent.properties!).toStrictEqual( + expect.objectContaining({ + $browser: 'safari', + $browser_version: '14.0.0', + $os: 'Mac OS', + segment_userAgent: + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0 Safari/605.1.15', + }) + ) + }) + }) +}) diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/user-agent-plugin/index.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/user-agent-plugin/index.ts new file mode 100644 index 0000000000000..162adad07e2fd --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/user-agent-plugin/index.ts @@ -0,0 +1,161 @@ +import { PluginEvent } from '@posthog/plugin-scaffold' +import { detect } from 'detect-browser' + +import { LegacyPluginMeta } from '../../types' + +export type UserAgentMeta = LegacyPluginMeta & { + config: { + enable: string + enableSegmentAnalyticsJs?: string + overrideUserAgentDetails?: string + debugMode?: string + } + global: { + enabledPlugin: boolean + enableSegmentAnalyticsJs: boolean + overrideUserAgentDetails: boolean + debugMode: boolean + } +} + +/** + * Setup of the plugin + * @param param0 the metadata of the plugin + */ +export function setupPlugin({ config, global }: UserAgentMeta) { + try { + global.enableSegmentAnalyticsJs = config.enableSegmentAnalyticsJs === 'true' + global.overrideUserAgentDetails = config.overrideUserAgentDetails === 'true' + global.debugMode = config.debugMode === 'true' + } catch (e: unknown) { + throw new Error('Failed to read the configuration') + } +} + +/** + * Process the event + */ +export function processEvent(event: PluginEvent, { global, logger }: UserAgentMeta) { + const properties = event.properties || {} + const availableKeysOfEvent = Object.keys(properties) + + let userAgent = '' + + if (global.enableSegmentAnalyticsJs) { + // If the segment integration is enabled and the segment_userAgent is missing, we skip the processing of the event + const hasSegmentUserAgentKey = availableKeysOfEvent.includes('segment_userAgent') + if (!hasSegmentUserAgentKey) { + if (global.debugMode) { + logger.warn(`UserAgentPlugin.processEvent(): Event is missing segment_userAgent`) + } + + return event + } + + // Extract user agent from event properties + userAgent = `${properties.segment_userAgent ?? ''}` + } else { + // If the magical property name $useragent is missing, we skip the processing of the event + const hasUserAgentKey = + availableKeysOfEvent.includes('$user-agent') || + availableKeysOfEvent.includes('$useragent') || + availableKeysOfEvent.includes('$user_agent') + if (!hasUserAgentKey) { + if (global.debugMode) { + logger.warn(`UserAgentPlugin.processEvent(): Event is missing $useragent or $user-agent`) + } + + return event + } + + // Extract user agent from event properties + if (properties.$useragent) { + userAgent = properties.$useragent + } else if (properties['$user-agent']) { + userAgent = properties['$user-agent'] + } else if (properties.$user_agent) { + userAgent = properties.$user_agent + } + + // Remove the unnecessary $useragent or $user-agent user property + delete properties.$useragent + delete properties['$user-agent'] + delete properties.$user_agent + } + + if (!userAgent || userAgent === '') { + if (global.debugMode) { + logger.warn(`UserAgentPlugin.processEvent(): $useragent is empty`) + } + + return event + } + + const agentInfo = detect(userAgent) + const device = detectDevice(userAgent) + const deviceType = detectDeviceType(userAgent) + + const eventProperties = Object.keys(properties) + const hasBrowserProperties = eventProperties.some((value: string) => + ['$browser', '$browser_version', '$os', '$device', '$device_type'].includes(value) + ) + + if (!global.overrideUserAgentDetails && hasBrowserProperties) { + if (global.debugMode) { + logger.warn( + `UserAgentPlugin.processEvent(): The event has $browser, $browser_version, $os, $device, or $device_type but the option 'overrideUserAgentDetails' is not enabled.` + ) + } + + return event + } + + // The special Posthog property names are retrieved from: + // https://github.com/PostHog/posthog/blob/master/frontend/src/lib/components/PropertyKeyInfo.tsx + properties['$device'] = device + properties['$device_type'] = deviceType + + if (agentInfo) { + properties['$browser'] = agentInfo.name + properties['$browser_version'] = agentInfo.version + properties['$os'] = agentInfo.os + // Custom property + properties['$browser_type'] = agentInfo.type + } + + event.properties = properties + + return event +} + +// detectDevice and detectDeviceType from https://github.com/PostHog/posthog-js/blob/9abedce5ac877caeb09205c4b693988fc09a63ca/src/utils.js#L808-L837 +function detectDevice(userAgent: string) { + if (/Windows Phone/i.test(userAgent) || /WPDesktop/.test(userAgent)) { + return 'Windows Phone' + } else if (/iPad/.test(userAgent)) { + return 'iPad' + } else if (/iPod/.test(userAgent)) { + return 'iPod Touch' + } else if (/iPhone/.test(userAgent)) { + return 'iPhone' + } else if (/(BlackBerry|PlayBook|BB10)/i.test(userAgent)) { + return 'BlackBerry' + } else if (/Android/.test(userAgent) && !/Mobile/.test(userAgent)) { + return 'Android Tablet' + } else if (/Android/.test(userAgent)) { + return 'Android' + } else { + return '' + } +} + +function detectDeviceType(userAgent: string) { + const device = detectDevice(userAgent) + if (device === 'iPad' || device === 'Android Tablet') { + return 'Tablet' + } else if (device) { + return 'Mobile' + } else { + return 'Desktop' + } +} diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/user-agent-plugin/plugin.json b/plugin-server/src/cdp/legacy-plugins/_transformations/user-agent-plugin/plugin.json new file mode 100644 index 0000000000000..dd5bd63d3d737 --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/user-agent-plugin/plugin.json @@ -0,0 +1,36 @@ +{ + "name": "User Agent Populator", + "url": "https://github.com/posthog/useragent-plugin", + "description": "Enhances events with user agent details", + "main": "dist/index.js", + "config": [ + { + "markdown": "User Agent plugin allows you to populate events with the $browser, $browser_version for PostHog Clients that don't typically populate these properties" + }, + { + "key": "overrideUserAgentDetails", + "name": "Can override existing browser related properties of event?", + "type": "string", + "hint": "If the ingested event already have $browser $browser_version properties in combination with $useragent the $browser, $browser_version properties will be re-populated with the value of $useragent", + "default": "false", + "required": false + }, + { + "key": "enableSegmentAnalyticsJs", + "name": "Automatically read segment_userAgent property, automatically sent by Segment via analytics.js?", + "type": "choice", + "hint": "Segment's analytics.js library automatically sends a useragent property that Posthog sees as segment_userAgent. Enabling this causes this plugin to parse that property", + "choices": ["false", "true"], + "default": "false", + "required": false + }, + { + "key": "debugMode", + "type": "choice", + "hint": "Enable debug mode to log when the plugin is unable to extract values from the user agent", + "choices": ["false", "true"], + "default": "false", + "required": false + } + ] +} From 1eddd0276aff374f810b8d59ba423343437398e2 Mon Sep 17 00:00:00 2001 From: Ben White Date: Tue, 28 Jan 2025 16:25:37 +0100 Subject: [PATCH 055/163] Fix plugins --- .../language-url-splitter-app/index.ts | 2 +- .../timestamp-parser-plugin/index.tests.ts | 45 ++++++++++--------- .../{index.js => index.ts} | 15 +++++-- .../user-agent-plugin/index.ts | 9 +++- plugin-server/src/cdp/legacy-plugins/index.ts | 18 ++++++++ 5 files changed, 63 insertions(+), 26 deletions(-) rename plugin-server/src/cdp/legacy-plugins/_transformations/timestamp-parser-plugin/{index.js => index.ts} (58%) diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/language-url-splitter-app/index.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/language-url-splitter-app/index.ts index 751fb8c857b8c..72e4d44bbbce3 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/language-url-splitter-app/index.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/language-url-splitter-app/index.ts @@ -19,7 +19,7 @@ export function processEvent(event: PluginEvent, { config }: LegacyPluginMeta) { return event } -export const languageUrlSplitterPlugin: LegacyTransformationPlugin = { +export const languageUrlSplitterApp: LegacyTransformationPlugin = { id: 'language-url-splitter-app', metadata: metadata as any, processEvent, diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/timestamp-parser-plugin/index.tests.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/timestamp-parser-plugin/index.tests.ts index 6a016816d84de..a63e633c2bd6a 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/timestamp-parser-plugin/index.tests.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/timestamp-parser-plugin/index.tests.ts @@ -1,20 +1,24 @@ -const { - createEvent, - createIdentify, - createPageview, - createCache, - getMeta, - resetMeta, - clone, -} = require('@posthog/plugin-scaffold/test/utils.js') -const { setupPlugin, processEvent } = require('../index') +import { PluginEvent } from '@posthog/plugin-scaffold' +const { processEvent } = require('../index') -test('processEvent adds the right properties', async () => { +const createEvent = (event: Partial): PluginEvent => + ({ + distinct_id: '1', + event: '$pageview', + properties: { + ...event.properties, + }, + ...event, + } as unknown as PluginEvent) - const event0 = createEvent({ event: 'Monday 27/01/2021', properties: { $time: 1611772203.557, keepMe: 'nothing changes' } }) +test('processEvent adds the right properties', async () => { + const event0 = createEvent({ + event: 'Monday 27/01/2021', + properties: { $time: 1611772203.557, keepMe: 'nothing changes' }, + }) - const event1 = await processEvent(clone(event0), getMeta()) + const event1 = await processEvent(event0) expect(event1).toEqual({ ...event0, properties: { @@ -26,9 +30,12 @@ test('processEvent adds the right properties', async () => { }, }) - const event2 = createEvent({ event: 'Monday 25/01/2021', properties: { $time: 1611587425.118, keepMe: 'nothing changes' } }) + const event2 = createEvent({ + event: 'Monday 25/01/2021', + properties: { $time: 1611587425.118, keepMe: 'nothing changes' }, + }) - const event3 = await processEvent(clone(event2), getMeta()) + const event3 = await processEvent(event2) expect(event3).toEqual({ ...event2, properties: { @@ -39,12 +46,10 @@ test('processEvent adds the right properties', async () => { year: '2021', }, }) - }) test('processEvent does not crash with identify', async () => { - const event0 = createIdentify() - - const event1 = await processEvent(clone(event0), getMeta()) + const event0 = createEvent({ event: '$identify' }) + const event1 = await processEvent(event0) expect(event1).toEqual(event0) -}) \ No newline at end of file +}) diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/timestamp-parser-plugin/index.js b/plugin-server/src/cdp/legacy-plugins/_transformations/timestamp-parser-plugin/index.ts similarity index 58% rename from plugin-server/src/cdp/legacy-plugins/_transformations/timestamp-parser-plugin/index.js rename to plugin-server/src/cdp/legacy-plugins/_transformations/timestamp-parser-plugin/index.ts index 4e75b04548fa3..d78f498fa2e74 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/timestamp-parser-plugin/index.js +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/timestamp-parser-plugin/index.ts @@ -1,5 +1,10 @@ -function processEvent(event) { - if (event.properties && event['timestamp'] && !isNaN(event['timestamp'])) { +import { PluginEvent } from '@posthog/plugin-scaffold' + +import { LegacyPluginMeta, LegacyTransformationPlugin } from '../../types' +import metadata from './plugin.json' + +function processEvent(event: PluginEvent, _meta: LegacyPluginMeta) { + if (event.properties && event['timestamp'] && !isNaN(event['timestamp'] as any)) { const eventDate = new Date(event['timestamp']) event.properties['day_of_the_week'] = eventDate.toLocaleDateString('en-GB', { weekday: 'long' }) const date = eventDate.toLocaleDateString('en-GB').split('/') @@ -13,6 +18,8 @@ function processEvent(event) { return event } -module.exports = { - processEvent +export const timestampParserPlugin: LegacyTransformationPlugin = { + id: 'timestamp-parser-plugin', + metadata: metadata as any, + processEvent, } diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/user-agent-plugin/index.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/user-agent-plugin/index.ts index 162adad07e2fd..f0e4d9c5780e3 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/user-agent-plugin/index.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/user-agent-plugin/index.ts @@ -1,7 +1,8 @@ import { PluginEvent } from '@posthog/plugin-scaffold' import { detect } from 'detect-browser' -import { LegacyPluginMeta } from '../../types' +import { LegacyPluginMeta, LegacyTransformationPlugin } from '../../types' +import metadata from './plugin.json' export type UserAgentMeta = LegacyPluginMeta & { config: { @@ -159,3 +160,9 @@ function detectDeviceType(userAgent: string) { return 'Desktop' } } + +export const userAgentPlugin: LegacyTransformationPlugin = { + id: 'user-agent-plugin', + metadata: metadata as any, + processEvent, +} diff --git a/plugin-server/src/cdp/legacy-plugins/index.ts b/plugin-server/src/cdp/legacy-plugins/index.ts index 2a6854468380a..888399e04a662 100644 --- a/plugin-server/src/cdp/legacy-plugins/index.ts +++ b/plugin-server/src/cdp/legacy-plugins/index.ts @@ -1,4 +1,13 @@ +import { downsamplingPlugin } from './_transformations/downsampling-plugin' +import { languageUrlSplitterApp } from './_transformations/language-url-splitter-app' +import { posthogAppUrlParametersToEventPropertiesPlugin } from './_transformations/posthog-app-url-parameters-to-event-properties' import { posthogFilterOutPlugin } from './_transformations/posthog-filter-out-plugin' +import { posthogUrlNormalizerPlugin } from './_transformations/posthog-url-normalizer-plugin' +import { propertyFilterPlugin } from './_transformations/property-filter-plugin' +import { semverFlattenerPlugin } from './_transformations/semver-flattener-plugin' +import { taxonomyPlugin } from './_transformations/taxonomy-plugin' +import { timestampParserPlugin } from './_transformations/timestamp-parser-plugin' +import { userAgentPlugin } from './_transformations/user-agent-plugin' import { customerioPlugin } from './customerio' import { intercomPlugin } from './intercom' @@ -8,5 +17,14 @@ export const DESTINATION_PLUGINS_BY_ID = { } export const TRANSFORMATION_PLUGINS_BY_ID = { + [downsamplingPlugin.id]: downsamplingPlugin, + [languageUrlSplitterApp.id]: languageUrlSplitterApp, + [posthogAppUrlParametersToEventPropertiesPlugin.id]: posthogAppUrlParametersToEventPropertiesPlugin, [posthogFilterOutPlugin.id]: posthogFilterOutPlugin, + [posthogUrlNormalizerPlugin.id]: posthogUrlNormalizerPlugin, + [propertyFilterPlugin.id]: propertyFilterPlugin, + [semverFlattenerPlugin.id]: semverFlattenerPlugin, + [taxonomyPlugin.id]: taxonomyPlugin, + [timestampParserPlugin.id]: timestampParserPlugin, + [userAgentPlugin.id]: userAgentPlugin, } From ebe96c0ae804fcfc333bbdf8298335e68a3992db Mon Sep 17 00:00:00 2001 From: Ben White Date: Tue, 28 Jan 2025 16:35:57 +0100 Subject: [PATCH 056/163] Fix snapshots --- .../legacy-plugin-executor.test.ts.snap | 56 ++++ .../services/legacy-plugin-executor.test.ts | 316 ++++++++++++++++++ 2 files changed, 372 insertions(+) create mode 100644 plugin-server/src/cdp/services/__snapshots__/legacy-plugin-executor.test.ts.snap create mode 100644 plugin-server/src/cdp/services/legacy-plugin-executor.test.ts diff --git a/plugin-server/src/cdp/services/__snapshots__/legacy-plugin-executor.test.ts.snap b/plugin-server/src/cdp/services/__snapshots__/legacy-plugin-executor.test.ts.snap new file mode 100644 index 0000000000000..03393ee21f850 --- /dev/null +++ b/plugin-server/src/cdp/services/__snapshots__/legacy-plugin-executor.test.ts.snap @@ -0,0 +1,56 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`LegacyPluginExecutorService smoke tests should run the plugin: { name: 'customer-io', plugin: [Object] } 1`] = ` +[ + { + "level": "debug", + "message": "Executing plugin customer-io", + "timestamp": "2025-01-01T01:00:00.000+01:00", + }, + { + "level": "info", + "message": "Successfully authenticated with Customer.io. Completing setupPlugin.", + "timestamp": "2025-01-01T01:00:00.000+01:00", + }, + { + "level": "debug", + "message": "Detected email:, test@posthog.com", + "timestamp": "2025-01-01T01:00:00.000+01:00", + }, + { + "level": "debug", + "message": "{"status":{},"email":"test@posthog.com"}", + "timestamp": "2025-01-01T01:00:00.000+01:00", + }, + { + "level": "debug", + "message": "Should customer be tracked:, true", + "timestamp": "2025-01-01T01:00:00.000+01:00", + }, + { + "level": "debug", + "message": "Execution successful", + "timestamp": "2025-01-01T01:00:00.000+01:00", + }, +] +`; + +exports[`LegacyPluginExecutorService smoke tests should run the plugin: { name: 'intercom', plugin: [Object] } 1`] = ` +[ + { + "level": "debug", + "message": "Executing plugin intercom", + "timestamp": "2025-01-01T01:00:00.000+01:00", + }, + { + "level": "info", + "message": "Contact test@posthog.com in Intercom not found", + "timestamp": "2025-01-01T01:00:00.000+01:00", + }, + { + "level": "debug", + "message": "Execution successful", + "timestamp": "2025-01-01T01:00:00.000+01:00", + }, +] +`; diff --git a/plugin-server/src/cdp/services/legacy-plugin-executor.test.ts b/plugin-server/src/cdp/services/legacy-plugin-executor.test.ts new file mode 100644 index 0000000000000..13a93e01f974e --- /dev/null +++ b/plugin-server/src/cdp/services/legacy-plugin-executor.test.ts @@ -0,0 +1,316 @@ +import { DateTime } from 'luxon' + +import { + createHogExecutionGlobals, + createHogFunction, + createInvocation, + insertHogFunction as _insertHogFunction, +} from '~/tests/cdp/fixtures' +import { forSnapshot } from '~/tests/helpers/snapshots' +import { getFirstTeam } from '~/tests/helpers/sql' + +import { Hub, Team } from '../../types' +import { closeHub, createHub } from '../../utils/db/hub' +import { DESTINATION_PLUGINS_BY_ID } from '../legacy-plugins' +import { HogFunctionInvocationGlobalsWithInputs, HogFunctionType } from '../types' +import { LegacyPluginExecutorService } from './legacy-plugin-executor.service' + +jest.setTimeout(1000) + +/** + * NOTE: The internal and normal events consumers are very similar so we can test them together + */ +describe('LegacyPluginExecutorService', () => { + let service: LegacyPluginExecutorService + let hub: Hub + let team: Team + let globals: HogFunctionInvocationGlobalsWithInputs + let fn: HogFunctionType + let mockFetch: jest.Mock + + beforeEach(async () => { + hub = await createHub() + service = new LegacyPluginExecutorService(hub) + team = await getFirstTeam(hub) + + fn = createHogFunction({ + name: 'Plugin test', + template_id: 'plugin-intercom', + }) + + const fixedTime = DateTime.fromObject({ year: 2025, month: 1, day: 1 }, { zone: 'UTC' }) + jest.spyOn(Date, 'now').mockReturnValue(fixedTime.toMillis()) + + mockFetch = jest.fn(() => + Promise.resolve({ + status: 200, + json: () => + Promise.resolve({ + status: 200, + }), + } as any) + ) + + jest.spyOn(service, 'fetch').mockImplementation(mockFetch) + + globals = { + ...createHogExecutionGlobals({ + project: { + id: team.id, + } as any, + event: { + uuid: 'b3a1fe86-b10c-43cc-acaf-d208977608d0', + event: '$pageview', + properties: { + $current_url: 'https://posthog.com', + $lib_version: '1.0.0', + $set: { + email: 'test@posthog.com', + }, + }, + timestamp: fixedTime.toISO(), + } as any, + }), + inputs: { + intercomApiKey: '1234567890', + triggeringEvents: '$identify,mycustomevent', + ignoredEmailDomains: 'dev.posthog.com', + useEuropeanDataStorage: 'No', + }, + } + }) + + afterEach(async () => { + await closeHub(hub) + }) + + afterAll(() => { + jest.useRealTimers() + }) + + describe('setupPlugin', () => { + it('should setup a plugin on first call', async () => { + jest.spyOn(DESTINATION_PLUGINS_BY_ID['intercom'] as any, 'setupPlugin') + + await service.execute(createInvocation(fn, globals)) + + const results = Promise.all([ + service.execute(createInvocation(fn, globals)), + service.execute(createInvocation(fn, globals)), + service.execute(createInvocation(fn, globals)), + ]) + + expect(await results).toMatchObject([{ finished: true }, { finished: true }, { finished: true }]) + + expect(DESTINATION_PLUGINS_BY_ID['intercom'].setupPlugin).toHaveBeenCalledTimes(1) + expect(jest.mocked(DESTINATION_PLUGINS_BY_ID['intercom'].setupPlugin!).mock.calls[0][0]) + .toMatchInlineSnapshot(` + { + "config": { + "ignoredEmailDomains": "dev.posthog.com", + "intercomApiKey": "1234567890", + "triggeringEvents": "$identify,mycustomevent", + "useEuropeanDataStorage": "No", + }, + "fetch": [Function], + "global": {}, + "logger": { + "debug": [Function], + "error": [Function], + "log": [Function], + "warn": [Function], + }, + } + `) + }) + }) + + describe('onEvent', () => { + it('should call the plugin onEvent method', async () => { + jest.spyOn(DESTINATION_PLUGINS_BY_ID['intercom'] as any, 'onEvent') + + const invocation = createInvocation(fn, globals) + invocation.globals.event.event = 'mycustomevent' + invocation.globals.event.properties = { + email: 'test@posthog.com', + } + + mockFetch.mockResolvedValue({ + status: 200, + json: () => Promise.resolve({ total_count: 1 }), + }) + + const res = await service.execute(invocation) + + expect(DESTINATION_PLUGINS_BY_ID['intercom'].onEvent).toHaveBeenCalledTimes(1) + expect(forSnapshot(jest.mocked(DESTINATION_PLUGINS_BY_ID['intercom'].onEvent!).mock.calls[0][0])) + .toMatchInlineSnapshot(` + { + "distinct_id": "distinct_id", + "event": "mycustomevent", + "person": { + "created_at": "", + "properties": { + "email": "test@posthog.com", + "first_name": "Pumpkin", + }, + "team_id": 1, + "uuid": "uuid", + }, + "properties": { + "email": "test@posthog.com", + }, + "team_id": 1, + "timestamp": "2025-01-01T00:00:00.000Z", + "uuid": "", + } + `) + + expect(mockFetch).toHaveBeenCalledTimes(2) + expect(forSnapshot(mockFetch.mock.calls[0])).toMatchInlineSnapshot(` + [ + "https://api.intercom.io/contacts/search", + { + "body": "{"query":{"field":"email","operator":"=","value":"test@posthog.com"}}", + "headers": { + "Accept": "application/json", + "Authorization": "Bearer 1234567890", + "Content-Type": "application/json", + }, + "method": "POST", + }, + ] + `) + expect(forSnapshot(mockFetch.mock.calls[1])).toMatchInlineSnapshot(` + [ + "https://api.intercom.io/events", + { + "body": "{"event_name":"mycustomevent","created_at":null,"email":"test@posthog.com","id":"distinct_id"}", + "headers": { + "Accept": "application/json", + "Authorization": "Bearer 1234567890", + "Content-Type": "application/json", + }, + "method": "POST", + }, + ] + `) + + expect(res.finished).toBe(true) + expect(res.logs).toMatchInlineSnapshot(` + [ + { + "level": "debug", + "message": "Executing plugin intercom", + "timestamp": "2025-01-01T01:00:00.000+01:00", + }, + { + "level": "info", + "message": "Contact test@posthog.com in Intercom found", + "timestamp": "2025-01-01T01:00:00.000+01:00", + }, + { + "level": "info", + "message": "Sent event mycustomevent for test@posthog.com to Intercom", + "timestamp": "2025-01-01T01:00:00.000+01:00", + }, + { + "level": "debug", + "message": "Execution successful", + "timestamp": "2025-01-01T01:00:00.000+01:00", + }, + ] + `) + }) + + it('should mock out fetch if it is a test function', async () => { + jest.spyOn(DESTINATION_PLUGINS_BY_ID['intercom'] as any, 'onEvent') + + const invocation = createInvocation(fn, globals) + invocation.hogFunction.name = 'My function [CDP-TEST-HIDDEN]' + invocation.globals.event.event = 'mycustomevent' + invocation.globals.event.properties = { + email: 'test@posthog.com', + } + + const res = await service.execute(invocation) + + expect(mockFetch).toHaveBeenCalledTimes(0) + + expect(DESTINATION_PLUGINS_BY_ID['intercom'].onEvent).toHaveBeenCalledTimes(1) + + expect(forSnapshot(res.logs.map((l) => l.message))).toMatchInlineSnapshot(` + [ + "Executing plugin intercom", + "Fetch called but mocked due to test function", + "Unable to search contact test@posthog.com in Intercom. Status Code: undefined. Error message: ", + "Execution successful", + ] + `) + }) + + it('should handle and collect errors', async () => { + jest.spyOn(DESTINATION_PLUGINS_BY_ID['intercom'] as any, 'onEvent') + + const invocation = createInvocation(fn, globals) + invocation.globals.event.event = 'mycustomevent' + invocation.globals.event.properties = { + email: 'test@posthog.com', + } + + mockFetch.mockRejectedValue(new Error('Test error')) + + const res = await service.execute(invocation) + + expect(DESTINATION_PLUGINS_BY_ID['intercom'].onEvent).toHaveBeenCalledTimes(1) + + expect(res.error).toBeInstanceOf(Error) + expect(forSnapshot(res.logs.map((l) => l.message))).toMatchInlineSnapshot(` + [ + "Executing plugin intercom", + "Plugin errored: Service is down, retry later", + ] + `) + + expect(res.error).toEqual(new Error('Service is down, retry later')) + }) + }) + + describe('smoke tests', () => { + const testCases = Object.entries(DESTINATION_PLUGINS_BY_ID).map(([pluginId, plugin]) => ({ + name: pluginId, + plugin, + })) + + it.each(testCases)('should run the plugin: %s', async ({ name, plugin }) => { + globals.event.event = '$identify' // Many plugins filter for this + const invocation = createInvocation(fn, globals) + + invocation.hogFunction.template_id = `plugin-${plugin.id}` + + const inputs: Record = {} + + for (const input of plugin.metadata.config) { + if (!input.key) { + continue + } + + if (input.default) { + inputs[input.key] = input.default + continue + } + + if (input.type === 'choice') { + inputs[input.key] = input.choices[0] + } else if (input.type === 'string') { + inputs[input.key] = 'test' + } + } + + invocation.hogFunction.name = name + const res = await service.execute(invocation) + + expect(res.logs).toMatchSnapshot() + }) + }) +}) From d9e1a651bd1c869aa77ee3e3a699c06aa51674d3 Mon Sep 17 00:00:00 2001 From: Ben White Date: Tue, 28 Jan 2025 16:38:41 +0100 Subject: [PATCH 057/163] Fixes --- .../downsampling-plugin/plugin.json | 5 +- .../language-url-splitter-app/plugin.json | 2 +- .../posthog-filter-out-plugin/plugin.json | 60 +++++++++---------- .../property-filter-plugin/plugin.json | 36 +++++------ .../taxonomy-plugin/plugin.json | 8 +-- 5 files changed, 51 insertions(+), 60 deletions(-) diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/downsampling-plugin/plugin.json b/plugin-server/src/cdp/legacy-plugins/_transformations/downsampling-plugin/plugin.json index 5feab1574bafd..dc79f040f11a4 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/downsampling-plugin/plugin.json +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/downsampling-plugin/plugin.json @@ -18,10 +18,7 @@ "hint": "`Distinct ID aware sampling` will sample based on distinct IDs, meaning that a user's events will all be ingested or all be dropped at a given sample percentage.", "name": "Sampling method", "type": "choice", - "choices": [ - "Random sampling", - "Distinct ID aware sampling" - ], + "choices": ["Random sampling", "Distinct ID aware sampling"], "default": "Distinct ID aware sampling", "required": false } diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/language-url-splitter-app/plugin.json b/plugin-server/src/cdp/legacy-plugins/_transformations/language-url-splitter-app/plugin.json index ed4bf5a6b717d..abb0a519bc623 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/language-url-splitter-app/plugin.json +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/language-url-splitter-app/plugin.json @@ -50,4 +50,4 @@ "required": true } ] -} \ No newline at end of file +} diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-filter-out-plugin/plugin.json b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-filter-out-plugin/plugin.json index 5421131984fd5..f6fee89f98eb4 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-filter-out-plugin/plugin.json +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-filter-out-plugin/plugin.json @@ -1,32 +1,32 @@ { - "name": "Filter Out Plugin", - "url": "https://github.com/plibither8/posthog-filter-out-plugin", - "description": "Filter out events where property values satisfy the given condition", - "main": "src/main.ts", - "config": [ - { - "markdown": "All filters must adhere to the JSON schema specified in the project's [README](https://github.com/plibither8/posthog-filter-out-plugin)." - }, - { - "key": "filters", - "name": "Filters to apply", - "type": "attachment", - "hint": "A JSON file containing an array of filters to apply. See the README for more information.", - "required": false - }, - { - "key": "eventsToDrop", - "name": "Events to filter out", - "type": "string", - "hint": "A comma-separated list of event names to filter out (e.g. $pageview,$autocapture)", - "required": false - }, - { - "key": "keepUndefinedProperties", - "name": "Keep event if any of the filtered properties are undefined?", - "type": "choice", - "choices": ["Yes", "No"], - "default": "No" - } - ] + "name": "Filter Out Plugin", + "url": "https://github.com/plibither8/posthog-filter-out-plugin", + "description": "Filter out events where property values satisfy the given condition", + "main": "src/main.ts", + "config": [ + { + "markdown": "All filters must adhere to the JSON schema specified in the project's [README](https://github.com/plibither8/posthog-filter-out-plugin)." + }, + { + "key": "filters", + "name": "Filters to apply", + "type": "attachment", + "hint": "A JSON file containing an array of filters to apply. See the README for more information.", + "required": false + }, + { + "key": "eventsToDrop", + "name": "Events to filter out", + "type": "string", + "hint": "A comma-separated list of event names to filter out (e.g. $pageview,$autocapture)", + "required": false + }, + { + "key": "keepUndefinedProperties", + "name": "Keep event if any of the filtered properties are undefined?", + "type": "choice", + "choices": ["Yes", "No"], + "default": "No" + } + ] } diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/property-filter-plugin/plugin.json b/plugin-server/src/cdp/legacy-plugins/_transformations/property-filter-plugin/plugin.json index 52cf4932f35e8..a7b3e49f4b98e 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/property-filter-plugin/plugin.json +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/property-filter-plugin/plugin.json @@ -1,19 +1,19 @@ { - "name": "Property Filter", - "url": "https://github.com/witty-works/posthog-property-filter-plugin", - "description": "This plugin will set all configured properties to null inside an ingested event.", - "main": "index.js", - "config": [ - { - "markdown": "\n\n# Important!\nThis plugin will only work on events ingested **after** the plugin was enabled. This means it **will** register events as being the first if there were events that occured **before** it was enabled. To mitigate this, you could consider renaming the relevant events and creating an [action](https://posthog.com/docs/features/actions) that matches both the old event name and the new one.\n" - }, - { - "key": "properties", - "name": "List of properties to filter out:", - "type": "string", - "default": "", - "hint": "Separate properties with commas, without using spaces, like so: `foo,bar,baz`", - "required": true - } - ] -} \ No newline at end of file + "name": "Property Filter", + "url": "https://github.com/witty-works/posthog-property-filter-plugin", + "description": "This plugin will set all configured properties to null inside an ingested event.", + "main": "index.js", + "config": [ + { + "markdown": "\n\n# Important!\nThis plugin will only work on events ingested **after** the plugin was enabled. This means it **will** register events as being the first if there were events that occured **before** it was enabled. To mitigate this, you could consider renaming the relevant events and creating an [action](https://posthog.com/docs/features/actions) that matches both the old event name and the new one.\n" + }, + { + "key": "properties", + "name": "List of properties to filter out:", + "type": "string", + "default": "", + "hint": "Separate properties with commas, without using spaces, like so: `foo,bar,baz`", + "required": true + } + ] +} diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/taxonomy-plugin/plugin.json b/plugin-server/src/cdp/legacy-plugins/_transformations/taxonomy-plugin/plugin.json index 88d4d33c07402..616e5dd911d41 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/taxonomy-plugin/plugin.json +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/taxonomy-plugin/plugin.json @@ -9,13 +9,7 @@ "hint": "", "name": "Select your default naming pattern", "type": "choice", - "choices": [ - "camelCase", - "PascalCase", - "snake_case", - "kebab-case", - "spaces in between" - ], + "choices": ["camelCase", "PascalCase", "snake_case", "kebab-case", "spaces in between"], "order": 1, "default": "camelCase", "required": true From 8888e8fa688aca15fdfb8e09bf2df4e6132849fe Mon Sep 17 00:00:00 2001 From: Ben White Date: Tue, 28 Jan 2025 17:11:35 +0100 Subject: [PATCH 058/163] Fixes --- .../downsampling-plugin/index.test.ts | 6 +- .../downsampling-plugin/index.ts | 6 +- .../language-url-splitter-app/index.test.ts | 14 +- .../language-url-splitter-app/index.ts | 4 +- .../index.ts | 4 +- .../posthog-filter-out-plugin/index.test.ts | 10 +- .../posthog-filter-out-plugin/index.ts | 6 +- .../index.test.ts | 4 +- .../posthog-url-normalizer-plugin/index.ts | 4 +- .../property-filter-plugin/index.test.ts | 6 +- .../property-filter-plugin/index.ts | 6 +- .../semver-flattener-plugin/index.test.ts | 4 +- .../semver-flattener-plugin/index.ts | 4 +- .../taxonomy-plugin/index.test.ts | 18 +- .../_transformations/taxonomy-plugin/index.ts | 4 +- .../taxonomy-plugin/legacy.ts | 4 +- .../timestamp-parser-plugin/index.ts | 4 +- .../user-agent-plugin/index.ts | 4 +- .../cdp/legacy-plugins/customerio/index.ts | 4 +- .../src/cdp/legacy-plugins/intercom/index.ts | 4 +- plugin-server/src/cdp/legacy-plugins/types.ts | 14 +- .../legacy-plugin-executor.service.ts | 200 ++++++++++-------- .../services/legacy-plugin-executor.test.ts | 97 ++++++++- 23 files changed, 274 insertions(+), 157 deletions(-) diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/downsampling-plugin/index.test.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/downsampling-plugin/index.test.ts index db38c19ace8ac..69ffb792cce20 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/downsampling-plugin/index.test.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/downsampling-plugin/index.test.ts @@ -1,10 +1,10 @@ import { PluginEvent } from '@posthog/plugin-scaffold' import { randomBytes } from 'crypto' -import { LegacyPluginMeta } from '../../types' +import { LegacyTransformationPluginMeta } from '../../types' import { processEvent, setupPlugin } from './index' -let meta: LegacyPluginMeta +let meta: LegacyTransformationPluginMeta const createEvent = (event: Partial): PluginEvent => ({ @@ -23,7 +23,7 @@ beforeEach(() => { config: { percentage: '100', }, - } as unknown as LegacyPluginMeta + } as unknown as LegacyTransformationPluginMeta }) test('processEvent filters event', () => { diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/downsampling-plugin/index.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/downsampling-plugin/index.ts index d779cdfb6b903..c9f17c822e4c8 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/downsampling-plugin/index.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/downsampling-plugin/index.ts @@ -1,10 +1,10 @@ import { PluginEvent } from '@posthog/plugin-scaffold' import { createHash } from 'crypto' -import { LegacyPluginMeta, LegacyTransformationPlugin } from '../../types' +import { LegacyTransformationPlugin,LegacyTransformationPluginMeta } from '../../types' import metadata from './plugin.json' -export function setupPlugin({ config, global }: LegacyPluginMeta) { +export function setupPlugin({ config, global }: LegacyTransformationPluginMeta) { const percentage = parseFloat(config.percentage) if (isNaN(percentage) || percentage > 100 || percentage < 0) { throw new Error('Percentage must be a number between 0 and 100.') @@ -14,7 +14,7 @@ export function setupPlugin({ config, global }: LegacyPluginMeta) { } // /* Runs on every event */ -export function processEvent(event: PluginEvent, { global }: LegacyPluginMeta) { +export function processEvent(event: PluginEvent, { global }: LegacyTransformationPluginMeta) { // hash is a sha256 hash of the distinct_id represented in base 16 // We take the first 15 digits, convert this into an integer, // dividing by the biggest 15 digit, base 16 number to get a value between 0 and 1. diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/language-url-splitter-app/index.test.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/language-url-splitter-app/index.test.ts index 896c8684ad8e5..d5664db0815bd 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/language-url-splitter-app/index.test.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/language-url-splitter-app/index.test.ts @@ -1,6 +1,6 @@ import { PluginEvent } from '@posthog/plugin-scaffold' -import { LegacyPluginMeta } from '../../types' +import { LegacyTransformationPluginMeta } from '../../types' import { processEvent } from './index' import pluginJson from './plugin.json' @@ -21,9 +21,9 @@ test('changes properties', () => { ] for (const [$pathname, properties] of matches) { - expect(processEvent(makeEvent($pathname), { config: globalConfig } as LegacyPluginMeta).properties).toEqual( - properties - ) + expect( + processEvent(makeEvent($pathname), { config: globalConfig } as LegacyTransformationPluginMeta).properties + ).toEqual(properties) } }) @@ -34,8 +34,8 @@ test('changes properties if new $pathname', () => { ] for (const [$pathname, properties] of matches) { - expect(processEvent(makeEvent($pathname), { config } as unknown as LegacyPluginMeta).properties).toEqual( - properties - ) + expect( + processEvent(makeEvent($pathname), { config } as unknown as LegacyTransformationPluginMeta).properties + ).toEqual(properties) } }) diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/language-url-splitter-app/index.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/language-url-splitter-app/index.ts index 72e4d44bbbce3..f71dfabc1b9d4 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/language-url-splitter-app/index.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/language-url-splitter-app/index.ts @@ -1,9 +1,9 @@ import { PluginEvent } from '@posthog/plugin-scaffold' -import { LegacyPluginMeta, LegacyTransformationPlugin } from '../../types' +import { LegacyTransformationPlugin,LegacyTransformationPluginMeta } from '../../types' import metadata from './plugin.json' -export function processEvent(event: PluginEvent, { config }: LegacyPluginMeta) { +export function processEvent(event: PluginEvent, { config }: LegacyTransformationPluginMeta) { const { pattern, matchGroup, property, replacePattern, replaceKey, replaceValue } = config if (event.properties && typeof event.properties['$pathname'] === 'string') { const regexp = new RegExp(pattern) diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-app-url-parameters-to-event-properties/index.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-app-url-parameters-to-event-properties/index.ts index 30b8c2325eecc..b243b5c5b6c0c 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-app-url-parameters-to-event-properties/index.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-app-url-parameters-to-event-properties/index.ts @@ -1,7 +1,7 @@ import { PluginEvent } from '@posthog/plugin-scaffold' import { URLSearchParams } from 'url' -import { LegacyPluginMeta, LegacyTransformationPlugin } from '../../types' +import { LegacyTransformationPlugin,LegacyTransformationPluginMeta } from '../../types' import metadata from './plugin.json' export type PluginConfig = { @@ -14,7 +14,7 @@ export type PluginConfig = { alwaysJson: 'true' | 'false' } -export type LocalMeta = LegacyPluginMeta & { +export type LocalMeta = LegacyTransformationPluginMeta & { global: { ignoreCase: boolean setAsInitialUserProperties: boolean diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-filter-out-plugin/index.test.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-filter-out-plugin/index.test.ts index 88348a64c42e6..6c24f9e74ec16 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-filter-out-plugin/index.test.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-filter-out-plugin/index.test.ts @@ -1,6 +1,6 @@ import { PluginEvent } from '@posthog/plugin-scaffold' -import { LegacyPluginMeta } from '../../types' +import { LegacyTransformationPluginMeta } from '../../types' import { Filter, processEvent, setupPlugin } from '.' const filters: Filter[] = [ @@ -40,7 +40,7 @@ const createEvent = (event: Partial): PluginEvent => { const meta = { global: { filters, eventsToDrop: ['to_drop_event'] }, -} as unknown as LegacyPluginMeta +} as unknown as LegacyTransformationPluginMeta test('Event satisfies all conditions and passes', () => { const event = createEvent({ @@ -118,7 +118,7 @@ test('Event is marked to be dropped when a property is undefined but keepUndefin }) as unknown as PluginEvent const processedEvent = processEvent(event, { global: { ...meta.global, keepUndefinedProperties: true }, - } as unknown as LegacyPluginMeta) + } as unknown as LegacyTransformationPluginMeta) expect(processedEvent).toEqual(event) }) @@ -151,7 +151,7 @@ test('setupPlugin() parsing keepUndefinedProperties', () => { describe('empty filters', () => { const meta_no_filters = { global: { filters: [], eventsToDrop: ['to_drop_event'] }, - } as unknown as LegacyPluginMeta + } as unknown as LegacyTransformationPluginMeta test('Event satisfies all conditions and passes', () => { const event = createEvent({ @@ -227,7 +227,7 @@ const filters_or: Filter[][] = [ const meta_or = { global: { filters: filters_or, eventsToDrop: ['to_drop_event'] }, -} as unknown as LegacyPluginMeta +} as unknown as LegacyTransformationPluginMeta test('Event satisfies at least one filter group and passes', () => { const event = createEvent({ diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-filter-out-plugin/index.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-filter-out-plugin/index.ts index 3de9132c76f98..eab6d9ed796e2 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-filter-out-plugin/index.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-filter-out-plugin/index.ts @@ -1,6 +1,6 @@ import { Meta, PluginAttachment, PluginEvent } from '@posthog/plugin-scaffold' -import { LegacyPluginMeta, LegacyTransformationPlugin } from '../../types' +import { LegacyTransformationPlugin,LegacyTransformationPluginMeta } from '../../types' import metadata from './plugin.json' export interface Filter { @@ -48,7 +48,7 @@ const operations: Record bool }, } -export function setupPlugin({ global, config }: LegacyPluginMeta) { +export function setupPlugin({ global, config }: LegacyTransformationPluginMeta) { if (config.filters) { try { const filters = typeof config.filters === 'string' ? JSON.parse(config.filters) : config.filters @@ -80,7 +80,7 @@ export function setupPlugin({ global, config }: LegacyPluginMeta) { global.keepUndefinedProperties = config.keepUndefinedProperties === 'Yes' } -export function processEvent(event: PluginEvent, meta: LegacyPluginMeta): PluginEvent | undefined { +export function processEvent(event: PluginEvent, meta: LegacyTransformationPluginMeta): PluginEvent | undefined { if (!event.properties) { return event } diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-url-normalizer-plugin/index.test.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-url-normalizer-plugin/index.test.ts index ef9e330acc540..81cec3a779d59 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-url-normalizer-plugin/index.test.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-url-normalizer-plugin/index.test.ts @@ -1,6 +1,6 @@ import { PluginEvent } from '@posthog/plugin-scaffold' -import { LegacyPluginMeta } from '../../types' +import { LegacyTransformationPluginMeta } from '../../types' import { processEvent } from './index' /** @@ -43,7 +43,7 @@ const meta = { logger: { debug: jest.fn(), }, -} as unknown as LegacyPluginMeta +} as unknown as LegacyTransformationPluginMeta describe('processEvent', () => { it("shouldn't change a url that's already lowercase", () => { diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-url-normalizer-plugin/index.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-url-normalizer-plugin/index.ts index b50780f8a84a2..c50eb94ee7c10 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-url-normalizer-plugin/index.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-url-normalizer-plugin/index.ts @@ -1,6 +1,6 @@ import { PluginEvent } from '@posthog/plugin-scaffold' -import { LegacyPluginMeta } from '../../types' +import { LegacyTransformationPluginMeta } from '../../types' import { LegacyTransformationPlugin } from '../../types' import metadata from './plugin.json' @@ -15,7 +15,7 @@ function normalizeUrl(url: string): string { } } -export function processEvent(event: PluginEvent, { logger }: LegacyPluginMeta) { +export function processEvent(event: PluginEvent, { logger }: LegacyTransformationPluginMeta) { const $current_url = event?.properties?.$current_url if (event?.properties && $current_url) { const normalized_url = normalizeUrl($current_url) diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/property-filter-plugin/index.test.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/property-filter-plugin/index.test.ts index 84cdaa40cc8db..c2598ec4e1bf5 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/property-filter-plugin/index.test.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/property-filter-plugin/index.test.ts @@ -1,6 +1,6 @@ import { PluginEvent } from '@posthog/plugin-scaffold' -import { LegacyPluginMeta } from '../../types' +import { LegacyTransformationPluginMeta } from '../../types' import { processEvent } from './index' const createEvent = (event: Partial): PluginEvent => @@ -25,10 +25,10 @@ const global = { ], } -const meta: LegacyPluginMeta = { +const meta: LegacyTransformationPluginMeta = { global, config: {}, -} as unknown as LegacyPluginMeta +} as unknown as LegacyTransformationPluginMeta const properties = { properties: { diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/property-filter-plugin/index.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/property-filter-plugin/index.ts index 204f1422a863c..54be9909aa9b5 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/property-filter-plugin/index.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/property-filter-plugin/index.ts @@ -1,9 +1,9 @@ import { PluginEvent } from '@posthog/plugin-scaffold' -import { LegacyPluginMeta, LegacyTransformationPlugin } from '../../types' +import { LegacyTransformationPlugin,LegacyTransformationPluginMeta } from '../../types' import metadata from './plugin.json' -export function setupPlugin({ config, global }: LegacyPluginMeta) { +export function setupPlugin({ config, global }: LegacyTransformationPluginMeta) { global.propertiesToFilter = config.properties.split(',') } @@ -20,7 +20,7 @@ function recursiveRemoveFilterObject(properties: Record, propertyTo } } -export function processEvent(event: PluginEvent, { global }: LegacyPluginMeta) { +export function processEvent(event: PluginEvent, { global }: LegacyTransformationPluginMeta) { const propertiesCopy = event.properties ? { ...event.properties } : {} for (const propertyToFilter of global.propertiesToFilter) { diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/semver-flattener-plugin/index.test.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/semver-flattener-plugin/index.test.ts index fb9f9b9bad819..a5598bc122639 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/semver-flattener-plugin/index.test.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/semver-flattener-plugin/index.test.ts @@ -1,6 +1,6 @@ import { PluginEvent } from '@posthog/plugin-scaffold' -import { LegacyPluginMeta } from '../../types' +import { LegacyTransformationPluginMeta } from '../../types' import { processEvent } from './index' interface SemanticVersionTestCase { @@ -25,7 +25,7 @@ const meta = { logger: { log: jest.fn(), }, -} as unknown as LegacyPluginMeta +} as unknown as LegacyTransformationPluginMeta describe('the semver flattener plugin', () => { test('processEvent adds properties when they match config', () => { diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/semver-flattener-plugin/index.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/semver-flattener-plugin/index.ts index d93739ce14236..94b98c6ef630b 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/semver-flattener-plugin/index.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/semver-flattener-plugin/index.ts @@ -1,6 +1,6 @@ import { PluginEvent } from '@posthog/plugin-scaffold' -import { LegacyPluginMeta, LegacyTransformationPlugin } from '../../types' +import { LegacyTransformationPlugin, LegacyTransformationPluginMeta } from '../../types' import metadata from './plugin.json' interface VersionParts { @@ -26,7 +26,7 @@ const splitVersion = (candidate: string): VersionParts => { } } -export function processEvent(event: PluginEvent, meta: LegacyPluginMeta) { +export function processEvent(event: PluginEvent, meta: LegacyTransformationPluginMeta) { if (!event.properties) { return } diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/taxonomy-plugin/index.test.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/taxonomy-plugin/index.test.ts index 2fe0ff497f265..2db8066e3be06 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/taxonomy-plugin/index.test.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/taxonomy-plugin/index.test.ts @@ -1,6 +1,6 @@ import { PluginEvent } from '@posthog/plugin-scaffold' -import { LegacyPluginMeta } from '../../types' +import { LegacyTransformationPluginMeta } from '../../types' import { processEvent } from './legacy' const createEvent = (event: Partial): PluginEvent => @@ -28,7 +28,7 @@ describe('taxonomy-plugin', () => { const result = processEvent(event, { config: { defaultNamingConvention: 'camelCase' }, global: {}, - } as unknown as LegacyPluginMeta) + } as unknown as LegacyTransformationPluginMeta) expect(result.event).toBe(expected) } }) @@ -45,7 +45,7 @@ describe('taxonomy-plugin', () => { const result = processEvent(event, { config: { defaultNamingConvention: 'PascalCase' }, global: {}, - } as unknown as LegacyPluginMeta) + } as unknown as LegacyTransformationPluginMeta) expect(result.event).toBe(expected) } }) @@ -63,7 +63,7 @@ describe('taxonomy-plugin', () => { const result = processEvent(event, { config: { defaultNamingConvention: 'snake_case' }, global: {}, - } as unknown as LegacyPluginMeta) + } as unknown as LegacyTransformationPluginMeta) expect(result.event).toBe(expected) } }) @@ -80,7 +80,7 @@ describe('taxonomy-plugin', () => { const result = processEvent(event, { config: { defaultNamingConvention: 'kebab-case' }, global: {}, - } as unknown as LegacyPluginMeta) + } as unknown as LegacyTransformationPluginMeta) expect(result.event).toBe(expected) } }) @@ -97,7 +97,7 @@ describe('taxonomy-plugin', () => { const result = processEvent(event, { config: { defaultNamingConvention: 'spaces in between' }, global: {}, - } as unknown as LegacyPluginMeta) + } as unknown as LegacyTransformationPluginMeta) expect(result.event).toBe(expected) } }) @@ -112,7 +112,7 @@ describe('taxonomy-plugin', () => { const result = processEvent(event, { config: { defaultNamingConvention: 'camelCase' }, global: {}, - } as unknown as LegacyPluginMeta) + } as unknown as LegacyTransformationPluginMeta) expect(result.event).toBe(systemEvent) } }) @@ -125,7 +125,7 @@ describe('taxonomy-plugin', () => { const result = processEvent(event, { config: { defaultNamingConvention: 'camelCase' }, global: {}, - } as unknown as LegacyPluginMeta) + } as unknown as LegacyTransformationPluginMeta) expect(result.event).toBe(surveyEvent) } }) @@ -135,7 +135,7 @@ describe('taxonomy-plugin', () => { const result = processEvent(event, { config: { defaultNamingConvention: 'camelCase' }, global: {}, - } as unknown as LegacyPluginMeta) + } as unknown as LegacyTransformationPluginMeta) expect(result).toEqual({ ...event, diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/taxonomy-plugin/index.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/taxonomy-plugin/index.ts index 1f356cfe43788..0d51bc4bbc287 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/taxonomy-plugin/index.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/taxonomy-plugin/index.ts @@ -1,6 +1,6 @@ import { PluginEvent } from '@posthog/plugin-scaffold' -import { LegacyPluginMeta, LegacyTransformationPlugin } from '../../types' +import { LegacyTransformationPlugin,LegacyTransformationPluginMeta } from '../../types' import metadata from './plugin.json' type Transformation = { @@ -37,7 +37,7 @@ const transformations: Record = { const skippedPostHogEvents = ['survey shown', 'survey sent', 'survey dismissed'] -export function processEvent(event: PluginEvent, { config }: LegacyPluginMeta) { +export function processEvent(event: PluginEvent, { config }: LegacyTransformationPluginMeta) { if (!event.event.startsWith('$') && !skippedPostHogEvents.includes(event.event)) { const transformer = transformations[config.defaultNamingConvention] event.event = transformer.transform(event.event, transformer.matchPattern) diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/taxonomy-plugin/legacy.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/taxonomy-plugin/legacy.ts index 1402ff385dde6..05dde9edd3327 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/taxonomy-plugin/legacy.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/taxonomy-plugin/legacy.ts @@ -1,6 +1,6 @@ import { PluginEvent } from '@posthog/plugin-scaffold' -import { LegacyPluginMeta } from '../../types' +import { LegacyTransformationPluginMeta } from '../../types' type Transformation = { name: string @@ -50,7 +50,7 @@ const configSelectionMap: Record = { const skippedPostHogEvents = ['survey shown', 'survey sent', 'survey dismissed'] -export function processEvent(event: PluginEvent, { config }: LegacyPluginMeta) { +export function processEvent(event: PluginEvent, { config }: LegacyTransformationPluginMeta) { if (!event.event.startsWith('$') && !skippedPostHogEvents.includes(event.event)) { const defaultTransformation = configSelectionMap[config.defaultNamingConvention] event.event = standardizeName(event.event, transformations[defaultTransformation]) diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/timestamp-parser-plugin/index.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/timestamp-parser-plugin/index.ts index d78f498fa2e74..f4964bf2d8450 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/timestamp-parser-plugin/index.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/timestamp-parser-plugin/index.ts @@ -1,9 +1,9 @@ import { PluginEvent } from '@posthog/plugin-scaffold' -import { LegacyPluginMeta, LegacyTransformationPlugin } from '../../types' +import { LegacyTransformationPlugin,LegacyTransformationPluginMeta } from '../../types' import metadata from './plugin.json' -function processEvent(event: PluginEvent, _meta: LegacyPluginMeta) { +function processEvent(event: PluginEvent, _meta: LegacyTransformationPluginMeta) { if (event.properties && event['timestamp'] && !isNaN(event['timestamp'] as any)) { const eventDate = new Date(event['timestamp']) event.properties['day_of_the_week'] = eventDate.toLocaleDateString('en-GB', { weekday: 'long' }) diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/user-agent-plugin/index.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/user-agent-plugin/index.ts index f0e4d9c5780e3..a79c9108ab742 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/user-agent-plugin/index.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/user-agent-plugin/index.ts @@ -1,10 +1,10 @@ import { PluginEvent } from '@posthog/plugin-scaffold' import { detect } from 'detect-browser' -import { LegacyPluginMeta, LegacyTransformationPlugin } from '../../types' +import { LegacyTransformationPlugin,LegacyTransformationPluginMeta } from '../../types' import metadata from './plugin.json' -export type UserAgentMeta = LegacyPluginMeta & { +export type UserAgentMeta = LegacyTransformationPluginMeta & { config: { enable: string enableSegmentAnalyticsJs?: string diff --git a/plugin-server/src/cdp/legacy-plugins/customerio/index.ts b/plugin-server/src/cdp/legacy-plugins/customerio/index.ts index 63a17618a4cfe..bd0e8382f9376 100644 --- a/plugin-server/src/cdp/legacy-plugins/customerio/index.ts +++ b/plugin-server/src/cdp/legacy-plugins/customerio/index.ts @@ -3,13 +3,13 @@ import { RetryError } from '@posthog/plugin-scaffold' import { Response } from '~/src/utils/fetch' -import { LegacyDestinationPlugin, LegacyPluginMeta } from '../types' +import { LegacyDestinationPlugin, LegacyDestinationPluginMeta } from '../types' import metadata from './plugin.json' const DEFAULT_HOST = 'track.customer.io' const DEFAULT_SEND_EVENTS_FROM_ANONYMOUS_USERS = 'Send all events' -type CustomerIoMeta = LegacyPluginMeta & { +type CustomerIoMeta = LegacyDestinationPluginMeta & { config: { customerioSiteId: string customerioToken: string diff --git a/plugin-server/src/cdp/legacy-plugins/intercom/index.ts b/plugin-server/src/cdp/legacy-plugins/intercom/index.ts index 358bccb2a63ba..31a5fcde6eedc 100644 --- a/plugin-server/src/cdp/legacy-plugins/intercom/index.ts +++ b/plugin-server/src/cdp/legacy-plugins/intercom/index.ts @@ -2,10 +2,10 @@ import { ProcessedPluginEvent, RetryError } from '@posthog/plugin-scaffold' import { Response } from '~/src/utils/fetch' -import { LegacyDestinationPlugin, LegacyPluginMeta } from '../types' +import { LegacyDestinationPlugin, LegacyDestinationPluginMeta } from '../types' import metadata from './plugin.json' -type IntercomMeta = LegacyPluginMeta & { +type IntercomMeta = LegacyDestinationPluginMeta & { global: { intercomUrl: string } diff --git a/plugin-server/src/cdp/legacy-plugins/types.ts b/plugin-server/src/cdp/legacy-plugins/types.ts index 0c57b7f17f991..a6a15b6031c65 100644 --- a/plugin-server/src/cdp/legacy-plugins/types.ts +++ b/plugin-server/src/cdp/legacy-plugins/types.ts @@ -9,11 +9,13 @@ export type LegacyPluginLogger = { error: (...args: any[]) => void } -export type LegacyPluginMeta = { +export type LegacyTransformationPluginMeta = { config: Record global: Record - logger: LegacyPluginLogger +} + +export type LegacyDestinationPluginMeta = LegacyTransformationPluginMeta & { fetch: (...args: Parameters) => Promise } @@ -23,8 +25,8 @@ export type LegacyDestinationPlugin = { name: string config: PluginConfigSchema[] } - onEvent(event: ProcessedPluginEvent, meta: LegacyPluginMeta): Promise - setupPlugin?: (meta: LegacyPluginMeta) => Promise + onEvent(event: ProcessedPluginEvent, meta: LegacyDestinationPluginMeta): Promise + setupPlugin?: (meta: LegacyDestinationPluginMeta) => Promise } export type LegacyTransformationPlugin = { @@ -33,6 +35,6 @@ export type LegacyTransformationPlugin = { name: string config: PluginConfigSchema[] } - processEvent(event: PluginEvent, meta: LegacyPluginMeta): PluginEvent | undefined | null - setupPlugin?: (meta: LegacyPluginMeta) => void + processEvent(event: PluginEvent, meta: LegacyTransformationPluginMeta): PluginEvent | undefined | null + setupPlugin?: (meta: LegacyTransformationPluginMeta) => void } diff --git a/plugin-server/src/cdp/services/legacy-plugin-executor.service.ts b/plugin-server/src/cdp/services/legacy-plugin-executor.service.ts index 0d43880cfa11f..a4c496ce30eb7 100644 --- a/plugin-server/src/cdp/services/legacy-plugin-executor.service.ts +++ b/plugin-server/src/cdp/services/legacy-plugin-executor.service.ts @@ -1,12 +1,18 @@ -import { ProcessedPluginEvent, RetryError } from '@posthog/plugin-scaffold' +import { PluginEvent, ProcessedPluginEvent, RetryError } from '@posthog/plugin-scaffold' import { DateTime } from 'luxon' import { Histogram } from 'prom-client' import { Hub } from '../../types' import { Response, trackedFetch } from '../../utils/fetch' import { status } from '../../utils/status' -import { DESTINATION_PLUGINS_BY_ID } from '../legacy-plugins' -import { LegacyPluginLogger, LegacyPluginMeta } from '../legacy-plugins/types' +import { DESTINATION_PLUGINS_BY_ID, TRANSFORMATION_PLUGINS_BY_ID } from '../legacy-plugins' +import { + LegacyDestinationPlugin, + LegacyDestinationPluginMeta, + LegacyPluginLogger, + LegacyTransformationPlugin, + LegacyTransformationPluginMeta, +} from '../legacy-plugins/types' import { sanitizeLogMessage } from '../services/hog-executor.service' import { HogFunctionInvocation, HogFunctionInvocationResult } from '../types' @@ -20,7 +26,7 @@ const pluginExecutionDuration = new Histogram({ export type PluginState = { setupPromise: Promise errored: boolean - meta: LegacyPluginMeta + meta: LegacyTransformationPluginMeta } /** @@ -43,29 +49,8 @@ export class LegacyPluginExecutorService { logs: [], } - const pluginId = invocation.hogFunction.template_id?.startsWith('plugin-') - ? invocation.hogFunction.template_id.replace('plugin-', '') - : null - const isTestFunction = invocation.hogFunction.name.includes('[CDP-TEST-HIDDEN]') - result.logs.push({ - level: 'debug', - timestamp: DateTime.now(), - message: `Executing plugin ${pluginId}`, - }) - const plugin = pluginId ? DESTINATION_PLUGINS_BY_ID[pluginId] : null - - if (!plugin || !pluginId) { - result.error = new Error(`Plugin ${pluginId} not found`) - result.logs.push({ - level: 'error', - timestamp: DateTime.now(), - message: `Plugin ${pluginId} not found`, - }) - return result - } - const addLog = (level: 'debug' | 'warn' | 'error' | 'info', ...args: any[]) => { result.logs.push({ level, @@ -81,8 +66,6 @@ export class LegacyPluginExecutorService { error: (...args: any[]) => addLog('error', ...args), } - let state = this.pluginState[pluginId] - const fetch = (...args: Parameters): Promise => { if (isTestFunction) { addLog('info', 'Fetch called but mocked due to test function') @@ -97,76 +80,118 @@ export class LegacyPluginExecutorService { return this.fetch(...args) } - if (!state) { - // TODO: Modify fetch to be a silent log if it is a test function... - const meta: LegacyPluginMeta = { - config: invocation.globals.inputs, - global: {}, - fetch, - logger: logger, + const pluginId = invocation.hogFunction.template_id?.startsWith('plugin-') + ? invocation.hogFunction.template_id.replace('plugin-', '') + : null + + try { + const plugin = pluginId + ? ((DESTINATION_PLUGINS_BY_ID[pluginId] || TRANSFORMATION_PLUGINS_BY_ID[pluginId]) as + | LegacyTransformationPlugin + | LegacyDestinationPlugin) + : null + + addLog('debug', `Executing plugin ${pluginId}`) + + if (!pluginId || !plugin) { + throw new Error(`Plugin ${pluginId} not found`) } - state = this.pluginState[pluginId] = { - setupPromise: plugin.setupPlugin?.(meta) ?? Promise.resolve(), - meta, - errored: false, + if (invocation.hogFunction.type === 'destination' && 'processEvent' in plugin) { + throw new Error(`Plugin ${pluginId} is not a destination`) + } else if (invocation.hogFunction.type === 'transformation' && 'onEvent' in plugin) { + throw new Error(`Plugin ${pluginId} is not a transformation`) } - } - try { - await state.setupPromise - } catch (e) { - state.errored = true - result.error = e - result.logs.push({ - level: 'error', - timestamp: DateTime.now(), - message: `Plugin ${pluginId} setup failed: ${e.message}`, + let state = this.pluginState[pluginId] + + if (!state) { + // TODO: Modify fetch to be a silent log if it is a test function... + const meta: LegacyTransformationPluginMeta = { + config: invocation.globals.inputs, + global: {}, + logger: logger, + } + + const setupPromise = + 'setupPlugin' in plugin + ? 'processEvent' in plugin + ? Promise.resolve(plugin.setupPlugin?.(meta)) + : Promise.resolve( + plugin.setupPlugin?.({ + ...meta, + fetch, + }) + ) + : Promise.resolve() + + // Check the type of the plugin and setup accordingly + state = this.pluginState[pluginId] = { + setupPromise, + meta, + errored: false, + } + } + + try { + await state.setupPromise + } catch (e) { + throw new Error(`Plugin ${pluginId} setup failed: ${e.message}`) + } + + const start = performance.now() + + status.info('⚡️', 'Executing plugin', { + pluginId, + invocationId: invocation.id, }) - return result - } - // Convert the invocation into the right interface for the plugin - - const event: ProcessedPluginEvent = { - distinct_id: invocation.globals.event.distinct_id, - ip: invocation.globals.event.properties.$ip, - team_id: invocation.hogFunction.team_id, - event: invocation.globals.event.event, - properties: invocation.globals.event.properties, - timestamp: invocation.globals.event.timestamp, - $set: invocation.globals.event.properties.$set, - $set_once: invocation.globals.event.properties.$set_once, - uuid: invocation.globals.event.uuid, - person: invocation.globals.person + const person: ProcessedPluginEvent['person'] = invocation.globals.person ? { uuid: invocation.globals.person.id, team_id: invocation.hogFunction.team_id, properties: invocation.globals.person.properties, created_at: '', // NOTE: We don't have this anymore - see if any plugin uses it... } - : undefined, - } - - const start = performance.now() + : undefined + + const event = { + distinct_id: invocation.globals.event.distinct_id, + ip: invocation.globals.event.properties.$ip, + team_id: invocation.hogFunction.team_id, + event: invocation.globals.event.event, + properties: invocation.globals.event.properties, + timestamp: invocation.globals.event.timestamp, + $set: invocation.globals.event.properties.$set, + $set_once: invocation.globals.event.properties.$set_once, + uuid: invocation.globals.event.uuid, + } - try { - status.info('⚡️', 'Executing plugin', { - pluginId, - invocationId: invocation.id, - }) + if ('onEvent' in plugin) { + // Destination style + const processedEvent: ProcessedPluginEvent = { + ...event, + person, + properties: event.properties || {}, + } + + await plugin.onEvent?.(processedEvent, { + ...state.meta, + // NOTE: We override logger and fetch here so we can track the calls + logger, + fetch, + }) + } else { + // Transformation style + const transformedEvent = plugin.processEvent(event as PluginEvent, { + ...state.meta, + logger, + }) + result.execResult = transformedEvent + } - await plugin.onEvent?.(event, { - ...state.meta, - // NOTE: We override logger and fetch here so we can track the calls - logger, - fetch, - }) - result.logs.push({ - level: 'debug', - timestamp: DateTime.now(), - message: `Execution successful`, - }) + addLog('debug', `Execution successful`) + pluginExecutionDuration.observe(performance.now() - start) } catch (e) { if (e instanceof RetryError) { // NOTE: Schedule as a retry to cyclotron? @@ -179,13 +204,8 @@ export class LegacyPluginExecutorService { }) result.error = e - result.logs.push({ - level: 'error', - timestamp: DateTime.now(), - message: `Plugin errored: ${e.message}`, - }) - } finally { - pluginExecutionDuration.observe(performance.now() - start) + + addLog('error', `Plugin execution failed: ${e.message}`) } return result diff --git a/plugin-server/src/cdp/services/legacy-plugin-executor.test.ts b/plugin-server/src/cdp/services/legacy-plugin-executor.test.ts index 13a93e01f974e..95618fdb2ad3e 100644 --- a/plugin-server/src/cdp/services/legacy-plugin-executor.test.ts +++ b/plugin-server/src/cdp/services/legacy-plugin-executor.test.ts @@ -268,7 +268,7 @@ describe('LegacyPluginExecutorService', () => { expect(forSnapshot(res.logs.map((l) => l.message))).toMatchInlineSnapshot(` [ "Executing plugin intercom", - "Plugin errored: Service is down, retry later", + "Plugin execution failed: Service is down, retry later", ] `) @@ -276,6 +276,101 @@ describe('LegacyPluginExecutorService', () => { }) }) + describe('processEvent', () => { + describe('mismatched types', () => { + it('should throw if the plugin is a destination but the function is a transformation', async () => { + fn.type = 'destination' + fn.template_id = 'plugin-posthog-filter-out-plugin' + + const invocation = createInvocation(fn, globals) + const res = await service.execute(invocation) + + expect(res.error).toMatchInlineSnapshot( + `[Error: Plugin posthog-filter-out-plugin is not a destination]` + ) + }) + }) + describe('event dropping', () => { + beforeEach(() => { + fn.type = 'transformation' + fn.template_id = 'plugin-posthog-filter-out-plugin' + + globals.inputs = { + eventsToDrop: 'drop-me', + } + }) + + it('should not drop if event is returned', async () => { + const invocation = createInvocation(fn, globals) + invocation.globals.event.event = 'dont-drop-me' + invocation.globals.event.properties = { + email: 'test@posthog.com', + } + + const res = await service.execute(invocation) + + expect(res.finished).toBe(true) + expect(res.error).toBeUndefined() + expect(res.execResult).toEqual(invocation.globals.event) + }) + + it('should drop if event is dropped', async () => { + const invocation = createInvocation(fn, globals) + invocation.globals.event.event = 'drop-me' + invocation.globals.event.properties = { + email: 'test@posthog.com', + } + + const res = await service.execute(invocation) + + expect(res.finished).toBe(true) + expect(res.error).toBeUndefined() + expect(res.execResult).toBeUndefined() + }) + }) + + describe('event modification', () => { + beforeEach(() => { + fn.type = 'transformation' + fn.template_id = 'plugin-semver-flattener-plugin' + + globals.inputs = { + properties: 'version', + } + }) + + it('should modify the event', async () => { + const invocation = createInvocation(fn, globals) + invocation.globals.event.properties = { + version: '1.12.20', + } + + const res = await service.execute(invocation) + + expect(res.finished).toBe(true) + expect(res.error).toBeUndefined() + expect(res.execResult).toMatchInlineSnapshot(` + { + "$set": undefined, + "$set_once": undefined, + "distinct_id": "distinct_id", + "event": "$pageview", + "ip": undefined, + "properties": { + "version": "1.12.20", + "version__major": 1, + "version__minor": 12, + "version__patch": 20, + }, + "team_id": 1, + "timestamp": "2025-01-01T00:00:00.000Z", + "uuid": "b3a1fe86-b10c-43cc-acaf-d208977608d0", + } + `) + }) + }) + }) + describe('smoke tests', () => { const testCases = Object.entries(DESTINATION_PLUGINS_BY_ID).map(([pluginId, plugin]) => ({ name: pluginId, From 4b6d7beba67e09d15c708d7e68c1c23576972c58 Mon Sep 17 00:00:00 2001 From: Ben White Date: Tue, 28 Jan 2025 17:25:36 +0100 Subject: [PATCH 059/163] Fixes all round --- .../legacy-plugin-executor.service.ts | 1 - .../services/legacy-plugin-executor.test.ts | 20 +++++++++++++------ 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/plugin-server/src/cdp/services/legacy-plugin-executor.service.ts b/plugin-server/src/cdp/services/legacy-plugin-executor.service.ts index a4c496ce30eb7..1f6a0166331ee 100644 --- a/plugin-server/src/cdp/services/legacy-plugin-executor.service.ts +++ b/plugin-server/src/cdp/services/legacy-plugin-executor.service.ts @@ -8,7 +8,6 @@ import { status } from '../../utils/status' import { DESTINATION_PLUGINS_BY_ID, TRANSFORMATION_PLUGINS_BY_ID } from '../legacy-plugins' import { LegacyDestinationPlugin, - LegacyDestinationPluginMeta, LegacyPluginLogger, LegacyTransformationPlugin, LegacyTransformationPluginMeta, diff --git a/plugin-server/src/cdp/services/legacy-plugin-executor.test.ts b/plugin-server/src/cdp/services/legacy-plugin-executor.test.ts index 95618fdb2ad3e..f73fc48aec0f0 100644 --- a/plugin-server/src/cdp/services/legacy-plugin-executor.test.ts +++ b/plugin-server/src/cdp/services/legacy-plugin-executor.test.ts @@ -311,7 +311,18 @@ describe('LegacyPluginExecutorService', () => { expect(res.finished).toBe(true) expect(res.error).toBeUndefined() - expect(res.execResult).toEqual(invocation.globals.event) + expect(forSnapshot(res.execResult)).toMatchInlineSnapshot(` + { + "distinct_id": "distinct_id", + "event": "dont-drop-me", + "properties": { + "email": "test@posthog.com", + }, + "team_id": 1, + "timestamp": "2025-01-01T00:00:00.000Z", + "uuid": "", + } + `) }) it('should drop if event is dropped', async () => { @@ -349,13 +360,10 @@ describe('LegacyPluginExecutorService', () => { expect(res.finished).toBe(true) expect(res.error).toBeUndefined() - expect(res.execResult).toMatchInlineSnapshot(` + expect(forSnapshot(res.execResult)).toMatchInlineSnapshot(` { - "$set": undefined, - "$set_once": undefined, "distinct_id": "distinct_id", "event": "$pageview", - "ip": undefined, "properties": { "version": "1.12.20", "version__major": 1, @@ -364,7 +372,7 @@ describe('LegacyPluginExecutorService', () => { }, "team_id": 1, "timestamp": "2025-01-01T00:00:00.000Z", - "uuid": "b3a1fe86-b10c-43cc-acaf-d208977608d0", + "uuid": "", } `) }) From b316fd05ebab35e4095828ad0c221ef3cdd52dcf Mon Sep 17 00:00:00 2001 From: Ben White Date: Tue, 28 Jan 2025 17:26:19 +0100 Subject: [PATCH 060/163] fix --- .../src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.ts | 2 +- .../src/cdp/services/legacy-plugin-executor.service.ts | 3 --- plugin-server/src/cdp/services/legacy-plugin-executor.test.ts | 2 +- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.ts b/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.ts index 36edf264ab82b..665c215acd962 100644 --- a/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.ts +++ b/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.ts @@ -15,7 +15,7 @@ export class CdpCyclotronWorkerPlugins extends CdpCyclotronWorker { constructor(hub: Hub) { super(hub) - this.pluginExecutor = new LegacyPluginExecutorService(hub) + this.pluginExecutor = new LegacyPluginExecutorService() } public async processInvocations(invocations: HogFunctionInvocation[]): Promise { diff --git a/plugin-server/src/cdp/services/legacy-plugin-executor.service.ts b/plugin-server/src/cdp/services/legacy-plugin-executor.service.ts index 1f6a0166331ee..d3514b7f95449 100644 --- a/plugin-server/src/cdp/services/legacy-plugin-executor.service.ts +++ b/plugin-server/src/cdp/services/legacy-plugin-executor.service.ts @@ -2,7 +2,6 @@ import { PluginEvent, ProcessedPluginEvent, RetryError } from '@posthog/plugin-s import { DateTime } from 'luxon' import { Histogram } from 'prom-client' -import { Hub } from '../../types' import { Response, trackedFetch } from '../../utils/fetch' import { status } from '../../utils/status' import { DESTINATION_PLUGINS_BY_ID, TRANSFORMATION_PLUGINS_BY_ID } from '../legacy-plugins' @@ -32,8 +31,6 @@ export type PluginState = { * NOTE: This is a consumer to take care of legacy plugins. */ export class LegacyPluginExecutorService { - constructor(private hub: Hub) {} - private pluginState: Record = {} public async fetch(...args: Parameters): Promise { diff --git a/plugin-server/src/cdp/services/legacy-plugin-executor.test.ts b/plugin-server/src/cdp/services/legacy-plugin-executor.test.ts index f73fc48aec0f0..8f4e07bd99445 100644 --- a/plugin-server/src/cdp/services/legacy-plugin-executor.test.ts +++ b/plugin-server/src/cdp/services/legacy-plugin-executor.test.ts @@ -30,7 +30,7 @@ describe('LegacyPluginExecutorService', () => { beforeEach(async () => { hub = await createHub() - service = new LegacyPluginExecutorService(hub) + service = new LegacyPluginExecutorService() team = await getFirstTeam(hub) fn = createHogFunction({ From e37a63dbb0b5b47a031dfbc98b6a04569ca4f0b5 Mon Sep 17 00:00:00 2001 From: Ben White Date: Tue, 28 Jan 2025 17:33:05 +0100 Subject: [PATCH 061/163] Fixes --- .../legacy-plugins/{ => _destinations}/customerio/index.ts | 2 +- .../legacy-plugins/{ => _destinations}/customerio/plugin.json | 0 .../cdp/legacy-plugins/{ => _destinations}/intercom/index.ts | 2 +- .../legacy-plugins/{ => _destinations}/intercom/plugin.json | 0 plugin-server/src/cdp/legacy-plugins/index.ts | 4 ++-- 5 files changed, 4 insertions(+), 4 deletions(-) rename plugin-server/src/cdp/legacy-plugins/{ => _destinations}/customerio/index.ts (99%) rename plugin-server/src/cdp/legacy-plugins/{ => _destinations}/customerio/plugin.json (100%) rename plugin-server/src/cdp/legacy-plugins/{ => _destinations}/intercom/index.ts (99%) rename plugin-server/src/cdp/legacy-plugins/{ => _destinations}/intercom/plugin.json (100%) diff --git a/plugin-server/src/cdp/legacy-plugins/customerio/index.ts b/plugin-server/src/cdp/legacy-plugins/_destinations/customerio/index.ts similarity index 99% rename from plugin-server/src/cdp/legacy-plugins/customerio/index.ts rename to plugin-server/src/cdp/legacy-plugins/_destinations/customerio/index.ts index bd0e8382f9376..0c68666461535 100644 --- a/plugin-server/src/cdp/legacy-plugins/customerio/index.ts +++ b/plugin-server/src/cdp/legacy-plugins/_destinations/customerio/index.ts @@ -3,7 +3,7 @@ import { RetryError } from '@posthog/plugin-scaffold' import { Response } from '~/src/utils/fetch' -import { LegacyDestinationPlugin, LegacyDestinationPluginMeta } from '../types' +import { LegacyDestinationPlugin, LegacyDestinationPluginMeta } from '../../types' import metadata from './plugin.json' const DEFAULT_HOST = 'track.customer.io' diff --git a/plugin-server/src/cdp/legacy-plugins/customerio/plugin.json b/plugin-server/src/cdp/legacy-plugins/_destinations/customerio/plugin.json similarity index 100% rename from plugin-server/src/cdp/legacy-plugins/customerio/plugin.json rename to plugin-server/src/cdp/legacy-plugins/_destinations/customerio/plugin.json diff --git a/plugin-server/src/cdp/legacy-plugins/intercom/index.ts b/plugin-server/src/cdp/legacy-plugins/_destinations/intercom/index.ts similarity index 99% rename from plugin-server/src/cdp/legacy-plugins/intercom/index.ts rename to plugin-server/src/cdp/legacy-plugins/_destinations/intercom/index.ts index 31a5fcde6eedc..a2f304c346411 100644 --- a/plugin-server/src/cdp/legacy-plugins/intercom/index.ts +++ b/plugin-server/src/cdp/legacy-plugins/_destinations/intercom/index.ts @@ -2,7 +2,7 @@ import { ProcessedPluginEvent, RetryError } from '@posthog/plugin-scaffold' import { Response } from '~/src/utils/fetch' -import { LegacyDestinationPlugin, LegacyDestinationPluginMeta } from '../types' +import { LegacyDestinationPlugin, LegacyDestinationPluginMeta } from '../../types' import metadata from './plugin.json' type IntercomMeta = LegacyDestinationPluginMeta & { diff --git a/plugin-server/src/cdp/legacy-plugins/intercom/plugin.json b/plugin-server/src/cdp/legacy-plugins/_destinations/intercom/plugin.json similarity index 100% rename from plugin-server/src/cdp/legacy-plugins/intercom/plugin.json rename to plugin-server/src/cdp/legacy-plugins/_destinations/intercom/plugin.json diff --git a/plugin-server/src/cdp/legacy-plugins/index.ts b/plugin-server/src/cdp/legacy-plugins/index.ts index 888399e04a662..b4dfa84a140f0 100644 --- a/plugin-server/src/cdp/legacy-plugins/index.ts +++ b/plugin-server/src/cdp/legacy-plugins/index.ts @@ -1,3 +1,5 @@ +import { customerioPlugin } from './_destinations/customerio' +import { intercomPlugin } from './_destinations/intercom' import { downsamplingPlugin } from './_transformations/downsampling-plugin' import { languageUrlSplitterApp } from './_transformations/language-url-splitter-app' import { posthogAppUrlParametersToEventPropertiesPlugin } from './_transformations/posthog-app-url-parameters-to-event-properties' @@ -8,8 +10,6 @@ import { semverFlattenerPlugin } from './_transformations/semver-flattener-plugi import { taxonomyPlugin } from './_transformations/taxonomy-plugin' import { timestampParserPlugin } from './_transformations/timestamp-parser-plugin' import { userAgentPlugin } from './_transformations/user-agent-plugin' -import { customerioPlugin } from './customerio' -import { intercomPlugin } from './intercom' export const DESTINATION_PLUGINS_BY_ID = { [customerioPlugin.id]: customerioPlugin, From f9edbca8af70fe0c7683d5ae350b78939275338d Mon Sep 17 00:00:00 2001 From: Ben White Date: Tue, 28 Jan 2025 17:53:47 +0100 Subject: [PATCH 062/163] Added input generation --- .../hog-transformations/hog-transformer.service.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/plugin-server/src/cdp/hog-transformations/hog-transformer.service.ts b/plugin-server/src/cdp/hog-transformations/hog-transformer.service.ts index 1a2ed946a6dad..3b5a18c1999d5 100644 --- a/plugin-server/src/cdp/hog-transformations/hog-transformer.service.ts +++ b/plugin-server/src/cdp/hog-transformations/hog-transformer.service.ts @@ -10,7 +10,7 @@ import { createInvocation } from '../../cdp/utils' import { runInstrumentedFunction } from '../../main/utils' import { Hub } from '../../types' import { status } from '../../utils/status' -import { HogExecutorService } from '../services/hog-executor.service' +import { buildGlobalsWithInputs, HogExecutorService } from '../services/hog-executor.service' import { HogFunctionManagerService } from '../services/hog-function-manager.service' export class HogTransformerService { @@ -66,7 +66,14 @@ export class HogTransformerService { private createHogFunctionInvocation(event: PluginEvent, hogFunction: HogFunctionType): HogFunctionInvocation { const globals = this.createInvocationGlobals(event) - return createInvocation(globals, hogFunction) + const inputs = { + ...(hogFunction.inputs ?? {}), + ...(hogFunction.encrypted_inputs ?? {}), + } + + const globalsWithInputs = buildGlobalsWithInputs(globals, inputs) + + return createInvocation(globalsWithInputs, hogFunction) } public async start(): Promise { From db089996ad7817bd23430a891e522df281d36e6d Mon Sep 17 00:00:00 2001 From: Ben White Date: Tue, 28 Jan 2025 17:54:28 +0100 Subject: [PATCH 063/163] Fixes --- .../hog-transformations/hog-transformer.service.ts | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/plugin-server/src/cdp/hog-transformations/hog-transformer.service.ts b/plugin-server/src/cdp/hog-transformations/hog-transformer.service.ts index 3b5a18c1999d5..091a3d169c750 100644 --- a/plugin-server/src/cdp/hog-transformations/hog-transformer.service.ts +++ b/plugin-server/src/cdp/hog-transformations/hog-transformer.service.ts @@ -2,6 +2,7 @@ import { PluginEvent } from '@posthog/plugin-scaffold' import { HogFunctionInvocation, + HogFunctionInvocationGlobals, HogFunctionInvocationGlobalsWithInputs, HogFunctionType, HogFunctionTypeType, @@ -43,7 +44,7 @@ export class HogTransformerService { } } - private createInvocationGlobals(event: PluginEvent): HogFunctionInvocationGlobalsWithInputs { + private createInvocationGlobals(event: PluginEvent): HogFunctionInvocationGlobals { return { project: { id: event.team_id, @@ -59,19 +60,14 @@ export class HogTransformerService { timestamp: event.timestamp || '', url: event.properties?.$current_url || '', }, - inputs: {}, } } private createHogFunctionInvocation(event: PluginEvent, hogFunction: HogFunctionType): HogFunctionInvocation { - const globals = this.createInvocationGlobals(event) - - const inputs = { + const globalsWithInputs = buildGlobalsWithInputs(this.createInvocationGlobals(event), { ...(hogFunction.inputs ?? {}), ...(hogFunction.encrypted_inputs ?? {}), - } - - const globalsWithInputs = buildGlobalsWithInputs(globals, inputs) + }) return createInvocation(globalsWithInputs, hogFunction) } From 65243990547b6492d549ddfaa17b3d65403de23e Mon Sep 17 00:00:00 2001 From: Ben White Date: Tue, 28 Jan 2025 17:57:42 +0100 Subject: [PATCH 064/163] Fix --- .../src/cdp/hog-transformations/hog-transformer.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin-server/src/cdp/hog-transformations/hog-transformer.service.ts b/plugin-server/src/cdp/hog-transformations/hog-transformer.service.ts index 091a3d169c750..e62aaf4f42704 100644 --- a/plugin-server/src/cdp/hog-transformations/hog-transformer.service.ts +++ b/plugin-server/src/cdp/hog-transformations/hog-transformer.service.ts @@ -3,7 +3,6 @@ import { PluginEvent } from '@posthog/plugin-scaffold' import { HogFunctionInvocation, HogFunctionInvocationGlobals, - HogFunctionInvocationGlobalsWithInputs, HogFunctionType, HogFunctionTypeType, } from '../../cdp/types' @@ -89,6 +88,7 @@ export class HogTransformerService { // Later we can add support for chaining/ordering for (const hogFunction of teamHogFunctions) { const invocation = this.createHogFunctionInvocation(event, hogFunction) + const result = this.hogExecutor.execute(invocation, { functions: transformationFunctions }) if (result.error) { status.warn('⚠️', 'Error in transformation', { From 979778c2b78b2b28ebf6d6eacc8870ab744a667c Mon Sep 17 00:00:00 2001 From: Ben White Date: Tue, 28 Jan 2025 17:59:55 +0100 Subject: [PATCH 065/163] Fix --- .../src/cdp/hog-transformations/hog-transformer.service.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/plugin-server/src/cdp/hog-transformations/hog-transformer.service.ts b/plugin-server/src/cdp/hog-transformations/hog-transformer.service.ts index e62aaf4f42704..a4c5ef3c8a0eb 100644 --- a/plugin-server/src/cdp/hog-transformations/hog-transformer.service.ts +++ b/plugin-server/src/cdp/hog-transformations/hog-transformer.service.ts @@ -101,6 +101,7 @@ export class HogTransformerService { // Type check execResult before accessing result if (!result.execResult) { + // TODO: Correct this - if we have no result but a successful execution then we should be dropping the event status.warn('⚠️', 'Missing execution result - no transformation applied') return event } From d44373c0d576e7ad3820211af87cb393ffed6cc8 Mon Sep 17 00:00:00 2001 From: Ben White Date: Tue, 28 Jan 2025 18:04:24 +0100 Subject: [PATCH 066/163] Added loader to the hog executor --- .../cdp/consumers/cdp-processed-events.consumer.ts | 3 ++- .../hog-transformations/hog-transformer.service.ts | 12 +++++++++--- .../cdp/services/legacy-plugin-executor.service.ts | 5 +++-- plugin-server/src/cdp/utils.ts | 4 ++++ 4 files changed, 18 insertions(+), 6 deletions(-) diff --git a/plugin-server/src/cdp/consumers/cdp-processed-events.consumer.ts b/plugin-server/src/cdp/consumers/cdp-processed-events.consumer.ts index 67b434c9979b2..ddbb755b12d5e 100644 --- a/plugin-server/src/cdp/consumers/cdp-processed-events.consumer.ts +++ b/plugin-server/src/cdp/consumers/cdp-processed-events.consumer.ts @@ -6,6 +6,7 @@ import { Hub, RawClickHouseEvent } from '~/src/types' import { convertToHogFunctionInvocationGlobals, fixLogDeduplication, + isLegacyPluginHogFunction, serializeHogFunctionInvocation, } from '../../cdp/utils' import { KAFKA_EVENTS_JSON, KAFKA_LOG_ENTRIES } from '../../config/kafka-topics' @@ -41,7 +42,7 @@ export class CdpProcessedEventsConsumer extends CdpConsumerBase { return { teamId: item.globals.project.id, functionId: item.hogFunction.id, - queueName: item.hogFunction.template_id?.startsWith('plugin-') ? 'plugin' : 'hog', + queueName: isLegacyPluginHogFunction(item.hogFunction) ? 'plugin' : 'hog', priority: item.priority, vmState: serializeHogFunctionInvocation(item), } diff --git a/plugin-server/src/cdp/hog-transformations/hog-transformer.service.ts b/plugin-server/src/cdp/hog-transformations/hog-transformer.service.ts index 1a2ed946a6dad..5e4e5a43bd0b5 100644 --- a/plugin-server/src/cdp/hog-transformations/hog-transformer.service.ts +++ b/plugin-server/src/cdp/hog-transformations/hog-transformer.service.ts @@ -6,17 +6,19 @@ import { HogFunctionType, HogFunctionTypeType, } from '../../cdp/types' -import { createInvocation } from '../../cdp/utils' +import { createInvocation, isLegacyPluginHogFunction } from '../../cdp/utils' import { runInstrumentedFunction } from '../../main/utils' import { Hub } from '../../types' import { status } from '../../utils/status' import { HogExecutorService } from '../services/hog-executor.service' import { HogFunctionManagerService } from '../services/hog-function-manager.service' +import { LegacyPluginExecutorService } from '../services/legacy-plugin-executor.service' export class HogTransformerService { private hogExecutor: HogExecutorService private hogFunctionManager: HogFunctionManagerService private hub: Hub + private pluginExecutor: LegacyPluginExecutorService constructor(hub: Hub) { this.hub = hub @@ -78,7 +80,7 @@ export class HogTransformerService { return runInstrumentedFunction({ statsKey: `hogTransformer`, // there is no await as all operations are sync - // eslint-disable-next-line @typescript-eslint/require-await + func: async () => { const teamHogFunctions = this.hogFunctionManager.getTeamHogFunctions(event.team_id) const transformationFunctions = this.getTransformationFunctions() @@ -86,7 +88,11 @@ export class HogTransformerService { // Later we can add support for chaining/ordering for (const hogFunction of teamHogFunctions) { const invocation = this.createHogFunctionInvocation(event, hogFunction) - const result = this.hogExecutor.execute(invocation, { functions: transformationFunctions }) + + const result = isLegacyPluginHogFunction(hogFunction) + ? await this.pluginExecutor.execute(invocation) + : this.hogExecutor.execute(invocation, { functions: transformationFunctions }) + if (result.error) { status.warn('⚠️', 'Error in transformation', { error: result.error, diff --git a/plugin-server/src/cdp/services/legacy-plugin-executor.service.ts b/plugin-server/src/cdp/services/legacy-plugin-executor.service.ts index d3514b7f95449..6f00345980769 100644 --- a/plugin-server/src/cdp/services/legacy-plugin-executor.service.ts +++ b/plugin-server/src/cdp/services/legacy-plugin-executor.service.ts @@ -13,6 +13,7 @@ import { } from '../legacy-plugins/types' import { sanitizeLogMessage } from '../services/hog-executor.service' import { HogFunctionInvocation, HogFunctionInvocationResult } from '../types' +import { isLegacyPluginHogFunction } from '../utils' const pluginExecutionDuration = new Histogram({ name: 'cdp_plugin_execution_duration_ms', @@ -76,8 +77,8 @@ export class LegacyPluginExecutorService { return this.fetch(...args) } - const pluginId = invocation.hogFunction.template_id?.startsWith('plugin-') - ? invocation.hogFunction.template_id.replace('plugin-', '') + const pluginId = isLegacyPluginHogFunction(invocation.hogFunction) + ? invocation.hogFunction.template_id?.replace('plugin-', '') : null try { diff --git a/plugin-server/src/cdp/utils.ts b/plugin-server/src/cdp/utils.ts index 6675bd6c3fedd..f4a43b312a5cb 100644 --- a/plugin-server/src/cdp/utils.ts +++ b/plugin-server/src/cdp/utils.ts @@ -438,3 +438,7 @@ export function buildExportedFunctionInvoker( }, } } + +export function isLegacyPluginHogFunction(hogFunction: HogFunctionType): boolean { + return hogFunction.template_id?.startsWith('plugin-') ?? false +} From 40ac5cc588db15996a62800a39b42d16e92e8354 Mon Sep 17 00:00:00 2001 From: Ben White Date: Tue, 28 Jan 2025 18:04:55 +0100 Subject: [PATCH 067/163] Fixes --- .../src/cdp/hog-transformations/hog-transformer.service.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/plugin-server/src/cdp/hog-transformations/hog-transformer.service.ts b/plugin-server/src/cdp/hog-transformations/hog-transformer.service.ts index 5e4e5a43bd0b5..ce2f4b87671dc 100644 --- a/plugin-server/src/cdp/hog-transformations/hog-transformer.service.ts +++ b/plugin-server/src/cdp/hog-transformations/hog-transformer.service.ts @@ -24,6 +24,7 @@ export class HogTransformerService { this.hub = hub this.hogFunctionManager = new HogFunctionManagerService(hub) this.hogExecutor = new HogExecutorService(hub, this.hogFunctionManager) + this.pluginExecutor = new LegacyPluginExecutorService() } // Built-in transformation functions that will be available to all transformations From 5887936c7ace54b9654b643483f2366f0e42646b Mon Sep 17 00:00:00 2001 From: Ben White Date: Tue, 28 Jan 2025 18:15:31 +0100 Subject: [PATCH 068/163] Fix - added test as starting point --- .../hog-transformer.service.test.ts | 56 +++++++++++++++---- .../posthog-filter-out-plugin/template.ts | 38 +++++++++++++ 2 files changed, 82 insertions(+), 12 deletions(-) create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/posthog-filter-out-plugin/template.ts diff --git a/plugin-server/src/cdp/hog-transformations/hog-transformer.service.test.ts b/plugin-server/src/cdp/hog-transformations/hog-transformer.service.test.ts index bde7475aec584..83a8c5a426454 100644 --- a/plugin-server/src/cdp/hog-transformations/hog-transformer.service.test.ts +++ b/plugin-server/src/cdp/hog-transformations/hog-transformer.service.test.ts @@ -10,6 +10,7 @@ import { createHogFunction } from '~/tests/cdp/fixtures' import { Hub } from '../../types' import { createHub } from '../../utils/db/hub' +import { pluginFilterOutPluginTemplate } from '../legacy-plugins/_transformations/posthog-filter-out-plugin/template' import { HogTransformerService } from './hog-transformer.service' let mockGetTeamHogFunctions: jest.Mock @@ -29,6 +30,21 @@ jest.mock('../../cdp/services/hog-function-manager.service', () => ({ })), })) +const createPluginEvent = (event: Partial = {}): PluginEvent => { + return { + ip: '89.160.20.129', + site_url: 'http://localhost', + team_id: 1, + now: '2024-06-07T12:00:00.000Z', + uuid: 'event-id', + event: 'event-name', + distinct_id: 'distinct-id', + properties: { $current_url: 'https://example.com', $ip: '89.160.20.129' }, + timestamp: '2024-01-01T00:00:00Z', + ...event, + } +} + describe('HogTransformer', () => { let hub: Hub let hogTransformer: HogTransformerService @@ -39,6 +55,8 @@ describe('HogTransformer', () => { hub = await createHub() hub.mmdb = Reader.openBuffer(brotliDecompressSync(mmdbBrotliContents)) hogTransformer = new HogTransformerService(hub) + + jest.spyOn(hogTransformer['pluginExecutor'], 'execute') }) describe('transformEvent', () => { @@ -54,18 +72,7 @@ describe('HogTransformer', () => { mockGetTeamHogFunctions.mockReturnValue([geoIpFunction]) - const event: PluginEvent = { - ip: '89.160.20.129', - site_url: 'http://localhost', - team_id: 1, - now: '2024-06-07T12:00:00.000Z', - uuid: 'event-id', - event: 'event-name', - distinct_id: 'distinct-id', - properties: { $current_url: 'https://example.com', $ip: '89.160.20.129' }, - timestamp: '2024-01-01T00:00:00Z', - } - + const event: PluginEvent = createPluginEvent() const result = await hogTransformer.transformEvent(event) expect(result.properties).toEqual({ @@ -119,4 +126,29 @@ describe('HogTransformer', () => { }) }) }) + + describe('legacy plugins', () => { + it('handles legacy plugin transformation', async () => { + const filterOutPlugin = createHogFunction({ + ...pluginFilterOutPluginTemplate, + type: 'transformation', + template_id: 'plugin-posthog-filter-out-plugin', + inputs: { + eventsToDrop: { + value: 'drop-me', + }, + }, + }) + + mockGetTeamHogFunctions.mockReturnValue([filterOutPlugin]) + + const event: PluginEvent = createPluginEvent({ + event: 'drop-me', + }) + const result = await hogTransformer.transformEvent(event) + + expect(hogTransformer['pluginExecutor'].execute).toHaveBeenCalledTimes(1) + expect(result).toBeNull() + }) + }) }) diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-filter-out-plugin/template.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-filter-out-plugin/template.ts new file mode 100644 index 0000000000000..dbf7d1801ebe7 --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-filter-out-plugin/template.ts @@ -0,0 +1,38 @@ +import { HogFunctionTemplate } from '~/src/cdp/templates/types' + +export const pluginFilterOutPluginTemplate: HogFunctionTemplate = { + status: 'free', + type: 'transformation', + id: 'template-posthog-filter-out-plugin', + name: 'PostHog Filter Out Plugin', + description: 'Filter out events where property values satisfy the given condition', + icon_url: '/static/hedgehog/builder-hog-01.png', + category: ['Custom'], + hog: ``, + inputs_schema: [ + { + key: 'filters', + label: 'Filters to apply', + type: 'string', + description: 'A JSON file containing an array of filters to apply. See the README for more information.', + required: false, + }, + { + key: 'eventsToDrop', + label: 'Events to filter out', + type: 'string', + description: 'A comma-separated list of event names to filter out (e.g. $pageview,$autocapture)', + required: false, + }, + { + key: 'keepUndefinedProperties', + label: 'Keep event if any of the filtered properties are undefined?', + type: 'choice', + choices: [ + { value: 'Yes', label: 'Yes' }, + { value: 'No', label: 'No' }, + ], + default: 'No', + }, + ], +} From 705558112914737ae09c14879f500e3db6b5a5ca Mon Sep 17 00:00:00 2001 From: Ben White Date: Tue, 28 Jan 2025 18:21:21 +0100 Subject: [PATCH 069/163] added counter --- .../src/cdp/hog-transformations/hog-transformer.service.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/plugin-server/src/cdp/hog-transformations/hog-transformer.service.ts b/plugin-server/src/cdp/hog-transformations/hog-transformer.service.ts index 3a240165b877a..a59f7360869eb 100644 --- a/plugin-server/src/cdp/hog-transformations/hog-transformer.service.ts +++ b/plugin-server/src/cdp/hog-transformations/hog-transformer.service.ts @@ -1,4 +1,5 @@ import { PluginEvent } from '@posthog/plugin-scaffold' +import { Counter } from 'prom-client' import { HogFunctionInvocation, @@ -14,6 +15,11 @@ import { buildGlobalsWithInputs, HogExecutorService } from '../services/hog-exec import { HogFunctionManagerService } from '../services/hog-function-manager.service' import { LegacyPluginExecutorService } from '../services/legacy-plugin-executor.service' +export const hogTransformationDroppedEvents = new Counter({ + name: 'hog_transformation_dropped_events', + help: 'Indicates how many events are dropped by hog transformations', +}) + export class HogTransformerService { private hogExecutor: HogExecutorService private hogFunctionManager: HogFunctionManagerService @@ -109,6 +115,7 @@ export class HogTransformerService { if (!result.execResult) { // TODO: Correct this - if we have no result but a successful execution then we should be dropping the event status.warn('⚠️', 'Execution result is null - dropping event') + hogTransformationDroppedEvents.inc() return null } From 680c0469e5f1f19f9a01aa0e46ed73f822ed70f9 Mon Sep 17 00:00:00 2001 From: Ben White Date: Tue, 28 Jan 2025 18:26:09 +0100 Subject: [PATCH 070/163] Fixes --- .../_transformations/downsampling-plugin/index.ts | 2 +- .../_transformations/language-url-splitter-app/index.ts | 2 +- .../posthog-app-url-parameters-to-event-properties/index.ts | 2 +- .../_transformations/posthog-filter-out-plugin/index.ts | 2 +- .../_transformations/property-filter-plugin/index.ts | 2 +- .../legacy-plugins/_transformations/taxonomy-plugin/index.ts | 2 +- .../_transformations/timestamp-parser-plugin/index.ts | 2 +- .../legacy-plugins/_transformations/user-agent-plugin/index.ts | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/downsampling-plugin/index.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/downsampling-plugin/index.ts index c9f17c822e4c8..640f3ff06ac64 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/downsampling-plugin/index.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/downsampling-plugin/index.ts @@ -1,7 +1,7 @@ import { PluginEvent } from '@posthog/plugin-scaffold' import { createHash } from 'crypto' -import { LegacyTransformationPlugin,LegacyTransformationPluginMeta } from '../../types' +import { LegacyTransformationPlugin, LegacyTransformationPluginMeta } from '../../types' import metadata from './plugin.json' export function setupPlugin({ config, global }: LegacyTransformationPluginMeta) { diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/language-url-splitter-app/index.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/language-url-splitter-app/index.ts index f71dfabc1b9d4..776b895ef9533 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/language-url-splitter-app/index.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/language-url-splitter-app/index.ts @@ -1,6 +1,6 @@ import { PluginEvent } from '@posthog/plugin-scaffold' -import { LegacyTransformationPlugin,LegacyTransformationPluginMeta } from '../../types' +import { LegacyTransformationPlugin, LegacyTransformationPluginMeta } from '../../types' import metadata from './plugin.json' export function processEvent(event: PluginEvent, { config }: LegacyTransformationPluginMeta) { diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-app-url-parameters-to-event-properties/index.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-app-url-parameters-to-event-properties/index.ts index b243b5c5b6c0c..804b1e15a2f4a 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-app-url-parameters-to-event-properties/index.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-app-url-parameters-to-event-properties/index.ts @@ -1,7 +1,7 @@ import { PluginEvent } from '@posthog/plugin-scaffold' import { URLSearchParams } from 'url' -import { LegacyTransformationPlugin,LegacyTransformationPluginMeta } from '../../types' +import { LegacyTransformationPlugin, LegacyTransformationPluginMeta } from '../../types' import metadata from './plugin.json' export type PluginConfig = { diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-filter-out-plugin/index.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-filter-out-plugin/index.ts index eab6d9ed796e2..da677bb5539e7 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-filter-out-plugin/index.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-filter-out-plugin/index.ts @@ -1,6 +1,6 @@ import { Meta, PluginAttachment, PluginEvent } from '@posthog/plugin-scaffold' -import { LegacyTransformationPlugin,LegacyTransformationPluginMeta } from '../../types' +import { LegacyTransformationPlugin, LegacyTransformationPluginMeta } from '../../types' import metadata from './plugin.json' export interface Filter { diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/property-filter-plugin/index.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/property-filter-plugin/index.ts index 54be9909aa9b5..f98ecc7c81dee 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/property-filter-plugin/index.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/property-filter-plugin/index.ts @@ -1,6 +1,6 @@ import { PluginEvent } from '@posthog/plugin-scaffold' -import { LegacyTransformationPlugin,LegacyTransformationPluginMeta } from '../../types' +import { LegacyTransformationPlugin, LegacyTransformationPluginMeta } from '../../types' import metadata from './plugin.json' export function setupPlugin({ config, global }: LegacyTransformationPluginMeta) { diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/taxonomy-plugin/index.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/taxonomy-plugin/index.ts index 0d51bc4bbc287..7b9f9ac8eeeea 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/taxonomy-plugin/index.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/taxonomy-plugin/index.ts @@ -1,6 +1,6 @@ import { PluginEvent } from '@posthog/plugin-scaffold' -import { LegacyTransformationPlugin,LegacyTransformationPluginMeta } from '../../types' +import { LegacyTransformationPlugin, LegacyTransformationPluginMeta } from '../../types' import metadata from './plugin.json' type Transformation = { diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/timestamp-parser-plugin/index.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/timestamp-parser-plugin/index.ts index f4964bf2d8450..eef40f78baf0a 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/timestamp-parser-plugin/index.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/timestamp-parser-plugin/index.ts @@ -1,6 +1,6 @@ import { PluginEvent } from '@posthog/plugin-scaffold' -import { LegacyTransformationPlugin,LegacyTransformationPluginMeta } from '../../types' +import { LegacyTransformationPlugin, LegacyTransformationPluginMeta } from '../../types' import metadata from './plugin.json' function processEvent(event: PluginEvent, _meta: LegacyTransformationPluginMeta) { diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/user-agent-plugin/index.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/user-agent-plugin/index.ts index a79c9108ab742..65467458f5e8f 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/user-agent-plugin/index.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/user-agent-plugin/index.ts @@ -1,7 +1,7 @@ import { PluginEvent } from '@posthog/plugin-scaffold' import { detect } from 'detect-browser' -import { LegacyTransformationPlugin,LegacyTransformationPluginMeta } from '../../types' +import { LegacyTransformationPlugin, LegacyTransformationPluginMeta } from '../../types' import metadata from './plugin.json' export type UserAgentMeta = LegacyTransformationPluginMeta & { From 5a13eea44a34d0e1e2ee98adc8f307b46b81469e Mon Sep 17 00:00:00 2001 From: Ben White Date: Tue, 28 Jan 2025 18:27:11 +0100 Subject: [PATCH 071/163] Fixes --- plugin-server/src/worker/ingestion/event-pipeline/runner.ts | 4 ++++ .../src/worker/ingestion/event-pipeline/transformEventStep.ts | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/plugin-server/src/worker/ingestion/event-pipeline/runner.ts b/plugin-server/src/worker/ingestion/event-pipeline/runner.ts index 0163d3fe4bb24..32b986f672f29 100644 --- a/plugin-server/src/worker/ingestion/event-pipeline/runner.ts +++ b/plugin-server/src/worker/ingestion/event-pipeline/runner.ts @@ -239,6 +239,10 @@ export class EventPipelineRunner { event.team_id ) + if (transformedEvent == null) { + return this.registerLastStep('transformEventStep', [processedEvent], kafkaAcks) + } + const [normalizedEvent, timestamp] = await this.runStep( normalizeEventStep, [transformedEvent, processPerson], diff --git a/plugin-server/src/worker/ingestion/event-pipeline/transformEventStep.ts b/plugin-server/src/worker/ingestion/event-pipeline/transformEventStep.ts index d24239696a1b3..1910a51b2ddaa 100644 --- a/plugin-server/src/worker/ingestion/event-pipeline/transformEventStep.ts +++ b/plugin-server/src/worker/ingestion/event-pipeline/transformEventStep.ts @@ -11,7 +11,7 @@ import { HogTransformerService } from '../../../cdp/hog-transformations/hog-tran export async function transformEventStep( event: PluginEvent, hogTransformer: HogTransformerService | null -): Promise { +): Promise { if (!hogTransformer) { return event } From dbea6e3fc0b89d05aba8aae085fd89edba68cddb Mon Sep 17 00:00:00 2001 From: Ben White Date: Wed, 29 Jan 2025 09:20:45 +0100 Subject: [PATCH 072/163] Added all templates --- .../language-url-splitter-app/template.ts | 62 ++++++++++++++ .../template.ts | 85 +++++++++++++++++++ .../posthog-filter-out-plugin/plugin.json | 2 +- .../posthog-filter-out-plugin/template.ts | 14 +-- .../posthog-url-normalizer-plugin/plugin.json | 2 +- .../posthog-url-normalizer-plugin/template.ts | 14 +++ .../property-filter-plugin/plugin.json | 2 +- .../property-filter-plugin/template.ts | 22 +++++ .../semver-flattener-plugin/plugin.json | 2 +- .../semver-flattener-plugin/template.ts | 22 +++++ .../taxonomy-plugin/index.test.ts | 2 +- .../_transformations/taxonomy-plugin/index.ts | 47 ++++++---- .../taxonomy-plugin/legacy.ts | 80 ----------------- .../taxonomy-plugin/template.ts | 28 ++++++ .../timestamp-parser-plugin/template.ts | 13 +++ .../user-agent-plugin/template.ts | 48 +++++++++++ .../templates/_plugins/downsample.template.ts | 32 +++++++ 17 files changed, 370 insertions(+), 107 deletions(-) create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/language-url-splitter-app/template.ts create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/posthog-app-url-parameters-to-event-properties/template.ts create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/posthog-url-normalizer-plugin/template.ts create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/property-filter-plugin/template.ts create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/semver-flattener-plugin/template.ts delete mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/taxonomy-plugin/legacy.ts create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/taxonomy-plugin/template.ts create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/timestamp-parser-plugin/template.ts create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/user-agent-plugin/template.ts create mode 100644 plugin-server/src/cdp/templates/_plugins/downsample.template.ts diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/language-url-splitter-app/template.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/language-url-splitter-app/template.ts new file mode 100644 index 0000000000000..ab786cf78c13d --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/language-url-splitter-app/template.ts @@ -0,0 +1,62 @@ +import { HogFunctionTemplate } from '~/src/cdp/templates/types' + +export const template: HogFunctionTemplate = { + status: 'alpha', + type: 'transformation', + id: 'plugin-language-url-splitter-app', + name: 'Language URL splitter', + description: 'Splits the language from the URL', + icon_url: 'https://raw.githubusercontent.com/posthog/language-url-splitter-app/main/logo.png', + category: ['Transformation'], + hog: `return event`, + inputs_schema: [ + { + key: 'pattern', + label: 'Pattern', + type: 'string', + default: '^/([a-z]{2})(?=/|#|\\?|$)', + description: 'Ininitalized with `const regexp = new RegExp($pattern)`', + required: true, + }, + { + key: 'matchGroup', + label: 'Match group', + type: 'string', + default: '1', + description: 'Used in: `const value = regexp.match($pathname)[$matchGroup]`', + required: true, + }, + { + key: 'property', + label: 'Property', + type: 'string', + default: 'locale', + description: 'Name of the event property we will store the matched value in', + required: true, + }, + { + key: 'replacePattern', + label: 'Replacement pattern', + type: 'string', + default: '^(/[a-z]{2})(/|(?=/|#|\\?|$))', + description: 'Initialized with `new RegExp($pattern)`, leave empty to disable path cleanup.', + required: true, + }, + { + key: 'replaceKey', + label: 'Replacement key', + type: 'string', + default: '$pathname', + description: 'Where to store the updated path. Keep as `$pathname` to override.', + required: true, + }, + { + key: 'replaceValue', + label: 'Replacement value', + type: 'string', + default: '/', + description: '`properties[key] = $pathname.replace(pattern, value)`', + required: true, + }, + ], +} diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-app-url-parameters-to-event-properties/template.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-app-url-parameters-to-event-properties/template.ts new file mode 100644 index 0000000000000..df28a52593737 --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-app-url-parameters-to-event-properties/template.ts @@ -0,0 +1,85 @@ +import { HogFunctionTemplate } from '~/src/cdp/templates/types' + +export const template: HogFunctionTemplate = { + status: 'alpha', + type: 'transformation', + id: 'plugin-posthog-app-url-parameters-to-event-properties', + name: 'URL parameters to event properties', + description: 'Converts URL query parameters to event properties', + icon_url: 'https://raw.githubusercontent.com/posthog/posthog-app-url-parameters-to-event-properties/main/logo.png', + category: ['Transformation'], + hog: `return event`, + inputs_schema: [ + { + key: 'parameters', + label: 'URL query parameters to convert', + type: 'string', + default: '', + description: + 'Comma separated list of URL query parameters to capture. Leaving this blank will capture nothing.', + }, + { + key: 'prefix', + label: 'Prefix', + type: 'string', + default: '', + description: + "Add a prefix to the property name e.g. set it to 'prefix_' to get followerId -> prefix_followerId", + }, + { + key: 'suffix', + label: 'Suffix', + type: 'string', + default: '', + description: + "Add a suffix to the property name e.g. set it to '_suffix' to get followerId -> followerId_suffix", + }, + { + key: 'ignoreCase', + label: 'Ignore the case of URL parameters', + type: 'choice', + choices: [ + { value: 'true', label: 'true' }, + { value: 'false', label: 'false' }, + ], + default: 'false', + description: + 'Ignores the case of parameters e.g. when set to true than followerId would match FollowerId, followerID, FoLlOwErId and similar', + }, + { + key: 'setAsUserProperties', + label: 'Add to user properties', + type: 'choice', + choices: [ + { value: 'true', label: 'true' }, + { value: 'false', label: 'false' }, + ], + default: 'false', + description: 'Additionally adds the property to the user properties', + }, + { + key: 'setAsInitialUserProperties', + label: 'Add to user initial properties', + type: 'choice', + choices: [ + { value: 'true', label: 'true' }, + { value: 'false', label: 'false' }, + ], + default: 'false', + description: + "Additionally adds the property to the user initial properties. This will add a prefix of 'initial_' before the already fully composed property e.g. initial_prefix_followerId_suffix", + }, + { + key: 'alwaysJson', + label: 'Always JSON stringify the property data', + type: 'choice', + choices: [ + { value: 'true', label: 'true' }, + { value: 'false', label: 'false' }, + ], + default: 'false', + description: + 'If set, always store the resulting data as a JSON array. (Otherwise, single parameters get stored as-is, and multi-value parameters get stored as a JSON array.)', + }, + ], +} diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-filter-out-plugin/plugin.json b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-filter-out-plugin/plugin.json index f6fee89f98eb4..d8703982b0b01 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-filter-out-plugin/plugin.json +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-filter-out-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "Filter Out Plugin", - "url": "https://github.com/plibither8/posthog-filter-out-plugin", + "url": "https://github.com/PostHog/posthog-filter-out-plugin", "description": "Filter out events where property values satisfy the given condition", "main": "src/main.ts", "config": [ diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-filter-out-plugin/template.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-filter-out-plugin/template.ts index dbf7d1801ebe7..b749f322790ee 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-filter-out-plugin/template.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-filter-out-plugin/template.ts @@ -1,14 +1,14 @@ import { HogFunctionTemplate } from '~/src/cdp/templates/types' -export const pluginFilterOutPluginTemplate: HogFunctionTemplate = { - status: 'free', +export const template: HogFunctionTemplate = { + status: 'alpha', type: 'transformation', - id: 'template-posthog-filter-out-plugin', - name: 'PostHog Filter Out Plugin', + id: 'plugin-posthog-filter-out-plugin', + name: 'Filter Out Plugin', description: 'Filter out events where property values satisfy the given condition', - icon_url: '/static/hedgehog/builder-hog-01.png', - category: ['Custom'], - hog: ``, + icon_url: 'https://raw.githubusercontent.com/posthog/posthog-filter-out-plugin/main/logo.png', + category: ['Transformation'], + hog: `return event`, inputs_schema: [ { key: 'filters', diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-url-normalizer-plugin/plugin.json b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-url-normalizer-plugin/plugin.json index be643ef352e2d..8c3b4cab6a998 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-url-normalizer-plugin/plugin.json +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-url-normalizer-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "URL Normalizer", - "url": "https://github.com/MarkBennett/posthog-url-normalizer-plugin", + "url": "https://github.com/PostHog/posthog-url-normalizer-plugin", "description": "A PostHog plugin to normalize the format of urls in your application allowing you to more easily compare them in insights.", "main": "index.ts", "posthogVersion": ">= 1.25.0", diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-url-normalizer-plugin/template.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-url-normalizer-plugin/template.ts new file mode 100644 index 0000000000000..d9e84c1dd4bd6 --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-url-normalizer-plugin/template.ts @@ -0,0 +1,14 @@ +import { HogFunctionTemplate } from '~/src/cdp/templates/types' + +export const template: HogFunctionTemplate = { + status: 'alpha', + type: 'transformation', + id: 'plugin-posthog-url-normalizer-plugin', + name: 'URL Normalizer', + description: + 'Normalize the format of urls in your application allowing you to more easily compare them in insights.', + icon_url: 'https://raw.githubusercontent.com/posthog/posthog-url-normalizer-plugin/main/logo.png', + category: ['Transformation'], + hog: `return event`, + inputs_schema: [], +} diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/property-filter-plugin/plugin.json b/plugin-server/src/cdp/legacy-plugins/_transformations/property-filter-plugin/plugin.json index a7b3e49f4b98e..487a77ad477b8 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/property-filter-plugin/plugin.json +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/property-filter-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "Property Filter", - "url": "https://github.com/witty-works/posthog-property-filter-plugin", + "url": "https://github.com/posthog/property-filter-plugin", "description": "This plugin will set all configured properties to null inside an ingested event.", "main": "index.js", "config": [ diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/property-filter-plugin/template.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/property-filter-plugin/template.ts new file mode 100644 index 0000000000000..4f5d7d64754fa --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/property-filter-plugin/template.ts @@ -0,0 +1,22 @@ +import { HogFunctionTemplate } from '~/src/cdp/templates/types' + +export const template: HogFunctionTemplate = { + status: 'alpha', + type: 'transformation', + id: 'plugin-property-filter-plugin', + name: 'Property Filter', + description: 'This plugin will set all configured properties to null inside an ingested event.', + icon_url: 'https://raw.githubusercontent.com/posthog/posthog-property-filter-plugin/main/logo.png', + category: ['Transformation'], + hog: `return event`, + inputs_schema: [ + { + key: 'properties', + label: 'Properties to filter out', + type: 'string', + description: 'A comma-separated list of properties to filter out (e.g. $ip, $current_url)', + default: '', + required: true, + }, + ], +} diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/semver-flattener-plugin/plugin.json b/plugin-server/src/cdp/legacy-plugins/_transformations/semver-flattener-plugin/plugin.json index fe344eb252a77..76f2c6262fdba 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/semver-flattener-plugin/plugin.json +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/semver-flattener-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "posthog-semver-flattener", - "url": "https://github.com/PostHog/posthog-semver-flattener-plugin", + "url": "https://github.com/PostHog/semver-flattener-plugin", "main": "index.ts", "config": [ { diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/semver-flattener-plugin/template.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/semver-flattener-plugin/template.ts new file mode 100644 index 0000000000000..1b6acc7cac708 --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/semver-flattener-plugin/template.ts @@ -0,0 +1,22 @@ +import { HogFunctionTemplate } from '~/src/cdp/templates/types' + +export const template: HogFunctionTemplate = { + status: 'alpha', + type: 'transformation', + id: 'plugin-semver-flattener-plugin', + name: 'SemVer Flattener', + description: 'This plugin will flatten semver versions in the specified properties.', + icon_url: 'https://raw.githubusercontent.com/posthog/posthog-semver-flattener-plugin/main/logo.png', + category: ['Transformation'], + hog: `return event`, + inputs_schema: [ + { + key: 'properties', + label: 'comma separated properties to explode version number from', + type: 'string', + description: 'my_version_number,app_version', + default: '', + required: true, + }, + ], +} diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/taxonomy-plugin/index.test.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/taxonomy-plugin/index.test.ts index 2db8066e3be06..fdedc85bc0364 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/taxonomy-plugin/index.test.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/taxonomy-plugin/index.test.ts @@ -1,7 +1,7 @@ import { PluginEvent } from '@posthog/plugin-scaffold' import { LegacyTransformationPluginMeta } from '../../types' -import { processEvent } from './legacy' +import { processEvent } from './index' const createEvent = (event: Partial): PluginEvent => ({ diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/taxonomy-plugin/index.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/taxonomy-plugin/index.ts index 7b9f9ac8eeeea..05dde9edd3327 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/taxonomy-plugin/index.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/taxonomy-plugin/index.ts @@ -1,46 +1,59 @@ import { PluginEvent } from '@posthog/plugin-scaffold' -import { LegacyTransformationPlugin, LegacyTransformationPluginMeta } from '../../types' -import metadata from './plugin.json' +import { LegacyTransformationPluginMeta } from '../../types' type Transformation = { + name: string matchPattern: RegExp transform: (str: string, matchPattern: RegExp) => string } -const transformations: Record = { - camelCase: { +const transformations: Transformation[] = [ + { + name: 'camelCase', matchPattern: /[A-Z]/g, transform: (str: string, matchPattern: RegExp) => str[0].toLowerCase() + str.slice(1).replace(matchPattern, (substr: string) => substr[substr.length - 1].toUpperCase()), }, - PascalCase: { + { + name: 'PascalCase', matchPattern: /[A-Z]/g, transform: (str: string, matchPattern: RegExp) => str[0].toUpperCase() + - str.slice(1).replace(matchPattern, (substr: string) => substr[substr.length - 1].toUpperCase()), + str.slice(1).replace(matchPattern, (substr) => substr[substr.length - 1].toUpperCase()), }, - snake_case: { + { + name: 'snake_case', matchPattern: /([_])([a-z])/g, transform: (str: string, matchPattern: RegExp) => defaultTransformation(str, matchPattern, '_'), }, - 'kebab-case': { + { + name: 'kebab_case', matchPattern: /([-])([a-z])/g, transform: (str: string, matchPattern: RegExp) => defaultTransformation(str, matchPattern, '-'), }, - 'spaces in between': { + { + name: 'spaces', matchPattern: /([\s])([a-z])/g, transform: (str: string, matchPattern: RegExp) => defaultTransformation(str, matchPattern, ' '), }, +] + +const configSelectionMap: Record = { + camelCase: 0, + PascalCase: 1, + snake_case: 2, + 'kebab-case': 3, + 'spaces in between': 4, } const skippedPostHogEvents = ['survey shown', 'survey sent', 'survey dismissed'] export function processEvent(event: PluginEvent, { config }: LegacyTransformationPluginMeta) { if (!event.event.startsWith('$') && !skippedPostHogEvents.includes(event.event)) { - const transformer = transformations[config.defaultNamingConvention] - event.event = transformer.transform(event.event, transformer.matchPattern) + const defaultTransformation = configSelectionMap[config.defaultNamingConvention] + event.event = standardizeName(event.event, transformations[defaultTransformation]) } return event } @@ -56,8 +69,12 @@ const defaultTransformation = (str: string, matchPattern: RegExp, sep: string) = return parsedStr } -export const taxonomyPlugin: LegacyTransformationPlugin = { - id: 'taxonomy-plugin', - metadata: metadata as any, - processEvent, +const standardizeName = (name: string, desiredPattern: Transformation) => { + for (const transformation of transformations) { + if (transformation.name === desiredPattern.name || name.search(transformation.matchPattern) < 0) { + continue + } + return desiredPattern.transform(name, transformation.matchPattern) + } + return name } diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/taxonomy-plugin/legacy.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/taxonomy-plugin/legacy.ts deleted file mode 100644 index 05dde9edd3327..0000000000000 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/taxonomy-plugin/legacy.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { PluginEvent } from '@posthog/plugin-scaffold' - -import { LegacyTransformationPluginMeta } from '../../types' - -type Transformation = { - name: string - matchPattern: RegExp - transform: (str: string, matchPattern: RegExp) => string -} - -const transformations: Transformation[] = [ - { - name: 'camelCase', - matchPattern: /[A-Z]/g, - transform: (str: string, matchPattern: RegExp) => - str[0].toLowerCase() + - str.slice(1).replace(matchPattern, (substr: string) => substr[substr.length - 1].toUpperCase()), - }, - { - name: 'PascalCase', - matchPattern: /[A-Z]/g, - transform: (str: string, matchPattern: RegExp) => - str[0].toUpperCase() + - str.slice(1).replace(matchPattern, (substr) => substr[substr.length - 1].toUpperCase()), - }, - { - name: 'snake_case', - matchPattern: /([_])([a-z])/g, - transform: (str: string, matchPattern: RegExp) => defaultTransformation(str, matchPattern, '_'), - }, - { - name: 'kebab_case', - matchPattern: /([-])([a-z])/g, - transform: (str: string, matchPattern: RegExp) => defaultTransformation(str, matchPattern, '-'), - }, - { - name: 'spaces', - matchPattern: /([\s])([a-z])/g, - transform: (str: string, matchPattern: RegExp) => defaultTransformation(str, matchPattern, ' '), - }, -] - -const configSelectionMap: Record = { - camelCase: 0, - PascalCase: 1, - snake_case: 2, - 'kebab-case': 3, - 'spaces in between': 4, -} - -const skippedPostHogEvents = ['survey shown', 'survey sent', 'survey dismissed'] - -export function processEvent(event: PluginEvent, { config }: LegacyTransformationPluginMeta) { - if (!event.event.startsWith('$') && !skippedPostHogEvents.includes(event.event)) { - const defaultTransformation = configSelectionMap[config.defaultNamingConvention] - event.event = standardizeName(event.event, transformations[defaultTransformation]) - } - return event -} - -const defaultTransformation = (str: string, matchPattern: RegExp, sep: string) => { - const parsedStr = str.replace( - matchPattern, - (substr) => sep + (substr.length === 1 ? substr.toLowerCase() : substr[1].toLowerCase()) - ) - if (parsedStr[0] === sep) { - return parsedStr.slice(1) // Handle PascalCase - } - return parsedStr -} - -const standardizeName = (name: string, desiredPattern: Transformation) => { - for (const transformation of transformations) { - if (transformation.name === desiredPattern.name || name.search(transformation.matchPattern) < 0) { - continue - } - return desiredPattern.transform(name, transformation.matchPattern) - } - return name -} diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/taxonomy-plugin/template.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/taxonomy-plugin/template.ts new file mode 100644 index 0000000000000..a8bcd3fba0785 --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/taxonomy-plugin/template.ts @@ -0,0 +1,28 @@ +import { HogFunctionTemplate } from '~/src/cdp/templates/types' + +export const template: HogFunctionTemplate = { + status: 'alpha', + type: 'transformation', + id: 'plugin-taxonomy-plugin', + name: 'Taxonomy', + description: 'Standardize your event names into a single pattern.', + icon_url: 'https://raw.githubusercontent.com/posthog/taxonomy-plugin/main/logo.png', + category: ['Transformation'], + hog: `return event`, + inputs_schema: [ + { + key: 'defaultNamingConvention', + label: 'Select your default naming pattern', + type: 'choice', + choices: [ + { value: 'camelCase', label: 'camelCase' }, + { value: 'PascalCase', label: 'PascalCase' }, + { value: 'snake_case', label: 'snake_case' }, + { value: 'kebab-case', label: 'kebab-case' }, + { value: 'spaces in between', label: 'spaces in between' }, + ], + default: 'camelCase', + required: true, + }, + ], +} diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/timestamp-parser-plugin/template.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/timestamp-parser-plugin/template.ts new file mode 100644 index 0000000000000..d1ed26e6fe998 --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/timestamp-parser-plugin/template.ts @@ -0,0 +1,13 @@ +import { HogFunctionTemplate } from '~/src/cdp/templates/types' + +export const template: HogFunctionTemplate = { + status: 'alpha', + type: 'transformation', + id: 'plugin-timestamp-parser-plugin', + name: 'Timestamp Parser', + description: 'Parse your event timestamps into useful date properties.', + icon_url: 'https://raw.githubusercontent.com/posthog/timestamp-parser-plugin/main/logo.png', + category: ['Transformation'], + hog: `return event`, + inputs_schema: [], +} diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/user-agent-plugin/template.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/user-agent-plugin/template.ts new file mode 100644 index 0000000000000..313c7d85afa59 --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/user-agent-plugin/template.ts @@ -0,0 +1,48 @@ +import { HogFunctionTemplate } from '~/src/cdp/templates/types' + +export const template: HogFunctionTemplate = { + status: 'alpha', + type: 'transformation', + id: 'plugin-user-agent-plugin', + name: 'User Agent Populator', + description: + "Enhances events with user agent details. User Agent plugin allows you to populate events with the $browser, $browser_version for PostHog Clients that don't typically populate these properties", + icon_url: 'https://raw.githubusercontent.com/posthog/useragent-plugin/main/logo.png', + category: ['Transformation'], + hog: `return event`, + inputs_schema: [ + { + key: 'overrideUserAgentDetails', + label: 'Can override existing browser related properties of event?', + type: 'string', + description: + 'If the ingested event already have $browser $browser_version properties in combination with $useragent the $browser, $browser_version properties will be re-populated with the value of $useragent', + default: 'false', + required: false, + }, + { + key: 'enableSegmentAnalyticsJs', + label: 'Automatically read segment_userAgent property, automatically sent by Segment via analytics.js?', + type: 'choice', + description: + "Segment's analytics.js library automatically sends a useragent property that Posthog sees as segment_userAgent. Enabling this causes this plugin to parse that property", + choices: [ + { value: 'false', label: 'false' }, + { value: 'true', label: 'true' }, + ], + default: 'false', + required: false, + }, + { + key: 'debugMode', + type: 'choice', + description: 'Enable debug mode to log when the plugin is unable to extract values from the user agent', + choices: [ + { value: 'false', label: 'false' }, + { value: 'true', label: 'true' }, + ], + default: 'false', + required: false, + }, + ], +} diff --git a/plugin-server/src/cdp/templates/_plugins/downsample.template.ts b/plugin-server/src/cdp/templates/_plugins/downsample.template.ts new file mode 100644 index 0000000000000..40927eacc9982 --- /dev/null +++ b/plugin-server/src/cdp/templates/_plugins/downsample.template.ts @@ -0,0 +1,32 @@ +import { HogFunctionTemplate } from '../types' + +export const template: HogFunctionTemplate = { + status: 'alpha', + type: 'transformation', + id: 'plugin-downsampling-plugin', + name: 'Downsample', + description: 'Reduces event volume coming into PostHog', + icon_url: 'https://raw.githubusercontent.com/posthog/downsampling-plugin/main/logo.png', + category: ['Custom'], + hog: `return event`, + inputs_schema: [ + { + type: 'string', + key: 'percentage', + label: '% of events to keep', + default: '100', + required: false, + }, + { + type: 'choice', + key: 'samplingMethod', + label: 'Sampling method', + choices: [ + { value: 'Random sampling', label: 'Random sampling' }, + { value: 'Distinct ID aware sampling', label: 'Distinct ID aware sampling' }, + ], + default: 'Distinct ID aware sampling', + required: false, + }, + ], +} From f58fa85ce300bd73c250d0f5b946a593636db277 Mon Sep 17 00:00:00 2001 From: Ben White Date: Wed, 29 Jan 2025 13:47:46 +0100 Subject: [PATCH 073/163] Fixes --- .../cdp-cyclotron-plugins-worker.test.ts | 22 ++++++------- .../_transformations/taxonomy-plugin/index.ts | 9 +++++- .../legacy-plugin-executor.test.ts.snap | 8 ++--- .../services/legacy-plugin-executor.test.ts | 32 +++++++++---------- 4 files changed, 38 insertions(+), 33 deletions(-) diff --git a/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.test.ts b/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.test.ts index 34411d4e9bc90..7d9a3743a76ba 100644 --- a/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.test.ts +++ b/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.test.ts @@ -37,7 +37,7 @@ describe('CdpCyclotronWorkerPlugins', () => { return item } - const intercomPlugin = PLUGINS_BY_ID['posthog-intercom-plugin'] + const intercomPlugin = DESTINATION_PLUGINS_BY_ID['posthog-intercom-plugin'] beforeEach(async () => { await resetTestDatabase() @@ -117,9 +117,8 @@ describe('CdpCyclotronWorkerPlugins', () => { expect(await results).toMatchObject([{ finished: true }, { finished: true }, { finished: true }]) - expect(DESTINATION_PLUGINS_BY_ID['intercom'].setupPlugin).toHaveBeenCalledTimes(1) - expect(jest.mocked(DESTINATION_PLUGINS_BY_ID['intercom'].setupPlugin!).mock.calls[0][0]) - .toMatchInlineSnapshot(` + expect(intercomPlugin.setupPlugin).toHaveBeenCalledTimes(1) + expect(jest.mocked(intercomPlugin.setupPlugin!).mock.calls[0][0]).toMatchInlineSnapshot(` { "config": { "ignoredEmailDomains": "dev.posthog.com", @@ -142,7 +141,7 @@ describe('CdpCyclotronWorkerPlugins', () => { describe('onEvent', () => { it('should call the plugin onEvent method', async () => { - jest.spyOn(DESTINATION_PLUGINS_BY_ID['intercom'] as any, 'onEvent') + jest.spyOn(intercomPlugin as any, 'onEvent') const invocation = createInvocation(fn, globals) invocation.globals.event.event = 'mycustomevent' @@ -157,9 +156,8 @@ describe('CdpCyclotronWorkerPlugins', () => { await processor.processBatch([invocation]) - expect(DESTINATION_PLUGINS_BY_ID['intercom'].onEvent).toHaveBeenCalledTimes(1) - expect(forSnapshot(jest.mocked(DESTINATION_PLUGINS_BY_ID['intercom'].onEvent!).mock.calls[0][0])) - .toMatchInlineSnapshot(` + expect(intercomPlugin.onEvent).toHaveBeenCalledTimes(1) + expect(forSnapshot(jest.mocked(intercomPlugin.onEvent!).mock.calls[0][0])).toMatchInlineSnapshot(` { "distinct_id": "distinct_id", "event": "mycustomevent", @@ -222,7 +220,7 @@ describe('CdpCyclotronWorkerPlugins', () => { }) it('should mock out fetch if it is a test function', async () => { - jest.spyOn(DESTINATION_PLUGINS_BY_ID['intercom'] as any, 'onEvent') + jest.spyOn(intercomPlugin as any, 'onEvent') const invocation = createInvocation(fn, globals) invocation.hogFunction.name = 'My function [CDP-TEST-HIDDEN]' @@ -235,7 +233,7 @@ describe('CdpCyclotronWorkerPlugins', () => { expect(mockFetch).toHaveBeenCalledTimes(0) - expect(DESTINATION_PLUGINS_BY_ID['intercom'].onEvent).toHaveBeenCalledTimes(1) + expect(intercomPlugin.onEvent).toHaveBeenCalledTimes(1) expect(forSnapshot(getProducedKafkaMessagesForTopic('log_entries_test').map((m) => m.value.message))) .toMatchInlineSnapshot(` @@ -249,7 +247,7 @@ describe('CdpCyclotronWorkerPlugins', () => { }) it('should handle and collect errors', async () => { - jest.spyOn(DESTINATION_PLUGINS_BY_ID['intercom'] as any, 'onEvent') + jest.spyOn(intercomPlugin as any, 'onEvent') const invocation = createInvocation(fn, globals) invocation.globals.event.event = 'mycustomevent' @@ -261,7 +259,7 @@ describe('CdpCyclotronWorkerPlugins', () => { const res = await processor.processBatch([invocation]) - expect(DESTINATION_PLUGINS_BY_ID['intercom'].onEvent).toHaveBeenCalledTimes(1) + expect(intercomPlugin.onEvent).toHaveBeenCalledTimes(1) expect(res[0].error).toBeInstanceOf(Error) expect(forSnapshot(res[0].logs)).toMatchInlineSnapshot(`[]`) diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/taxonomy-plugin/index.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/taxonomy-plugin/index.ts index 05dde9edd3327..32dc3e2b0df9b 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/taxonomy-plugin/index.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/taxonomy-plugin/index.ts @@ -1,6 +1,7 @@ import { PluginEvent } from '@posthog/plugin-scaffold' -import { LegacyTransformationPluginMeta } from '../../types' +import { LegacyTransformationPlugin, LegacyTransformationPluginMeta } from '../../types' +import metadata from './plugin.json' type Transformation = { name: string @@ -78,3 +79,9 @@ const standardizeName = (name: string, desiredPattern: Transformation) => { } return name } + +export const taxonomyPlugin: LegacyTransformationPlugin = { + id: 'taxonomy-plugin', + metadata: metadata as any, + processEvent, +} diff --git a/plugin-server/src/cdp/services/__snapshots__/legacy-plugin-executor.test.ts.snap b/plugin-server/src/cdp/services/__snapshots__/legacy-plugin-executor.test.ts.snap index 03393ee21f850..72cd1119ea0bd 100644 --- a/plugin-server/src/cdp/services/__snapshots__/legacy-plugin-executor.test.ts.snap +++ b/plugin-server/src/cdp/services/__snapshots__/legacy-plugin-executor.test.ts.snap @@ -1,10 +1,10 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`LegacyPluginExecutorService smoke tests should run the plugin: { name: 'customer-io', plugin: [Object] } 1`] = ` +exports[`LegacyPluginExecutorService smoke tests should run the plugin: { name: 'customerio-plugin', plugin: [Object] } 1`] = ` [ { "level": "debug", - "message": "Executing plugin customer-io", + "message": "Executing plugin customerio-plugin", "timestamp": "2025-01-01T01:00:00.000+01:00", }, { @@ -35,11 +35,11 @@ exports[`LegacyPluginExecutorService smoke tests should run the plugin: { name: ] `; -exports[`LegacyPluginExecutorService smoke tests should run the plugin: { name: 'intercom', plugin: [Object] } 1`] = ` +exports[`LegacyPluginExecutorService smoke tests should run the plugin: { name: 'posthog-intercom-plugin', plugin: [Object] } 1`] = ` [ { "level": "debug", - "message": "Executing plugin intercom", + "message": "Executing plugin posthog-intercom-plugin", "timestamp": "2025-01-01T01:00:00.000+01:00", }, { diff --git a/plugin-server/src/cdp/services/legacy-plugin-executor.test.ts b/plugin-server/src/cdp/services/legacy-plugin-executor.test.ts index 8f4e07bd99445..e6c2ed675b76d 100644 --- a/plugin-server/src/cdp/services/legacy-plugin-executor.test.ts +++ b/plugin-server/src/cdp/services/legacy-plugin-executor.test.ts @@ -28,6 +28,8 @@ describe('LegacyPluginExecutorService', () => { let fn: HogFunctionType let mockFetch: jest.Mock + const intercomPlugin = DESTINATION_PLUGINS_BY_ID['posthog-intercom-plugin'] + beforeEach(async () => { hub = await createHub() service = new LegacyPluginExecutorService() @@ -35,7 +37,7 @@ describe('LegacyPluginExecutorService', () => { fn = createHogFunction({ name: 'Plugin test', - template_id: 'plugin-intercom', + template_id: `plugin-${intercomPlugin.id}`, }) const fixedTime = DateTime.fromObject({ year: 2025, month: 1, day: 1 }, { zone: 'UTC' }) @@ -90,7 +92,7 @@ describe('LegacyPluginExecutorService', () => { describe('setupPlugin', () => { it('should setup a plugin on first call', async () => { - jest.spyOn(DESTINATION_PLUGINS_BY_ID['intercom'] as any, 'setupPlugin') + jest.spyOn(intercomPlugin, 'setupPlugin') await service.execute(createInvocation(fn, globals)) @@ -102,9 +104,8 @@ describe('LegacyPluginExecutorService', () => { expect(await results).toMatchObject([{ finished: true }, { finished: true }, { finished: true }]) - expect(DESTINATION_PLUGINS_BY_ID['intercom'].setupPlugin).toHaveBeenCalledTimes(1) - expect(jest.mocked(DESTINATION_PLUGINS_BY_ID['intercom'].setupPlugin!).mock.calls[0][0]) - .toMatchInlineSnapshot(` + expect(intercomPlugin.setupPlugin).toHaveBeenCalledTimes(1) + expect(jest.mocked(intercomPlugin.setupPlugin!).mock.calls[0][0]).toMatchInlineSnapshot(` { "config": { "ignoredEmailDomains": "dev.posthog.com", @@ -127,7 +128,7 @@ describe('LegacyPluginExecutorService', () => { describe('onEvent', () => { it('should call the plugin onEvent method', async () => { - jest.spyOn(DESTINATION_PLUGINS_BY_ID['intercom'] as any, 'onEvent') + jest.spyOn(intercomPlugin, 'onEvent') const invocation = createInvocation(fn, globals) invocation.globals.event.event = 'mycustomevent' @@ -142,9 +143,8 @@ describe('LegacyPluginExecutorService', () => { const res = await service.execute(invocation) - expect(DESTINATION_PLUGINS_BY_ID['intercom'].onEvent).toHaveBeenCalledTimes(1) - expect(forSnapshot(jest.mocked(DESTINATION_PLUGINS_BY_ID['intercom'].onEvent!).mock.calls[0][0])) - .toMatchInlineSnapshot(` + expect(intercomPlugin.onEvent).toHaveBeenCalledTimes(1) + expect(forSnapshot(jest.mocked(intercomPlugin.onEvent!).mock.calls[0][0])).toMatchInlineSnapshot(` { "distinct_id": "distinct_id", "event": "mycustomevent", @@ -201,7 +201,7 @@ describe('LegacyPluginExecutorService', () => { [ { "level": "debug", - "message": "Executing plugin intercom", + "message": "Executing plugin posthog-intercom-plugin", "timestamp": "2025-01-01T01:00:00.000+01:00", }, { @@ -224,7 +224,7 @@ describe('LegacyPluginExecutorService', () => { }) it('should mock out fetch if it is a test function', async () => { - jest.spyOn(DESTINATION_PLUGINS_BY_ID['intercom'] as any, 'onEvent') + jest.spyOn(intercomPlugin, 'onEvent') const invocation = createInvocation(fn, globals) invocation.hogFunction.name = 'My function [CDP-TEST-HIDDEN]' @@ -237,11 +237,11 @@ describe('LegacyPluginExecutorService', () => { expect(mockFetch).toHaveBeenCalledTimes(0) - expect(DESTINATION_PLUGINS_BY_ID['intercom'].onEvent).toHaveBeenCalledTimes(1) + expect(intercomPlugin.onEvent).toHaveBeenCalledTimes(1) expect(forSnapshot(res.logs.map((l) => l.message))).toMatchInlineSnapshot(` [ - "Executing plugin intercom", + "Executing plugin posthog-intercom-plugin", "Fetch called but mocked due to test function", "Unable to search contact test@posthog.com in Intercom. Status Code: undefined. Error message: ", "Execution successful", @@ -250,7 +250,7 @@ describe('LegacyPluginExecutorService', () => { }) it('should handle and collect errors', async () => { - jest.spyOn(DESTINATION_PLUGINS_BY_ID['intercom'] as any, 'onEvent') + jest.spyOn(intercomPlugin, 'onEvent') const invocation = createInvocation(fn, globals) invocation.globals.event.event = 'mycustomevent' @@ -262,12 +262,12 @@ describe('LegacyPluginExecutorService', () => { const res = await service.execute(invocation) - expect(DESTINATION_PLUGINS_BY_ID['intercom'].onEvent).toHaveBeenCalledTimes(1) + expect(intercomPlugin.onEvent).toHaveBeenCalledTimes(1) expect(res.error).toBeInstanceOf(Error) expect(forSnapshot(res.logs.map((l) => l.message))).toMatchInlineSnapshot(` [ - "Executing plugin intercom", + "Executing plugin posthog-intercom-plugin", "Plugin execution failed: Service is down, retry later", ] `) From 3e1ece068ed06d1ed793f9085f3ad745c5cceda7 Mon Sep 17 00:00:00 2001 From: Ben White Date: Wed, 29 Jan 2025 13:48:44 +0100 Subject: [PATCH 074/163] Fixes --- .../cdp/hog-transformations/hog-transformer.service.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugin-server/src/cdp/hog-transformations/hog-transformer.service.test.ts b/plugin-server/src/cdp/hog-transformations/hog-transformer.service.test.ts index 776c4db97b7ef..3a1ace9a521d1 100644 --- a/plugin-server/src/cdp/hog-transformations/hog-transformer.service.test.ts +++ b/plugin-server/src/cdp/hog-transformations/hog-transformer.service.test.ts @@ -4,13 +4,13 @@ import { readFileSync } from 'fs' import { join } from 'path' import { brotliDecompressSync } from 'zlib' +import { template as filterOutPluginTemplate } from '~/src/cdp/legacy-plugins/_transformations/posthog-filter-out-plugin/template' import { template as geoipTemplate } from '~/src/cdp/templates/_transformations/geoip/geoip.template' import { compileHog } from '~/src/cdp/templates/compiler' import { createHogFunction } from '~/tests/cdp/fixtures' import { Hub } from '../../types' import { createHub } from '../../utils/db/hub' -import { pluginFilterOutPluginTemplate } from '../legacy-plugins/_transformations/posthog-filter-out-plugin/template' import { HogTransformerService } from './hog-transformer.service' let mockGetTeamHogFunctions: jest.Mock @@ -132,7 +132,7 @@ describe('HogTransformer', () => { describe('legacy plugins', () => { beforeEach(() => { const filterOutPlugin = createHogFunction({ - ...pluginFilterOutPluginTemplate, + ...filterOutPluginTemplate, type: 'transformation', template_id: 'plugin-posthog-filter-out-plugin', inputs: { From 87228f89650471d7b1eb0fb3ef1f1f71531dd8ba Mon Sep 17 00:00:00 2001 From: Ben White Date: Wed, 29 Jan 2025 13:55:11 +0100 Subject: [PATCH 075/163] Fixes --- .../legacy-plugin-executor.test.ts.snap | 167 +++++++++++++++++- .../services/legacy-plugin-executor.test.ts | 44 ++++- 2 files changed, 206 insertions(+), 5 deletions(-) diff --git a/plugin-server/src/cdp/services/__snapshots__/legacy-plugin-executor.test.ts.snap b/plugin-server/src/cdp/services/__snapshots__/legacy-plugin-executor.test.ts.snap index 72cd1119ea0bd..e94799e39b0ca 100644 --- a/plugin-server/src/cdp/services/__snapshots__/legacy-plugin-executor.test.ts.snap +++ b/plugin-server/src/cdp/services/__snapshots__/legacy-plugin-executor.test.ts.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`LegacyPluginExecutorService smoke tests should run the plugin: { name: 'customerio-plugin', plugin: [Object] } 1`] = ` +exports[`LegacyPluginExecutorService smoke tests should run the destination plugin: { name: 'customerio-plugin', plugin: [Object] } 1`] = ` [ { "level": "debug", @@ -35,7 +35,7 @@ exports[`LegacyPluginExecutorService smoke tests should run the plugin: { name: ] `; -exports[`LegacyPluginExecutorService smoke tests should run the plugin: { name: 'posthog-intercom-plugin', plugin: [Object] } 1`] = ` +exports[`LegacyPluginExecutorService smoke tests should run the destination plugin: { name: 'posthog-intercom-plugin', plugin: [Object] } 1`] = ` [ { "level": "debug", @@ -54,3 +54,166 @@ exports[`LegacyPluginExecutorService smoke tests should run the plugin: { name: }, ] `; + +exports[`LegacyPluginExecutorService smoke tests should run the transformation plugin: { + name: 'posthog-app-url-parameters-to-event-properties', + plugin: [Object] +} 1`] = ` +[ + { + "level": "debug", + "message": "Executing plugin posthog-app-url-parameters-to-event-properties", + "timestamp": "2025-01-01T01:00:00.000+01:00", + }, + { + "level": "debug", + "message": "Execution successful", + "timestamp": "2025-01-01T01:00:00.000+01:00", + }, +] +`; + +exports[`LegacyPluginExecutorService smoke tests should run the transformation plugin: { name: 'downsampling-plugin', plugin: [Object] } 1`] = ` +[ + { + "level": "debug", + "message": "Executing plugin downsampling-plugin", + "timestamp": "2025-01-01T01:00:00.000+01:00", + }, + { + "level": "debug", + "message": "Execution successful", + "timestamp": "2025-01-01T01:00:00.000+01:00", + }, +] +`; + +exports[`LegacyPluginExecutorService smoke tests should run the transformation plugin: { name: 'language-url-splitter-app', plugin: [Object] } 1`] = ` +[ + { + "level": "debug", + "message": "Executing plugin language-url-splitter-app", + "timestamp": "2025-01-01T01:00:00.000+01:00", + }, + { + "level": "debug", + "message": "Execution successful", + "timestamp": "2025-01-01T01:00:00.000+01:00", + }, +] +`; + +exports[`LegacyPluginExecutorService smoke tests should run the transformation plugin: { name: 'posthog-filter-out-plugin', plugin: [Object] } 1`] = ` +[ + { + "level": "debug", + "message": "Executing plugin posthog-filter-out-plugin", + "timestamp": "2025-01-01T01:00:00.000+01:00", + }, + { + "level": "debug", + "message": "Execution successful", + "timestamp": "2025-01-01T01:00:00.000+01:00", + }, +] +`; + +exports[`LegacyPluginExecutorService smoke tests should run the transformation plugin: { name: 'posthog-url-normalizer-plugin', plugin: [Object] } 1`] = ` +[ + { + "level": "debug", + "message": "Executing plugin posthog-url-normalizer-plugin", + "timestamp": "2025-01-01T01:00:00.000+01:00", + }, + { + "level": "debug", + "message": "event.$current_url: "https://posthog.com" normalized to "https://posthog.com/"", + "timestamp": "2025-01-01T01:00:00.000+01:00", + }, + { + "level": "debug", + "message": "Execution successful", + "timestamp": "2025-01-01T01:00:00.000+01:00", + }, +] +`; + +exports[`LegacyPluginExecutorService smoke tests should run the transformation plugin: { name: 'property-filter-plugin', plugin: [Object] } 1`] = ` +[ + { + "level": "debug", + "message": "Executing plugin property-filter-plugin", + "timestamp": "2025-01-01T01:00:00.000+01:00", + }, + { + "level": "debug", + "message": "Execution successful", + "timestamp": "2025-01-01T01:00:00.000+01:00", + }, +] +`; + +exports[`LegacyPluginExecutorService smoke tests should run the transformation plugin: { name: 'semver-flattener-plugin', plugin: [Object] } 1`] = ` +[ + { + "level": "debug", + "message": "Executing plugin semver-flattener-plugin", + "timestamp": "2025-01-01T01:00:00.000+01:00", + }, + { + "level": "info", + "message": "found candidate property: , test, matches , ", + "timestamp": "2025-01-01T01:00:00.000+01:00", + }, + { + "level": "debug", + "message": "Execution successful", + "timestamp": "2025-01-01T01:00:00.000+01:00", + }, +] +`; + +exports[`LegacyPluginExecutorService smoke tests should run the transformation plugin: { name: 'taxonomy-plugin', plugin: [Object] } 1`] = ` +[ + { + "level": "debug", + "message": "Executing plugin taxonomy-plugin", + "timestamp": "2025-01-01T01:00:00.000+01:00", + }, + { + "level": "debug", + "message": "Execution successful", + "timestamp": "2025-01-01T01:00:00.000+01:00", + }, +] +`; + +exports[`LegacyPluginExecutorService smoke tests should run the transformation plugin: { name: 'timestamp-parser-plugin', plugin: [Object] } 1`] = ` +[ + { + "level": "debug", + "message": "Executing plugin timestamp-parser-plugin", + "timestamp": "2025-01-01T01:00:00.000+01:00", + }, + { + "level": "debug", + "message": "Execution successful", + "timestamp": "2025-01-01T01:00:00.000+01:00", + }, +] +`; + +exports[`LegacyPluginExecutorService smoke tests should run the transformation plugin: { name: 'user-agent-plugin', plugin: [Object] } 1`] = ` +[ + { + "level": "debug", + "message": "Executing plugin user-agent-plugin", + "timestamp": "2025-01-01T01:00:00.000+01:00", + }, + { + "level": "debug", + "message": "Execution successful", + "timestamp": "2025-01-01T01:00:00.000+01:00", + }, +] +`; diff --git a/plugin-server/src/cdp/services/legacy-plugin-executor.test.ts b/plugin-server/src/cdp/services/legacy-plugin-executor.test.ts index e6c2ed675b76d..24a2f279fd0f4 100644 --- a/plugin-server/src/cdp/services/legacy-plugin-executor.test.ts +++ b/plugin-server/src/cdp/services/legacy-plugin-executor.test.ts @@ -11,7 +11,7 @@ import { getFirstTeam } from '~/tests/helpers/sql' import { Hub, Team } from '../../types' import { closeHub, createHub } from '../../utils/db/hub' -import { DESTINATION_PLUGINS_BY_ID } from '../legacy-plugins' +import { DESTINATION_PLUGINS_BY_ID, TRANSFORMATION_PLUGINS_BY_ID } from '../legacy-plugins' import { HogFunctionInvocationGlobalsWithInputs, HogFunctionType } from '../types' import { LegacyPluginExecutorService } from './legacy-plugin-executor.service' @@ -380,12 +380,12 @@ describe('LegacyPluginExecutorService', () => { }) describe('smoke tests', () => { - const testCases = Object.entries(DESTINATION_PLUGINS_BY_ID).map(([pluginId, plugin]) => ({ + const testCasesDestination = Object.entries(DESTINATION_PLUGINS_BY_ID).map(([pluginId, plugin]) => ({ name: pluginId, plugin, })) - it.each(testCases)('should run the plugin: %s', async ({ name, plugin }) => { + it.each(testCasesDestination)('should run the destination plugin: %s', async ({ name, plugin }) => { globals.event.event = '$identify' // Many plugins filter for this const invocation = createInvocation(fn, globals) @@ -415,5 +415,43 @@ describe('LegacyPluginExecutorService', () => { expect(res.logs).toMatchSnapshot() }) + + const testCasesTransformation = Object.entries(TRANSFORMATION_PLUGINS_BY_ID).map(([pluginId, plugin]) => ({ + name: pluginId, + plugin, + })) + + it.each(testCasesTransformation)('should run the transformation plugin: %s', async ({ name, plugin }) => { + globals.event.event = '$pageview' + const invocation = createInvocation(fn, globals) + + invocation.hogFunction.type = 'transformation' + invocation.hogFunction.template_id = `plugin-${plugin.id}` + + const inputs: Record = {} + + for (const input of plugin.metadata.config || []) { + if (!input.key) { + continue + } + + if (input.default) { + inputs[input.key] = input.default + continue + } + + if (input.type === 'choice') { + inputs[input.key] = input.choices[0] + } else if (input.type === 'string') { + inputs[input.key] = 'test' + } + } + + invocation.hogFunction.name = name + invocation.globals.inputs = inputs + const res = await service.execute(invocation) + + expect(res.logs).toMatchSnapshot() + }) }) }) From 52485d8a34b3545e90d9143ca5c48319a19df6c2 Mon Sep 17 00:00:00 2001 From: Ben White Date: Wed, 29 Jan 2025 13:58:41 +0100 Subject: [PATCH 076/163] Fixes --- .../legacy-plugin-executor.service.ts | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/plugin-server/src/cdp/services/legacy-plugin-executor.service.ts b/plugin-server/src/cdp/services/legacy-plugin-executor.service.ts index 6f00345980769..c05b6560d4ae0 100644 --- a/plugin-server/src/cdp/services/legacy-plugin-executor.service.ts +++ b/plugin-server/src/cdp/services/legacy-plugin-executor.service.ts @@ -110,19 +110,21 @@ export class LegacyPluginExecutorService { logger: logger, } - const setupPromise = - 'setupPlugin' in plugin - ? 'processEvent' in plugin - ? Promise.resolve(plugin.setupPlugin?.(meta)) - : Promise.resolve( - plugin.setupPlugin?.({ - ...meta, - fetch, - }) - ) - : Promise.resolve() - - // Check the type of the plugin and setup accordingly + let setupPromise = Promise.resolve() + + if (plugin.setupPlugin) { + if ('processEvent' in plugin) { + // Transformation plugin takes basic meta and isn't async + setupPromise = Promise.resolve(plugin.setupPlugin(meta)) + } else { + // Destination plugin can use fetch and is async + setupPromise = plugin.setupPlugin({ + ...meta, + fetch, + }) + } + } + state = this.pluginState[pluginId] = { setupPromise, meta, From 46ad019e3909d0f57c9b2b9ddf21779687735f23 Mon Sep 17 00:00:00 2001 From: Ben White Date: Wed, 29 Jan 2025 14:00:44 +0100 Subject: [PATCH 077/163] Fixes --- .../_transformations/posthog-url-normalizer-plugin/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-url-normalizer-plugin/index.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-url-normalizer-plugin/index.ts index c50eb94ee7c10..b9512e3aee046 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-url-normalizer-plugin/index.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-url-normalizer-plugin/index.ts @@ -11,7 +11,7 @@ function normalizeUrl(url: string): string { return parsedUrl.toString() } catch (err) { - throw `Unable to normalize invalid URL: "${url}"` + throw new Error(`Unable to normalize invalid URL: "${url}"`) } } From 1df1231eff8196e72e7c22927c808f503f3cf99e Mon Sep 17 00:00:00 2001 From: Ben White Date: Wed, 29 Jan 2025 14:01:13 +0100 Subject: [PATCH 078/163] Fix --- plugin-server/src/worker/ingestion/event-pipeline/runner.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin-server/src/worker/ingestion/event-pipeline/runner.ts b/plugin-server/src/worker/ingestion/event-pipeline/runner.ts index 506a04ff66e73..c3032cc53835b 100644 --- a/plugin-server/src/worker/ingestion/event-pipeline/runner.ts +++ b/plugin-server/src/worker/ingestion/event-pipeline/runner.ts @@ -248,7 +248,7 @@ export class EventPipelineRunner { kafkaAcks.push(...messagePromises) } - if (transformedEvent == null) { + if (transformedEvent === null) { return this.registerLastStep('transformEventStep', [processedEvent], kafkaAcks) } From 5713745f86741b460652801679a23b193de3eb38 Mon Sep 17 00:00:00 2001 From: Ben White Date: Wed, 29 Jan 2025 14:07:01 +0100 Subject: [PATCH 079/163] wip --- plugin-server/src/cdp/cdp-api.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/plugin-server/src/cdp/cdp-api.ts b/plugin-server/src/cdp/cdp-api.ts index 859f1f898d2e9..d9d558728fe91 100644 --- a/plugin-server/src/cdp/cdp-api.ts +++ b/plugin-server/src/cdp/cdp-api.ts @@ -42,10 +42,16 @@ export class CdpApi { router.post('/api/projects/:team_id/hog_functions/:id/invocations', asyncHandler(this.postFunctionInvocation)) router.get('/api/projects/:team_id/hog_functions/:id/status', asyncHandler(this.getFunctionStatus())) router.patch('/api/projects/:team_id/hog_functions/:id/status', asyncHandler(this.patchFunctionStatus())) + router.get('/api/hog_function_templates', asyncHandler(this.getHogFunctionTemplates)) return router } + private getHogFunctionTemplates = async (req: express.Request, res: express.Response): Promise => { + const templates = await this.hogFunctionManager.fetchHogFunctionTemplates() + res.json(templates) + } + private getFunctionStatus = () => async (req: express.Request, res: express.Response): Promise => { From 64874b000fb89d4b61deaf94b601aa30e265ed63 Mon Sep 17 00:00:00 2001 From: Ben White Date: Wed, 29 Jan 2025 14:07:55 +0100 Subject: [PATCH 080/163] Fixes --- .../_transformations/downsampling-plugin/template.ts} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename plugin-server/src/cdp/{templates/_plugins/downsample.template.ts => legacy-plugins/_transformations/downsampling-plugin/template.ts} (93%) diff --git a/plugin-server/src/cdp/templates/_plugins/downsample.template.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/downsampling-plugin/template.ts similarity index 93% rename from plugin-server/src/cdp/templates/_plugins/downsample.template.ts rename to plugin-server/src/cdp/legacy-plugins/_transformations/downsampling-plugin/template.ts index 40927eacc9982..f21dbd82073ed 100644 --- a/plugin-server/src/cdp/templates/_plugins/downsample.template.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/downsampling-plugin/template.ts @@ -1,4 +1,4 @@ -import { HogFunctionTemplate } from '../types' +import { HogFunctionTemplate } from '../../../templates/types' export const template: HogFunctionTemplate = { status: 'alpha', From a2d2f2f64255536efde52255ec02400410b8343c Mon Sep 17 00:00:00 2001 From: Ben White Date: Wed, 29 Jan 2025 14:15:13 +0100 Subject: [PATCH 081/163] Fixes --- plugin-server/src/cdp/cdp-api.ts | 8 +++--- plugin-server/src/cdp/templates/index.ts | 36 ++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/plugin-server/src/cdp/cdp-api.ts b/plugin-server/src/cdp/cdp-api.ts index d9d558728fe91..a2c4839f1e946 100644 --- a/plugin-server/src/cdp/cdp-api.ts +++ b/plugin-server/src/cdp/cdp-api.ts @@ -8,6 +8,7 @@ import { FetchExecutorService } from './services/fetch-executor.service' import { HogExecutorService, MAX_ASYNC_STEPS } from './services/hog-executor.service' import { HogFunctionManagerService } from './services/hog-function-manager.service' import { HogWatcherService, HogWatcherState } from './services/hog-watcher.service' +import { HOG_FUNCTION_TEMPLATES } from './templates' import { HogFunctionInvocationResult, HogFunctionQueueParametersFetchRequest, HogFunctionType, LogEntry } from './types' export class CdpApi { @@ -42,14 +43,13 @@ export class CdpApi { router.post('/api/projects/:team_id/hog_functions/:id/invocations', asyncHandler(this.postFunctionInvocation)) router.get('/api/projects/:team_id/hog_functions/:id/status', asyncHandler(this.getFunctionStatus())) router.patch('/api/projects/:team_id/hog_functions/:id/status', asyncHandler(this.patchFunctionStatus())) - router.get('/api/hog_function_templates', asyncHandler(this.getHogFunctionTemplates)) + router.get('/api/hog_function_templates', this.getHogFunctionTemplates) return router } - private getHogFunctionTemplates = async (req: express.Request, res: express.Response): Promise => { - const templates = await this.hogFunctionManager.fetchHogFunctionTemplates() - res.json(templates) + private getHogFunctionTemplates = (req: express.Request, res: express.Response): void => { + res.json(HOG_FUNCTION_TEMPLATES) } private getFunctionStatus = diff --git a/plugin-server/src/cdp/templates/index.ts b/plugin-server/src/cdp/templates/index.ts index e69de29bb2d1d..216388bdff308 100644 --- a/plugin-server/src/cdp/templates/index.ts +++ b/plugin-server/src/cdp/templates/index.ts @@ -0,0 +1,36 @@ +import { template as downsamplingPlugin } from '../legacy-plugins/_transformations/downsampling-plugin/template' +import { template as languageUrlSplitterTemplate } from '../legacy-plugins/_transformations/language-url-splitter-app/template' +import { template as posthogAppUrlParametersToEventPropertiesTemplate } from '../legacy-plugins/_transformations/posthog-app-url-parameters-to-event-properties/template' +import { template as posthogFilterOutTemplate } from '../legacy-plugins/_transformations/posthog-filter-out-plugin/template' +import { template as posthogUrlNormalizerTemplate } from '../legacy-plugins/_transformations/posthog-url-normalizer-plugin/template' +import { template as propertyFilterTemplate } from '../legacy-plugins/_transformations/property-filter-plugin/template' +import { template as semverFlattenerTemplate } from '../legacy-plugins/_transformations/semver-flattener-plugin/template' +import { template as taxonomyTemplate } from '../legacy-plugins/_transformations/taxonomy-plugin/template' +import { template as timestampParserTemplate } from '../legacy-plugins/_transformations/timestamp-parser-plugin/template' +import { template as userAgentTemplate } from '../legacy-plugins/_transformations/user-agent-plugin/template' +import { template as webhookTemplate } from './_destinations/webhook/webhook.template' +import { template as defaultTransformatonTemplate } from './_transformations/default/default.template' +import { template as geoipTemplate } from './_transformations/geoip/geoip.template' +import { HogFunctionTemplate } from './types' + +export const HOG_FUNCTION_TEMPLATES_DESTINATIONS: HogFunctionTemplate[] = [webhookTemplate] + +export const HOG_FUNCTION_TEMPLATES_TRANSFORMATIONS: HogFunctionTemplate[] = [ + defaultTransformatonTemplate, + geoipTemplate, + downsamplingPlugin, + languageUrlSplitterTemplate, + posthogAppUrlParametersToEventPropertiesTemplate, + posthogFilterOutTemplate, + posthogUrlNormalizerTemplate, + propertyFilterTemplate, + semverFlattenerTemplate, + taxonomyTemplate, + timestampParserTemplate, + userAgentTemplate, +] + +export const HOG_FUNCTION_TEMPLATES: HogFunctionTemplate[] = [ + ...HOG_FUNCTION_TEMPLATES_DESTINATIONS, + ...HOG_FUNCTION_TEMPLATES_TRANSFORMATIONS, +] From 36210ad40373c662523da6f80f67455601312c31 Mon Sep 17 00:00:00 2001 From: Ben White Date: Wed, 29 Jan 2025 15:10:45 +0100 Subject: [PATCH 082/163] Fixes --- posthog/api/hog_function.py | 5 +- posthog/api/hog_function_template.py | 65 ++- .../test/__data__/hog_function_templates.json | 547 ++++++++++++++++++ .../api/test/test_hog_function_templates.py | 11 +- posthog/cdp/templates/__init__.py | 9 - .../cdp/templates/hog_function_template.py | 3 +- posthog/models/hog_functions/hog_function.py | 14 +- posthog/plugins/plugin_server_api.py | 4 + 8 files changed, 636 insertions(+), 22 deletions(-) create mode 100644 posthog/api/test/__data__/hog_function_templates.json diff --git a/posthog/api/hog_function.py b/posthog/api/hog_function.py index 30648a2164b04..c7e3ca2ca54a2 100644 --- a/posthog/api/hog_function.py +++ b/posthog/api/hog_function.py @@ -13,14 +13,13 @@ from posthog.api.app_metrics2 import AppMetricsMixin from posthog.api.forbid_destroy_model import ForbidDestroyModel -from posthog.api.hog_function_template import HogFunctionTemplateSerializer +from posthog.api.hog_function_template import HogFunctionTemplateSerializer, HogFunctionTemplates from posthog.api.log_entries import LogEntryMixin from posthog.api.routing import TeamAndOrgViewSetMixin from posthog.api.shared import UserBasicSerializer from posthog.cdp.filters import compile_filters_bytecode, compile_filters_expr from posthog.cdp.services.icons import CDPIconsService -from posthog.cdp.templates import HOG_FUNCTION_TEMPLATES_BY_ID from posthog.cdp.templates._internal.template_legacy_plugin import create_legacy_plugin_template from posthog.cdp.validation import compile_hog, generate_template_bytecode, validate_inputs, validate_inputs_schema from posthog.cdp.site_functions import get_transpiled_function @@ -156,7 +155,7 @@ def validate(self, attrs): is_create = self.context.get("view") and self.context["view"].action == "create" template_id = attrs.get("template_id", instance.template_id if instance else None) - template = HOG_FUNCTION_TEMPLATES_BY_ID.get(template_id, None) + template = HogFunctionTemplates.template(template_id) if template_id else None if template_id and template_id.startswith("plugin-"): template = create_legacy_plugin_template(template_id) diff --git a/posthog/api/hog_function_template.py b/posthog/api/hog_function_template.py index a8a24145a02cf..0cde588af6ba9 100644 --- a/posthog/api/hog_function_template.py +++ b/posthog/api/hog_function_template.py @@ -1,3 +1,4 @@ +from posthoganalytics import capture_exception import structlog from django_filters.rest_framework import DjangoFilterBackend from rest_framework import viewsets, permissions @@ -5,13 +6,15 @@ from rest_framework.response import Response from rest_framework.exceptions import NotFound -from posthog.cdp.templates import HOG_FUNCTION_SUB_TEMPLATES, HOG_FUNCTION_TEMPLATES, ALL_HOG_FUNCTION_TEMPLATES_BY_ID +from posthog.cdp.templates import HOG_FUNCTION_TEMPLATES from posthog.cdp.templates.hog_function_template import ( HogFunctionMapping, HogFunctionMappingTemplate, HogFunctionTemplate, HogFunctionSubTemplate, + derive_sub_templates, ) +from posthog.plugins.plugin_server_api import get_hog_function_templates from rest_framework_dataclasses.serializers import DataclassSerializer @@ -42,6 +45,62 @@ class Meta: dataclass = HogFunctionTemplate +class HogFunctionTemplates: + _cached_templates: list[HogFunctionTemplate] = [] + _cached_templates_by_id: dict[str, HogFunctionTemplate] = {} + _cached_sub_templates: list[HogFunctionTemplate] = [] + _cached_sub_templates_by_id: dict[str, HogFunctionTemplate] = {} + + @classmethod + def templates(cls): + cls._load_templates() + return cls._cached_templates + + @classmethod + def sub_templates(cls): + cls._load_templates() + return cls._cached_sub_templates + + @classmethod + def template(cls, template_id: str): + cls._load_templates() + return cls._cached_templates_by_id.get(template_id, cls._cached_sub_templates_by_id.get(template_id)) + + @classmethod + def _load_templates(cls): + # TODO: Cache this + # First we load and convert all nodejs templates to python templates + response = get_hog_function_templates() + if response.status_code != 200: + raise Exception("Failed to fetch hog function templates") + + nodejs_templates_json = response.json() + nodejs_templates: list[HogFunctionTemplate] = [] + for template_data in nodejs_templates_json: + try: + serializer = HogFunctionTemplateSerializer(data=template_data) + serializer.is_valid(raise_exception=True) + template = serializer.save() + nodejs_templates.append(template) + except Exception as e: + logger.error( + "Failed to convert template", template_id=template_data.get("id"), error=str(e), exc_info=True + ) + capture_exception(e) + raise + + templates = [ + *HOG_FUNCTION_TEMPLATES, + *nodejs_templates, + ] + sub_templates = derive_sub_templates(templates=templates) + + cls._cached_templates = templates + cls._cached_sub_templates = sub_templates + cls._cached_templates_by_id = {template.id: template for template in templates} + cls._cached_sub_templates_by_id = {template.id: template for template in sub_templates} + + # NOTE: There is nothing currently private about these values class PublicHogFunctionTemplateViewSet(viewsets.GenericViewSet): filter_backends = [DjangoFilterBackend] @@ -59,7 +118,7 @@ def list(self, request: Request, *args, **kwargs): elif "types" in request.GET: types = self.request.GET.get("types", "destination").split(",") - templates_list = HOG_FUNCTION_SUB_TEMPLATES if sub_template_id else HOG_FUNCTION_TEMPLATES + templates_list = HogFunctionTemplates.sub_templates() if sub_template_id else HogFunctionTemplates.templates() matching_templates = [] @@ -81,7 +140,7 @@ def list(self, request: Request, *args, **kwargs): return self.get_paginated_response(serializer.data) def retrieve(self, request: Request, *args, **kwargs): - item = ALL_HOG_FUNCTION_TEMPLATES_BY_ID.get(kwargs["pk"], None) + item = HogFunctionTemplates.template(kwargs["pk"]) if not item: raise NotFound(f"Template with id {kwargs['pk']} not found.") diff --git a/posthog/api/test/__data__/hog_function_templates.json b/posthog/api/test/__data__/hog_function_templates.json new file mode 100644 index 0000000000000..abdc857b83f7c --- /dev/null +++ b/posthog/api/test/__data__/hog_function_templates.json @@ -0,0 +1,547 @@ +[ + { + "status": "beta", + "type": "destination", + "id": "template-webhook", + "name": "HTTP Webhook", + "description": "Sends a webhook templated by the incoming event data", + "icon_url": "/static/posthog-icon.svg", + "category": ["Custom"], + "hog": "\nlet payload := {\n 'headers': inputs.headers,\n 'body': inputs.body,\n 'method': inputs.method\n}\n\nif (inputs.debug) {\n print('Request', inputs.url, payload)\n}\n\nlet res := fetch(inputs.url, payload);\n\nif (inputs.debug) {\n print('Response', res.status, res.body);\n}\n", + "inputs_schema": [ + { + "key": "url", + "type": "string", + "label": "Webhook URL", + "secret": false, + "required": true + }, + { + "key": "method", + "type": "choice", + "label": "Method", + "secret": false, + "choices": [ + { + "label": "POST", + "value": "POST" + }, + { + "label": "PUT", + "value": "PUT" + }, + { + "label": "PATCH", + "value": "PATCH" + }, + { + "label": "GET", + "value": "GET" + }, + { + "label": "DELETE", + "value": "DELETE" + } + ], + "default": "POST", + "required": false + }, + { + "key": "body", + "type": "json", + "label": "JSON Body", + "default": { + "event": "{event}", + "person": "{person}" + }, + "secret": false, + "required": false + }, + { + "key": "headers", + "type": "dictionary", + "label": "Headers", + "secret": false, + "required": false, + "default": { + "Content-Type": "application/json" + } + }, + { + "key": "debug", + "type": "boolean", + "label": "Log responses", + "description": "Logs the response of http calls for debugging.", + "secret": false, + "required": false, + "default": false + } + ], + "sub_templates": [ + { + "id": "early-access-feature-enrollment", + "name": "HTTP Webhook on feature enrollment", + "filters": { + "events": [ + { + "id": "$feature_enrollment_update", + "type": "events" + } + ] + } + }, + { + "id": "survey-response", + "name": "HTTP Webhook on survey response", + "filters": { + "events": [ + { + "id": "survey sent", + "type": "events", + "properties": [ + { + "key": "$survey_response", + "type": "event", + "value": "is_set", + "operator": "is_set" + } + ] + } + ] + } + }, + { + "id": "activity-log", + "name": "HTTP Webhook on team activity", + "filters": { + "events": [ + { + "id": "$activity_log_entry_created", + "type": "events" + } + ] + }, + "type": "internal_destination" + } + ] + }, + { + "status": "alpha", + "type": "transformation", + "id": "template-blank-transformation", + "name": "Custom transformation", + "description": "This is a starter template for custom transformations", + "icon_url": "/static/hedgehog/builder-hog-01.png", + "category": ["Custom"], + "hog": "\n// This is a blank template for custom transformations\n// The function receives 'event' as a global object and expects it to be returned\n// If you return null then the event will be discarded\nreturn event\n ", + "inputs_schema": [] + }, + { + "status": "beta", + "type": "transformation", + "id": "template-geoip", + "name": "GeoIP", + "description": "Adds geoip data to the event", + "icon_url": "/static/hedgehog/builder-hog-01.png", + "category": ["Custom"], + "hog": "\n// Define the properties to be added to the event\nlet geoipProperties := {\n 'city_name': null,\n 'city_confidence': null,\n 'subdivision_2_name': null,\n 'subdivision_2_code': null,\n 'subdivision_1_name': null,\n 'subdivision_1_code': null,\n 'country_name': null,\n 'country_code': null,\n 'continent_name': null,\n 'continent_code': null,\n 'postal_code': null,\n 'latitude': null,\n 'longitude': null,\n 'accuracy_radius': null,\n 'time_zone': null\n}\n// Check if the event has an IP address l\nif (event.properties?.$geoip_disable or empty(event.properties?.$ip)) {\n print('geoip disabled or no ip', event.properties, event.properties?.$ip)\n return event\n}\nlet ip := event.properties.$ip\nif (ip == '127.0.0.1') {\n print('spoofing ip for local development', ip)\n ip := '89.160.20.129'\n}\nlet response := geoipLookup(ip)\nif (not response) {\n print('geoip lookup failed for ip', ip)\n return event\n}\nlet location := {}\nif (response.city) {\n location['city_name'] := response.city.names?.en\n}\nif (response.country) {\n location['country_name'] := response.country.names?.en\n location['country_code'] := response.country.isoCode\n}\nif (response.continent) {\n location['continent_name'] := response.continent.names?.en\n location['continent_code'] := response.continent.code\n}\nif (response.postal) {\n location['postal_code'] := response.postal.code\n}\nif (response.location) {\n location['latitude'] := response.location?.latitude\n location['longitude'] := response.location?.longitude\n location['accuracy_radius'] := response.location?.accuracyRadius\n location['time_zone'] := response.location?.timeZone\n}\nif (response.subdivisions) {\n for (let index, subdivision in response.subdivisions) {\n location[f'subdivision_{index + 1}_code'] := subdivision.isoCode\n location[f'subdivision_{index + 1}_name'] := subdivision.names?.en\n }\n}\nprint('geoip location data for ip:', location) \nlet returnEvent := event\nreturnEvent.properties := returnEvent.properties ?? {}\nreturnEvent.properties.$set := returnEvent.properties.$set ?? {}\nreturnEvent.properties.$set_once := returnEvent.properties.$set_once ?? {}\nfor (let key, value in geoipProperties) {\n if (value != null) {\n returnEvent.properties.$set[f'$geoip_{key}'] := value\n returnEvent.properties.$set_once[f'$initial_geoip_{key}'] := value\n }\n returnEvent.properties.$set[f'$geoip_{key}'] := value\n returnEvent.properties.$set_once[f'$initial_geoip_{key}'] := value\n}\nfor (let key, value in location) {\n returnEvent.properties[f'$geoip_{key}'] := value\n returnEvent.properties.$set[f'$geoip_{key}'] := value\n returnEvent.properties.$set_once[f'$initial_geoip_{key}'] := value\n}\nreturn returnEvent\n ", + "inputs_schema": [] + }, + { + "status": "alpha", + "type": "transformation", + "id": "plugin-downsampling-plugin", + "name": "Downsample", + "description": "Reduces event volume coming into PostHog", + "icon_url": "https://raw.githubusercontent.com/posthog/downsampling-plugin/main/logo.png", + "category": ["Custom"], + "hog": "return event", + "inputs_schema": [ + { + "type": "string", + "key": "percentage", + "label": "% of events to keep", + "default": "100", + "required": false + }, + { + "type": "choice", + "key": "samplingMethod", + "label": "Sampling method", + "choices": [ + { + "value": "Random sampling", + "label": "Random sampling" + }, + { + "value": "Distinct ID aware sampling", + "label": "Distinct ID aware sampling" + } + ], + "default": "Distinct ID aware sampling", + "required": false + } + ] + }, + { + "status": "alpha", + "type": "transformation", + "id": "plugin-language-url-splitter-app", + "name": "Language URL splitter", + "description": "Splits the language from the URL", + "icon_url": "https://raw.githubusercontent.com/posthog/language-url-splitter-app/main/logo.png", + "category": ["Transformation"], + "hog": "return event", + "inputs_schema": [ + { + "key": "pattern", + "label": "Pattern", + "type": "string", + "default": "^/([a-z]{2})(?=/|#|\\?|$)", + "description": "Ininitalized with `const regexp = new RegExp($pattern)`", + "required": true + }, + { + "key": "matchGroup", + "label": "Match group", + "type": "string", + "default": "1", + "description": "Used in: `const value = regexp.match($pathname)[$matchGroup]`", + "required": true + }, + { + "key": "property", + "label": "Property", + "type": "string", + "default": "locale", + "description": "Name of the event property we will store the matched value in", + "required": true + }, + { + "key": "replacePattern", + "label": "Replacement pattern", + "type": "string", + "default": "^(/[a-z]{2})(/|(?=/|#|\\?|$))", + "description": "Initialized with `new RegExp($pattern)`, leave empty to disable path cleanup.", + "required": true + }, + { + "key": "replaceKey", + "label": "Replacement key", + "type": "string", + "default": "$pathname", + "description": "Where to store the updated path. Keep as `$pathname` to override.", + "required": true + }, + { + "key": "replaceValue", + "label": "Replacement value", + "type": "string", + "default": "/", + "description": "`properties[key] = $pathname.replace(pattern, value)`", + "required": true + } + ] + }, + { + "status": "alpha", + "type": "transformation", + "id": "plugin-posthog-app-url-parameters-to-event-properties", + "name": "URL parameters to event properties", + "description": "Converts URL query parameters to event properties", + "icon_url": "https://raw.githubusercontent.com/posthog/posthog-app-url-parameters-to-event-properties/main/logo.png", + "category": ["Transformation"], + "hog": "return event", + "inputs_schema": [ + { + "key": "parameters", + "label": "URL query parameters to convert", + "type": "string", + "default": "", + "description": "Comma separated list of URL query parameters to capture. Leaving this blank will capture nothing." + }, + { + "key": "prefix", + "label": "Prefix", + "type": "string", + "default": "", + "description": "Add a prefix to the property name e.g. set it to 'prefix_' to get followerId -\u003E prefix_followerId" + }, + { + "key": "suffix", + "label": "Suffix", + "type": "string", + "default": "", + "description": "Add a suffix to the property name e.g. set it to '_suffix' to get followerId -\u003E followerId_suffix" + }, + { + "key": "ignoreCase", + "label": "Ignore the case of URL parameters", + "type": "choice", + "choices": [ + { + "value": "true", + "label": "true" + }, + { + "value": "false", + "label": "false" + } + ], + "default": "false", + "description": "Ignores the case of parameters e.g. when set to true than followerId would match FollowerId, followerID, FoLlOwErId and similar" + }, + { + "key": "setAsUserProperties", + "label": "Add to user properties", + "type": "choice", + "choices": [ + { + "value": "true", + "label": "true" + }, + { + "value": "false", + "label": "false" + } + ], + "default": "false", + "description": "Additionally adds the property to the user properties" + }, + { + "key": "setAsInitialUserProperties", + "label": "Add to user initial properties", + "type": "choice", + "choices": [ + { + "value": "true", + "label": "true" + }, + { + "value": "false", + "label": "false" + } + ], + "default": "false", + "description": "Additionally adds the property to the user initial properties. This will add a prefix of 'initial_' before the already fully composed property e.g. initial_prefix_followerId_suffix" + }, + { + "key": "alwaysJson", + "label": "Always JSON stringify the property data", + "type": "choice", + "choices": [ + { + "value": "true", + "label": "true" + }, + { + "value": "false", + "label": "false" + } + ], + "default": "false", + "description": "If set, always store the resulting data as a JSON array. (Otherwise, single parameters get stored as-is, and multi-value parameters get stored as a JSON array.)" + } + ] + }, + { + "status": "alpha", + "type": "transformation", + "id": "plugin-posthog-filter-out-plugin", + "name": "Filter Out Plugin", + "description": "Filter out events where property values satisfy the given condition", + "icon_url": "https://raw.githubusercontent.com/posthog/posthog-filter-out-plugin/main/logo.png", + "category": ["Transformation"], + "hog": "return event", + "inputs_schema": [ + { + "key": "filters", + "label": "Filters to apply", + "type": "string", + "description": "A JSON file containing an array of filters to apply. See the README for more information.", + "required": false + }, + { + "key": "eventsToDrop", + "label": "Events to filter out", + "type": "string", + "description": "A comma-separated list of event names to filter out (e.g. $pageview,$autocapture)", + "required": false + }, + { + "key": "keepUndefinedProperties", + "label": "Keep event if any of the filtered properties are undefined?", + "type": "choice", + "choices": [ + { + "value": "Yes", + "label": "Yes" + }, + { + "value": "No", + "label": "No" + } + ], + "default": "No" + } + ] + }, + { + "status": "alpha", + "type": "transformation", + "id": "plugin-posthog-url-normalizer-plugin", + "name": "URL Normalizer", + "description": "Normalize the format of urls in your application allowing you to more easily compare them in insights.", + "icon_url": "https://raw.githubusercontent.com/posthog/posthog-url-normalizer-plugin/main/logo.png", + "category": ["Transformation"], + "hog": "return event", + "inputs_schema": [] + }, + { + "status": "alpha", + "type": "transformation", + "id": "plugin-property-filter-plugin", + "name": "Property Filter", + "description": "This plugin will set all configured properties to null inside an ingested event.", + "icon_url": "https://raw.githubusercontent.com/posthog/posthog-property-filter-plugin/main/logo.png", + "category": ["Transformation"], + "hog": "return event", + "inputs_schema": [ + { + "key": "properties", + "label": "Properties to filter out", + "type": "string", + "description": "A comma-separated list of properties to filter out (e.g. $ip, $current_url)", + "default": "", + "required": true + } + ] + }, + { + "status": "alpha", + "type": "transformation", + "id": "plugin-semver-flattener-plugin", + "name": "SemVer Flattener", + "description": "This plugin will flatten semver versions in the specified properties.", + "icon_url": "https://raw.githubusercontent.com/posthog/posthog-semver-flattener-plugin/main/logo.png", + "category": ["Transformation"], + "hog": "return event", + "inputs_schema": [ + { + "key": "properties", + "label": "comma separated properties to explode version number from", + "type": "string", + "description": "my_version_number,app_version", + "default": "", + "required": true + } + ] + }, + { + "status": "alpha", + "type": "transformation", + "id": "plugin-taxonomy-plugin", + "name": "Taxonomy", + "description": "Standardize your event names into a single pattern.", + "icon_url": "https://raw.githubusercontent.com/posthog/taxonomy-plugin/main/logo.png", + "category": ["Transformation"], + "hog": "return event", + "inputs_schema": [ + { + "key": "defaultNamingConvention", + "label": "Select your default naming pattern", + "type": "choice", + "choices": [ + { + "value": "camelCase", + "label": "camelCase" + }, + { + "value": "PascalCase", + "label": "PascalCase" + }, + { + "value": "snake_case", + "label": "snake_case" + }, + { + "value": "kebab-case", + "label": "kebab-case" + }, + { + "value": "spaces in between", + "label": "spaces in between" + } + ], + "default": "camelCase", + "required": true + } + ] + }, + { + "status": "alpha", + "type": "transformation", + "id": "plugin-timestamp-parser-plugin", + "name": "Timestamp Parser", + "description": "Parse your event timestamps into useful date properties.", + "icon_url": "https://raw.githubusercontent.com/posthog/timestamp-parser-plugin/main/logo.png", + "category": ["Transformation"], + "hog": "return event", + "inputs_schema": [] + }, + { + "status": "alpha", + "type": "transformation", + "id": "plugin-user-agent-plugin", + "name": "User Agent Populator", + "description": "Enhances events with user agent details. User Agent plugin allows you to populate events with the $browser, $browser_version for PostHog Clients that don't typically populate these properties", + "icon_url": "https://raw.githubusercontent.com/posthog/useragent-plugin/main/logo.png", + "category": ["Transformation"], + "hog": "return event", + "inputs_schema": [ + { + "key": "overrideUserAgentDetails", + "label": "Can override existing browser related properties of event?", + "type": "string", + "description": "If the ingested event already have $browser $browser_version properties in combination with $useragent the $browser, $browser_version properties will be re-populated with the value of $useragent", + "default": "false", + "required": false + }, + { + "key": "enableSegmentAnalyticsJs", + "label": "Automatically read segment_userAgent property, automatically sent by Segment via analytics.js?", + "type": "choice", + "description": "Segment's analytics.js library automatically sends a useragent property that Posthog sees as segment_userAgent. Enabling this causes this plugin to parse that property", + "choices": [ + { + "value": "false", + "label": "false" + }, + { + "value": "true", + "label": "true" + } + ], + "default": "false", + "required": false + }, + { + "key": "debugMode", + "type": "choice", + "description": "Enable debug mode to log when the plugin is unable to extract values from the user agent", + "choices": [ + { + "value": "false", + "label": "false" + }, + { + "value": "true", + "label": "true" + } + ], + "default": "false", + "required": false + } + ] + } +] diff --git a/posthog/api/test/test_hog_function_templates.py b/posthog/api/test/test_hog_function_templates.py index c24e8fb9ba37b..a6b4c6d5a54c7 100644 --- a/posthog/api/test/test_hog_function_templates.py +++ b/posthog/api/test/test_hog_function_templates.py @@ -1,4 +1,5 @@ -from unittest.mock import ANY +import json +from unittest.mock import ANY, patch from inline_snapshot import snapshot from rest_framework import status @@ -6,6 +7,8 @@ from posthog.test.base import APIBaseTest, ClickhouseTestMixin, QueryMatchingTest from posthog.cdp.templates.slack.template_slack import template +MOCK_NODE_TEMPLATES = json.loads(open("posthog/api/test/__data__/hog_function_templates.json").read()) + # NOTE: We check this as a sanity check given that this is a public API so we want to explicitly define what is exposed EXPECTED_FIRST_RESULT = { "sub_templates": ANY, @@ -42,6 +45,12 @@ def test_derive_sub_templates(self): class TestHogFunctionTemplates(ClickhouseTestMixin, APIBaseTest, QueryMatchingTest): + @patch("posthog.api.hog_function_template.get_hog_function_templates") + def setUp(self, mock_get_templates): + super().setUp() + mock_get_templates.return_value.status_code = 200 + mock_get_templates.return_value.json.return_value = MOCK_NODE_TEMPLATES + def test_list_function_templates(self): response = self.client.get("/api/projects/@current/hog_function_templates/") diff --git a/posthog/cdp/templates/__init__.py b/posthog/cdp/templates/__init__.py index e1b732eab312b..bdf94f45440d1 100644 --- a/posthog/cdp/templates/__init__.py +++ b/posthog/cdp/templates/__init__.py @@ -113,13 +113,6 @@ ] -# This is a list of sub templates that are generated by merging the subtemplate with it's template -HOG_FUNCTION_SUB_TEMPLATES = derive_sub_templates(HOG_FUNCTION_TEMPLATES) - -HOG_FUNCTION_TEMPLATES_BY_ID = {template.id: template for template in HOG_FUNCTION_TEMPLATES} -HOG_FUNCTION_SUB_TEMPLATES_BY_ID = {template.id: template for template in HOG_FUNCTION_SUB_TEMPLATES} -ALL_HOG_FUNCTION_TEMPLATES_BY_ID = {**HOG_FUNCTION_TEMPLATES_BY_ID, **HOG_FUNCTION_SUB_TEMPLATES_BY_ID} - HOG_FUNCTION_MIGRATORS = { TemplateCustomerioMigrator.plugin_url: TemplateCustomerioMigrator, TemplateIntercomMigrator.plugin_url: TemplateIntercomMigrator, @@ -133,5 +126,3 @@ TemplateLoopsMigrator.plugin_url: TemplateLoopsMigrator, TemplateAvoMigrator.plugin_url: TemplateAvoMigrator, } - -__all__ = ["HOG_FUNCTION_TEMPLATES", "HOG_FUNCTION_TEMPLATES_BY_ID", "ALL_HOG_FUNCTION_TEMPLATES_BY_ID"] diff --git a/posthog/cdp/templates/hog_function_template.py b/posthog/cdp/templates/hog_function_template.py index f76deacc3d4e4..7d69a4474aa3e 100644 --- a/posthog/cdp/templates/hog_function_template.py +++ b/posthog/cdp/templates/hog_function_template.py @@ -1,5 +1,5 @@ import dataclasses -from typing import Literal, Optional, get_args, TYPE_CHECKING +from typing import Literal, Optional, TYPE_CHECKING if TYPE_CHECKING: @@ -10,7 +10,6 @@ SubTemplateId = Literal["early-access-feature-enrollment", "survey-response", "activity-log"] -SUB_TEMPLATE_ID: tuple[SubTemplateId, ...] = get_args(SubTemplateId) HogFunctionTemplateType = Literal[ "destination", diff --git a/posthog/models/hog_functions/hog_function.py b/posthog/models/hog_functions/hog_function.py index 244f887d3724c..e1bbb931558e8 100644 --- a/posthog/models/hog_functions/hog_function.py +++ b/posthog/models/hog_functions/hog_function.py @@ -95,12 +95,18 @@ class Meta: @property def template(self) -> Optional[HogFunctionTemplate]: - from posthog.cdp.templates import ALL_HOG_FUNCTION_TEMPLATES_BY_ID + from posthog.api.hog_function_template import HogFunctionTemplates - if self.template_id and self.template_id.startswith("plugin-"): - return create_legacy_plugin_template(self.template_id) + if not self.template_id: + return None + + template = HogFunctionTemplates.template(self.template_id) - return ALL_HOG_FUNCTION_TEMPLATES_BY_ID.get(self.template_id, None) + if template: + return template + + if self.template_id.startswith("plugin-"): + return create_legacy_plugin_template(self.template_id) @property def filter_action_ids(self) -> list[int]: diff --git a/posthog/plugins/plugin_server_api.py b/posthog/plugins/plugin_server_api.py index ef6b312ba874c..40c322bcf70a3 100644 --- a/posthog/plugins/plugin_server_api.py +++ b/posthog/plugins/plugin_server_api.py @@ -90,3 +90,7 @@ def patch_hog_function_status(team_id: int, hog_function_id: UUIDT, state: int) CDP_FUNCTION_EXECUTOR_API_URL + f"/api/projects/{team_id}/hog_functions/{hog_function_id}/status", json={"state": state}, ) + + +def get_hog_function_templates() -> requests.Response: + return requests.get(CDP_FUNCTION_EXECUTOR_API_URL + f"/api/hog_function_templates") From b201718b680f8b2eacf0cb3b6fb66616eee69891 Mon Sep 17 00:00:00 2001 From: Ben White Date: Wed, 29 Jan 2025 15:14:47 +0100 Subject: [PATCH 083/163] fix --- .../pipeline/hogfunctions/HogFunctionConfiguration.tsx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/frontend/src/scenes/pipeline/hogfunctions/HogFunctionConfiguration.tsx b/frontend/src/scenes/pipeline/hogfunctions/HogFunctionConfiguration.tsx index 86dce22800f2b..c1eec7364379f 100644 --- a/frontend/src/scenes/pipeline/hogfunctions/HogFunctionConfiguration.tsx +++ b/frontend/src/scenes/pipeline/hogfunctions/HogFunctionConfiguration.tsx @@ -100,7 +100,7 @@ export function HogFunctionConfiguration({ return } - const isLegacyPlugin = hogFunction?.template?.id?.startsWith('plugin-') + const isLegacyPlugin = (template?.id || hogFunction?.template?.id)?.startsWith('plugin-') const headerButtons = ( <> @@ -167,7 +167,7 @@ export function HogFunctionConfiguration({ const showFilters = displayOptions.showFilters ?? - ['destination', 'internal_destination', 'site_destination', 'broadcast', 'transformation'].includes(type) + ['destination', 'internal_destination', 'site_destination', 'broadcast'].includes(type) const showExpectedVolume = displayOptions.showExpectedVolume ?? ['destination', 'site_destination'].includes(type) const showStatus = displayOptions.showStatus ?? ['destination', 'internal_destination', 'email', 'transformation'].includes(type) @@ -266,8 +266,7 @@ export function HogFunctionConfiguration({ {isLegacyPlugin ? ( - This destination is one of our legacy plugins. It will be deprecated and you - should instead upgrade + This is part of our legacy plugins and will eventually be deprecated. ) : hogFunction?.template && !hogFunction.template.id.startsWith('template-blank-') ? ( Date: Wed, 29 Jan 2025 15:19:11 +0100 Subject: [PATCH 084/163] Fix templates --- .../_transformations/language-url-splitter-app/template.ts | 2 +- .../_transformations/property-filter-plugin/template.ts | 2 +- .../_transformations/semver-flattener-plugin/template.ts | 2 +- .../_transformations/user-agent-plugin/template.ts | 2 +- posthog/api/test/__data__/hog_function_templates.json | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/language-url-splitter-app/template.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/language-url-splitter-app/template.ts index ab786cf78c13d..26c423e6ff281 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/language-url-splitter-app/template.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/language-url-splitter-app/template.ts @@ -6,7 +6,7 @@ export const template: HogFunctionTemplate = { id: 'plugin-language-url-splitter-app', name: 'Language URL splitter', description: 'Splits the language from the URL', - icon_url: 'https://raw.githubusercontent.com/posthog/language-url-splitter-app/main/logo.png', + icon_url: '/static/hedgehog/builder-hog-01.png', category: ['Transformation'], hog: `return event`, inputs_schema: [ diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/property-filter-plugin/template.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/property-filter-plugin/template.ts index 4f5d7d64754fa..4ed18296873a2 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/property-filter-plugin/template.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/property-filter-plugin/template.ts @@ -6,7 +6,7 @@ export const template: HogFunctionTemplate = { id: 'plugin-property-filter-plugin', name: 'Property Filter', description: 'This plugin will set all configured properties to null inside an ingested event.', - icon_url: 'https://raw.githubusercontent.com/posthog/posthog-property-filter-plugin/main/logo.png', + icon_url: 'https://raw.githubusercontent.com/posthog/property-filter-plugin/dev/logo.png', category: ['Transformation'], hog: `return event`, inputs_schema: [ diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/semver-flattener-plugin/template.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/semver-flattener-plugin/template.ts index 1b6acc7cac708..9ceb82f16b064 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/semver-flattener-plugin/template.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/semver-flattener-plugin/template.ts @@ -6,7 +6,7 @@ export const template: HogFunctionTemplate = { id: 'plugin-semver-flattener-plugin', name: 'SemVer Flattener', description: 'This plugin will flatten semver versions in the specified properties.', - icon_url: 'https://raw.githubusercontent.com/posthog/posthog-semver-flattener-plugin/main/logo.png', + icon_url: 'https://raw.githubusercontent.com/posthog/semver-flattener-plugin/main/logo.png', category: ['Transformation'], hog: `return event`, inputs_schema: [ diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/user-agent-plugin/template.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/user-agent-plugin/template.ts index 313c7d85afa59..5a90a75be23f6 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/user-agent-plugin/template.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/user-agent-plugin/template.ts @@ -7,7 +7,7 @@ export const template: HogFunctionTemplate = { name: 'User Agent Populator', description: "Enhances events with user agent details. User Agent plugin allows you to populate events with the $browser, $browser_version for PostHog Clients that don't typically populate these properties", - icon_url: 'https://raw.githubusercontent.com/posthog/useragent-plugin/main/logo.png', + icon_url: 'https://raw.githubusercontent.com/posthog/user-agent-plugin/main/logo.png', category: ['Transformation'], hog: `return event`, inputs_schema: [ diff --git a/posthog/api/test/__data__/hog_function_templates.json b/posthog/api/test/__data__/hog_function_templates.json index abdc857b83f7c..69895372cf7ae 100644 --- a/posthog/api/test/__data__/hog_function_templates.json +++ b/posthog/api/test/__data__/hog_function_templates.json @@ -423,7 +423,7 @@ "id": "plugin-semver-flattener-plugin", "name": "SemVer Flattener", "description": "This plugin will flatten semver versions in the specified properties.", - "icon_url": "https://raw.githubusercontent.com/posthog/posthog-semver-flattener-plugin/main/logo.png", + "icon_url": "https://raw.githubusercontent.com/posthog/semver-flattener-plugin/main/logo.png", "category": ["Transformation"], "hog": "return event", "inputs_schema": [ From 52d9fcb64742778467c9c191c6197a394e1f4fc2 Mon Sep 17 00:00:00 2001 From: Ben White Date: Wed, 29 Jan 2025 15:22:07 +0100 Subject: [PATCH 085/163] Fixes --- posthog/api/hog_function_template.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/posthog/api/hog_function_template.py b/posthog/api/hog_function_template.py index 0cde588af6ba9..b491fb4af26d8 100644 --- a/posthog/api/hog_function_template.py +++ b/posthog/api/hog_function_template.py @@ -1,3 +1,4 @@ +from datetime import datetime, timedelta from posthoganalytics import capture_exception import structlog from django_filters.rest_framework import DjangoFilterBackend @@ -46,6 +47,7 @@ class Meta: class HogFunctionTemplates: + _cached_at: datetime | None = None _cached_templates: list[HogFunctionTemplate] = [] _cached_templates_by_id: dict[str, HogFunctionTemplate] = {} _cached_sub_templates: list[HogFunctionTemplate] = [] @@ -68,7 +70,9 @@ def template(cls, template_id: str): @classmethod def _load_templates(cls): - # TODO: Cache this + if cls._cached_at and datetime.now() - cls._cached_at < timedelta(minutes=1): + return + # First we load and convert all nodejs templates to python templates response = get_hog_function_templates() if response.status_code != 200: From f2c1fb486c8098ca986bfd8e6bf37c02700f3884 Mon Sep 17 00:00:00 2001 From: Ben White Date: Wed, 29 Jan 2025 15:35:47 +0100 Subject: [PATCH 086/163] Fixes --- .../legacy-plugin-executor.test.ts.snap | 186 +++--------------- .../services/legacy-plugin-executor.test.ts | 7 +- 2 files changed, 35 insertions(+), 158 deletions(-) diff --git a/plugin-server/src/cdp/services/__snapshots__/legacy-plugin-executor.test.ts.snap b/plugin-server/src/cdp/services/__snapshots__/legacy-plugin-executor.test.ts.snap index e94799e39b0ca..d92a16fa8d060 100644 --- a/plugin-server/src/cdp/services/__snapshots__/legacy-plugin-executor.test.ts.snap +++ b/plugin-server/src/cdp/services/__snapshots__/legacy-plugin-executor.test.ts.snap @@ -2,56 +2,20 @@ exports[`LegacyPluginExecutorService smoke tests should run the destination plugin: { name: 'customerio-plugin', plugin: [Object] } 1`] = ` [ - { - "level": "debug", - "message": "Executing plugin customerio-plugin", - "timestamp": "2025-01-01T01:00:00.000+01:00", - }, - { - "level": "info", - "message": "Successfully authenticated with Customer.io. Completing setupPlugin.", - "timestamp": "2025-01-01T01:00:00.000+01:00", - }, - { - "level": "debug", - "message": "Detected email:, test@posthog.com", - "timestamp": "2025-01-01T01:00:00.000+01:00", - }, - { - "level": "debug", - "message": "{"status":{},"email":"test@posthog.com"}", - "timestamp": "2025-01-01T01:00:00.000+01:00", - }, - { - "level": "debug", - "message": "Should customer be tracked:, true", - "timestamp": "2025-01-01T01:00:00.000+01:00", - }, - { - "level": "debug", - "message": "Execution successful", - "timestamp": "2025-01-01T01:00:00.000+01:00", - }, + "Executing plugin customerio-plugin", + "Successfully authenticated with Customer.io. Completing setupPlugin.", + "Detected email:, test@posthog.com", + "{"status":{},"email":"test@posthog.com"}", + "Should customer be tracked:, true", + "Execution successful", ] `; exports[`LegacyPluginExecutorService smoke tests should run the destination plugin: { name: 'posthog-intercom-plugin', plugin: [Object] } 1`] = ` [ - { - "level": "debug", - "message": "Executing plugin posthog-intercom-plugin", - "timestamp": "2025-01-01T01:00:00.000+01:00", - }, - { - "level": "info", - "message": "Contact test@posthog.com in Intercom not found", - "timestamp": "2025-01-01T01:00:00.000+01:00", - }, - { - "level": "debug", - "message": "Execution successful", - "timestamp": "2025-01-01T01:00:00.000+01:00", - }, + "Executing plugin posthog-intercom-plugin", + "Contact test@posthog.com in Intercom not found", + "Execution successful", ] `; @@ -60,160 +24,72 @@ exports[`LegacyPluginExecutorService smoke tests should run the transformation p plugin: [Object] } 1`] = ` [ - { - "level": "debug", - "message": "Executing plugin posthog-app-url-parameters-to-event-properties", - "timestamp": "2025-01-01T01:00:00.000+01:00", - }, - { - "level": "debug", - "message": "Execution successful", - "timestamp": "2025-01-01T01:00:00.000+01:00", - }, + "Executing plugin posthog-app-url-parameters-to-event-properties", + "Execution successful", ] `; exports[`LegacyPluginExecutorService smoke tests should run the transformation plugin: { name: 'downsampling-plugin', plugin: [Object] } 1`] = ` [ - { - "level": "debug", - "message": "Executing plugin downsampling-plugin", - "timestamp": "2025-01-01T01:00:00.000+01:00", - }, - { - "level": "debug", - "message": "Execution successful", - "timestamp": "2025-01-01T01:00:00.000+01:00", - }, + "Executing plugin downsampling-plugin", + "Execution successful", ] `; exports[`LegacyPluginExecutorService smoke tests should run the transformation plugin: { name: 'language-url-splitter-app', plugin: [Object] } 1`] = ` [ - { - "level": "debug", - "message": "Executing plugin language-url-splitter-app", - "timestamp": "2025-01-01T01:00:00.000+01:00", - }, - { - "level": "debug", - "message": "Execution successful", - "timestamp": "2025-01-01T01:00:00.000+01:00", - }, + "Executing plugin language-url-splitter-app", + "Execution successful", ] `; exports[`LegacyPluginExecutorService smoke tests should run the transformation plugin: { name: 'posthog-filter-out-plugin', plugin: [Object] } 1`] = ` [ - { - "level": "debug", - "message": "Executing plugin posthog-filter-out-plugin", - "timestamp": "2025-01-01T01:00:00.000+01:00", - }, - { - "level": "debug", - "message": "Execution successful", - "timestamp": "2025-01-01T01:00:00.000+01:00", - }, + "Executing plugin posthog-filter-out-plugin", + "Execution successful", ] `; exports[`LegacyPluginExecutorService smoke tests should run the transformation plugin: { name: 'posthog-url-normalizer-plugin', plugin: [Object] } 1`] = ` [ - { - "level": "debug", - "message": "Executing plugin posthog-url-normalizer-plugin", - "timestamp": "2025-01-01T01:00:00.000+01:00", - }, - { - "level": "debug", - "message": "event.$current_url: "https://posthog.com" normalized to "https://posthog.com/"", - "timestamp": "2025-01-01T01:00:00.000+01:00", - }, - { - "level": "debug", - "message": "Execution successful", - "timestamp": "2025-01-01T01:00:00.000+01:00", - }, + "Executing plugin posthog-url-normalizer-plugin", + "event.$current_url: "https://posthog.com" normalized to "https://posthog.com/"", + "Execution successful", ] `; exports[`LegacyPluginExecutorService smoke tests should run the transformation plugin: { name: 'property-filter-plugin', plugin: [Object] } 1`] = ` [ - { - "level": "debug", - "message": "Executing plugin property-filter-plugin", - "timestamp": "2025-01-01T01:00:00.000+01:00", - }, - { - "level": "debug", - "message": "Execution successful", - "timestamp": "2025-01-01T01:00:00.000+01:00", - }, + "Executing plugin property-filter-plugin", + "Execution successful", ] `; exports[`LegacyPluginExecutorService smoke tests should run the transformation plugin: { name: 'semver-flattener-plugin', plugin: [Object] } 1`] = ` [ - { - "level": "debug", - "message": "Executing plugin semver-flattener-plugin", - "timestamp": "2025-01-01T01:00:00.000+01:00", - }, - { - "level": "info", - "message": "found candidate property: , test, matches , ", - "timestamp": "2025-01-01T01:00:00.000+01:00", - }, - { - "level": "debug", - "message": "Execution successful", - "timestamp": "2025-01-01T01:00:00.000+01:00", - }, + "Executing plugin semver-flattener-plugin", + "found candidate property: , test, matches , ", + "Execution successful", ] `; exports[`LegacyPluginExecutorService smoke tests should run the transformation plugin: { name: 'taxonomy-plugin', plugin: [Object] } 1`] = ` [ - { - "level": "debug", - "message": "Executing plugin taxonomy-plugin", - "timestamp": "2025-01-01T01:00:00.000+01:00", - }, - { - "level": "debug", - "message": "Execution successful", - "timestamp": "2025-01-01T01:00:00.000+01:00", - }, + "Executing plugin taxonomy-plugin", + "Execution successful", ] `; exports[`LegacyPluginExecutorService smoke tests should run the transformation plugin: { name: 'timestamp-parser-plugin', plugin: [Object] } 1`] = ` [ - { - "level": "debug", - "message": "Executing plugin timestamp-parser-plugin", - "timestamp": "2025-01-01T01:00:00.000+01:00", - }, - { - "level": "debug", - "message": "Execution successful", - "timestamp": "2025-01-01T01:00:00.000+01:00", - }, + "Executing plugin timestamp-parser-plugin", + "Execution successful", ] `; exports[`LegacyPluginExecutorService smoke tests should run the transformation plugin: { name: 'user-agent-plugin', plugin: [Object] } 1`] = ` [ - { - "level": "debug", - "message": "Executing plugin user-agent-plugin", - "timestamp": "2025-01-01T01:00:00.000+01:00", - }, - { - "level": "debug", - "message": "Execution successful", - "timestamp": "2025-01-01T01:00:00.000+01:00", - }, + "Executing plugin user-agent-plugin", + "Execution successful", ] `; diff --git a/plugin-server/src/cdp/services/legacy-plugin-executor.test.ts b/plugin-server/src/cdp/services/legacy-plugin-executor.test.ts index 24a2f279fd0f4..7f874e0195bf5 100644 --- a/plugin-server/src/cdp/services/legacy-plugin-executor.test.ts +++ b/plugin-server/src/cdp/services/legacy-plugin-executor.test.ts @@ -7,7 +7,7 @@ import { insertHogFunction as _insertHogFunction, } from '~/tests/cdp/fixtures' import { forSnapshot } from '~/tests/helpers/snapshots' -import { getFirstTeam } from '~/tests/helpers/sql' +import { getFirstTeam, resetTestDatabase } from '~/tests/helpers/sql' import { Hub, Team } from '../../types' import { closeHub, createHub } from '../../utils/db/hub' @@ -32,6 +32,7 @@ describe('LegacyPluginExecutorService', () => { beforeEach(async () => { hub = await createHub() + await resetTestDatabase() service = new LegacyPluginExecutorService() team = await getFirstTeam(hub) @@ -413,7 +414,7 @@ describe('LegacyPluginExecutorService', () => { invocation.hogFunction.name = name const res = await service.execute(invocation) - expect(res.logs).toMatchSnapshot() + expect(res.logs.map((l) => l.message)).toMatchSnapshot() }) const testCasesTransformation = Object.entries(TRANSFORMATION_PLUGINS_BY_ID).map(([pluginId, plugin]) => ({ @@ -451,7 +452,7 @@ describe('LegacyPluginExecutorService', () => { invocation.globals.inputs = inputs const res = await service.execute(invocation) - expect(res.logs).toMatchSnapshot() + expect(res.logs.map((l) => l.message)).toMatchSnapshot() }) }) }) From 949486f005a983d9b5e98f4b8f076ced9f39d88c Mon Sep 17 00:00:00 2001 From: Ben White Date: Wed, 29 Jan 2025 15:41:51 +0100 Subject: [PATCH 087/163] Fix --- posthog/models/hog_functions/hog_function.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/posthog/models/hog_functions/hog_function.py b/posthog/models/hog_functions/hog_function.py index e1bbb931558e8..751b73f5ff11f 100644 --- a/posthog/models/hog_functions/hog_function.py +++ b/posthog/models/hog_functions/hog_function.py @@ -108,6 +108,8 @@ def template(self) -> Optional[HogFunctionTemplate]: if self.template_id.startswith("plugin-"): return create_legacy_plugin_template(self.template_id) + return None + @property def filter_action_ids(self) -> list[int]: if not self.filters: From 10eec5319122d453d78bdc688611526a7aca4329 Mon Sep 17 00:00:00 2001 From: Ben White Date: Wed, 29 Jan 2025 15:51:56 +0100 Subject: [PATCH 088/163] Fixes --- posthog/cdp/templates/__init__.py | 2 -- .../_transformations/template_pass_through.py | 18 ------------------ 2 files changed, 20 deletions(-) delete mode 100644 posthog/cdp/templates/_transformations/template_pass_through.py diff --git a/posthog/cdp/templates/__init__.py b/posthog/cdp/templates/__init__.py index bdf94f45440d1..ca81f8915585e 100644 --- a/posthog/cdp/templates/__init__.py +++ b/posthog/cdp/templates/__init__.py @@ -52,7 +52,6 @@ from ._internal.template_blank import blank_site_destination, blank_site_app from .snapchat_ads.template_snapchat_ads import template as snapchat_ads from .snapchat_ads.template_pixel import template_snapchat_pixel as snapchat_pixel -from ._transformations.template_pass_through import template as pass_through_transformation HOG_FUNCTION_TEMPLATES = [ @@ -108,7 +107,6 @@ hogdesk, notification_bar, pineapple_mode, - pass_through_transformation, debug_posthog, ] diff --git a/posthog/cdp/templates/_transformations/template_pass_through.py b/posthog/cdp/templates/_transformations/template_pass_through.py deleted file mode 100644 index 5a4e88e003d31..0000000000000 --- a/posthog/cdp/templates/_transformations/template_pass_through.py +++ /dev/null @@ -1,18 +0,0 @@ -from posthog.cdp.templates.hog_function_template import HogFunctionTemplate - -template: HogFunctionTemplate = HogFunctionTemplate( - status="alpha", - type="transformation", - id="template-blank-transformation", - name="Custom transformation", - description="This is a starter template for custom transformations", - icon_url="/static/hedgehog/builder-hog-01.png", - category=["Custom"], - hog=""" -// This is a blank template for custom transformations -// The function receives `event` as a global object and expects it to be returned -// If you return null then the event will be discarded -return event -""".strip(), - inputs_schema=[], -) From 13f2a2b257a6100fefa0a83fe3ea0800bba6077f Mon Sep 17 00:00:00 2001 From: Ben White Date: Wed, 29 Jan 2025 15:54:25 +0100 Subject: [PATCH 089/163] Fix --- .../services/legacy-plugin-executor.test.ts | 26 ++++--------------- 1 file changed, 5 insertions(+), 21 deletions(-) diff --git a/plugin-server/src/cdp/services/legacy-plugin-executor.test.ts b/plugin-server/src/cdp/services/legacy-plugin-executor.test.ts index 7f874e0195bf5..2a80995b2e160 100644 --- a/plugin-server/src/cdp/services/legacy-plugin-executor.test.ts +++ b/plugin-server/src/cdp/services/legacy-plugin-executor.test.ts @@ -198,28 +198,12 @@ describe('LegacyPluginExecutorService', () => { `) expect(res.finished).toBe(true) - expect(res.logs).toMatchInlineSnapshot(` + expect(res.logs.map((l) => l.message)).toMatchInlineSnapshot(` [ - { - "level": "debug", - "message": "Executing plugin posthog-intercom-plugin", - "timestamp": "2025-01-01T01:00:00.000+01:00", - }, - { - "level": "info", - "message": "Contact test@posthog.com in Intercom found", - "timestamp": "2025-01-01T01:00:00.000+01:00", - }, - { - "level": "info", - "message": "Sent event mycustomevent for test@posthog.com to Intercom", - "timestamp": "2025-01-01T01:00:00.000+01:00", - }, - { - "level": "debug", - "message": "Execution successful", - "timestamp": "2025-01-01T01:00:00.000+01:00", - }, + "Executing plugin posthog-intercom-plugin", + "Contact test@posthog.com in Intercom found", + "Sent event mycustomevent for test@posthog.com to Intercom", + "Execution successful", ] `) }) From 4814722ebc87919d9536420ad1456306547be44a Mon Sep 17 00:00:00 2001 From: Ben White Date: Wed, 29 Jan 2025 16:00:17 +0100 Subject: [PATCH 090/163] fix --- plugin-server/src/cdp/templates/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugin-server/src/cdp/templates/index.ts b/plugin-server/src/cdp/templates/index.ts index 216388bdff308..9ae554aaac68e 100644 --- a/plugin-server/src/cdp/templates/index.ts +++ b/plugin-server/src/cdp/templates/index.ts @@ -9,14 +9,14 @@ import { template as taxonomyTemplate } from '../legacy-plugins/_transformations import { template as timestampParserTemplate } from '../legacy-plugins/_transformations/timestamp-parser-plugin/template' import { template as userAgentTemplate } from '../legacy-plugins/_transformations/user-agent-plugin/template' import { template as webhookTemplate } from './_destinations/webhook/webhook.template' -import { template as defaultTransformatonTemplate } from './_transformations/default/default.template' +import { template as defaultTransformationTemplate } from './_transformations/default/default.template' import { template as geoipTemplate } from './_transformations/geoip/geoip.template' import { HogFunctionTemplate } from './types' export const HOG_FUNCTION_TEMPLATES_DESTINATIONS: HogFunctionTemplate[] = [webhookTemplate] export const HOG_FUNCTION_TEMPLATES_TRANSFORMATIONS: HogFunctionTemplate[] = [ - defaultTransformatonTemplate, + defaultTransformationTemplate, geoipTemplate, downsamplingPlugin, languageUrlSplitterTemplate, From cab76528da4840843f8bac6475e77475b6df13df Mon Sep 17 00:00:00 2001 From: Ben White Date: Wed, 29 Jan 2025 16:03:24 +0100 Subject: [PATCH 091/163] Fix --- posthog/api/test/test_hog_function_templates.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/posthog/api/test/test_hog_function_templates.py b/posthog/api/test/test_hog_function_templates.py index a6b4c6d5a54c7..5271de0ce4c5f 100644 --- a/posthog/api/test/test_hog_function_templates.py +++ b/posthog/api/test/test_hog_function_templates.py @@ -1,4 +1,5 @@ import json +import os from unittest.mock import ANY, patch from inline_snapshot import snapshot from rest_framework import status @@ -7,7 +8,9 @@ from posthog.test.base import APIBaseTest, ClickhouseTestMixin, QueryMatchingTest from posthog.cdp.templates.slack.template_slack import template -MOCK_NODE_TEMPLATES = json.loads(open("posthog/api/test/__data__/hog_function_templates.json").read()) +MOCK_NODE_TEMPLATES = json.loads( + open(os.path.join(os.path.dirname(__file__), "__data__/hog_function_templates.json")).read() +) # NOTE: We check this as a sanity check given that this is a public API so we want to explicitly define what is exposed EXPECTED_FIRST_RESULT = { From 3a92713b17bf689270b9e74e7578ca8c674a9ae8 Mon Sep 17 00:00:00 2001 From: Ben White Date: Wed, 29 Jan 2025 16:04:46 +0100 Subject: [PATCH 092/163] Update posthog/api/test/__data__/hog_function_templates.json Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> --- posthog/api/test/__data__/hog_function_templates.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/posthog/api/test/__data__/hog_function_templates.json b/posthog/api/test/__data__/hog_function_templates.json index 69895372cf7ae..ad0dc299dddef 100644 --- a/posthog/api/test/__data__/hog_function_templates.json +++ b/posthog/api/test/__data__/hog_function_templates.json @@ -198,7 +198,7 @@ "label": "Pattern", "type": "string", "default": "^/([a-z]{2})(?=/|#|\\?|$)", - "description": "Ininitalized with `const regexp = new RegExp($pattern)`", + "description": "Initialized with `const regexp = new RegExp($pattern)`", "required": true }, { From 467789bc5874a3ac4f9e81347c8d152b822ee069 Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 29 Jan 2025 15:06:59 +0000 Subject: [PATCH 093/163] Update UI snapshots for `chromium` (1) --- .../exporter-exporter--dashboard--light.png | Bin 111206 -> 74740 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/frontend/__snapshots__/exporter-exporter--dashboard--light.png b/frontend/__snapshots__/exporter-exporter--dashboard--light.png index e160d331bfe63b1faa1f5e4eb07debd286556798..6450d0bd48607c5f7dbf0d38d362bc656b68996f 100644 GIT binary patch literal 74740 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 literal 111206 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

  • S0iYBObW7?GDk&5LOU3Cv1@QJbkY_` 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 B3JCxJ literal 74740 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 From 047a2b56da06b32136a2890ef76db71a4227f07a Mon Sep 17 00:00:00 2001 From: Ben White Date: Wed, 29 Jan 2025 17:54:01 +0100 Subject: [PATCH 096/163] Fix --- mypy-baseline.txt | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/mypy-baseline.txt b/mypy-baseline.txt index 433902cd00910..e2e2ccb2085ee 100644 --- a/mypy-baseline.txt +++ b/mypy-baseline.txt @@ -149,7 +149,6 @@ posthog/hogql_queries/legacy_compatibility/filter_to_query.py:0: error: Dict ent posthog/hogql_queries/legacy_compatibility/filter_to_query.py:0: error: Dict entry 0 has incompatible type "str": "StickinessFilter"; expected "str": "TrendsFilter" [dict-item] posthog/session_recordings/models/session_recording.py:0: error: Argument "distinct_id" to "MissingPerson" has incompatible type "str | None"; expected "str" [arg-type] posthog/session_recordings/models/session_recording.py:0: error: Incompatible type for lookup 'persondistinctid__team_id': (got "Team", expected "str | int") [misc] -posthog/models/hog_functions/hog_function.py:0: error: Argument 1 to "get" of "dict" has incompatible type "str | None"; expected "str" [arg-type] ee/tasks/subscriptions/slack_subscriptions.py:0: error: Item "None" of "datetime | None" has no attribute "strftime" [union-attr] posthog/warehouse/models/table.py:0: error: Item "None" of "DataWarehouseCredential | None" has no attribute "access_key" [union-attr] posthog/warehouse/models/table.py:0: error: Item "None" of "DataWarehouseCredential | None" has no attribute "access_secret" [union-attr] @@ -348,6 +347,7 @@ posthog/queries/breakdown_props.py:0: error: Incompatible type for lookup 'pk': posthog/queries/breakdown_props.py:0: error: Incompatible return value type (got "str | None", expected "str") [return-value] posthog/queries/actor_base_query.py:0: error: Incompatible types (expression has type "datetime", TypedDict item "created_at" has type "str | None") [typeddict-item] posthog/queries/actor_base_query.py:0: error: Incompatible types (expression has type "datetime", TypedDict item "created_at" has type "str | None") [typeddict-item] +posthog/tasks/exports/image_exporter.py:0: error: Module has no attribute "ChromeService" [attr-defined] posthog/hogql_queries/insights/funnels/base.py:0: error: Incompatible type for lookup 'pk': (got "str | int | None", expected "str | int") [misc] posthog/queries/foss_cohort_query.py:0: error: Incompatible type for lookup 'pk': (got "str | int | list[str]", expected "str | int") [misc] posthog/caching/insight_caching_state.py:0: error: Argument "params" to "execute" of "CursorWrapper" has incompatible type "list[object]"; expected "Sequence[bool | int | float | Decimal | str | <6 more items> | None] | Mapping[str, bool | int | float | Decimal | str | <6 more items> | None] | None" [arg-type] @@ -743,11 +743,6 @@ posthog/warehouse/api/external_data_schema.py:0: note: def [_T] get(self, Type, posthog/warehouse/api/table.py:0: error: Unused "type: ignore" comment [unused-ignore] posthog/warehouse/api/table.py:0: error: Unused "type: ignore" comment [unused-ignore] posthog/warehouse/api/table.py:0: error: Unused "type: ignore" comment [unused-ignore] -posthog/temporal/tests/batch_exports/test_run_updates.py:0: error: Unused "type: ignore" comment [unused-ignore] -posthog/temporal/tests/batch_exports/test_run_updates.py:0: error: Unused "type: ignore" comment [unused-ignore] -posthog/temporal/tests/batch_exports/test_run_updates.py:0: error: Unused "type: ignore" comment [unused-ignore] -posthog/temporal/tests/batch_exports/test_run_updates.py:0: error: Unused "type: ignore" comment [unused-ignore] -posthog/temporal/tests/batch_exports/test_batch_exports.py:0: error: TypedDict key must be a string literal; expected one of ("_timestamp", "created_at", "distinct_id", "elements", "elements_chain", ...) [literal-required] posthog/temporal/data_modeling/run_workflow.py:0: error: Dict entry 20 has incompatible type "str": "Literal['complex']"; expected "str": "Literal['text', 'double', 'bool', 'timestamp', 'bigint', 'binary', 'json', 'decimal', 'wei', 'date', 'time']" [dict-item] posthog/temporal/data_modeling/run_workflow.py:0: error: Dict entry 21 has incompatible type "str": "Literal['complex']"; expected "str": "Literal['text', 'double', 'bool', 'timestamp', 'bigint', 'binary', 'json', 'decimal', 'wei', 'date', 'time']" [dict-item] posthog/temporal/data_modeling/run_workflow.py:0: error: Dict entry 22 has incompatible type "str": "Literal['complex']"; expected "str": "Literal['text', 'double', 'bool', 'timestamp', 'bigint', 'binary', 'json', 'decimal', 'wei', 'date', 'time']" [dict-item] @@ -787,10 +782,11 @@ posthog/temporal/tests/external_data/test_external_data_job.py:0: error: Invalid posthog/temporal/tests/external_data/test_external_data_job.py:0: error: Invalid index type "str" for "dict[Type, Sequence[str]]"; expected type "Type" [index] posthog/temporal/tests/external_data/test_external_data_job.py:0: error: Invalid index type "str" for "dict[Type, Sequence[str]]"; expected type "Type" [index] posthog/temporal/tests/data_imports/test_end_to_end.py:0: error: Unused "type: ignore" comment [unused-ignore] -posthog/temporal/tests/batch_exports/test_snowflake_batch_export_workflow.py:0: error: Need type annotation for "_execute_calls" (hint: "_execute_calls: list[] = ...") [var-annotated] -posthog/temporal/tests/batch_exports/test_snowflake_batch_export_workflow.py:0: error: Need type annotation for "_execute_async_calls" (hint: "_execute_async_calls: list[] = ...") [var-annotated] -posthog/temporal/tests/batch_exports/test_snowflake_batch_export_workflow.py:0: error: Need type annotation for "_cursors" (hint: "_cursors: list[] = ...") [var-annotated] -posthog/temporal/tests/batch_exports/test_snowflake_batch_export_workflow.py:0: error: List item 0 has incompatible type "tuple[str, str, int, int, int, int, str, int]"; expected "tuple[str, str, int, int, str, str, str, str]" [list-item] +posthog/temporal/tests/batch_exports/test_run_updates.py:0: error: Unused "type: ignore" comment [unused-ignore] +posthog/temporal/tests/batch_exports/test_run_updates.py:0: error: Unused "type: ignore" comment [unused-ignore] +posthog/temporal/tests/batch_exports/test_run_updates.py:0: error: Unused "type: ignore" comment [unused-ignore] +posthog/temporal/tests/batch_exports/test_run_updates.py:0: error: Unused "type: ignore" comment [unused-ignore] +posthog/temporal/tests/batch_exports/test_batch_exports.py:0: error: TypedDict key must be a string literal; expected one of ("_timestamp", "created_at", "distinct_id", "elements", "elements_chain", ...) [literal-required] posthog/api/test/test_capture.py:0: error: Statement is unreachable [unreachable] posthog/api/test/test_capture.py:0: error: Incompatible return value type (got "_MonkeyPatchedWSGIResponse", expected "HttpResponse") [return-value] posthog/api/test/test_capture.py:0: error: Module has no attribute "utc" [attr-defined] @@ -803,6 +799,10 @@ posthog/api/test/test_capture.py:0: error: Dict entry 0 has incompatible type "s posthog/api/test/test_capture.py:0: error: Dict entry 0 has incompatible type "str": "float"; expected "str": "int" [dict-item] posthog/api/test/test_capture.py:0: error: Dict entry 0 has incompatible type "str": "float"; expected "str": "int" [dict-item] posthog/api/test/test_capture.py:0: error: Dict entry 0 has incompatible type "str": "float"; expected "str": "int" [dict-item] +posthog/temporal/tests/batch_exports/test_snowflake_batch_export_workflow.py:0: error: Need type annotation for "_execute_calls" (hint: "_execute_calls: list[] = ...") [var-annotated] +posthog/temporal/tests/batch_exports/test_snowflake_batch_export_workflow.py:0: error: Need type annotation for "_execute_async_calls" (hint: "_execute_async_calls: list[] = ...") [var-annotated] +posthog/temporal/tests/batch_exports/test_snowflake_batch_export_workflow.py:0: error: Need type annotation for "_cursors" (hint: "_cursors: list[] = ...") [var-annotated] +posthog/temporal/tests/batch_exports/test_snowflake_batch_export_workflow.py:0: error: List item 0 has incompatible type "tuple[str, str, int, int, int, int, str, int]"; expected "tuple[str, str, int, int, str, str, str, str]" [list-item] posthog/temporal/tests/batch_exports/test_redshift_batch_export_workflow.py:0: error: Incompatible types in assignment (expression has type "str | int", variable has type "int") [assignment] posthog/api/test/batch_exports/conftest.py:0: error: Signature of "run" incompatible with supertype "Worker" [override] posthog/api/test/batch_exports/conftest.py:0: note: Superclass: From aa2bbca154d1b87bdb08f745713fd6b126dfd159 Mon Sep 17 00:00:00 2001 From: Ben White Date: Wed, 29 Jan 2025 18:10:01 +0100 Subject: [PATCH 097/163] Fix --- mypy-baseline.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/mypy-baseline.txt b/mypy-baseline.txt index e2e2ccb2085ee..b97f5c0b98b2b 100644 --- a/mypy-baseline.txt +++ b/mypy-baseline.txt @@ -347,7 +347,6 @@ posthog/queries/breakdown_props.py:0: error: Incompatible type for lookup 'pk': posthog/queries/breakdown_props.py:0: error: Incompatible return value type (got "str | None", expected "str") [return-value] posthog/queries/actor_base_query.py:0: error: Incompatible types (expression has type "datetime", TypedDict item "created_at" has type "str | None") [typeddict-item] posthog/queries/actor_base_query.py:0: error: Incompatible types (expression has type "datetime", TypedDict item "created_at" has type "str | None") [typeddict-item] -posthog/tasks/exports/image_exporter.py:0: error: Module has no attribute "ChromeService" [attr-defined] posthog/hogql_queries/insights/funnels/base.py:0: error: Incompatible type for lookup 'pk': (got "str | int | None", expected "str | int") [misc] posthog/queries/foss_cohort_query.py:0: error: Incompatible type for lookup 'pk': (got "str | int | list[str]", expected "str | int") [misc] posthog/caching/insight_caching_state.py:0: error: Argument "params" to "execute" of "CursorWrapper" has incompatible type "list[object]"; expected "Sequence[bool | int | float | Decimal | str | <6 more items> | None] | Mapping[str, bool | int | float | Decimal | str | <6 more items> | None] | None" [arg-type] From 8f5b998bb7be78f92e70be20b6a08c94b7f36aa2 Mon Sep 17 00:00:00 2001 From: Ben White Date: Wed, 29 Jan 2025 18:42:42 +0100 Subject: [PATCH 098/163] Set the cdp api up as its own service --- plugin-server/src/capabilities.ts | 6 + plugin-server/src/cdp/cdp-api.ts | 186 ++++++++++-------- .../hog-transformer.service.ts | 45 ++--- plugin-server/src/main/pluginsServer.ts | 12 +- plugin-server/src/types.ts | 2 + posthog/settings/data_stores.py | 2 +- 6 files changed, 140 insertions(+), 113 deletions(-) diff --git a/plugin-server/src/capabilities.ts b/plugin-server/src/capabilities.ts index 55aa5ed25cc4c..cf6610deaaf73 100644 --- a/plugin-server/src/capabilities.ts +++ b/plugin-server/src/capabilities.ts @@ -27,6 +27,7 @@ export function getPluginServerCapabilities(config: PluginsServerConfig): Plugin cdpInternalEvents: true, cdpCyclotronWorker: true, cdpCyclotronWorkerPlugins: true, + cdpApi: true, syncInlinePlugins: true, ...sharedCapabilities, } @@ -145,6 +146,11 @@ export function getPluginServerCapabilities(config: PluginsServerConfig): Plugin cdpCyclotronWorkerPlugins: true, ...sharedCapabilities, } + case PluginServerMode.cdp_api: + return { + cdpApi: true, + ...sharedCapabilities, + } // This is only for functional tests, which time out if all capabilities are used // ideally we'd run just the specific capability needed per test, but that's not easy to do atm case PluginServerMode.functional_tests: diff --git a/plugin-server/src/cdp/cdp-api.ts b/plugin-server/src/cdp/cdp-api.ts index a2c4839f1e946..d1711dfab4fe4 100644 --- a/plugin-server/src/cdp/cdp-api.ts +++ b/plugin-server/src/cdp/cdp-api.ts @@ -1,35 +1,51 @@ import express from 'express' import { DateTime } from 'luxon' -import { Hub } from '../types' +import { Hub, PluginServerService } from '../types' import { status } from '../utils/status' import { delay } from '../utils/utils' +import { createCdpRedisPool } from './redis' import { FetchExecutorService } from './services/fetch-executor.service' import { HogExecutorService, MAX_ASYNC_STEPS } from './services/hog-executor.service' import { HogFunctionManagerService } from './services/hog-function-manager.service' import { HogWatcherService, HogWatcherState } from './services/hog-watcher.service' +import { LegacyPluginExecutorService } from './services/legacy-plugin-executor.service' import { HOG_FUNCTION_TEMPLATES } from './templates' import { HogFunctionInvocationResult, HogFunctionQueueParametersFetchRequest, HogFunctionType, LogEntry } from './types' +import { isLegacyPluginHogFunction } from './utils' export class CdpApi { private hogExecutor: HogExecutorService private hogFunctionManager: HogFunctionManagerService private fetchExecutor: FetchExecutorService private hogWatcher: HogWatcherService + private pluginExecutor: LegacyPluginExecutorService + constructor(private hub: Hub) { + this.hogFunctionManager = new HogFunctionManagerService(hub) + this.hogExecutor = new HogExecutorService(hub, this.hogFunctionManager) + this.fetchExecutor = new FetchExecutorService(hub) + this.hogWatcher = new HogWatcherService(hub, createCdpRedisPool(hub)) + this.pluginExecutor = new LegacyPluginExecutorService() + } - constructor( - private hub: Hub, - dependencies: { - hogExecutor: HogExecutorService - hogFunctionManager: HogFunctionManagerService - fetchExecutor: FetchExecutorService - hogWatcher: HogWatcherService + public get service(): PluginServerService { + return { + id: 'cdp-api', + onShutdown: async () => await this.stop(), + healthcheck: () => this.isHealthy() ?? false, } - ) { - this.hogExecutor = dependencies.hogExecutor - this.hogFunctionManager = dependencies.hogFunctionManager - this.fetchExecutor = dependencies.fetchExecutor - this.hogWatcher = dependencies.hogWatcher + } + + async start() { + await this.hogFunctionManager.start(['transformation', 'destination', 'internal_destination']) + } + + async stop() { + await Promise.all([this.hogFunctionManager.stop()]) + } + + isHealthy() { + return true } router(): express.Router { @@ -105,15 +121,15 @@ export class CdpApi { ]).catch(() => { return [null, null] }) - if (!hogFunction || !team || hogFunction.team_id !== team.id) { + + if (!team || (hogFunction && hogFunction.team_id !== team.id)) { res.status(404).json({ error: 'Hog function not found' }) return } - // We use the provided config if given, otherwise the function's config // We use the provided config if given, otherwise the function's config const compoundConfiguration: HogFunctionType = { - ...hogFunction, + ...(hogFunction ?? {}), ...(configuration ?? {}), } @@ -133,82 +149,88 @@ export class CdpApi { }, } - const { - invocations, - logs: filterLogs, - metrics: filterMetrics, - } = this.hogExecutor.buildHogFunctionInvocations([compoundConfiguration], triggerGlobals) - - // Add metrics to the logs - filterMetrics.forEach((metric) => { - if (metric.metric_name === 'filtered') { - logs.push({ - level: 'info', - timestamp: DateTime.now(), - message: `Mapping trigger not matching filters was ignored.`, - }) - } - }) + if (compoundConfiguration.type === 'destination') { + const { + invocations, + logs: filterLogs, + metrics: filterMetrics, + } = this.hogExecutor.buildHogFunctionInvocations([compoundConfiguration], triggerGlobals) + + // Add metrics to the logs + filterMetrics.forEach((metric) => { + if (metric.metric_name === 'filtered') { + logs.push({ + level: 'info', + timestamp: DateTime.now(), + message: `Mapping trigger not matching filters was ignored.`, + }) + } + }) - filterLogs.forEach((log) => { - logs.push(log) - }) + filterLogs.forEach((log) => { + logs.push(log) + }) - for (const _invocation of invocations) { - let count = 0 - let invocation = _invocation + for (const _invocation of invocations) { + let count = 0 + let invocation = _invocation - while (!lastResponse || !lastResponse.finished) { - if (count > MAX_ASYNC_STEPS * 2) { - throw new Error('Too many iterations') - } - count += 1 - - let response: HogFunctionInvocationResult - - if (invocation.queue === 'fetch') { - if (mock_async_functions) { - // Add the state, simulating what executeAsyncResponse would do - // Re-parse the fetch args for the logging - const fetchArgs: HogFunctionQueueParametersFetchRequest = - this.hogExecutor.redactFetchRequest( - invocation.queueParameters as HogFunctionQueueParametersFetchRequest - ) - - response = { - invocation: { - ...invocation, - queue: 'hog', - queueParameters: { response: { status: 200, headers: {} }, body: '{}' }, - }, - finished: false, - logs: [ - { - level: 'info', - timestamp: DateTime.now(), - message: `Async function 'fetch' was mocked with arguments:`, - }, - { - level: 'info', - timestamp: DateTime.now(), - message: `fetch(${JSON.stringify(fetchArgs, null, 2)})`, + while (!lastResponse || !lastResponse.finished) { + if (count > MAX_ASYNC_STEPS * 2) { + throw new Error('Too many iterations') + } + count += 1 + + let response: HogFunctionInvocationResult + + if (invocation.queue === 'fetch') { + if (mock_async_functions) { + // Add the state, simulating what executeAsyncResponse would do + // Re-parse the fetch args for the logging + const fetchArgs: HogFunctionQueueParametersFetchRequest = + this.hogExecutor.redactFetchRequest( + invocation.queueParameters as HogFunctionQueueParametersFetchRequest + ) + + response = { + invocation: { + ...invocation, + queue: 'hog', + queueParameters: { response: { status: 200, headers: {} }, body: '{}' }, }, - ], + finished: false, + logs: [ + { + level: 'info', + timestamp: DateTime.now(), + message: `Async function 'fetch' was mocked with arguments:`, + }, + { + level: 'info', + timestamp: DateTime.now(), + message: `fetch(${JSON.stringify(fetchArgs, null, 2)})`, + }, + ], + } + } else { + response = await this.fetchExecutor!.executeLocally(invocation) } } else { - response = await this.fetchExecutor!.executeLocally(invocation) + response = this.hogExecutor.execute(invocation) } - } else { - response = this.hogExecutor.execute(invocation) - } - logs = logs.concat(response.logs) - lastResponse = response - invocation = response.invocation - if (response.error) { - errors.push(response.error) + logs = logs.concat(response.logs) + lastResponse = response + invocation = response.invocation + if (response.error) { + errors.push(response.error) + } } } + } else if (compoundConfiguration.type === 'transformation') { + const result = isLegacyPluginHogFunction(compoundConfiguration) + ? await this.pluginExecutor.execute(invocation) + : this.hogExecutor.execute(invocation, { functions: transformationFunctions }) } res.json({ diff --git a/plugin-server/src/cdp/hog-transformations/hog-transformer.service.ts b/plugin-server/src/cdp/hog-transformations/hog-transformer.service.ts index 420f0af0d2cf2..7fcac3ee8a5af 100644 --- a/plugin-server/src/cdp/hog-transformations/hog-transformer.service.ts +++ b/plugin-server/src/cdp/hog-transformations/hog-transformer.service.ts @@ -3,7 +3,6 @@ import { Counter } from 'prom-client' import { HogFunctionAppMetric, - HogFunctionInvocation, HogFunctionInvocationGlobals, HogFunctionInvocationResult, HogFunctionType, @@ -80,15 +79,6 @@ export class HogTransformerService { } } - private createHogFunctionInvocation(event: PluginEvent, hogFunction: HogFunctionType): HogFunctionInvocation { - const globalsWithInputs = buildGlobalsWithInputs(this.createInvocationGlobals(event), { - ...(hogFunction.inputs ?? {}), - ...(hogFunction.encrypted_inputs ?? {}), - }) - - return createInvocation(globalsWithInputs, hogFunction) - } - public async start(): Promise { const hogTypes: HogFunctionTypeType[] = ['transformation'] await this.hogFunctionManager.start(hogTypes) @@ -176,27 +166,14 @@ export class HogTransformerService { func: async () => { const teamHogFunctions = this.hogFunctionManager.getTeamHogFunctions(event.team_id) - const transformationFunctions = this.getTransformationFunctions() const messagePromises: Promise[] = [] // For now, execute each transformation function in sequence for (const hogFunction of teamHogFunctions) { - const invocation = this.createHogFunctionInvocation(event, hogFunction) - - const result = isLegacyPluginHogFunction(hogFunction) - ? await this.pluginExecutor.execute(invocation) - : this.hogExecutor.execute(invocation, { functions: transformationFunctions }) + const result = await this.executeHogFunction(hogFunction, this.createInvocationGlobals(event)) // Process results and collect promises - messagePromises.push( - ...this.processInvocationResult({ - invocation, - logs: result.logs, - error: result.error, - execResult: result.execResult, - finished: true, - }) - ) + messagePromises.push(...this.processInvocationResult(result)) if (result.error) { status.error('⚠️', 'Error in transformation', { @@ -256,4 +233,22 @@ export class HogTransformerService { }, }) } + + public async executeHogFunction( + hogFunction: HogFunctionType, + globals: HogFunctionInvocationGlobals + ): Promise { + const transformationFunctions = this.getTransformationFunctions() + const globalsWithInputs = buildGlobalsWithInputs(globals, { + ...(hogFunction.inputs ?? {}), + ...(hogFunction.encrypted_inputs ?? {}), + }) + + const invocation = createInvocation(globalsWithInputs, hogFunction) + + const result = isLegacyPluginHogFunction(hogFunction) + ? await this.pluginExecutor.execute(invocation) + : this.hogExecutor.execute(invocation, { functions: transformationFunctions }) + return result + } } diff --git a/plugin-server/src/main/pluginsServer.ts b/plugin-server/src/main/pluginsServer.ts index 6807430d9e931..1bdc01d74710d 100644 --- a/plugin-server/src/main/pluginsServer.ts +++ b/plugin-server/src/main/pluginsServer.ts @@ -522,12 +522,14 @@ export async function startPluginsServer( const consumer = new CdpInternalEventsConsumer(hub) await consumer.start() services.push(consumer.service) + } - // NOTE: This processor is generally very idle so doubles as our api - if (capabilities.http) { - const api = new CdpApi(hub, consumer) - expressApp.use('/', api.router()) - } + if (capabilities.cdpApi) { + const hub = await setupHub() + const api = new CdpApi(hub) + await api.start() + services.push(api.service) + expressApp.use('/', api.router()) } if (capabilities.cdpCyclotronWorker) { diff --git a/plugin-server/src/types.ts b/plugin-server/src/types.ts index 2351c75d2d9dd..09e5a9c0e42fa 100644 --- a/plugin-server/src/types.ts +++ b/plugin-server/src/types.ts @@ -92,6 +92,7 @@ export enum PluginServerMode { cdp_internal_events = 'cdp-internal-events', cdp_cyclotron_worker = 'cdp-cyclotron-worker', cdp_cyclotron_worker_plugins = 'cdp-cyclotron-worker-plugins', + cdp_api = 'cdp-api', functional_tests = 'functional-tests', } @@ -404,6 +405,7 @@ export interface PluginServerCapabilities { cdpInternalEvents?: boolean cdpCyclotronWorker?: boolean cdpCyclotronWorkerPlugins?: boolean + cdpApi?: boolean appManagementSingleton?: boolean preflightSchedules?: boolean // Used for instance health checks on hobby deploy, not useful on cloud http?: boolean diff --git a/posthog/settings/data_stores.py b/posthog/settings/data_stores.py index 8134830e41f20..2b4f19d32f733 100644 --- a/posthog/settings/data_stores.py +++ b/posthog/settings/data_stores.py @@ -330,7 +330,7 @@ def _parse_kafka_hosts(hosts_string: str) -> list[str]: if not CDP_FUNCTION_EXECUTOR_API_URL: CDP_FUNCTION_EXECUTOR_API_URL = ( - "http://localhost:6738" if DEBUG else "http://ingestion-cdp-internal-events.posthog.svc.cluster.local" + "http://localhost:6738" if DEBUG else "http://ingestion-cdp-api.posthog.svc.cluster.local" ) CACHES = { From afb36bb3bc6ed4a633d7404ca2960a70f8aca731 Mon Sep 17 00:00:00 2001 From: Ben White Date: Wed, 29 Jan 2025 19:06:56 +0100 Subject: [PATCH 099/163] tests --- .../hogfunctions/HogFunctionConfiguration.tsx | 8 +----- .../pipeline/hogfunctions/HogFunctionTest.tsx | 22 ++++++++------- .../hogfunctions/hogFunctionTestLogic.tsx | 28 ++++++++++++------- plugin-server/src/cdp/cdp-api.ts | 22 ++++++++++----- posthog/api/hog_function.py | 10 +++++-- posthog/plugins/plugin_server_api.py | 2 +- 6 files changed, 54 insertions(+), 38 deletions(-) diff --git a/frontend/src/scenes/pipeline/hogfunctions/HogFunctionConfiguration.tsx b/frontend/src/scenes/pipeline/hogfunctions/HogFunctionConfiguration.tsx index c1eec7364379f..28672943f9774 100644 --- a/frontend/src/scenes/pipeline/hogfunctions/HogFunctionConfiguration.tsx +++ b/frontend/src/scenes/pipeline/hogfunctions/HogFunctionConfiguration.tsx @@ -469,13 +469,7 @@ export function HogFunctionConfiguration({ ) : null}

  • S0iYBObW7?GDk&5LOU3Cv1@QJbkY_` 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 Date: Wed, 29 Jan 2025 15:28:36 +0000 Subject: [PATCH 095/163] Update UI snapshots for `chromium` (1) --- .../exporter-exporter--dashboard--light.png | Bin 74740 -> 111206 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/frontend/__snapshots__/exporter-exporter--dashboard--light.png b/frontend/__snapshots__/exporter-exporter--dashboard--light.png index 6450d0bd48607c5f7dbf0d38d362bc656b68996f..e160d331bfe63b1faa1f5e4eb07debd286556798 100644 GIT binary patch literal 111206 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 B3JCxJ From 80fb31411ee9bcfd35889f2668709a363838652e Mon Sep 17 00:00:00 2001 From: Ben White Date: Wed, 29 Jan 2025 16:20:30 +0100 Subject: [PATCH 094/163] Fix up --- frontend/public/transformations/geoip.png | Bin 0 -> 15661 bytes .../public/transformations/user-agent.png | Bin 5792 -> 3575 bytes .../semver-flattener-plugin/template.ts | 2 +- .../user-agent-plugin/template.ts | 2 +- .../_transformations/geoip/geoip.template.ts | 2 +- 5 files changed, 3 insertions(+), 3 deletions(-) create mode 100644 frontend/public/transformations/geoip.png diff --git a/frontend/public/transformations/geoip.png b/frontend/public/transformations/geoip.png new file mode 100644 index 0000000000000000000000000000000000000000..45ab21b98ec71eb40680112f42a565db7c56ff34 GIT binary patch literal 15661 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 By?Fot literal 5792 zcmc&&X*`te+n%uv!pwv0WUGR3u7d z&pzToku32a^}PL;-}~YHy&vB9!*$>Hab3rGT*rN#=lS8jlgyBMjPzXe004l|Kwrm# zeD6OUv^3=FW812G%GiL9)2<_7(y6fZFsGVczw{y;MeP$*=+%nh2o zvAL3WsE34Y&a~d$oC=$5*+dk5e#RuIEM`KWOsq@=3S_uwBnopt1nFpE^`rlnT;RdXsm>S~&m^yQcT#+7~Q0uH79T-8W$+ zA0vKYWs=?H>t+OP!iB5`Z4$(5Fiogw``cv(P<|11MRVX_=G_CIT@jyzTE5bn?v&&* zQ$W7c*-^`AeKFqjHJf9gd{H^n{6oytk(5^y@*LoKm*1QDA3GwrgWb>v#lrkUxbcJ% z`)>~)*v`-;{qaK!i)vCrNcXd}s%O3WV6GCeZ3HBKo3z+WUc_vE@c>5G1>5$$5us>|p z_S>C@sqS>{I-j(n7SvR?-xr{-NO1~<7aJM0m4j=zkl*8X4?3Eqc{~Zu&8~j72Yb&s z&uiGx$HrG1ZKg7lyq0Z@<1V}y!$!oxk#;xtjW;z_r=NdHt$MpUY?c$W)*oPFHpBjL zLWRFBO0Tva*Rcav!-MPIF^L_7Bf781p0A3vPuUH;&fzP(bt`@8B>HjDS%dmr=RLlO zm&ozGF+=CCGretFp<(>7L0C@7>4dWB$XHbc*3kocAOnjW>kRqY+1?B!Zo5&AeI25k*To*F>Sa$n(`b zgWYWH2*!M8W?Z5!=EC2FYbFQ!q-kl$u&(*5p5G0-WKUlStk;o|iDYtpD#X)} z>m*?=J^)Sx1aRvGW^ovU6PftEGo?(Swe-}8L>I6Yo%YKR2u&Q38RGUW4si7O6c9*1Kky`LMZ&ClE5ePRp zI{fhwI?P>ITtyjZni!SDjvQH-s~A?kz4BQku-E+%r}9LR#Ws)gZ{8%2tgRwna#q zJ5&-}M7uAep4@w=mr5wcc4e{2_fx7? zRyKQ4IP$8)#Elg|ge86D17Il29K!thPM=2PGO1_aI&`2Cc45ebayd;tLS%e&sqf4q zbuH}D^9RnEw{NG~CBz1MGN3u*dj&e=N2W52T$F1!hy693@^&G^X|JW(sSku-gJLKJ! z16N`EczcYyb0NJW#b@#g<432CNqS;5tF}UjlNk$SG(r1~xwrCGdL~t@bXDugv8I)E z1Wn0d>E%bFR~PkjQ5(iJo=st`X2*QC8`5lHH{MO%0kf8JjD^X;2`-}*hTRQ8%GC-) z=i@KD+KnNOlG1k-cDacNeP@F);w_fAqS>#Htbg=$KF+^}ti?IjqwI~DDfW~o@HOY7wVq{xE>sZ(|2ae-x)9a}4A;jbGsM`m7i

  • )} - {showTesting ? ( - !id || id === 'new' ? ( - - ) : ( - - ) - ) : null} + {showTesting ? : null}
    {saveButtons}
    diff --git a/frontend/src/scenes/pipeline/hogfunctions/HogFunctionTest.tsx b/frontend/src/scenes/pipeline/hogfunctions/HogFunctionTest.tsx index 104511e2ceaf9..1332fd9d00926 100644 --- a/frontend/src/scenes/pipeline/hogfunctions/HogFunctionTest.tsx +++ b/frontend/src/scenes/pipeline/hogfunctions/HogFunctionTest.tsx @@ -17,7 +17,8 @@ import { More } from 'lib/lemon-ui/LemonButton/More' import { LemonField } from 'lib/lemon-ui/LemonField' import { CodeEditorResizeable } from 'lib/monaco/CodeEditorResizable' -import { hogFunctionTestLogic, HogFunctionTestLogicProps } from './hogFunctionTestLogic' +import { hogFunctionConfigurationLogic } from './hogFunctionConfigurationLogic' +import { hogFunctionTestLogic } from './hogFunctionTestLogic' const HogFunctionTestEditor = ({ value, @@ -64,14 +65,15 @@ export function HogFunctionTestPlaceholder({ description?: string | JSX.Element }): JSX.Element { return ( -
    +

    {title || 'Testing'}

    {description || 'Save your configuration to enable testing'}

    ) } -export function HogFunctionTest(props: HogFunctionTestLogicProps): JSX.Element { +export function HogFunctionTest(): JSX.Element { + const { logicProps } = useValues(hogFunctionConfigurationLogic) const { isTestInvocationSubmitting, testResult, @@ -81,7 +83,7 @@ export function HogFunctionTest(props: HogFunctionTestLogicProps): JSX.Element { type, savedGlobals, testInvocation, - } = useValues(hogFunctionTestLogic(props)) + } = useValues(hogFunctionTestLogic(logicProps)) const { submitTestInvocation, setTestResult, @@ -90,16 +92,16 @@ export function HogFunctionTest(props: HogFunctionTestLogicProps): JSX.Element { deleteSavedGlobals, setSampleGlobals, saveGlobals, - } = useActions(hogFunctionTestLogic(props)) + } = useActions(hogFunctionTestLogic(logicProps)) return ( -
    +
    -
    +
    -

    +

    Testing {sampleGlobalsLoading ? : null}

    @@ -171,7 +173,7 @@ export function HogFunctionTest(props: HogFunctionTestLogicProps): JSX.Element { {savedGlobals.map(({ name, globals }, index) => ( -
    +
    {testResult ? (
    -
    +
    Test invocation result {testResult.status} diff --git a/frontend/src/scenes/pipeline/hogfunctions/hogFunctionTestLogic.tsx b/frontend/src/scenes/pipeline/hogfunctions/hogFunctionTestLogic.tsx index 7a2c07ccbd0bd..dfa16e713b36c 100644 --- a/frontend/src/scenes/pipeline/hogfunctions/hogFunctionTestLogic.tsx +++ b/frontend/src/scenes/pipeline/hogfunctions/hogFunctionTestLogic.tsx @@ -8,13 +8,13 @@ import { getCurrentTeamId } from 'lib/utils/getAppContext' import { groupsModel } from '~/models/groupsModel' import { HogFunctionInvocationGlobals, LogEntry } from '~/types' -import { hogFunctionConfigurationLogic, sanitizeConfiguration } from './hogFunctionConfigurationLogic' +import { + hogFunctionConfigurationLogic, + HogFunctionConfigurationLogicProps, + sanitizeConfiguration, +} from './hogFunctionConfigurationLogic' import type { hogFunctionTestLogicType } from './hogFunctionTestLogicType' -export interface HogFunctionTestLogicProps { - id: string -} - export type HogFunctionTestInvocationForm = { globals: string // HogFunctionInvocationGlobals mock_async_functions: boolean @@ -26,12 +26,15 @@ export type HogFunctionTestInvocationResult = { } export const hogFunctionTestLogic = kea([ - props({} as HogFunctionTestLogicProps), - key((props) => props.id), + props({} as HogFunctionConfigurationLogicProps), + key(({ id, templateId }: HogFunctionConfigurationLogicProps) => { + return id ?? templateId ?? 'new' + }), + path((id) => ['scenes', 'pipeline', 'hogfunctions', 'hogFunctionTestLogic', id]), - connect((props: HogFunctionTestLogicProps) => ({ + connect((props: HogFunctionConfigurationLogicProps) => ({ values: [ - hogFunctionConfigurationLogic({ id: props.id }), + hogFunctionConfigurationLogic(props), [ 'configuration', 'configurationHasErrors', @@ -113,7 +116,7 @@ export const hogFunctionTestLogic = kea([ const configuration = sanitizeConfiguration(values.configuration) try { - const res = await api.hogFunctions.createTestInvocation(props.id, { + const res = await api.hogFunctions.createTestInvocation(props.id ?? 'new', { globals, mock_async_functions: data.mock_async_functions, configuration, @@ -144,6 +147,11 @@ export const hogFunctionTestLogic = kea([ 'globals', JSON.stringify({ person: values.exampleInvocationGlobals.person }, null, 2) ) + } else if (values.type === 'transformation') { + actions.setTestInvocationValue( + 'globals', + JSON.stringify({ event: values.exampleInvocationGlobals.event }, null, 2) + ) } else { actions.setTestInvocationValue('globals', '{/* Please wait, fetching a real event. */}') } diff --git a/plugin-server/src/cdp/cdp-api.ts b/plugin-server/src/cdp/cdp-api.ts index d1711dfab4fe4..8485aa190f45e 100644 --- a/plugin-server/src/cdp/cdp-api.ts +++ b/plugin-server/src/cdp/cdp-api.ts @@ -4,28 +4,28 @@ import { DateTime } from 'luxon' import { Hub, PluginServerService } from '../types' import { status } from '../utils/status' import { delay } from '../utils/utils' +import { HogTransformerService } from './hog-transformations/hog-transformer.service' import { createCdpRedisPool } from './redis' import { FetchExecutorService } from './services/fetch-executor.service' import { HogExecutorService, MAX_ASYNC_STEPS } from './services/hog-executor.service' import { HogFunctionManagerService } from './services/hog-function-manager.service' import { HogWatcherService, HogWatcherState } from './services/hog-watcher.service' -import { LegacyPluginExecutorService } from './services/legacy-plugin-executor.service' import { HOG_FUNCTION_TEMPLATES } from './templates' import { HogFunctionInvocationResult, HogFunctionQueueParametersFetchRequest, HogFunctionType, LogEntry } from './types' -import { isLegacyPluginHogFunction } from './utils' export class CdpApi { private hogExecutor: HogExecutorService private hogFunctionManager: HogFunctionManagerService private fetchExecutor: FetchExecutorService private hogWatcher: HogWatcherService - private pluginExecutor: LegacyPluginExecutorService + private hogTransformer: HogTransformerService + constructor(private hub: Hub) { this.hogFunctionManager = new HogFunctionManagerService(hub) this.hogExecutor = new HogExecutorService(hub, this.hogFunctionManager) this.fetchExecutor = new FetchExecutorService(hub) this.hogWatcher = new HogWatcherService(hub, createCdpRedisPool(hub)) - this.pluginExecutor = new LegacyPluginExecutorService() + this.hogTransformer = new HogTransformerService(hub) } public get service(): PluginServerService { @@ -122,6 +122,7 @@ export class CdpApi { return [null, null] }) + // NOTE: We allow the hog function to be null if it is a "new" hog function if (!team || (hogFunction && hogFunction.team_id !== team.id)) { res.status(404).json({ error: 'Hog function not found' }) return @@ -137,6 +138,7 @@ export class CdpApi { let lastResponse: HogFunctionInvocationResult | null = null let logs: LogEntry[] = [] + let result: any = null const errors: any[] = [] const triggerGlobals = { @@ -228,12 +230,18 @@ export class CdpApi { } } } else if (compoundConfiguration.type === 'transformation') { - const result = isLegacyPluginHogFunction(compoundConfiguration) - ? await this.pluginExecutor.execute(invocation) - : this.hogExecutor.execute(invocation, { functions: transformationFunctions }) + const response = await this.hogTransformer.executeHogFunction(compoundConfiguration, triggerGlobals) + logs = logs.concat(response.logs) + result = response.execResult + + if (response.error) { + errors.push(response.error) + } } res.json({ + result: result, + status: errors.length > 0 ? 'error' : 'success', errors: errors.map((e) => String(e)), logs: logs, }) diff --git a/posthog/api/hog_function.py b/posthog/api/hog_function.py index c7e3ca2ca54a2..baff545e936b5 100644 --- a/posthog/api/hog_function.py +++ b/posthog/api/hog_function.py @@ -365,7 +365,11 @@ def icon(self, request: Request, *args, **kwargs): @action(detail=True, methods=["POST"]) def invocations(self, request: Request, *args, **kwargs): - hog_function = self.get_object() + try: + hog_function = self.get_object() + except Exception: + hog_function = None + serializer = HogFunctionInvocationSerializer( data=request.data, context={**self.get_serializer_context(), "instance": hog_function} ) @@ -380,8 +384,8 @@ def invocations(self, request: Request, *args, **kwargs): mock_async_functions = serializer.validated_data["mock_async_functions"] res = create_hog_invocation_test( - team_id=hog_function.team_id, - hog_function_id=hog_function.id, + team_id=self.team_id, + hog_function_id=str(hog_function.id) if hog_function else "new", globals=hog_globals, configuration=configuration, mock_async_functions=mock_async_functions, diff --git a/posthog/plugins/plugin_server_api.py b/posthog/plugins/plugin_server_api.py index 40c322bcf70a3..6cd970b4e294a 100644 --- a/posthog/plugins/plugin_server_api.py +++ b/posthog/plugins/plugin_server_api.py @@ -63,7 +63,7 @@ def populate_plugin_capabilities_on_workers(plugin_id: str): def create_hog_invocation_test( team_id: int, - hog_function_id: UUIDT, + hog_function_id: str, globals: dict, configuration: dict, mock_async_functions: bool, From f4ca3c782e8f01669c165bc89a10ad38ec196ae4 Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 29 Jan 2025 18:24:25 +0000 Subject: [PATCH 100/163] Update UI snapshots for `chromium` (1) --- ...--pipeline-node-new-hog-function--dark.png | Bin 134746 -> 137011 bytes ...-pipeline-node-new-hog-function--light.png | Bin 133981 -> 136288 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/frontend/__snapshots__/scenes-app-pipeline--pipeline-node-new-hog-function--dark.png b/frontend/__snapshots__/scenes-app-pipeline--pipeline-node-new-hog-function--dark.png index 53e9883e83ee095c50589ffcdf9c35eebb01c94e..fb8d50d32758d33108110ff089e805190898a369 100644 GIT binary patch literal 137011 zcmb@u1yq$?v^L83@ri)6gtQw8B}KY*1Jcc=LqI@UTG}9_yE`_q>DY9rfOL0DH%PO| zyYM^b{Ab+%-ZTC?#&s};8+=*sT63*6=kq*sP6L${rEsw+u+h-aaAl+=RMF7T&(P5B z?P2~6ev-MXbO8SP%TZMdidNE3wTXuI7)?gvmHLO2ojF&nhh4Y#f6F{^dKL7Q#QGz# zN5}l{{&^3({EYdj0Rv^F0r#}DyfpVb^9ifTS1*&)B{$pswVujiK_IB>@*4+~|Jy0w z!KKTS^K;+6{$6u4bEfD%QVlha`**KKLlgAXpZn|2r;l+)ark$iGOV8W+`s!U$dj;& ze)nO6qWAF6Lr%D6sJkDa)#s*Yiv};g`14IZYY^9O2lJKH-a|M}{GSJ3nyRFHiWhp) zK`!*~^ZtdeD7dNabSXZa-1Q50(= z=f#`es)`aq3uqrVKVowum4y4X4;$OGVf}elzCfg>W~6f2@f=5Ujj#zHPWw*dIUirT zn2(FmtD%Oo{V{BGOTV?gU0hro(baWL*T*?)uDmu#7z5Icr#g+)NX^M=R#dTBB#qls6dhK2RNs$;riK zHCmflAE_DtGn8_ha_b@V9ODXnG=KY6U7>Dl*jT^bsk^T$S^;rW`@R4h+w`lE`9ost ztSpmI9Nd%?s*Vt9ubV5sM&bG%IbuW+=f8GlK&Gkc>`0DHLK5+9(a@0XKTD5nwkf7{ zv^S1U-1H2b1G1MTj3YC&-q>DbDlA|1-6p(?5ErO?KYr`2rsi&%R& z{qxNQZu&tXBss>a64TcbhimX3p&98|@vOdb_(1o7{Aj z_2et{KGmFM&8Ez9nGaZu8&mP-Nu8DS>2fb`AEdW@*j!)Nq(9fYm??5aq3-o1@rZob zU`ndq8q91el(X_`_FhZoyTSka=7VxY!&mMe9PZbL&Mt~! z(a|QaUVZONcAanb=qSTQel{{^AtjZPcufQy9v+bjr5S>&1tiz(BP`1+U>aj(FSuv) zn~T*?K2`Lww}f`DWmolC92LZjtHH8tt}piX=Ga(S|M~mXJ83OX<1DkLuZ!ae5slp$O-EM*aib=oc7O$C0d)sXhVZ*L~cN^y_H($UQ_?4H~I;1@^FT@UBn zx32hT*zC?mj7iCRaYgxcn3P4=G|4{}|8@JR=}>8Q)l^R|Tg`v&+ET`M^sR&>QSU>E zr>r#3%M4Qmow>IqacaUmH4rYAFv%EPnB1M4co1TtIoSDwK<&!X$V$H@6aGD)S5iKA z^^m(RI`?xG%wC8 zC8Iol`|V?M1&Q9AUJlXb$+iy();}DE%~V`wF;2TDwyGzind}P+3c&SRTU#sEK~_~%TyPXCJDpslt8U&p(fCzc&Wwi=IGm-W zOVB-g_H1(+>DcOzODh(*JyN4-t4QO_T~kD4)FpO%uFAxvrQ_Ya(=f}MLyYxLoj_go zYutwS2iTCeqxnXq22JclnCiu)AKlEOnFvUelRR%i6^i(e?Tb`RPaCOOCOdbqm_ zDv-of*VUuc8&A|@^E+0tf`Zl5MzWun$ZsWJ`$7BM30sSwD2*u=BfeH-4v*?rlA@#K zmkaW+A*;*s_ntQEjXQ6#=XbBTBVfGEx%N^bXGQ z@KrAEQY$lqi4eh!r@C5N${V+jBi!d2;u|8%3B7Lo{ryq*?u}0P|Hz_KQ){ox)j?Xn z!YnT<+n7dH!r}KIIi@|N724iLDv?^Oxdx9YuU#$t5Ua#eO!xgJOwCcN)d{h1$$_*z zQc`C}$D>Iq-sJ0rQb9jSNo=*N3L8meZ`1*7+uPyvqv~XlJ2jDeXW1#HyuHAqA5|{xUTDeQQYnPDh?5dTgVHqfd~6yJ47D zlP^7}CJjNfe<`*x*W)!?s{&sduBtk2IJ@fZ)36AAWJ~S#=X&ImAk6Nrhsi-55%9Xs zQU|Lq2Kffp3%o8#*?JG7M^s;jQK3$EXRfZ4EE%ldStX}L{dm8MPg}ud*kW(F+i6^G zoMqB}dwrvb2}wUZ+D=HKpm+q%h=xYoC=pcJ`_-p{K&|k}7F%yK#9%Wo1vF#Ip-t^_jAh&MA$M&-B&it+ZftL?s%o}og|U6pF+HuLWT;d; z(<>4fTxp}6PH=eMrRG;d(;>P!RoL;BP<&*Vi;9{W6QX3CP5@n6T553J7dAd0EGlBv z)QnF_(VHR`<2YLevrtGA&UDNRBPZvWZ}yT?t3q6X06Wy3xvFXCR)$%)vncLynX5;` z2N69Ozcu{GpW$PeHy+lPG{rzqPY9i)I-M|dlg?4ai;qsbS@3g5#1#hTwtt|a2_M)V zEKiMv!_`$4Tb|8mEG`nxc{tfS+q)ec!khHfSgZuZ}s<$(Bv39w~dI2p?gZ)=#3KOvgFSAv1gaA9T? zg^Y}hG%^a8S5VN?OEGNezPh9})FvuWXC?~Vo2`@6dOP^-X0-e*OnsI1(fRWum>Ih6 zOylfqqj0K0Sd*y%jEED0Q-8Lxw6c+q80)+_OfuDHu8vHA#wK#JM(-9nQN#L5 z*pY`rvfIztROlkFP-iV}v|=us!{yBiO@o8KS5}DsmuHPZD8~-Jc@G%Opx_}W zOEqe6*#FG&Bxw0|?BOQq8=^_~~%$^wNvo>qFV8qbj=W6^synxM{HX!Y(Kid2A%~4w} zn|0!iY*Bf258WC*q6I7#Igh{rGl*u;SjPDasZkP2(@QdDd@x?NvD zcYG%fj@Y#AYH5MkT<^}ZakWX%b(vyZ{Fem|!8lgpSWA{$FlN~N=!$%2pU>OIORWlf z=m?$BrfW;QE8okxtW(#b+2O{vEPBf3P)LvRSb-2-V6C)H3`1{sbpG}G%V|k^qPcpn zcmf5+TtwP$9T_F>;&SF;eRa@Y<70T(gLx5Mt`=^*So$Syq&zQuw@}q?IFSp zE0Ay({k01Z*sna4wcb87KdV92XPcUZ{Vpt4!i03#HVL?FD|`#%9SwYmc%kyfy!iI1 zfY>5zv2P;Y5u(aXCv-%te9Q%EY8416Owz?CTX&&oMd6W z`^Z<*50M2QZ=t$MYd79aFkWa*Jm_cPxCvA~RHj&i4QibjlKdg{$ca2UPUlI3*!h05 z9)F%JgY??uvElJIy9qy8NjHbPk8KRPR)3vEdrT2P8EbW29rKr`M-vE)HI){`_Dr1I zUr~BQ?}eH9M%d!zjy|}MUy-37F3TAhu-4x&(j5pXWrkkh%J+K>eY5o}67 zqC^F_xm8KnAUWkEKCS1uz7gQ8$RvJ#@M^z#>rJLcpw1_4(K~~_>mQ9s=E!ImtS&${ z?>IZ@`~_+AvI()qI=dZUEimcqBO~6sJz5RMhi-kSQ_i`1CeNMNd5Lma^UF8`JBT5C zU3>DwG4J(jvd{C^gy-x#m#3DdlUdDwon$o&q!Z9RjU1HPo}HAAs}4;)Fbt2O>M_^y z3;M=Nd-rng^})gCyxA{}7WeThSmi%_nR!OjYDQ?G>#^K!$itc(KCrsZ_oFhvrD|jC zjbLSZU)41hLN=#MVtyqoig-C_r4f4Pvl8aqtf#EGIzBOOh$LtNYQ z_RZ^fcqpdY5j)*nmt%(RbfzbXRj+KebIq{A7W%2?rx3+WSdg4Fa*n4r%Po;p(8v{s z-yWnWI~_ay05am+0_WvMgMPE)#7@fTp+miIy4!-n0sj7x1?fIEPYLB@$Z@!0q#o{G zhG&JYk9Z(?8z_^buU?m#1f^=os*`o}&Z^FFZx$TLHIb1KlS)W+Ftf5UGY16)Wh&s_ zO&S>}W%VhPVa_k}dan1RwkUZ>fbWM`V?Qb0?z?hZ>xbx~0R#>^8fF1&jMPcVs#d(n z@tV@Sec(1^I|EkuK4a~T8yNDflxh1$uoug>Mv3ZUd8eOhl&x(0pV6!%k$kyCg51v= zZ~4!3HUc8`S#eQRz{Jj$Ms$%x3;B8r`6)|8UB}J5yk?CfXO)7;=jEKY=`8jZFVw#p zp5`Imh0k@T3{B)pKNWCi;i^$??9Be&S)ze$u_>p%wHr{wm(Gk*_=r(h(DX$ID<4Y) zl9Mm}i}USdaD3?s+1~Fdud8^qEi=r!mGX>#vKO$$vmK*uCu8Gl@MA+Z(bzz@ou%H|iiBX)ps(FhVGZBVL--_*oMz6I-7-DsSK_EhS1PFKly-jgol& z@YFyL5Hs+b&DR07$X#xy^GBJ={6;?}N!gMD0z`-cC43`XcBkLh=>Y|T8H;DmZlW3& zvSRc;;it--Yy0R@uEqMQJflAQ+yN`-9!#)@Ro(~3DFmHWhN$W7xtFNw3w+5b+lp<7yn{@faM#ZNImlqIyR=TB?V$ zKYC|hE`01*g}EvVD$D`|8{F>mF3y90>IQKFRvA8!fQ;E2fz|JIXV6O{9eC)-Q;oRdAlfXZ0s=D8nHQ& zhet6ANW*jfoX?Oy`$g)PyNR?=KV%L!d`!Gb^0p%ID*;mJ@n1aU48HM9zr0!3%C&z) zaEmyP05Mxr_)LtRe&>poj7jkklWxWSw?*&k3oTvUg}M`0gZU0YXL)Vb@k;Xp0Q!9i z%C)XGRNn5-i)gcI{B9+HO22zo3wUn-7S?x}KjJr!%#PKynZ`X;I^)V?QIaD}m3>H& zGGX042HFQ*%kt>Z97_h)^+wOQD6v@LvexxWz+$G-wY9ay145gfP+Nf8goNO5aV?|> zI=lbK*&Lm7etR|&lbUU)to4ZU(zm|)2|c%7@m;b2?WJi0g^8XPYkxP3cO+XE*{_%0 z@E$U0gST??>m**RtW7tQ5d;Ml76Q}&;ReX!{1MWf{B+Tc$b^i<9m}7Et!Y*${e8$B zPnnW?Mz3aAN%ddiK1qEv--p%e|$cge)jzJA&mV`I^bh>7C$uZC4 zR>P?$)uSFeq<_Xy4_U%#oFy0NpZo(C{-^Z8e+}D;tf(m2d-Uh`UnbXTYugnHVf^`S zkTIqF&+Gn=@)7?v;Fo{lz{isB|Ayw56L0=Mq&5EcVHPb@qB@ojPtNt&1Nwey{<+0y z#gWqVOT(&}%1?;`d3s*8YBzuS)Ld0n_4qbhi#1A`KFRdo?22jmlr?*Np52>oV-sY= zWL6Ph7%M%+(Y;gx%gV}LeyRF**-CVgF)^5Ovc9ihGh$qLzA9IqqZ$>R^6yxdm6hXc z|C5W!xAyZx5e548tmY>x?`n%0DwWo1W!V0?lxTw=m7fl-AB`!%Pg&D@R)gBPwEQ;So}fx{_lUiO+l^UtRM9`TEZ&Jtt7pBdEu@5}x_uwIgmaL+)uJ8HM4 zXEomQa`&*PC|;-6aM3VbvkwL zsUV3OVKc+)?d>`|5-k-*r7~suEI*E2=RhNNe=)Wuv}&Gz8W+hdbt&LzW@g$%#r-Y= zibhD7>3Nc#i=~sJ&6n@2c8}b%9##$}Qu?=MiC!|lJz~eu+PY{wSb5{%Xx(GJ&QWt4 zuAavHpw`7pL|IdT=G@Z3+7#A<=e#pXNE*DlSx5i)aYjYYaEJ?EmWwWu5#N1!S8_22v-X6g)ZI*QYb4+S(in3JN`DB?xQ;w&iT?K_XY)@G#LsqO_~y z5u=9>qbm+7r*_lY_hAnG1(!Rp~~2FTmNj87m?$SWz~ zVIQXR+Q`-wzf$wMr>GbPgn3y71^)NHfDOE+4)^97=48{R>>awvO;8>;0(=y6KCrRbWE!E@~<&gO>gTAy?j$^*Tp$DD>}><74%9 z#&9!DRT4z~Vyul8eAkd%DCToTmu`(q7Pfx#uM4!2aPheow9OP|C34+&NBP8I9x1QUp~DUh=976D(6hd;RT;>7L6o!-cZ4 z-y|%}iw7T~85PPZt?J#5G#KH9TX5`d@1GXDe{(%c%H6|hpi)>E|K+Hhreney?QStk zk|^)2j(f;0esH)dr<5)V#2e4s8=Lz98gg+Tezr??bcks;xbQ@i#&vj_tGM2>1?L8g z;8Y7{xC|V+38!TOA&bew>f1-}jxg%LV2`cMO%hsKpPk9bo9jzeyHFrJb%YhadWA0{ z5?`iOG*xN-qRY_xg{;Z<&u&0o)~$DP)6ww{AK*1#9|ug!>oSGko|Ti+;``@ze42lD zrYfy||9Z8*;FG{_&kUhwW_Dj{D*yo-TYE_T73>w0(#_7sMw!EElFf|g?3~B>xit&r zVuk6vZngE*I_-krgZP}B94f)87V&`7QQbSH3~sbt zXs&czcbUS3+n)3QXsD~Gh!@;WA?CdUj$DCXs*r2_hXYTr#HHnBkTuIyOuf3?&%=Vi z;cz~?`K)mn85so`nM~fLG?4}h9?M6Bgz(Z*F{|+qkgHWLtdJF_7I0|Wn-l&19rA#T zoe(p~yg#Lc+ncRA6Jh(@d23~7$J%ax_1QBE-5Oil8GsahQLj~WgkW6{iHH=_MH|3n z3k;MArbtytHNB@^uu!P zG*=vz=Q!hJnz;9Z z{t5fuW)wr(Kw3M8UTp$1Cr}J$XNziU69lO@ZoGWp>-YYgCl?omQvC*d)FBKit*l(8 ztfj|06V0TALZKuj+X?&iw5iZdwxn@>l+jhC0WN; zd&uft_ouU^YBDl@k#nsn8}6C+CZvLN6lPa{|9suiWkkdvxZs3O{HaAQmcM)A;yXkX&egY-m zjZTfNf`!HAc=7ECd#Fc2$nXF^?&~-H?F9&-BX+5g8uGA3ko-9$CnX^nDlK&iF;(RV zZJ>)T2pjI*OdeWT0GU^G--tLiT~XnVUK!H;#Hn?yPu9))Th5F9We1qE0Y z40Mswm0R(v`%1g5-o6ANz`36k0X%-ZF_cPBVcPSPO_#TO+{xY^3mcm~S^VY8#}JSM zy*XMR*qkg!cMb~HiG@)hzK9n(uHQs3golKv3igza^5$TLyZLW1-ku*UgXy^LpAusG zIj;4ZKcep|k)TCjAfxTHl1}q7zvHzF*O~pd9eq?nPC~-wK}~b>?uW0vtfSh0_XP6( z3Tt;^Wt4SicgD*fpVob~8zhn5`op<0x40DN2ooSMXY={hcNe^^oWg8uDx%_)$*)Oh zO^`DriAv7IgM21BIv6KeWo2IpqEb`EF`qqs`q=l;qeq=&OdiFS$8VY|)!*O3|6{+# zDz8Mn+`}pPrU7R)YQ+GVqSYQvdwZq*g|o|bTElX!B3s*1Lv#NCv8d~*%Djdhp5XQ< z*;{kdsiu2ctkqh2%({wdCD0prY5L)c2imNp$;GE#D%>z-*x=rr$1SR(#^=_1uHO0J zQ0Mq)paozfNh507bQ=&PYd;(uUSHk>;87o(T+2yH(i<5qii$Rj1!-HTcb8#89FVAi zU^0$$5f8@8%AIEfF~z!keW^l13=AiwEjM_$xbe5fkHd2-5YW~4H#4e;Y8`krMBzg( z*XUz`}*U0vPO))>n`+I5p$eE&?fO&YIFoF^1k4mOIy zw_6y?>(a_9U|7~6Nu45u{`k*6?R-1D2f#Z70#PX*tK#>6-W!x_ufD3hSJw5mBSw=a zHJZ_S>ZVh#-J<|8!0#;e#tjjaTLAo9N15Ho+x%n9jGrdfrcRQ`T*1RcE7Jkf;U6I6 zg`OG8-n)ORQygT50!e4B`cW1-I)@*=&;I)B)960q3*gAbZar6q;YR?=h&im}YsdS}9y`}@}?*~sHe*HQ;v96TND+#*_z`Opi)>~lG5vJF2 zV_vAi7SC}e3cPQ9i*^1B$F*Y+e~@+yVeU^~Sy()S0F~pKj3Z4Kxi#|%{k}9j3go_5 z*VoT>P$;5A5M#J4hF3Zx#la;6slxc^Xx%);RJFV?CZ;@It6YZ%FN%tKIa*S#4m!Tv z$KN|TY2tbR#z%MNf}|_XjdIutz3+uOA8Hy|sLfj8b=eQ}w=^MFGk{H9_hwo6c$4&H zz9=U$Wm@>K?J4WXC`QYEiS%s_eql%IPhG2BX?A(G-{idA9|8H2nnG1jS&~?KcP))H zxI?pT4s$0CtWX;%a zYf@bP8GNv9_wJEBhGRF9FZ*VBB+`Zl)U*Du3DSi6XeMv+#tQ^7=mS{|TRyZg5p zJKi*um$gdipW-P)}~F@Z*s4r z6>-;6QrhoLth8Khv>ZzUaTjT&$!4%q=e&)_Xr2TJsa<~xDl9BaDxAi}b-x*aWdJhO z?7$}d}A)hq)&V>ONYMSPodyT9Ww_p%ZV8(q-ba4l19=ZG)$s72Ma=(wavkUH6 zcdQj~GMVF+I#lA1%p^zn5e^x&yySZchlpIqVPAc9`Uo+lFM&a4s<3qXt*~#0>Ldzk zcd*xJL8AuWiuY%nE=ONI-BCdpbWLZK0NCN!RBwIqwf)e|vNN z79ToXua>6>N)bY-h1>yZ2L%*cn#Hor4_*fRw{gmkF89rMc9LAS$JL5~tA<`*(*hZX z8L9e5JKD&haQzupSXepSfx9zXcfB-e5ItM zc@bAxIg+muw7#mQsx|_MmwLgce}^jT7U5{As!~@{D&098E1W=DPpO*9>#>Jxj^VHV zk)#I6V?9`bA9U^SFCJ4^-I*2SI#O7b_~%Za%D7~9jiaT!oa~ZmO0EP!1|aM5@^BWm zr+?HdYR$SzPcbncaq(9B<)6=$Ubr1f(1g3L_s1V<{ac`1TsW(J170iU**RbD99>tJ z_(KQ6Qj*Kj+O8}v&fSnN17LfshV(=LV|N2X10JjS!Rlno8AJv( zty+&WJn$+2i(+D8)Ggo0di^VXjc#9_+2K>3@|@KFcU3FXehr+HmTQ-~IJ#o(ZViQI z2CL6Wr1+j#RWEFx(~B7Z3n_#3DPaN^0sNSURLG;X<;u|3#eAK<>FJBMz;wWJN)20{ zv9dN+WHV1(q0%ZU{lln*ei)Yvi^Tu)&p*!xUIpZx7=ERwy=V1C8VO)ep4l>nICf-D zxF%Rl45HR(6<=&;Vq)U#P-kUl)*ZNg5Z8bPcv_N}IQ5mV^$sutncIFj4!8%{E9q5P zMa9S_+y5n8SxKNNC#$d4LjGgE*IJ^c9bu`<9ZJ;|6<5pQ3qXkix5%y4-}&-%7YnmC zJ>vJ<_5NMpsc3mDXUk1G64w)BPaIH(aNuM*JEfa_ZmR);W{?wJUdmctPA}4L(DCkD zYz?@$T)f9fjGdHZVoE<3e*?88(4uPR#5N^sRw-XukT(K7J=ckQlL0k zuKFh96*`z?VpuqU0S^H{xp`&P@{{SZhHacoB#&IX}~-`tZG^2~6{2gAggwpFGxday^< zydLw70^h&?1%#zfpS(?ehGbzC%+?Q{X%#!sxXE%IAL~UVqGmx#45XfP#aOmiIa;W) z=XEO0Okcl#kv@>;aC&Osu+sUxswyWTL5j;vUaim#cCW~3LB;hU7p)B@#Iz&SAz2;P z-lL&mv4WjAG77bL#oY>WcB2L`?j9yz;SU6ZNUH&h5i>yjre|gpQUy@}%=khV?OIZk z0h9@+X%dG*$NC)mQ?xlb!zYSR{o~a)eh*qe76kMcIoqrv^j@+W{U zh=?=+Z6=&rXt>44aH}}-iPX;g?n5f7iua2|pE3YM*~CVfzZFjZZ#ohv(e}AP)kx4C z{C*@+{A-aDVBpD#vp7%0*}hn39?0kB#3`i;3J3|!%y_uT%1)0LCj-bRj=4G4FaZbv zD0{4W7dZi3h|AurC?Mh$JR)v~=2B7tu`CnTGi%Fx^Q|4`x^>8GKCAKGgoK1TN3t~l zc6Mjh+X791p7Vf|)%*A3Hv?(<0L~cGP(ncM9eFnp@G5FHW_1h-pt_jrG*|fLJpSmVkc|aC+wxfVUg#-Gz=yW2Ch0T>b9KO4Wz* z^|^+#g4Pt^XXDwg{Rx(~Jw~SLkjcq~o;N`81I%TV+s|D^ogNbx_w4s0r{j$QUjqE_ zS3m*gc=|L#38tr46cI#3K>>76-mPVU7cYtc#2*^s?1^&|@gQw6Zl@?v-`c={cyL`=*|OsdvnVJWr0)~Bmm?b%H4-bFNEKCnc_t|yNa z_xAqFS&81ioDkaBMCReap`k;d;0x$>2v9wDn^Kr3Kr_JbaF#V5;>U<}&x?YXVYbe) z0+?js;2O74im+SvzyJp`vp+#pO3Ds6YgQBegoKf%;>N1+z;=kOa-B_K`D$k<^ar47 zAlD7h?NqkZ*{@%Nz(Q5FyD@2LduwaV>9N+dfgvG5Avlh%`-Eg?F-Av-ET@H`HxIeU zZJyrUy=TR>!`%o9gYG$O?t29k1|Xbi6b@@QkHg`n${g4af-xaLM)4)E#4vgD=4BIz ze0SoAs^;G@ad8;;13DEo-@kw7yf;f*F-!L(o7fB`tKZ^n0$eMouI=mXP2#fy(IVKW z>fiutGT1R#`;j;>;MdPq(| zA-c)M*}3lhV${w6&=>N_a7>=~ril40UZEaTh%qTriP4hq@lgHbk1yjUzd)hX zqz-^EQtfe;R`1*gKAG9r^erD;UDI|tDA$K~W?LA*)OUp}+XvoP4J8Z*St(CBSY=i{ zUQ+-#04cqjw#xP0xI9O-ouOias^4dwwR|e1-|*sc-^XQV5^!5I0M_o^yC)y!rAkj! zP~jIen1LS~(mVKX%K|;fY7Vb`o zGz3jyY=3ry9?T!y4xneeTWHn?HRgZ+{TWnVdK_=q+SmXPWrbpqirssHP5xq5$@I@+ z4X0u>^uBb*={|tpi`ytHqu^J!E&fb4VK7T)Iv|FG7 zqidH>bg)>ehj?L0v7yVJV5|o&StkaUQ(eH0^l%ZOX2s}@%u$2Oh{UUkIAk_N<}XMpp=!BH8=*7wc*9y4(Y(5oj29q|@F$O>9BU1+@>t;&>6N#N*6X?AqyT>mrqC@A1Ry1s^+E z_;5yxvmHniSq6Im6y+~J1l!x&0d9N-+(V1c{?E__ zKR=NqzJbcwX;9z6$jofEI}HPw0^m5mtnC~Lx$gBPaxDxE9c&D>f+i%8w{_eexA8BJ z4QBiSXyC>9&emAZHTXw&cXyDOf$<6BR9*ss+#iF zKZ|&HM=ZPtzFUh7{wKy}s&Xqh?WfT~e6cg~^Pj$WfdJg&CJV->&Qpr`XmLs%%2#g2 z{Z2KhS%RxEU-v;0m?TkWx*9le$OnZg(HfQv`Bt`ZCu`2|b~T~jsH@4zuiF#1dQI;7 z&K;@^+FGOV5@UK_z>2(j6IA+=g)~3`45kREg2>RQELeD)*X!1J>~y=OTTChZVG!&^ zVp7u0Md6{zz_&pFRVPaHy;eGvl+uK4#t^GHG7oKMmEGXa-r?Mh@auuE32F-ne*mEd zwJ(7}m~@d#59a{22K&0%_FxeJY=Ic@1#D2u?M+9Axu!~FM*Z^evyE~9^#xykF+u%D zF~{gT(6#9dCUM7TK63n5=jQ*A>(3YIiI=9g=4}QA=-`j~J>F!tNa;~Uxb5HNTD_}> zqYD3)KT!NS{9s(JHKz`$3j7!tE-)~*a{n!OzcuU_I6! z7Wk6w^-o6McA^3w)^xRwUQn-RZ2!zqDSTjEE-)s`_TRF!4D95-HIPXsPkzs@2Pz@H zP}L@pus$U{tkp9yQDct}4z~X@Sd}zgzM4NXgG+B13#(ti(YN9>7%#%o0JtDq(Q@l0)6ag=cfxeBPC3=+XCcQ28=evP5f{B4`%3 zmCMP)6DbS-Z2U=rZYV<$Xg*<6ps+58vUONH!%fF#f(3YaTVr1YFR8XBm6rhmcBt;|RA z6Li7mf8xGWR`ZA(SUqerF0U^@LqkMqv939cHSb~Gtz-0Ml2<_#)h(HPN*g^ zIgwiIz{)uQ81vNV8mMeZ-5*6xXo^d*=)3(m=XABbN&L+%$p3pjH*_1+dYRgtY|b5BjEOBR z(}{oHE7d8NR|?nUMIB@Zj6^Lfrt){2`OuVmRoltRh3OvbszSde7jijQD0op)LOahI zs`D5+R1Be*fNEmgTu#KE*Gk`dG;`%DMXt&)Bnn%2j%EkU8>DeF=;&y9wpe+5==|cF zr*dX{E8?-n?I(Y6y19BefMnua6mqp*JVKEaImh>>vZsh09Uo+WE&H~s%a`~iq`t5@b*1Ee2wm$-1ua`|QCDTRzkR#7*bVCx)v_F|tj%jv|Faf=3EDZl z%91CaEaGt7rq+_i#>N(lf4F0}o~M{z>As5r?d*(f<);e!i99{kX};C5^uVt}}@AjPRvSdh^g!rS|wd_9#~lw z0^X&9QeGh4kh_{V8&4Q|5_awMobl9tcI1QDEgItW3w6*W{TUO&`19wuhK5~51$Uuy zO-U*iWT99eH8&kw;n!cB3Wc`vOv{3$aZ}x0(wTF6ljfW5Cv0rj=YfVy@@eH}bBs{w zyngELk~;4>1Xl|U?O=Ah(tD9f-s!i_o6U9~~WzYHfV+daCY(q}AwC~>! zzzFaKvSn7Dhb!FC75Y`3nXRGh_*Ut!tY`MCTl=IQY`5EXhH4K-5=Zh+f&YM{?+p-X~ipsSRhQw=Wjn7U(<#&i|K|=*PItgAW%M7kccPF2(ff5x}(vx3@!$ zfNbF0ahSONSpb(}GB+3A-oZo!owO!}tc>JcRak|AlTT3LaB`8qkr3G$(A{#vDw=5$ z;BtL=5*89PQ?=fqXBDxB7GK1Fn8~NVa2w`_ zO23!RkBuGkJuqPmp%lk-dqgB#d~~es*@3q^>){j}=r-FBF9ca>4cGx-Yh+X|Q{th? zvUX*z41JXIHXo>sHW2Yh5wt(vhR|4z+r}_#ZqAD^u5q5+%g)I7J$HFVF(D`{oTZkB z2bxwzJXan$>%Y3b;61`Npv69C3jEyWG)Xx6M%G$yUAb={S z2v!1bRT$qd;(e$N+RUAu8GTbOPOPVR937582ONO$5lL}o6IBIQx?(f}E?aFwZjRu-AH%hCuHdXn)6qOxRw@q#{eTi}7i0HhW7u;@R|POYQ6e)rYG?b-iDG ze;mHJWlfHTc3vbH&f}n4tFt3)_v?maqW)8wocR$?z)3yc6;)ht=;CmiFqv)`oUqjE zN%q+F6dNmxK~|VZA(15osH2l9$SKR0rYABADwR|%P^Tv%AtjZ`Q3VaC7|)pJ5-Zo6 zwaa-SoOQ@(6OcGeYiBCbZa7#e;4+=iD;s2a!8|=USQb{WSyMRP)j~pW{(k-kgHn2X zjlCo2SZhfUZu06l!|r+*9TiS<=|@oEeB_RrkzFGr28({;D=~DxiUM=CSlLp&rt(&EB}q$aDk5H!)6d{C#IYsUSP%WUef8@cYowxLK8}(Lh~Mr1 zrhBZdx1KfWreK7SS-;p2eCaJ?@yfeDs9ZwtZEZbm$DxmHv>ws5><0vt>`knf1_Pwc ze4S=XJJzDh-Zug`&RjU$D$b|f@uQA{Qh3MmCNQ0b>tn(G&d&V1hF+rBhFrLxU;RS~ zwlF)xzj53Fyn6iwAGswQH()gkHNr|BSWnO;G7+iHO#7jm;%`R6iZ_7pl?DH^K&gOv z=yq9D6cM`D^;Q@_(HXgg{o2j+w`x;UdE2QJuLxex2e+vtOmx1M`#; zbTnly*m{8JK~tfmrcWehaA9uk-Sc<`FSPuvn~f+fYXbzd%wn1wK}D!MIqgo57(&~arV|xRfXNx=&`{90YOnfK)R%*^$614E!`m9p@K+vOLt2*s7QA= zNOyPNh41(N?!9At_x|xQ7{lY?aM)+>XFqGMx#pUS#LZNHK%&wNzo1Bx@#&EJm_BY> zzn{s>8%4kfCKUMD~xbW}j@9sxxAG@l%&L=8z1GrnM<*OfS3vuXt^xy{9@~>Nl z;@czE6UVWab4>f%L+fI8*zS72^2TEO`a`CU#cfn=(;Kxfoqo^$eaHtNQAv8aX~&*M zRnT6qpzVj9gYhhTuqMf!g-l1RLK6{1P7mpu3eT%{T*!}Hhw?7%*DH5IKCdaiZc~F$ z?u*AGyR7UU=y-UxIU#M~uI5tWeCOdN$HzzwnXOxuF0-4^;2L~>3{gm_Ub4RTQE%hm z3Z9kT(0ZpON7fk^mr>l9q~zM?#+M&kUhUK~uiw0K@CoB?qjTQ%EO??5X+Divu4;?fu7=<)_(9 zAAR+U>Pu{u=}$Rk5>BDPFLzNK~xS!n9!{Y*YJhv2y7JL>wT#hnP-kk zKkmOAMFMLb0Rmg;#)2wdJn( zG1c!1l{^UPU2(iOxyu=?cX(yl4mXDuH=jksb9t3JuI3A3Hnr3-8_bj=>Xf(5gH8`N z7`lhj13nt^WLBc3%naWrbC8?QNzv;lc-p8NQn0b2bCvLB$(&HD2k#SI&OK~NS26IC zprn-C7AJp%eb4kl(rV*{kANa|_>yk*mT9mIdAK)ZgTXUY1vA}??&w58LVT%(uEC!* ze;t!AQ@x)eLKr^8575)se{o8t%+wmsKTHW)qdG zHO4xIN+)rfw#7zcUzJ>U{%rG7s#ZG5DkvN+h_1fBe_3g)2<%J%U@$fk>k-!M&56UQ z!sV(cM(+4P7UQKr6J-|nuUtE7rG*L#oP7I-H@$28M(>P{SdT~M>zaL$-i6CioJ_fq zLg9V4)t{h=rAW#;#-1Wb=oVk__~DLO#2FshT>*`!TLM7>(R3-;;b|`9H$P$h4B`GNj`Crq+Jk>ZZ;n$W2<6qno zhKI@A14H|ZKixR7Ug_tiWdDl3b9U^*yvp(jX*yN0*qt>{Oc9bdb)=k_(-60di4j~o z_Ct{}?DlQ09Q7&Poj>ON6REml@h2}gmRkp=B-x;izx;Js8fw)ik5F`G@m5wHmNwQ@ z-DAV~tA;{K*&gYFJsT^rF!zOo6e@U?G8VqyNFXh@Iv!1Jlh<*Tl6FE0XlgR-6YYv(6aMLL4nz-* zIP7U?u-b2(7ma4JFn26E#gCg=XwLo0(^lJl+)qG zyHsqM$3%>lhyIQ779!kkB{ z!wy+Dv>yf2z2J8useR=8`$rZr-l`ZUd*DLpLsB zK-0CeacU&SL1^M*$!lc{+TK;Bxz?LpEBfoo`?Zfq0mSm*?%wjdPOe#7)4Z0Jn#H(hm}NE-?@NL*27hK5W<(za-;6Zq zlFHnl;SJYuqaBGnnIfhY)h#0H%z0{y?Csi~NBagP|ikPgugGe)( z54UDbtJHYDAc?pHxQW#yU|FG#3kLPEw;3M~83qMw*1 zm~B;=HMOl%-ro*$=)umKwewCoA*VLckBbVte~|0&tqXl8)1W4Mqv6LJjHoxI-_s0j zzKNp3Z(b@@amL zna$mE8=F$2+RIY;u8q~fJR74C&C5^qVLoPKwPR!1eG7g@j{9redI!FEW2N_Tj}AyD z&hI=d+4-n`T5nt9V57c!UUqO`Gg{B>5+aatvM+P7VoOAoLllxD0gt?VpF4_UcVzl3TqPkeEngeNzuB7CtYOu}J=6k$%ZOchP}W zi@oia>|$$~aXtkpwq!R3(H^w_eS3bmK9YBqZzOM0_eih(drVkt&BCc!x_E4{d7oU= z+9pRQ+VeBn#6UBJvc{$+%VTd@*EnwZilc$RZnjM8 zEA55zQ`Q@6Mds&d#wK=F41q-%M;Yamv@GG-SdkVgGi|p+tkgDLtMg-T~r=0 zGBVOo|LKJq;#S^WAMf^#z3=z38Cxp^g=_ADxs}f1+)~^nra^LI@q<*0fSk~@bn}q7 zfaL~m())ybr=Y&vzt`HSGNk@OeuMJDF~R$j%kPJy&XpQ>&`Y{+_Ul1% z?Qq^_CvMKRmzkOZ=VAH>^^J-RDwM9$ncISCOd6R)tZO}%3~L-o3Dze#DYJQy;0uX& zbyJDq@l1^?l?piSU|DWoL;S_tw&RRQBX~*5mB*mRT-$%&h4vCbMjUf-ipwL~m#(1L zrJzZ}=iS;|x?4PYib6$2oyr<7X<1K%c=B21Jq<^SEd`%xCx)0;sH=m!2o ztjG(iYyleX5lp*_>3JYwL>n~ zBWa@>mfAT^derFB-ihJXqw1wz?>#j<;5mE(n9akWuRm4C<7$f#5>a6DJ43N&%IEGpz9lUbO zhdENt!m&Oii<$<=`$yF6Ht`>{(IoH~*h(&LvSEr|R}QkFwHizNGl>kCS+<|Mz_hqT zt#~9(QpfP&I|PYA;JKk2uoEl@0fUkKhl@83F{t6=N%tb3uC)bc^Sa#t_Z z@2MhNoay;B!7gGReBz;vg2*u^DzfWmMkIAaE&1PS#p{8qcT~S@Z$4V@ zeoIMQ=h(uBOB-9cwf{M2M=)f&2>lOTQDCo28co@mo$-V!S! zX~#ZybZnnuE26H#{+E*?86)4|qd}hX^OSzkJt-rBh1qqmdV-94p)MrI?C?fW>z%&f z!|Jl9qwSg#A_@JZgLVRl?w`=2WFSqZTD!HFe1~41F8&-E#rTK_izsN=TrR3dtnvO% zy}$p=;iNyv{b;y+?2$4{uBEv_Yf7E)_x;>}&6)9KE$7p{yj9SNS;Ak>5sn5nn|}XFtA%+}*QU!uk0_{{7{;eU(cW zXYREL%Z0V$2wHU#ca=(~eYyViWY_1o^u|lS>gxurtu9~b^d3!JzAinB*^G?X-VR{N zyCnQMbvV2&ZH=eo926ZbH$;edbKbjHulD|Zbx3-ri~s@Ck2(=j4o9)BriP*7O3nz^ z;II&W?bdYlF!Yi42icn8)ha@R`0`liB?qLUea8)ZJzLSw=gDdlC^^$hkwT--495dx zJlS%%)r&eO(u!YRf7ROBW;gC&`?++QerCJ1Vcr_gBaa)O{b)M*rtY_{Z&jAI9RaKN zeC|d?aN?V-qfIqTRpD7PTFtl@v>KnExY$jPx4?D2wWt+MKYB$w*IE%o%$+A8@e%`{!{YT^E1R-%Olz(Y zWp-wfLCbc0dvxtZIy2hll>dFSryRN|2UhDGi59E={tw}&?t#aadcfJoL`KSRm*krn zBf7UDqoYNlUz?5(OJ;e^Hu^_%8hp2~(8bGYle~Cmvy3G+gKBf2C50#y{D7uR*#pa1QHliz-6tYJ7BQo@tBGMor@6~Yof z2WeJAq@@;}Px$PPI%KIL4%d1_6)Eg*0UDkYJOe+F8|oR@$K|l6KGq#HDv6Qm%gjd`7(PA?9ZFFoX5aTq z6#L0)#G9V6dFRXr#5Z?uT@R8V4;Robtjm}F#gRnrT|d2L#~GbQ6;QUq$S7U(`!|y2 zX_8r!Q=J11WAyzx^MefI%MWPRdwiw9t=!cww|rorkJ_yMrSlVGhE!W zE^G$O5N{vh_KuE=QPea~w2jiTvS&EQcoU!NE_J?I!bkMfaAh z-7Lw4(&RIZNA7{E8q@6rk7ejs5c+ndCQ}{r7SNtU9VDcPWwg}&YmrW6wA9&FwTW-V zB~;z0vnh}}PP@(E-M~~8X2lbujMix`DJiAa`5oV5r-z>Pnl%JQ3IncSe@Ge<78WL~ zv)Q5O^!}~JCva{Izb`1*(oT>+m4b=9i3IPS`ib@7?T~iSMvICwIhnn{w>P4Ul~~qC z96xM)kLrkF_3`#cMXkWMU0i3rN!R7?9(cz#Im}Ne)zH*9!QkX)qB`31lM+75A+;I@ zktaM3n;YvlZabgtjaUvCgG;2zXe6y#d4ga_W3IA2I?~tI7lkla$^JRV_TgR}!C;>J zMMpcYw|DJ1DVOTHShtLvupaX|yXdl#&FApuk_G*|Le&ZCJc%ejsQPNGynkrIm-Dl|B6q`{$8-M}7iXd1H|^v!fUz&kS?Kx;Px zsb_h~DvKd3-{JD=>hEdGO)V4v|2x04;aJS8bj3~BZj5F}ugA;_U&Y6@qs&#--(@CK z)-%Iqt*;bt2O}vdX)o5xi2KsSRwikV3INl1+DgC?X8G~ly z!GVzieRIDX+N|-~rY>lb&vLG>^sVoGdr_!y+uS;%#U6_yzokLpDvyzMJ2k)dm%tyN z1ImU0HIDn`cFT7|dV%N8kD!*K)kz*qcIPE969>(e1o^OWX+YT!Jh2`Fb?W$haZSzM zuDZ3O;|RPdm)-s5X#&RNGY*i|c}sQHpx)h+=UAmUJDDdKG(9>GNY3(t-iXJA5Ad1( zmeqyN81xP+GV6AU;f2!S(5VDQAIvrPV7BvprQtC0(HG_& zwsh(>qqSNAUIP6^{H*f{mSYD{bT%8QHeX+6TvTSVjooc&hkgM0xQ~}(1pE=sHN!mx z&@JTk@S$ItXjEiam^18OcSk0x1vf`YwmN^hfWR-A|A9Pr)rYYI5Rsl$Lns2e{NV=1 zE6t;(KD|!a@Zh$2b!wHJ#TMeW2-@SdUB1UF{W5ZGF5MB~u?ZwQdwafr|NcBho1s$w zobhWIfw+Hj_vb%OqPsgHU^PYI8SN}Yyq z{3I7^&Z$P{+EduD{GKq^?${qbWn9$N(aS%+7+aPmGZ`z4%d1p4h?aclot^98F?+`JLp?ukf~r3gwzKmDx_`^}-o>tZki^mMg7x5_TxA_U z0;{-R*}Kh=imPt; zI^v&(q`Uh==>PA71Xh+9RX|XPpN?G#Q>0J8HGRpii#2N%>{$NRG%fBvkY{@j-Z2i0 zkD~gm!KG0CY#YQovU=b&zqN60I z%UZRAqjvtY@Z`~N*d|yc&LhExC85l>T?AkJ=WI;#<4~2iL;om|$v`UoiqA9KqQfdK zy1L=hxW6l$5N<2Y&FxH&yxKc*kDR!5!R^k?UzbDj$iz$p6`?{hvOd|4Yt8!**f*djh*dRaP2wAgU_SxOtLSZ;p7i zRslh1w_}Iow#mOb27>a{kuM&BSw2vPxRsCpy+|U<&;D4Q_5w+cB_l?_HZu#@P{he@ zQFnM?zAc=5_Z6n&**SYWn>7<5_raeY^%!Q$C?<0i-r+C^KV_zhW`>4YIVnW}hnscy zc6OIGA~M6aQ?!haybKdz3BuQY6c7+WWl1I`CQ#{DC3ODlR+}wHYinUqZFxu=-vw@# z{b<>B(j7l^_gg>zMrO(k=BdWqIJIeyW2T{5rwC{|{Sk1&m;7HXfNfV7PP@*IoxKfb zxvO5f|2k#}J`NU^FD{$$VV?T^3F50i>gLe-H~9>1JAeT(Od2`a**=C2rlymX`!Udx z&0^S|0z4!JveNdRT*p?Q__HS87|zYUOLQde$XLJ=W{q+8OrAVdag=3%av^#b)#Ag( z2U!C~o)4e(B_FyeOZLRYvLD$Tuvn!d5GaINO}?C`iv^yCW}d2bS9_BvE(aCsKuE*w z6~QOa>~eaayOoZd`b=Zp;p%!6=F9Ze6BBJtR!!v^MX0+>{@BZIa7V`v$c~<#{9=b} z-o>GdiZCz$N5-zWI%r+qJr+rd&qpJi%cjFgBWX(KcIh5zib3zK4D(FnxP{p28{L$r zzJ&bZOF75kOU>Is>`K#C?`vem-(M>I&LDeq9Z_BPx}>|2fZrdD;6TztdQF{9 zkqIJkaWUCqr+PIW=HWoZy=>J>FntB#NGP3pouF%(-8Dqkmd0k>yW0<%J33D7jv~{$ z;xLhNc?#?f{aXY>Ik*f~+N%7pm?f0O8$q?WtIj?_8cyqa{C;swJ6F&QovChuYR#AuCcRy#hVd+QJ<*RUV zxlWG7q=5p+dSkBuQl0MXMex8tg zj^*9O9cwwWl;7n4e6q%J6@DTlENnz>2wu?^g-~{Fl}1DQ z`+@Gy`}G!5exu28W|ob-l9K)2d~ESp&bKDp15^3Uot=r~hDSdFE~=I+NhO81X<~*R zoa;l=qL){VmWfu)T$=}GV?Ynqdp};-k$shL=P{8kN@WxrWcVus}6U~ZNgU_NlcLb3%+6Nc$=7FW9B~m~`Bj~_5ru2-# zqWus3Y&tqpMO78zjg_L~2LN-B;i#3{p%T9QIhFtKLcIuSfkn`C#^pQ+lO?(^q3beI z2xa7Kx3pk3-Kk^`)L(lBAr#(ODrbKa)tf{kE05Y>fC1sV+l3>>>kU!QQXz@^xOPPG z?yLW3)#eLJRe6&q%oq@eUfq99H?w=p_tEw&*Ys2_NoV_G zApsA=`g*gko>NS3jt~f3Xd(npG?1$5cVS~<@}mfz8Q~_rdGTQo|Ao8N>Zs06%62Tf zO-A*>Pmr>p5DErJqY!58x-ywa<-JRA_#(Y3hYuT@_1Z&AgPHdVHwj&(C4NAjU6qX7_VB5TW;FW+_)An&giQ2_ zorf_U)lNs`pU^R${N+bM-M|pIG6`vcTvp{3dAIuU<;+ZQdk!^CgD7v(@vbhb z(L&+e>iqfbhoJdlAxv?d;*S2E5%lj9Kch0Zj$nGF*QL5NP&DRt;Y=6D@f6dsDKV(V zN4PIISW36G+y?Hww6wIbQd1F8VWX2@cQ*0c;pTfjT((;zzG$_Hos;kwZ?uexl6GT4 zFEE^lPOaK_eAvFelG$j;Y%0)9Rs4dA;f zR5s#j?!elYkp5rGoqUm$W}otdnOSWY4@R*OOfpz}=C8rHscrrv_`R*N zKZk&Q=yt6u|DFfZLq$?bi84%x{?8pZ!(;<^7#a z$KUMV_*o~RsQ5IIoHjyv=o5U%e%r6O^SgdWk#FxIn8k>KAb48i0UCVcte0Vb3=|@1 z$kPl9#L-Z2i}rK>Ul9+wL4zhmj)hE=IIbt=AoE1mG|};hu=D!&oq0gwqv^$qEa0Wa zesdx#LtfNhOECNH^R+DgqH~Us`cvmREg#XhY}Q5mwB&V zvj8P=g&p9gJ|ZJ&M)QWO-@jM?JLAA`>%SOiO?pG1?yskw7$o!wtNDcPH46zF{8fz~y*0bg8eQak0GUqgLnA zm*_mEqwXsBXyuT&Y2_sVpPDDno@pfjeBW2W^W8Bq&Z)A!cd&;Fn2gxH+HPAaG2z_F z8IIiT@Uz5=pI1oO(4Ci(S)#-Gl@>p31Sax|!=%vbjJw>)o_}1Zs1TX2QoyvLyHtN# zX1jhg($n*{!T|KE8bfqs00_9tn_5~5kZ%EFjZHge%jdrO$aA&s5tD)%8i}6_^S(&B z00smEv^iGe{7cVrti(iAawQ^5q0FWv+o%XO$G!DSNzm;cQcYdlkK{Jl0iobUmA2w$ z?H2-)m-y%BfdvHxhQGq7!WUq|(!~hC9^#LOG_dNh>L;9)S$B7ETq~GJSxna=gDiEs znwy))D;%fb&ZV4-Vl=zPC)v4;@)V@=5B)y#M*1_|4QGZog|oSd8#3{K99u=s=pMWwEB$3v~cZ)f-3UT*H_t?WdWF$Y)Za2>pOdkb+} zRxjDb6)Bp(wYmd+}Oa%*=yTg=~IL;=9{nq2X!i>5KF8 zhZ_T5x)z5<-F%bCr#dIsM@u2!Td=4=CT_J37(v@B#MXQJ8)NKG=3R%Y7ON|YZr{gk9RJ$ZUZ1%Yw!F4B zx4ynSQE|@_HQDj-1L^txDl4R!F14{B)@#Ii&TKKB*~PCo+D|s>2QW4{lQCn$TKCSj6yY;V zt$+=yGpA(K9+!o%qzs_2?T^T(<@!w)tiB=>n_D{%4h}?w98}cQ#8AwQMt-+tYqeq1 z3>(|n*gU*yRkq&5{vH)IFw&H*TE((}y#SU0gziVjCwohiVQsU~QBi>JnzeWEC#t{@ zMkP@Kifb&u;yBnCFZ41oQUMmwGB8jyP<$HDB)vfBuAmGlyS2l?8e1Rl+qa4N@d3%n z^}CC7FmlV3riPP)#nz-z!;-S=flko~7=75m;;XJMi_fUAw`T(fkqUcGd^+;(%lE^g zUcA2-cgM<%`V!wc4(W>ONGZDfn(EVvJeg1`)waCY$Siz8_@}KXIX73-!Qrc66+XVH zO=)SAqT&@)%rN>WVQNZSv?{}DFzVOJotCbrZ|KvtcxvZpqdzs+G6IDkd1`D%ojyhe zP7Z7Rsl422adetN zG}{Nz%8#B!`;)6yq9R7k*`BL*UdYS}=RWM5$9eqt0Rfw7&+l8OHfF3WEG`FU$7vnP zQOo;6IyyR-q@wEk6FcII9WJ4PN4s=mLl-~1NA`B zUo!;@>2TuPHYa?VpVFz$FhrrtbV1rmfnJ!0sLagF*BZIr4=Tk*ht@Yireu*^Wb|IWjc?T zn7P4Z^fAXq=LFj6Lpf9S{YAz3sj=SH@+t>h@2>{QV8Y<6i0P-n%On$KJt&H%F4BV{!{W9b z2}$awYc3d>dutWr? z&*!CX>7qlwIwDhR#+~rOxG{S37K%)fx5UKBsIyHlbRYw71{3*G6E&g@wqBb9FK>Se zI-DZ5=a`t7tYzVl9T#uD*C8k=CJAX(D%PJIX{rjUaR$I{`OBT6?fy0@A)xCE+jECK z$tLI3?hHu-K?=ss!QNz7e9o1azQY756Q%v!{!sPxN2#ZTHxWuq8C7PGBES0dGqOV? zA}lPMR0<1jtEh;GDKK-%$uS;~#e2qF=o%9DYjzv-PcSQ~<)xCc;&!=;?n!w_$ZVUu znJeR~5y+I+*Vev=h2>W3`6Q8pX9g6EqDbhXINM1bIh3PRpyZlN-In6I^%hh2lb9tV zBPj_ZBNg+y*QgjYgFsRTtKBSL4BDN&{Hp3_w}H-v_cSp!9xoH81HIhJ{_5wm>jmX! z`!TFcqO7LluQ@oB*<#eaVB*#2=#LTO=rjPt1Z3&@hCv;=V3z8#v! zHV3t~n{NdLGh>v6X{sbg_-RZ$_<6y+ql(F7G*j#J`}jC9zc{?hvuCSCi?o!xui4qP zwY6W#o9(S6`=83~#|~J1$XFQaR}saN`M2meImfMd+@aGCfPl}xM4*`Gw6t0!M}izq zlPpj9RfIDc-8}5=Qc?F;nrjZg2 z#6?F&etf-FUF`%HY9?vsou{kOP^qSn`0bkj@|y^0QAv@> z+FJ4DUk&89gNcl_7NMOJK>WUD(iK2Q=+}wB>M} z#$Xf`$md=uG>fvQYxldOfD++r*T^4D!0w5hN(0H`T^h=jLQ(1M4rhT9l3V|i@ULixJzBB?~JjP(Q2hVYA7eU`}7q7zh z@)MxJ9~uNL^LMd*9H6^x8`Bkr6J`#E2L=wO3cGoFR*8@biwTB(BWZtr2xM($N1QJ{ zmkM>bKx%I`VWM~(w=_+L1i9&i_r^f>DYzJ0Sng6XLAUwva~s}2V{A(dQ8_Q_-q@cg z4ozev=7L_u;c#uTx`HaE!2zIV0cN^>w2g&TIk;+rAlAv*Spm!X@@@4_=^f|Oe`ob2 zm)OQ6*Kltu12r{LIzYz2P;BZ;7P7C^_&-r+4WiED|B+5E7mf#%ggwUYxP^Gvy*=A7 z+M4wnV@)<|q>B}1h-&}Qx_$Wgs+Lyq+*zJmu0gj*mVo-T!S7*+<6Mpx$f!?Ev-DJd zj9^XLapoNeK;bLiOh!p5EfWciS3p+U%xhjfh)|yXZY63TF#)3l-I0k3DzrfYQRlBL z8$)YOABKg7e&bDoPN4Y>+0s-q0_%}76^#{%j>CCbGtH zI_qi@?X0g!E6Ai&8w4k|p~tt#>2&VwSj-nP8X7EjavaE1*=9m|knwOtmTWKkyfXLw z)seJ5UdlF3ZRI@K@VhH2FDR{k6}I)aFCakZCF=r?k(6_;oBOIuRhUW#07zP$2eYO_MoKEUxqRn6~c>g!pXJ2>dnIn z2?++eMAoriKZ&ydhqrmZJXjYTE`5@Ck%SR#spPF6A6$D=|6UR+qV4fr53JOMPYEUB zr$rDv5mtw_+^3}}#i4EUPj`V5^KAG%dIpr{|1DFAya@$@2d$^Jhq=KrltDt~hg=Az z=hiM)R0q=&y8{}a(q3j14esg%wkHESv~@1%ouo?^{; z!x|+goW{mRunqQTSxGTo21 zwvJ`7|E}nzVM(Q$i_D%0QUFRy?ZTk$x^uX>)?eVP**p9B^Jht7&-KaudqoCq3)b&= zaG}9nQ*%>cOo8s$Vj39ANKuzsZV=cQ^i>W_I37}XN7FmEy5gMkmxR%A$6p?q?_|s8 z4>bNbIeiSqTUdM#2o8E&ahyA;%`Kt0Sj0@TE@-$Ca+k02_8pUv;yioHq2cnX!NEb{ zIwel)9e3CM3PV0MwK$c$t=;`&-I3r{ztPcAhz*dJXCae3J!66%Pf}|DT0*xeJUVt6#sN zC zljZ9dPZvB^jg=X?2n^)c`%jy7i*y)9d=+;ibNlo3fvK{>I*`J5qkW#w&xqMjL#QTsmRIi-@X6$V?SLttDVH<2xEFj z9GeNKz}pGF+}Y1R=t<4dUht(LIM`@WaT$s@aHT7;A0A>Txno&!s;rS9yN>9;oopr( zw!8Wy?h`lXYLOD&d`vV{3U_v=@|0#5SKpx=ue5o9d^a#FxhRAh8my>TrB?u9f5pTF&q%rtW;`$h;{OGWW=D zyrnEGvR@}!>s1wB{ZA?MVDqyr3_x%PctYr&!F5NR3JZ&`hbcE2p#MgnUYD#Z%`0sN z5T|bZ!x6ZM4YjFzy^Mk@67jSN+tY%6!2Rug$8_&Hk!QW%(gp*l$2a z#Egpr#GQh|+`FT?F^?G)U|6+#-rWdHz>Hu*&KRlgL|^s%WjHyTsOC zk#CJg|B%yCB$BtbwlYvrRke~rwngDhNlTll<_>eq*NjcNzSq16YlZVW{Ro&%5q3Vw zD|}zF^8)CUba#GUxhJ;_TYqX6Tt9Q3n3%x2%V)e6Th3t%t;R@{C@#ySi_RLceMnx%Bu8mRFe>6DxH9Mz`H)V_M^Z3c%#f3pB!U#__ zwAG31rtsV`s?EC~D-YnoZJ#%fVX~1U$5O!`ny8s?2}zIVa!t&^`dX=w>wW;vO!CgI-63Kk*tsea7tvZg;j=@P-JL?db~cifmc zAs_uvQgm4esxK(_0=Bld!`rkY^3{y`n`@!)i{bV1<;%U1U7Yr(OMWq>3{hdOs{`gR za5+!)vx2dWrlyp_k?4FK-uk9TAx%k>Y}hq@o+O#6Tk8Y?OrlfO_SqJ1*Qe&LV;Mf7?|_OiDs*Y-39=cAOI?x7W{o-ab>}X7Cw^P*e_mn+>BC*y5nVxj z>|kH_=^gl(h@vb{m7z(mNFs>{Q}_=*Ain`M0adMJ;`~+o_Yw(pJC{%@0!CPbeRnge zURR!WXG`)r;mcP)1JE(XNuXUaHZ~+GD%CL=T-bDU<||cLKfFgvB}rwgsHhx5LzGaH zFz4FlZ?sPwV0g(KtIo`i^mGm${QL!m!>$r9(2q{7LFzAk+l)?}k+BG%_;dENm>6Xr z-42y2(ew8R>1u zhl8}W!pN*x=)db|axxBjs!k6G#>*}PYhrUF^qTG-`}x|5;<8S<{_UQ0z60DxO*MPX zdc@byH%|5|9zQ;BJ5UASYcf_WSM@CW(bwEuznwp#1DjRyWFUfoBi!JzE4R0l5C6(2 z3X2VF5jtC2Ckw4l2TeG3Ep}OeM!vtvG>`z@dXtY>JUfyUIBeE+{IyFqPp95OPQ7$s zotA}r$M6=D{VCNd-jsd`(}3dqvveDE19o2KRH}kuYAvl_c@w93o*rmRL+Lri#;5)h z)6=X~R;c|L)oOTV3p0h6f2ZL{fVDifbF5uY>)iAhoy}@R6cb1aDRJY2vGTzNN4eSM zuSUPv;K^{7i$gC#Tb9Z-eok>(Yp5=I`-Z6mS zJiee{``9PKDnM^#&I_=EQppZTX}qLeDbr8E6c?nenNIUpItcf`iuiUA&JDFbZbKpD zu-WLbSg3`%JaDO3^x(x}K1!5`+_WB%XT8Bx-hxh+H;fQ>0`yJ|X;qKzht6^1P zRL=Hx<(}?pI8Zvz1RI33RrMSCB%)8pCzPRL4MT1L&w=*GkbOvYq{a~q_AH>-D?Fn7 z>Gch>Eo4Di^qZInraDB_gFBN55ufDYMw5JVCQCu9K>yB1+k5wX0tHfF=FW-VM>Mp< zyH6FU<@VN}kkI7zUzWTvFZkfSe^KMTt; zPoRX17ceNO@20SwJXswe!e(7k^fCXx7K_*E9;$*RqS+S6$p5f#9szf`a1 ze|4wCB&Jlg1O2?h3oP(->M3(UWQfAX=!)(NOoRma()dV~ZGK#O*LPy~$fziPd^Q#P z-k^>wJA2q_5+xKQ6n>tmYP6`X|IwspRF2*s>fn@ZOvub_m1QI*{WK*xUeZ~3x_(@8=VPLbm zW2VdgK(tTE1$)KOJ^R6%eAle7saDDx-7dTo26x}RKqsHVo7aojm|l)6av)ILkGjTx zOxvL>A4SG_rVE`wt;?`WU#b?WNM~ltzYD{;SzM;TMnZbf3Bg02){%;`a_VPd^xZfP z*X410X8HRvMX9M=J-zdAQ{9WmyT3Hm)cnnsh6#%FIlm97;=48nOMJ4k+klW-*~f#u z-?wYgwL8UJvsY`OT32CMbbh7M!-J1MAdZ}pc2Gu6`YSLluj6vD8b#Or4Oef&*eLPR~8L|)V zb))>Z`LQ`#y6c-9@n$cvhkizc(*BZp63S?J;5VqI7iRW7%{lb|eIPiG*aHa$xS%WA zIZ3R=lYfwqGPjQBHgKAr<%nQ+3D!Thllf|*E7$P!#CF^NG!g$dlD345X1xWhu2rL1 z@OVs%A@-2^#rpaBx+C9)GeM>*2(F%9neLu)@BKTIYlL8$ZYQ~p81J1 zNum`c&omLH@lH~}^6AiZw8?r+rHbKgZvy@tsX^0va|%jjn) zTK4`iZ_heli1L*h(%kPT|LzSytt*xQ8AD)1iplDa~-tPYP_JLH*48r(Kh)#ZP6_1>+8$+_$o`DP4RATJ@O}_4qQYqkuYrK z^om**u=~>iU5NhQzRA?c^Ix6Nv)H$)%7&`77`;`tXtWWI9Ph_e6hyOpc}>zmS_}FI zvG1?^(V=0->a;XE4Z7_>?8`4HgG44Vz}dy{Vq#!0{OMNfR4b`wdU1R&g0f7Z<`syQ zX6KfEwlfU$O9Kg_d)HB0TLuohC8QB%iFS8CgMvS$wgkhaky@>Xhdt!J464!(DmS$! z7>!(~*4NkI4U#EbCYB!`^W3f7eU_^gt7KCZ{NFX3-2F_W#?fYTe1v00_hm?ueA8nK zD4KfBHu*1nWD6ij=cT$_mpQEIrbpoM2r_v*dW?sOwZvmE^V*9SCdTOi-dQvy4tv*2 z<#vcOKh9g{^8KyzkfmIak;_zO##5Lx+n-UJ)ykXCIb4LMJ_9+^xR|-S6B!=XuUK-#Or?7jBdYhBlzV~jZlP_Ddx zJU@RNrJ#NZ?vMcJ01F1h=&v#;N>d;wz%Y@=qgFOH@4B2^$Avc4>3?lSUvDr0Aua{k6ILYM>8N3ZFld{&1AEbU=UF zn+`7b4wx+3SNzvb#}5LUDFgh);qn95X+OWF?e`LYc-$$UD3n?25aM&t($G{m^MA;S zXx2wQKC7&%0{(k+ln;h}SmnMPZLCjykGxGv&cj0iCzLW`Xhj#~c#REzF;uxEehY+D z6rd*1CI3(l?ed*?-)cbo5tK$|Rz@W@SI>kqVSK~g&Dk)Ky!X!!m`iNUtFf=p*3b$# z+z>FGJU(t$lAiPR_F~k#<92us1A)d+RM*-c;FC7By&p-nn+InJOkq^mMmn>Vlm`KH zf-=C~-X5?>Ft9y6*tl4;5l0~f3nt(?%;5s%0%bZTmPz~A?F*G)|H)R@aolJ>SlOg!bH_QHfIniM#ROn8W+h(OA`_iK`08hWhg}II2dGr41f*q>Eslk zKd^^Be^TL1HrWVB0VgM;tke6M%1CjT<&e8{6QlL=FxL6^`YsZ0jxA#e3OQz+X_Vk? zTNi_V8!<6pLlN0q0gX$0G0$P%6a-{gfyFsF>mai4y5kL1bv^hMX(bCt=ex}b^7zKFuNlUw0cYqf~O&eaX&3!svMl1iwjDDDHE><5GZ4XEk-yJi3kJ3Dbor>5oO18(B^nESKe6T^`nAf$BMZ~M)j5)*Cta01B# zaP3vh#?W1V;cF?98g3QRlm_W(*O8Wip@wPFc_>X~CC$uq0>GsVE)<(zlSBReb0C=V ze`5_=w1s61cm5A!nu?10*2XPQ#YQG4tINyPmG~!I)>nr)xS@WBPF?zito3Ha9_}4d z5NNmR@Mp$oh>uVltBe_&=z{G4Zw;h2MEvewB^=1% zZu|LY`@B+~TDc8}A`RSqu|+(QGC5<@0a^#n`c1n3^=E$%5|r4s&d#4Yar6t0A-WLK zDxm}cKf|tJAkcpb`G5p?eg`m95LcRi7pINXxmUBfyxa%OurNwkYXZAnd?e)TKBtUO zpyj!7aq$_4lAMwy5L>i>`XOv&>w^k0Y^0=t?ey4p-+4W4N(tw8cwh@5r9eiBDuAkc z4WF>daC7gKoQVn5jSUJwB}LxxM)%I!-61uC39|1bNSFSA%lIJ}6Nr#-A_!Eb?QCy{ zWFIO&Nb{?%u7+=wrc7)m^v?EC0{1IIQgTw3mBS5n1{LUW?%^__6ZUP%xQo4d^~%t2 z(D$0xiRZWK$#Ym}Xo@H@QW0Cz*GxaZ=b$If_uJo`dfT};9ePp~xIDoET9F9KP%6L4 z3Ww+lt;f8)EoDVVZNv%M9Ja`4ts(TknQWjsW*huKgck6&`OCHB zmUcr{wA)fhu(T@I%YXBH80=qhj3hsA8r$+l2i8)RhG+^Pcmh^p8gI++d%3WaRcftd4AC-iI-xFhe;Fkb@incie+qDwUBRFVK1&dAH_j0{D%p(%y z*F)KGBV-(#G`H7I4+mr8oshTU4e;OFRcw0i@QQrU!x>|nDCh&Z*B^3 z@#e1{0a@UxC%Mc9?ZL^9C4UF1JEQ>63g(@JO2%`)vRoe8yR%b}AxE?VHUD^7RtLY* z;^vTIBlP7Bd3v(%!}vXI!y+OK{NE6wEtNH%spe~@a5Bpn-8lEk*A zvj}`PP>_1@DBE5Qaqb66oR~X$VZdv?ZO5ysjOp0Yx~epnCP;CL*LeTE=sn!?Z$NXTbCwfuJ*X5eQizE5o?n=b;a~W%19^<2WO!FZngpRuM6?Yu%tM{JzX4 z8t2MntF|$=@>0+@iQp3kiii>I6N#aRcIevD&Ai`-hKJ3G-rtbN%=Jhvw|zo8`?E%K z;=4+=)3>&VxU}zsgw~!5VZ0W))xH%rVG~ON_H!)cwR*5Uw0Bn|?N#6=EkwjQ^lFqu z3FutbMyTd!PWa4@1fX4E6>t=*$(i8vNatphrK8j;QM!kb6q!nH`_?y-7(eB`oWQ(s zqwMO$L`N%)MFVc(ywZl9<*`dNDsfI^BbNMWF-J(VI7AmYi{4sRfesC{P(kK7P&Y?`IX307GpwqS2{51DCN|j&(IU zXN!Mo&6WladzI>7?mqYH*RNiD8G51XHZ$!S&wA`1WYW-8#K>bikRj8LnB;T#eF2;3Xrd2=|l42q>wzS4RU z$5B%HgL$UNB2GeClFNXZdMx6ga(^vR?pPe^gs7Yp14|5o_ov+*X!mq*ODR50A0KRo7vfG)1c}HGUR@qB-4c;I9knq} zE^;XIPTUOt{fbKNqQdzi{i=@6>G&sc9_G>hN|B@_*Rkhi{Y8~B-^W<{s5qg65Due; zz5y~nW!0doU_j{xY!W2CZ=^58DA>AN1PS4`-hX$F<@Wsw`%d^d^Df_MS;DFY&8V97 z+3D%o@W|f-JmzMXUxfWt(6(hTrLi`{rZG)i|8q19h3GETx@oDKT3O)`9BmC*R6Q)E ze<<~=cqHFp=HbJ`wKcat>pbpKNdh&?x4~=r(3wF|(K;=4GDuE3bP``Pt)r~OYIA+E zyCQ?{g21cJo0yg`^iBLCfa?|d%0w^R0|tTfAb-R3eg0^)M5_Udn(EuCM;MSAV+-RtOZB@??}W;R?b z9=3V!Pmk(vTkHjTu4@$p79C*kyCG;xZg^nNH6p;-9g7B*6A*opSu#UA{qP}Vba;F5 zgOZ?G-)=CkhVD(Yt9$v_DErxEm5nEr++^*yRUGSoJ2f{8)T>=)>Tx=w%ggsm%PcU9 zUA7pqcBGrbFCy3w*q_mi{lhrL*iy3WR$2Un{s-pi*S zL)Jv_xvZ5J3np)o)Q@w}QJ#FrkCq2Hf{7dn{GeF4Hjt}Lyuh#9L;W1dS1#mM-eUCU z&mRamtn1ImZXb%_J$iJ$x31yZo@}ui3@-*YG)HTDyc|w@i-s)vXb;!< z)M*|v-P1HRGKzHHx`ZY+?8?~>WA`zG*G8pGwi*MQMgPKK!k~s=cYdLPn_ER$*jDVv zMOdu(yIhN{#FdxdanKSf$nbQ^2yKcX$5@7pGGd?%`{gimu}R6)l-UoBPbq{u@u~qD zC-qh>SEuL+qkvt{q-fOR7Kkyxi*}` z&Shc0EIzs@max4GqC zdSQ2NUOzav4kk`!mZ3!Q#1ZY-fzS8n6Zzv^EGPOav}|Okkds+ZUxgB#b`*^Grhuxz zX;qj2CW!|68}u(59!HX8dT@TH-RVuZAhXvRgl=PKkO=8#YX&U2x5Ux8S9LdX3*bLvAY(zS~}jDzDw+U%dhj+cC_Hq?;XY2S0Cw z$3btI8gd)`ba%29H&*_iT7Z}bcxGh=Uwh>~;@uLvoicJ9;YD8twmJii8-pZu#;H@X zL3Nccr{yd;rP>)Mwa?0jT;cQkH`*o=Jc>?RbLkeq{t5z7@UdRc9x0brQkcW0qb2`p(~yu zlRm4b{F6@_L8)l%bSC?~qT-}5**}8HJ-bg0e7c;)MN4g#KD%#0Z{uAX@sQz5wjnND z74(paor4WSPa>7~@=a<9jQ<*3cyE zVoT(j%%rEM8UU$5HLZT+}?$^%V0ZdIzxuxKnLuC%3@qT2HWv3o<0 zyL*)I90e1_4@R*3K0?_7t!aN-&^MNtE{-)}C8aHhUz{%HsWu7oQCqx+63;*8GG23L zcJ;0r_wPm(E;)+()>krBUevgO_v%P0-?!*G8${AJC~55vKMO-d@5%Vu}=vlt&0m9T`i_7nb#Jf(oE zsI0t_;HoT`BM(b@O5yhiA72?=HXl_@RW-e<(49-s`ohKj((FPgoMkH=-kgp`ExVe}eO--rDGJUAyMdR!H5B(W7iAa`_;1Pvq!4b&{qsxxNs|vVrxYQ z@pF0Iv-pw%FTtg$`r!kM$6Kwp5xO35JxTa`0_#gaxQCOAqWgqTuXbkeC+|iro4MSP z^@NG^5lBLEm0HpKK7tw}%T~c>;xO~V!{t;qW;9DF0B(3@W@hfo)~wT2;l~)p#sXGr z$IbIpG5tmdH?P-6bY+0i(ssgAyPz3Ivw7q_W6+joXpBKs8~?O@V&rP(>_eKxURv9S z-YgDHr~b6fo<#nU$x=5NsVN8_p03kN3jTZz$&^ePzF*o^*hT}^y%{JuAKNN(-qcdt!c-^)IoxCKUZrg&qlMW4M2Mv#CIDd2J?NZ^@jONEqehEXm}TT7C%rNkaMqg^qnQ z@9>GQ^5EF{vIz#oZ!|30YZd>Hk@9&|===>(4oBWA&En@hb4sWKbB}W+aUfDt9#B{I z$7U<&b+(_1`ju`Rk?)y_JOVF$B*jPn&_X6#Af5w{Wqia*gh%Bq6%hJ?J}>WCXWr8^s+RI&|U?7;-69ADmWqB>cZ6lj}E#O=FG2%d1-?bYa z9yks;f;cw$K(oxe-SGPwqXy!%IW@jy22hnF0dT+N>jzU~{&1Hk_t;i}%+`}TMLxB5 z_84Y2;nBx|cqw=|qJD>)3LZLd;kob-8bKVz@gvAm5oJM4QH*9MCkssx!O(2zfB#LG z+npcV1?dwCdTTq+j}@7~)ll@~2LMr2H53pv+t@fTI+_Y>Ay8ra;H2K`bErY*;1Dbl zfF^kq5*k`sRAkfNWC^Kk-Ek9;5;0UH{U>*br=B`BjulQ z$ox%n!47VIKzaOukcU-3U~_$}Zg+Qgd1J^WKkJ*qH?gCw89^$liOx=o{bdrICb(#b zgvQ3bbTf!x66fx8ZEi_SMNAB9Hz2An+RJbq*#52gJ3rHUVK1HA9TXf4Q(T%*c$%BP zn8EJUF9;*#d1~DCNEjvBVt@M;Z*X&AQPI*)2WSs~T1!h)5fyc_x8LQO@jUw@!}zxT zeOQ=_lN00~dF=fODRKxav$@Aj*wYw*o)ACI?`m5o89_PZLglBt1py?G%n0ki$Hw2v z%F68KgU7!c_u<2=vP_g%sPV&j%8VEt<)+YrwozGz#Jr2Q1X?r-!2g|B2eUd_AQ`?N z9qMfijy@|z5My$Z^F67}7h7)~qi!E^Kr+kkbAxV&$ zmbMbcU0_J^4QFX-DGXeLt6L#K9G6XEL!ySiW}W5yeNTIRAv z9Q3vJxZ&4WMQg4Wi~>S)32CvQ3Jb3B>b$(D0giTR7jmdrSXdYt^ENlFwHc#{Un+SW z#S-$Lb~Xn`!w~`?4#B}pclEC$`J(+KkEUdHli@5j+(Zw);KvrRIxdFUoVVZ^oY z1-5N^okRn1`iCIQ3!_6uI>soZfa(uujR8C}F@627U~vz#qY`bgy@FTOx*c{-a3A=- z6Vegv33~oyj5HDA7Qz{J?(OQb#7MiN#mMzRENPYVhOxoGQy!j^(SjKQOYlQ8MHy6C zj8^+xq+!W{eA`#?z~wwQ(mTSl)HC3Iz5zBRq=6grSf%#m<_HY35g`< z(Soywg!0!AD;DuoC(E@{^aew!Kp6DG{`iB#~jjvc^ zz9gd}DU&@67JZ`N=H@RyD4#b6L(SD0u*jLGQV0a6wJ2(DaJAj_zn&tWys3+XB!Caj zZ`#_E<#q-#RIq}MXLP7|9>@>|Sb#R!i3}?+lt?gl1@=#&v$m*XILr#Cv&2)%P{gGs z&$WnygE`^+5C_MM7kI5cN|hJqSxOn3(+ohIV}~{=UBEw-YN)x16?OK~r73}ZNW8DG+ z6qa>gEqOLTlr?QTF=ZZuTUb`3Mep829KoZ5N9xR?;xY&1xr-CY+K6G@vkTHRcBbmi1Zx@WD zBLel{AI}U}V^-th?!%A2vmGLP=XYQ@*8S5pq)d`iOU^L0Jj9tnVo&c;5lCXfkRpe~ z$JO@RNV6i)NGq$V?hbL@V1ZNDn9)&&17OHPx{gv;FzV&bQUAVOcX^2*ry8HH>c%*q?pNZvpJ;Hah+f2K}N>9=~Q# z2?;g7^Y9IgmHfEq&z||6pKEzn1GX6Tj`tK!r<%%_7we_UwME8iPWLz64)=H9c~qbq zTxYwTbY~uGF7K&?44_V95b}keYJ$Er6H5G|&S!5pr2L~weT$fweOP9GfRdFAT zJ#^V7sZ#qy6bh!RJhD58Czm<#gDPuSP2UXj-P8fau-eVPLsPT5u}OwR&x!|nE!ixjMS^lI%R zts_7p8yTs@KU>K`7aSBsOVK6-@-IM zv1eaOJMVV~&NDiZ;iEJAw%Qv3jlv@pm4~E$&-Fs^miS{Z<|aG67QdGi*5f=kDAEA4 zwitew-(@pbrlv0+^B?}`bIR59`m;vD?Alg_FbrQ^9CGmfk_hf6Rh8#cP#lsd4M5b8 zX*L~hZy!2fvauxuX%iP0ln;cTly2M)8)fkv{RpoDoW$mo57Z?mGN`FcOJ}mET|H4s zi#F5vhz`5{ZS9Bq=ng9banqoz$1dfW6YJ=XE8Dz(VlICi6>@Rv-nLm{HZUBK(d$}X zwK-ArEvuyDA1Dxj6->J9`BF==j&Xpm9qjL`=8yfbxgrhv>Mc-R1{ante?r73B|{Mo zv;OCDa!gG5z8DQJna|Uu?`xLsYD&|SE`SFzK#nrd<8L(>7m09Lg#=4IMqu zE{fXPZ-gGh-2(JFb~S|``-Pri^RF+`e`1|dG&IKiKFY*3mWf|_B#8*kS_XZ6Aq6tm z+h~`b%Re_)sTk^#fA%-biClh$hUNA@Z+5q*i2&3>d$%4M6wl(Y37#z|q_S2-cSsBD z?)ZKD&waglh#S`dG3mliM1~)Rh{z3h1E5(*Q-NlAp0uM=<(QuD*}3Vo^$eeM0d(g3 zN~dRyf(XmR``m=?1_0xrkUk4uy3LTtG*ytGG(oFUo038bURF6DF#3zLoWLsEX1bce z>%iUe{N~mbWpPEg{aRg7_~&)LX7T!zA^Z?Vn3)HF!x7&P$f>@Ofo~QgJ*C}N@#DO! zYn>IHr4V?>>xZ}q5WAEzcJ&j#ybSTMQvpMj?ZUKsE3|r1>o2{G-Zy_*^#DTcr%6LO zjKMoBF-=!HiI+;}XM_I5_^CU+*XgR0b>uBz{xSO729!ZC+>`GE2CL8S{vQnCqW+@aDPlg^2V?A>6$qZE z-#V2+JwGU$A2FCWqVv}LCoX|Ux^S>I+0gJ6VzwppoSZfiqAul6KVN$J{@MH^OyVjt zV`UzqnviSdV6tQ_@ZjQTozInla(LE9&|3MGwRK{2QhpN;R4IO$-V8v%a4|E#WYutY zKL)3*STzkAn6rMW3pMf;H-8?AF4QRH+SY%=H+>SSgluN;N-{jkHrUe7z^{2Y%iy+f z8)(N<{kOm{Mzi?G6jrGKJN@};$y$e_vig2cQ@OtkYOQx&ShmM!Hh;C!O7TSg$94;RK*yHE8 zbjDrltH+*%FM~%DmiC8wHU|n*~2QK}l?IM$2 zjxwv-mz$Oq-SP2+TQhP>8^zFd(&A)$K6qdtoOxG354QG^nQzyS248=rri^I+y}VX5 zwk7pKKsXGL6>=bRRBPKiuhu60e{^H+A6l>!Tzg$~BmE3MKLyv3%xKhm5Ee-tX_j-l z0-B44cDOOYN29TLCWm@ogJ}J@85g`Z9B}D*$ZZB+kNAhN8#suh&~ zh2qBni(uuDze5J0riJ(Ai#TH$xrFoFuxvGc#(88AqS6 z5~?bg1-<>ce{jH&@V@l)e?n@M6_zmhAiEZa#G;E1YTTQ`Fw0yoPLQ1M@sJ_B=vcY2 zm|Ma^5juh|tDX!=?S@cv9*~1|w;;d(5rHvphL$D&47_93hGNQGafuc($6rraT}yr) zAwll$6!RTi|Abm!VnOW>kN=D${Hu$a8P~G#3Ws1tdK+hYdO0} z*7Lv#J3EJ-vO-0xr_ESq5O9d8#l>koDKSt1hc=9HSJdyLD%xR{v*lQU!`9T`*(%-s zkT-)MDND>z-Fi`>Jqqc+Sd-Ndh5Z{sji70}LkdHdoUE+i}NBRl>2H8dWF(hlK zgL@#6PG(I9x-O<-njzEQhnh7e|DhF*^Tk>F4{!G9zXn-CL~ z5c6lYPtlnyt~)Ap8}d84x_&}Y3RH^^pF1^Rv07Yn!m7vwS6c>=Xsmx)Nj4{?lL_MD zVyv`DK7De$P&iFkmNKC_`Ea!?ehVq-CS}Cf$bia#2I6B{Ons+_$jY86S4*|SeDxj zrz^ciCL284=T4^QTTPcuk%XdI9^F?`Qu^ClR0?@TaAkTm^+X1nq=v0j*(`EC2z>6X zbYrFSq!#}BOaN-swUw1z-J(}Bmm=7r7oM&*y~I5=?z|AT*yrJI75LQg828kUF9V!8 zHHwy6TPV1>iwW7>;igZk3`0W_kolqjSF!-17h7Je>FlP~;sJQPVPFB7*f)35AF>fY z{v!K4HI<-$1L3b~-L#!a^gHWlGb7DJJ2)^zPU3sUTexTutsH*&S!tebwQJ1Ia>4#e z_DDs4Yova`{?57oGnNqp{>drWNKakE5*FAl%M7R#z0l0MbFfKjcot`PQV2)j+xml;;L{ ztryJZp4OWL6(E>qKPa0CBfzr@@Z^Blau^6R9bP}N*8;#(D~y;QhDQL)^w?jH`S@}B z_qBokesB<3X?+s@{_7(%GnEls_$EFo&X;%w3~!iE>88o-hJ4PiB_^$+5D;Jy5KxGj zwYhf>mqFw570nH#!La@PeZT{qwx+$#4mV~z_pF?qRdv*U;R~D-Fy(t+yLQdI6MAe% zjmF9-unb^>DVpxCE|__SB@!y3tQEkOW(WSVA0gC1=78P+=yiuQj6`sAptZTFX|TEZ zU}xTk3JXSYRz?dRL|Y6NrZv7pB|srXMn=YN=Jq<>|A6-zyZIy9V7QcZ*YdI{AKw|J zOqcWe=&$MuhL%IPFW(^GuS_-2c?SkULiu3kuZy#z-uCvx-CcbCQ}NRw9)~}nczXtt z?PnGvd8L+PpDrqO>ylZCeMogF)q-b>hgjtbDQ z0ZjV%j-wR~09hW-s24nVNM?*-iV zabdorxR@OfLo^VJg0-Nks%meNpo^`oda>#6&os4A=HUx^$45s;zww2QCM(P34k;W{ zu4V}(!kY7|yEOQD7#N7yI668S7O0c#Bm)wc^7B2w4JGBSI}O=tOkkjL*v&N~p>`2b zOY3!daWn&$pyf!9S;Nhwl>waYOc4caubP>jp1c*^g3Scp(ps{or0uPU~lRezjv^2mUliHiwq(M&b zw;K;z0A8F?x7yIe#0Lgazlr;#aP6L)1BMM~rsY_n)77O{f6L{icYAw#dU}X)7fOx= zWL%)NHZ_%(mxBb{u0s3%_%)>Cn+m1QNh4;rLFE6Y%w`BKdZ7!@2XsQ^lcDq3(}xYT z0+OL8zJmh95N&E`c)mI$Wnp0<0}S)_oxqR~rNYI@6RF7a<*c;;eD8KBG&lkS0|9LX zIRq)Gg}~Lh!)Jgpk{+LbfS`MeJl&e{5(_SZxIb`Ut)Ulrh#N}ihAjioi6b}R7C}Zf z;{|4;k6atl|w@1two25ch7}8=x70*2CD-LJ*4Dp>L7jA7)#FJX2Yl**I3zrKE0>5i?C)X zlQHaQ&~rr1nV)~2v#*$YVTTt+g?(3H?uKs%pdIqm^mOxq6j*sFDd_{GP|kvc`R{(F z`rRIr+fbneiA&^7%7mb^0jd0gS-sKtK;$ zLh5XCh@sRunRw`ArI#Ft&$F1ER89!J(8T(#3)MV8xp|8))YySKKlA-)X;EgTM3EWn zp3Yf{b8=ved6akqXs6gpCAG+2L)M|Ap_@no8n76)4{l@s6ixxF)!?_P=A7W;(Dy{; zg{O_p78nP-Ajdk;d8jrLK=HQzr#fh(U9bXENhc3ATjn9#-g$Y@VQ20nagz}YL{Lcq zw=;b_IjOy94J8t^c3w5Nq!R?RpjdKxaVIAT87Vo4l{l>1IA04FAHNbAQAtn-CKAVj zF`K_|iL>8dX88JRB(BH6W;Ob)$sxW*;NwPiudTkW(YYtwHgolK!0`zQPH;2p>~hf< z$oaYl2b8?U6^zn^9?#ypiG$Hv=1b;@deA2b-ZF_3_PFoN92_faE4t^GHCcg@ zTZ#O}-MT`Ug*1*EKe}@I`xOlg=Ceog>U^$r2h6@B2e-3xu^AM6VbB`)+Ux$oBw#Z? z+xH1E-MqKF6A?irx3qBn%@B-KAcp>wP!>!NF&wtwYaS%&4(sggku8|1k_T1@f{GW2CHjoK7SVg!_m^ac!KpF>)}a0-R&oQ zd`nw;p~P{^Y}Q|Uxm~!T-{+?M`C}a0Q+%m_ZDeiTqR&!y@@FkGAzTmr;=4@TFffws zhNWf$r)w#Cq8lFwIW0{S^jC|I$h5iFiDjoNlgQP`>xod-9@^J zjUu*zQZdUJ{Mrk$Kbgu~ckNYK-;`!Yi&z{lwGO2nfyawaY>_Vl1UOh5sn|a>XuXnv zbe4lv4pMT@PBGx@spJx}7`{Y7%#|@qjNs+*+pW)m_5|RElVptiL%nOhRgRVjWMP(uW63L?kI&!~hxp@&&qwWL;D5CRv$< zP|kt^GqEt77#;&nXXDs{0+yPo?4+f!fmRZMa;L?)7!vWY&!~mAm+RjIw|qW@n_H_o zN&HlMdmCijv}7BH&^^`SKmC!OmnLVisaJs7pA|ECIHRHxxqy`6T(~tc=!drZ(ab|&6gBWL2?)X)fHFwj}(ry=V(e$Y* zwWEY`X0b(ArtyH|==*$2VG%qm0SP)4 z^z!mTX1I5ETK3B_gbB#h2)_DC64pxQ2ZF)Cam+-(Ei-uT0koW>O-$~QVs~Cn0)03r zQTA#VuDa)cjSTPuP;z6C@3l-|4dWz%JU z#9+U>1ilD_A5LIIhFtv9jmeq`&^SI2hUllG2^%Woo5ykoanf0ws4{Px^b1i)ttNPB z2h`h*uLQk`(&rIMePsNm_X%c~O6ezZDI|jJp1ii(+p(IvC!X$>y@<@L$4S0s-cWts z(JU2y65iBI#cpNJZ@k|IkitN|Zmqr3#;MGZ$jOPU`~Goz^$EDE??f(JgoY-aoyb-` ze31QEuA+wSqy66SxKoPX$H765odUJf{neB0-`~)?^sKwUF|dM|z-MdvhH%?J&N|cC zk%RN5`;xV}OdyRZ6IX56QTc7Nl|9^Bn8J)Yq1J21qG!&sB*z&AMHY5{)++$(#1Q`U z$$A_4`hPr2*m!0`_R5KD| zKLp4HNCRt!p5JDUxi2gc{IIlxU9~WV{$cjBYBFdsmVIyS#6WUcJKXunBzqm5Yn2{H z);eHAqNtRr<=1ePzR=loqP3}OMO4SV(@ImIE{&1}p7OQfyWKzXRbJGIg^`%_qwCi6 zfVjvWRq&}#1|So)QW_>Py%kJt}r4ip>t7c}cCBk*U$E>BwJ8 zLG%G)zCLTiNmCLPP6w+_$TO0km6e1Rl+RO*2UFa?MX`rP>L-7Hsv?Rh z90LSVCcS`GDOk*#^d@FML@YZ3+OX`vE0 zyop>tEp~_k4pEI>Z@2M?O!nOF6fn=8OaQaazjMETbI7sS)purNiz~A7AZ@mf(y>GzdlwB%@_G)E-9%n8) z7NNO67ev+9uD$zc&?g4YkvR40@J7|X&%1ZKOfhOZ%<~F>Al<+97UOG1#z%S|Yvi{4?oJ*}f~6 zt)W*6YhgDOstz|pgC>7i4?$|u?fcv2fO}!rA)Tfz5YWI!^g3m)0$tnYl4)%gLNoaG(;gJ17Kl8_Qa*bN?ubn{kX|I zft9$oX_T63X$zK$5q3QR2J;c!MYW%4@UpCl!|^O*{=eZ*=H?!#R!&X37!&=lI0brYgbyTtk#_E{pkVGVzIZ#7YXuDQw1-!VmtJrlCD0Szj`sBIEG(a^P+)d`f_lksJ>GK%*hL!_HOa>gC^umV7m_}9=||u5Hq8$sRHqGG`zvb zX55oS^FAl`lKt*=S~qu`3}w;1Kb$a{@KxNC!(v$Z5Yh#5SKb^9t%2LU6?T$(F0kQ2 zQZG&|y4{xF2wFO>pBr`1?CWF|>JUUjr3`nAKoW1mb?LOQe-UrB?8_^Ij7()Zm^ z6}Ti2`CWIH>7fQvg7Hc|Ou*LsKxwzZ#7(bG?kF#XKFQq#St+da64a_^p6N>j81 zxlQnN7U@9P!j{h^zo8^*#7$J<{Y9u{S^KS9pC={F@~a+F$f1`h`JBgwJM+<8uD)>- zEEuzoBABRQW94JBx|&Y7f2(n_%X0qoBZxM``nR6z@$$8sc*|s3i5`t@p45 z(ir$&6IqhAwG~+#%l}c}{Orb=9=A)-Wo?0-mju8@tRz#}9Gc@bad~-OdAdB&;m&7m zDK-7SyZ~g-tg#;pY5KB%#&xtl4uX;#_l7%x?InzwP@Q)Sbl9CB;UDFdA2(fXGFmMS zIyGw0;19igmd<%v! zYm#vd1AWf)&wmiTg@wCf9l1c$(cjZ-8yuJ@+Imcy>XJ`f_3a5w(2EMU?6jJiq%Cr^ z041a6WAS6JFHYSIO_px<#M3}xV4|UfQ`_EmD`KkNR)0ROx=d}caE zM$%LBF7B;%08{NY7klz&2Sk3EhqM?MQGOs{^Vk#?NFpg@bp-;aF6*@)f_ep3q4x3` z>;%0rMCHy0yE_84D)zwP{@xO-xV&VACW;rDC=&CFmTs%@;!iqOvr5~WUN8%c9Pll_ z*BF|&bX}sTTpShEyO7l-B(Gba?^K!iZXJ{W`!@uV>WnwUDy+``C&u?l_&-$GT4bd* zs590uLgTfv4qK#Q_Idq{x_E)1GVsn1(i4(2qq&Iugu!ZYcs^Qw^L9X#fmm2h&SwWU zkqAl|K=gKZ?=B<9UHoNjKj|K9{fo0btE-GBsx+hHzp=jaAcLNP9(Zt=eL=Y?eA=1` zl59VWl_{!!O0xfsqQ!9r*o2)Q>H7Qr9K#lkjEce(Mk72w>3cmj$6RI}%5SX02|a?XY9 zo%jZdT#bt$1OL3Hw56x8;z83gp23P)^1n@B0T-T@uNxKux2M?YK3tMlq0}^EEMAMPFU|$cL~Jx0NXI7}P`rc$>SVe6 zu^x(eTrZCF{0tB11Vn1_!5e=%P0B=ALUA`_HR0~fhOVv*D8*gR$(86Iif@cpJ#h^b zdhGh?q!`wu+6XGyoj69T>X*}&;S&4obX73X5<%s5cG{awdO(pIQ{QvXe%4xkTw zBba3GlK4IHDy;U99ZWi{{wyrGUU|n(Mgv}ly!su~TbL2;gR5t&NXgd#SS!h!Km4`7 zG8KWLsG)H}$#j+PfZM<+u59D*-z(Wq)8I~?$%5_c3((a8M_W=b@ARnB%p!6M`mss8 z5)(1stM52cN{ZGp@$62U@j_f&(WH8)a1V}_UCEC=9TU^h;f~IOLQmD%;sCs`(vA0br+%cn7eRp!rn5QQ;J+(Glxd+t|1h}Sc*WwE z`vFoi&WCdC*Wh0_=#Bg=H(Q^>6K3SRuMU0$S4F>ZNZWME|7A*>E!SYN+>oud9kA-yS}?#k_<*THyZ_zdX$(xj9A;8rl)1$a$} zUvZw052Ozkrlw7cyU5qWCHz=U|6Xffq$z=@lOW$;XG#7bDivkHi)_Sz)*q{U4XN1Q z-K3v>en*|Y^jbipAuHbfuB97D-!cBup4HXD)D0@(yt~To`BE8Yx(yiul^ED z0>s*Se-YUKXQdld^8e1zeuI}|U;vM7X;`1BgA`o=f{h@@k_5{WJ3BjF@7L)L@HTyD ztGT;Gn&VRBpMXjD)X(}F$->~8FD)cf7ZiY*4^O$!(jaR zI~K=)Zhbp$LO{=6&Jt7oM?rrj)0^z<4lWK38hUzVC0nFp!x|@Hv~35IEQk zw%O$7&;!PdtC?|(r{DS$(;kZAn0k){9LnG0Z0x{G<4S&H}*jVQuI2Vjr3b*xa zz-+H|>KIVvLQ)8&n#Y3izp4M(Kd;HbjOJl^78+9VHE}(klfq_jzl)WH#l84TLBX%x zv|v#Ea5URct6cVxzI}&=mx>TpnztIVp1D_=|ApP%6TPOsNIOkYWFM*&eD+hwMM`FP zbRYPbtn?!Iesf1aeB;IO!+B&_u={>nk8~U!x6CtywyKbAV0B-y6>Xy@;bQ*xTP;0w zB54$a@(#B1(Bnc*uZkL-e~t3iTNaDMH^+YD z!fjxPo2OgFSVj@#4Q~nBOZ<-aL^#EkW{57Z$R~khcPKgP(D|Tz(+gUO@QH7LkfbLW z8yStL8G?aIP|&m)FHA7>acFun`1JW+UVm+?(&i4u$)f#b*f;SO-KsC)&K`T|2P&J| zs@qtkO}};=-0#c%WUpKAVu#{_L+3{sponGh|2Gh^6wqERZlA+nNk@Y15*%D?*;^5R z@0)EBRPD~P(o*mZ%w?S~5s~r`ZUb3zN*Otc&amr>hRFj+I~h>eVF zW2Cj~3Oi-CMG_Fqhz+8^$|SmnNR1J_e`I9FXO_99^Vm+1Sis&98Uc{bta^`w(Bjph z7qpB)l|F4lLXkGZsgkgvEL<^J3}h;h2}?jW%hinP9k=brI@XakP)LI;%&1fCfpFe) zJ*^V`ELckavhgtI%P%37RLA@t!+yoyQLB|{yv*V2k>WO1Zq5f>f@mR>3SwM2!OS}# z>z`(Q89Vb$T<%R&E)WzjNG=nn8HUHNsJRHrNCcGjJ%Jsm5Ar=y517NVSV;ZoW1GI5 z=RUmZgGvka^{wycFenD7MP^|*F;)?WXVs@?5HTl*QsK2^4YM%TGm;d3_s{zgU9;au zPAkm|?{##uP8e=WORIhpy(;=2a4X^PeLLr#nS)VWq-Cr?zt4M;&vG8lfS4T#HK^44 z>wUt}B#hmWBr3zQJ{18{zm%gHBj%PX68zNJG<2$m!8TKs5AaJ%yc?mdfZab(RrLhs z^b`1;s@>LPU7PAgE^nAfuML-gMILxmUq*E0FedOx5}VujL&W6@mvzgeuwv;c*(Sr~;j8F0{AlS6T(;sGHxs3au!27b8oxt z{P)U~R~y$@L|?yJb=qtU2usz1!O;CxCr17cOaDzbpW2OGof->BB9vznPa1#+ML6oT%nrzyko z0t$JpEwHyC-Uf;2m;Cqh!NIdw*}tDkNIcU{yEZW~@$}7&pBZ;-5K{3Dd4tH_-^ct2|>X@N$F|;f1jC9`Q0;I;<-W-FkUb=F#Evpm9;E6 zR=7m!%JWqcQOp}&`dmVWTtdZbK>MvAWGqS}Q)M_f=Y{<)x89|L0R{y{8qA0QS#7?0 z56LkuF_Ac+5x`c6elSXFmNpiN;Y=pBLzQ@zgGLPUF4Le&0cF6cO@98~?VMRDxZf4a zK0Goq(vEJ7bc_OWsia;eIuAXXoY_bCFvOp;@?r2zA~zm_;>gV2Wu#%?U@uPHAJtb7 zm9!HLjIF%pD;G$tl!PHBX_l4#06vlF@}R5Ng^X?@!m4P_UYCYcpZ```Z7q^efJzXB zAM^eDA>@7?ot-0&ya!ugg0wNt^AE^* za%?r2PDA?q?XaUNwHRA4A9iE_QKZe|AvOwWaACRTn;sD%>%4R_;wXG?-Th_?+s+3G z`tjID-p1!i54G^u-Rn8wTn>7zxw#e%=Z7J?y%WFzwD!U|tX?!&(kV`9lh z&-F1QUhjaldNK+^PQc(We}XE0PyDaM2Ngf>77R>Ayb1_die9`rZ^ZmN^#H&AKZLyn zR8`&DKf08ZR!Zp*DFK!45|HjLl`hFmmq;UB(gM<5(jna;-AH$L{wKcY`|kOlbMLr} z@xJc8$6kA{J=a`wKF{-u=l-)JhnHmf91`%8pQdiS1;-2Ct0=x3IR9LaJAEO2kKGD< z@x4bWdtN=03RnBP3I5NUXoLU+^{?;v`?Y%AuM4o0m!ntyYA=kd3#4pQbQ*`VCihgYOmjAO?ZFb27m$TcdD~B`>n^}d|&qpIVYo9n+tRxbl?afVYI8MW4wCyuMS>cbZUpv?^HuweJl#-eJ9&b$Z5sL0h2$V!OlKnz_B_@ayLhA z5Z~f7-;IwV3?M@7w>BC}cdq)z4dYf}m<3!(vSWGtn5S zLiK`{-SQbBp~qw+DY^ds_Xk86)R(4T&A$}+Vd#Lf1mT5)q9P|z|9MF|dp;_;w}0DW zk80s>i_$XN(tB1OVDL{6<4!gtDT3-LjtIbmG|Jx>nwPKi;HgHbMnxZRm0RLJBMIdE z1vm}=Z~;`P;((-=h_9;FELH}T2x%D^Nq9G}fI%ac&@Gq4XFy5b6k@l9Z3u(#={5ok z%UFTBIuPoq)9wP(^1uZuRs)h}kJ%QdKS;d*w#&7k)1)Ncz!?dDri3$V5(EFgnTeyjE^;ao!egM0N9`s3((r0=_9gF;?S7JvuYvsy^7e%^F8l3` z^VfmWmApsfAc4dZ0(^`45rLZEy^$smsZo3XzpL!^(|yEtpRyV@e>;doCNI$7RH{aL zZ@6H`%Uchm@nWw0?Pg7bAV8X}u?J=hoV1)PcN4XH0G2&_u&^gB^z;L~`Wr9Lhi`C1 zCbP3?6jbvn7-w8G&A_%uR8a7h;p>3D5-GRi{7U!?_bm}9Xq6M!dwk9TOU5IpcfCf=;p7=Q%fM%7-NPjr(;Ki4G z`RC948LwMRleiQHdJvgxok*;ZsU{wt8$?F&1_f%SBzb3{Ez{Shqo7W-z4La**GKH3 zXG24BYN{ZhN+cTNR%0Ni+6#`4B0$o%wt!w2r(KsSh9B}aiQoMF2j;z4+Z|#uA>{f{ zuEpJH(Ah~bX9Vu*Hk8btUe)p)(amKK6DWq*ecj70FFzI*3!zs?J@+nwZx9$XKbV;r zzk~Plzg=0u_cz@2!#pD3>qUh=&0?7TL>4E*il`**w6q&uEmA3(wOU#wwqV#nIC=;^ z?bt7A`uQP?3vIlG7+lyDtR66F5;ZX_nYe23D-y z)!>jEka4acKw&}lGg^KT?8S4{E=7|RhXG=R89CLTcDftf3L37Rx6Z{C0mJbTgw3~a zIg}9wTs){QF5n?iw6mCaF`2$YrkvIj{Mc_%Xi!TX?}H|z)>jY>EiD5bT?}@v*FQf~ zDw8f!oX>c6p|*t}H}9pYyy7_9dV%*kme4wH#xX886c!mtNO-0XDpBE>Br#Djrd!5A zqzv>$gJWZSc2<_6hM5^YBEzMttNFSflP5<^S3*K99^8)S%Ll@eu`h{OjNi{UsBCU- z#xm+E^F+8Ix!)a)49Sick??klk>Q|`EGsE%@2v61)lEiemFvwGSvK2ZRBMU3hZ_!P z@EpohilJHAeS0;R(jsnWPl5)8rs+Ko>}rf@RtY^gZc-hROG>)2n!PObh9@Q@9Jsl%GdHhNR#x>zJK;J{ z$SJb4mXcaetR`*ictk+(1J7~_D~E!E!*&>7rPkrKn_EJFG7Y@(Fnb^}x%YFsRxQ42 zVci#tq%u4#x0H$?m?^1>9lzaI(7p-AH&N_*OR`}%gdTfr{#Z* zT-9K)|EL-J4ld~NpsQ(rYBTpsQ*tDB&&fb+IR3g$z3{MC- zRfSB=&0JIIyMtwV#BYU+wl7#hJHooMvf-G^i#V=s!{ftxVt%X%($+*WY3b?9NE>Xc zQIzJpJWGFC%<-9C+~w2LRPjtWt>(qz;6}F}9)hK`%*Ga(AXrt=)}T{v&9X6I{$N%t zbdm*8*wmWyd~`IpNOxFTI%m(_@NLX_f)QtG zw_VW7B2=b5^2QP>U7#T)_ntGuQC1EN?9NYA9n>#kNR@l199u1D<#KWS(Qf9X$py)w zckU*ubQ%oBnHW+*4{*C35y8RE6(hT3rohDK)A8ewabnY-oySXh@ z?q$=z%uFa* zFlldn(m~PPxpAU5LGuZrOM@l<>D|vTT9U?Ha^%Gtzcq_S>kQeH2T)}V*T%x$P3%iV z&s#}6YLe?DO>KreM{Y7?$fto+0Ga#;nO1Blme3x)wpfQaW_qjQ2pYPZ%$<>RU0~)a zb&jRSZRpD0z~Nb}q846eQIV}{A_eDY1+4l628MMCu{|&e9*mOVf)*e6M@BRw=e*$Z zvh(*r)|lJx$r3VeIn2+t7vO}sQaY$5dHK|*Cgs741OL1THq{drj&3l#;L>Dg{gMYC zaw0$^^U=u96THCu=kLXG5F#l`Emvw12rCVrRcmTX2?)#DP;SQm zjQ988`n~QaPZ*~rV{JL`ceZ<;JgG4kCdQ){>Q*`3erCU9$XYLPPBBPv|COm{#M-L( z5KCWQS4#6vDS8*JduC-+@ab6!_A%i=Lb31s&+OW`M`q_Y?MJ66*etxyPv|P-zo*I{ z{%P6KDOd#JFYrXz#Duzi5XJQJGVLy~MTWj`LwU0pKQ9?L`XiGcH>7W^%dOif^YQ}j zvbLC56bYNdT##OehPK7S`B zVM9h2I0Do6AHRDKuOCXG325VX&(+;1_A8s#mW&t1%f^rBRMhh;v9NAPy$Jq=CEF@P zzghkxQ#GeWC*K*7Xob>y@G9iHT&7JjTvJB?k(Vf?kp~=lunl1M*RAvj2&dcP#pw5M zZg0Cp^7B}lz&b2v5=NAu1r=rO;u1GvA~dM&^=o`{^Xk#j(m-|#e2K=PJ1eW~KSx3EZmtYn(V}`(nq2i`-aTmY+-b8b zGM0#lNo^YW!WE+V8i>Ne5}{V2i1paosrU%NEMX*)`i^8>DLnBdeTj)~#Aw(^qSM)) zF3h_A{WSkkGs4AerpZCz@-lP#i#ota0vB?Pp#VG-P}ABG;5zh?d-e7;-}h`QLwme9k`m*Rjdgj&6kb+B?ibE2 zM$q@P41-1+k;R4^sABFc5uC%mzTXbq+_WEdnW{WJ52T*=%%ZJk>^}t2ng**VB z{7K2l;jw4VZGkJm{R11t2YO{OMSXh0?@ZE2}}U7l(u(dQ!_K8ygz~Ta=y+v?s*ihXOAT zDbUj%$QXoDzt_}A$nb#K@HR`tV*Ih~MN3^hweb6Q9@?%?Z78@F7Sleck{|WD zlD03I)DK^fQtcfvKx8?aIwrnhCwrvv+TU|F*au5iD7>&-Wh zJ%cD%G>&RgB=hxz{Eqt#*lP_|a-^g!va_wGu6;rZcESqgBW8t7)AAce%E@y$C6jr` zkv}%I4}7z?hhukRq&D9`f9xF|t154Y2H}W^ot+*=kVdp@DzhM_$3|j1seVaZ{p#l0 z?l;4JYO;dn^ENB`5(gXp&(ae1vkM5KpadERB3gQC^?*ffd7+L<#vv2=Y1*?M3|!o) z2ocn5md)Oc;WQEvewR}%J1_`?M(4#k^>*}mxwnrnXzAz>Aulq3`x6swKLB1iH-aOe zlW(Q#Bn6~a?J92U_|~R$d0bOEbZT-EgLJiT$z~7159IURxhi!}Ko6^(l@CVaU>@2N zq|f6J!~%`O>~Aa{kkN8d(QQ7yuwkmuh&Wmhtf`T*ab&rP4Aos*;dR`{f>!(EU~9Ye zi;GX>S8U4Dw5hWmylAiAo4N6;O9t5whwW-n?xv}{FQ7h`=q>r8k=Pxb&Voey;MKA& zV>rxAmE`)ye}rU|5x6F3`Ep@CUC5TmTEoZj_7PK2syiUunr>|Dn~yC56iK$%ySx@t zQ6K}JcGwl~lR}J3xT?v|pP4K4P=>(<>lc6mT?BRYq30*2+4~RXvq@`c(7FC$W2*~v zVVo1w%F4~{}9Nqyick>TuAqj|O^nKi7tvh3^aJ3cxpUNGF( zXW;pzJ~!7Cc=NqeJ5OMbPV)uwLPSpV{G0;x8hkA|Ihrlk2TiS2XRj=oczn}@yuXL()$Z(7q-^Go3=(=+SJq_oR zG`hB!U;BHGd>!1I4c}k79#kd$YJy6j&9dh7n25o}LC5l!Tgkg5!SaM&7j3j{<~yfQ zMP+4!5;aI5eF)5cQ1$>O#=t)zCgvxNEDXjP*#!^bAdQ!o?2xaHw0C%yK#e%=VhcW$Vf}~ z;lV^65zqf!D6?j&T^VB?_6Ox54#)NE*4mTSgWI9k+w+*DdS!Y@7>?7Az*HlAMsVu{ z(lve=z%Fofs)1`tZRzMDh{rg{AOYPRxSY53(YVKq&R=nk2HL`&0*kQ}JUvmZ`5->VcLr za#q*W+wmtKl81J9L`Ryv|Gu|zz}8IO{{<1#zMF|gYM^s_e8BWuW!4Cnwl7z0ZMBlJ zGPTu{KDA72>g(EF6-_ZQRaJIgO2CYHw!y5`fAP>WW&WuJ@s9UeB48fBZlHwOmA9ow z6MW6b9_Z|ahnQoYLB(pB`uvZJ3QS}pkJ3JK5sN5Cr=`82UmMMzNmQ@qEJHmMacF$x zBa~a?Y=m@Pv}{(c%P#+O`)a`68!!XTKTR@$?npM(rlzKLT=j*8KN}hf8yZ47mI6h) z+NDo};{YIPV-o?|#a~~bsuXBQC$p0O{Hk#+3+NdKcATkfeIV9^M-AdLx9@H7lTQ-{ zrNU*nsoIK88!seJhO2WIHu)**@*lVJ13U`yN=modsxny1G+NGS+YQf$K$Q59F*YBm zb9BZ=C}Kzs2+ks^I0W_dT;8V=a zVf5m7G476v>Nt?WMv~H9o>oXmOIm7JXu0xm(ozK_UTWsz&Nev}V?*%sm~Q>zu7}5Q zh{7$sz1yHYXfS&4>gr3I{DGyzQr(6qFafhbTSQQ?7d-Pa%QXXvVuB1be57vU0ui2?$)i0xC}R3?N=-wmk}{ zC411BdY37k0Blj54wuDk9=Lt@Kz<>u0n|y>)+OsOf!xK##fgcD#l?@DP9QV}G)y3A zP6Gk3#vLUq3Fj|3D1nk_X|B7{aYHt-9F;%+6vbUosY00ZzGHv0+EBC=@B^iHK|rX9eTpqNq>^ zrrpoVtFt|zrUZQuHtcGG0mC6Fl@`T^0_$C2aQJ!(@r*MecYz$u^Ts4I?StgpVbS~2 z?$6S4wO35u1sH1T{!PKln{HB}h43jaT+t(_ zJdN-b%d_R*nHNV7LHyKZ&6YAS;C}JDNkCUjY`U}a)zu39;maR}FSc@u@+e7J*x2Z7 z@Y3YEkGfPw-#pcZ04DNl4s}eD0hWVz8x#RjRb+@)C&vtYXIn{Jze%X2zdXl6P;IWYfzr zzoK#Je|+c&LqJ#_gJm`}985cm;Nqzm85!wT3mHmsI*)+7K>yg-@a}ez+g}nVHa)OU z=FvCBHp5E2ed)dB^daLEj|N&0XfL%7NJ~qD%U6__cl8U2)b*fk_4@i6%J6r1j{2q7a>Zd^|tXpDkc?5Vht}HHIj_Z1Kb){!y zv@IQg1g>xCzyB9|&HsP~eh<7x7kSgBaE z8p_H%2V{9+fvSo}t^-A2Jsb`TkHrUFKj!A<`c{>$>2Tk^oq4vUiuHIXqUV$;K!$#e z_VnNY1UA}~^Rt&TX~}31plktQ%*uhIlao{0+ko52Uj>yV!!Mpce@d9ZGJSlN38r6$ zfkEXSJTd=;5vUUn{r2ka&TV2|!zAXDxxLysqulweQZ_m^GV&_U(#RFo`cpSrS{tfeOfEkFZB_9ixI?f;iQ`3mZqf2EO#-84h-jU^%Jfb#A zbM0Et#wP~P?W@aKVQy}rX`w%GOhxiayD&5``$=d;SPfy};J9v`&Q4c;0gdy-+*trX zMTXow+!8F5>d#l-*`1H(t|w|{VnV`y(4mWe7ZX`fQxpGd@cZz_G6`$C*i~oaPX*b} zLa9-zN{Nm8XJDyIY<+IFWadwR17_4%TMCw z+ZPQt6L-@>nU2>req{bzB*~RG?a_R0h|tXD=8rFpXS$_NA&}f9I_^8{)~=P99E7~Z zil{w&{q$>zUI!W}9|axV=I@N4Ol5dpP%m^9c5r+lQD!r=;-LTx1`1WzgpN`(o)szr!bp(Q|Kv3%CQbsA$;r`?iwol^h{85iQ7CM@@R4!iTw`T^ zQ8Rx0lLqQg*MoyqMU9VlUToZ4{c6t6{{9F%n1|OGY_v55IRVmF5ig}}6_k|45_!U1 z*Y^$9$lED>6+r<`p_rqqb2^WXzNfRNY%d-qXY9tCjCvB=VRDvo`BOE}9Eq+e( zbuc7>^_>;+OA2vQLG@qkgucjvSU6rn847+F)-fJo-+}QXySG z5M6&ISy5D19~~SFOGw~ucL0{_oSYx>zmHXDYHKTMYcKuz^8!{K;vIO%j&W)am~n-S z!nl}HAwM19PM6e}m_k3p5d&>tMftX#v9Z<0Z(OLe-@VOZd1^xK$O$M-%`f5%OpJ{I zAY~6D?l$cBc|2lFM`y?7wvJ^42j_NR$w{>-B_(xuY!IW-z09As){iu*`3rZ?Xl7%V#u3VK+0lwIGb-`?5;_B0=#z(Ony^#>K|?Y{pc(ZTW}NOG+*X(#$mQzL;`!G4eXgjp%5a(#tqEjt@^j0 zf#jHlojoYXLn4#k-x;(}>HwF1xzsx%;_jNI@?1VgUzz?%O9*J{sQJpHAIx6`1qDI^ zf)$)7T-UM>{@2(48WaRVXR_F=OY-A+X&8 z*1ny_Jv}{TcePJz)Cmiv8cQkEf#I_cG8kS<0K2me&)uw(~k2(}*f6 z+WH@nK~+v$yIXaxHaBS1cz0k`9I8@RF;o{4YBn=9)Ar?HetNi?_@jpOiH5SWh2=ay zC9~uRXTsPs!ZV;$SJBioTYx&oi;h0c)lfES9;{EbG9IFmChx%)2tSBaHkDDUTx#{@oAC>Cy3F z?44)1WC*(R$0x@VLp#;6{C<(Z_IOa8a~>Ns0G)OL(aMz+J(+s)-JxXnkY3%?lnxwH z&CZ<2t{^SzWAe8)urvAnf~*Wn(^>Oc3I>LP<}?bTMF(ks1_L;DF%9*(5SI5{)oTzf z^M$4y3U$j=A;X>m|6}kb&j*BTXcNVP;cvaeN?}v8l?5QWGM!5;Yj$bWR@QbSw4SLgyUe!h^;@ zA*DfJY#ap`CQA&uhy(;lWySEG5mi-XFKyj-rlsYJTpSIx>(s&%T_;VjjP~YG$JoYs zd4lE{0&)O6$$rst6rpnCt_tKxm3sRCVKsI2Vf0P+%IWux_&oa zp7vC`5ke8}R}6+rYdO@iIyP2y?=2{o@77}99_`NGximFRjTd!wsA_95O`HzYS$*aq zsJ2sIoT~?S55U#j)zmfvu)~95!?v7$4IJGrb^BIOw$n*29ryIeI6ed_O?hw1 zGWleS)nDqa(9J)bf`MUBdrW=a)et_nsJk9jm<;Pn&z0B5gtG^C=R)DE=f%VCQPmk#(w||a+?1v1zGUj zt$2o9LQHOcJ_RYD{Q>JO1k^WAzXm!m9pmES#>dAmFKutfzKqHF+;9srtj_}P4Gp{B zFa!H3mF5i!fPjw}EdrvVqySAX>Lb=8Qn42b>vrghiV&GG3p#dWO&Y}~<>;^he#?tJ z9JIaMgx@&q9Mnz$4<`R$iGlz2+jSX$ zjeCAae3t`$P5mKKBc-Le3^dvFG$RfqS_!&x39;1iJw^>Z!o!0Lqa)>v?*V<{X;QU+ zacL=tv+0#bM{N#Y#c=`7G}NU3#%!+s2b*SbW~OgWx}w76&-W*_iQs}xs-10(O0sjU zmI#os7{$fK#e+Kd&$BpS^Dv;lu!;zYnA;2LFAwfacfL&(fP~07NAUI?K^dx9UqMO9 zaHWm{J325kRgf=C4X@~v{s>Fi&kESZ(VEa_PohcGDy_xYR_r>L?*<>_V^efB<=%6y zq~lY|1^`5HZ}lrAaK5_)NCq%z39XWrrNi=yiWU8L5;6xT#2J~HqF6xCB17rsE2)o# z;sdypb~PwTGNgRH^i(T8UI>wqx@d%}RCM2>5`{9?mY0jw3|$)lfsUM=eSB;zGq)!G zI^vhd%+n!@UtnwM)Rd%ZFNODMK0Na%UE)Rz$}ssL_yTYax{vJuNUb_v=U(vPV>7t@ ztg0=4|Hd)**Hu?fh-xj$Hy>ZmaBZ?ut4Bem_;4n>oPvfOxf#pUwQ-l*EA>Wxw$mSk z)T(OP?8b5yIy!m=86%izV1)*Z!l*m4%|Bl^NVzAbySkeUJMi%E`r~-@z!DFPhO7@{ zsoo%;d$sK{~P<{ZNMsaJAT>ZOMZTN6v2#ki&>TAZ%^-bRM7SG zh|SEnB26~82piqRv_E`++1sB+(;pJ%ek@abxT;h#+08@vMx}YH>Grs(sqX5m>nZ@j z)qOKZu`WJ-&BYye<>R-wQCz&-&5hC3wIwW4@)2oT>$ZMSGAo0}xNrnuPnzA3UaH*r zzI<oV=u14v(K_D~;8PTr9^wiwx&Gdm%mc>h&}0MGnq@-Gi;< z^#L}Wv;F5vN|*iLJ{$~*B{`7Xr5YNqUrc{S>WT5#AK*m{VqIJg5ZfB#zp)R%{{>y@L&g5t7 zy>{vj1B6dIdou7At_Qm%fa}FOT<wjE8@HSQ4}qg`gBKVdMv1&KCZ2&+<7@n?kHN z$u)&VuOty8gC7!-d0g#U6Br19n_$s&xqWN|p=?(8hYvzK@+^&w&CR#B)oMt+zI)0| z+;e}hHjTT9SD2roVWDhoZUJZrYaS?y3jlrt4j$g1W6_YdurTr)?vpd2b0sM%O9v#S zk)bG##{T!O;2#P~TQJ1-w2|`(Wsm-C%UqC?^BBbk4L!IYBP-$c#$MC`N8SG%!`ht% z6e4n}s#!lS9}DfJPStRcA>-oKg_k2-kDJ#YcxawV+3sx%fH8s57!*v%D=HV{6{#w$ zfXGipPcOYDo*=eoXB)7m0V7Q6{J54p@*x*D78pZf33b~G7J^_udcns+JHZ`GL&jAI zR;bI*+s|wXX_F~%P*DFOU))SI;N22g`SN@|Lcc1`0ofKf*;%&~;{7?Q8o6_4lz8{< z6&o9;$F1uc0*HX$ZS3~}^Bm_lsd5lJdGZrN!o0#lK44MVj|z6~u-|_PQO^0@i>^At z^)){W9f3c>OcbpmFk$3kV!Zz_$eYZn)<)Wd6P_qn8J*F8Y(^9dxwr zukyqD_&M7szVXa!#Ey+SvqcZYO~VQ!X~ynJfV4Wi1#j1yA`@s_7A zS7z1AQkmhwmM$0li7H&le&})-gF^J5J&$kNyP|?WC3B+81D80 z2l(B=R*ZlG==|b;Ii@zq1n3Zfgyeq`l_0q%_zp1!{Y3>-lGuT>&EDKVz`+IdsI}6gE6=p%dwT5mz3u$5JXIY>4rrv# zSvfk%Vsi@>s<^mNj#F1mp`Lj8jkLlU9oYxM`cGm_H!ADtbU}<_R&QrFk;_V{VG5sq z+Q?WW1~HxUvGI0A-tCzenwFM+(hKORlN0!2f>0vbIl1NE2JMuht~mH+Jg$!5N!syo z6Ek+RkXQ;gVWUOu_Y!$0qd+h}z5d}XK882DKA))-g!Z|fw+{9N*Y)8*iEM5%%8F&$ zRmG|00CI!fTs1}{lvTzO%31s>%#>VCY}9I3rAJG-;wPl56!c?aQY-3L=tL&;l$V)}Itg32H^evaDNCb6*#JXCL}YbxS|*oI z0+@61%rLk&3Uj!F57l2X-B^yS69I%=F7Y1?y6wDZneA3k4t&$jYws?czQk02xDbMzzjGd<~>}kkQ5)# z`|{;eZ~VohxT^QJiM=c#87tH5{lC5>z8U6-qz;Z%^iEXNt@yZ+d^a8aZU21eOPJ>* zGt@LP;;r?v{8L{yI3gw{<}DOwlE|7DOUhZ!ABXL5)jrQQb`!&-*&2co>OpjJ(SDL< zj)P(VL?CB()zQ5HS8KZ`p)X{k6szae=BL?jWyHT?K5F0vb+t5!b-T-f+Qu~=7owwA z=C^gw1^(x+7Dt)rprNRgv^#+4vnGapuuMI`6fhXz1T5IX?*dWAd>>q2Cosf!B0*jf z!P7MbJg;BhP8WdWhznqAFNR0P+1;-~YMPtDvb0UFy3DTBW|#%voAa?u^RfP6s&MqP zXW)~LHxvOY)#gqjr$E#7{Lhi@X`Q%q0TW|C>Z5Rv_$MHHgyk1tV-gv^WRm5v66l!H zI3~Z3GiuL1Me%={`L_Pl-rBk=-T1W1`Yz;b2}7`do5tz{H7n^)&q)S z+P)YUtJ8aFBzzhr<|VOf(=H33v;FC4jI^NOuPwgWsPMRxBU$bwB+Lkn3;#cJNSNE= zk^^eBw?{~rB)iw2ww}(*>r*jj$&zE2H^ycSVHcV}^436p1ok-Vm3|$#)1k>^fP0LFu+D9k11D7|LNyg!~ZV zai0~&YK37AOuH{hr0^-~gn9Y;hCO2*P}iU${8A;EV z>}-AD)6;G2hx4S5?U&Dv>LQhcX6^caBevmUeYRhI7Uf>1m87$7li@8%ok!;`=f=FsO%yhEm;Ze=3Qao1b4JfDJJ$ z{Q@v)0Sc65^n*T~0R%x5Ky@JDtmx9{KHf5Ki3YW{@ehp}w72micv|bZg{E9%L+0j} z+8{ZJI_ets29`|{D@0G$-i$nhlKF4WYE2l=c1Uc;EobH~W-Bi*oux8E0E$_da>}9! z4*`Su+G`f3Mqoeklprus50spi;cVVCHmcy0Nf@wo{cQSW+IuMla=1YbiQmt+@ z-tB5a=ZAR~1%+W+X|`LJ^NY00G^7yCh{rgS{Oy$r^;#If4}mW2?jp8c8gmxCrqXRo zRnJxIa(D zxLKJ79GE1}yLpTU1%~uE{aSf}dkiuK+wP-_wfzq|X|N;9IVI7zd{4Oz%L^HgfCva= zw}wHTV&qeH!fk$$)X4y$9J82*6CkyXN$dvAgMg35%S&6Zjy{W1$7kxQTPYj`MZ-<4 zNWHYSJN>E2KQ_eR>DXL)tz}2>2^I3k>5wFO%=9jgY5VQx$jz2tnV{w@&xt_Fg@k`II%3g#2?3Iuvf(Y2>!0WTT;n=eZS=P#^Ma@ zR*fYM{H~2Kkod2x;M$mentgU3izBC=p6~@9b_93zFaZ|oFhJ&MfoyHcGu$7~>_%UT^vdMxx#@L!4Mz;MRdM0m05WLjp>3COk*&YVKQI1BNj z?UulCH2jmDz7yOV3cMfv2V*m8+_#}X^O>~uPXcB#YjPi#58eh`1)2p?-*TEvd77}G zH28v?H3DPpK4VFE{r+k8Z>9N>#*KfD(f%vs*!2^V^CsKgrn%4K{^upp)B|`P1mJ{; z|Vg24x}pG=mldfTj62FO$Rn8`W;QJ3dTqHN1HA=uvz7 z3(&!GukQQ(DUttkmOxa8CE%ac+TNa*J80`s+-QpRS$e#!Z3YxAyy6O)CB51ksf4*M^L*`c6lq(^4 zdJPQPWo)U+HZuG}FEX4?e=H#e0W5C;vh}~Clv)T1=s@o!mi`AH0oSlL97)ONyPVw<_J2C!-gx! z=aXbqRTTk)1P5ieFBHoZ{j&h*2D!PpRju{OsN=VCqKiG_{>HOB9hEEU-K}l4`*ReL zXcPp7M2{PFfRM+&6?^}bZ^l7y7Y64mXw=~aN)t>`RVv#M_+4FJN&&10_HMT0i1(fG1g6#Omcm(qaOvl((g>0Lwl&!+lM=>;7e-)P>Zarl6f z?ACtzY}r#Sx90wjYXu?Q{Qhg?27n5VV7^X%XlzwQ5+fX=k=fP>CvaeZtu(-O8Y4RA0Nl82B3`*>sjP29JA4z?GESH$zC zCZ^q{GBZfGdY1F&+;Q0W_ILLP^b^4l0?x($0{VfJQV_{#D=Xtd>vlE9uNABYgEJ4< zsV-lX{0|pE^M3nr_@phq+KO=N1dK<+ct97ov*E%$AP!c1va~l}TW$PH%YY9KIW@WH zCHbGUBsWe!TYyNuBv0*O8~I@UQ{Eu}o80?dlGEAFT1tVWw6x0&mn?-FfZ0KY`SBIV zpD*}#X9^d{V^jOH?l`zwo#eWMMgZ%eH}Un(dC7c&#it(w`*2t8B|mrZ@$W#}>AQp? zHYOboS)P_qesd;TS}NqlNE5}|vo<3LpxAGN!rw5Ly?OJz@w;(De-dbZ+;idjIm>VQ zr)A@FY~VCwuo>0GLcqYb8_@9iHdd3ZFM+$(M->O?2}S;01OAhIrR^Dj3SG~wTt>$} zjDsIBC1MnE=y7q<^mX!x>Bdc&;$|t7|DorXMpE=|wd;4cmPKBE;0lRkC^+1nAAy+A z?dFAON3kEqv+X~-bdA-(fB~-Qb{+$vSNnLBeHwZX_BnV#|2K&kJnTkui+!364?IKp z;@@T{fRdo`>)*d|%`5>t|Jb7pwma$aUZts7Isv$|Su)AK85;UU0*zTt;Jk7e5>qrv z=7GfztW}==25bEw%#OycCO4S`(De$_DJ3uyxcAyh^oR%LI&flbMSV#~(Das6lxUQv z?`upUBXQ~1Vxb>@WS&)&!h06)zk`s2{FU~@vit}k*9rqgLjr2blWIT;FkXL|#fjv? z4XE8;pD5=Q6&V>DlXVM_poBU?kVbsGFCwBey@^TEziaemUd%OM!$W=^^A5YcU2)8r zKE5LG$(-}%n#knOQUAR{1xB0mbV?aeI;Qe|V$>#sJCKpfHVSmY`jM%aqXc0gpF?0t z#N~P0-RE{AYvsSLtK%+M))xS_q0E3~BA)1}Zd9rYna=_f$Ag)Dx&=D5f7<)Fr!=Ne zR}rqAHrFw^D=P}+y|Erb|4-4YQ&WWS|GVbpQHeAKa+drceENt;CT{e_?OB&!Q>!(8 zM%KO4>Jk5cC9ikq8(C(bcGoV|v|!eK)vzEb92OQHjk^se$vglnKjOc6zV#LQ{)&!n zaPZy7HW`2f4xT>(@-?p)Qu$J{)Rx)kEG!cIwyU{8TOuxgBZ^s_i5Uk6>~5mTTYA<8 zorAzgVgfFZxKoO2HgE@{NJid~u=p7gDTtds`$dgf=^~UQ<(nfB-F&Z`kI> z$L}=z47krI$0Zl0Whf^{vFz>=2L=X$SHV1bxqPtPYid?qJDtQW$$$?NYo{1*4X z^M9385TL)lQG2N)9qe23S}^rhZ{M@k)dGJIMsR7XCid{#EuW`k4UDmmXlfi^Tuu|C zkT{>y7~ZC>9LOe5O&ph%`~-Ieg>INOgI+UB_8%NYc45 z&;KFpEx@8|+jZelR1^@TMM^=syBU>1O1eZ!N*bi4L8JwwySqU;N2R2@OS*>cA!gtB ze&33{*1vy`_jtiWn0ex!`?;?3I-|u&$my7u6L{5vj;i|5KUI7qvBoVi>GIdmkp2%y zr*58UwOl^Y|Lz#TqxpB3-{1P6)W_%L|JQ6eW2#a3S*qvU&}I?I){vJs7XVyD zKkX@mLt1B>?RHO+UB8F6w~N$$+_xP*T7P^3oHLAgo)yaNTg1m}EPBSoL|^T87uzm^ z*%uARM>{kxD&B^toVM#HHBNUP+`Vmkj~sGGIK`_wn>`+Q4QL3IzOWc(r+E<1Gric- zSpr2YtEuZ&x;BK}u1m@RiG2G_eXXdhKzHnCaHnl9(}?s$%~fAaobe)~^NWTZWcl$n z0M_X?f|wjd_fDtFJRGE?ZdjR3?w*iO1iirRE7}v(Zr`ZxV~FcNeD*TE?D8rVaTfYh z-}K$vZ-D?<;}p@Jo=O12-a}-SUtnN;db(G)dt^)wfF~uzfIWcUjFu`FS3$L7FjY}+ zcOl?_+g!Oo^5otSWP@xoCY{Cshp41lQYmq4pBr1Y>Z(V_q( zuM}w*aM~`<;MaAN&#tG7*_IaGtV!xLFH@Ki6 zmkv7h<;xudgUG`*n_UCY&8$ZV%u`pf|Ou=<@`UVzf=zYZTed$ z-LW(-GMH-FxU(RYmv@xR(XyBs7KNK0R9$_@r#pX)^2#Z(sA1|D%5 ze6W1!9pme}*4zXrB`^Mi>pQ)B8`vEP{fD0hwbnDAN1I4M+)HRe>3sfNYK?SpL9CbY zexVHnMk@AEjbZQ*0nLKvyu5DLpY!|ie-FBe+u9C%2^{7ciXK$MsnBohDkv!U@niUA z_?oBrpHx0=o16JvI`+xtm?ak!j$K<^y#~6(M5yOKon1uz;!W%Qikx}vWIKI+Tj42n zMC*qo>%^yD>Z$&YhCg?53@&TFK7tTQ||qpM1ybtv1>?Ak@~&F z1Kb#DXZ(hgo`sE1E>ym$!KMIR&F#OvO25j#xOs;t#H`a><#AH(kmt#A1HmCB}BcrgI1+ z?{fvr_b(0837mdCos;S8?yXx33a#Un4GS8w=U+K|iH-wNAO*>f5U=_`^->Dljqy@> zn6QC?(daT>=Sxyesv%zXJQ1gv0+L2fUuOYxB;V2&DPH*LdVDeZnYw%&h^-_34w4({ z1C0$^7xwMn$O=qN2~$%WjXq{|YMoxL{QW!arS8ZJm1ozPMM4htBv2hNUhq+umRtOM z=^Sg;P00f6vT?W^LGT%E3i$Mtb6jLl1%7|+J^=JGBlkdh==`r=GrtEEfQ`&gYCy*duE7&6_!7jUc&w;oZRg}N47%-lsy4PZjD~*U zcgI-N-NojQabJtyMjQY?618mGnrKhx6Dz2A8$jEUA2HdHrd) zWJ(8IQpVGt041jK_HA5D48^Ii4sE1>AoqYhKSV*pSxRb`LexH4SYvu%zz8uhQpRo+ ztio7mI^87XDp91`5$ZBF=1W4th_p}Sjb?fj$8G)^=G@uYl@YPLysn|iMHZUoadq$N zqHyjmn!Nn@53zeGDbYf}2h%~bObt*Ma-rP-WbGB$ow23_rlSz zdi7b0Q3SD`MUT6-93{BbG?DaIbEA6 zVJ(YOc6xmLwy7ZF0+>mo3Q9Kb%RLAVMWM`?E4m$M*24Gk#sbC zgqlxZipw5fFcY>cMD?6G0Oz3``CSkkRq^A+Xm56Wd>sy@G#A$>88+lkrmD7vXbXE> zux=~?5s`lMb1xk7{)vgP4m$nLp~hvP>o_}G>L5(4eE5*2uI}!}24dt1A!Cv>WUgHIxo+$vdvfdd)%r^ir4vidj6z>nDEr@nBN!} zC0NIp_Vz)O%`N6jevDI!AQzI{U_Q;Qh6C6~=j3EZYxGtH%Dh2jO5eFxh26*C_O|DZ zY-B`K$(9l$k0%^3;4pT-s8Cfd!ZJ*poN5Y+&%>E|dNt;{jrMtpAh#e@$^N-D36vr$ zR|+jvz%4V+?(xvrLg@x-BjGv~BMg*>;x7C;0&&&lg81YYG+|=)b7PpV$=XD-57Yqh~JMDKN+rOW$Hko7I2{d_u5@k#%qxRr)Rr~W(B+P&+S2mvD z6s7`i7HPsh|l`n;1~@}&E4sS3Xm~`NyG{!?I&P% zFzxDGFc)kqV0#n>!e=}Jkv9(h7Xb#$$Pge=|U1I7i%W? z2rRzaRr>9rz>zm4r6h)^#sHlS+*NE!ksLXuh1s&`!ZAEW!{sZ|@y}B=hi+%=)h52C z%Cv3mqA4}+%e7eH9!oh@Rihu_sO`xHC=Csd%Hy$2*YRy3M|{Pwo!OE*x*(EQdGO2j zp3*>cbfKrd?)*c{U7+ME(e*Sy$<^Yy0k(VY`Z_pm1b0^9xOEwubiL!%>M9HTQFw)2 z%Y&uYr#q+4F31K_KI`L++3w!rjb2TsE-Wc`xWzokRu?fD`0=VduI1yshfhB3T(;>} z@3LA$AqsSXG)GtWCYOe_NbO|(fq|%Mx|-oRetSp?yA}5ZQZDb~wmj*mDTvXtITIf#&K!Pf+Zs0R zW6|L5Z_P>x4-b!wT$*w)YtwoI)&v6cMB38@DMY1@dN(k-Qi!iqXEAjd%&R_az5bpG zqn_+5h#e&u;*t!Lmjw2&n_u6BuKy%w%)6)8GqT`fW0ycOfBV&K9CbbWd`hCgpT9cnMR@zOnZ{7f}(*o-ivSTr& z4&=i`1AT)PTS3nSrD*&65C@m@YryzoR;vQ5@Fz4ApvKO*I8M+Lt`8%I^Xqh(rJbmsu~d3!KtH0PnY ztNkvoWmgtC+=5~NUaG&f!DS2_L4g!1;aO!KFw;Lb7aFk2_V$T0baUGJgNK04 z%qGv)^N5393Ah`L;kzHqbP^pQP}Dlqb|N&nftXD@f5L@<{w*_6ZZ6!w+Ez7JiUas& z&_=*rPn%6}+IoF)1MCymxWI|~rH~D?wn@!T*>@8FG z>kPa8Pjd9s6u_csZgP42c%4$%9ySbc zY!x*p`M%FAgj#U$kTH8^cux3p;UnOx&__#xb%tEu$Cvd-1NG9BR>3+IxONJp&$>(R z$G=%!nA89@062QTXLVY`FM&VvXudYE42O8Vj(eIOV` z6(m%a*zD2VeKxnq%;M6Y_0fXe_y>As{dc!O*e`alSk+{&#AG@7xTxzI8|7*Knq14~ zmjK=}{3+c<6Oq4xkrBhA`|O1mVjz{7=FxqS+(3dI8JQfOnp(OM06qtSu)&)*g%s#G z^mcoquA5kO6~3M7$Y_p_WTk|LV#fxn3PIUH{%%^@Q#v|j#=f;Jl19(ebnweBT9)yo z^j`Z_r>FCCMS{jarH$=G`b2~Lnft)U7Z5L1ZPcN$k@41s2KzhV_~%e2YG`4CpZAmW zw$W#chDOHbVD}v;JIc$mYjSgEOM;G9#O|Kk<*;a)HTG}b-|+E6yA3HaYrNn+-`1p% z59{Fj*7Z0aOJ4JQPtwRX|Lem3!^+BY>-iiLK{CjIFt8GXW$D62areK5bdRdBhLe?* z3BT8K&PumaJoSA(+t5+v`CeAWPMNwR-pzMB z9TyQfWUY((nToXv%(uT4FYn*+63m?9+6C&+o+GeANIwDMQhPfhxS;DP(`4@VRg!ZO zH&!@y)>%P3W{*RyAozQZOOAz7vWws%*6|D{J3$L$J5E-gI>eu$i$~01?^nOWOnB>ISlk zz!sdX#DM$2_t%Hs1k5|g&`>#`CDk(*bvep&zrgnMdrdBA?{>5vuJ70j4CP39?W-#* zkt?yfQQ|2H30*5@6{Q9S2FEDp-*56W3xDYG+ajIb6@3S5fs)h^RJ95$5!SOoP>+m` zPW&tDx%XynB1VC4GU8f)furjk&h5v;n|qUTnGHwL*J;VYe9%$-<2BI{K~S zGR{l18+PZzgk4WcftLrU-DCL9BEaFTe5BzN?!N;GYSI0BY~OX8HUYDK{#S;6??Yh` zZ8V&H3rnUk;Um?&!aDOoygIFBlR=Pc26fmRxr<2zF3RtL0RZ%Gf_c=`)Rc0roOa`< zKp2(YE>&Q@!WxoIM;&cbjawhBIiSl z+%s5Kc4_I>Vn%kUu6H%s367S#L$5md2O^e{!9gILGq(OvRZ$@$E$woJtb$Q-u(K~8 zIsnCXpk(>Pi$q?_Aeu>F8aO+WKYV#5arcTVa2Z-3KS6<5ID# zIHnONiI9wcvIrYd@VT34SO*y1_4fC7_xDFiwzi{~MNBO%wTt>e&~Qhqu)UKLsfbGt zIGzbM^dPw_HssikA897e@wbdWd?0ey1aHneH^tpBTtg?3q5yvYal`rsocLd~neLz4 z#=l_=f27#H)Pb5*dydAcq$aJGpY=}@hf2II=&xwbrPolYgfSCU@cjlUE=_LdL%@|` zZcc}pr~|MUNOG&Hj=}u_D*T=l5!ZmHIYQVwap|X+^ARelGiHeB81B)o) z;B-mC-oYVTDIZ4ljH)dJ&iGp0aUIr4)sIYJY&Ys$ImpV&>ItHdhn)S166%hXHrhmm z0$+&xetKP6@plUDs6QRx;F4)_l8=bQZ!mX?Kd$ll$lHp)}+<<|r*HTXZqJ+;LY z72qDWT~m>nk6BM^c{Deo4` zT0bTU57ORv_jTrFcag=PKkv7@xeba8WGke8WROWYrn)XmLJ53#U3ubo>`MBv|jy}kW`A>BlpcZ)A3 zF&Y|9G~aJvg@;XcHkq5so5*ddA0JK1YcbK}>LeuOiiz2s;n~`?%<0$Fj0CGWr?61S z@b|Zadk-L#^#2qt>+5@GZXR-0R#mXR?eiiAtP9h^>)t6Zg~c^WII@;UL5eSWLw*U z9D{sTWjQ(W!;1+Lom-nd6Ay53C`DXULR#-as$Pk&&KVkgAbI@c{+-VNCJ%&V^lgGt zU^GPUF>7r1(!QaRhMHR4IO~`Cj?(y4T`s7sgknfF_C$tu$TV097FxB!Mov>p7l{oSfBN3LXrHl!gY^*+gI@o%X72HxpPs z{vckw=`kTyk62s&)-NMGzYlH!j-PE{)>9T)S|Z}C?9M{F;-M#`_aSK|uWt6OP3f>8 z!PTjwWJ&v;DpMB~6>>l!|M$NwjuuM5Zni?){t2+l5qyG{^;^5=ref!9u7h-C$Um*DD+3m61M{A; zY6Tn~$>FE-r z4FMGu(L8JdkN(SJ8B6u%sa&uv30iW;o6|^J1W2qzqiproXkl;A3iTZAMH|$(yEKH< zdb)Tk?IPS!q<%jBmS6oS_w2-t3YUyY^Ey?Fx$Q}WczKw=YCo*#{PKV#j1r28^(G|XBMG$mwg~Abm->4Y zoO?&i6(80M71ZsPQP5eg?AZIG&Ghgo1sklrGN-0wHbuN&Q*xbEcW2(IC##7jK z>8$0Qc3Z@JV{5CvHcG?t$F(6xO7CE2+pc#^e$*ky18BMFIiMgBnzupiUw7HDMks^AV}ISnwrUwSTq=Rh%d;b4ElocX3L{GB`GlnqQJY zn7iugg8vA6qd89YO-XP|#{ig5SZp#ylx)M;6=u($z4 z=U*R5D=VFsFTHaNZUHOGTCT?1?U$m}-ZnnPXw^3rwO4}LPv-f70k42gHhPdf`-e#p zlK1ej--2zOTQi{Z$HqrzZ+v`lc$sj1&T}|yPXJz5F}!=v4@6GZ3G=b3GZRs_p?kf1 z#TpyjwwN@dB_IaUC=;;3>Ua^^mAtaGzW3O#v%9}E1&~A-LEcMP3p+3&O}Gj7d|`0& z0~;HJL2aY>SfIb6CkQ@1D!3Yyp(G8be>V)y!K)tph+W+j-UGF#_q^W8QiXr4dn@)P z60{^Q-zB_ZW|fqQRA0+|O@m%g&i_*RW6(mT>JGE6jLa+@!EF$0q{TW86r%P7lvSVs zF({Fp4}^7SCFV$ok4|};G0P1QB8UYlBwOmIAhtl z+I6qsW72BzJHn>LL@@IhD~Mh!7H@F8ZN7!kq1$sI5jJH#dFc$R)ehTvmf0Z*_Wgo2 z>4H5gHIFdHb9^Nw8qZfCRsQdWu?e~(hp!=%#G?uCRBgez$ZA&7OUn1VANmDg%)`Og z_DW|v@hb{C`H|guIL6XA2hS)6$GPDcGYbV9aG+vD)NwLaBvx65nIR-_)2d(IY3L>JO{Kxx~mLFq*JWw}$uR z$`j&ir%(ug$_VKIPGQUpP4!H8%iND{9Q@6HT)0-dW^ z3zJVQw{wlU`T`2s* z{AMC(WP8*u3wu6QIcd2`VCn8|)LZD|T&T98h!a46^%Jelp1k{H`)0S_8Ok>4jrd6_ zD7Z>XvqnR+zd711`=&?g4d+Ld=wim@+?)d}Ea~mYXtyo3%YA$ixs-L!!+V+8;xIi- zVgYQV+xlTGMF(ocdcdpL(S7mxR$np{6Q^QlWCPhM6|^dkLxv#5n_<~gXkDcF{_B;I zUQ~VJCLfzanDoPZ!@U*3Hkym5Zo`@!bbhSI1BX2uxo%(oNXdEao%Q+B&mrSP{cwhmUWEkB61i)pxKIO3^F(hTDAh-Q=xj83^@K z!R;y2bwp21*?XDi0s>%oh2>A3rwf0Hx`ss^Upj*!jn%DThc|C#Zs!;adnA!l_NzJm zx5?Iu2u<4aKAo@CxVm?roUpMri@pYI_~by^Icelf~8m}j#+L( z3u&$gh-yfutmHSDZ15B(pjk{JO9{tSfBG?z_qb2J;$2T_hg=*38;iTUHgRl_xY5}6 zl`GZ4NfIG2%Zg(9ghrP;las2wcq+Qx@Ci?d4Zq;)*W@**3AQXW(ADDQN+7T(>~q+ao4jC#`O z?IWh6uWzLkv9y*dGF4I2!wjDuq&}XRseFi~a@09gRmrS2l;-IfTx|(%TN97SkGb)2 zgJZ^s?)Z#{!Hp}MTf_{kr*_sOH^&*}t0KME#r`!7?6lao2;FTft56~)SS=sfHE)SJ zo_T^pGsA8<@6RN>TV2gr&cDh{HO#MPpBJRc75Z@TH(Fu*S0_ULb4*}IUKmNfXhS_1w`$Op`J-fJWvpA`;B8 zm%=EG3)^;POo65PaB-qdB(1o*JlmL-?|)46^lcKV%m|4ezqQZ4jEK6+R#wq1BR9su z13m;Q$%}>a#`*dl=QM|tTwpjzM3Qe{CJnn68u~iqiMaKg*4d8s(P}UF%Z3nt6q6TY zE^}7I&x#R&w(JwriL&$`bE07Ic*?qw_0bu^kCH-+F3isYDqF%bEq~ht^9ofd*0XQQR|=?`Cut957aV)nl*tpwQDKu z2NuocCU>Bi^vwXSXEtEh#`p1QSz}XRK6uOdi5GozaX&z^qZQVB*D;8(s$Z$|zfT_T zRhKP=ZU_E+=2>kK{g`aXMxqY*Uu;^bDdj(le$t4{tsM~+vY1YAJ6!c`zG7~gvN=9d zk)L{ii8(wvO3rC08I7QR!O}XxM?c>yrKT2>Z)!S9Bww`-a!XUn_W@MS13>5zGA`xe ztgHi;=NYTJd|b-P3(4Q4zo;ci5^V46sKuJyHi-d_UjsjFzO8%jcTKHcmt#M`DG88oBquI=V>_-TrSgmQwMSM~cY_>@ovW|8vg zJMBa)uO_%5O><`(RMd$u#LO94A-#El(cI|nqA$W%aF7-+9)5lMM(6LJ-Y|fILwHsr zH7Z2od2WW>BSfPfr^Q;VyVJSEVI9;|-Y-gx!>l6k!ZJw(mxE>2k}2_aLAYHDz9&U!NGSjQ z+5#9vPX4N;%K&3aIy#BAEjjY()Qhc8U+FJ@G|$GwczR}bu=gOWn}e|64m9f)AmmP5 zlW=8nY)SG@^Lk!`Uix?}o3=?Bpt67=Rgf#yyE->q%?{5mx-a8)QI!pNef1norU7RQ$x|*6=_a0g$i>dSKG;NxdwjCpxpv5=d)}{Y zesyuIQ*IHSoV?xiTT(Q27y8S(w_pYKJhGGZuD0G*YnzDY2|@tZxyro3?qXGbW|nGK zf&+`obg_1K%}u$o2AGPZ4*Wu63RsSjhCh2&1MlHl{N=g%r(~IZp(XSHLg40|YBi^)caR zi(FJw^_FY*Q8v)4%JdOGuG=|grl!Sjs~iXZEQpF+%swEjwi?Yy&h6M1`c-*kPioEt zUW+vrRQ)eu#{Jp~gD`RS?45=NO=YSC0rL~^La7fArYoG_DvZT5owp!)tp61tka6?X zbiD^keV#4qNm&Se!BoZNUchfaKXEXbFH!9#vYe{K6NduBV~s%!AVCXlq#Ui1S+msi z&|%*dqGIHIkW25x$46D~aYcknLqpTxK9y4(>oka=r-ut8AiW51FU-wVH-ny1?cxt+ zcE|@x%J1&dQZGc13O&p3w5P(IXUw-*2LjO#dI3p%*8U~ z$UzeAq}I~`*QY@q$D36_v;{s4fmexo7u6x&=%;57$;rrUw|~48?goC2`TuKE(u&cSG23C(B}d4&o}~me_i=u?~If;1xkrf zA-52|oQZk&bFwDqdok-=dK>KMYH~^pUWYse1Ipj!(B{+zh2*QdkUYKr=#QLBsvMv4 zBzDt8`0!T4vMk@TFHb5vxQM>jn>5?N8<&dgB2{w8D<1|75D8h~ix(Dd&FLsOsMgmu3R4`ZTfSR!=6gglSjQ=qJ8r$qq)hw&}<%z-iZ2t2ed2!suid`oL|2VI(URq40DVw zZ`F&&QmN`Cxgon5&Ic1msnU05XbX8)IXExrsUFZrTj+FV-hohNB-F;BHb+6anQO6T zRjtR8n%TRA^l-hw;*H%opy<5*?$ABQ*qpcH>2G4kY zJ&VKDR9=hqEMO3PkxEy<*cX|5op3yQL~=`6L`39?03UPJ&1dptd-i;^$8@&drF;21 zQEwuchMyjh|Cr-+-Cs03BMhK5qUWt40VUdSu*V#&`*ZXrx--UhI$a^B4+x=Zs^8#< zi6(1Q`sHUg3IBVC#H=h;eZ|x1nH}yVHf-N*4A$C1p7?4ti5lj?7rk2$(n3L_4kM-+ zugcvig{%(3GvQlXn8=e2KzO*?N^$2hjuvTn0C_?E7~A;U`Yl{{To(OWEhS^I9o<~^{si(;0Ki&gu0dVHKbs<3=~VlFZFlRwgH_}3 z*kJxgw$?*1n^hhjDqx|I1%h;Y%SGD@ITJvmaCL1cRPVzN_cvavuZKe%665pp1wG}6 zv;U)B;VjITe;(xMp8ZWdtEO)=RLIMcJY zQW}cyn|pY5DXdoN^4(e&(Wi~(o8HQ9+;GnQcmTe?K3Fc~M0>~oNLaZmn%eij@g7U= z&v&#&2@2w7i@(125Sg*wcxmj99baQ=u#UK7W@k5>MfU&NVdT}O;2g;@Cw9~k6)K)D zQe{e=4>S6Q%7}6$=w8?#5s`+0-FrZn%MXO^nR1OtJPH5$)zsc$#wlzm;w4^{>cq@qyV>N;&kH>d3l%0#cm}?e7YLp zo&g)NIH`H(*4#Z&e%I7AUOsh|hNj8&Y{22b&{9kcB$@zBPNnp|>7R{_C!c=*7_QK_ zAtcq-sPb#^BjGiJVHu2PO7wNdX4#V81&}2`AVBv3i;C3>l{YuKnES6y?9*IyFa5o$ zS$MV8^iOu~PXor=!qd&bLh?^KwfT5-vl&2)Rif`*A$3Th-pdDHn0`7oH%+_(z_YP= z_uNT=u|eeNNijc@3~MxnqrDP0!P;_OoPNp8FixqNt&mVU)M_G6F`oytz8PcJQBQE6 z^>$NJ)u|Ndi+%-N-o@v_T)NIqMQF;*moG}E$xM%GWfNW23N=_(`VC3!<7T`Qx4Lee!e^c+#bR5xUk*-BcU6+(hM;$3?sL#(Pkg{?TY*(nY+Ww@@!NE?C{fyga49W<4hH0n&3hh>{ z$D+~Ta6;U^{EueKlM?$a+&YyYeW2M`Ui?93Hes{Le#81YAg&)36@wJ(MH13!8l^^5aDp zfsl09iHPHx93~D9$PD^pO8ghc*wb8tlk zrPb`1;jd3rW}JF%M`MP0QkglfL8zRMXhphZT(AAl9n~ z5CyE8ZgHSY-MGJ6M-7SJvBi!l-!J-8otykFWJKfUV!UX%_hpU&IW{q>>VyA*TSyS{ zLiOit%}t#Oad>$1>k2YFvo4?$8NGEgZ2?HIw$BZs1UjUWzewX|0w8dJaSqxp`d{)M zJ=)j==${-cH)}X(=(6XVuYI5-Ju82Jv@q@DFISWv-OjrXTb+&?DmLs5aAU^5cg2k# zOG3_8Rs#HP0RUk0LImyiHpgpfaxx+G2BmP5sEn+DU(N$dm8yGieDwD3*i(6Q9^+u$1$`R8?(s+x_Gja ztO*{-D!DCHg|Vnw7w0CIc2)9x#^_5*u&I4};b6z(^w6EK;D)vOBHFCyKxE3{#AMug zeQ!`HKgzxLT6yp}a0B|2mOe@x!6}X()zzw?dgT=qwcjFGcH4U{TnzqjeVz_zP};iH zwHt-VMH=UUf1{!7iS>KXS)EuCr_Nb63Eoh%Xfs)M%f&yS29X6VJ)`R5SQAi%zt=@+ zG!B4xIlwfs$@5faKjA93J{p#BZtY#j=r>iy<}ieA|6bxEtgNO66tFuB({R)eRI>1K zYMK{-)m5FTz8<`TfFr7&k6g0{ZnxNqG;DvoJ{6-4zl6;umykYOEUvY zRf{PxA^9+-m&TN$_oq?QteBXGO0Ij90?cOzbrb8Z9iBKUT6c^dk_fL4oh#!|e$f_o zGW-4KWzazcC}lQ1t`QGT^f$u_W0~|#W`1kfqpUhYw@dFNhv52BPSi|ybwzAv0g|4P z4AGZ4jKI+P2J;T0lcA#T9&a-xLhggSA<&&7m&;4EudiQ;wLQY{5vBaNKf`cRm7cyZ zETM?s9;P=hm%zXC=UP|wRQE`!@Vf>LBX;KIVKzeHXnTp&h3Sjl<7(jYbX?Ja1D21v zyBUErh1B3xgD%z+`bPKT+$yHv?Y3Hu>+#QQt*_@Xg==#`gJ)*N$m6Gv7EMwt*io#h zZcQac^f;j&C;!_a|M7fP=j}{Q*kX_OBGq zhb{v?k_%(KTR_}I*yLP&%#T;gCb((!cKvuohWN?)R>jJPoE&J04r`-353k_jK^qrf zJ;#1kOQ!TN%9U!v2^6Fw7iG>c3^#V~&!7P#8wLgjE@d>&gxio8`P}gXyu>4EzUQsM36ww@_ylR zmoU)}hm;dY(}&6BMkM@Ig^Qj!J(ng0gY2t(b7iSP1n~Sw+EWFC4cjbUd2ka>8Y%_MZ3^{ zW<>`*W|iq|MDfyeVaL@4j!v^%_TmVE>Vf3JxnPrvBPG{P_X^Y4CEb}#CY>xNXLMKW)(<_^lzxG<*IfKsII_wPMY z!B3|pmWZ6*#Ic7+H%M&vLrl2w2dk5onaRvcev%}c`=B%1E#zwRMkeAa~ixuT-t z*ch)r4&|7Z0tB*0nc$YwS-`WEA`(oUkve8QQ}66PDEr#a&rK9{6km~_Z+b|?tf)n( z_U9viB<L5Pi6e4OFBV(K_iPA;_N z>Y6TETJ_Q*>O4|o6UVKS5|2k-(Cw|EEr}TFyo&7KbdyKDKe}@G~T|Qs0wx}RAWJGTg)cZZ?5mD9KE*$f)=G+tMGE*oWtQV0*_Tx zB|}$}{C$iKK1pYXh8HK>p!*vr&r}a4>UFTU2gwGhq9GMkRf@Clf{7@~c4>+rQ zf`y5Rx;*R$xt38iXb`U_s;;T>qNcW9{t{bO+Je?|wL52l%1r90qtsrk!+y`}_5~Bd z**VWYC=k|1U`-fi&5)_d!;V$qxy$O^{Aan7tH#-99sl zY-&nQDcJ!~5uj3y{_Kt{E$r55REMLKz*7V}(~J?j?U=NtR?BKjjYah8C{oDjOTH;; z&Y>6q5=yU7xu^v@%3Og?aC->pY#1J{V&>~U#3|SAy!w)z zm@eD{AOAvfKjx~z=)~#pRf*2w&P-=<{#KkKb$iG@)D{u8yT)47%pO!w5Q+VSZ)JIe zcvrV)4}+f9fKFK04Me^s1cRz8s4!c;ZD?@N#&kxK9!s+hnOkbyPY%#teL_6^$f2R( z28Znodeiyl+4;nXh~c5(;v#01pRrl@wej-P-VLA~IXq z)70+DE~M0(H`4N+3hRc~_`6B}#nf3giu zKz-Iz&WaT`J|V7b1wpp2U(O)(byLBH9-u{&cEm0hCy4E7pX8EEKC7qBjH|-uHFH~ne-X5JDYp-vLT`O&<4k6)rC0=8?u>q7b zpg;gcC#QjolvHnLCpouCT5*WObhYKG8<$LZ{)mSm=i~`FAmm@B2RqSeY9{{Fb>p?U zoG6~|vN_jfjn!@N7`9&jv&CnHtN8ZQ7n!AYFh{VO#a5j~CGtJ6#=|3KN!nUp{{u8g zMe)O<(7#uCLP{w0A!2@{s?#_4Wza%Ya)SPd05x8qVgDQ&FlX5W7$GHrmIV}D_%K3LHH1%%yY~&Pis@t{UeQ;BNE+0J97)gpU=kc9qrmJ zwOgK)bd>D@b|&C@b|awg+xeK(nMIve`+r~HsFiA;cE>#2q-^KR=O=&>5P7gMF>Q@) z`y-m_n2mCC9`p47_LJ4b#KZ*eZ1KVD35m03=TNjzjWw%XWCBNCN&c!0puHZFiXtxr z=Is%G+D2DzphMeZ`9P_*U5=+2yQ{28s1`ChI)8aln+7~L)pJGlm9g!C!KLOT4tHDS zaazuJ;pNR^9%zZYv@~r-`4X+?p~H(@IdTB4B}XJ60kpQ|F{e_lNbt`(2aQ)A<5zdI zSX&-oiO($9@#IBCtyan6OSu$Fk!s8HI&t$-PQITVKYB)@t}rFs&8|IwR)}!!K|ai+ z4M+=icC4!4K(wGD@SlK=j4=X|X8`u^n>j~@GnYA2p<@cgp=X@k{?H01->nzEU{LzG zk~{vb*0&hi(mpyGb=$Nj!OHGc?9U;2)8AHF&@EbWEO98?uN$o#_EzkmB^ow|C&@q+ z7G3uM!pJPbxEW_Y>_jyPkctgOML^m4&)+uNXmo=(0C5qEcC*XI!RX4=9SC82p?X-r z(bhN}6_6%B0dgmW&Q*XM0X`76{HKc#sjxzwNO0e~OyR zm31)yQy)-+{t+XC@9qim<0+bbI(v439GcOp9tB8ce;BzdCH|pt#cIYw(okMAw}NDK z>GgSei~m3La;&1oKdhcxV6dHf;lPOZ%tvhuk<&vbKk-LqHd6^Rkp6IE7}FgIP^kKW z8ePDJL1Xmc1@aY%?on8LCpa1s9%G;lc zh%FrC@NLQ6f<$55-~}o?e&xHvYscr9ppfk%1gwKeSNXs^p!Tjha|6I1&_ZygiuTG# zyPWQ&Z!LFD`T4OLbmaK?=Ia;@{TLqR+4}}8X4)3bPG-q6X=TA6G!H;~6;GDH==w((z= zZS@~hMMyp8ERO^1)xj}}lT8}Ux7*9M;?KXMJWQYVbrrm@eluA1FHS~`I=>FY3!@O| zp8?L%A%d%FnF>UuG^l+1&d7-iRni9#2tnIN{x(|;zn1dT_#bmR^FO^=qGX;U zSPK5%2;bYJT?2zj_U}te;6hZMxz)XC@55D^IV-dXlP21noLJ7zk~};NluSrXO^wMh zw$v{F0nSdVI5D<#u^%nWF**49;wlV3W8I&J0$>XS9ejN9o=+oWo&#-r&@ZI>Mb>@a z6A@QN>BlU^d*Z?K;t~?fT3N=HmLqw96#Re7tyOWN{>|c4doJx4s8zR_EJ;vjOUUt6 zBbZ3b`$b0&^YNSrz#I`Jvqn3L<*(2rBtW=A2A?&n+bOHd;Ro;+0?&R3#6+vO`#@Dv zR#y3vTHttl*nD_Zmzx|gBNhIu#JZ5a^5{UnvJ!!ckLs6Gs@Yg{FGqRRywY3Kb*iYa zpSo&H(Th{wZtOcjL=$O*=r-9NA0=J+-E-$STLC_Hr#lPl>yH$>&ktX{4iTN5Y#5|R zaRO4BUI+O!zK__o#UvvKe->a)j1mh&UO11oHeQ128h*`~)*!F$?*0?ZTYZCL;m(mS zVB5kXK=5X0WQt3Hdk&g?xO?K_bm2ve?ABzbfaj(2!L$IBe#;KDPy1{-IvDjzn`)@= z763n(S{f1J<2Sgkl@?HMOm{e4q*J<*YC1p9QCYJ`J_94QM-Wi4bGDHD--^B@KUC z+T+()q~D!oHi!@CzEW~I51koxt2=ECfb?bF{VU()3mZAh`DRJ~yeRQc?Q4tU+ zXm%<;bCd7t-rxxv9_4{i7|5^+Q!X zY|wldtr^EZ;W#FT0yTGoGkq9p^Yimf2zEeo>*&N-1&7U0-yC*d%(o`-cgj);+_uM) zpb6sZ=o2_;OUru-&x@|O(sYpUW#=$RN~}*+;APT0!niu2%8{dZnGDXENvq=XUVj+i z(ImLeUoV9chWP-twzuKi)wpCtfQE(_pHpV}XF#xL8j4?H`=2&QXq86&ix8}!;i(Gi z=_-qPvB+t<%PyRJ|93{Jyl`LE?*~Mhq2Q;%Fes1RwGLv2EWvf%b#awE zlj09Zy7=VO6gw#j2Cg=)z2F4R-PF`}y@~E6oWo`lB~b@m3?)?-6e|mEf7tGAe?lNe zDwR*bv>)9Ee1<5A;hd~eclUQ~(_jZCEiNtSurARi$q*$`;&QQ_;T;5`C7fr zY(=$E<$4n{9ghT`3g@XG4HTxJGsOuif{H#!g!MZH24=dn%B+`soBi;+8HdB%-Ro&? z=HR7U-`hUXy`MbVKoE19!C&uGb`abU?bp65syUOgc?DQ!*`U*td(lzJwq*TbTwEL| zyJxQM-kyU3|9|xB7P4us$Io`GDCMd-?e*(PqTPikf+UcgmR2>u_eSpE1XZQnl@g`= zlV0kTuaeccgw5cqtX1LP!84bXvP%@DbeBj z7%*sl;H8ezeSvUnbyDSg!!@3i-e39{$SDB&X4n6?d_xT#uSM7a`SQ*@eZS^slC5_k ziFY`ce`N`+5q>htTmxINM7kFp)X??bHW3tG-0-=V5FG&zNXf7srG5nW6g!wRV%LTW z@;1&LRblY7o}wB8-olEP>2nCP^m6Zg<_8VaOt$8;vV$iB$22K!( zw_SJ_ayoh@6)fQTQj6!SylIP+z5S^#UIzz07$Swc;LpDVO_k@UrG%qQk$Oj zx8O=azP$MZb?n|H8C7v$U4RBj=l#3}I#6_1-n`wX^7w|f=K~IDH>)N9scU6ges!`k z7F$kS;0f;4)a|`WucQ{C`C)?614(DSJ#umRtMLE)NzXkO%R)gniWiDp4`F3Vo&+R0 zr2z|YQ}J#S*k4^KVQk!SEuzk8vt;Vvav>Y3U~I7aZhNoQ&jG)PVi%nhH6;J4Jb_ah zK^^Th{a-E9vncTI_W1BIvLsLmo|9uU zXofWQ8!9nI4!b|3d+{*+xo4vH&xeH~4Zj_4%FKvf`aEiUd+8#0Rh8h>&A-6IRkQoq z%JQBK7HXhY#a@OfEiQpwk#7~BN3l6szE%gz2F0)U7zBSnp5JaHl!2^rtBW)Gw}u6Q z8t^@acIWxS4B5u}cOVZ?6R8)kXE@xOA({(paRLG~UGU`Au&Gxlq`GU1jWv{Yjd#eL zdF`8mC=y)wGcHv zQBALV#_~}xFptyL}moIgTi=QNeH#mavP4VIH zdk}+2LC3?0-QE1f#aB2uIMDOs2}GWH`7<&y zTQK%AGEWCgFHgC=Fo@3=+ZKW;L_m{j+Jk!K1W|O%n4cB8;3V8@!Mc>F87kn)ce?nf zCPcog0uEd=|G4S2vdK|BS#9kT2o%WVfCm2_8Vc*u2ignZ3#aeu;sn6c$;r?&=j&E- zx5FW2t(3GhxT>lV5gP>s1;b`xQBhIha&J;&g3GZH@TT@j2hEqTP+*w%{rl%DN8E@G zjr@=G(HthAe_W);X?=*gzoE5{`rP8sFrm*;@($!Z*2(Reaa(VyEZ`=1OE55WR#kz2 zjeG45=U9jvxR|!eVaR>An+yB;zdV4gFUFZ2K`-#aj*X2ybr!(hJ97r?@75r46T%XB zsktKS4yc!un2bmvm)k<9lqiF45X>H9lur{F3ttzziyKo`b2G227Szop_~D zB@9-hL#iJCO>2nSJ$qapdlop9JUF=0&7Z(>15ZnpV^p;$;HLd>?0lY8S-0%oV5%J^ zMnDj;)DdxWwi0y@4fAPmObp!C))tsoV~0gV06r?a$uQ8RmY@pxkRk`o+#efTuLt*! znvybOxU9VV!>b>mp`q(9fo3ewVqacdd@Wo})o;1>ZL9$tUyb4em@G*olz*lC28>)LSF4fs#n z*HID%8y+5p2GcMzOCMgQWL@*Vrkf`M9M2gO#Tl|3cnY%8R-dNSlS zM@Rdorcw|yGjFW8mh=zGtr|iLbUbJVcf&YuWR#vw zfPt58v7J>q!IyJoK?rvl`^i==!I$+9sqR8vEBze-_~3A8q2Y9_HwYgX&dIYA^&n!E zuO7}<018VY_H8y&F|P_Qa^RD9a^j5WC@N7J_L7Wpw413R!h%;q=zdCZOf_?Scm?Gqi3lDquktXk!|OJJ%Nb__q6daN=> z3MGo5`DDQjQwWZ*Yic`dE4SiOa<*)0;{jpFNHsdxLi;fCCmG`n5o<9GN0>KQLt4k} zuSlQ$3FGPO1K)lS6X&qW6TtrY`qir%s|XOD2t0w4wO?~)Dg7NtZ||)_LNpt)G~tk; za;!ILYB(?Ekiki(f3r2X%V-JVG{0UEQcOZO5K?jnHUEL2hm? zfoUM%LF8{r_#6*698F79SjmqR?umxXY^;%;z5$wsOwDT+&S; zX~6a|ioN_5)w0J+i&2scb+wJKI}rBUX_#0tE)n;GnAoXUaU=33=c93jTWM6Y&Q%}( z>gM+(hf;H#;9te;My*miV@2M-osP4cNO>t!QlcwH$oy2^)ChvdA~c^F%sAr?Af++_ zrBEx} z@nKb;lMyouP&WA2ADEbM1{oM5dX_C6hKJkF^J(RGwkMPqw6p9+*k#S;Lw#YLU2Dme zcJ%xE#w0lwSxaGPU(Rp)5!cCjT3QBMJbBVbaPz})jMn2x2{SIWWHwT&n;mPPEh_4~!sPMt<$22o7^i^1SgM5*`J8KE%*}$}%_q8|T=lbR`4!!G zGdV1h(;OGC29P%5WTe<8&pVxT2R(Nf{JC@%{L^=5N;mgXT2lR*Cnh|#%KF2M8~Ihe zR|beq>m$dJ2E-l`?_vACzUh?WL5{_I%VSh?8+piVjR7z|5ALF(U)7V;Jf)2o7St)$_s=A8!+*HC{K(`C&8ItI3inM7?vahezu6 zQT_Np(dsn6hXhr3Coa2LCao0IR|7e&yGe}T7bSO|xAVhiELM1|u5m%i!^g7lt};bj zM>&TI>Y+|nZdh=!+9%W<>uNct<6xc}?lUOI+c(5xrEz}fz|Sr`N_%eprNg_v^sS~D}D`T1UjvMxs^P5lKnx2Av~b;`L&l^{vtzK7OBOV@~`0)jkS28ee&N`p4+J&X>W+2<3>O ztjIeYlE0LWe8kB3XvD-1;8ni|ySov4$sl9v_kY5O&ggPkc2Wb>=HLf$v60b-T7k%$ zS(nP=Ek=Gm_4DH)u2&F4?p=Ih@TmH8v&p{WagtmFYo6 z^^2{vt1UmaTG^h8V$hP0Wz=-)qJ8n=YP8_Du-}v?#=*>PSZaH3>5K(A+xSh#(U1Oq zuxo`iEnD6Y2Q=#xjEysk+89&G(WqpSX5sq$ooXAb(CN0*(`OB?;yr_~lZ+Ms0D~FJ zuS`OTNRcTRHzh^UhzidK^jNJ`BM|c^yuYBqsoQG5ef^q0d?=isk8U=a$r#{O zl?KV>AQ^QC%>|>{;LS?HHlWvL;u`3O3pkJ3wmmD!Fj{N$x+%7>z&6ll&7yZX{r({j zDe+4w%%?Mkld+>MI4lAuIJ>HfkGW^Ex%pd<^6#Zi&X`Q{m7MT0^k^>TDyNfjB7Pw- z9H9BHnWK6inCzm`gu0H#{~U3Ip8tP~IH^(%!?*_fxf;zUJM_Q$tKg*)TU#88mW&LM zjBg%iD3qD;4s>QJ(!0mNFUNb(fg&T&Z5O!AG=d^Cr9Z9BX>YAXmAN?Sy?Zx*yfqvj zkH5W5;$Amc|MFy6GD9@95K(&^RuM#KwKCBqoRP7_!xNR1bnXd*0mCLHCdnW&{>+~H z5582~%7kdfbIPYJwzQC$4ZT@*qmPW6ih! zAJ0#3(JJ-9C?j>7S@MWGYDLPkhJ$7Fr{4ZH|2uf8VdkQms?GK0fK{Vs!uC+5(T!_6 zi_`XGFe$c$>mtoc;x>tumHt5BIBIEd%Hyw0(8Ro`wu(rLXms2c~6t``ru zoI6UzCA&mz30~IO)P3FJX!1Lbw7co?r?u4AO^h7iP&vm0Pv= z&#&bgtHF~2Y5eWugMD*lrzhLD6`%!2nw$MWD}deH+zd8`L%l#fFdj95KfX{M$xYdu zqQ>*v8ZX_cc1ytICvuOBG(?5oZ>Z$6?&CNkeAs-9RS2N^EsuC@QNbGGf6J8y-{{_M zz}Dmcq|5o;5hDHy&%vSdgoUFM6`MVed>L>c3b#pPMMT2)&y%gCZ)y0p7h)2=_kN^y zG_oKXrZ1F$$o!oKlKl%IGN|E0)=T};wewJL1W7Cig3LK;8{GQ%=JL&Hk6={$ zruRdAry$E#%%ZuKX=%Z@&1JXA5$2=WcarxqtHq2*m}qHvhFdmvzCVSw`veLMw}{KB zWoA^SW&vB0C;zu`_ALxEj`v_|leMwYNAz%2_QQM6jXHPOE7$YlS6B6;ypPOMrTtXO zym22xD_E_s=R=4!&ZzOM(wIYjZSrR;fZ~|0 z&J~TEcYaG#G%{g5yykK3QghHt8NV~6Nr^OBQBynbPKYS8A;x}GYIXvDonWG!l)($B z`s;Q}GncPoPVt_P$1dLc8WY3j@xzY-)M7Bwa5uw-6{oI9+29Ce=N--ezI8wfV)wJY z?xj98=%}lUZdTN@mF!ZhzA6bzqb^YCdVqz`@AzwJ=MHoqUiO9J>8Q=>TbSP1SOwZo zqpj&81A>&)X)Mg{fS{9T4)Gq!yI+DEC%}~dD9ZRgpo-OmlNL8BDIx}gxLu*pfzL`= zd097aSu!S^--(KrR`&NNkeSi(iou|a@J`8FY{(d2?r01I?V%E|zEhc=Q*j9Z!;SZ_ z$59d;uFr|d$(O(Anls_~SO$_e!^@2#R@2=OcCNR4`E@sjAF6A_IBUarDuTk8w{gE78WjA zIKUJX)ER z3SJoA4W76-JslmxA9n?`bY)+>=30osmdy}V(9~oC!tHDqd4}fTOaDC#8l0Y81%#M8 zCp%-kVNeTI2i2iuAdOefgYDBa4>AiYHY+PDHs~|IjwZc%I$<_8Hh0u(%{msUQG_!R zFJ4naEuvHyay1vx|F{c%77+$s5=cP21gb8os!^gy6-}yVij2|G(Q0ZcPh}k(95O@? zwF;7}iWNS!vmuKW821?%8GX{Ll9P*zLt7VARn?x!3JVLj`Jv=|#@l_p>FDYG#KqY0 zDZ`qZDb+Ds$H$|+=_r59&+A}%>FSzE!hn!zx?0~iT^$oMA()&`aei64abHu@aEm7( zTpDT19ziFDTf)76v8rlozl`ZBDMC_0qa>6P)j~S}0Tft)#O07;x9RO)HvB~=j31+y z=NR4$a81yZr5>*(Q>PPL9mkK|w&V-i9PxvztNF2Maeb>UotLn@-25J+IiWEMeyHQ- ze(=ZBzM{y;>@4NVT#ZHrxK^LdS^#qw80}^$`;$@`DDeyn1${jO!;nf8(oDpmM2R02G!6R2b-SA~Nus=UEFe=BJmI1{uccPHk5VCx?|KAcF`S84a6_ud)Vb1KEW(oB{nV ziR|n{jKcHHn{VgNKN!0^8C*{Dw|-E<6rv;3cNo+Z%Ej&~DaB*z343p$}?eRh3=4QXYzdsIjv9tmp)3O>_0!l2#U)ch@@;%7HAHL0aXJ?(R z#9%}9Cl!vMOXhbmkBO3zk5I`>bxKJS>9NX@wwcnN_@v{{JNx z3k~aj_l^Mvr__yWiH&z8=6J(t4%yeEqjP?lpD>oue^uGVfWHAEp@G+vu1cHA>%SCu zd9hV1Dk~4SF5q5)tu9x%tb`-&nFj_5@x+{$)_K#C7dI!cIC?gHG7C#r$1}^xv;^7& zy^M%;<&5)jfM9&!wFPjIFbpiG6#^IF)P=BoM#xjp(9%{kdsL29->` zzt!`5KDxTPktqx!!9DXxmd#CoW86=u0hD{NySeS(drN(E*artW3Kj|`0AK$l_p(L; zlo4An@JS7+>FQeuWcL29KPVga4gnC+3dZvjvzoJJ${v%r_8kz`VVXphBl)=>lSuNU z3NHzJqQuUMZ;_XC?V{1ga?epU&ECJEP_M>i(Iq+5sY1qYcePii+~+XCKu(m@THWXpL7qrF<|!_%DSLu__tYylN)yn0Z2REHhQ4)r$ln?lGphTiN*nc zCjD-2r(t zjDVLMeq0|I__o6sk62w@_e@2`@?TBtY70HV#)cSDDF0S%lFRW42;g92i+Qd6*W6!# zo3iqv1JS$O9aX;Vr)aTP8t$^YX9us{8O*psw7Oc_+Ok@!S7a_FK>fmX!5}X$| zNni||4SyrSu4Mr+y*^}@&)U4QXCK3vi!)t=O-jgVw*E>l54+JTp4W+0^|{^lW*hi{ zH4GSp*V=K_kB|F$t4Fhckj8PXHDj|{ztZyy@vz_{%WL0UGineJ^-Ol`s?zuEX=kbwGmpO?72+_r1U zx!-76Oy^i6aeF1!$)lT z6Y@16rye3G{Cqtb8af!3kRwZjtDGlXR>tmNFdu`4Nd|Ps0s?(V`OmH@ zuvc1g%qLWyx?jiCPMDjROz^+1>#KL~SfVNFr(-1(_=FkBJ8*V(9|kfeD3py&z$!C8 zKV5@2D=325q%YY4E~o`%<={}{1$5RO1oia=tiDAuCal$XM9yQJQxml2+s^+g>;m)B zwMJm*!ggak%6og#S9VzRmE7`d(15{+TS0q${d7yqcw?hW<4BCwsBr5SpH;7WUigg8 zBLhW>))Jk zLG6_*-DNvcqw*x~RC3{7ktzdzOtYMm*p`~LQKNp*C%LFXnvk~4(w86`CL<=UU7KJW zl8)nb)81MS`}*Lxvy?IaA*QJC={dKl4id`G+)Af`S8ASmHQ1PcdH3~PtL$?|p&Yo-aoUK^m4l0u z4S5BX!>ew7`P7~>p3_k{BG5?|@#O*IKZ}}w7fJ>-?maIOB6pHm}@y6oE^|hV7q0iFM#^ zLV*=rJ=fC3xucg>xl+svz<^&LNgG)md0)kI9l$u_(q)RT18VvwTN5U4Z)zkgMz&pm zoOw*`*y~Tbb<86<@oY}--@Vf|OQlLq2qH7tSgG900-idXH)qk(Q&aGCvl5dN*Bf&U zK(_}P7+*XNR<_5GE{7%*VFuC(JQEZ&`7V_uX+5z+1%6bS;sCMLLa3BlwT90%dP+PO zwC%0CZm%ta5YfixFk%DOASoCham;N|2*6n)v9Wk^8JN`@&El2T5XciSv&T~rC8pQr z7x^#0)?C;wG@Mz1upR%}LQGb+Ae<@U&qn;}lXsQ2W~HttHgLfi;T@OHwCoZNoP3u= z*cyKRP-sEf=5o#iYZL?PzN6V+u@~FPv6GV+TCM^H!-tG;J5fbfAEt|0H~hQxClfU` zpYy4cdpqWUPS(->{$%0VM&hDINPMc1SGq#SJ0_Ycsa3QQiyOws8~<;lr9eK|Z`wXsd2w9*%|}zpPLe*ei`9hu8RUzmiaBG| z;d<{cA1iZB&FbcRiCnzA@MYb4Sh4L%jBC2WB2kjXFuX=}zFjAL69 zl(#2aPvSajZAiqne2t4b)p8#~{KMQnYLU7ftzL*@oa`rA#0Z$}BX|A8@tL2q=imJ0 zU^Pu5c&ziAQV8w-$KwA4X2Uj^I{-GBAWCf znV!v{9CKCh_&7)ZE1HIuQf=ipt5htB<@%?NM+opBzGVttt$`xLVAeL>>Jb7inn8Zx zmRzjImbbs#COtFyG`e)BUa&P!mA;ig5Ax;Ziw>_ANxCrenF6P}+2Co{xUY^Fr_NKJ z0HOiIzuT@`F!|o!-g27{`-+rk9rtOS^Kq#68-|&avN~J@!lHlc%ORrzwqm+wxwghK7%K^IRmC zncDGY)URK#ze{18G}u1H&fG&y4Go>*wLu{ng?oG2oINTPl@WRd0Atr!NQx|_I>p4K z0$eY^mvR;nk=0L4RApO&!J1gOz_uP4bsv(bkJ=W#ow9oJ@I|r#3yZ39o~=fuXNJP> zrC%~n{e)q_7mq18-^_GWeS0E^JlHd@GfNlr$Rq+KYr&gTci4ttzu&#EJ zT#n2D0VFYAyA{Zg590fE<(}tEn3hcBYjX+S{5iXd}_)zUnIYbieE!0E|oth6&|EYN}f7%J&97fiB`G11Rt|4G;?OI{DFKHhY7vw)`-`{+&~)GUERlmBr-eG<3pZPJJTz< zIMat~0(MJdnk2aswR_t#sFC#qk#NjiSD#0sLlT1=k^qO+I%XKqtf|N7cbpltI| z<$uHtW)-R7@NS(@R+>&3j4p{_TupZ1>40$qX1|~LZ+yDs!^5GOiX9xkL zP87UHSwW#4?=c%&(YX&AB(VhW20yW?0yHn_p3x^xCJqP0*GU8R?Uj3w=U;)+Xvwd# zZ{C00y%W*VOy&YHd;!eC0OtMhk6(#De#i-V4V-3=(oIyw#yLNBV!Z?Le}~}~9f2DW z3Gm*S7>hsfsi^E_uprOlC8#3AD51e+g@ygS1?*xhe>R5)Njw2;706&f^h{AkdU_r4 zJOuJy2ujHZYGieWvz&Y=s(RbfH~vWQ`+KNbOYclidiGzN*%+h%aT&y}0^H5rOkz&0 z@N=2HB`4EOz=VHIbm{}lGBYns&;+c}0VnO2SOjpT(ObCX-w2Su%tVQ+C(0x1$s^Q} z|3Q|y1$!27oz`VW>vz19g`7b!r|I4?0eu6sgpkE}8peO06r*~PZvku`KB7m2aqgK7= z)Hfq(=d}X2CCD$@A5m-8KduM$CUzn~U?oI(U>cI3egkt6E4=>7dq(2Ix(1v>>`#WX8@Z$Q;U0ZIk#W#Zmk zK95}a;D7$jp2eY$D~Z`E=rc%M287B|%~laWIO;w9U(DR+w?2+OtXB8tX5tHcXAEGPD-$piOFBB|x9^A1cEd0Q=Cg3&v`6yp+ zJ_|S(3>tZ7C>gP&(i;T)emsC% zHPZ9(Y}Bf%M{z?m!Ym!U{jHXw+$Audl1yNcPLfdo1~3Eu_KgIPw+Lv#={WNDG3hQD}Y_Ekrq&|0MuO`JKUr(j4F z!I{kP=72Lqx?f0x|NhO{{BQFIyz>9l=`ck6Cmj)do#$}ka2C6vxL?SeeCuo>W!wAkjEHYC_wmh;1_U5#@ z>UZP`wxh$BJaTs;G9ECeOHesQhYeNO(7W*7t?O_XtiJ;}9PV%A=U@!r0Tk@@iOPt$ zI5Ue6ZOz|%4SFoSy_hNAKYez7x))-% z()}v=Y^S@1#5F~FC%%1Qaj_DJC6qZbCL093;EaijjkRQiiZMl)JT{t#FD%TPr>0I( zK)FiwE%vyg)UGHX`b+U#t60Gmlo%*y^EgYPhC z`SWL)&MA&ZPxgTUELRRL|Y7TYWU)%|2oB{xuu^JKN`BN0paTTa#uuxM+ z@Q`uni~=YpuJWY0si_1p5}vU+4E)SO29g>D$uIZv%gY^t65nneWrE`3$u{p(Qk;oW zJ7(xO(n%0|YZS@FbC$c#0+dMh)z}f&3rQ3JkFvG4ULDTy3;+HY9UYWTy}f%@V)Dm8 z%xbr&v~_iGIIBFl{=6R~2u8H~u%BJn#+X`BjiMxQ_fi ze53Cgy{-(-^KL%d={yH4-L~$VeCpyPOzR zQPvW`p7IWm&mEkY$Z8*q*3YP?3$-@-iHIFPJ7>*G&(02QYHCtM)aewMQ$NhQL_gd+ zu1Marp$Ko*VL1Xd(U0c2%0OzX9~DNwOpj8TMr|f+k$^@7jEAeh1o*9#x%uGH^JTJH zU=TcksLptCf1u@?Sp$h-rgHP7MMT>t(_=qG6D~o-Yc${%d)NJ~xTu(US2Qh~?9c*! z*VJEs-Bo2!LYdKqr5w{{4hUq7*WbVUPSyh3N?lbo!*sF~fdE#45n1IoIVFB?U(hhn z$vrUA)=o0jQl*t8W8;P6KK5Y!pr~<;uAFIvuc4%^r^m6j^+@6hfZ{96->N>RWFGp; z^MacF5?!davh0;sqKAhtC=&}-`(AHQ28&8?8((AGdnFKt;MQ)1WRj}CdZr4$VdD;JmDdNQ*UgQYJj9GZHM8e8r6^%D zf?&~4x@K`PC-X0|WHemE!hB)L>+)G~!V!wN1Oz*y$Wlj$no=er7T~s^^L5FREGSEl zCRp^>rn%YGUtE4bE>Q=_F4*3me z1-$?Z?p^nC^_M`^g#tRHtDp5CrkRt4WoNZi{W;2GZ1a?R zUXTTWVFA9`+kJ2qNN$?crkl&3w5r zc|Y}?goNqI+nctXDsU}rpbL(~R?(^2b_5EYiGIxlh0?Lmsvbpp_is-naN4l?HH%3} zm0M5*j{QvLd<7f^!BSBk6}9i~-=&`5#JvCl-%BCj4>d_#&|@}|E8sz=uOb;@OwPCb z-ZU@%-9Vb;%Vf|Tq{rv{i%(IN7W8D#oBVvw*VPU>r}Xvv1}VY&K!Gn!GEM>p#ytxy zLn&;jLvO$7S14`B^zvt(jEobVOpIjIASH1=ku(csE>@r$qU_G62eE?aDRBSACQQ*A z>SHOWb~=g_FtY?y1OgzfcVOmDjnRmsI9GBRXy?gNHR8#jV)oF^)95! z`L>+>84YZoIE>~yH(Tvr0tgML8ebn@WSnD(#jN*ya89~&?_9us_cj03yRd~fvB?F! zdS@8#g#1D}mOA6uOfF6;y$vtRMwqo~k{51FAc>s+M&xd@QOAz14tnN1&=ZxuPdMPY z)1UGh*_xNwQh4e4)F7feE;}bDR~XjzsSu#k26}odGv8&DlmcE(y z2~pqvA5H4O(f)UN-xuw}O+t3##m~2O?iSPr>&rB#M7H1j*lvnH{6{Amy;uT)rx+&bk4z+CY=$I1fq6N>^V}(Q_gr{>rFJLBt}ad+L)9~Nb9dgk0Ai2LPV&MG7PI#EmK0&D z()xR7B-d+(0U|P84Z!uZ#or}sJ2}g^2gpDWbS~}LA93CPS6kj-FOXPV?uPSO9+oA1 z4JuH{zJIW?I#dmOaMRPvrjx(k_>(#V>m7~#7rt}|otx`3Q;@?<-L^Y`PnlG5a zpV!i`7NjuiFZjr$9hi9*FV{}+dkg~a-Me&qBoG?;nS%3ty`o2174WF~zz2a5eYqlC z1d1c)3yTGZ>~p`d8`&|EiKIb=fGy2c62B!5jJ0A`ICugB%ZMyjlsW^qVsbzP3>fMb zhXHOZfb0oy@Zx_wcUx;99Fk;JlQJq7W572#LlZ|d3wwITZjG^}w-aMWH>4-K%}mS6 z_O;I!XM{8`U4?sjpjB;s1sLU^o<5DeZ)M`r-6W>yW zpxI!l6s_Y7$y=6R>WI=<#l~OHUSFZX26IeS8U?qv`pb3Orv{UglYI~YGqnh6Y!CGc z5-W;aJ@KHC_ZZ}?_8&og0Z4|$o*ghFzbX{WWik8u`cXbJ^1|H0B%Ne)o8CM;d_oq) zW&JzxB!W?A+%P)Ay99AzyGi_$heXPBQW8zXOInFR*_>s>QmjKU`m!6o(;WvKyII*1 zT#>Fh#_4JeGR+GX{QU5l4-%hz0p-BWuDg>X!Hvgo)x-<)rpl}%6_n~aY2E9bx1z&L zM$DeSwqL|T!*Z)!%L10zPy4{+Ym+y?4t6^cFcG|wv-!;UbI*R#NPb%NxV{zP?=~^n zDRK6fD|q{G4!q03Zf-Xi;oUsXT&tqNzx=Cd5eRb_pW6X>1@yoqfCU&%MzdRJ?oa;- z_4W48bmbJRot@~KNY*u<^bR2B(HR}ysX=+urzUYC##}E)aKJ`vy5?vip8qce=?^LW z)mL1;#mXA;zxHS7m2R&zakXpLwqP0@GR$ zN#prDD50D&{C3Cwpb%Y6V(Z}@c;ncBG#WTNSbnWx&o>RqPwLe~?_jmpw*75=ds94Y zd71&~*`bSzi_c`u&B?IKFL?`(`?yKE&+|B^Y7?YjGp5rOx)=JXBMr?RDhx_S8Q$KQ z*QW!3zmQe?n5Vp4DljnmIu1Y3FCGkyTobk1Y%XZZuIWE@B`A zM8d10A5=MPaAHqMR*S$uM#lW)MY2V!?*NSd%j5%s zKXn$&*UQo;?7qPcfwO(;?@P+v-9B9?>%xH+_YT<51`+e>0};Mbkm=%tFneie=R&ZJ zcv6<=kjB7}1#Go^Xg1pwNh9*cJ0~Ym>`!d$@-p^Gw3f); z=Z3DI$Bqpk)w2i!TY!;KXh=xMw|B6xurP2LTm2~^a5iN@EpmBzj)cN4W*V7o)w_^% zLKGmx5el9_*YkS?Wehw^+XV}?c=-5=h=Vn1a>)cv9{#I(aHccf0m@w>Ss@*Gpu3vp z@G-Cfs|}Igrt^!EE{eRTFJH6%Zc2`KXFo{U(?e@*BCRwbujeo$L|-alzhiIax>P6Z z7)*m7K0c2CV#o^pA*NTsto4BbA162fX`|^{GwzYDiNNR_Kgh{ND`8h=UeYNWpWigeF%dVOj`jI9@SwgBNWY9olbPoVhB~c-b>r#LR1Sl zDli&=Zf^#BKV6*$TwNWVtE);f-fPerTPP)K&8; zf>PRFa3mUOilh4xvZVq?F=+t}#$eHl!)=F;H@U%$URQ`_I>xkOtrXy=S8O~~C?az* z@u)$QzjEW*`!K_Ug9AO*sIOmLG+nO;(o_LAL8H=E!0qKXo{22w@2^=tjB%$s)ur^l zUM>R(kI#p;c0Z-7H&vgD*C!{V&o1naur@EucsTH%JsvJqd|q zO9Z&U;CO4iV9C|7!mHAWQN-!;^uxij`HV}Y_CP6Ih}jD%~u3=JFdn=$C4wjA=uX|^TZ0I!_(w~{>rMzO?iq8u=nHppV z9_vm$*Bn^_V2ddsSxGR+|FJupuxN!zhVy#0QBN-8aQfg+4k1>8Mf>vg~Q^g9e}0`rAlsa#>ayv ziMon|lAhiP#DEFpk$4#e-^GXrC@{0I6o9Hb&NNAu?RTPHc4q9ZNS>Vt-C8x3hwlTQ zcQ09jHPW!H(5+;~5cOPcjThbY&LwovxARZJoh#O@NE_3k?3-deZZl}3l{d4w{1<>Y zQ2YHLEltAJsn2OAYkNVf+<41?Ua-b`u5++$he7jP8?bs(1;83UpqSuR?%K7kNUtC= zETCos(qj3v9tW4s^zY~f;coeh7jczCoannd?as-d+SLN;&4YrogpgUq0T7ytg6$9+2 zNe!v^(-e_BcbiiVO-S!3&*(%}wO`VGRC@Zhk;8fPJCN!E<4Z}Ic6#oJBMbJ^E;r9R z>`zzaR+mbrN$gfdKUWK@8Q^y&R9;<70Z)*`aMZ45mb@%t1_G#LARqGw>S0AiRzNHW zY(FGt%xUT9au0lnxgmHb#Rxmf%}Y3t#b}+zx{o`?-%d;JkR^QgK2usAIks% z_nedh1bk}4LA21M(Lh6kR0MpKF#NuYQiTgy(B!?+WGRVBMsIC#6xC^qOPwmOUcEZU z+TYbPFg~QHt{M8$nUk^+fJWQA>!!BX23yaO`^5R-5%;>Z9NA_nG}es+LVBotKzr`)<_ySsk0BT+%-R$AS*uTt1o zoUTZ2LL8k+gp$!>_)%kS*<)&vPaf}%t8hA9zXvK-^H}d5K8^~9X?4{ zqmSbc)$0z%)qmffJaas->)F-Q_xjxQdc;3C*n(CIAs2J@pr~Nmj}BR2&>lgxrs53! z8WNts^{;-3$Bl}PCOXTw>8lc+tafpK0zD(>bm4cPvPD`z@6Z==Z=Lb!!>d)?tV!0E zowDQWm{bf0tu0okrF9rp`DH)phx9Ftm9=`^A}u7#ei=yfP%?z+Qz?Rjw_U*vyfKhx z%5=qIM}EIZ4i*kbm_=5YF1UE3=P@BP@LAp8SHik({2;LT)29*if8V;LFDP*&%+&Vz zNH(0jln>8Z@%!$>HRe_(*NSi7{*D7~oj&144glru?k>aR-!EbruL^G#6){8w73p_BK<|+h%-c=W7;gEhQF{1}%De zdY3Lms%wnyA98(0B6Y9B+~v`&Z>l*v9qsRLpO|TJ)nVZJW;$72rCGMg!jc`yW!6JjnH&KIAUSgJ2~hS~W`&ZC zdh!eto|j#}MTvxD@L+3xZ>hgR6srQbQ{8WCBh>0@Tlq)0cFVl!(n9 zU$YoWQ&cNt`tKvUjZZ)TP1W6&hq`AP{b^}v7+M}G)Sewyw9QOUYuC6DP*8AN&3=ZA zq>Doub8(_dJd*2Kte`6l#dz+UU&t0PN7E9g&W;nu|=$pB5ijMOL66jOHo z4t5vv^YQuW3tzl+34T^?Ya({9OjFSEPaL^`lZV#??^Ujxf`8KjIPXq&#tNA*^cl@< z4HEh`oE+|~4d&>8*F30+^QCe|=9JTwAhZPQC34Ey?nH57Vq)-wVWLcndJv*^`22t* z>y10>T(>eY^G_VKm)O(p8@FyngHD^Z=9tZFLt1GgiSslDKcq1Htx<;EBO9U(%|je+{?0*6 zp5&+ddLRA$JHg5={M!9==JWXnuJws@#vM_^;FuH*wxg0m#*vDIM7M7{{8gc&oQ2E{ z>Pq!-ULF+;=j*qM`;!-&4(H>tMO#r%gA=zTGy3KcErjhYF zqy6{a4+C>gbWSL8($dmWQfMhXz>|nc4J4G0+1V8tf??^>Exk1~45gF*gsYIFEf8#D zcerg-=XFJ=(jg6T=EkgEc;Cb2aJw0duG-qZ;Oasfr|IcUxV%D=1fFoZN>)o@er|44 zM~8ur5Tac+R*?DCI&|IGyK?b@bZ=WW5e|+$G69nk_8da7ZjO`1URvp21i~R_9Xl(G zTE!ydw#XEt5S#A^;Z0v(->voa*EBz@P%SQkI*OE%>kOCDvcNq`uG+;B+gk)PO(pBD zf-O#z7>-vCRza;0cMDR3As`(zlP&n&e9njV2K;DU?LpIm;6B`R{8JkE@<0n%Hf1dVKM?X;E zVwLk=z3%&W@936|NRs(E*d)Mdk8d)AjY?p(A$C&)}{SxZ&Z!a$9WW$kVX zoGmz9NrW&hE-umsyhun)+~Q7ATKz=r@$zYkRQTpvjUn7uvuPaIf#YBI^uUdm3yX?s zJC$mNWOjXh9gNCCcww=Cm(j|XJM2=@(nLpyzTb8D{FKy8-_3{PzMZXtuUna>?Tp6= zmi*Mz)W*h)_}oAn?EXkmDrwJ=&dQMpIJR7lc9{b?f7jRNdi*fDZ(*^9|4?Fiw4@WP zp>!LNS2_&cj1B3bzac+Azkq;NtBg`A(}*s%w!SOz*1}}zy9=-YAne?qtqBBdTRMgx zwb+{yNF%)q_qe-TTu@*#TD;Pi)(gH)x_WwGeMJ$1t(v2KveeI*rgZq{*QL1&6R4Kp z3^+hGQLVi_Jt3?*+OVO+V#(jOTySi@AuL)>EkS7k0ReEryg6NtE0#M7SJmhp98?A6-AExl zAD?q~LIV?J-c7>Ww@Ymn+2)K3WhnF7df=G(+n<5hSyI+YUO78I8FKOkeI2(gGz&4j z&Sen$azRUq2Y_s96d4bfz7HHNu>fZ)k1~Tw*CQ@uLS+@>V9pvTIr*=i_!rd1y~!}) z+QB#t-sLYs5GwzfZjjX0PGZ%rv|0c2(Ebtq!%23H7ChUtixy~!OZdJC# zey50Aw|*Tu)(ww@+~%_*lLV%=T#;~oy1}>ROz_#pi+b0iT`vz04r9WuA20PJB_$2` z{6>UV3QR`uZ+r@bEtI7{5V3hKo<1XF~1j7%@L36*lj}ZA|zrK)xx9 zP$;Cd^M``CkrQ3#2i505Rsk#tTev1TWZ!u9Ouze7soGoYhEOW3bt5(Fo}gj%)84?a zupf#yUln6uG|Nfd>0X1N<$h>O; zmy2@WU2qdcJ5qd0xs6U-es=zG9?IM5JuyC)*Sxlb)zfQo_AenAvcKB(27O_+Q4H0mETre!2e4)ou-XoaPXuh8 z!ruy61H1Y9XYj5ojC<~hPS2lmUyQdr(TVKoMIdv)B=kcirH% zmc9#TqSceqckoxPKml`eGyR~22a9%PRPtifm?kqR@wDRs`YAUTf=~p)0H(D`NjvhB zD}V~B>-%|n0~;t+z`MzGT(xR)ipfhDugB_j^G;8DpzLj(*%G?Q!Jq%2zk;9q7i3`9 z>pHSUkL=96uHj&jbAeuw%g*lMb<^p;8nPSZW|0G8M9t9dNlC(3DTmi|A#Sey;tkX9 zXJU9>8zPEP_lpA47GK}s#uBRUqtDT<>e-I<$}ZRzX=A7mZY#qh<0Pa+qgT8I#l@?; zlJk3OLZ+kDCP!b#p~3gsuB4%fiOH_Yt)SC(LHpB$)7l&FW)a&%J{g0;+uR=AaCs@3 z#AomI51dY9;n*~_#{P{o(Wve20x6OU2~Y`ES1y_vb78L|&Q9x~p^Q;PveBWn6iub2t-nVO6*Vh0S!g{`{q{({%X0Ms`w^=60@WXp<=59coKbR0jk{)nf{2c`Hm3F4{n?L;0x~kS#c+&6%ST>p?x`g@_45{){H}Bp z!JM)xt8>35E>;utf*N_oL1?X;sp%5o!wU%X|!YRQ^TYx2{G{+1r{H-J2T2HfsSUz#?#8_ zeW2~ITOIj3JNqg}`<`%9a{DM2B`LR=AJQppEZ6M>T^oX94 zoO7-wd8o+yV$;#)w|-a16Gu3cxvZo!R96%_DSSo*^|Wi~Vboet_`(vwn^H0|@(0q% zZdK(FpTlFp@K~fNJS0Lze3eu@xhRX2D7?O$@-))1ODS8Ln!#FoA<--L4qBoa9UdMz zKkF7808vkv#;Zp;K+X)gTjsr@BKEbIC|^+=j!a^VZJ8)BA2(o_EWNWdqZm$~Lbuf1 z`vNMRh)(i7qL~#P=2uU;M(x4AA+`~m4|-{oRwDT^{pISHSG0VR93lKzov30Zmb`rN zSGOK5QJH){gP^VPAiv{brYM#2VpKvZh^3%ha4<*D(M5Z9i^SvGpoOkvP!k^qMQqEU zusMM4KB7*H+cF1dJvwPp2Ig;mO8s=C{sF_><> zP*;eLzEUn`lGRRQQkvUGTCs=fS9TTS+R<~G^ScF)@qYLQT5tN(Uz`pXj)}MWhKv$S z%uJUh91#Wv#9LGk9@O>pK$kyB$<{BdI)uS+auZU3WJUP16wQp~W_FW9@FqB8UJ#R~ zr%&VJx-xb(&V-=oafX?>kTcaBe70zn5haYIr@rduhMO9@P)fP|ad2|b$&*MG8- z7pp6sqI{I5==|k+giOZfn(7Fd-YMtr{QI^M*HAAwb`CZ{f`@8>LpmEGca3;^C8y%t zT)9F8sk^Q3jnZ{q-)87$$c??oJpatBEIYuZRlcqhZSV4-W1{Np&x+Q@vwaaUpA!3q zJqkgFi4eLst)@CG@(H5FJ*<}wz*aRVw@CVBf4WsGaeK$HvmpHxuVw#*;>ZdVHQqsSLg*L*U{feYN))~pJ*wDY@{nwJ}nkyt;y=|-hO`XM7 z+O^v4-rvctoI=$!i1g#-Qj_};(J-vU4w?~5B!^vnF&E1;JEkmWt;&3`bRr$)G_p;o zC$0L{+3V2y<>+yo8#(CxkwFj+_g0ij*0f@xQ_<0zDdhDFr?;e~_GxHn{4O0QCTtb6 zNj_8{B_$2cPDs$cdU>Cbq2@Ev;7(`)9d-lks_=#Q5$`Os#L~0x>hxIflyN@5+;(&n$k+HB4>UZgGIE1xf>!b%q^yc>lY|tZg z+eP5Q&^?2Aq5PKD>G%giLk~2Z)-F zmk#lS6B2Y77&2@RT8bdyUq3j$HZ@}Yvp?TQlLm20ReqM^>fkbwp_)^7dPBr)3$}m@ zes{6&4#na^<}zcyCPXKQ@B5I1MSFE9UQ1Z_`{svcZ zq%6SXdaWxyD?HrDN&Ppp#^Qjpb!T<_Nct}KUH;S>DH)VOvd=CQ}dNX?a{{=&YZwxG>mWR2q%7a{}Z1x$u(x0Y#%ebIr@@^YIS zjG_s_bGL{PwWfBBEDbJ;uDH_`KKr8ZYegva=L!7to`SH;$j4}_pR_|w!a`Yj`6GnL z+4Gr3KlKW8J$cc~Vm_Vow{XbIr*fpdu|bCH)#Ld)&Bn=JpI7W79N#AcP@2VCqgE?Wed;FLgTfw0TK`S z$*b|mFfgiFLD9hwyflI~y?q)3UgHFUljt-6!=<6Go|;=V&V&&iYdQk+7?lkD(!hQU zk_U3gi^8R7@O^B|K0^}l=PaMC0FyE#B*aCK!g(92NaW~{(_07d0}mST3ie#Mzz6Vn z_4nX#ePd(eH*Y!$3a72!(}U*!Cg>t0lfFkqX#&byN{+t0RD2&%&;3yl;Q!pa6q*M2 z@?R2?|6gBa{ru3eZ)|Jp11`F8#=5MO5K@w>;GdMF@X|c{DK$P(_PML94wxs?c;F*N zd!oo9l5gFEeC+rOjKqe6fkARI4NbV38Vm*ckxs!PG$MIX^I5hi)iWf}EMCIBg`!l= z&Fx9_x@LM>pU+2P_Aw`CJe|aS!n^2-gAe={KYkFAPfkuQ=Mn{$~1O?5LHNeXG`tPNuyslYw)x zpZebkr~ZpS^7=KWiHgM_R}*@weUP8J!;8)@6-=dmB>N9!PnOz^|Gqf7durPSCf1mg zk=)r3bALg)-61_v5&8w3i|jS@!%XSx8^S&xEd23lsv>RK#g9@n1Xx?@X~*b1SPm1N z8eV60s2{TEY(T7V4|Q~~un5L~27?d`Gzatg-TMtJVDMn2O|63E$_ca?9B-aE<3|93 za}sH7D9io$v3wdXDZw35g2lfY7Ld>&S5Bd|>=!>DG9mMzWb=%DCG8qJ{h@Kv?oNt{ zO1JmT(+A|M<_xwip{y0A%@ifVEMh_khrhp3M6JZinR41IT}+;BqgShU+`VsA+s~p@atlunUN!V;e=Qre*3whZ_iH3 zG_zS0VHxt)0g#9x01BDoRJ=M&O3%oSJeObGz|D(L)A}?kufpxM!tv{ zjI*rBv*|5IH175)|A%j>9D9*!p_vWSOnGkADXh+|)pU1I}& z4LcT~87+5F9=rr6hXwQveu$s;f;xc?NpCut-5$#rLRU+tIASup9&a{UxH>U27}Tef zER*teMTJSDerDrc1hJ3k#6++`M;JctAECEHbm3CTCbu!-2sB7d*l*lmX`lr6lnYlP znow3B@9#+(glIRO>%Liwj0bM>xX@9)9sN3&<09 zDHF*X&^Zrml5FU$_?qh(vnq=YSzocDi*SNWBmNNe>**hhDV;Ha&2Obbbn<^R=0Ci#?=k^`CK6`t zn1~RSB4p?9TNzanjxy0~Z#MhFJ#b|^ze^IgLuCv4mU7=)+u6y82u&85sBmDfgJSDv zNEV+>lsrb)$AUlt%FU={(~oj=fzzQ&Yga-WfhH(<#BFcIAuaQ=d-5q7(vfq};L7sH zyaBBgT}CntxseIvf=-nV&DZ1QUi5B<7tC{+O`NXGI0bJ~dn^wl9`W#a;^LDY+4T)g zx{H4qFLWeTj0X)P=mT=>b0ym(9Gb$;sA)U3EanW#0j{WZPgDoh%)REBuvA zAiI+BIZfa_9w*I>G#PRa@zkKpT-f(L$OPQJzdXDXA8)^O#A!5?MV@ zl_o>2(4TX~(%M@8^vlm4WLm+SaRvxbcz9^2mx|wGVidkHcoPs94Hac^uZ5x0M-Ri6 zWU?SHx~MEp2kESQhfD_V7?1N1LYHUvMvHD44-~#!QRlpL@fiMrcgVxxupLO-N!TBh zlh1f95no~IU2pJo@AmeW^hprK*eiS(e zsB)s_tC0+)0GpV=KC%n4*YH4Ud1J0!R}k1?YIHKD0_?tMSE-qqM;G}21^a8Logbl-YK?IG5R1|-r%*pcu1kB9LnP-o zmd#k~CuA~%+0j^(gBD|NC3@J|Fe!r>m6iI8hPqT+6DvT2M)|`eRHh|#E0X)h%?jt4 zrKEzkQP4x;3xfh7ajfgtgkY*|tJQ%Uy26#}9G%p`rr>!Ft!b*3DtXx3+S`F78;W1a zrMxt(@whX?i0n^J+9xHNXd(N7uR*rz-+$6v=uMW&EiN{QsVOsGY4IafSsNz zpC8nNQdA#`G{QNpWoc_G?-Hu`F)U@o5PjUGmn4}_l#6Uo@2jyV-z4vg9VUfpTWKjN zVpg@sdWUaOeD=!Z-*Vo4k7RrW`kUWRG8U0IrRneb05)qfD#icFc~~le9{z4WU>(0%LdtdH#~)*Bo~}2F5{54;m72& zMDwv|*ElNr67s5gqdx3&rSZvzQ;G; z&}^I2q(Nx-Hcf!<$ZsyJI}N)Xul-8aiw!KwDLj9#tNw0~?NH+<{rHi6Y`xNhT8i~f z9PkNDao%1Ptl4_@abZneFh&=eXJM=dsuviSNi@e)9Z?86B`DkmEEIsl z8tE`$%y71YJ;~4W@TpQ?f6INU>PFJ%I}H_35hz!Izse|1%h=Ssm^`+paItbw`o_UTAZI>dtgdu zwaY;QovLPKNl8g0yGgn3TFZ90_2o}F)s}<5ew~K0L^A}j(grp|(nl_6rMW-Jy*ZGs zmIxE%>sM@mOQ6zDo}cF_iuF>9#bBno+ltd?)ied5{A2hm-l*8w0lnm|USeZs*YcvX z)b8Xrl)r_mC}$iNVi}{BXM1!&e`yrjdlb_J?gxk8RJ7NiGL%f zd@bQXnpS~xiH>%S&v=1-jVP7T?BpXv9rSCL)+ijX;7zY#qa~qMZgReQ%y%7k<2s*D$O|7cs z|Bw*z5v?`d@c~HHuG2p-QKY`EacT+&i!w)7Tk`bCE?}HV@Fvoyzf2shwtXa2opf>B|(76^5bD0!Tm{lTr-pt6f8DqUz!abLf#R zI`L2ESv8#wIeHo!D_jn&m?OAvmEk!-hwNmbh|_d~`0|v#=VHA!z}TLhI>%eK59{@E z(5mC8XXc`NO+eQA{act)a$)Fn7R*7W;Qr)R7r`2&EDFqGTUE19s*L`^x+OjAHxz_E zNKpI2)QhZ}g41oa7vaAK3Xq4UX5oV@EFDblzZ)7t=@r#CiuJsUt9Sn52~SiZWbqhw zSNP@{>-&^a=9u#G+I&LXiP^Kwu*Cn_k)btp3lS#TA>e08esM8$f*(jWfCa5n{TBHU zzwzuvl0v*({g~F$pVzW@J(+5+={vszGSyaK{z<*W<%DK;C-PPDwVJCtW~OG)xi2tK zG01t&sth`%Z|`Q);bKaJ?i%;yKGW@)2MELC+j7)08eyLY*RhOOd9~d$wBd9563!sa z04+^L=@a|qu7Qz}!*+yT+Ly@a=zBxdKbxBaaC-`0&V0Z)J9>j7h>+>%=rHfDOv~^s z&}7MxPjvEMt!YAS`;y2rT+7ZYn;UvHC))a~2K83$)vLFL^Y416&ScLU4We7GJ;LVI zKY-5K0Gg>BQ`4rn9PiT$Qa9IzR6!72<;*I@ zbb6*|H*0l}5jIG~5Mb=M>1w1u~AX*=3iwP*q(X0L2FRMGXV;`jnDM5 zQc~&KRmLI+A$$80sJ@8hasH|@>{&OWsc6b%oq&p)w93C8fKrUn4z@7{RbVEtxe5x3 z=$0fuORVheef?!0*$~DNG0TSh3&s)9)A^6y3;*o4|EbpeLqI$|wpAqshIbvMx`0oL zT=d@zJ@h1fgc-p$wRP*>Wx_9=^8^w)MuKSUA57HsK;W@}8}}Nt^d2f(?Z3p%yJA2Z z9MGyyCzSEJV?u2-#Hs)MwM43b7oNfw8m5N8WjG>2*Z>HPPegCE;IGvk)dk0ErX-40)`zi4?L7I=}8P6mUUeR39~IrXs^o5SAO=Kj$ymxw(aGEbWce4=rkB4~}_;2TPz&d#{$3Rg!S2UX9ZzmQ{wViO zS676Ng*g=S3V!_0t_vS|$yj1>CFlqj(-7xhM=HPozQQ2cqg|!T)}yU_is;G-EV1)aKczTV&ZDcZZ>$AVlNx6qx2GD=_asAc^3x! zTIB=qj`mjR)OtjcM9BU3I#0DENm%9hGy!#myx$M`rEcSU4ILZ#teb|`C<+XmBd z&q1$1g}w3@(u3J&CE3w+s}uXyDEyF!mXXUvm6og2l+;`_Rt@=LQd2)K)OcwMP!QdD zKi3yvARYNG;#Nop*uhz!EFCP&`^%6Cyec-m6C5tjM<>b8+wPtZWqX?Z2;`#KXh;o% z3LOP0LE63&9uB+hg$t*H*-^g8FGD4U6&V@63F@cpsrWxy?x^xPFN-`nzS~!0hZ^%W zoN~6efULI#@g#!b-Q57xYvlo-WM%a~Hw(B8)m|8@ewJ~+=nF$g<-|t*qRkjX@1pR-CG+p9QndSA?O0B z>sq4sE~iCTk4B8@6?CLL^9LYrSYf8uSQBa!L7qeM#WuD{!d zy}>ZYS+LUB(qeTq;Kz8X>3nkNC;z~mge8oCUpLdr0|wQxIw&oZpr{d!hQjbSA#+S0 z^D=RnuX$d?x^gidQ7wt-<0~7+rPonN zPY}-Wxo)a0{7BqISob3*QN8XJh>yxbw?eOlA48OqM-q*W;dAJ^Ic|DQXS=ReMEJ3l6hJIH-TVX>5f?M9iUU2 zDc*i8NU_7bqCrXu83(&zC&cF+5d|L-l z$+}dN?n=&^XS3|?+JX2g%RJNfoN_My#q(~PtDN+fyPG)ixR8zjYr;3n7**~`fPHq~%>|I82yUdnC(>yMVk+*f_%Cs^u+WE_}T8z10 zQWswj`{(_@ldw7A%6|AcA{rh2#L-~(bG&BKyzF3>qRB)QkYC{kjE#-7 zZ{NBYEew8eY@M<^++AE;Som4FB4jx<^qwF^>QLbRPn{zSeEdL#-X1t-kq#JkR8VfmyP8MQ}pDl6^o?dee{9r<5>Z_{FB zWgTv4!e-GLMBlC*i@KS9OQCgGv9+(S5F&{;Z>A(ePI%t8e9;zAj;+oqzS#O%ojfyH zug}VhlWUOdcI$QeF{3LOMCZ$6cknkeRCe$cn`dgIsH7>xKu_+8{Xvkial^wj>!`7W zT7ZGwX=S+IT~4MIW#fS_^fSM|v^6&$-6aHbvaNxYhpj5vIt2+oZ4}EoYq-qSif8=+ zLKGMqKPKfG%3E1`cFFA|Bm$nps;ttn;3>#(pf<>{b@#y9kn5J|^3>Esl}6~Fcz5p8 zDzPJ|usN7(A3X~b%7D)T%Lr+;-Qw6sSGg4-hWc)Lke~X)66Cgj9?q7e!C}EWRg$;7 zqt+2OavvP9i(s!tOb`765)xK)@h#(n-En6#26&yQU&g%8nGM5|%94M+Qh6~Y!-$dk z^PE1#y8KDo_3Ejh+jco8qnVkR?qsz_{&pt;t*lA<+2EwcTdU!oh8tw}mSg$-mb)`z z|NKqSUY;nZaVg6m<+4~9E3#J!QQ6yB9jTK2#&}!Eyik?C)1&?e6z&+eJSfTx4+(*6 zheDFLvxdmY5P(#TlC|$Ik^o1yfB)X?gvM@p-^bVj^>)z(@LaYsmyS#$D6{vE<%f-N zq+8@@=vx1FH;hP)D|wI~sgMz1>GPfBh~wGe{NN0(lcJES>SXp*`WMWGtTl!KouN!8 z)>9PLP|yB=To7Dz>D5(U-;r;1@3;A)-jSPpzDs_qjaI>sd;af5fJ*(|RS<^Vj{0|u z9gt1`-iHf5@?W3>`%hQ7?=MA@Lf7&S#paoC4u=23u>DZL@^1uFH25nzP$Q#|rZjlo z=Wp{otUB=e3cST1MqrW3$}awk=6QhcgW@oQZW(ARc#KT|a$DcBff0p?2s+?73k?nz z!^|s$NbF!J@tk9lCyeccZlcs2V6DI+iZk@_I>vf%W*d{Dq9Tw-F-b(BzLJy>AY-tF zYcd3bJO$Y>UAuGc#}WL3nxTI3ITX26Qz(|2$y0BmawwE0{EMy0Gc}8M}j##Wsn^8_qo|F4zmSI7)C=;c|sU$Xq(RY zxVf!`K@)qyIC<_XwvQ;Iz5So6rWCeYo8y@S=Cb2 zA3yIt8hi#Z&}#`$6QY^o>pPFhV9}k^q`Vo5H38E_ITls!M|SCiOZ_<~S4W438yaW{ zJK^oDKR_1%t-C`6g2vV`mM!;~F+Py@r^=i4{w~^E^AH72ZM7|r(*4l7OcQlo*@Xcj zG`8hczlC`m3+Wy_|*YD>gB2BO{em>#hw>f$oI*77 z-wFCRj^^7odig~{G2-3BgKVvWg!doLzWusXY&!l7Gn~toDz>x2_V4bGG&BU!62e?y zzH^;|Vhx%NbVSy?YaO@;lvWdB&&+MS;)Km~KMVwNuv?}1ir zW21B10^k(j)19?%oh3)N3_L0vB%sGd+Dm}3V-T|VI)3C!VK^I*^?Mv)QV6U=vLl)8@oHdf9c<1M9SXu3`3VoB`dBPPH*nU!K-z93+r^*~;wqe+u3IiC7*A5OEh5 za@@>JrJC9(ZF4PY@oWomg~e}KHU_U&MEnguXpExC-UBZB$NT&CfpvI~HUVwtllPk( zl0_i*trP!BA!rqat5EGS?0I8uAV<~kSEX8X`8HEJ8t>;H8|N30WFT>JC?!!o!&u*n zqr2|B;rs0s)4-$%5)J)5Wd)l7NzBErYCo%Z@CyXc4G`Ri9#43mY%dL))mrEgo{^j# z+AbHbRA5q~T7#7r{kCqM-Q;)L4#VxqHZwuh#;Gu0ABQ8(yh9nQ0j9&`#4v)*b*}&Y?rqAt&oY698ivV&K_-dW=uWpU669*>C z^0XEdfrNswv0-60$MtF0(as*8p6o=$D?hm==%k2k@*L5vo3 z`iIV2#Xo*@f(2Dq{X7;ASBZjwmrc1AJA)`x1tbSIa~&Fb;QluO}v;f_QnKEF3B` zpsAGCvo5ZGqj}#ijD_m~@N6I<;T)`9K8qvV1a#Z_9~X?Sz?kuh7#>?{2Da%b_fgV; zECw)!j(@=L#oF*|ky)?0FLLr8ItqHe+OS1O9y-6Gwb=Db05<&2Siw>|R1~7_s&1ac z-131H+Q(AhHaY*>?%Fau917Vm4zu?18sq+Sx8-#=5)#9?-l4E2I7^5UGUUIg@cq;34 za|yAqp69ql^4Q|R1D#B*s>*fkwzPVcq)qc&@p@+CX~Cke?}@|bDlK5m zI)2kq%j}y;4PRIGwUo&F2c*Bb)|5v8O7M;-el~swG^2vtgmxPr-MtBf$%Wi_E!WpU zZYJCm)?H`V9%i*M9lTK^>qu~9he9?$2K;sjpr850-loShG}X>$ln9Ysb0EaI&#GAF zb|NQfWh$JQ-@^6*ogVa-&Z^${!50+FGrcO-Rj%{8HP;@QmPSA%ausJkDvIB6Dbs!p zia_~#8yh2W!7g&t(b^E|FV9M_6zQ=!;}8@Ao!Kh?^5aDg_2?shyCO-SfN2L4uf4G9;VYgWv6#dV7utQFE15TNGzua<9R6p;^%oUzkO!T74(TAId%j4OJP@ zF(e2EKX-Ff8BYEZIKox|jjnPAF<49tm$_Pd%ZyK?IcKXr{~c=FZ<;I}{s*O;ZcIcW zjJYK??(uENqSW=XWA0Ev;~OMAUrXgso^|KY;|B~%boN3_$0J3gZ5D(haxUDCF2Yol z^|d`vnVF{Bphqjiumm?)2)v81x0Vump8;VN9)P+e*F8C;Ymkm740Aif1@qZjJcIfA zTUn!ab2h0FePsk)_g6SuqUE#4Ul4AE?UwWhJeVO=PHiKu&UaM8=nPGf1 zpDpx`$pgX6nf1auP;KnS1e4|?4q-nNmInqnjN-k zZYwSD>Qyu4+}alK#J`f`_&bR9ZX@-51sfX7Ci3f`AV%NUfHfhZh)g)^(iz#Q-sv(* zk_6OP=;D*b9_@13SKa3#FbpX5XFSuaaBBDB%F@VID{^T|ox-6ISRAf63uO&&OCg|< z22B3gt!hGRza8{;dm^Z2qDSo?q+YOGPe4MyWOP=UNr?z)sz|J7fVU zA#7hRr&Q^>o|^*Y_9uH|jw092d&WZEM!Gz1-_=u_AUTiyzzgT4q~s*xPi%0;z#U#X z^j;5tCZ9q%{-UrBVzPV|9i$`_P$J0#QQb>HgEVptLCW&WRPCizZ}BC z!xwX`|G7#Wvk9XK`eUZ1+fV-OgN$geh0lqJiSd^nUj+*#k7Tv#i_7O_a_|jVn4Sqy z%QsoPXO@<9jf{K*4n2ROtUzr6Yz7^6aNvm;9U<@@{4WVG;vQvYWkQ({ShO%|kd6*o zOqfpQ&9@C(dwc`dMdyiqzNu$A+PHOZ#dI<)GMPLqf?3`Ev!83Lb_)E)i4Xh!f8b)o zCJ-MPpG{smq0U&K<&ko4ZEAYNT|juF#?rw6d)E#Wqx|IGUycJY8Snz!k>~?4+nF-i z*{TGz2E~TCYPxMeQ*WAdB5C?fkDiS?h%0e4#V9jDkNWdkISYj zi!?Nm>cQ^XxP1N(ZwYUH^0nBLiVzm7&fp>}W5=nnm%=X~_k{~s=lw?-rKc~dyILy}NY7j;OfR?(^6Q z0hSn{hReBTtvbsBkb(k3!NS0%I~lVAFAZsY(3#otJ}vXI^sj=OzZMQ9QzYkQ*>j-v zt9FeG)EnNPTUq}@2{e@4%^!BUoLN*?AT0&6!7>Ex>_P9f(c)v&)Ks?qYo_@sCHhVX zDWM|mvBjBsjZ=S1(iLn_tjOR{jA55KO+fNDHWVOBK3?H;Fd4y_!A?X~D{RuA`Tn4G z5wh{lXr6&|ee0E(?)-=mlxlhqtt5doK|_8M;@!(wq4~M%W|B`zOP$KgLx20r2(e_x z6h9=hvhsA2-asc2PYnTQH_y(7pe!wDZ5;9KpivMJ2*rm$XtA69j>4f3+&wDV1MD!O z_+5tPo2gGci+Ap@8?-C(O@p`|KyDU%A7bV(KEy=ko4Q6^VSuW6Hul~_KQ_nHW3eO& zqEHs?*T%;AhLg|cj0T=%X#h1sMfo_OD!CT6Z%|2%S2%09*3eMJe}1X$eRB!AevO3< zTEI)w0rIQsPq+P=XI@DUM@VomYB!5i^|);JaECPU`BhfUN3{>Pelqe`K*~ET^7CsCm`jzHHQFSBY2#_7&`T zjT-Yk?UU!&6#3Sp$q?AtEcdy>3$`|pIZAQ{dw`^*hEE3t#7yeMq(=ie&rXyXL%))3 zjY5rbk&k$5E_9@-e%tMWLha2q7r^Vu-^;X1ctw2R&jQOHp&=dj0*%OZ`DOXtqNHY5 z96d=T!={B32!a|fuL(*ICiTkW8E=1}_L0}?yl#TS3(vbIOX;Ip(4X$iNi$K-cJ}`T z1VS1!p3ZnIEMUehlF;dh1t=!LH^35tpzdw{Eo^Ug!&Qn6ET1a_O1QocL9XTTEhtDS z*&8s6&WM6PPEFFnVAntk`KGwvO_i6xR_1-tH$n6Ac5};R5TmMD6F*XYsT{#baNRRB zN7IC$kAbz_xOWK@QzO?x&IsvfM8lz58uo{W|HfeP+=%EfwX$ka9cS?Tg5J{+N)uyi6wC z1cJT2<&hSv<(8qrp~=W%+tt2!5U03b-PR~G)`C*mk9Aqv6_k%38x5|W)YvQ*I<4pk z$6LbxT-!yBZ$rgN{$^*9lsc6BzBHswfx_9K0vF~%eo0P)1IUXa)Z9R(EoeJ8dC^&g zPoBw_?CIoriZ~}}K(nPxV{~S+){z*Qz9snetcOOr*K_qqm;{%QE{ND8-~Gm)i-P$` z_Rmzc_vse?u*c;a`95$6!+R@#`~BX{CWgp&Y7+6wm=~!kY!XRrBnyNP9w{*6U(#RA zUK^b9A6-9kUE7=ZG4JBERq@g&{K3ovs@hAJE-~q)@(^6!Ke|Ebw6`ocg?bM~nmxw0 z#SCHA?zjo5sY9);l9+gR@W}D;kDGfuolbU?Ma<3fN3L8I{yZY;+!|o#G*NT*dgTG1 zoq+(imB)#i-EMGX=viHH>_E=J8Zh#JcolahkwWsL64kq7 zVJIx4-15#(S%w248eBT|Br3%gll?`KBk5}cc8fiZ%cVZ!REPkncJQeZk8~cSZfXk0>+652_vrE zzIk=a`=&9+-PSdY_F?-tJLkQaDm`s7l!Fj9wnoY65zk_QL7Rj>CXU7U;m5~%v`-Wc zLTK;oKM2^o@RKJgN&n!=m6gCzv!lnon;S5f*E%LN>??ULmkyUcGaDNsk*?(c<4@Gt zLEErQ4A~RW#In4rKl^S!Sb4<5)jm+IeV02=udz?C%86-z?^?!SW~+lm%n2`4uBtEu z)2{>uepg>>AGWBzzvQ4-ax6I)Vy^bP7hYn|z8b6=w+Nf5UQ2v(95PMi))v?}r| zl5IDY(%6_o{&IYgqB%P5m92EG9LyfiHI)7OJW*{UHc3V~ck3>DxKvC`42`cyRr|P_ zAm^(2>Q0QvHJzVrWN%M5ayqM?yqPvp)Q;`T(jaLh zZDOTkg5G~PI28_y_FF=f9>VdZ(E%ZpOhPqrg@sI=(>wh|H@2No0iVq7W{dpmXY6sS=LUa{5qM>K&(PFUE&*L#ZM z0z$iiC`PuDH6_P1vRIQd&bg^6(C!x6vrp{pZrl&Sm#-+;IaxWT1$H1uimyJj7 zEyI4j9x64GJahBkUEBL_rCob8+u65|+xDlAQT$M)Bx6iVDOy#nXEch|bH*!%l1@<> zA(TqIwME~Usz<4yO)E%~wCdRqjiRKLHYHIlLF=vJ{fx*xF|+O;cip?z{r&D*`Rlv( z{+{e}_WquI&ffd8Z^YE4yEQuH6eQ?hZbh)9Atb$Zbea7VfCXs>CiVIABguu(70@kV zUR+hoH#mMoHSKKcQ^Hqi%$Oi1*U;t-+n7EFYgeOsbxKN7<_A7B)mY(_{Qu) z8|km3$KzC92p1Dh=omh7@Foox9a23u*Yic?H&<_upU zwzo0xZT08Z&$(V$tRL2IJwFxk^lGGNJs(8+HT>E2Tnw*AwK1II zn#?(@mEalv%K)b+Lsn%;xyn1#HVJlIqaXDntki(evEJFr00F#HeVWCahbkyo;NI=h1Z`;1Mi&AD!RG`XI+1mT z)^PCwJuY(X=^gw9fcRc$5tsP&abvgd!O;N>f8!a*lm(5S0;WxVXV`oyW958rY`bD+ zx2Ll*l^h4TOh-gV-yfdv>6I}|>diV5B;dgkFGXqA>Yn}{7a;X!oAkE4Fh{&vH}LrJ z#Fg5T1XzNXer^!9e{jMNGQhV6meHGLDGtfACB{U3R3tc0Z$5twRU9z(10iJ--2}T4!1}$hk%0!PTe}sBQM6;c38dgGV4+{%R zMKDzpAeYH)0fwoF!Ko=dM*!$mA*JL6g1X1cv}pkT+s{v@t|o(SmWzNu^v@mMyBO>1 z;ZfyD4)xjwgz)M_8_7h6DS|1G{;SOL-;*22|F19(QL%sng3l%0(>2plHCH;I;Ybw> zyEv5a+AH2`d%|XeKgDm(G4{Siv7up`?oTJ$+{VlP)IHnBF3NJIxpkLrNpQ$~;Mvf? zqf06Eiq?~-tt2UzwafKKpHy1+o&=F;eiTDo@CmN&HX{dR zNxGZM{A0@AeDCbc0rh<AOc8#nEBI#o%*Y{OE%k4?lzRk4*s*rc{OXxIWXWb(y81B^nL4p3B zk7Z~wXCdYS^K-)f)xiA2@C?gPk=^3W8IL+kpF#t77x7vXt&V$husrYew|u+UzHy5K zE~YadiSqtJTa!f;UR7cNkb%mBMTNeVCN$(e@j{S7;rG4uhII6LX1|fCwLjOT@Y1>2 zk=}ne7OZxp>ULLlUT^Wh{1BEH!+5?{H(I+ftp{Z8^)ZI&td4);#t{D*r?u@q?97=3 zmBm3CCc^$2P9`cej1)nZev-Q2vcnYjaE{*4LJe(A-L!&m_L^wF-Xr%adDM1}J`>;M zf0vgV`c zqpCa_f%wehaLmUmvm(v+N?id^-Fc68JGk1B$Cy$Iw!`gGZ0EZ$c9^NV>KJivk6ACT z$aFUpK|I$YsTD1VoQR0}asoz{E=tm_NV5MkM)=^VF6DfK*u;>79A*fIxb1J>|9p+i zSC{W^$WCfyIiLjEhh>G)7=C?KuCP+3ZN&j`wsej{W5Z04@?)B)!zFogkDT15xB2In znfIp1;cj`U%%g|K%@=24w6-KDhRKEoPs)8)UN?Vnxc27x)|uK*a)GVow@LcQo(Z0L zVj%9W;{k^xV~zcsAa=P!KTP`~E+Z{n5{HI_JI7S<<9qlyg%pvNg^EAlubMKzIL+-k z`P?vXtn1O4L60fc_eb&;O{sFPU#6B+EjZkomq#>TkHNwdr)E}JxE%~%!Ll;qaaG89 z{xei_CI`(j@x}6?-3Es5P=AoEh1~>?X}=&lP|Lz6=hIeW>uiIb))W(d%E`fY2#v|;cySw{@$3W9REzZa`o@lza~)J& zlWEckk7qF{ta^F*Vnsx0m94O^I(?B!_@d7b5^cm%hs~n5r=@Owp+_t4ESbhW{5Zih zz$h#Xlnc8}_0jSlZmAO57mmqg@haL&EeDebNh}_8O7ZzZU*T83 z&~>0MCdK|UM_~m8_3L+-(VazVyZ2m*yl29tFwy0G#`!5v3wzR`2>;)GC3`@@{Hg23 zs7Njxsz*r}YOA|dhtWq-K6a}YD31CY-o(vXw(nqoA2)wr*nfnWx*N|h2aaEFma*Tp zIA6h73#q)p25M2xQ88D-B5ugRGnVZRtml(0@6n(W9$n&NXnqP?9^ylSNcth7HaPGh@*nMpxoY^<^`J|wVD3L*I5eA z;B743_KJOEJ%cpcWi3+FBR1?VY}mjidxrfp>V(&aOak^cb-W}gfnldB4z+VgdW$5% z_w{SX)vF5K!{6JXVOoXGj082vI8Gg6G35<0 zX?-;~;%Y|B`M27%qC|b4hMJW!XQrP$-puAuREPj$bkpfN%UE59!cnzb+AX_`e7aIr z>>t@bbNPXwxytQBx%e|IG>RVJz>jQrO+MDz;xor8U-%}pvEZ6C2UA5BeAnME4z_+< ztcQcd(vm-Rkq!WftTOlfV1W5jK_SzvEYb^2*XFvVcK=X+CF;Li06_FVZiOHe&>mpg zz?N(l&a``6p@k0)oa%rZ@$MkXmxzl}epW*L^`7L(2P<3Rk;dAtD_2&mm9nz-rr8Ds z{Y9E0H+-xjb7!a^+Z=iP>b{6w^S4-0!@(rl@Y`ueI2gM2%~}0O+Q)(;&brJHU+`B^<-!#fM%(+Vl%Y{%c;*36lF mK>Yu6*Z#M)`tN?8SbAx9jc$~iP|}PLa>mB_6cusq>VE>s;r_+| literal 134746 zcmb@ubzGF+w?B%FAc`O@AV{ZlgMu_jHv!4>Qa&``ORhYp?xY@AY2z%YP8ZKqEv$K|#Tglz6X*f^zQ^1?9mG z>Rs@c)NQ$a@avA9qPQ?hVgJi@6qHvelJDOsJ0)z*I;vxLAs_8YzP5iC@EynE%L~_z zxxM~5S8G_x+~k0cyxf3GQc`x3OSbX2`NX@o0g5=8ctrZCG?d=n%cmXZJ@EKa3t^h} z)6M<;$MK1=1HJuLRPmD#RReMAn|q_6xOhWm@7%un608^b?B-R9^_%WTH!lXc@ZtAv zUaS%QIJmv&2ty2U^9PjktONzYzy;RZH!y~P*L$|&%kUounNMPFFMMmL@bYU6A9=?Q z;@eByTSm!)9!xj)|LXr~*=vyZfhqiZAZ6QR<|HEg5=|WS_M*52b#+bi$Y0?Gf10an z$l?d|a;}KKwCEF+BF6ah7E9BxO#PV_@84dV#ZCg}!{^@Par31={Qmo*^pu@mT~o6^ zk*^F^e70TZNa%AX*(XbuI4J0cAnprg$5$CEj+~GR70L={j>@E`=E_mTrM{+2W^=nz~Tx)8dC=Istp9V|AS~+{6|lE+_{t zM?=q+Nixxx)}L=At?1Mx32cjFGxtp`NtJ(fiHKRk^3V825G`HBm{nRZvOZDb?w>-j z^T%Lgz`=5Q1rBd;i^|l)pnGuGFNR9XNZZ%l|FnVhWJh3jTp3PhUO;Y1*%U;V(?_3k ztURR0oZJ4iE29ksMUd~cCZ4?s11IMzMOv#4#Ts=6)v!?h-euijjxnZ3LE?x5yZz0{ zY1aYW&lmmMUw)woced_hL$tNW3U%p;I& zwWPS%WU#m~D@-lM7lVk9kO)ioOzaZ8G%_+$m8}^a(of`tcJ}neVB}tNJEo$c8GhF@ z#(wcQ&0taHN07F5f_M;#^T}4trCV)}^o!hlj()^qGEw6U0r3oG6F3MH3#kod8Jnb}zxS=oa_A$~Xf z*vJH7#@&gMhIn&n!Q|wqC@9w}i5(F&DOA&;)`iWYM-5IViwC1ZIIPOmB4OQ^7>LVv zI`_7=wnzkm9qlbFG^Q01Gfhf?M&6+r-Wj<4j0tgEQ9%dp?s)a?hdalc<4pbilarIz zrGvcZ;C8bz0 zqW$98<@;gP?Wqc=qq%7(SFg(z@1D+Q50C3!0;R`f(G`qt?s%7O%Rf^x-U~|T&Oa#9 zsqgOUgWA|IF)-A7T#dz7Srb&Iet5HGLD?MM38N8~a1m?&9@fGOHF3OJv4k>aX&XW@ z=tatA;nPwOQh6%}gk?rTNH=G8U$&yA9K`rJMRPK(sd6WYgm79qO<>1>G1)O-b3?U!uk!WXHce zepREbK_%Ai?t03^MAvAuH2l`PZ(-!z)8eUkWp3?dox}D}ih`F~k?we9E+kKt5#Gu3 zGC1AK3#R-M|AnHa;>OxqXIRhJ)KvG-(1D?Ep$_H$ClD8*-zdou)=h@R)r{J ze9~HRcqoq3?&xTXO4?kjqdnAm?+t5QR>4%I>1>60_P1}%;`{p!(2XA?90?su0&e)C z1@1x=+7!6-k2J;`3rqYZHO=CDGDVXG1#*1gm1J!bohF^3ML|!5`tGo?vJ(nf{U~`p zj4}I>hI6PgXPxbo?Q{ZXvXrR+l=3P5 zL{W4NBE2KTr#al?>S82UMY>L>_48(@ai&$as+zYgjMt0><;%dYwMrk1{2-RrB3!8D zHjyS#uo> zy*=v#vDQ5iuj;=YSLesk1%G!IsG=l_bguH$UpehBw!su$&uG;c3wrKsY%tJ9Xg8e1 z$kSY&?Hd4}#(45XN?JNc+5O^_g`KnB7NHrI@>5eWFB(y98WDLd9BO{|$xrvp5!J)2Z>e|irUYVvXSJ@mR z@eZAKyi0*4X=-Y^+E2VWUW8n;aVwe=IIum;DXZz0^2tPAF3HIn6<1!{Pde6EO3>n> zg3rW$;M?w8QVMr|e=efa=%&Je3DeTObJ$NM_lkjq>*9zgwi{QekX5#&d(c`p4y>4~ znAk&E9OcTIEQE6X@ko@(FZg3Asi7fO{WSePvsiQ=;cb`aEd{UTWRlU7LmyV;t9O zZ*>g~=+4Zasi^?Wp?YVwNtz~DwK@Z?60?k~%r&}5pKU-{U_`SyT*0G;(a6AZw4p9L zN@csg-i_0*&j@*%_d&s?q#0AUrT`Wl%WC+|B5^7jw>(`|7&T0mkCqW9_NNDLrlu89 z_kF*a-O2Qe(4r=C?|L`HhK{@h54`IStgH)vAL9WMDf%s|ZJ9+Q$m3!tKV7se&%#dj zFvn>#hkoDcZ$pv*f5#631u7XaD^m9g&&V`161TSF|>Y+N4&g(I3Q1fS6C zGZJTz@wJG7NK$(Y-{!~2$w*%+(u~h++8msN0fc(n>>>vjyEc#rHyd| zafq;~uXj%!g;5de^71`1GZURK^;g9uC0Ws!!f0sdeSN)WZjJIZN1q+bN=lxhH8S(X zB*#anO?Q`y_&Gd({w#+wGn0H0vtD}j0~H+3%LgP`T&=IrR1n8nAd0eda_h4GQm7xYykEUzWkpgj zQ(CG+??z-SF?=mkrj(aq3eyCq#9LXaD5=jimK=+=HBXGzXfku1ejDoBDB9Ypj?q8d zyzUqq8~Zi*HhsERAULF|NG%5QU~M1Ur;4OaU}r9Ux>(O^n2j|tF%gw4WQhb@cyS54 z)?u65V(stNmW88Z)yxe4B&LwiZVTxulS~44p@H8?Z0x{%y|ai?dF^GgfahFu4nj@W zxdc@jag>^FV89d-|1;En3Ca9+YrLvTT6&dA_9Yf}#A7mxrx%+=^%RP1z z&>jxaVSWCmAswp~=JW0CQ<`+N+4D42qh61~Aazh%M?2T+MW1vL10t>XKs;upDtUG; zuFb*JS@8K2uMBGuLo%||Cw9@mLkSKL+f$VsEG)J*4!BVPEyyPF4NaNUZW+Spt`n7- z;98^cNHN4(28amUZWEZmop6Yy6StV@n8ph6SY~(+Mz%*bU{6#i8J}7KjLWNbof6QV zm!HpJKK{LJ@er@yXXaVz^(QBxGvDJ)Y+;?o!M}CyY;AK>B=$Di^6KmPeX8{Jm%e%J z5bfPesLl#^<@O6>S6;iyaU1K{^-$?gWW z0tx)Nd;!dw+WHB*h$%wKKThqzDKftd*c#ZHQe?Ino4!DPqm_PFbmwQtI?XXR zQod3`u+5>4CHKj=sLZtXn=GBR_ia_U1vA(c#Juk(mUo={62Un3(f5lO zy=U-_pAR)9WnHy-^ywI$$UGx= zIQfgHbvopjH2E{#aU-hbb|Kbz4;qM2p-oBgR^5D09Z`zMpEEvH9Ssjfn^NNW{-1{V zRvW<*S;^hOZlzREs~iPLLJQ=p%r{^fO}i&u{R z)Ma=$bJqag8e;D@vHW?qsrMn!MXZ!ekSrrAeKM_RtM3Q*i0N5Q!$zwRb3h+WedFBh z)Xb~DHggM`8Uu4tBNgmF2WK7Z$9a`=S^bYJ2IJ9B=M7h5rp8wWw#-kVuABMV3u^JC z*uudxPVUR?n<ANJW zu>>~8$9a5N@ddReITYk2_DiG$*MDj%WSx~Gr?{fxrtTB>4*il!SzC+gR{M+0Rpk3z zCmM`HI+KF)qJWzze;F9>&}V^8=_Do<;!69nr|6K@CC<*wnH8F1G}nlEKN*4qXh zv0W#g3%5H@IoRaF=O!XC7v($Nytx*U5!0g=FDvZF^$unFurh*vF0(92G4y>|ZondO z*HE|PTM?7+`IBjO76@C8LsK}45AI_F9Ec)>u%4Ete*ZO6Q2CR7R)OAwTsQ#&-WDC!cM96j<={ z25a6!fjo@z((|?8lukG4Sx#zZ3d4#H7?H`7fBrM(mZ6ykh|v0$SD;=TYM@lepXYcNU*}G)_`rq3H1NR$B z3fgXr8g;Th?yFc5Nuzf=sd;zI0-^Xn>6vZY)iaUf-z+0u+AF4IapOjh#btF#NULvw zEED#Iit2-&o{bF%c?_0jH{NlU;8gKqLSq<$KQ-Pm^rfekB*Jz3_k|iyGq~H>535hD ztC(QJcbbil+z z&arWt9uI!xaIhbuxxMf;xii>Kzr7OKQ z{Vc7GvK^6BRG@&hy$T0so8@$^Fkd!#|sn1LkLq37>S7Dx2HM9y=SGR zzSlV~?GmtM6{|A{cTRESu?}H8-qDipoOl1*Sa~}h z%9^QAx98~&G1;fS4IIv~0Je*Qf`aQ>2feKch04lGO3G#<+4y`&nb~x7kauY4MXGn` zLyQfDoT;h{`=+6j?dj*jnpFp{ygwNl8Vb6qmblzJ%onR-4GWvt0HT$*qnLqh3{j^` z8JSl!$mN`QCB7qH1zJZC(QLlP@T2F? zM9H6%Wyr>>YBP<`&IU;dW1LQxTZy99kP@asm2ak>V!%L7^IX{xQc_(HdEeAg}mYc%9emO`pC z;txwn)AczK*{7xUii4hld_l-vi&JE^HWOLv(9kzbTcig*cmkpjK#5=qGeH`|!{f$4 zDqU%*sYLJX`wwv|ujIy{*MwA$JLb%*y@$XuZ0K@1EjjX95z16P%2BU#*xeqkQcL&6 zSjIClHlCiGBo=VdpFTSQQs;X>>I5pPWeuF#Fekxq%llYX59D6J0l~j!TqB0 z+duO!tnXcV;s4~Uo?LI8jUHY4wa9Cd7`54V>pWrV6g1E42YUJ#Ur-tm;oEUEGNq-Z zyS?-OIJ!|`>~W#up(j6h#bO}#l=yrU5g4PWG^|-}Qh9N)%}-)IjN5c^+M;h@5Eu|3 zBO~K-vUR<4b&-(~8G50ol>LF^O$syFvWJQK1Cd+OnmC|y*>!~3J$8WkMZf`i*~I(z z-`lYz-ilZ}UVx)llS(OvjI|%{_B-&UOsVbbS!UgzBGte zuz}9Ui91&HB7VujgYtsr74u>B0>Ndq8sWfb>c1UWDb{rM)UvlXfqng7(EVaJ>>lLd z?H8g%ZSL#re6LSXXv`J2Fg=-I{AV2ew&zYYuWfrLZe0EUSUh-`?soeX_m+y1l7b!l z+c$4xswXF{bNT+c$spzbmvOxQ!2n+xf76iO4EA>ZKc4e{@o@h!oqrwXbH#_QB^$&; zH>Q{tqourC_xsW>L4jqX5i3vg!oPJ2o&u+t@9dP{qd&ht)NrAP^dTRj`LkrIM z9ykFZfei-614>HG<&-{}`H4l81e+c`1u+#^1e4gdZ7+$}aoh_CrlfvvIy4w#1zo}C!+Hxcr746d`_ZcnOYTf+#OD~+lPbcQ?n;4JY;5K#E&b$a^5gK|Qu@xODDuf+@OO^? z3Kdi$6k<3H3=azbJsE?DbE3|%sR?;ZN0Uc2Xqo3B z))6{TwWwd>)Pvi!)Zs}Y;IfQ3#N&C{e*mB7cR4ycfYYns-h*X8xXyl@5W;lM-IzSr z27iD3TE)huboZY1Gh$x#>jH2@i;9YV`joLXRbkMDr5ofis&W=Ui!^pPLD!uZ^?SN4 zCnpyi!I^(gX`gB*+_BmU&#n6OkqNHm|WDwc9nE#nJ!3y>5U5u(KgV` zCW;qwl&FV>Be1YZkw?St*x1=#V^j~|OYkx55K8R8*nMgvJ?TFDDy3Tk{&U5N<^It|?k z0 z0XI3X#7*`(VCQXT&50Rc9x%4tgzo!j4MdHgs; ztw^UU!w1eXufaa+8TIGrbDUQJ$yvQP0?>!*fX9kx7%kFGel7mAtwU)40`xUE7pTrK)*Cw%cs7X<>=o zxrSh3zVOIMe(Skr*VAtRe6W1}`vnluA*4dFm{`QbY#x`(fL-#*H_1&YOaZ>^;NYOy z=r$V7=#i8(dwE$&BVPTY2Z6N%{XyuK98$;o;2bPM24#^bRc1j7af2F3$Py7Kaa zWyT@8o>!DSJkG!GCdYGGTh2GCICu}0pLc^9hsVeF=$Ety6E`?)o0;i#1QYis3r#n= z*Liz;UtV5zMNnT~Uq}i|XlP6xZ*sw)09+_4DdhxJn~w(rkSQ)E1}d8UKUu(_r0lu* z-SU3G5O&$RCMFY?h`{^b!q8Y|F>nQL%$Kb@@|8XB^#qv0CBw|Iry8uOBb4?QoSD4IO>HW>{`keG1r~2TRrVl}2(RuI zqZ*8zWs-l_0?bN6U@Bugu`joAEM}@}-g%t^kJPEP-!IT|+?ju7pva3&C$v<~eU~kx zaHqHKrR;oR=W0q;hdR~qP%S+x!3L~1p>eFtsPB7_d%4-DnjcWW6}TFg@xbGpUGMtRN7?_u1E1(=f z0}_T%`PAa~Jkv`Za`*4we=pvFB@DQ}`uc|sJ2Nb-tO1y|SLZ>p@s)~sj2erF0L_&e z=4B|Fe*7!o>UseJ8mWMbOyEXdz`{7G#{{2#>z;_;NeBU(*=%hPNH}_;8R4I~H^(e5 z5v#gjW3&aX0s?vltZ-=Ppy{xYg6>YXY)E z3K2|P=lhg+ajC3W{Mo?fxb;j8_FL@uC%29S0u+0G?a4UeX&Z&JC|V|&uC6otzq`*s za?|BXJY4x56H|Yg@j$gLJl9s@UmPe=_dlEl7)+<`WIT=|ue-aO)B3u|(R5bYP}!s1 z)aJgCdO~e2+paCUd;9{+%*2GcV9Vm7{`s*Ai9zg$^k!k_Cs;bG-MP_|Cr@U|j7iBd zoDYo-R|mFd>pVYv*lBS6E#mhSRCUpxKBcV!+`%qG+)fkPnrn5fSAoY(Tmn0s&-El> zYhZt=BbZbu+GTrV>L5g@Uz!4I{#sS%EL{Ov;H2WUU0xf?3L@AQdM5%>^mN$Wp>^%kFUc4}YG^oE-@y%J z#Et2S9*)cl6~e~yS9T@?1*kHPq?e*f51T5#{$-&-Z@~SZ9l|DrQ7O-W~iRjNxUI7H}qU zaQ3G$=Y6X%n7EeuV^%N1B3C%Ad^7^RT?ARgH}03g%Cf8SRdG*4$a>2Z$GO>g-tbtk zKC9^F&34ce^1K#45o#GCXfHZ2$!UB3^Dz`UUYI<1WEuCw?}>s9hxCJxlG^QnUO!%` z8dGE!WlM%n>@EY&j3@mP_hk*NR>32LWVXt3dM3kvX>)v%g@xtLov+~7?#l7WHm)i$ z5C?a-jJH zA93X7%P1;_U*3m<&80@66*MIe;Tq%>2@2QJ)Xdkexjb5*=Ywuk8}_u*Dj9WBE8`LM z6=sOSdLss_nhG6g5-GAJGuy(v8v|Lbaeax^8&kkOq@bYGd&E2L*G?Ggpa~1Y(aOzAhwh4xXDJ-?i0!cxZ2Upns-e)9YRRoVIVYb|KGgZV7 zs<`;u%kM)XZ;9isl_!!&*BuUD9?|cPl#5A9;60uWCZ2{>xg2jS{QaAv(0{t?7xa>c zpN9tyk_k5B4{ESLsYg#Qoz4N5{B#?6VT}}Ycacrt_ShJa!watWNSw`<+nt!Gj_1PJ zb(dCp5S5`=YCWHX$E8cS@#~Ds7;3GHgs=HS2=$ARGv!a*&Qjlu_*NUZwKH^ zUh>$kZHx+m=-6nVvpZR~%6?@ik_d2)15nghPIHckJO(ELDp{T5u7KxtZvf8x$?m)y zT~xW{bXH1AitDLdOeeqRb-hBmnBP<4y~Amn&9TIpnR?9%vqWwic1p?uN$d)(P(4FK z4+Q)yL}+Ke$up!08FYPhF*QA2AW_*n9hyaC5|f(Mbpu%S!wR&js!WH^dt;S=nxqE0 z`Jtxd&z6*?cQ^lI*^AB3enXPBamt!6CD-a`4vq9CzQ(hgmOAe4fI>D<{tULY#l}JV z+&G9yMDUG7hq*UwQ-my7XIS7Wva1=7`Ck1U+nIH}KHntuy|bASlY8-F_z0Cj4swIB z=ivA!ma-Sc4jmJ=o0*W?nAoXJ?aCB^Yt&d~QibeR-usKx$O~}YV$iCz-`7vZrvC7h zn3wg_xqU8l+_=u-9`{o+dBwB4$Ut5Y?ppxaVe*GQE8773+>$l~utxb&PsWE7;H8>w~QnAT`tdz}sOxxIa6?g(rU_k(0 z2FXEA6)w{e3kQdQ%TaMq5U9Jrlob_OJuWjA7Z)2`9gB34koT=EJa+X_YVn+wW7Ac; zCtHZQY1g?zh!89g2_^-AfcaE;*Fwu9;npOTOOvCR6gG0-)c>g((4 zDfD!RZDUgw zmQ_Rf=1nL@i{1P*U^vv=>xjF3q8od=$MYWsX_`c)RA!2vY^wE+ld^Yip6w?yLuyMu zewC}C2YK&Tu*07auzo{`QcsnX=E_1?g;6IP-8X^iDUK6bID&QO2+M)eJu2}^f4oqj z&LOAzT>)<1ospNiAF>Mymkwiwn^k%Xtr}+P-GaiEcWT^3L|Oz~8$AJR55UR8nIq(} zJ?ji>!NFzHs-)xM0y))e>H2uo&2bhM_8j0*V(!mhigwc;W49wN=3(`VC`ULBlqg@~ z9(NeSj+_zLR6p`m@<0Y>ms7{krQ{5i*V9W485jbg<#>Bq{NxoyBA?A-hQEIoKv#H7 zNBw<$=^mrG8Ko#k`2Y7f1bj_MkM7^8HZe@I9CYNYto(2G=Bnlk2WfC_sUn?R8#afT zS%`|D)yIpI0+8>K*WB?C&b7} zN=sW0ro791cuVM|bkI5KcxY(Ee*OCO;2?^AoZWg(+fqiGDMW4b8T^*W8YGHxWkUAF zR{`M6Aq(G_Vx`}N!r$NOeN&Khj*+Bzr=4x0W$|UD>I`b*(Ew=EG1A=36GcF+CjIiu zm$wE6OMqf#kz=)4bRr3H+#HKJQ2$4xoCxN44ewE#ZyEqn8L!MtpJpup<-rdcx}}8$ zr-gcabhO9Sd7l)iXHQwjtv3f({Sr2H%6y+V7WK7$o_P#_u$7e*GtpnERek}@BaRmk2B?dyS&KTNt z4kGVcy}E;QwL$plF~T6`vHjkr@9fG9PAL5(FOp}QO_&^?(Z>KKWgaz^G)2sXulQa2 z+6frh)fWj);f|z+J$=z~P8qya1Wpr(I2T^(RD}p8#$E(l3yO@H%Y0$MIG! zjEq#C+yn~Xt=YQyd8A8)*nO1bh+KZFPSPS^Aetx-rsPt~>5~!z>Zu8JKJ&DQHpf>x z!$d%aU*F&k>>2!$XQay7dZS>W#cz9N9gmQZ)AO4AYs#ndqZ~AZv5_E=kOwOn1uw7B zN%;*5;ed8DsfF7Vf$Y&tr+v}n2ij-XkeEbAz0x(-iB zsQ>tN0K^cGf$_SY$iU-Bgs#iV%7zc%d6gFXheum0t5umZQ-9)F3|uTMwzeh@(b3Rc zPIsVN6JZ${%g%?mb9H#-fH(Sj@6nr(Ak5~a{U2~3SDw^5+HU$U+?k3|Art?0B}0T% z&dj$b@tTlwG|Laz?ctP-@tg(7RZ=8ibu+WFNVtouAa#{=BKnGWaU^*W{jZy}A-;~g z4}rK2^ea3U22_JE>iSS7TU3VmIFp{U0W)9PaZZ?Z8O}~X1E}{3s8n)HqX)`38T&>1{qk1T0jhfnkM2B zRO*1FJx34iIIUV8ljQnWkNK~K79nh5K*`KBx>o_Mx1froq`xqol#|n0dyp0^WEBSc z4&ZWoYb(?LNYSKbJ$)Kfq+R2%zi22dAt3>iR~Kf?&LM69amixpXgz#QG_XEdQ1+*g zC)wyzz#EMSgyFY~q5d03W%!Q$d4C*-fZ(-%(XL{ywxc5#5TL_fj`(EHMjI;9MghdC zlozO)+GpC#?Q!}3?c29NB0xq#N!iw8{t<|R0WOLJ>l6dXP}pV(?>fIsB~)L35hQCs zE4P2zG*_TCJ~+6?1mUf)SO=8` zK1r9AkjT)fcZm{31*s*OxBb?{SIqF}Xc{e93M|@ef&W67YOe=f`3Tzv_Ds?_qHxC3O5tO|hVOdgL6eZ+>*Q0Kj{VGS#vuj9*F zjo9LvjsV`Yn69kxM0%*IR-mJ!$0l54NKh!Mpi=_b?d7GzQ1&c{a6-tl)9vYO>6i~7 zTzOn0VgOwnrN!r~&Jf*Q%l0FPkcHoAPyWRCbSLQ`HZL%=O+T>BuoSLeqMp82K3&>N zpQ&I27t8xX^+c$N5kN3L&+En6y04F(zC1WZN(ag;=iE9CTGyx{J|-qNpfmt`JyN90 zOheP!*H@ucRS4?N%({)?O}5H#!L(4amMdkk*~WB*%P3jHwhEX<%}r=Nq`C`svBYR! zPZ1M{qqX+y@7>jHxf4LJ=vqMm&RStUu3BjXf=uiuP`SZ-`En7?0XE>@CZtY`EUjw0 zQH;?0?*RcuCF;*e_%p6)lj|wG@d*e{Hpi)jggoaP?-_J_;D>H-O^W)lSI{#t6>5zK zyUYSvcO3rWQnLNppw7waR7GA6+rErGJ)W#LfP2Jzl`$CaVCXnM{lHclg()6rg=TS6 z{h>>FY%l_Q4q&9iWNB2>l>-$sb21f$FH~XN06OISVxy3$1(XhYy1P{iwWAXf!V?qS zcXi~!R&zbsV)7(U)_YDDuLk+We(_W^P&S{KF)p%^Ift*_L=!!ZDz}uG2S`;2jyxkh z_x_TBS`x^F02G?3+I>WwjVpA0hJ8H7-1HLU9J3((j2Zt0fXC$YxZ%&$(LB$St;uu% z1V9?0RMii5u>1KD$WoczRYBf8k)zZBN+iwzR@G0eE_WG#H)`tQbd-Rc;_UkK^m=rso>!fNK;RbZ|a;^eCu#*s6!MD6$zhgEm;QygM%keQ7i=ztP<_ z1fRvN+1m#2hLQ81(a2be0=sXIi4dw5P-qbo?{IQ)0H+k0x3{k^C}h;uR@S@5{3(Yb z9F!*OH|}OlI+oA1PW?%fG(E@aTM!-A9AsB``>Wu$SJO~sJFs8hjr|T+wn0f05YnJ; zU~OfE*Ril_>tG&~P4hL6A>)M*kMngDzZM>+&~@tb8$XiD1c)A->lSE z!pX^bv;ZJek?AlMm~8@&-BgIsb-7U=uMbp8N~%31Sy@}V9%Svun@xe=ow$vI9cwDI zO7vCi?YA~Y=|O@E&LpC~twXJMv1!5cA z?ht(V@ZrPk4~b*!{EU?hhf=f_@8TAJzR#Os*l7OwU$fv&Yi@Wb{+Os?$f)G10_=q> z_AKDjQB&{IFfoDP%tTKg(PR?!7}}S|^fAs|eDfb~%VPfXQir|Zfh;x-9{Td?$Q_`I z^%+f3Ko7zisVwjGxXK+`MDXt+E~~3M^VE>qHMX+Qjsxeio1Cys;y>qvJ1Iw)E(}0d zYupDCKkZEC`lBP{hE^gG_vc@Kf9uA^$0N`4HtYs|4FaeNND=3?K{>F#rhn29-jA0G zAxf^9v?dq=xHsZZZ#zVsUfCo-sQzsr!ki`vUb#a34xN{5Yd{qyghZg!8}(3Ht*CFq_EHs+*JsMVIhj+BpBUuK&j=k~vIb z!om%-MuHz0ngyp8d6-Fp)bM@%lAk?r)3}>9Ruq)-U-11_{gPYN0lKs_M$nJ{&?CHI zWyeL6=8(EGQVv~e3uZ_ARAia9F0wX$y^}l;gGDPk z{PK3_x5S+BeQa;uuy8DWuKtFVip%r4f?i#oeA%WZD{DoY2@h}ScCfY3_vFt5+bD%o z)ftAE9x;j|yfMSn^V_U%yH`=P2%TOgoYx2 z)!j`2Gy{O2c!5U$6DQajY`_M(>(neD6PcO$fvM^*Ps2nPE|ocDco!u_1hi=Mz`+oR zhYv~7(aQTDbpX4=9B`2GOCzPlW9&gSHL=QcIFYCnSpK-8cTtqF{NDpK z1|yw%rBy>pi^EEb#Kgqq{q>7HBbQirP*N0a^7w8<%md#SL>#wg=&<|^^p~9Ytu|Fj z_^PZ+%8R*Ow#GM}0yBL1t1XD~g%g)CK^#@%pzpv2J1h95fX#69ws$D#4N!5^2G5v$ z^E@dm=JfQ!-`!_xN0PHW`KD9tLW&-SDAycRm!w-kkZk*sGA4V87@Olif8zJdZH$&( zjP`g^w2J%q%s@&k{J-9j<3apsc?9Mv_mZ#I$dgz9!g4k=39D&qx($d29o5WAWg&cg z!kwplm08)XO1i?RWM1{>$cynz?=#phzU$$v58-f0%2;kai=&o{M!`9DN=+>d_a=i) zuVPn!xwPGxOV^bnTC~`sozWI#GJS{vsl|My|G=tR@mM0S1GnSJ2(a7h*WaqFbgYfc zx}D-d{6hW$Dyp{zpq&DhEKiNG4th0!0h&KICIqRePu)KRAH<0~d^S~SC<2&VeSHHA z42*0U!X~>5)r|tCL_QJ$+rhpHLyMW$xPcsw{kAr9*q$(i2r_f%~rE>^nssdksX4Y*RaOr)-7-F(*Rt{tf2{`~V=wcsp^ zyuxa}ux~v=e7e#!>SyF7NJRIS#7HmT*=QDkQDY{&>1ij_sxWew-*Xq1h@c8qqLSlw zKZxnU8yL$m$g4N9vYLD5`cx*C)Ah1-eUsCI%=>74Hp0dHPqVWHrz`Sza=ToDpv5xx z*SBvv&A+%!y}Nh8{8K19CFSGEj1Oq0D~0RnJ@>}sQd8rMpFL?(@Gu9e8gX$y5VmL1 zJG?`k?TnubgJOWwP-N_U!L$TwESu}Yz_xJsVauG?64i$~C*pM($$!@ZtkcQ*7cxRn z$pT}TlGzPc_QICrE?sK#^JN4(hno@;`JrRoqnoH?fL>ns%&j>-HEw*zuVu2*eZ2Dz z_Qg~Dmn8cskm<2RKImW@?md*V#lIUs7#7qp&=t#RP?(-BlB^>DpweWj2;VcuaYmx? zzN9L7y*{AD9Kmoll1Ua0>NLbw*4C<|@V@`Z4@ZZoj?RQ@JR(G-KnoBI`rNeUe73f)9Os2Sh|Dgg$cI*OjOf&yQjAl|kM6 zcHGEAQ^hdNCeY3}wp~~1sZmrmS^%_n?4OxP-*!B4;s>l8Qw)8bDi)mtc^K&R!b&YJ zFE6h(K(xBf<+~hj4G#@tV}+xVSpfO@!M?%QgSgk?Pbj?^bZab8zc^Le78ZN$8%p#{Z*#_L_!h>KflXm>z58YM@LHUgtO!FgSZ-J#LCKw-?MFLX*oxe8K9vfeMZbAJy=Oa4R^RA5R=S;6N0`W6$L$k@mT zv^~n+{<{HrU4B$DzmUtJ#@SR+^cASdd|{LyAKCJ1ddGTQrmcU2>+xqhOT@974#?`e(wOQRbeCWhWJR$Jbs$z<1Wd+t4qLc4)(-fGnPdhT-L3 z4&?|&@b5U1l~-%Ttk@j$wvB`Mn--_W*gkKX)NG`@TfI(hwk>1RnMqtV8YtmhsIdH4 zTFP7(3vH7gDKl0PmNn4VC#Q{gfrA(Lp8Ux^RO06FPXn?5e`Pa$i{4kFnS9XmUalTh zYN|}oE@t&7ZF1PVRV8nnRLDlkxzG8~JuS`PM^KP?Y`kZke$b`XyWgj)y}C!sro!~k z9CO~E_v}Y#Rk;_1^3(Fb_ri2h$>O;RqnZjnz0=ckyHpaE098r}3Q&lUmXf0K_7>+4 zJ#P5&E5%1pT4{J_y2?BvBBIQBGgvn`nW4%Ijk5nI4{`<(n3YAP>&fxGV~JSE#%zBG ziBQq&sU&uQT4XA&XZDO&BDQlS3Pf7Sv-%S7h_R>Zok7TndoYJi!e=Gra1w880Ll-B z|I~Ya^%n+r*R`4cwx4^L-hZ_m6N!Vn%tR#H+SX2g@i`y4%Gs64!t6s$XMZR9pp5P@ zna8xF!a%N!oIkbq5%C*t0RfS^OZudHZevftrcHXMpr8PKl(q^K>!8_Plw6_49GtzT zyl+Pq_P3?Kc#)vIjry}FQouU^KP2Q(Qg2>4~`X^=*KcE7V@x4TSVt(sZjd4CLN#=z!bi zOydb>VgSQfS|a8t zb%#~>Jx`$G7G*BF$rhZWgu1yn^D0b06yCN~Evw)%SrL13Mp?x)Sk$>W`(qa_lPJ47 z@zXYb^2K(w>_JcRjP5pAh{0!!8`7t}WKSIksg~kwh-CC)U5on?P|3o-y1-Q9VV?&curKBqk_3v+98xhtBSu5Q(t zOJx0z*uQSZr|;a3*PaYU?#RpOBkVWwJdMR2U#?GR^99r#kzW{$qU=m1P(8GQxSN;L zBZ1>GVKX`v8^YtS?cL1rI(p=LNCxj?`RU}Zmc~7^j57GIt;sr05`SH2R~#>eW@pi( z^KsjT*2u|8k_*`vc9daKIc#2^sYsr^RdZ_Qf+C+P+WMz4;}^Ew!30t*|BUuz&s6kH ziFfjc=Ef8pOD=(xp1kSMCF@rQE;mXAgBp{{op2<#lc?r>m@97*z?d_+h3KM=`SRTf zy_uzH4V9jLQ(*}6qs5xTT*08C@_egui!Ei#&kmpQ$*>dZl<%GHXsMw(QWQ&DRrxqTaFNcBeJ9~KCA|sInsM(Yxjco?E$j*b(^+0KKFG>EcI$}M zya2d9U3s2fw=+7gg9=XdX z{0sniE>z`km-ODi3*L$#l|c+8>veB}Ytc3@*?%mI;pi2$o5**wUrxPuFb-_(?mBN> zqv!SGid7hhL$i+PLolVUk6thc7sZRGfFj3E=&7TCIunt!IE<5Ndn&K>9}e(NkVs8p z9~Y6-^ZjK}yic2qz(?c42x{fv=Kg*b%o5M1zdGr#6_)#l6?ESLYoq1my!fIdtPv;E zs|%ptL}?cavIyyq9!^d`HB_#M_310>P*$BvYU+)uw1-JkO(8q#e8i>4^ zS{L0+bk$~tI{nDV{PH+H{|5rtf3BS@Xj=(W86=F^csKP#{18{NiH)`O3}=z%^0;mO zioW#2F`Bw35oQ~ehpz`s@*@i~$rmmJ`SvN9Zp~}^@k0-1DJ5C22Rl%1KgN?WtZ^qN zB~Gr^q7G$Xn=04q9_xR=rd?~##xM_1Ay4c08Hyx&51Ar>8!bESC-qpq{i47DsVA%L zM)tF}o0>*Xo>T*$_$~9ZwDPqPjl-Gbz>!*2zr7?>rYOtk3`a61_rd zWw;gUgcQk>RhAI_c>Sxph8{1k`#(qdH1l*?v?UqnQC$r^W7fLO_mm#WW(o5E>+ z%D99{<=&w$e0a^Sy=CZv!rJusitHdbCWtrFf|plbBP7-iufenR7(X&H3d^g3UOt{Q zkLpvWLyUahoRj44`X}Bh{c#`0v%7*OKM@{e zn&;gPo?qz#VpVw<>6Zyb`QNQ>^YT^L>qOfjtCQx zNBmY6L(iDoCs2_9LVmg^*HdPXOf8=*CsZG6aAWV}9BYS{O)jAvz2 zf!wKU9~zwbv@!p121?bZ7@IPQdpjm8-OEA zcx&eh+AnQEm-COep~BEI0c@5!5Hj~wkDm-0O=2Rlh-%!R_hSG_6r``Rc0pvy!wk%IqU0ONDl$G)s%qf(hdU; zq_z96U-?*Gu8oyk!oqQ#YB|Sy`xPC{`QDyAk!3$qO$TM~yOW7n?FYtI)g7T+j0W44 zjl)Lr{oL|GeTlkHa^>gk6n3+5sv2oVo5m)^YYXyaOg0ZbbNWcw z5>?>urfW7UUW1dcK|eC9%X@Zu{62}{?~R^@`n$&50i*3Igg-;G(q<8rO*Tn!Ldf?Q z1%82r9N7=wEpjU|5S>*s2-y0iCT`iFOFBKH3`o#jTCH)IidiAlCG_3j!1Il0HRyQ$ z>EhQFle<HnC)XdiykGs=o_Rpu7HL8-=2vW+ILS?mB5@{??|pC> z|Mf(^Ex?iKDD=u4AhkcES*hu7R=j#?g;_AepG6R&ZfwRP;+t>H;HQylHwt*{Sjo?6u8!~dnrd_*kiF23(}j}_p>(8pumjxtW#&KE^u zEeIc6CDJ1ync6vOZMw(nuSI{-jzEAFy({aadc5*?T-cJd5gKOycfN zVy3^xNdw10dvBHzlzfU+PR33bp{n(Kt5&J8$YJyB6jeHH=p!*J?Wc~b^;9`<#7>$% zE0r*EBAU+g$E%c7LZ`4)x*a3YZjcYbkHWQ?AU=8`arBbRqoTzPVZFx}v0q%PC?iyb zBf_eeYP$Q3NY;$5rg9gwJiGe-hIjKD*=YWiT`v(zq0bf0N9!KK@oWS)BV|q7clMUL z^VZNVxZjNR4W zJX83r?0m9tB^^D!r^1eQeh}RsN~8FUhCZLUIBYvsR|aM$kq)N2mf5IVOiiO-s8{O^ z_5uhILbd7%R7nN#{^7~oCN$hk*Y(t@(n-g^g0`HWd_$DhM zW%h^gimD}UW@g1?8;^c_N}QcD#rP>b3^=~%5Z%>B2JEChjo+kF`RBb>=ic&W$zS9y z9Urdk>1~0CsJG7Uh>2=`U?ICK$%^u9uJ5znGdzm(lDy;-AI9L8i}Tik1*B_h^_L)z zV(T-!E9bSXf$>>ueXIUML&cT2SI}0&g(*?&?6bfjrimUWd^j^?A0M}M7G|wY8(ye5 z4bF)Xq*fL_%kpgFF4QG1nqN>VM3pBk=a=x9M)k0z1$mFoGl=Y4z|6cK>PRra#6=5+>5PpyvD{KB5gWbVSUp*cr?W1R|&!=Lm%-ywg zU#YZJ&+j;zd;DQNpV`QWP9&uNqH44@df12!zFAGJI=l{rx=1|LwV~qn@p0276wA(` z71Fn&0kebTBx`<5t1-nOk2^siq)ySh9NkHoxPUE3nf} z1Anr^doPbzyS?=F>9vq(=fhR&&HaKVA8RVLo&d5PYp;*g-?qoTHNyV_JE>x&t*kB^ zD=Qs%VKB0PAd>6kcwukg*f#d~K}otf@@A-}gPom(bRfNFSG<4#3*SsrQ_60po16Z% z_ui00H5h2Jo@)tIk|pD@ADWk(3kl{}Q_v3Fr7VSsDkymJbdaFk#kRhwv5|#) z)5@!9L%=@%c<l?%a zdo365=k|QVK{ie9@)C=kg8^c=4@*mODMwCO4CsH53zszl~64=&T$ z@{U9bK6~n)$iw~i2OMeg*5>1)%0Y{+>~q;{cXX;94bP3Sl?UT;&a72iaW`&KX@9(0 zYNZ|@^PPWdkgcH+k*85nZ5(9Pp+rYVhjF-3SX@jdWUmCZI9~i*p~C~}qHQ7VgzQCy zwXgPv2aS7c)l1<5Wu+^_ViQUa*4JZvgN<*Cg6vMjdiTq~fM1Ynrj7+#-_KK+8m z-30Bn|J2<$hLWRW$K_w}&KZ1A8JL$hI@Ye=&Qqk5JslDy=jN8t(t}K|rt7}fpNY8j z?rN?21AcKgkzcwg#Sn_!{_H7QAVwX0<`#=JgDgxEa|)7weBPv9mQ$t zdfZh(m@Y>sO8p?YiQwj76Ht&)aO+XBe?Ka%vEjizu0l!BDr!kYWd#=gFi(&71Ed~1}cCO>?-sB(XBI#Yw;`88yySVySJ#l^v|%GLoACUA3? zJW9VtMnGErjg=K@RPK?G>)W!Ln(2i42f&6iZC#f&EU`nyS8eLR$)DH8QVK~8h2$C1 zXIN@KBtc^O|Kq8Jhi!bGk>Ff#dsyc}SyGbEt$S?-gN3HO zGuLdFx}fw&UBymDqV1qu0Fh7S=wwghDOi3#*FuF!)mHzS>>vj2ne(r{e3qyx)atN) z!=HW^2L$D#<+=3UxNM`&UN)1#7Q3AWblE6QtLctUm-SF$n5M%Wb7Q`B>4&F3>}z%r zMs!Ll`x`tfD`pNK(+V~CvrR`$rA7-0i^cN958hmRf5UVy%u);20aA{hU*R?n2UDHb znS^-Ie6fglO2p~*IBaO0^voz=c_#LA9mbKJ3(g4E*5gbS>QJdweyof;6hcz#Lj`VL zO_GGIhDJt@9Sj*wxYp1WiBx}!ezmEr4wGHuv8s5xVpH~uRd^<@$Vg_oXzQ0lw)<=M z2hB}Qx!PrlE*YTNU07J)logSabBc+vS?sF#%De;)8nw2gI>Q#ohq~;rKkB#2YuDk| zTAt()wz?~O*AM(?QBkzsLDZ2)a=%kLw5nHoCOV8#vs7n^9WGwuOf?_BsX;xOba~UC zmAKD^c{Yhf(Lu19xAy8Bg&2H$KL@0U=}(i}UwfP~viwBD%5t-1!@Fhe#i?VY*0 zfk&VFxnXN>-TEI?bp8w6iwM#MWQ9wRLWs?#tU^NFgu49iQMQX8_KjN}WU8`YqmcF0 zay#Kl-B}5dd~i!o)?*j@)dbfH$KM}^is@ej#=T}`w>WaD%Fpk0K(hM#1~4;Z@Onuq ztFxRu)zz^77}j=^>Y>ybamBqaIB56KqSAVGGhWQOwA2}f^xoPpi{bv2LXP$xt0zVX zStg$kFnz4(@Yl4tN#Hc(kdO2Z%EDQF_BW@K#cfATITlTj zVuSlZ=Cv+ItHUUA7a6gnIH5{LjM&N2t(gk9&cy(8`FK8LQpH+~yb%STbM>z03G$q` za(%5iFfagShuvEfPRov_oSb!7zf{{$i?Yt6tREb1pg7ps?+KZe?~bFxp(aY^mS8Ia zBEjZu_nFC0Zs&u?-4K>d{;=w9eTG4Kj6@L9$&S{_^bLRQtJu33 zgmP}Uw-(bK`kSX03@;+1xIv@>%W+6(*x3)~h470rc^QkTsiSyi|E~W{=yTE$kauP} zcZ{P6S;h*zX*^q_-p^IGh^CC4(B7)v2tFQW0hjJWq+C3|>0DZFhw&Z2Oy})Wy&9z! zwbo}r?zORX8kMsh!{%A&!4&j}bEio_Dv&1ANtA=-Y#qbR^m6*@ZpUBvc{arJsbg zetY8&!)j_`6&RYx+<@dHkM1VH@acod$IT^q(m>r+@*TDrgIHCyP) zJv!Dg(9qWQxv}1u5nSBG=z`%!eg7_6Q5?hesJ)l%k;1b-Mpx1Lo z4vwN*qqlp&GG67ybE7n02?Hyu=B2&I`oMD9(FC`5baw9TV*BCPlf-{O&_O)G#Kg)y z75Wke!{GxdXfxmk=f4K?|36;@TzPQ>p+OMgW~s?$R@C<}vaQ*Hp|=75ijVYq7i@WB z5MgYOO$h&fox+EDQJe*j&e2xrVW?U1C!a96T3W<#6PB{-+?> z{nc5lnKxIq8VM1JRB9m@FOZFu>7#PMxmex_aW?bnzghsn8J`*#_uAL2!L6M|bMd!TK`y(#179G%RW^@g*k1VFh+!M!E72BRXi zTpaWuXI;aIN86hZIeOqH%SO z?bR~UE~1!_2tcQ5is&^afZYO&*6M_BceRNR_oQqv2Lwv=+}r8*P~^GoP>R28>? z(`gw*C-gxVX~)~QH_Y*_qaVkMxKL9GqxwEfgWHs<)qpwqRf?eSFtXlLw}si2qrI6K z&SBWTY}12in_9hfghZ?x#yZd=(JJ3<*qpEXVmKtLc-@{|U44T8x<+Ttv@2TxV2KW7|Z7pcbna| zMMbo2F}^H-Tx20T6?t1UO+H#iQDtEk1MiIAs$r;EmzCBNDignU8=159Ofej+eDnf% zN6KzD1kpm|TBTWI#y3o0tMz_59kF(6X4e8r+TENIKiajn>G**^H)QnkOn_y>ceN8n zl1b%0g*%7fY1)+r*oQI_5`Ka;{?L$^OKC&|uc*YV?jR=m)hk6PG^!NMQPyDkfUTi| zXA315n^QF}FeRO1L@^a(FOp4zZOE?<-;V*M!slw@Tz!WKX+oX`YD}uc(MC+Fk&&0B zrQD>Xk;FFaPI*X7up|F0R?5wd7#J)e5`jkzKnCK%a1e0S93`C=c1^vVu5RUJPmnJY z0$@4W_gYX&g2oddzET>9gmN{jj?*dCb{qu_le7{uv`}lb{!rFFUi6|2oUSbe?A3Y_ z6#6`GN2MK1>_gcPTxH0Gstftml2GQy>Am9?0Z4LUKQ^t?;TR`uxp+Q=*&BY-hOuiW z_U?77(W~fb`kt@}|CPWY073ra3Dr8BzUS=@8vy=U5 zUoAP+d{d@Kq$DcJ4{YNd)+SPA*Iu5T9QX!#j1^qkXw2s_$m$JA{2eOLSiN8UV^^QF z!2D9k?YOH*8ADpS6S6KRKajsdLW-HNN7^Q?Wn~rM*3Ytq)6&pzSsp)y>IF=yQBedS z#D8eHx;u;X<>Y$Gkh$X}#&r`PnbmR&%8F7=kH>bDvukxbY0MafojP)m7raaaH%|`N zIu~pX*M)$<@QP~qce+32<1x)tyW-*^Fq~?GhPFx%%1k%4sfML!lCFCwA!oeTabmb= zXeF8Vbj|)_WZKvFA5Cgs->*%Oz-4H78HYk*Psqpi-!3=*-#|z}z1jdslwF168-M?J z^+)^)+Hj%*g8{obv}yS!vC0p^?)QhE^j;i^Ccf`W$*I<&3-0H%>?=h}NK9hzYy1uS z9jlMBl7`0UZ`iP_F>X1!Ycmir&HF(B4H^zAVKkNdvV%&Q#VIK&PX|RmBwQjeG)KI< zW+A%COCj{|U}mmvlK%I%giW0Y zj9dI@-xQ4fpl@RQFQ9Jq%#H;p%{mSO^whal|NrCzxYqt0wS3tU7yb5%8oVK#x!{bS zD26=w2h8)pcZZ)Ovn5=^jgR&Q+BOCLO|MkL)bcl=j2nJ3Xa~h(iJJccX|Lwy>(V}; z%&!8gA8OCL%Y~2PRM629C|Se(VOY?r1u|94n&wC|Y%T@v`}))b_#ZDF0jrt4M6Jqw zq%Qq26DUdi#W7V!L0$9Y+7n{pJNGlp{{Hpf5vRI*x1MWkx9=TC#~sWs;jR^r*ac~J z^rpOQ>3#4{K1(9{r?^=7YVQ%Vqr-zZVwZx3M!ZtHO$s;F9_$!yWK~#=jE$F{zre=c zlNZt52VWE1jY-y%V`)EyI!eBm^!0$i!5sANHGuCKORKL#5c>MlfVa}+UxTqe)qJym zI2QWG(%PkRy84di)hm~>RD}oN)c(ON?-QwjO-0P@S>P~b_-s+9;kqpOU%ML`To?RO z6?vXf&eYYIxAsEieupebg0ZUH`2Y}X*9*zX1rlRH=@1fkOS8tz>%$3*KSVcjU^|7Q zVoUyI7ck%@0zz(XU<*f2=2@EiDb=YoSC(XJYe&n*^Q9v$ff-`y z{njrr9MBiz`C`xX32I$PfvOi)Uj{!WR#qy{^+}IEuy%D_wI58EJATvueS16F^Xt~j zv+da^Ys8Q6k*4b z!bL`M$v1`G{Amr%hXVB>BJ!=`M= zVCE*o##ZN$PvI(DH*&*(Nc_kiWi?tVt7!c=yptHqOD#`FT1JK%_SSc^_er&D36c=8 z|DJ1DZMl|QRjK9XjLSA_9h!NHsfP%1LNEjEN&8y|%K>e+*(Ey za$fl|#>Vsrg#1>Ic7*iZ=_(A4m3-Yg2s5|$He450xzc`Rd}{=KNrMGQ`JzHY7XmFc zzbh*>Hc*?@7^9&fA|oSl@AK0DaT=)YLQ8Pdr*Wmf#(XPo`fyW3CE&=jr$-LNfF5K* zJ%X5Lr(V+DF4WG3SFX4RtZ(3w9qUR;GE&j}M2;dyeMlDs`1q%49O~}9ds+2b2qCSD z8*mSVmfUnGxmX_UP7Go+2GY$<&C6gYQ;=k`hx3MKcrcZMuNa!eq|`L(LbCm z;OI23{wZ_qk@MF1r^f{jIXMqo14%2a_g?yn@mrscK`6b?vqC|!sq0ZO2G1g}ft<1s z(14s99y~q8p(#F&)&SBH+fiW-<-r2|=bPgW+nY5f;?2#>VQdwSM`rHswL03@-XDGp z3kt^?kGSt*mp-?2U-mPKBcDhdDC{->6_Co|NarBJ5`Sr5T2^MPG%&ZjYXe5_og_Ls zK%x~D*5Q;z}>A+&Z z?aY;ooIFm%<0+=Kt@+BrctM3Z2X3^olNCdB_a^eN5hmZ#Ld}_2cXwxJdpG>d?KlGZ z;c2h}yd)fttksUjC7-C`HfIX%*u_4Nqi7u38Y!fW-g5wl&YM*5?Ut67z)Gu39&EoY zDk|yj>Y%=airSw(+1T1fYNuC94qB<89)k>1S<_V***aMS9?Nn)fuPcW(j>;{xNpD5 zGoPKsXmfRYAc{(NZsu|c{Q6llvo#50ogQzO+Q%wa0sJ(t^G$jb0(T?)eo8%0ygH)jxi;_4n7;Cw^05 zn%gOVb_3_(op9*?5FHh@zcx7u?R!jz`V;YkeM~1()s+be2}M7H;2&;3^|xj4djYE` z85++d0)k*M(Wj6_OJ!F#GmA0kcQ+s);M3JJsB+6RR#q{zJc)7Y(U%y&E{8hn7D_f27-FO*ag;R$^X?eMra?tsa zy@|Qp;ioLo!GQY$92he24>P*+S3VDxM&Io(`8mxEEK|0eVi9m41Ky!ndM>y zJ7cQxeZ{)9qXFsi@$o^$wr(X^L(Dm1bK(o4i{29$1DQkxtqSLW3Xh@o_HU6>NLq$R zbqP=S457bLRP&q1##Q7O9gS~o>||VaJsG2Q72fU+nWnA$V*QY6XxMS@Aqz_^89CFf zej0+6gEhSmSKKW%TvCLbq9P*pR}Z4ghR=tkOx0H|iFf8}^6^4zLj^)Xt;~#!IJdTg zB~p!hwO{Wl6EzzZgrLJy3fbe}uV0DoryJG!iBUi1daUO?2Sxa>IYktL5(n;dp{-5j z4GhzXfGoFLxAxU0UxH)RK%8KJU!^Rj)4?iE(aY%I;AAa20goN_qWeI2)ae@2)z!iu zZ)e*eYaMq);c{yUloS*sF#Crd3{yu%4IaXiSG=1$6rwwbpDN9BB_VI9-WdST7FXhv z1Vd(VMOf8!Yut0~v!A@4sw`L#%tGq_THK==k}cZ`_W*O^q*Iu7yk0R8}MpIVz`$ zi0c+!#l99?1sWOrzz6Z1wTqsmWM_|**s2hO_5MP>Rw{C4TIf#E# z)8~1Bl(bZ&b!iyI+1@WjWK1nasM%l^A~o&QEQnzmj|#0X>FKl^kiPG4UT?9$t%8&q z*S91FX6DZ7>eH5h`^JWb*?FsD8Q;#L_is@I8SCqxoiDD@* zM1N>U?Jk&uB~?z2#e$F~kvJbezg8W7&qJs7c|Gp&$?=Z)-Su@cLv^~dPk2#}ujR$lHo0&2TQnQPQAVF9qM?SeUzX%DGkKIO-W3S?c926s4`}?UL zJor@MoD3_$R9V(mPTI9+Ty<%SW~&JUmy>Sc_wTe5s_SS`08};q1_H@v<7EpAZ@fhH z^!0O(>o;K2Fda;*yRaS-qapIA?XZJ5i2g(tPBj)8kZs?vhCBrF(_b}*9KGz363;?z*I zRHrax?Gh!2u|#2eH2}R7%1GoN8XcX@&sZm|@B~)(g1PjFTgfkQ2=OMTk;3$~qQ#E;n9UfsS z>U^LW?$+7mqGu)8H}GKiG%6~)?9hQKo$qUlyUg^?15EHi zSt6c4&m1vAAbMp^Ul)x7Q~^#Z8PJQZ3G^p56vr$mDk6Wp4u`@{`^UEQ&(qW7+3y6D zlx!tcicFysuQUtBb0C{c2;=JnpabfzBJMwVHyv=RbH4bgJi9X-4?3<>jxrnN!o#?lvbFeA?g;wTnCa_oxzWjUx4#P zrpi44s&5ALjZ|Y?4k;PT-=3QL9%AjKhmJ!NL+HmfQd#1o=}*$mm3Rux4HV5&q-FFGZq!6@#t*Xt4Ax=eqSmkYp@QCn`S0ktMMxi`*<1gqMQ zS3_VS85I8q3ZtY;N}42K zUq(5nNGaPMQ>YL}y)+v9yX)v*?m7zpl}^fqFSrAg`C-C}cK2f-CELi$m&9SO^Od^Y zYpr?79z3AnD9`^1xwOuO<*MBRChq9m(8N1n+U6U*2Y(?W8Ei`BMq88XQZTv z(GY}&gfKYd7qYmWWOUXp*zmDOYVq}phf|oE$subL6IYl_`IGEc=+I2`%*?Vnhfd90 zGf7vqYGLCe5(0{cNL+Ppyx^TEU9hRMW7w;QMid`ZgwoQ|9(d#7;MDBQuW%3&enS|? zHzZLn$Ohli_WmPN%48cB9?l#dt0*n)FjJ5|Q6s&8ln^NHSdY8 zA+zdYzXL+Wq*5yCI0?4xC;o?9o0^d_Ex+IXNFsrX9$cT~vx=gUCnwWoB?(}XJadMj zU1h)0$OBnQr1%P%`MM)V=o1>erY(o*h9Y@kXO+-A3?GIbG;JKmO9dFv4(dWg@*g% zsEAyqSeIp=4#GA$)a2Q1H2Mx)C$`TY42eUHu@&#KOjQ z=cc%8Ex_!Ia*2mwzi4%4|NdsVsVlr<2$kw=UVxzV##vlaOmy*3_WPO|ZF{-E`q?XJ z8Km$atgMha6W1@@2i`&cGAaIVQ}6$0E_`oy?N)Sm>5oVBp7V=~q?;dr%Z77e^Hg*v z-`;*}>tMdJy!=Fwh)OI`q1yRiu^*zPl-76T_b&g8^E!@e2<7cx#(Gxnpo`LA&WHt> zGr%nw88K^X2SjU=p@D%Ck)gS{J^}%5H)FMKV0OuN_t@L~@}HY*-zsmB)3B-D-GcQC z4(o$^VgGy9@a$7gttS3xS9iDDwMyu@Ju3HMdpWeOa#i|vKCER=LZ`%44y}Z0Y=eRM$GS(Ns6bgP(h!mZTk5PY)>=%EG15utq zXTRK+r`(FlVpMyUcYp7QnBXs&x3A>FYzjh-I`cz!(Keu1LVW)>+2^JdO-=Q)^2Ms< zwl!b8xjY?y4#NS*;Vc05v4ZECqOKMoV+45_1dTGz8aA#PHm6WNKWp`(E1pL)a&~q= z<;bBdPrD~xXy=#J{;DeTmoHx~IR|;Zisg>x!BA`h(PPxgJJ3#g{u%4bUZR(~oBhSJ zorGxF15%9e-ZKn)XgQR%QCsEGd9s5CMHOamq@*V1=2eD?e8qOR6+ePuPSu9R46)B!4!9hh`!w8vpCGtjgZyk&cA=${|~MYA)!kv zcmTAAV*^9kXtBF@Fkay%4gJ73XR72ChrX;RpI0r5WaYZ<9G$iX1?$#0=W=@wBlFvO zdJ-ATApZ*=+Zy>sUnUoGAx>3Nj#ptA;Nrb3aq5n)YH67+DJ|7G9-f5^$!T(rD)a8{ z?!1jxe10!aXPAoB(8x#&ln#TxBryS$S%3NBNX!mBE+-lqwElTCq8D@(?MW=;z{fiV z3iFaPR3V&|y(cjGjtDIwSc81w4hadqC`{AH@c(mSeK-7PIyOllxriZ56_-Qukj@2} z%nDn)AF~_()dCn5zRHo+bSLF98u*p?0AdQbA-T|u8%OhbV5H;zm}JRgGQs_|@|((c zJSswNvm3NM;f`hrZa3Hd)gLwycTXksq*RIwm;O;- z-A^=sTKEcfweK~80+m&34qGw5A8lzoeX6JM8qFu!{X;F};d-Mev-|F$h zs|?7YAVu8QCASOoKaioz?k~p#CcA-} zhbR0f`NpVIHoi^8sjM^j_HmSG%E$2+y_@XGowO$qjS!KGp2e2F4CXXtaPp`BM0|dpIqzqxfjN>+`RCuxeaklr`s0_TsttyZ~97hgy$nu2ro4a zO#8E}CWGlQgWSj{vFmzr+EnN?*aD15TncCe7H9l z-V|4S`7#Vb8&z(IwqzAmtgR&vsIR}*cx`O%vbc_(sq!4gH|R%Ek^TUq-!9<$tWXRp zpi^iCsS6tz6qIj3``lk!Ynz*C3Jh=bw?OA1VB10@+6F!cdD?Z~zHXzQ71|`hT9fb- zEmFmKMXC+p_B-E18%-JKg)C6wf!9ATrx|!G+iW=B4dyjQeKAkuxq9^!x+5>t*<{J) zuoINTCNVJm^Rst4mzHW?QS^2^Kj3b+R$IeK};2gw&$E`<_A_)R@!Y4G`%SzBn)n7Krmfw zn0T;-SY`_4W!-vmf}eWKYI5P2VhY_+G()k?@PZT{P{wV=P6+5tbu_xIhCmt|RE!-d z&~xIp=w))LCC7=wsQYB&FnJW=Nl!x;`B@B}TC0&R!}ih66C5&r!wCyfiHlmd2qobE zZc+(VVi1bP#E?N&i=5m^`|#1nt7oAX(AlZItK-c;q(?6qca_aIpCAHED&W36zXC7j z`4tr5U0n~S?{~MivoJG{E>vFOaAsg(>~8NKu5m}pFl5X$t#Ns0yjdD9kthtVql;5iWaP%Q!>_*IVArI@z(1Y=&N_1EtGPq4yVaxLjm1}z+uekp2hn*w&1omMV_MdJ=!kGs{frwyC_s~ zY2ifK#jn~kpY;t3Sk_*)Aq<+#2g-NOulb-h1pvuzEDWz$)^QS%_2u{GW-RNLZutcU znog#bOiaHDcw}K|8A5NB<&Lc3hwdtHhHo$GA@->%(BG- z)XW|xByY)y)F4$Bqqc9e5$Szw`;8d~p@*(d>Vyqb4JRGeaq;TLQ>7EaejThja@+|A zLSbv#p${}8t&@9}Jn!7uhA7ljZzP<3RvOsdF)=hOaX{9)o#1g>t*02OW#IffRmh=g zvQE9!ptZD{KJW~+)Gh+@Z#=S71=EiqS>#6)VY+Q z-ftf5i+fL)O*?*V&akty13wlP8VW`9N`n=79biRd3v zw%t2Dy(;=yg61JBV{YN)BsmFeYH4PyyM4K|(9))get6P4ouLwNP1Uz^0WheXw99en zFM;ZQ*kKjVMSQK&q; zX(V<@t7_;K6Op74POsm;>#GGWPl}v8JJrFgad2Kcu>H^rB$yWBT za?@YFMDZFfo>mk=ri2KKf8TiwPbAOSN-a~&pDsO8^Mp=D&x;ig9be{Ya6mpeDlLdyq-6D^q+=f>RVU8v)OW)1z>134 zW`pt$MxNJ91P7uXblHGCW#oHbmFew)yy{>YuS|ayFe&CHfg=}Rf`KLd?M0kllUFv}jQ3rx&y^G|_l)n3rTbfmEc})46 zL`saF?zUP<=)u}aVsM{g8@rC?Y1!EfsZp_^%QOEl**<=oSkFG=5{9!N!|C!wafgAo$0uz6J5jB6_PpWVP6R@t69l3wb`u6UCW z#RB@ZFxK$UQ0;O%!zUNQ&!1PDCqgd@L*iQ9BUg6_RKSKlF#eBn{+1)G{jS0npXdT0 zcHmgzYHYA#QX6G?_>kXuUkt>}X@ybjAt519SbI6tcsqR#Ir-9r!zS5Kn*{7$Z(G~E z4d3vq_0$dtPA_9CqZUEWF$gnXX#5pA(Hu>axh@ie^~-Z zH>8pz`;PZ2sjjTh1_aTdE)bPavXOfIo-zphZ}{SvlutuRLm;G&I4Okx+NzR_^{af^ z6i`3xnm{@LZM&f16goE=tMe_}`H)AWmA_=&o>ulmYJ)0^R{O<~6FOklaf{Nj(MiSw z=XWt_rVs)cqIGCyWS{9Q-+dn#PZQN3AtQr@b?X-pcoaN8k!p$Wz7ey`oHg#%C}*3m zKe3TAbAFx{pAWDqe3e+<_th>h-@;23JKv6nGH4m`DN5t*%IQ_t(LtokY;xJ|4df49 z7O<`FY^)1ebxDv!$Hv9EvvB5!s>_nXihljZAK570xZ5^MJ=1@w<%S1Vfl!4gCYG{V z!+$xTvs<@TCzRH9L%+hx^0s1X^6|uCgPD>cjhO(kn*iNUq{FPIwdYr0S0>AbIb)&P z&boou4>Gz0p`=43ArdLj)CPKKF6;#WFWswKu2Rvw>a}xxaG(AKhEZ5%S{#rdkCewC zl&QRqE)&P8!>Z=#J@eMqT#@S2_aoiCU6zMkkf9A(i8AYRqhE_tKRK~N7$3p`WOvr& z^4l&Hw_}keVb(T7CDZS{jhckYh}Y!s#_9m=FOU)*lfc6a zwTjcbd!=SPXPaEr{xEAR>^EBKwsrkBtF#BHG~{u$9pzQ$q;ZyZ*SuxuTnBISKeOW z$-!TNp#nLiT>Bm`0q4c~^a^21(C;7PkOix7-0`cmtZ~8NqJHpTuWNko-GE(e27MKI zg7YJ&nYc%H*#YuUmX+MV{)tYgf*~p6J(`F^Sy-2pD?a`=PFX_H#jZGf zu+jo41;-L=wd0PGdSXye__O5a9&y6V;hq0$q(ki~6VJ`}P!NhigPPw!BGCJV7<+F& z2fi=#!IhK8y@c{2HhU$!5Q~fIy1)30g^+sEL zv~eJQcD=|zX=Fx|4aFt4Aj-bcUPQV}=FINbnWDn&xE|3y-!{rsY<2j2wy9Y8-08-6B8xdtB%B?eSypP?v-)C)!sQCC*5r%uPFjMn9 z32W4~dG?Smsf(<#stkTdnAlf15k*51^rg0-YV`Bbg5hm)S+umL>7YUx9RLU3jm62LK<+9wLP`^W`t|JHZTvtM!7`n;EsJXyJt?L2N z>s}B>6c*7a4TGv2%*RIh`oLd09Y(edc6Lk}E^NQ)P^rt$Us~$H5soEA-K9-li$lJG z$%46*RCE!q(jLrQ5h&?lg+D^r-91)1T=bQ{Z*qIax2PywBNv8>L;r3*QYZ{12yir| zAg(<1SrLlE}*Q%4MyoEja)-4a_i(;2WL|@EO55cY}mE=A&FI{?cZ~ zcSaH>|Ce7W1B45P%}W6wZHvEg5N^L?PnnR5aiqI|IaCK}m@m7Z{UV1!_nl{VJy_C1vU2!bJje zApk=MdACFb8`4q2t8TO5#wnwt6mmRm%;&pc6+IPk#K%~ z0XCYKF?7$KI>6C{D-7EMHBGhK)2~jOL>lymjvZ5yFQ$TT+}?^~wc#O|gF>Za;xf#f zt4*i@@m#BJQD0wQo^Nf<+G`yhS!oYWc-Ni(qA4EP;j3}W~LL>qd{NVVEiS0Gipv~D ziwFc(I+u4e?E@_l?TI{28OocoH=c4F8Pb;XL7ue~aNS7%Y1T8o|EnS#Y#u`n#He>A zBmRFaEJnRP;*h5@!Zt5Z;&6E|Zw|6E(ee_;4y zV^o13A9El_2X_7MY{qj!r1|$dQ@ulDmBtF9>8n5b~r-x&cg$}~h(9$|`8@`4#KM8Y9@a=z-_ z)ju^L3shVrH8EAr>hj}di01~#719@MI-MkD8gA$b3c9WR@pc6$i3WGhnzQcFWSPsr&62k!)P&QUuJCK&GhI9yo$YSX62BcA z{B*rpC87#-?Z2F7ddqr%!RxVT@a9;J`%fdgu@VbO+0QG3z^>D(l}|S(v4f2H_5K}x z_uAK!VA+e7&#L^}ZNA}Y7je$|6?`k=O z!Z9;SSohuzwu)T6j89g_ZyC36oy14%w*OQY4C1u4+i;z9-F`Va9`735+@@%@+&90; zZPNIyj&%nFVK@Isj1FaLhSB0HB$6;BHuXE zMjz^3JlF2_2I%n=Dz9*?-t3IkgAbjMmHV3UL974J0)raNwieo);Wn5Ve{58W{O)bh zM_w>QU$M8<(!~H;5nN&5k*H0qE8gP@!w9guf?Q53Z9#W&d)rzC6jn&7F~B z7soGkKA5gK1TslSiv&-;!a7aS``4beOMXfJK4&N>+aJ^roz|`d8>BxbpF4<@V*1qG z2cJK$Qw)}M)ZlZkd>5Jf87=-EXAO)j+2eT2m4IJy{mEF=IJ*61;3jGb>=J@mHcomL z={160&1=Di;wSay&XUvJh6yO zNRZEHc~x5n+nU>JYPU?2g$;E>vP&V6<`pBo8H&VMaPTN8C}0U5x$*g>N~6n$ z7L8vuZF^-&E@}RquA@_Lb|x*Rfr7j<_L-(l-Az^#-M;dI5y=>Nu%(Ve@}dGKOG%>E zv1oroI=qdBulgoMLZZRgf*k~GPki>a*LX=^o8IBbTY2h{bE0%nmV87xR~l8NSt1XC z?}#tY39@%gDk_|-4vF4Ge!?dxq_-EpTC*Mahtzh#>6bw9 zp;|4XyE@4fQs)JZXXzJH5nOJs?+kr#UFnZ$Xdnc};3&V-LCFcX#Fs!lG$9Xvo&nDQAF(-b%O5RCn2*UQ#A7RNo-6Dzv+a8e#W6uld|=f3&vZ5fph? z>z#3H?|%KNb*~18o4D2TK=$=B0d4K6pS8}mJ2`W6>NYoC-c=X+rf}@&*r}mWSy}GV zUDtECzMA#x4`qw0PIM^lBxQx{A`y5I1Qu_DQwJ*~0qROd_uagQ#}e#a^nYJE^4MxDVCBLXgw?M={UH zyM%<=X`+oMO3DnL+5_jRm#Zl_$`TEO2zso{3Pa}E;qzunU2&Rfq9BXmu&n0QwFANF z!?ifiXXZ=Dl5EUJ$1zyoCMvU-D)CtV2C;G@>=vkdxR#gOt;sWwI97!=_~Uq$sj$ox zUt(#gWZlE_#;AI~UWO9Oft`l!RIS9C^|e?GqG5*Byt?eQxTq*?r6xiq-Ez3gC#sy{ z_>D?$`lS*ZRorNqP?3j3A>)g;q(8Hx4jnfQ#)?~~pI@mx*%(th$w~Q=FGGuWxISxQ zZcd9ANb0l>ss05~Mpo)$=~HfJX`6HYV@?V+zj8WaWZhiM0*LVf4>!2Wb&qx7^I{%! zdgFE8x*e^i)RYQ#p3HJ0!a7HHv7^W!IyFuO{(+kZIk|x8eLE|%&aSDgE6tFLe0{+#Mc^7FOIxUPaDi20C5?l(;mKIcSj#YEg%yu4?8mX^0t zfzi~@R|RiOx-XlVt%826Be95hWr)-#)Q>95Y{){b#x%=$wyA7$JZ`_fnwlHrLm`LT znmE;NO4G%nCLv|(A8wE2i};d$qxyRpGl|1<{)}C_{4;j?9^S&j;l_k^h3Wc6%LlH6 z>|ei9p0+FHXcj$Tr~lLZ>=sOsLrCJ?^}BX!v7mZsyk8OOnAa%)(Eihm@^bfPR*G@0z)x$3WGVm=c8 z=Im|Pg_Ervq7K8NfWRq5XWFv2l_MNwt3Y9l?7WU3ZJEr_l9OY0Yshqw+`3pBExLfk zBI@~!91FdF=oUhtKBM57qkyvnxjY$mPl#q!6=V~p$qyyQO5GIDBlz`@doMz7^O6vF zl2-bC*<*KSC1}Lkoo~HU2>!A$lKHKH-*w^egT*HR17zf#5QxW#Hy@i4JSe#0fL&}n z`NzA8+hMJ{p())jyFILNmq9CF&|VG#!R&&%M=KDvuH`VHQJ(}t>w3N3~jqw~z0d&rfM)Du`caHMWdc!HfUY4fY^ z?jq$#VA;STDfOAc&q5cC+&^vip3`^_9j^21Pra>A zCRrVyaBbC$lYaHQI5sj<-jJ$ZG|OatOz++;xO0-*td>A>taL5!aEWD$Wl_qqre^5- zgo?Wb1ETvw`+X^KtC|FSPRWBb$+*&Bd~PP|!H0rkID~Olq9KpDJ$9NxJNGVI*b{oz zwfcl^S#Z}q=AbfOzUd;%e=kxu`tEesYsOzs96u7xvcQr9zT>Ai8hnO@oV!!*{5F&C zU;;>)mhJn?>@b;ITAG1_BPnsX&8n?oo@PiRsNx)~&3dvFj+@UIExs{KgmoJFnU4|> z5?-tftY*$qlx+?}9p(>7O6UOB-5@zWz6Juf-j;w=rkD=4RY72(K(3_Ux4<~Gaq+$F zJ{G5I;bt-1Pv;)zGe3SNaXD=$@Ge%Ly1^2zOcX&N9;N;{2IW$5)Y?QDKK=!Vz{Pnz z@a+H!ENAR(oz*4P5&Jx_=G+s3L){0<<^5o^S%D80$Z$c74*V!rTF>A}3>G$FoPc!Q z>iBt`!8fz@88AcnghG<)UBTNJ7~igE47~?Oh`ga{4Pk59IrlUFm$mY4xL-BRiYw_I zC?M~$VV*!~3KOv<`#b_1ohb%1?^eN|_i}$=c_nl3@ufLO5F zmE&hiOnVBPEY(YGPi*)jC zbFgW-h(fwAt$pRj)pr@oT`qt;aXZi8)-Cro0p*3~|DubT*>&GOfybme?}GmjUwmjm zJQ6=i-mG}#tN291GrQ6l&7#=IN;^kKm#r^+_o=QRxR_@Q%eMk6HXFo8>N;iJhT?qbiwT>Y+EZ<4suLB0FRy17*q`nF3#9_e|WbiW)*P_}1LwtA5 zu9{~Y3_>zXS0JIfLDxo0>wLh>=BF8Zesl*jk+i$}AH5rhFTcw*Ed37FbDK3DuKOE7~ zL9jkt8zX}Y1*iOd)Z$rrpPEZIxaXb*UWcUo(OXfGq*!vBta*qO(6S;!FJ)-l}yK$ZD(kOFUwy zz5mW06BzmeuLX~8Hq>v0hdstO2ugy7rZ+rgS_sSk1#Tl~`;>I@PEXw&oWKXq_1}LF znZgPRxWY^@21B1C@tIw^vz@EBcs9&n?p235AH2LZAA=+-otz}a#Zx&Fxt%t`rPY8T zfw{zDl~(rl#WgiTvrP+-+-p48K@YTf`oz5B-dtgmKR&*`+SPAJm~9uwD|WA5zC#z0n60~Jx%vf&O*%`!MLi9(3SusEX2C%C`!1vP=bz3=!oX&v2z@oxJ zwPKW_{M8h0yKX_q!)H8(9S;Q2qs;b}WT^ui3(K6Qx7$=`m0-+xM1+H|urv6lqO?5# zC+~dC2s7&8AO$+WHz=@%D;U}O~(>{kbK zQc|8=vshbOgBi-R%^?OH>}G?sG&JH*K4A&=_Vq0Xq2_>URfAKD)JNaI0MLY3$Goi6 z`1tsMfSZD1!otE@T3SVhopVhJe9mw~w7|CFHQ3VSp0kX*Ld?Zgsm>9#v}AnEqNAe& z4TgMrp1*^UwfcsJ=R55TKA{^ERe>*3U=>G3MxtHMOI9znoEZK2Q)jaJn2L(ZZt=Gs za}3)${SZZx+sxctu}=N_U%!4~5VFG+1Ls#;P?j5f6WyVxjb%MF>4XdBnD?pn*VVWl z%HZyg7)jC7PgqTW`>$tch~I<9ba#Oc6O#_Pzq?>tVc!m;G@Bb5$}1{d-|WFZ>>=V- zs)pv4dXgA4%8jiytpN*wWvYY)8=F010XQx?fvBUvsPEk=&m8+9UlUBIbslGJO-<<} z%~ug#Y9><;xxtH)Lg}JGh4^t2O1DiF0Ot7E-(oC4Yr-`Kt&g|&kQ;J2V_z;fn-rEm z>(v#cjS?AlE9Ul9giMgRe^Sy(Y^#2+U5lGfu0};~nz*w1nBmKpFPnqU24CxeJXh#T z?p8d-p`ffGintIK>&iu0uwcW6EK2^sJwi*qeG(4ANJUTPmxDH zp))hO`}dILc-MgN0i&-yZm?#uiSB(QD1gu+zw1Jw%~>$nl%9= z{KnzbPtPNB)Sr~Df1lnR=zj#;tXJjTo4zlesNxpO{rwe#nW*cuu@}Rs_~aA%)fIw~ zT%*yJ6e(^{|8BRn4}8V|HwF_cD=l5lSlFYsx;iieu5pZTlOD#hS(=;ZSQ;C9#WcX7 z_BL6_uiMFS9;6azt5r;V0LK`-l75)+%P+Q4sS%eAv}s zbMKy@&LbGv_D4u^-42SnCF-{VUuw10;PXw2CgkxeDg|uUv%;(`@Wm)8nR3C!Mg(Qq zMW9loB~Iax!XOB5RhSyrhkwV=PRIr}q}i~bKm0aA;H=xI$eE`D)+yp~9+&d5Tde@? zrw0!nWKukaBo~9%uVqwKTb0Ld!<$E7BvJ1Qey*m0xg`}Ec!9o5NJO`Q3~nQQ9UAnX zLc_x3Bs1V!`|^@HIG@2;IVCU8%GmhF9KrIk&dq0t?&`Kz1t7+vnbnyDmjz;AGj)FV z_~`jV_%q_mrC$aRRS1v2ryKnDe|G@>>qjz+I`(jvJr6g@M0_kDVYk~}Zr@}*_FlMK z(||Z7cr#ftQdXXRR#;V{W9gG$YNm<09`u+a-D4IECJ5B2OdTLC7Ep^VuS!J(vj|GJ zI$v!FdvaLYnuKxN}|LBA4ew3zS)xp}G=#6H>qJg1n zFi8e-OGmfr(jAQi0>MaqcfD=j5NP)lhcp-a^kk3yGRz<`!{E(wuxGt?fl+ z*q$`dx}c*f>7q&F(>hD^SYf-gpv;pd_Kj zx*$2he29smy0|eJ2oZ5l4cCrZeXyd4h#&_l0wb50Ag(i((XwOh*{xdS0=LkWE6W9b z@4uUk?BHl$c>NTroq@D)Wrn=O-PHbKpppjXH_;GZPNHL_;9gPJthJ8y__4SPtvlt! z#wG`Jp|uu9Do4S8>figk|Go>9#OApWoa9!f#O}|goJ?OErpHrz?hbx{{u%~9Jid#! zGMG(KQ;?reX>WHFG#AHVHG#i#{3V}`@Z@0o^MqMndJctD!oi;PRGE<|4k6(`AP~9q z!#d+X$n{fLz|v8rq=e)zHKa4>J&A`&WmL_3Jg`X}ZL1 zwaOjS4hO-Of=rxJ$vCOF74c0-TohurfM^5?pBIn#O$>N|OQFz7xH~k}JL_wHIMEQnSG?LE^*XxgF;5%G2}c z85qAY+Sqn_Yhu0Q5$9k3X zG(+MT&+BejSdvX4H_ge&kEOO{t&K}WQpI8Ph?W*#0vf7f{x7_O?M5LTEp-^C4(#y+ zsC(ufo%&sgp+3N)ajH#6dY|4}qSlUFd+l!Up)qX5l}?ps%IFsyoZpKU>Bp-bL`x;| zvSBWD8n4F_cO+8MF#?T;C#rYJ2bDv1 z&r6BwpNqS9Hl`G7V(vz%D>Y@?O+o*GF$=xQXX$82NsQC^p-i)( zS$!H0H60z0J*N5kJ|md}$RD88Tup9qZ3m~#m@uRm^7Fa7*Fw)&5Z_j8?HCH7;I1;H zr`L2RM{YtC4XiM4PtWm8sQsRBFi88`fn)Ek?3o(yFIKN#|Ijbo+uKsSgka4pl%vJI z;aqLYmvo`yqETSZ1cE=?3e76}CqAdn2Rt?IMstalnq^(l8=H_W4-ug^NU9t+!|`gN z#5JEBw*>qzv^yUXvZ?xHb$ZlMD*CZ9Lw&SUe~`Iv{e9A zT|vXh?v)_MO3&aoo821;&sf$i;Iti@yw{F*>;Des;e>?j)PTmBT6dmsgQi9J@Rg8> zP{=&_;ck6b{Iju++>z5~Mf2=`5fEbGl0KNmoUQPEM^fVBjb!R0H(6CxP0tWCgv|AT zlxPWZztp@g!#WzOV+8NP%JM7ptnc4DT02j6*N(e8|Lh%PGYwt2Y;;}XaC5K(UQuc( za_j6mrud;+zVQfvxiE=%w8T!Sua~my(%bx*-=+aKZG&C{p63^)ni&f~lc_~?}*t}KH%q-goKZeEdoRT}3 z-*}|0Bs*$sl)ba1<>sl5CEUa{HTn6*t%ZtaL*G9HiGTPwPGgbBspbHU)~vhR&{c+{ zdz|aCqq7#=PlVXNXH9;u5+4E~RXfy1TiYpXYy3H@eB|U!F!!iBMUa>366BFxxpGBN zthI{kK9Ze%@c7!bm6xyLIEOhd)}!hzi6$?<%}-~~Q7_@~Jou_d)7hnM0z648PThg+ zVQ#ZQHQay%aNJi68Q^B_z9w{cXBU>G+`jbXCmi%fGM{T}YtwOhlswPQc#v%avZQr` z_D^2L@O3iis*}Bff|GiMN4L4VJAXzf$Qer5AOT8NR@S4}SN0sbel^y|ml|KuL3VV^3 zv%Ig3A(1kn7rN?>U*kNmx}>ToFV36hZMloB?$v24c<3^G0_X-n(CyJ^4VGAHnKo&K`$VXm?_W!hIG|xKC@x z_nzSOIM;G#(9$H!WCqjsYt1vL-_=OdXZZV2cB+G3`B%@7Hg+-MDJrTDhES)@i{H;t ztC5matVKf$GfdCWPtV}wDh-`zF)T_Wb)(njyeeY8_06E^ z!sC1I^Cq{$^@O->dp$5W1g(09 z$73WMH0`V?%K{-48|Eo=hX8Z4Q~-#qBcGeKQ`GNAkNKU|G&Fcz`Z-(=*Fq=EHbPjE z_#^>gU?U-W!eif{Tpum5kTd2)M@xst5X<2){s7|<24aaBU^KAiFVn($TaAg0{bsd; zRuVE?;r6F^kIDToXWa1LiVb4k79+!HdqA27%-A6b??Xyv66U7E`bKa4z*ERrnWV87 zZ%D4^?+Y2@DkJgfh~?dMF|SV53j`qwFy`M*mJrjkKKKsML!M>r_q_n#lseBSWWy&E z&eeB#o#B?|tK%YS^Qd~&lHYaX*AVG@(GBz}r;F#WOh*TzS8cZa_w7O93Xf6Lvj+#- zqw;!*_&@QvpOLF0%jIYz`4Y>BDHlc^q(dfuTf_s$%HL`f>}s6=I^+*RPHnG#@~J7o4y%I}CkN}=9>GXd0Zf}3zYO*?O*&e?2*>?bW3ItIG zm=1m)fzlM61k8iW1MQs+4gH;+XRYC$QM~9~7dUscdPP>+gSqM87|?2gbgG zyLj*5fQT2#jU0IF8P8(a;q>(BX0BFEZ;J3Oq$tAw1&{e~{?-pwJlDe3pL&SOS?Ngy*rf?jGaMWo&WFF> zUE3YXQICp=fdl~+B_%^Mv-a1F5C1-JeIP;1tiMIVF>pK@4mYsSXDj7^(u~J`sZh= z+WYhx4>>Hqmz9;h^LqdOJ+ReJeL$X4YCaNxwX-wdzQP1p0Dz1&J*}<&jk@kXydn7V z{bf=|2uPUu<+}^9hc4SQ#{iE~Y3@rSzwIq^Ou2B_$;+e2wHQfQk$Zd8$Q* zd*U#+BnA+_B+tZ=I?uXdUhkhEDGjI!CF~mApKo3N9s^z)!i#)DVR9rWb`{95Ct!kdZ?D|wazz+LwcaN}1Vd;R-D7#0nqo|r*bAo>$z|IBO9Y@9f1p$&_XJs)sC>}p_#}w(lEKq}keie8d zLp#d6O!TSJqRdROA``gGj#+XuRwI>9b;o|+NBR*{-(OT z+~?qtrswhcVSKzs$s`|KODoGGL+m3YIwfpf*L}%Wn}J#DMMB`>aa;Jq;?+hITHky!Cds z6Kv7;sm|uHL)n=5V0}RpFKm98O!c3%_IL z2eZM2PEW4`b4g;Q7KOf0wly>iH?%~jD2yazWi+!gGO{v;Mg!dEWHlAh?->BU^f3o) zK;IbI*p_GKWluK}vix644{a0~c4eTC;1)WnxQLjTWI?VZ7!ttJld;S?@UBr`Ejq49 zr2~&{t?txz*eI#g(cb(yI$$_1r*q5xI2nb7=Ss}D!gpbZbjQp9RsDwwC%6DBAq&m( zM(G&}$9ZM041z5qea+YJ5D;6iuvo{Yv*aY0|HgP5`+Kb8he^yb)jp9@po*%hh>!1q zqf^`*i{-DQ&5+YQaiu}+7*)?Rx4q#PNd-9WO)h(mx26bmb#!uJWd~rG9i$K^3coQ^~oQ7xoj^}Yu zL+({dryz?2^TzE+|#XD zX!66!*@52Z8w(LWBT5QA0=to**l=Xfh9$3bJddV2Bb}b9Zig;&-N}af6cx@qJ7_ea zT6kR$Eh|g$`A)kov!tX%fWzJp)EHiO@}4Ukd-8jF@UqvoHGTXjfsvNVWzm7JumzF| z!_ra+)gr?-@s|d4As{7|-DF43KhJH{x6_c25dRrh=~fJ*l1=-|a8T@%^%x1gBySRd z`#U-s8*Y9rV~++Y$+=+mf)a~YA@?9lL(R}7prC-MW}+%@C`Mj3glMwT?a!W5s%RUU zDx=jwPtpOuWR;PQmV?-jMepm0{I1M&^9RR86#S2Q^3!wEq|H_|H-h2Y>dQb{P8Rb4 z4%tvnGZlire}3E^^fHf?r^iTRFO9!X&QhD}qB*K0C@M3{U2^*d$y5-L^Vy1T@#nEP zjvS6@>u=59SCdCZ*v1~=f3m$;m{7l=&SAyK&iri^KX*sL@&{J}h^xL$+g8xB2TFAf z4;zbkUHo+abp25HH~QA1EyfL!^z?LE_Vy3$il=_vTumYx0V%*d0V9T6#yV|Xi!@b9 zcqfmW=4u4UynsBxi-<1W0#J1HXR3$K$Dp(YSh#wYJZv)Z^>YE6zGKZoY=Fu)*itoV5CY&VZ691ELqf&sv#TleX^PIa_I43%1% z*b=xM6hfol9oWu#GTCpfM2Lr5j>jL@SKst#Jk%4ZyGSw=ZHVmRv#8Nu1~a0N>w&#y zty{rOBbCT;F9$BrYKAtxX1tt!d`;qz8`;i;To?tt@y2M{?lw+Fa1!TyZ_etHEcnbP zYu!&)^O}vW9!c-VNo28ONP0Ku7NV0_jB---sk9niJg&p+vw=p*>oSIJ!*A9Ls=m{V z?vSK(enQSSZzSm;Ihwb0QO!KwK2-V#Yh80l5*nQ*n1pm^XX023@L62uAT(fE8GzkF zP@k?OpSn@-dhFP5R0G{`WV@$Gks*ENMxhy0kxMLHQ9~2zwmUfLK;jeX3fXO%b>rE7 ztEr+PQ!JhpOm`lgACK;rpBz5M7ECnIKiC`^YD{Z~!r1hc#B{3Z3W~--(o&>HZ3mIMUyePE?8PhhJHnGf~+wEb5`BE8W>F9-{B@PFB;Kly^ ztL_|WL=c0yKAH$3;E+C}8zeBW3_$gzb^ePT_1>U4j8*U18JuNxNqat+O~(L zfD&y_Ne49Q3W`DX124_s=bkKwWOLaeOYqo2O0ttQgqj_p?wjKS3!5rO@>>g?rDax= zgB2K{Sdo5mbF@8Zs?wo-D)@|k;tcfT1KFpQM56%M8eTmrGu>|unp20mL4GUY#Y?tp z3g>&&;d1-8cCnuvZx*QhdM(4ua90q$u{*)+7&0sX@H|fm>N6@~_c%ZGHH8_((4Vn= zpuX6T<0!KP@u5?Jirea7LTGnoj>wBCE>(lTwr4HU5>J%AscP;-5!yBGG){T2%p7fI zepz}V2GbWg#3I0|a9MFy>v+2tOP(kRrmqHuJrqUWvo zwgb2_$9>rnukH|~gJ|a#hH8~-*W15=HXHnJINx-86&5m+hqGC~t{e9BeD`4I_=i8= zEWKuJY}n3{dvNyiyXd}XE-qb(29)yi{H~G7f^LfPz+ib@^bt4BXL~(Ai#mph9XEt0 zD&3>bySio0pYBqFbPCvjh@Y>q1o!s#Dy!^WKOaPU_&3v(6_lntE;C=wEO(NeXD~a; zPp&(4blVQQS9eOV58p;jnP+Sw33qp$Uh~0C0vbDGm{J}RCAvK}0|PyRcDK)tm#k7n zJT&F8RN2lr|sm@hU)tGJnU>nj!SGic&7 z{-|=uj+8&Wyc7jZ-<`+odm}|1GftaLA%ya2ud;iq(s!cv7bHug{7xnq_=bjtE-d$2 z)Y{uBOZnd{i5a(4hGP7L*I6EG?rUm7^4^hr^2uD|@#)S$c8li8$Dof$=fh7PjdE`9 z&5(VR=$UiNit%#y($XNQA^-dLk63bYas)&L@|NDch0KiVUfLGCjQP=k4N!kFE5#mF48H38V6L2S%%_-A0sauHWv23M1)g6Xz{fp<`!& z(K0?Lf90ON`wZnt59x{D?eN_JWrP33`LauWLL=I}nVE|)iI7=Dz!|#D74N8>14|QB zK5p?RdCw&VuHfa%M%<;wcS{Xs%L}H~M{AtG8&ZPiUyIG6w-65o`1ckg!-Ex_A7kvk zvdxF3WkZ4~qtkC% NI96pdpJn|@eFE{Jd;P|+!rM*kL&M{(BKM4wkY_ikybyT8} zxpRKD*zryxw7=ANbuUB=%?)Bdk=)TzY;zgBvb}X}3A{UCXy+=~Ut`VHwIc zuM|Puce1i?=F*@H6Ycc1O?w;O)2nFLIqMB|C~f->a6w&P{?!L-XSb`)auS=w$U`4u&V@qz5$`n! zi=sMXhP+{vvUv1BW&)xpuB4w7jF|5`TmqdzG7510&z`4*y@-)RIUV1N9bC&dN3i0x zWgtcchBMiNm!{d1Ul{!uY%T>;A@p=~;885gXH4QXdb>B1OK+lUA>0);mEwCArHy&A z);Aa&OovVa8NDG%JiInD3YvK{eNz~P(E6HxpBoGGt#AgMI-yr)=PakKDo#X09O zAxT5yfcIn#iAH=}&Mf+FIv3*_$&hR9PK9=B&Q>@d@gCuOA~^sxvSnX{t8uvA)6C zBLaej)%(*En|7NXNhio_KJVXW$;^n8g}qBDGi}+sa=g0fiK!}APxk!3a=r{FVNph# z;dK{bk+$4S8*Qf*{P`ock%VJ4EKi`K1VrgJTK%taH=tVQ8q7lEH(zM;u(TR!NUH8{ zuO&pk5klHSwX2uXh0;{Juc$m`xZ|P+B)Antv%|{ppMQwrXTUN;2;p)R8jKG+Uwer> zX=>X439Dbi)2(jMnCj4~vg)wX_ShVh)}($1uS5@HV%XGbY?p(4qWiCN9IlNTnvE5^ z{Mx?pvV>j@PW!gLHk)Gv!C`6n(NUdK6i{A84!dZOX&|=)v*1}1Vn?@+-z_p|voSNV zv$H#Q`Dc+~@8lMLEkDw%d3yNsf2MqutfuEjxS!A#7Z&b_?BsxWlmSESM}OZ~(Izp; zR{#L3NJ%LJUe!8h`+9pPZIXfD%HYLFNYRy(gMAPU-Hd-GN{c7z<5y|Fx^rN$4JhVV ziJexpabjj+A=28BJ#GMW*M3pu_qcp=`|w79wcf3|9tpM%4cHZdmA7=kJ}WWvrUC0A?)7KfQZwHfZ^I ztZB*$Vu#(ve&=sJs`taV0emw!ye7~5Kq&MP;fdy5z~*)`n7tit#8AVI9OQ!#o%h?kAOz|OB&6x;zh7< zLcYbQ{iwo}=c=t^OM1@U?fk#iDmCPZR6tPBeRQ9_5Lu9B`(Q0kQ2eJf8xn(Vki@b3 zS%e%dYWc!V^B*2F%!jATfPxO#{ci6nD=c=q;D_c4#ywuCW&p{Hc=!yBG$k0ce_=~z zBtM5mhZ=WKZ3Aqalc-2X^s_~#-z$UC82HUdq!??K|8hBI{Y>N`HIM9oV3 zLNmkF@urvB+P^hR!LU0{5Q&;sbnh<{-BWn)=iWrY|KMuBDIXWkVUFiH@bZOBZUhm= z!R+O`f(vx$0TqrDeW_)3gcM0o{q5i%t`^MRx=)u~cq^ot)Q6RsErR<)W*c*T;SH1! zT;;9GHa)KJBffk;vj$#=6$#xL489~!>xShRvq?i291@9ZcJ76IkhzeKN|Dg@lSpG z_Idv%g~6fwDVh7vlr+zH9(MbdX@04spjzy{=Jr%g^+l4TJFi~8TpzE%fi_@SWMAsq zWgQn}5%dW#j)4AOmM8(@H?im6VNB(f26 zce>8BF$_F&zzrnq>b>5av=rid93Pcb2T`!ybhcbieKhRrMpEc^=P zQ7xO|6|nU+pB&}h(Bh-99tDf+nB$B0<|^_vs!mb4CkKy}-qZpYh}mC44Fk3Gi}p&4 zYg`cBH$^mZ1t_u5P3zu$8B{DF;JbdZPD|C#Jb!G^ViRTA z1VgreGaD=Gj4g=I#mIg=aR0m5B*?=%4Ph{t%fSPgCfI4%65bX52P-=&Si@oxx&4L~ zZ_KM+hdJgp+QRN7gyM%r-A`f)T3RHyf2cj&U0{8X+(sGf9b``2KwBw5*$|Td7qfee znVa|jK~paaF9%tZ>Ez@tE-tvjSyf=(c?r~ZVPRo;IVT5$HVQpP7+>F4Iu53LNQV6q zBo!}BVg$TOz>jbbVvB}{TOek+eclj=q4Yj@huxP=9~R6G|9Vb_ZE&{l^jzmIK+7^Q z0>qS*lpP&Naaz1G>--@UqKr@z&>Y&yp{09@y#s?6*dSFPqHNb;PLGe{}=^vUD8g z`_~b`@tN9lDfGFJ-|b#|Jn&h7C>dDU|8M~0@znr7JMlF8K(XMnk8BR&3*Cd;@3XS9 z(u?4_<_v)j$DJ+TacGdg<93ULbPP!Up2x+!?PKnfU)hs`bblyU&wA?BwQoO?K!;N9 z2yB~6QKsvd2tKrOM)(#trE`+VN6((Kz4qcD(Ff1jJ|Vl?Dmj`rli9&(`Oyo(mXE4- zc^Wn3WlsY%4fi6$(z|1%;Mq%0GSt@} zRMv-yhyekUrd$AHceAOv(Rp@zd0c*Lt+?g#RzPOn_8bsEtvVMH0Q(jsSF^_}zE3|D z{1D!kvG*X+%ftf!Kd9bKD_>igjeWL!X4g~`53A>)Zm`G6@HWFAP58vVi1?d4q!pOU&vtw znoB!e+>poo7;2S*Pd-85A%}@Bi{YM_NrWd@)k{`6vK)I60%Z{q;Y#%{(wls+3azpA zc9hjlg=kQsa`0fwKTf05Dokk;aS`i)Qt=fUKI@mqBy$G|RfdK^mQxj?==j4^uAnDG zO(Rx6cU*HaX$Moen7H#bhJip!O>P`PqDk>c%eyy)dz%4gAJEfAe1Uya?l;BpP|1-N z6|6Iv;(Z*0=^v16%74L0U*($%{=M5Z7xdmt=bpx)@1iD?RA_4*R;9$229X%R9Kmr< zp{6ZKraBIc+*ViWliSqtZ@LMD%^W9x<&uXXw)Cx=`K~8iw1?o-80D|tWI4s+(?z>>| zBO!bTV>qik)~P|Zx*U8{OHT4HY~l(UuD!m5Pp+Dk4mYLs6JGQ)Sz7wRD5_BBsx!;{n^3Vdz(Mj`|!) zK%ge;VqTD$^0hqWh2#X{NAru2ENS%Auf+-|UjTyD-n6Gj}(Vn@v1p-2RHdFoIf2B8qd95Zz02Y%zpbP zaIFP&a53P7y6Z2jj`K?@F+$Iz9R4?_#vN%#4$OMC1N7of=qHm;cdU{)$rfe0g3!K#V|GFt2TGRTow#)$*n+ zzxeylsMofzHC=0Uu33El)W?hqWE{q*UJYT4KeUib^^EH8XC+`oi4)Y`x4sT&c=Fei zf$>+WN+)y|0+ozWN~QmNmVB^@Z|0LST4mJ56{rVm+zJf3TI?qAh6D3Qb8zcx2#H?8 zVY3;{TK>yK6Hv1K{u&dLD2U#QT-~faHcKGp;s~hMEehv zRx|f`e7@hY?9Y_da1#Xim&+YYL>u9R&fXGc>faNYRl~F{a_ZO5TDy$k-i#WT{V(Ef zPYwQ4qa`ElX!^A#ru~~OuGXY?)#081s-gfJjQfYX+@!Sx2UD{QepKeSFw~r#oo%+c z|9)0D^8XLU-U2GBw(A=|s7Q&Rl+uW_fRfTmN_Uq?cXx^)C0){zLnGZK-5}jacMRSC z3-9N7|L^xb@4ME=a;=-0dzd+M&OX<*_x{C}agAmS{{U2PV4O7;f0afH!u4gKpdytQ z!ST*`Z2#@S+hYrUvm>~E5CGEokrVo|kWw)D|BDIMQ+nDp?c>c`6UZpFu~Q zj+UodYd75CF*cW-sr6uhHLJdbjzxpDyA{v^0}*^0V{+Uwv()ZJgexufkx&8;-9mrg z*~Ist>$=O4Bfo4K8%dbLuB<|J8K0BPZ7D1imI)?>@v`e&o&NShEm4GXLtYv*G&z5l zjmQ-=%=iEk+UkhlEl20!LrG6RD*P6=*mej7NfDBW$Ct|6BCxHxT z2eAy=snbC51!iMB;RgcX1}>&FP?3R-tqk_c8kg~(D~^((7)DJ6U!Ix4e{%t3wPcmr zO@Tv>po&qn9vA@ccDAs~ki^C70WYhnayrVkJlUOmk9>Z(?F@v`wFI-hvjuo$%iGmK zs1D(3l?M8F1cJ>GxLm|DRx-v)Z5DA)<@?J1V$2c(_B7JFb|Pd*KX%-pf^!Bhj` z&;!*PH&(I?OA;XH{_ql>fd8TLAPL`Tb->mq>^Z{Cqlds0xMTedZ;Qw`z&s`4Hu5qn z1MK7Uc8O=q7Ej4C^u;@9PodsNiG?s&rGaLrzhyfZuN-ejUJW+Vf5`f1sAvgn#pfho z1jE?KF)wsgFN#e5jZ|D1Q|8^*G-@VA1?-%>Q8m2X`hxP2BF&I7rD^Sg^s$11x4C2W zKRYpH{k;NpI+eoe+4Zh(s^YWAP;`7&E}h*jd~64t_WF!%Y;1N#8$Fa*@ca7ApFuMX zSTAAz+5J4O7`-Rj*gwz3p?&yK7*2Ki7aquL;@JY9<$=hsK@zup{J@&H2Aygwzmz)k zTK7T$dTX_R=a}0tT`j&@N(wVjjaArG^;a);LxHw$cfxM#+O*wE(V-Z6O+iebD zR^GNWRsk2ul}ol(=g$-9M^TZXk&%py3^kRSP~m<4Y|TM?dxUqxGlbB(gaMxA`hhQb z_!Eb81;YkhppWNDt&&gW@ymrq)FL>`?!@KUe*5>LnmyjdB)53Jn5!#6L2#VBqiB*Y#M#FP|`4Mj9^h%u8`nrwcB_%?W6(bNs z6LSzkV2UW1-MQ?OyI}tgxg3o%GN)TF0z-}cZ1?BaP1+uXk&$wA7dkRdU@9R^KN+`mE~7+Y>jI>TwnDxgbnV` zKPKjNz8Wma40?!;KT%l}T~l)biV2)hU;{FL%WZ!&jBeWg?F1JY@e2}%qN4KUg(R6J z8wK8u%IOxtt+xQfc(``PWGQZ_+fjD-eL^Bisr@CZr-?~O_nVfojRIc48GT=Y1^LoM zyeVF>NAv6B;_~8E8)sVD2$2E^gG$2$q9P!!Mw){KyXp+1jVV=4=; zR!4ig?kz770fB|$Xf&As55|qb=8`-%5fP{3oQQR?_t%FxZ3%3Gv>1>3k?>(d&LSsv8g*pah71NqmTk_)C}S=FDSFI?(T6-TKJ8jczfSx3qY}P}iIYm-vnD zY8~*Ghf`M%hZs2$2?chNJvmQnqr0uK5|xj9WBm&WFg8qBtr9X*Oo?FX%Ru7-L0?N*XL;k-@| zGc*CY$;qBNpC``_7`Pfw8DEihtV8=B51tOcNr&-x8n9WIjkFH{Hkf#i$7r{Y`z3-` z>8)4GrXp>8d`Vy>QA$VpZ@sy2UrhdN9AqVuX-g) zs6Bsld^91e(`O)0=oh)LPJRBqCYWOdIe~rNsnu$lLcEi23x3Fm;gEcMfg>c8S)9L} zmUO+HXyz@vG&-X3oN45I+H%ps?Q{r>Ex0*MP2;hLz5P-GyPdrJW^KyzaEjekRjKs? z=Ud0DVAxko{@MQ5?3Rf0E@~H9V^8QDF!_<~Rm-cZ`FU^aGNY+|)#U}ohf?1>du*yC z;$CCDKdv3{^^Bf>bVQ-j+~bAufL&#GTAd=yTYcyOSi@DVqC&xX#u}JQD1v0A8m9R5ty?FH$ ze2|RpY|qOP<#f|)Elx*iBw8LF>7->qYAh*XdlRm$I%(GLQJ7wWwWwwTrgQkje@+LB zkxOIt^Jh^F3&_rOHCJvnm*VmyDOEdMo2aO{z;>WegRGQ-gvaEq+4rvRZ+dU8+t@o} zsTo9Y5|Xz0Dg*m0*j~~Gx35`GyBUCYyz%|#9VdEIlnJqH8!Y)wZY#oQb{luekQZxe zJIv<~CTJ)_SqC7lSYEJ~H)`NK+XPV(IJaO8K=@g5E{rkyRNLd~%wCb(GMQP!%&@kzE@X zV0wN{xqp&^#lrLQn6_N@N2=`aKP@|21&co1THt|zs3=wYAgam5McQp(ixgeqhT`T= zyu4%t5%B$v8q&AcW!CK!d3b=Gfu@K^6fv9SO^m;4DVDNETEqKW#7|Cu)a?$5(D4Hypc3wXG*O4&CqG&jpO zK1idIE_8L7O?e~Vxi+8j=CB5)tYgO#S6z0GWu&R~9SUq{CGMG54Q-TC-L-tbqDigw zcwxMB@{Cq_J)Z&#>xKkna5JWKs}$X4Sw@y}PK#E)%R@qNqYPe#{MX`5=q*fc=y@MK z*k*Umy=0~5oj|%RZj4_4W`4e_JTJGUv3-Z5n~)ePWR;i8Ffq}9^7x@w+WI;gTgA+# ziR!-=?GRgHIby<@-*5ZcLsOEQhDv^7T2-sz;tpfI&Bx#+Wk&M_le1%1tlqwgImh6; z8qAB;y1j7-4-eOK0lR+XDX_*cS5~gf$au+gu$JPfRs3mXukm)Sw$i#t%f*eC1Okr1 zk;9}91pwXK1W?@d`hnUJw8)K3x15~OJlyKQYR}d8|jn_p{BC~4+I45XU~u4K-aD}uJpUaESLz|jhZZ&>Lqh_Qk{FVjNlKgYy??AgCdJCi3IT0! zd083Y(q^tkO*I2E6cle8Fln~jbo|K!A$fUmDIcPl8Yft^eY9TvSJIrQx82%}7C7fk z#y^&TinQa~iG{slVIc!5F%Ih#Zb6DC<{E!~1lxVw&Hw8@ zMkk=D^Jh!FQaJJL&Ba(j%`bjg&R8VcTcZ1Q&WDur^gQgRecjP?t9=Q;OdH|j#gX*Y zRr}hqo^;|{osHj~o^I^i+*eoUjlWu2fO}q!0xjfrGMU9;YoM&C2s98XY7ImaeTf{V zz>_8}?l~_nFK{Qp#o=?xVGC9tJPjrj^_JbsOYVDe4RC7PnWr^5krafHLE#yr$)QNF;Hl6 zpl{qf*JxZyhR4V8P!Z*`B!J2yMTR=NurLBk8mW?D z$XIsTzE$JKig7^PQ@fhv>DVUz%yI~Pg=FXTq7JCqMw;7jU?bhpV%XIq#G^trpI=!N zGDzJ=vb*N2^{;3&BGu3C8DJEbC{wSN6H=dc7)o3G{>+A|7Hn2ZK$4KcrA&xtSgR*W zXEJ7ETgv6A)YDghqmP!JPBdP?n(}tYdRFbgm-;jvvESh~4i5*_SH03BFDt9U<#-dA z@qjxSc%8R*bXd+*$%=>=9&e634>xs;cTTEF1-kGK_>?IF#5N=nS>A7^s{es|xa6ky}^B^!&Vxs?F`~CmkUsr&}-s zBWgNneK9cs=j!2AMmsZeKeOwYZDKhr{o~9mbkRQ5t0S8CnD^P-+sjG`p6ypB{>)?- z#1u|W_LUuiWS-%SP6ML;2ax#;Kl`R7CAF-HZWGUZasmct%um!tGh_d~!6h(#Dk~iY zTLmD0fB$E!CQ4 zdrw+ikAD~B=Uz~~1+MkT5VE(GBf;vzM+_(@#~6d0>x z(WC67q#gf1THFk$T2>!GJ3QZ{jSkq|YbEbGI9&Vk|4A2-O|w%)OnAunr2WD^4~+dn zhD?>{G0gj1IY@~-?Qc{Qr z^HOT>W-5u>5OaS}jum|zYTyPxNSK-N!yrLu0+!l)Gxf>}3a@fCLX^t3mRXuwt4@<@ zZ^OoICx80_=O{%*P5SPi2BS6(65CLiePe?i=e50q)CVIcP0gecPEAeChi@nHa&tjs`}+8T zuNa6Aw{j#}X8D;_!M3ht1zHXHhlmh3td@H2<0F^R(Q(jNprX26^W@BF?&P$FZ)(;# zaj-MaS2^xU^8#UJJi`q~sj+nWP@-CwSXmDP@j2>;8Up9c0LFajjc z>*!WaT|`+{)=_hFb-d`64w6WW3&u;wheH;qdy-Fn4qUDANGU2tw|zo@f`yrxnZbyr zqWt{j11B}LsWk4<(NPIXEJ#B`LvYFU_w7xk^Tu!e_wT_h>x8W(K$`@nOJIdHF)@LD zWMu*PFbWJ2v9Ndyg@jl(8V(N*X5+e2nB^Mx19zyMX-7d3k@3+{6kmRu`mmfFl=qm> zV4gCgH>nT54$yMtFCct8g(-}4)M-*Z5FIxvDU=IZ$H0GS=q8=p*E766n~>p~g933o zwGoHzq@Fb!XCw?FM6WM?A!2pv((wy;IDI$Q`)}B|3>Lg9lbZCiOT$zDq=1<>7fWT} zWWf9gAVN;J#Qf9^OzllsdisL&4k_VM-8zv+vGN5~It&8GM@OLl=e`*_yt-=eue*xw z18VfIk4A(%4iWUY16~M^fg_t=XmXXybxbU*HgOT?-A2cUxsE$FN zbj&MAPtVIsh)HhN+x!M6CR=kuXc~9SuLyCtLX!f%(kdykoS@v$;km-EoOq>!{+FqvK9u)`!rMXj%| z|NPkeYcy{>%uv#wJ{|W~9?a?H)|ASYw3PxHkzZ*T2}IZHvKi}PU%x&)_d2nlFG&PZ z-NSp642f}t#Ejft#);4Zq^ohO8^xQ@S6n)3HufccM7(^{O2jc#9j`9+feC+eXGM{a zfX>M5)wd8fI6Q*nsieHTLh6-aDSf3!zY|Ct+JzYsi0EP%>VJ$ZWWGRNbmkU$kt`CLP9n?;CYI8&;B%n745}x3 z{;U$HgRYtdTwS?1MkB!T306N~+Ln_e>f-V+SWI3~Q9mw0AB-zLV>J`19UW!O%+z1@ z3v6&NkdqsZS0W(56Oj_@dI7tMF%bd*xcvZXJ{8cmdYTLd$uPXy5sJRK)=-; zO&yESW^I=T8gxIUJ?Bog<=mg)Yd%fsXk0i%1Q$qd%g*ZO+99HH;Vjr@-7ud3XF?HJ zp1+^nUdm-!*m9zGV4#?QySt0aD!|tnwQA(F=l4_4=;)uRQr&Ehn(~HP8knF$1_lOh z4W!Il(N5+iF8Kd69fIS4aqof+ot!*H$$^PU#AA}@*4swEERos_IVl2rn!yq)DY2}q zL>s0ot2;|=*6*nXm@&)fC17I#5?nTBX5C^H4F+}&j%s6T{zZllkj**?CZ^TaSYITS zbOhQu8Y*h1-Bitn#zo$n$naA`Mqw>$EC?IDUlb>ny^rVb@{_NQY z*s!^oS^Ul;NiYoa^E(^!Z-Tem3uDt0#=iVy=Q|o(fu{;dY~794OBK_q1P0eN4q#AWbi5zx=Yb?b6(TJ%tQxa|U zvbGkTykinDf44^I?%#JOA~Gi@Zvo-hRT>r_mk(J^Rmh^GD*PUas@Sx{(N1wMW;og{hQ9o9%I9oDnV?%qg z=ETa%cydKc^^ybqxwDJS$?g^yGHWK)RwQ@x1Frw##F+hrLjRw>_HwPV> z>2tFeGik|>0CpfOodO2JJ5^U#rwIn!Og0x(77v4>0*Ja+(MH=|97OMNZx7Z2KuHI1Xcvq5GFRZ+t$hKbmf=bnQ9Ra7BNY2kUO``M)Rlo z^VWBE=Ra!D?JzPnCgwZn(8jxsi7cq8iEkeKF}$%%%&Ob&)YDiYC;gc}H7XjgJNHjF znrst&1WmU*K>Du!XvGlA=ajEL^LRji!U1m=$Yl+_ThU1Z`Mog zA~7B8e`ID}Zxy+M0D4aetZ3P& z*R@i3aX}v0pTsqN^3&eV&S7tEWq0?_`rD6JPTD!zH2$3^TBI_1a7|9clq#ekTqJz_ z72mJz9@oJ}?4m{U7`TGM4kp&;|(!~ z_k{&Vq<5RBuvW5ST%2$+ao?(0kD^KVzmq>lV0Kb@+G$`bR{p6Co}EoiM6^0pzO}5k z1s1H|#=4(Ajf{>O_I@AghVv+_z4AyKRsVyryqm-v&*#_E}DsFRgb9m5q448z3l$5AP+>2bvt89gmTpUp;4~|b&<9+ImkPoP@ z(ikH$aHDYCN_G4(AG{}{R-mZhvuTA z6~Q(Ka&KLCVK7NF9IGAGMDuQk1^;$;3*}v`eb*TVOtfx*@>kAph3QLr#hHJg9|lJ2?ibPr*f^4s zLtn>U@$to^q$Dnuymog>T#A4Gyi9?SD7(Oq-FF|G4e+M_@dfxu>(H}41-e{?nsmv) zI|}@s_f((Vd}^fZ7g%zD!HDXMp|LSGCU~k7YOpZY?Za1mxAX2oWBI^=qA%XSHqcPL z#*PM>sdx3eitOF{`@=}*1Ubng+oKQWmG=-Q@LHebDYN&nEw&zH1=K%$oOty59&}b- zE?wa!IzAfx@BW^Y0DC3PkfmeKPt~9A`Zqq%KZeF^*VG1*==iAKr=R29)aLYepb!q} z=FCpJ!S!x`jok{X3`@~%(P)7qm}D1>UyT~ZNlHuvtt*PnsPz=085aUBa|wy_tq%LjkJmkY5NsH%L z(gnz|jVizOfu~2j`ae+@V9){PP%~(N*Kfyxd4=ep#f#dF+A#TA@CN1a!SS(bnUz-3 zgchA#P-#jl7%ThgJD+2-+wHHkMJj1*7rbQ~XHb$%PEP8Tfy=FWtNCa)f2>@bVd@Pt zb9jx02>-+v#hv;0dOaX4|qAFjJlw+u3~s@LCf> zh~?b!9PqdED3_CdPl`EHsX0%qAw#wl= z#v}6$V`Nrw--mqpux~1zhw2o24nLAREO9(r`R6dUXbQBh262VV9Ci}egK=;M9B(sb zi&AP*Q}YE!upOq99H%BGCJ@n9D{WO~Nt!o@OZs|TPT6P~Xk@UPX*bZ2A3 zzuC(zDm24mVwB$NkUc)ArL?y{elK<3dI<3;8S*d7D-~N?+>{P!4;pL>FMN; z54f7Vf(ek!X0kuL;QuhmxwAVxmXn)XNDsXQkX7~I;?nnL`_R;SDX@!lE8kuQmjifDAR$h{FUL^Q-0)d&9?Kap5!%-aA#*GH8}q zZAAFak^2oB$+2B+j1QP>EF$*A(i3z13gS;20)@IwPF&tn3TUPe?EDsZ-)+=eTnr#;UQpY_+(*&@A3+=_U0XMn1nirx*e##x3#dpC^3;@Pj*y!tByeqv{=Tuu28D&feS$Hm<=zYQ|TuR$2r6 zDa5;uxs46`HSC`|49$_PjL@7Lg78R^0w+|!FZ6y>vw%os#M}*bNKDMwkS%@y{B*ltopG}adG#&VzRPmle82R@-wyoE;$?ie*x{^qoYxvi~fF- zdY6^2e)r$?ZH+6cs%G)>LNW=l%F*E*J3@7RzX0KD%v%N-!oX#GL&WSdb!cd?)Glm( zCE=X9`t@r_tqsQwEG3wmhj6T*@9L5%PHTT%b}Tw?sZcYC@I35?oX~k56EjJ{{A>7# zD7w7-d)=iL#k_(nX%ffd!0aCtnfm4)?=?MmHI*yb7>g(wwR?U?a7W=f8GKS%|88rz zRAG+C!bZE70dUnF&fVQ##F10 z<&Agqxq|S!LH1_V;qRX%g`BXSep=rYn{!2lq!3c%M@P)?=mD=IM?yUBKimK3wGt-P z)ssnFc`KCqg)r4D-Hr(M#uH**jh2b3;xuoGqW*qj>+NiZMh}y(U(^dA?4UJNYUuIe0ogfKG{3o8Zb?a?n~auL|Hs$Z zuR1@z?w+q7jE#*24eE%{(h{v=6<|tw0ctG7d!OUtXnJ~IF*A#!z=AOOd1O*Vu}EI2 zqVnrle8`TQcVY_XhnaqlzX!cfH~#*81P9k9FW1NI#<|B6_7QYlZDI=K|J7uT5Vj-- zSiOus6N)!!qbLMjzyH$OahgG;-k>M>{p0yT$8rh)kkw*h9{ApaY8)K&(qf_&%{@Fc zJw7)aE=<*3P0eTwY49Fu632giSd^7zk3jAt)3M$w2*1tYOm2Q(yEail74V6m7}M(cjV(Sm?G>C}~5PYy{vx3idAl(Z|m;REQGBMB9V zTT{8kKqBH;7_c0p68I!n5;|Z64%D0UA%+e93J?E_FEZ3eR*sp&itBb4{(q9Yx)^a@ zegB*JO3OBbQFu%|TwDolOiPD||75%s6>N&5qsy_6JeE2FEzpFK$pq)rXy3$8P-DlW z%nuXIzfpN5iH61`+qtBem5Bhz6g8`T1n5{;|0aPb{v`9z*Ej$40`NXG(_`TXEg+~! z{m*9k!abQJByzRuU%+lb^P^MHo}Apm-d^+l=eZy6L9Zk@I$DnJH6TLZ!CGZ+S~^9T z4c)dsT2j&7|Ia)uvJ<#4J$)Lvc0Qn0W@VwO%5f>0?~OPq{Rs?Z!tG6|{n+5CBYP0( zh*+(mB`Rv{;J^m4u-O1>x{?yDa6bU(J>R1QYbG*+SN!Mq8)zY69pI+JqllMIlxa06 zb#oV@c@T|XYp<@#&p%VC&iXCbKEGf?A}c3Udgg=(1lK5lwUi4xEwcmrq)t-Q+YIOq z%{o<32JT-D-@9+SH&3-kg}Z-F1Cs*HDXr~u#i$@110`jdAGt8)gjhd7!;@jpY{lUk zSmFTxmY3=;9|!&x3cbUDI)*}td#)&c-ABT>|IWqbt6)vOcUn9ckM_mC6X{8~E&_Y9ow7&5sWbw*Nk3X)@Bc#G-Rt4B!}#=UvI z;%*7YM#qW_rwWOTTnC((fD5x>dq)g@q0GJBI9w9<5$Cl760mjT*m|vY4lwk^u$<&twXEXa`lR~ZV3t0Kl?s7JQ|GWWIJox|9u#jm{ERg|czTGi%R3t}4I<57ig$2rx2edh-qDY;($=zuggOux<$1 z0oMli`%@x{8>->{$9Gwk44%*b$*4&s{vcX8_X2zh!(YnNU2-M;<&HFTmuI=)-*Gi$ z297$~RRMz&e)cQ8|M3Ix5~?O1z_7|TjKA%v?oPYX=5=_^W=|HK^?ULUNZmGN-rkXg zcw#EvTgKjLI3=&Y3X2-03W1zTwF1` zex%f-*bWZZcV8jiM&$g_!}~tJYA#$r;oDmI+(#x46s+k(>vlD$!AkP#)02Pfm=)qt zQ#!V`sqZLXEEN4k%+DSH)tJ0`&x(z;e<-F2YpP7=iVawQK`&lFAicC@!$9JKE0Ygp z?_YnT9}M3B`a@85)8u%7Y%aT^@G&yK5TfNvbA-lnb<3;y7@px-PA*JvR*1w@=^REB z6t_0Fq7^v!*Xu_?7p^et&K8y{%wlP-nFx#T+M-kM_Ma2wCpV_S8!Wu^M@+^?#uapj zSnw?nX6{X*SYK<11i*kz-PP4qcXxL`8Y6|#;2Ac8;qJ-*}|7T%k*B=_fjwtz*=!rK6n@BjNH0fl*JQ2gpD z^v;m!AC?vkNc%SS|0rQ^FT8!ipB?-+Zh5f&JK^_J<7b?IC{&z3QbTAMg7Sx1>>LfB z5mct}Q?vzO@{4(2{tawFLng&I3HWC@kxW2$YFT_4T8H16LQ{A88!x$yJ zeDg)m3(+y-d+*-s>HSCzZw*Z9E-U+if(70WQ8G~>E=G7xpTNlI|77%d#)B)u`VT=4 z?|)#-KQeDEz(xj*9U!F=$*NimZI=#8QuXK8lGUGDjGi-#H~`-YS+7N zJdhF>Oh8b6z`Wv%f4HO6V=QlIVDPQhMzvI$gh|$9E5PqD4zCCI?v!Gq>-&cc3?))n z|BahsN&RWkW+|MUFEIEG@tYUmfvn4a&`g+xkjW_iA?nTlM{a2F|CAdNi2XY^TpbJi z0UEpb?d=bIf4yekV|HeOI!$<%iqP(}yN{VJyrUNv*R|ZkEtiXqQw@*b{Ap2OQl#~G zYYY%eKs^)gM>v+B_2yZ9Vj_^r{rG_c1F3yd9#=42`IUCvr}M>%@xzS^*5^5i$6HEW zU8R5%@J&HV9HX8uzTOKt)gogcBf!%8%X{ixbQIP@0%=OQbvuxjL!?G5VC$**^$R3k zWGS9=zc=%rZ~gZA3^f(xgv-+_%FEA=#pLEp28I!`=Noz!bJ!fm*X>&|2`O!{a&Sm+ z)hZ>ikF2ibY;1uv8S=^nQ20lg$OLiXr=z07g9}|elHksbuNkIJX=BnY?w!&KEdqMQ zZ>L1l#P99zWrqB;J_{Li?Q9ywflw#6ul$i?iWuXlv5`9!IU&-8g9jQ6LW(A&PGAbS((vEH&AB$P&Uv&zSg^J7t zcNY{EIv)zRwEFxT&B8JB*H%?c_}*ERZ>E{JDJ=ZhbUcLDmGXLq0}31EY?-OMS`inL zDy6wQpJ^{V06e!3lzHM}ENbOllwePIzc))gKi~+86vvL!TCmShWE~m~Tr9sgl6gwy zFmSsgrz|hOANDroO}d3vZPeKd%D3z7+o~yH0cd!=eIHf;nah2Nkh_zHJepY_{(wWM zljC@lE_Ua+L9Rn_gbF$HMM*5 z6S$N8`FWb)VE|U(gxQcFFNCRJvOQ+5kAh-7Z!!9sZLGK=i#ZO|oz~NiBRJR~$N`h= zMPn*08`q?#1x~_jAZg@dqFf1SJh&5J2j^^VgkLI)na?h^U}#^Jz+O;fHe^$?zZASN z76Uwpk>U!ew^l!j^I!dGLLf3V9r7atk+(ZnD|>TxpqrjHDJ0Yunm)1Aam&@6!oBfj zufh`x=UVR?hH#92d}C`sWAkBV|4*`?=X`8@%*}>j!k(Xx`TWJ`FGO2}1)$Eo_xa<@ z#2dW|Yh96l;MEr}WWY;32OcOXo(GD6jWFGC{TnpztgXAeg?bPcCQHu{gJQ-Trq88j zyq#|j7@sywUL=9?6sSgO9udL&D(8iGUFvMkpGt>To;DSz)?Wc#&HMMwTYTsr20E|K zVM+a8kVFgImXV9TAf04nWfo2EpSSphI(Sst=MNZ_M5r=n0Wl?_*$opp^I<{}8yC8r z*~z!nz9b@&0oBso((c?`Hoi#YRHc$QzM{93t8Vii+qdRVDDa(~FcoOGX4Bkkcxt++ z_go`k*d>$X(u7Ff5Ok`2iuu5Lc6O;^?<~S_AQ~^@M=F`#YQAlZlBi#()AqDDrI@+< z!dikv$G3CC%`($M3+GfF8>z$Qv^GH4bWTkz*7hWy(y~1ZZvp=!Hpy?<_bWQz@A&yv zA>`D;fRD$;=CYsX>VpHt#p3(2ieRu0Z0Aa^v|lw~&UgR52LWR;K^kE6;rEzCxsD$g zDfs_%oj1E?*zY~(C^g)kI61c3vj)x3dri^eYt#zeQK1H597USvre5h^qRT;>W$}jd z6h`^wpew(6*|C*xcgDhUe7?i+DU`_J&MzrxKXt?bS>4X{fnI{?m^)?T2VvT^Ilfcn zd^HS+vb~;!7y0H-M@lF$pAhj>?d`&xJ5oaONAQ}dWAzk|6GZ6B@-Qni2=9?Sk(QcT zR!*0{C;s))u676^4+12SC6&UvI*R#y)C%xJ^$OO&#Ujh??4JguG$Adbz~tI! zK9OFL5DTl};~%geE*muf{LpAI&EU}jkq19XL7JFB_ z>VR87Dc=DG$B#?9#?#+}7_}QTJ>nB$F(CX$=tBK(02mBM;CQaBruGm5zzf)sZX60^ zot@jf5T+9Dvz=_{?ydvU9bmV}nc7-O(UzkMe?1oCt9{3(Z?~ZeKzm!1cY@yP)XyoZnm~eEJ0W z0T8C~-Ii2tq}IMat+2c{$sbtPs;L3xvCbciy92(L`onh(2AsPa6W_Q`&Bqq`@;Lz^ zPcfZmN^Eo_DRSHz9X&V{utJo8&L8o`Y)F5|Jy1~8Zv0NaI?*>WdhHZ89QnP<{c@f` zM77dk6exB<6%Hg%fWVQ2gz;3Ln8oPFdU2uvP|&zOe((hAGV02<)k=D(<{WTbke*<4 z1QVs__0&1;d4Q(W(-kPzTD)2DqW`^+R8y@n* zMT)q}nQ}0fhd;?w%r5FIMS_b<<8LiD|*HUmoCC;lC;(&-(dAk25UHt?rbPcf2iw_x^iOZr}OV=4S;Gzq6$ zzNhCj&uwLoW8whDyrpJ^9hkXNofxm~2pSTgFv)hWmrlPr(6v2;gNZRhGywr!KvTEO zl$x*H)gbXF*X)y1=NK3zc z;Ac}k^ZR#6r-&>nZn*wZ)~zwqv&NlUA??QKOIZf(;)?7lr*RA}Fm=4jasGYsgyo31 z0`XJ)2A3TeKuugvua^Jpo{X|jf7$cv&Z)Ks*IOAsHapi??=vH@1j58?=o8MP=QT@N6#MSvWx~^1pZTmjsI1_YhIpr@Li?NX+VkB@^@8}hDJUt0H8tJazdcXF!v;sk@Zb*2 z80o_v!)DvZw;Qo*#R5n%n4gpj;Fem#yW>Zma|d0-*{)pEJniU4wY_4gPi<0WX50Nq z^Nv~?cb|{@_wyZXthz1bh9}Ktc)`IAHb#|y5+TLJe31H17*st7-lPxiy$2x8U8!f^ zjlTjMvGCCwse#&%89%sv>41|9^)B4<;wE@;dB`*OAj9zQ{)Hth(nc?wFmA>@0h~H znM2#p1N|z;B~yIH+J+r>4F@{83-l5&1MKXWlOMU9JlU)Abf|&gu#n*OBTg<{@GUg4 z`-?z>gI!)$J5^Y|h3xJca{MhQXTM?kIF(|7$LIe%ek9$LF1wE;3J$63Z-ZphAgwfg z=h5~*=R*^}{~?HCN2Zk{KOt5jLxTqn4y1SIoaJv7xw||ylZQagI}hUJp9m6fxr-Qk zW{Xve#CuYI&v*8}pJPD{rdqK7+XSg$`FqXS1t2nPRIb0LUK9Uspe78-4(F_m;*EiJ z$=^?6_}44b2gh9V)t!uoKp=4a>uc_2fU0+P;1x`@G|}K+?_5~O6uY;w^2T@(DTzpE z%l`E2@69J8oz_}?^-W=epk7s#i%s&%dHhs@@=#|vy`;ou#W#a{md68}IuMMJ)YehA z(pJdlA3<6K`MY5&s;kq{9a5)YtlLEc9@|RWa6{+7Yu<)PK-Z|%y+Tgi_5Zu4{u3qk z_r(ZT7`3ZI+}=I*Z=5~h;UJC4Exh}Ya&D$pcMK%~s|8SUwFjc&n44SZQ-Bix2hA2@ z<6p?kd_;3m5SdJFEx%{r!-`&p4IoK8IelpZWFl!`ScN!5iu~f^Fl^uxiSL8?6F6LE z9}}voetfw#lLE5#Pif>0^9Kb0vB_U;bzflLJqd0EX}Zqo-mC9>j8B%=Wy$wuYT%2{ zFFTT^0bYUNRoN5v_1|0oDOeCLIU>@m5x!@seaN$2=e4oumr`a6DILMz9(4&^xVZ4` zTqIw=<>nyUy{S(K!t0k`W%k0{JpZ3$XNCm!v7nLRKN9CS?{PDE5fwGUZfQgDd#yjN z`qTNRhhM@I5>A);cTkCKo^QHcgr*B5B2hF-5I$$XZP zZ8xr=;f7t?T&u*W!RafB=Y*Dq20vyi6+o``f`G1*6w@D<91yLtVB#L{huo65DgmA2 z;QStj@Z(tY#=8T8XrZ3ByDZMbd@;G z3bK{9=b+(WcV^`}I;JxY`Z`lWh9Pjz^W&yo4m5^qcf}XiOQC%CyZ}r7IHmIJ_QtZv z+i$E$T{2V7@#gHXY{S;gjY>$U)bH{0?Oiqr4_m-pL-4w8B!Yui=UG-DGyw@RvNxvw zTOucm-7AWOO1JwEi91W2SSH+wd+1Y!KHc~%0Ow!a0B^+7(XMN{52U%Hu;%GX44zZs8NfkBzc*1A92g z1sol}7F*NNM&<4R_eU9#&0bmHq&_4!e5s~-@12U%1IUNRUuHk-U#fgk^61E9_#Gv{ zX#Ew81JEEKC(I`_xk}pF1A~L_qCRtD-z<0Wj3{YQ=h&k@_?6{% zkIl0jXoLya_l!)9858$z*K*SNwr9p_gu(fN{u>@_iDUl1!@;++#76&X!^=<<=v8+m z&-d#xM~UVSaA;^s@LxnDA$4$I&|o~aFiwOpZ-=GFHD>#di>o%7UR{pSGszAP?mM-> zfIy8wWxDnDC^Dbjy|c)0OrlhX+G0VjD3Jv%W6 zJbmKX(i8F*G4yzuFvR4zXSkfJGm};3(}=;r5a$2g$y-SRof8^q<;37BhwHI+>2cs% zFx6-N9q3F@iKiSFP5~TnZPyYHNCk-e%?t0PKnc*`A-`N{|39ks4^&Smf^e%I5ivQ1 z0cl6IgJzi$FBezG4CtCM)_UCNYXjX%(54w+h`9WxWdQXCT|C&$d7eBl{pFKb`~DSQ zBRd({*TO=FH7e zI;Cb|;jq^KTT)W}iNgG3X%`ZJ)_LakX^??9^tq?zv#O0!t@}s-$j{UdqRsE_&Ho@t zFLN@;xi$0Hk?dtNo(_Z|kd<>d^$W}~FW{#wle+EF+M zAfP0VoUE|ga>dVT? zp1pnt$1{m9kk1tgQogbO6Ol!!yXb?Vpr9a?cxBtt?qwhj(vs=pIF^k0X&R?7KK`BJ z)N1Bu@^#y4=cpro4H@Kg zwm$?~F4!pVW_Gwvfm0mSmXXBvFmP37mkbRwkp9jssf1Osi9HY#9!_&(C~CZM+b{!6 z5(ZFwILzj;To3wq!o5-~7mjr01K@OIu zvZS%YWf3ye4dZcVDJ;bUWXq?8?v&DOcJ_T$R)2=3>_O=1)Owh?*Mjs%v}Q3d320Z@ zEmY(+G$Ou7nOV-0K*S=&LE>V|Bw+c@K=$SjR}uIiLk<26#?=7O!+2a=y)=NVN{A0v zRIDl~kJmY;>wHSHFrxm;+_yI~!!`!(&dIRIbQ1&N(J>2IA8_LTKg!+$D$2H98y-b5 z0g+M=&_O`ykWOU~>F$zlhVBrR7Le}lmd*hM0qO1-y1ND#;=AzK?|%2*-(KIpe%7-d zS-`}7&wXFlc^>Bx!^K)$mSZ5t_};xVJ1`AA?ToGs4#VS+?9KT%>ZvFhqz%60(q1@* zOM*eNZ-trU`=DC%uUn_-ZmBXVGBPQtsj1UVgP>~4To0giz`pd*@14OoIy&06EV|h+ z+cn;(R5;$ZdEHG0$ov~iyxM+e$Z!1)H@R-FPJ!RSMd#-x0{EBRuWL>YzjOV zt2)_jCTiTw1qpDBG#yy?LOtIGU$b8ZIgbA} z`^~qT@|?yy8qWeOrmq2F$XHf-{<9BHar2DLcsvh|G#hb@*Vi5Ll>!_Fo}HXhdAMH) z$Y<}FnW4L5^;lfW3s_x#81HSwbKH2&_~vjNM!r|+dT=e^+IFw|ZZvynJK+wZT8Dx6!wyj*dN(qdp+G8%;-ay);HO}L{>Pc& z`ddD%zb%T<<9#P!U2v133K%o|S+IBDq#rE&0)}cukiXq5Yh-JiHX!9KSTO)IhSn>A ze|(}^Z=54ud=*qF&AyrD*H?>cojU(>TFo;6qiV!^q;f(HHKJBnS7fTmIl;XF`$doY zi1{wV@0RbWo1sVOh052rA2&UmM;)9TiuUj=AW(AqHn!U}vT@kiZv3mWHz1}MQvm9c z;n-UrrhtNQK7T(W^MZmn4Jenfo;Yj*6SHC^Se@;QauyS#k>r)3o4XKnpNy~U2y(T4 z4-b-Z{0^j^fk%J)#>tx$ocmftJ>Ez^qky=1DcC7Kf$-_mH>-ifel)*5BkZ$-85Y{| zi?R%N#o9$Rz6X9r+>4uk1(?vYAd{y9m&Drb^`3>6c8P%t`bA? zlo;@(n4L1_ecv-gi)Rje`}VC0_@4#ehI9nYSz-E7MrMrb% z|J5tY<7e4y`9bb(d8a2gAZ}xM9G#9fcHcXffFfFToC~;h4|5YLaFK-=eN2Jt8w021 zq_)OBJ5Gqq?V=zO+CF9+ZFG0NqAe_@X^$C($!wJ z`_4?qnVZY(N96a}u54*W?DH)gQ#?I~>psq|tr0u@`8il-BLb3(e4(zi*0>|{J7DRsJQbvs-dAMP@D@z0-4oQ&{S;*4b=g* z6m0g`Y>ce5of%qu`lk@`H^IT!BI>SV`Oyj^L2@0YHo%&P=wWnz8kH+oa(?j@++U-} zya4k8tzK?H;&`}PxOU^Xp@A%4S<&taMDb`3v+0S5i__AouSJra%<3B$m;sw3?)#7& ze6PYn3A!Gq{fxyd-3Kpoa&kSAGj%x=!k#u3WoAwuu4&?sx)DCfsjYd#%8Hs3GBM1G zYX(ZEq`8unE|B*Kl>Z*3dQh z5Il#ZR37XeDjlR}*OQxGhbcnrv4tkM&)F^nGzYKb=k}0qKtS#%Oa*iq9GIS(K9N>t z+8L0S6akLrqJRG$_=}Dj^LK{0LUphB-0|({Pnt6P7v7M{vT?hfh0HYYv5^8xWstLJ z9oM_>zxunKHN8zL?hV)MS&+JI!-eV86HiGI1%vq{Kf_xRvh|7l2a~kFl-Y=JA#V7v zG-F9$@Ypy7+GUV2d%8by6c4jI74r)Z|AOrMRfpOfw38X0A1~t!`nEBd?;DK01D?O7 zJ>Mu1xIhhNdSu zia-ES`Ou78tv#3RKFSxT=`%wD*)mF?(yD7U)LI$_F5k^b!tVGMK3uF-q=+z{!|xr# z-ut}OE7lT=<*&oR0j!T!8F8?|FHNTK`i(HqM5GfVBUIb|etw1$$&XPm{f{4IY>2X? z(Kc<2@w%`%Bk8OD>d=Ge@YZ`f8bQ^r2a|Bas2ZXrT`+3vKGMlTrgsA8bt^e`KRkc&o~^0CLXq`Qs+-jDcOWra67ry2`J+TcaQwT zjr3VmagvKXQL}@EDJipZeM@QS*&qXyjK|*(N|F9PBrzgBUfqW1JvzkC!x<{L<2%jtk3${0P^Y8a9OUhE(i}+@0vHIv_>A zgGH?JiqQ5+Ps&@2-o(a}&g1E&tvitQmnB~tkk4J+;>`4l)?|R+EX_~=oo7>ZKVEB) zp*Hd!LD7XtI(H$kRc>nd@Ie9!<@3@ixo@FX^)l^F=sRJ56*?X51a5bSmQrt^dk0cw za53h4YG@ncK?7XLik;j+&=vTMV`GL`Sy+G}rAD1pVN9UE=iX=cUs`M64U51wwI9^h zy4k;@iI$A_>|ZuTW06Y0-bQ~3oyYj}&INOA@7`;Eh+v{DZRt6nN|=vTX9^z}B0W)( zeM{G=wDxN4i=`|yfAn>9aLq<7Ue&Cfdk2aWc5iGC4rgc~3N|;t11`0>TvyEj5fOdN zWMO`lzrQHRD>^DnNcZ@#uF|MuD`8%sA+^G&kr#%2BgOK2r@zh{QgBTxB&vlj`Fe|d z^+rB;&^Vf>7-%9-b#d9|4+N{wj_&TV((;cRoIE^{Rgvz4@i4ch<3z0nVuHi^9oNKDXM~n2|cC*Ev6a$f!PfmL+4P-Xu=+~6;~lZ@?=#y`Ir)YER_Qssyr52Pdu58XU~$A zI`s`S>KT{8=gc=zK2fe?5wbmo`fzBitYiHpC8evY{+ z>WsH<2e!-~9y)GH7OlT|y*%I23EOwvnIYOrjZsCq6*E3EA209e>MGaxrA+q{7-d_L zKEJ0zuYM32m4yMklWCgAIILbPWBgy63O|IGjPX$E4+y#^qEpLSQ!#C7vyw_>U}j~L z6cb}m#$)qrjE#-Ezw=cAT0Rd}Y)3~9$zlBqmv;`!dFQ=H9}Ypi8{wN>6-UmubRpCH zrtAJXxM-@BaWF70fV+e-HzDLx$NF}=>W|FN8z2-_kp3%OBNSNY?i)PFd53$ka?kP7H7lEe- z-Vlu&zxLn@h~QD&c3bSWFQXIixAikU_qI%d%eW^BVX?mXJ$}6$pL|qMjskwjPkg$% zy7F>8b3~U74PQn_qjL4ZZy_nKha;C(R)`k$8%O7@z-@H<<_$x8Pd@;f+`8!%8A;i! zV^hkye{jG?iVIQT3w@;qUKAoYoL}U8x`S*SU3zeTf5yyYBQ9=eu)lxKro?pR&UsVj+F^YI9&8+=0e#)|r;q&&zLDYnw{J0dCoNj-{P`3aIK(+j0;GN5S6(QLKtiQ`r<-6!LTE2iHoBXo6vNHUJ5~;u^;W)6gnj@g4TZDbTMvVp`FsEM_L>QlDd63B4$enF%2%Lof(&#~wmqzj?L1c^+T$d9g6N6&O zKwX5T$5Oz~}1WXe%(0Q=*Y~ z7+On-)<@vn4=^5IcbAcI7Se4O?P2;hU-;o=X;pEf&5{@%-XN8v22gvpN42%IIJ-Im zdAj-J2rCtp54}d6!&bDm6mWHmkBqGF_XoCMl+aX}u>w_FFF|B~ze#r-n|ZfL2w4Rj z`3o4;2>AwwknzcN6^$cY9h-f6fWWTtd_S=CkG#BmjfTZBK!XY{1#n_t+uIRvJ`|C< zk_uFG5CnDp7dmnfrY*c*sJ_=GeaPvYcGXQH5mcN_7b=P$uA&0oo%7kD>bs`+_VC6- z6gg#dr@!&;?A7VnvCGAoBC^4qN+KpF#|`*g0gO!pLHro+;JfC-vyI}$r~Gc$sqt33 z1R`yXS20hyhl)WAYHxDe>B-5-DVF>jWQ7^(0v&49d&%Z{oY2(Nw2!H6V@FW^tO(|_ z5(^QuKB4{l-hhymPwLrxD5(CAJgdbK|*2VjC- z3;a#MNqI_4{9agiy2e&rUEOtkC>N;SLO;CR)YJ!l0WV(s0yZB3%RqlzSy}lEyY1KY zmzQ4>pDQ5=i7Wb;oA36RU&3t-W8- zip>$roq+qNy@_vO|7rUieqMUz7Z;u8HFu?X zb8~Y8!T)A=0Wcl_&IG_OBkIFTOiUlQ2B2c5$9+vmVhmytOJ1VUJB{(&_Hm6@IVql0 zxCoDniKeE{y(@UQ$-N-M2Apz&y7uYmM0emArtEe5p;&NHlQOAcQA4dEc)2A|t*^6# zh7BUa_FMJJnKPhh!r3&Ehim`$rWDe$z9QE+Rf0;-XzfSYN{cRYa_`EXle2Tp>8UgL z@3H^bNpdDN@G1*D;&R%ZjfwHy-nImh5wYC%#ONi#{@g2JB=M6c`1FjdvtY$cgNuU~ zZ&i;sbUZs={DZg7apP0SjZ$F6pTkMdugwtNPWc$x;dFi?Fd3l#Q@BIyHC^OCP28I2 zp!SjT2nwGvFdzyf@El`~7Zel#7qtnX)t#IKTjAK43flrG2f^oatnHaNUS@=Y3w}dQ zO}(3UXT!{;0KZs@e#Uv3+8!i|*d;b=I(V$dHW&Lo@<*pDktS-1$ZN`{eD-Y2ygb*hs8r&k7KTE8~%K z6?qPVPBSGkA&HV)mYZHFA|cy}-1ZI52g^H+&0M*w0IGehUSz|if;e{c9J{&X*r;?;AvjOf; zb&flhMn-TQ9o54Sl~|2dnQ>(#_^^Of2w2<6%{;#`i_!8=;X+DEmfYLh-d$Mv{kl@n ze*UMklM&=QDbxO|r1@8sQu|MpQZ=D{&1O>bT1A{`pyxu@S6?VB;OM(@@;B z=$X7kV@}1`A-XzGf&EN-M+djnM^8y_Oo-{;-=3NJ_3Ii9zG$?X$J@855)8oPiykfn zA}xMNIWoWlaO@vOHz&>!FfF4>PASgx_I7~?9%RVH!k|6v z?S((n->3H2Iyx4+4uU)mg`5-j^4qs>pS*kaw`?J7GH|;7=2I=ED#-505oZX+&&X{5 zJ^#73-*bC*5|UgK6TB1@!p?qIIU;oqiq(^0-H})s z{QeT9b0e(??alEaxSlr#2IR!*MJFig?Dv2G>|4+{5rKGvnR&{$^N0M=a0aGgpH&Ei zAM($ed+5vh;qa4RgC8NY1_LfZMkeWxA8ziK=hHO;&wl)PXlJEwX4uxI{fue%NBUq* z5`u+HfC_4@G}7P70RQ~i?-k{nLt{N6Ylq6xWy(9c|K65kQ#-(}EKxyv547ai*N35y z5S+W_SJm!-aH#-ZKEEgR8&6iuPb;h!Eb8n@?F0sjL{L0s4OCDIj4xU8RDgsBgQZEv zPiIN>kBlmh)bGD!xfd*6N?QiVgy2HA1S05(Yw2A5=FD*V~q(gQBp6>e#anq0zgDd z!D~cF5y#Vy-GxHQ4Rk^Nb_uA;sk_ENE776L z`K(5x$cgqxMWlbrQ|To9e5&?#|M7eK3rjHZ^L1fC6Zl^-wk1XjcQGuLUvQH!E#9Cuv3Jmsmj=#Pfyd%N-5=*hgL6B zSFVGWk?XNoV7)YKesj}Ty^1ZCDakKR;t4szKAw|DUG<BPonM&g%?OV7KW@qHZrWF``t0aZoHei=eUyL3AlulPEPvSl zB)qPuWd>qr4BPxY5i67V6&7{<%GtveGxC*v3iPtOAXO9&y1p zA%d(lHcw{&_k$sNWqAoitY@4+WW<36twbduJf7C*&(A)=!8ve?8_U&{xq-E_nF(y* z!+1ClL4&2GCF>{eMMOjfy{`X20{j6VAL3m}wae!lkQ`!f-I64&3IUfjJ&MP|X4j~c zj57EW24!{Vulv6tGWnt}A2Se{`I&@)mgIwYxPQxS6O;Y*^P!wYK{i5are zjb5$Ssi}&?7E6jw9Qke`Gi|?badVayFFkI*@t_mM%)GRo+UkLS!<6K2-E-vh{{1sD z>9(PW^ulgd7P$!DlMt73GiC2O`f-hML4x4Hdd2)SWj8SM=u)eYtFJH~mHKF*Id>B1 ze6=czrjHj0xm&f-&!>O*L{v&j@km3IV5YlcN0!p_omk$Y^xdDn5B@F~d`TrG!6nS- zG&T8rvFl?o2_(F|wpIh5c_cfBMuC50*x^Mv$c07!vkSDc&N+8h7B>}Da#vht%R=UK zow7Hd@_Pe^vdS&lpo>>GhuE)|yp(n3XFbpRF`#Eqwcl1XFoo|?4nLdrk4_I9S1zr7 zHpww#QSk6z&VTX($bZk#X5`)pGp>JGX)wEkGafz+22PX+Nu6I=StJ`H*P{*4fxtSI zcD{V>AfZ-%&SYaP8W^CVr49*BkZjSZ<4uutkYy!XgN9ty}BFKVtYU z6sn29Cw`A~xt$%pOKt#fkl}DE`t&eYH?4E<%N^tvA+ zbBAUl6X)e3CyWLv%seZNoy%nx+FXy%_e@wFEJI3@AAth<`g9N7`e$mh<#J?Nrh5uj zYLC6%@qM#})=7sA?p2Qr&5&(JLwCvR@tb6}MDR?tzaK1~y-qSQ4R|mVINLM9hiUlM zZ{TJMIHklC>5VsBfae&|ti>->WpNS`X`9Metj~Cb3y_vi8##SG3XRrEd|Qb_W8 zo9kX02A>WP@JMTG$Gd>fd@L{bdu2WcZF`%H<4!iZaFTq6Mf_v9NIF4(y5_)WPG=;M zTQiwcwEuj~>x;Y4=epZu0V?${C6yeyZ*q?E7t5L3vbirJA2w)YSL-L+#BPopLVRyv zxFm61oXux~*R=n9E2sH-6ZzD-NJ)usJ?XRG*S8Q7iZ0QUZ*Cf^z>b%Q`M!6uSN8Mg zkl)?ty#?C7vVU464C&tcgrm) z`|Kq@uIe$>pAL@Juuz?r=Q%9mVqGy?lLsp?@syCz-2WxDNbf}4oYk$P4USc0vR`SE zK(EvOZ~Bk5!T$d0KM%jPg_JEUjCcIE$yQB$TSk31fold`M%XV}itcW{fxgoZLM!PE zl4D_V=q|VO>e&mlA+np<#@eL9_yuA2t)D>D4@qIJxp&Vd_wYx0cp5AryqT3{{{AiS z%rmE^N`a>bWA|8>GBb;dH|FI^a=O$i>l;nkc=X!9EX(FLe0DJ+J|ZGI0vwyLrLG(} zwso^pA~eo(Y>~D{0y@9&*(IJhMe8E|F;8s6+Y%J`>8V_g0L#?Ou zn$r!xl>I(XMJAbVNaqQ*A??pL>?^RN3U=#oUOAVi{TxxEA~fQ4mMe&RNlk5t z4weGhf$(v)!w&0`WttDHthVI*5x`!W?!(}6cQV=iHzp$z!lf#vyWkChO}BngAN<@F z@9}-$L1DNeY_fNHMfJM_=Pqt!7kYedDoc9DWp%dfryz8otfXCBG<10dFZ7LofB?N@ z@%qSxf_w>|9Ijl+tm=Bgyc~L{qOwQw!$(iGQtkRz2g^79qi0J6s{qc;zkMZ_o9)5j z9cT?KEIQ&hgQfAt0$z!}iI$CxgPYs=-hz3>SRxNSFRytmphXX4tgf%M#N~hg*F;Z= zPO40)aQ-GnU{Qp2Sn2F-2M6c&cCajin;=i#?Fdb$*u~hW50rt?9|oWl_7al*e}4A& z1LM-w?)V2tMBq&Qc#bTE`yF4K+cHZL-U=*EQQXUC9-i#!4 zPWJmfK8{mmX~v?%iwkNQewPjQzqRY|?*fCs`YL$0TBS^c1_{qLShaPntWM5v2MCji zWA-km0NX;`wV0Tknx9g#na8R$|6}s_lh9$RgpZkXu{Wyl@fRWP?N_J+Gx6zeF&efE zxi89B_|X$az831?0Qc#uO3qZ~DN0IO7v%xTytTEpLOu_3!`yG>ct1g7F4b{?Mv8@v zzHnkbmynd(X_%FrA0Xxcg#Hw#XJ{H1q0rzB8#=!*G&H5D$CLB#3&8VFo1UItmy?8a zhb=E-a&uxo=IguIx#$pNkdPp&oj0cIP>P&TQ1A?qy?&Sa zb0jLim{IMjEVZmG0Dv;UWM8>JMe9rdjiGW=ZN0Ys)VH04-4xrxrSQorcw5$qsa$AX zesQ2QoJ^XBVgaMBIZN!A6wIwDLhTmbZ}w=0O=*Nn3v89UMz43g(=nE8xt;Qqtt4zY zrT5OO((|{;SUFy}f$g2wU-_rjU}%}r>c(O=LS1GEhTNYf(k6BqB0HDd(TDSUPOz7c zR*yFOCFSO=?Sy>Ytk=$S$5vgdinO)Lckg%!hWI)0lDRU+`+*5&5-yXs`*cg)puYf4 zn_m+gStJ5w-UEN;H+_e8ta*6h;#;nk@aR#K7NTNAc$((z_)5hQHDbU_*uZ z9Jrz{!$iX|#<=mQ? z!6xRN$~J1`No@k5i{%H0)$SrOQAw717E7C(zn5(I`1t%*f#)YpT+ZFBA$LbdGcg#e zQ8yWQqO!LhuE*QaVwnIL1f)RW{|XTJ7h9S2f>J-*(EVlIJ&3TPYZN&d?&^veWjE5Y1QU{1TqwrUhA+{Z_(NNVA)*+)0P?y7#*SKqI7ZSfnHmSzVlSD%uKC8VWh6`N=Ji}`C|A!nia!3qEf*YEf3X%qsUK#~L|Nj=YL2XA z-kl|DO#SjjsECx)&&U}}qay#erX*LDyxsDqpe&m;3Ix?=8)8aK*Y8pL=maNoYMO7H zlP`aJ%E!X6eL8MSwCy9@70*-Gg&jMPwBH@pp}+q1WqA9zD`o&8U|8XXN>EWk{b9of z``~zgpC9<%!Ub__C92inYD?s<1&PhE5~~1!|R$S)eFWj>hzlP+$M6) zLV4J14%~oFmg8@PER96~p}YbOpL^{|dhS>|Z&_n}s+f{7~1 z&#l%OFBJIQ+YM>XZRINxP`j=n5XwBz^|4gVT>Gz0Oi*r@&0cWzQb}TSx-FuB_V33U z506Q9p~%KIzo!4{)M=mryKLcV1Ig+}@7l2Si6}0>)EcU&Dr~s^E%SJYWbm z|MaP~6ECT}d~hXu@1GB{Wb*>+{Y5NMoCRTCd6is$$!?pjd!I}1Q>vDmQU9o9fb$lJ zk!6N;(>g~$W`z{+M)$Q5E~JDW#rFAxfExh3Wm(^_(ABbCOuGsRHEUN|IuR1Btj@}m zT3nnR3lys(9Hv_V_oT{vy1@v=h)4a+V&;6%2P+Yvjhx)LW1G{liW%SrOjOIE0Ii`;uEy;L?%iOG zA-iD8axRlzVmU3+_Ec|}Z_B&CA5B_^+7gJ+{=T!?_wlkRGjpWIp2zO6nh|Vh?3SA# zf+QV`^L-y4Df3#zXqt$W)CO})D!i)P(QA|Bw>fkKbQB~%0uUZNr_12W1*JwDHz3Bh zn&%!q=6jyTm?r!%S{w$NesHa6CkuEwZsUOOFi4;IzhdJ(|L*Q?V#zSMuE0~g(iV&e z@joq7we9!80^K10a1SjdpzaWl@9h!YdC0rDYFA`b4Elsftluu=`1t*$!|0Th{nlLb zLpkA#lZE!mBrmLIQssM2N2A*2I{V%6;B|0RR9elEJyC!DP93Fj$awo(7gXe{+FVgI z8$X8971kR9|JQa`*&zawf(^J;)e-q%Hfvz2rdH9C4m$YYB1sKPbY&XJ6XamGQ$#|_j1 zUuVB38Hv@{WbFP|-eU*B96BaOPJ}wG{Li11x7zrL(N~*&FAmr(9wsIx-^P4jsD7n7 zrrDAO@R13-t)`w%qwJ&j3e%+nbjd$d#!b%0-=22FVF(s3&8-zKi%2^Q44JN~UEWEpg7-?2j3ixc{GQRSy0YGU&NEDQ%^hLW zeS+q9*Gg^_gu#n6N{lC?5YiETaov|nfxV7Tc`BdN(;uu2)@Nim;*i%$B^6j0ud+Cp zo2taD6THCU<s6>yBSnxdY!NfT$NHm%R1PD@IJN>QN0p;#Hz@lUzMH^|h zK>b}K$%=BUr;14Z&)nXjxyAUT){(jS{!9abkwJUrKy$EEfX{rwGXnXpitAxq2g;!0 zU3)(k2^&p?q2;@GLlenz6wnN3XV}YVF~u%lX3*tnTOA#~tIsW9TC~ z^;%tyYY@{3e8)^)DbB*KEbv@>s*Cxde5DsIrUwIiq!z967T1 z_#Th+`-H^AgXN&4prC&>a%szg16^GpoF+3imf&vb;;N_b8S*6$kh`E;9-v}bxwyH< z8MPEXJ~s#;r8Gb@{8wm~@WLi#@h#@3kVpTs**e^5zOjMY3Wwr@)c4$1I_2d}MD!j+ zq@*QQp-B}Lvgz-6_4L%qlYx)sL0C8d1{OldmHA};b7toCTs*iZ3eaT5Y-}G4430;r zscUO9L@rZNbx=O;&*~<4@*!4NM0C8NZF|aOZcb;gFy1OzAJLwP6&vGan3nm4 ziZe4cwy4I@iOB*D9oha&fg6y3zuhA7@%rZYNT-kH%x4 z`gp)JwyxIHs_C1d$Q?)yO*NYEpF;KT!%cIu)B));h5PySv;~ZT%&rk7@HNEt&u|40 zI1Wz>;jK>!^u(4UFt72&)hzPM`-=!iY|R*U>coU*IzU; z=6(clF(3)etCY*6-kjxijw)W(M! zc^2YF%wc1wjj#9dqt)`_4gpD#HN^^b35u4~K2tX**dy;t=|b@)Q(lhtf$55uA{dnz z7}Q*559_3k?ubmyAZ*RRhP-Z9?QpbDuFP1h+*_STp_mzVg)x#u{y4|7Xt^l0_}yRD zYK~lxYJ*5!57`zs4k^DHO=M~DxF<~g*HtrtuZ- z@v#FaVENq8DDsoW@a#(!U0#~H4p7MT>)hbm-YEAbh^ZnA2xUfF55q=m%!WFoU+!`ynh)gMC zPTR#mJE*IwmL?A68`Yb)f9_L%(J$-hxHZ`y#rx#ji;^nRle36RB;S z^W>dQ-wFmp5>HD5`O%{^PDfyO23KXhxn*;plT150p zY!1!P{tkFQU=lWH|NYDy%>6pt*SSP43AH*_DiDU*QP4{em6Uau`E2H6^8iq3Nu& zJOuJ8lB(<+^pK60$LQWWI13xQM2vCDcHs>TA5lGpXq;Qe+h}K9-GNfd2V(5WOdef} zq6^XV+S7$1$-usnF5wEHJGiSO1eEDV3(_RDsLd+VZfWh_`@7_G*#(oG%J1F>6Zc9n ztCiXr^uGYOK>ua4k^8~)$`{|YwRHxq6AbkR%PktC6(79Dgpspi-a4@bRLB&*Hy~p79d8ER#j4 z5_fe=!155#%A@GihryAhw*?n#RY)XC(&)Zo{~i!$O2#1~atG>iMi=akK5550mQGK@ z+cx+M4{UDy0}n7(h_4?agDgLGFVYdOzx8NoE5+L zkIxVqj;~H9Ktt0pF|oWm*BtlYJ_K?p6yEzr6=jQRN zgy@HJ%k9rQhMtHW!H>DqDjk)2|Kb7=5(dc2$&H%`!}K-v@fo7)bcZLx-by`)X=#Gnm~bYN3*yU7kw1-d!X|_pbL)`5W28X0Sk9?5(te zf3_}W%fW!Fg!4zR;k-v?0U@n(AMh865N{$-yc_R`F)#a5*Ns|J|3V@&mJerBP!9q=H znq9>OfAI45d$;s7ZylYvWY5bQkbt}I0IGp>)18BMYDw(fUE}FO@s)0pQBnbG)1~#~ z%d%LV%hO&Y=d;4JNi@#6_S?5!^M&(!^aghMm_8utXPUB)D1p~``FN}E^XJdVivCQX{wX;3eraN(yA(;CvZPJX+PSi;&yW0x3?b;M6IK=#Y`1PbQC08QmZ`{2%|eK zrt?~|lRa!!f|=O1kab0oiE;b$#sH#Gpg~vB#uju*E(N6?#+i~=0l-mx*sQ{J3>F;XhOOfD>szc+qG1d0UqS1N!(0`>`C|wox#z7zt9*5M zv{?htUfF_oy1H^|E-qD|+N9i?tU|lW%h{;8)J8@wfsba)6P5w^cWLU*&S91yqYNix zM6cDOW@8iyK8m$V+gK%40l>^(Tia2)ViisF0~gU_VF3lt-=K)FdeZaNIvuz}3Bx!n zXMhjXW?zHmpW%%>G~iWHLgn%BDiabZwCLCkd}T>#u&>b~*X?(3WD>&N^~a#SLUNq$ z5Eq50XuGPaDw&1aYq5!POT}fXBR!exON-Es>#Lv7DOUf$05lD3GybasnY0}avH6O@ zjiq9b?>GCcu9gUZ&p!r;gLXra#aea1TI-LXw&(kw5dekph-u7`zIwYrCGjGy0pU)| z8Woo^d4VzT08=dD2rAy>F#Ep_1tgyxRc zG>AcarSz66+ydF)x;F1TUF!m>GdK1hS3t1yn%!c&R_~}IvjI^%IiXU8+CR4+tz=Gr zpR>wN&!fxT40mt6Ey~c|919L*Dy!!t3wd zP@;|ckoO6AfPtp8*iee+(sh=+4SRd0#=d7$6>RjXr;qaUrh@>UdZnQQ- zA5Z7$fq4iCo8dgje*qY)^Ao%}gURCxGo_quqm^xhuD|}=`3_(-Ch-Tx^Dmg$TL#h7 za4t@Kx^%aks_vdSdO}DB*qMOq8GCW3>%;7;1siBg3KXaOOUJU z(JbOAuo53kT3u*2yo?*q@6g~i3)c4|u?Eus-v&zq_x<#w5h zX3dheT%UkZh5KqC8kXCRz#Rj7<1n=dZ@^w1$`u08n!KF+HMBO}8&AU?@cEzX9Gq)K z?-KZ|ToI;{Qk3wD3WhOQvlMy%w3ijORniMo8E=7`?c_;JY+R}5T#j@%5^k84)x`56 z@@^q#=&On8>B8gDTcD3x`8S}m_Kg%`5724X>p908IM_;udj|C`acmj1YBiRrAURAm zFuCthRCs|vpSw7Gnz0CoDtgv=#e_b$Aoe7sZYQ5C#>%(mn#Yny3$YvAw%gcfK|XES z$No|ljvIpBnIgiZwiahnFS(Z|-tf`n0m{yQK3bhVF&ZZ&7|?bzJCzwV8)`M;At_Bx zN9(wu9v5dYAWc?E_cp|&8Lxj%zXkX}bNAp+iVXQrOgH;(Kz>-!1De=S3z+loI)MS~ zGYbo?P@|9D|1fp19xAYQM{daia?JjV8<2`=KeoZlU=L@`5!ZV{k{ zmPOH@Qc>MgE`BWA(;2P0w^zx}4~qaC4g!>0-ZOPsLo9xjd2>bn4Bpoq~2gVmdf2otb^%m+- z7g~HO&Bhs*cHK%DhVW1gsfR)^7JnEOq3yRIzFov3{$n608Qj zCU*D_PR9QIHsBSY1N6_?0OtUz)x0g%ZU3A_0eh(PW7kGg3cyZGdxD*4+;S+FL^X(7z70;b#`-^P*JsYpA-{D{UXvF}l%lygm@){1z z#{VVbYf)L1MtCxQYF~qqwD7(3Oo9gEy}H#^Kkq>BJVC^4fE4^c<<{$7|HZ{<~UtZ*TqMpWIVfSJd`7QxZLf%Gb0S2F(1ka zo!@y1fdu8-t2W#R+1~-4FS8rmT`VTf1GB!|1T5A6DzPT;j?&Wl78P|N5WYVyy3Vdy z&v;C}tMqE~e8bWqN~$h-l=Y!)UgGF@5@GEpLIsMr@t{lWm+RJ2-beq+BuDVu#lIhhcF@5c(N)TtC??7G^$Hhcz7boqFYTvd) zt>qOnf_ZDT)!se_{IOq7Vs;ydSt_8TZ=!%+H~Up^c{f*kWT^=s57$bFfV2nC${hoD zkhe>9+0qGQyJFMp0ybj+ekd#~0OFFWopVL|m!SLYxzY(XLNjclepIrLjJ{(yJ6uKc zUJAG0&0o2D?UV2y1XdLt7X^GIK$wIB)4TzTxb7RdcKKJh&K$xsIS%6~`lS5Z-D1J7 zU}+UfZo>^-nEfT(Npg0|c6gwt%|Xm1Cwoo4qkCiLuzfljn@0ICnrD8iF>u`tSFRkY z&fex?0;RYz%|_qvj2S;2A$Iudi+7g*XrPiNZZtn#x8yt9oOb*OwtPjA$97yQ&mRE< zC#-M*XoN9j4}sM}=6j$3G<7*0RjmUlt*e!4K%rM0R~;ol|B^ zp%g+w{K=kYUE|;sI6sK`vGExHpU%0Ri-tRa$?|Z6;~o$y)71Dk&q>5FIrL1O0`29a z{&z$cZ+pHBxUtull8Wk%|Z>Ur0oCchk8 z=n@ejp~Kevbe`e^S044MtRil1UZ_j>cc<5@9cy@PJxO&y1o0?dC%<`Y%=oueW6c3! zWG#DaMP#PGC_=sJ9RX!(>eWn!qUlSR9eB@6;zVGB9_Z-UAC#VIOkhc8!37sKF4kRy z{9$29{>FDBX*MEk$j`RSi88nUwL$vGAsP2_v?7ISl_!VtD4tZu@f|#xXF#e5U|B;j-iO2CE5Mql zxy;s7jTqN{W4SwOV|A7DO%5Zz4`^OEET_lob?D=T@;zMqLPJxp{B(eelCly#GxKYD zK7{%xLw0WhATw3Lv$-Eb!#=zOCw_Bla$!(bG0AS*5Ze;PVY%pb9`ci6CUJ9X%LTQ5$$NQQJt-;)$cSp~kJy=* zUX4$J4$Ne2jaoYKb&Wq|czY!riLl$Nx70*{*h+qqu`5t%D~L34+b?haym2uVSF2V1 zD1*Dn-b}gsVT_uv?_V4|WgQ%Ha`gY9>@C2eYS*^mu>%1C1toPz=?+PCP`W#%JBLn- z77&nbq*GdIQ0eZDAsiZp?)dKE*?YfxKkxhh@Au7dJdfkJX3bjny5qXf^SsV0n#a7e zvFGMKR<(=qJ`YEw<%v7r?5(t=$UHBN3Wr}AJ6S6uMZzEuwsdtnt~tVr2Berv-|woX zYIauEM<`*B)e189Sg9ue8m}{}hD<9UesDgT(|vW$V>--(%nA8K{lNF(g9q3Bw0hdR zRmgx4DIzgZ`XL$lWwYnD{4-xy(y3W;iooM&vR&B%RBO&(7J;PP1wkMUHFci+mah4^ z7howLPT$FanKl7x(p)A!CnOFw=v>XoEhA)b-J}}nx0?+Ew7}1X0nM(^q;0S4p+;i< z^5k!?KI#b8z^f)_zT{ziUs5s?$I>IeybL}ON04^*HG{lak;Hepi!j*9!8$V?UChyt zWwBb?vKj=!`Lj8}!I4hzBcn7I8Q6*`-7q|Y!N{H;nPT+f*z>kxD5wbpvibDa11W%9 zLHif*5#84z*&LS)1D?F3>VkcC?!!)iw_LkS2=MnG_Q>EeDDQNH6fS~<=^L2P=bgXl z#_Q$dv4oh2AG%nO6z0ox1sLpQSZ&Rsx6lJnybeT)DEN`j`de83Fqqf|c8%V_#nmm=uWJ4czAk~es?B2lO69yk`&8Dl zW&?2Lx$hqXt^~Xsu`#hJ=+}Rx+5iqj`!cl>L!?}?Fkm(rxg=nqY0~f67lpzR0~wbn zelA3%A2cSui7Vzsqx1lhRu(2L^$xKgV0lQy>FnP3xA}bo!UgCqj52ZvjUOM0AIv zO$mR_1>!lJDTjN(v*N#eR*naMZj0Ju{6HYemn7R4?&wDRfBU8T4PQt>y(!4beT=wX zgy!|vcv+ii^XE7q=7wHgT3J<@{mra_M*-*ulOI8-L-clv6VoaF%Dt8p1{ZbREA`)) zC(v9g#F(>DaQ!P~8WV0^n4l5!an<9z?}YD&-~J>h3VR_M<^FxrT~)K(e6{dfcdm_v zrp2mh`Ovt=n<)|lzT5kM59vtzuhi*^SjmU;)fOS(||h-BB!3hXSK=h zz`}`-2eE0(cW^a@3G9brlUWFY7D% zQUbFm?Jr+?A2t8&fUW$3fYy8?N$8xq`tqPEa~P?kt-#96p=5l;9#`hV#*502ug`hS zw_>TQL&iqMsPGAcy7Y?+^7A8CiIUaXw_D`F5nmZrT?DWE{Ks!@F*1z_Bo!Ah#do08 zHC6_rn`Wh4qc~3IeB4MCUpa#eDlYqp)U8{Uq0=B{4#xIt2MopxHsyEeJ*oGWg3h5T$hx=Dg-0qb0l@N`Rz&{5u z1U}@#-gOwDzqEyV`f1vhcmN}gpFjQ1j<@+jV*-=7EUvwyK}baXh0yUC zZa)tB=}^|0-GP&R&bV-nCS2;ZysfRrh_&diU)8>iUm_Bk^q*CJ4hUEt%qbOlyRb0q z22xw4P{6ALl;sA@)L5P>8l(o|%j@Wn`!yJKXJ%%CbRv!xPz2ZOVCo1YL7NYEwlz1a z&`09S8yTV2#^9Y@OqQ0Gpmc6WStlr+J5p3%RppbM>+$MWUZdKY!NQUc-<5)ZoS3a)!K^_C8s!l#rAx)VgB_KUvHQ z6OW()T9dH@Rs0-!&}Ip+PRy+nFpdJDA=#3Z=m1a|}n%$kl8t zoS*Bw5Z*~hv=|XQwkwOSi?$DzqjPX_GA8s!cbGSG^8yp})=d!&Z}IPf$@<6>2_^iH!#^5Nawat$Ok%ls$m5e>kl_ z4}ksd+e&il3gFxPTnETaf{_p71q^2(eN`A42N(LyOXI0$fYDLG0tcFEv| zYt7%KJKUI*3r`Q{LkXF+IIYHaXS|)SfctC&Nz;ENBRe}dc$^*<`|7#xF3B_PcpYx) z0rKsuMUsFkG)xRUuS1UrP|+eF8~Bxu99qbv|0>auqHLZrl9l!#)R%NH9)uyFsLGd)rww`wofnTg>U1vD+F zS^M?zRzPc06E!VuR$AHze|ma)dpKOP%v4Wb9~vn^jeiGE+1;tacoRIowzjr*E@ozC z{bF7&u5x2B0L2v-7lZ#x$!7&{YE?HTz%HWBiCJ#42{!(fhN}VtG90Xfg`>~MgrvY)ea-b&^?|D z+I#lw89)|6^m-LfT~jmk5gVw`BJ6g&YxeqebFo3ogJAC?R5ZDu2l#_1GkX9AGc+@M z_TVnqO#1ZbR>$=`&inf1%gmoaEk1GxtK}u0Aye&DP4960nZxs>3V5;nZe9Rx+-JvU z!#O*5Y$&jcM)LUyi*wgAa`N+mIQ~;rRg*K)O3-EVRwg^$(jpcSbA>s|ZT5FlN=kBU zdlBN;erI?FIg-=3ap|8(wH`hp1aUIg|)7}h~s_19jMY^-S zL&8W7d3UU5VgVAazbO(i)g#8n#+$Wp3tpKH0Vn-(8}z`TCN~pEOB_j>_Lkybf0BJh z@G}_6F0P8hptk87N`o*qMtdZkg1$;Brv21ACSLNy*U4okM=!7=Qb3-@x1Z z(UCc;I&kQJEle!;lx6G*gNEL!g3Q?B%eklA#L|Kvr7jbs*WhNUCX7<={?mC&>TJl_ zm>-vvbz{GP@YPLS-E(2+TwJ_6OH6W^6+a%eHFCDp_R2`7l=eY2xKC2OOdJ9~^k= zbu6{?>Fn#2-=%>3ygwrZwkU%f@+whU^nwe+8uFBccU_~RNZHEFf5#@a3@bJx( z?55V&#>O<{DdOkv z1{7rzC4|bE>`$V?kledk0AwL>TEm2_#iN#B9;LgRkZ7cDXXoZtZj7JGYSc3=baFuL zS+59vkh@y=zAPQ~HGz@onX8^8n7%-cTs7lW$j=l^hj>>#H!(ppDgITG3N<#?xyq7w z3p-Hvz|~boM%~zWp)EqHbkOsJ)Wb6#m(&!soOAl&)O?DIE;v9uk-eOgX2IvF-6&~* zl)_>p=m(d1=_!Td(#cUoB<}gr6EUHx>|9rnR(i#1-q)`ukvwO-7`B4qM@LelCsBMR z+<30tv595y=pXr*XJUXS*?|PPHjk**F0ZV_8hKvcdK*QI`1q=x(l65Dhq19u=9E#0 zwlA*pVab z1bec~VM+^&9gTDCryf=5SCE>zEde?j3BI?YaZ+@95`c#52}B(BLMiO81+wVL)7eka z&U*TDcUaILK7D%4-zOO~usPFzh133W#y$UMM7kh5%RL@o?j$=_x)`Hr(bTm}pfml< zeVojANbyrQXHOYzuAe@|-2}16|MMb4qMWyq_$IN$*31Zp@39VvH7at^8)+ukk z8^4@`_ClMCLnUip!R90I)k$&ilou8$9Bn=9&rgFRlR>n655$rhQAWl(q#XM(nydA` zI1Dn9lu$df3TnMS(1I3eiq0y}N{ei%!{gNfuk#lLTC#hao~Mh+QbzkjdG*fA zZj*J|iD*`ud@!oG!6l_OZq9op*LJ0mZ0Vh4mx1_mBRGasFx zzacN4tk3+?^&F>gwxNxd2oO_Ie_ZY`##TApGBrW%aaQhIo>#LC=@q*i3emqthn?C> z69^l8_eQ^afp=}OxG~3h=uRhc-nWzMnoPd;5QtN6SR)tDfex#%*@P2RViM6{GJCZ! zHnw^L`&a)|-Ql!gl@fh>J3gmPA+roLP`j?#YLX;^-0&XXQH^?@3NihS?(VZt^4Nr< zl!lUw;=GajVOlgnTD6UioiV8X$;)Sj3YG0{ zE>#$FaimlSDCP3WFMqt%w7e`NNk}p=ddR4*l@O&86`P}%3S#PmvHG4gD^0}5Yl;zD z9a|yji=Iq_H*g-FEUz;M>^l%*+|xj82xRy5*GTGUiO{NYl|g@>mq{7aZ=P(bmu()G zptOawoA7R%vz3I!#s>JkDYypTJdiwe3L2@c3cV1>dfL4&iL60bx(-M74%^7su1rMp zWt1p@DC+xBJh*Q&S3W$TBJCT<%Iu1JymbUK94jO%T^5~!(SOY63tL)NL?n`NWMZ6q zDv6AZ)N|@_K)}tg?e4qTM!>+rf+9|QdG%fM<$i4m`(KlGvD%PK8_J%NfIzTGG~~(^ zo)OQDNB4gk9<9t-?u}dEczHc;34Oz?Gso`n1KnISsO($2SFt+EhS0SJ>t5pKHVu?J z`UGjp2zH*s9xu!l*uu2B6*km>2Ng4uGIJd7a}^Rr6=mgu*I8d6DO*27K&G3EtD0qn z7PIOD(6QKyfA9*{X>bI{#gv%XB%hsxBak2-9daBI(NKU&$V5eb9b}f7VAh`Q_A;NY zE??k{g^ihP&0P_`a1f>w)6Sq{$XlCj*q4k3rE@ySg-)}ABO>S+g4DIg8vLZ(3iP0n zxzdK?wXUw#g=HX^PJj6D;n7j1jLd5fgcglK#h8+JLgni@X4A2frH!dYbBBB4%+V&7 zk#hSr5zosxh6NWnjhK-E(f+fZZeyL_O9G~Hr`0_jaQk+44i4EQ!?+AkdVXeg!ed5+ zK~@$;^hb5I@P!!==6ub`U}M|Euc+o#bV4&%dFwKV{Xte6Sz6>3hFcgnTlR%XQUy#n zvvx6#m*M^R(O7g+OGi!Zwf=lzR@IRpM+puEJxtR1Af1dq_byh1)7p0jXCAmC>h|?! zCnr}P8;MW92hVPC77aSCjq)R{pUFt(wRUygz0=1<1(gsJv$Z$(S{r54W@d#93S(x} z&F!*yAtn~?eYN-sHi{xc^Q(x7-}=Spw{HvZIH;W$Ou(p^86>BxI^f-w(Cq{cAShtA zQwK8zi*y%n?17B0R2=D_;pbqv{%NH-q-@W`SnajHd^Ahhy}L2sA>!Q>Q|Gka8Q}ke z)x6B$Ed=-?jFo+}2UB17v^zQ}OZBBUdVGC3vUl43;wZ09TUh(ybI;~j#Mxw)>_Ba2 z2t_Sgz;-5sr#W$d$J>ossbPF5AUK$djV+efZZ4>h4#LS8CCM;FE(5mgY`EUH??Z?M z`86lj)<_epO^^1vx`(+J+)T+{<}tIe<#@VNyfVL29UcgKQD{AnkB$+zV6;@^^gjR4 z-`@}1be8zvFkr9JiE~8Sc0=S5#Pn?rxTojho1QwsC&!VU!LQ55hGVS+r1!zBbliWg&ykJ^*|T$`_Qy(sAu4ZJ*5`qrz_kO6%fxx{=$c)C zv%jf_0yL=MXW z076>;{eVzx;q}FiY7`i6EFq8GI}7%7U->=E%E*cLmw!M?iY(WZxBXsdmm9C0@u{d@ zXnhg0LM!1STfEQPlontx)(Q6q{7y00!UtZ*%iwc@X(2N|oE#E4l(IsV)BhX{3~5tC zSl?onr5PjEit@y5#;m6AdV<{fK;zi@HGrL!bxy&fJhVy1rv0} zW8CH`aZ?`n>&CPus%V0~zeoA?zF5krHTpXVi6<0-?0h>f{EQ?GREzZJ$$(4UHe*YU z_l0>dn(WL>X({sk3*nX`ZQ8tC1-_K_ymb3@EjS!~V<)1*iZ-oro!^6yc_uD4XB7uh z^yG3=B@FCsTf=0#b)Tfwzj{e}dK$9`3&{;aI-G}3%lt90UKuzC9xpkju(X;-8i!68&lIP9kYbe)6sotLvgDNTT*i4 z0V1x|$UNwV&kgq2$?@@ae=8X5hK~<>Z0C4;d+SntT4dzI7J$^<4Z5455F42RvTmQI z{Z51CC>?)D^P%?Z0Dqf<=D^I&^nRi21G2M!0ADsG1`Rc}%^zA&dS+%qNh)9}DZm`+ z4~m>N>K6mA?a(Z%zRAwc(a{)x2?@zOP#N=KS7+y8b2Cf{Xr$GZjf`n#NOl6U%gXXg za<}XC)zyuRGGax3F6J@ay`D+qK>(#ExXVCKe?2pkhUVg-H0CAcFGlwuN>wQ)E-of6 z20u)Wj=qSlNQaY+jf)EyK8h*n=vbTUo12@zghA!2vbB^7Oaacs3jPfk8q%}y%*Nlo zo(ZF(CklXZ1$n#|5rL&`+lPrYF79+)zY3*iVIiU?VrcjIy&?+A<-s{Ppt;%EbOd)V z7*tfgM&1c_ZC+St7fq_F;=@x`QURjTEf)bSI2@V_JWf63FAaUv?#qMn58`Gy<7q!~ z)6>6p%&KW<;LAsmppvpr0SY1;cFAN5K zMqj9IsHv$=5C)@XqNmp{9Rsy$U{sM3Sdl^OX26FyzN@6T;wdy%NkvXct)NxE)H1TC zM2(x98wT=g@7~2yXDlf%riR8!>By=5gqD<-Q$v9tgsxo5;oPQ;ytRo%)-1!FM>pMt zg`sYQ1QDL=8V8ORu@Y5MukHDY)xb%_4E5P2+5`XE2rG`In0FTz8ntTL)P#`DpP+8q zsgK=ZvFU2C@9aE}e>tyxVLsa2yfVyuxm_-!xC_xC!;VC$o{;AwTGP=t+D-BvV%-J* zB{L8%%K>vfw?egDt>SEr;vZ^$`fc#0xo~{!rtp=f{>8wI=MN-1+9f*L2OoB{x3?!T z-0f)pWFGWVFILVH& zG^Z&+hm^jWGq-;YVvk)&(|RSZvz~u?RJy&0Aa2#&EdP*k#BhgGl##ne<3opjDcAA_{svE=7fI^;iG)@NkDj(U0b;;xlXc_Ur#b5=0qw`mLMqHLah< z5j}?Jycosomj5G+Q&FlQaf!`lz#e4`_t7 zJ*+xQV`31(q;H*auX|MbH0ht=-FLBG`hKceNy01^SxOKFHa&{~b{KFghyi&<|yk0 zZ@yE-?#PI=#--)5Sn$^cd7bkn7^t$K!9uF+;@H^4f&?yDhLfrs9TPK%s0)2sKB%l? zX*v2ic7B73^~gb9qTNUS8|}2}iE;eR4CL6A8Rhv^q-=+cV6~TG13!>&i}aA6xfV9{ zrn1ZykH1EZ0oR|T`jP4R4s4uq&pld@A9}eN>>*F)A2|!^*&}P}SeQWzZ%g=NqN$HQ z7~E;sQfk)`l@=KtP0rJ~h=zSmi02jN9x+39um3T3b*L0^$%66S0Ks*R2A@RWKWquV0r~ zz`0#;FyUWsFpMT=zvT4Or|Yy?3oRjJ1G;h`%qKNG3Z81bGvA#c6c+x7$eU8XrNTzD z(#c?jqn`k&Spxihhj1M&_?#Ci@f!SAD4~#E7)^Ase})#pKXGf=*$qGu_q>QgW_QJ( zbRV;*v@{_#&d=WUe!?p0(A|()g}+ zacV4ZzECiI=1ZsGK^|J^I&j=L!IMV$%0wzTZHddi@%zrAOa@hRqOi01AAdb5RKges zdP*8(%shc+8F#Vw6VxLSK74!iC`kqUc^3hl@-fg%L@aL3=#BMf0d_zLJltKkUb8bD zP(@DYO*!}RAeug|cx+9bBMT(PnMmkQX6$&^3K?_a@$vKYh{;l=9T2j#ipg^F@R$(e zlCdvM#v&CW=3ag1k&fflVe@Y746G#&Xk#!$#UGA}4{*!2Gpa8YqLNxYl2#_}XnQX5_W` zQX2E4NJ$XR7TffMfEKWBer-Kf(l*&CwTerT!Zyyga3Y+QoH%J_Uw#}DvjoV;XiVDxwh4ZuH`+Y-svzFWwJ(aicj!sVS!|HW$jT0c=$fcN35XQt^>5So~ zo|Wl0u|U|`+dua()djBjLYikF`DpOewbcFQ`HpX}DArS^v0@%t>ky~$TU6IW8&?MG3RjOH&7Oe?rj1GnfZlaUI{Y0l8! zI-aJk4(G@58PMY;p6#&2N^u)*ie!#``E?CYaLhOSO#f=&>GFAg*-)osuL|q=5#-IA z$3^Np;su}#9C$6=5Ms^z11Of`c~b1WvXY`1CPWxf{z4Fx!+O5 z&0%JAIP?}tK4n8o)g6B#vockNtKtETX#2pTyS2R?!*h_xkgAFX_4P~^uircbh2Z&E zf3iGZ@J+VCWnl^J?0f^VktdW7+g~Np9TmnZ*mEd~_q?1@?0f&g?|@iKnk_0%x1yU)S7Uhdxd| z+Qh?~s*CUD>~M>dhGkw=gl_L|pROM$vd4b*PghvpTZ$QJscq)0abucGt~oqMXd))` ztgOgp&?Yt}Ehzg3>SkQAxD@QSIun;ae0T|^XCS~23%HZar>vwjnfkHm4MS9=`A|t+ zC=cFa5|TX9u9lXQ%7_a=`R`^sgiXcmObJpFaE@LINw=?t(#v_T3TLG;sA$F9qvUDIrXu>g&xn&%r*s0J38ACkP(pJ;}d%C zM70}V-ZvJwZ)1?OBQO6nx?`Xposog$ci-0%+pGel@WzDq7uQ(rE@nnTN#gEM-}dz+`6 zrPH`r&5}?O^(@k&FB3byD_kNyFinZRV9XczB&l0ko-Wu_4huM*|88=$P{Ae#3a#<# zvV2C)wH2%y)5olMAoXSbv`O;X@!sCfx{&wu8U+rSdc9r*H`x_pTA+GS*6J+(en0x_ zCL@!2g6}bn!X@V*{hSK$wxiL=Y#%>uH~8-#=8+j08N}68S?R||P9~*eFhfJbKZ_HT zeAJPAj!Xg)^pVWE6^GMxpA!;HX7S_^h$;7j6<6u5U=OBXGLGZXEu_$q!+No<7YO+a zouayq(MUCW3G)MM?FUVvDvZgj=X=#s(l8*kSevfPo1={lAycwkqD_mhv?d682!`TO z&){yoCK@p9szpAyj|FTuXRZ*j=oIGS*MAQ(4y4{WItBUJghUU$>n+e_v2bsk=R(U} zTP!Qsz1e@4?wHcLnc0~a;eliaUu?{Lx^`HUCwyjlRMOPoi<86!*XWh?vxo1TKlhe@ z6)otF>*D5SdAza%lBBK24@LP8D%=mwekRZ}G1WQpr<$aA!wetbiMC9F?Ot7-U_+tr z2z6_gY$=e>2`-d}*~qvUtUBv*#QQt~QdLkEj+{4zLUFRrq&_RF1*|AIV9n{+vsmbk zRaR3|ip~jGS~O^xIB~H#UJ3g3@uml2Ur}{Lg+6|L8ve8LP35{K59ap4Aa-{~KiV0m zYZ~J)>Djxtiyloqi!EI&TT2Vs4oBG^jgX9>$EzG0H&a2cA`visH%H6-jwaB=N*ZR& zq=}E$)1KQWqg3i{rE9bO%#1|tDhMl0oOpAR2;<@fPu*7u{485OqH4bsy&Ra z1imvWT3TtT9jGt8P3Ssg>Z=ngr03Lhi~E*4S{LVzasoCM7H!&QD;*R+pxtJ=nkiC( zlClUihbx-7yQ^d35VZ&r3n#0svS7>cOWOy++yu7~$n%24L?+q8uGC!BS6Txyd)c*&2g*^4cuQFmz8R9T&DtePnz z$7|3($9w9P66zj!h=NV%g9oqawC{Fl56bJ<1GUg+;QK3zibovlqgv1{%}p#`Xm3X? zmUmwN&RRb^!_MzD8$fR$FYsyH_@;&mvY9*b-Nhh~=XzCT+r2rjN{vH+r!F=j?z?VK zMIpB3LxAa2oU%d7=Z(=OS>%iN=RhD}Y_hmCDB4kE127G?kypB-Qtlu-Z++r~^E?W; zi?n&6N6ob65&?G^U=(`-p4 zwXc10{db;?GOhNa7ii?c}nxQq7CzMZEd7j7dX-37m z+I5Z^SQlInKhNi7lAeNnPMeuIESw7b@X;|i!tP?Uc%D2d%k9uu8_{|FLXm@ZK}p>0 zIYDjv!<>Aov~c$W=Fg;#00ad43;<>sB$WuR%($Sll4bTb1!vbIoR+>UfN+}lc8Q1S zcnB%>c$EcmNxq@dX6kfblfe714QzmWt1|%fO)ImW5{Qzr?Rs46f?@gs{ z2@r{5o%RDmjuqYi4`k0@jWh)83ebgsx0N0+X-m(?^&Ffz$z`d&ZI#{1BHg<yjC~P1ULQ{ zue5*r8TqMlWm;ThW>-A$)s|qO_ZQQUmk&#}S^1+>^Ar3c*(H3>0P<6RCBnd}nUq+N zZOLbDXf5@*qc=5>7(AQFxN;wH@vXd(nCE=z)ipc$`3Zj$JH*k_|5MqJ$t_=^R*5*x z1!Tc+v@Twd(MSf?niIlGrZKnS$4emTkdyOvlbjlXXmCK z@4PJfh1L(@}a>3NB8dfqLAk}ma0-| z$-%UsY|*Fl>GFvIhSa))4YMCJS0D~P^?^4azIVJI<~=>9kCFjENrQ1GsWa{SwuK-C z1?JDhf?MZxzA-?|9q?PX2}qu37W$GGHSUoehm+^s0Gr242Ic0~;baxy$IbgNot9!c z9Z0yb_5Xn#5LK*#jX&pqXeny@iI@5?RiRigdw1~qPts;Hm!w>6+$r7A{fiU^1rVwG zg@H7598D@oj8FVKQfHdR&<@P}nK;~RzI9sM+W_+TM<2!oJT)~GBw$zH12anfP$38>J|J^DPtLBc4FR+ittF zg3oVaMnp<|vb-zxnA!?IM)pm6xXuW~l-7q=&Bk9HMWc(0!D#*Yc81|O_;jNv(PfDc z^0W7C3pn<_K|GIA_SZdk$pGlGM1VOnc)j|EVE$+C42F{PeYU?l+xrTMCoihr7AS1J zBX#{a1!NcOpYrWJ3{1wUgE?UwIYt6~Np8Mt#&|@D3^eG$sn`QB z0Np=7nKwc|Jmb|s0!Jp1^{)5>>yII!U|LC8{u}ruN3yd}S7XwXbTjcP+5}vDtQ3tw z>7Hw}q2{0chn_-@R)a6UcEr;Z4W@G6_!}5r(Sb6eEih>B)GPikjq7nRxKHyJVRHoX zc6N3R)W?|XbO7bQWKP{Qde~a2FkMb0;#a(?>7*q=eR3SZ|dg!Sm zqdX)%K_7=y=SIz;$4cE7@{jDRY60l+-nfy)16%~ngMi-+l>AQnx>fd~7l=BLbEGrg z@B`?(Jk)kK>wZ#?0_3sx)7n^Ryz3-44^Pp>&!=SkCmksJRIx!@juI^mSI)^LQ2s#d zuQ+kM5>!(1FfhRJ`-VJN$H0sLHlV^6cNO(fV;n|ACl**aIfcj&%&Oc6rlf#c;hMD3 z0Ijof^&lD37yf~ef;a!!f4~R-*&FOFNzt1dk$h)@e~dmzkwR(p1JUFH;SV=YAkb=F~Yk6BSk^B}o!g;n5t4^$Ncibt%*ll30thvgd`a`E_t_?QHi)!7tXMkhTwq zD#mY3^-?G@3CRJuFCC3Lh`1>F(yi-qsdP+U~A9*qe5SUSC;>1X=#O%Ttn2Lfv~fuBV4>05E)-b_3mA zh3`~uldKas|MhDPe6h2dki0L?2&E8y3UyShtu*`voC_lxRQPf}wEr5jbSg@^qN9_< z9?QtQVSL)9*{*$h>c;78e>~rrtdOH5pHqSr)U-0h&tu3LV8La#b#~;N zfz$dJ1p&cC@hkVtk~as1Aviby%ko%ip#+&6g4fQtMi@AFcz~Nd*;3wrMoR@<}sqA$xxK zXOU=a?4Th>_4ifo*L7Uh*NIp6MlpK%BdK!c2kOM^#gXfjW5N$=JW3c3B+w3j~uI2f1> zPWIQkIOTMN@XF1mj$0^&f%jx%clU5lrF%Jbc6$bUCg@7BAP%df(LW@cv9H zrJt8Te~D309QNpI%lAJWkmn!SF3z#2(#Qp?$HvCM{rv%Fs;a3pA~}l?5a{SCudEz- z2B-ovj1+T8>a96F{#k^*c)9|Cq9S*Eq8Hi=Y=)&WDl1jROw~YipvG85NItfY?3C56 zE19T7w;Gmw9js9qj*4q|TRO(Zqo0@C2&NO&B2gE2SM%G;MLW?kB%@3koZj2$*B+?( z*6gKCe>(2?E|l$|2V&uwxx!@+t*aGCOG^Xa1?6Ju&z%CRtfXM;p0zcO>mDnirTWEz zx(?6BfXd*3!DSF{&Cn~E?ocAXnt|EPC&4b-fha?s8~&^ zPL2*gCT`^cCho&byD1zMc$)NQCj2e29E)Js?B3h#=;{cnCU2OWuPiHbvFMMt59hG@ zJo>uxeYKlWCz*4OQoC{e;cF>z@n>*3B_*o+w>dwh&-ADHKnawEtcUukS$mV4^ueua zs>N6&rO|XnRpB%F6mp zY^9~5%+UK?_5U`9WQB0vyj%iR09|TsuAo9gm3I2!>O!07L50=pP~xqIUky^^ z`>RE?fIiN3R8rNo0A3-ku0U95WTd>0{!af&!E|=bL-6=@*F3{oYuUWz(Pb`YJGh^1;# zm3(IfmD#AESa2*+!DJgPb@hy52FMaNMoYdVWycWR0yr+VNLM=q461iF@q~}su{Y(4 zhY)xIj?0rd-t|Ipt6UNO)SwBuLIbj$dql`XJLb2JcQ<0g-Be$RHTYozzU1lsnD##< z+uQa~1tH@K^Y7}Gl$75#rmE?Cbl`W5I=`>bD9iD=NVIDk+k#>D`*0+lpUYsSNxv3(nNlQni^$|o)&!gf+j279Il+lq{|!)FsElXbBkkOQDZG|)wmhfd^~h?LWQ9;%A>DsnEuTrA4I+W7)a0Ll|G z)6@4GUx&CHVd63o6Loc2OL{pgN5ypu_%^*BX317BsHoP}Qo){Xlc4|(CuoX~35 z9hHTKH-U;^^z9Ha*Kxc38CXjQ563qqbkx=d$E`eOH8@s+N`NGF4zb8wCS96HYU(VR z_-#VJ4sppEB1MDypjKA>-AKJ`i*JW1)c671cD_1w+zy)}-rh+6RyypwkdT1&fttn6 z$BIB117;L5Qqr<26?yqSQ0pL$3m&ILkA+>gvOE@&W3#Xn6S=1pj|Z7S8B~?^um1uN zmZ*( zYN^_uh@j4ALtjX?ieuV=hel$524SvV*AYfH$YP?@O{T;f@x(aP`N#ZH(t&QCY-Z<+!B zZHLF~rZZv5AwzE0QH-@^We#gU#P^7_9o-Kow90JBLt89BBm z}muT9BJ^i)lws$vwKj^ljo)Slj zZgO_EsiAMZ)g#1a5x#{nuho zqCRsU_?1eyO?;BuiO)CcX+$50@xRQ#J8zAH0S5>EmMcMy$)az30na^Jf8Y*gOs;Ey zlq_5=0){)6S{ON1V0j2rQqP%F*#b|DZy*uRQIPX!er>&=# z3P$<8P0gBP0u*5T))Z`S!xO@`X3o!U!3Mv%k5CG4tPYpM0hNJr~~`~jsJ zP=z;AT|NO$evgbjyDs!#eZsl>{3yEpMef+)5K$8~wSI9r=w*Jm>)Nc|GOu6Lgtm}? z$>$=9#dthBKHA^O^1^A7#DJGn#&S}c-@QgwrBKe2ZNyfhK{@VtKizq;j`IF(u=nsP z*Gsg|CjKqgJa0EOH8CwNp&tl0?(uNm;I|OIu;2VXf}n3#PtjDs5#1;t^<5EU_){SObEhkM39cj-Z=t4eEVDkHa?r#uy|j5S;A+83>xpYs@QuOI>H$q$|JUxD=Ae1imAKTfNzI6h`i8$3Kw z*y|wyO*y&4^{DkeHjvoe97@dR0Voh$(q7pqIn9a<-mTR(pbA+V-M1=Adf%P}0O{Hz z$r%1E_##VNtBVK>&7^K>Xt>+tD=kba(5YYS_1yj^vWMp$h`W zqdrvQ0!8EaQE8`(M-vqB7$~wqQ|zPd&4(>}d;1Qn@<@NLA??_cR1`t z#3jG9)=BzUS6Ap7I*+XgvFFr4W^;Py$L<;xRsR=+9J{7pqJ5T(X4phIH8gH)&9 zfeuZRR%3SH-#R$>$L9*5_3`?0BN9T5Ze5%e|2q z6;$m>8|F_g==J+SZ1Qz-AvY-)jmJ6GqTs=Z&(?hN##s3}CeTg)3iJZ83?eXM8M7Y} zRvI;U@}{F)(7SbR(j6!U;Nyu=d&-HyWMsMk$7D0~Y5Pz2!zRPz#AK7x%6NAyi`QfEVA^L<@teg;2&U zJxEReXBm{-v+aj4C?1}=y2b+0gKRDT?LEM2Ur2Xx_iug67SfPrBwTGxJ-f>BYk9!r zTUr_@?&~e^qtcjzW+RMfCrjneRp`Ko``{MA) z&;zAYpg2#3aggF2ETpqnd!_sFZQZ@NuZlDGJ53~`ZNv9 zcTdu65L1A}sPaZ1U>&Xsl0Kjv#)A~!8{K&Te zv4&?|s>{0+-WWC?fIequo>LPQeKeoV`&5(z1_scs?yl3)lF@C3?)&6wHOCPEIRiKd zxK%*w#&OuBrfvCAFh+|}9c-BF-rG$+>)x$GpYQy%^Lp`KiiU(YMfY?6c>+<0ilVi3 zk{lf|k2L_&7Bzm99d4eX=;MzLU4T)6`PqAbCckqA+Xl`Q=S7x)pn5YPc{bDAI`>O9 z?Rymws!}K;UbVWgFrNAm7f=<^u^bMts;%O zjZ$R_ZG?-{@nB^%?R1)ZwZRh(kvJ7S`#Zmd<1jk%6J*v#{GOUXL#e-+dBge&r+oIULJFo6Fh&+0x?|y z5w-GkQ!fM)n5JwI1BQP)BM4kfgrWjaF9gu7xP2}Z{%j=V;lTH&J)Nh8LWp?qZP;+U z57xr^p%<=#2PB&klm2>mYXime(vsVK_CpmxbKS<$lA4;D%QIfXol4E1AjuWav}QO4 zl+M(c?05rao=sfL2=3(_f_AhFRAM66|2NeAQjGck56u7T5Z+@zGNk0p*;Rq= z1ZB7E_((C(E_`+ZAM&pI`qdX%A!LQVIx)KoBq1R=E#+ zkgJLVHxu$^eQ!co#lTL{-w8dbZ)F9T7I0PKVAD2U4#z51(_*fCpNUzIQb-WRTX8)q z02)~42i%wGZ(!#!>`~GGsmCT|D-5%3@daggmXMm40DS~cink3;R%%kX=;fEWWf@=VZj8mh9J_H{{+Ex` z*j>6OPv|wHcx|7!GYH=p`_MWqS3#(8y*-l&*f6J$)eBn(XGr&_%Uzv4aXmhyqBW^q zoDA}GhudF+gcgc;$8QFK^m2$TH|E(w-j!bZ2)w+VI%FOZ5mByD@a>vwZn1=m3g&ts zPd|awFjyE=R5nRqc}XP6JRd&^QyM}}?!jFSq~rD!{i`aR>S*63GQ6;cT27cQu&=C9 z_MK#j)y{YW#Q*h23-K+}1?vva6QyWmWn`S*-aC>jGnF6kE}c*B>h7n%A`84rAugTa z5fPx$h%_>sgo47ReZTDa)|O=hA|tR1S;hOjlh%C4nfWA&UMR_`ccnxkKR;YOk-vWX z>;O5zt5s^;6-dN#{^PMLZlzm5awtJ!=&m8&9?$Rh?!E86d*AQ9#~(W4%zS%)_Fil4wWoXrhKi-jDefva zY#D?zyYz}1t%Z4SjWVMfmS8(fzqfOdURV{?M*RN$@|K6UiAg1kn!e!kNxv=SHt(NU zjyGb_*a!L7ztzj7jCOlinMN{U5B5Esm5`xt5-%c`CaRA)>Uv4rc^fqSo8S!R3wX8nN+z0v z$8lCRV|IUY4(vas78eIop6dF^t!E_H}|uJT22JK>J;st+WA zF_Dq2zsASK)hSKDRD?T5v^g|BUc@zuO{;A}VzQahmrY-kO;~tDIcb>{SuzX0BrrXT zidxfTe`97q;lpTWa&mH{^x)Q(6O+Gw?(X`!nCr@?;aIb<_b9c?2slv5+u^xdg-sq$ zK8UzF)FgkHPVxQ*#WvRAQ<1v*c{d_>fa<2B-@VIoSst12(J6$#K!dGPuFf1m>Knb^ z87BY}y_^;YIzeT-GvCu^!Arzc;o@DbSJ^@y5r6`-&${62?|lEF77H9BH35%37XzYpuli*i5gm3`h}eaZa)LQ1rRd| zIXoh?U-)rXL7|&PEmQOS-Da+W0_m_jyH6K~CKK1@h2*s-ui|58*u;y|=bGMLJzB`h z4ZX@EjFXIrmm(=1Xozmlj#aysO!tL+FuRWzg z>3XmQ1#ek3at;p`IVo7xFAx!ZknRQI;o-v>2lotR6eK1r{$);BWgDBNk6g*8|M&y8 z=e;soUREZUv5~IC=scKzU6Cx)A$ckCoT^Np53*Sbmx4M*5-7Nk1@nb|pL~A&+T88pWFsGTcz(`A=j!z?AAK`WdkmI8sPf8d&;m%DI zb}n#U9N5?VIm*0z<>Gk0Of)X#Oac=T?E(S8QXg7J0Q)u1uwFJG(qLdr_Ogd^-q{~Q zGFH$heSLQCxN=z|t6qYszp(B!3BI5O(^$^&u@SZR>tgi?m<% zpPc^GIP~$N(nj+~_8I+mqY=_5%QGh2F%bcA>_y%)odZ9tij^f0gng$A*+_m=xo_T( z3bo*kJ$>%t6RH%nzX`6#i@aPxyZP+NFDc(8X7;rPSmf%$`zl_DvK%C3BeMFX8Q+2E z0=lfV*QXl6HLOV67QD(C2Z8H0`g=-B4W)0cr=k zPxmUX3Hb*G>eu?{9azl52l}mwQ3#C2xJ~C&W3WMumsT4Mr*7FZ{8duRRLxLKLcmi3 za}d$+Dq%4uZdk1XWcIMt)$wX;V`iaWf|iNT*Mz#dnMDt7Boz4>+fV&b7aooa_aX|I zN)D4ef(d;$qV-9|w9iM~>9cJ20+amIN>mjl!m)cbu`0O`I(=#$M#>=~I&dSBh3m7-a zvhtzNCE^@El@JpV2|F(o$){C1FGlH+u&Smj!lA&VF)1l2->sfISle@j0VTOr!?D|o zgM(NnSUcrpWN?R@8S01vZpBBB8n&=JDOo)(0VbxAE-Ry2utivoMhb0CTJ3wqe@;4n zN>p&$k`lE%)qK7EoFB8)?RIw!ad6Dyx78M~9a#bkDeHmksGO^u+C_@D!{J1h>sKE% zhb89qCTXMX26L*z!d3+)I`j)n@>R>M`nxeDM_L6Y8Mc^X-QtBgDN;;#H*&V)DolsY z)GpRGG0|Dn_>TS)dGA*hbMi8*+RuNrT#~3oz`|LqjFyScfprv?21XyjQ74&(NLCG~ znbMwyubFkmdbqnk#aMPwj?`?FTUV5qgArY9ZtZcZYDO=FTS)2)vdUB3H(!Gz)}u$f z?S-?+*&p;A5{p7fj?af3-(hB-)GWyRKtWG`nn0A|ua_@Btd6!8YCpSw|Hpf=eOg4; zm4LYUE~KhIB}K1yO&ss}T360Qox2I_0?6Pu@lLL!cj(+qd2igP$6)3L&~abCejOk- z#4o=&q9n}hpY*HGUgAGqc>3f?QI@&94uP9P1QD0*$0|#tUciR}w4*K^eOGv&UEXnG zR+DA@uZyiwkWi6BpY8PY=Kh=tt9tew(@itt_q5FHcf}KwK9v)!JtA?J5&wmrvnx$8 zF06+L;Eh{$g<+Y+R{h%52O6va_kzPI-6-+OqXN+vc>Rxw#&Z#ugi47{K-LIe(cXLZ z?8S}(tX>2JqDrUFUic({)PHdiUpTn%;Fg652!^I6$R{*E*UAAQS0{e<<A^Z>4ca`1d1ij*3OnS!b$&14-`}xL{6+1gkRRrSvG8nG zGR}@y<8-|uDF`O*Ti;Ll^5EiDeLu!!s8fA^4!Q3$MzlKX+ot?Pc?YL!@I{iL(+PCMwEq@U<5!C4ou)x=y~VuAbi5 z*eE$sfNmG=3jskK^@X$X@@Z`y5cf(-+nP&_?k(2Isr38^BzgZ! zwomZsx6?ks8BY zEgykjz&w!w9ln+w5zqDk+cQ-9ZsASVK02k2g9(4pe#6FGBn!T4xt3CJ_{UM&x{bS$ z^+ofUDUCbdd$N|^PnHNh8)I3I8(_gL`KAenEKm$g8EyW+b4hU^PanpZR5 zY6!b#>ZFxKr_F8C3WP{b*PU8rA%8Q%XPtsdV`iSX3b<5Ha6|U6332lZlUyFJIRweQlwRa%haG+)ep*c6(7; z?>tk4bYtD|Doey0mPl61qLGFM%s`xp@AJ8?_(3p2v>O{4*Vl)E#P?`Iq9h~QXMW1j zJeDlUP%8|)_h!%Ee@U-D*Pv8?Oc*lO2#1g6&$7S#dd;7Zh~+>T*OK}(gcTY)KDvpBiPbA7p%mi+ z1ww5w73v`i85tSK+I^27`B&5fdGLqd8lL9BmWoxAX*bpWQ|49w4vI%a>Plg_x9=JO zQ@FE7hWFGm26{tU97D~zxeDLU33|$jeH82Ol>AWHy}+rC*2`)N2#)qA19!#K2fIN* zV!8cRawoj9)@qgNa>d$usj_$bMq=ZmQ3toxkI|-K1F92CO!oq!kMdE@d8&3YH<7jd ze7M*-&w=MO2?+_7mRJ|QpW5OCLul33%lY{yCY(WHzDXuQ_pteKan`MuZ$+^il$&7R zTz~ur)I;q4@GQP3GwK>W1BF4MK(D96DF&REzB5wUx_99|LJs{_N+6 z^z`XVa`mWhR{i>Aj?AXxe>E-&9FM2i*N?uiEUpl8qTaP;qk>FS z4rKo62Dzb;mlpfc({ppBCfJQgiKAR)6p?9r6qmDdq2#vmEoqx$?_mAh?s5FKe*P?f z-qTx+BXwH(E+3f`E8yGn;`FOnzV=w{G2!CB-cah*OwFO%)uFeA-|okK7HA(Cd!MW% z(WOI8;e6O=rG_StB`JnG=EaLsumhSUQ)wGB?X7yh23{4`e01l~NcgwwK6C8OxLt{cldxx!eRJ-YehQ&E`t5m-{`WPG>O!nnbP+m&PFFN1WA6Yug*RDNl z?WK^3M&D8TQkF5=TgBGjz4`gq>w$&?raV)6wn)t7r=fh^yV>ia_#WHJa!n)sBJ7j8QVH8wdnb^`u8-$&0o7n(w^yQ&)jC9hzBCFbH>B(nF2pL66&7tik+>7d2jAf`&G+zG})AtM@J7Hozvoif>ed{(j|D_&|jb3mb!$n;MMLkwk&)B zkr6fK<5L3>U@o%wYN*$+*c;o?)zi>x*m=Ag$?n+U9jYb7w5AmK{=Apz%1gp;M#tbV; zI&#!^QZY0^OiWA;EgNiRsvos5cU3afLN1%}58A=6Hs#gtMce*)Kk1cwpy3H>4o&2Z zRLxZV3Dbj8(*lV0pJJkkDOe(+mcw3*LN!VZtr`>g84io;?EFVHd|c}=`!HQsXl{F2 z%I1UEEZ`EXoqc_MM~4M0l@%4Smj~Q8sY)yuF7;$%cjzl^g4iq1ktz>nYemOeAGj(j zr-T_tacY!I%PdoIYT4lrIrHrW1A|16z##|?7$-zu6~+(GaOXQ|+8PKKI`ZbvO zUE<&ICoDuARJn2G*e3^~q@$ilOSg4&@b)cCbR8WYZhV5^wx19?{P5X5>R%4!U!Og9 zRvRg}Xx6?Q@=M7qWbP{9y>)g{$)SVk$432BnPtC!RK1g=x2dw|Lxk-z$;QjxXJz&F z_ZM5bO@AE9Mt2CKy9NgZq%Xr%UYWit~dWAO$@+N}5HGtPyYE zQKsHTQk%s|(TgyQrzoxJi}{nK$;a0?Y{z16g-GI>OP1q>U0y3Z>F}xiAo}<*J#y%? z{;nOeO;7jo<;yOs!<^eNf~Y5y9#dIaIVM+Lx*aCLPN7Y-4dJXamLJcC)D^O{lDF~ z(qI+ur0k3{ZoE-P9aYo(iu*6p6fBv^0TbMPz z-5`wWw+e^g?^IO&cf-uTlErFmdAyXp?Fbn@fhf%N!ftzb>HwH{pE6!POK~k^lE5!5 z7-V%=r=1dfrN{=1XO1)~8FclNOu*V*y~^^B_ZMp$-=h%|6{W+=^0h=oMYlYvwb3P| zOrTOfWZr&|N%pP1T_!`dK#GxI@(zrT$WVpS?tJnSIk|Qz(xv-n{9dJqKXC?4%hr~n zn|o#1^Sgf%OcHohKS=9KO*02r;&2;JBnV~nFL+f65E0b%y=+&7eET1W{Qq-~4qx%F zrT3CQLG(VEF}<=f`}1d#;vGHRGV(tNo)c7$f9|v7;a0UO7WNgx10)ZnFu7vA|+(m0UiVOIYK7t4;X`nP}aIrr|3+-?SPM7z^ z;mqGZ-ewg1)#JGCTCsaqw`{FbXFUvWDS@wZd+f&FFgKnf5Y_iOpd=s~Xpa}pHo(;u z`qGQII8E$zi@P{GcPcThAe=@6<`1?PFVYGR6^;4!x81U*`tpi|-X;S>m8p7cx09V- z?`hnfFk0$~DayF=FgV~>x=N02%+1#f&mm`v(__8XlfMhfAEjB*RCw;z5LBft|*khn0rI_H5VIm?s5lvf zjYn>xl=FS+XS>t7s=_}gZwIhA)|^zB1m*Q42m10JTw!{oskv^Mr~o2j*rj8iF3|XG zL5|RUJR1yk;ly&EBkZjUm_&+@vnxL$eCVMa%fh3QI~}%=77+Zcacr!@Bxa~QTCR#|AGkxvj7a~`3JiHVU+w(px|cbsGQ*s_<2BD{iN zJF!$#RxTjB&atvF$;1emxXDUYfN}uC*aT>KYuvc(TJqla_8&oc{7{10eck92$m(}p z#|vB=Aa~2f2bia>;9&W5J(0yDUR;cw{qEh?I~N}R zahllf8a{YQz+DZf>C7HiwRWvOJyS!IoKAtjzIQugn7G~6Ff~)d_uX^-Xcd`?0-xoP zZF&*cxROP%yq6`+_vjp1sx;d2I9lMdmdhQP8Ct(0Bz-+LkeF#8TaWYB(FXvk`>ltv zP2-|d(mKDz5i;@H9f*0>k9qD8F(HBj{Qo?Mp*>5Wv$uahXecZH)ZXb@-fbbF$1VQ3 ziO5si-T0F(v52#JLw=qdpz@c0ZMmRkBIB? zaG85MJXt8BMg+p23`ey(6+5~613@4PFOjcn^{$9+9Iccw=OVj4MHpSjvChkawWD||bL61LYxoGloK zzql7_^I7}961Yf;6g6r2Tyr!b;y8;`DPGbTE<6{0Bjqq3#oCtzMMV2iCgSVrwa@p< zT7G59;%X8|FDA9A;`YdY6nlXv{r-dZ{GHKvRfIhaGd|p6keS0`uh!=1i#%V;^B{>U zwj8{>Fcd`Kd-C*(c%*(vm;xCB%8c7mSp}xjar%)fhd2BcSt3)37#JA%&%T2nPZyz^ z<2CI5)ayM%BipgRj)mM8isHl1KBJ9!#(B&GOEtylau|f$&z~RBi!k}m|Hyy1^X(%2 zbz4i;Qm+uvd)FpA1u8C_OnUc4ZUj?JQ*vm>ilHY8k+o!BWTG_+YMrk|w;On(3(1Uj z$Gw$X$2@oa$v}~Iuix@VdQC_n25L&OsbUTNE+3&<)8N+-id~P(wLCo5CJmV$HAjz_ zBB>`=8HY=phjo2HV#9FbcTB0xIhG?-0)*{KQ^@w(8q{|%dve zC7&B7c-*%;K`VHWOp20OH8gC*)nH@@?T-)7WOQv%NTj8it7a?u>;~(F+Gqgdrm{Ix4{%gbv0*1m z({*)zSC|VqdUG|)rIULkd5IZ$tjfZ|!kkVzksvYJJQ@t9>TqYHeMLZE*7{9a-O1?! z!I~-|c+q)Bt0bgUj-;icuth==Q7{JGs4sp}0^E2NRvCK7spIA678ZmdVi-#t-@!4v zI*wkO_;K^bL%zkVVZb0d#5^X%#ZdAW&c2k6#Lmpz?Oz=)Tm@Zcim)xF=wLSMs-2x( zuMA+V2*MCa9z4L7Jg*tZTd=ZhUe^>o0&m8?Ka3O1K3afTl-36SG<{Q+)dkD zjjaA&O`s`PKP>CykGoa3M?6gg29r8OGLl7w=pjmZdqw09%R-qLsHxF!D2U6QS;u>n z>TdHhWY_r-^y&SuX(+}Ai$d4lt+nR@Y9G0sXPXmcUx(mfuJF|q**I})SP%wqpVk{S z0Kob9(M=~w(La!qN5>p~zq&)~HCdM=;;Ial2dw&Vk7JUivr@?o2=kY_>A{7{guHZM05jF0-)i+%b&^q zWXa+OYu(EtD1~j&?t-Tu9x=q%*LPGpIRmYVH$Kj|LcCpQ=)vS!Z-0~=od1BYoe;gWk&&+Jhv@L8Od>q<@a~Ze4DzGt-tyFCr zrw&*dfFmVG<@HW;esrrBOnmXX;krDoQt&9K7bO#tc<&ZO%top#$r4+=B8&In;j%nZ zz5D%VvT|0YT2@i8jY`pz_m#UV%VU)%&_!m(Y>`op8p_TNT7D`1bQEuYFu9LXEGsj+ zK{1e{mTVoZ!Kwd7r)G*7t$yZSXw5&h0GJf}O7vS}0XYD}dxM*xy9&?Inm+02kgGrC1-wTjQ~O z0AMEM*m&}(FUu2wnA(_;h(DS8wx|c==(o=>Jwm;q0H3dzckjxk($so-<|?XoqzhB! z99&H^$|LdnAM#S%P!~`<0ym#xtKlzd?Ubo$^-`f+)E7Eq17R-o^qg8P5!3Mj&IX`@ zys1V~m)e}E{+%(%gBtL&a(0*eJM34bwC%R?nN4uEE+9hvahlQPB=M11qaM~*W zZp4Jv=9O^e;O|yE2^Fuj?^`_y96KC9@6E9KL|q4Vb!%&j98qN1w*kL1(8%SZ2?1=E zoI^Jqs(Zi-TllOuJJAhhrgAoPTj%kRBkU|dBT+{ps}L5*uHU>lzxMuwC|AEaN~FEJ zZ$%7;DY2xWqXU7$=x`xdbgph>TktDN5gO-F3ts7Zy)w59yZDh3CpVnS2dus3`uB8W zW?2OR+i@1LJ1P}LWJXOzt;1$1-D8ocS#_hrg4_HT5B%|RF9OlpmGNpS*L~>~z$wi< z*uq;KG+O!wB#sXkbLUyq0NvLpQEQ*Yt1!XL=|^JEo?@q$GbayiWRJz$O0WIhiIWxs z$Hjat?JIHTyubYR)bxARzScQa2{#TQ)+kzrv9R#vg> zeDHwR_@2Cw%py6AmQQQ%P?bsKAR%0fJ`b7(0E?$`a&mgB?XJVS-0rSSp#(Et1(Gc6 zAMAtd>Dt}9LZ00KL_K_U0f5)Pq*0((F$tF>xqKPP7$VH!|3pS1*n)<`c|21sn24e+ zSF2LrO20z26?74BFmN2O(1Dz&d}~!xs0ldYU<*q9lqa9bAj3!n5GhQOzS3LdE;~CK z&h*y@k4erx++N_U8Q)fW`PFSjS2i?A$Hq>GpViuE`g52=o~W2uUvIB1cCGdyz7)R< zi)+`{9JX4`{CC2|)-gUGK74>6Y0oOo1in$g2!NvFschBJpg~V0i~7B|nTay@tft_8 z8vbnGy(bZpn-jJ!7tYp8P;r3vs^n>MAO&kg?eq2C%oDUyRzKo3}_Y#-*4ub)59XI(W^rt}br%K56M$2cXnXTDUb94@FP@|4~;$|yHnSWfwh?kLJYzCC-1z+??2sj4gi*`Is8;7kIo2++dHJlQ(|Qc_n#-p$UhVP_yk zB=A$=qTb?;>1l4p^t`B}0nq@{+p{lg{0^A$kMDC9Z8Qgu%B}ErfJjG2M~9M6RNuGG zz)J2Da0prtVHH%56|u#&$EUW53A#F)F`h%;zb=myuWxQwx?y{PEQE7$-9KHiW|UE9 zAo{LBe{-`rRK1+lqm9a>%AM-AB2A5YCHiTy`!yD^+l_GH!C)r=E^Srt&lZ>h<#N+Y1_~IQ?*@aMtSTstDTI?ytr4dnmMzg#-5hrzP?Jfn~={ms9k{? z{o=)o+Ii1|GUP!lJvTV+Wbtvw-6+UxCo-RVemaD?Zo+q?p?jQBcxo%AmcjT}TFf=V*armZ4_iE%&hoTunpurO9)P=!Q3uxZ0hF-9BjO zW~HJcKF=Rn+p}G~>}w;sySpw?YUh=_Xq{@G+)cN*p*;tAaqTC*PU+62k;f&+#_b@C z1XP35%!`9!XJNl9AzsJ$i-OHaUd7{0vncg?={d2A+G>#E4;7{jptaOeWwCUu#x9Di z>qEsChpUeU7BMAu?>uw_PhJttv^VO#=C8JTBc)xjw$@bXViOc3p6{>^mRZ+SibtjH zNxc&aEgSAH6!%XteCYxIF9bVC(M}1X%bUASY-)MDUm{y)1r^xhQSLc;+q3HOHw(>Q z>%IK;22N#qu`nqovKDd5Cm?ujv*?hVXl-pnJ*)pr?35$qQcJU|27)&Q_ZnJNgxWL? zi=?F!90afvxe8$`$gry-D0J#UuT{M0`N!PgX&eY?gh+Ab-mOVhXym$ zrjNJhF1G)u&pzC!`$Q$prE@*e`%|**$^-Ul^2?V$g*o5bhnNO)6-~EO2mF6YXUhQ- zQ0?JFEAAjWaA#WL>OG5e)C~*%Cqa?@JR%JcD5WtzRn^s$>>oE9qYI(_6LC{Kxf|XD zu-8pb|0tFrx2l46`-G42$E(}+JPl0`-lxezBHri|KSvHBWvID(P=nAA z$46bRJ6?-i4U_bBn5<)BYM4_o*RORM!(iG~lO`&wzkPeN>*MN&I-2S;_i@H5eo!PLTW}bMX&{CKhB$+HHElr8I z$vpAigZg`JciQyr!RNBANBM3iOZwKQiS%Kv*^#CKZi+P zDCAC$j7Dq}U%YKqe2_HGX1-4pV6?EMB_x^e^e3ps9M8qfEyU1&Yv>*v92@fngalGk zae9HLx7*9xzuBCj4b!+d+TD0B!Ma??DeiC66Jl%Gt#Ap15r z$b4%ihkg4Y;;*A4V)O3O6cg0*Bn09$m9#5+;6s*66kq%GAP&-V|(^s9j5X zC?yve#!wt(KXRRMaZM9{80@W%+YY3L4x8$SK&A1c4XRBSOKg2tUbhVdMH2Bd40a&FYQTEPU?rP=!RYuCVP_vb^f59yIFj>eX>}@vBynelD@XVdE|JZ_{lQqv z>Lv4N^85-@n5$TVyl7ghSS9m-T{Rfd`P%k6>bA9aC_KMKwH*FZp5ZXvM1m%@!pZD^)N{dJz1myNhe|`i zARhgRwoAOUDIrtRodjt|ng4kb@a9f=andM{&1HSY zi=L|(q!|pXXws|Uwmpc3R?#BWlUIy$izEkF73MQ%_Y(J654Hs=vlwa?8w+M6B3X6J zepaoD)b6NTWy&Qk>Q&gFSRV4(%zNIF@o!|lX*X0htju6(+zH%pVo0K>?Lx!Rxr-qH z$Nu$LrdW6kzvvUOc&sarzB9bh;S2rCx@Bx22f&YZMI7# zeW!k5NyX_*jnA;&7!lNxaV)ZjQ1Lr*-t>J%ao^>C(GUKr8U`cxi&WHPfQVUW{2nAo z@V`@k{geEFg7*JbIQGwvn)ED^z=)3?Fs)i60}5$yaoHB%5u@cKj7B8kg?T{RB`E^G z^EV9_^*hn({rmj_8iQ{yME-*f(nfPphX9cGH-`i-AkRUoV}5f;$KeOp)Chmh%%D_R zhLcGNo|D6-R9Rr0s=rc7YU)h#IhW6W)IC&YOaTF=yNAb}s;d7$e)aY7@Y2W~dw}VY z-=`l|&f?>=Af>~8HD!V0_;2KK|2)e7j}!FoKAQa83C_|Fdso-ZwKc^%5gZ)4L_nYV z=_9{mi-4#s){d{m&vlz*s;Lo2v~c%Z>3LN2SU5Qe0>SiN_O{9$@|6`>`g2H?RW^H0 zM`sXBO29iMler#7LbngVIs~CcyfnqywjPp!O1Hhdyf=FA^UF-^9UUWEO)@)VrOy39 zaGw%p;zqYZCcb3*1b?NVXySt|q2G)t4H~-h>c`bV75VOQlH!7s@ZY>0NZ}ENyRR%; z@w{Dd@TclM;9Wyt-|X%4ODXV0!2kO#^e=rhfs40Q0q(t&-~1 z{|Alt_$OpA`T4g&VvJO-<$lm9FS>}Q)$AY%;Sp26Ljig{AtJ^bSk5=4<5+JHNN3!9 zi0WP3Ug+rfi~1tLB;D_K7ccU0iRC~tpBX9RKu($dZ$zDfnIJ)3Azsdyz$Jie3)30}=AHdaT-0 z0mWTQB~c)k1sIemfU4!?kt|w9hvtmH#C6Bjwy6GmnY2nm&;5iD_%UGgcx_rHnTr8E zY@P(k9{KCMys19%#B{|wvz(f`)#@)<`WLGDrpI921j?@#la{7x-Z6g(ga;?bFSltQ zt7fRDsbNCrx*E@(J4ZtIU4xLz7ocPP6S!1*mHOfP#FTOLSpc-*NZe|ri-k#O1SA01 zCo{TVE1oJq0?5PGvFB-jGQg0t)suZ@GK*h@(eYT^H_*@+WG<#A@MFeX;t@T9EF2Et ze@hqv_4HvOg6-06f6e1Qn5tvK^hkGf=IcZ8C5h)Pqwd7ZG}b*+ra-b>LX6hL^SZY= zF+EB`_`v!dq3@0B90a%Li5_!ixtJW~jJJ~02PAP+>{|K+;b#d%+3|eCeO_YZb^oC< z?Cw%P<4wb#&J16%p7ey@cKm=a92Dcrcv$HSpkkVY>qn&qG2X?H@&SVU-!nwZG^{FU zez5@y-rm#uwIVKWDA}RMXKPKv0@&ZqW1&+730Lt=f!9*3H=}CUWs(#LnV>@GyIr92 zYUe$BtU#7pp`@GJG}Kq1kjGaPJm$wfy6^9hbM(&H6;eqvo}3{N#Pjxd$bw3d(Hv?H z4N}H~=m`sywueVkZr0#&g$J7Ki*7l$%I%O`jnbWz^NBTwZM0#mAAX74g+~N3dvneU zBGK|Crbp!k zrVd-TPfF#*KM6Z)6ZRx|W0nHm+iK5LJ_Kb3-dK^S;Z0-~FOBV^!ZohsK-}IQA=4Ww z(j%9hY@rF?YPYX+u*UQ)-VQz4I@&!OdOZJ-l#~=Z8|OW-e9K=DsNRf3xSf0Bw>Rlq zbFBx?GtvCW-R)^1g}~}hhSn)|L$3KX^D4o{@G*m^0mMp83WvJ6gS)GPb6Y_Vd(1Qw^sXVt)V!aOTQTV$=dWz~9J;g?P=q;GK#<0}O_E3a*JJ?X znlcoW9Qr$UwO`L&q^odbew?8?UPW=TxKKwH} zErw@N?a{e{!gK_Fd~o@8d?JYkAy&77^x3t(!l13L9?!k#fElvFBoe=*lW3WjL6!~u(@tzDrfhri>r60%^Dx1Tv7?zbuE z|E+Y1;9n5n|4SK$-x#dX;#Wl$)?VnrLUNpMdIX@qv(qDmRyC{e9c< zyxFH0m!WmTUyAJ-05`#vWVWj^3Vwar_a+;DuWyC5$6w4ss3fSlDx6Kyge1Bck9yVw zDr#ouKPPaUIzA3(_Tjf|+v3P|t@J(d0SJq2{!*cNHOR%otRFSs?h6~d(S`C;r@ntm zNl+ND`6lwamxl@-KZ-y23AIlE8D7{<{WgSV;KXzr8G3Q~?$oM*CP(y1zHQ}}YFeMz zPlANQ*WI-0)NXBf25KpJa4~=ks7$$AalRA6fP?f-!x|(P!@Dw{BxZghT=M*wG%2ak zpN!Ml!gcFA1GLKZpJ%6Zz$VtpL=c82;1{uf!cxcFq5FC0RA>Tx!oPSnl(YyCr~4sUH=kz>S+)XLDg_iKsN#=BPPp zyZ5z0$DU~OYnL;LdZBjct*E%+dMi`K!p#q=SqI-r(6mM%)+uneg|34V(&#x|wqmQH z&*M4tI$cNJUX29-&^MW1@NFQvXDR>enhD&5e%{K;D-b}JdmnEG`U<8T1gpulmj24p z>T8c=#rRI*mJK}kZrm^)i8zmV4K=r?hsOZu7c%=e3OZwWd<`mH^ay*Px&>FeV?b$i zN|yz_XQOWfk}j`z=?F=V<~x=~M{DfN`bs5Md(C7tTeRtX-m0igJ2ynB-p`( z?LFvs1X5t-Xr&HBC>`s{5VbsZ=63*X!j!CfaolzRmxDkZhEs zy=rb)Pb6gW(Wy)VunO@Sn0{|TBiS9MgKCU5#5Pz&#|z|M0Xr1Owy4DR(?Rs0Gorx$msUw zkVG#aDqP*c0at?Ns1-T~{K*m(;@yu%oWbxBvV2qW00fuwLAFfvls}n)&(ZRy)mlnK zdjG=j?8RY&p6O9bEtRV&w-&Je@O1^ob9PJ(6>+q|pgg*_B(|6pk3X~7Jr;~KTsk_Q zFS|LUO8Gg;w(G4d@wcbf_O>f1n8*>?`?lUa*B^sMjQz~B_UBQvMySZd++0vDrFQCk z2w(LQw)?QK}$Jl0i5Rl~9dzMyDoscy=X$#44-^G+Ta zvAP`X>$83)0Hh@M zJA-=ROYNA!_sPw&VWbS=y|o6&%0QlW=`27mN>_)9Esqx1bxLh6``RBoCHKV# z2pFg!@oA}rwl7LHZ9FodQJCtx-MPyhW7F6hpg*op%Wos@wU@4q)-N%sQFT0d@sKFs zi~MpdXN~4^%lq_SBCfLWP78+>+K{91#nM`UaVgLxz%zNRH)cwOGQeQLSX16X@$OIE zs!|-?m8mGDjhn9am!M4`@Z^Atx?`Y-JTH=>MDO^o(ozAPi&;V2?xqGLRORi-q|xSC zr(OmQWND;9U*Ci;t{+qcX`Oq$CCA6cxcsw<_wftgPLK`8qkXoRgX83u4zQmn<)@6T zXlSWf6+aEKq7u(;aZ&|R75ZxZS^xugmPbmEgOVdTH~c{v4QjN@K*w&6IAf&Sw0po?uM_HnsyuQuzU6+S4Tk-y|KeJIUt~t958S3y<NyB>hesNdOt#|P zka2XPPB(#}>Dqo;2YR?&BOSAF{Efhrql0nP?f9S>E`iI_EymAT8z zMT^uZqBcrwPxum{F9;$cZH?&;SN-fE&+_@HQ#n8jcmhFaX8R(s>Vn^rQQV-?xV}1!)*Rw>B>JLsViF^E5 zLj}*eySeE?-!*1+_KD7r8jD)@_)(gVIMj_VyBiyh*QTo=Ovlo|L(Ia*xBdO6V=pt? zEvVmAP16HOymLXw2skSVt$Cf^-7)cmMYA4V^yGzfk|QqRx6~1o1aEVCAtP_w;-+%U09RY)Yw`?xsNzt0B9XLlI|LF;#2C#r>VxRRc0 z!}#|-HY>y#A}Iot3}@{}^;9P;kTDk+_t(e0&u#pDvbJ5fhAsPQKXJS3`qiEUg{MjE zJ&)L?C~0VeHT=*2kQ}$w=pElJ0&dH)&-|#|I}PkwZW0B2+X|2-Ss28vzd~9KSpJ&- ztawduplVo{sw%TG?+ zme787zM1AZ8+_<~RNB7w@e6N}A6DDxJ&6f|?rlB!qL<-jAPu}|*I(eSsHnIRFQQXo zhug?P7Ea8bk$-7SA}YCG7KLxP)k>I!VrESg+=RNt@UEKwdaLPwk?H=o`u;m~_-P2F zN_WVGzS6MjEeVBTqDYwJRYWcY7%?iw@TbYZ)arwH6Up2z%W=be91tp8t2(f?~a3{D6B zlqYcH_RCsps;?fO?5qHSzZp8cSGdGcNnDvn-0pDcxmMBl%xmLDvTwIP^tc7tH$4g| z8Q?d@aA8#qoP_!NM~X%)t(bY7Q%7XEKAgHmWOPG+{g3we8K0dd zI|G~GKMmWN-my2Y&h^*-6g}CLSoOhu+FY`FiTLxzSo>+2Tl=bwgT=_`h6{G$ck!*o zyC5wIY3cwS<#!lsM|C;iC~Y3Jvi z51zQb7{B20QvS+2k>ChuK=*rh4P$#kYRNb9z-#!@?_*G;`TdE{yeW9e!+pyqzg?Aj zfA0eUs^?J!zKQ*o}v#GHc-gie)6!#8zm*%KCo25~g zpbNs}g0HTyQUVP=wx2Pfb5DApHjox%@&p9$mZf~F)^UirrSr4P)^rN;g$!&t6M1v7 z&4YZZ+r<0{7(GqC*Fl(_iA?KtJ3Y(QEvM?TQls>PtM=DV+yUYzxFPd%zv+r;r^ujb zcWa@4sB9O14sI;cRbC*+?FZ%Xcw3fmboUc&L81I5b=*${n(G+ZKi~g8ncbD- z#r9JWu83&iqQe%fW2-ICSFCo(Xo>bO*le(QWPm7oPIEiKxSzRH;X*ax4}c17m@vVH}j&A+zzOw$qoS}Go(AMHjH{)nY?bQ5! z>YvFj%by~S#TPEDQ3n2r1SdLz`w1IuU0#*>gI|%|GCG1nT)gAHnRdEnOSyw)aTgZC zI0TR$M;~`g zAxX$(=(LoxZG53e3Gw>gV-GC}iRDZc?cz;`zmzIX8|RPhn7qCU1Y4o)M-tHm=C`pf zbQOB~!#xJ2Nt>jUgITJXaP7hiLu!Sc=8$tVGy3)|`4A}y2;8+rj*?7q$6nr5?$hB{ zlh&9!C>!SR z|5#q0f_pt%iwtjQwirnX)j0Opm4C@xq`$I$=75bl^@)P7;EL$;Odq&v;U`w^*t+Fh z(*_1XwbGMB=QM)^wyen^)rS`Eta{Ef-9%pVDxKyruAGza;I8p32`fH4HU3SJ>F-UC zZhh?4S+2gga+G(wCq?gG#x6~X^q<6ZO>dCR#wPun+=8uV6-ivPUU)0aOrr(LowyXT(>x%ugJF4Ct4D;+4 zD8}*PBR^4@)Z6+QAH?t2E{H2T6fO&_s`9(;tx5=W+g&)@*uZA|qF#0Kp0me_*q?$P z@S}XR0>Ci>G6wU zkHZH$<&J(@5BeUa#cdK_w)^oUm%)|8`%Xrk;bp|lxZ*U9(yCp{CP2JqfyGGUQ z`ko68i)9_NTGO3BgF!)*)j^H)ScX_Pz?+dLB6phQUDq6O1 z)y2&zS&#hZ$>m;H*(y-oXSuw`&{tAe-0E`PW$Q^_T~pUN%bUlno)&g)%in~;T~=PI zEi>i`m;cIoz3|bm*;_AhcVD*OaUluvHK6k_0+z(Cj`_Ls=frnRwHmtm zwRQ7T8Px9`2Hiu=&;TpE!AX$8%lQ;AWiU)>162UP1Mz^@nb_^uzZAQ`eE$9AuRfP= zJER0osx3kLo=j7^%UU)0ju5zJXcpmMStUJ-tw3pCh-q(cqk{vyc3E_FZC1|bu&b8J zcRAjzTvd8)`>!a~o7q2SJ$wnWOW9|s_uYq=-LkFr?6dCtnAUu6!PN}qyH}V`J^${R zxANfCD_7p_j#=A#@x(oSN093p1e`?fcj%q?Vio=6tz3TbY-6tf0T?T5R4UX{()|JwaX+aO#+}dzYc-T&=189J#BbwObS~$vT>B{T*f1ymactBK1{| z&t7|&2?{CYGwD}vy;14E{yP6vh`TH2T(@tpA4mx0zDvmUzVNtoyI133@g%lD$Ek)^ z>5yvLBXq*UneRTNMeKUSbI{}ar-rPlA?2~J9!c+VT=S?MS}{JHZ33zomtqTJ4PGK@ eRuVM+`Qs5L6Tq5lNAd?oLIJ4y9XZ=?19>r33^-KuWq>N*Y82q(d6%ZfQZ{ zuI>AsGrsfv&wt!;?=#-Pi<=$KuhyDt&bjvZ2dt?qtWP{2k>UsMaPL0Hc@>B?bGa<* znYi|YKvl%-#p=@{GaZ|9`r)Tznwx|4DXRUKvxZfl0-w+Ra(^lt0 z{fcS_VLjk+KE)M1_wB)Pjh?WiahLXDY_cyyILG4Kmph$2#3o;-ZJs`TTA*F|J}%Df z&%R}VSPJ*^Fi9~4i;auRh&^UuVPW@GSy`D6E)HL=TvB#kzw!3kNXg91%=1P+Iu2|s ztl#@%)?5d<4?R6SU%XiU^6Gq~*17ZNXJ==%Y>Z-SYs@HnuxXLUS1}C+^E7;LkEW;k z`}_A+hbC*?q8g_;Bi)r+xKFJg?Pz3K7CO1LGjpL(C@+jVNJg6)-eun_x0!c$cRzBN zbeSZ2Jhm3JUw9i66H|=i-Jtj<_VI149O@`-X0^JBYUj;#nJ5@ZhC-t7^RGSwxoWv& zxr(&Sq2zihDti-dn-?!!u;KmK)6?@IUQ!7?l&$6r_T+BgCVe_eju=_XqXDVe)Y2Mu zB4%r)8%v2uglMv2Tx*709O2dG1R=MGP~5CO_q+m=-u^N(Gq*TxGxU8%HKJ5biws&) zQd7I*1u|VtBI+J)%*`1Z80^3#B_-W;9ZHo5U0GS-6uTK9#?8%rU;d-Z!MO7>(_(jG zre6Qxps?q$OHa~6Z%iB_cEj%y?;4ip z%y_sX1H!|@)6?f+yb%p7dUZc7>T$9LQ}2aI-o;JJ%nZH5;WuHozoubry@}~>YH!4; zH<59VS3p2ZOG~f9=6QeCliikRa^!exy?ip`fK1rEA}edj`?~P2-QBBKuUhn^E*KD- zha-vB?WZLdOw(cNBR+iSs~=R>%e90t_e5hX4Bufll(eh8X zwL!n=1C!Qg)7}&q^}_soynwUSS08M%mT=l4^}ME_+i<(b-vVwbBna}^OnrfI$!@l_ zwOzh^Sz1o++vlk^ z)4Cct@n=QqJB!_+Z{NbUl9iW7Z!rQ=Qp@MhA8xKA{n<*2iajd>xgtkPsh6&kan|({ zg;?gvdDb!g>Bf7T@F1T1tq5{@e7Lh16%oPdlK;6u<`azEa_n=NR90Z%{^^@acJUqOmS6!vB7`9ViUWJ5&Sl4Vdmrr^! zL`oyXA`31D8#?pK9QfiMXD0^}6BGOU`!LKJr}bF7p%795*S(ebsVP?Dj%feuO;hXRk?)6q;q?;3>x0D`97i4=dByRk}C&;ncbv zd@e61=Q2~4mJZ}T;eHn#y)-?|=C*HfgsktckLe*NjuH|rUS6pA#=I)sJ7xD%Tq|P( zlN4#WEl2ySovklj@(-hwSeTomy`Hu>oJ?!B+?RfGw8y~0V%?MKXP|-@w$=47^eDS^ zo&~?yNu;hJS)X3k5>D)1+-ZRkXov@OJ+95k$?5CsYc3q%?kWHHk%Wh*d~|daA&@Zr zg-Gk@=n$u8(45wvovvU$5iu_>09m)ly(f`?&wuUx*Y zY0siJ$za3tW^;41-eRN}Ub%{73&8O%yomH)W!1mrusvdu7)&m53NL^FQ)@FVs;nI0 z7q*0YX0y8#Z@<_D;RVb0Yf@6upMy=(AhBmvwLk7uq{Ilyyc6vrXlgo@92u_ITuX zmG9+tnv)1K3YF?hUtg|xxUus%D>0b7^ikzbx({X~xx#sdxlW^GeTOG|zI zZ8(PhetwA6&x+phaUJ?dG7-NPG!5AMPld-38$SXfxFw@i9pULNB-$5ByHQ5=u8 zZn>3?tLssoMhWa-+v%?tUDX^N9mT}H#d4V!=pwcJ&Ko~}{rdGIQ{kp4T-V5jWw9z@ zT1U&wRlLpL&_qh##KVI;^i)a7+{`SZ(%0YL#K?%6nmRo_-L~>4waf?3u95ck)kNg< zMMLBT6e~UEov7=m9{qI_?YbQ6`V&>hp;zZG(FRIONhMGAb$^7F_w8Q1bm2k|lSBwY z(z>`aRG=gCq#wdm$Kp=4}I2F-7i^>RJFWRI9uF*|0ywO{DC zqo$_DghZfFq@w;Bx2~YRU|oPzg;Z)EmHGSouN2gI{J|52kg;68`|E^Rr>dukv}VRv2%pM=aZ2;dN0<>lr1(`{D=nMAQ_-4i6Ko?f%+%?R>5Il3Y0y1VocD?2;; z1xZa+RV=Qkg7Lswu`48o1n#PYIP&RgFzdhAf6Lui$^4O5@ZDqCfMEaVpni7Dwl$p5%*H2rgW@lzX!M3im05=hy96 zsAq;YEaa#p-+Pplk(5qY6jD-B{G@e;t?vVF-Y?LrKkZAG;j#LelsJ-ZU9f5h0cx}U z%+tbR4KPSaNr}0+`F*+AepMX+2aq643=MbJMwBY^5VG2>ufA8bK3j~GTPGH8<*4MU zGk(a-T!eUEZaJ1wbGNPRP`PE1lQ^Gk{;6|va&l#5r5uxER9M)<(CBk0m&oL{ODI!{ zc^_7$np}`PH_wevy#_H7|Em3$#c4 zbYO@`qL7;tqaFxsAFUSN#>Xe*zY@O(&`Rj)5C#fG{dO3BqEM{dS=)bqd5e?dj(z}T;7t!7 zgF<~7PJ(5C>;LOh5cG>s294Yf=ip?jq*uslYcS`Bf1hVZ4;e*zKB#FSI(Cck@PnXv zt&8tY0!ryM=*z;C2<$yHj6)m)BOd^Kwwaav?&ge279-W#dOF`KW7bVL$Y^jzTdt zekz_(iqK|c9#Xo59v0b@y75sw1_0JG_lQC`)+_Fa4um@zM_G)Fv|aHAzgxQj*u zc8#amVtNgG__tAC)LZA--!@%Bc?|+iI*Lojfv3aeRrC;!JY3H#6z9+X9X}y4`X=CN z#{Bo^{9^xZ$d2iKIMWnFK%%0powpeRGyVO4pXpjjd3j^j=+@TO9O6*OprhvT>uYO} z1DBXW$%Kgb9SB2BmRDCHunW1)LaZJs%dO7OA4e2KPSOPTcb9*}3shgez&%!O?d;@K zX}>71OQxVjG39VySuiSn4yji zDaBmv!2~%U``q%6n%>mO1TJ%KinM|PW=0D$b@ky&dy|9pF$MB0MOuLSr>B25&-T=i z(XH9~+XMuCW5nbW$`Uv4%M-Ebf17Oyef|11ja+OV1q}*Ch~Za}Bb#|XL8N=fX7h(d z$))QYr7tj1NeoO#t)NRZ1%A~MzDCrfb#0ub>XRp1zqe)~eyeL}^j>d@jgE$Jvk18R zu8&P6K}?LZoUAOrvr~R{Ha8m^9TUP-R_^>x((K2NA1$FsX42Hf@K$sh#=L%wmY$xG zmX?t9OLLL)!9UaSLX=b+= zBXQuQCWsyj7Q?AR0@N5=>9Hnw1H18AJwZ6-$bF_p7+c=k6IGf&=`Rz-rmL; z9Oz(cn_XDg-PZPWd}(d%`1h7UUqLpu3$o!8^2)Q{U;7=h}E zLDhX5P`^Yd87Z%g7w*jJDnocH;1HOjxq#%+G*X8m2kZJ z02cb-Xph(j_nQAsJ4Dj`wgco8qA(10;DkgPe=9m63(A8EsD>?hyG+VzodcJ_w?m7TnTf{4S?v+7`M z6l(od-Q%@4P4fVZu3hWO|Bi#wb;OZA{{8#+?Tat93#gIql*9Dcw=UnV!8yY@2_Ehp zs&yypj%_d`xGgfI4VF2cnOS5c(27H+l%)7Lj{Dk$TYJ3muWRGOU#DEYoS>=} zWK`^W;&=h|Sjv&{v^kiKM;>oFC%U8HKS)#C zRmv739vEIl`bEPeAu-enUuLJ7Ax1j5oT<$yxvqgxz$&0wTd5k=N-}yNw{nl)IyUZTHEeSf! z#gB~fNu|Ai{n|Z7_VJU7Y>0oIZ$d+rLYgK&TdoGZu5s0f*S#O&CVo2*kO;NwwW8Nw zGov3}h3$6G_s=c#i2nWeC{w(|8fy$AqwPi1`bSySZo^{aYCPPOSHqI_d|Y(29x%CB}9&mUu8jEyo~q;E2U---EQT2bCF9!7RkN7PjOuJnW? zWttDmUGlwZ82gaas=r zL>u0yr>o2+gj7a`As{WIG%u;3KtB0hxuYry#SaNBBO?PoF)rrs($uVI3WsOJ+&PVa z1{#^QRRehkBmlrp7dPGbN9WK_=P$ zteN%LZxre=3XM~)KphC^jU%Q57!dhHVV(BWrEbiTkrCIff_X9_*WEvx^~i3o1XDGW4$Ha0fA05L=d_L*4LHiCiDq~+&70zM6@dm10@O1tz=pL%-k zlm$a*_w(_Ij)^H#c>vh(^W_2c#jbc#5|Y#79fcD|3{=mJzu5B@4-e1LMPSOTj{l?l zTndIydg(vb-EF--T9#wGT|g z>`jyYKx$XKz-?UIyZD#AU%%d%tgrX@vp-SmR$Wmc8_!=>Q=@%{iiyb*qU61>@8jbq zv!Nmdd3nlikD>O+F|dFrd9=UIfPdNl>YexR--}v9c32xNTLrex!^4A=loV=YzzFi2 z{I1AuR(-ul{1{+j$T3iv#sx2|t+^lVz91(jKl-_|v$L!NwVq5A^E%Kzl2oz0wlhHT z5ZxCv{QeGpG21Eu5ble{B&L-|M@OUrPPn2_Z2;aUq%F6wN z`C8Z%j0EO$-@_b;ot>SL>(@h@F<=KU;$o>Ys%mO#8X8j7vhne~gbD)qtL4dh#L{vd zqB;SidXj5D)C&3e`H;iW_yJhPU}B!C>S`!?A>|+dZyn;$dj;@MTO;oiO-6&tImq?9Un02Oo|VEVK) z$T~o%L0Qy%yYru_xOYq966)F9u%A4H!qn}Zom4$2f?zEe%8>)W-#+i&ym%DcGvSXyG15NLGAHDzFbw6KDh+vS21=W>s2zpvru3E49gpiJ^3AMi(6xvbwqx&B4UW zkAQ83D(Vg)p(NJfl=pQY7onygAtd}hZL_w%j!kjx+O?c8E{X8=`CPTa{oP$EANTzc zlOGv!siD%cvT3w!eSPn7*%7z>HCRTNEFj*#CwO9_Ooe{7!)8i`2mqYd{^n$TL4now zTxPI=T3T9NU0qoj z|Ah4Q$`d$1)IJqeRejdW-Pd)h94LH%749QOe)RP%b)P@?>dW9Ddh|Z;V6wR+lhEb6 zJ}{J6t=-)$OiTbllI*OXJ(~jZCDQs54pEvb*V0XE>5|?EayJ-mZcfh95XrSg04xk@ z1y`?Ji4$`BJep}?X<-quj)C&>a|3|SgilH;cRK?(bX_QSGZQ`ejXRKNOiSfUtLEOV zhTOvRa0v@fjE{S)4HuoibPX2KD9GN))^-^-!FIkRm06Eh`#hc~aJdoZ-w`Qx$~k@s z)n*>rGb9!>cdrmlCKT~>S5X;o+pLA)@nzbkf1iWJ%#qPi_LvU9bw?at5AI4TD5z}!JCkSky>9yt#^~~o%;lTbD=hk)Zu-A6J?O3F%YD;QiPgBljaY;$mGIg3WI;mo&y)uFtO9cBG_dv%jvyty^YxE2s(RC_!NN zK3k4Kj9prHa&o$gIg37hv-YvUgV7)X7*h!HI4VL@ zeO^uV14j&EK};+Wl6@yGza`)u2C9@Qd_ZQEYNn07sN!`?i7U2H-%cZIjBK(Zuhmap z*KdLpRJ^>rkiwvh7ZTE;#eXa=-We~T_EOox!U94;qyP1@!$oqY++($)s@2p8Lc|8a0p2BI!phv7_~y+LNZg=OG3nN*kLcQ$ zHdkm{0a@2Vj?`F1=N=}Y~NIjs>qZy5REQ2u9BjL zp;S~4*{!IWtlV%-%@yq>5hP)8dO|`(%8`nXk1zFcaFxd&ZvVy?IdaCv8TIuDNMP-9 za=}n=KrjF{sdSoM7tn5}xBnf{a0KAAcHK%k}M6EhtXE2MUZd!z#kopCtv zuRkvhz|tjQ#Ba_I4Gr1L+O}UnjdERU$*ZgD>d_!90c!cP|1>|~Dg5|ccVE$v7E6qz zc!Lr-))Uy|HEpbV|0e-}oJYf_{|f;CfB8!i{J%ZrU#TSrKPrHsJ>3znQrmb5a9xtY zGXT~~90}Bu?@jWDLar*#C-T{6l$P!S>Em&z1K}VN`TEs8Q1Mzw7b8tj!!tsDQYah= zX#?Lg%uzR8#FGjJh=0o8H~WV4AtkU()IJ@Ff5jMp1Z~^^w~l1aqmnvr5iHpA#*?hY z4>NL017hwW^KN)nGJNftJc&}t*A$bZRmshR?epi(RcJGxj5aoUY&^t0Mt_1^oo!7zO_krvarF(*ID!OeqZ!eXqvH0@bueQs%;Am39(Wv zwy)GS8lR-04g6ZmiE?@IEz&3?q|2`ha4El_7IDk%iwb+n^Tf|WNXt?ksPM2oC=+pK z78fN#*C~9`($j;9xTLA@DyxxdQ0Q7BBO`Mj0^%P3`4a$&1Qs15<3}Frgv`v$)Kr7B z(-UAK_xrl9qI9wQ__4!V+5h2z*kTy7l~W3*Tmy3%2!gom<|L2Gs;dQrJ}++1w*wxj zx0{zP)UEyPehRc8FPyBvln9A@-CA@_3ZjM2yB9w)5Oij#tD6`aD*yE97btvyb9sJZ zV|ewxZ#VjViVMLpwBgy3^#|_S(BZ9}9i=*mM#ppE3jN&w4iTcr=Kjg&|64l8z`)oA z^z{VbW`-UJBXPLz0leVjN5se1B5ns(+{8ORXgL2R_d7~q(hSmFFmAEkJRm4FdY*|Y zM;VQIDDc%Q(OC&n!}TjycDA;*_V*#flW|+96USdbZ_o!4`d*pVj`K4!eV|y)YlTUs zxp(THSeve)r}QnME#z=ums*;dEa4v7BItk+&tFD}(63FRG-H|FR%tanBTeMAnR*~9 z37H!z>!(lqK>H*lB+Sy>Vo*_4Ezl}w77%a)5CJIV(lwI1J|uj0bLopvyuwj&SvQ|^ z1&D5MZ*S6_aB{eqSid)@1)9ia%}y7n9l(Y|$Sc&V@0#5Lf~f!Z0~r~lJt86kQa&l4 z-M6u^KcG9|H1D9_4OPQGI#hc+JUk4je12~37}mxIqNg9A3sVqg01BcLIgsNZD<<>n zOAIu4UuyjN^_%_{M5NW#RltoPoai3iL7{L(-xL-m!fg9q>>e801elS3-DRbP_o``MtF%=o(@0{CTpH4?wj`7cVlae|lie2L&;kx!wWPgm3)- zI0pxJ!m{#_bU_rn8V}Qk)M%n?%zpAzr<2aDJ+OosAO!$sH7C-jVrrKP1*KC&Nq zUw~c;2ps@pEawY^z1A6k2Dv+{24jJs+rDfRv(6F=2CX!k)7mhTmOh>I6BX(~+!|Y1 zUr{#8(Opo@&I;@MLdef*;k+2d(?StSxo#c;af=f6cf#@UF^El|lo}ct>D0NmLg5px zpqdAy5Zo*fL4fY>g}rGttonPIVIe9yI+m85YU3%~#!&r0T0u@rMmstzPL7szYx_Hd zp^Sfg9ZmG=Ubt}eIeQF{Q)j0hU@-vMtf;)42TjaDjmXE#J2yWM+Jwi^@_i^LmLEO}R20V1IR)Y>}_r7XldU`Qu*Kph&@$So?Ba!-NZvK z`@#VMrN$jLblbixjZ(6r>4vDKY?x$3RFs;IjvUs@bc0eRGIcWHWI;W5@o=?f(%r_Zu9z^cW@$Hy1fdV4pDoc)YwH3V?+gU%Rg z5Rlj?Db=8`x_|H9`%a|!yDV&y8-Mv^Lzr1Qx@=&gGt$za>J6Qsjg*!MA!(Y`@H`k_ z7_Yt=8Qj>o2)P1EdKT>p$)RX~YJAQcAM4MiC?28H3+6hU+}DINIqdP*TuueM8*4)+ z(A)^|buwctUuJ&Nl=;na;Ex4=3N(M?;^IQM2ncFSy0@Vy#)$_~C2MPHnw05mcj-t1 zXL9ZwiZBjJ-^z-LUoJJ)bsDqF%K<=wP2KV-w1pGZ8N*@X?tT;?)&(`B|20xh2T1zvy<%{w#X~K zhGL@liyT#EE(fGFPjt2REW^i@w#m?L?Fr~-eqv2*(&rl%u1Ym2fTxg2ULvS~V6DjlzJ;mfQBM|4M<^m!y5hC?cuiYEe2m53(&9mc%f{?xi0 z_eyQQqTi^Vg<*^Eeb(nufLFxm>1e-4wHgwpqM5qD|6uCk^A!;xeHBKO*N0E#_IOB8 z`<2Uy(&^E3gfLxA`bB&s95M-BEkA~I+=l?`8B?U_M8w^E}YB8>}rTp-DJ` zuDfq=XBHNWm&9~)R${?ipK&ZkK1)**qfpVOXKR)VVvf|vr!km!D&*H|_m9(a&i}2r^?dKqw+e2L&S20ub z?IFv$qyyt*8ch~D1RRxbuHq>H|w#nE7dV) zQ0Yd;X}$TV3nBwl^Au@Snv4W$3nFe$l@O(LkJ6f&7Nhj0@oKl2IQzXAaE5)!ZTdC4 zeKGhWzhfZo%;qKna=TF+22#gL&?+jezDW zOhNIy`RyGLF+ekgPzTBW4@7Nbp(ExFhcP}{Tm13)epLI$&y(uUpONO)*7@K%PtoKO zUtel~jjV(O1Rq3`cZfYsKKd~8H|6CJOjiCW7ftTb8bnVaNu%1K&pW`c2T6f{C3~m! zQONSv*_PJBDSD4KiR=d1MJ0x4G6zo!fgU%3x)$s|V1oG!*`YQ3{*SRt*_iDg3L?N% zF;Y+>@Ssav$xPdD|>}jsYubRxzi|4c$7(8=-z)E}V+p_2} zmGL+v3ceH#y%i7=LV$JQ)~#E+`@m;W-o5*!we)_GL#9rA=oVZjt@Kp>B1GXO*~4Yn;v{J`;; zO3FTRE87B(U=)kq1{j(QTCRamKd@5YBoYh8B4cA?a1TraDF-BhyO@~Rm4Z+7Wf+cC zV2D#pSESYddkG`QzaAY;kAm4KJu_3`Be1LA0ssOXP5YB$Q2@#SZbqlx^8^G_+Q7b3 z#?i{ySToT#>FKHpx6DWKYyn!KOlzIk^cSl)>;7z1Z!Qf1ECo8`sS=ZbK0SW~L*}*^{_>0!Z4gLItcTQDX^$-Sxk&^Qp<_~WG7xw_Au_>6NNCazCgIx# z_5co=j*ZPHKf!_hf`&Njy2He?V}IPfdIVu%DFj;|*b2(nkuo-u&e&Uogu{h;BBGLz z<$-h!g}VCB(GhoKwBNqjEa3i^Zf@3NpM!DP!6x(e?OQ^xnYj+=Zz@I;Vx}h`$>&k8_ z&0{aJSE=)cyiCy?m(YUx1FaXgRZizx!(pF@)^qdl4DEpn)4?^X)DF24cT&G&=d32; z#Y;(P(3d8~NDyQtV%l@}h}n*KWu)Z($!oGI9i9BT*S&Nw%tLoiaq*@oJ#llG(3;v$G9 zqfJ-|+k#~g8YRaRLRa-5w()io_82=ppllbm@?tH2zJ9N>K3fCp+jDL`r42;+{hb~2 zt!aoX-QAl|j0`Q)9`&|jYV~uQ+v;n6VC0`K*|puj*P4r5txYl4{xsfS$yx9SNlWlcYoxW`HuW%V_qX}itD))z1_ z{rvsW)B3Z*c6SPGHr;jsV^ftuO9&3R@JFkc5NY73g~CpObr@Q%8RfWgMOQ;ZN+nV~ zj=nxeda(r$v`;2E92mwa!oL#FF(8m}^5-Cvwhb7RZn%){P|xEc4N!r=DsxY#rp>Sz z0N38~5B~`xD=V;l+8^z~3%CxfvW`_q!G6QYXaVIP{5QK3mzW|pw2$8JfHt?+L89j3 z99-xJ!4(QesJe@d+QH2x2=5JSPj>d*oY?7{c}s3;qVQJjBK@Xf#Hby1y{t%r zfX9)8h)6xqW>oj@Unk+UIoe(JdAGg24Mtlyy}+gQM{g)-*)T1e*u51duS(P6CkVdW4(6G_!p5fXVFYp#DqeDr-Clvsn()ZTGT0e(N*SDg=D$Dz zcwZr*=2^q*as&)+;6!b2Ya1=MCa-OuoIHd01{R`{&!6QbB~5MG>mP3VwdqAlQt`js zo&y4v3a=GP&$#$A7(3Y7u&-ZFgyM0`y50jq2jsZ2?S8OQgPx=7wq7P4L=ez411aOt z;SHIcTUIL)sT4^cqSWlAaelc7Dk&);WT6sRMa9K|Z{J$}bQuke6t;}0C%jB<8Q2qA;o6cAjjD$z zHre5%LyCae@a&{h_ zfxf=GyZh-*;+X{~I|Df?z>yNU@+hC?fE#0VYU-2C^jBC+_wCmEICxfnL5T=I!hcj) ze+uNl2W@7(Qqw043i%Ln!9@f9XnHMU_^#V@bXFEtR@@NX-X;@qcTreZW4`P=^SrC}_*h%VyV}WV zvtMfQk`oTYUF7avUf$?dL(nWEPKh#4T)^Z3@%OY>g1n4Z6u8dq#b<7 zI{f{6r;VSjvs)yjKs;Q5#sQEruOjgl#DEldPV1Dlv>sux@s#88)i%#+*i{OON;;OQ zGZu6p`(i`YzkP6-qYfVH)f?BVro1>@ore!g?h()AEImFMoYMURy2$b(8{kn8L&jQb zk3rr78(EK%LD}1tS|<-v+{wvFWTe;#Xt5zBXMpmWS5C7Wzq=uaC_ifa%QtV{g!+l! zdZGq+`_0iZhWq#L3kV3Xv4xS1ayl&az#%8r3s4Y=nVLSG42U)}%OMl&IR2BkTUsxl zapJhWiS&^Rc33N)ErN=lDDXGD%nKaaV-*z@06Z`I zeg!fJNc&LlLIza<8COC=0zuATm_MpR$b?olHx-bHXmH`eQs(Ddmh^s%kB8j? z&R|OX%izsM;u6m$jk-Xq0+0P@t($Y0Bqv~i`wIPnC2aqaSZjb9NbcTk; zkF$h?NJoIvKJVaU{P?j>%wule@a*iy*@AFKyW7trQ|2xmZJlues{ptkG%gW+159Ro zyv{7`j)3b2x+#DbIRWhA1Y!vo zkU{&Cm63@9eHDnMFbMaDZ#_C zs9Yd8>QJta2(`86B`M3@?9Dyx{V`OaUNi$?8PFdXRbzOp$sl{wdkTp^0q!KxeOt=Y z^VAFDJgj$)<~+H86FNhE`0xRSpQ=`$5tRyo9P9GswyrM7&E+dc*QJ}9nlAgk_9Rkg zMxt6kiT+s;rn^@N_I7ZZ67yK4t!_h>1^N{e6BAEV`1cf;0A9lhyM&2p$4lb6`^392 zjr0GmtOg4E!YKlCId@kK*}0)Aax8$+*K^#x24`$|n>p#~_A3?$NUI)O*Lry&DCQ_F zZEbhp9xm~(+>4!hhgF+LN|5H1C29~Ssbr7qSyJ@+(RU7Geh~*BE zyRT|%Ywcz#FELEi}8VhfpAiLdKAm-b#0+ekkrr!4`;|SPKou> zX0~ovq8oTbL|%aW?Oh_jd2@DY2`bM)a6#wf&>tCq&{K+ywg%+`_bXQDH|PR>+`o!x zi9@YCR#8bwYD-^_IXfWs*9i$KRCv7xIuW2COuEblLv^DdeCUJwR70Z@6fmejA)SF+ z{;!YC4xA6o@z8nkk8+9}X`OrZ;9WvO!B0@aHRj)Wh)QZ|#%==w%3St|JCg0QC~byN z1|2=_|Lw~j>hI68R8>?=(zXHkp{bxp>7>s18T>@XJRCQmpF-ZS*p(o+_9_>k>^Zwi z$fs%r+7v!-iFpcaFB&;ngIMf+ci0v$<5RYK(y>0Msqlsi7yg^U}qu$WqpnNQ+ z)ZQ@2THuy|=)4OgG*lT@l9H0r(rr*Gg^=*gtgm}O;5&-hY>v14H*0rzlRoHyyzK1n zCtM)$L*AvmjX~v;r&$)*qz}*lY>u9wO70>%#k-j|Fuft>VGnHabv`!l06$~<{Ki;0 z3nwQh56=N~Bt0Etq@+v*FMg*Un0Ne)>WjoUx)h9uKi&mzew&}PU--PH?*i1vY zfOc@l$B#oe8!FVBtdO&@v4H|MGtK6=`S)!z}c=9MvQv!2T`+;m zyOojIvmo;~>epp@X3sADqB4YTEBXIsA5X$rwspy^jaPc!Tk`sKCINveNywe%rSlKk zzZ>p>&skdq-eDa~e)5@(!!<8Ul9hT01{=Z6X#c+${r=PE=cxt7m5CcdSO5y>QPM#+ zy^EkZS-0k7Eo^DDU10%SAaMPEHI$=YgEB}xINSyq2w*kr|2lzlD9UF2q6_C5%nNIQ zu7hXrW1grUf*Be~r)_I!Xn=+O`BS?PeUmwmBeSw->FChf!AMDF@(-YVmQcIA`ip?O z83}UXRieqb)WWFJXh4C0E5@sNz(U}5Q1D+*bz~9Y;fYGgg6FT&2?HNp<-C~?7PbWy zs1v(G=2LB&0>-F8sX4otWW*laAtA4t^kGB0d~laZDAN__Q;XD7Rb7~xO3Iu<{c0;N zDgr&y;<}3G^XJ0iw3j4I2*R75vHq2B9IoPvx6T#!y%+t<`br_B%jVxgw3*9`i~E#p zP%cIqT3Y4)nTH_R@qLuPwl8l%Kn!<|G6hjF`jZ?92_i%jZKV%d zp=163@WUG#Q76$>s%yj6SvRUX{)oOv(9Cv^psi6%esFDa2`t&Q?#EM#bj&}Le(gV< zKidZC<9Is={TI`OQu(ij#ec1i4ldv%rH@>qtC&!0>U)ofCbK$Rh9DN!ZiN1HRDR1d z)?FSMEw{?3Np{FHH?S_i{v7Br1_vEP%1j+-GWm6I;B03nEiX^w^ZU;qay~nsi2(}$ zm2hDW73CfS`noCV`DeZSGmd~k^HpEbuy^lR9hMYDzXN=ka9w38V|ucS7TzBSApN?J z*Gj*@DF^HdU97-#h5=<(Rb5@KG6(Jg0349kV?9@wmq%2?Fi=F$YBY2KXy9LMD_;(_ zkw5D^pxyv2(%j!)+OU2MW(jL+Yp`oTG#?$+WOBYC%FfQd#3l%79UtogNak|mpAg^( z$JzP$HSgjg%F4<;V`spY0Ja2Y3dVU%DjFL4u`3r|!}QrfH2}U6UteEK5408zRdfsL z04!uXV2vAjHVZKcn?eLAB=2>~$8SN`F`ETnp~bJM25@e{n1mrcgAYU2T0vf3x6b`A zO)C6x2nqPWtIUU(=P8JsdJ&LgAO!seH{GA#zw^|KQnCxMDd016m@2iIDds@Z1Jl_{ zFpp5)yN7n+-MB#!DGf1AhP)VP03iN=B7pvY6}twY?Y}IpI%nI%f~)PGh!OyuP@G6#+>NI3U>-hz~SxIGU8OTJwj8Wgm% zzd!NWl9<8}<^bF{z$vAK_I(O!p56NE{Cf53)oxG*Xj|E~?GSHwR|e31U(o&xDCO|* z5VToU)kE-&Xx6$q%CHcl2uoHpXV_GYn<#zWDTwHIxo4h7dM~38a7bh1xC563Iht!& z@_N7_i0h?S?be3bM85;517wbQmGHs!sTdJNm|_kP9L&d)&uc@4iNz5^pK|3ujk+lc zO>}ubzZ#!yUM`;wKM%b=w1NH7Qd_>)j`Q#VNidM^v0N6yMZUkI$kAG&PsF_!)gC!U z?EWGNd9Jn!>K-r;=*(pa(G#wvv`4CkP{pBnjl@Jfv?G$5pPyey=mff=M3X-O*90wd zO5AmxCso^JemYDURS9ZfQ=kjn-rGx8>)>--iSYFmU#&*J8LCIhpX5kNAspUqH2KV* z?AH#MV{eK=0TCV+hM?(8I4{sxJO%N%?L60-*_~uX7oaXcV(77sV$^t_kbo{nNku#b zX=oOplY$qH62rgVTg5+b8@h>U@B-klL}E-w{UWMnUsaYMwoWf-@#L6@Q_JEv{*C+|Y!e^r%!K-f(6BFASQ9nw4#u@_~ zCrUaUjC_AzY)~=+j<%crj|PTh?p%cqz(*!&w|Bp_!pB;Q>Zu@g5AJ5=C->2N|^)w;QqhY8TYkp}kxh zj0AB^`GqEIG0edC{(^rPfoeKa;1nyI&^>?(pA$ z1<7Q!1w3?#Qt3OlUf_x43Y$Pdd@?$jI$cJ)9r&X!OZnazyZSfTd7<0Jypys1W=(kC zQggIPo}VyAv>7qhuc3ywd;Rw*mG7O?{dKPY)oj;^HZY;{i^|~OEkh2obnK_0o&d-g z)qky=q^-T5^eE5Y(`T_{``a{7fVP4!Jv-LjsaCRSK@S|B`S(4abBwV2cKnY=a52T7 z{BA6)3;hWN?ALysQ_UONyQKH@&r)Er$H2SEWWRrxrLPQ0$Q*MO=A(v6hc zA{tD?ahEQk*5$SnI9n07_kVNuz6?0D$c0=T=G$(~k3l8cOI`Qj0wmnK-@Cd76`WlA zZ8_8irWm2qGa)k47u^X0tsri9D4nj6@FwkZIq+&TM|Cg$)0luVZEnxn23i=tECGu` zzvwqm`~aLDdXszpZU7M7(2%TFs0%J@{URh97oN1#aNh?PVjw7teTBeELI#EgPDr7Z zW&`xu$~ia|sW%lb0O(AyvWMV}gjx%9JrGgO5IE`}{xHG!I^cfQFM{kWRc`C- z41V&vJ|Ce*0zm>QlijVYWLkP|?)@N^I>q46`b~l0WrdDYU{|13JoOai9$5 zCn>1@%S!;1RMX-P+TaH2iXuK2SV)h0pH|&D(Fe(x=-QKJ?Hc5NG>^*}30sbqg7dh4 zXb7Ym0s;b0@X&(U3#ZR~<*DgX5ZXBg3L<8a`r5+6XONl7L@wfl#Z{%5b&INiqVV<( z2jD13n2O-LzICh2adptRWMF|G^&l?Eko|>JKuVpi=2OwuUEcarnN{E=5Qn=3^govM z_Pxg_UhBDZ{YQh(O6xC4g1 z$4?@(naL3$S5Ei@M7w}5#seEtu57VCp%fP3l)Adldb6ZP237jDwIv5_gb>?9Z8n3ez zylNR&@{s?l$Z?~{F-ILTdbZrV7aB_j-v|M<=9hs1bjL0@QD8IdgK)9Ct8Dof^`fhF zv;q%xx~L>Dc1pd#N{C}6xAeUcLsEd=#>9aiSQOANkus^Ork1-QCnH1YlZW=Nv_grRBmwNBKzUYGjgF0t4GgI4Q0%V$&&GH99iZvKx+B8K*wNC`GByUiw(5NM zB>%`He=Y>y;|)>2>*VrSXjO8o+V6WOqT81NTC3KGwW1;}McRLN*u!!9yK7kIcd#i4 z3Tq!@v6X$cS|rsGj<(r)P!n%new}B-di4SZN>`l4K>HLrJn%1H6!J8Nt_Pfba7Fw2 z`JDo*2*j~k$lD>=#+1$%wsKP=`{3sSSWsmn&p>`75ca?zR^ zY;tmZ+}yIzwZ*_Nzi+Q3Qd4hl64b~oT$3GAdOvC))8bF}+V4p)7lZ3IN_qaKDA=nT z3=#H6j~)@PLGz#pg(3LM6Fv4dl$1EzPOXzn;7cU1C_rSt>jTyh=&r<^ylTq*zquoa z!Eh=19WWHM8!+qE{Dv(?ALQBTZ|dDuQ(SBZdkyN2IBrWwzNz<#_`#@pg<0GH+;D9s z2=-DaZSadXd7ade*~F%+^Yh|Zd|(}?n1e57@xcWS9tdrmY_pgD3{%TKFMs(q6GNSz zrbJn!5sh&?<2vWbIY1%tHE-!Bf!ZA!iXt0-4hQoYi;e?CDMx_sxvKeQV76K8@_7ds zGczp>S9B>;#=yWpA`~GL(*O1bf*{btrioGjb-f$ZON`~fT47`J3*7YeM=K1_fr((V zy6&20E4#vp1D+I z?2yn7^S)D+n!_KJEhCQ`1V2ee+AMLFoEy2VRmozGDC_K*U=rnbq^W^4L8jf9g zOo}D&C}I8P6_F_W?%(5a26lG#8q{OHRRQJs~(ze-7Q31{xG}VF86X?RX0ku)c{&RDAr&$^e7=sk5nR zmbdpKef@i{9{h&<1?>?g%Z-ErPKbq=m`_QGJ@g0xMFzeWS=p=BM>y6Wu5F5X#)(tm zz7d4JBJv+y`1Dok z)FRk`NJuPUG0i=V(P~=a<;3%k;ukRGMyW3UUXDQ-xLMa03{@ zqR9JPP^@ziz8rwD<5DXl8GuHj7>(Aeeov|cB@a(O#6(&=XI?KAdI13p!k43^JT@7l zRu>`1C}K7*ecauglG2`tqw#y|{3}_j?7X+1igY3p{lg^lj*b`}`Qu<8FVA-+n$*6z zUv}z8r>K3-Nk^wWQ{gfurkILKE1#Zj{X07m*AbTFctKA1Xh-&vr)kGaFE;&SJBHBT zp3oD{g^zDDvKEwKT!U}yQTy5tQ%bzaYJ7Cq8aWpJvvuh`{ttZL8!9T1X%&tuQ`%9n zeP0W^W!Q><1@r2E76H`A3DLtRVGn3n+Wx%pA!whms}!TA*0}UbPRv4I`FsAP6Z5qr z5sh-dwgyZk;<8-eBo`KD*)wvx2B7}|@joAXMO-5D9-nW!J{Q5Q>2^J$iptubMI}WQ z70iG=9c{7u6aEH zFu%+4p*<>+no66*l%>>g=%5;LSNcl;GNZ7(Uc&!+>P<(-+=SfP5=DClNDmf~g>SGm7L95e%Yy}l z;6G~o+x{yH+;c`LoP3e}TrqONTPc4hoZD}PR%2D;I0BI z6a!wTAuwZ6QTEXt3)^GC~C&!tMRxpy=F>+>J(KU``8FQy!H6@gAiTYmBFGW$cyBmJLx<+%^RwEBr$JPF2 zC5gcjD)2M+rk*F#^WqRYG2P0oWaVEg9uK<{zBT2EOwa=w22;~tedLTa>_U)y9yGAZ*?QlZ71^+tm=j#|; zzKYlNUUJ=a+)?7K;`7}WlNiTs#6KnLiD$XvXbi$(0%knf47t)=$7%VXo5C{j?$?*C z|25cikvWp-GsNsCQ)`BgzBb9|)x05^cXd8J#~kP3eEK@;NddL3t;<57S(@y_BYt+? zw`2->m+0tH0#}gJ2%8W5-9pcY9s#+2TN@1|HIdRz*GJI{o}ay zQOamoNmN2sMuSktN(k9mDKj!ME4-+qLdvKVNl00#5JH5k?5q&7iEJ4ezx#Q=uj})@ zzSsAE`@40MbU5d{#`F1j+}C5-Ax+6uaU(8NLw0+c+2I-viiVAmQFi>!^whIPPTjX1 zb==4Dc8g~Gs)_njYBSV*_^5NCFE9U=3=Em-gL zE+7D)wE+sK=hN7;;uJq7CEI4_-1nXv;~>LLY{t@!!f@TDh=$?oC?^=)kM&$d)J#WF z(K!VL*PkZ|s%LR81&jRhazR|fY$ke|3x#BUooVR)dv2C7lI~V6TNxG^Fzp@lQ<3sM zVc#%t&v|eqEuB8=U0+%&DW>POx}3}KS~0hPJ<>T#t-+34Dya`}b4)p-&K%DBBm9fR zeo?E!j$FN{XT@()y6)Uj89P!`lKibfj&(7GSiDYQ2(Qk!zO1`E7FGUi-sMF~U0w3o z8pcdF*1=kpV=APW2b!8V3HTf;S5#f;aWI&>&PE~s3p9P^X=E=iY4Y7_%jCqw*YWZG zReWi;VCXSp;Sv@7*5WRGn^9Y*X}|pU>NnHw=bfySf7~E3DBq6rl$DkB>;;_ROZfVB zUs;xY_!GX?b7TDyZLA#06xn-dD&rypRUYUa9~Y6j_ARIHnaVBZkL7!s`a53Oc4RyC zA`$FrVgt~Q%X4>jHq0g*8Q;zWe(TD+#<6|- z@9|Y*6_r5Byq_~0D=QFR9X-8VSKH57UG_!k9ncl6EZ+cghpThcy=&plZIfVz6Xd(9 zS)e%21r5ARnLQJs&C|jrSo8p>8H@?ISbm?dge|^A3m&obpG*^y9`OE z&+_o|+2|W6llTtpcWJxzcYCo=-jDoS4d1wP#{_@a_?ccr#j2)O{r$T>r4GL1hmRh? z)p5XOqwhAzFRJ^mQZvwIh8Tsywe<83aXq)UH-6DT+^iY7Z@U186qp9cicIdmgc5e+nHb+8#feQcR-xz)h9 z<8nzr8fCrb0jUw4@<9`H8lI!-`V`qhF7h` z+qwSeyH}6i7yB|G+oJH%Xe{%J?~ctDU_5Zfad91jjRa)n^mIdAoqnzxs(gWD*DLve z2qz{~AiQ;vcpS!?OuGdkLBZc>l|3wp)oNs@ zfRj`pK`(sft|@VB5N^+uH*XHpMap~rIv?)B7!&itC1Dt&hRjlKQZ`lg-XuX*;xeN6i5RO!y9t1Fkap{GO6JT^Oj|BvAE zXBKZw%vg?CH)LtEvu|3hW-8j%!En1?DTv=cNRIUAOX)8HFwXJ)xW<(E`Nfr#mQpv; zRv#`6io8B9bm>R-wr6JDXAeysC>61T3=tjqDUvk8JP|$9bG7Hu>8_m{T!@jiQM+88 z+YzI1+HRwxgKd|ZW%-a#3%ax7_F3Gh(4rzfvd%gK2s%3}D;MS1?wZw=xdNybC`2$^ zDj+&`?$`kVDgzlB54hV9BFSV;_812ti^#JI^QoH~wg6fFqEl%~8(EzX_dJ=r_O_YU zym#3SJ&j`?z9ML%y_aY8D8Wbz#DWW>S?Y@y?=BRs1?EZ7z^R0e(B3{ZCub4=D<~*f zvUAY!n8yVY!3E+6UO-S?PE9>~PIzdXJ9m@*!8S0%==Ow#g(-E|9H0-nY;9ehY;<#5 z#$Uim^qevfCGJ>_8iNp`4fN})S3JVEpmYMB3hD<^H~~Syh2mh(91pxOX!G}B%Thl2 z^Xf++95)c~aOqMUe7+Ee5iYK-=t7~Jpf+17DwSaOp!LM_dqA^!U^njKiUewgm+MXIlRQAJVID#`16chw(4-(|&`UQ74D;F0OE$Yx+?GiRQ zap5zKQ(55Z#C6;jXv-lhhY=aJ_c-GI<@ z$K0zmeW@`oXrenmtC%&nprQ%Z)`l%HXt{Jeos%FKo>&PSh)qfgeWX(sD;g!Xsq3A| z^7u~qNY)hfcwU(q*=Q%IN#?D#`pb)oiwp4cW4kqbuMfN{?*y_GwfT3B2j@*nkNq9b zg1}*MpxO|fysNXb9H}29z^`BDcy~}3{Kzp4!vGVZr66(LxN#%zAzE`|<0#5J;paRs zI5__Oy_k?t>ihR0mHj=_espv*hw0=<_15Ba#V}MO1uFX|Bl%Pp!yRGE-&+P=DusTD zig&ArFSBTVPm6|rLdt8Uml_OYw#Sd>DVyv4FD`~U%5gg%*~OgvG~(j=wdB(crHbcH zUu65nE-@w7C_&MvnATsW?JeowPb4Sor6jyj3x4=$@|sLfa8r|np=sS8rm>73lOEhV z3i3;S4A5-9zf)%JbbPXwZ8$g9XUqXB=gpe1-yb{?!r+GnDp6HhI^7<4XZ=tMQfy0UjXnc(_ zn?S$)yc^v!HKaNLc_u)W%d~j!qkA65-1XmL*#)J7ohml5xiIu`1n}Gtu+wL z1!>-aZVyIsOiT=PAIPoMK6h^OmMw<_1^XZ9nv3*o+f5LN>mrl7jq6tmqKP7DwW7$4 z!BRJ4xqVel9?m&wdJ~npBG$Slzz* zr&1-HA<3}@c)`;rbO=rYtlz~sf}MGC>$wplbJ{{;A7$*4O__h#ifV5LWV3AO-WX3PSF zzRy9(j$uzohVX-*ABdoIqFk+%qpRzI5X9=8EqNoG{K4Qib^3(~MK3{UYZ2OB<)y7{K%TRy3Y?^+UU)3-sr)CIE&p!v zwFjxcj#7vEOYt7dVU#hz%&MwXk*i6){XCf0K=)NuRU;xI=H*PGMFme#dMR= z{U?IvV7edQ2`~tf)#$>CeKD_npVRDPn3sK`Lq~T^)zZ~gZ_uo%<#qH4LSmq5xUsij z(sXzSuh5#@SFgAPgN(HrT;Inj3sL4Du4W((Z*sK~@Dr!3H&kKbqM{c?pQ20IDI_$t zHLUOgR3J)9rm_g=l=02a&SqJC(A?Ol4NlhDnwRS(&IpCF-q)dKElMcot5>nf`kBOrsz8% z0%&N+8eqM;y82So``FllUI&Z^$z`8Ml20U-k()QyLIO@#oR*#r@iMVN zQ7_arg}M|ze7NJ#p=9j1kW`TL4tcT~KA|kO$6xB8;mUC6k})}BV8H4xpZ4JcRrH6| z2SgoMH43L+dDZ-FgmJH2Lk5zz;i2JDo@Xcyu>2+VKI(1HJ6eeLML|(<7c(>50*=Ra zhdA7yPkxn}dcWn!bz;VP#h6=;$(_{M*$Jg0Ed3-rNmdr~lik8xEZw>|TY>@t$fp9! zjq_JkUHxd~#z#G}`a0<~|G}mnJ>nZ4=F>>rXm2mAL(d7@hEarQiV%kP%fbt|y6wNuEQD0;} z>w_>denmyRiHsS8=NYRs-s36d@=jjLPs*RQ$=!DRK6-C@uzH|cMaJVa`7{$->;tw( zSLg3OPqvLb9xtn+fG0Zt@p9GE1`b1G?H3JaJp6%iXSt8(Prk{LQ5mBv!tyK^|=W~rp=m+7oHsE)aSBJ^Le6~=y5fmia_6k2fo(eb&wZo+M%jC! z3RQV5P%8Qrcu{4Q~0L1>2@2*x-Oq5z(JL+V?|AZoG2;v9fchIC<^7@c%UdF^X|>7%EC0lBEecVMz>(@(yA z+c>_62weVB{-k10|J$y;lqasPHU-P}pDrIDmtSxC+Sui7m05!|s544L)lo+V6{-82 zGLolZI2tx36n>hXK{a&C>f#s(Ytv|Zr#E=332h8H~$2&9RAkn9gL!@qM+~$>3MO;Ku@mqxqQb5x}J=&D#Ce$ z>||NAN3wFx%8KD+!|S83)m}f?ed?TU>0r}c)g5ChJ*2_?y!`j2nV)(0^!0Pev0f@; zSp6GzDYC@LrA70@@#8a^``W8opAzYfLPgY->V)@FHdY4^CYevQUcSv=6opP(?$)o7 zL&H&dk%r@{uhmYUMspGYVtP~MoaWTW$6bw$F38ju&Y!genoc>lgPcw$swmx;^2 za5pxm8V539Wz*5n@PLQU+t~D~r4u%LxR+3i_K%HqcFJ86eX6FWhPrZjW_Y`=!^WhO zQJ@FmYQ@j*LFI;S1x4vZwBdC_i7P$BHUwUTFd7kAGh=DE}7Sv zh;MDIqsTnkyDr~Q5t{=(7Sij7XUb0(=K5e(z<#4{>R<@FR_Kq7mNg?K(xKepP&6~sa5yQc|Qh-p9u$|zTxP9O9tknJyxI)hkH%lXo3`@ejwpJ zYrW*+8-sbj^qsRSM|kE_9J#9t%@)JTNL+4m#Q;Y)M?~~6`TY&ezAZn{=<4*8kvY)8 zaB==L7WooJ@-CI|^8&{F#}6uU3-T?vFAhCx+|ij?K6Yia|HI3dy|tw6V+6I&&`H+q z_PV8X>@_8IXlUrk^XEJouTQRy4m3-(+8{>-#Lbqlj!fZFKXbepy?|>IijR6Z;wsCKEjfl$WQ%d+rO_xa6~|J`pKwXh=^=qRLoE zT<|Uf%HYAgkX2MhfL%h3j7Dnh+gI5azF>(#K9G&QJuXY1&&;=L zX2faQ^{}+;sOMtiIpbv>>jL!^=j!zYdI9k;W>!jr`UFwCxm9&1X>YI%xkZ^^z3pan@F2S3_p`dk6y8p6h znL70#{^d)GY!!)S4;+uF=lTB8D*pafnTi-4nxU&kyOKZVJu5ap0_LYgcur{Uc8=iL z;n~l@_zj!lz$guk^M{6V+u0q`I!FC1Ia5bWj@cx%_dGPAr-N3}@n7<2n`wa&mEu`z z{oqPMSjk&D*7Mxn?~R25cgCtX2nmnZ;1`~BGe7pV0R9Ds_P26JTe7mW%g#dq=Y95+ z+%1gANQhV18@6+PEX@0z-9oOIRZdLI7@yD#QiA1 zq1+{*t>S;Y-h;`S-X~AeteluYK<7_B6guFZ-yMs zjaehSQ{tleS$TP#>x&j;g?xAD5ss}G#A47UdEk}M(YfY(Pnp%KSB|#EN$!&~lFLq# zayQw=dfs*L?2X<$DWZ_5{4K?8_dE2@IFWaGHr{vO+Tp{bn2g@C-Xm9Pj=7HsOZ|1+ zq9XKHR-H*O`^Mb(scl=MWxCt{i_6IJK6%1q+2U7sas1>YIEtc8^rAO!alSRekc*1V zTR;8k+ihkW?zEEEw-21^$FL?|0stH-2Ce@I*f>1L(WZh9R^I zdl`;z(Jbx>Paqoj4us%&oSWJNsOa1OhB_2T(E3|hTJ{{uMdjbqPIb|$si8U&l>~QO z$If@z1|a;AnF+_XPhj0Ai+=2;1pf)<+QCM=`PnT;(O52f@ZsY}l+UD?+eJlfbH_U? zlpja`Jz+c;m<_5SPFR;s_1Vwmfe$f%U+Nb)STJ8MT>d+zCv@<)>`6|EZ}y)YYP!NF zXZ{!ZKQ+}d(08K!ozv)o^Vf|VpFMtj(DbcDq*TlDubn2RT(uf*#}YlIvD_Qfs`0Fg zMcfivTAq5kr-w2&%^kdL?D5fSlTza&KHN9QbH>LEF~oz(AA=UOjg0QShL-sMYTA_> zf31gV`K1Og{2WN0J*}$xFzx5RZM(+?I9?~?b6f4)&@dyU=+-UF7QlFy+H#`&U0Pa^ zi0Yq%xJR!arxS~ilvpiQqtqb?(&Y|)*|;<`F^dj7=XiAw>58KdHn#v1uJbCsHN`+q zS1;@Q`&a?4Q&aq5mxY9mm@zjR8A&ocECfzf{q}mC(jhSx!KNmU>&7OXPsCm~Raf)n zQu*-SX`<`ipr! zW^$)|BU)wSJpY4|Q#3A(Sh0dkSM8XaM(T_x`K~-Sd~PS=V`F=ukm1rP2ggWZ8X%O=xgGgD$l z#*XN-TIbIv)i2W4Tb|mwH3$a+MG*za80*^?NIOHxkGumO_2;VlnYu6v?a?C%;LfQs zqPU*p<1R2&wGeE5yus*;!v;TbS~TbF}7Dv3UXEhpJ(RbB<3q1%bNEj3I^Pd-YpYWLYiJS#pxc%4)s+YCh9bCnmzvM{ zz86s3%W~~{e%nGic;zI2+UMmK?=PpNF_5c3J*Y=NoSpqI1jh2!!>#XaFB4Sp#~a!2 zZkjXP`4_&wF$@$D;Io!pc^#1N-EoJ0zJIlCZhTVyfMk?`UW>j_iiC%w+^JJ4YC+|~ zNJpzH##SPWo(GIli9fNv0?-&LC73j*iN*K|?F6m)-jLJiwxI2-h0h@+IT=%?ge(d{ zLW0{SP>R{2Rf4k@@To@*(EVG5Xe3XPG+7zO36k9nn>#^&NE7sR1o<7^1 zrXQrBkSE`3qovZ@8^ohN5Pw1bVo&DpM~R8T>$3R7SlD*$!p`O!*2j9A;=iGZ{l+mO4rC{x*Ax##W5RL@CgVwrY(>GLh6+lTilU6ha2 z9C_*KS#xh?-YF~3l6Pd}!B&|-Q+ZCOp$nr~kr2szDev>$vg=7z#DRj|dHR6VF^pSy z?zzzPusuur-aY&x?|ie~kxzd&5(V);SG-Ome>KtiEjf{@H~ZKI;3w6gDsDL+jsUQ5WVdEYu1M zA9XFCd8zty{O0_Pic}TQ(dF)}iO-`kwZ-ZcQVjTyt2DNwxKg?@mRjCLe2X<5Ja)Wx z<=JWON-6j%X*xDji?x%{@u_i2=U$2v%^BmYgZTa;%~R9Lw~233-XvbOx4D>ZkY>My zrtTo4|9*uH0TDT`a@ce_-_OXfD4sM*&5Oei)ODCkeHG?g_@ZEx^eQuUt>5|r=5;iZ zl#+`_vc#L3bdO3%4E-9alzL`z`!sjDfnx~Pf<+G2n(usk3-bG!hI4N7f4wY1eAz_f z!s0KltEn5;u$6W_S=q)nuXxyE<_$->>4}H#R9{I>WQyW~#0MQC3gTPMwPk5uaDHq4 z)_b)T4eQjlmq#aOI^2GT``mIi?&E;0+Q~KH& zHRowb?CoD4KE&tnA^J7DiKO?3$)|nK{QgJfl#POJu_BCv_j|Py_ML-;)U&(DvtjF2 zEF!UwcL84gd1GVqEsM&osN)S=x4No3Iep=HHFlSgpH6WT|3l4ZC}Q`$Goeb$)c#Y- zYX4ixKD*J^bbsgQz%hud!v49oX4F^+-=9&T_ZT9=B5uN=5Hm0}g_p1r#4fxx^Y+lU zVpUZ}vW34t^SyhmTeo^7CNS=E=H^cN)*vjk?d);SV?T;SfeL-gCicA436H#CTUrHC z-)iKjW>g4QDwk1Hl+=GIH9t9VQ@)z!=gHW6ey0h8iZFhUG=GfiS!ZH;I;hJ?Tw+Vs zJJq%V(;sHslz?cNt@ZI+s=g;ye9bxT+NPkD9@+2;rAD`VD_&DNS?;amai#BV7SCjb z7ew%8tZx3JrDF~_e;&%otxl=q9e0(&i*T#X}zR3|!i)lb5d8r}{>+ zlM`azz5C;J-~DPR(LX4o-m<_g#LP7_6!>W4_JbY;*RBl7@HLMcw)#adWLG!d_;d5w z9ICtIZ7e$831sOiFN_4gHupUGBuyzuV5L1diUvg>1f_dczWR1!hm`8xt3f+H+H6I&xo2 zc^>nQvH z$MvpX_wErnKa4!XJ90kk-?=j)ZXY=TV#alMaB)vHMJg^(lSlwj4UV*ax^Xl}u^m|y zloRmW#n)T^?r0)1u0ikosoZC{L8jy5t?VB+Z_I6#mY$Hu#hT{3hD_QQf*qD!GA{TDmeCy8kNzN-;Cq~EmIAj@t!8-bGnwwCsc8VJ-yz*3zzZHtXX~)D}+&S zaJgU`O(mV#W-eKDyIYE8Ik;w`8x;Ngd~DeR?2e_|KknWpqq2$Xi`jePm@3bIYe}^3lx#8!YhuKcpZ-R>wiK{G zGSJgo9VWuk^78|H*W9T{si}IH#^t7_CU>3<#Lp!s{`z6J{QT`g6w5n)T$C>#QZL#W z&5lKh-{Dg=FfDkW@vx(j!f+7_>!a{P?$#m5%^dPw?BHER31KdJ4Yo++%B~HczIE#s zB0X8@N+DqbYY_jVL>M#Ms-7H%I=ZHYsG%a+JS_CgBQh($L*t>#19=;Wq{WV?XO(%f~yE*{NIIz$!*jK9JD`RZ^Vusr6-Zl zKiW;-rP9Gj;(@3tF*(_N5hE^Lg8Qc|uoy9KiYOyEKdZcau-l)}U-3=Y)a1g@VsBZ; zRD>oIr|gk=fg%w~-Cem&hv@>@4vSi*DJ_f~P1_RMK>YA@C@d1uN}maPt+?m-JuI*t zX45>k{I@&Q{<*(b8?tpekL2v(YlMD#hF;P1$Xzj%&q#9>xz+3=YfGIWV_J?0@ij`GG?v zz+O!Ni!7M$3Hg-iUwxA>U0i=vmG@x98yb3OF^ktLA zKsJC+6O1w^@er$;X9)S>qId%rf(IEf{?@j(2seRg1KD%Pn_0s}1w8RWq4VLQz{Umj zF}jL_ORAf-Wl(d2iiW4Cn-OYOFR&26tr?l0_w9>7irZ5muO6PE=$D>1Dyq&{irj0{ zalX;nHQKI|ZqCO?P~k@7$n;NNnV9x0dX7asqdXXZ8r9)!q>L>4D+z?!+F4k}mk-Ik zA*5T@6?lw~jvASmjG+%xSAy9@kjyJB?S;vZK>BWPy)iuyy+wh^Z89(@NP)D~+PH<4 zOa8K#T|idW95sj&FWSK1X;c&k@)0c=DZy{v5Q6z;*47X-hTDg10t4&0u7Kq&I&+4i zw^7prY-sFY!$R6mKC!w-_A!KAAs>K8QcZ&HQf$Ikow}7}xPbxNY(oOWvW#LG%h~7m z<{cDJUDid;4r%6pcD=q-TO;twU;PX2BLQNw%-1rjmzn83*|R~|nM@|u zIv5rSd4o|j5^{%Nb^g&fGVBsEg}%Ik)CEW}u7IhjDM&`}sxl4aMYr8)h;g2HfM`z$ z7ho^rezB9=GS@7+tvV!q0ERsxbRnNxU|ig$PFG%GHBSr5j(3V8wdd z{9r7|NA9%orfEfItbUaARjEx&E`#Ur#nj=FkS@u5mV2}5*Q8< z2KWwRJUhvfq!{GQQG0fL7oF_mw7bWUWq?Z?$7X0E5mQmJp}0f_V>Gcs&3<0#kKMrE zmF;v1eq3r06*g$_>bs?{6WUYT3nks~GV$@kb~aAa8*`2UqXLu?F2UaP`7=#)iY%~H zATEdsq{ik{TS8FyJX{UO4T~rq1Ly|2ZECzBd`t_R!u`X;i7#H9jH@r5l=d;c%IJi= zdrdvP!QNi2AN6-qkqKNk>|$!#g+-gN#|&Oop1@>JtfJH~2Ne}VykKq~FUc)o1#B_v|wU9r}csG7d{+2DZhpQY) z7~&1xksO)S|4#?=e9J$r-CqWgwaotB7pYH2^ACRh==6G~z~az<+M?@1)TZ#{_@%f@ z+rya;j`QL{Ge-Y3ADx5jEkS?xY^#>-}CiQ(EN;L`a1KW8CQUg*;=SnU% zPirpC{GT6YS4>@9db<>mnsn4$pu7U%`c(3rh zxS{OMJ+gQ#t}_*L55Z`~IDgf*2?-2mG1Te`qFKR{p`tQp4jrBOGmkfn%g>a%STq0i zYvN%3WWnk(J(sv;6${9Er>Ec*k(VtlAz{I+-cnRh{H3%M|Nc{opZlZVjH|cCEetIL%IDsA0DF6>iexM$7;ScY z8^?8I*73P>QDEvmf!h(;c!2znG6T7UqLK4bDloxczrJ1&k@-8C4#bf0$KEi4IAcD5zj)c*|{DfL-Vum+T3phYGYjoy0`utkh>dL z!x88#Cs&O1?es^gloz$#gm_%=fAh*17(rkRG%(I3_=pE;;nn`h5>gGVjBQE4PSA9& zZf;PhZzYkA!R<|KL~JIE0nmju4w(gFY3hayZh6@PiCp|Ee?U zelqDwXWns8T)^EGNF0drEbg8Vg@1Yq-3KI6iX;pi^CwC!VhB;E`Ad{VaIX8#x2I5( zSPI;Jv&qa56F=8HfAa7lg#lQiwz+v93RJ`=sW2_=rlT2J|39&iy1Jk7vig}z9Z$tN z>NMtdggeUT>{Cn-Vc|&7cv({h3b8}^dE63|7lEp^RaNpNv;dPUk1PphnHQ_z<*dac zGBf`m2Ah$L4JPErZkf#IB4%sf7C|S63)*DkiZPUSOSxZc(rf;yyKtfz$XNM2dLRH~ zAVb^{1+4)NaQE2Q3i9hSTCqIcjXN37e=c$UwyIKml-+4JryL~{i9b`nlG<{9dji!7 z4<-hAqfhS-P_;^L&HJ>rCH=r?5iZwjQ-?)FtkaM0332c5arT~YlR9Z0wSMg>j}{oo zZ{E6v_6ra&vTbP{9LBNlx~FL3mL{34zv+HPlx%(W@@DU3Yv*g%?g~k-mIg7eF@zDx zjOP_8Chg0YEsCY1&4;#W?o%9)m)9oZUz@t^es*=&W|64VwNW2 z5`VkQAN*>T3ZbwU>2!RYzFJ-fny`)iAeq-@b)~#p$ZVZzs^sxO1*Km9X9EL+>;)gK zFYmH9dzwKMWbY%BcFKCO#i}qrd9M}r$kq8*1`r`Yq;D&}7C9sU*Rk@uZ9=$Sz5nh* zSv&UoBh_Dh%(nrqwyP1BfFvQ?u;8Pp`kr~ACHr`esjt3ik~km zzC(&xe86ez?7a2I4_pF%ejmo2^TNUeWaqBs)G#}JB*iG1riec|R`|EtR&GxDy?57F zA?=;(ZcTC#LRHnQGu5S(&sRJyw)t;AP#aC;Dw)jYxoVYFDF0yO`X8ns)4|>*Q_<4< zQIDQ{W)T|zl5}m@fGeh9&yQ#px90c`+@EcjflS$s@*-~o7OM=rD6uSGg><({m}8j73B;`1H!JBVRg>`J0?Xd z7J&`((EmoTvdJTHvb&d$Zs*&Pyz@{*Wun4A^`p&yb*;=B`=JJdhv7hCLfvi-MlyPR5|^&mC(&EJj3oZj(seoZHK@y6 zUOzwfwF{9QUmp>N9zA>r#V1O9 zuCu=~{Hf{yM&40*ks~S?ygs<~2_f?vL&on`3TC@^)itbB-_Og-%g(ZwBEqChMRs1k zjCtWxX=y4hk$b^y0W>5m0UsfmefO@ z^Y1QIg9iEGT2@sAUx!;f_+%{V5D`O7J(y`?8*?r()V=#?Ue8=!X6x<(SmUTf&W}!{ z1`9*hcFYTz21{G!N#b0oXIJcy^twd+8XEd}2xaWaqYbPwyScQPI8A5fC*N_jPs)QQ z1b=P@n-7LQn~BnD^Zz5fXRP?ydUNfV;CVYy=iAfIJ3!8n*bPrGMxSjswf2F0-`}t= zbJE}wY<CDRN{f%4q*~}N)1#D4h0-*42L|zb*a2UxzqYxe& z>WscJAA!vwb^g&Wqhy38LZ2o7b=tGA}f)L+|X$Df8Xo z$7v1i6v&>#{~L8v+W-HeZYoCRTK;FVre~uZxx}ReRIgSeZ!jSt;ngyA5fg3NuC#!g z3tc{B=+L5K=SDlUfbF7wfXW|ydja-&|7%Bs6vh0V-a*7`HNl_pH5>$b)x0}JXFeuZDnB0t%k=3X5S|Bm#5JcEACWC`sdhuJ|4hif!`$@=uIFTA zl#Pj{Y(o<<0KH`!qf4xY?;Udy`O~h3o#vwX)aMObR4uDR6)5Jevs|*v9>va^;rJLB zk;r=_&T+&6;AO}2-_`ckBBpuz1+D?FCKw6n^OGfIBQ1<%zq#>(&U9we8&&{&YLD8f zO07h`dh>foEA+X4rHy@l)&(;;&5#KHit>F|s^V4uBgbrsqE^If^{*IP?vC_r4?F^Y(78E6iBd-{bT0sVxxmm=gimN1jO3MfllM@KV2M+wW5*0YBVf?PY?jQk>(Ry)%P{w z(e!H$5Em7!7<)C*L_7RPY4}xBg+ZPrCw(Q~3b{(v_u{vIL^N(i74|C5I>}!O3 z*l%80;OmwB--caKb3kwKG;>3a5iEjGB*FWHd}2g@=M^Iz%@_OsZjoZ!rRdeUKQqIS z1syakXij~$VQw7TwC9x>mcbWZ5r5o{6Q)NZL=}vp{@vG67v)GeKVFDc_?Nx%vl`vM z-d=3>`ITHZ;)Ez>0~=ul)RXX1+l8);_T z{*w^m!A$(=s?Vc5`_{t8q_~&#VE)mNiEi*a2xM)AKRZFAz%Xo-JUfSUXUle4m zsl4-X-itObf@WsT|0}lW{>`17oX*A;afp8+QG)5}8PnINcW^`n^zA?(hQ2oC^~>mE znqg7Z7Vlx2fy^QliDds_Dt@1E!Oi7<;S&&#AtlA#l6TGYQMVrslg5L)g#8~AEKZBni6)Dt9z%dy0HAwbf}(E?!VJ+Fm)gQ>ZAP! z7{j;gTFN0AvW<@?;`@h&Jb>QQR`SoeVK>)nJMTmNL0pPwLszMBKPX2q?d#v6IY@Hr z#SD%xcX{L6igCX?{*G@qsd+994~O|j#q~5p%*adcr>?s+*AQbeRb5pD+c*Om5F+qH zsvr}@SwF`^RsD+L!cmBdt`PS22-8&Pat;X|<5xgYp z6HQrP#B=W*G6L2qIO)E>f0@Jef$o*xjou?k%Zb@~#BZ9ydj8a#@d4*I z-PL7m<-a+4fW0caK-NC;)k!iF)6IEr>1&Jo4_bMSeQNZi<}zU>DyD3oi=fjtg&JGPTM4zG&br@rVxf;~@tH`T3`sJE{u;*a(Ie?`*bgXIMu;7AiHH-;1NVfz61gg#G*$~wlgT@ei zCP15WM8;8?p=Q$zAF7)DHN8uyBpXRb0@)Zv3OPrX6f zkV(r|Vp3DjJ`diJh7bXn=v4q>Ok|i6gL~Hb=o=6l>U$Yp&Lb66F}Hkbd1*-o;g&eQ z-V6QVxEf#|R~r#GHVLb+Gt0r$)`{8KD}SVl5l4cn$Qefs3P|8^s5!E}zoZH_A(n)W z5Un4^=;C0;mhgNHk==hS#k8Qap#k1WOa(Z5fMe~+vXh#W!a5g*@Xz9FNW`*|kKd++xIpEAc*SLl(wjrf z^4|UXX9LyPV1i=@WkS8A_Z-`OKSKn`d$A#vA9tR?s6SkCZ~QmWNbb+~dDmV-#i0`v z|K=?I2_%H`6H-!^5a&1?jyOupRiY*@W4taV;FN1j$Twh$?Kr|uu&kJ3Fgrk0Tx+Qw zTpZU!R*+5r_7^h_G;4T*@Yf_BOG`_OJ0VoJ3@kKtgjhhpIo_)aS-_!8~|wch-pL!z`IHb1`dZ{Y|rJ{7G%sV0s;7Lmr4GF@zKA4-PM+tW3~&m zF9ew^^ijwP00blhiHUq(A;~Ay*r0?r7hwsyZrz7y1cc*_f0{GFh+cBxA!a5--2b?> zzA`Hy^Ub$!E_<ov(j5g;RtnJabLYHf2?3e2gR1!E^;G2Z-@ zsAyQ<(vaqi+#9c`N+QT8&%+6U+lb1+ve4uL0RD}{k8sxLMvstn1ZGWobzeMWLGmkt zGY6CUa2x`eA;0wSdT&4x#&v=hELlGFC?-Ds5vErRV_((2UAR}R`4CE%78c7u;czA5HGhrlZezL6bF!?|GNk&@6L07BYXbcu?gMnzJ{cs2`Rlfk zp8^Gmy!-e{0{d1%OWK8jvG}d8 z8c!l4lV7dG2x4T(O(uVR0i)!t`2UIP8hVf40^=y_#=9A;jn>oFzMwlda<_I6Ut##* zy;xr9ZAviOHZZR7`Gup`>_TACdvX1jq?E0trLK-a1%p3}swKKwIk{UWed3>(iV5se zK`^48(w&}Zb{0C^)@n-YY1kBL*T3cG|E{RG^!3b@OP6l*d-vE{ME@cUf4GgeBJR3e z>u~wKZ*}-+(DBxu9|Wyd4L}tH+NQH?Ea1yKM8`)kR*F$4nIX zpai5i*3XZGtB?*mI_{-dM?T?ge9S~5Uwa&LqH??OGo7Z_qXXUa#brw@*`rHW75>^e zz^Hwz&|KsR56>!byghAnY3i`-#@n)V;R1I3E9-r>$$gUTgSr*Y{YQ@RM< zQ!8l|;}ByV{d;R*Ni44>nS8Tg)v;o#fFN4|hHC@bPT}gNAWkOLK~pWPNSe+*r_GYqiDq zmtISsP$zO+;wk4G65BJ>+bW+t`P(TYeCgtjt^SIP0fiUtnqmM^Yr7Q2jI}m4!i~HP z2Hj-=i25D6YwdM)2@a-RFjD6@Vp-z!d}j@bimq<+t5=rfm|zAbPR{ZECN0yh&lQQ& znOE9}BA^{ww~-E>_C3cOaY7?J+^Xw|IHg<{r_78DNync)8Rbwv>kAxNJ|Q8|^!c+! z>5!-ht7?FT;1~mqN5fuf(%%Q(Jl8`bRjrI6R%IPaVf6!gRU1}VXOTAj2cmf0SKvlo6oS(s+NdkDfLLGGR}A8g-a?gcn7f)Y$GyEN3{ z{u*%^s3uA(hR21)8eVNc>dX>`VgN5GENm#LM#Lln%5H%Yz`8kx2!Mc%T`n%H`l^pl zQjWi;sbk3pJ?nE`d$8}`u^YFGd-+;^V7jREQg-H>H&?5BulD>9{Bb~qHvkK(Fs70t zzBzxsr!pgh5XZn5Y%Egl_82S$L!#aHcba<{>fC9nzJ3h|`UKBApS=k(C~R#j%|!u! z0^d-7@Mt!qQpIJ|KBim8Iv^gVN`gRR-Vd( zUD~qeL0PvWiEH;Z44o1^c<_#QCH7%&45&Fcrsb`s6mHh^`4M6G_R5yUYk9G8*p_Y! z;6-WQGSxY-a87URgib%V)pO&KES|n*`wDW%Y5Cf3F;=zH3!dBV#9?5Uo}Qtpsh+9n zL52yn6@%;F+S+n7qcO7pGAir}9^;>mLnaeX><)gmD7=8E3XQb95+8(AA22<_VSGE- zFt=s#jX&}1n(hh@%>>U$YD4q2p~sOo9qj@rTMfG%AoIvQQN)ibOZfRL%xM0VFooRM< zmJ$q29)>sJ(X3oMnVB(PX9vN|+#n3J!H>-KsVZZ>$dolsrQKE8PdR^EzG!K@<_@@Y zH4;a$Dz)4%GLQ*NJisu~w4IK|=Q3%%C(rhyy|!Bfyu3J^r$Y-?m>L^j?m6Ce(JM0~ zlg%IGou3W)1C+Js8qq*cI3`wWGkSMLx2OuLP@N4xT?Jsv)6^i5?1liE9-sk6Y|{x z(bbkm^OE$`MQCV}>gzOWEp2Tj1CIJ3K)R&Hdwl69mr%OZ2mRK%`ugkT!KQiFpZ^-* zx^%6&`4WR-T%stB={`lexoLJ!FvD1y>R*=vj4iq#g7)w4%y49|qq^c9pgU{U#KATL zYSVS}`<#oJ$Nc2bP-PS$d0bJ+bQ`^Fs&z2#BGAy}2YXBq4PyQNDk?k(8ttvDYI_aN z2)*Ttm8bhQg)9(STHwymbLohmrtwfaoRn0#^RkAX-adxADQ`xbb}`YMwC!g$$3|^z zEM0QGe=wU`+V;NR(cyQ>-h6U zxbV)u<+iHg#+ABNRXvz=l6H~5vu*cT6acb^IRxqr&|ZmbI=7=oUbPqo;5f#D+$%R2>ePrcC`Zk(wi|As=XJ5 zcH+|<(K&j=e+y0928-s(qvE28DJi_Xo@Uk;QsRmkyK?AhPTpF-7R7*LwC2!HQwVqU*~I(uH5{%c(30?_lM2_BiLVmU?)T~af+yA zj0z34ad)>=cm55XvM3d&{Kt!EAveN5fE+77FR%IcFD3zwIf$6qK#`SNQjU>2YaUX! zi1WU?RN@&c8~{*|q(4UO#?7>gZ{PJQ;U!N#(U6|RUUqkDSv!^>w8?)a`dRmO-Dz2b?y8j zaW*9(VJ|B~96CB;EXvC_8rmbjcf}<-&iM9Da9Ofg-Z{WNzrsCY+${EnaWm23C*3Jg zSCF%5e9S^j%nyYKnfft{6uv+Gt^XeWA6~}s*M}Sb-2$X%qqhKgL`df5h3BprQ%Nnf?O`|$N(od z?K@m0QX;3~JjRA<4UMfV^d6n>jpKW}&crwR^l}JN0Uj9Ly@)2{>Vtai5!w1iTQJe z!OrFTcR03!Gi8vqj~WxpTiwqN52d$18a_{1UK-UaOG$1f8Y1wKyQZRt1*uUN)CHhn zx}_}P(`p3t4O%&)@5%(3nR$6@XmCUD$GdUO46c#spDIk~UqCBWh$(_!nBVmL0sVXY zhW?7On;Qo=iHQSqu^}%PhkTRIc7Nli``ftNrk#ek2ox-|bXOP|H9syc|B8PalhFdA z9s7Hzk1nO`At9k*aM-;!DFrF7bxwN+C(lm57cO7Ek_Qyc0%4pu=OEk36xG;4O!&w>HaPT+DOz(VAG@KiB8BOTLKNlK)tGKHeI;*I7 zr=}KVeY?%)(|dj-F)QLjjysT`?KdLd={*wD~B z6c67WnlU?iw;fA}p(YIXCZIqYWwv8yqzwnbZ{DI1oD3Y|eoPJD(FDEGg^r9&o-64F zGwC*kg^`={0{t!|8g9ts?xCK!9J*U*Xahfbe*ZZr`EiEJF<{@HAfA`6B z^-ex%PUqA`xi6H+JMO5j>h+Q|jkD!wKe~#ZSumYts)Hsbd%Px+?rx&(x@uI+uob3R z$-~FWFMWRpde?v^==GARU6mIuIwt0!YUQe9N>*n$^vwE895&wOc5?za*mwn-<^yKS&p-KZtWTDZuszzX%8w7JpCBW%!yruO%rBpG^GSO5?*8N;m`bkg*rr04@PlSR3Lu;k{?Iva|7DFiY%N`2;~ zq6;FPNMg~~A!lDhD!%dYByBh^Z>&w&cS4Nw0^1unnA6dlRgH|^(^VV1^G;lyn19M0 zyiMT|f+yM6`83D5L=(mZMxN1*zVQPV$(M!-&qmviGGvCf^_x8we~WdmhVBym^W!(_ z0(o7Y@*jsZ^8a*7|F=VW;LF$MY^Du7%8ZpB>J59c_6z7n1UEGrPRb2Bv%Zk<@C*P_&*T3Q^|#>2dji!S*h6$e|4y?x1EKGEY1>%7)U`1~Y?ht)n5+}k@UY57&bP9xuZ zyh4f_CFTTvUcO}gYxo0~U_<$*ALC;+@C_Gh_b0n{_0zs^I`gfq&Pf-UQ5;Gy=*0HP zcr0dCI(}~~;6IiNOe%v^$bP4n3mjIYlo^o8_&(rj@GR19FDu6%#hNFnP}tR$#^}nV z{k3<9duNl%tN$h;L$v~dCCg7LQ%cTjI=5d~uKKZb(N#Z8ws%OEiCZmsz+d-!R;Dm` zh`6Tu&~4`~Vdurq3TjZlu$4JGM?AUpyI)=FYvG3;*vE^@@W{5t14}x|h`2(GV#MEu z%N9|Lb#)aSFOEzqny#|-7XCuwxTdA~-KTS@#)2mGCOv+w!xROht=SC6|0mcMvnQ^My?YG=ocuBuCMAW--3k!5hQK?xhC_1#CdT1bSLsd~ zxOtOU<}CMz#h-UUK@JB8WtT3=-=NDNJ4$V0LWWy!XkEWvo|+mUU|Jm&6)4Rch>$cd zkL@kChF3%}S6s(hC~>%wdheZqh+i}lI=Y{pbO9OVt!j5dcs{9Zg9biy2^2-5iWCsQuyuIPL8=RZ1%l&8( zmnhRa_3~8CH**~qj74jc$-DGNic_1vC3*SW5Z<=1SbFadt(_O{3+Q`HKO`*;oMtuV=xk3v)5uL0wBDGy@IDQ6TMUthS1!C_hGQN)sIC1XH7Hiy(xZNbK=)K2KGOD=&}=wegwT`2UvGk9 zi76X@w6+d6&4LkoVH?s;u|A@|GNt!xA?FDi108cl)|b8+p2p5y)Ba5V`nr>oK-~se zsj7w-)b4MJufkYoaF7cEtr#Y>UOQey9rUG%1trFj5cp(o6Le<4uia}sj{`Bc1-fT4 z)Dn?k-3Euc8r}NXIu}At*=TuM+kCXUm6w;vsdmF|9k_yGVsk(18t)Fx*Hg5WbTqi^ z{G8YIC33d7{V37X%+&mbFl^9St>+R-CGK1@{XbqBYP_seo{#YKIrLdcyMjr^8-sA} zs#zx==M{P4(7T`+;Dnp?5;QW_J#AC(os@A29ro& zY%z==;;$Wj6;-qgo`78rDbf6znZE=xkOq}hRk>iYC)U=i&d@o9M9l3t}klEpC^lA4sQ5t6TVgu0HK&zZf2GMV5qrq^8 zjgF72fdz1dyZOut6Lk}v7es~wylzzS!XC|1Zq^pz(9wV0drorSM62UD6q!biG zsx}u)#5F%=5JUQ9*CwZJtbaXxDYY}h4}D%vJ6al;P%A`LbgN>TTJEkuFNF9&VM!I0 z0Wgy)jnmpa1sU4*>evuaY`=ZehtU02qmmK(yc}OsDfdegs~4Ttb2@k+l3=YI1X9<5dWK0<;xoqpYmK1rWi=Wik8< z%(Gw^2q|u+%C8|;oDEWQAoLGB*#O4x@qY~nb&kJSczKXLdkyj;h z1{eEQ%89^f5&WG8kb!}=wpFn~`5FTi2&Gl1IwZUV3fJ*B9O4C9?UuM4JuC^$7Gj8z2_FUa-z>rZ{}{bg+I zgDHPj8dvc9O@vW*ef=r?)m&F1!pNKpsmd{Nae(wqfPx03GxooM(hRm;D%#qX#>RAA zLnKu&*@Thl)YKGY7gTDk1q+@&1org%GzfB4(4CV}OHT$?Ppb(Pb&OHPN!{L0e_!8U zTL>D}!?a5r0aj(+kVepMm&Lleh4E^}r$fz6wuE1n> z=d1UXNsl9DN*V@+_qbn;fDQ3=ffrnNVXj2wM?O#B$4j{25itd}ObTEPdBaG|rAJDWVuU9}wTT4p|NEttlyaT8O&rcM78?plw z%IT>oNK(TkB60#f66A`(zzhW_y1AiFE&&#St96V>Irc>?<{E1B$ZsFCbJbN!1fbB2@nU&!@Q5* zW&7pH`ULV7#>FsIz5&Ey@Jj*S^$p>DxBAkbz)J?fyz$rJ(a~P@GJ{LN(--#jX`VE% z8;~4lQ^c>^46do+qs%BTk7DU)X}N<6==DDvBS=g{c1MiixdVkCCT1DwuoEs{y_(TI z1uzLn{$4pd^YHSvnNab?WG(1oAVysYV9^Y*xPp*|Jq2pfXjNh~p^`TI8wyoUj0)1c zfO0Ug6;0su8`+o*k`%|H-TA;Qj{)w2Fz?LYK90?*NPY%xKZ)NJfG-$1Hqg=10$BeXHUrG@v9Pg2LAxVFT?#4lzHdCy-35mn`9cyp>EGPx z05te37e~S&jdD_wh;x*HuC={x<&&O}{?tw!Otex8OPd|$#1SF_g8Q+uE}MQdF1Nb@ zI2J@sww2laT(yAzzqO?Yuwj!jDl*;5+{OG_Y<4cBs z@R0H%Hf2rC=8k|?BXbfGf23XCxKp05_ba{+1F>XT=ifoj>-L__y^iG}g~?Hko>wOM z<1%vb6}GG}V##6dn&so=g$2aT!eRwu;78=^u!6(Z<6>jMqq8fHv%RTl7RU>MqmE1c zi=Yp=LddiaI}nEdRj>mfYYkvu8gOjm2UE2I76ek`RBYs99~ky!$-%CuvY8f#u|$aj zxHR%XRy-(HVW`a(+X2SDFztrpqet+cA9kj&s$6zgq!fliCq&D|$H>XZz;*i@@ONQ- zH8nT)O)@?$TLnBd^IrobbB&RLCOYK$F@ySM^%d?pC+p z9>|$t<-!Y+Q}elsjSc>TsDND9r(z1x*RNbz?n!TkS#69gYOkN|HJp85Xo1tKBc5A9 zO>GZeH4JU7%TZ?2us#Q1qz<-{Fd#yZGZ>usWl#DVFZwFXR1Ay06Cd+{2S&xxLFU2< zgmk$Hoc&GQ4SCN+h9=Z>OLi_JL&7VhH8}hh6Cq_rYAHvA@c|XX(%PW{tgqie@?Iff zu6sdad{}2yG>cjdr`__2F0o;od1~Dm+BNdk_WVPIh|Tz<6Kif)TQw<%z}F93(1EKQ zmvq?tM?Z<2H9Gz)=>4nbFzIjR-@2`I?8@`#={5^ddN0G##F_B9^dPJz|OQ{ zu`ZBZ?VNG;$FmeCenO_ximxGe$v|yrJm!|Lq>a{iYeB*Bl9_2|=IULvg%%Vz?2=(j zqeKQS&7;7eP}0(pRqO{*C78QLw=*M5O#Sv+Xej~P0iv8IM^Q<|a*0yZ>f^PiN#UCG= zUfSMf;YWrxpRP)+?D(l=e+*kOCd1juW}Ob?bT)i6@#+=NzNgr#yGPCI6oq_DASToZ zO%nI>ihYsV0B)A`H@CX@S}}L~y2Xyw1T~)>bD0q|ROfDg(fu71^!1&~rQNUyOQdxA zEUh^x^IlJ+)1=&Jm58|Aqd%Y1Cj@5B&gbrdU9$~@kR~w?AqdYuzlN0&e2eWZ?gNN< zAP>%71l=Gn3&uB(ju?bUvBxW9oKfwS`%&+ZAHHzyU_sB+ORf1X%X`fE9UUJb5=24X zQMc6B(qM+y$x2H2?aEEcYwqq7--4k@WFS&Y@5xkIe&Ajm>VM_M{T11 zGnQQ<=@OtLDV)54*J=f<1yXZr8P!(*Ijy&(f&-*`FsgnSzn_}E zVm1ECd>W%V1m6!!3=hK-x;89PH-WxJufG^6b3Wg=3k=y*x7xOy$eJ;CMr!?ZwcXq5 z;#aKHF)+?>D7QW$)UpT}ndz83w4<tucNUXyTr*!k9qxojOX~(x&YEjff=`Efc6UgFEsL>d!Z>|pU-Sf z8ymaS67OYhX=v=#k3Kow-#B4y=q)Mda0 z!_2D||1lR?B8xEzF}9gGNn6`OM4aVwA#88xj3b0X0P)60m+CWNMWn`d`@i`(yB`i; zB{7p96Wx`W9=WxjI9}qK;Yh>i-1`M*D-I_f#dCvHy;s zU~^Md&FItxv?qZ99TJ0syO%jbwKd4%TEhnAAA*B2)b5)#pXH4g4yy(H{F}SS}F+KYQ zmH)y5l;!#nD$UB~*+x@dP#1@Y1*&@>;Clk8 zwNNL-${M}DNGul+7ax-ZKgCH%N|J8?nS`XA949-wdArHYiHeq$$!B>O1>-K3nm@Xy&)-4G%57zm?)y}J3mKp7+pELSoPkB6tF!Y&l^q=ymm}QNAR~s_ z!?Gw9eAG|Qor*N149ww;8uDbYmkUCqOF&egllzfmR2EOM5!#n4z+Fw6iNID+9^qZFe(O z4M9}A^Y+p}`{<|(Fh#*&)IK$UvJp(G*Or%M1d)Ty%-C3UL{bv##1>eK(0PPUG|YTZ zH7$`gSP>s-mKiPBpN-aqDY7UPo;DpFU2}i`Hu!@TUk`lN$8TzEOhiaH2Tpx2e}5I% zECT*3q+$RC7Kpx31hpTimauW$&S^549kB-g7_Sg8RDg~ZSNJD#=3X6=c3Nj!R9~;- zhQzqJyH{9@^e*%ZoIK*<8tm@w2K$l8NkL|2ODMaOl9GTw0CZIsc)pbtAnXeu=O2Za zJ~HAwkgEa=C3|N@#U6NffHn%PvkMG$p)<|uY1?XMUA^DBPaQg+@@@Ioo@GHaVHpLiRLn`2@Y*4H7<^=WGxeD?4Q zf#cKO(ShR9y?ptyFA4fnunIpNvu@x=PLF{d3XaTEZ-U7Amu6<{!J!6r14yNSEJMy~ zFR4)h^atQv&)QZ285bUJ>K07eEv&3gkuJVdA`dgS~74jhOD zX<6{W0Vh#rSnrIC%MkZlUQ`6Fb=(SNBV@l+X+f*WBBlr(D0BqSpQcR$xCXs;=!9T& zyQK>gO}pXBcf#Q4Qf;t_);BhG+M1KA?t;Dp7M&r*Z7I1uB;&(}MMwaqfG{d8Ev71c zh_3)IXP1qsCg@&$e0+e{(Y_T`dh7Nz8a@r@T@o=x2wc))*@F&XyB&fcpop8b1=>=h z1F(lU-4E@nYvKKe(l_hgdym+tC|ZxT#YG;+m1pokEYT1d^c$Gs1Ox^zUVNFg6+A}% zh*amn&VrUVHZBh6&&T-{KOylnT|5xqPn1CH(#4A}tF$0C3(O^flJ>G{99A_4{}1NV zjrNDKy{D2hJPS)9NufDe$Yd`&KD~6t*Jd~sbo}F{nnS*!SY|XbAcytGwFTYvz#1`B zY3RW?XCY_l5qjt|yzf`fUqa#-${}TcrpsQ(Wj^$v!5&8k_WK{Jo z1Jntrikp|cQB3^;ExZQ!7zZk>b%`gWjp_oGerG*aB7md}bw$O;f(>@_K``(S@K|_4 zO-(Mn^SZ??A6Wcx3Iu)HwUV zG5t}q%=dyA2rLKJl7)HTVc84QPygWHOPkq(*e-ERN`lMUT3R&RC||D4+Plo}#L-)G z=;uAs@3oDOC$!Jf($V#?9=Hi477GGZUs0~Lw0?w1kc#SUS`Gw`v{pbk6U5{pz&l=0 zh0Q;0TLlnM=JfTeS7&Uiw$%gxOk#;Xf*x3z09b&#aOP0(*k&wRS#JdE?!GV^6+QJV zux;5?(NRAB!#AU*q|BslJJa$)TYDm!wVm_S*$Vt|4SA$jFi6LInJN6^WA z`gDK9Cku>(9pyYBjma4Rpr*UP5Cs)+)QPG;n=fXVqVnm9k%7Te@JQ0slxZMP$Y*=- zAj~-LE_ChwxKj%Yk_{QxeZs?UP-Z|73QH|?gDAMq$3V+#Q6#8oY4r{ z5;qJ_%IOJ^ioj-q@U3ppvI4*h=VgRjbQeC2-6ards{e*pPKuMg_^cR)yON=j950Zs zUO-f$SI*i$H~@8qY#fJFP-o<5{>q7v@c~e+tsr#}f)&B{2|&r)BqZR=%&pgmb@0tf z)xSp3G5LXG6JVAHZXUoQ2mg1>YquMxHyZOZ$Zm`EqynVpCle!UvwJdk@K!dUKabvKx!qEWC+cJ@9HWtb@qz-j_KYQPJ768v&ezC7UIPt0!Q zTex8oh3_M-aYKd7{zqj~s7z9WTYcJ@E#a0R1qGldW2K8EolU?o4lL`d(E}njc(?<4 z`WzNGY!h>rswCK!v9ZMNrqlp5ns47gz%!1Wy#(T(y3X!+)w=9Ri&tEpCGAcYgiOU< z@FjvYK3;gi8>SkjCxC^4*#-b;5?TPrsKAAHAhEr%0Z1NzF~koHuqeSkj1v4`g98J} z1l%fDPOn|PibV+y686r0jG~4s;?6C@H<<{{A+`zx41oaw2?!kZ+V@~zQ+4Bk6A`Q% z2s?+6N7 zDNukCuX402$jR;O?KQX^*aA5O(87fdp)lY9!>$lGTu6so4emBDXo3akJ9!AcYe0!7 zCnk=C^-V&;2R9~fP1s;QSTuQl@rA(JoE#H2E@*lS3lVtPaC8BpX$NLnQx$QmJ~)U2 zd=BscP{!e0yEXyf3TX0YCrM;!?EqW?Q;u)2ycP|gfel$Qyj3QElb?*i*T@J&FR)Nk zPeTL@1m1@s+-)M?3sUG-6CT=30elP=OH;oy$l}Cjc&IG6Tz`M{h9f=*Vh~$fp%UqU zBS*tc=Jb}qmHr5%0i1s(!4xIh#_+;TB!A?CGM#V>xCu1M(9pktrw2YN08YUPOf4P| z1BMvjNV2`N1D_s*m_X_B0JJ^V1@@nV#fca0;d(ab43F$4N~?(Wc_p#C-1C-&C z>#JAKG&F!z2!%$;NY~!#*!=8l)AR=DLBLLxk>>{hk;8?$h#lfCMH?HNlf#{o($cF~Se5pRN+t2M`rPHWvIDd!@%2tBS zH+gw;%T6nyCT~>+^YuRqvsR7Vwh(vn9hkTa-Uo)Ym#lsNM{k(KtQxNSFCHS;OFU)y zODPl+MEXY;!Q9H(4eIe5w6YLmhd?a^xHxQULFhjSLED!JV3>&M2j6b%d(4>{PQ-p1 zD1OZB5RS+{uY<0s3)Fre%-0-(wS4IcA!I5+#)<({;4oCM^tZn7y7zy@w%ZEH-d#i^ zi$`eHdM}#Xg)RVAK;M<$?Pr8&_vGT+?|R*O_z+jPR8DquRY)$LJ0dE{c74}Mhs859 z_If?nm;;c568_Tj&WU~hhu7E0|49!8@Y-fv!!V1V>n+dWj2}4!x)Z_Mzg)q8!nA_&Y1#Gvjl%L^yvGk$$#(sxjVtDFL8mr8 z==@8RvvsmEj+X}5O@|7?S+u)%mUT&qBpNgmBOQ25+`N?MLFkYxsSwSDyJ{%g;clHA z&BpfryOb+Fo=xw-s>M1Y;EWtU#-uPZlxt9l4SkF`3G@n0NvsbY9RcJwfKtH2`w}es zXx$?%|2rPJ0xzrod?tHOp}E6CTrqVbN3o9EojpD0{=;RM`;L#r>zlmH6O3;Pa@a%zwU0k!ZNs6r!SV_^Kw%pq{;^xCX?`o&O5#iT8NRLeq+9 zY6@d{HuVBih>>ARO+``og_p9ijfjU_V1Vhe1_nlTL4mY{;nbSELJ}Z59~qfAY#mjr z84t6ugQ}_seIUsc)Qp6LoX*aNj*e4ruqc`M`5(*4nTrERgL95X@cY-hV^acbbP0mb z%WTlKCH+Lb(y%-qW8q<6M5qLkP4&JSlw{8v^m&^}C{9U=o}Ki()>X+AwZ7W=>8?)k z)vGk`=`*JGTq_$05#oIv%mBS3i7Oc_Doj+3vX~}C^~9MU6YaM7TW8!vKr@u2leC`$7;|Y z0t0*Wkq|VQ0s1c#cklV(V=Z02+~|$-M*scv^r}PlmBVCV7V@u1G zsWiymSw_|oWO@il7z5Bt73rw2N(JK?;inW{(D&slP-n~Hdq-#F#um1BHMPkqJD-9Z#Kce*DgZ0Mh#JPyN_%S}HC+fs@2$reDobKV59x z6-=~WooTzoeBdeZ)e9S7DTpDCg@5A&5y3mF*NM_@BmhYuZ$R^xWdt&vFqa2ljP z)24j;dooBL#dIo4Y zSwONkF(+~~pS>Agpnr)%_C@hV5kU5ozmh@o9ee-9+n}H>STYdC!%21p?KQ^mGhA&l zCDq4Fog8F?VwbIBze6W{JSh-KP!zwFZ+mGK{R}@WE1gefqcVmI^h5d5tSv zJ3$Oi-W(|R-5dd4?<8u z8epzYr})kNY%5hr@hKq2hK8AQZ~uW`c=ue+0fEt1B-5KVq0!FIx1^$?;vj^A_} zw+&a=S5ov=z*z*0>=%}nKuo8i&j!z^`22xenW-GEHTLfddShen(aI;7fZ0tMF)-rP zD%&oj{gNa4^epByVYK$`h{^XZI6k;655@IgK;^**!wo`EG@g_zTg<05^GF=GQZ@%gfET*-DU24L*Z+|ApASDI)&DK2|NAZxh~2s> zhY96Ct|zM6#nKfTj`@m>_T$a@bY)W_p=FGWZtDUXFHS=B1$aVLnAo=dQf|mC0id73 ze}sDaRbQrPv15T@!Sye&Z@i9FZpcy&^_y~*-~B{}f-&tL8k z$iusDf9L!ojJX{?>Jp}zvG@>PgV_1I_oAd_?xt)5&)e5cl^8^5Db$~=D_2IS? zY*@;uryCOeU&$tn{ZU`^I`1)Dtt8F=DqzS#+9cyE6-bwmG^l6H)SvM7Z4rTq}R6& zJScEA?d^u^;q(ipKfs*UhK81AX0!yCu_-~62a-ML@*i+=j(vK8uDu2H6OnhdmWYW- ze?_6SwDkMZmYcUFF=#!d@N%2r)VDq#8>SF?S6CS)^6*h@u2jY&4@yf%U)jHr{H%9M z*9>Jt@K1Lq7qEr|k#0i5=_%I(ON~Fkje&D`X?>l7^pUn>GFbij`#!W;)v%D3E{=*K zq?|c}Y$@=@_E^c1;H)K|P~%-=05aV`K=i^}A>shOXuJSkrQzh< z*rvI6esOS0IfuGeZQmfjy*x ztc%C)ZOwPXH!SL#Anbi3^xVXx@%I84>EjDSO2o~;26ze|Z&%tbms!FuPy>SK!Z2!= zzc%}mmrJ9&)Rvf?t2NV60@FgT@lj^=hX8v-L>Pb!a_?t+i9xH9FqYVf@tQ|^v{y)5 z8zS8r4K+a^2Fwy~iYbQOCB>1@)T}8eD6g*v>ZJUgU-2zP35NdYt+=-4dm0U#q3qpl zlNZr`UOZpy&KjgRckz4np5;p_oOQdNb;g|0|F0rmPKMI*aviX{0Mib*;Ki|uDk@l+ zm_8zfg>+t^jv^u=0-$YiY3Z`J@Eai^EK~bci@sl=zzz=&zbgYEKH$E<=Isjs(_gSy zhJP z-|`;gjsE_Y0`;2|$X^P>wAj?_wU4cwAArRssh~iFbrDeevQM86^XI&fqW}Cq^CU=bUO?lZ|NkR%9~B3YeW_cEu_!kCPk8&(3Z{)N|$m|M|T?JkfbE&l(( zZKZdo`#9u&2QI?Pr~;)W53)f)0lS%zp}aN$z#89YA-FAX{=I(wvnfXD)|;;s?}885 zUczOS3xtnfoaZ&uOs1tpNB`g;8@qn-2*@(0Hiy+q`hrLmtH-iBGq2GIzCk2EEvvR% z4-Z}?KRR2PaC-VwZ3y!h|F_Rq>pVFwUCellf z?t+Pd_QU0z3pCmbvGQ@TYzXF#Ontm%N?0yFxsS1>$;8DTP`6WH|3;a%^!6=3SNWoG_fLPV;^yyMZ6n?vS{3B@DR|8q{FjqOJf*|Jd?lZ#I{ru( z0rx4<2vDj&4#xRMKv%`C>CLSsB36Ng!MgpaChm6(l-T}FX2p?c2%5MRpFv0-MW zo11%xap|Do&`8g!Yrj8Q_MmL<8yUv6tFGHWY+8QtZ3$3QY3u8+$Fe6`jEjnH#B&{W z#C|CZm6C(M=3wmUZ;`bf0ev+k=O)45x z_gIJ6IcIO(K6HDu+?Bmm?NJn=Z}KcS$sr(Mo6r80e%X&niP^~cRkE6Fa;Tq7Fwa+D z%di%UrrKUz=}^Web{(qFg@t8q2ef87GQOyn7RYqIoZ!{7`VY`en8bcUIY8ci-((~M zv2HRVn3zREPr?$I)a0J&oZ24+)$<`9n|*{YoU3x@%zUL~81eB%Gc&C}693@3BS2Tr z5{^1N&Of@;SA&Dz!P9qzUl=-aZSBkWeEy8dsH!?Q^9Ss=g2@eY@17o)1STh+ASsWH zl9GzAw?vy4h^x!WPBlsyvawlT_4_u5)$|pk`zi=^&(*cpX4wpFJuFsEcdgj`sbj;E zNIaewJ)?8k} zwYS@9^_xR0tlFaffOhcg6p6R+D+uj5yUVX1l=#*Y{u$sMArK$VEV_tM*?xBvKk!cNwl{QM@4 zS$@?s6W!$Z`Er_O4DjchnbjtsKw8>s?z0p$;q+VBm{Bm130*l?a^Fr)s^Y;TF0*Vu zrXMHd3(?!k9;;Y)vWe755SW=gu|L|S#i|`JFR!?TwuFf~T{r~zm6gkpB=Vu&Fi`Is zn=vp4Cq0@S=}O#LQ!T2>eRCT>`%`T~)kE#g6?KQlkL%J0tgdM}qnVhUf5ID!v9UIt z9WkD?au-M*hUCuiR9H!!VP?jyGGn|xJZ}>MgPV+engnUnoRS+$9+Fb!vfLZi*N^wu zU8!ri>uO<6U1)>NMIaOBX?`}tlpG{ofRHPEV+6m(8y`Gssghz^v-&*#*VbgkZ<@4TC;f zPH!9YA@aTJzGm}o9Yd_E+ooCfk4DR=wQI=i_aqNzqXl^pCwh;=rvpOL^kg~P2U~XE zBvzbk__?Vvwh!!#X7|dAj*cvBi|rTjA+Trd_>xq^vF&j7^kb*%ev+c1Q%g%@$!^t? zO*<(y662#~W^)B*vlmjlCxjhfVsQseO7!v5b7;Ktjkzfg8+S9?y%whd?i+S?-?Qrt z+6wC^){y%q&xg}}f}PehK29Nmv}&VGsPuy$l3KAHyuiut?N^FE)tMv;^zkYoPO+zdsx(Yb=4v(n2Cw$$sMdw5K z2;(h>%Png{sbkxuuZ%V9CHd9x6+H_Qc^A>(A%`zzs-)y#V-qcw91mGg?gtODL{!k} zGao(~X^+0!+A3gYWXH_pHrK%-B|Vj$l^c6ckU4+7D_L%Q{1q3MY^y&yCSUhiN1C0n zJ`qvP>B-+0uzwRpk&+cN$`VCrM%SkpnDHW_mt*5>uU>N(NOG9(bJlKn*2c#1QKFky zT>OxXx1aBRJla|1I`ZcbnBjfK{f#3V;Z;roiP6q?M|aM?5AQ5DCn#w=3ymeNuyTi& zp4KK!73M2?Q(Yaya&+O(D^Y``-(M_T6gfkB@7%=9ZE2fQS7&oy`tu5TWWK!FA;GHVf{R6RXILr=5}#!BXO z8C6FiaNOV^cglj>HY2d%%-<8!x-)5-fOKVV!c4_GdNTgkBZmIAgvz;ZZM$`&4_pxJ%~l1njVEX80skItOqgqf}{V;96KF$~GJ-xKUgZpshX7 z+)C3X0K@z73huiM(kd^+jA+jW@`94xzRuYm6zU)++Ra@BRP5ogWM>16v~{ZNAxgIB z#KU81bF+TAxxSJRG^q0A`E%p!Z_`qZdPebq$E&rM{c2u}l{a-Koup?3QA*K-!OXt* zu;39I2Z{iS_QUPJ+&;MC|JU0m{rUm+Kmfox`me$EGa zgGSU@=)OnQF@O~TvHiST_ZSBTa}fw&^p(d zwYDbS)kP{IQV^+fs*7N^x8HDDQxa63S4<~I&O{K{Eh;)XpvrK#=I09yx_C zmoQ#`KR?*xf}0~+p?EkNt7&2iGLUw$+JDoem$)#p-0>sjtxvG8H6G(C(3e|vInfwra{_^j%hxU)BNwPAs*Sb%JY1V6k+O<0BAN7TS^km2 zhNFnqrR)*sFK#}MJy`;9_|_;w9PVm#j;r_u1`Z4jL1NcRyea`&9ARF7?$c}xb#>D< ztHd}aav0Tw9v>GaCMqY!^=~4}nwwkAYgS|=xN)`n`%sh|nkdWVFe-|PMU==u^>>2& zyfSvXGls1M`l0TmNg%okEr%RGs*jpz$NjzWvfLo27Lu&8{Tz~IADX*_^Q$um2{p9m8e^wOFlai(n>Stl5|GZ{8 zUxE9Vr>9_&K-APsvAM|-;BQ`p&Z!BCHP%>Kw3hRFCQzY!Ji#{r87((FeP!lPmJ`)^ zSNJzM$rzNln4>iV#q;ouLWOmq`6IcE5gNp;b9) z)uKZ}Jok2vO?=721ivbtFZ104^~Ui@{MS}jmTq59f0LInT>B>PB3eFPQU>_j!*A~h zFgN;6ig1`}j6ku$(gnvpr=In-ehX_*bdjE00RKN+;J){O$LcO`DR%RwY~Bri*Sbgc zIj|$BwST@p+i3P&BZmsDt>$P?R~?S$r0Zm5a9MVzKE5j@m5ZTApj(UVr)cg?`4cVQ z&d%+Le6LxvTZsrg<(t>9_b|;?VE5+Xwtg_?n!)E7b4{>&Z;gL$*{NlWZgEIg#UaN& zGNMcSe=qk{;H7F@Juh=G3o>UL$4Q%0?o6so%0iw;@?n_o$7+XZ^=Ae;b%?a+Y@u#+ zW)|;7417f~Rn-yy=oi7$kFwpBz+e~kACBLV!Y#bS$JshC(${XQ#(gmLapt` zmlvOtCY!VCSGE`FirS;tz02!d6*@YLNbU<2Y;N(pN{H$b6O^rgQDbc6$rw8K43 zwBPXeKV;;E&_fJdx|9jdk4DBscWGXA#-y6-^}aaVJ|2Fyqu|AXgZ1bAs4*$-lZMI} zaW$3v6W8_F%91z?Sh{v**^jvGI)e z7{h%QiuU(p*H7k!TrYU1!1^SWqcq$mxZU;l~^ z+t#KY&O;20ubZ2r&4_Yn><9ZbI3MAuO&r+k46#PRHN5;l4DE-9T;wdBAkslvS>xa` z-9Ashf$!(#J3i`UQ1q-@IZ2X6kIL43{hMBqvd$ws|0dD?p94OfM%lHJxc*|OKg4$q zI&W-#kffoZQX^s|BMTaDkN)5%och{VD}kSfRJ$trLm`rceUVdiX1u_&pg?CSkLFEE zoNvOI^TLv?Zk>GYhYz%Aj+Xq}+m(g7bZSFS@f63(*R=SPj@wJ;2L>WV`v+SC+Xob6 z6h7qU-l3vv-hGtxX1sknfG9F@R&-|JS8kzoOz1{&T9|?;4h{>(p*V3YDT|u z3X58fmU41E=}Y_(ICd26%ez5f>C*D~7H)zQHqD43V?FxmIx$CZxG zkDFJD{oil;wzL%oR#idmFaJW31y5aLR3l~#QW*#d=5RwZy@3VywLNY8?ORf+;`Y@f#Mqs_Krj?K!oh&8_KBR-Hj!kzjIXlOS8W@?G zB9@lAN=h;D(T_#5vIw~bacqHS?{90{=hp(~^5FNP(vSD(=n{Qp13E1&$=ciAm)Q0C zwhRw{49r(1ZlK{i;>05+i7hwni|S@=ZS#;56&KHPv>c6!jy5u?qoNWKx#3#lxXH@c z_JCd4ujR+*;h#UN(%vSA9)$T`y&6Eef@m`^91s(;`n3p6wtV>lzQd3Br!V0xE|c)` zR)TslBQqNpFWC6k%&^Xyp@YNha_PKiNPiDu-rJ6O$A;`V3MyNHXzdEK?ji{L8{zIJF2Of-*}U{I?WU-yaj+0F*_@hAwqIFDm*X6$(Fn`Z*f8n#0mbOALIdBD1sY$jBUI zWtRw2dwXIZU!mZyzRzo6(Kbo)is1pf!uA^b+U%9{cCS0-7j`UhulYgr_n;Drho6_f zU5~A?-W%ofY7!i*D56aJCP0RMogz$U6q#liARuB)tbxik1`#Tx;2uxlpHD3gosaIyY9de*1 z22P#N%Ev!^C`7lEm61J@y;D|YT`iR) z30W1{Gue?*gbLYNMUqYSUhi?~xu56%{NLyOzwhgFf9`H_|9;o+y1w7@d!EO6oW~)> z{iq`|mw8-=Lpu-TR_d0Kr0>S(UT5yzGzN2z|y0 z6&;JVq}}q;<^4rr;~_5wr`OGgv+RYsQoY^GA+tES>Kzhqw32Zozu3#5HadEtenWzz zBik`&=j_3b72iX4Oykpj52T1ZPR<{Ni@$6}-}u|jWU@E?p&@3{ zIOM`&+tTisLsa?Zy#jIfGK{hkW^Yrn&weg&=xvus&06qy_a(N_cNwyRt4PUl{^80H zW`ooQN7||?&i$8W11mydIFLzCLZCHoYTjKlF$leJ!}3bWBfLI^1^-*pWAfjG?WT@p zq%LW!!t>wKxBmoW;!|38ZtFmU*1 z4aIwj{S|iom_x{c_yc~Po0o?sw;&IX7f;TcH(CWQS+P-sHDh8=9l8Z*9ayokQ7B9E zQ)fKj(A~9fUs7r+^s}_8HlTgIa+AO4EZTS_;2I6|_U5252pyEtvND*=!z^62{%WJf zI)b((%eo1a=6!;ZoN)>g?rXRMbzkZpOcv@6`9fb-*3NRuB@+`V14u1ulCR9zdS^Qk zgWtfeNY#J$>_!V_QNX;$Ak|E}t5W?dLDIqA0@`o0_e09f3wS*QbD9`)nMBF*C~M#^ z6Mp>4!J@M?o7dx2jnaZ|^rHs|=~aa>Ybz_zSv&Ds{r$PNZ9#l?~6mL`x6wc_sc za6^v;nW<=bJ}HwX#=)=P5CQ5+8XgaLQ|~bY($+>gmDOdFG;-7~=t7~5=a0L3tczC! z(-Uxr9v&XXSPMDeLJP0o#^PeOlP5zU7)fJnXowBPDyX2gA9)i6=;6{OK(wa|c_YK>|n4)*@mB#suhOO8prE zJIBh@H0#EMnXqsU^gZAaK~oDNRB=fOxbT_A<&+pEgNX&b&W_y&yr{0G)OY~(~<0FNq&Lg3hXK*vazn^ul}*R9h#@x{nksWve^eeunuTT*NxM~%zS+^(vsf`|%)OR}=2 zu-K4V3k$u~hpkOd{RPS6g2hE0D>YXO$Zu2Tboj(y4mLBuqYi-uu0vIkRvy*sHli(lW(@+unUL1L~L!{uAWOZ5&wV}04*LwD2z8izn}xr0Fp?EZOu;> z^e~%z1Ww~%nL=8J^suqAIwtXm_f(AIp{#@h^xmfac0J|$&hpbIVT!`s1Op_uPYT-q zmZ7|lL6MO9I6?_CLR1(c^iiFABgz+x6fPc8Z$IpT>1FK~cqG8nO4RjB!+l_p2jjF`?-#CHZO;TN@o) zBgGwij@*R%w@%iVl9H14c8~Wr*MK-ezzQ@yaob#$BPL>G{W(cyC{~rAuyBwlUx8Dw z7daAACq>{{U<}FfQ;k#uc(G8XmXV$tOf3q~FcxZqIL{`~Lf5Z{YIi>mR{X7Xn~rHO zW9Q`R{n-~5&$R;<$G^VkI$+K_+jZ(O{vZEN(vjT2e;GZU*612wag{q^`f+Mv?Z zSz)B)Gl&A1KwiTA!~D=?utMuL_3x~GH&VFouYwFlhnmv8c+*wQ(zHl7$m5oE1b#*a zM|>+o{90=fGg(>X^(o#~mkkYCpjJFWsjFR^qz~SGRoQT|+D_gOu$eLZO&)4Eupw0h zx8|1!!ecJE$ryRWWgzMvftnRmbU@(+Ew3)4GV0I#%!m@|vvW{qx|Gp!zRtkTb($DT z!&En{h44h^59&l?ub#@nfh``6@#Kc*&l%YmL>&7dt@0Fp!_3Sc7&^rJ;ebkJJtLsK zj0=g1io$|QN=k}T2#d|sfv*zC(cp04hcImH1^@Na@^IqMpO+w&ux;BmGgGU2DY_P( z!NEa{CN>lA-(Q*@YBBJbGllEP(7MxWbPkYxv47iQTob}?BDs`qWILD(Y;O>&Qm=|< zt+%-@gDma#J3HOuLSeXeJTP;0Q>5f+=FcBL^1g&CHipEuBio2cX9oGq{&3RaEz{Q3MS zA!i<)`Q(9IxP*kT+eeSxO{f>}jrpJSAI8QDZX@jg0`KR$KV4qvT~YH~Pf;WFr2EVu zJ!VPaZHt^PAD;zBELPtP3#o@ut)Y6;-CS>%8LG_=u#T(uz0bSDEW$+ZV43$wKLHcSpK@e8Hv2Wd zI`nvJLsa=aW{Dn=>6Ocs&pGHfTS**<@sx60x7#y1}y=qY)l2WC9D^w4Xj75-=BPDlRtvI`lM}-NTC4;Y)9qHXt|eL;A#n z_+wmgx7MwpXjtcAxf#fCs{W@3)}7SsekKoDfRK!mF+{rd=3s9 zOig$BSrmCEWxk5Y*Q?5QVmfeOY2%_hmB8V{W)qLnG8{}5AA5!A7Q8d^5d4slUe`JH zK<0jVm$q(@xfz3qh}%9J@HB5PDoH**Y(G(Zh?U$vI_qzqpMe3K88b6rAIr@R(gB2x7Sq$5jCCSy4u{L#Uw-&W`5nvm(0zH zfj_U!&3bn3nY~o{<;!zMP|gNwqXstddV9$}Kas%C87qDvU5lznAD#b|(YD5qM%qrp zXifgs(e7TE3VM?-cC}Yx`MGD^-p_Ol|M%8YUAQ@V8kDO*c)?*wy^#v!euFM z*Y3uIe>5~g-|5D2dV>Ibm0f}$Y|MMr;yAMw=bxb|^iH>Y<6X|f;>)t-kHrrbndx_z zmtA}za&YJ>qkq+I33r#vQlFXwdKHe+gJ&K1-$ZAheT^c%TKbtMxmZfvKB^Dze(k^!1BS zr>DO~SH~3ZQYZ&F;1m5-TRbe~0PWULPK;HRvC%`Sxx+*D+5b~EyPKzxXgNNfJ~Y&{*3c&{Eg`nV(w$BC zp@-Hd4;MzN7O2i|oLaPk#hQ+~`UR~3aD0$@GKlg5{4maN!XVA$)Q;<7dZnqp!Ph_g zhIc(PrJy)?VZ~ZVj9@Md1oMdM)|1gUfLBs(^^&Z9YCo;>{Le_n`OR~yqnz9af_RfY zugwv_{n@tYOi%H%s{@35fj^`U;JBry@ieXyU#hg9^;hh)giX1z=q+RCkg+9h}38f-Ir{RVJj`qGa zpQSRBQxC`7b zZ>v@d|1Na=LWJ!T0=GMpGeha?-sN!W3%-%ZNu$I}i|ZIfIBlKIV`6;q#>G!|2g)58 zN({TKLGb6kPp;~eM_I1~gGvr2@Ya$_Of~l8X0dQ@=Fh&%P9k&^I)})MNTIr4Mg0)Q zX#e#S-=M4}u-sOEG%!*^P)S(TE&GvIAs#+G_xoLvwIsn3Lt<+Y;^K>=Q7F%a`*&U! z2Sso2zvE)_i^8RLt!+sp@hal7qvMHPUiU`ae5$>uEcQjT?7<5#o2a^ZNkvB0t2KQm z-=blMrY{$*pHLXwwYv~e(9GC2JS<2%#NW`;G6|V0VI`Cw!h7ED}$osL6&|>&ynN(Sg@0C8%gtS@7&q2G76I-AzW)2V!4LkIo$>$iR~n?D zRidHsEH)}W?#q`i5bNb(UfGuQtUu@diHwX46yJgv^NGJp-sP_#S1Z0G#4uja(s!yF zDwAoWNTlU_2M=;^lr}c1kG-8po5TF5IK1g;fjA5D3h^~(Svx{5TZP+^j)9$#T4dBBk&}JjxMmc zK_H_hChXLx1Gq6C37611V*|pV<8a}u5nd1g6RmeCY9lDILg?TET^$nIO_Yqy(0S%G zWju0;Lg4x2+xU3Y$!7rWVQdYM?eysAem3?6Q{=Y^}$q<%`YB_EIwy?SK{6n=8Pu?bnk0xwFmU*qlkZqj-u44D2P&y3;`yC zq_ouU&Q59;mX0WCDs|O$6gx=N{icY#lG3#+S1vt;BBcrK z7{$ac3L+&JK0k%9MCn$^S8qUzfwlw$r^TV6;`j$rQ&7zE-hJ2uBY4W(CSRBs8R7Ib zh_=J=)q}xFLr2HT#-@^Su)lw7j&MuUT7+P^&pzSDJo&COm$kHlU%g@ts@MUcEi(_S z;i>-m`z4*T^YcBwf5%gu0cyE%Yl;x?+O#j{BI)%_C_;LMC$40y9^3=0!gF^>;+_sQgD8LGo zyJO;^UfkDFByqo_|9fu6>HV8GZwA{Jz{1z+YR%{3)H@iC%d|q~bEf;AhM?RjGgkF5 z5howjQ)cv-ITmBK1Arwr2-`B8t)X5Za=_Hd_I+Suq}$TbcJM9U?j zoRZ@73oj`)v#zbwfH;?cw4fW3os&aFN$HH4p2*^2$(9$p*!&)xR8~I(1-P3JU;rz{ zcEgw*9fQhSfr09>vR}(x$BM5XzA(M*xDY19<6^xw_NJr zy3_JxBQ4@9G+h8MhA`2hkpX-tP==kH;xBL|W`YbnWTk+NVKy}l0t0$y)Vrt9J})0) z0@~{Tr%##TsTxEK5q0Q%qaq_ofC=_+8k(8{@2VTOzmi#skP{Ua6gty31L z&nx?Ely+B=^?nKH6e{(B-}$X_6ix87w6vOI754RTsaxJKHRZ_Iv7JI^KY38b3s_rQ z_uO`bLVc3~0YimSqcle+rZT_W*`{=xJ%WnQ3aVos$w1 z5hV!3hp$7O8gzAbfWLA6)YYj7g;m)IKRspkK#J|w=__;NrY*H}_n(wHCRr(KKYTIM zS9_;FKQnU@tuS(gN%d>DJ>Xv$l*3SEzv#n6PDE79+g!RWh7B1WZeXjSesvxT-tTKB zkQnG^X_#|T9zu>}Y3d8Hih5$Aztg6qLZxVE~w)BL1m&&FTE1Fczn z0y9t+Yq@rWi%Z(YhlN6iK@aIxyuj|=L4e^-h>A{J&j;>?(?(NG4K4wgA@@o+0yZ?+ zMnX(Qv_`|u(9m$8qH!Ch-O87q$jS zIF5X)`gIh?B{a~Np$FQYY4Eq|Y;CB-tTU7=p%4)!&h11S?oZ5~!x=j|J^iO4!TxA~ij=KGJmeotK$Q%#h#1CiYHTcU?0=e(AuzFf*Y+Jd{C=&= z)FMdmW5BpI(-KulM97hMb0M~pfCym%!AuPx)ES6fDCG_cwc^K26gP_+JrJ)bPCF#R zhg^&t*7!fw7XQc6f-Gk6Mf|^)DF55b)I7qJJD^jo)#kP{NoG_wr$0uj-mP0#{X&%M znh+07yJu0-rp&WJnl(Aoa)y4A1+t zNl+|qBMXW6IAHu6H*G?qb_%T~C~F~WOI4k~(*F1nW(*ar!U^5gR8=*ygP2(KhH5G) zK``~_AE9lCM7YT+U1z!Mu$$4*!#20dV@174LFVhH(33U8A%i7~1A~`3g)k^8RII|V zDw=Fz{35d$B%Oxa!r1lvvH{pW=A`ARG(fB!n-qH~qyHYoO=2Bc+3XNRYn<(&udk-d zG-3}V#d6S{8yh2~thBZFsvdGxcyY4u`j`>kYC7@Df4w08vHv#j*0l`4GrF zpsNSr0R)=ehxE8z#;RyT_8sWVW{&~gXtM{ntRER9fS!h zR3DBUMg~njJ&&0m*U+*?t`iKb8bL2uwY$4}8$r*nm1U)liW9nbP@p?tX0C`929lr$ zeMlqhB^VO=Q$QTUe#bB<0A#nbXFVr(EcHeCqS1uTf$P$|6M6z@7fFrjgVj8Nh#WKE zu$Dr3-`~n5&(y@^NQePwx^&erA3*>W)qI zfGdrc6rV;iYO8hBNZRkVd^;*>i;xD8!D5NQ&{xnNekT4KIj zyLN4KIawzZy5$-%aZX9#A)`r;I{1Oi{+;myZ_p-}`rX}qRtl1;cH4iVs5^hL5FyI4 z6ZOzMfzuU#B9+ELMpKKd2@5C{JPE1aLqnDMCLX)4T)hgi?w2pZk0A}sKu0HtbQ=%w zT7R7>^cdl}eTg^zWQExHPvU}CN0_D)Y#Tv#wa)$(@p5F7icQ(*3iJ8I~MGlRf zK6NzgB91P1H#bg&Q1&0=hxm>ivq$U((*dgiu`{4bnhr*jY({BXPFqVpe@?)&uM2{# zPUV^mp755|i*c0uj{Sk9$0jOfH`Ig#TA9dAY$1+<>izud~btAJ^QJ6levtG#x zDGWe4LXCR|n@GMF4~A}6T%N__c5*-b+eOs|>0QT-UM6%4K^MTG0q^;krf~?vP8e^Y zI9;%Mm~g3wT{MBjDs?S-j7U7NhHALGF!kLNVO~wV#GQDFMkqpHi0k?K`udQ78B~5C z-`G1jHP+Wd>q!FiB+hG|x~;OO;u&%>GabwYY*bTU{|-fSbDA+yC&==h6O3y} zRY6F=!+?_z^6ME0AT@Aq&$YlfH%JZ_FTTvm%DQvsj;AN7w<9S@NCs!11oFGwV(uZa zla}@)qd}fy0&4Zjv^vKXp8_7Fn2o@|0G)&!Je|lKO?EbIj;^JMM`xOXLg7BSkBJdV4hiRYRq9w5Wi53}7Xp8tqxb56Xn7W8ahT@D3yi;o|PltGJHi z6^f2f`ozvGATV`>m5X`wVZ=eOPf!NLX8OA*3m&3o61#o{r7h^osAQ-&uV-1D?gTKDKM=J3GcvM*^@AKhcRGg=P}3w0X71yA?BKzE}^YW_UgYrKWSeruF??(#&QXAMbMsVs;livAJU=0A5v$9ILjz@ z{Y|Uz8j7Dgv`iP-6v_cgv6-006o$X0?4y>wmfxU) z+70zx3zb$T6zqA>76nDsF66i1^CNPbPEr9{i4ohU3*2hOP{o7f7mOcc`%4ODVP% z1W+6YkeVPn^UEjCCNAP2RM@HSPjcu;MB4Ty6CsKmv;?{BzthLaN&f{KSGkq=eig^Y zpa1{yNc>O#g=*f5>c{swNp2<#bLV4kh+fQf#25^Fz?3JnOWt=^nWo8N@JCPgT zoc~l+_2kLNY@aAf-TS3RmLiOJEhhF|UNZc=pQ@3^y-CZq%ezIk86v*gI|#hhbJyMH z`j~PP4@uT!_AdR^cg4nbIM*frdwyl?zRjy>F*-By56&x8c|GYMN6z1zS0Ji5c3sfc zzDl!$9N3Sz{6)??ncsF5qRn*CcxEeC@h~*rf!yj&s`{#>W`h=_yMVi_c%J1xb&bMyGb#D=xRt=M&zwQuBVbpCQ= z*&@O5L$)h+ee`twtxO9o4vT~2^EggNDXp1yMKz$=_U)CAHh!FJb;1AH2zwY8@LFYE zn${3_8OL36vbbosYV$keblzwjzX`jzeYelR&nOg`Y| zc-o1QFSowd&H{Jy8w&jISCj^tC8{#jF*tF;8J}V*1&Dz>D z>R*>_2QuXzCa0vttjG(t-Os-DL3qCJA&btvI`y>{w^lDZ9h>Wh>jF}GZ>dN?c6N5* zkSaS8w^jbrP3h^CFT~{g_Rz^(HLdX(dBLQHE48(n?d~%_$_9ozN>FZKqkQ@#lf9Eg zr~m%5SC04q`&U1J4FKDg7Hvec)Pw{}YzAlNZxE0RR^7Gb^YT9_v$NN^;$ora8~RG%D?c#j&UL*gfLyYro3h7scP7NnqRTV`6un>VyxQc|=uH1#nRy7%s# zgYO@73=OTsA#n~34^GZl_xb!Kkox*JK1GnvvKuk8ZZpH-nRr`MeFD+Z{@TouzAV(fdZr z=D-1C!EL*kn2@@4I}hpMB+?f(-?ps+Aqw#t)>BEm$xC1OQlQ30WaI5kO_#u8NLYEo zE#&L#ivpML@L?`CKN)~F;Oj(W#@5ZqH1bl{BDL4%pT@r%?|pB%Zupq0RS(tV-=AsRJX)S| z_=UR*g(pBHKbKGQ&|!mRb)?hcD{h_b1q%)9m89|*pZ-{kunPRGrL8T?{tMY0jygGZ zBk`j1RktU>S7xJzcB7h(G(SD+ieXZZ2oydKc!&|`BdB{i>D(v{g=`{R4v>qeI8Fw- zFGM!*r`GjAN*vf6a!S@nu!dI+DB5%tWb8-$Le;JImr|ve-LdPb?H13ZNZCw=tuoSO z@2s~tsMeE_XTvKlOXWIheyodF=uZy*Vq=vnZ>hW1f2(fqfZX7ORa8N{L z3`-Dc?nTiG_>7?NJ`Dmk&_vj7joWY9v`HuoWv7@~t*&ipccoQDZzwt z?AHd>8>sA1UywE6^oJxbgR~wn)Iht^=GTCCiIo-aMExgZxFIYDaq58F3AB<(=Df}} z&{LY{I77@FOVo*JRn^;h7Dx3#ettc8=H}$gK*~IL(|+EAt1AyYXAFE5E{=QSBtB$N zcwV7dE-XVU8Ty%M%-neZnJ4i}?`Ytb2W#hkS_$%A z#nEFzKjJbcL2`1z;nURVqvNgo6ct>sGFuGOhZoPEKX_2mc|t_Q%-lTa*)ybz#>O<3 z$TaQD%+8Tb6Z`%lU=M6;;wP^CAuTdcxp;A(I`spYba@vwwQo7>N)+Vft=qB>QacSb z`&pV^y7azxq7f@g;M6Ji(uaYhoDY$l93aD9`I>IiCRciltpQW!r1hnF<2-} zK?;0J6`H4sw>EC?Bl>C#*+|a|GBA)%T|E&0 z;76ex&{O(6brP9)oDz+Q^JIvKQ){v|i$D?MrO!4|&)fmL^XtCmR4WZdhv16y<=r$6 zGsL%)usYR^8#h+2xj%7jZ8TQH!|&YlhOD4jaFlMiKoSD=*U)>Hnsmx-vo0E!v);OX zZ7d7hQ)ccMtB5u0Hj;QfTunQb*TtLH%Yhc!dNvxEuV z+FLhs|H;Of8%_~8ED(x{ifeD}KYH{++DZ98hh*3|*N_xUglDn_HCyM7zgJhMqsA0E z%Jl>y)&z;Bjg1Ww`A`vXa-qVCDhs9(bxRU*T^DG?s7pV}Z`{-{u|OGCMep4$tjMju z^CapY0G5?0A^7#=*RM6ywH5+3b?O0`5a_X;{}%lGc`_%v`nnq~F0dLzCMvC@R9#Zi zqC|p1n-?h~fdZ%z&4+?e>7~e$k~5l08k7|MA@B7ZxD7j#_VQZqcq#JBE{8aNX1h z(LqcNj07juI&GD$;)P1)=cc9+;7Cr|*(-hvH9`)f_TklL%muetbSSDwtg*&Ti+?U| zd@ln7<=$g&?K(Wj=;b!M)1eBk@Gto3E9&bjs;gI4THaN^;Y`oj@srDkee$C1w)h7R zjY=MFHQMB=z0msRwLZ_ohXpyMX9=~Tf@^R1*yw8-^{7=hsd*dfxZ6{#R7aOVr_Mm0IOPw`V>pf^BEL!235d$sSk;Rb;E3FdE6ZwVLDYj z$U{h@5J->mCC_mrML!$oN^!okQWiygmfaSi|0sV^Zh0#e#p&|s7>*`K4X1kc?m6!w zYmufT;``1RNuzH%!Pi5FFDbf^$Hb#w+$MQ4W}S)kYT);zai?Ja=uR#p!I zmkdZgc`9m2@OG5QZW9@q^Cm2on~cpdaQyOW>sksoQAQzcAm*r~^b(11yFV>wq+668Cz~D|KrZAFL+dmBP&+U{UOscMx^<$YZHmrQ&3M z?I50?TadTQ)_{s1h@h~;PE`^9C^Il~t@6u;7#}|~qFY4oM zp~dXV8?8_m`qeqDDU=41eoxMwMM7QuDRy>wxeHCIpo;n$EfTzMFt)LIHZ^u9mZ(Q& zE^_WrX-7K8qVYFZV`GOMJ4gwK)2A?$x}8%%c*Z{)M9Z5j=W=O@iZ~^0e)={7jP^4J ze%(M;Y-%BXIDDM-fI1t!u2wUKP&VFq)ma08UmD4dpG?5q(HEsgy4tO^HtJrz-Cgf6 z*~VMa`bGTkNc{rU2>1$8&9V5nlgQ%^1)1BJTsDbLNSdW3^VL8T6BFF<#9?mS4chmG zf3bgkv<8D(Bi9@Y8qI|gS^Oh#{=v^iWqJ**bzlKN`QzWns;LD`PQmM`P=g)SUl3m| zFlV48_+>pq|MA0xMtdZV_pecT8~lY+H}~u;>odjap+;^~La;$UhQ&x-J?UfU;QQoG zK63X)Ao3=|bOxO(G_&vDy$g3HE`EM~lmLWN61vuM2IT8HbYiB*1%MC&R@3*LB1$ed z-vraPWN1bmpV#avAFqOC44rNp9`)jN3=p*e=1w=2AFu_<8WvIb2|!9s$xpxxA%%z9 z8f>(%LEOig%~qDkT9>&tcbIPg5wyyrRn zcOCuk=7$D0_2I^YD%|zH~JU`+_bn;*}(7M@<1o# z2ew2lYUs0U7m${LU(jNcolbq0jhart|MTSdxD`Uy_g^G}I@w3ms!;_1gIJiGgT)P$ zn=!>e;wmT(^bX}9xk2*UAOa`{NvG@GojB^@T8!jMQbt9k4(}o1HhT&29wSkJWO(`1 z=m&6!LAVeVA>bg=gUG&Hj;=%&O>TEgQ`mcJciPMHh$c@4DRwf(&?*wnXqb?Y4Bivn zK`S6Tos4uZjkg{M5_OF^#VyuIcTT0SIyg8O<2CSNd9;&_b}J0E0Dz*QA43R6TbEus zf>eX|UG~=Am}C#nO=l13Sh{=OKh5xR$l7!;BKf%@J0f9#{)AjLgNGRjF+^koCdb)b z@$5KZ)zF8J-(Y>~^b<5hQN7U8284#f8qHMl6HbIJtmr!hDLmcG3N8hl_qO3s-M;<2 zqGF+a_g3{ov|A^}#{s7Qgm3uxZ`s6A0IQe=!-`;lyD;2U|-T-*>Wd`w9Q=O$}%)1r}$Cr!GN zV2dE##aoO|O`W>>YrQ&YK?JY|GXc&JhV<}Q21pb?j(~>*{szq7aeKiQQNIv>qyes5 zF|n~5(Hh*iQQT!xhMS;GWLBIq(s-*!$E-0gA9v-k%2qBu+O1eS78cxri5e%QJ$r+T zKLx%h*s3%&?#Dfpfz}b3DFdK|vQvUeX>+qy_5?OjY+M}jb$rRM*e3)BrO(qb)8<;l zsIjaS`TG>?tuOX#msFDi88=%|3&8HhgpkK$oS2wMXxil!4LnSYS;@el2vrg0Ej4Q! zu@li$j}A<*b_&o)(Mf9=0-9P#ti0syM+yf8a~XED_JEQ(O2%}2Y-`hHI43E2`(Y12 zDV*vUrNfOkl(iPXf@ez@F{+@5;*a1DNg_ zneML#@Azg#H<%rA^;q!*i;Ezwdb@rwcDjG$_ci48P1Pq)xZO#-Bn6&S6P$gC_dEi6{0jbN+7eiamQWn`D zK*Y(E+}x8>#dUSZ(5FAEiG2oWHu@q69*8xv^L}twWW8HOYU?Hb#@-~SKxEiq0EmtP zZW_@cCDemzkgf{KtO$uFccK|ZogbBVU2xs?rEBc^-JKg@BJpYf7Tf&|P?3+`#~Sax zxxpK)qI1&H;h@H}w-;hnsVAyk(7*!2$^ef*wwGhrpykRWkKQ33)BAzTbv)0b0@6!( z7bfUloO~uxuJ-&1-RnC;35WI(Ug_dLe*9pO@E95x2!%snZ!cGq11148108GCD&@78 zww65c){vWGjmV|kTG8n9Ti$GkgaCQGSS7(S9tq8d4yF?lK6-R+cI*J1fD>zo96gv$ zJBdRl8@uiiOHh^Q(B~Dy`ND@kZRzDda)3kf{?%lO3GFjO$9!m@#8M|KXrCR5e z$prC_Unu#OVpLB^^N8HfvUgA$yc>NZk#8^qJDThW0y<`7WHcdUG@28Oi5omg2K@;< zV(YhWM3S4{G87rfhW+wJ?}tpG$+uB~Rg^LdQ%U_;qgSt9MXwg)ZHN`fK!?B{$Sg*b zdHC?*`n4ax&xzIzIm!4H0i-+63dandcfuF6A8o!RhYY;Jp0>2?M_$t}EuZljCt=J{ zVZ=X~vlD2mDAQn&a|fC>3)2VHK`w#x`~(VjG)!u!eCtrWV{?K8hv^68hyZjR?Suks zCPG(c=JAbpK6GA*AFSHCEzon|&mYBuc2}>a+2j^?HbmOwa^r4P#i$;Yrt^=(OT*Y${r$qQ}fp3Tkt6?dDw}%YrhTPFZJAsRR ze|JrZoA%3(N3U#{XrI+Tl5(!%J7ql^LXN86Y$a!16`x<99?~9QJ|Qh#j>e)KA*l32 z`>n}mjIZM%()2rDL6;Xb7>eK@B_(Hc>WT7l%*_cv{P@-KNYD?_i|y}kts`f>RaDp( ztk*YM2pMLypu9lyh40`&r)$?nn$w~l_ArS$i(z}AxY@#r;vz*g?(yUSW?y0SA`y2n z>#d)%v<~|jePgX$`xqJaL_{xa&G>eaYkwH`?&aCJ82};z2a%YptR0V7g&uc9xu_Una*Uky{fGX|c0JvyZBoLE2PKYw)S=Z8CJ)bMpA zqThwykq){lK^16uSlipD4`5{}aXlIjcwq)q<^(KHL5oA4-Isi)*gn+n)9V{C<~t+^ zfpZtdQ(nA44te_So#?y78hdsV3ytf8@#$4ndbv*Kh?rk65#XFbzKcOj+x1 zA}M}=_SvxzekWbB&Bk5QdF7$}!%R}f|Ghb~(G!fP{{5TY!QLK^I%iN2DZ)QjRrE|u zHQKH+t+$8Uoo0`$tUPYJ2@G*%P5}X*ow6M1Zs#s~WSdFinpwMhdX9wfA2>sED5%`b zAyb+O^q0j)cZ=(O{ff-bx7p&iXK#pFG;r~d5aSeGJ7lOue*N6rEj9kM{=DrNXn=E|f_t&KNbAy_@bE`a%j%+v~+zhAo z|7?b^mKQ$qsB&pGJ9jMWSB1Iml;nIA9^mKKuTlb-M$V* z2&EJZynUNxIV@;xcKTPnl;@cw?&$V1qVz}0uEHz@>%&~qK`>){s^PIXJ0j~Jn{Csn zU0zz%mQ}cxMWtuS8%-3DNzGZoLOxudym3$>4k4Xuu5>gaohfbEg z?e**HS?_+h^gt@|$GaI|w!BH8x}H~n492`v0kB>5?9!F8sW9Vm#Rz-M{rLtxeQN)E zv^F^GsPtpz%$%I40lh*ur!OP*2`s#`r1bazgtO6C(|LDUJx)=4K+&Y>>nWtZh=NGX z*m(HF9Oj2AL)O$CL9w=vnfdDSqGyRTB@k#8C&+vVSexCHVUHYO=M;yu!r97{X4|&n z7Mn$u^%2^0bGHe}fBdA_mUOqZmhZp&*~I>a%eiwK8JIS&6yqs;xpViv+CvHZh6k4Q z9!Q7GtDlDnaA|3~uUs`=EcbckFf;s+F3D5UFia>#BU#VP*m&tzX+?sn+2Tw`{lQM% z-S_V8lvGhwF|Q2PS5w2$SpEIRKhx9BIP<)BAM|;ccfys>IKAZJG|V*A7+T@8?`Tp} z^61oXnOR1u(NR`*xELL|@=}?5a$=BKO!$3W-8fF_kQ0V-Dk|e&=#n&3E-G%8m6M~~ z`kZT*SJ@*bLBrw`#>P?2&2MoHAHU~kxwlc`QXBLxP$-v_`-X33#r|dXtuQiND4UUF z--YUv0Mun_+8rDgW^s8xqdhFHCT7B$-(GOYm8eq!Rxi2vs$Ij86DLj^7=(W|nV%Y+ znqgV$?&9R&ppm?{oyo@7SYh*3XXmYL`A6O4gOA&_7nmD(TrAAUs4r6(j89sC%+5Zc z*G|sPU#br`XIv}FI{>^oQ8Sgfq4650zwBlLmO}=HhOfNku`o5k%h@G40L16|XGzFk zVLe73VuEpEPJH~Q@^aDrm!74iQL{#?C(*OA{&V+9fh(2UoCrbyk~O1aA@uaI8#lT| z&vww!dncq;XS_LLy7TGOQE7*Do$>1^)Gm5lT#mDk4kvZvcm5h|Q^=NWdQH42a{Hd= zq`!qVOz7T^A{HfmZJc(YzP>q*@4jYP3vXscEBVZ~) z&bl}|JKp}d>n_=au7{_`1K}I-&t|XO8*qaiY|_s*d^bJ3nH3`n2|Nh{N=y{I&%zI1 zII6Ra2QcO>i@P|dn>VtmB_gdb{2Mk3)= zB%ONi%;$QKaIuudxV<4E1D6@sQiyEba289zR(cIx*VyFx+iYiuDw`S-9g5Jl&ndVN z)L!%iPa-}(qqo}Nt@G4;$$g>}h3UtSms{VSG#WI!Hgm7es$s*_)-#EWi8PJ+vJ%+Q z2Uil_&?>dAsoCr$Rorzoi)n+}dEcZUgZ(wiy{jbx58Nm2KG96=_(ZRwJ5co8&#&|5 znx*2LyFUtu-L9+y=lwZ-GSbuOD-ERFt<24(H-i`vsK=LhcV2DY__VBSLZ;+DYkqVv zwY9cB3xB}~va{r;#KsC5=I=Z!aaaA%St&A5SorO{T`()Lp7OrJgI^U#4JgfV#>5`C z{ubc!J=&(TMWDAYQ~l~^i30~F-Y=iAZb}aNmY|;0L-ZXzU|R1|oo&0BRoa&+j@eKa zteW!2;rs7q+V;u@DvP?Autc)#z7|vi;w#>F%$UpMOo4rOqI8A^Q@*IG=9q4lmF(t| zFk-VQ0j+l1?nux$Z{1(O{L(6HrqJI}ba<-Z!nU5n5G2{@T?*y^kmk4n4Ft!#1ZsRO zQ@UoGZ?`viqsxj!<&3_IlAK(7w&2s9pwK8Jeg#Wl$M(r^4)JgiCo`9MS4rs4u*539 znByPIEfhy*;~%xGjXSWkk zzrFI|DVCWJL#}EO#~$L?Z%t@mbj??FS7=@^pndZExmfqiUVl!-y{EpgkVjL`k@4}2 zET(lx3s@nxSyy+@%yb}O`rf&FHqRD!C?d~$Dqd?p78wJf)7a^E>E|Ojz`(GF~~X?!WInhyh^ua_64>3xYx;OG}`yQ@)9!&t+vf%E!kO z$GwgPb23p`IpgfC-Z-$A6j0?=>hwRJkQx?K`C@mbiL3<@SL99n{0G;rHM^i|trmRB zyyxvzV_TbKd3jGBxtD?`T`E7}Ji(1eU*m#`ij|#RN2)=%fVq;Ks+*)t?^hgMPDhKSev&+-5(jFxgP_=l4#lbGF>8Pxqm?Cenq#AmR4o(NvE&RLnh-o-d(G{ zbAQ@t;J2}@EeE}KlG@c-TS&&o`O4x|KfO0tU=e$p1N-rB-!wQlVzRS^fUTdnF~F^Y zL)d3^Qs|hKK5~xP%bhyQR8kI2<74a!$V6_2Hw5qq{Fb{%#ZnjV_fF5Sf$;3xc44=$ zjnB&h7YIt>COss6?W=6cip86&Qq{x+mKP@~W#fv4|VRPi~uy52wj< z{L`E(&!tedIgn%kBK_(GLx}t^9`5`04cROn{mZ1FxPcVE(0ePuBdx2ysa70n||LsA9#K26yiXO!m`Fg4zjZ_G3gtT2;Hd?b#en4B{ZdDZ?XS3Sk5k@~>BvAKL$xGmX9|5n_m z8wL>Tn{$GDUrlYf?+WK#GKCm!bA}uBb|#T%C|s34W-jtrGMjrHAgD0BPL%nC0nphiHmfNqb7$ie@<4z8j?aR>ubgPm2p2h& zMfYd!bLA(S@6i_s|5%?btY~?eWK`6B5gmJwu9Isp=)SaS%SgWQv5>n@2o&or#SDUpLDl1Y4X2tI&wms;+bZAarz&Exor_kd^ z!tTQc1)O&)LumMy9+Vmdj>u^{(4{44(S+XdF#dNT_n4VIeq0oAIIb`~RMP!Sc3z${ zF<2MzstpgTz-?@Nyqga@ez>K=cm9D~RM8v4GbMO?+2O}Ujw9_^_EqZTu{H{QRPzT7 z4OVGIZe`!TO;?5G)a9h7R`^%B$3XMxrP!RNK}Q4>!MeC+bF6-hPY<6MIIwBcLeSxh zD%$B<{2p`T>VdqJ4bpn23Y1QsBpL=MCmreTh@5CH@|e<@pPoz*07%MlS1t8|KAYbO zk!8sxzoldLB30@iV~rFnDCd2E+^^028;>O<@Mg+wP}g%yxP2SsdUp}QsK`U1p}|tHbn{)MsepREo z_UlhiPxoFJ!B^PCy;Aw@v#;c(xWey=S+JbF3h2zOt-EUTVYORRGdl0HCvl=8C>7kC ze7lZzwF!Vg%S$s7jE~#F-b2eFC_HFnamDzv57b*7m*%TLiI?01^`^IbY1;B{2*8@% z8UP|%gayRPtq+#~0ndK&GlovnVk|*d6sw~ z5QiOuQDd_9#M}h2JIA=|C5QO6*ZpEwE)fH=pgo$V#;n9xz*{%^S++txy_T!#F8VsljphZ;`Kq`FTfm9i8+Tj>6#$6du<{ zI*81)v>IpH)7z@<1cuDDLy1r4l(9jOG9U=z=4C$_TJaL*LSd-=sSWJ=(DxUkh|t}$ z{gra`@csy+VwD}toFpiTIzKi(UaO7ubSXf&Eq7kM`brkmXzRibrLbv9 zWN0YY|BFk6)j0#P-l3uBys!8hK_Oo|t}#oanLh>F-NBBkEdY4&|&1Beo7 zS}rz1-bXyysKcupYgT2=8#I)aVb*zR+qQ|RK}`VC@S;C_+|klyej#l+D#i(ihthMg z$i^d$6IHtuS^_T|?Q8Pk0<_I;(ito9P1xe+G@Ytj3$dSgdw^2pQF%Shv#lIb#q8Ue zrG2ktGVSDMW7}1{UyPZ=B}U46-WE{OG%PfU<+`%z>pvJdHgz^xiTLq>O#7*E=2Ev%PqK3>)g)A{RBF*OaT+GSk`)r!pL#44%3!!TlFDWX%&Sb}(R4~`jA<7Xh~$2=Jkr->h$5wK{LCh*Ym@ytO<9I{B(k*FC$p=6ll0(+0bv|Z#hoJdWC+?Qr%tZi= zs@eN_MWD{6wBRg$C~I)$Pvc7Cps+n()D>mAvG^>?^y%SK|5syY9aUx9t$TbG1CdYx zkrL@rL^>q|lvGk0kxog;2c)D$K%`N+OIkp>K}xzqScEjvXDkE6KHvPDkOy>nnYQ~;S8*`%2xre>DBsre?Pl% z@ur0QJ;`#aT&o>LT;Xz8zOF1+q`4yZnlBDHm)0ZRU+n*>jf`{@fB}TKW;=ZkbWtVGB+8O11{sIo>{|=*q_y|qe5JkSE5Sq zyj)Hw#AnQUwDqR-1E1W@t{xDby0Eg0a__Pd7+&ra{w$(_f85M=Ch12Qu~J<8!W1*4CZ_?Xde5konVgma$9dVt z#7x1bIUT%`8~ZN96N<0)?}pMtF=33pEjTeK$Zr1*lZJq|s3bU>z+GhbdtzW@gweL= zXtR>}rgEa=x4D*hy0(RuUt3Gr`GpJo>9W?o!KqLtMplKDHd)r@WtR+ACCkHoea4!?Ou$`+P=uVB>El@gvk0hRbc__D$M&<-iKn)n(CBC#r_LsQ4#B+0X8e zS`t0~+cw2Ar+MG^)>c+9e6tKg1fh11>Mz$TGLV_)RZ;a=*kF{~<$uQA3{e1HrtIu} z5EH{OQ)jSy8gw!LfSe4zj}S;a=ftggUPAW&ylMaVd(t6aY5C*)vcj;RYBsmT{#N(p zIvr9$7u)`H{5Ek!(Y}7MHB6$6jYLy%Oyy}!Dwc`2aHksQgB!?(=TWr_S-4%c)6D^i z_iMM<#xsLiU8IJ<9RQyM^t|bhuX4I@u}iu1ZQHY(xH(S21iATYQes=!`;2rf-@CEV z_0M$QTAl5cX(DoR%9~gz-mv8(yr%(fGsWuas;VkzEB!=cj0}|nyDu(r|EOkV zspVL{{XiQBmqo*mkXl&9$Hw>tjs_X;dz*8mIZpuhuK8+ykDyr|H*=!t`~F9to(2IUS;QOb2$9NQJ$Hdtsy? z5G8`+_};n^;Vo|G`OJA=oHn6o1zk)6s@~Y_LY`g?UHwj_3vh`u#&j_;c+TuGoVs5C zGHbO>e+q|L;F^%}Sew;(wJM~^a6WvGaxBOE9@=S#d?CgFG9GJ>5UUk5+I|Lk7dT^)3$!7F_b~a`7YD!Qe}zgD z)Dj@gsb8Raa*_lkWZPBL4ya5JBDNj<`ESiedX`KL9pE+LcZM3=BNbZu+Se zQ+DD@vFI=*PKjuy>@Veo-xXsiBlxm0?p#9GxB3lqbgZVP;^BE@qSyrJ=pb=#a^XT3 zWVq4_4{p%@Df-lrnCM9HP^hAOKV2{4=$eB_ z1{uCZC_Mg^)ExM#Q1$cG$ZyE$XW{du1E$K`zqurL659kfBljx`p5}Sp^sI$!hK#(G z`7(}J>T25x5*0SPV?pE1Gw;kI83~S1ssYgdFnOM2AKL(y$Syw z0L)?gli-S z3se=KKaY)xsei>5B-6pzbDrwHkotL`yd8bVx{CRaaJ9TDBVj->w7~{R>XS5WOlQv^ z*VNWE#=-MvCq4`eO;0kuNYlJ?r2l?f|9OM|`Hm178Ua~rs*(xUBlw>m?tk9sI}nK_7% z>$9(|Z8P%Ew#e)UUK?R-Y;2mSGC&;|8-eWdr{`?;j)5BUcCkElM|XQ9(~*CA0{!ml zPLoLwVVK52JtG%6wjS)H0%H}`>V zwiNj2?JGqf*cP6{fnEOEnIiQAO2I)}2}5H^c4S1Qxtd2MmZLB^>oXl~b%#L^0d|N= zpcfSdIXwD41}hCJ>Dn^a6HAO+|JJC|1^8uy@4?|V!qs(iZb>$-qVlKl)IjwtY;4*k z5bK!+hRL-mR0XHmaGg#4fy~Q-vfzN2uTWb0@!-rO#Q=hG6vx9y)YtJv-tcR|_9{h6 zrD4o}yo4pT)8K}Pg+>jJ{mKQ#-yqe%@Rh)!2r4JGnOhWgcIG1wF*GqbSn8w8}T}?U}joc4&Aw=}m4q>gzD|nACZ_ZfX)QD0r=q z*72^~(e$u{>}0kHPeeIemE|TDIQ5EO&a;t_EJGSbHjZr;Xyfq~S0*c1*&C3tTOiuB zO}eU205|t;i3RvYp2{IY#=YUbxF`Pah6J5DBvFQbbaE%h5aPW;2|2mA2R4ie-5{}< z>3ko9`m1FnHAW)Uhp=91v6nARvh%uUt#`hXb>YHXAa;koqn+TJlx*Jf0cD%>x}bTx zi?2)||BJ{XZwF6#1?UtwB?SevUNHK+%_nYE(*zz6yH|tn=8dy6z6c~F7P_ucd(F+w zeRJB0KG^>5Aqoc@t*Q#jEvR2(f08!nj(3Bz#EM$8t>Ffv$ol*G* ziOhm^`FTzSnJz25ZVoPV`V1ooF5SQ6lzh!Yd$0GD^3`zakWC&3} zqt|<2r@+o-baXU><71_>=#nW>`zKcNl|$n2|C`k^JLyh)Agk7N9!kE@4Y`$me`k^ z9&Npva;IEd>lDYPD0bePSXnW%Yo2N4=hLomNT{H2$J?B)za;W5Y4(1h4Ai3!^taBt zCp;7O`2M5i(de^994ac$qa%@PcEPT2`nXLQ8a^JLx;|Qnc%_J-Dx4Mjhqr#+*Qj(l zp02L}Z5+;MT;^k>1$IR%^ZWOyZ~CXlRK2?Tg8AnliUPxp{AGcdHidTU-oPTsdQp)L zM?cTmTgWddaobv01&ss^Ci)WnS8#zC1V}5pEBoKdug=Wg0J+5$wOx=$;OFiA*aR^_ zRXQ*fdNVL&XlEf9e=>CLpQt4FG&Z135J5%*nKYDhE5lob-ceCeMEAsi>ML?L6zsI* zLx|C8^iF>;l)H1(I3z-dpZt|?EPHt)rY6!UDFNDiwa^>Jd8Vx|dTKq}G2@H+9JNhb z!7W=3^+d3HAtI92)|=szhSJg8`rGsfNJ$1o;A4b^0E~^%Q1e2C@{N{=Ul#$hD8p*= z*jOwcB>&ZyBmvYebGaNo2@tA*7Xk5}#(+pJUqI^kfj+;7P8_<6_h)LF*1mO=rnNC` zu;26PL=DK( z@oNG^n{SBREiuc|?sjbs){J=9&{wpR=XnuNq^_Q748@hZJYogA1h^fKGy5>10&o@m zVaO2wF+KIXvRYkdN%y@tPyam?U#%U|i)j{-Ub!}2aQI%I( zLrYdJSn&qjUXft9%Yr$y03trpxd^q>#+TCWi-uuM++4^_JlGii zeohqT_-vKIDo=-B(3%xw9u}?Z{>Y_Y=iO<&PKohJ`D>n0?ZK{0I*NB`T07)m=a_?q zCGMcVwMQS8g@{FEyZWaNSzp4gkqnmXne2JHnqbC5qngCoz`&qjQh|Ka!T7CTU%(AO zb<-bwZE7WEf(9$}D+A{+t_>IH4Z3?A6Z-hbTP_deK=X!hPDWBKV zI&g)%vsF|XzgUyusIuNA|Ppi*E z%^I#sV8YZSh&`yngHHuX|MGl{hvD z|C~yu$n)pci%1;}Y|izIc;d}RnYF%tZyA!*rGxdd=2Y-?TYsw}f1jbW>&8(dEO}6+ zqLn5EXV;y~O#oj|-7PUR)ZbfNGBq?TbsKMr%!_wxq-p$XZvRnTeeBfS)U?09i*x!A zh+JaE<#4;ddUb&^SyvQ#SzHD;wnz&Ff*h6GwrJtExk~7&TO)zzHP*$(Vqs4ehC!U59V5Hyx#Yvb=}j*FMDhg0(*78#t+DN<*vya zJLB5FrmH_~Ah{`J%;eiNuPJ5y%vCEcv0qh#aUa8O-tlW|v}B9g3l7JhweUoIu92)P z4UddhQBpRvwgP;GY43M4mqqV8yx!VoK2|GEr5qPg2JdkToU0D6Gsr58&Uw}vfli{P zcHnV*2_o%hCJF!$j|LqC2?u=a+yTU&-QBboMYO`gHkVQHM6@|`&6QNYivAnKF zaNY^eGcz%Xjyde!oX?u5b_CPT>2(n>iDiT!qyz(L41$6lc)j_;H;k{uXns6E3#Nd# zhcY!$XEf)|risL{n6Ly$6zN2}IJ;hW9*-VfcKrA!TO}{Pm-pH@$q{H=5jcmYXI+Oe zFtlF?3!h8qHe`z`)-)Nk&wyg*AkIYGea&zDKM~vgwk=T+0Mz}gL{}C#5!jtn4<72crbUo%wbRK&6@^GD&OunlJs<3N35DB^2{G45w|;D z`JW~mi`vinO2f{T4a`TrxMUGbKy3d|4Ux6p!8F+fP{DtHp!r;bb6dy6B(uVB*e6rS z&az+rZYaA&Z~BuyqzQ}__vq;p+c5vB9mpD#mXW5FB`q~|m`kiZk4wQTg1p-Vt1LN1 z!X5LSy-6f%KtuGb)inIYLXY!@_tn0#123~U7gDPuyXK)l-_)$I{~`#O2{5jws(T)w zYeIMc^;sUY1XCx}#23K`P^jI{B9Z$UC5ONP@u7W(*Cc{Ahzx@N;i$marsl*o8U~yW zt3fAr_VY&Fm7!;FY>2$C3C>oWPRg6N*^P80Tk3dNYP`L*V8b)gBPZjO<&0RSzfFfu_Yy!z50=uS2VkLb^0t_v;GYhrGZ7E`=^FLr;vaZ z6J^)(%^QKuwc9qghpcF)!2L5r3LFm^@3UDCMh`4E1rkG5cn8KIh&q5&2q^JHpEKcW z9n%Mg3_|p>@!ZAw?VL6?MbJP+HsOKNs4)6y0$+drA;mH5P2K>H$DsY4gxc{^^iRQj z21QBrgY~WSEhjH8=&j0m$~-`$u0se1N^)MIRT)hj7NEtR}j#ihNE7Vx*AV z_m>aADIY4C{rM~)uZW0iSxop`i@7JJ+0ohh+S>Xyk9C$K9d<4* zhaY+MUGaPw*_Sd6wi|6gj?7$!-ze&BVH6^hwfXr%#L^2vB#|$&^AhqD-_RO4B__-< zA3RCTu=5c=XnahgL71jUeH0!CR;SEzY9*BdRcRdG`dMopL|Xav6Ti>`51)&F1459` zfmo-y>Zd&Invu3P6wLIWKTGxXO@{1Hu;f74%4KE_KgTB{zK!OI_9r!CfrsrK9oSc1 zlr4qqT)SQ7+IUmjUTbni`aPNC^3|))dF`1&dSeTaY7pztE#PtCoHpf4q=2pr7B)E^1CKtu|5HHzhUsX*(_%|5#6&spg=9%wUD(Vza3ux5mI|p#XSW->s43l#=A6_gx*ZiRN^jrD=OVLo= zvziqTR5=WWT@f}hKR*YDj9Si7XhclpSX3hO8v;@JU~++p3_UKCovdtdkwP5kG)Q{s zG$M`-M{?fxb>2$$glOJMXB=Nmxx})Q-MB=^ZAhu|MSEm$uR-w;L>oLr$is>6b=_~6 zShB6kz)uMqFr&eedJwZMhSnuKFUowY;)VD+k<#baNej5w$?ZYfX>QChC8~kxXnXea z3z_MqyhSq78{0A@~mdpT9a@T)_pMgqBwa z%$bv#(`?h4^*lK6Q%{QSPrj1+^Haci4+w$jPCF(L6+l^`$BImLu*gIBEZ2?zz+ygHpMh=H8NJi~@qR9%(#-*z2ROdYeE0xf2s{z2s{vn< z?wN-K<`)$yX)3ghRvzb$ht~TB1yzOj)8ss@@>1~hgz3Gbow8B{t z%*1u(4;*?;q{1F|z&4qYu{44dlf3m5v0n{NzVXR_7KRuH*h^r$$ZdZlFLLAwo=&OA z`-zKv-(8y`ASJ!)e86G0s0@E%K!8buo`7yYr0WMFIr&3UL0x7l3oRul zlB3>M6%;(^Jh&TbwEVM0DeI%7QwgmXbm~&fQsxktYF*yD99-}i8LfzxvwcK=}T3=XP71WdLqo%dFlAiU)#wy}Y`;C(^rXPr0FE9D_`=YG*_7`58v!)eqs9w* z154_n8DB!3tKe%xA5zrW73a)omSV;X9~KVTH?W`grAWc9fbV&0mH7A|(Bya>1jl z^zsaJDu8?JOu}IM^lqp=lAL(i;-?8dLe%lm6QW^lm(NulTsVzuKIR^!v-a#(Qrv4mtU-UqMye42w6ND&e#&eT? zPSU=RJQh|*-&#nzd7ap)i175{vlGx8C#q$5t>=cku7st@#+VtTPI+8mCN%9|oP?4T zGcdelDtoJUdWk>D{{=Ge~zruzo%!KI5`0*D|Z4b42T zHj^awe~+Z-%wio!6MiJbzBmiQ8|bdUYM4Z}kvhzPT+ONz7+^pJu^6pzJ-Tp;rOrUI z47+X+PiyRVoS;qs=GO@FR*w6V<$0E+VRYR`hjTSualB8{l9*X90XxO{&>Ms;mwgl* zgO>-*lgmFnhl~4P*A|o{Xc3Kq%B(rR`VA2{=4~@?48y$~!-ed<&&qJpZ3kR2E9(y- z`|URXOoIfxN{)u85aYnFA@D>%sjT7MXPaM`Ia=Qocp_%C*C!zr}U@U;rzo-46Ib3gjVZlNwc&_6Pc_ zfwejS5uv5$PIfMhx3JhT?2LclHb%|M#GvFy9!~aR*m1wzMdfy$^~YG&oNE z1@p%=!)1t9vS=2%z{}C*)_^6iR+tj1v*2LNp3G0RE042=C1YUVHkUD!VHIoKG>0d9 zdQP3`QVysu{UAk0$4d0z_;n9h7wT#wMtoR*DE!1`V0ZgyYDG5Ao?OPFP()+90Hh4xyoO3xbfx`XpT(W{)ZDwA>rN)-$4hT%zr6uf|KrzFNto>!I2Ms$+o}mXhs;*JqY?8 z)LGq6LCS+!($SFz$^b)2@IFQ)?h*z-Q$VnteUeTs_@8MsRb7Dhv=IHT5jS|%iKdVi zjGQiqcl2K=xhu#SaRFq>J(`WWx(-8?rvcWDDc&Q&^K^}K>pc8=S-f$kcG#I3xBwhk z$s;2`pL37WuiIm@=G$ifeB)kVt{ER1QBuGEgkWBOF7qrI(9~efiURK~NOul#GYCs5ZW!#mV~9VuzXs&-4vRn-lOAFWe++ z^r&rWddGCD2ZK#KF!U}17AM2oE0>#uutVv6jGEHZ#mr_Y=DfeW#bz^kCysvuIW6EY zy7A@Nv!bA)HZ@~O6simLQwh)Z=B1g5AE6={AO{8w3sd~%Fb)si?yLk2mL;0zfHf! zfVP**lRQTk3=%n$5%d*$WG;y|H_B&&>;aPxL~rs2p^=L3P=iossi*F zPz^plKBu+8YB~0h2z@Ga(Esp=$5Z{afvN3d00lXOdg9kl4H^|7)jd%f13%TySU4vpZNT@|a zvJFB*fVHhCCFYtrGs(A+3b~>CMULZZi-B1h2)RJ!h;3ensuJWSPfkuCu1*jNv|gZ) zE$titofAx(Yf5o(aR4C#kS#Vg*#B-}L#!At6lyUh#cKG_Y&udo%iO}kUTaJxR15IF zR`ZY!8&K2K6o6LAL=HVF!a2(7fG+_`6~H8J-*y5^I?*^U2n}G#QOg_j_4ba4jAR<$ zP`CptA!yoJ0|3|^Fl7U{2HZ?M&%L0{`{1WTLQETR6?4As>f_ zzlV#`^3<&~tpFv*8)pFwK|q8&jFMK)D$cWr8&qc|3=&sRRwg7PtFjp9H9YmVM^+s8 z(Wn~?eXVbAruj>Lw*%k=yi~EX;7WzwslbUcW{2edC$QhDGk*ku4*$VzXxCK$oQkf8 zf8gRmV4MWo2U=pjlQc8SwwbDsl*92&%FXTKnSij3R^IO{(Lqc|?G-==PW&iNbMTg^ z0%8pQt3B@Df&jod_VgJbk)f+`w;3mROyqllkAu%ft3G5aCXVlPItNh*RXe-PHeJIf z2AK3-VA?O_?jit1U^@kLiJ0hUNHJ60b*-vd{vs&{dpj_nIXXImOa@z30GF9KI+g<^ zW!TIg3kEOmC1^c^9|}p2!Etd!Ow2Zq;}$l&z0*bY#rw(V`+@!jA))=&LZ?<3blBjH zy9X=9;5YH4D1dCkIx?99ZhJ;1CeXh1fX8EF1L%o^k)Fq)@xi1#fGErd_5`|-c|!xN zCBRmPa~=~5x6XHGa#M#MbFfV94NY4au}6ngd!L9P=)2RM4NM2(NgI%ghx@>xtAB9t z*Ze%Xzvg%Va87>?41oJBc6bkL<^YN73W4r`;9YN|I)fQSc>T!PR&!MIZoOlRw!Ram=YJ)sEzZ2jNAfA2QDF?cpUkw;OOR|#WzY?_SN zfl4PKCdSUmNk~K_@%S-|*0(?70r39%dV4?K*NzVlw}oK^xhhz(06h>O2{;mSb8`T- zjaxWWe;=u0WMu`hl#`#IUr=zuE;Js@lrRwhYAQ`S${N_uzvf_fbklBth-iGotOP<8 z0DnY)f>Q#V3qbg2+aGcKIa4g?p_eSD#n>BQIm6E(6~zJ_m4WI5I0(1BHejg0eGD@a zOb05-On&ZvT=kggjx6K_d*ft#tM>0sR5qH5#dp&}gXvUQh`{KcIsEfLEZs z!zO%`n3xDA0M}rw{1(*SKD$skJx1H>>n&|hOoKFoI(;LCntfpT81vd0})2 z5+TQZD#*}D!?@;@K$pdjV~f5?0+#8sIq5$$>wlYA-ACS|<3D%qvVHGsKiQ;1&)O^Z z6P&QOpCkXCw$H_Ts+l>Ak7GKSo=mF;HQ!0eI-6PX@R#yS`72yI#x)a5B}$q2R#={@ zg#PRKei$atMio!bB#e(=K3Sd^#N0fp6c$VhzqftFehja5>fu90qRklm;NKLn`|3q9 zLh~VR2d!G75|x!1_rs6p`;Y~SYS-<~E1eIQKe@XPYUzb5PnE)12VU%E^5+G+*?o+| zI79pH1TAAV^_a^JzZl~k@B5DVJqvd{3iktVJn|Ox2<`35eU|a| ztM_OY#;jNP5?>ePSgf=epF4gY&v|BHW>KRVh^wK%^j=@{QNCjSKERph3%&at$B*DrpVCJkR_h8%7%k5xyI*U+WzF-6Jsf;4|;P zt4`SZgqUP?@P2e z^}WBpzcY?^vJOdVH7t$tuxKtlv=-S{ziU}&)jj#O(5Lt1B_RcmWy7n>RGz;SN*IOM*wz-2Z@I(6!#q4ZzHfw14ofIA zQ}4i_?_vkNv$JDtY^++K6?U%9ZQDHVh=)cubaUT>H99UVj6<*ftaSNlXdE$ zZJ}1#N?u6q%I|LCWS_1CepMYE*&~f`B>%%H6iQfH3D*MdoRBBh|m z-dqxr;bdfDa{lbuw2X}7-)nXBa)}O3PS2h4f`bvlz(zio^=DpQ$GXVT_BT~s%nyf% znLPGWeiWMR4Lv8>pImmHTz6I_GQYcs;DSLGd+Cx zkehpZxWptNAYio2S{Gxk!e0=y**>aq7RRT^u=VZw`nvsG*X+-qUOU4k3JMAt85#W( z&&QF z3MP`zKiS`Crt&Aau5V(pJD_Rp`Fo`do$_H8F$jSNik9J~iIl24Id+$mlcS)BD>7)F zD7O_UwdhV1@cg}l!C(^D_3D2#hiV$X4GVj;&24O?_<<3Hx`mPyeWcv+j^=SlOLK(d zoh8-UKI02_J1Q_|uRgbAxl*KdduS5Tsm z2NPahT|H~zN65v(%R4wcTv1zFJC0nAJxb~w8XA(!W_We7R(DcaUhZ&56NRGe4-qow zdL-kgk%XsH-Qw)HFlk)!IFwVx49DvI&nONquCxioc7FlS1LaZIQ>MYM>vV1kbj@ts z`1U$)dX0BcN09ms4UNc7dFjp34|*c7;*VEnE!@tn8gF=ddcyVI8SCEu{)S7nx3{q$@8kFM|7cdr%iH?)q@T`_!4~>FL;Wum{X`H1y7xd*ysheZ1xJ(Ajx+ zdub5g4^2{a-+gEK1ETjO%ji2EK0ZmJg1LFl;WY}s_C+HjqwVG4t%L2QiHQj(CnuPw zOxc9@PTZPtY)nkjUS@Rz?=hvK35@tV!PhvZJ7PF=J}U!*x6+GS2FBgaR-+0;IK zYHzn_eM?It9yC<#T2dN*+M9=2v$UhWzP_ht9cKFC#fuZgh*2PXakoxTkWy!7+4 z8Y|y`2Sfr%S?~x6Wu&EPe9xXc2m36DXcO+-(ju|{UF!V>zxutsz4G#MNG6q)eCp3r zQc@sZN=iyDEG#gSTFkaZve@seuVaJEh%N0Y)UVf1n`Y0TKE|yEwMXx+lufp+@p6o%{+g=o7InRnj94;AO zkf)e*ougDg-JK*(#tGBT^!p3()Mz1*stgNT!(MPjB7z17SaFz2~N5>OqXXoK! zxBezXI-1pLvi1nJTyfLVV4(^7wdc>Dldf{M_s; zrnFz%*qDx69}o52@g*Y_DmlUMO8rN>-@0{+ zfdLMV4E*LEFRyO3i>;`rD819=Yu8+k50QNar3`s0Vgf?K!2<2yo3rgYHEy(b?zG4i zU%zst#BR3j@X$k5b+E>LH{`~Hw>z*=;sc zA=PjrnEOJV8jYz1ty+%ZUoNBP?Dz9m)kw~ZJsx%_v z;`pDBmBYN;i|H5`F?Abf`ZhZ^7kX2GkCU@+Xh?0e0)=uQ&wG6i)q@f6I=s)${zgOO zb@!YFKWxGU$hq?JJ#s=vX)B`2#4u*#PV$I76zW>h8C0@}xw$!jp~b1G4Ty~tDMIuee`&Bn%ainlQs`)57ZxyWTk7XZXwtZ-n^o^qn0q2c5# zEhW3 zraVpF-ZKTBOI{9>tSI%W;(HAvshek~j2{Xdgo|{%j@d>aXJ?BXq2+AtRfB*u!WZ z7a_yO6BB}$Zaz%rIR=0!;9jYwu72{XPp;Si0z;*<^Ryj{^UBEV_wOzc4F@i&4t@RN zf61S4i<^^kcboEfeM*FuLsR-D3ROw;XLz!f%RBx35#v=qAkk$v%0o@yQ)J+iDAY|Q zbeyoiMJ6AZ=Ih%*aN$CQ&D7y%#K%AD3=|N{!}fxE-+k(irGy+8cc-WI&E4C-XfBy9 z_L~_r1-Tylk`xyH1TiGC;VcRzAbh1?Im_`=AlZHa>(Qv?&b(~~V`gp zIJa1%+pS#hQ>Pg{z8#jOTX#%CG7G?ykdTm+G(IoyH&k^j20IYjj5c94qokvIRV-Ue z=1k{9WB<}X^0E8z=g*(kwfn{;!8$rR{rEg_+Db}Fnwm_+0YjFY>OQftu@Dd;3HK~G zpe0U@PmaFn%FBP6+k$O;f!g!n7l5%lz7LV(>e(qkc`W>lEG(F!qLhogJUpcsOlNmD z8J-`D0i)!9tx|}epWiY@nUNcT(&yg2oWjD?ucpN-GBPsC0dlxuHSAnm5tx*(ru|NS z{(++JqbMcQ7591iHT^hXxI*8Z`B2{C%F4=P0Bqi)hqELeKW>IQFE3l}2Lr5;OA?Hf zO80&9IP|6h#l8m=uU=MGEYvd#eVKr{F|e*#@JUJei>Mdr>5*4doc{hjnLT5%^$g0J zRRp%Zc2m6{J|^J>d2eqo4k3AbR8&S9q5qfbywAr*FiF)e8`FRi0Rqzby12W;_+V~e zlB{coB{0k1a-+k;m0b2l+;>;uDJia9`{-1T767mZ#R`ns)zuY#CJB1RCnS(GA<6=- za8!*TmL*%LLH!E_H6}r94r&;-VDY8#s_yTfCwQ$VZu_#wbm)3XPE>C5(`Vi^Xgd2r zjXB3ifBG*hDm}PJ#Ze#HsIPFwQsd@Ctcd@`jI=bTwbh8XZ{6m59?@cF?b5RlO4R)6 z8R^4Mo7$bfBmV zL6Zfa)u%`%)f<$Q!zl_Dd0VT34{wB{N-YSI-D5`cU*p#Y{?-1R6bM> zur@SItI**RR5=%C_1Rc6tA_~U@H2LTG)0DB@jj>9u$7-5tx_J`$VzHQ0^=!-RWbNF9Fp|X(GNSPb{e^3lN_80Ta znC98O1p;QVad)z&vqW9 zB&(pnP5oEAm3fyTIUE-ATO26V_A5~tSOj5tYFb4ZIW4$S@{f@R%^@){t|B7Zu3`!X z2C1UASC{i3=89)>1_cGdEjf6av;hdXjAIZw83ThuhI81WoL9$&8B!pwA08g^(MCLZ`jj-fJzb|zy~KET zbv%Ira_uE7Mlq!q5~>3n%a`Ca-4O7={%4lU)$QS1^z@Iv3}Yt4>E*mmj&_ky!Go;v z%QLw)_V!yJKYr|LFQ_nTj}m+Qn3s#ov;ZIFU4RP-S(qOpKbA}xDl+hWV=-6&n8JcT zwJYr1yK7gk8d_S8sh@(?;b&5o9eX)xx0>LJm3=m7{22^>F z5oR}B&fpNd3Pb`^d3d;!uagEU7x)0Pq z`T2mAJ-^h~pFRE8p#mAJ*80}gj}4bor%qj>5*))=s6yckLBgaf9>@{~{I`jTg3rh9 z3nr~iPj3SKp{lCN^f0x*UwNN{$JW*s2Jju(aDf6<0*W&pc}W_p_U-L8TFhXxq8hY#aN-o?jP!!3d2A|&U^s;%{cnSo*=QpkL`*oc&r z^ySN!$jOnjjm>%#i>8*AmM?k=23C7~V3E`LEmsvdFprfH3{va)+wOim6>@0X7QvvH z{ZaiiI)$B|f3&YJ+>PS@sf7N!f=(Cz3H7tWr1e9W_2gmt>$G2e(Rv;p9&l&C;M&>= z%VQ!UA|)jy;cwqU;ib+z&=$$0se^D`x)h2W??3LOqC|Noa`rf3V^Cyh=%wu!t%lEs zK0GQts#>e1@%8od%h#^jX$hl&5**90Rl5`cUQtvecH|f0;3xt{g~h<@U?G#!xML5n z{#;jr`&9io^VIb8*-FPHEcI#F@@(R;Sj4Zyx-W@hrbbCrJlsC+wYXn6QC zx-umMIs3kqvtFYJGQ?-q&Mq${&u&dtuwio7HUTiM>-t@Hd{y1ep1 z@5@Xfj^lK`8E5RWL1=I=U@OAy*j7WGY8Q^%w{PFMWBB>SIXon(81wOC1O_4`-rp6r zvhr1xcdJ0#Z!dmJf(7-xa3vNKMwM5H$Cog%ndP`Numbu)W1|to2DoLwt5>GZT|j~X zQ5aq`yS3j*4#)&80i=i}N|AH3qV>|=<-o?pM9UIWmP^qRcOdW;WM{iV>^nLUSGfE8!v<;64~u6tD|3GFx=cY;^KFRvu~dC zq=@*wSs5)ufXwNlm(_xyT)yM{%a~6V?qLg2+xYIPkVkCprAqvDeJ>CEIEz+U#uk>`vzLud+!ai51q5hP(V@}M&1GVzm7mPfkha|rw zIxmT2_5tgeCK;Y}dCT|Br%#`JeSPQW=YxU>ZiWKyQ7INk+8XjYI9MD01IV+J{W&U< z+(RJK5G+>%VH-+Se&wVK_Lu$5PrX@CYIhuRm$lGz6WOYPxidlZdR)**0lLZCG) zZUv;IOpRbHbZb3^L5%SAJ-eUjXrQnEv_LB+DQRtSvC?^M0t!sU(fqu;_o=DTY`TKx z{aL_Pwu~@^4iE=6O3BI|@BKhRd96nJ>yQ%&$?dw5F%Vt+U%o8;5era=BQ`k$J#LVb z%M>Ht6cx4Sb`|BIU^d*|B%TrzGvasrclpO1uB`g@UnvwrBcuI;gQ)1}x{>~?E#2Kz z0&Wf<<=raa0g%Dh zb~ocM@!6c6Dd1~qBME-?A1aY3d?8h z|F3umi>Lm-XcvRz6&P1#j`<_MHl#R@!fo>~{i&D51KqML%(D;8b8-CY5 zON_pE?;dc)=?e`g)JUd=JOgdRnAT@DfUYPNsP0}j5+~NA3(L0-bo5XLtL{S->6CIJ z4W!Ne&F`no*jU4baxpsljl{_bx%dZOSC%d=np05Z!u-6<@nGpFO0|iQ6%je(NRvb~ z{o{v7@CvnWT4rV_1#d|ATvd6w2MBxLzJ1$#L`+N!&<^&nC@uploOPF?xVX54gz@A@ zK2ED+`)%C>C>{LzCLF$mls|+~JFu{1cobY$wN#CJ4cV_D!0o5&z!l{U+zWJS_*^$< zAbJ6>5dCH9aPLQ`QpS@osr2Gh9?PK~$X<^hKSsAK50_x!UVs10%8Is@7FHO9aHQ!@ ze-`B?%}hG=TLcbI`N#-gXJ>E5#$|p%LBZm|si~t90PNh1f6WW%l_uNv z?JDDrcb6$BM#BpJFp!nEwXa9B$=x|v+e@Psc}WC3FdEcW_!xJ_&aSPo>2992K;D!+5br?B!5qtk+&q{7PsDD@gks1C@3@e~p0#EAvqbV3bQ%2tc zq!PoDVPL+^T$5vB)Ikb|M+~77T3uLhg%oW31Ly?!=ulNUvIznIVm(<~QBiSnxOIuz z5ae6{6_kJwea-{r=0Ze7gscN=+!8tz<_w4grsgkU8<2{DOX=z90p&&23kQYrC7NDY zF|e{SFmD6(p~7R|uKCPFm2-E0giH--fG8LF5d?6`OK_YIq43~I@u9dFQY7HHXZKOE}8RK|9l=fiu79S zUu$e^EL;>lg+he==Lc@^qfl-?KmBzEfE+-oR;W$7x@z6FUl$c^&?n^u2L*X-&IFW{ z*aM4|)ScP40FnlLq|hLxX{g!El-&(d*VWb4(K${!v1I4rnW%n{W379xhaZ|62Ewzm(ANxNB1s|_97DXIp;M@yR}m3?etzfn z_q*xj5})8g@W57`syjPJu+HnZKryW?G3iz>799@a<0216Dkrg|kMX0KNpf=XHlAKn zE(}rVTxzIuX=!PJ=Idv@8A^8ZCO8T*ch)A^ zEaWo#Xo&+hS+6$DY{BE~1Ign12G$DC4;`Fv8%D_bhapCTsQ&xc!W9w{6&)P}81`=v z*i1}J&@G@K+1uHP-u@a)!JC(#Z?s}%Wd%^BRN@XJGqW<2F~}+)(baz$gx$}^%slo9 zMZ<|5DVC+t3H$G?n|Z4I-5dN)H{z^z#+^rZ*5uxBUnJCi7Z#@X{CSb(u$`j;9oX)W3V(~B+T{dn{eoL=wf@{y;2)${t=@Md2`F?t>3Ic?Rw@Ck7Nm&O2 zg8k}oR(^gm*6f-8wP?(mJTe0VS~$oTZ!7bHl$+e##1hP;ddz|-2R>k5 zwcmYc|5sA4$CS&;1TYj|L$|eyg=U)K0#ra!?K-{lSUR)f#s@}Hl(0L9bwFVPyXS}Q zxJrOR<><2$ynAB6$rIf<(68-*hvh(=^h;QV-qpk49f${z+P_&c-ty>6Jr@!mx}tGy zoty@oZWk(BY0J2}W(&)!XloA--BmskrlHA*iSfHW8vg~OAM?)q-eXXS2CwFLc!!KY zwF!oa3)HFN!3WcUY#$T4a&Lu6Y;M|ZB?(zgP6Y?o_x8T;?Y+Ip)%>-{U&)#5VGU5W zJr(lE?3s^`#l#w+dj6xNH>OKR?<^048wS{l&>Ii*jg6nGt6v9>OD`pfrgUl1e9h!Y zNmtiIU%#yk@#*9-C>rv8GotaAu)MbX9u!dG+ot>ba!hT~?z=ne9C~K=EU=>t+-aV* z*96A}Gj+q@;O^eWk7Ay%u&|LXl_}Eq5B&g%mH8q=?o0A^8ZbG^MK_RoW`yI!R z%p@;BZiXHn4mb9}HStqw_z1W~4ie6f*rJSm}Yt4Ez>oFXX{Lzkk0~*Z%cun8x}v z%nYQTk#+@|oE#>6yrd$v4B2t*h_dCW8O5hh$9m)o-PRB1=A% z-_%o7LhMa#dMzp@W?M0qlf!6^{08c4YHrTN+FHnE{X4dfy9FfCEUZv|em-z-abo;V z%R{i&p}vHlp_Kds8iU1U`P&8S!mQC0pf^AJd>R~z$k%%reFyHH-v zY`B1h2J5Wbv>4U+JurO*4T$-nfB;+#Xr09+ge2nmV&VuaG{57b^7-@r09HEUcnd2l zHO867mhyww28@i+<4!0o3qxPs0vNnN5vN#T{$5U(?896|JQ1b;q zS_l@dKfH;C#z@e0PPg4vEEG&mPX6%W1K^R?i$F?sYg*S}kP|N`{P*P<7#OyGb}j)= z1E3hF$GOh9Xn;=-<@{=xitOiPH8n@UOarUx@8>sO<6ib-zbr@+Hvr)6t5@&9Oa>*U zmbNw-!lL1@GQv$8z+hI$?gb`-SZ)gsPtV=uVFOD`X5FK*-;X)%e|CVq>D{|`Y;}j5 z(~W_8dU`A6^_y35l$SP3NgpMO-o{=wBqWjBf^^$v1t<|pW-D)(^w>H&Vh~>Oejl ze-yJy;xq{|p~+F6*EwCFPN%zSIa)(8>4DXNE7mBD!bu{Zo`mZDnSu>DTwb<^V^Y zBH~|rv@5Zj8aVAZgvWZ*%Lx1J{-})Np>OeU+a+ zeLh($z|3shuI}2r%0Y(>k@4E)OT*uuixwtm`KIF{hQi{rIUO``-hNSVb(u_egM;RWYvYdfBqYc)j$ofYJ8keaGrSJ zdEfwH=DZwaP!^r)yjV>le#x*e z5vXWTF&h(hbaomM{tC#?KiYbMSS*=s=+^cto<&up5VsgX8PK zfD=ee5CKnS!sTLV$2NATJk}q9Hyt7fH*)Mzq*e|7u#1UcULAiM9xn6b2`D2wjbys8 z3N78EsWW(GJE;o9@{R4e50P|Z=m>!7D!cm{luVmdi9(1hNgiZ3zz>C+_@lG$-hG&` zaiH^^nG^Cn-21D(Q0oqH>yNSX=gzq<;y5p^_U6ZpTv|4dPD;{!_Uu-ItdQ;W*=_ES zRTr@MfXfuSYcTxir%$7nzXVlOxN2x<%oz=i6&tk!D;p_<$OAesPq|>u*w6R9G0;Ob z`stc&+e5ebY$;Cff_W(E;Yb%36}j$jrVHPCCxi%M4y_awdx3Enwk%CZ(6+VR0Lugv zYM2B|`f!OmWyiIOy1I{Xfi#^Rt@~^oSEOc%maxhfzLmv#*CS(9+XIao*yv^9Ef=TD zq@au(Zg0-p(uK}tJtb#6_Ofs)Jla_q?ccfvyBvr`K07u943wZ8_4li-&>)4{S7IA? zx3{rE0XUer`zcYv(F5RA*51?+^mYPoFc9BBJ7bwRkXK+?+o70mZIzUeNPfu^eB*%~ z?DqrJX(c(j^2$T!+Zjk@W$fe7lH+4~nxjI_1tekrcTy1iVnhL;%9z`>N>Rh$BPh)K z0|#WM{#CjS44;ey@e;U|o1t?6f4q*|Qsjr0)j;yM8sTD%{tbn3U?bR&A!U0pBkhJG+HSS}!G~K7cH^)S!$E3=FVPKq~ z@FV+`mFNa*$(=K;}L=I)dX4!r%^I7(tL@LHFsG|Hza2A8zLR1_nP=a-aJ7`_m(| zw3pFn5fLalL72UNql*#pV$unn{uSS;<+o4=y7;4KI^TaQAO(FMKp${J8 zUo~8WBSCf&TvW=8gqJR1;|U%f9?qqk9@7mM_8CuLi^JH#YRB)gehYKJjk)3Y&lK$J zAo2#tv`C3N{e7SGi|)fbK_gFp4gwy8?(Xi%Vk3MNEHoAu72W#{hG0Q}g74qI2Uj@c zbQqQSF9oEeFfHQomB2>mi~$P-jjspjc>r}YLZl=l-bkhQGcc=txOM-2xlva?QF+Lb zFsKv=b~>(xxAtdBZm2Zh<5=nCc58;ssu@d39@lE&AH z|Flo{FWNzw`rf$qZLoD1GF)y;vwgP0a=#Sz_)R{CyU@7-3}#;+C)iy1s;`?1@Pio+ zE%E5lBUjh`H`F4A?y$ZPrdiziEr;0mIYHG@v1_vu4;PSl1`0u3c~q+*xUaOToLHd2 z2VxW6ABokn`C&c4Tk;@aQ%p0#ewLP&21+F|KW|#3cDRt4#Asb;^Gq==@KU^hJ4o9R zZZ$y7Lo@;Qw+t#JR#w*g_tmE2RY8&-qqN2oU$3{%vg5(ILk~8SaKrXRrS8}nle?STfWc5nkzrQ{F0_x!& z+gS3VFVC(46@-Ih^`zk#M-05I{-z+ zK0p-6cJF__%zP@UgE9Yhp2~xWg2Ek2Bw*|g4h{fEwo)yvuHK}e;J2G;fvJW5EcZu` zkVjBRf&b%y*I_wedg#r_HmTKgYBRhC~U6eYpQ!U;TIT>Vs#Wyi3`DzRnU?LUuPnT&x$*vp$VVc=8X^TqB|l z9(d?TfIMD|!E6Ke;IW<K^BY| zSdY*6vJLc_0QE?Lp+IhBi0QQkg8BK*ixOXu2B!>uUbB zKS%BKBO`6?;QV}%^LA%YrmIE@f`LX&2oJY)NxHadW3MMfYEU_<+;>^STP^d0GX`rE zwV~w;kTMj!!NHERG&exM`TiGZX<73X z+1U@m&4M@%P(6aT5ruI1p<7wmhU^4+`7R@}ne9V=yVFSqzMMwQJH&dv>TDX(xs{cd z$4G)l(V!nD{`)k_vk|P}F-^vntZ~6rdkL-oA**`RnR?{l@r=IcEbVb%gGvEdRiFP= zQU(46w*CL2shWD!9#&UZ1LpwYZ6O3D9LjNx0FrwXg|#XFW#G079+jv-`MaC0m<@~x zOd`~Tf5r$|0ea)W85I>7IlH=Au;Ku>szecn3{@Z}fA@Vsig zjU}K_+Gl+9K5^YMjT3X_Z$`Rth9b~1}i zy|ZLiCZKQd{IkKH;~Eaw6koou@K9kUk-S4}l?e|*T%_h(1YAasEhr!MB_9{sY7Td> zX(qZIlpN(=I?$%uXLB%sSTt*q+FH=mpiRt|2ucZH6O^h(mjCF1!b_D-kqDqKbc=P} z(LjYBRu>PG05$kAFa!U^6Vkr+`m=`~Ro>Ijq=X71A|U~K+h*WeLSBcU*`$};o|~I1 z)WDb@?rrpI^758-y(U2QD2n-I(5b=x^B#OkRvUc+q~mpRa<8pUUU6~p&gXOVckZ~j zxox^spTj5Z9~{gr4e8i>Z~Y`x3yRyNxdF91~m~Hn;_AE zzIdOS`q4mQ`z*{|G>oRKv=oxq>T=*8&2(KzqdZzy9+9{C*%4z*K_%q%1b2VRpDNah ziqn`Nsay5h9H0;I`j?fIP=N6fQk&56o`Jdh-yBs2^pycG2$etdN48#6C=Q&SnQ?&V zbV+az+xQ07x_n0&+#+a%vTlXW2Wm4jGjOK4fU5xd0-)XHGASwDRXi;JB}!QgBqyLy zzn3p#V`8%HKcl{5K24cAW!u9dGQ=L=R>fEGQdb`ap*2l0`>^Rx!3S7zmbcgRBP2;_ zz3d~X^1$~6nJ9+n9*m3IV&I|sPLRegK~~nTzCIvs6MWx5^C}QC6BYLUd{V8fcyXN< zadDAbz5tDs*fjc}&n}7u94rSrE5K@sq<$3-CRdS{{{V!adZZAH7%XZZcZ*MX3+Cq2 z7hDnhX^F<&|3!09?Oe4^mJj<5|D*h@yNiqEcqL|7++3oAKFQ;~{u|(Sh|v(2wV+uO z`fSZn-H=UisbP8Eff$pA%mR;WlwSQ-I|00QK197xf)10O@j20+lJ&5I&W<2usYSoE zUy5X2Pl2&_kWxJgL=!epap?OVlIFD0`@U+_AKo$6CBHlT4fcCyGqihk3$2ZP!0t(u z`NCTju*tB&=%;@CxPncg0k2=TwX}eP{#&4dJ-4!z z7gttf^55Bm?+cKBi1VL4lN`nR;8%DoozT5|b~7g_IERN&;`qJ+<^bF^Smpo<4yXXY zN*Fe1<`sn2s;*`E*oO=)eX&PiJ}LeI)t03<(%6;K`ez5%KhAQK3{(2ZsdH53q1 z&9Um(ORq0}J`QVuG|9K>oK+0#^eX#w-;$K7+(jV%>9LoFQe zrv2Qyw@;%!W>;5hjWWQ+98SgNY=R5!j=t@qM}NJXhWI@XyuV^V!zW?Yn|BvM_Frha zGd@Y`h-h9-Vpf=fHXxJ{*LHI+uyzeYHWKCnX}&c)R)4`%vF(DxS?7u(v~ z;kr+Pf*Xkin2{Ii;}Q$B0*AjKeT=-%7GeY|&WlC!SW$FpA3z!Et9W9!!(JYoMjaVH zd-lojUKK}7Zw4K8Ft~-&SAw`+#NNLSdtb>kr)|!z0(^uJa~VnFu3eA0cHJzC2IZnH zk=tRj9sQ!8a1Q}-=>NmPsn4Lp zNm!lFnrK6`(E$AGygdI%il&duhV2Q(2A0`-cqd#^AuR(hUQlq6#tGaTAVxlU@&tZi zgKZQL79&T70^4WpDzs#F;kNL(M;;HZQ54rqPlCwW;eD@R>vl{60@F5s_sJLzs;BYQ zo%dF%`beB>Z>&-N3b@{dA5VexL6}VGo4k?bvmj#l>j+_x^LrX40wS8T&R1+e;vv30FtJ zA#QhjX3(bY!w}ObCILGfF04$h9n?)BxchqbUv6@y@-KQSb_swqbuJq(h!k~nCKkq_g&3Il+$&$49+1~ugobPfKP*9pqW<~}W8DpD&KtCgpDmr(sw)wm5txo}_ zU0GfRXG{VDF#cZ5uV25QJ%3~0gN!U1t2O{BL{tnc0BdxWej{E^Cji@Wt<@}r)QB6m ztGYcQ<^akDvI$C7aGIA!xdICYED!YA0{aRLOCLdx@EeqxN|)~?qZVb1#AbCMk$_MD zLeAYn$Zz0wnUf?*qF4!{`Md7;Ah)oPEwq_}{h2UG8_daXEmf+a^uo%2m;~6o4us@| zoBkImHMF&5RFZ%^H#IfI#lw4R{pSDnz!OO?sQibQ0PBaR^<}>n1n)CYv;sO5DAEhm zz=X&1SdBmet*Nd?(iA~gTPD->pr*2dqz$prphyig&_rlLFERX|EotF&GVgD)q{k|- zfnzLdhDg2RzjdN(`~++Qz`;gIDIBC5m?2OaYN4t#UcfjpMEnxN4G0DOAt2x~?pc@{ zc*lg|o{~Iq1x7k&op~nx552_8&+oqe{WI820IZD`89a^~&atC}a3djhTTeYEEn8CU zDIJNKjn|Rt0ATT~NAgN319kC_ozrvRJp}6zOvk>5AgIRBC!x&f340S@Lov`B#;Q+RVfze%vVZ@E zESU6^8L>9560gyqyqUM95ApZ0uLX$Q{tf9Kdf}mKIiOL0_RsY2iFKt`kslmIZOs=b zGcGhBF)=X-6N-$H5fhcJpGGQwt!bXm%a^tuUmlA`Fuj+Lr>Ksnl8%p*j^sisk$$fG z%@>Ve)rU!!X~Bwb{{H@8Z1nJfB9j;m_M}>%r@`2D1r0exBhcwjKRkET%kDX{ zt5>-!KwuKV1&xqx8=Or~y{y4NPkjeW@3(KG1v8S8CP7_Bz(EPH49;I9xTxIV8xI~Z z2>H4R*nAg8OF(-bE;aaT@{I(c5*8#fo%tb|FHf;1>ZX{p(9zEqjerNe^va(Me2rM!MwQC;-sW}6JNg} zG#k;E)=Hfgxh>jLx@3vKgZqoIX;v=g9;7|_A13gUn_kN`XrO=&gcA@yfcI_}Z(_#b7jtkS-rn(n78=aTQY$q{hCSz=2NysJh_^n6S%eupS-{ zn|Nk9#_$@dPNFQYt&6F zPocNJ%;5!ZxB&w1fcP1V z8lAE}g?#ac&RaTet;hS+ZLKEHjg6z;z9qhX{Q)rYV8MhJHYp}C^;cfaj}i2QJdv0OvFsZ+5>Yi&@wVI0s(?8`N22twxQ$X>A#YA>IyN1bq6|rU=qu%<{xL0;GTwF>jCn3RV@MpyL&zmHWW0_U+ z>rX7187nu3d&H=Oh+1Z%+S+U^;u`l|uLU&Z6xQ)s?)7*l>8u{O?gY9hE?K`j-AMav z@EgYCAnn;RL|9oRGJb0|IFMmCHR^=8w^anx8XbC0Cb72A#2v4!R*zups~7~-MIN1s z?yNP`ZwIanBY)U;bdh1`Vj)F+o(Me+;#+&3Ov!g?p;1Ul2!&S(%hmfT;1c|Pv5p@yR?HF8y$f(%MV0G zYX$7Z2U{hP-a9#wBqyi$VRo{DLy;;6bt?~#IO@xicX*_iw}@cBI-r68bSEQ4YDLw| z)Kk5;O7_-{HX_3OFX>$)ePE~j>+0_2pfwS?qq6ejq*s|n4IZ^2#!Y3>x{h;cv4Ldm z^tG4rnTegybR~@H071a}Sga4}>e?DsI==e4V0BOEgp)ooFNaU)2W73*%#_~D6m?4Z zePz|b6t*krt=VY>t(N9_3@plndJ@imKj9$Ok=b%ROf<20wYkqCLwo4%bP*#wJRhn6 z`}&4AbzxhBLGg9s6AEzC$y0y65C41K;#tI>*F1m|S@+DJdGdkuI!lSISzKQz!0i8W zhAFG4j{joF6CG`@o_BLpzO3c9o0!GCJoB&iByzl_Ek!^6t&|^!h1B z1}Qg6%<>ilm%GHq+n?;}#mN{@;?Lr)5xYk5F{ni+dMzqPt1=0&29o4STeGr78xT+m ze;xRF)P9MNMQEy|-^%)_=b1n2Q>WC^a|_;AXKr@6#qb;YU*L7aHVa~(3Hr0@beoqf|bGt$GHB(d0 z)rqsU3#(RJ!We^&_n1p$kJsd{a<{FO%JN$bEJK~p{xQG_!#ZYWTW^vbzD9jS_>Z9^ zE0Sa@dx&IE#V1%wl)Eehl7*#eud=GO7?rHCs$F}4jHKUTz1Xd#5FEiEq5Bxe|Lc#1 zrCYRO$_htyLbUPeUG{^;8?lu^h^b`ljSl97?U5)zV-nN>z)WIV64-+f*8^}p}uIUYwxsZQtk zj?d@)eyz7$fE2a(@tpQS{}#=HQvHS90d8K-bZc*{v4cV3V(xS5? z=osHusuvR<5@TgB&H4^%|VhdiHh%U{SCj4LwozqAp9gryUL zOfTK1O_)@h8XH$2>GW8E&c7}!IpxU{Ni%&E+~oL)tHQ@m#^0q1 z4hwT992mh3TAP_MF*4ry?t|@htc`DCf{BmM97WG;1v0p+i?11s|wW|+`0`=HyPSZhkDKIlwB&y*S78BlMnjC0i{7mTk>7>2=Q&+#e*Kwla45kX{jDWDzzo zXV;of;?9<4&5J&b6O)r~p`d(WYtSja@nn;@qK2}KmEx<^w{M4%dX<%f_l=Bx8xJef zyDVV!s4KoJ&@R&4w_J`qqT|!GGLv|(c}D4@BcC06TumdjwQJ~O18VQz57W>vzxBa4 zG}xM#dmvp%|ITXviFaTbAs^U9A(?Ibr(KMK^xvLF z>o)T-y$Mbu1vRyUG(qI4_T{}L77c^2ZR4tnd$4MMvA|*A5iW%9!zJ#nuEwvgp2%5( z%>o)jR#w)L?~BID$|3$s0nSYO_ZycV(AU*{Ya72&&~I0phcozx24L1cTH6z z4oS<`1mM?fygRvkq9DGpvHEcE6zKx_&|qcdIkzC*?)eK|q2}d3!T#;Xa!++MD&0^z zuB;+Uu6>A$Habd}_xNG|D0&vbwTd;x)P02M&(HY52;)VDpRX(VUx76Nya*AdG)&RD zxDG(^(}>?XyQ96cbJR%;z~*VUsiUz?2>gnsQWGuUYqk34yK7zkYJGh@TvN@-Qsq@5 zfZ|*{>=cW7y8tKPbOj+~t05Z@r4Y_dpwm6<7c zyxHrAL2hv|tfrTD?p#uG(|N9|3ytCJz?f1Wt0HUd9o9O|Zg2LKW{ysMy^(v^(b0|B zXS+(WT|vcdg0r#r)Ccv+4WoU4zIqCFs2`2On?aKWEa3&jWZc{hU0u2|GUhk~sHCzw z^Ov2RovSMO3<(1RNC;1-mzDLsdw2aTJ0ml5OT6e_4$v7SNlvz5Iqr!s#S3rY(?Mk7 zP~6(;o6Kj=_AsBM)hN*fosV0z^1-b5CPtI!1OpE0zeje+S-RX zJp5bLZKmwLmM;nhODKPQ-qgelpuRY6KOv{9Yn1dbG<5VC&+Y7fD?NifJqND4?~Mo= zT)FQc!|5DT>J@P5^S9=H&Ft(SYi|_D4P9gy<=M^@A3C{*LyLy_xv1Cc;c!$!2cKrP zvarm&WjZ8Whs*0r>D$oRAqj`T0Ez7SSjMWC-~qtQY~`3oFR_#M$epGjT~1$Jg06Kf zdzXtrt7q=otF)pbF?ROrHD+rt#=#v=TFUIZ(6vJeiS1cBt&M?K@1lhTtXU4HNWxM` zzoqN@`uy<0q2NF}N}dWr!OPQAOkA8on4@G78X&X{7J{D0(?k_J_-rJ{zu6L<7L*d3 zs$M?zAs!qeGa%0lknAW2x+9RLKcMPL`Yb8Sex&k z9bbIs$J*f6LP;LDah_OgSCg{s&XogR6I%iKNJqy4c;~*pKD@;0B?U?2(|BXRZ|J+z ziXSsO6n(|_h<|+j>Q&q4&mO?Uii?Y(sRaFL>bnpf5FiJiAb_Vp%Kf>ptVP7lvhUc< z1QX>xHa4|%%~)Q2LGbW6uPhdHK4~0cV0ZOzWIu$5;;rYLE^k48t!H)>i0?p#s*s8=^As(Iy zEK-pE0=L|M$|0q2sD|(;g~bCr1!J7ei$F`jX=^J zJ9ey3$5o!W+h7sw-A?AzrLKNm&Hh`uz~m&ou&@FT&zs4v8o!}G-CgT7F#1IVPczVzVF)4IA+cmNcE`gDq|od{|K{ltT^buxt!ykUga0I zYQ-@2APMCkkIq=!A^d00yhweiuCErRkjm~a%*#8oll~EH&HUkkK-tl2ynH%BCo0Id z`iDQs&wm_VE&N#2>(lM}N%GYD-(RiJa@}cC)Y15dT*EM)o%`6(FP~}-*y!JsrTjSj z)vBhnn)XO2^i8`saK)LsFLDn$4JM0xbeG%I+h2S(V_PCuj}6iSf`gZ|Zh%kR>0~GSG(YU z1fsgswFptFc;W}C>bj=f?kNC+6rsI6r;S&u7c%+Aw_`UA4G#YJ=@XQQ7Iwk~y$E}t82Qa(%Srl$6n zE@^0LIzcDK8F3ZgtE*G7vfjN?NNlV_-LoBe7pU}rYnHT-cX{HzhmOq3+8Ss$LIr;z zU902>cp&65{7X&#hGK|K8f$sm!2W#*hrg_@$=_n zInKSdC=sTxRdBiAbA&N4tWu6Ecv^Y$?x6P#mjq|Ms3hYJr4`JWJ~}&cE2|7c>{VUU zMi?6%x-C2SR`}p@#}wtj`C~s1Ckb4VhD{UJ39z^`&&RR-&l_u;T`cGuNo1)1@zZeI zwgB9Ddu|IMS_MSMU=Y?U#EJmJz_kpzTVbO0*B;!B3Wx5erI|ZUwpiQh{28p-g_L2S zZ4?25Fb$a$7vAK&weJdnTVwMqOs&m`o@ur$c>P)!>&wxQ9rCH_fI`5d2ASSw&^>-$ zO)ae1QddjM;>wkto*pD#KwgEQ2b?LDZ%>l=Y_YrFI-fB#RD`qdWuIP3#cI-<7T~%(gj163BDRM z{|wtvkgIp8K6Z2@3R}^{sW4pCRaCTs7=gsJO4Wl^2(^#R|6sxjS_lv?!Z-23$+t@T z`IXg+!<>7boeG}2q49C7%PJ6HK6!FvJ)D8xeXk|`mMwcC^?Sc`c>V_(v8pS+Em-*- zUQhC)jgQ0u4Gqx` z$x>lzDg1nVwHs+*G^1l;;Codfe~L2#Ma8%Hyz2;_Nl0Kp!U=B3zE!q}Sq1U~9CXQ* z2>Gc9^JwEwpi5>~qLc?v)3;+n(&yXj*LRfZx?=sAY2q%(LEKxlV^ycwpe*l@ijmC3 z=qSa+gct$$343J6j@{M=4{69IrN=ybHrJJ7`0Ux^EZOU4Gw2s-4E6Ojo_<2YI9$rB zm2tx#*h9~t0bxLyX_w0KD?O7jk)p>Z7plqqC+Cp5( z%FO(hta5WhrQ2jF#b1_lcKuJjzOAb2UAIZ=EYyDnB`%KJAzc8X{gjkZ>VbY%yHX)VK%)OELUJ<+q|Kl;m)W8qSh=ZwIu1=<1vso%hwkp#J8=p2WjP+97u zM>mNB$GEvWI+coF?aY9(k*f0CgvZFz$!XOY^e3pJVBZ!O7iZaXZaDtn`ZJW^L-}|2 z3k4Ff_2Jd5H)CVFP_o1KuEZIEx3=|2oie$6Aw5nO>s`i9BR=+^eL zYG>Qph^GqY2XggPFJD&j@}kgscH{YEQ{vkMLag5I8n5?4qsQ-S{;fSiS6mVk=DAcK z8R(Zvcz3>8C@(+jxN~<$ZC&}|pAW(gQuzz#o_(M#iwu+HwCx@&@Lp`;I=>3){CbOT zG7MSn{kCUZCy@MN?&DM9ze^O=*~62zu(yp9sB~t=9Z>t^zIn5UlF|rSUZ_;jhG3gn z=BnFO2r*(0l?AiCh zkCx}hTQnig2-C?v;Qd>rPG=we*lQY-LA;%5B>qTGX1CmzxFsN-xIWQg!w{masre`; zCya|?zQxY2G&1od@^LOJ=jj@9=te6StYQV7w%rF-}z-~`x3b~Vn|5hN>CXnAZK^3 zEzu=H_RAL=Vy|4!y9;?*xWqRRn>IlM9qw}-$8{-3hD2cFfDbz#o1?<$0~DXNcbSox zBp=EMou18&$w|%aRYFko;2aLVDIHgFi)1o#$W8q4YN%GsB#TTc@_%#PHk5AXql~mStr4`-98zYp4Ns(| z?vr496BsCe@!LAqc^D7e+><$%MfG-8IG_4x!Vsyv7ig1`B%L`f^fQ)ck13x z!Qzu#w9GKW4_20wg-C$aj7sS$HkEpy(gW6=YjF#ezU%+?(1`?G}7I9GI9Jh z67W&;gS;8LxGbq8*+mM=A&`T#1d|LVMIZ_~?%XiPMMgJ>5cy&yFmYnltQ_?Q!R=Ua$K1vy<-SkK=!- z1qdy>{Rr>aOHTy`?pyDnBZNh24pS^1c0`r-baLvn5FbPTo0;D+4yQOn4*I{3 zJuYPWM#brTsH}m(-1K}~(Npdc+lXH8>lxoa-oGbxQ04EXhqmeN;_d?j2{grzV2|_$I74tg_0`l!j3ruT5s2rae->QB1+o1WqA2d2Ljb+aaYth3 z=1Z(L`!;goqoWTuA0;5mgmep5m&eME=VFD$`*#CH85Gt+yQ zcA|V5z8hb2nPG=%$S5oih$f=R4T9C}G}GPR5T>Ay|JC9_o#_lq1}Xn7GZXzfR?xVD z>aa78x6dA;(S1P}Jxxz{Mg9Hts}Tlh+|a#XBru3odOtHW{<@dtl=`<_GFQBq$ZR(KUG}qnxTUXY$maLw7JY9~8u8Q1sX|IaL(G!C)=yvf(R!TieXpVjT!)it zd*Yj_#FKE(^}H?MqvMm%vrT*qEQDg$ulLQ{n|PB>5JQjU(Vx5V?p z@ME7=dt>Z{5G3K^ONP6|hkkvYFn}wBRHoi;aus966`vE%nK(+et1ON(A^H1*)gxtZi)2A96ZX zTxgYoxC4G!=wUFYW?kQn%tZ=eAB=i5f-nx0y6P+zdh#*4TCS-(wf2v$aK}RVP3%NGSDRp>@m28$<$-Ys7F&FSgIf zq%V;J^yXB(Llp6g7**|iV}IN3g}wdLd-=U|7Ejb?g{_t`2{K(y|h6x`hZ{X?4&g-Q4pj2M< z{Kjum^6mnhd9zh}rQoI=>V3O$W*4SGAdX<$RsxYeTUU`%gQ-513*gd^u(N-=&E^O7 z3D5I|+9xM>A*U08#DLS`@gg85g@c+r6+udf#vItchgF84C{fzSP!QQU&frWt%+U`F~Sj4ly&-||S zbg!t4m2eB}`Fw=+)Qrc%!=_vB=N_d|OIvZBueR;G@3_8d`o`_jD1*^&BI%HU!{X2W zTR(7@R2Jlji3jK#l-jQm*F8!`n?4;phx-Q%f56@Y7F)NSG0A62Zi%F)acXYk{#tty zC0oNeoL(oc=Nju!rBO-4;y85X;M3e(CwvEz9L2FmBM=K9C8$0RSDxkuefiAV-b1kInyk`2f}TW%{DRx4X?`j8~Hxn{Il5m!bFh}cLm6{O2V% zCypH1Emw0_PLAT?L%ixjfSmP_BTFx`zJ-tP{i#M#y5v-M{$A!Wc==kLUHW?qpo52r zwyo_|VIiYhP)GqK$|~s6gDExK&=GbscMTq$L}%T==Tn$a+>Lp zeyq-``{EiOe@;WAH80OI73DmWw=r2S?1&<#%lgtchqaG%BqV0b@IMoiaA!Yyt08hW z-gKchRKrrh;Mw!p`Qnn2SJ3>&$6=fIjMH~@-K*oVx9330nQM&FnNuCdD66*Hug6&1 z+D;d2Ye};{{WA6C%hW3;?#|i%7f4PeeK$M#G7gK7E79K)0+xfE zgC?jNePk3q9{kMFu{YNGO|>8`-`(HZ*@@)Ph}?_X+R=aw%CghaE@*29*dtgBx_SmW zx(D&`=8Q3kiSTV%K}?sv1IvPM=ezywY}p2P3%}m#Xd}n>TZBY?3BF7!kFw zz^ttFAV1&eqRiR#U_11$_RMHPSJ3Ssfd}@VRL*^QiUTS6(v$HVgw$r2F?phk^#bbw{#6|dCemazZ0|HNhx5NxI(6lM}7W@Pzp-zqfn z$!xy#>Xgcn8%l@68$y0ba&~WMDrxG}if(;UbF6K=GOnY!`B-FR5z8LQ-0uQ$OT>0e zl(7T1E6Tudl8!#^ecam5)otqU|;gOerR$J@A8C&zLp;4Or1eO#j zU(L=g-T%^qXr8S87j7w7zt|(!7P55n(px{)%9oGdZCOg@Sy^#qll&IDVbfn;M;krn zf(FRk@l|Iy$L?+0j*EzJmJ49_*h_F#X{;l9We=Mq3)ea|DW?{p0u+>#By%PLO=`o~ z_``3=a$@3=8SqRPh4WB;1OJcO22E#HMh1FqQfLdv#-ifl!Es!_l-sKSPmmyU_=B?z z3BTjq+`tMZKhokemNLto%it6XXfhLFpr57LL8v6_FtOPSU#8&Sc94d)U^-Jp)p!3( z)X!(E!_3CtlvZJ0BhMxkL(gL5u*l~p zb>epOheC%N+NQ}qV*>oK!Ks(*GVZ@C!8Y$R^rY+0@4d@@HWh1sJPwG^ zMy`~c_7CU0TO0oZl9tk4I)=L4FE$4Rc#c}$y37C>i_6{jSr*%?GL7XP`w&g~31tRbB=cJ*|L=mV*#598PLc1s zy3DIp+!u-m-WcGoiJM5e6|x`U`&ezqB;|rb@t5J92kLg?CSwc<~GD6le?;9sf2J;fQ@h{+q6b^lK-K`zf zaq-SxvM~XgpsYt(@)^v~;obkZ+4FXL)0m-{MY+e(>7#hZE~5H(=WMK>7m{09jW=Za zVQ|Oe?DV>kTpOF`!>kG-_Jad?Yb*Tgw@1aABuFZzUL~Eh)h`c~&|vJ>-a|H4prxHD zlJCH8B~cAg*ovqWL3<$f%AQS!w_K~<8XLA}lMsPUdG#i0MRw+Wh1^Fmlz2p zY3ZAYIY+|N@IK5JB8&UCWYrf*56Bula@bXMz1rWu8bB~u;d*{PJVKD`R|d%-6CSdQ z`FpJI`-izd&kYja)s7rF^0Pb7(9aS7H@4@mzNW09Q+PjH`n7F=)oMRhLe%(ow!-FXEAXIZrxhPtPITndq|4N_Y>4b$LISMu~*ea-h zV|u^d-k2N@_utnC|;o~vEjnY!g}&K~9@ zuc-(Aonp;IbBn02b3?T-ir4XXn;dWBaegbpda-;0<{3whO?JK>k5y5@ou?5Q9ngQ7 zBu=8(Su{FEXJ;PoIB;{g3LhqE=U<_qDU@5sk8_`Nad-ck=WV4p`{*l+e>lKHI;QMz z12X>VYjt7yMMZ!?$Wu{Wd|4DTfqai4*;~qZ8atqWYZ|}KewBw~3#0j|J;Qqa_Luir zOTTlY%@Nz>-1pYF^pUs&d(zR|h1Zc>%1Ki0(0NchE7!GYe7*EPr5^pQLn5YvwRhta zAG~9f_&xiO-*a5Qf}@@7YcieJ?8v>ugcycyR(PiB$w^W#B88|DNV966v+t88_bql; zbFU^}J$b=D{8m2$XMh>1EOY^?Iy&YW8kY?oE_}(iaCL2pKGsA-bBO=w)6uDGtX>+; z-9=mec{MQ^y`M7ltonX|iz#5w6^tgQ`EPfly0Wy-u{5h>lqxgM{xmwQb;a_F-HgWL zwriqGT30NHPa~s)Z~t3{fC{UohV>L`_VAoO;YN^mAe(PXn8KpweN~v07k+4%l!b9u z-e&s6dRj9rIoX)p_74x%$3L)Ek1%|FYd47f?fsdAQ@s2#P_d$HU43yt_{`*~ z(E7W}sXMW^Q=g>l-!yjHMLzT_d31H%zB7K;uw^FIk2uWei<5Kg2_b4O3GNII-YV$n z*jESXYsjx(F?95eKQZ4nDJiKXNi&-_dHLF%4ZCBcBbxZ|eAS2<Tvn*LZ?yGc*< zwx8$`iZx?u}L7TK;6ciN73`VlCw7wY8)0 z2auFc6_$si`@9{P+)CA`o5RjRF1{zHFY?U5?N% z{3F8V5#_>XTE?3?hcklM=&|u}_)@2&Ck<%HQvr)Zunw}yfY8@ZJngADr})l%7diO@ zXsqwuL+(nD3;#Yi@HAXpca@7ykL zm#O+{bVEtR!POU<0&_=4V>`Q_9nbGGKl^V{?v9%(Ql7a-B;uRQqRaiVGt&cdOu2Ji zhxCcO`@|Wo#E3R60|STYq~R*M6ic&5m+X?fRy~w2U!Is9Vj3MS;TOZI+OzOh!_UHE z7_T)$Xu8j9CR9ezn>!DX4tcyuQjAN58#UVxw)WHq9ELWbOlpf z-2GeO|2;toRX}AP&dr6R1l``fdw0q?z$=x~J`TE16o6~)Wo1px`#2DKdW_OnF<=E> zplT~GCE(feS|l-ln52$_tq}Pce$YvR?*OT=1oX@!37J~!2+{+oOB!iXRJ4p3jG@6n zSsg$0-LO7kY#Nfp+yJmaB~wCd!F02mZ{GaIQ=rU78xE~k&*?`{jZw0S&qJdEBW9u> z24=EayfX9Dm#$3z$8xs+&o7$sH2F)ik2EG9`}jx}ulvuQND@*W9ID%vC<7S>ljjmu z;3aGja%ztJ#G1U@KTZ$hr;i)aS(`odo0!i`J2xX6k=(KzIWl zq6E+T@F5eJX6Q1J>c;7hSMMt)umU^yt0TYzBX==1(LY)7Mq`F*yv~ISzsA}Qp00!< zi4uY-%y1s8jP#L3c$-*Hy4u&RYmrg*{1E?B25P*uwrlwlZ~WFehWGH{jt?K+*f_t@ z#W|FjZYjMVIn(B&O?a(jj|(waRJ}&9Z+Q0mEV})^nF~4Jy}s?6PE1IE^a8)nj334O zmm!B?g5S6}D%KfWgMHuXv~+~O@v51*Ikof_!!B2O@(2UA_`#8p1h}9tN zY~5Yvy5yT-bFvvXr{Ukb4K#fARfWBsW2M<=2JO zXP?=@0H{Hdli8AJ7#X2SG}P0pzbF8kFDc=xDIq-kT~Ex*kmR&9=$%+km~HhN`2%ks z@hp4SE9hO$C!9JVE)LAZ2h9~aLR*H2(5eBKF_u*|5052i7od~5PUW?+Uh3ZnB$0)g zxyjbpn2((uymrDy?CwphlAL7MeVRi6H_HzW+?#(!j-DfSwNHmH9DJ2bVft!zkBcr#%O&e=9ZvHmEV6 zIYJ?U(0+YPM=gw}k!AzUVM^Q8q2&Yb_=@h3Y?E2fpa1&(oA2kC zOxLYjx5izH!@^pi?}HIc=}2Uaw&dl;DBQ3a>FL=9`NRZ@9DG=;tk3)(O1Q!9%-t~7 ziMi5bzG`76=I6*H50)E;OrSqYS}&hdEzb+8k7KsX+oj71^16f1Lu0uR34dYYK^U zCp9i!e1E&g>0fu7o~-TNa%Ejix$g%4eJu5&FOvDkeD6t0jEcH7@iLPhM@F@qWcD~a z9l2dIVes_3xY*Myn-2X0BJ94YI_cS{{(ZKp0Ve_f*$hPdK%$B93TOTPA+qdF=~njm3bH#-#LEIwP)} zgYWp0Mq9+ck2&h``#7FZ5XX&fdGYS9TweG(=3LC!He#%Qm ze7gVfhC;W?r3H0pX_^w8(`WI>d87aSVOy5`fnVi1eyp#~_r1b@;q;LJhB03TFFMcVF>L+# z@xHM_ZhOzrzgMkq5aqe$0|$r$OSOQfZc!o4Gz*JP;d}7iU^TE5=Z0k14D?^Z;*O9E zFY{H*2RQn73(@)aEo`$m)wp-(&I-|)O^!f;B-t6>jX0g4#31O18P=3t1I@0U`MnHM zuyO@8ndNKgMq-MjhzJD}6QiPz{ZisJn0=-m6MZyXh-n)Z;!l>xSkKpxeM1h5h@xMb zz|p9rS@vAPgf)=zxYyyMONx3+C5@sq3HK_f&$zg5j`fHI8yvms1hTZcfnhPk! zNJGSI0kIvEYU_6O6-jJKVm@conro@s9OKtRWj|DNlo_2u)U0KabC* z|Nb0kvF{2Pr_#wjg||O^&|1ZYe?Qva-!H|(V+~cx=J6#hHgpjrTFY zgc}SQbw~&^1{Y!A3Et^pQFo}I{adk(LgMPTwxH|Zip3WmcmAqp+RqUy=r&wW?cWL$ zus*@d5T`P#Dvy+GsAp(Mrv&IQ$*w&lvw=nVTM!BW@>H;?=VWEE%lyH9a;T!E0w9L^ z|A%2~_ik4;$DQXMOE^8IxYc%pLTH05U zm^ePQwqECguAP?S^|kI?(oi=XV4&8fKC^Q1zzMIPf;NjYI@rG6Me9ogj&Fuxb^fNL zBx7yEf<#a+V>ELF%Lt+rRqlO#N%7~?`;6E&1cBQn6g&am5 zi4y#`_dG9l&M0-{^uND{ ziRkMWJW(yTF!Qom34NHwyBhcf_A-*;iTE$a(@eq$jOL6|94;xd9ha&ugt12YJiuua zT8PRpn`K5ovGf@BY4TL~d6bK6klwVEKNJ4<3muKhL*a%GAI>`&A|R{e+0U=ArhUY{ z(%wFFxzK(3z_ck8(CfzJ91I~HH^c*=MVjg_mpXWGsFgrWJeyRR%Q%hn7#v}?i9Dp> z9J%A!>4$3qfBwAZ?FJK%NmM+Z?|^uebQ=9>pF32~HkMwU6zzJ~LC^6?T~F_iWZf~b zhC%1)j7eXr*bpkO`5K;EinOJDB`00tStkEvPf5d#`haTn`=hT375&$g$UooXdXjir ze{QviFsv2{dtfMj@7~}&at>LxGhSATC(J9iu8XtfEZ;w2_jRgzYwhP(VO>5Az`pjx zt$$m8+#15~J_RK(yFbn5H}M|L`VW5f{g|NdKsb{-4jH?|8OJCo=qY@XNKsQDQPaPU zeE)v?(r3UpQ0PN>&cJ6>2yNa;-2&q4Yb#pmF=S6*7W>@7LXCYvO-}w0sTRdtywg2c zs63_Ezx9g()^|n+@Vk>+#n^Zaab&pt5oY>T7Y2%6_kP6(Z75!hcUFPyYAW}Hcz(R^ zOfU68Is>+~fo}x?3aNT8jJl0XOd_K@AbE<5u`G2vu5a8+AZsbtf9s3PW2~pGKYZv+ z-3LZdg6dOBO$azA>cIWQMIFTI9iq;I5PR?Ut3cS>TZERjc4_vOk&3!S{6!@ipYl5iRU?p9PPCP1~^u7 zLx@1CuQS-@ZHqsdy$XkdFqiZ4mM#8N@|=HRpD1BEI*0Bl+*bQ`-(6?A7Di>(*`=Hr z12dC_m7wRp7W_*MgTSChI<#t(^N${dE+2K7iFd;p!)&WtdUrganBjU&|^4av=wl{Yuk z;H>jGj0$MXQv9GitTTf{AqS)mlsWGpL2$TJ1SmO(>i;Aj_n$UYe^*+g9D1eM<;&aK z`nvn2~yZvp^Z1?>9C)N{#zIva9l6ONgcK*+zYl$)5e`J!! zaVZQ7MQ}MZp$LWn>&7fyUsD6emhK!0z5hrOWM2vT#*H7#aU<5U%q$*TUkl}O(&t@e z&<5YPmZ8?Tvcj`{JIj$gaiI&1fuBW0c3W9Zb?%^3%1KR0fg<{?YCL&xFqp!bkJ{TQ zGrbniKRihDgoJh)G}>89lfQySu(2unFkkLd*^Hi*C6yA!`>aenMV5TAm;$2Jz7F7; zEN&tQViWABFkfjNje=qwJ2yJ0Rp0_wo%i9bTB-jR_;s+KqI}B}wPSa~L>~$U#NMP1 z3hHs!dh(=<^7D)L7q32uKc@Z^&^EvOPh9gakRxqt6L9^@(o%tPEabigqSCp~cw(FF zj`9M0ttrQc9~;2GD4`l}ehUI@J*^-gu66Kw*bYG?dP(sKnDmK=9GJNGT_ zzckLJ))_0oy@`W>=Dg%~rBc!JCdH)hBw8)1CU^vElc!a9+#XG|{Q(J-HJl3hVm;4c)lXgg4vt{yY8lU&H3&_2<>pzm>9FOw z{pf-*r=o&rL@#hQC+dGovj#&+p$ENF=31`@qPTk<3~l=(A3PZQQ7TQUn6IZKoiZoc z_(hxkg%7v|H~%v`>o~V(6B#p^?BC{vOjw97W<%{+zK9CKRj!-twwxl1W!Vh|&{02Y zbk1o{B-YRmmtPIDp8qpH@eRER*S}4V>VGJ#6cy+8WcYfy&R_$jeb-xnKSa5&T66kq z?VvnM4r*vH7tcrKi-ksCHT(Nl$77dg244$z?(h;<*Xp2_9x4^xom^ye@-M*tw;v_O z{)315M@GizBuU<=X|%qwz?xIkOVt6tJfnG?-V1}nV)0QGAvJREN1w3SEk^O9PitMf z__sw?{a*!b&=jI%2nEln)o8$Rc3#g-`VOVDOZ^VGm zpMPv^&+m~l?5Xq)$PNo5DMCrwsYT+iV7{Y6yY@S{V=vL}dvj1rPHHP3Dco#9$tChS`hf)>`?-~#^9sPC{**3)sU zt1i@);%ko8mH1za^l48o)O5uCYx8}GF)I3b%?$iHBoz~($$Q$Ii{s+kpe~kp*GZ8nbkf8ef>C$zrws@a!kC?^1Qu;Y%Htx(WA!K z!jt(n4HFYF$8=*3AG(9tP)6rTx*Z9#|JLms{$%wZTPVXm`sZbYNn8aMPOuW&y}Xnp zw~Ac3GT9n}9XnA19Z&0G!-T;)?3sl|FK`}1CwBBUG&b^xi7lfY5OCdb-6T6T<@#T{ z`v}R6DEGl@UmPvR-3%UWV%ez1kWtAtiMMfc!Q9-8{$ z8c(*_2(KMZ_YH^#H42Azip@4HAJXn1E)*I$+Wx<7BJ(65VO3d2$nEpBH`}v>D^J_g z?j0C^v}GG97~*;M^J}^{Dl1Pr=T+ER63QwnEGMt&ac$mmW>3kJ6!(VrN|pklU$kW# z7YiVya29%d-v7wI_y8d0-_FN(FTCwN?tZ#4mLync4Fs&rE#>v)PwHPtg=}7ok@J}5 z<}*LGocS@7v(7Pn6$|4Yi}fiI?3NPGee~(7gM{+mW%D!%D1+h@Un@+@VRKg5kPx%5 zumI`jy`(?V_o1d{#M*Ed3ADmjtKuBn$U%RA?98C0XlO`dz~O9MM?Tuzz09W8tFL|k ztov3rVI~*yvN*f$;J8U69j64CG8wzt88eeIh>Xx3O4mbk4gVo_W4!`9BC+Jw{hi8U zXCAD9Oo62V-8z-R-d(#6Mm8WjE=F1F@2j7c_o~kK@gpG+xEp3{Z|oXaC6N4zOyJ8f zi=nH7+#YxUt-hg)%RJ`DApZ&II#gye)o^eo%BNj^Bl+}%q$I}VAfZV4=*Qq#80nZ9sdG^rN z=+}rpbNzJ2AV2@YpX}$qGX_oaw`@K4ypSS>$C{PhK+fVhd2Z}AfuZ&FLfeO%ckVp2 zb#~2bZeH>Hho!IHPnSsoj`=-`irEi;pA$7XX@c z(0@}nH284n3O-Ni98G`pXb^n`$+XRP8dNew(WHqFtsJBy_4>*WK9C|U2;vO>^!2TK z4)pqHPsFj-Vh$=ozP8h2;xcMVO9ULkX-7DG%G5y3*$q;J!vf3|<@OQ%fHKm>7Q>LTHSc|PU&a|u8@<88pN&L^E#B%l5LThepx5C-#>>;$$jg#q#f z{eJP0kwU=v0g%H<=8G9P_#~l0K@sKjN^v4KIzXa9j1h-}*3o{VS3JPTcnYo2W$!K5 z_gJ|<_-qv_a4}Ay{RTF8iq|3gCBR^y+2o8EL(Dt`t`wt*K(jVr&ELrZ#n;cav^wyf zUh`ud#se5#n-aC1vy^o5)O;I+GcW;Qm;g!WbS0c-{S;V@DuD(PjF7q}yP3^|EB zh}1cD%o}C}@BpPt| zufI_@G<>e0prlW7JY$>>z?Hx0c?z?e&OUmbFqXGd6Dgv5(u^&Z!DEuN*7%edJEn}Q z0aRM8D-(pP7AJ*z?^ImGI2+;R#7kcb9dQvAxr}Q9-UAv3o;u$4<5`?zcwrD~j3IK! zn}u(x?OJ4v#_2*_-0dD%-*_^^-sW7AL(bCo?O;3K_kqjWonX^gFh{uzt9n`+%hhCY9D3RXq0+Jln)=bs*-SpO4~ zG+W=@kVc;>67^u|b5)h`1re+MWO~y=`L*ILK?o#_S87qvnn^p?y*K~2E8m8Q6SNHg zCgE9rkF17vAn>m{9oQ_5%&O$}U=#Yb5D=1WAbp4i#8GgKnQecWac&A1<{~P`%Waib z-LVOCG;i^iAI27i`69xs*h(7(7$3KJ)j+ArR7GkK*q_usesXV^&RQH>w>q_myR@_FwR z@sdl;Y$kJ|vR>U$nx#@CBA$BY*zw~XF1TWF%^Y>Tyv)m>%u6#eJe>KIxT*|GLrS$7 z!>%q;b{tvBfltvRllaQ(#$tuI=8=(+2OVqTG~`?H9V9<#N0pMBJG;8NTx7{oyn=$l zLyGx0^sP6&NPa+_s?hs9EMET($wT%t3?OBUQh1z`mWE$X zZk+vbG&nWA=AvMc(TgWCo5*$_BUBZZY@^Bvtzr9v=a$c{TaJ#T&q{Vsf3(>xv6bwq zb;(L6h(t4iqsNbf8GRBHv$B1&$R%qltI3JrIF(9^FWuc%;w)sXr&;-8smw0gtC6>z zIHB?E_+h%h8D1HW7_Q<|PoJqRH&-19yt{njV)yb$C=kP#4e-)f9&dl7pYF)hb(2Bky*&q z#ijZE16YxjULc?El_ecR6ozV_vC4h664h>Wl;LMFy^mj~*sgm4xxQ~ik^A_l8um6n zzpC^m_psf3&-j@Bv=fEgy%{ebz(8BzL~K5LJN5Ut7w#&2O&mZr@|WxPf`VQI?Oc=9 z-fwa2PgF-q$%}+$GxzIsrjmGGKIMXv7hhHSL-{o7PHev3MQwK-r!&}0ATE-IjavGM z)cME!lUe-SJM#zIr*$4@+!j0au_f~N&%0|^RlmG4&*(ivoELXbmld>s+Eb|ZrD<&J zk4Yat26OEWlBW)?xTs(P0VEDF^4YsTeR|)~@dL_Vmt{crw<$e)T3a*iO_O2sFLnTO z$ma9%DyCcM=zzQticpXxCm9>&ry2$rPl>zry|HCB7J?)D-SrzVwqcP_QdNz9|3Zww zZXU~bJ|;$I#~zk&dL$X22uW9!QbU-SfIyT`F^kIfNKUOhTnl}|^&sxf9;dMDZ9MJl zCR^R@D7sZJp{ha0VbK`nzPfg<)FXnGO2Be` zb=&<$EPK25N;&>8P;qj)9}`0`a16r^Ge_8X}NpyZoA5udaU9!x`VguG+K9`Nhu zT2uwzl}-&W+j0>skji^)1S=>)u49e4p+_Re;RgHm?JPM4JSbxJJGO(**M8Tr<@7*H zfU|<2;GGiqCzkg$B+sFvSE{adTF^BQpjt?-r=5{T`Fhx^_?CkB^&VS!8F^X|R zr)s$TuOh0Ys1U!uPO<&gGE`vF^Tzs{(zrB6$9NS9FoayTv58Mk-avXpsybw|)s~a` zhCNi$&z?OSYeW%b7j8k!HPj*ECQ-!3ZC)z!4oM&}H2eA5Ne%=JA<7l1k#>7e506$g zSQ`%e^1PTGbIM{O3Y{kNZEr}0>AKO8jeQOWe|_DWoab1&=>&H|#xtYp_jxwHU0eIV z95+775HhYWK0oD&BnHbo+}D;K;~1kdlMF5OKo(;YiWJup>BD#5lm=*3{Jz`{?Q0_>n3k z%Sm1O{`GLBsaRUm7uTR|L6W~EoodrH=%R52wtd8^l*5{y=X2zO@=>}>E=Dr44l0Um zDo5;UyBPFX?!74ToHZqOK7W4U9*gapvNsVCFM%Y!*3vURUT%5W$fz+iF51!YIAvCw z$WvYho92i4;Dp(HU)K^qzdJgjn?_K8di55E!HNXmH!@%NGO8d&dYqn)sbwfM4fOP= zq%oCfaCDTEYJ-gszXL*dg?IJ*Ug+G$df&EyUK@)=&Pv@7Y2+-@>X6XXY!?AbB0fC& zi3P&lcxW;-J_@p#?}knN@?>L#Lez&jU~iMA9Z&y$s?3}mB%HOm(AiVuv`B@-t@SNe z$Ru28v3`@AdTR?&A8vZuJ85E+?}}%0r#Y4Ut*3(EldJCxzz>O4%5!C&d*#y-!UGs7 z1D`)PM3D2)&~(H2OT>!x6PP&J_95}@J=Q--e3ZK+4$!+65f`{_XJ$5Cx>Ogv+o>t0 zd8kfptgXscKw`Hv$exN}9kaY^*0(u3FpYuuLbXAr3`5>Xd{*r z+MV~E<85={oya|9)_}k1&YQld4^8}kD0|DWtk!OA_*RyJq6i46q#&J2H;AC5fV6;! z(kZFrG*C*ULAtxUK}0&GOS)T9`WrWE?`N;)dG~RA@5|v27fbY>b6)eh#yH0rXuZlF zGPc4E)Vtx2A(^3;MtV;a6{B@FzIT07R|{n+v!g)0y?E*4`-6k>mKJs|g-@Rv!f`M; z*_t@$n>ZwU6jOgzf2;CR`{M1bV#ti@S}WYhR)^i)&WHq&pvf+%SfTG|qC0$%UCjsmgaQE#L4RnF z$A$L?0r&@x)pHI-;eQdH{V!!>vF{8e7W$r^DWm1VwA8k5pZ8CHE4+HOg5}vZ!uy7YlGPv=;{?cE=!>}^`;laN6~lT0tC)3;6>5? zzP^-{g*TBg$*r=sC{0od`5X|sAg&HihXZqSrw;eD=h{v0cYYEUw$N?7>f#)pbt{5w z*eO<2TN6p_PXH+2JiFIk_w4M#hKG9!3M3Q4eiKL-FD|ADy!8k(?4*sl zT3zJS`1Mq2PaPCG*8yTjiJIQ~D(e+b&Bn*g{o-A$pitF)E?qmj&Dsamk8niBD&%2r z%qT-hr3pas2fb{r6me2?E&q=I!gH{a8$GyG@&*Z3bh z^ZWNzfF2&kVOtbb=kDNgpni?SU}}l6O4RkkoV9X8eNx+3jaxSc%p{pyq}wDl~0S1VwgwrwMT zAn=Q?UH{(it*vrI-aov-Y?@zJTi1@#w@%6I4!kHqA%^>0Tzz(S2p4oJ%tUFN)Tp;1 z&)JI#A#ZMbduRJ;{u^xDyUetv5~s5^k2_cOx{Y-vp2-2|+|~c|>#Uk`r5?M6fq`l{ zzZsE?W*_i1>E-ANdTY`%GU+oinZY=|-WvM9%MK*E`Va+vB%nYIv)FJj(FQ^eP{Ja- z|0vGn3mUFP=UgvizTIu_`j(iDqfzYzTWUYLM~bC~GxD8iY(|}`JS^b!xQtdJtY^mj zUOdGA*h{2S8|;=f`4l4RFCpRYA6&6yhM$7iu2%DNu~9Gc3iFOa4PZ}+v|d&Pc125; zrm>h<1zBqQ)L(M7l%IHr*1VuJPfw|v#TUD>vbv}_Sv>NNm`8rJXxTTdhng_EJ?s#| z8aLV948xo^V$h7MeNr5owz&j!0M}>D+{*NQ&Ec-00cX z8NDP&qXH3$x4Re7>n}ALUK6E9j|SR4L%Q3`qxC+mdj$q52OTngUPh2T*k zJgph-_k3-Y#i4n5z_)I`g!km)v*cu)!3x8vW2fRfpBgK99;s_*rAKR~)kh2+u$av1 zNWRQE!anx~aiIiV8LF=btP!~G#l?W)%4Am5Cn92JNd?T`0s|jlIqYoe%RT<0(HR|Z zaEGd6*q0>b`yadZHd{HlikxRzNeN1u13HW=Wn51S!dA__hganBVyo_>H>32%=j?Pe z!c=auu8~d8A%$y)hK5qG^@hLy>j8xLdqI$F9g^D8R!Pb$Jes!!4PbCe5}w#!!NGJD zJY4uI5F1_yXl8C zW}go=dCw=OwJW@)ro`TaINZBuJP!ELnDd3*8xO{Z%adbcn}sh?N;&N{Ag+b9tAlwx zYvgoM6;G(}C=+K+j^5@Q59Y zU&ko@8tnnz)Z^PeJW6SEau$lye&JW1^;23r33Qp$sW~xa%L(kz?tywE$(>H9m+}RcM=OtQP-vEPFX4^lo5b8hbXgPqS#2DKkHnEkve#SkpF)Tz zsZUySNa^GvcUKlYIBJM(m&Da7@}5&78xxoIrT~rOAz}{V?8Lfs)uW7`dF9XXRg(G~ zy=>9G83Qo)Ze}rIPi&}VvhyI>lk%*GD6`jCS4bTukGxxJ1_*C3;ZuuZenMY3XEL`) z4KqWt`A;eU4?@cj8}I4q#UdQ-`A#-@87F)qRp-;{)=CGKg`uv-MwXS*IA3|};czFX zU}0yd$>rYYt*s_0Z(PKQGTGcTxqP{;suKMhYn!VAJTbE(LLdiW>paRT(Obn8lT@)U zUiYti`1r9Ixi2f8s@n3_{uSE5mzLwkqr760vjv8;i^$?yYv^KlcsZMnYIKNdaF~>u zy-vAX{p43iM|?38?8`yF=vuf#&HOX{h%wo`Q0N>l-{6q>9%q+f!k7`L%Uw*y9%z&q?wtSPMfR}VHNoFnc>96Ij&V@@H zE#2&QyKE3EV-4lGXV0#cm?asmyvfvxQ{MO#B``2EKQMRWE9iWiTXUM*gAvx#%~H|t zdP~G4*ZALv4)hknGNpZuMfs!BP+rPaeoMV<3eM3owU_)g93MsA+@(yUW%D(+$gVfI zE>;L|<5*ixDJIz1VeVtKuLZ)woiq9{R}DUNCY-u;=0~GeG^mp&JL<84?mR9dv!ydE2-R& zo&6pZ7f`8VWBcd>e9Zpl0jB$KWo7u0bED_=C%(sBW+5UgGncX}DvIOcB880|Lqe`1 z()_^cUmn?8Yz~A~Q?j+UUSNkENB_z8L0Tv?21eQo={Pb)680Up1x?KXB0|rlB`nH#Xzl{=UHY;P z(_$}bQqv_~tnDG-`yscR);K>fGuMrRRD1x6VAnXar-AuDQv`lZlWSN`af0rGmA@?l$h(PDJa72*vglK&rU66wM<^=qY zTa-V#e*PRB+W#Gm2<5c2kgpHD1TTq+iCAS6BOMdG$bWxuRxz0=G6$=pwa@Q%>?|Y3GpPZX z*Dg`M2O~k3LwL#WxRrLq?7bOxy{{6SIcM?$0ZCVJZr5l=XyVMl=pG<;4r)1Lhez1W zRK7kRQfw4h$jHPz!1v)j_KKuwekZNx@^X$W_GO!uNaqcO(+O?c$=C;&TiFdqOS4h0 zxeU4}lh$=cW(x4Kj(JsIgqU|e2Pd&SEi476Wa^pu;;UC<T!k?vs>_w2zP@i} zrjn5jDP7gPLr2HQ09iXQ_F1puskoYeVPtBe^Ku?|s^F##*0o7G4L^PW>ccP=dBXk* z26w>BE$00MqY^Z?FFauFiyn1k5Wpzs+x9U|>L6>!Qhvyf?rd z2irX$L#t(L`Ey-Z01h^g!-@fFu(pP%Q^G2&0T|W+H(V|A&*2;c3@*U)%;hjfz>Q}} z{PA5m3nyLONtiZ*?Wz{2c^sh^hU9`iGAg)A)Ph29^emW+*T7^3{*0R+5TxPdR!n#( zrN--WfR3UAFIxB+ZB0!uY5tic6ABGgVIk=37WpB!3drbget-Z0>Lc`Kr64Y~cX@{^lZUBOViv?k# zAXxeXMqm_zsoPVaFolr?t%6gCi1{#Qvv^c)E^=3_NnpM5j&^NZn;GyQ(eod$lC3`8 z1&dleJ!(pEO-%t#&Yq3Rv#pZfj8hjgh_XWUhew1{R-%lUpzeitQ2&Ho0aOf5<&3!4lM z?13BvYZwO2H^DAEEKCFLqqMXa%m^W2>=LEz5?h96+UD$ubn&KX~qj~73$9wQ$ z^IsiWpp0_p<8rGL#hGz?LxYZ-TDy07puSJjuxv6o|CY1Ja37wV)vH%v=d=Xe0gLhK zUkQ({g`Of~EY2_=hJ^(FJ0On)+e;ykjNvvdt-@^B4cC$Ovyac!C>0qspz2e)l~$Zq z^=lUOl3h>w$#hy;R)!}ARR4wVz$_K>h>3}rsqy(av~L*WVgTQ^uyBPa27ch;Wxaii zI$(B%*(fM(A)N>WOp&ykAB;brAo|b2X$`3VkZwB-6W=lLeAWm)hvLlOq?`=WECG8c zB~UnB#~)r5H780jsG^Lxgz6ri(%XO>Q!vJp?u;>ZKJCrC-^rSmM$YpM3MtsEHMI6O%7oT!+T@0cChxMXBcJ`C5 z7oc4D0%^98+2CD%mlV3yQ84U+g&7#>2p532T@1562*Txpp%F+;U`@puibnZ6&D)?* zSG(_V8!Rm>gooWLDzf{G_|VLrwN%Cio)3brBo5DAAu@fa`|fm>zTRnWXSs25E7-@q zzon|T)PZRaA65BfcuK1py#0Vz04}QFpV~708)l*m$JwxE!8;Quv=BuG25#N4<|&V( zK+0SFy!JhCK%HUG3U7exeAH`DB7))+@(fHsDf!|3du77;AO#1B9(Np`r^Tx@qO%RT00IVs*nOPw_???t~dX% zY+(#MRT}_IL52XYbNqZjgJM%=YPZpJ8g75-xI zx3vNId=_%N7;&+L|4e*w=N~bP$(gAb#~RcYKb#I-Pq#)uEmaY-L2FmAAeZ zT$$9MJuSco00Bmd9AXbTch_vvBbi8L+cHdpn|6k{_oL7Up_`GwYvpuPc6J1R6{ zqt*ZH{M#>?`RTH0kDfZ7@4KIroP4s`$_CDh1QM_-en!W`BBLNsl54zxV(|}&j!j0! zySuN`OJ{y>gcE>YU5?K;gTpYHRCwFcQ+->;ms}&1^+{0al7oRqu!@VZlt%Y-3=6Xb ziw-`y-v+!8`(*>dmzZPl4A>#$PbW-xQ9xPf-Z9xTY5UEIS$y0~;t< z85ghIybA*>ygLtZ#lRW9Y5F%O*$tFvWBv0%ALP~+6}uO1fX(dX97~-8enl~l`C+c7 zX2q?eWq=kC-n#XFf{$#7gyaJXijv%E032DoGin#|)9u29e>Dw--Mv|S=b^HpKHiY` z16;AuP*z$ED=n^(lH8QS0CWz4R;Gy_=kIeRRz_VG2 zfRLEj@%X?F%%wpUOP@ahC`RGKl)Q5T^X+s5!A`U3S*PPAa(b|Vj0|>#RxP2+f1=Bb zObq_?qj?31ak1r3I|yt5#;giq?d*DEd&h8|*0g_Sj@Iln`_GAsqB#qt8@}gEoBKU0 zn}nF$ZDi*oJCS1umvrDr$J8tMz-0v{b};yOX~o-OMuoCI>#_#k*lKX) z$RY!}&d+1oBmPoR0l2(*Jw*Tc#yYNb=K&A$t+y|@X!Q(CWf&twRr1|M6Pkx{J~W&8 zO|XBJ6+ljFs!u2yJ31y}`kLwE{G#dZfK;KQ`DaP-+~6R9^PAhY7cl>8KE}xS6jyKh z=PAxkjg-Bdg+<@iJUNpZyPaJ|w1{P`Xg=qlOn$Mpx^|TFh99)%zI(`8;E7f2@5dM4 z^5CL@eIo%gZ13(zp3tg9#IP|GUc0G8aKn(t8eF*JlI+$quIl?zjGt~Yxd@LB4rw(0 z_|ee04*iKK_T_YT4vvTlUCIBMc46iJUa&3u?^#&T<1_%c>HSloF~+}q{q?#r`tu;@ z4Yj%{1(?31A!omV`!`U8=fvj}%QF_D*xH|^xe=4Ot>y~g^cvigHK0OLtDx`ucfKGx!E@lrnV$cf6ikf`%kaA zXm%g{d++l!MB2DAl-RN&Hl5LU+!bJ8EJ|3pLSrR5=pg_fs`A~-w~R|i?K{mzJB4mGe>2-khvskm!65TVPc43-Tv{&J(C3HP{SH`d@;85_VP5g%&3wnN(`eK6Du?Si zNixWJ6zAJBJcs$PJE`PkuP8ae$W)Lb=AK%P5&vb>Lhc!2|LH=*n7^y>)Le4yC5~_y zzKE*4+5zr3p!1JDpnseIiBFaRV=PfJ^q+?>+p{S@{Dz=lB?=my2G)1hDb*EgLYJfa(Vm(;e8Q=m;Gk7W$ZWkH3R@ zVj&a=CeS0WU{W-{*E#H)jF3WN6`DXUEJxo@ync_)fcW_ zhOtZ5dN|BrxX1v0Z%S--*oRKjWjU+lkMjwTVIjl)=kwt@s84}h24?agV~qiSe7$Q> zIc;uk<|BoL+gn?$O-+8T%TNgd)h-97!65AiZwzV%%TaSULV$6)7?=k1*pMH7K|zoX zaR`PFZ=9UA_K(140B(ADAgDZG`>rT2A08Zp3=1D&E~X5MVJKXo+^?VCf>w)^kWk`A z%Pp}fNL_+}hwUv0!3D|_asd;w2Q)ULd={g;`^PddCc#`8;vE)2NTuGY7jS&*f&0l! z)MJ-lPypRz3?xpTc1d!M1wwKKIJbk7Cs%oV2 z;XbPV$3(YoNnt^w{3Sb^m2w8GdQz|%md2TGfR7y}J&JF-?>->&;(okum9OokGwdm#h6kX9%9eEYB!t{}@lp^w zxrnQofkA(%U+XN;J-OhrC@CR?2|eWQ4sLC2-FH3#p)Sa`Au)rLfS|W@9!P>P{s&(m z%gz`P5r@r0utL;}MOQ8S{801?!(wG;&xa-$9&|t-aJPQJ11i(90KJieLmBlaSP1YF zHON+nK)`JvUq0T0zeEpo(STJzVF*mU*FXujK8OR~a61Tt08d!7p*1X_9PJ8P1j`fZ zCe~0O`aV!p78eH(-g*eXT_^#fB{WyFd!4h^Box?-)_fItL(?>-YM|j586F0LKwx$O zuvI_9i06E*)UM(+ewvfRIpXeGvL|z)a9C!N8vbrQfq>&%7#Wm_A*^X!eC? z`A{(k%$P!3t6UDbkMk=lBhf|N=+x8;sVG?YaxubDtY87W2Y?KNSG*3;xTK?<-n{ut zCeYu}@uhVZx_7W|zi{y)@F~D>`kDxG!fLVZ!Q0?-&9(qJ0_9Mq2)+bPIe%gjUFza^N#XWiUicXs=)By4_~e;; zGaG*KOJa)vz@1RJkqW*MEpobeO>pB|LY)Y~>4M_!1GzvA#8q1Q^6@4h8qjD3WJd-E zOBh}wkbq3={@z~Bh5Pgb_KV%KKw1t6fG5&z4zbkU-r!fJMr|qa@F8$U)gG60Ygm3z zF17L_kWf)p4hjwJ=>!EnG)H$*DENClJg}q$Be>1MNi#^qt&sA;C$~?Zo)8q7^rh!i zAG9F-{l&1wCMP#QVh0~z+0Y3*HmS16U#U&M0}7btI%8@K+KCU!z=0)7p@olA?hE~~ zAb9iCs(Of1ftYdeDl}phRF=(NakZk>CXA)y^j_>dJitMRjdxq-ov3Ka3iOGl3QGK0 zmh7!y%alS7FOBp;7I02&%CtbNr&HcnYBde_98k5zKN#oFLnPsGaA!lqr`FjMa3&^@ zfWqAsT%R9(AvLZXi8!7PMZOaAC?3UTkTTFU75+DwAm2H7AV|>p+CGum4XIE_SW=gl zU!QGd_d%Xby@T4g^4YZ={r&Jt8(rX_Ez${+L%gvxa=-!#A6WL#y$XO07jVUaTW;CK z&BFr`Jj#wjxQ?5sszfPRi4a@iy|dT#x^tHg%!}jw=5Qzr&cOYyZoy__b zkZ6`M(aJ|xzNq&C0pr}v4Dgw6gZ}9x&Nu`Vu+tO3r5<-ZG?((s$yo;`;A~Bw*_D8I zGrh{rHMefv0t^sj*j;QiN~S3yty zfPsV$R}c;`u4*k<)(vfKpQ{V~?N^7n4m=S! zZQ&BZ;b0y<3i$ETq-Z8tf0^%$f%obVdUz0HfUl9;3};rRDI7)RDEncsDZ6FBYz-+{Pv!%zon6S-odKz`%K^89>CTrBVhF0FF50{Ik(6BUpe z2qX^>6)m)hhKGdodl51TA1ow5z9s<18ygz{&Pa|OfGHbP5)l329~DJ*{W^!;k}`iq zKwlPwC4Q*&`23lfjm?sqo7}f?@)hL6M?;z=>jcsPH2MlLG94YlaL0g`815qT`{oSr zPn~NZEOh+m&))#TEDhw0!vO=daw8LygGqM^?(Cs7B|5PtC~v7{sEfVtQx7}B*2c!f?>A8BeDU&nGQ*;AkMm~gkWs`hOHgq|g8v*aB8MP} z6Dp^GWhIhQ*o)zYW@ToAo#5z9U&;0sh&K;tel3B$z;=ru65xs!MS-W@Q+TGs!)W^I zy?g$wY|PABptPu{r~r$6clSmB)1PQ)4Bfscaq>s=)mH+Mh4haZmMYJ#qYre9Yr!JD zuXk7y&{&h@1qX+rj>o_Hc9SqfFKt`%1d#CkDz$nEvZ3woN7iHSdBp!Ara=BnO)+HR zB_r#+GC4VRVhY7I?AxFRdWI8Z8WsxjeSBkw2Wo+y^3R>ZLPKg-@HK z;MT_r#R(60#X%UUrhx?7ZS=kBSwe{Kz2NtRQj#)o-n-$Ot>x^K!Fe9#K1)f?VFJl)wj<+ZoJ zh=XHGiO>*qho%DOA>GD$J1gtIxsuwo@BW7+QNPbl^ARV3kwa;G_^Ee$@{bpeU;Kcn zuA$jms(`*RsglZSkFfASCKMdBu(i*EeFY^tJN&ECnNR0hz$p_weJVI%n3DrW!};SM zM27&e!?(yi2`ejmNF9;B`Sj^t^{~S3pWnx9HQn=DK2cG2va)7JM{AZxn1Xr8}uEx3XyzZmP0Iu3BTuQSovF`#^BQMHuGn1lA~iq$B@VWLEnr!c(1p(EO0^2J6e!t`TNIO z5z?1SOZZ}y!l%pzyH4|%*Q}z2GKN@8w0IY~YAZADAvKN;^V&YQY!_k=-eaN^R5jaL z*}&FoF2WM>tCTe~_>8(@foeZAG6FL&otkRsn?S1z(QHr>#&phug%%Vx?$MDPa1z5A z+Pq`~@juY2!SQ7qxK7S*i#7s+oG~c)VX9MhBFVu$PwBwu46?O&pb&bW&kF>RAsxoqDJK3Y7 z%C&LR`)=h120hV2G;X|)!>F{!Xy{WxuzWn~FefYJ5D06etEt&|rXIVS20dDvtnpp~ zI^f`YqjRXIIV?!WEV5CH378e2DS&QFyy88R$AYB_;5g>cu){ZV^YZqg&bmYih2Q~< zq86L^G>gsr`*f|We(wPa_VW?Nz3}pdW_#lPwF z=pe~6Vx*VuRyZ~$YN9=U_m%+gC*Ujxb#zI7emD%^UcJhlL9)Pj3tBM{A$5jv%zLv{ zE9BNeSr3?!TqJK^^Xo;9weI;}5>$!>{@@{82=@j)I+%z;cLJg7CUS02;h|9BNxL@t zfC$xqdJ~WZA;Nh$=M!4tZw2U#8xps{Z30~(0tz->YITP)u{muQAE3rZ$QRO~Vsp(v zy3I?JAgBjC#NXe4I}?n4KwCZY2!y%){SZs}83+X6&1`UDvlE;Nm&lKQ?b>U!dJ_1! z@GlFJ`_@g8inszqa0oNk(cH;(f!oQ6iGTL@wSi^;TL^sOD1lQKCmV<%y7&R_)-4Ul zK`t$EK@NArA|mAX*Dqh{1{e?W8h^vG0*nL3lTd&`@Ryh?b)i%sZ4_iTQ&Q?eqrQ1Z zEBARV)Fmo~PG=XdEWfHD>|VzQb5AfUJltL^faarFnhVgK>FJVHZ8 zSuFj7#-{)TLP{+g!R1GvZ^kVG<$ieh5{b*|lbA|hhyTF07H~H@LGms8_3nK& zwLM6K0bnrv%LSAuMkb3#G%EuOU5G(A4(zwe=QM;V-4N(nBzq`fTfP}6^=eN9DEJsk zchPHDtWZPzcCRfibZ1m(4n2IQa`YysnYloUi%PwSu5fO<`I%=Fs+tj2T$pkO3Gkz{ z;RF_?o=dJ#<5L6czp@9;qZY8vh76Q$KWLuRxtwgpi7F}s94P_LV(-r*z(*HD7bR}E zRJZ{by=UpDsWue?xT5Nre27xR34}XF)C(+yIDfjh4c~=EnhsAp0fZfG7 zp#>k;uiHAllNPk~DE=$xX^JSis0hb_azGjL>~h}u8NIwrhZ4nnj~>P}QRKZ~Z)2dciu)rD&y)8RD?$O^3?;1{E(<9WzG-R%N@dcsM{;FPw}Ix# z*?dL$jJb|$PK1ZWMFyK}D$dyhx^gk@6(eZRG0vFbKYG9R_WDPP$u+`6g7NHM_u%$6 zdse$0b!S`WvnfnE`OjxYfXA&HZvL4pI>i-cLI~E;2T)i8c>VKitOK9Q2>nbef8t-K zFy)7TO+n~y&5blY$X=l)g&A^?0vGm5$^VFxiO&lC{}d1vy8n;r_)KW3$gjmp`QzJI zG;aLM7xBuP()3p*5e=XK$lF>yQHj}%aQ~OAD0sD?3h(mAD;EQ6pND^amDCyN9G>&h2dO1{&=DWah`IIv@P<+TMmX=D?wQtSXNpO9}v#!iq+z`RpLcITkw- zWKYWJ7MaU<>!H4FVLs(gda-*quPlZ`R~Q{F#7BzpsIdbqL=v!p;0UObk)EF5tBF|X zcf}7{urxB#GchTKoT2i$ z{}dV{8vaaNe*b@(U?bzA-wR@Se)UK4Uk(Aos5)HRZfWrX0ECzS0$o$IZOn(kNw_K(2o?RhmL3`tgGX&If=5Q=NCMR6snv1{j`7dZwmVtAGy}>MuWz3 zjZ&#l^dMqz@NSqa=Gm866TmtUPy~dudV>ORkN*W0tDwL%0nSfGC-`E2{h9#%qP^!X~`8xk=?_yV(5^Hvr}*ettmm1g*>}utv=Wa~0_c@`D6ZKr6vF$~^j_;3dYd z^TEXDQT&2uy>QW8Kt}3;Kq}t)O|-LKqV-$s1#=99yk=*(SOr5(`iAR2CA?NXn#G(c=QHUS2?1z` zq27QCpM>Ehx*V$jBOeI86$7tdZAC=U9|lkm6aW#lm!Z|YIM4xN3rHMiV2~>R0dA$Y zaT600IdRfbQ|X0;t5Z`wT+@0*t?jk{9>0x7-CtP0dq)2L6@%IsKHCae8mC^_wzqqq z9>@SM!g|K2V5i9Evl|!JPEykMC6j$Frs=6C25p(2O-m(_%J_F&<+OG{Y6EmrEN%k= zDZmnGThGr?qe$$+x z1|F+AjO4X?kN)&-xL~{j^j#RSE+MCD*HK3?UrH=wt1 zt9=eGLJ&XBBYYAaIp=jv9HF}<>|C0aMdoYBP*s`y6`wBNuxjIRf%k&;rRM8yad>-R6F zcb#-N+?R8CUo#1hLp%9|N&=-)T;4fxk$ri}{6&59Xm$&}&&2+69vX=VZkHZft8=0i zXH^S-^6k#p+nu-N9>qPBc5wn5g}>Fc&z}j0c3GbzK>l_7Nhpfxx=4Ad-%e#?h+B8t zDZd8hdWvVSnZ$qU*D!Blp<)xxDjcuwN$X3L7k78v{7wzmtLwt~M?~I^!(hni1M`Ky z{<7+QH<5asQ2B@Sahjc`QHH8{)>8@1tm0z3Le;DclS8DKF}u9T!ip(A%fsV}#E{d+ zu${x{?e++RW}L+FPOu!R@s=S!e9T!Db`b5%VId z%MmUPDz)Km81aQgaA5ApR7aS{2BhN)yIa20>-p;^pgHHygqFqXX7)y-FR2_4rAWQ& zDC3vjzppB`2jlKvzUsvT{jA;f!D$SVa#gtOb8j<7DC#g|@UMGHs;k;J6+CE2k`WJr z&5g1XN9>)bw$1FjoAsfrLMvklf4Y+fkWY*>4Bqu7`N2@yEuLM;@$bw`+W9%&3l|Tv z_f4!VI)3!tklo*TT}SGE1;=GOv?n+2^$lk+ajoltUkVI`h6=+xrnoJChjB!hnq_7? z)Ok`<;gBHXWT=y>v%l|7Mq3A~m+xLQm$#|OuPjC=vYT{>)Ei7CCR$u$7`rq>{n4hry>Ff~o(N%lK$3ALegsW7PWxJxSM4AAk8|xnxzBRl9xb|kkT+Eor1yank@pgjEpm%-&0pI; z%>4Yin?d;t3j?2i=-Pyk z8M*q#z#S_5%YXgWEOGegj_0rTW-u^o#O?gM^0}*xRH`h92s9pnxol7%6Su@GrTjE1 zv1|_AFgM3M+0AgiG%)z*6hG!(W5@mb@QXM+<0Uz6*YV_s;a<+^2o=wC>sA|cA2U-P z=pmxcGTLH4@S1fT32zyYhVdQ#AT@`cEcQYvy7MSEelg&Az2b2%SYe=s-DxH;n(9|^46bZyH z8jP|z#KD^#Ei)a%qX3D7NA2PnZvEkKiI`$ku%Qy;CGVs?}=znXzHV z-H$^6*Hf(aAN#o*P63=SmSp<7=i)*LFxdX~4@g;;>L!-JdhA08PoO>lFeVlD$|F5|ndq;q$6VEn2LDLe1>Po2kgpT@K|F5y|p z$?|tZ@96ayPE) z+xUgJ#55(YtLdjz*Vs61a`nQ|???BTRPp_lsmg?2N6SbyZS&635vW)ZT&9KTw#09%C3llH#eK|oMabglO-=K4e6K~Gb*&c6lHN&h+|D#KGXtEX3Zr0W&--}IX?<^8 zM!-h(L1dJ0I09R4m|DWe_jYtQ?-W zje#cdaY=XAB*o2G_YkBiPo0@tbL&05(mg-rmH^AtjQ}_& zX-~Vqbv%xfmy+`llZp~UKWYKcmBsGo{y!Z6g|1fMImpc*(E6iY&L&beeud-p+QjkU z6RJCXgsOT5myu`HE89OGnUTwHY|f6`t*kP!bA%0DdRDCm7GW4^iFf^A>Z1QFx-{L( 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 Date: Wed, 29 Jan 2025 19:43:47 +0100 Subject: [PATCH 101/163] Added diff editor --- frontend/src/lib/monaco/CodeEditor.scss | 18 ++- frontend/src/lib/monaco/CodeEditor.tsx | 145 ++++++++++-------- .../scenes/feature-flags/JSONEditorInput.scss | 3 +- .../pipeline/hogfunctions/HogFunctionTest.tsx | 48 ++++-- .../hogfunctions/hogFunctionTestLogic.tsx | 2 + plugin-server/src/cdp/cdp-api.ts | 9 +- .../_transformations/geoip/geoip.template.ts | 2 +- 7 files changed, 141 insertions(+), 86 deletions(-) diff --git a/frontend/src/lib/monaco/CodeEditor.scss b/frontend/src/lib/monaco/CodeEditor.scss index 5c7ebadabfde0..007fb8c17f3f7 100644 --- a/frontend/src/lib/monaco/CodeEditor.scss +++ b/frontend/src/lib/monaco/CodeEditor.scss @@ -1,17 +1,21 @@ -.monaco-editor .suggest-widget .monaco-list .monaco-list-row > .contents { - display: block !important; +.monaco-editor, +.monaco-diff-editor { + .suggest-widget .monaco-list .monaco-list-row > .contents { + display: block !important; + } + + .suggest-widget .monaco-list .monaco-list-row > .contents > .main > .left > .signature-label { + width: 100%; + text-align: right; + } } .editor-wrapper .monaco-editor, +.editor-wrapper .monaco-diff-editor, .editor-wrapper .overflow-guard { border-radius: 0; } -.monaco-editor .suggest-widget .monaco-list .monaco-list-row > .contents > .main > .left > .signature-label { - width: 100%; - text-align: right; -} - .CodeEditorResizeable, .CodeEditorInline { .monaco-editor { diff --git a/frontend/src/lib/monaco/CodeEditor.tsx b/frontend/src/lib/monaco/CodeEditor.tsx index aa8b5150cf480..5e7ed67d9127e 100644 --- a/frontend/src/lib/monaco/CodeEditor.tsx +++ b/frontend/src/lib/monaco/CodeEditor.tsx @@ -1,6 +1,6 @@ import './CodeEditor.scss' -import MonacoEditor, { type EditorProps, loader, Monaco } from '@monaco-editor/react' +import MonacoEditor, { DiffEditor as MonacoDiffEditor, type EditorProps, loader, Monaco } from '@monaco-editor/react' import { BuiltLogic, useMountedLogic, useValues } from 'kea' import { Spinner } from 'lib/lemon-ui/Spinner' import { codeEditorLogic } from 'lib/monaco/codeEditorLogic' @@ -35,6 +35,8 @@ export interface CodeEditorProps extends Omit onMetadata?: (metadata: HogQLMetadataResponse | null) => void onMetadataLoading?: (loading: boolean) => void onError?: (error: string | null, isValidView: boolean) => void + /** The original value to compare against - renders it in diff mode */ + originalValue?: string } let codeEditorIndex = 0 @@ -124,6 +126,7 @@ export function CodeEditor({ onError, onMetadata, onMetadataLoading, + originalValue, ...editorProps }: CodeEditorProps): JSX.Element { const { isDarkModeOn } = useValues(themeLogic) @@ -202,73 +205,91 @@ export function CodeEditor({ } }, []) + const editorOptions: editor.IStandaloneEditorConstructionOptions = { + // :TRICKY: We need to declare all options here, as omitting something will carry its value from one to another. + minimap: { + enabled: false, + }, + scrollBeyondLastLine: false, + automaticLayout: true, + fixedOverflowWidgets: true, + glyphMargin: false, + folding: true, + wordWrap: 'off', + lineNumbers: 'on', + tabFocusMode: false, + overviewRulerBorder: true, + hideCursorInOverviewRuler: false, + overviewRulerLanes: 3, + overflowWidgetsDomNode: monacoRoot, + ...options, + padding: { bottom: 8, top: 8 }, + scrollbar: { + vertical: scrollbarRendering, + horizontal: scrollbarRendering, + ...options?.scrollbar, + }, + } + + const editorOnMount = (editor: importedEditor.IStandaloneCodeEditor, monaco: Monaco): void => { + setMonacoAndEditor([monaco, editor]) + initEditor(monaco, editor, editorProps, options ?? {}, builtCodeEditorLogic) + if (onPressCmdEnter) { + monacoDisposables.current.push( + editor.addAction({ + id: 'saveAndRunPostHog', + label: 'Save and run query', + keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyCode.Enter], + run: () => { + const selection = editor.getSelection() + const model = editor.getModel() + if (selection && model) { + const highlightedText = model.getValueInRange(selection) + onPressCmdEnter(highlightedText, 'selection') + return + } + + onPressCmdEnter(editor.getValue(), 'full') + }, + }) + ) + } + if (autoFocus) { + editor.focus() + const model = editor.getModel() + if (model) { + editor.setPosition({ + column: model.getLineContent(model.getLineCount()).length + 1, + lineNumber: model.getLineCount(), + }) + } + } + + onMount?.(editor, monaco) + } + + if (originalValue) { + return ( + } + original={originalValue} + modified={value} + options={editorOptions} + {...editorProps} + /> + ) + } + return ( } - options={{ - // :TRICKY: We need to declare all options here, as omitting something will carry its value from one to another. - minimap: { - enabled: false, - }, - scrollBeyondLastLine: false, - automaticLayout: true, - fixedOverflowWidgets: true, - glyphMargin: false, - folding: true, - wordWrap: 'off', - lineNumbers: 'on', - tabFocusMode: false, - overviewRulerBorder: true, - hideCursorInOverviewRuler: false, - overviewRulerLanes: 3, - overflowWidgetsDomNode: monacoRoot, - ...options, - padding: { bottom: 8, top: 8 }, - scrollbar: { - vertical: scrollbarRendering, - horizontal: scrollbarRendering, - ...options?.scrollbar, - }, - }} value={value} - onMount={(editor, monaco) => { - setMonacoAndEditor([monaco, editor]) - initEditor(monaco, editor, editorProps, options ?? {}, builtCodeEditorLogic) - if (onPressCmdEnter) { - monacoDisposables.current.push( - editor.addAction({ - id: 'saveAndRunPostHog', - label: 'Save and run query', - keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyCode.Enter], - run: () => { - const selection = editor.getSelection() - const model = editor.getModel() - if (selection && model) { - const highlightedText = model.getValueInRange(selection) - onPressCmdEnter(highlightedText, 'selection') - return - } - - onPressCmdEnter(editor.getValue(), 'full') - }, - }) - ) - } - if (autoFocus) { - editor.focus() - const model = editor.getModel() - if (model) { - editor.setPosition({ - column: model.getLineContent(model.getLineCount()).length + 1, - lineNumber: model.getLineCount(), - }) - } - } - - onMount?.(editor, monaco) - }} + options={editorOptions} + onMount={editorOnMount} {...editorProps} /> ) diff --git a/frontend/src/scenes/feature-flags/JSONEditorInput.scss b/frontend/src/scenes/feature-flags/JSONEditorInput.scss index 3aac722d947be..474cc9a9280dc 100644 --- a/frontend/src/scenes/feature-flags/JSONEditorInput.scss +++ b/frontend/src/scenes/feature-flags/JSONEditorInput.scss @@ -4,7 +4,8 @@ .border { border-radius: var(--radius); - .monaco-editor { + .monaco-editor, + .monaco-diff-editor { border-radius: inherit; .overflow-guard { diff --git a/frontend/src/scenes/pipeline/hogfunctions/HogFunctionTest.tsx b/frontend/src/scenes/pipeline/hogfunctions/HogFunctionTest.tsx index 1332fd9d00926..1862ad7e377a2 100644 --- a/frontend/src/scenes/pipeline/hogfunctions/HogFunctionTest.tsx +++ b/frontend/src/scenes/pipeline/hogfunctions/HogFunctionTest.tsx @@ -105,14 +105,7 @@ export function HogFunctionTest(): JSX.Element { Testing {sampleGlobalsLoading ? : null} - {!expanded && - (type === 'email' ? ( -

    Click here to test the provider with a sample e-mail

    - ) : type === 'broadcast' ? ( -

    Click here to test your broadcast

    - ) : ( -

    Click here to test your function with an example event

    - ))} + {!expanded &&

    Click here to test your function with an example event

    }
    {!expanded ? ( @@ -242,8 +235,45 @@ export function HogFunctionTest(): JSX.Element { <> {testResult ? (
    + {type === 'transformation' ? ( + <> + Transformation result + + + ) : null} +
    - Test invocation result + Test invocation logs {testResult.status} diff --git a/frontend/src/scenes/pipeline/hogfunctions/hogFunctionTestLogic.tsx b/frontend/src/scenes/pipeline/hogfunctions/hogFunctionTestLogic.tsx index dfa16e713b36c..d1db15b2d4cf2 100644 --- a/frontend/src/scenes/pipeline/hogfunctions/hogFunctionTestLogic.tsx +++ b/frontend/src/scenes/pipeline/hogfunctions/hogFunctionTestLogic.tsx @@ -23,6 +23,8 @@ export type HogFunctionTestInvocationForm = { export type HogFunctionTestInvocationResult = { status: 'success' | 'error' logs: LogEntry[] + result: any + errors?: string[] } export const hogFunctionTestLogic = kea([ diff --git a/plugin-server/src/cdp/cdp-api.ts b/plugin-server/src/cdp/cdp-api.ts index 8485aa190f45e..a44ef5d42a549 100644 --- a/plugin-server/src/cdp/cdp-api.ts +++ b/plugin-server/src/cdp/cdp-api.ts @@ -115,14 +115,11 @@ export class CdpApi { return } - const [hogFunction, team] = await Promise.all([ - this.hogFunctionManager.fetchHogFunction(req.params.id), - this.hub.teamManager.fetchTeam(parseInt(team_id)), - ]).catch(() => { - return [null, null] - }) + const hogFunction = await this.hogFunctionManager.fetchHogFunction(req.params.id).catch(() => null) + const team = await this.hub.teamManager.fetchTeam(parseInt(team_id)).catch(() => null) // NOTE: We allow the hog function to be null if it is a "new" hog function + // The real security happens at the django layer so this is more of a sanity check if (!team || (hogFunction && hogFunction.team_id !== team.id)) { res.status(404).json({ error: 'Hog function not found' }) return diff --git a/plugin-server/src/cdp/templates/_transformations/geoip/geoip.template.ts b/plugin-server/src/cdp/templates/_transformations/geoip/geoip.template.ts index cc11f03b89575..8202e9c566e5f 100644 --- a/plugin-server/src/cdp/templates/_transformations/geoip/geoip.template.ts +++ b/plugin-server/src/cdp/templates/_transformations/geoip/geoip.template.ts @@ -29,7 +29,7 @@ let geoipProperties := { } // Check if the event has an IP address l if (event.properties?.$geoip_disable or empty(event.properties?.$ip)) { - print('geoip disabled or no ip', event.properties, event.properties?.$ip) + print('geoip disabled or no ip.') return event } let ip := event.properties.$ip From dffb19a9176344e44ef3daec5e3e1fa71f01f3f8 Mon Sep 17 00:00:00 2001 From: Ben White Date: Wed, 29 Jan 2025 20:05:37 +0100 Subject: [PATCH 102/163] Fixes --- frontend/src/lib/api.ts | 2 +- .../pipeline/hogfunctions/HogFunctionConfiguration.tsx | 7 +++---- .../hogfunctions/hogFunctionConfigurationLogic.tsx | 5 +++++ .../scenes/pipeline/hogfunctions/hogFunctionTestLogic.tsx | 8 +++++--- plugin-server/src/cdp/cdp-api.ts | 5 ++++- 5 files changed, 18 insertions(+), 9 deletions(-) diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts index be3a42b71b993..5e4a36632c665 100644 --- a/frontend/src/lib/api.ts +++ b/frontend/src/lib/api.ts @@ -1908,7 +1908,7 @@ const api = { async createTestInvocation( id: HogFunctionType['id'], data: { - configuration: Partial + configuration: Record mock_async_functions: boolean globals: any } diff --git a/frontend/src/scenes/pipeline/hogfunctions/HogFunctionConfiguration.tsx b/frontend/src/scenes/pipeline/hogfunctions/HogFunctionConfiguration.tsx index 28672943f9774..e0ab482a021ee 100644 --- a/frontend/src/scenes/pipeline/hogfunctions/HogFunctionConfiguration.tsx +++ b/frontend/src/scenes/pipeline/hogfunctions/HogFunctionConfiguration.tsx @@ -33,7 +33,7 @@ import { hogFunctionConfigurationLogic } from './hogFunctionConfigurationLogic' import { HogFunctionIconEditable } from './HogFunctionIcon' import { HogFunctionInputs } from './HogFunctionInputs' import { HogFunctionStatusIndicator } from './HogFunctionStatusIndicator' -import { HogFunctionTest, HogFunctionTestPlaceholder } from './HogFunctionTest' +import { HogFunctionTest } from './HogFunctionTest' import { HogFunctionMappings } from './mapping/HogFunctionMappings' import { HogFunctionEventEstimates } from './metrics/HogFunctionEventEstimates' @@ -178,12 +178,11 @@ export function HogFunctionConfiguration({ ) const canEditSource = displayOptions.canEditSource ?? - (['destination', 'email', 'site_destination', 'site_app', 'transformation'].includes(type) && !isLegacyPlugin) + (['destination', 'email', 'site_destination', 'site_app'].includes(type) && !isLegacyPlugin) const showPersonsCount = displayOptions.showPersonsCount ?? ['broadcast'].includes(type) const showTesting = displayOptions.showTesting ?? - (['destination', 'internal_destination', 'transformation', 'broadcast', 'email'].includes(type) && - !isLegacyPlugin) + ['destination', 'internal_destination', 'transformation', 'broadcast', 'email'].includes(type) return (
    diff --git a/frontend/src/scenes/pipeline/hogfunctions/hogFunctionConfigurationLogic.tsx b/frontend/src/scenes/pipeline/hogfunctions/hogFunctionConfigurationLogic.tsx index 4994c7c164471..1604b7b96d510 100644 --- a/frontend/src/scenes/pipeline/hogfunctions/hogFunctionConfigurationLogic.tsx +++ b/frontend/src/scenes/pipeline/hogfunctions/hogFunctionConfigurationLogic.tsx @@ -525,6 +525,11 @@ export const hogFunctionConfigurationLogic = kea [s.template, s.hogFunction], + (template, hogFunction) => template?.id || hogFunction?.template?.id, + ], + loading: [ (s) => [s.hogFunctionLoading, s.templateLoading], (hogFunctionLoading, templateLoading) => hogFunctionLoading || templateLoading, diff --git a/frontend/src/scenes/pipeline/hogfunctions/hogFunctionTestLogic.tsx b/frontend/src/scenes/pipeline/hogfunctions/hogFunctionTestLogic.tsx index d1db15b2d4cf2..fde1e9bd2c1c5 100644 --- a/frontend/src/scenes/pipeline/hogfunctions/hogFunctionTestLogic.tsx +++ b/frontend/src/scenes/pipeline/hogfunctions/hogFunctionTestLogic.tsx @@ -39,6 +39,7 @@ export const hogFunctionTestLogic = kea([ hogFunctionConfigurationLogic(props), [ 'configuration', + 'templateId', 'configurationHasErrors', 'sampleGlobals', 'sampleGlobalsLoading', @@ -50,7 +51,7 @@ export const hogFunctionTestLogic = kea([ ['groupTypes'], ], actions: [ - hogFunctionConfigurationLogic({ id: props.id }), + hogFunctionConfigurationLogic(props), ['touchConfigurationField', 'loadSampleGlobalsSuccess', 'loadSampleGlobals', 'setSampleGlobals'], ], })), @@ -115,7 +116,8 @@ export const hogFunctionTestLogic = kea([ } const globals = tryJsonParse(data.globals) - const configuration = sanitizeConfiguration(values.configuration) + const configuration = sanitizeConfiguration(values.configuration) as Record + configuration.template_id = values.templateId try { const res = await api.hogFunctions.createTestInvocation(props.id ?? 'new', { @@ -126,7 +128,7 @@ export const hogFunctionTestLogic = kea([ actions.setTestResult(res) } catch (e) { - lemonToast.error(`An unexpected serror occurred while trying to testing the function. ${e}`) + lemonToast.error(`An unexpected server error occurred while trying to testing the function. ${e}`) } }, }, diff --git a/plugin-server/src/cdp/cdp-api.ts b/plugin-server/src/cdp/cdp-api.ts index a44ef5d42a549..06019f0e5ba38 100644 --- a/plugin-server/src/cdp/cdp-api.ts +++ b/plugin-server/src/cdp/cdp-api.ts @@ -3,7 +3,7 @@ import { DateTime } from 'luxon' import { Hub, PluginServerService } from '../types' import { status } from '../utils/status' -import { delay } from '../utils/utils' +import { delay, UUIDT } from '../utils/utils' import { HogTransformerService } from './hog-transformations/hog-transformer.service' import { createCdpRedisPool } from './redis' import { FetchExecutorService } from './services/fetch-executor.service' @@ -227,6 +227,9 @@ export class CdpApi { } } } else if (compoundConfiguration.type === 'transformation') { + // NOTE: We override the ID so that the transformer doesn't cache the result + // TODO: We could do this with a "special" ID to indicate no caching... + compoundConfiguration.id = new UUIDT().toString() const response = await this.hogTransformer.executeHogFunction(compoundConfiguration, triggerGlobals) logs = logs.concat(response.logs) result = response.execResult From 7af78e5e42eb513d87102d214f215aa4d3a94289 Mon Sep 17 00:00:00 2001 From: Ben White Date: Wed, 29 Jan 2025 20:10:34 +0100 Subject: [PATCH 103/163] Fixes --- .../src/cdp/services/legacy-plugin-executor.service.ts | 4 ++-- plugin-server/src/cdp/services/legacy-plugin-executor.test.ts | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/plugin-server/src/cdp/services/legacy-plugin-executor.service.ts b/plugin-server/src/cdp/services/legacy-plugin-executor.service.ts index c05b6560d4ae0..c98e59a23978a 100644 --- a/plugin-server/src/cdp/services/legacy-plugin-executor.service.ts +++ b/plugin-server/src/cdp/services/legacy-plugin-executor.service.ts @@ -100,7 +100,7 @@ export class LegacyPluginExecutorService { throw new Error(`Plugin ${pluginId} is not a transformation`) } - let state = this.pluginState[pluginId] + let state = this.pluginState[invocation.hogFunction.id] if (!state) { // TODO: Modify fetch to be a silent log if it is a test function... @@ -125,7 +125,7 @@ export class LegacyPluginExecutorService { } } - state = this.pluginState[pluginId] = { + state = this.pluginState[invocation.hogFunction.id] = { setupPromise, meta, errored: false, diff --git a/plugin-server/src/cdp/services/legacy-plugin-executor.test.ts b/plugin-server/src/cdp/services/legacy-plugin-executor.test.ts index 2a80995b2e160..c6b05bb58ee85 100644 --- a/plugin-server/src/cdp/services/legacy-plugin-executor.test.ts +++ b/plugin-server/src/cdp/services/legacy-plugin-executor.test.ts @@ -103,6 +103,8 @@ describe('LegacyPluginExecutorService', () => { service.execute(createInvocation(fn, globals)), ]) + expect(service['pluginState'][fn.id]).toBeDefined() + expect(await results).toMatchObject([{ finished: true }, { finished: true }, { finished: true }]) expect(intercomPlugin.setupPlugin).toHaveBeenCalledTimes(1) From 2d8d6380143a84a78c1757a6b09261dd3f66802e Mon Sep 17 00:00:00 2001 From: Ben White Date: Wed, 29 Jan 2025 20:33:01 +0100 Subject: [PATCH 104/163] Fixes --- .../pipeline/hogfunctions/HogFunctionTest.tsx | 104 +++++++++++------- .../hogfunctions/hogFunctionTestLogic.tsx | 8 ++ 2 files changed, 73 insertions(+), 39 deletions(-) diff --git a/frontend/src/scenes/pipeline/hogfunctions/HogFunctionTest.tsx b/frontend/src/scenes/pipeline/hogfunctions/HogFunctionTest.tsx index 1862ad7e377a2..369885c22808b 100644 --- a/frontend/src/scenes/pipeline/hogfunctions/HogFunctionTest.tsx +++ b/frontend/src/scenes/pipeline/hogfunctions/HogFunctionTest.tsx @@ -1,11 +1,12 @@ import { IconInfo, IconX } from '@posthog/icons' import { + LemonBanner, LemonButton, LemonDivider, LemonLabel, + LemonSegmentedButton, LemonSwitch, LemonTable, - LemonTag, Spinner, Tooltip, } from '@posthog/lemon-ui' @@ -83,6 +84,7 @@ export function HogFunctionTest(): JSX.Element { type, savedGlobals, testInvocation, + testResultMode, } = useValues(hogFunctionTestLogic(logicProps)) const { submitTestInvocation, @@ -92,6 +94,7 @@ export function HogFunctionTest(): JSX.Element { deleteSavedGlobals, setSampleGlobals, saveGlobals, + setTestResultMode, } = useActions(hogFunctionTestLogic(logicProps)) return ( @@ -235,49 +238,72 @@ export function HogFunctionTest(): JSX.Element { <> {testResult ? (
    + + {testResult.status === 'success' ? 'Success' : 'Error'} + + {type === 'transformation' ? ( <> - Transformation result - +
    + Transformation result + + setTestResultMode(value as 'raw' | 'diff')} + value={testResultMode} + /> +
    +

    Below you can see the event after the transformation has been applied.

    + {testResult.result ? ( + + ) : ( + + The event was dropped by the transformation. If this is expected then + great news! If not, you should double check the configuration. + + )} ) : null} -
    - Test invocation logs - - {testResult.status} - -
    + Test invocation logs ([ toggleExpanded: (expanded?: boolean) => ({ expanded }), saveGlobals: (name: string, globals: HogFunctionInvocationGlobals) => ({ name, globals }), deleteSavedGlobals: (index: number) => ({ index }), + setTestResultMode: (mode: 'raw' | 'diff') => ({ mode }), }), reducers({ expanded: [ @@ -76,6 +77,13 @@ export const hogFunctionTestLogic = kea([ }, ], + testResultMode: [ + 'diff' as 'raw' | 'diff', + { + setTestResultMode: (_, { mode }) => mode, + }, + ], + savedGlobals: [ [] as { name: string; globals: HogFunctionInvocationGlobals }[], { persist: true, prefix: `${getCurrentTeamId()}__` }, From 2dac3e8b7d343d21c67403e74ed241cd3df7fd80 Mon Sep 17 00:00:00 2001 From: Ben White Date: Wed, 29 Jan 2025 20:40:37 +0100 Subject: [PATCH 105/163] Fix --- .../pipeline/hogfunctions/HogFunctionConfiguration.tsx | 8 -------- 1 file changed, 8 deletions(-) diff --git a/frontend/src/scenes/pipeline/hogfunctions/HogFunctionConfiguration.tsx b/frontend/src/scenes/pipeline/hogfunctions/HogFunctionConfiguration.tsx index e0ab482a021ee..c3cd2e8e9c16c 100644 --- a/frontend/src/scenes/pipeline/hogfunctions/HogFunctionConfiguration.tsx +++ b/frontend/src/scenes/pipeline/hogfunctions/HogFunctionConfiguration.tsx @@ -196,14 +196,6 @@ export function HogFunctionConfiguration({ } /> - {type === 'destination' ? ( - - Hog Functions are in beta and are the next generation of our data pipeline destinations. - You can use pre-existing templates or modify the source Hog code to create your own custom - functions. - - ) : null} - {hogFunction?.filters?.bytecode_error ? (
    From b1d201456e2e44e5fd5d29755875608c3a9ba2b9 Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 29 Jan 2025 20:04:14 +0000 Subject: [PATCH 106/163] Update UI snapshots for `chromium` (1) --- ...--pipeline-node-new-hog-function--dark.png | Bin 137011 -> 128216 bytes ...-pipeline-node-new-hog-function--light.png | Bin 136288 -> 127673 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/frontend/__snapshots__/scenes-app-pipeline--pipeline-node-new-hog-function--dark.png b/frontend/__snapshots__/scenes-app-pipeline--pipeline-node-new-hog-function--dark.png index fb8d50d32758d33108110ff089e805190898a369..fd7dc14b86bbfae917fb0a4866cc3d8a35989ddd 100644 GIT binary patch literal 128216 zcmb5W1yoy6v?iPyZPDUZpt!pgZy`XTxVyU+cWEhZrMMR;?gaNj(E!06(&8RGICJTn znKx_AoByBb$_h8hz2}~@&%XQY{e9nQxRQc21}X_E2n52A`2bb{fu0~ipl3f{JO;kO zVzamg{ycI~kroG4jJ)0kf!=^*!0**OGxnA|v{YwEpC2ME#JiR;Uo$v#el9)GZ*bE^ zPB+_^PM4RL>S)^U7ERe)yEyI^`9yNzKYQO9-ks*M<7~sZPACLciKTL6b}bK%HAhoJsc=azk&Wc ziNW^U`jdxm5_p5nE~7r1KOBRMn3}i`#kr5gYka3Wx&D2{k+?|gnL{jKvG?y|0`C{9 z+UnK0rtn1?;qY(C0WOJBtP}k!2~NdnlP1zH{+-nOcVSM5c&anYQBzf=`wRUB_cMR2 zPD@V$;p+HN<)rw;VT)vN#=ldI81f)DCAlRg?X#VO;yAP$G&{vcj*enZE3;*C+P>mn zl#M%|=Xkqeh3A`lONBH&$r-;lVJ@!b8cRucI+Z;PTi!&gN$6Mx#yG_7-5Wy-uX)Pc zqFMG1%x>5=u4>sE#Rzu2_wU?a@Td#UKHF9=uJMs|-pOhUAR&)+$o5*;V0z8zThtb` zR3I;gpA(>u-YrMO-z#pC^NqRw{wiH6kI^DDm}w3h>QRO|UTeK-=jGlX6uKw@mx@+V zQ88$X;XQsK*L6rpip>Z~tafs0a9oo4Fp1>kMNkrIJN3#U1s=L;b$YD+rIL!f{S5{& zCnyk00ggf}Us6O%Wunc}XQG4NZN|8D$9*(rBs1pCAqbtxj5N&gy=13TZ1UV*Ni5Y2 zv)cNWt=6!btWPE?Ul)j%LFm1<+N{lobx z`LYB!XjC}55zOi(r+ae{B*YLBr3JB&SNX15Co^^TBy_o30mJN1q z@lwFM4!>*6p9MQDtu!;h7-XUo!+RIOdC8SHzWAv}&#W&f_<<4hjFWG#ld(dD1a!5u zP%k8#>T%HvN0gzEb$ z!P!nk&&urVEL5{%w{^P)tX8fwMK03Z=6iR%brjzuI8-OD+??&RX)bI38- zofpv-Z#2hHX*mAzT`%I~;5S*$O_#&)pR)UiG{=RN3> z3j8ALJebvX&paP? z2aCZCM@J|z#^D@?JxS&L=U6Y^Il`q=jd;&a;~(0$#VZ>QI;KkARN z9f#;pQKNtkbF}8U^O9PE(p3dqo*!4gI(#;*vYVz}ZvV6Tqt8!P5&J}b)|q?12&dik z&4uL5wzl{@)nSslM5e^QIk<80@p-|&SJt6j9b+RyLu!mu%gY8}8az~BFl6^h={r{k zF493)qUAabN=*27k3LgXA5K(cma3J2!R_ts!)d$&{r$cdhjA?}J}~knS8wy5N#A4k z7oas}o3M_LU{BxneKFvz-sb&ngVj;Ek2!M2ary0A!P)uwjpz*6rjJ^AiAqVOK}YeB zhPS8ZpHFlx|cpl*qs z4XoITUAxnznj__UTAnG6c~T+#u9aGGVFpBpQbj5yrn%XkUNdg)?(S}G*xF&@4#Inr z#clOp8e$1cjbkaqXyc)V?Y?sz0fvaHWtc&0k!EE*owUo%Z^mHpxU8&$KY!lfgeS<- z&XkyLViMp)no2904VdeNmzNJ0DR&>EC@I~mmuieCZ)S-2WSP%}XX)TyymdY9n_D6a+v$;GR&o{yS5B+)9xcFV2S zJrVd6!szxh?eenDOdfpv?D^uCGlrH;1Lz702)1iBS9AfcCSh8Ed`Gy!b4+r|+;%^o zHt#bXx@1B%F~V*`yXLhBJJMYbc-;DbpE8^60ROYe0gqWz6O+@|;_apmc49pgZC}nA za9#i*LRp#f{i9#g^?azoxmJ}_PqNAMpWUhu)<(AG>_S?Fc_i3Zw^*Lah=U8MsOwh_L15Q>}5pM2@Z-ZFmqNm3dyQ})J zsVVi;@~E(|$zg5#vT|++S5pSNsAybRS$SFGJ;Y3V@!IKA|C1%meX_khvc1OmnE9@< zp|a8$hfiV+_7k!;v{Ud7Fx5c+(3&Wp*?<`zUz4T$RM}q{a7;|ha5_Km*wEyX@Cw&y z`zOg2Xp}Zll>HnJ=w|qlq1vKQt|zuRe2hlMX-VHVU9Kx2DERZ|&*KYWas=Gbt;VcI zkI{d*dPU0OcNnbB?e*UNsYN;%Ja0`en=fl~c939`V_M|4x=~(luY=qf%M3GU^Y+`n z8Wuk<>@FfqPs*Pazk@x0DMDQU{bqBOZ7AXv!LfH4(^P$n7jTL?zq(#JGhc7b`^gCY z+D*aLbqJVg)O|iU^}~aqKFaR)zJ-Hn@;_{BPDz7?Mm=r#`MbF_W%Vl(zb2QLW|whe zielG$kOoJ%h1pNisVbAp<2c1^5-kBvPR8@IbU`;;Zgkw&c)54TYY(d2(CSOUuGteg|vVIz4^<+qd)l)+@W49@)&wOzQJ^ zH}M<8KZDOI`!zn$;(UJh4y+<`!2Bav+<2-)J*3AEo2o)t<~O*~pLllvzb3yVo<#S1Adm`gM6iIJxsW z#F!{VMpl-oHr{ueqFb-Rpk=vcot_1{-0ap@)JAk>vf8Lw_NE?B49&-bEci3E#RuM7 zSY?(VE$?3Mx$4ar)FW1yoSm1}?f!GOcuwa66k6D>X}6cIm)(nDwzsPdV|Qzh!l-)z zEDuU))nzEj3u$rh-f$R&C}e5$z@1`rZfU5f_|EGdx!td?tWedwBCZ=+>>eFG#vjBU z3kr>Ow)cK@T;-OYY$#XYzbBF`&oKZc8+-2h^&Cfq~X@KR)bdGZRNTMNaV@iZs{gkJbSycbe^L&4;Z}j%~`Cm0gwjpRavs; z8Q2k+R0DBTrPw@kzD`~CY$*Wq=MQYCTYm_8870iI-3^t*-IRDrF+9rPz0XaVB72DO z8J> z)vG_79`N4Yd1&eKu4Z-NyiyoYHyx9ou~-t$64avt{Hz+D?}_ z3GmOfcpxODL|Nxc`#UYAHQ#qkUNSLE`r`kMB?ZcwaMKdcNvK@dbp zFB4Yj6-ZikRf@bO=$1tdu*AuAv~)g{|Ab|?@pynKvXC4h=BqsxY(yfa#=k*-s1x#x zkp|&HPVC$A5nWRnlfU{LTtl6wlQyykDNZ!n&EOFFO7p2jmSl|L747ygWE`(vkoFyJ zOG5go^|L&_)GuS)?kkg0L$5eWjzhPmGg}n1z>~$gecj#ETwFU{Vkd}SpNJ$tK$ftS zv83FQ5oy0^-r$@fT}h`9>PRm)M^LP^o}hM*VR7jwqM4BT4SFsXsi?|EtS4E}uYF-@ zT>2=9RVtuYc0k6DD)JLdsPoLlDeqG9>x_FUMcwEU3acZ@yNYjToFUK^?MIb z-my%>kL4>-e?$AfZ6Zy5FhHQr=lgm_-%L`e|)@s8xr?HAL*R&Wm+a}Ai zU53!BfR)AaxYcgURA~fV8h;A1i~;`hr;Up0y1B65zp7Lsqd8j*@+F_Ihd%d;e0rie zxuJf|rSPwGy$gn49RBNgFNg4k_~@;{?u7D9lWcmkWC)?(^1S&wG|dY2n2;rp+x;Bf zmrlil$CxYCWJ}&W?h*1DuJ`AZ*!DvA77LgBGn&(zbdT{TW!Tj9Sq`KvzIF@>5+>~F z>ccZmz6jcJKm%kOj>nwlG|3CJbWwZVnE0#mKfWJxmRGZJpSw$ed>+8m=!)B4LcJ<7 z{X>bTT|GVf2zbB)WA!eWQx)+`^@2?WlMvHeOZ(19cvPjX%++nb&2-X@u5mFWz(;3z z*tuu4qW9;f8j0xvTf{Ec$PBgY<}5N#j!ek>I{Y2#m2 z4?ctq;pm3Hk;tk$T7wdXqaOG+i6`Fcr<}Ux+Frw}pF7aWZEM_J<<&Z330JPNiG1Sr zzmN9psEdx;8HHrfA|(WTII#y3Np8ocOKGO+%MGV?CU1R5)=52I^nCsgtR$jBLsF(6 zGNczAU=Zn2OW;4xffTR{nMEPjP;Jp1T7Z>w!xR@k?##^|#q03p2{lH~5*dt=pSkAl zZe{*Ja5H2J2)PmyO`@j6e$C0n7S{VGK z^z{CBkH+_FxmqcDx9N%S3On_f;BV64k^0;^YLTqgBr!i~ibbYuTVsqprQ|Ft3Sroa zzmYZ{4ILyc-8_76OzUm(KR`$lX)V!*_KWf4K8*NEMWgcjskZMqB6-!fT%U-`SoN1oC!s-<-|;1XT&mwQy>FQb2~ z)Mc!b7ZCz{037S&cg;VHQ7y8eB!1tZoIp~-yu`L8@v-~Bl4cjoocWnz4( z4F$?<9(v|0M~8-vKD|Kl;JKnS`n!Wm8K?S0^J9Gm zG)5!OXtNuWF9c=AAl0j19woB&_)^Wgi%vH**Ezb8R#*T&grynDjHMd0C==xi*k#b% zJ2t|XTmn37Gk+oZlcFi})vbnTxpdD*EET}iP8hc8-}L;yIqx~FSPo~D0Sne}8U{n1L@3>MvpXt+-qAK8Zybi@y zh9+fy!YWl`JFI{9ue~3JW00G#U!PLxa0FC9R1Z>b6%=9$6<(9@6m>*Kesr(zf_T?S zqP1&ZE^$#cd+qXJH??T~(cwM!YcQ~0?QI87{%FU-?&<9n^|@>H?EBX!ZBXCgq`)Oh z$`eGWPPxUgeynO`uH|J1;*$H=IsvFAYt|!O?9(_+OG-;^?dd8` zggpZbEzf-Xk?KtN*t~XHhHqYZ`uI4>H@bEh2VH?(ck2z~@*eQlK%Cxh!nzIzZ$u4_ z?dE)jj}v9rTfNYDN#tkPGDY_nl|58}gYY8X%j5g&)dr!4ioMzTVAv+EGNeGhX07JW zbF8qU_d!=@JJWF=SSUh+ah2nN@YmnhmmcN-Z`WNbdN9xrGix4!>X+d_ny`9C#~D7g z7C>qw62Z$Z#=+o;JP-@BnyZm)UXO`Eusw3W4t@9L4V90N5LL6=E+)|Xt<~LyZ(%X7 zMF=i=O1#f(02Ja6(_;I7AhkbgKfpF<3z$t;O^3<)`xwsc?Zlh8hA%2rB78K<+{Enm zMyvJLWe=Kzot&H;92|t)_jH=Bu3P0DmX6Z}{7N+2yiQc#_GhUYL_sKtL6roc{ZZ zsCKs8M(uWC-Mir%$@^>528X|AyA3Fqdq8f61hRf%KN59F!6ufiCR%XUI=mbhp2DY3 zpENX*)R^oS>fOXmR^sX7_#Kys1)M#T*~r4TruT(<&|g0ELiQV+yIN6ly;RMqKs+&x z7)p&FH-`J6^Mf$POV#x+wRV)*cpj#|QFtz6smv~$Ht*m~s|}9#zW}Jk<|Cio{CJLp z5FueQ+NNS*LBZVoJO}#8h0|=C&)uSV7l7Z?+ApLTrz|uayc%0-K~CE6@|SL6(*47` z6X<@i_mIb={x0#1Vi3{&V74f)mGkyHL~WZ3)yw|46)FET!K;K z`*09ggYti75?*9v>JO3qBN0FeH1G8mbzS=YT@9jr$?g8{=s)}Lf79UqPEGu8L-~Jd z=BcEEwJ~LVZ7tr5ufC$9OrXHa^);(Wn?yM}O$NN4J=6d=FiWiw_XQfiS%9J{Eo?x8 zVNzS01xiqk3&fJ&98vGyR8_r-lmtM6C@N%7ha*6dxc3C8t}e_A5HIusmzZmN)$8Sr z;Tb0baX5F(N{uP;9Rpruk8E(w3z0tvpjt`5>G4(NzJgj+YXJ=k3oXosfIx^)J+@8G zC@mJgddj=@N1%7ss%!kbc@W=laIRW$l>IZ%p(vv|Q~2-kS&fTpVCwRf#+E0eMvk97 z&e197tH%d*UjNi6efBu7Qm6~g4+1d+N1PW}xF*3iPd*8dfD)WppvRZKrn-OsbwOOx zN41;E>EE1~|3BIAf3igXuT!Cfx4%#~huo}q{@y>9d{NBbHQ?UguQolGS|X2HLPWTP zpy^}hkr4tjD)i9TsExI)4`2WnUnM`dH8qUi zuy020B|EPF3c50U>%n!SJi1SsoW!R6CQ-IFeZJ1#?`G?+YBW%YpB;AVo%DfbXu-E{ znsqELr^Vf2{b>2N$Dq!{+SP1p#&DR1iUQ%iXqN8m-~g_5ACB1SS2~b*f$g?Ce>qf4 zZJhH+lRcA+*EywLv0`8p_Wap$2e0WBq4)XL+EycQx{pUiuo!CVDRR3yPkL?Vr(X-7 z<-OoJ$HnqaBDBZEi8HoYdxGxy`M%fbpY{%TG(McIy1s_%Lf{c7@ctdZ;~CWg)iL(i zjbnTJr-QZeo|AN5`;3HyjFg0g28+t9B@O?ET9Y2s_rHD-5D-v@U;s8mb(o+ZPCuBf z?8RGM@x0%FSonIh5@#x%MHOG{S@5Z zjuBqe9!rS5)V|+GJE6;0i99VMe?_#nwpQ!;x7Ep1YRCQVESZ~%ipsDfW(v~BDprA* zX=r%&<`C>Ct?6jOyMsi{qG zeisv`rp_M=dEA8cml}wW$IfiQSvo$~)zyVctEbA5AOE4mmQQ;d6Xt(oy<3HCO2}p) z1;*`-H71f;fL%rFqF|A^UGzJN_>^fjU}6tvh*-{sRcD`(*4QGKGcsN;y+PhpI|3)1 zY_QYi!bEp_HcujbC%Fq0|DIejYgX>^8(H0VRmd=Hb;GPH#B)gmxT7Eb;}~Ux%xc*=#x|$G)r$u{;CdUQ*sN1jVbhmA%PEWeExj z0J=I@LTZ*yfz=um?5S2nqXFhBC=BSozFr2rJ$m!%uPEyld^Huad5zqK>?aTZqQnxX z2-a`%&J?0zt+=_qrV?Nh0hS4Nh<+^qK{2ugFN#-X68#mm~KETK~ zhjib%UF`ux!@Sl)6uE#M?0~FPgAGb5mVKY?5x?^KtW>j-U9Yw5mhhUc;s>yj{&+sJ z{v?4TPRYvHnDW(&+8J4KPHP^LI-SgNC5A*!uVeGA|+8KK|z+im)J+1=-By% zmTTp6m5z>7Y21#=MoNd4Z9Y2=-meLkXB&+BFzuf_4r0Vq_6-Qn4n$E54&3P?`UPMbljK)+FWtR%vp!z}$ z=UdZf>6agC<#iftuwUN@1H`*sF#qMaU7SspYp_(HaC&B{Pvrl9-Q+Sm?K_BvQ zOk}b$%QZ?*M0`*sJ0DifSK(j9Kfn1%$L;YJ3>Na(*KYQ_8*CntFhf}UgBFP+<( z!m|tIQzw5`JNi*LQq8xo^53#UTYc={715e)%_7Yn7#M!P^|yUC4+7emiTEwlOyN=( zJJ+wqs&VO?w67(Fc7}ER=O8|PWvQw{wT`96yuhz!W)|+-^9w1Kz|y?Tx!EN3xv*h% z$1kb88BLEVtTk;u03tcrdeTzWx=6#Vi`F)HlKHbs>R{Z9%qvTw2X0E-qDh;;r&6pI zpEvM*K#d7s*LeF&Nty;6I;`hsl{i_=@sxsUgP6h>Dca_YflKt`3&6Ou-e(aVndONs=?tp zF|1v`{)311K-AFC=x85!4M9<)QXC(znk5|mCbz{OSzFl#1SZ8y(Q9ipS0FIU$jHDe zv`%&%I|#k4n$F)^?5#a{j-{(DNsI%PIyZ0?UTpHwcs98ldE1jsTm^sKun(~n+Whw`AoG1@p*aGFHK(F`CSwJi6sYh`lx>8=XJ?~ z5^9_esdwQsI>9&>0^gWaSp~h9oAPV*Ixi|El>rPAy7oM z)q9=MVz=z0E}q)Adc;9^FJh!kK#}$FB^Jz^JnM`@|2gP~E7xYa`FVa`O@7iEmk-QGChd#U(6?9VeBi5M}<8ZZ6#mq5iLuI$TZ(@Mca= z`zd>is3k6z(HJDg8vb%S#y0$xjQe!ta#k;{UF92ql^GTFXR`TGmu%va||e8_qVTjTSzZ`6^I3p~j!f8+YZfUSxjm zqy9L7*o0Dkdmv^T?CY3UKXMo4ekU)MMdPO6#9sLT0P7dPj%lasCo~T2 z$WV(nR1DU(C8?awgC(w*Q5Sq7TBbKf3QjW1pJt6{+p?+H{EfdJ_OFx;tjV#Ebnkvz zvSVS!{kg8w@2Tl(H>5%moEG(oPvon?LFy=Pp1Hoxg%IgEPB;L%y$k2OoN5?kpakDP`@Z5tA`$ixy) zbo@DR`5BrYwj?~Tg_pfKD!X_}(=kXW97;grN|Qqk%XtQTS}CCS!YtoW%ZF-08 z1ja|(yS8~8%-dRArM;&UrSuz8GiA2vVv?ns_ifXn`-O`4a)L)KZSNMqY~tDFCEsKcw|Z+0YVWiLo<6t_9qia;x%HJsnT3(L zMtt`mL@PK=-PU;>+b-=(SXj2A_b)oLMDR}#0(L^k-sq+;#&RYyYrG$wp-EkZJ#CQF z5m9yS9Bi0ftv_BNY!sBs;Rwv)kEbcVy$aKR_XUWpLnh6%HT=)a6r3NY7b`7?8S|DM z@Fl__H@X9fbCwjzT8*F$Try%o^W(1=Vr4+WdzYD>9Ew3Ulz8IV%9$sv*LWzh8NKhVL+cL3WGvc@A)C(>b-Fl`xR2aa^b|v$veKk>6?N4cHGUfE^cNt}ZYikTzs@ z+5YZ4@qIQGNERzci6KtQ=H43G7eh>({q68|0bK2xwR%xWhg9BmUO08pJ# z+Ag`drqkv6b5-yCD(*%pgTl-$N3sBJ*>P_CnV&bZzok;6T*vO(++}T|NZW9v#r1OM zI3G;W`wnf$9a7Q&d{>EUxlcB& zq%&Q{_=%VM$TL=M7!GvMJT2kr((vF!PRXt!@kh!5HM@{6%tPU_Lsxgl9T*d93FUll zHz3-BNSW1+6dr@?QAm=af&wP?QVXKP=1h((T4W0+hF6AnQJ0B8vMORo=L z#k^c0kDbw>Mv84IJ7PnxFkrD9*-ah#52c%e0s?@U&#t5;TJRWZM<0ZS+pl+NAa zyefs|C$7fY33Kx|=8fA{(Q?e+OUOJy%m6KxqEir9zCo*h7(qDiK($glXLe*AfU z1@R4zsOCFY@}lZ4WmKb>t%O8ILdv5tvSI9}3P2JFdYxnrq%7m(<14s0^38e3xOM*F z>a3g!qwd2x%SSUO2Kod)amEU5t~p8gUmmHkWUkWM!MPk-0#@&gIM*=*eHyu9#TNBjXG?P$PLKM>_53Apcc+vQ&;4R^q5?NXUK)SMjG z2uKw6fuU~-)bji!nYn-dJI@9`$vi$jo|_8-tddRLKwXLz^ns?PzP_feI!-p96PA_k zAPvM8W-Bnfh~2|hylmu*sxO!l#qhY_lX2wmNe zCO@xATplb`QYuSb_f>E+t`v8J@?`pVxxUgw)0V68-f$il(nv6cT#h0l>l%w(yV>p; zwtB@{MT?Bk`|L0hUYl3g#HkHHBKU$c>Rdu{SA*4zix}wwHcN280@BIdSem_Ob(0wsF8Px^zA4ug{G;) z-47;>Z=0v;Y^E>#VA)>jj!X3rnN=w<2c4NF)zD%?xy5$3j|c+<+xV>?jKXbq5`ax2 zMluD`k|`20`2Gnpxl4`G}&h$C!5V zXfi+U<#i}mfg*yuC19c8cBUe;9TtX5FaPm_oE~In;pM872rQCVdczM zVK`A+j2K^MR?HHNZ@A2dY8EPF?(_(ZjqV#1iV5Mgv5j6n)^< zenreayo}^swXxiy{}%Zx&G8)X`&OvErL7`|{o^BY_ec4uKsu@1K|;Zh6DU%hQbN*1 zgbB+@1lB*CrnTiCQJTT5(=>3;n=dG3Z!l~}9lw2+*k9WsB1BFXy~Vh>)+n`z-b%4d zi3Fl@4-HPZaEZpt032njVXD^R)Em7!@?Q(!b2#EcJ4xi%N6!(}E$i=;h2@rWzLnHN z%=*1|o`TtE1dlrL)NCvUD5M(3Y2Zp1XM)#Y~WeP{Vt ziwbucZNd_j%O@~bX_JFAW^CJMvV`r@vuas=V0bxf!-?_qO$hn#(K98w1VsgBFyfw3BEwh_May~`K?JP2A28c$glnjIa7d1)jeRGufMVJ`O1`DZ0E%cskI zVA(Q8XT6Res~XxENc;F}=qp9Bx4^tW_{uOy2k8;)i;3ieR1KKm0SZ6ffXluo(~c}?g-tWuz+P;A$y?QmM{_LwF@AN z@rT#x)8M2ERV>me5Vfqd&_4S73(}F+;B=yhLE=dva8X!lo2C2OSI*fFoXU5y3n;l$ zd4ZaVI>n_|frh$tNM|dlUt9iho`xltwRY3Q6@4Y#Qg&EwK^7V^wxwXFhkEPzeIS7w zoZv_ak}lUkd-^z0*8H75gW%_PKY(7TR7fK$vfa0Nwj+rH3>(2<;LR1tll}$;pp%R` zBC@ZC$d&@4$V65;e^Gx-F8@Ow5+b@>`}Z2qcvA>?cP-{55-2N=ZD2kE$tD905kyM% zDQ1kQFSlr7)#=>M5K}u68jcTnvE9FAXAGX4Z!t8F^Txe*qhqJnmE4W7;kwiIi8r5{ zlsi8h{yucvZOGMhpqgGvbA`d}%Q|7iRJlvFoO&IVB@T)a)@8#;LY?WufD=CL*FnXz)(+n<&6C2M(AZtxpK!5^2@OWc z5OQhtX;5^#X0eKI@#!I3sQ((Wh;`i221L(k4UKtuGND=_zm>z)&^tm{_LXmcr>_M5 zi>C(%_;qmUXDH7aY!(C@BVemXy;0;&+g*kLdJQ<&O__({egRviP?mrj&5iP(^+-z; ztex!AoW#V_{v0uOLt%gu%kV%vt7F|V0^s~YxvZ+cp}YXj-{Ep{T$K}amb^>xwbiFm zDaY5SfshSZ+&OKapr~E(p#p*2|#Y7 zedn~^UvT=HJRPXuD&~HwtogKfu|W4PkALKq-VeH)#Eq{MLcVQuZw8~{9^)zGt3ZD1 z=(P7WnNv`F{1_f$o;ZlLffg_5X7lVR$|lodz29bde1MY}7BSBoMgjAY)@Ng!oRNeh zCr1DZ1#lFXP7}=lW@0gtMM_S7v|8Y{KVQGGvpaVRO^OEAU#(it%e1;(vCE%xB+(ly`OI~;GZ}A<_YzBIn&wWP z*_Y@wjTcW?#$>*g1bZA@Feb=45dPN%g2(=HI`j7_TYt~noijjuzUePwdoTm(>ds~{ zxXKK8r&M9%bAE@IKTD^i#0uX6K&(v#vNRrR0EQCwT4_8y3?qhYe8P?A;)d+)+1(RA zO&3ERxVpIPFIg%9$W&{Pl9H~-^J{nx+J9Cz>_vL8H)|~(!QxK0y{uHt?yGc22{y?{ z4A5X!`GPxYaF_q>6@xkxdH3#2C2YqWp#{}C#9urtebN? zj_eI4GEbH2hKBt%;l!RQ*WK)|4k!dtQiq^(O9#5|@V0Bmm6c<%uV3bO1ZKa4RQzxj z3Hm|rED}LL3QCX>bC_eodl8CG{tisYuFpuzcqg5(PSO0Hl>U2&IqbR8K-L)GPy6Fx zI+Ho63$j(37lFlU09I#x!0M+g1u6v=jSlnm zzB)~X62e?}CXE3}jcX~ZZnM#7pucoXR-^mv#^dBp zEeZ1Nh0~vtBBZY=>ky7_=PTHb(<{8m3u?`uqFU%O47(d+mrqXIhV?6hBhlQPB*3 zOLsNbp`7l%Mt0)*o7!@(lLF>;{S}H=DM?8_sH*Sn&!U2=Ptm_*^@oS`t=&iKy?hAJ zAZU#xEHU>c>!jN3QZKh-J>ez&J_xktd{qenHdj5Ym~i)f3`{Sj;k=y-r#yh|IKSsl z9Wh@x&lIY+2RO$DVC7xR|JX;8H3h7oB>}k58er#@=h@X+{eFkGuD09GtUgHFrXo#`YOR~gZ=dIV2y;^M&I@M1Z#-R^yy-)@jgJevFf$n z0AaNmBO469yO@|>Zfo{9n55LqepWZdjoCZ?;Jw+mQ~Vk#+lpcp9?s4@Hv*4A47C4= z!KWmc)p!D;oRIiD({|?OE#p7%hWR?1kYF)JsRzy-L3EL&0zl^(|Nl~xf7*h)qf1|x zP~bue;rRCS2UQ10T5#~}A|-%oKZkX7b-89z|NF8ov99T#ayctH`XyZcL2Ez+q;%Zq z0s;c5LVyJBB2Yj`02^xK$gZ{W?^k*u-MGZm!ObI}tjkwn+}L^o3jBNF+pfms=Hg=R zDW_qf zjsRyDpo?(#Um)Z_pk83dOu`2}qV^AYBO@Q6BOD&WAkZP@3fgaQYt)b&?pf@EL`KSo zl!6^c223z5KtdYAzzG}e_>T)7772ZJ$00NeY3Ow8Rq24Le; zssX(Wic0Ae5T_W6tyAfQB4NLTa6#{|&CM**4$?J%vSOt(itM2>`2Q5&{9kBgqF+L8 zw)#iIs{Q~^mD@)^cmb#`(i{pO+6EcTyIem0Yy@CJbW8sPdZ0CvV`3rsbe@ALNP02y zL5&0g=^#uE?ryvcKjwdLT@QMG*751U>>}^S0zVt8e$6}G(Dd1fm3WIn#n+f<7;oJ@ z4eM;k^Q194+*Vh*|M-YG&Ka4S=I#IdFVqa)SkgdQ2h{6nslw0>7i3msvP1oEFbwN*S?iV8#;VTFgIjUkC7Q@l zRsh!2?)ck>o!5&+Ea-hSjtDD~xUO%;_rEZn82xKn0K16@8*FOAAmMTbsC?TQ`!|3j zV#meJLN2>&^F*`4V8*d?6yTzk&ssJYir$}C$jkXDfk0p$yRAPR3gi!XcC;-a-%V8k z5Rdl@RBC45F}?&mU|B5Q^3r;H8(z8a=GBWNR_!8P|C8xVK$!86kggX-1NkX6RosUn zUCK3M2JAM^%LpGLGBOG9KfLB68d}9+{DyqE)!!QdK%t_HjRhz`#K8bq$$9qv5$LAM zVG2Nsf!ThF63l=f0YrpsL8rGf+fgc|31;`+T@17Uq2t^56k$vOFz{q=e>hgC?g8vO znTU-iK+FSdKjQj1TZUL+|80l&nIOwe__^_3dgDqD8b~QjvBmYO%mW5UFMtiifWq@j zSi5ff#R=t;nF{So?T%j?F@P%dkKYo$EnrV)XJ^-sS7-b3V3!S6oermuvd*$&&A_Ic z>}xzur{mM`_(Fu(rwbrwYbF*01u~+)9!(DOU#JWHcoe?1X;^=~)WV(&rNQ}V&kM*I zVTER@ zTU=Oyq9?yYF_uh3Jb&=OQgFz4VF9NdYv8g~2smdH0nS zPZ}B_yNj5#bRSr<=aq6xeR+eO0b*NQKPCGc=gKP&j%=+?*re^TNXY$*^GM4S^I-SmPQ@a_=4gpR^nd?vE zYuk@LzsCvx4Qx~`ZIWzt0YsesdA$m#=%^LSdEOX`Fks6eI*Bk9+kySS!$`4&goLPM z6=9=>_9sb(fJ%nf^|uyPiNR9$@bJ{Py5|66b$nUH3An?X+$2^}#(%>pn%V(8!9`3l zMfmpA&@$X={I?VMbX8F|G@7Ud;0dVGIQ-GOIlR&lx2EvJxYdneLpgA^-*hBwbJuZ1 zpMlrqJj0l}*MHivzhcR!3E_5vkq4#%B23K7&2=Bo=?TA-xVW*O+NAs4Vf}?qP%vN( zH3zV4DV2#hCo9 zo80sLc|diOtK5y^HADUaNXByMu@%EVc-$$zPp{p5wPo-}t8pYn*gocjXexcb`~fwyb#xALk09^NI? zk)@j&7{bW45Fpi8vJl2ZqtESakFKQ!)Y!W-<#)#WvT+}NvJQ<|9oOsE8XaUU2?@H{ zQ4n9c?w)6QF{q%7C#Swoz-I2%Hco0CfN4 zHiU1QFHahC0Z=)VBtjzyQa(R0zRv;eXW$qnnWz-lv^VOLM`eGaTqYN=$%-7GNll3< z@oFKdfBs2xL>1Qv=G!zos#iPS45Ukh4vB<*_jUq-XT!NN>2Qu|&pba~HZ>!8N*V00|qg zn(vL*HDuAT8@89I4fw5I@$ODLwgWj7kpJ_Y-+Ij_$jFmiPa}`$askJNA`>nfIrB(> zJ$Fj*&@iAV9gweKFY=PIu()=&Z{3;N2c%0@TX}#k?$uaki~qA=WvYLc+5{Ab5hfdZ zO&^mheb4ur9P>~xF)`^CGcRj>vI`aS(CoFSY_YbOx&)sBg0PopJ}PxK`0SO#zhD@`l@G_-39_zKRojEPg zk8~x58~d3oAXwx5cf-BzZF82-BWY}E+F?t40#Xtb5D+A3Ss}P<-~Lr@46pvEc%ZNb zWHX}pf8{YAdJO#gUlh6(?ax4gUF*%705hE6{NMM7{8#^B$p2Xi+PlVV9r0Bh4Grz{ zyUx|WxX(a%!BTn3K)Qn(3_#ExEWjNpP$n!cRf@lAeYdnMow7+Xmv3eVF_aR8V7~w& zw-UoEV7KxHCcq>Ad1BOcvirYkWPCvk0A;Aoq^^-9*IlC${(JE+(4a3!E^$2)EKUV9 zm|heBx_B|L?rcJoqhxjP4)|GCNY(t3zyxjR$Fo43LNz8yZCT<4ntZx+%e-*2h-2RD6NdSWtVq!eQLjU7+DZ0if%*+s_R8{S|2f9p95k!!Z_&@|CrIA!xx;v!11OyhP(hVXYBHazrEm8u~C9yz2y1U^% zxc50@oU_mUjk1TQ^03bh1jh>1E$DJXVNBIiW$~3qXEoe|NDrPI` zNBj`}S6Lle7WDx8Qol{Z7(&(Nz!@cYsTn0E4HIkri~G`C`*c~!I);GX=^K|iOYGko zoJG*|>s)eIDN*#>w{njwmynSmPl;jDHDgIhu=_1=z+z-|bzIksMh=vjzgBVr2de4Oz&*ShL8Q=}ocN;Y8xgN8#Z?u81>HfQ1 z8+dwYpVpT(cYbyza(WhfKK~7PXr>4A*4p?X36Il7xrJ;2H)s=$38+|EgZ$rIoG(D+ zuDr2I75wmQZ{h~{kf~At*zi1>Nso$B8aF@vB@b||SXg{kpXua@-j#1+R|XtGW|0O% zF{r2>$EKL<7#;~b0LsuL7Ftuub+A6_-dfuJrkn$O>wWo2O0?q@7UNm+*A4<7EDvWh z;05_-X+7gaLpy;yWtO{kWI%>JIzEame^Q>GDIGsiYs8b@uf+NVfe5ZXR-ujJ;^xlz znB(0nT&AJ@C;s;Jt2Tv5Ow4^yf`ZsRE_Wr;{|Ci^3hdq9p`oar*}4$fUefxOi_w~m zqKpE5%-;*f*!TB+5sXUTuF$&HEN7LMsaT*~hx#fy%g4Bv%`L2QRa)w*%gj;NM6SqB z1Y%%oW&JF=Ho8GJlJSI+Jm7k`hk$G%|MZ{WDZYcxUnX4FtF#znEL1c!j7_aX@!13o z%J;T`dp1Ayx{Z*^X%{^zegFP@m!{K1XSaHtdHOL;VoDZ@r+ky)5Dkk|My8$;FUZMy zN;nfokN~4A(hX|)6v~bYzXs)l4u}dueLL%#{E7HDBtsdFxg9yQ{9)7);*bfV;!c_? zMg1^c>Le&TIy>o}JXt%CAY+UfH@(IZm?9|j{QI) zpTXuz)8e@8k_%T^JqsW2gxCA4?*&tqBnj$V+j^Jae$X*6+_`i28HrOg<6*bI9<@wD z#uN#UM_^qYDkEg&>$(v&B_;cD^c4iJiIo+&j>3a~*2I-t#)wnWV*32pIw7C+0W|%i;g0~-c{9H8)G~6%2EFJ?qib@krGLTZKG~jb6QWb zq{yV+;n*q1n44c--Wb{I7+R5@ba+Ijgh&kJt zFQ>tz9=`4__J!B3t-=iIr$_toHHBJr);2br$U%Ry;er_PwAr@Mg1np#*XV_nm7OD# zL|Ef_+7TDOy z*_HkM3SJy{5jwZI`E|^VQf*fI5)%?_Bm^B2UD9cm^OJ?v)6#Wo9b)<8L9cT=DDeIJ zt1PS=!-IqO1Z#QTpku5~77L+KMXD<)Nuf{Ma@D!E;Ns%?UPn7Vax5+`b2;86B&Vcj ztAyKVi8($tR-Kn8MHP9lgxcv#6}#>8zVVgA(!`U0l3Z)bt#02Ax_YfTNO&k^*&Nd^ zwL=KquBZYFV|8nD`cZYj*RR{XMX%m{TkA`0^S)=Z5&G^5YTUKVfLYyf!IvfW=ZkA6 zWIT2`X@8EWbBfZ^)M#*TOL7s*C>b6edEdDo;N3ZQeB!t?+%=V}{V-NZMPt8bm!R5X zBa|dI&$C=fIsD#zf?ia6J2MkZ!_wB(wK1X2eE(fg&_CTrq~{Y&vYfb6{ITX(wR~BK zi_6j2*v#vy#urymr#02C=-z_KYV;YM^K%PmyNmN1BQWZRQ9wX@&oT-78J{#ccBQD$ z&82M_!R4fX=+C^v@8Q`ttY&Y&;}=GnuU?aopi`)zt<7iseQ>T5ua48iSOWC` z?u*At+ugW7w~spJBw^%;^`ry|zrIxd3WCS!?9WiaTJ$eR8w~f%l#~=ZPE3SGJu-9U z@Yq?=JIC>Td0yVBAN$IZ-HwIVO_oo=JZ6M=ZaRrN%(x=`1FSc$R+L+5vc?8aOk}d_ z+lkx0GJ9z@kIMW0okH+OB(*FVznhYd4*5MOtEwc@Sup+h{_KsIH?zmas)N_95Oa0m z_bXXWT3h?{onGk*tC80J{tx#k?p2mqjJJOOKGW#scXV{*K zb(MIpC|2rba@KIZ#@qsa?*fvJE_WT(nQW1pmzR^3Wm{B1#`34^%gMpUWY_CAb{=bG zsukAb>dKE(Rx$~KOLsBu@E1B2Cg>MaQZLl!{NvZOK2=z#MxV^olT%Vrv9RNu?bMG> zPEP(}a+0LKpH)IaqN}Sbm7?ijoi8c%;ZTLVrY0a|LONt$K1U}+Crq!*q8g^?8qVrf z|FN|g$Y4)w>#Xu@sI8^o;tFq@ZHW)aY%9BImCEm~Nl!1oHrmlaYo)onflAJOc7~Os zA?=Dp_M%C}apbfA_XF3ItU1|-mVnjdtkC)xmFH?T?v$acTjP;>@r_f-hU9d`e|fL< zSN=nT3UfMf^dqjK0!8 z%DHgv1rsn}Fa=-X{)P(dVH|4N0o9wNyl%fzYXtn7vUBc>hwhE`HgNPLP@jb$ z=RHiI@X{d(Pe^dt`|Nu}3%zqk{|D00nyM;1T-<&mudA*TH8F}xS`(~P5}KSjHU@@< zm96E~jys)I7r*<{JH5lHWfS@=%BpJa{9zd_#>rf?{@6J;tw-+QAymQ4LXkIF?|yn% zESL-?2Pt*+XXL`;c-i=U!(Wool=Cx`YZNp+ti(R@$K66mr{5X|2+}!w%Y~O@LjiYg zd1m|1l48ilw)1>E&q)GLVpcUj-LdPQyVJXIAR(1G=>fV$_WOM6l2&@Gz<@o=Xxr(& z?cKBB;_4))Yb(VVliRB2KzhvX)oANz=fUrbQ;Di{39|m&#(Dsyy0Y@z##GdK546rX zOH@VU*-qt_c+nHq5PCS{EJSST>cUigH}+<)`HOG1QV?UH!TKm0(P|-=X)tVquDlOpj~j@;&vA_Kpvf z0~YPdLm7?_8dIyD31QLhWeho`m!A@a(VStCBYKlXQ0{jSz3;c2=cVvUs;fo$B2s8v zU28skP>_`c_?lu*nFO$=gt<9;E@Ck;Kd~_TC1EsISIjtRSu!Ul{6G9I=W*}4qvK8n zo0?LKio_jz@I>UVz8kOF3E2G*1GkR>--J3=OGim5k5)3X!WtE*lLQloKFd6zq50We zRcMz=dH*F6X$*hlUSSC|Fsc@6FW7QH%~e$;3%eK%(-}O=0xrj;d8JbH1un;{u?pF{ zySwzXv(chD2df$s>d8L6pF5vkNlF4wPCoSwQJFNeGs z&oY_H*-*Da^F?}Cb+He=NM~p$f7CHDqIn^NjwoeDp(+S2p?uMU5*rKiW*|xX`d*xQ z@jOj(jpMNLIB_pbzoAPXoz+jWx`w*(p4FuL)^+O#52A7?t~ynixVY>-QGWM4{4;XF zrTSZbO3K%il=mjq1xlx^kFtbZneN;*dYtG8U43 zSsWJAhkHw!$jLkIv+qI~6nYBtpFP5aMW`G3&X3JXEybQQF^(FKKM3q#V~LmYUlYy~98BE2!d|@bG9N+4heu3aO~H zv$FCW3RyQ|`G-1;OTg8o6!}tR zNHw)hA))th^Df+2S{veMu%pqh7nGNC>{4(vO(6#gEfzG!^^J@O34S$o4f)*DH#WxW zP1WSQD8T=~lsz&(Gb1Q_`ido1K4LmFv?ujJ^v6o5B!7+L2!4r=pCsMS$j@ic4(Hc& z<6+PLnnTOQC1=1Xk**+y=1LX0^?Uk}puZH=!NCgh$=q%CYgzTEtn~awubZM`BE6Ba z+;0};DCnQy-A18qysr9QV<2|?{%7vBV*%&Q&8=CjN9FQ~^^d)wVd({Okz(&uBaU@5 zDQBqkIL$5OsEp)aH`Vy;V5sP(-=&e%H~V0HypnYb zf9CVYAwU%A*>ud^zUhU8D4lq6K}$oURLH=`_lTSO`^bnGRiqKBGur?0F7d(Y>dENZ zdKmFDE&6Ej^nQ2;z?CX7Wy#Sn>PWfFMNN(rPC=z;&JI`piQZVDf9KgEOKcv53&&&i zDeJ-h;rP;Bz|9d4P;7>)f>nvD@XbGh`@hyT$QZ|t3!@=8REJ=wExKoU_7>rD4)kz z3(=4sksQZP?=imm7E?t<mK)5!ou3!Ag zWYe~8gE1&k;Lu1*UT9Y zQYw(68T4CS#yMv9KoJ$gmE@b>I7Vt_bA~0?$>%O1&BpQEIdBjT&PVI@QSFBGcz5W` zuRMqPS*<#eXB^#O%6%LinogU^YE+5IE}}pk78S~MG*hN+I7+?SI+LwN!Rn{TiYl>@ zE^7@{qE)<~4kd2DGTPgVf9&lO1hdnq6*$cZ4h?~0o$vMrWKWplril9HktIi53e5of zbp*ph5qbDa&w}DdU$ple{HPv)5D~pooyV)=v=RPY^PlXi=<$LYzm1(DUSaqMl1H}Z zCwrU}WxGLHrhZ z5=HgphWU04z%m+ux>2MSje!EU_8v!Emm!O}c{5cG*(DN+ks(J5k2os*ab&yYvKoMo za|UL5dPZhu{R_M^T7ZKy16rp>F~8${Kf5#|qxHQ_&d8o- zZ}oiLex0qP@u&cx0JfzA+_}D-)E76jw7vud21(;#A)YruW#7};$(e=D57P&dTv%o# z@BVvQf(B9Wzd%_HqMw~MEK*bh!^6J!C~%`ct-k|Y9FSWwZ+Q=#&Aq+-t#z7vmGZbE z=<+nUvI)FU3HAJ_9Un}-1h}3{_I?*T!p7Ev4CIGNW>xyWT3u7~F4FOBdU{Kl1t+6! zRdk~6bNP>dISG3ysk8>qkw;>oamT(Em%qhyORBO}Mn)xPxlB&&LxhV{k8A^}fL+Jt zKOs)cOL-w7(x$>aA3pTAKFRW`i$`th+00LP+VYcUuiJP3?CNse3&umA@Hn%wadB}G zV!yqa=)(@_E2-rv?5sbjr)hWab8~a!Vl?Ugj?w=1Pe{cFh{oBPwhFK2nl-K54yYfs!vP|CmHDITuqvb@-;bn zZSpm1kc)AJC2D!VK=E*K|GZe8wKEXwv+?n{X%nhFl5U>~IEy?kyVY`NrrfLxkS0e5FOUmaCnH!@_w zNlZ*!ueRC#Ct&tmd0`tU&d0B-DCpUox4s`aK7{e4Y^(N1`E9H5+eR3EpfR8U3tmPjuVAL2mdXeB_fJg7vwQE)B}HSIL>8xRspFQ1g;*@diwept?qg|yLiWc@-AzB4 zk~ARQ50RQ}wA|evCNd=?dE5F5BP7S#gRhCT&(FkuI4jkSj!sfg+-zw_+2XsP*Ddlp z{3l%0bnGYVYvI2=TMq!a{>Qu7T5YFOc6^GEdDZgth)>{5B4&9a8;6{deEKRmB@fq(MV`{TvNH8)k9>D-z4Q*ZU%5HOu~IVVdMul2`fukgHj}QTryFUOul=gF z%9Ns78Dbs7&G$Av{yg~U@TZSD#( z#t3wF?rfW9ZlS;gT{ufs3>caikO6iCXlu;>nWok!t{E#jEr9xag2nKcer;mrHP3NcYI1#Z+MbSyxT2u zb@c<7HDM8Qwan<%Qv&SiWPbk1+debPLo49v`^0Z`pWJYM_lZeQysWI}Ik`G#Ut6Yu zlA*U0EuYKr&)K%B<7T-E2JSj*Wq3?=NRgVVY87SWc)pnD)ZL$-t-+J__kWtLgk0YE zLWHvtB0MW9kNW@{dwv!baqmNPLd3_9S}OyNV2ZpI@Dg?d#?41i?J_@!)%hr;`2q&- ze##Q(;Thi!3&r(@igILhSi9lstJ%TcYIRfkdL?iowbo zpUB8rO`H10`65jhBmkxyervM>Ti@OZVUh6-t9X@X+#P!aLJi5P*JWay7foVe0v=y- z?HY3b%=4SU+$!HO{0jbHE;(gG7WM5GNdvJkrM>;2Rnw5tA@z;WfnTa)nDSOjw3k0Y zTnem^A36;)XS%Das}D~cqr@pqx|;>uc0ci7J8Sy9=U_Z!;l(l0nQPUvNOLnXHXf_eFSzr=-lhe=m6Ukb#yFPdQ+>%KCX2DJ?B+-1yN$gd8-XnNxIiOEqhc z?`JR9P(DqnDTy2?KnZ}VF|KXa$IoYwHj6<>?$z6}vb}whn*Gz=+zKbZH)~_o!6=Eu zPNjqeo`;t#o^;0PJf|IQ^$8whY(-rNCpT5~Czvf92fR2@pLnsB6{dldB zYoLZqNWh+Gid`ze^#B1otxnV#ERf=xWPW?QUbY<WZlnH#LH!x!p|%>eo+ zB(5CPdik1UH6F9`_x0MbEQSY~c&yk>Eyj}gz8f+V;u;J@ukA+U*)?3?80sabdgz)g z!=kZ}|FF>TRmWD$A}zlg>GY+wzWVIvsd|G0n3}@J zhjMiAY5n&)VLqbqgq#`2QGjaL7-_&fwKg&;vpDFnv)kI$v>3Jm*7?HKX863WVfAD- zGe2&m=j6~mLR?8+eH03ii}`Nu(~)|c2K9Q=X*WUfZJ1MI>!C%Kt3(Sz+(QW27ZWEX zRaHkY4P!6NWV?B*#>rZ}-hr9i@S?FnY2a)rtQ>T%z@446{$lVBAKj3dsuv`) z4t`M?%Tt2jkDaQE7z;Qb+%En`o>(pG?pbDnU})x{A6*_T-t%o-*b=6QNYGrKMQynW zet_5FX-{Gb7b}UaEDRJiG(^#hi+-!B1T5#j&>ZkxkH{S<$gaP~zU~y2LyMi*Hc;|V z`7);bvQ$ws6_@GuTDYP6N=!D9xva&9b(cjT+vB;M z(EI^{1P-RxQuXvw{04sKkChY@K-@T&=y5LXzCP&lM{<^0I+4fKw=k?iFgaKtW^<2o zs;?qT<_p_w6V|-2hzJJK@s*x@U-?>%if2PKqa#1L*>doxy-CmR!HR~CPO;H=xAN3^ zk|(cZj3>KMzjL}m)kcq3r)r);y65)j_nw6w3y7pKjPc!H&PH0^m254Tbh&sA*9=^{ zR=qGcS9tER20`KOnje&zPD}mIsJL0yrD|!Jv%7sVC+qZo{`?6NDKpbEMDM4M5_3?E zS6F9mAJvA3CwUa^@;MyRkT;Kwng8*h&WQ|_5?$-`8e`c!I{muMa_pG@*vSzmsAbpE ztaXZWXYip{dHJ%+P{+U{dV24aETEl^TWPJHg#-yd;!o7o*ypVUt}v)URZT^#aPGOu z@13LSJEYtWY+PJVdD$w9vS^O<1Nm90_ilML8vdBO%~vt_{kx!}PxsHSqRE9;YozhAO}yZh&-Df#v8ND}VX^a2fUyNn5oe@Vh-ywd}qw+DJLbd>2C zGaMW}v#LEw`LBxQ#1mhcZwy&)CcPUk-!Kn<3N3b+iH^E@dQY@UkT9@rf_k^efmM;v zEC=ER?Z%CdQPD<|UE4b=f*(8LDij^&~w4n`QWo6}09Dy}PQ`A;j{0N0Y?XU5?TAU5K zk`&QC_cbfaAN-nx7`{Lms+n$UqmT4mD>u1Mh z$)Cu%nokZRb-eGzDj6FoyS_?hmQSQoVk=L_7LmsYY&2xKh8#?YDhNY9>Z_DrT8iDJ zq`~yVc|a&L6{LKJL?He}nTJ>Fdtyvi7^AeCv(FM6+&`FNrfnwI}9Bzks(19q~sT}k*09>?5ML_qp0_lG9pJaD`XNM zc{6E&I-vY13kyps#Un0aOuwd3bS&jXEkhAM{EJPuCAMWEp3Of?C5awKbrvJt(3!`@ zU9hqP2_+#dUE{LzM)LT^tzLzf>0hDv77-H>7V)L~7$_D-n#ko6=t1{*PQp~Y_mli1 z0x4r1ok>``8b)PC;#F>sStu;O9~OIuuZ-C6Iv@Jq`|!y@uX)@Cn#}+SSLFSOTH#*e@EgM4Dxs;}~C0>Aq_qit$Wr|GzYrluzHwkv$qVwOLg zkJ2$0czC!2tgN|_T=9{sPJMp9^vjn4wAz|?kDb1r$2x8};hQXE$|jlx>3ZH2IWK(A zRGYe&rzlpDYq~-g;+GZbz}J!}yEszeBrY!3?_A%l0@lMp&OAI0dJ7{6Cnat*|k z{=NaE7Zg4c=`c+xFyY%Z#N8i_lasx428V90PqLV(etARTm74kpl#{)^?=3kj#s=G0 zJSI#X2AFi3f(Z79!%IwhF3>L07pK<7F#C*OFMgNo@Mi++hz$#ce3`g+BOl_MWOi0% zWrpbL;$l+IS}m}GCFSK3QWDXfb65B-(*xnyM9@g@KR_1%T6oSQbZXqchG=c10Xz9O z0#7Mbfl$%*cXyZ9rpyd3vEI1h;~k!@NJT|SCF1P| z1<3AhaC4gBG2zDlZm&k#Up=MxF68c33#9i@?g%u!F9h`X*(kFVsdWX#Rfg-}`=F6c zs{`OL!$%$fOub9iI$e@l5Z$k-`0H20r`LDijt4g-lZ8WP_4}1ggTaEeVAjY@EDVfA z8G)Ax?fNVRAAo;!g&MZ3mRh31d@$u1_=oo7z`#V=g}my^2lmjQc(8~rp^3c${H z-xzp!{8|iS;Q%e?Y5~~U-M{~%vdj4jys0ADjMMs`@zk1&cMX`5={-Gpoqt~2lZG|E zGDLM|_#N&p9bFcGw|l55kB*%Omi~A3NR}Ad-X24F-^|f*Z_*uY5$E)O6{oq{Y^bf^-+mdndY4Efb4(PBe&h{-riHt(Miq; z?^zfqgiPe^<0FTwtfXXVWRz`Qmv`#2l0yo6is+es#qPUomHb+};ioXt%Add-pBhz;ECC9!PlfP6=FjcrK~N z)WO$~b+c)udVZwgfPzB~gDR4+H?@Y=1c{`IkL_8w>uTW-I&60w+eH34;Gsn1QKx6f zR^h%_wB3!B>1pc}ovVnqRE9bBN5q(M_Y|}h4N{J;{1(Up1wzk4$*5(v(pV`Fh+k*) z^>lPd$)RbxNKh$m(pcZxhweiCdVwL)+*7_L+tF*=h7Gm z28_5aj)m92Yb|#zy|PJ+iLsHJLweM+*8_rmUS#a9CzkG3~n(Z!|6~n?H+c&&A3b_`-k1VyCs>;50 zNu#_K?Z2Mt2Ko+YNUpwr<(J&-yUzON&+BL8%}q_(QQ#3P z?!lv1I6K-eO3nb$7e|s{|l~cxvLX!SrZvDY1z7JlMxlT>BrV*b ziMCrfh7LWe=NSF&EpGc`)3G%=XteEMwB^HKn2z87SXZn1p69~w97CK3F8YMIQPQ}( z{g0IqM(UO}lAXQ11XkD9t`+=Q8IrAojT|0ht1G5bgV^n9w+wm``6A$_GYME>VJsH# zlfBfep@kBV&tn%sPDxF-7greF7XV8zG>j)Y0lC-PD1h7x9>fq za-s#wLAIs>G)d+Upnq>TFEyCkeoS<2K!t;Xmu-D8Vs|iCU}JQCemUjQO{=yK>jrs~ z1u&@#Vaosc)kh}W=u_xEc*Jm!V~RH8pURpdr}f8XG^ zbBuEJAmm!?GU?flW;{x3E#?&TA4kK$tF?F(=NiNe>O}2F63?`Hmrr z@e0d7JI;l5{Hv`Yak@V*gq?m8UF*e*Di_a?A`$?GzZRJ~FI*ct+ZiKM@$tP**vGo}rm&nSLnfBXdNoUS zfoYX-0`c>=L2Aw)OIj)Gd6!%G^ z7}O-(D(8r|eI~p^&~!c{#uSm)FRoM}aN`e4$pX=zp!k9HdCeQk^;P_=gm>1Q z^k1EHbUYSh>ODygQ5BNUrKP1~y5l`2OX`9=!$Uu@-%Mhl4T+G$b$s&dJjh292Ed7^ zp05XIZ*;j=)Kw&gzOk@Mg##_BYw+BPg_WmW?I%GB`|wQiTT((_CHb8L$->EJ; zJ*|nED}+49@dnf~qhr5_1&~Ry4UKiKqHWE>(x2F*DI$h%zjPX{Zu2cCC?$~`>tErc zx9bsllzT8M62-v8G?nyu@6-+MsHvf-TQ++as=_-QKqNSo_gj1W)?Q;_cRXKH>zMi8 z!ODb|HX!KbmdEl`kprUy7!qfO0(_?w-ne*pTh6?K{<%RV@$qXucVd)W7_}vmb$LO6 z1;v{m=BeH8uK-X8W@^=IaAE`q_ve~Og^{9oCvE_a`|~FkQEGaPGDXCXx$V!Ni3I)C z=;vQ|p_6m#4->pC&temuS)N&iKpXxkEs*lb(TuD8)DWrwv!H+xbeb_k)iV=E&Atyml-uDUq)Kl+sad(Zl3@56-X?6fgV)+Gt~M(cL_r=E4d~l!4HJis{cnW>n?B z_M)IPM@QxZTwGAF%<8=9URI`4^=t^0aa#+SUZzGxRp%yUHC!AJdo?Q6C2$zX$jLq5 zNa$M`&YSf7UHCxP{bVC$aGgZ+5bx9r4U3vu_kK_dPlXMOwNPnG9`oDkfH4Zar?KAm zsD@BV zhdH^IF_%aTCwLnsP4k%S2R9aCrP?&5o9HX4s^SQO+7ADY4c=GXsZdOCUc4$x)8`Min>1!-woFKcb>P*}Oo@l0lt~GCQzze8$mfL2B=U9Q+?Kq0wHjK?&oTkQ7?O37 zD!Kmre5p0?ZZ>z9O8Wc17WIY?*qJP8Uw{HX=T&QHqP~qyh1qO10Ef43y)4rh)GjZ$ zIoi-l5{+P@p`iFV-0FL-rs$c^3=8qOwzTbm6^M#py#3{tTo_4rr0XFs#5y-`i2=Y| z>XO?RD45zE3S@#xgU;6eF4mpVa-$}1yHv+P;C=KBDm<#3Uu@_wgLEjIQ|IA!>#O5@ z%(c4xjgxc}sgHhsC*RK9Hw2v3axL}rRz@ADG3hs7)0hX3q4XRcH4q1_ArfB zQnIrN?SMmEnRdA+Q}e57V`VXsG5J-*=?Gh6)oEa0;Pj)`pO8;TGz~x`Nyd*XHC{s^ z-?Nh&g2n*k_f__jR&z^R^h_7Ob^TUCUs#NOwaU~=KGbid1}!1j8ju%Fvis-de)nGU z>~#l5O);Kr-vNdh{F}804Ph1!@(Kz_IrWyeje;iQSXmkPX4~!-=ho|3H{+QFsT&}O za2$oVo5uF;2N)iGZNc2%hl%W3+jD_k#VZ)gbjd}3c6@XT)atp&x4xs1dBC|jnAuY0 zn>(^VOvKQvZBP%o4Vl+(<$OM6cnHtW?BgCafZX=%Wj2K$1zCBl?kA~`4*Y7%-)#28O^%Xs7 z65QvJW|$VMy(Xb2yAQua+D|1>>4=hfJRNE8+XCK_K-()@H8n^1qOZxb;DmX9u^5NS z921eWMYxqd429CSe=gSp2oIou*3#Aj)f9iCz{a2K$_&igh|(034R1dmV*W}_C^euK zDzzAS1Mt<$TbY*%iH3C^IyyS#`FGcJo*_+jL11xMX)rsK@vbkbx99NhmLg9xks5p_ z;0e{$=P%C2lVTTuZ6P_Z9}s%1{_r?vw(ahgA*#qyl~Q`WEwJ**%q>sHZ(m1M@qW$G zNj&$tYHPfO7}lC=x{b|ozDCddO7EoPS)a$4`M|zEw>#;t7>8+n@JyV&!ACp~@{#wj zKWG}e3)1DO!t-UziDH|2orI5WTR|d{do*YW>80E#$bcNVN-56~i4miu|G4;X~ zzb5efZ=h}TWbEt)2WV6nJD$KL6r$kZoXd3xV{KSd@{4+Z!9rFzAyflrlIcCIcV_`2zw5mnIw^L53mo(0A>xh>E4F zgBFjnc<%7)wh`69_hVm99_eNK9~|%QmuO&kg;ril_Wa|$yF>!kBq#idD#vG!kF42Zs=0-eX){{BV|nUgFO5%z zP7bc;8$LIkF7Kt+rNUNJqt>?m7*bE4y=Rg1#hpMv^ZsdUY=_`|75IFiBi~5q&THh$vUW!WaD}5 zI!c|CV;9_a>!qk_V&ZW~@mO5?E3*d@r%dvY9J+yHr(cj3xsrQXHBEkD)vlEmu7lc{ zGW-?R?08B(5_m)24rT-%mm>41;V9|Y2G8qpg03Q>R=iY$@Oz5!^<^xtRqx%z| z)4r5vL&4nK15l7??X6+m{nZ?MxPy(I3}a|1yca$4Cg1x=s?cjw-m5e+nM&Mg2>ZaO zl{PSx*oFVFMmb71(Qh|{a9C4+kQlqrH_7cmu%{JHM$!ANjSk2oxn0rv;BywUbA@JL6|Kxp|k`noJ^W9KTEC1*ZG zCNQwj05yxnvMoCKpT;IJJCp8Ef^!u`w3HYp6B*3|1A5;>1mf>12;Mw$la&n{+Wr2C=JAHoR zKjn)5ty}#6>jhjl$Q{4V6pe!Lw?&_sfXe_NStL|MAY&E<(K98L75ZfpZUFBkMag5k z-H$Kz5tQvud%c?n_}rzh93Y%waYt7YM(@n-9C>KhdkrN1#*huiY2#;Fhqw4@&&-{n!? zRuh#GGGMq42v~vxc^(iGe@IA>p(f>dlc&wYgof6pUGpZpqqU`_%zS99yW6A5_wM>S zX?Aw@SfGU^4KQK6yx%)IxG5-DSXmXt;>X^@^N2yeM0!F@x;~eIwaGsOb=W6HCP@i^ zN|9a@8pdK`+=+8`vgT5ep#_L7(1X=TixG+*-WKc=B*4geyIOopJ4grR!zP`Spp^~273l9$uIXRHNW5;w# zOG>T`X78+xSFEnCF5^*q4_b*+MS@WyJx%z>+-nPSb2#E~pnn}&o00qy`-N^tFIew5 zw{8b8_r(yc+(?1No(~svc6Mg*cz?0a%L9%sPPDbUyYzJUlEKwGEwJK-6zjANO93XO8L6766A)X*J{xcm+wdEEGd;z}v1 z-0^U5-t-7vWEaaoLru6Di-~x3A1V{o0xdYZx-SCGCKjTNGBz~KqOPt1gxgaWPR$bp zN@-E}-Sf@Et=XI$TEQP@CkHT)t9)jRsH3N+Fgu&vdvI_Nnr!$Mi#)}P-|#4(HNo!E zAriSE8^?Y9T60Uwjhi=HT3VKV0FcLH`}>>A4!;ZZfHE@Og$?Ht=(jL2quUK1a$18x zU#r~mS$v6*A7;L0#rff^3#?Ew_g&_omVOnjRL~(E9Ui)!?TrW~Z>|(J>~C#>^eexW zdk+il$Y)mAFTdyHls4HoILfnAO`SC>tdnUv#>c5?XbwA~a=q_Clj(l4?{R=SfFHj! zUV)X^lWD&`TEfH6&kswVn>!Goc^u1Sypj1BT@QY{8N*zaeAzEtN1aRpD_Jf>!4{aZ=#q{=FbTT*%gTQg zOg3Hi|J>?x2cMUh7y7ao2F=N0gSMN5p>Zjf&jVA+(N2XbGFx`*I^t>Q@x6wu-%o;C zQbxj$u(<8#0gnBc3fWRGKSGv<@P^Q+trj{>?`)8#i8{vEfT&V{RW9C2i0|;=98W(Z}oNpkW%VNq{=TsdQyjOv-zbP~n_X9q2U&6xp>{eqA zicG{NW7#{(6xeZHP-Lmo4o$kkmiRJ8`DPbntDnrae)#Yq0~6Q?9V)mHQx`B~k&JWLzXjTv zzrh^rK#+9GU%gaBgPqD0q9Jtp>sJU4RX0q`i7fV%e5W>qH zlT8|3%KLV|EC;Sbx%l)a7jXnStVF~&&zQzkw_>#vIa=tgrE#zj--1{;Ib%BK^ds}1 zV}R-7l(2+RSj6Q9Ik-iE^!V9__SX1)3Jv%y7Ip$YK($OY+2y8Q>?{)4#(aO^y9 z!m}WoMnnBu&k&}_*Hwa4kt)goMeA^w_}i_6zQ7iJt$Pp@VL|)a62Y#7%TE8x0snt_ z@$D7h3ON_nI+8jS^O^wWXJF=#=d0B__j?!LRftMYpPTk_1#BS-V3XHh6_!RM@fZj= z-zYT=V-dby|06<{*}$tAU1ojUejbS z56;xvuzL`{?kX#5gNN+)ouK30b+c-!|0-2$FNwK+Cg8$o?S8*i4J93mNkFpmum6JS zn@hqlTY$UC{kS>#(qJ$mlsFp}h#~OC&=WdkzM67w9-iz;lC6EsOIiZh03Yv4kvqV$ z@$M-ABJ78?eo0AqZZNHlmBl~xkW%=*G|s6oTzJym(IKJyQrx!JreU?(eq}pK@>XZP zmZ_OuvRx3eMffqbuENs_)@ki7RhOoolPXeILmm#^J1Fk03Z@;d; zg7a+n?YVmx8Q=4^{Vvr`?$U#fj!JbKt87jtF;U;dXYL;--&_(=3{V24OV(F9ke_$w zcWQE+f!Af|F4xlc7LZpXSLxsw#Cw+&O>i5G90(y4L2s{)(Wb}k5BPua7od}aq!|!e zhlDR*PDV!$R#YlqAHfd4U!{eOM^x)P?Z&-T)_5(3N&63IWIbkY#&nVZ(p;#@sK~Sj z$iLCtoI>``p2E%ok~rh;Pah(8-Zo9|Zj2mw9heNbz;uU=$*L-_%z|?eLd`}nLAhh6 zZ}k;huGRguI_s$_p^IlAZT!Eefy*6) z6}D!@#)o!8Xw{Ns7i8m^sj0=9XfA5VhR=$fF2IeFr&+>H!5b_C&O#N>ihYHSIf&xm zEBf|%qhSd}Ct5dHF?C*a@jLm`;twlL4!{X^|GIe2!Q;VyvYWb&I$h$mU#RUgWQk$C zK#uQgx>|_a%9)DGZMWd= ztmcEW9k4O|;EV~f`}aSfle->}*uHl7!tbaccqz*KFBkwvQ0t;V-N}j`&*G>> zligE=EAzh`OBOtwXa1N8{y&txWk6MJw>7*G5d;Jz6{Ms?P*Pezx}^jKloSw=?hrvl zq+?5qbSvE@h;(;%gLK0?@qV83p7(su`SIOR#w|>MCbRrP%w$QU`B#qb&EH z@LHzfi@~^M%0;q=2rwwV;OTeF_xQa`tn&^=OK_{4DhN_X6L8qh;RcCQNAKXvJn3Ur zmOu0x;99G1!o>bV_c0Nyk>|2y+e((Sda?XQUV#c(B|Od_l`xoCS-<_H@|LCz)fF;u zhUhZQwlueDV4OP5_UxsMpBX0P9bJFFT~B>u>(bV)0L_PcC)U#>2_9lW+x;0z1B1R| ziP_Iqc8+%FKU0Q<*<1jGhs=AfN|{Q=%zGdMJX?TXJGae@K>6DnkIdY>|7x;!pLi0Z z>|QjW4y8Fo=2`y#No_}sHwG<1Po0jV)B+~|ZtTF*t4rt>Ix*4`eZHfp%}-6sarZMG zJ^{H*rw&ZPCr_qG{b8k7y*T!*KH|R|iV)U2J1fV3>1QRHn*5yF`?8w|>okhT05ZRI zAqQa(beKV`%skIJErd^`nUg=YOL+~D)n zn@0UoCLG(+LtHJEi@8(Sl7gfAj4tckHo^%5>@LPWVr01ZyQJ8cx z3~RW$RdQ|`LwP%-@Qj2#mOVro>T)cs8}a;3GvTJ2nH-%bOKwr}_Ur--f~czwfWBg zw$rkiNZ8xh1y(+Y%4F5(Rk$_1QO{5kes*|t>I6c|8$Bsu@kyu4oC~ba%Db9IM@L## zemdp$-`QOjEXS!M znu*=dGoWyi=10 zrt(ek)K^bXDlyN5{Hex&`&EB;7Y9&Iff84q`iqMi1%^j$Y;44&CCchh-AG6lzs>0P zUfts`9dFq=&~uP_d;rYwk!{;on|7y5?~2!B+|Oj+#jL(NtMQTAH?3`QL_u)zLUVw$ zvvujkA44)uPH3?L3h}4_&Zzw>RL}*fICDmOQ`1SMoF-D(9uU8~TPrxza!wu)ivuX5 zjUE5v=j@glsd)$NgjTdqm_R(^6~XSac%?;i$3wN9-C-{zJuKE=hJVUjiT&?|HOmuV z*Zxb7iw7~a*?u?pyCD|i*KgP)%wAu;W(GzE%;HKL-wkQ|Ygp*?YpgEnmVP(Ova@VN z;~>N%`H=Wq8E@Fd*xh4{M?oCF9^++YO;egu>A~lNc$b*i*jN`fv4Hnb@4PB~@*T5E z!Fljb>ME5tUT9iJl`C_ESIOvI?`Xad>Bl*(Pa-g=y_H$s`@gvBjVFdd-8b@ros#xAWE~uW@a#%90y9GwRHA!9$ z|1gi^_!1pGH^0ad#zE&tQ)F~7fm(5J2Q*{`dV6i`4xp;L+v^x`3mF=!opv8S|A9E_ z`|RvBej}Chvw%kZy|r$)&5g<3y}cDOD!3HIS)gQLIefsb*T_8Fo!8_W-O{v8Lm{Pc zO5kQ~&5Uq#4lusI)YwSJO<<5lN!@q);1)LGq3sKXn05^ZR=6rufVK0gg+wnYB$ciFx^W{@K9G_ z*SkR=qZ;~D-M^O-&NTC^9C*l+e`F99My5Xtrf*XQy7` z_`bztZ49@Z&0S%_jfL}eLg#_joD|L?`_nlo#ci#H5^vd*^Gc)9t=h1wqNi3XU*tSY zG)Wd2Lb?refoc*bxW{lb(p^V^sMgw`Bqe@nU?61CLJOWVD@o!QMGB?gaNy2;P+d;* zBi*Q0pFj+rBfi@gU5xaf&jbYG?9b?eYKh9sddVAfH}2^j&-Xz5{WRY|ch%SXgOs3Y z^7}`Bmw7s!%Fpo{uhn;OoS~G)J5|vL!Jo-n1r4lLLqw@`ow#W0iZM@#B z>CiiUxmW%{E#RNWL9VYd-i-y{55w!LO2%9Q$lG>wxOK+W6mC9(4A2=H5V?r8Eq~L3 zVVeaWZ*7*;hebYR{wCgBlR z0W`s1fwvXXdV|p8PmRlJ+QKoYlg-XfDsJ7rT`)WXFCIUB%vJda$mGW6VR=Wc!lKF# z?<_C+U&aA_<~yw=%QH_`?K8MSRJsj>W2nmL1W&CT4-JekJJ}fzU$!5A@cE1<7C^(s zwo&i1b9TIU2`G{1xkiPCJ|9eb<#v8VqN^+J=H{kOOmi8ZvYi4T#3*OIeD&(pix=PR z*R^ll2mqRProbKeKntwP_$2Lv;RU*Pz$zs`R66baYH!z(k{TR$n~LG}V5h|+CkOQg zc4B`RV=k~5Ce(cvhC4ed~*C2D9_RA`e#bCDNtj5j>QQGh%2D+>#Sa||l{ z>wxstIBZgcNI%uk$o%>z+6csWver79LJWMi#TZZ4G-MrVq%#z<()U)8Pj~LT=6X zI9dUEV`XFeckSzShChsi$|*7A0~68~fp%z#l&z zgf(A9(1)4o>jT*Q#=v0bCvMh4ryd@m?ck`(2P6{db)~4V5crWOh%4{D+AOjmfsNuOD9YYm5!bCol9{w~zk^Q~u@9~E z#l$K>(kNT20LiLS_zg!CZ_d%9DUdt`GvqW(G0mSB|Eg%>)QKQm35mlE*l3s&Gcsfp zn;5h}aMHv?Q(Yb9k-EA%D8Bmmpp~`4sM6%*#X>vTyLazqcHxUL5;EtC8Aky$1}dZT z^Ye({=xA*not%8}$8d74e=kC`7+hm0b&4%UHI$XV!%_gzY`|X;LuEFLqa~I{uIIgJ zN&%lgH!Lg2%j;G;HZSe~vc9SY+BrE95fL>PvN*mwX9wq8q#bRKVZ*?<+&lXocf=lX zGA_M=)gvnE&>4FaHue;<9J4-H4sih6qhF&SVbCq@V&LF-qN!=Q{G_Hl60{66f`joK z4&DK!2L{Iz6MDqbJ>|aa2gRglH z*aEOT^m@ImEhsfLbtMqcpJ40`HVRqy-M&5X8y?LP@Zc@J}+frI#PP-Rj$|G;~*l`)tQ-LtG=eLp>ajDYuWa4qHw2EuQYSw7Mx|#<4_$}IMGG+ z=b#6`oGAx?mU@lAr0cFXDLJyW-;kr9Ox#Glf8xv+6AP=K>>325NQq4U8uNo(w-a1- zbS!wO5mw;@rKAj}>n|vR(C>Dy6p{V&H^h%K{VDO@zS@tKfB{Ck zpEH>&^Qw%5vHn4LX0~@j()q^rwRKf)Qk<7cu_Fr>*^EpU(|7lo;9}%FdQ?ljZ9fXa zg`J5%U3RVgV76>I6#r0-)f+D}X!{e$7Hxt1N{QuU|D9^b%YFCe@A<^6vgeR~^nCi8 zvh6j@TW?*Xg`>3-1d9C4^&kVBdMYdyP@8Fhj*xb_5FLNV#l#<%+uR4^=pdOSnZle9 z!jAX0Y|M|H;4GV2;X(=o!qP!-;%*3SP@P%14trvT=D5-aOV1T^lP~WvA7qnFd5W%6L(T59{ zrMar=B#RcIs3*)Fhc&mcv(}4VW{l}KGdy(NW$~p}CcnOWgLu1o+}NEY`LUOE=UMiK zv^m3V8=J4+?4K=^Jf!pMU1}_*GEPlB*FPUPdiB}LxS7;kx{i?mj?YT}IG{AsdL5KJf73F>$F7ZahWBN(M zEJIXO0^&ty?&NcwSde#3+;~A2l^e(tCmF-Pw+dzhzvXLR+=n~@sh9?8Q(8y*%Usd9 zfr?fvXM3oyD9oJOzacNqfBJTP$H)k1Ze}vWOgq=sZ%d~Tc5f@Z&&z-z26L_zhqU?r zN_dZ5I17TJ68cDzWMREn@QnWY<-DJL`;%%%oWSbV?C-YnLeD^+iu_DWl2nMl-lRUq zCTqWGY>NHLoLsDPf9I$8LzWAnY0f?`F`hS9_oD~7jmXk*A5$1DmezdDX&dD`O9EX| z(p%CIJjT1f1xj*h-^PO!j8WTC$aW_21yi1V)NMEf;KoBoH91y4loGr}VudZNf6gMc z=#P@8`3kBi$10*~o$NO|irCF!*s0m8Dz{Nytc1%X)`?z^H4(TdQ4ed*)-u+?Z3j005AtTc9g3IG)qV%l@fvEQaoVyE79n7M@ ztNf?hfRCrhm~<6!aT6tcB$kS4zl)*2yL8i*U&l&Tfqtizy=gGxtryi0tH#eOm)D|e zGFpN?K{3tT)HL*hi;IgexvjM|fx8JEpUC*jnn9e%+DZ~ZQVI2yw2~Wq&vk(wWZos1KH3*)0Xf%*pJI2Rp zV{KETSH`n`c5-8NUisW8raXIy*3Fk9}(@-~BAwx$rw}6xu)r zNS{&pi6ydXRn~SoyqxmujfhZ3+;@n<+XWZRdKcdgh@y8+fCtb+AzZMNVm@Qy(-#AS2b9V0ES7#hbU%>0Xyt>CtZQ7d`iJ90V8@VB%J7F09~FCJfac8bvy7 z_ay|0{nV>+zJb^bRsDcVwz4+1c;t`VzM>keYkX8-(&5YuPhPfR29B8khc;b+=M#y9va>&>^7dT2?mf_m( zjJKkk&+F%RNZ3O1UeA!9I{dk}BUI@W>4sp;qYJPOY zUfAJ0^x=s=@&~uek)2TC>^lbJG20r5Mu5fHB{#s8K_#^EN`$4Zkys|*efBHeHF-XF zF@1uEp!=w--TAay`YPhjw0L=yPKh>w$6>H;&0kod?=1wKgrj9us3rG0*a$HV^y4ij zRZ7xshLkAET4Xq2=c5u_kC4?s!W}7lc@~|=pjhg-ypq)QM;t{8{4ldh@z9p%8|4$v z13b|z z6Jnwv($&m77Tve5`_1Kk`Xt-yp~1g7HY{nizVb=0XK}VEPRPNkJ%|i^brRFlOS^k( z6l5b~mnO3`anbxD__*l9_%3%%m96vlzEvjdhdXU$&0*D{njdMM(j-OS$?{m{P=|Pn zO~+TEACO}_&3}dRz7F9ja(>FPFuWr}z~CzEzSjH2#3NovDzX?1jD${b$OUb^Ppi`H zrmUS1Z;!T%v=d%5&Tg^kRcrs(&CzGA$%X7wNbuUP>IFf^TUQXiR|3he50y9^>;q~u zYQMZzVK{w+g)}>}qMG{ig9hcK_^f<+qso^?(s9^+S#YumIYxAw=KkgQ-7O7CbIr2JyBuSsU-MZuT)UDmO2v-w6%h&bNN|=+V9{P?q;M z1ZX3^Ue1EeLLI~MW{5bg@|&1cOk8v{vExj5<=(aoif<6p#ntDb(zMKZuO8f^KY9TI z<~`s4G?DR~g~r4%lnt6Ul!k}*eF&cn#z#yc?~zT=(NSUvYHi{-K$_ASi*rXn_m}3R zbHRGgQAZ97kIo&G7Z9hLnKEh;PLpl|nwH3uD-k%)uD696(>PM&;F}0aac~)DR!hcy zjX1+zU1=gOL7}Ek8?=9`h>!U?yKtx(kvZWD&A_u7{4O zxH-TA9k`Rw@~Oq5=DhOQmk8|Q*>(2gD#d7<1-LOW)$*N)d}$lUD$knQQNW0c1D^$m)PoJaWMtvR`IB=K%eV<0J3pqWSci zpHir9$iv{8pvz2z(A%gf*RJJ-nOP_@LC>5+92M0ll)4W;Dk}QTiJhvh`@0`MsAJlN zomM*ik-1lDH3@_h9=OQ@Pe)NP9wam|FTm%GZw04#D=O+%*y{YVXAY+`{#)baBO^mk zQ6I_5n$5(NfcHhtDx>hxI28+nW^;pVb=57>TZTqPgG|_TlatB6M!>i(Plpd3(ZTk< zd9D2=t4E_l>*s*azf*p!NLgp=5GA*O;aX4i846%a8R`6R-rXx(i34|=wr@VkQ_|9c zTZw_|;VSs2&u>x*Bu9o@GLyi4w=hEwJ^mq)CRIr%WDhbAF0=ik?NAYi|y zH8s1+{rdCZ_Xk+6t`2KMszGek(zGq?tWQLf6=>JbCVg2$3#7Zi#Me@%=1Z*;@%Gey zc2qB&gOf9Mx%KkvD~rQne|%YLE+XrKw?GxacRz*J>Pis#H)Y2E01)0|&!nb!A1~;K zf5B(Iup5kCLh!INS5HW!!RCHhcd^`}N%wS>%U-3!qBekv&j#6XD0#xixXtFiLrNAr z$?HB^J`C?lZqO`>{N=P~JN2l<_rQ|wZG4=tle}PslAhk?Vt|N*5RRX}oAd4;cN?4A zA~q6&f!TYBbPzei2k$P<(`BYW>^&X#bMD?V%j|4pR90m)Osx_2$_T!k|m1E)|iD z>+^a*%}6)f0GoQ|3)mE?*{Y?0g@u5eu*KJVjLb~;^0M_gxVbKiIMJkm=^sU|?>t+9 zP(0R>xuT6+HSO3y*J02D0-WX1Ja3dK@o^ifXVnSr`moENoh<{0*B5qmr+iIUl%T1d zQjNt#*s@WDZokO~&*JvfMgTm|()-&|L3gSRyS*S-9tl^c7#YoJ$|4Sp%RF8^^}bx- zOkr2A+~(zkL4rWpl!dvu5Zu@MQ6eVmN@UGx=inkUT9 zoqY$P2pvg)N6mi4#vn~z8}|JP1N7nJC1^+b3FJ~zCAK1(W?_6xH>3QgauUQPA|j$| z*Vdi))~sk>O!gSDcx_A|WoKVlq9f)CV#O(ZP&^Y8A-t%7Z)DPYRD`#W5Ir)|dZ}B9 zd2`zmd8PIYy)NTI(EJFpwmf=Qinnh&TQz^gmU>SGcR{+wjY^~WnNHIQAYY3?#S0M6 zoT-8&(xg3>{+yGUe_@iw8Ncks`D--a=jNt{B$Nnio1F%%LAX8C@f2>yyTyg~9ln?J zb_H7@Eks{Z%XK87LieQ~^%CSvj~}oYlAjC~q?tBI}Yf#20BRw5~5Yo|!3j3H$!O40X z5h0e-MDUTwgc@U}&o7CUXLY5YOey}cx~Ap@g=48P2amtHuCB^rIh;oGJBP`;-pb-3 z4%(|LO9Nv>Bnb!v>NpTho2K5)-T4w5`+R%Y6*0x#hl*fjZJX6kkQ8yEjT#)ZJj&9{ z8!hV9JD9~qd~G*o-+4Bc5!N0}IT&>Gx_<1)ALYf`k_tD=Jw`@6jH`3ng$cq&FfF&g zzDhd*=rBTYtfofz#@lsvcOl1Mf!p6JH))F^HNsL-d!9jPi|LPUA*(GHSi^FOX$tg3{s4H&Be{l{=EEd6z`ToPiDV} zPKCAm*ibi!_FdL!9~Y-K0&xtQKF_?JiH(J0hy4cIH0!d;1vQ%-Z~jkhQW(07jua{HFMnuXs&>)@}~55+j>{_e&|NysY{o zS|lrw9Og~ug@@!spJg1EX|`}$TH4QV>%Bp(G89$@Ay{QjXJ+D{CX#p3s;c{^asaWR zD=6Gcluni>p-ic|_%rqL&zy{2h5%j)tJYe9r3d0Krg}5uZ9v?oPneOB#4(MT3)avR z>FAci=*Jbs1DEl-7ceMey>~15Gwc|e4j3)kRmF|opYX))`Iu?;u;!(I?Y$i>Pahze zr2JNh(p%XL!$%A>vb3}sXJ;AR4V+_kbNS>4MWG9W$*jko&@{X4cu2!~S$E)xDWEqE z{{L>z{>P55%8~OC7@(K)^Mkt-6^8nt51v^BXeK=62P;9Zu!euOqtE0YxCV z2rB7}_gEIKw}zi$Bpw57I5P5Dhn$?)KO%ZxcYGRgsrB8n}ghV#}+my z!SD*sRalEysX6Vs7qht0@~NL%EZM3~K@vECOhERsy{f$a|MYYh`3&)(r(0pDey+w! zH=V8Ftt=;+s0aj; zym|h~gM0_jpML|U!4+mXIMjW_Q?*y&5SKkxnbz~Kqz~$`5MaqRP};aLekOwHv)#TO zF?~}B@nV^GQUu$bmXpr@`0tuj=IncJyw>-=s3^h$c{%7wAz&O{q~0Xyx>4ohb5xCr z;;<=1bs?{)Xqp`g`&%k_eAxMVLf|ektur`ElEj|1b^kF1 zL^*ZiptN2Yw}Il3l$7)mz|y)b z48dmxur!b?u(RX=`i5`TCiG@vzO7J#u5v5ytHIogB-GP`A3V;^&WNd{R~HF6cOB38 z6^+>^$11Cm-k&M0dR+57!S&*`IPAWx>w=;X_gxk~1Jrz>ZjLeqnH}s(fJg{KYK_EohQrb@P z(Js0mxTW(ci?fTc3BLqPBbg^uRUeYbuZGlcQ&BxlI`x)TSA|^8@ez_Z2mHj2_=wjQ z0to^hGO^o@txv5qb?b5^yYup(nd^T0-bDTER%4#(lU~vvY|xszi!W-#J?B$A<&tvQ zXJWaxM_@Gf`z7M-&WJ?wJwtX%1Y@6vZyn=B9BJ|k&7aqO>c4zh9CcWGGp7Gfq|6K> z9&Y93;aM5aTI~I@=(2w6`VkJQA^rUFI zu~qXC-h)ovU=JM9CCQG#e{p4)ihgG#(D=ICy!zv1ZzN=xb2T?&it(;K(Emb$ICbAH zd|x%bZxQ2*OI8kLdTJ{D%x6sdekBc!V&A6PL~+62BDiRGY&Hf>HaFIP`rg>nS+hCp zs@_Tnuy{fAH^yW1tGBMr2i=K0Vqy^%R;i>D7Jjg!O3W9q*wRl1uB!G{%QY^GJj|ej z^2f@G1J2jv=o94-D@czDVBffbn5z4U2X!XTz<`VW*@BS4$}~+A&+Frqck2JFdo|7f zQEpef(ILQ}WYl}DvsosMfl9INTrk~ z>Y5O=E$VrR;W?DP&Ac|x8AY1Oa)-K)=RuVG--v)wgn%EL7Zm;T)}HRVzi&>rheq1Y zk*gO%TwI(e0=E43pRsR<(n6bY1t)py=G9kbkD6|t2GSh*vSa++Xdn`*J4}LDG7?E- zYPJkTR(ND2^p&q)y+Q=0DrV!L2OQSiH((4rjqQAW(ws6@vaXL$qk~CCFoUol@|UNt zh^M5dOM|6CF6*}*?i(Y^=C58Aw%{6jD-(c>B|JFu^K@5qiB#n(($k-g-^{pL_Lk#v zhhtRh-(kDQ(%Zv9pN>sklUZ1rsV316O+$QU1ys)`+8U`Fk6=Y7E-3oUt`76@(f|5| zsBihifuN2MO9xRxWfpq8PYA|n?bkQ)`1s0JaMHHGkE$Cye*WRxsH+M1(+e;_17e_; z6O3RU8lXdG)>XJBGc)shdMUOPw0|+E=hG_}b+k1;h~)i?w-v5d`j_lQ#J983_GPk( zt!g9f?}>1$KJ>o^F!QbI;F^=I_|=KQ&rc=)_L`MBeJ(q~ef5NzM4YDbYcp`DCy2kj z@cNIB9^;jo-G)z+_}hHEhpqQJP33Jh-T(cGyqa@(Tu5Ab5^!(&vZr5NHezXKmE7W= z#h)|{ruCC!z*R850XT|SI$at6?5B&bn+JjuS^u1s`6Wv&k@#22dn|^SB=BQ^An>oB zeUMDt@^SsA7p!&JY{V$Ke-#fpuK!%1fqzcuGn;D3C%p*O_@sXhs0RnMo7xb50f{_N zq!Nn*B@^$FP4S-8$2Mvr(*-V9WqUZLW{)i+zXT)eR(p_4ncYPW)K#ZKWWul@8Ip!-B^y4MZ$kh?&!(U)9NC1 zDG*(4@F*`UgDx~3pfvyNVwrx5KH6{QLX!jQd(CYxP~QK5Hu2>GYU6JF9CuwW@Zw;P zWR@sZqFozEAXZj>e=j7QWRbEHJ4}%TkaLFCy+9!RQ@HgHm4)@3QA$Bz)DGHU4W%SO zKPj^HNq(LH)Sm;VoZ^i__yADjnRkR9~DgleppvtbwKp0!%u zJROV*gN~cQw|X8+?X-4d)Y`~|!8BSc_u}QvA02Rs#s*HG$ZK=x2B&J8a)tw2F-acdG3*3(< z{DL*}E9oPg2%m_bBuU+#hJKnk*HC14hRB~E5QD1-B*op*;50~R`i<&pr5Q)l;o9E&j z3IGaTa{RF0u6xkF06wWNK(5!l{%GAl@Qq-hm3wy4yOPOL38^MVnN)B~=JKXcqZUgd}v@OqLCYxuRs$O&!?Z%&vK zF=04so{SNN53N#=D734WdQx@@7TIsyxZ$B)VKxyPRV0}uFF*yo){mWe9i1fGj{QeR z-$;M5-1~9aPis+iV!!B_H=mIan6r&z(X^DNhYWt~hfUr=_K|98`?={^C*r>CM|1>? z_5uiH7>(wC`ev;11_IigPZ-0YVW1erbFyF7|w%i&n}1UMbr%?3{^53O+WKT6)fD~T!uto80{aC&;>(v?SXaG3YgT5Rj$!98 z@7Ss-*Ubmb=ZQ2S|1Q8s*ErIWX*>a4VFz&5aBGV9tqaP~$M zON&&#$z&Z!oVcC@Y<5voh=qIy15Qcbm6`H21W)}d|ruBp0m{tSHt zKW6i1=nw9%y1ZBp_2~esl$|Ei9ky)cjcwkpXMsS8Iqy`5fHMO0~l=MnaPui<0t&_l+ z(9vV-H0pb~w()*6QOKu_Nu@14(*ldEc%aA7=hIK@3L{<-| zD7=`yTLaYxI3O&qEM-3}lQi4R>|e#lp8#D{yUmS99ar?ezcPL9DwY>7^jNI>68D<7*(|Ufua2Sd#BkfUz2c8^7yL8F+j^Fg&-ez@ z>55V;zcttQZ{O-$GG|8;cpwQKkWcz**r>p4M;Ho^x7%4{jhy20;2@KTh)3@NnAKhA zn*R-6(h^+Q*r|(#&pzK7wrpKk<{LJoBu9uXc5qVi8`ftbiGTV^uwCFFmPSVQ{Ye_v zhp`Z`RVQ~MSyKy!xAS>pJv-BS<|aaXE>m=j) zqy4Y!-b^7?5Qm|oTjki_7FCrQZ$_y1byscf&O(TXhOv>($~xElPZ!L%j(eW`91}qWL)%d)#RtIp^d)tEBixVe{gw zyH;Ervbd(?TVqJx^KDC+R{a**4E+wXH~M z6w_Z98xM}4huSOJGBuwS9yXjgs{#8@n&Og2tsEoS-SxF=S+!T>Q9x1g)fqe#%7q3(Z@+B)v^kx$yDuv?ZI> z`bf@(J#UeWSKZhInUa#C141bfBO8W>%g(wIoyW^%*n-(3bl&wWEj282$|fb5-|&-7 zx_(%BjS}+aM328*)e+*+s0pVB4@?B?0?%J^mo?fADtb}nmf@6g>l>c8Y9eY`So}ra zejhz`LUdW2SC*opI~f_l&A#G=P+mDefomyS?{t}^7cU9$yYD{~v&QNj8k+6%SZL@S zUX!uBa6e6$PgjXdl4SbL78H*}_U@RpB=_}oFRH6Y&bjwI;85M5f*rE|%4H&sk#Rdh zlJdt~(@(5lu}^tq#(%)VVR`qL7~0oq*K=i12CMH9YAT|582P`9i72x)OgCf*T!m9x z`Ae%dTYc>6Zs%Sd+>{0R%GObP@i%&Z0%~;{b^~f$N`ge`c8z-8cl>{cQHt!)e*Az? zfyEcQW4{lt|NUkb$30B;S?&JUbgl&H1b^4Sv!<>=bcDSx&Ro*l7z+u^{EL^&28m}? zCf;#Pf122cxL8++jn|%tEF@c9CqoY&P0DG?% z_*oa4__~FmX2nm!so`V z76Mzu@G#=h%lnA090JeAMrXICeaGv&2nxwuGWLYMBbR>Znk`))!L9JA^ho7od_@8-O@7yXErW;w`>x^FAL1zj8bilElH?qIqw9a2Lhn*wM*g>O zvLF{tl%J}+=9xG1@M?f$;CVa`Pjjiw;_?c2T7W{wQ>`$jRuJw8yg1D+YG1!e<390tIpHQS4Gn>1mGgUvy1MgI^QhF?~NIoaNV@x>AS z4G#n7m%0u0LohIACM)mYH1*{FWUo2x$&C{&d1HU`=FPc%;KSqMf-hOz`I!zrKK}0j zV8H5?zU>A@cm;*_ks?lLBq9DM@tk?*&H}V>_|<)gOl{17RUP!nM?Xz3@k~ulXRM@8js*{eV8=41bLU zriHf;ZTXWbEAIgH>s@iNqN{6VTQ~>%_sU8^udmOmob8sMhD#YqI&D1yg z4%bSWhx2HDcxWA^sg|U7Eg%b|U4&d7W~Q`3)>M_IWIC3qo-=Jpn*Q#8?qjMwPny>g zZ5E?&$vLL`<@~3A-bH$#BWx`!phY~hzlMhBm~g|9Bkmo3?KG^O$icAqvpwd0Amz#1 z9b+TKKu3Wf+}(?P6k+$%!wns4XpIepI zDHoI;rx{d^h_Ahmthe7+YJ`59YA^!JEBm7sc;bZpF&NU-;2dpY;=?Q!mYEc3@G&Xb z_XEu1*{SdD^ zQDA~l{&UQ&uBuvMu8BrnC2-a>6g-t^a_(CFGAX!KeSBVL=Ua}`C-OUYVAPFGA(`}rHK3HgwWlP@7dXEqJj#~EBYk-NpqQgZKIc0=#)Ah z)%^^1MoxYEj?8nk3=6@Yg$_7$K!8rQ zYgumYMkt*e%rYEZWVeHzw*AJK9?V{FKX^c05{^X1#KZvMo`od~gkrBe`e(ofMr&XY z22_S_Q&J{`hKkYeA6&$@8y_t9q9cUej+3d*qnQ@lqIz?l@p@7H3Lt}{O1hKX<}}7W z>ev4rObrS@AoYX^N=pm^GopPp2OKTtbn_7@X}J8(kJD^SQh)rYeMuIZX)Vlw#i!UgSRYwkxX`LQwUyGaNt29BP- zb_FYGoNBpOtQr;ZLd_MVglZ-W(+?J=Q8|O01C^z93OSS>h~iODP`rFOjld%&hT#_` zb~>$(+sJR>sd+jp-z09NjZg|K*^Zo+6=ZO&gy%6x3i`S3trR&T{rrA3eSNn+vWyIm zoT#|C&%i)}!0-ZpdHc6ee?!FwO`Sv6bFqXn#CON(((_fkEc?gIO$P0{9ytgH zIUJn-1V0X^MpnlLqgeHLD{JIjUj?$d znqpY!*ttiOl9Ti2PfAqJgU=Zyn`0n}LO_!HNN8n!xWL7Qvlq&>kaeI}tcjc-W))`3 z$JRofV%t_AT;(!eYR3R|HV1ZNJ6$o5kHJ0=mTZ9aZNwwMamFhI%rwJ%G9u~=HV4hDfv9LAFj-*^?`y?DL4nOe$Nn|M znf)<*Tl71^x}X;2qznb}&JGp`U%?E-`wgMDE92lbU;aIPNwKR6HW9>jSJTpBDrh@DJF#>4KG@auyX|!M{v1eY`IUYH`jX4unhMN_*dDk8)=)W% z=DBa2iaI`yjXk2Sp$_dRMfuat3tNu&Ar4c9t8pS8OgpNPQ5nn0GJ5oMbaL*U*-k*E zQFF2+m0oA&;A~#LF>|kS+xkN;TR@}rA07Oo_3a6f+=ytCY+B}+oZ%w#vv2w5D=ga* zC1@9cR3ib49&J8LBIi4|(-IRw6ze(yrW>GN2TxRTGDy#uo3DQC1qmP+zCw9a<9^m~ z=F)>Jk{vDLA*$S^W4U=-S6BCPcY8b)g{ckYAj~fp%Ax z{7l^U)MzG74i26-V<0Q34p#)<@Ar||!ovD>FN7ydCEdJAjqeABHgjmWh|%hl{e)kW z&dD* zJgej4z6H;z8Lse5ofWMjf_G^1&+wF-n3h?I5ld&PXbN)vv5PdgFh@A~BVJ^5pz_sx zoW7D$(9R+|;?b*DL%&04=28p`hV9A;Q@GsLpOD-$d)Oa1zvFm{>Kljv*F&jf;hd&` z+dzJGyFc~H)>b}f!$a@t*)f0AEwTi`y+t4eg3enRurfTwQJQ|^o7ry}ZGeZpur=3x zX8vCWU60TjWvw>`2WxAo{xoC|)FiBQ)rnQ==>CeXYsCr`OZom(TPpCHq+CO9RfV{_ zyJ&65tY+@Gt%Hqv@c3|9UgEnt_=vc}v%(BND_G;0pXgGmJS<x%{^wirkE34!6PbM zT#UBIeY?1-oIPpA$x5xqwACjHwD>CA-2LQJHTbzb_D8B3n@642-Vp0{I}7!M6FO(N zzR1(RuS?I)PS4ED&di+KahlQ1*9x#RpjV9j5{$RGv~=HmV2w`BwK<8NHdRILJR>LP zUYU+)I=5=zZO(y~OaoinzS*XmGdggQ=CDRYY(j{x;v%vRFzk<2Nq>|iA5Crwa;Plr zdMri|Tph+jR;ip9(W-~Eg^N)Hv7-MT9>Ww1RSn6gaDg-ck^~0kmWFX1D`_sCXV1UR z6b$2WLr#}A1VhCf9HFiAAAEhol9pl9pJ$g~QqXfr(kqmAro@bq4O^)*@O+S4x01Nb!~S! z{nqn2Td;NMy{L2>t&?Y*PHCA1XRd?ASMRGis$ju=d&3ukY{(NvuCa*;MbRJ05CWMa zGIMe)-b@Jd@%c!mn7(@T)c*VBEZPxIwp11P<$^8G9P$&8t|sWKvVd;0*x4+~pxdg9X_O|tGW^^lEQyM`O{JZ1DIciDtXySXi;lah7L(|R4Ryp~|pYzt>-%bF5 z(@CrBl7QV%y#RHY=u4P6fN2K!mavu3NR?(~W^RmoIG%LqAi6FVb+*-67eTz}U_-=_ z=tP?ekCcq8nYFjBPL~P~CR&6*TY!u8F6R!xdr>4d5Z=*ef(h?jiBrtW&6I^XZ0Ge~ zz>0@MUi5y?_kx(bIq8A-2aR}P;Sz(S#=}tUosf&9-;@<^hq+g2_xt|~`as3k!xZs< z(TB%=g)O8DVtxy!Y4#LBQWI3qfI>a}6CDc+3m7OALC7y(o^lFM+mX;BhW8E*4r-is zBwxIs;(I47AP{p(mjl8!yu6yH!c?lDQ7A7j59736Tp(5qDt&Nw2fDiOIxj+Q2YF4Q zP({;_EK9 zi=mN`0p|yOj}w)>#g@w+JQ*}07@Vl}*Pz7@sp;rvcQH>X);-@_EdjM%j%^hT{7rbzv@NGTftja!@-vGEzfjYch+M2zgrZIjr(C z$9qlT_&{=2m_>SMI)j8_d7xXx)>KLtovPHqP$l9;mR^4S44 zBY3?|45lFUYMdwOt5*9!*b>fVqDn4Ti&e>Q)C^W71FXuYuZj7JOb6PQbJ;o!MV?XN zB}2#sJ;tmo1AYD2{c}_&LJoqkc{O#7Q1;U2xg1i|I1y_;!AYM(6Ko|VKO{-w=q+(t zHr!`tjt~pk(P}j=0A;h^6$iRi&S&=zLE%yrFSJq|1IIl%G4X+E57Nfi38)H8_-Bh% zPet!@n*$sRXFM7c#c}I1EkSkyE&3XvO(a&Z!xEY08$ zr~oCy^=%^#t(g#-UmYFmzBv7>0}9q?RvBxU;gV7eiU}&D9WF2-A_P#N0|rvJk4|AupY--^lt=$@?por# zDvY*k*0^`T|Bzl>Fb?P{d;tbSuEHLx0~VN1m~pBX++tf)&l_aAm;3yTOpjb=Q=uwe zB;yG^=x7;Z+|(nw@AfUc6@v~z5D^fOZUJdFZUhvRmJShV5a|Yk5EKv*kS^(x?vR%56p*e> zcfS+A=lt%u=f0o&{_*aARM_sd_FB)H&zxh-IYxhon)@>y$?%^nmcX9?I)5r`!_Gew zH1!pR)1A-i6H`)BWoZQ(yjYwTj?eE%(lOuw~7AZAYYLzlYMN7tp zrm#DxXlRng%RjQe)G*ziuCi=UGrftp&A4z1{j0CWu*=+z*bn*{7q#y_AOzA0YMqT@ zGAG|>di6izM@G>4O&({<#}b!+wY0RTv9ba6o2|fYo9Z)F6yk(Qf}I3uDZ2Dyifk+} zUtkP6+>xy*@{UMGRyL_8RkqSa!KBaOSC9Cso$?;};N0lNXfk4(BBl8SYL#G3Er*r6 zm{SqUZ!~Eo=c-*VBP#Op_A#!Ox5r*ZjLmg^6ng!6uhS_rypoB?b35G?q`_eF{QVWY zfDa!^UQY@O2)d((6yDt~v&*-!U!^AAdh*H&0grH^r2oLc0OX+#&;M9iPlt1RT)8~9 zGStn?xUdTUxG!w3tvv>!MrCFAuWiwy)-#ylNTv@=m^zHghU?n*>EyH)qDldm61{vC zd9=Z>VftnDD#1&shnM^*jqcTiBqvF9WCWGw&K2I3YyV1u=l}C<6EXf}E$b!y`;LS= zv5QZn4H?2q%0rTqBTDd6c@4xh(qv6_3N~ePQ&OZ96~Fj2{n1crDR><(_wwcU?CkNL zBzNpDyjClCrN;26Lz{5*>EW{ToMloU_kr)vI1o}j7%7Z|F$GsqU02y@;%k)^-oLAC ziow7dy6>Ew%YM?tBWmV10Gn#2ej!SYyw9~^0mSlD&)vP2yE$sP_4R!3uatoSI)&KG z>KyYa&0|AJr=Np2y>C1qBzcEQQaswVQgkK3!mE?Y@I3STVZWw|ijIoP>e7hroGm4jgclen*u#t(kT=ZK-!9@+ z<0iy5mXwz6E==xEihCE@N$Kk+;*haph-r*mr3y1L8H&;zp644e-dU&Gl9u?ha4)H?WB9=Tv>XkZIb%y9LuKX;=P6V*4RjuA}jqW zK7V-Xau%DEf8H%#=?C^s+Z7d2F@rtasTuZV@(P!!ZECcpLc5bdsaLy9`ChoM?=R5( z_`aE~mYZPs7LEC&vOZ02ZRNP<~L`#O2)_>30~>ZzndYe)+ilB>+^^z)A&gmR=@4K$3dioodf}tZ zRt~!j0rj(_m|R!(u)UebeI-_jur1oH4u(Z};UYkmW_Wm*JNc;&r-!T$K51u6_1$70 zDdCvooquUmiQ@m6r>D78i<$QloBy-Ip4e}{RJ)=9!6h**yByOM$7NHC!m7f6q7l== zEWgC8{Op7&(o4#?;$mV7_Cr{-VZZkRHMbNcAHDhfW}>rq-tAR~$h5;dj)Ph2)zURb zC+8on*Q~8icZ^qCjLeiZi>2fBCQD08^Dib>^VwNzX}PLH@2z*?WnH5i(jHfJDMf#C zydL=UMcO5+Z%7#JUh?n`);9r>=W_*_=%}N;{zHtU-w zlrx9B@dxw|&f(TD$ch7t+${#Oql5gPT6oD*o^}r$QCol}k62BcB!0$0V$_Ink(2uJ zK#{;T-<#J94OY=_+>mDpm65;WX+Xxt&CNNZG!#RJ80gu>@yg{aO$XXL)I75#U_#d- zBHsUfZjFycruB!5e`#2?+O3t9m86V_9IRZm-#NNx$H$GOnTSHC<90qPb+X%c|L4kS3Avzs1!y zJe>9}`-6*OoooqM*^Q1;Fu4;u>d&_Dsbnjs5F!em8aYf1$SEjvhv(1RQC{wQ1J2eF z0#D=RA2!u536ge^k&@m~B?|*5AepfD861P4eT-N!F&x}{U>On?mQTQaQNE?U-7GUY z4}Kv=+m6-%VL={2@8s6I*SxfJ=z}=>seUITF?iLZmU`dxRxOG?lyEciCNw>qZeRAIhY>%1Ljl5yyN5wRXI9T{n}?S6W_;@7L+?98E^+V1kR7YAiCt{J2 zgn8>HrBqaB!gj57b#A8>ka@MgwzhsL!|1oU((AGsr4AM%H+g;_16Aw1L2t9PJ(`l! zV;l`?x>o$RcqzaEx!PmG$p&M-9S=R&8J56^dQ_aP?Tt@fukvksRp{jKAg_r#S+20C zsHf)$#DC8i@r~Z?bJ5I18*?>G6QNq}et|E|!LCzrw!z?IZvI|N3yVg8h)60bisj{* zxu0`#pyj47@j-5`hyCiE@!V<0BYdO7eZy*vYf4dHtet-ECuYAQ^A2SE5SU|eTgCnL z>zBo=2S0yCG&&jd%rxUs$T>PoxSi~@mu>|VT7)^D3e()3D_=t))hIpw)U4@@H;p$SZwF8g>*>MS^@^jWJWdbhqDs-u zG4*vmayw|d+Fq8^KM(r3FCw}GEDR1@c2^lZU90C)hK4+-y!1^?4|}UHAV|HxvTY>e zyt`m%roXjpDTRN!8JU}FrK=mebW&hvb2M9AcNPEC(7<50T$O;A0;qzzmY&T&9MzP^ zv+`z|k${I2p%i08Y;0|xJJ>^`cum9^T#A(I~rn5QrS!Y!h-&l#SU$x|yO^ zG)clTGIhU3xf%^`zP(CtLx`WTrPY#6Yv#UH!dY5yi??BRcHXD9FQ0I$yu4UNv?F5l zmX=y#q=juAZJP_@A4uKdGgtl^W@f1fLfeD34iloz+iLOh^^GD#>c?ArPXR8=pcw>F zml^BBxh$cTk+Nr&c1Yob3N1hb-KXbP0w~A&%@-ECH|E+$&Ff&G+{xMbho%F_11c#g zUA=mgA{Xqa1rxpT!TI_v`ST;OBA}Hmr68LqSy|%fOSrkY^el3`a@a)f5&l*BF1rgxXG7#hM41turc9m08bHa0K?Uo0ur6~+h(;8HY>ACHuI zc<2k2Z@#-&RKd?{S5p<+Hd07DG{nf#+t5J6jZv1PYt&yUM00okCx&0b`2O>d=DNh* zh<JR**4xODfC!cSf#1L`2bk$( zuxm5V?`0lQdra||eq9z?`@tb|kNc^%HWR)N;fu-1pQI6GrN8dolfHK^tIRGW$*8%f z=j-66M9$LsCx4T)0{07GW`L*7eg{++4^Q@jVWO9L>BGZ00fB3%4bUFT} zz&O;VLldTy!!8`8dNrAyoo;b4?-TA|@r1MWpXalzv3pAq&>!TqV|NiDhBUEiz-LQo zZ);Ol{^0AswMB7<5R=hwX(Vau&jXBf<>xmVi{&guCtw>QSeW2rU-gZ^)7OvPUYk%I z=A^$Lz$W+fsU28`rk;!7S2}Lgj#Y{)SaBUmc?Z#YGg;z*#{BL;4|6GN!z4c#ygl!o zM=>xElaUpDj$f>;e~9`}QKELZnUq^u+4b`QO&HnTyD&W=p%>3epPcaHheBe`B#t+% zE6;?4BKP(xl95p|q)VgDDTlQ53mkuQi&+?l4i692E`wXrf`emrTno{99=4!~GB=~m z?+z8hBBmKY4#k9r3!GH`+M2r}C?q8LP&8BVfkOO;0N+S13)4TDiy#`Sw@2G&e#0KA zbtmXjTND+ijfRSS$B8Q5thL6SUtlpw=lM0P6^uL-6=QN!;6{7quw%mBOY`u#Mf8zc zTbJ!)N$5W9;QLywo>}A)B`}AaM>8&xGgEG{@k7SYpi#;+j>1=ITf3@)x z6nsrep=|)Ed+CoX_EZwS)DphK)09Hhs-Y}_FR|L%^^Yvh*gKg2)!Q;X&2XwrMU5qx{c8riA*%4XjebY*of(hX?}~fl3a@i( z8ZR%)Se44=kk00t8KSQelnL8MIbyHuM;aLY+oG>a3^b_WoJ=OH0h>*Yie^X_8hpX#Q8o z%UbZ1Po~+Kjo!keM&Kj5wli7TxbN|dUY2N|u!Q7HZx3~oP~+JPtAgU z(*Bq)!E_<+ZQs^Br4+7e;vEEH;TlyFSODC-LFSyZDHG&#HFOz%NTb#Ij~9T+XGlp3 z#5BrwNMX0aIsfnQV#CY($B;Mlin=I5taZD!g=utr)qQV2u9)>O+`znakt(oh z@(;yjL?5VQ$C;EBWM)=WXbtA_AW0f%a1)mq;Txox@SRWidg@6_%a}wMu5)1^c2Ei= zflc5ybVWEunZDPKza2NsNd7@@*|`G)h=1hd;CP|DP@rNA2EvN2h*UmrJs|a!nGJ48 z8k=V>zm6b33W$y!ug?D05m;J5upgrNmuqZ01c)3O^~YH8j2IZdAEmzyk{O6SS!)_) zI9w1UJpV=`8o9KbJgiJ?{fhI5^$}(c4!&!c@afqYCimGrs!g{2I?Ds2lb6Nd+#j1P zYlsFmCFWiuLENNx+AB;|gcuMrwS8GW{+=a_6E_&77S+^J%i|!0T`mZjwc)>&2}@qG zi4*rey64elw)xW|7X@YIuhG#b-HB3mWZbQ`?s+&lX?3{Rh;aOf@osdM;y3O_^q1ozmwm$j<-9-@Vj<< zlvH@ENRto|vRA596w79zR8DX+0LA26es)?#)U{M6a*65r33(bexlvSX>c%N@|3` z8^(X(pX6MFzRK$|O?~}vIAJZB@3ws${l{Rt=EnQ>Yvay5=2P&to0xysU_Bq-{Q7}+ zXy`2r4EH3X=U#2!4qi@F(cefli5N^SG8x%kq%FwLkn+*+BL>rsAG+rYx1P)wSK`jD zScbaE-&0dRO_A%*(IalcLUf7!U8eUGZgpJR&_^{3b-(ayqV@dx_%39Xx>^;EXVY@{ z!$Vg}ORk};@7xLC7f@|}X`aFD<{B&))g0)5l5az{5kLvaDt~VipU`&fRg>!}-?gat zPE#{Ry(Vd~r>4y)K7>%;x;32T;{N(aveh-z-JqcPD|q;()7E(|feYH@%Ac|sS<1^@ z8x&NFr&oj?x}Dfr^xpM+IbP+~+*s7&7hPLlFOw)qZD&``5ilCXVGFFDAQ7B#R9!+=$>Ip`=hM%2*gm+}* zV4sOu=TEQ8d^7qnjL@6L^Y=HACcTBvSb(Q zH{Y%r%0MM3EI2dL6-OIHai6)RwUAE zKX;g^*j&E2S5tG(_d6$7?p<114*Ev@pBFK>x#5RV>#zGMq$3XZ9WPOG?QEn;$rmV_ znj1-3@uQ69@9Q^RLzz;DEDmkRQfHd>rv;~FWQ@fHA?IA=RbwEzIqudIx=A- z^-W|c-+!c3ka7U2l_lsv@A>tm;ebB4ik0<8Ukp!uzqFB;g!8OV zy~tKi@ts<)Qcj@Ygc!nDa^w^F$vysWoKHsj%>rnpet_%d#L!RZUEOeO+})*9SI;Zp zNluu8bwMPRGBL665c521E1QKwJ!gP?EUR z12-NlzER;e|Gox+ZF#&Bi$+~fkDSj^5RbZp^d9!*a%msJgg6r~tqv}pnHx8rJarS@ zkyz>@OItJeZnj3>2Ujql(nhnd`=sMb5}X*FXWxD2_o2O}IoD*izyn#FP^GqnJ%t{?FXVWcQV_lU45atc<>kOZ@xy zD0YrVkK}FQ{Ay9^Po6-wa5BY{SGc2*_p&*tPVW6XOqxP}c3b-nkIjeCCMrhembzb} zwG}j^G1C~?*g_tCS}y!Wk9b4nypneuROb^C*2?FWn*=((EwjgIO5>GQD~g%?FOEEl zZ(v<#sWviUG$510~u0!x(|>#WnB9F^|nsl{t5LS&?iz+$RjDA z-IeiJq!4<1r}?N@)#Hbjx_{jypjB40Yt=6rh&*(Kw+2eMm<6d<*BnmK)K=X=s_#4H zVzvYFK<}v9Vwr48YEOL46Ed~r!>7;nHOmbRx+XC;atX3n+4_&HE{IJNt7*=Ql5+EC ze+i0bwWSha7GizkL=#JhvMME7=9`g_lFHV!G&eFDuM*L{YrVep@pnnuUG98-I=b1J z$Nd=By}TNpM(U7|ss!HF-$OIG9)68C`Bmow;RAgd!W*!9&;5AXvWTp{Da_C)MY}TH zIpWzjklZS^q^@*vaMID)UL89Zj;e;_ipL>GI~4h%{)bDv9#p*!YhM00HqKZ*(BJrw z6Z^wZ0ko=QGO@8l?~aq$$vztZY@!m0e)7n*>_z_wi>mnj>@!=H8e3FzX*D~vD4gku z51XFWdBgPPDW&T(3E}~CO}P5-a0c?2XLdLrZ&`F>8~rVq77H^<4%2C$llk`RDs+E` zL&)W}Z=^f?hxt3Pqr<}|&g&^qo_iFnZb9!5`!{4t&Kh#BhE0d(tT%8_tBj<=ydfgx z@vqUU@!wfyVK;@S`Qs+`%8ME9ifWg+?f`Ul-=oU?+XP`zFjFwSwDs0N=E+?qfrEUx zLHpGi?fFlJPhxLgr_-sheWs_N@ZRGKLSBy2u%j7^069{mJtRfVf@9(4#>76j*q`8P zFH&~$=RsIgKr1#5#r~E%)MuYXCtjs6EPff5 zJbuxS#%`gk(;B^nuIh=H*JZ_WhlPaKdn<_ek3Rcb92S}pogF+Wtu#EN-b>cSImp-C z+cu*)@yP7twz`1WCZ6qU)K0vTr8zqH=&qegeqMocN`&cJXRNjpO;uzD4h?)83H2`# zhc7zEPnH}O{yg{Kp4l{7Mj~zZ7hIGWEJoI2Pv0NwRz0Ehj5-$iP&fO@#q65q*7h%b zOGlT|!aWtY^gR6s)e)FpYqH@Tr~dmH?Gz#+f`@q2;+O)*qn#JuPI~&T5uviRM9C=j zUT*X}OOL!u_krWgZT7^g6_aMyKFe|p^K>)A0=a^0jv2{aK7uqqNAxEI*+p)TEVUgP znwi8x>b7Xgu0)5k-@k>4iS_#4cjXkhZ%xY~iBS*lY~8nrq80%MSt}bGyeeD!<)Rg| zkb2Tqq7MO#{|4L?dh1p?_x}1XCME`Vq`IbKx{>Yf?*jvtfz-3Di>W~o)jI!5#BWNR zOjtq|zLc3Pn#(^jI+|f+_J7I3p$&hQ`;iQ_Y0jXHh$KF>B&P2N*XMdPeji*PVUka- z6`$4Qcz(vG9HB&Rq~y)L_ffE{IAnZgm7ez(F`_~qpG_psZhGu~6SCPop6eV0eKWs@ zb6`xbSv9U>M2u<ch$Y`Nd%jH1%CwKN`oO3HXfPN#np8E=YQvl1O69L>EFVNBC^HVtRe@`(_p> z1$9L&+J)-Q3Z9U~!o0BS=AyT=Lo<8#!EzgQh|CacEMe{BsmIB+`3Y(}W?3fiM_FHQ zwwOStx-U&7wNeav2)0>1V!`>CFg5jgnQzG)g{i=ajW?f?jkk;p=dr4#_K2x+pB{** zJd!=NDkzE|k?0qkh^SQ(p+$6Qrp|^w%b4*=M_dCTUR$lSBkVVHk&d&$1&q7#S<9 zwnEQ-#FLUHw1(wCDZ6Z*aMQ=M3-9`LlfQuTao`R`)osrLD|^LmrzhrGfvegf9D~{F z2Ruv%^=pOW)~ajd7v8+`$Ej33NNTRbXa!f+h{C~z)ls5TcM=JUp@M_mXcx2x#eIup zM$>&xB&wOCnrd@%d#Gp!2Le{HYblg>ng-(6Q+6$}jDEB_%0`1V?+KYGxT%>>w9Hz&A8!~L@WLsUTl2#681gDRTd4ZPxp6xUd0OC6 zm&_4jbcSC=AJ%GPvjrwwDTcu#na6)rN%c3wG5pkw(xHo7)+T4+ffD~BAt;W``P7qiSqi;xcveN}lGY742Hy`i+J zlY{&Wj)ALh2HTe_n$8^%IfwLpZdd=UM&*0W3SL}=qE^k?elb;7@~Q}S&$;hl++aj+ zArz!TyoN?aJT7fH`V3WEK}|o`ULz2x#v)+ogM+MMuP5KMA!gt+M-V zIc;}dxh^oFJK>kd=!$%^_BZ|0B0+FKvG&(&=SLti@^g!FWB#wQF7UiuGIlqXQ9_k4d`h687?jd^~ShV(KK&m_ab zaoSk^qmnBu2JT=Ga}|a!jAcnV?+7m2KGGs<2&{fdB+?X!5Mmf45=J}vD(rL(@izPDaWYwO?Z z^`@$KN;X0<{8IuWaeP-z4Z>{uLfkl`-#t!FP9kh3)c1Sg*>!Mm<0}Q_wmQtg8wakm zZ{IXcTk#J?QX`hc5h(k?hZAaEIUf5b{Ckmbm8h7VoT#(621UP07Cx4c?HQ`M|g!#=mO%6 z>EAm*uJK@t-|;pUETO;GEF9}8-p-HRu5&BPxWU)Fm~a`%_eS^4O?D?A`~1Z)7I@EL z6JuFfIb@+>%~G_3{o*-1&}H_|2T~hRTP3RvDnMM*5kD_}+t&GoH6ASgU0eTpkDokl z-NJwVz~-$JlXe7M`#%f4`W%ko;TFcX3}5R}ufEDIT={4)7d|kiRyG2GXz%>mp_FVJ zlW-1Uh#GLSrKLzusqf0EDaof%gF(6n|AVa`eIdDo#HpB3-Kl!+GF;YRhOKA8JO z>K`MzZuvHJkB{s81G!vaGd$QLV>KWc@^l>Y<{0uUk)-?k8o`kqjc|dtZ*uZz>&QQU zZb;9;Is=XR!WJjBmVf>O04P%ri~V|AR+74w|3nxc9`^zsg`KP*aj=()+NC|K_ zUCVP?1?pE&H zpSQp78OG6@{#DMya`R}*2qZ?JgEr^BrSg_?C)Jqy(QL}*o(!s5C!+Zc5VOG05y*7YcnK$QqZ0!D0Hii(Px zy(!Pm#1tH2Bl*Cn_?a@J>(Tc3k5%HL*L&HP^c5ljP19ad!awqg4riu$xs57PCWWvO z>mIH+IH(W4`P?*~9M=GU8Tq&c86SJYaUVQil-ey zmPDwkv&j?_Zo{*^YN0;iLi5Et>|x~x85YF!Rr7qVZIxX=V2|G2SWI#*fhw8C6Mguq zvw%Dwd18)1^K$&Rm|w!z>+V7%JZ^JQh14FBm`=-CxnMq?Y%=@eJE&^L94(_l_LtE% zM*(9>DVMX=kFaRI>+0$jb&a+L6XwD80mrW9)oXeCzWSN6a^GIoWyEb)8yoysOJkE? zT%3O*BUxI-|AS|J*4CUXM;#xnEh3nyP&W_li8PR^>WkHN877OPokgATb03n@{dOV?)>cH1I$vZt)e_l78V)sY%;jK z+qXkej7gJBPY;qi{X*RZo!}ozgo>$WxQ2pZm7!YD>n1|&p8nuoSbxhD%%Fo)YPGQ5 zE!cDsk&35#_a2Gw5Jr|^65g#xE>?%JqO-#G=K4DWFV`3lVu@V_p}O(%eQF)E)>t&y z65#uh%+e>^;Jh(4JMmLBXA?sV2|Q4haqDtOSE=zW_ZVBjXr9+Ybhg_W^sh z=jW&oA82JeuIKEE2?36EQ11)YyGS7iqO~Eb3O65syYj`)N~IP5@67IFOy9%9<=#rS zcOxZHOT#QXIn_fb3H9NzS(bpZGD^z{nx*9&vq&PEyIZ>}aAerENa*QxZt@-iD3a8z zk4;QGOkqkeJVZ|=>Y8qpLmZ>Cag`vCm+rp-GdaNakc+KKxy&+8-5y{rE|2;ULgzR& zuN-t85u*ZP_iaw!B|%scaYpX}@z~HPq9CGT`-GJS93pj5&2eu}fXo*7l9_34;00lT z`K-7^Eh6Nklj*+HyjAJWLcX99aj=05S&_x^?k|slz?(vPyT83WKX+ROD6ipRAml&B zC(XhPN)r*S^Q+Sr8Y**NzrI9$0J89%N>)#{E`#TzSR{w^W2^NoTF>U5-j83tFbD@M zakBi`ouc#9!=&Le?6kT8)7^lXO#2Rn`)Ixn9FpQ7IXClZjLcI!r?d zt@m`=SzuGUNh~4Ye{*MQiY_IHR#&Iew6~mZq81ez1h==skXQBgYXV%??fy_;)G(BK zE53arBI9_Ci~Ab`Be80{v8%u@Vuh}nqpJtz1X={%=CN2@1E}`=!-{zCu5;`sS^?kja$_JBe?xO499Bj5^4msOv;ELVJ zZ#}s|zp}*J6hu3Of&hvB15duAu)fRDmrPc5jhjjeR(<^|9};`lEN$<2HYX`wAG7{T z7?jB>A_Z^Js2ww}A#0=w-)# z7JJ9@cbVP%K$g(bCHY}!r3cR*tH@ojAtaIp_Pu% zN#D%N-OGpq8?23dqw&@-Vw7KEV#qA4vttH1)`|?Bk~Go8rN4ZCK8v(W>BR>UCMIbQ zUu0SbKc&^w%%PJ3+OleYjTunKV^MU$vc^jfcmC} zMtZ<~W@eDLIbBddyWt`ZC8%>|SxEe}%lFVDl~RdH{<1rQ)KDFz*R_#D_yeRZ`s6bW z2ElO~>B!-sqE>cqhQtB-j@{AE_w0d`023qE=TFeg(SWs87caesos0?NeNiw&dBslX z{X0CI+{?Se9_#_G!A37ODT9Dpicay!P$ zOiPPeUthkEPcIzsBL4t(WtqgLC(aS6{<#|8F5{4bf;gq%b|jR$Zw%A^hZgQ_x-JA2 zNqx+j-8h)h(wAfv&H8@$fQk7n?Yw44@A=p76+2_sHV)`{nNiR5^c;(@5DT>D+^<-p z*LcRU;>_gRSwSc_ZmIyy3$K6JzVq7R=J~tWh{byiuK5CPAN=S>#DE=tgvr2948#J) z-(M=dQtrbd=oHS~_w+G(rTjI!uKP(oJ{3ZY`p>E+-~hgsdJizUtnBGM*Ci50(z$rt zhQ{9#zIbk$j&%5)PVQ>aBJ*9d$(|GfJ?hG3)-|qI-uAV?as92%fi5f+@ag0$C;-}G z9eM#XBsUy^^MWOGMb0q=D*-#4tM`?d$DV+VvZ@mIz6RGr9hJky324K(jEGy*YXD;6 z->hJqsBfMu@A4mrT5kt(W=DtAA|u1CO0}>9mCaw>@CHe35YlRH>F9>4GmguzF8sFp z6HF%tjZ9Y9FiP{yo#4wk`QTTT+ebJo$ZKvb@#Z{BB%X)6np74|(coW-d%e7fyJ`_fti= z^)m(QyqcDD@tP;_q?_Wz5PqS)6;&`NUYw=*(A{0b{R}WFz7@lRSJ~{ibuj8jdR+!a z6bc1Nc?3mcElo{dyfUOG!46i6Sp-hr_!49tCfu*ke4rSMH;W_3cNhLn#M%abP+!lzmGN-6MwLh_MW^@725%y|&Y z(uG~)uFJ-q`N=wZCkj{?qHNhiRyKC=CV~R9;dl86nA~lYb#&N+X6Wfs0M}OyyI+QX z6>&5D|KRb~NA9O(W+m7C&_h$^W*zPE73H&VFv@qQ{Ab5iTiZxNLXx7skdR*;k)Tj9 zxsb{Z$rCqM;dcSw4)=M`(EukL6xgv%G>$~*HE;EiX)IAk^yJT{0U4ER?zrtjd$R#=Z)ebIuZ#?E)rixzdN`}`cE<9KNH^BTz@X(*w|R0 ztf2``uVG`Fx%XByK8%(VuUx@b7P*}5>l;*F?hQHCO`kCGXT(o&ZnS)9wp{q~*JoOv*ejRQ zUg;>PF@E$-V`Npf;Rom_v?$NjFX0DiH60TNM-%&FL_6jGNe<020l%t2&(hj%5H^sS zn&uwMd+mRbO+Q2HSBI7Mx0%FyzxWW~v9Uelvk@ZPk8U~`m{+xHOG5g+G zJj&!J*xR&syiWARC-Cbmghl)JS5CT1Te0Lgk`qu8Pj`+o6@0?Et!wK7i!8`?yPM2? z6Ee|DGfWS%U*)=ZY>$;D2GK+PT6W&~Y)_%|xAXaNbic4*SyQ8kiP-ZvhjnhRxrUaW zd$frIZrY~*)8YI-f%F{7ubOKy=;4qou`GE@c^e@%@xOYR&-s4@8@>cC?q!HKBZ5Ur zL%(!wDe(|*@QqJ~iy_=S{bYIfczf;PIan4a^Y{G_TzeKo0be1^Kcf@%EwftbId;mo zqYNUAMjkaq()#>`dtf^{F>`~pokE!A97^RssbN2(%l^-l5I^>vDpg5O2Xx> zqB92>+rE?X;%r$LOKbni13XIC)$KK|z`FMw(b0ml+C{HbYPP>$%FW2YIm$B&>v)Vc z1)rW~TpLhg-`)~ecXdjinq7w<$@Bn?M%TwIfmM{^yZx*UeHMb1KhFWpq4#9f%;RKw zaBi;WhAl%{eKsAVojdhm%jj&Ot7;7J5udSgDyOsEC4$gF0UUoYyX(OICJLrvEndAT zPqCtS$Lb%d1>(bwi4coj)DE>Y(^rz0kCSa5e#@Pom#^xvZvtpK#emz?;&86C7tGbK zp6D5&rh2bu4Cs{J%(FU40YaA;)p246QrY_i6fG)S)c3ywB(wG>16bYv;qLm=dglRJ zsh;@k;k-_vp`xCGl6qV>FY|JPQ5QLSZdEQu%+SDDJb@scXV|DO7 z67u+ye7o3*DjNroWdl=N$PNZn~=J2MDe^O3@(xNkerS^Z;zI9sP&L5np@*jZhjyc~D&uw4`Vzz)Q` z$4|znP>;&(E#^D$9c|i1!J-=k63BPvFgZ9nflciv|8lsC9!^=p+=Tj%kq$KaxF-NA|32&eh*(jZvb#)aWz_x${QhGceQ z*oF*f(t5z?AT4?p8U}yB-C*@BgsSfcVE!0aU<(%#0+H zlR=|Z!wM&T4KiVTKvjR6_^bl*AkafRXxTUX-4BP1Bj*e~9WQ>R(6m3jL@m8C+NO`- zF(n972l#)~zhS-6GvVJugDAko6=#$ld;n{Dy89z5Quxk2;i(6X_3u~5%HeT3+e6Ai zzwkBG%e*fGMfc64?N@&DtnB`*lk!ZcrKS48admqwL+cjUfH+Riu>t4A#`+M9j+i*Y z2H_+?^d8F3lN0Zvyn>Qrhu5H?Vxjjs^~%_Bocp7t=QrkRC1&;z@*z?5SDZKA!gJ*> zUcFj1R<@$4q!di|al%M<90!jA`Kk@A&OIK^=?=XTj3jni6*=eZaHX^TM4^5p&}(4W zcV^YO3gLjIGe*RrDkleWg^JD?p&Y|tW*CW?0B)Z>S}0n;rK(8q-|W`Q&yqnINF1R0 ztmu@x+cC(tpluBQq-8Vs%a=evYYttMV0ZSyI*i z-(1yDq*lL)Vgs0lV(NkD2+N*)LkofEJtFMt(H;MgkR-{UgB*7eZ=Ba}NO6oM43)jh z_}it|4Skv5chpr@Xc6#G(TF!NU^UlD?k1rccg_43bYDb?IGjQ=!Lndz^19&hlX$w^ zyn?po=Dt+li0TO-j_X#vcy1S@*H0N+T5@RjzdUi3wptb3nzz4u2>V9*|0Lvf_u8Cq z&hfpR!SsNO4OQ5pASvRF_szMw19Z9b>IC%fIb*83yS?U*;tl8cRjJk{&{Ww2mzXxWZF;UlH0^T88aE~ERar2(c zpcO1K+U^&jowHNy*`XN7r*A^r-$+$yHIj3Fq$>S^IwuswXuGETDBsq|GiRM`MT+G> zTc)-T0x?qf65Hq($?;nA8|l8 z#g38)!2?{o#vsl41JOHBb_|{Iv~Pc8-i7ihio12T* zFw3&glkl$R4jUfE(|4`hcRiJqwK8D=h&2JO&;IF#z-CVaPMy@J3df&$ntaiFY5t!h zcq?cSH9b>f#o_Aeu2o#R&o$!Xm`NN*q_>pfpEHw?=ss`ZB?L#YWyq|%ME|Z&R+*LmT z3OcObA&Dp{E3+6Y3&}c|0Xs#vBfv-YmIu#%1v9kt^B(Q3S&kM(XIX$(!EPU^ldLR$ zpivD~Q$s^|g>&;0|HO=%cz)c2v%H~^aYBUS*x%;YtmX+*uw3#WDL6 z=SSw|xaH*A>CdPu_Qv}_H`Ht}yR7VV%fN7RhN;dQ55}`Mx3(hh8KmbOz^Oxzi{t6B zuGxUAnvPDk$$Gz(>f3V~Te$NQBE7a4_{}cAsXpa1C5FrQ@9&tsTAH3Fvh`g1BVRoi zpB7IwKouf$!&|-DJzDIxT7hx5QEZmW=8SK6xH_;Z(9Q*bA}O0jfnJ?tMDlQXGtf*Z zRQB-52y>)NlH%2?SMhzOr_Ns}OsB(b6D_UZW91H)F8&-Tq;}uwSAG2WaYqNt#PFJ_ zM4r!Hjz<%M<@|W^QwslgIih9QaCkV0Y2ii_o=b=V$G<7GhF*V?Or?@53Ujc&zIiO# z?1f@r(X=~7_3YsQaPO+@V4e%@j4vz=VQ3m+uH(kk<)^)H>o2|ys;tDId009u1{+Yu z-C=6C0{iZS25!SPT=&8c3m|_6gZ&rZz9ED;4|59(7&aPK8Xt$*IGF1axzCvjC&Y5d zHev0yTr$yU2E|l?`8jnPXVv`ibmuh*ZQ)j*q=WNe8ZU;*T19?_bv!Emx$5GNjCoK) z5xWg80CaSDJw0D~dagx;e$=rLF#!Ynq)w0?1MJPw#pUv)HjM&fuA`>6Eiz{;)i5lc zrKMw31I$E-t_rob7ZAo-dN*sVUf=>Lv^ z$vMyI{{KFGA1VG>sQOBuO_NQL`7qg7vcHT)YK$KjQ|NV zIhkv>JOJX3S(?Spj*dVMr!7nl1AY7?zo-_QKBtD%rgb3T~^tA2@?hVJI-IWOs9vgKBqV7^OPcU)gMzxhKMv->yM~Bh zYDx1im&C)xJGl5(j5*kZ4U!sfNOJ%rheN= zEiOI_Cr`2PypMK204em&&dy#tEq_*-2;SX6tKzwd%q{EFcb9Qsb>UMUW~HGp5QW=+ z$A0A`C6gsZ%v{;3Gc&b*a!N`{M%)K>^(NZd=&6#cv7Y0tap*(IcPG$tbPmea?+*1! zA!|Prc^$r{X!o}{<@{Ft;94RIm(PJ=QP~e}y2s0t` zTL4oh-0|OI_Y5_Xo@Y`Ll>$CB4gU^83!Q5OTIpf;d z7l~Aeu1Y`b%THUA6xBtjc(K`Hii;guHDUTD4t27Y$i~7_M_qqjP9nAm+3Vnd{{}S0 zv~iz?J-&-wTJhwLUO}mW)lRqh~5F+V4D~Q>W z9feVk!XH{UH}3v}BF+G}|M??}CN%rbZkB8S+Qp7btw2aEhZ#h6opSBZDKx4RexbbS zT=BlVqNJo~uK((b*{fHj?3ZJxZ)q8&;_5N_^^8E9Ox)UeFN)X+`Cs7A_7+#d{2WKJ zfr0Gf$A+zz|23BCcaGmKmA-zk-J$JCxRE`k^O5xCw{Lv|6_du7+o$`^!a3pWDbLT| z_$3`VIS3$A;gH_=7G8Sw%ABW9ez~4~lu+?)(Nhz7dD)lWkD>&27KZzpnrIh3qy@@F z^NK5pi)4x{U57RvnK#R~V9RTMX<~9d1F6bq;H$+v8yUIfiOOmCxSFLlyn~G0^n~&3 zHV}!lcF4&efnaYWlTRQgs2m9SrpA@?%iPuczwo)jkPY0r*Z!XhC~FoMQ)C;WMykG| z-TvJR;AAwot1w~g0semp56>Go<^uLY0P!r%`) zgUJ<<-Hw$Ox{DN%52{ZzO6zOtXU6s!(7Cw$hrRsds+CY5?A-2cNOv1w!_DdQhYHTTS7N`R2H0R1~2iD$N zJhZ5y-itnO1~WF@Y1?dXdx8}Y!OS>~#N*m!gqzs;z!xzlM7-nLgge?k7-f7(TVI)? zQ~owkjiq)PIC667%|8%Q$7)Q-gP=I;e>6k|NIWQW=MmhZn>um_DU zORoJ+b9MS>E~*jxw050{LD2b9X^GAa(K|vs1-WGwCC+mB-9}%jDJaepZf+i!Ai9sd zuaZa=fsVcNrs{mu=KK)vK?->Hllt79P? z7KhtJBg6edr_7tN!iz^^tHrdmw@!S#F0~~j^)xP(zFiV&_wG!6dxlCk`%_K$W)25B z?Vz-)`X_{GJk7evFTXPvs$CvBJJ+I9b=ULj{CulURROw3LVeIZ%Cv8Y zCfpW{A-u!EEI9u2#Nah2&0Qg$ZO5w)PF7YH?HL61Ed=-h=m8NVzgjQ96Dq#6y!f^ammlJ`XN778_~ldz$Y0ySIGe#p#8e7Z#9cAox8AF_e@#y>JqsKJxtFu(GRB z&{b3ay=dq@@i?knWb2M!LbEOG>1>yBkDGTDk{`p?ip- z-wi&$e%|Z)uJ^CcKky6#%sFT8v-f?kd#!a(y`61zvl=mYDY3oS*$w0SH=aq~dP{t5 zT{k;BSf>!UxPT2{>gt=DpJ$<*%Sr2JZKh$6T<~Ac15)=N{EudAYq{aX5ec~o2g8cz z?e{(xcyu2Y8VMwF4;lL3B2L`M3kzeE-wboe-HbcpE-6g#6&p+;>!@jP{6Yy9e zYRg$!uC(JK(`CLunDMWdykD^q!NI_GIPC5tl-^#z2C@6mI@yTI-=C<7PIkpfM+ba% zJ^B+|4=ipHqPCIFY*Nd@I;S3w1Ap`gqQi+iH=dA>Jnu~@rDVY%YB+4rfP>7;*t*d~ z7%hn~iVbvD1f~W9S5zxdx%r?uP3NVQTAdGyw$|Dyp3DXeMgN72NdPScz8-_o-)pdL+45DOH5-9sE;!^|s& z>5}q?+C1}JUcNZqX0sTT#3WE(Bzk$jbbUxPcCNf3hPA$0-)O25z1oR(3Q*OqO(`A~ z6x7@Xz1|qn?-vwinwq{87k}yhv-Oi@I|p}Br*!E$PceJ?c}>kad2CIM5?T|t7G*kU zos0PrIK5`OosDoyi>k-I-x#w<1=Uiu1SIb(FloK}ZBQNq3`6m%6YAl|<ART|=zMP}M5RSpAb3t#Umv7z8C==;Uq zGQPO1VGbdGpm%TY%*Fk5DZy=kcSkUFsSh^erYk_}J2;V{ko$%y1P(E%&yqIMe}6fg zJ#=8tKdXyVanE{jjb8e7ct)11$L{5)SvSeveK%5}rQaLoW(MimvR^ZUg2bdRT0SN6 z+e-+zBbE!^hNV1d=E{GasXs!Lhf_N?W;UAXxg9d!#}z7}DxvXl+(*y(aJ^LZ{_0w! zf!XO?1{+Fii`iZ0@%bwdg68m{tb~X&?$69dv#W2Xixbv&wJ#tnr|UOoFwWq(ha8=R zeTQ;Ou-6L<*e)(ckA`xU^E;7W5o=bBL^7E@h7Prh(qO(G9!k)tjG&@z?(bntO3ddd zEkUr7j|q~CC<*MC_`wso@;h6QWM6_o>h6<{3s0q^dqN@l;>8EKcgpXNFF3z+V5~iO zP3ioV+`4p3h*?{kx~pqbHfVLS9Fvt)E>{VdM!FcCKDgz~cny4b=NZV?JC1pLkObt- ztTG-R`<+on*EM!ijW{}b`n6Sg$Lh}{{MQr-*P{ySDW!|;HE<0-SKwG3o>2oUG0goM zmeVx8pL6Euo1ba7z}LrsNn0#G?`s8xgKUKFajxg-3AJ=?qxJvI;lArj&kP+|R-k{W1c51;3 zJ{p>i(NShj&We|4cqxQ8Tb^?E9^!p$czEh#qP^zWfw4R16-j#Eux@=YIX%LHHj0a| zhtpa0jAbciqstHHD(Y8TLSUSZYWO51Ut-YAe|}3@u&sv(U$|iZiRJ*++IR zw3PGa1jG_x#PahII)5T@&^Q~BR9AC0UCKc(zPC4Bg={tRUBwaF#0;}VBwJ2pX`${k zG>!}ukGg(?WpGgywh&!{YbYNcV>acnN+5gollrN*xlyLqpbT)G%gluNV^A_I;or(5 z?@0GtW0A>I?4(wtE2wa05Y3kPfO>R9E1SsX_yi*7V8+Xvne}yBjN;yvc@{8vqf%99$Av6b0T>=w<`68Ts}M!wg(_@IsF@1(Se^zmzuwmkDj8E+%ED*t z>a+}}%wu4IEan9Ao1&h*m5~1`ym>*c{50(za2|=4%=gr~J`MJrzQi-=Ol?Q#?UcHl zgM4dchSBf0E&vkZ@84OzKAoN2FtNBQ7K+#{wxh+=ziM*XT38UB+jmwGF*G*rNb03Y zD}mFRAP;~>aXVwMiR9G1k2#v-tR9Cj6Jz77b!`_OGw02LU!Nyd4ZM80kF45MXoN7S zgfKbZ{Z>AxlUfNiktGYk6WN-p?`el6H?OjxaO?yPY>Uk{U^{{t_WV#~{hS{`@^F%f z$c~nMVJgrl2)K;~yo2qx2ZY_pS79YTA*y%OZPkA9K;G9Gbn~F9bClWoNKCL z>Zh~nJmYd%s0nn70oMyj5z9SbO7@~1>gdRl-ANJ|5zZ+F($01%YpqIdXSu8B9=!3w zjmzWNdf@20*b%Bx?|K)5KDxX42TSQ06^)Pwz#B2{-m{-5wS>F%dF()g^zeSH8a$_$z_pGTfR`ImXn`fGga>1J$tpXRn4mXbT&%iSnk-)MQYmY z0-ahV^VXxSsm(J>@oDZtOj3c~zCIEzTSQT!X29w}tewb6tsSjnb$=osCU5}5`|ASd z8cb(P(0pxbXMW~1n~xS9Zk@XT-4=b1CeX7$AY9s+CMRB`f-Njl^cqEWn+Wc@z@0S${*LxMjbp^Ayv*1As}ymd?t%SKM-^Z4p->~Z?QuXVDpm)XekaW2RPj|ISHhZ>5TsncXgkS4p)gx!<O z9A}ouyk=4==Qn@H($LT_s$M;{Kz!5jK%Nc#U2Q(i_kC_>%(CezU`$j3|Dwmh{k6H7 z3fBhf#E*+K zy)I4XopiCnJ1B@9`aC=WR)HO*NK{BZei1P-Ecur|kgDE+t`?Qa%{x%a zG{9WP3XdKYmX))f_#Wi{qgabFZ8v|sf*S>8^|{DgUvdmWVs~lY;|kT&;<8YQfmN$U zLPVR-$!s_aC(Oh_7yWFZT}n{;09ldvEKzTIEQB1{F^rJ++!iryW}nO&4YhpTjV* zl9PDdE@cc4oZu_b6XfYz^m9x;3Z@bgl9_>x#?Kpv+S)$*M@inr=z^WfL^ivhFkH^D zVv0m1M06X^1FPMgp;`cUB4H#7*)_G%xPGL!f{xGm=+;gZn&ZtG3t@GJB?`QsNu~61 z=kN+A?|cN2Ywsl{_WxO@BY5OeH0t;SDL#K`AIWN4#L&!uW1-97l`C{-bI5U0A=z|T zl1RZQrwnMZ^1||*kC@{p>ua6?mSQ9=7nyWB%W~}T;YSBy%l`iEv5D^GrWvnfMH0N^ z=OUnrN|{#YFl%72y=$$VoAYc^afbv6$v?*~8&Y_-dlD{UkNBB9oP{hKPfF~%dd#R= zT)xI%|1SHJZawfiL>~k-Z%K=^=?YV6k!q_&oo0xfbnN7G{Pc7=D+5;{)5`N` zJd@-_oTEJ@Pvs*=$n!&aoRxjx9VueJS+i_QLUC&7YNK>8r=p?)S_bF>t#`**@d|5T znAdL@>;&r8Bw~L!6%v*`5Ev3Vf>|;Uln@v8q+P z*%0Ef7Jc*W{Pn?{6A_Qq$iy`Nd3PlSWhaiH&9}y3nZa2H6w@@nj#Ak;fs0@BbA^R2 zNXQ4k+492S7|LrWr89d6@)jG#kK>I^vcFI;&QmQG44j8PXNlT6N&HSGm(}3tr9h)U z9S6gDinySU$d4*M40Oc7&+r5ULRhm3(_+GAqJA&H`+KmzSN}WzhzJ%G0>bst-xhP8 zwPa=O&Gs%cah% zEG|>&_YvPefcHZ=;T|<6VMVBw{SjjZF|ZXftC=NbHGB*s5fNgm84()HHM1h~ zuaJ~`1_J9m^va+39JJVdenK)f+47;Pex*0~33pJ{B8@nm# zB+k!IN>MLc7xk%gbF=Fl&7YY?&Bcv|9c@cbl-kh=f5cyYGoW+mhhin5_wq+5Nmoqk zS<;a0a*hPJ{Dp>M!UAoBxqQ`Jir#>*aXvnb2S6J{bd-8pcTS6pHfA*kt&&ftB`0Uw z7;7eT$$mH7jzKx=YHuG<>5I>!w@HSLvCbu=VVQ6 zT?9J6G6zV+sRYWJYj;BJ*GFdj(2_rYHWU-5%tnShMwHKbfxWK(DX06y*{w?wP$)tp z7C30-+j8my89<3H_>qa7CMzPeXEAyi{Z`8ItFKpNnF_V0aMpP4EB(#q5D1jK zlHdMGn!S+l;Z&ugR7^=ULuP~$gJWW_0*h+5A+eiVXzto7-oWeQz9>e?y+Ma97t;g$ zfz(&$Duq!2BI53GYf~ZZl~tG4{t??r1)&C3MH>^!4AB9Z{m6xo@AW8*jv%^Pe@YxG z_OZw28R?4`UaXq^@ZFNjV;OUX+5~M{O00mkVjcC`+P#0Ia`#X(e)dGRwbSqKccUBb z4PHGs?4#0*NMLe3DmY2tCAPC0EH^R2m%s~AA9C2VEq&%WeZ<1=)OOh*dJ%@yPD=oR zzPKqyuB(j{B|7M)MD%3lUI`MHK)>H2aBmWy12yO1fY&1n)RU#9gF(2OwRKZ2Zt;`T z^~dWtjrOse%+kPq*8U*p@&hnv|6EE2w7W+4%Mhy;ji5P&76pB8x4xp8qPcnWOY+yR z<-1@XrQ>^^eqkXj&)As2a@@2zg4=CO4mX)99uh5?CmKSW$N!{%!_Mb5pbGoTHv}=) z%bI8Z0LAdl8-YvhsDUCarw!=VkKg2L?w;eiPNj^>w>>omp7w$TiNoUzJYV?@Pbx!Y zM7WcZie~FB2Fpgnjhh&JevFJ&_5HZJ^6@lzbEdse{5Wk{l~x*Tu@SZ>wj&>XM32ju zq8G}Q&L@m0ZHS!5ycYP2Q7qA zUQ63OZpt!iHu#}|N+8hzMJ@0NawHDx*w5a*^)e8@l|mBJ8qEm%^ImQnlki^n^9^AN zv&~t5kJOfqj^&ZPxgKsUgB`Ij%IPO7l+K9kIuc*LMB57@KsW zS2kw3d$V_u=PhmV&{$xWJTbm0EGP{a4IlzncOhR(OG=5N^$lD{KFA2R9&ZELmmlgS zx8W`%T;gE!BnU`liBV@)wzpN>Wsmr6KHbx?D0M5oB)MI0Y_g(cRFRpeX?V0#Fu>QR zZfCUh#p6}TjYwZ9K;6?g>nVrgx5z}rgSdxKe;P1yqbB4SqJdkQ^6Ox0=b&ZW;CG9C((pQWm-BRkjvK=} z_T{>BkQcCAI`HYBJJ?(mTNOEQeigifIMaT}kL4t6J7>+Gm}qXkr8gs<&Z;XAkA8MM zIQJ@0=3^PKV>%kI#d?2C$(kU@rjIB|J6)K)cevrP-OF}lxPEM|g5CaWqCWgLG_gOC zUkCg-R{yDHCsmMpThIcxp`I9);2Hu_B0H0nqvT1&^14$!X?$Vs^My(07b9JZ;;!~r zD`B$yn2=otE#F*8&8~e`LWA*xR+%N+bVH- za3|F?%MU9P62M&h=(QpF(&C`~Q8qXcRRN7xo(P9VX;)7P6qn)oEeZ)N#5i2FSpE-e zD@EKQhJ>f08gSgAMQn+5(OMMKJ|p7+rpfOPr^tkpi3F@R>9l8c8n7=1 z5%imw>6tw$xoCRE#(a7+-)eQ;FTiI3A-NQ;N6%RCnVthk2Zb+}hA)wXzerH!e`Ot; z8yKO-5k1)!2Jtk^!nro3J^1Q!$JS;2{4w`N7SggVp7T$<_xJ82f(5Umfmyi~} z1Im50=Dg-B%(YmtS5VeMMGVKivgl}KpxT`9_;`9)>$GzT9QF?yxYh#)GJJeonwuJF zWiAcdyDP2l2)q2K2%}}yZmKg$zU3E2tX=L}OCEpFmcj;lqd!jIVP%!2a6P9AXBKil z@V_w2X!@;_t8|Iu7~qlk4RO#FnXOT;QSRzurIYlA#(HwUvC*w+;F}PrtAYl71-f(4 zz$n|jXCO3ecsS)Cn?!{< z7zVVwlMOl^k%@(c0OIIUG-2oPnPb+{uve+M@7Eg3?J%SY$J1)SmJun|p{}o~aX&e+ zpa4u3#PC{+T~m$cA}&Uwq|Axy<&WYOk0vKC|1m4w=C4MEhGjj0{y&v1v4OhX_7JW8 z(>bRzOY2jL@NfP{=ZRl)vw%Uuxu$x=_rA-o~ZSyVz!J{%{!Ng_8t-YgV6^(<9v3; z15EEDlki87hsv|(;T?8E$YR&_KdP0tMuZKP_2L{9fgkv?leg|EB!AE4ZPK)L$upe|3 z!+!phkdo@bpcKPr1!^^LQK9dM6-HQNoODn(}0dpVj#(PK-077XG;sTyE}q;YI5>YGuis5 ze15%??sJg=-3G&;&6p3MKRJ4Mrq5kX0{mYe{F~y1cj`~zfrQ-0mW~_TSG|*PGE|~E z%Z-luWBog&doiuWWv11n)R%^YLm2|rd`hxw3kNSHr64fe~J9)S{dYi9Kko^qOnml%YT>*+YE=~~XoWjfM zdvEcL=aLcy%%OXW*;ccGbWBX=md8g!j%f^ia6=Z|+PaGvX;M-L;5kPo;BLP))hF}e z5*HZ6b-A^kjYnuibB3I*#Ri&>Tie#oI9h5}zuSXuO`pG+_fpl~a5a|apTdy5{ise3>bLqak8)M{^L^`#aW}# z{~fN8hxw!yr9thV))91?2=%yd^FLKHXqN#@#_njwU_3lPFUiZ2Z3n|_Jv0$N5T*rg z!oW%&8SerJ69Bc*M_nE4Sgd(#6KuberE^Vx6cm7@HA&E`<&qNgHBc0s&6A9@cXJ*& zgq!ocAEM<4GH+Xia*vnn@cKF}H8mF_WBqJ%&`3az-OBS9776o|(0ZrjR+}uDB%6y4 zX^J|}MdP&GkV5nbfx_0poHcb%?KH4a>X@luv-@biAAN>5@z<;&4 znPrX7u;?{?lMznmia6%rJf)XRJgI=CtIcZaT(MRoY4Yy(jwx5i-C*CxMDF!(_q$h#;LIN1HGq&C#^d57u(WryZF!_qBnq$;|95l#CB?k@_i+`=Y_0B0K=E_});m8*2LIoJm<>b40Whi}*KD$#p2JFCTS|L$rO&n}4DbW1!} z$!faEyerMR-f3fDdz<^b^;kHG-Lz*+A>mRrS4p*a79Zfql^Z+(bnFauuNOOXk`Aq* zfxqUi@>RQvBNTC2<91-M$$XRJT03I`p%C9+95|b4iN}7ZR?EW&=RPHA^Y)EggeWfJ zOSUK5f@i8sx-*$};!Zr{moGfT!${>9Xj%<^U9YKCU{B648PKeH=Q+Q*$B})xIi0Nv zq(RHc)i`r-0E~$o4A*gqR!cB^0)EpB(cJ^f?yd*NUqC`XQEAYl3SX9_if|9l0nlu6 z-{rwbfUxJeCk#{>Fb4~#VIFzm$kI|jfgV+Km7A%`|Rb+WhgyfA9*jYYw-B3mKIC(>I<1mlJly|bHxqE57bs4 zonA20(jF0o&ZC@PsPuFNc=0z7rzs1je^L@#0J?~SI9)}^} zQwR%tO$KsZ`)iKUKEwkgVL*P7;#rX*j2d^V~g)&zeb|u`}ceO5i0?Y(=sw1 zz4G;S<$5+4x1ldt_?RR` z-O(QBc#-y8SfKsZ5NxV*IFQe!;p+SM-5$YLsA8<@dF~f=3TVKvhw+*nC^v}TVes8M zW~Co%FkdPrf#3dZu^@HYS9lX=X~{cwQ-xHyZ^wvIKriQK={GsGSJa%QiKH#M7v5Ra7oxB?rb&OdY8!#binX4rsV#medkbSP0Vo4Li_5PdypJ-L^?SLE*efj3ZBRwt&|5~&W(GA28^KhtIGDa zNSjTqij@^$QmA`2Nv7UEG?N>Vqru%!ud>p&w4BVaN3&Bh*;#C5Nh;kth|0>Goc?QzU%o@_fn3 zu3H-QnNkyy(F(5_aM(~V=wY^*Z#qh=SH8uCdhR6@7T7Z=%Tsj&ga0Qz1FWyCgIy=g zM2jjbH7fIZ_ijU?)Qt)!OgCrg_QF&2&)&FG(OnLFkszg>$)At_+Z{>k{JW(x6u(6b z?1SSnm;f4@xvXrJ0(DGzOis55#a_NDxJ&^jr_48B!JYen1OPs&_wRw|uB<&a^2>K$ za!JWDTRT9)kfV>@Buq_v8sDcwTfEyZ2KSl2TC+C7%(zmwW%%SSJjnk-^{x^+6 zO*xJD)W)ukp2F74)w@&bmb(v-8yq>Edp74)2spWhK~%x}Guw!;B|h^cOsxeJ2) zO7u!X6cj9`D^BnXkbzKb{a% z#zULN{ix{?nee>fxD|L@qn$InKL3w$j7pk69`wI6?Q5S-%K=mo7v(oO)*I-Sku z>zdjgGKqX_Ym;zL{5GBC+fEs%-? z{M;AK@$2sRnk_-9w;(7=pk_6GQBo(L?n~yiZ+i8C40Kvi?!m(fujH~G4`n_(@;e|y zypWV!-_Lv$u)2`B8`dGKhquC{T_vgN7aEFcARabxUVemiqL}?ALCCUuAIIGFh@iw| z=Th`h(;(SvkW&hf7JJllZwkG*IGw?36Owk#;sSitr@#KfNJV2tZ$rMv=PhVmX1CZ8 z_+kXKiBzmqBe@-KKB3^()O_?PC^Ea>(D-4qW=&KhYsO0sM#_}yZg%f zlXhM>b8YiBS<;CIqvv<=Sbe~9rrO2A!lLUbq$x`Uo(eA??T72*eqOYJ{vSV{COmTy zQ~eC~rW8Dr{tC^e-U)dKq#-?zqQ~ohGe3fxbe?f@sLMdSKS9vz@+E$}9?=gd)N1pa zJiJ-6;1Ft9Nx3>0e* zY-{saYdIZE1Sp$(a%Qvw-#5_x9Iy{m#zEKO6*7PV1cYlfI=0TtG+4%uF>7!(lf3u4 zaC;d`355ROeium&>nhvI&q_%pk zhM_}JXiT9q;}hT_pSR}&UJS6M`tLA&^0WgQ5_*X-)ToIE3U(J~hn{fB!P|g8+bC?md-e)>E$p+y!bmJoM4xcO zdA)?*czxdRR1>6iHohwjcz=IRfk_vHfXRqG@bf;}&$xDic!jMmRvdpE-!08oxvFuW z{L$Q>n4?+@O&C}%Jychpe1yxaS!vs~APVQI19rg#0NndpI2|pSnS|kVc{=SYt%>J( zdw1FT?_ytX-nOt`0 zqxyx+&Ad@zEyP9|K-s+?%Jp>7b8+=~lUy16vq}lOFCF{wq3MAmtxTjxo9L^l-DQodM8MFt8?luFPNAMaBDx5G}%s zJ;5L_3=|3oZ#DM^_#bV}DyykITPb&8kDZwS`ds`EnwDjp*#%m*@7Wyzlp=@m1%`#; zus+mtJ&O1Qey?1uZ9)GbYb>S~Fp(%I6j|m6VgXy`-ZeM&35D5Wt5)zYCuFdkI|toV z9w^c(lVf{z(hmqe!aUTN}_=`uO@1GSfgHbT^U9bCC=s%+>~ZECK=` zJLA=FyPw_;&d%uCjTf@2%=h|H*Wh+YH)tD(CWQFj-!EeV&M&9PCK?A6-B{lFF3fX` z=1mfZ;PA`K%L6?0sfHT(Sk&u}g*up2e|uNB0*GMOtx0n5|7uP0S6u~s=cbhn7}}AW z)+FG-?8dAJ9B|#Vvw1=>g`H->`CQ26>yL>KU>@H6e%(UH+tBvZ_|yLU>mp4{13yqt z!MU|^eVJvpUELpnT>xYR|K_rl81(TCkiq1^LB5VozPypiQv2}$BKPU$?$+U_S}cBw zyuPu>GP7XW+Hm*%scVFO1rfpkXM&mln3T@r?PM#LoM9YZl-sG`H|=hIbA4-I{?VUG z4g<=~;3=|nEp?!RGhslzdF@M%BLjwI5N}B+JTD#!Apul5CG_if@p+b(23P#B zQJTQJdqpjG!6@3yab8b?IIchrb8MLla8J+?KG!+tKgv-5xLpmycRs2a0O9vvF&Dn% zzcG@!7?ot6Qe89<>f`N%C0f>b|LJHH3Luqj}1)itZHbrBziHfOsH>^f)YLRM@4b|*-kPo+|JjfXRI5<8o!s#T_Y(z{nCXcL4K=m8+FGn| z2`UE5^LFH+#c~C2V)7v4Es;c8I@a04y4>QmkRb&ZRrhNUXA}9A;1VnhC zNi>k}R#sF1*b{ig_;VH|GLtrBXPangCAfz8`T4z}k&=+0BUJ#hQbR*SF|m7^Ory_T zvt+)|>=v&SJF)v;phg1ji|W|P9q;O!^^lIACCo%8UuAjDtEYY8b7H;Z%-qPB3QIV) zu~ApM{)O8z{N#awaQBKilnVOmd!Tk?o#(}=9~xOVY@gq0d!$G^UiQ{7>;-Oki?xODQ?yjzM+=^jYA^E1bDb1H?GjRxgkI;}9LPU;6uw#7U>FM(xK9s?)jseiH;50bE}-9LX?&coKYzpMi-vIXTgcs!Ff5Aacyt z+oyM&sZx)8x<)77uoC=&%`#%3h{^ z@xUCL7D9(Sm;U=L?lC?vgU`C|7Xa~Px3hUdvEh2?|Faj`6o-cW&&{l8brqzDx3Bl; z?{dbDj>H@scC@sixw)~4+VUQVnmshfZ6>?w_V^9LpEkCc?yY?xZbCDSroTGak?%ov zkN*1ef9*+8%qK-Pcf^Fh3;Z`8j}v0LX+ux4n}&U-?H~~+@Giba-4m96|bHiwQn8; zPV3WXZY&67^hT;n$8SfQ4fa`wvqSS{+xCfr3i;Qvlc%6hP0jHp+a`3j&ck&s>5La6 zc4~q%o=wnkk%j`(VrrA$&NsKRvS=AbtJ+;lEHFm`fofmhmz0mM6&1@D7u>UCc`7R{ z0g`2GNae=U3%EVYLhcAEDk=`Xx~?dCqt(fO659gY#2Wm;%B+~RdkbQAJ7EP-Z64CR zmQne`H#0NTFO5wkiRpx2*e`DlELZ95f)|Xh2582{zDmbM)l3712uB+Mve+hTD>{hI zkZu;#w}6iIha#3c(d`^9X{2weK6#7yYIUNt#(>D$N<;Xqwh;)=;2Ve9aq_PV%@)PL~5Bxi=3lbWAM%f$hMo*elb z!j6mjzgS(ln_H|v_YkO4_a^Qu4OmI|T%%^0^*T{agoUe<1h?Y3m78BQiP;JMlgLHw z3my%aq)TlW3W!8*W@&f+|C6vK1}rTfyg(qA6JEjs{_E~Zb%D3=Sm%tZJ>z$pu2yaA z7@0ctLL)0F7X0{8;N!;w&hhpp)ne{JdEZS_6Ft=@tP*(H{P2Xw{P#ija`!q`PPw;W zTT_noiS|;+v|}%}BlIrh`+b}1ziyT#&nzBd#@l_J%Htzn@2G$CD58IOGIpKTc&Fan zs`h^e&i5r7tUYcZXLHgwB+t40uA?vp9hEn|7|F(yw_CthbvCw9#}tO3^5vG1TS|5} zGi@Q3_ZfP)L!;|vx=sGp^Y-{o>uKEe(UjGt1MnL_p`-lrw{;_fThOf(i$5RAQC_|X zs4M)u@83xGZEgmxw|0hjmDriY)-yx3#-GCi6j^Y~X|wIf1c04o53yyRN`EY}cen4& z`6R_QHr6%@o~0=$ez!%<$ak6Qix<7RTbQ@We9yb=25rU(`xi>?+e4l9GS#NJya&)e zw|lP~0sYoL8?Qc_j(+XOmsCKc>_6IyIol=2=-P*)@mTFMFa`UwQ&6DtSW#Rv;8=yw z8EKiA>ZGR=N=k-0UW*(F37PU(W5`bxP9OEftp!Hd|?pKXT z2IE|Cz|Ou|rSsnY4b-mb<*<)^oFwQwTd!FyyEAPkOiQ6i6mQIt)d|?eB7mRhVbcG! zvTAI!fqlZoZAHklK!FbMLuIqYvJo8c z0Hgh4U+T0_(<77&5ayd4&yF$?wFa#J-~v2;0LeqUj+NeW0>E~&=2`OEu(YLR{F}Ha z0)j4`qp5$V+497)XQBvBBhJn~6IbjVvr4I<7|d0y!6Ruc1q2T}>G6UJwX_LP{Xp;x z)P|FZl`7oCf>UGD@%UL*$wCV&z|*?h4N>xxHXAmsJIQ4_%F`}L=qBCqw}EA3M$ZJF(o8U#sNoHP4~T8gCJaP;3EXQk+`XV za%-2C8=z2xuWifzVE9UuSV3U=*9au-WOG=1aaSLH+^G@JA#mlsiGMSCKk%M8`Kwn$ zO{O|oWs(Q6OyIob;OBTwY@I({J$Blfik{7=RES6*hPQ^r4h}xdjq+D|=tD)_o+m?s z_2>~rnfc5&?SnM)CxtI?$s{5o^yTw$r?Ufmd_=dV7M2X%X5n)A%K2&MNz|SvL(op~ z#w;aJ@;dx*{hS5+8d&wSagkgs@UjJW(A=!srWrLqsh|Ab$?Va(eS3mQS2f`f8Emz` z$oF>VgR+^KnU1;%DTLwS%^t@Gsw*b5csWb9re+LoSYpRIjcG9~ky)7mpq)!7 zsgaY^L}}~fz(Api>{*udsP&iPL9H-%Az{5u@e_AXpU;Ji{RmypF1LLV>X=76P%bCi zA!SoD%RZLDKjh)bSqmcWB^PHAR_(7?WMqwo^lp3kMIR~{gueW(fD_c0mMhJSE-$LJ zm}DkurLMwVQAu3AW={bZ{SiBR1gkcKrGyAJ7S`hWIxZohM%;sY-hnsEeV)wX-XdVU zzarvZ@Zv}=e1I?&Y<)_5`}V-W7HxxDn7`rQ@`Q`o+ZPRY_+9>8m6?)1^grxlA%4r* z2*<^alMkP?ZX*Fg`eqtQA(CmP!$JRab*2{TRk`$ZRL^{3^%Z;s`X8bDns6_!uID_o zd_|WGg&*1?4705P>mD^b`_RykZJR#^bsrr>iRE7+PAs(Bcf8GtzeXwochuZCe8gGd z*GQ}@M`zxb!&K;P+}7Kgkw>406eOs~lXL*iz&zK0dt}M!aT*K^@-EJf3HZ^yt3UlK zY`!k#LOQ}*R;0jGNQ`&0-a4)dAsqF_PWA_eOTd@YdJ}+$Hx%Qi5hEMpfae`0MHR}d zVLw?p?2T)EOGx;~_0D@kZhjhfyxf0XEd(TDK(7AF`}@D*X1P5jTbES1oTx6q13Pkg zGinER-r*W(c0xFiJ8*ju~KOt z0gID`tHv)MxbN{MG5U8q2WCrw0^5Fty=7PeHN=@{{sI z4k`!u2hM~hukxRdy2<+64W1?FeLNF6&@1qsw++aVqniP;nEjbret7fj&L-x_Q#L3>59Gw zj~Fcz>cqwwmd|#=oOZAGYEY&Q8qS^sO+I)gV1lBIjuUr9UBnJCfF|F_kG11x%Teuy;G(+2+9;{8E`C>b>3l3Jl7CI!;53MbX{eufh-wG~+ZhS2$Z+ zTeSeEi%N_vl%w0)+N=SJGozIyKuN<4ta8*+yneMzxAYJc_y8M@qPwdvx(7xms;Q~T zas9HdFS-j>RmE+bWh@dEA4412WnpOnlu*o5YMSx!@mTUYo|hZr#aw}7V`I3>%OX+9 z$-0Qs%1W+*A2T2Q_u-;G#gDES14Td)ZA>7^$ETkn%*4o4@3ja;X-!IsH)!{0dZIfB zjDos{BK+I*ODj^ogTwBjK=2}<;(4BWMFrrIQc#qNP((`>GJ&pHOaT%U;gT7cA!(VJ z)C1jt{#2L&A`F!kB};n+{t^_?5>)L$xB{eM{!|LN;8RLUN|yKhB^aUuM}zJvl&Y1# zv$C=>u#~=rswhek&H8#DB&fJ~II2tKS>r&-3tj$&UaABYI|oO3Whr?Y0A$W10s(di zw1XlCpi^_hxqG7MFnhk_S|?CMsfX%EKRTKbdxm)Koo zbc&1z+uB;7;FYb5jMW}RXAAU$|I$NI;6Ulu`raNu3qCKy3w4wj%F01o_>RGraQ~=> zMk+E@V&U^oW!5zbI)I@!(7qa}IC8gsiA=`=a_+o>O(x%;U0nhGKnnkqF@H6twOLe| zIa|ZkGxB+#T>UF#3dt3?VzIV)wfzIlTm^>LnH0X{6q#B@QC-iDj))b92U(NKqoeZ> zt93^WoUXwuzsNoFRk}seD24hCoO-ThQuiB8F1SFb;y_X_naN&S!P&qFaduKt`C7b3 zP*DFfYe#)ulPgetlCXV~Cy+Oj4oEc`79T+WZ>~@N9H4}Lz+yDpMcKrY~rjug2aDVD69BRyfrI0iwitu$N$xO^# zS9vhh_Bfdexk{EMrXQ3vQnJ;aFw@a7;9_HA<7I=2?=7Ns3YQ)W!ZZV1cR=p3eMT>)L z=L?Z2kew&j*wugfZs0195j}hIY2bKux;n>~jV(ZFz?!C?M2MECiTKv=kMfz;3z{{r^x0*6HOmQp;z$4=}8#o|tr8kl)4_ z=x+>tjBCGWPoD+uDBf}=kkvu z#sW8u@73QqVn{J^u8U z=1D;-mA8fivdOtXdyk`~qFIL*Q3x6Ya^4j2DBa#Kzp01y1e1s= z+Z`Q~*_=d_rUQ87#tsXA@-Jcm870d%wrv0t;@njQmd*b}LcTwVwqFARHKiM8I`Hj3 z1Pe&1?-j8cntBY#Y}Q*+%q72j+)C4^uh_=OuXnwRynm`}3k5byOzH;4?j z8Qd)1uFvj~?H_s6jfIw^U#pE$#hmZhr&+4Ba>CCQ@1yz^p z5D;>Dle-U{6bvVDsg9ve@{C;9O}%AmUV)R3>i<(WuzkmI9dL}|-2_f+_lNF)y1=OU zU-^hm(%wg4PR;Y@Fa5pF!|-Hu)_=ZLhEy1n)jzPw&HxbD9}_y49s*B{@=qrC{B?n; z8*2Rt=jmQ@x&|V5AfTJFq%Zzq+<&PJkjl=@EP;ImAopV9csOn2>0VXrfmTzR=kF2T zK|$rEi|k6g&4Imfa9C!KDg$Slq$;omzV$AeMTRvBZ%Tb4R~1wtJa$`<9UUF(K-~E} z`p0PX=+sicA}YzH!CnN8h5Qr$=@L&%!0+}tcbS<;RrUdes1TCdkOn;fd=1zp^`WAS zsg+{gc%M96YzOx>&W5E~W6`!2aOnw_lbeIZuP(ffw)t7sPs5N9XKs&b3?BMFtU-Qd z$8N}P6XyHXs9*ApVvlaFDU6OooR{N$hN)3z*0Rq1vODj6Z-1=8F|w{XIa@RzcbG|Z zD)E)^*$k&;|=cS|ZMASK-ri|%gj zT>QW9oW1wi_l|qdxMvLqG8SvSIp61b=KRHctjXiP^P~Hr=TQCrL|j;iJ#e$5eWN){ zBBJ6?PLKLmGS55WqrSRIKe(L{7#|hSGvk>V!;vc~QtPS|)?4Nb4(*V`>`&)2NFO^&y@H^kglHZ$qYGB>tJ>_P2z+Mk4z zCaKqwo8?i*=Yz9Wv+3|>5zlABdE2hc@=v2x)M6_jHu@eb6S*e(&Rf55U)izt+zV)? zsj93ry*c^Cb+;awQc;?%sHCj2Xa%h9r<2f-DI)k(1DiokiqtR>~kyU0bv_+y$Oxdt!3!A ziJYC4h$RscRLoP0dCm#=RePg}W9spKee6wav|&D2yj-$6eONNS>YaLvw_0LlruN&imP%D9uEc+AE0=I<$oI^RmpYWmJ02zh zw+eLCBO@aV-~3gWXzlWvW4Tu+NB5fp`)pfCLI|D0G}K_M^MFGwp(;=6oy zgbz0PxW~qA+@=<4IpZT)R4{WEE1%B-l)zNY<=+3y* zIqo7D77@{@;Ii}F-}e*6LtLkds@3D|_i7B(6j5UKd=EFnp)a8Ee3A}=w`qS@qekB| zE>7J-lUx`boez=z`)&W&cb7Ym^|#`53=E7a+roGb1N&?RdTtlz z+aXcVWNw0omv?XVe4q3hokFexnMtfaQJ@`{-AaYU`6azL;Osa#S16h-_h+Nyp1M&= zD^rBL8TPEhCOtjMhBlXbkUYSXs!?mbYZJ3@p9%N0!fiePC(VALo8cid4F$#0A0!=7 zHq1R}eGIyXx1yme`1&=l=qsY)typ-We|<7;da$?m-e{2>_p!FUd<+v-vec8lw?m#b zJ1RY*Z_u#*)n_HVRIeb4qJQBuobKA)_8O+h;z$`2(ivRTK>fSPO z{aFQ#Yj%ukdjBK?`SXPHqE!F1Y7S2Rx&*3Mcs==br#-{HI!zp!UVH1u$muTCa`V)} zk+co72@T%Ce08=5C6nmA_3RcSOxBG|$l+H$!AHl3wSB4Ei4DTLrb&TIVRtc_+K{3i zLlySWXxbvcoxa3F6RD|&78v#evc9LPWsfO7Nz z-o2ue>t_AsCW`sPOpaO$(d!DXO!O;%kl7a1oV5i7Z-hfxa6?%1-NsTy=j}`s?DuxQ zF)z=+du_$i(#%jFC6E-<(?dq)tA)4fT3OAT+|eECyFbjy*&gY}i5wR(88i#aI=K^f zx|tq9(X+o?XJc7Fo#b0A-D}|5mnrK5v*6M0-sy@zWqMa^E`-HSdwyO1k-gb_k9F7| z#?_CS_UCzpBT>G;aP&LPN7Fhg+2~q=T}-wk%>3)r(s*2!c9i`DzeYxqXBXjD#uz4Z zAt7P+3vFm?Z&P&L?mv;_m#B0!YR)5=}nMge_8~kQwR}vvC!i zEB%r-W-M8)tR`f4(B8i!98`$IGVacal9ezwk88T3RX`=aGE{yIujlJ)L=BJif#|qf z04Y8p!n4LrVF1rFdg1V#108xnY=?B^rq#{PF2`}Zu%AQ=rhI8XcgAG*vWov;tQo{Z&I)A+WvPfnI6 z2VRGm?&eD+Rd&P_ZQmU?6$c}Vik_AAYu22auMf+9`MPB=Xx?fvc-E6R1Md~#-5DyT zbXbceIoz@^8zCcpp0P0}Zg!;*LjqCTmu&EJvDdq~wQ_gV!o${@`!Diz;rD&^1I=hQ z1IfFo>BT=yVndn*C}`V$J!iHZf(^tIvR6NO5AgK60N7rz?iregPYr zrv^P}_h~j8?5g58E6_VA0LFG6(eVIr-O16Yfu^g`N&=e31Gr5V{BG0F48IHxicY#8 z&315?`~6B3#@VJ4Cwu)GS9-C=rO9FK`TjZtVK6y?!G!zjx(O+@c)+RFbwB}a*Ww)F zj(6Qwm+nShN}M+sT+J)vKE}b~aZFex&TkQmVTgRQgFqej=lDAVJkJlp{Y5%FKPIJr zqTy~mIE>T|F*sg5+x;_5Y3(X~9gPssdc!eLvUvwionFVX*+$%I)IBDC0NmS}pccc&T8Z>ml!RM}H4^Rt?HM6H0I%|%}Xz+t6IxDfr z8t++Oy_{Igg3HVkU+~(0D_xSZsR<)$rJUU?{1#$I?< z^U!4dvT#Cs$E=1qOFR4IS8<6Pje?;e*rBx)pnMpOLDtn*XBX38Mapxq6{+c`rcSR~ z4%?QYIldRlgnU%%GlF#q-0)-9m``&wqEDIrAn4b_sr z(J^$h*7m_)wgT%$^yNyWhLT=8*_Y)cee~76d-v}5@AY#X*#v}WFLE#ZjM)Ik{I>5< zBtT2w)h2VVjD($)o4LpC?+d+~4M+{OlIQ0~yP}u`H)A-;7rS3-fs=aOc`Bu51izDv z^?YaC>9HM-Ts%=3E7IdH3b};)*%~UPt zbr3T4QZO4CvR$xrYq)OZeztpy*p$mw>WudanwISarjf=aL5K(q+fl9B^Fwqh-s3tv zWLC>t=8xGL7GI)2`V>pTyk+$6-A3@}kUCe*V2(cFT>gZ+FgkWyjdkhT$Jv`+tCJ)) z&V36mJ~-&>L!HblkCMm`9``u4rxX8v!NzOmcMy`tyu#tb&qsV{5F8)eC3M{!JHP5L zh(Ww8nl7XDezIuF^0#Pu_C`WRrms;X|I{t8V-8bBMgoiYtT{KJk`NOuA*8m$-uC|bqS91_>K#cup4kKs-@;@=-28I3NdfNw+97ql|nzX zYyhJh9$dUVt;=ZNuu!#?7dQwRCrY+LW_uC-Hza6GRH!Y(EA7I!2+BH{ zKMkPU@3*uRPwqz!IS-RQe@)`_P`|6>8vxz9(E7+xpHIsi{Iyvl6hxim^^zfl$sTCt zQgs)j#lQCeTV!JUZzBf&^3Os1zx@agY7|1Vqp&)c3>mGXq^OPJWNr=u(Zdn}s(95q zd)jt&U6Ja-b%+Uj1?8jkUw8gqDA~q9@`B(Vc)|RFPyFXAVko9h63w}ju3eSC#txRc zXx#}@p&$z&3f*lO)Qi-|ndE$D8`;ByeMSx?x9BP0{=axM^Te8UChD#NJ`pzz{Du{% z=Y^FI_|*@*pzMeC-Wyf-doj~6vyoyAVf#PdnOR$ix8(ncVne;y_3{$W?L7;*_%8W1 z*9sovreEre#HdYX1QiryPEAeOab4v@0HII|xU&t+bFyFUtd{4mx@{Gq5YhL|^9X#+ zhmq~e=J-6Qn;W3U!1$DPYku@GzsR21qRAK`JV``hG<>*V{4k)N6b#Sp4Y78s0J%0b=+c&5J$W?a_uJtLUO`^>Jfjm!MR1sBORY76yOoYKL zm{>cEPieDu;|)4u6-v8XT1028N;$d99S=67`*lul-RF@)7!#$-E{huraJrezC@_+_ zsTk!Xt0|RYo{l|dliAxF8(wsJDGxXK+O_K&jy$i3FTXfNX&kf)_EtxEop+RI!w0%7 zYiV!73P|@bkD(KXj3>uAr!KigqK_xQJHfd(pS#UVzN*{KEh@|ErQWmQnVcN>>iv=1 zXiMm4SK&aFz=75xExou!LPGr2$ICISg>VDi zk2{8RipaGInkP&zXhULoNUhG#JTtY|Z}*gBuE#<7g#caZ;K%lX zhLA*f#Eg;crCvL8-+BlbHEN^cUkc{sF%U4S>-)%(sVb}NtrCwVIc)>GqFB0Tf1UpU zh1sj$(VS+hm4R8>CM0qqI`bsh)IaLVtEytu80s81x1iE17jtmnSZX+NlK)Cr_gPet zln$P^_T|ebW8+-7J%M3;Lqm>U0xt!jaN{53x4XF+A(EQj8EZCCtz4eR2(jYp`>Sey zEn6JnJ5p{jO8wlo0SOS`>z2ks7WqHyZ`t${g-Wrj3bF`zDJjJf&*k%dw8sV+QZrSzumKE-%@+=9wA78JWnOxZiyCU)|BPn0``y{89qfZc>I$VCpN(8UKM_W|#ozaUyf@WXve>C!w{~C@ zDfMJ$f4g5j4^lDX_JLI>H9ma#L9m3zl^EA-tb`JdZ1Xl&3I|tf~%^#Y=mrq=Zb4zYXhc~|rXSH8j z?Qd^x);|u4Ln&=zpmFl*2<~YW@A$9_`4jus&8*ZsKumXw(UH%Xfu>Pwyf8 zgLW+)dU5|0&ZG~a4>2&%lbsafYVJN~;Co2L9^Wvv;lu+%&hb}rsa8^yfpJw;4xfW1 z!BHF387}*zTI9rd(gu>jAfF^XSS}Dun(_+D66YBkPfFGqevsVR1P#E7!|!{4w>>OU zV*uZx*-fX`L5thc&ADQ#sxeu;)Jd2k1mP@pHp?mOC1uiC%$SsJON2z}=450mPXRuE z%)I%o9|NmFttKMJ9=)>4jw{{N?xCMHVOe!aiP&R&A^?K=h*1+K6QNIgNNc>={Bzrd zjNsr%4-3RfZ0y0d-wKO6G4XPFb9LT>YpOo&Q$^?_ZSsYZV|vf-`)r?}GrxY_wDJ;K z3%q&!2lwmOY=8e}c=Ej9hSA^E6{-C#Hdf8VgbsE7E>eMU{(?#MNdkMIji96R`E*s} z@tyB#4ciuh;(`6)#w?(&$mJ?&mfneaEiK*CEUub$JwYeU zyO}1kos%R;je+3C0aqn0^i^jsFLdhkfBhDzrw5`YC#OR}T{FGhryLv{**W;jrWj~K zo}_?IUYF%-yL;mxlqyJv|2b%CpPkE9gnWg*bea%%-rAK8%Oli}-#C0Xm+|Wx8L3=i z#=UlQeDkX7hZnc;7Z6{g(;lV#^b`P{kRU~(+CxT0Mrw+F9sBI-K2u=FuEFb)5u{;v zL*BG})dtd9G%zpfE7}%Wxc7+0@t^=%gcOYI?Fu zOq10j7zU8Z^d~EQiB@WpQSE&6`Aa=d+b zf+=!7oTvV;3(`OCH0_o01Fa4~61*N{yW-kdO*#+dJy9qYv%Nd$Zc&!}{Q2{cbP1bC z^%wlVlP<6)!LYB<3T$rz`_dRATk|L1ke=_U^6;(Cbp$lh#u*a7K36uxdZkK=^on?! zdenH|iNRURkY7`CeW=2bv{>Se7zV};g4HTk>1j}~q~SQ*Cd?u$qj!|M{ z_nkUBav!Nq{BB+BNp3mXK||XOwldQjB6fg`NxoQLV#PgR-nyV12OE26fBt7B&fCZ= zMCgIq>5b=B0dxFD-@ADuy0eD8K05y(rS z?3R;5eVy$mDzEcI&dx#WRnQ`4)U=dLaobSI%xq$26pGb7Y#c5Yc7WmI+YlVxK(;|J z@OAcx@3ghkfv%QS3AK#Mn)%f;(~Ny7A%5(Q$SrJ)Z%pn_gMm?Tu<3`F?zaFAgfOSD z*c+lH+_i`7geUnNmDjvKk_#l|J_z{lQXJGW_5Y^?4Giy1#ju{@8hwJG6Q5E!asZ0En3z zo)-qyZbVf4Z!$*Zo#9WLIws{FXpWaqo6JZXs@P7K5Z2Y7SR5&BkJz~7Ch!rYM~#WfEa`YE zZ1~O=+L|hSdxfuDKIzl&?Y`$7UHkAQ3?Xgg)Dn)|TfYn8aDA}Xq|Hm0h7wPk8oz-H z8o=6$OS_@~UlZRR3Fc*Ulv>zKMjxV7`R-~2kX)~9K3{qHY&OB=E2obn$n_WvQBNBU zxXb28xQ5&&K%HQ2Or3+hxis5fxU=51&<-~Nm3hAqS5{WOBdwR}E!_0T8|UyS`!xeY zCsg-gG$TLXi=nGk&dNnt)M|W1AQ(eiPo0mSYE-<~4O3&_x8wTA+xfd1#K2}0TTA^q zgOaMy1|uTczPO-Zqk}oQ-0bv&uLOcK#&PN$bW0Y(B7bK2;=D7SOfmn=?cf)4U|fds zS%)GZY+BAOe%q1!c)!#SaL*jW1uV-$unHt4N!CBRy25IZe=2r#{drMm- zCXXO%|L;MvTna{GL(H-b6S#hY(A)TIZkaMGap}gZ@E1Qo4tY8`m|x6ayT6u_DLbKj zf-LHO`N)3l$nJDRzQ(O)Se7EhvUR#E;;H(C#g77Vhm|AqehxsP1#PJ)tj9h%`2DJK z*!apAnQ5Pw%<foU&Hkx1`bq}ApfZSHQb zO6*FVSG{>%_Hpj>Xf;%>Up_gNlN3osJu5|$)9KaxEG*mXxE;})aqUR=-QCaR&JotA=4rT_(fn+h@ z@pt3*@6mOMc&77SNoU*j=7An&a|1~niX+RtSeu?g7&`7B&n-WT3NTBZ2@8@ZVq3aP zF>=rl0T!Iq&#Vv1v(Lnf>^>!OB~yt7Bfb~2J#3Cy6mkvnD@LJ zIhGxwy8J;4(5B>M9Y4eM+AHFur0Eh))LdK&Pa|{<=5I?&NHE~Cou?a&*K9gM_Y-Bi zWd$l+w<_oH=3Cjr3go|IJD9{!knrlkA`sQe&is zyOZ*{czDE;f^NrGCF5CZ4T9HJ9%-170^l3&`^Q}Wc8HYW<`^2__qTh1-QddZ5mZ!A z45l52joFIle9kM4Uw&TkTWDCWB!C;_z`}BCy{H4c?$?yTD}(HU_7oQ7bgVG9T2urHLJ9xg3f+?@IV$tp*KDZ8 zNh0BIaHIrZ88YXRCvoQmw`~xB2`7xCri$ z%q~vx8P$)jixHh4$GagqH8YOkw5F}ULshUHMP6OxcS{9eiK3{qu(hMT_c?C=E)<+6 z6FJqcFnT##$TMRVo3to|q%OdYU|b6i59c?VR4a{6eIuu$qQ5Z&0y-SK-2*k`}nQ{y-}&oV183#FDMgA|?s%BB<{*U_(;PL>%BeffzxiJ7o9 z`AU!Tmw*?ao;qvRM34}6t0I{ClE7aiv{e_#0pFdV!GZxKNBc|tGY|tS!#`fs%`S6< zg~PBH>*uQ*?CE(K*f!D(D*bkj<`(kbD=Qsc_P6$Kb+&d;Cb^%oT1;A7S~^C@mt~b-usPsB{=C`UY3+(y*#(hugT=F;45vKl{Fy3R^QETZY3fD z*tj~E3pO#=bIwEOV6t()CugpgaTlMbx`bn&+3D^Hv*>rkt}QQ*C%SRF)l2!qvzL-? z;8;0wNNdT&y!Yh!tp1v%cj?j^3M5P#finkg4;Mb|sowLk7aB^GtG za;pU{lJ)m$&B-r<;isHks~|C4iT(4ZXi&rD5ZDn6c?>rE@wtW3>FLL%CaunzP)xkF zff@$w43~~s7&R)N;$xWQzBky!CI~8i7ARbxVQ*#sJZ4=V3ljtt}lQg3VU;D_#BSobu`Bw zR+G8LwdD8S$!4SMYvys&UDUHkB+$eJUz1Q%AcF{aOU?VYf+y{-UEL?}ryNiN_bVu~ zEXkN>N2Q9fv9Vy7FBuo41pE_Fi>Q!iOrg@Aj4YzAEmXyr=Yp+ClxD1Z=(rEmQJCg3 zFJz|whHv1VZ~ykc`JW~J|E-V21HOP}m~+*&_WO6v;FcN7jYa?vcAy)5s*KTVy;P;A zdA%Pl1{5E_O1$1pR0khlMF&&n2R%Szp8wj893JL0NPZXLUh}bQCOjg%e_ZLH6k6Xf zg}mw8O=Xy;kPL7-m8VWmd$S4f6*&oZOKaxhw?R!_hdoDB3GP!p7fJS7<#jYBk;@<6 z`$8{uGXn#Y6(uO(N<*uXQ|%e!cZ7ThP2pD;7)QYOsNp2WIaL*vf6DWXPwD9i+?Xfp z$Et&lgi&QtLbBk&a>;~?%Dhm%1>yx+e%=J7|3!Hm_TJpwd{LoKHqgYj_!V#lC z0g^LWVOs8=+8wjuc7*ptQ@Y;T=6n@mtUGA~i{Kp+hUd?RnwnDYO`DrysibCsYg+iZ zLRNW9nfK4ZqXl^jm565f%ZBe!H@UB+QNl@YBW7vAMCaAbkr%#884> z~qbe$xoC#4TM9#ke+4e zJrWA|)#f~wr_Nod1V5{Ca$4Zvp1HhDeBgapu5WE!lx@1G5^WNt782Qs8C0?>I#N^`!-Z+!V&VTNIa&DP9aCj zBrP@d@EBc5b@b}htMEGw#}}x&=BiiiNl0`6IW2|W(Toj>&tr+NgyCE8{_*}gbrPIt zR}{-&Yv%BDlYdKqQ}4jQ8aUdFd^E6no+8EL5#BdDJIjPM|M#aSi~et2!l2aD@Stmc z0RiyqNKhP@&6F7TSBl^rle-`MY-(cT;D^^nIUO#77e@vSH!LNlEUy-!G%Y)OIq{W8 zco;sT64SZI`SG`0`{_o%VuMHb=v7GW+~ENdjHdgcyzqu#<9x`{9J*O{O7qX3FB886 zlWRMMpPt}veYk>7yZj5e<%dUIb9S^0#y@aQPDeYLWdfcGwZ`Iw zN<8Lc2hcPbV()Z!aGWbbQO8D7Qj(jy3h*XWu_h8Ig4MyXYy9+(Z zehWZOhi| zJofS6FYucg=BGc}TV0->mXVT5c;ra2G}Rd^aT1S1wdtv; zLTe<(-j&;I_m=9mh)lq)=^55`69K|uZOo}B$&0IgU?^7m+m|n278VF8J_iM@^`-}} zec7ptPE1UUWYomCbt|_SL$StanbUEGLA^ibUIOmaqBJ11SS8&6SSyxjNB^_OMMg}ZhRg{(Kmf@&|Mn>+0K+!>k>v%DJd&0${ z4Q^&jF!^AehX;l8#^0B{Y2qCn9e%&Y#>QG(7a_XnfmW9U4N{78KM81yi1?wditEPT zpI<38sUANLi;M)DW&6R;#WtAU+P)bgp;TB6AZr5?Ekn$BUj^63-TQRvXUROs2a=<- z_fwWW%I$7Uy)rN`uzv|g=7+AeWdfu>vu~vo&cWCTa*r-|O}9t}b=yxVW(j<50(&dN zqCo3@jYGsLr7CVOTydt@3y$Ux?3IclF8k?1>i8_MbA$=6IQG;x6!P zpgWn&S_OTpWjU@tHL%}@Uh|jyBSaUg6ku7 z(&4gh_PnJrvual51NqHES0n}#R)hW&5$3L@Qy?4VotvXLxHLGdf7qqKlcy8G&v*|E z``-g17+ks(>Ntn2;;Y63ZIS?w)a637t-F(Az`R?o2|36bANr( zU^3e2cyARhftlA)esyy+xQRj^C;j5)ogG`!hepJ|AZP{#_U3_GbK@#0x4r9{vqGv> zI4q_BUWA{!D}sR|;;LOgv&Rv;JkGZ)RM2MX1A+78kyRTCYa6$%)>xClJhzigYS<%) z1L0s_d1FO!JAm{n7}y;i$EzHqqFW{0l1>lS-t16?It4?oM% zy6r54yu8J2zcN&5x6ElXM@3ETvDHelr-vT+&*}2<@d11f&jtW`qLJ>oj~D~v>@YTp+J4Y zPI%V&jttAT9^rbNIVpng*NYcgmX^>Y=UsWP;nO}3WcC0n3k$^0`cl7%O1U{6(TL{b zJNNry`_m=gfC1c_Hybu41G?c@GP`MP<{NM$#m(VHzEe}RduERX8IJ&z-#EX2@5-Ii zvOqZU6*<3plcG!TK>XN$ek^RL>k#|Kji*mC<`h0RBd27SSOucV8(5}8dmyP670rfz z0PH3MVh#_Vz?8}u=55*gv2}azR?oJgB#L?J+-!!5mjfOZc7Hrpk&(i?Hl~jTho<@v zKYMjK)@dU;rU@xkKl}UJ*x8j96{%G2EQ9rM;c#`cKn4Z}y^O12isk%ew7%^77S{UJ zhxMxwtmgKFxVVMp<3|8&lkqyQ3~&1c33T7!pnstQy|p1b=xey3^T^wk>&$x{_W}(F zYR?Px^=Sh}=l`lwFUbRmV`F23i}dmZaLnrN0)&5_2h9~5IxeWlir=~ z$82qD`sNn_nmUpcF`chmrQfh0LrY7yZ1@~CPI$xeH%03Axv3wjUD?u%zTE9phkn8myb0V@qjVW7Gl4!rl`|yw<>ENB#f} zja30AJe|qm-5n!#s{@O`xSbs%dn4tj_zo}BERgx~(X?tI8d`kMwrXM6)yp5gN8P{3 zP%7ap7G5(%il+YA`i$WxnB`Hg93sw982U9qXak^Z%h-@F`B~cgiKhU85w{j%Z zng1_aks^gbuE84{J7%jhY@Z5;m}{uBbML!2I5LK{ytKuok=!<`99 z{XBc7vy{I!t8p7T-L+iVk+WVV+i;?$tR;m&HjWR0ojfs5eI4!n^`B5s$f;ICM8EuS20n3BuE=PCM5!nElEuZ} z)g+*R!(uV{Fo|YeoPmzf1&PE3Rl(;^^~X45wD_Na<+0&d9Y0xWO>i4d9gtT{b!nc`MSI!rI!H%k)=5!i#fwn0cJG0B#mjO)Qi0 zynKp&3v0C}*Tl+dQ&mSpvf5I*#quqTKUKDiMI-NMyNp#CkQ9bhIY*3f&78DGu`8f1 z@YNQ=BHo%^>;;#O`A+E>-ZWs!q_U$d>kEl|^b(8TdJ>w<&K#0kj#s;j|b z69f^0C04x=VPUNO-V+lwBrB^9CnY8=lKdPt#!H~@QSJO6)C;^&fsdDm>lxI6?yy~7 ze&YBy|Iwwhn(XUTA#XU$N9wU{L2Y%!Dh;LCRmm#%;aZ(YPtO{Vx{yACS|a#4!#nd` zbtiLk1BL2UWo;5D;JQ+;y+JsrP4`)UC#PDFmzU*o87auK8ho%B80l~(tLdqz7@i*t z_f-V4$%vC9=|XKstk2jxj$Ts*FH=IWih# zB;_iZx8$t!>8<(JrJgshp>P^0F+&p0DiP5>V@=pOmpeL~v-#bo&oS62Ky7q9ds88w z$gi2Nm&Lr)bP?8Yei?eAgDWc}rFFX1HO|AMqZX5eaCNFw%50@0OkyI-j=E}|828XF z)?Piz@hr1;qmWkJHtr(Q(7hOK_06@VRrU}8DJ3O+0#9^R{DN%j4Cy0$M`rm$jE$~zHV)8)f{6#u}KEx2bv#oZwzYt31$s&HZXzcEutNH z|9%t?ALHTCEsC~^a3hmrQ)4o+HQW$scu4N}v2s!6o$@c{%TX2I#(-mVZqDsB=v;cz zN0OzQ4q(N*LZzn>k2T)BBNW=VJnF4bdpp9AeI}Z26$HO5H;G1uc|aK_h-jVJEW{*b zM&VrYarDtsIy#T7$5kMB6YQ&12Xk|lzQJx!Gs=7Zlzx5$@@;HgqN=C!8u+D#;kKHW z-3DH&eoqYP(;e8LnE40LNl&U)dyEei+9#rd0@~l-PJoB+Z!&NJ@D-#WRfp03W4(Z$ z9?(gUnJ8w{*H&GU&SL6kxhj=MhlkIwLPJAM%uZv=FAii~(xge$(F#9*X)5CfA z@FjQ>FbsZ4z8#|URGO?f<`zDN*R$2K!a{eG-^&=zWvv%97%=?=s@T^0YG_<2o(CT@ zH@C2AVzU$wJhrznhi2+M29#Uig1GsWs{bppO4Jql>+*M^boyPyT5cJ2b7cr6<#v_C zaEz9lxE$}rK`scKvdmp5(1z!R`KRel z`=r7xy-Xl5F(NWtSydGZ_RyFheAmXO7txHA`D3rVtH~8;Bll;%NWmiL`uG>;iKUSv z=mEF0zdT+k-@XFwdY>_F<(@5$K{wLx+kX`5Mr3;(Z_ZkCq89wf%a=3}cHK(mhL72z z*s6b_=*2&@fY3HxS=%?<>)DQkj$;tZ>Y7VmzkEx{xxKJXeS72D<(EgM1HP3O>HQ0T7SDWXerkkyM$z!n$Swv&6JigR9pM zfMCOi7|d408E;sW9JM|*@MBe1;Ej|RB?;g8p3@(bT=t7?qGrV0j`tYfze!nH5>Vq_ z^E5@X@aX7!_=FE`-?)0&{k?|;Qq`xl1NA6)Cx(=XN`28>Se)uaTG!N^ZR=y1EhiMJ zb-j^8jqu(C_v2Aa+?y8#R_w z!}`M@iUkSw=fJ=^_eZalSvCb^Kmg{4*NiI-Q%zBk5RebBNQMwy^O|q|I9$n#7oUB0 zxJ#S~s%-z3`SI$gkON6Vq8Mt8HyBtOF|1ZRxazg8ND!m8$-XO`J@AtSFxxG5S8#Xc zZ9Luo%El=xKREa_)!_3jCI&fwmxSA01~r+^%;15TnM#rfknTs1x*)#$m}AdM=p&cz z7*+pk0$sFG&gKw?(WKg?bbQHqPejnjhy(r89vEKR_bWh{z?uaXk$C=10DXzau=n#a zn(gl1Or|v}SDyX8@FDTk)vZQnW_ni{JrL2HhrWw1@_OF70kt%u)P9MepAi|(l=S<2lx~`8>D?PYf5Tgq9 zHxzIF1@8qBpGlcRMh3tuAOpkfLeA^%I^AfbBYYK2N)i>`f1^f5P(^mBs(*3m(NODc zczq-JZx@#Emw!YWsE_=oD*q3bXHl`*gctS=8{}9GzjU9yQbiF;9EE7ZHyjUyETNRI zk z$5+`9j8J1B^Mds^?k`>IOgR}96{>sw*@G9nd^?zJ`W?+s&Jw)e62YJGU_MrPOgeSOoZ#cR#5CjXr2X5#-`z}XILr0ensj&tQ>METzjEsEt&CJLF zl?NcFIzDjY7%2ci<%x&>l%PwIQ&S5h+xUio_E!_Ad>CFyIn`nmArGMSJDQF(K+}FH zB{^b0zl4{{vpJ*i^o@Y1sQ236AZ_{QvWq!6ar$Wg2Ad@_UqC4S-iMWV#G3$=XU7i@ zU&=d)-Sz*vUsIb0GMRlS=mPD;M~)V`iRI2he{qj1)UfxUmBEWwAeOFB%S+7tcYOV) z;DPV`pF@#Qfh;tTJ1Y9h2kXLuL`%l888Xxa={7OR0ej=sqd<_Zx|)x2OvZ%4wMuUm!yzpZ8Ze-XSN}1C7tt|{sE*d(Z(&>iZ?NZ4+y#*&A zbjUbTY&K(+Pdm|4N-28M%@+7DXv-$+(l4Nz6}U|TjRbs zHjLL^tFbbc^j#$mIP(medwRCBt$oXpNy!qqYPBUgK6^wy1Jb|F7M5ptku|M7($H`7 zqzVW(ZEXM>n=9>`xrzw_xyV3*^l)|t2SD`x`UN0;r3IScO;gAMfyvo$5}0E)-_>44 z0G^8l;#9zM7iP{oqS<$ODF9$oV{oWHD1`7Dp^c1GoSi2a8XhjOPO#Piuw2r~w#*LrSVqm|kk$uL25RU820!ii9OwcvS% zU!9#sqh%eH{dBxMV`<(sRfd}~6vjQ(!w%WCP-NwIpYHpbU)$Q|JU!dDq;N8dM|(Sh{Fa(D8q*6| zVPOgI7=lgA%^#K&SlTkRceZ9$eL#)-2}kS}Djv+tLaXG+VVIlW z5zc`df+nM?vNZIWc!EvJS4rcl+RD>`Njzv&Zs!(#eN-(i;n3LqmnJlt+~YrO#1{IVrcw{eA$cZ}-G;JII5JM@#sJx zJeFs*E(TU2t>Nv8U4nuT7KSfhK2%M7y$z;z@m8X_4^`{q*?a}F&PvfbVmNd(g}Qq_ z=N4;^I}mjN6PBm*nd!`Jw1oz>Cr{~VKnS$qdKZZl2+@<)mR7e7f>AudoMtk+ZQ9a! z>NT*RW3F5eBs@9?Tc8|2)4(emB5!Ua?$gTTELVcJ8LrEuiE33I#l4jwb?%SXFb7Hr zRx0gr2Pa;uN#o<+#msn8Gq^K*#%#)d{SMw^p_z3z$F)$g&cjX)1O0sRhA5H!yw%ZC zqm`atEiI!hEiPNLo5`ot%}u=F?4dPn3^4&}Z^M({K3Pl$Vg-_4k7>i=_Isg)o&}OT zXU#5jm7h~vVq&az^yGA)t77$1VO?KaqpIrZPjwEz%1A@QYk$&LaZ{G3X2f=FfaU0A z07T!$^tHZpBcsJnU!vK@kk0*<4x#REc@KSV*Xuq@y)|T7_W33>$%>rh=DZ&j_36v~ z;yJcWQ~9&`o*5-J2O>1NSC5qZ&(&{ZctyLM;AX){E$-)j`_+xb;Tgr@z`C;$`S4oX zromsixjENwt#G(4Jzfmo;;{H?zdAvuIxt!7=5zZ`z9#uFKWX>kPOX~@o`CsSrMOvZ zdl&Vm6S?%V67lwk4*9XFQHQlV0!Mp`lrEfgua3)l_RZC+T!WmU7S1d1CivVgbk$JL zXt-1~oe1fUDrM4sq9sRrAsa->FLJ|ZDB*1S_4be{d!W$raPsjt=|^GvN%4xkBQ2`Z zR}$Y4;#X(^Gu7jFw2->9gMA*mbKebtU+t0kne4|nBn&h(xw2IQ-gem_ILLUtK|GL~ zC0nInL^J>6=Oek|p&{x|Uzl}Hh)!e>&O7Nl`Iiu-=RdT`g7?BX$wCw7kHb~OmFlq4iv8M;5mub=XDM|XE|+(z#WQ)zX4 z%Z#)RCAG%_^>(fjG#*qXs+I!S7I>o1)o5>F`^<1SyV~x&vJ1jN%V_KAdC0~lKC1lw ztsIZifP%uG^o-0KgRmf@&9A36DjL52=Q{L`jpp{S5~vsb@Zw8cC~*;mt>O?Lk{LLI zRd&aD(68>g=hW=1l6H)`_OVc4)b0EC!$U*cb)U(~^uk@lgw zwvNUsT%L)M5%KO^JW0?f_|F~0?JcIcO~Z4)uK2pYn~p$qNi<>4QIg->wxqQ^`aOKf z(?_$~mXd|VUntq*V8d!Z{*?C^E9_Kn5r~0T(2t?rD`2BBt-5LH2C-bE6UqB%VFzev zL|!H^#q0G|qSk{Q^`$54zH@oV85(_9!=UkJ?3yUHnhksW?M`m7vL>#wU+jY@&FnS6 z6VUI9wm)oRTgrd|uAV%BN3}S>+%(>eJa7Mb+Hiv5kv$8q>+*mkW@svyOHjcP8AG5 zx*J5KJ0(>bq&pYg-OU+G-*4|9XMfk(KlVB2`GH=r)>CuNF~=D9J?>Gdq!dxRx1^Y5 zh!HHZOExtJ)luaa9^`q#DIYYs9E(d!TRS?SrYUo`<&v!@evmsjUYRQ#mK6$l>+|yj zWHqIZa63XU`F4OL=fec|Rn`K`u!paIebf1J1YBANE%jgjW>FG|R0ps352Q4k5 z4y*H9hqf3(#~6-w`TP#k3iaQ2pd-xcU>Qfr*@6rI5hlqoGo``B&$l|a+)kZag!7_KoLnvZimw_ z0CGi~40LqX*VZ7M*8g6eTV5G57WUi6*UQ5o9Tpb~iigHQ3Q4oa0`$0{@&?+__{l=8 z{|16OGH;uIrLmnGImBgU;t~=r`>PG3jvE?qN7!N z20QxWzL3<5AcW#7pH*8sz9#q1Mpq>(68^HCeAiw%BsS!!tW8%Qx+1h&L-`R%_xLgY z(pu&A=#pK`?h}6@I>clVqej)~tf*VHi@n)Ec6Y&x%GffpPH$nEEs79nH6>b}oL|`# z*rGk7Wu6!A1-9ei3*U^T6o_}TepSG(0`ab(n#+gSEWh0x@x5g+0%a9O*681fD`!BH zb(ve<13YEIzXH+I7J5uWNm*hj>6nQZFO{d7(%9rlf`Iav_V(ZDgMjgfT?Yb^{p;7^ zHEY8un~4(5|Kb9;zV0;i4szT;^rA8>t#L04CpO4s{pTBXF`OS5>9~Y48T-oIT%&c$ znEMvWo5u#ax^fB%*-h2BPF&-A_vUCiE;FnDiw_cB#ro_ZNOXv^B$oAejHbP<|ATm0x9 zG#xTsn&X~6NyJ^6r&&4RBX5+xZkfwi3mZ=7!szfYG;ANQucz2T6wEdP(r-E12&A|e zte#v+GZK=GlkVFrtVMuuayu9sSs4MzhmhTzDe1xrGCxNG=@_Ih5Dn$Kv&pOgCZSkKZH-#62NZ!tZt%6%(PQq0y6VGGo{tW2mps=Zvjg zCbfo^bsG36!=K=EdanO;g1_7C+%s_aj$cEbR&9o2mQYrNmc8kq%Nf;Hvo^I84>2hP z9UV70d6DRhkd{_^GZ4~(V!Xm2ko0b4qvXSA)mdkUz|cNRLtXO*UC*8W->Si83Qw6b%( zahl8N{Xkn(1_%J8l>ZLl+~Q(TOw6}6`e&51#Pon7?e3zNkm+e6XEMs0+~?tcgf^1Q z{#sU)MS{Pk7p3C*pUM6vAh>HWeXHh*9ant%E=7XawLyhf$gg&x-IPph>2tu|be5nB z?P61$Picib0!r7D>({T3NL}I!Y-fKMf>!giTrBDDA0Fuzd=d(cin?#ceMbg(dfr$d zu-hZ!_V6=kqIqF8N@lfV1cbhtn~ z@axyI*3t68%)ElLLl@J*%p=G87Bzc94+lfq)swj$CMTJT7Mvy@%8&sIZoh`<$2S#| z#a1K37>O3eLK8?wqy+IGt+LFjO6Kv%zk`jPMVAHNj+fVh`kFyKS@5%mvs;|H!EZ9G zWYp9$liwoWX1ag7Aqj8cV`>ho<>Fy5GvJ=z(%>9~#;^;yyglq7CkJRqk7>;Ek?H$y ztG{XRi%oY53X3(ci>>8;d>$&NR#cg8qdFQVvgy)!z^JT^2ne|d(xS^q84?~Grzgs*OwL7(@gXiRQsjdyL19KIj%w_2| z<^^p^g%On{RBVEx;;Q2n4i!%4=%f@&_8@cImhMmP=2`2!_RH@u>eJ>7#1gV>gktY* zld#V=mfX78NeqQr_2c7`mWH%YfX`>z?v+jIbwi^9OX}rliQelqwI`ukP2&9F^DiXh z0|O8CvKF-AZpUgzCnlsuvQO`XiOa2jQs#7R{g~ubvRUP^ce;~y`}XRnfti`ShvVHR zGHhQ$;={S6@5ZMl#_Ppb$(f(^R9~J3GBk@(;04f7tG`zZxgZ@O?;W1yz{8@q(!MT>6urO)kBjXMoMkIi9OT znf|t6!7kMI2%s`Ey|Q2HX|d`nFW$AAV@S=_tbO|1ardozCB#OX!&{k zt+1=roX)?K+7(~ayIu&k&3&UdjOz9PL|Q$chI48z>zgFcWxS5?6?G4pmn2U)hm>`R zze**bd0y~+rzZ&9kjc%D=ovH$xX%Y3=2L@|s~sfVe9yY?WHX8Sh74y}e^Smv$h3KU z_V;(#7%OyX|3JuLDS3aRmdz>nqP(RLpVl;KdMkv|F+rg`@ByZ9IKEPkrl_u12Nv#) z_np(WoEn$|E2l|+7E{yjzA&sW$4~Ba#jo>^SPhWukdvpNlD0+U9S5EM92ycCdG$N} zPDe-osgS>7v6fpyW(mj>cn3XMVYeJE4!{yf>Ap;kcbsHH}iJ6YXIs483PIw;v z+>Xm#b`l9ecb0}t?_7f&%R30*RN7xd%;b3VXf-xF?4*Dr^nw6BPUA_NqQBTX`8(%P zWt$^=vED&pVG6+JRAv1}gRjscj2Rwt;}L{MqHGO{d|p3QYRD8U@4ggfqNA&H3rUQx zM*A4V!usS16VoS7M?P+Dqn}@15lDK3zkH9Oz@ufW--+|`1biXIXq}hi&O8ZU>ciql zX^EAf^DJ_SyP%=nx=H%&8x^P^W)?fbbSf?m$|+0NZTK}7ht*SKu>zWgt|g{T3v_N# z1d4aC1z(_%`c zoSpC+EZRx(HgDWISTCAO*(OgXds(DBF|>6lAwiP6*sRE8W(0uY57`%Ujj)P~iHS(m z?&`_9wih9lcAJQ$3-anN+-~(zEtgmgJLBeOW^!CEn=Ii2uWNtbVI!1ZhTj{DD)fkw zfs#d02Rt@QORe5-{;BG2ZfcGEuDLFo#l^coD#2VE6(w|ab$vvluop;M;8-XoCZ1tr zCTBH1rV(DIY(c$Olz8lN2&)(1su4R3eG#x%fngUpHk+*M5bmLyKq2V`CE5!S3mDk6=RbQ@gSdEinL#@;8($d1uuGsvqx|llBq^pO@ zE@avX)+$UQWQ&AIh6n$?^Fwi`tC!75e8KOcZifE^qr;!~U5r0wUp z?#1pW>yHy}s9usc{6K%Z!g^i7e5^M-*{sZBBB`qS3wb(?D6Uan6~smYO4+Iv`oWOC zf}kp+*D!17S<5Hd_3SV13N{~bAsmScVf||R5f??j8(cnPZYXWBdRJYo=@4*Qrv)xp zamPq2Eidnj9L9e-Q~z7be}@9uN<%T)7w9Zpakn;!=JW zexddd+XuvfOdfrN+hx3rd`Z$#F)>&IGuuwTLVzjqWz7!WudrZB_kHvaZ!h`5>r_!t z@Ift?xQ4PxA}^2QBPiGU2p9D#4YIaL9omyTdr0$$A-wC8PlhNZ8S=4c>U5W22r(nG z)&${MTR}RU%OAY)zO_$(#xXgWRQq9<15_WOBu?JC3VAhq*W^Fnd&LBG45dZzheK}} z8hxg|n&^AT5yt1`=Ty1OeJE4!f9(Q-LrD|=n_u)6CTmLnOF`hs*?)G zybH|=taCEH`Q!c_$Q4C&ewEH6zBAR7$nWm-c>6f)%}q7}hN)q4lLs)@nad%!m-MLG z<-EdWj^(-+X|RYBkJfmF{d{Na+RO~E>F=w-B7&$UYctiaPYkI-pQBFMe(%qGim{3) z^Ow#-9N!j|?wqtPhphd*d-Dp#(1QjuZMx)8liP8Zh%c;du26+uIdb{!tp!N7f~1oA z$Sczy^>12cZjwSb?O(s#T4!%+n}N(4$MDEVRCLVp(o#3rk%jGefb_|N)02`OveK!i zTtUvXE>xohQ_{9~_t6^z;&oz6%^x!|nk@CZ)gAZexK|1L?~WI+e#T;12*j75n(2tI zWW9(=3=dyhUoW)*ipJh@03o-FO;;ficb&s3IvsRIcRo8gV>ev=YBp#E%IfCB#kmE- zDFu2$>m${X$C_WXsw;rPLFv5;JH%cLh6ZDqa;G-<+5>}w7ZviCQMi7Mw=0qbgJ*LO zH*)jH%_40lzI$*G!cTT^^H^A1PS&gEumAD|RtAXOn}2%y_U9bkpi;J)x`u`Ts!xR@ zKMkih7R&0vPX)rrYAhp!kPQ)Bq_3vMe!#s7Sw3sCdidCF)bct+LfdLTKJ85chxxNR~HAw>q`3 z1i)>!8YpJoQ{I3eMLBO=M7mSPEL%8G4-5=N)fX+5s7QvrEz>Dc)Tgfdp%P>rnuqV= zue>1%%klRY6bRvW>vJHhm~`L1A@IrHzvRQT9`vz&BLw+69091GRG&%%&(vgT>8)Sn z`b5bbfnR;D**sNdqbx#L~xVE++ox8x}RdEAtMjm#!69IXltc zJGhDBVFz12NYjpV2sqxohpVclqHkjOmJeyW{evcgm8QxJnD3(ti2Q0(j<4K5EpZ*( zUmIcDH=doDLC$dJ?ZfVKoSEqaB}FRq55)F}oK+#y7I-siySux-zT(C4@7HN)X-f+l zOTg@IhzS{fi_L2Lthxi57Fl0aL4X!A%aEskiJGC>>uzG5WK=};(+_WVnKeI?rKO|A z-qz0AAs4W!mX?Cd=_@}1y@XP97(8%+uM*=ba%TXt1;+K`l?SM#yyt_G4k@tp4>HD^ z_E}SVtEj7D5DZxz_lfpg_<8P7nGCL1|3$ODbk|NMzHJKByGdH>ut8Q?V73qtj)@9p ziw|kG2y7-N@9=4NmHKza4fje)NT8C!nzb0isxauS$d6YyW{Z)bKu6PHGMO&tHAq zmq=^IHoX^H|0BtOEV$Y8ck@tG`eFHvGAoU3)2WszYr}TchruG-JGYcR=8}6K2I3#F z?VtA!EvgSbc40S@Q)^W%G*4xdayon}(5!MHyJd4*=ugPohk zE`KyCCj!9yUcyM8cKNp_+ci6?7#l?>bUjG(M0ht9+uWXHKhn0OzV4b30PwDYn zf>=-MemTX|99^I*M_iCY*Y%PYjmnbFbN*FxqDUX`5&l-T`}CgCNq zDOBxk?7Ihskxl@zEzouP(~X5IZ$6-}A~?uS58-)*Z?9X9ln+2cV-#Pp@#s_Nc%{8; z@)XAnGCoCkzxGq0^Zx8l$Y@lnmrm|L>hro!f$j|ywpZB~zz)8m zN8A8Y)#$9TotPLyikKU~adG#UT`mu?AKqf#iW3DrI^kENdF%)F2+L}iz9~&Q!wlle zO)oFsc$4f=Qw!AlhW7sOpIs-1%#_O$XKUPb`ATql{V>EPNH;QKstHy52YS}%jAtrM zXi-bQH{*V?M?pnp!u?(hnc;rJ!rzDdM@+^YO5bic&Lr|4?CnH~P!x{FLr#9Iir#%R zzd%`5_D;MM$<)b77wHuuqE&tmiQz)~g{4i@<-%hy_pnL!d@T@0oWH1(CJA3j=bf>MHT?bE;mi;aYY*}d zvTJ3QYRIJXd%t&p5snrb_%-+~(4mnT^@$*UXJ@N=(mNfTyF=^+dm6R?i0#yHTAu@P zAx-MSODGMnqAyd!iZ}l@&U}WeUSKQP+7xz&{KFZu*$7F|H%f{sjbf*zx~L`|;tfbC zdk3jIl4an*_HzRhQ9*={cMyiPO~st?It>$|YGPj40X4q6@aoSIWU3wo$l-p7XVWnk z_?30d?;QnpOMG)Bu-RYSxII*KfI?c|km>^1Ri8Hc`gmpM%nt6gV4Sl_on~Jd0TI9! zX4kSNv=|cjr8kNAc8_+vRXu-Gdu!C#=6E>q0!2fzBUcRRM}en}8!AEpl^nyKn<9|9 zgIW^Egf%COkGk$IALM>naG6`cJKgqM0_&!!kvVwrotV`7_=}~0NDtl^pCGTws)>2A zo!_&EU$wl~hWVX)%GF9t#{xZ^&W;(DInWeEYGZkxFvMf&8taRoX2@6WxAYo)wb0c| zJk{7p6HC)n{MTK8Wz=9)CJU}|Ih*fa;ebNgT8}-qG$|s7^%40Ro381jD<=oM&a|fk zYt67%$MYR2htEE|=SRZlcDKUe#ZlmZ#gN_BcKE)xy1a|ZP^Jh zPOxMzqz%w#=2njCi~J7 zzQyP))H$)WNbd65uL9Lg(SVtQlhVRn^Q9* zu~JsX+oR52o>KdRPAC*~Jpn!fqAy204RB`37^eO1new+@AMx(ql}iMBHaOI5ypw2_ zlaqr=s@Ejh(b}PfUs&&1jQHTM?k$k~CEu1P+|y<4xLx#8@A&u_d#*OG+fWb@x8n_l zo5|~iM9jAIX<x@)``G&i1GE3r8$_H}1l6Vls*2&Z+2klB3T^CNDD zb?5!lmoSHK>~5SK9Nij`LIV>H#}7PC>&;KW$vTM>yp8@u(8telaZ@|ljKh@s(O*~u zMf(}eUCWU|_uh!L5vf_6@)Kn6^`B9w;?mNR3HP<}8jN{g zIkf|&%F4=swDpVNOsor#C>EB&va*zJ6Uv5buqs|$nG%Iow&6Ou<~)?I1Yk32muR1$ zpjyWqC9zrOA^qx*6NA^8Xp^UisIQ-G{>gi>(l0H0HPfJpkrzB%TB{kYteVFCE6tP7 z�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 diff --git a/frontend/__snapshots__/scenes-app-pipeline--pipeline-node-new-hog-function--light.png b/frontend/__snapshots__/scenes-app-pipeline--pipeline-node-new-hog-function--light.png index 5de1b974272b9a0866b2a7a40c7cc6bb47fd6a6b..b5fb84532b1e030aa26b2653d977eab1b68e0592 100644 GIT binary patch literal 127673 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

    oqVM)^V8 zg@X4UYRxMV5mKIaM(4*nYNcimc+<3yDJ`8hG$}M-&izxGT>M7&1 zI@>$vJdxkHY;1()eJW)!q_Rph-JP7ABs$hby ziM%&rN?)OScVlA%jSLo>h{dLDs$_Tk5;_UH&QceDMMbw$TJ_lx={cu^J7_gRqE@0V zLZYu;QT4C3g_=qP!^XuH%1cjSB~WHJ9#ftaKyKa{b6snRcCU)&fZ0g}#tv;DcIFBP z^d9ref1n*gzbx`N7dGzyK9UOitE;Qa8)K`=_2fK2L#={pB9_-aJ$-s_Z%-d^^0&2N zxnDXv{sSJ7R7^$ZYc!tU5*{r?Qy?3|`@({!RKk&04|#nZl(d`Cea(|Bv$G6O)G-_m zr35jBu(1P3d7ag*8_7y0&#bTQrv$ZMM=ZtY_IQ!B#-=6KZIp9Q!KMhI@>Le+{amT zUttR^b~w$wgUTqd_lTF*`$T*5nxeGwBdOW^^c3|Pn^;UFc;m2~TEDzFQ_9n1qNOGG zym@<8T>vir%)FkXEO;?(N+5~P?Z(u{=&O#zJqw!?`%_N&YPv924HA9{pRZ4?XZNA8 zyib7I==J(8VJ27S2|f}&{xp<|pQ^ihyRmxU`3>qS%fAr_c#RT6FS?EH*Tk z_Iy=p<7fngQ4^0{FQo)8wpY^s_;WekiwAGD?7tM&3KxuHeH94vrW!CktUu2waC!k7NT*%yr~wK@n>Nc64r zPpEqi>m!s*F}N%sbM?bo6N=&`3&g}RpDbdg~f%hCZF+2y6X z8E16lV1KQf>V4hfP;a&00S{+hL2? z$KuUfES7J(&PqnbdZ;gr^z(47))35k>xa)rYYHn3; zqjMAT(Y()_T`@<^|EP5?k>36J zh7%c)`T6#m0y3}1r=NOB(O9;m5dXc$3zs=F{#5nL%4aJ#`NXY1ow|(I_TGN-7eyeZ z9;R0CWM%8rPUV#QB)?P1o6X|e-8)`C)sD>V#pl9F;G%R|9k;PY3N!{kM>X1c7)fy^F`BwYa`d82v@(x)e=F+i?3Z z`*@t@4>+m@o>gyF3T`WY*e5&j~Ezo z+hUdKyvOQ9kWOwuA*_K6mr{$#Q$RJ=Pl*Ht1$|zAkBK3|!U_rxC&0qeUhjbwfZ7F% zMZO8p9U7UnwYB#>QUT}vWv04wAykzgdOn47S$Cq+{E1;X5AY#Zk#l=zC#5H!+i8f? zLlP4Av%?Kg)G~6b0et@a`RvJ)C%`8%GN==!si)uVFZTi?d&xjJ<^7T zOGncl9w)mCyB3jDAZ!>9<&0X^U8Ly;LdU~-^2TiRhyJJA=;s#~cy+DJvADa^rl;$2a?B1^2f-cH;RNdA3@mgwU+d`LAl2BpH%Yi? zc0Z5&KeGS^ow6pE{ZfKWEiF}5Rr#7VQJn^CUZk$u*U08~7ur3)!3$*Z^6~MJ=#xTS z=}Ej1ECC?;5HRhvYu73&Dtdc+3kwTc-o z*coOA?O_bSe0OcAeCpgUD&=V<#l&>(fVp$` z?%n?W{u9^Q{Cp;|`TV*|a?h8-!W-jdTit?J6rS{&)yh=Ct!F*~0Rr!qdwvH!E?H;c z;0TS3F1DPm1?uCr(?mejMM&_@%iDWvvdRGl?)lApy!182cUsbei3(eN*3lSl+W=5i zLqc$|vA>6eyn`1Wb?ko)QEh8=z<_u^MMO*trdDKF7(OYf$NcXg-~^gg_8ZZbwWF1G z*|ME)VtH8Z?$T>ihH!5&;`>EPB8lcVcpErE>KYpH{4Qw&VUe4FETkM998zms!03Xd z0ZcudQRB`(|8%sqiAhN4y_tu%0CmvmW0x!8%1|yLTQ4;o9k{JtE8Zv3?+^J8x28RS zfF_Fs9v&RPBbbB781a4q_l)3$uf!ML`1H!pP%Etdd^G7?US0-PMq93J>H<3*snXn)`g9e(K(4mhAW(j-6sXm3ufR741f#!)4+R(ysc|)o~s(nT~!C`^XJc>FaYnqM1j0~fdzEp7F>ObD1qM~XE zASUE_w+!qNpFx$v(q{G-8c^@WyI@$|>h~B7sB}9!gjGVs@4SC?c_g@4`7vE89!jd} z)ij`)`@HQ;xa~UeU-I#N2_p-iu(KN1d${S)Xhh&d$5}~5%;~OimMnN<@QEkCx|0wb z6JyApXl}OQM3qK_F+B2T00-MgSzbnyo}QkG$?lyMi~Unil{N!-NVuINXaRJzw0W8} zVyGv(?Mw=$W22+cL;WAHH8(cS0kQCnPJwHUVZ^G~-A)<@-PY1r9Ansne2pckB+oUoYo;t~*ykBt28pZ)#&H_TsWyxQsd#SC{)ggQRh%9uXO{(POI`=W_Y-giOFiNS{an5J_%~P*v&?%&P6LSm&nugpe zdnP=B>gD3%!o zWy$GZbSQyr){<_i4;G}r^>h0u1$BUiIg4Ojm z2b!Ut-$*^qgDR9#pFMhupJ@ceVemBH{ho9>9@<@~OEE-2LH8ByyKrhFQ&TbI9Qw8< zt?Vt7jX8uljxP@xe@2raeyYiN-Q&=WYnNgQq7D6`_s+tLK0hfXBT2$oE{eG-sw!LR zD9ck-R5`>_TRZiIa0dQe+i3Dc_a!O-5mV2bJ9cxq+g#F(qta-i(u|X;&p(EQ1+B7d z!b_aOQ3%S43pC6ejpavK|7NsIzj`Rqqu_*nY9*w+U0GlOWfbg=8z>lh7D^TF z7pKsZRQqdam2#B-Y;I1~d0fG(p_?*|O`NNt1+m0*9~EIjaN1kq?64kKo@oRdMpV6MI1L5*BOx>93rF-ieeRTNEmbna0HES_&Q5v9D|?pA;{$ z8aEAzylyG7Bv1?~Ew(2aqki?umB z9zH3FyQTGL>Lh!Bx;-w`lepILw*$p$kZc^lILKy1qtdK1P!9iN>2By+}k&4aKCz?*`Zw$P5t36 z(_+DL%T!d$LW2{(I6ntTHZ%{Fz^@6kPZC5Xsv+-kED}YbzF^<}UvtU-~ly`%SnM^@pNEo{*nj z{Z9WR8n5(*I@@8uSflRqwOg7sNByy&V%Cn7{^cobd`y;RX5x95=?6=oFqUsptl;SZl zF*>@s(Hy4uczB^vQPBH9+BfWo=*`!v19QdM(NQ9TnT&?UC$r9eL)ABFc6L@sNC>iH zrQnop+AN64$;q7@-H}{Q zci88bwQqkknccd-g=EtMNuEuo3{vrtC%+&@$Nj5G_SESXL%%D2&&Ng^W+z=%0mj?#zmcQ<_3&9Y=heE#OX^*P{*T|VEcv$ge))zj-A z5&%epD@KmwO#>}0OD&`xU%!0$-QMo9J@X+xo|KG&fq{XV+6dN6P*4zjUZct$4Gm4= z2{F&RnH@)fDZ2J7|JsB&LXd%Dz`X-JtwVj2Rlh9+EJfxr7l4$}^0fR;y9)F^g*u`li_0xZ27q%?B=YAu(-g3kNYf`VGKxowg#_?g%ygQ8p4pM!e;Pi1&n&_Z}@ zd7PkWP4P2P&DgfGvjtoCt3w#XhT)yd5i&ZO$3y`O-vT&$^M~V@YKqFwdo9TO%gafG zWjxI-Hu;-=r3Nx;|K4Ry5m6HBGEa`5$or7|-MhB{@5wvrs+Ol=KT~g4d7Xk$$3QLG zDUXjY-bqoJk&z)8%?UWH8q70znLt*;EoD2H^jRavnlh<<0fuND+vEqf zNo)7!gOBw^uC0yh1ajfxdtSa?#V^HBX-2IVod)hWY5(#4^v=m^<6bhFA8l1t@%hxl z4Q>=kR4f95^m9G)#pY7;Z2o^#G(RtwJzp=Kbn3VN@o;edeH+R2_n2|2Q*+F%@mSIQ zdQ9+nSU?as*;~R9BH?q?#cOX5XJWf&6!k_-%w#Yt)Wr6`%&IK}tXX_~ru3f>0&H`6 z&)*w6{d6Y*IKjIQ*z+P9S1?o@2Yj1|c{9G3Q+`Fg5 zryU;|$!*-10?W`H2qu*4hl1|aCVPN~fBpIe%=8?r>$9)1>s%lI_)oGN%o8G z0Pp7ElXZES0NTIP?# zBYz}t(1bxkUsKR%b@76~2&Reb4p7~!)Lp22P%_EM$?=&qp|-qZ45yoBx0ra!8O`s0 zetegJ5mEpS(CV@}A$3z>&>6MAzrVe`4W;x=9G{B2+638)&!Dqx0fU}v_74_Ig=R5C z!r5Lz>CofgOiUBICtYlmbI6laCy;7OV&mOyP~o^dSW!5?v@||C`hXWsM`Y}+udf%= zJIhGkjxW^h>q_QZ5RiGwBxTJ#q3DlF_A+hLj9;>-JPddID}MIX-S^nONl!U*>2hBQ zR%px_?%)+ZbQ)ZL=aJFEe&M1>8!m`6v|Z2FoLGSji{arjs%O+>2O>SXKtaW-ei;1r zw$({L1c;6A#j!2(wnpZ@dlhwP;+x{*@>1}S(??l2Q^GiTkChhRTRi{r1&3Q8+as|f z0e^AYP*pWG!heShUNgiZ&<85&yBE}?9{jW~R-i8=n)DxUP!PxQHPxTW`Yu1m5#SYR zNcrT^wS+Wj^|;oTHBZuC=U>WkwyK>UnXQy0r!aftE{f(8<6ZGrnSC$YUlsIhnw8Xi zQ5d@As1g0gMhzxM;FEjQ{d0AJ_T~ob1^GXf$#`6sx++nTcJAFaW((o7k>v+v6lT5s z7e981gxUv`l9Y+E@u~c87j_<~@rS7NB%iMxp?M#%if&<~6_rRvA`sm6(QnQ*@Md#a zW+(T)NGu6ch~N38rW$vjk{uFbzs#{vClWn>AK5ANx zFZZ!Mlq#N|p=6XjRo`6JdP@9CG>;@`QZi&xGG#ArWhx50{DJs+Yg#84= zQ=iCTpi2tGwqP88!4xu_l4H2(Xei^aW*d|Yb$URogrz-Pp_;l@AUF({O^OD8xOl~? z2p@+!;KP#!UomBp%X2hwG(U|?)B8`hb^9Yc^rS38yH~rk?8B9Lvl!UTL}64gu55pm zT5MG+#;Upxv&vB=(na`=b|Dz-`Xmt&>Rl57-u)YF4Lbd!SG|E2Uq~l0x8g)l$yhV@ z>u|%0s!YhvYBtCIJ^HeJ_`uaaHj2E9TVc*^*CCWmFeWd)mR_e2jm$F*jqIG^78Bh% zR?+lYUM(i#866~jfMbE)NkBj#C|CzTg8&bYB^sG9nYvX{4^jlk63bwILofvdJ9dVR z1?S8`pQ2J?T|3XupEpN~T1I3Lh~>5n6#+HX?iEFM=;T=sWhkt6~(&rwPrJ^tO@t^N3j?*99-4K2ylQzA9&+ve0>gb97 zk^!p?=N~ml9N=GPcBD!J5eP%klbtyzBc_Fo!ydPC?7!uMw%YL;0joJ5tdWibIl>#MK#L7F!43uBm`ZNWqg+yW5;&*$X}Zwv+6=~_45 z@OEP6x9@{yom(T8b{U%A8IIT0dQgn}GDTd_v^U4hMnsmybXB6d>auag!mj9YF=2-Ga@tK(J*_Rj_yNYa?jxZ5-ruxG8Twl^oM@XiK`R{&pw5@l;^YYIAqqhq}=3|RtK|7!c9qU4%R?YQ27xi zrV!{IPcTtI&Qp-uSR*7v3hQjZMMW5ZuIdeOr{wZE5_h;DV(8)lA{wOX^RL)uI zMz@PMd4xPd(`sN#&yZu6p+=XImUjE*O=>(y&0vu5ML2;=z~ka_HZr0NiFZ&dAxwZN zG&?uf*w_e(FB}4brHu_@a258K+s6-$N7)TJA^_=JfSb<3!UD@Xz}Hu&4MG?KCIi$` zQr&oTiu>!MY_zl{p#Eni1`u)Y!upQou{(tY4~GUaSH8G|j`o1fV1I4c5@I1FBqR`a z5fu|7_auV!NU)T^&i=l7iHQQp{poLFotJyaoU%duEGQ^g>W<%b%>6+9_Mi2>caBGRH z%ZoCzQF5t$us13oVg%Y^ojUiB{_)g>Z%>t6ns<4~mLCWD&=ZU!Ofa)vFz4NS2`BwDGy` z%r-5whex&mzbS#+6}-I$w{tndKt~U4)$2$jbwAbyTp=+IR!u3$_%Q!4Fd1Hqs4)|6 zWyu%m`l1}@AJb~+h|Bx>X@A{D91OxIhoA^R;ebF-nq2yuj;f^1cjpqB4c@96m0xms zo+iGaTq=$HdffZ$LY!&+Ey_Jk=_P6Jr#$7TUbk*}bqgo`Ridr_D$#S1sw#Kr8P+;f zTtEB!qqB{^zyT$1@k4YpAsM8iJTYxxc(7-SM$Lp!y&D0)YIo6@PO}5RgGlKGi2wEV z^~k4q`}UDs+`p`(ywEkBC?Lwg9p8@wz#xRU;ON|1PvQ%(&j1w)*saR%Eq3B%!-Rpt z)corgpX2r;N=Q;SsK1VhisAuNYIU#y4Oa9n75oS?#qoCWzJjqy_QG2Z^w=~ThEEBU zHVj$#uvf)F7sv5yS{u%P;<>*%NXN?BH-c5OpQm@SPBQnh4jsEld3~ln=anYMNYVNZ z!-^O2chj9}ONXJiWzr5Rs$2!$c_v7}ddr z@IRDHO-%(&`{3~KaxoO?8>k4daZoZde`^)P!NvwbUfyAKZjKG45h;O_mxVoTVosEW z#Ju)j!CzAGU4jszFUu1usy5r{h=}|5?gc^=pT$G(7!(Evm?_vAJj~2JLqm!Oj)SFx ztQe<#tf;EwPdtA!i75J4q`|{Fzfa15T?c4Xe@iT^o8pw}u{*`>=;9lQ1ihJ`AE1~3 zN15 z>+O{}c+oyMcra3^3l)-@iAk%xME@nR|j9V9F;+*J3iGh z*8)m#jmJGr{?7|^B~)Z|4)On$(s)1hOQ*2#1;o;Lx)b85kdcmkMO9Ukb>PkkU2Mzg zXvp+H&BD@J*=-j1R91C0U!z?B#5$e{5@Co?g$lSFc8q)g{ebvze??A}YR>BE?bT`N zi|dF1C1Bo`CaPqi;Q9Yf{ZWUTdDEs*x2OY3?~Klzdr&;X#eau8t{=Y;QLXxO-)Tn^Ri0UArJ?l&mO@s zoiu9SFtHOVDpwpFT@lcA?a(FV%s88LpiVwCx)%|C50Ov?M+IKx|l3za>aOhyV4|JZxlLJUEn7t|#P@WIXq2@BI{232zTOXHvI6IU>68e^M0 zDjt72#6U;~q5&v>_I8~bInzJC{!I}5-^MlkuNAp|cs4UL13_YjChQN-zJ-Mul*b}P zT*Q0r?gjDGJ~+zp;N8gdaQ1&@0sbb>5DC9#*z6hA%L{Cm-n`g?X@;-)spjf)SX9t; z2X~?V-MW37hnw5U#YIF|xOI&g#|2!z!NeCR5P^V<#Uy}E06XAwS&S6?fut_zN%OHW z{4S-?fmFPGd?Pfw2s zBE4HvH4yYC_f)TSjm%_o*i?^{;56)t0hgQ#TBoNc1tn!HzsnHvM$h`86J`q@&)iT_ zQUZT+XRZYffIh1&38Uwf7H@z@F6#z&#NS_1LgMSHemRt;-Gc-Fz(BC)K>lYZdr6N^ zr-(FI(a0pD*tO$Ije0*KPrbGQE!4!49mYd!A zv9+!U5bJeyEv9Sv$@Cz01G%6nc-zo- z`@r*W`bvuUi3uV0m#C3Xt#tsML^@Oe$hwV!vUhSmJFDYV0ufJpDC1xjLm)(^+Ud(- z2?{ngHV`)O5n$-?K|wMrR5e3n&=DZK(=i8C8PX4|fk#k0A3j`NUq6SqG?P}XdI=fM zGk_bSqM|^A;P}bBhvHx#-;*bXv%h>8Ahr#3rmpS^wEkjmnOG=+&wAA=k8%S8yA9*r z(v%zY4k7oj0>O}bg6GmD7U`1FuVe-wj?@QB9)bb?5OS`Add|+pm7kaa=RCzFB`G~` zqM)>kPfSbz$8enac@4rQPJha#-AxS)3@j|5T^+#DHCC_CJbq^VHrx--ei{@(q$Znv zSY`wHL~n~m0U_&=QBhW43WXb&EB81LcnwzuCNwBc~%*?>*14gB* zqZ1*-&jQ}EfU5}X!))DU$7Zw(RFf}PW&ku&RB#Pm}XJAoB3KqBNu?{Z& z`SS<*r$)II8JQj=Pq2Mny4WtRNyGUexBoFtkF%-GH&CtEDDk)$D1+D}a?wDH={72~o5yNukR~5fF<|rSVgGEI5BNN zbcT$Ai;L@Tct$ugO@}y98j>Chi;HQgPDoA#T+(^p2q+hH?Zf5XWROL9UMZCPo%lm! zR%U<@EEDatw%_7Eii)7#UBUUMu<-EqyQ5~DY^GX;=CC(m7QsnbUs>UXr7_qQnH>?L zs!*8;afljR`IG!86W(xLs_U;)Wr?F<5g^YPKuD^fzzULRFwHT1PBw$r`&on_>4{8V z_KXACLM4L3dWrvS zUZWaPL}BoIBe;No_ktS;1^DETHz|_6Lv{hG4&di57dy1?l&~YqEUX6r!Jx{3(gLZJ2jF_pe0}HxnCX5D1`N33XfG^-6Qy=WOq8 zb$W#});ttvsMGex3cZpEk&#bmbA`6;d99#rt2>Q`6ZhYzPH?;XC?XxxYjyW@4M83UdPq8ajG zO!RGK1qH0qOK@)N2Xh$>_Tpdd{QX#$^xm#8-U5vD2AIKH^d#N#6trCMTWMZX=wNt9 z2S>{&AAh9Tv9f%D`nt~HhSU!3-+$#dG>rBA6c>|j(7EXH)i)5(ZWv^| zm_pacx`tR5Zz&7^PuN@(uw^OX9v{0m(AUu1)~1-R0S^gs1Od~Jr|Nk>hh$o)6pp3Y zj>Vz?VoJT~V&nY~oJlkN7wS$n8Wa%|YoH~fR}%b?*e+13T>BwD20a{F zzu@UI>^u@04qU$`V4OZ*zA$fF!;^x&;1~V>koM-`Sg!5A_|vSSRFVuyNit_BLxvC{ z8SM^q4ARnfK>zz3;ofd++Z)j^93h{#eIq zt#Cj0eck7Eo}cM*5Lq+h7{u4x)8miPo@f(?AEp50+P5Hk={H+P>&(j5MNSURK=b@S z@!kEDHLZ-Vuj%L*7u_^8QfBkeao~0OSki8vEk5!n_fw?rh@8WheiFk8moO(2hHW}> zvpuV#KlieKpL!PQ%dzn!=lWkWzCZ@VB?kN&D}pZyeDC2+;LSaPMh8gF_3PLF{P7YI zDMV3fnna(mh-nmEP^JHp4+cJ9%320<0eOb{kct%-dsOE`MWeo2uNUj#%*Wog(qMyx zDm33n9~gs57@H^tN6=@Bpq(UGZ(1aSbP6_qPgpWhWHxTu!WHz^>IpFGCCI$c10M*= zF|8=azhLKf%*KBQz<`2$yLMo=MLj8XrrH&ghb@nwa7uaEY?E?haNVQ?>j${bfI7m4 z=7-yd0Uc@vplQ7A+i2E8`-=@uRZR^OYo+2ojvy;*YoNb#^Yc%Z-rti>qSw~ZQKW&; z(EvhQJRwXu$4fSoOg9*s_(4mb7!$Lfo(4l|ctnK9o+8WzXqllFhD5r5YHoOV7%fLz z*-{hXhh!?fiikYdg>pV!>uKRyVB!w~( zcfiWEY!N#KbUejRo=|dgp_WJ%wTp&~orCkpk&-7*T%mUTG@Iq7!1nxIK@s`P8Mm1c zG8FF^Z#&bl87}Z_OU$Yqpe88a|&CSig@}gp6t(=`T zXe2KFVC8p)DhAjUxKg+~Pwx5!iMkCs`{^$LG6%nZ*T7g-U2TbNC*QZ8L|Q&5E4zZt zXcpFCAnRFKc1}*?0MNVv=K!asKE5X;AyEeXDYSXU0txiO=uLdoSZ8>Ig@yU~$MLa? z9zRY^>BF9zX0Qwz7EO{n_00F+246lW=}WVGGBQ1Ts~(TgErtG z>I<|S=C{#rpxdbehm96f)DqPMiupePqoIPFoSHH*HeS7YHKt39x{&y6ql_U})JG#C&NY>-&CWIeq?4;l_I!vApe$Ji|3w??CZ zX9p}xG#Wd$yh=x&i5+L?$jAt6s7C^peG{Pvz+L=Q>a4Nm!@GAQpUXr{cDg|*gC#GJ zFbY-Q@bF3A!X?b>m@K?5UFvXnEA+l5Y}cHxe}&>A)}U*d?~A}yLAUT?!Z5ytUFPn+ z-{!sAA`xRpfLUcTXWzfS4`ypADfGR*SP3O1C1f)B;OVF3#>x3siuyepwVgEH!cst!?p+<7hOk&u>Osg$Edckb+#KKsqJta+YtlUx1B4pM7& zUS4aCeh=Mu+`8FR(V0D;cQ8G|Ikis?;v_W}eJsH)L*R+#aRh>1nqyjfCMINDvB zUsx!@k%1o$q#w!^j0o^u+>zURONA4Cu;}5#_(lzg(Xf>6B`2$EG(7R3HlYFM13Wai zbGxc|;M zIVD2@j<*u{2)r4*($Y(^D|3xUdwng<&C{Nu6lVgZM&CHxQz6x@xi4z0yR@;j)g1%r z%#aWe#+;mR>p3kgEkG!MgiA_GG3p@0;oFsUn_Ew%_aC7!^J0DlM+Bq8Pq_BM@%)C3 z9h;pDP}9SQ3oxo-(H}W-1Z5iv>!Qk%?niZ>CB?-bUn()x3pL8^qIWCW}%!?0n~>+}1|#o^7UqgHj> zXWt~!v22$=a{lw9(#}<1JvyksP!D8ExG12c^RIQ_Z2*TEbcK9TTn0oUal*1uXa|f)9r+W->4IS>9+0`&gkRYxb{kUxg>KN76y<- zIORFML4}S*e8JTf1KuNwozCYsxIKs1Mq!%4`er$7v34m=$Iwu9jfk+Y54tRHhYBk` zr!>4qA4lL$*t?%cR2;+a911_>;;%={r?an8dYZ=abjW6H+cN8{R|lpsef39Y#BZpj zbuaPapevHa5-n6S9wBfMb2=y?OTF(c3+q}22hb;FbnUDb!WzP(n~p70;k0nxHiZ(U zr`G`l7*(&CGY)$y>fN-5y*-^>2(XU?>00SqmYOOz zY!cYxGn(sMIcJSY3qKpB7#6SX*(}wQsz}7xf`}}kTlcD zCVrbqO`&8R9kky*yc==DJUvUUUaf|X2?#oM-gN|9Jl=|jYHeNFH!y8G*xOSlf5VD% z3b$8QD?_G!tt`pqzE#G?bNH@A-}xz+e}^sRq^=o zRs1fVo}B;(u3WypW2Ywm_ldBbR9Yy z;wY%xk5K4r{b^PO^5l+;7?A=Q)tA~zxwyDQA6lKc;FCOv_B(;kK-sT5V@h~iW?2zUW&kuW_uQ~J4T82g_m^O z7!S4UiQV0@A+En^ECVmPik)BizN)@$nSFRQedeFdGp_7iMJh`7*MctaXIpj$5MERr ztSMJlSDC7A+fD8n}aYJV# zR8;KTxCw8BPMte9h?SdfT(+Hw=}{-qYJ*n}sm{vImU8J|dx_@@un9DZXjP`eYUBBh z5XWPy0K?dpkg0Yf=Q~MBfb|b0D}gkKi#rfgN6mGF*&Y{$=7_p3HBlqIxc~tpesCiB zI8C5Qb^-`Or6Nz_-N3|;o(eUM9z?g)wzR|uyzcNM?kTfGNOjugQLiFhNhk=YozLg>TF<4!9p; z8*nF2?9+lM##rE?#GQWnpj*JIu=e4n`q=UU1LEw&L{=#WELvE~03Zh@yj;tk;|&HR z?FMPXSOaVv>R7#0md)i06gd?(cGc7Gt)J&qb z^h*nY``|c@Q|+ov0|jJcWHdWn$kp=5ItS=48~At3bi#MufBE7E5X)HL=z#<0(8&Xk zBrtkofq>E6QtI#BY$aMTWe0+$e|%X0n~$~=Ed{ywM5qqk!|~%}XWNoQ;{E0(ZYu4y zGp1TOr3zQvG_zU=_&(`!d zb1ai?ybpMr1l&Jbg%O{}v6aP8T1+{XD`_Li{`p&9N?o%rn}62usIg3>4QE{Y?%fq5 z%ARZFM!DkJC%pIvB5(Q1RGB__OS$NjSAyvYiWXrk19aNJ@IXJcLmTqtOL8u-k`ZZ5 zLd2TrDJT=t~_R^ z`}X2aHt9W0BUuvnMUOR;gBaa!Tqwb>=(T^kgnQ#H&%)rEv0Y^iizwT&I%xTNyq8M-cB-d z(A0`#_M08&rT_bNmdb6om0UUw{PyUQ^n$M0cYnowYFiSFG;ifTTwe86Oh@xp!@%wv zpP0PXT`8-k@?0cuC`HqQj_6?m(uFXO~zLI+xukpz~@OR zdYhE8zpu7sr&z_L#f|4gKXTI|f z9G@VO8kAW?+|E>d`9q6exg(BDe4uMRduFZ^bgSWjt9Nm6bWYCq;$pGjn_&A4?~UD^ z9vgEPITN)U{Gtrdma^KJ=1!r7}XvD!oTVGr3%1!(_1)L`F zAXcV%4PiVX$vONxu%P&5x$o5|)l7h7LG&9oY}mhl3w{G1+ z;S{2+Amc4`08)Y-yt?lKE!$q=Zx(sw9;-*&WdXKS>gV&Rcgz$$P0e3uPJ!NG7=hgz z`ma0Yx3NoOn?Xk~jSdusYjhospN``mlD>MR*IpXaH!|X_Busped&0Dv?suzvYI>TF zhX;n>qJjc%F}k+5A3pQ|V?m#czp#IMTa`4jmgi8{;6*_@&>VDi_4ASokpxO4mN+2o ziMbV+RUdph?Ci1t-oSuq_wMIW1+Av4+RSN(mIq&iKcFNn*xueAd&UZrzg^R$Muv;TQau{tksyTW|WJ)&wPIaG?;&=_H5Prds zA<3gUg8)1{JUr0kYx0GMg*|g0jT$n>-aJX^>R3k$0UEQtrXkz@OfAQNNJvoDBDs3XUU1F&0~ znVA*ne?|k0Ht=y&lRc=4UlSAC!fH^&8{kZTIzAoRUE1SQ52&EvIc8~Mcd@tuoCeDR zyy;RivDEN^^7VYGe9;rizXjJzjl@0&b>$-NEyPKfHjDAC5TAc^`-OpJ1^XyU{5<$k zOf~4nAf%5eDfgY5M5@8^-1ihP5yG^E02U3q**X8M1wh-c}8Me~Uosj*bS8W)W zfQV8-UY>c&e1Fa68;Z?kZZ(LFhMWkph)W?c!nXJWg|nhv&X5O1rK=LIB1qDAllTCnqM#JVteWeUEW`!!Q|i z3!^{p7}d^a(ZxV|*H8ao?+TNI?dS#dC8zGc$5??qF&1MDzi#kVFh3 z8Y3DG4CT?PnZ7cz`}SR_1kSk(NrjI(0NqnG2GBIYHGY(!th-8_l8gmxcztKu^kAqF zozW}re1MU?ez3&3*R(+Bz=1&wZCE~8dSyUhS-0R5DGzwB3)IV{Q0zelrKP1|^oqQG z{Sk$tZ59B=QZoF$yI;CR+W9X$Z9yi(LhacGbzRi!hS#*<5ZJvtD>CVTqLLDHWkh%` z%OyQ?LI(u=?$)wc&WpDl`@nETOHx3q-VxXr2-E@G#yx%9Is$RE@(Koh*z zcpWSorx6Ab{}(4jnLyKbaPQu;@J1Bv%oPILCVw5BE!z2tsZDsDb#_$aMIz#kmZwXO zByzS)^M+VdFE0Al1fwCJE;%-RF`#-6CV^@p;~y(`#$o#a7s%~zlnV0=$J$+ASBu)< zrNgi8^PvK6gNauzv9`nL#t&qBL*X*3Y-3|1{}v3cA{BA!9dr7 zZ!l0dPmkjM!VSQsdVdgrM>MzPJ%^$p9Q1jA`!IgHYuV@edR}hs>;7Ze_P6id^M@Q4 zx-MjMqxO_NeQGOQ8WB7Jk`v+`yQjUd6yf#4cu+qu@g|Zz023J`o|(067$qGX9N1<3 z-w(RtUm$M8(`m_6+eMas#B7)?jVtT_`ICLuE@kJL2kupc(5bM?_^N0)+no+s6Ib(n zO8F-19E#kN!X;rT!$V}hBjp3JAL1wqI%El6WK7~}0eNA-!kg}ozzs-;)~$XCh_V~M z!)JYB-XTrj+_vCe)Md2w*onXf0I+pWQ%qmD!lpLt(q>B`{KH1mR|GeC74vcoXgJEaBF^VRav^hJn{I+eI04WVdn!w!9`U22Z!%PY^4n>ei zw3L5~fzd7-gW}@=PdsKfPtOMnN(zvQS*`RwP-jtAR<602scl!y1z?0Ts41z>_$g9v zEo^M`^3!}Q3iLU*BR$eyTVnryL^3zOe0h8dUEt19jCG2NiatJ))k5gCml%+~={M1T z0$VcVRVPl=0NEZ9(;;(kaXo-S053EA4QeVXh^$?Pm_@Gf9M5v}d3}9jL;0r`)~$>{JW z^DF0h_V3>h_QQ0ELZN_#!KQ#OY*8O0&xz5YAC?9}56=A0!a_@odg6Phbo1w46IbrV zH(RLzN=b=}+sVvKgag5%s4@JL+2@l3&=s^45cUFjFD`|E)^3OTPo0@md)wUE>pDEp zbVIXs-yo`a>4U4MC)D59n*X#l^~vRiNXN}a|L+R~jw@Xq9bW+57}z&&-MZ_BA_iJN zO-=fw)rT_Mf@SfhWan3IqN8j0@WHy#$)Qp(c8^}f`qbs?7Fly%@9XLepdv%d)zy99 zcF&$RPWF5JH_^eMt3x=0Gw^mexDWC2E>P1WJJn3E|B17Hz@7ez6-`es1OOL08HfYk zfBt+-Qql*(rl3m_p^VyP=9~6(ONc-w6jY;liExiiO-$Gr7&5$%VaH_0o)3Kz4F|Ls zRtXC&C#JCXBAOIy2&FX7(6dr`b-{;~IMlSgm{wZ?JelBP(J?jd|JDWx5EGeXV7;n+>FN(QFi~lm;=X?_%&P^n`m_nyPIfSASIsajdSRN{-jh3)}h1(U?%lf=8KYpz zv5-KD<9{Q1qo8C;2r@08BFOId1w`S9i0bqaG8A1GJUqk}Hn@{)@8R0e#zsbJfc(c? zuc)}T!V{|b>r0o)n`J~els&pvSA0Nv>0j*F1eHuF}tib95F4RI3+5-8x+Yu1PvNMxO4 z6222|a{ePV9us@h=j1mz_Du-2sluR>TF1RK@&|H&=3a4%&)7_5Vc%a5#x*9mYjU|2>iAiu%(RD-Of6J6Er>KuGT|}@v^j8G1CYI!A zVIhoXhfeIozNm0^*?ad^X+QTvg6bSZ@K{H?_ze{WU#trp%6jrd_|Ty&>_O9%B#6sr zV=u?WwP6fuGuBI|T)B4b1o)zum>rk4Rq2*haw$-S+9CC~X-I5CGYkNw+^WoOzt&Du zz|(a3fbl1MkMPRMrrg{9uCdXE_mMp}V;Iok4?=|2{8e@J=oepu??R_eo#Nr;wewCl z;@N%!lNME5L#*l$8j)?#K|45f<2z72pv;*4*-7zfvX2(a$?>nk>)&x{Htf9U#s{X_ zr^Uu^>h{{COPy{rJ|wN!7V7}1vr48m>VHl51&883)iq7yDU_jAwtr6E9~#hd`AKEW z;4*dHlbELG6Pes$jMnLULsCL)Y!ONk4!po1fd>Kl0{?>U3KRuMJP(9w&76O_k<<_o z74`APj)UN055=iM5|DoXerG@lbd-pW&^&i8(^vq*m=wf!6(tlNFK_P?N=mAn(fDzo z09xw3L7!p^sn`?CXfM20ZCIZIoE;R84}VMvb3{<)Q!VsR?%RNCwc(e{gIatn6Hx`8 zA8*|lk5MKafLW*&3JVJ*Shu7!+Gu;Zevq>FHWa86T80O;-AAD8Q9{AN(r2| z&WiUjAg^H$gRx{!ZnCtkOr0TFx3CvjTUSdr-9X1(-6&q4IksH$6#EAzQO#xAKG!LS zI&PsP;EG`md6r??O^5JnJ{CF5F1BeO7UZW$qH~;DQxin|_sEDnuw6Qi!fy;_1?X!40#dm4n7~-$X#)lEA@jA zOJ4Jns|9t_m@Ku6kr9z+ghTSz91Dd9LQsqr_Sk{l#r9W-IBRM)L1zls^9x$vk`0;H z5@}y*OU7%X(IQBAhzpR#<$n8-(tfRSpo?20!^4dWt?3#nVg-Vs;O;<55mWHQ*G%8<+V@KbAR(hC}g*$-M|elYNP(pkV&}`&X7X&f_s)o36kb z0AbKI)8x1=zTZi7=LT}y)uEUi?{hN?(xB%t22lEm8{6UV2RnbYOxr0bwSO;9TkJmI z_rqb=(_6GJ?!z7c>U0G@K2R{ouVAC6e{sI@{DOQnNp$hwcNwh;5={^RW^H9(U0q$>>(`tjA~6*y510WMBO{Mh%$^~oF)|_|GhkKI zyZYZvoFJ93mz$kf} zm#x!Lw)X;C*H0si-gM2n@iZ#!odcGSWyCtwWF<{UneM=+U@@g|!6| zz#yz!w{F7gn7j&zeN2778QQ8UrC;h&JdaV453$sr0Dwu|^ zUbzB&5}jli;88Pi!T^#Cml`mIAbM>R6O5Cm%$U`N!VARWW2jbfUigQ;V8VI5lB&f?PI*zyKB?p+Mi+)YOC$j;Z$f zq#Xi8^)?|!HP91~);VDV2nYbg;bWn9$5`U(`XnS|197SxluQ3hUp7rEDTYFt*l-4o z=2NhY0NU2AUd0coL*AoDINJttba?87`JuJw?M)f~1zM$u$hG-T--pjqB*ekNAta>9 z%@C@1AM7jAEw;;}y6;4Cp0za>Jq>6~+YzMH<=*bm8;gzGm5>nE+Nx?LoK;!7I#3rm zr`wq}pBT~|+NY*aJBdB>9tP98ckfU~;rM}C8Gjtc@d$pU85ytJ+v6)C@Zddk$eiU# z*D;T2s#cn&NN%48^s86$l9JLdo$+TtfEW7Yu`5bz04W#U)mDyc2q>e@)9F$p}jVFCXr0cJ?Si?O~NzTPyWKa{|+7tm3{b3I*Vp zB37l&4CH$l5r7V_%#RAnUsv2`fB-~K&m-_yP0i6(KNiC>w+kU+4k5Fz$MQ2V`2u3H zYdf?nhGj@81@($PGw2qHRw}*)dP>V|Y;6vZmC;tCV<=!l{>U77V+=89r7ay`6aUxt z7jz3Il#efhG4}$(2aExABHAz1YD0;?dzAcE5onaxzd4l8-A*~~=^32l5UG?6A#q{R z6A(QWijGj{l6B(~Vj0DyJRFOLVq!N~6oly}JN%iO9pN`A*Kr|BlSt3cyZ_02kiSz( zta$t70!@wNH7^|0_R_bvs?f3lEthk9n9pa%6=T6KLlOlNAalMG|FwzxvmiUToWFX` zCdPl|_WoZAH41anr+>XZqKyasx?w(9w$nA0UAUZS65Mi6H|~Wc<$@ft+d-uW#c=k zqoYPTI#yIU;#BxGv7Og81N_*?-?4W!@tCdLv)Z#ZrL`0xh%Gc*3GIUU2x~*^}ASPg3_=TX& zlPAsVL5ery7#O_iXwLyJAEhO55Wual6hX~#4`vA;AD@a%?w>KJ;XIQpG;lCQnE~d< zVns$2o_D&8HF*^`zs`<2Kv%t919T8z0RHTK>Z0Hx9ZhjDAXfa$%z+@x*aV2dh#*BG zrWWV4An^iNlp*~UPY?B(NgPwFC^mE!<6 zvA2RA0`Ci*1|Q=Jq{NssvC*XnpHr5Jy@ zdtcuKc4A0sut9Y6_L}*@b(r5=xme|U@i&ugS6Fet%PBwhRg7Y7)B1TDvb}$oIH85Xb~5Q z#b?8$#1rRx{x*K$l;S~}Ufl(rgtx*nM<7M=lKV5pL|`tx(|maeWc;&$Q3 z;WYtjoSTz_L2P&M90uJ_NWYS?&Rq2y8=V4_NO&;dnF|kCW1x=KG^D8t))(0j*mi* zll{np5qsk46xLP96Y1>vEMPEq4~-aY2@!$!5?HOZjSahmD<{lEc&CAJ0jGx46%2M- zwhs-5=zt=|@Jbx0e?YMupE>^b5@t5Y${y zv9Xm8Y@cM_cCE7#o}B@$1T1Y28=Exo#>Q~LBBol9Yb*f#+86jLVCB*8-w|eljnSi< zo_v^>F4a7ZqCzeC<#nvYMj#=(?x~?&r)u>bEs+X}PwFLA#9>8z!$~i=W>gqUK@QD~ z%G?s8H2XZAxTAG{U-Re8>)%d7%e@l{TbP8rtYh!(y@-vvpa9L#py z#=#|!c%sb%SGx$jIP?%2EC;6ngHR ztYWxaIXhe~dKRoVe9#Y`{`gx;wYkcN*|D{Qe5phU20Y4NhNh6k`MFm94K9&%e;@Kp z6S!kFwR!*rfXJi$BLJc`0$`#(!(?CI*q8!RDkP*96X8h2qe>?Q^(O%yQhcjSo6!-# zK}fAl?03i{GNG7-Ur2kdr(@Ou&kYd0*d|WJoLa0w2UJio9_Hd&nGCH6n*~@^eA&{-Zkw z;er6;c?gb%A4}L<=rRX3%afyi%`M`NpwXWO@`A8&K-Vij16I1JPiL@NUBvGK69XkC zX@Uk9)FaFaP~8$HKU4va+q-94vMC$(M3$a?c?AtQ-fl==1k5Ur;Z=ghaoPks9$Nl? zN!am8=pGuXcLyh0t&Di;%V*ov|Hx~ zj3IN57C@bkj_Cx%pH94KDatZbos4#j^BAW)fI-E+| zmZJzYPr?>*WSusE&hxYW~d6ht~9C#{*-PZh8;^ zz>FzxuG~)zVrGNH0DujCY=0G zEC;b%ybJ%GZa^~J17#RdBG3@X!U>ca*}Q;dEXrzudjw>r|cas36%AsC*~M^to3bI_$o_v_5)bUtk)-LP9G-$L_BzRrKTu4$#wG(U&e4 zmVtE9&jCdm9pZFx^X9wES)v7MVqsXLKTio#U^cczugeF@ThTA--t*I(fT+Q{wlR6j#lGTdBUu^kj`8o+uBqM!eB3>b%} zOBj~|W~6QM2#egvfeDIE3g!!fs5B6p*O$gtQ&ZQ4BMTBAv;1>8h;3kC_t-%C5AmcJ{_f&@Gwi+4GDaZJp^pDt}>vEg8TvqF7W{{>ahPfygpv#zWmr z*k+rwR8(l5S%lDU-?lA{JO%IGDNG3fnn8$GUHUChVV!W#xZ@oPKOoenkkOzS#)E_8 z28$P0fTUcsvtTf&Kq?mWr479xv&XOk<5*xWSXR|Nk!X@#u-Ne`7VdZ@lceF|cRIa7 z?6u5@0L}&A=T~n_|AEv1IlWUjDB{E4Q!%braD<agwhwLhSUr6S;3SN=w(x6 zRaF4aUBGLArWiKu!$?a4t|;0^k57n!B2m~{di;7$oxk25stp>@aaAod?LVMLUJqu)%07`%S}r=KVG{0F=^cv~KhTdyV+348G{ zl8S^H`m*$Wpe$7q>|R1&+c|oXjvG)uunz2*2x_2=P#4Pd66@c-WkQYsMu8ytv^2|A zP{|SBFIOLrKwf%!xfG7Uu`x%$Du2y8cyT+QUCd8g^YJZc7TJ|WH{uv50-$UP2XS=_ zbRKwWP-{?x2XFZv0I`k*m0q+M-HQUeu}yr$&}*v%5qVrvk^_~ysGYd-$Hu@a_5Z@0 z|I1+fuhWW(ju6o{_%i(WX+^pJKCS5g(|rA(AEOiurC@3%53itrAa7Py+*>Pc1;R|t zzXr-yA;!@6x$jTJ`>@dv(4D~Kr10)n;^HNPF5awLYMc5n_}9M>3O^;l%l9@;g)%mN zcls#3F3$V3lNxZS&i;E*SFgsr(7&CKl38_`xCGmWInRSQ zUcY)3O8@OQu7l;~wQD=8-{PpGX;Bh^7mUXy?Ck#3$s&pDRNDzhC)WCRVKc1~fBzQ% zx~InX($dj6Ge*`AwzaBEx0T(SUk3!tO88Jajz@h#H z6)T)N7YM*7ff4g;o|}!Hk^SF$1nM>oIF!Tjj zr*P_N+?l+Wh?5FacYcl4V{mB0seND*l+)Xw)o#jQCIy!K*XF}Xrnmg4VQx{;G+GnL z2?=B7uA0fYH`vURBt3YblFU)J=-)4s0&!Ugg`z5xf)su$=bw9{bb6nAyDk3Nh~Ncd zf!|=U)@&g3dgxtdM?0z>KbGTWfE?a)w0-I2)dlRdbV)lP+T)B<1t6zoY|P29>A-db zrjafB(^2Gt{UGbCF#m;RbPycJ$Z$g9M>R#jP&@|u`a}qhsX4A-cypAB4yFzv&#nVW zHwr$+Fs}$v22SGi)k2{j)JWBZwcs#Tr3~ZHD15fNw{B?~=bR^JgoUXA+}GBQwlD`= z{MS`M%Yo_pU=fbh1JafbCXId)Z6T;Vas!zz7IBJEDl`y%aw2OL)m?R=^_Ul49->^! zg&js%kU~i6HH1n!|MW*Xqy31!fk6baAj1SJ-+%bv40Aq|tB{H)MzwH>8Lb zl1Iob9;T(+`aHY10#Md{xW5ata8S^*Ao|O47pAFEDgK{8eAM<;FM%AgZ2$p=lTPR6 zx3G%7ZG8VeT;Q=?r*RxAvZ8V=PC4R`lpMnOhW7KgX}YA}ovQ*F%X-%G46@+wf;z7{xvbs9` zWV4IkG|pgbxLmm`&MU;&-l{s?DnWWK!*GTe#Q=G(U7eP8RX7~e)a>}^;MnI`>@y)- zSGpXDXE-Eh#_Nd!MxpSqvVN)rzWS>?JE0$1SCNB9tKYvjZhbJJELPmV6X)wS*;Acs zZdQI63{#9}6(~*JYoxKXMlQQUs78?c?a)NH=t;z2lM>7DYln*iqCTK2hr8y-H`+hrMB)D9gAWRn^V* z_CRlR@-0`%In)g7-ndrYEvi^0s1UnVQ~%NmL(0cdZl5ahH5IEv+YRhBw~!+sEz@lxoaWpwZv+F8rzSwo1B2dSYU z-4oZm=afkt8eV~{sBc5oFo6WWJn4t0_!_ufXYrrz(v`bN%{Z9bHX1Qt+OnYr)tb|1Yr*H| zyPk$H;qU#3!vzxC)y)fA597}zMWzMSgOk6m^O9_bGMXg?5^Sj4*g0vHM)^`2zjxsA z?x00Z)}t&8+i!x2(^g)bE&4DCC!LGUMtsk^cVYNIrFMTr&%~#hlpvrfVTNa#4y?FK8H!Er7htDuW-Ght<%#q~p)Z5pL z^!0HH)e#f-8R94_a260j1t{R66^Wabx>doV&t!_{FFzl$Rp{^z%ws=~HPh0C{^@t(NLE9Cp~F+%xx3FuA{{K}7Qo^GIJ zdW@;2#-A#&j9iL8fBt~Of^i;lzk;7c%n-^8(uk=rAz(^_?R2LRQEletZq!%+4k0iW zgyMpox#;3Ta0WnbI!{9{34P}<;vS%3hki0XF7D8m`-rrx5j%p-p8Dk(4l>#!YO9IV zG=At<{@ag7_tWZ4#mYzMWsM%AQHw$O*nrEWPxAye?tUR2X6Rh+@+h{ zNqXTxY&3yCu5WclkMAtA}hZ2aLjZrs6B(AL%lD~87f0248o4wvdxi@k^)78zZ3>K{8W7Ozo`gq@DT9v^UwJ%BPS1J>2ripLu&(h z0N`!njK20VuosvIoxhZ~!B2@y1&|1*5=C#A-}YGrBp%ZF3P3v3wGi>0M-(3%$xwp9 z0yPVT7AO$;UP?}mFCNZQcj<+Jjleh%9AoY5 z-1{!ie?7^|`>&P&KCmi8wBzx^n+ElTps;XsJZOg$dSM_r^4B4ZaQGu%thU^MgNOA>uE0KobEOUq4YBIH#d?BfrQjC|O; zAqOX#I5|N^r9^`RD@`j%AR0DuAmQWRxh)kqBp#$`Fx>($zD{=BYzCXF=b!RK%)ap- zFDNa|fAlCdAIfQbJmQYvJAvzQSG;QnRi2BI$?*5=KW;w0aIOa{OFsi-7sd(+1u!@F zBU84Tn)yW3Fgl}ah?f6c*3#DI0mu&c{Scm@`^~PJ8bUKQ776@%hUYeMT09J`UF%e4 zxZgZ@`c;`L#_n0cjE_SnVoA`KSKhzh55#t6YHA0~TGd8^v~0>hfF0x&OkRNrAdVDi z%=6vGVMyTkhRGZUjPDYlmq!tyGBd^N@U%T&!42Flj|Er)Cn6NFU#?^v{0R7z5g5;Z z#M`G14?Ki*rOfy|j?rG)f_cFYllJTv%<~0mvt3nNHPOnsSCjop?TSi+A;TsHXejFe zxq&Nw9Ps(kL7<;K({Qu=_&%9U;W-dZfyfMdP6h{pq@cE%m_yR&nH@U{^+YUGRIcB- zV}QO7FD(rR0G;$fwRpZekeWb$2CTuK@c(-7NuSi{NXJQf$f5m?=>?UjRBShbQDHY3 zLyXx4H#fkZZ4#e0?%-NP<_el_03e_PIk%fs`B#{VU6yM?8Xn{W?&F{0{(|P>IGLIe zIN3S8kg!vqvt{ngwf}~4tln30AqR$^b+^p^cx^gJ5k@&P@AdF$P5iH7j?{~=m_n<~$F6csLX6D4`=t(GH9BvQ7S?>(mmdNEebf^o!BiwQ6 z3Pu0D(6;+R&6bE2ECld>_j@(qW`?{PDG>ude&CJG^D6!U3;`Cm=%}bZj6o?Yi#AXN z7kzGT_r&`^?ANzA6yRfe->%ndqyR??VL55Q)KJ|V_WonIT%NlmOf8HO-5-cE8zree@}ij*Y{P5zVi_9FnyK{g7~)9AFF zJar1(F(S@=iqj53iE$W`T|6w&KFjCmNt6CEOWdu{cgf?CaA9Q4AOiXStu#*xcv{>q2-8Y7GLt#U+wo})W znkEk0P?=sc`Y(Ed|KJ_}|6r4FT8Pc$)X2J~#-_U3XLzIPGXC**uHVzB za+Z(bj?tYkz8T3|tDY=kIzU!H#ncC>e1BU~-~;3@L8$YxfIpG|C4#pA6ER#LY;NUh zV17LBuTUqW>hknci`w$b*eABjBX|fghF74aDLD;>ZTS^iSyylGJF73>xLOgjt&!#s z&ZM@*o4_;?j)lMW&8z2&d`om0zuo5i{r#~rri+Q(c1{kCG#H@Yy`z|)!3{> z%M2S6De(NecWO-Xe0;IK*Rf671|t9_F6npwO2Nfmb9Iw|H7_*QuccYF-6|;Ux+4SS zt>(59^vAVyMi|v{4L>vP%uP2m+GS5Rz8=c9k8#QFctM~MYoOr<8X9qZo+!2>Cx0x| z`l+|@y`nMqmBlX9Hx&L;S^@`Qe!o|5t=Igm&|UdQ`XWv%PUDb@Qyq+4kXjpTE|jpI zL=_zy+2rT9{{-Q?P3;~{&s{0VP&u9MyKbytzh~TO1%)*-QIYhnE-o)SX7>g~$Hbst zbB3t|zG=MsNI%-lxPA9-JA3=`OTW(t(TCL-;%H{Dyr4lKUxQ>)a_K!Xpye&ktcg<5 zc4xp!DVN?C)!Ny)H~-b3&Upto$I%xp79e7%vgA9aT`#LpE30BoY!|)SKn+%6*Tsept!ZV1{Kuo*Nzj9^wl* z7?sFEqi3C%!b1X9BZZtGnEyZ;0Yp%sf$$}(1*duvUvd-W?eAv(@ox%Zguwko0olB9 z;~lW3;6q@=x~|v{TFOrJ6M+hd*xTB2a&aL`hLAsiL4=x1e$@N#&mvK!%iE3p_4RKf zr5tn+M9ZP{%wqPfVBZkP0yYX?xQXazd7fG*Yod3TlOrWH;7Di-bXl;Rz{vD41s`v3 z(5=G4FnC+aB^$cB2Ek8^3ku;-E0g44*`W*KYgZozAhT41M@`N8CY}HXX-~gPnkbOL z*ZEHeiPKv!d|4ebR#yIwv$DoF@5WU125w71V{!0aUP!kAQV&|&(8Q#Id;sWvO-qX< zx@Om~ZMY>G?Zrcwo>FV6$df85nFSif&F~IYpMeb+ZeY(kB}{31mC@(EeM^Ppj3y0% z6A?^(l(e+r5lN!&^tw`1BzkE<@R1JG1L~L#w#nnYz-1pf;yE|DbM5M=Dotg+f^*1U zOgEF=v0j0Qmp~NA;}Sd-9}XrUqk+B!`g~XrK}|3&zPZVUO_>FUix?DI4>W?lMw{H_ z=sAN?0PtVr3QeH-F*KG6A+LnO_0CksYd^KU1HMe?*C-T)z0VSS2UU-S)r{oqAk<>l z+;gQpv35fU4;`AW5IUAwG#u!aU^xQK4LS08tUR>+IPE$&H&;wllymzHAcXkR4n@#i zTl4+Rd$t)_W59x)K||K*jFuJ}uhw?m_dlw!6nu{T{RGBO%OU{xxJyb)&gYbrcq%Cc zg9r=_-6XGqw@^bP|M6pZf~FUKc3=kLK781&=Q&A3L*vjoN>&yI7Y2^kgRbZ}wh_qT z{c{jGpdIMnfD3}hL~%@KMV+-7|Z~G1(?ps>ufA#7Lp6lKqAzJ7Z7j5POP6LzW z<#qci*Ztx}eu6+;N%|H9Eq`BFm>E5&bNy=y=FP>c2q{zv`i&(_jrjTn=PiDdCzE%Y z-Ha4Dazx+Q{o#GYN!j$x275e4FrU&Zakdkd_8} zfw_|tzdRMR4uom0QYUaPOm>A6Ye|HbN$AC=Pm-ddMHs?GMO)GRx3)5H6a}m-gOEWE z3-l_e6#hQa(iIr;Avi!ODy5u)oE&1gTemzgo;Y*_->Q2oj3YmN_7itl?%<7^GC(j| z^HWY*SZKRX8!Vo+v`mj|^kP;YcyamaLk;E09UIp9B}GTy&&n$HoeOP`_a1uB4j$Um zgH6IdmL*R0+6e+5+q|t)Y-jE}fZ}OoU3<%b^^n;19~Dfl6FpC6+}+#jI@R3JP+t2+ z4A%RRk)q!}!)9h?cCsP=fppk)O4w?VYBwIjycmuDAPqorD+JUi7_;M27;4S0qe-Zs zUkv~IKQR@uu?FKwW@cpEm1R$-5+SkNr1!+f(I_MeXws2G1Peh5T}xy<5MS;k&Q)~L z4s)(4_4(F%kF$AMl9-vIwT-FLayU74R??B0%;#-KEtwJ;@^l=T7|!k7!gn0#UX#xig++WGsDBtjL+_46 zcW|een55<9aXfsric}FVNzP9EQhsU9L!6Gz$9uP0PgN07=gfnG|I|i?B*Z1y+6sNs zNye{=P^V)!=j|tkqZbSrLRBp-uUc59ek`I&nmI9x_Y%)|Q)k)$!LE%a_fY6a#v@D=<)x1K#)0Hl#hy&$6hZ654xRCgbYUqE3r z?uuVF*T)tFwM1~R66{2!rKRz@jm8BgXR1HE^EWjk6piO?n-Y{-Qnl@9(moj;Nis26 ztW3?zQ<`nww7C}<-iC&quh?+%1}Sh$#vhM4+HD|=m1)Y#%T3GOZ=_hGriR`uI(Oyp z#dWUsrO+N6W|}O&s!)ogb0Qa=5a7V}iO~y~)lqQvM~@Qd7|;t(Y*@Hcu-UEvP{Yp{ za4Ea{KF4Ne#$)~ZHwx>Y3Efc|8DT~tliT|!+W~*Ve`6AgFd|z!@k@z}N<`Mi7`5WM zzd~(eZ(bn(ZtA0EcLdg#7$y3Ye#KwX|MFvS*JG-pZ3!n0&-q3zZg#&Kgx^+y! z2%3eA{N+OwkqH)42AnQ7-mT|y_}vMz$GM4x)jZ@c87X(BW0%ws3ZhR(H}hndss42Q zOKN)7N#~2V@i9qMX16(u8_7)odtP*e1_cKTGlhdESp0*%8NeJZEiLp7`}Xf&w;7v$ zSbl!~&6``oYPR!?Ljs5m-A4Eh{z*jNWl-h7TrXrHL^9 z9mF05pFCJ>O|BSVmWx1ARGp1Z4j+h%q{rdyMWvxCQrpa2W#So1_v37G5@|78EqpQwwoREGQdhpUeEbN-^l=&l zssY#2%+d*JLZUiaN{4gjh=Ux^n_?P*iaI=H@b!%yurOnPD++!SkT(w5kTE8t)Z#uW$Xz5}1fVFf8+m8Rzj9$>K^7487c*w6S zfG^TkTgiqA;GQyaWTbJlkZXjwcJQMZfh_1apgYDYj?;=RulZvML={7~iYl+5mQ23q(umLCttPQa4T&9OBCS8-;$=3Y1#b!tHlq%2h zCcwpzSK;J3K=Xm@KwSesIUmDw2-5WMn8D)vH2{&Or@K25wS#Iip{DLb@?Cc`G4WJy zrPV1e8ltx)$te$SPhTYDoNxpNgf~W(pi|l;|H!N=Ab)m$%!;(2h{(j|>o@}nHdNSK ze@#Q2@xmrb>K2Ss%OtaMe!C^5?EYOwT*;^NDU?qD|53kwOY^gG0!0UfCO^!tj}U zy;*y6%+Ms_#}dLI9JUi}VBAq^{#Ibt)s%RwMaL7E1`(HnJc=R-DUhBnE?uC%vt9cV zPH$q{mXyMMTKla%^JaC)~1MwHT}Z2WRgc zj`iRF4=ZJ*Qb|T-MOKtVkA|op!qew!sS5}3PWR{bR z`|;NGy^rhn`yR*r$9*0h*QHOK&hve~-mlm5`FK7?sLAWZZ@WO&Tex-W7LE$!f3%Dp zaL{P^JO?5f_Ni~H3m`o3`&%6=fQb(z9+ENTAjAMe!L8E?1pak0X@jzxO)?|j`Po;@ z+aDg=iFxLN-y$dxeD(Bx``^SZFJt`aM%8Y{$#M zjsT?fiahuRP80Y`8HjwqHUUk75_e5PotmW||>u_JbcKIumObwZU)S5TG(FoG`Ak+6Y$hC)SDHS z&93k-4i9(e|DOM2OA+~We6~Z=FJ>wKFZaKSiHX6&1P5nYcD4xFRz)QMqVAh@4rgd- z5DefjtZ5Jckd=NO5SQjmGwT1?v^h`@!)H5B7CJn|t$*Ey%^h7JOr7pO-2z4uFIu z4rb`+kMRNPfcT)fwsxQ=#)b~WpB?!}08L2i3Sr_s)`}(;{=J1DWeZ2Xh7j?3Y&68t zSOOS6JrXfpf!8;dI?b;h?i631GBHiM@SGbdclRa3h`XzcdG&$DxpRDE5JI-en87>( z()7swmwgYSSqrjr_({)SPT855JnD)way@R+Kc4aJn{M1|W!b|z?YgEfUz`Is4SqrL zTxg$}R#{TVU4hBFyDz~IsP+4H92g6x7sQSnap)~-zS>M{2zL%_Bq^q6QEbQS(EN-J z(6u5@B{cP_e%rm@)ZU~-s21P}`d$#@;M?_gdQegPNg3YQ(w{4A^AaN`~Q(8DM2?4Hn2TaBe|H6f(#k#as&Wflsxy{y{K=#$89KTa9ahn7m+_ne-) zEH7OOkbUSx2gY$EXgOiEyN|nGK|1UgLf!(cgSluFnr^I)YLPz-KIUEUkz=NR50DFv zP5&p@mlS@&9V-Yp=xQUBi?Am+9Q6_LgFL*v->6Z^;xIxQ+4rA6Gmsezt@Uu-&ZRU^ zW5C&2`uiTPCioEH1Kw(3gSrUvQ~0@=iADjAtFB)4dXfj&2=Ez547jPWUo$b@E51O) zS>aX>``m(!#UQ~jtDai!D>FTnj92hvU5VM-^*&9&*rzA{+!Q}>;Js33&g9H2s`OVC zV&{`qczG}Mx!fCoJvvNmd>#Uu!9dXZ4txgyRqgP92RI!=7)wk7kRyl^ktPAf$j9g- zo2p*ojK<7Y61@KMa?EG??sO9qeC(u1d4OeTh*C1tQD6=u3;@|^*uCufOQTeI1BEX- zK=1PD6MVdDR_Qlw!X*9qSS6IsH?Z_l3lUewPsGtn1J0CX2>-;Du8 zO~qAo*0=t6YsgSc6rje+$Pjw_dh?tsm^RqGvV`*zOY@_RVe{GDw(mIY0pDCvg_9=( zPqkVeTs+%eGJT|OslJGxF`(=9ap$N=hF9 zDIv`Oz#nMW8xGYtfQv8b-^#etZNC^O2QAK=5|KvJN`l<5@AFN%MBKSAA z_PrEF9s8LNrwcy*Lx--S;03)8nzG1Plb@BzOb#+vuz`bdvP1~raXNs`VSuHxcki#s zNs;S~f&vi%4C&7w78Tzy+jnQ(mEvCUjM(iTxuRog1|dkc!t-Gp1cNxVTmifMTxr8a zaFA}Bg#*u-@HMqD2Dfi99DE282p)!H*FBucNlD(>DQ_ohp@F%@fK6UPOzd4}$rP+O zw!l`swA39hh$i75@d3KNp92-L$Br!k(5Fejh}B;gY2oZ#A>^^v{Y!f5sk}o%Lo2=h z;K`SG{?a6X0<$s*&B>Q_rx(>*ENl|Nynrv5E6N=#WeC-<=lQGYYHC(ie8new>rYr` zz@F{?AHRx))gG{Y2kbVp5??DBTcw$U;T@rpnU;oeg942j%+JIcKw1E*T8vv3jR0={ z4Fha@E4Uq@B*o4I-rZhV&!^Cno0`gA?htxgimefA6m>t~>u)gJ8-um|827AysrF@Vvaw66sv4J@IiywRRWJB>1|^4_9J!1x!<1C&(QWp*EP z+)!b{v?@;GARO8@bdKboi{``8AQ0h(m<05K5Q~60s9~Fn<&GVsuy+)1){;BB*gYUu z<@}D`fejQ*Z$XIUW8A5rIK7H4{x1fopct4cDiWcnP)Tf4LyEyp%x-LKq9FFc?T40Y zknQJ}`Z-7(0R)5Zj$0Y?(HUqlHdz(s<9u1dKnPp;lI;L&kw^d%%%;?+zPrZiNX?U$8bly&-{SWUjfw@^g7zxN5@zSxPi7X z{K-jvsL~#vcKOv~%`BJdAB(WVg7Th>Mh!*;$~HqD1A^VyLLR^zLR#8bA?1a;F93Go zludm*vA1zCU}(iYq%A`czf0i2vK&{470Wko$f;m~hiJ3OfGzU8sD2IMV?qy_U_52* z)f+Fe9;_R0{Tmv}`rH!IX}?-x(nQE zk9zs89Kek)yI}{Of=14R&*Ce&GnClw z)*f!G#gmb7u;0|scsuMkYz!JAHELaYsw4m%0R#_`Vs!72RGrtt43D2VB+?)w!DzrS z6K=m+SOln5A#Z_I0N{}S0H|ya2LoadClIQX3>q||F_^&(|Evlyuhx=qNmAN}+Dx;V zxH&2MZk2w={X2Vz9>;sA)&h!JD0>A^d*bv86zWU+FQ7LO(g2Dk>4!>|0l9Q)-dQJF zLoX}Luo5JY(Xp}Gv?~C%QZh3eEOJUJ`QoYt-ZMN)b&%%@Mc1BKwf3WC%{*2Bqs zTIb^W>$ACzW!2v;AOqp++}VFFy*q-L6dmp4W9E)MG5)^T5fh+Ku0RDvxTZv|9Wo=dq|gSh^G zl=e9S)_qDB`PVqd^4;Jx;~rqaAAJ5nxY&giIT(ftPaEn*)jK=^PI!?)gWWdwwQv#cf3Do3HnVAABYuubE*j^znVb> zd3j_?vvN8?6b#y#zZ$K#C-Iq4G;MT_*ePZA5H6B2F(_2SvDIOu32Cmgb}ddX)F2rC zlA$zxv`0sjg5n-dmrg2TO5m1he>@UP$%=<31$zx@xlL*#ildW)%xPZO*x1)SV5SVf;`QSToZ$ac&zxx9SkUmg$< zMYKdlO9OT#QnrPF=Aa@(Q8%5Zbc61qg#}@F>#^vB&I!CR<#bl!yyJchw^%M8@PI)1 z?u8+;BOLwEX&b!04$;9NcF1WV?1JDJz*FxbpykV~^b}2kO=B})uX_H}&x7qqX~x`76f$*9Di#m7@rjCr73 zhx`(=b_NozJXEO|KG$4RzCov$)QnNG;>AKhY6;%t z(8pNY_2-m~j1b*cTbq`V0bfK~(>HmL;rkFaM>x_K_+h16$6j~(M*2xyM-Cqpgf+4x z-qoJUJ+8x8>UZBB)E>B}GI@RQatb~&C;q4lvF5w$sxc=Q4<0{f&1pCoY-;Nf;JOSF!N>>xazh{qg*qfsP!D4_~sZndLy?PJM(ILKF#m8I^96V`3N zE3t7?SaQ~Acq%B2XN@DEk_j0pf@{wi&-M`0C{^CsqF#jk9*fvYG3pB>_~71P)n5X; z7@axRuS?TBTXFTN=it$8yIe(wh+mo<(s?%pCsA~O%8tkUvJt!ztwS_SdlABWi}TXKw*MvJ{Q1`g*l^<^_&U?`=pXkLvB} zRpr`#|GuHiNQHd@%oe`L8Lk}-a_dy;1BSG7-6L;y^IDcw{i>->*-3 z3m}8SX@AZ-FW;if%{Q+oHQ&bX8EyIZi-Jy@Mh1n_|2rN}6a6WL(_u@lsH#D}wPCO^ z>)IIo2`9&TKl1=WJ0KTHMU*n57sPbL*p6<_FnILu7dgM>W6!U|MgdX*@ybUhKFH71 zeHAP(Kj@|WQ&WKu-Ngrn4ys4fQ&DmP16c$jx^iM~umqxRv2$?vfTu21T7n)PmQ7oP zUiE}rmJ$L!S6T`X&lrNTs!h}sAM773XXyXQV_Z`!WS#zXZ2^14~T(e$KQj#WRF0EN^0v);@R-9K5;Q< zM#Q+Jt*Pl2gGF99Y{WT3lt{G1d;@JfbU+ZcV_uWnZR>LuJgsu_2F{^x-*R#6FI`3A z4%jb`RiE9z-+ zcpfB#cXc0J(?%Ka_MNsmvJc~rW%mGOUtCNK#rP)PBA|4@hTxx-lyu(1V;RFs z5X}8BYgB?X+?5qj6E*;!Ca}N(>i-7fMompkkKU~kI&kW@Ew~)1P{yTbLf=JX@g7ZUP3=G82RX1?G!a&C|rVi^fWx)Z!v-ZjS6Q?DA!hc z`r}Y}UAu;l^l)JHXAH04@a$U|*2bv$gG7TO3W;vP4HqyOL!^12$Ij$%p)}>{Sl{@6 zI1@fdg?@3r|WlhsHnm-RR?=;{7!c)DKV)-!kt)Ju~W5f-8QO z6BB~U)u#u|*T<+26kpr#XAWWwlDSZ=-Qfp3aESi&nKL!3NQl@@Z-eYbCrQHBAv^Rt zYSDjO0A|0d#YV_9kc{z!By!7LM5vrfZAAf(4H<*4QKv;rr&27p9X~-|1xQ;ywH51& zN5S6OS}C;^M07v%F6eXN&f+yQq@2JM)`Ba=ir_T7o`TD}{LF!a?Mg4K4owkD8`Y;- z85|?H_P-G&N(+rd-9#o}3D2elO@TL*BY4 zro}6EDIrN~ap-Ez`;3PHUu4N!SG-r<`n__!h=Wb-!OfpvF#iQ^1!uYl@QXov!HEpQ zz}pSz=TFf9w%ogxU%Q#J4d+{0K3*5M&s2dJ%0`T0NVG_rU?4$gPL2aD@lGSimfSwD zi~}^v`&JYKW_J${A|DqsFNjy#J33J1f~KR?ct>`xq!SkuK)Srj^W0~ zhrDYBzm8udeCOwWVurJ{yo|L06S-VByB# z`^`QK<$j88OuIvrY;#WeH2rU2sluX~Eeug@Rd-h~j%{ z8|oiUC#4p!UveBEXvA`Qy7Md2?}uZ@9qMFTj0|67q$xksw2M5Twjk^}^}X z7wjeLu8g&<*a)}B$p|gz`1wlXy;=S%;@a;q(rGB$TsL30y<2Eg)xps8^ioRlFZa5z zqQlCHUeP-iUlVM^bwBhvx{O@4uzRc7ObLl&Gsy?cHmjt9yrSXp>dsotQ_Dq$O^}JnFCj2QaEW z#8p-vZ^Mpatrj&FDv#DfJz2c&g3W-#n_zmWmvM3&?>4h}IzM&k&Ksdg_#PeoF{vhl zNm!0X|A5id%xKQ{XjbKJDT>>-&c6QDKwM24!tdl(zb_1xld0!720tJD@nB_8_VTxg zw)WJK(n@x=zhvgWWTjeR|3|4K54~bPglg(|_xGMPT^{rH+oJU<3@@l@{bM`}MZDR* z+?Jx0C)`YGWapH}J?Z!-Ys!EB4n6aPnra@6=$qI%#CiW^n04`s91@8G;|E%^3{+>1ASz$x8c&+HQUWZ0u^X^CaW z?=_uRuiIvGbv=i}aAVv_3Hy-${$437Nz4%X3q?EydxZ@{!hdQkT8Mv^Wo$V7UE;Y zW~2Wr&D1je-}L`JadmyIu;&@7%^kcjufKe0CU_urzWqhm{vM+Y>+8xp3yyulKDHRT z-G@O#pM_J~RI`$O;>fk8dFmT3UOcA9|9vHs~m)fBqDKI-+8#IO31FYzLF9RUzG^=!K* zzZ&G^(-bLkn8s36=I_l@df9xOUz`2zUv&DNsJ1xBe;FB$Gf&*JD#iu|OJ6uTGLRZ< zuLt>BP5&OJNjQ_1c8KXw1BBJNfx12~N-Dt}b*hs+b#;q4Sjp2+r3UStP*1WsD)kyhNMETj zLuF&kCxZaLxTs2X@=5(Lmk)$>KPM{UHQWErOQ3?R)&fc8(mNWx&70NqIzw={!EEs8 z?)A4_0Z^`^{D?sa7*9Mg50b-WSI9}l#Tz5iWz24M1M6fU_4mUXa~$77P*yNKwy|j@ z%xjh34&IaZF<)Ihry@ujGFNG}_gHAj1-nmUt-EZg-#<1qGZH2}N2C-EHIm?U8Po<> zryeq23w+v|x_a@VX;cKySiH4qsCGzK_@qNGxxShIeajCu)u1_h?&va6LBRl-oL`@flx~;g zYO|>>e+RNR03{TcG@Qbbr;}{py%V6u|H?b!(WC7&@8Fd`T(8GKBk$}S9Zn-veEqGu zU|Ph!vbz*HLr3ejZrSpl9hqYe2zohmU|A}h3yhcd7Z)O4DfZB9qaTDd@%Yfs+`(O+ zWE-xF8{Ew=79xiPPH61u-z=$%pe%@>NOkmEwh*DuHM$2JCcIiHCghttV(SGC&HTCE zo8uw;!m{IaA$j`sfY8A?=E2pasF2RC@GtE&oII=z7izynn%`w0vE1F#BW#?QoF&Zv z`gmL3xsB7)_93Zs-v-C^g;u67Y|&htWR3?WCNlZ^?>x?3>V_xtJN?XQ5-{NXOLpD0 zbLZls`njU1fhUvwpab<5%fi|Lk=n{!Aq*sR5%?&r2@!k)5V=0{PIx=Mu z;(DpC@{L+KbDw3DyWNbxxy}u7b3-X?YU-*pu z`gF#7+I8!r?1R&@Y{+cZXesb((Nw0c{==iZ)BoAfkt2PExou9L#j-1e%a#We_!|Vp zU!y5Nfj$QkUkpv7yhtk_W6SK0U67dAcD; zet;Nzj7X5GKZ%OwuSs%@gM9O#uOb|Zp%GBdi5KgcFKVpiGgn_44^1`|KS7SJFPNBlfc>G z!{<2z6o@4Yr^IH{V1)3a9%{j%OhkHXOa#Jh?BFr6vP1`wA zB)^X~@38MTd$709kBMm_-@?>l{q2Jw^b8H%T$^koGentxnq)Z{K$5Juo}j+z^Otf>KQ1km%eO&_m0qBtne9&ce&YryjRTxuXwb?DmhvaB9JvPudgkVw%zksTn1EnU}d3F-4xq)YaQid)eFc@k5LC&^aqdB# zj=*m*R@SS(tIOKno&aH5ti_r|fuX3VxRIyKJv_?N(#8lcN~aa$J{ER%FAV%y?~8-0 z3_^>tr0LS#PAJm6(Alb_xFL2PMA(-R@2I%P-c5HHzOs$NPh;T%x`OU{W5vaQKl$Ik zUr=^nmHYamzoI5mVk%gyvqRwWrO-6(9;?$@<(u}F-Uw@wJkJu+)~<8lKo677sz}8< z_HzdoU8tHr2Wu_SrYJ0r9DV?%OxzJm(Qathu%Ix=h4|=H>z7oZb+=KtF z{QV96Jc2@_B}Cq|w(j7B!CErXsS^|LFu-TIxwTbLNC@zj2+11sX;p0(;9i-U+@D@8kD7IH$PesSrs_s3t^#MGmFQw>? zffuX!e!i$=D|_4AZ)E`iFfKQ3azEiq_TIi2YOP?Q)e#xqv^c+?!Jme+q@W=G;XdtT zSqoRsUMnAlar1z;%(#Cdo@@I&>=+UFaOo0Rl3h7mOl&mHw5enJM1H7=h0A>NNTaD7 zO<&oQpA*vRnkq|qr~YK|CC^^o^&>xic}ObqOv2fzl=a|WZC}hMyzdJO=K;b4ZC#iR z0V@qIqja>i-3a)?XauB(!B2VU5)qW+f%B=Ozd zWOhGKlJ!*Rogt*6i{FG53;Ox$NDVF@fOPTM*{jIKIAByXg;+7oTvNOPFzUf{A+pyX z00Xm;Gv(Yy7zuDCXj=Hi+P~4Z@*w_d2EX|_8vZ8>0rM4pdBIL=Fzi=^_c|nAH<(vbh8TPiIU)~9yRiMG* z0I6UzyNnThv*8;HjO$Kwz~)me{)3am8>8VY72L8kITB}mCT!-}I>rY|2@Db87j`jy z6c%`DtbKH3kb0%vf!QxaTW9B{qv2h?<$sOf`RXd4g1iR^YSi( zRKaE76YF>Khr!|yZFZV}M0EPd4*}_Yxo_Hn=JfKU<)t!{BfI+gq%khX zMu6&*Y+DlkiQ}doW6MH;i)-pey4Q=k@HoXZ3J%HB)w{4SzuSsQ7(g73wQv{&n0a`tHy|&^~5)z$tbphCx2LEE< z55rr?=av1Z9kJygBy8kk)XAjgDRw?Ao!bm8?d`v(rp{_=Zciti)sa04RFX_hc&|^_ zq{QiEZ()}?4z)KX_&~hnxpJOASF(-5C;B;z&&rRm)0 zle~xcJa7^1yI$TVB}{d$-n*|zlrs7Bwi&0{WWQBcToHA@^ISB_d2(RTBc4|_uCs05 zSbTE%J_EEW+LYeWOYTdWYOmx3{n5Pm6&L6Fz3z;}VD z*E7&)_w(|;uc={UW+s!#UnYY<6S)(pJQc@pxQRU+VWumx6-|l@Bsn4aVnYw6f3jA2p9;3bv4@)TxxtL zn3Y>u#b{(V<@iriYYXgLJ+6y5s=O5AE*~wBCTPBLJC|NrI<%_x61@s?Et8Yw6&3HI z$Mv}O&h!YH)6#tGp>#4l;`@|Sk?}|b`63=fH0n6=uo9}LHScW8^d%)g0EL&?(b-wy z@wWrEhrcUY%QW3sN9nYkoa$N+15ioG*VZvL)jwU|)H)kyCtLipr#>@V#AC6|vX0T` z?0+Ls+3Ke6Pa;x@>G5NF7mrzd9C|xOM*eo7BQNr!#AFclq)NN3x5yCY zFOXhTC!91#Lm3wCcPa_uWK8Tq)#`hFGUX{pjC$nu(oyH9xnaw`I}#6>QM7lF@ecGi zc>cC%G+Fwrzg9VGSZMu$rGL%v_oc-b=t{=^ZdIaHR#9-{K4rO^zSYIq?oi6l%sE2* zu!*T8=6Lg~J_TRN%FX6=!$LoW}4K?f&%@#Sh^_MtV zS8s<0S$Ibp|1(#@=7G~jL^|cHS#+-5=D^??Vz0wy1-2v>U1a2bs~Agf$uY{3eh#PtsEe`Rq7!6^71a4ALi?R(eVy#S=1G&?RzRfRz{ z$CkSo9Ag4kbF>v&Ho?dvGIk2LY@on`V+2tG6E4lhL3bK*tJ161e=6lFMgzg z?z=7bGB@|Sqa)aq{^9E}O7?Jn-)hYY&!FQTi>m_*NlA?`1^7LFG4%0c#yuXdmd!6; zx&*(cz2dfH2+&K*YLr(u>#9s0j};1={8me@?2B1#0N}B;&|bQGiPB3#x%0f@>0bk9 z=3<^pxHCfyWva*hhX7XO#dtzUEY6amf=lFT&MhA`?2}hlhPo`SKaCxy`O{PA#_d=5 zI9EEeO&~Ewx8?NDUow*5#~=dgz%xcczR@0x@72^YhwD?y!dx6DCdTr? z+4-{pV9Z%t!%ujUh?TY!g{1&FFB4M&!pBWc?iREy_$-hSKXvtGXsD*XK8sBKUQq47 ze7R+seSe3$u8?x|48&__(8Mu;1ISxbvqM&7X2%l^&8Y)^ocC~qN=OWp-q2dqKPoBt z9s({Zsu|Z(c%s+W)+#@$#O9Bw12erJRv>Z-fN(fDJEvt9@5{3LeF?l=8yn*?H=Uo3 zdpL2J;%|6qdkQ4f|GvJRntBo97ko?ygoI4W-Sv%(N(t5#q<8mob7yd-P2KPq^XCCs z4(7mTkcBWab7>;&h=c^l*l?>#Dk^%+_j0|?b5-!Eus1`UIXB)3!22f1-eiT_q4%UW zuD^W+notCm^z}{GdSa7%YZQ7Xgb3V{;{dZu(MiMe$xYT#vANrn`=!Z^GU@^)B}uDf zC&EXK~&) zd%Z#4h5OdG*VvMtWlbl*gvNA^b}wgGE%TpA2P1abmDkxHK)%{=ahfNEQS}vQ06u3) zl>_8B*F{`87*^_O;taH4N_UVCdgXbU$>Xc-yRwR6h1l)VA717?aTQ_Ve3$v+X1;ln zXpHdx3*40m3wx&Vlg|gfk>gM07MCJ@IL`6rIES2@%KE6|k-sNrnng@6OtA+}^n2`A zi>|-)SGsboFe9wPT?fAGP~%S##Ax@Tl@(nC|9{TG&o#ku=$3ar3-IB)AZ!YC`5Jl4 z5kdqURa?nRjke$);`GOyQg&(TBnFP7jW;z2*%x%EXu@a`P?{wKCT@t^d0I2a9l|Fp z0F2cK@-7B@dsBL8-0lhrz2k6*z8nY`ru=`P@nqV4Bd|f{`pyeonk8#ARN?RIw_u4N zJOEB{a2oVwphKPu0&-WD*Ez@mEjV`6MDBmM04qiMnik4aW!9iRVy*G;M4f{&`D-Y0}^^9Llct~_=G_rBbUa0GgSSyJXb@Utf`@)6es6b+-7)>r0i$O zX8RJF0VVTdAwwpxW3jR+HkL{*F#Ln{h)QBl=k7QjJN>0@j|HKxB_0jk#i3o_tG7-} z^B>5}Dk@4#|8Zk<@8-jYp_Z>r9{{uWEM&mOU;rBX>Dvb>{RR|FEYel72BU*GBE#x2oh&ucIjHEv09Xbu;*z z(9!TpF#Y-SgQB8$I6XVxUC4v1ZvXWvttP2y*HTpRmKQIE)mpyQxlZ9!4iZCP4^C%anH&__z`?j0g9QlG~3S|$bz}N!(hC?yRk-PZg z!nK9?dQBajYKIFODB>VlQk)1UI7XO5elt{fX`9OEezC;h8nIP+AucjnO;s&ZAI&khJumB$aSyE!OO4Nwl{4x^+lW+wP)53{&dQ5;G;dsjyByFOX@- z%Wv4F-F(<@pmKnph$n!5z3i1$lhL&Z2~U(U09Y14xrTmnpTQ^CEx_S17Z@Yx%vWVh zyV@*H!LpN$jo{JX^oEBNf+T&KaA#r5p4vb-?gR!_vEw$&Lw}(T?gfQ0`)#GUMkS23 z7eu|~c)5L8>+3L%+R3x;_~)0?xB~ggUOs05`Ch#L*Kz#El+o2HzY1xua#6wY#N}Nq z)LSufz?^VpY&P_9w991Z5^vnN!{YqW$#ig_cYYOsiSxyKRfJ>MRkfJE>~G@2POLZg zvpTmvH{o*t&9D1kD0bf5KTqm<+zwFf2-1??d6)?=|N<^Yo&7oLOyT*vRozF3uA@N4=hiFMsm8U*qAf zRcKrM=U3(VvfkMDH9JV6E|cnu9HM`xzQ%bhf9sVFpIN;TYP|w!>Ucqa=t$$(VA#6l z!Q*9n%a(2xy}F_SyVdI2+Gg=rmG9pDJb5?kfNP{lW7y5154E>e@a%bBeVB5A^u3h-{L24Hv@J8W2J-T0yfQ>4xQ9!u23! zI3tmC7n&sznK&Koqui5+INzl^mMw!T&6u=iE`Q1MPzo zH##GQxm9>Lhvr?Fx`J6*u1;p1oO=A^#XzU=^98-E4RK^1=X%|q6Of z9w<1W$vM6^xazCw)1!w9()wsY{iq&xydM-)Sycs0HPgHb(oT@}(^$b@YBh90dKiS3 zsQtkPG2{({br=|GH{IMc8B_jBV3Zt~#8)#t7#C9^M{~n-Ay>_e!()=4Gr2KZi8pfm zn3t!qO&Xs>A$aM>54YZ&ws@761`ae2KmQMxC^1^PnFq%%ZD(u?i2qp=l;Gewameys zh}=|SZm4j!+oIFx?d50o_C{=MI#&8RI^TM4tc-W+FZ|s>QdwCFOMKob<2umW^Ljp7 zspLV;#PbonkJb0g|4s}E*~aNp zZ2=4V5`?h*eBw~9q4RKcQX*YUChiP79NFO_u$c|u9lmU?f%QF>#vGha!2?nUmk7c> z1r4AFAh)9@wdwR}eRxsm{r z;PA}3U3xkvZJZ0Dt;SW)pFeq~vBTH0t?JQPMoype_UampHx{;?|GhZ=d-mq%%!1V` zyJh>WerPXRshKDE2X3l1V;~8YmW`2i=;U*&pb&+;DzUMP0b)dgj3U%}xoL2^=asF_#1_ zMSZpF4oX~n*?l-phO=b#S-6#@JiFLACI{=wJw>@`uW#Nkh}KMqKYY_yTZNs0^r4<| z$HfuWgtMAwzld7Rcvs1`HO}b2f1jSEt(Sp=XaAn8F7v$ZluHtOe+^WuVt5BN_D(tl z5S1!cerLaTh7mNNAmna?=DGN1;)Qt?Tn&H-wr|wQKtUWC&Jy&n@)&H5N3u@x-5;zuG*YGzhn3A4D%|OMc~vLgzSz;UW6kUGz@By z*N*PKE%ybXK{Q_-i_voQi&S_X5D);0cd3)OH!lww7)GX~2wLSv-&wDd(CRadP4@w= zs0ttN?4QNW!z?T=9hnl_X+HM$ZT{FW0o7vORV_7ha~)Xc`S18ay?y`wfw&`vPoHMg zcQ@r|dS9RTc>lqt)d`73PYYk8*yXCNx$ymRC``LFy?gg1x=qJ0h)!UDHKvn>q*2ac zS8#c~satD)M>fBBz(P+rRReSwFe1!oeIBB>kl=I3N=U!R8+Hr}jW_a}kV)w|=TbQF zwog;DyT>;SVIP(aF20s^z=*M@zQKOzgZbU9&AzWFsWnxf4c03x%tf=j-c}{z=gZdJ zy;;iXVfj57vz|hUfz>Z_w{9~&>ASq>G*TP(TOxS%Z9xu8(ap|^UCSQ5DS^ioYw%3V zITpix4W?x)(~8)do19gRjMC#|czmvq-P;}aP~6J$-L4mLxm}LW{l~9A(VxD-AD;bK zvCaD1NxEucOUsYKo_zoHz0w)wJhg7Po@W-n;feXYF?7V^~_2UItY$`0G{24+seaxMPkR|EV^ zq1B~6@9M7|_wsMPhZ@O=QnKQa$o9xUg`LhKmxdjqvXIJb?_X#9rvVx@osYg?ySe6C)#Xj+Zy$?MkM;AW}lj5x$!*#T0md z^Xq)BNad)E)oyD}SQ~M?47`88-SjN3?lnTB884+Ju+t~;M(%&e?1BUJM`feq;s zk&iz5O1_$;Z=9Ksrm$QY9^M1%(?c-)Ff}56TG9(KNED;hTLo8iXZE@L3_TOI`r2~_ z!zfet>Ee}dV8T7Ph4_c~o6&{RS+z4mL&;l$i5JL9=tS^vlK7ARZpr_|7OgFoTfda~ z!`dSr2_U(;MKeY9?p7A-LAgCl7ulR6j$+LdKU9*M+txPNFEczgR#WCtr>P}1@qd5E ze#%XoNcVEW;C}U{x3qOjoB7q2L_6C&Si~q)ZF#T*N=U*7*y#+_g$wkQln+vPyc@)y z&(mkTJ9dqI_tBj=T1qmm))lqG7>9UZUobE>U#WjTv%Fqp-0QvekS(b z?95NaU|t_3AJTME%{H5~y$+49%bi#>1ZX)cD)QcVwtcvF(`C<{nqH&j@?Ru1{lb18 zi@Ld*;5%oQEPC_y8+Q$jM%|oeTAo&%N()QaC322>11>)4-bDO~ya2k5+^5>uM?Y68 z+%d1>9Z=s!zsJq)E0@j<`?(NWf4x)HyUX&-lfG4N>T5u$skoNf^qH^j>Js_lhzJ8I z*0kJq2eZ<^1r?gHJl@dSVYg3lrg{bfbq$&Pe0+@`=S)f;+grH>bKs3Y$;6F3EV%a31mX&3ueo!qYiL$P21*{JDHn7Iz9F zfPlc|MN5JtfKaf{v2M=!WUBRYyIQV&^BJL~{r@Q8LgTW->(3h-1xp@XSu$`yols(+ zR=8Hkoq3jXRgg4eSj@Fh6`UoaFHq_hA#7tm_v2#Y$FefRBMw~K=!$i9RfQuNDu*14 zJ387I7UuYM;!v_heYQabscy_)--rp?%^MLZ2as!Ung%ok)tehqs3jX-hLls5-pQb( zq~%not=-s}b?m{O)4K$A(z!)!;gshL<4@M4x?5$IcTvVebhf)Ssxh`)hoh}e?YT5m zF7`$e^E0J6wl`+(vTq|rvz4jU+k~qGBu2Z}rq9>-&kX;?rswOFB2;0Fyt-agP%}fd z;6$TzT7*w_e3AzJDewFUi-WsQo?PvnI}@9fl#_SZ_<4Bk=xdD>9i8^ZIGn!IKPPr- z+E}SS@T06%;8MQu%xK>wSIzYw2F8N7zv=S^1gW41P1LT%APicFg z^oC1XL0;ZC%0!SkMk~<@BCqvnWSlC*IPNm5C^s%eH+8lFT^)zs1(@7lG8x+YhYWaw zJKS>4YHKsbH6pIimVqL!5pl86$F4n-<6ws0&4ml!v0{!NKaR`}%=n+D^G4QJKn?|x z1{4tRa>0x=ZEOiI6aI3)nv?EURIF|lnU99!5^RgnvTfP21^5-B40Kj`n{q(mx5r!s zodibN(aS{?@yk~;GwWMgB1HAoPTWp!vZ*A08?tQobtlnOTlSk&Bpl}+{8aM$)cV^| zuV3Fr4LjQGB5t3~FO)&Z(5{k}<-QFK{mr`&7#3!@&9bAvEb;DEJl9y?>)oeMyRFvo zD!Rmq@lQN)5X{TF92ht&VyY|KK!y4SmEPg&6K)zNu{oC`@!UUxccaev6;VG!%n~g> zJcPsr;^HCj!_sHs0DB`!02hP^gDf@@j?pP}O)nqt{A-742;LuPG@EE?;WpHpcX2DH zB`zzY0w^&LvY3#rIP3@!Yr9y&M1L7n#MdE<#qbqQ_CPanAz=LC!vNn3%+K})cODIb zBMlK%0tn^?QbiG42seuyU~!+d4g*=i_U6P0+d9tmhp?mlceYV^m-dF^YlQrf)+LV> zDFX2j)U)?mjJkBQfpcJThl%o1N_NGR%D0D;hO??hC+= z^VlI1Cks4xbkv&k0WhN5{I4?eDnGcZG^b)6%r+~sABp9NBa)qez*FQ54H1a|_7-S) z&}1Ma?rBU65)uj_0U{}Zo&q%$MBj=uwVyt1p`%;;nb$b#l6ztVy}>SjdAQ9{6sJNMgHzEG|*75PsIE`53SZ^M}vvF)eVs&Bi2=+6Sm%EgkC z6Zi79C7)z}s4F>W{w@FRxegkNc*A4GW0H$!Zx+OkpD-;Y65TO90Xt=Em=E4rh(58h z0*Z86Cl5ot`CrQ;q>yN>-+katF|^DMmw)nM=%}xG1|#wegMxHKmX?+-VPS%>b2*2W z6GNW6G7ung_-J2#RqG9Eg@>GJP+@K^mrqmYC!Ndpg@l9vo#PVB!_L`F7*|;B&!gyQ zNw@~IPgtjac(UotauCMXVo^8;O81X^+w=Yb&u<3>2&1zyv@R}e&T4HH>B)`aEo1Nk zLE`u6QK2}K%q#(iD~*q$wFDRyKpyZwDAN5i0B+!_g9&?NL`cXy43a@YbX7ijcS^H_`*4AzV@QJy#9LLnm42)?Xr8~m5?#H=?v_&Xo!d0xy0RIdFzkf>}K4puh z8^dO#&WId1ut(PO_}wa-9Dg@D-$b3sw_dwtJ>8)p!u`(abKGrCNo^#%?9XRBeT0Zh zB(}>KIBPCDh_hP%4x*5&s0_33@Ur{I<1*l(Ck#K7aw8ozTsY7f zk^UPV7M6#+31ZA@T3)&`m_j;vax*=yE~Y778cX>=~jXY-%tI-g9SZFY!j?PG&a+paHfw>y8>8^m@v0P2|*C6P4N# zk%b!m%_o1UfER)LS4_K}WS7-id~rESK_Rxelc?00FUKCaTb-7X`LCP0vDVhOF*BZ2 zj`p-beg_dOdP~^9+N_pQbZx1{>>~b4R!z@msWF?-azc0VX%o@9cII80s>{wg<-01T zy!PAo`!2Jf(~aFDaXpKL^;j)6zUzN}ZR}j-97k&WB9BT+ZhXAlzU^Zo5Z#Z;ueBV~ zqP`h#FD&5XS%2Hu);4414hlncBE?ItGwG}VAE{jg(C8sCv8Up8(f{?2yR@{8wfDa` zJQ&?BqN-d>nD7BN``)vT;*H=27bL+s@l3`T%;;5{3Q`F>ytEkk?_TWFM&kMPW{kk5A6LBa*^_vK6p~WLRtvnm-x<9%G`ZR|Rw<_F z1o*d!MhWdWZ_5o$RMYxBzmjdv+ohfr*t985M=vnv6k2OF-Y{Lgz)f^<0<<}=wvS5f z#zhyFE0oCTKG)-4y>n*-55is?i&ayRR_Ib~i5@sBjdtMO2EFN3?(Zh5bA8-d?wv6^ zTt=mcjx@-rT*uenzZieAuz9#Te_+g=khuOd>e}b%iaA$;7<~d+-Jg)n!A{-cq z?w+De4q2*A)qMV;iG2Uo0qfezIQ8sd4zsHmyv&{xiLdd2%5Kp^b`zPzyN%bXYUjXO z*|(IcT-{!+_)KA33exb=wkt62jTbz2;GW6_0z+|P+WxICyai~6@lh5f3{GMS5( zCgMgaTrT;SpGVuSPA5fF@6v}CKR#~0==X2=zQ{AGufQ-Ha9zeEpF#GE8W;fqboqZs zd+&Ix`~GiSbygbMnMs7o&Irj!h$PuEOJtLsU1t(Xlw@UZLI~MK$R?YR?47;&J&(@o zI`8wkzW04Ue!s`>c=SgJ$MIS3_v`h1J%{Ikw86QRb;fE?2q?+eZe;*|Ho?9O#!=or zDZ4y}b;7qbgpP1jA0#1({q0;V`W3~m{Ut3ce~|r=297N{GXFs**#>V7R!hN4m-2Mh zVZ;&}uaE6|OaM-PBipS52Wj{%-Gm~J{v`PQ(VB@QD#{-#>z`>}Cuw*w($`tob>JBd z9`kEK6XJRdUCoM2M<@b=rhT2^l_f$u^YM&}0OTvJd9FhKz;CRsTJB>h?vm0n0MVV%Zuh`8rLzc*}9E)dVc z%?k`qOZW|Eo7|F-A)$@Q&Q{JMM{7uk8X@^*dG0P;A3F5@ecHn1nVuP8SZ3 zjuPqzK)yh$$7j#rq-x(X8nZADpPiovKA{J90o#ZJ&oaI9t^qyZcJ6V`x9FEJH#Y~> zXV)M;$oC>=N0?MSeq4&Whx^h3Sl)>G;6Hb+vaPMHq~s_5ja5KxV|N8N|99*sPMq9Pm)bm3~|c8Jk0QSJG_(9jT~TuyD0u4%xI1!1$l z{a^?#VVZp4yTaWvQIV3ITvbtV9wCPC-jQA7Jx=sP7}FeL+VUGW+z#!qn!6$3JN5H< zc6zN*v33mgq6;Fl_c`BN!Hi1~ZbItjg)I?pwZ<}0Az=@5o~=AX8PR;q`w%jD?4gT7(wh6B4D4Fi#ghmLM<^f7dW%92Sx&T{54v7{ zFH`9mw13g3>F6lG^y-A83ZKDNH_|MO3Y-n5T?}qc5JhkXB+dQ<-?8H1%AnwQc;?WDs8Yr(!5cuVV(M93zxvY3 zyn*xIAyLF9r>v$i#*esd`pF9Q&8>iA2!_NNH%OOcH||@T2|}l>^ps$IymM#L-UV4y zv8(aHO*-Kq2VkP-=y((sb$c|)PoBI%9E6V0ZO>7{q!%21yB_=avUG%J-MwvAcb+%? zpmnf+w#BLLG76`$7zI^T$A#8A(`Sf8@*DKufFTyb5>P#ulu44RpUf|AWo^AbrJ|>& zsH{&hxPUo8Ywn6q*It>$%G(^2;mE^k!}Ae$JqU~-nCSL+czA#_2Z*m zITg8%Z%=Z(=R4;qr2Zd}ly;A2^Mg}r;-V#?MVrU>?CC6*B9`jT7*xD{6POf+t>Iw; zSIL;h#x_-RnOe(u>bg(cRKFRzCq{g(z(ugLJo~d3wVc`1lbNFctk|yyo~^2lHBa!y zJ%+pHOP}$|B>h@fP4eo6~JnLo^->t%VP>ML$0khBn zs6Z&gD8UJFW?hFn1pLW^;98g<7Y$p&W}gH&_` zeRaG}oIchSP(3`XU3iT+(PdWbZGgV812?T0-7zm3!55O!3lAK77GkSCV9TIwGqmBRjG@(Le@L$5N-3Z=ii9`&Kw z8NOWhQD3%O<|sm)F5Z2!1vl$&;{*j-0rF8vWK(Wsx<@6j7cJ{xxbyk*XH2nrp(A*f zubztJy+fcjy^-=`OhM3RQTLmo)*D?|J!7Q&MvPq7x_TA0Pc^ujvp^nz)wFZ5BM^*m zpd1%q8i19VP-xP)EJp?(mq9V_8Fiw<$JKUoJMF;AVD*JZk9M)sY&7c2(EC*{ycI4^ z{h;_dL|x(L&9I=+!UyNOnwzaQ*I&Iprl+8CQSti!g60ymxb%bb@{f915+6u)nRVNw z>TgW{*h5oLKi%E+rA(Z|>7z2W7maXgP34Sc(eu*bH8ySKpioW!fUDuNnt5*6fY#<| z-I|Y)9ud`TX_45zt?c%%w{IP?dxL4P5;Rj}uEU@j$!nh)8ql6UMH|ratNHz%(ib$s zkn?Pzna2ypUKJG;ZQAKgF?gzh0YTURzIRhq)ffOH0u->Z7sy5K&9gCQjbMO`_)dBM z>Gt%}wzehwQeYbK+0GX*2{l7Qpp#BeGeK-aaN9%gxkjpv{A0~}&!b^y=nutmZp|Kl z+Hfe(_s;Fxx=8tqRAo6WkRKdHhmos1n~#l!;MkAlIpe}TwBN>zc)B@Z-zX>;R9>D! zEcGhf)oNi)|EUk3u*-H@v2y`OMU%n>xt12qx=%2O(zaE3RSq5z4b)(3>&#mnlqgRU zx+NizF=iy{(OY%AlE%f_xl+EbB{_xa0FlV_MIF66kRT{CfUk(2nZS&L?xUyLm=zn& zm~10&!MCM?Y(5QV1RH|RE^nm|f-5{ryFWT%ZC&8~Wym3B<=mP!)Ym7s6`PgCQ|Nb) zNMv{Bf&KJu#~99#Y2>$>_wUNPnRxS1>?;hT|H2gAbNL_FhONx+Y5bhIlj`H8Io4sR zzK!B_Q8gu-?_E#XVgi~z$UnYH*Xjr)(T>=eUAg`}N64p7S1l}f->};4Cz;%88EW-G z;`f=*8MRFeY)M&EIbf)Szrp1DU0Vc9?vPv1jl@YR>9UJq!q)9Y%-^J6T}hqOPHnv|M+{T>L9RwQHEUm?b``H z+jmKBI-KTdy*I;NTIhE>oPPAFrn~#o_wO$ABLY;smmCbh@K>JKj zgKy3}Xz6(qQ0h0k&85UY>--pFo##yTY;8>~EH($L$Ml;Y!jCX0-?2PZlf9}M&>g3h zoyvw_lj?tAehZBNv-bXBJD-ZdV(3-<@B5xt>Zabxlvuc_N+O#jurTg~>y`b(TtJ z^B=QRtyr%A%`DaCFl|@!hiU!ru!GbUoZRy+=IKUfYJqlrH8y6K@${9ZH#0BPTe9@L z*H5t_v=w?rzy64*n@Z~U4NI=Vr8?i~B}`rQhNh-joSfX9(x!qwdgbL+H|c}aa{X=S z?kR&itT4{-ij3?-Ez>zof;(gP>yJqq$-6}R6zp=^tVwa>u6?bPIZAvY%P6XEI_K?o zZo3>(+tHS54i4_&;pS_t&B{{Os#@&Lug1iF>u?fRZhf-nXuM!eOPOkS&CjxFlnf&y z;q%nQMAlM(g(zL-4M4!7P{EK)WVfRwThE<|_5&YNve$9}N+KkJ&^d@)3AHGLJ*M^h z_?elF&0ML+UJW-bEngK~!3W%q9)jpppA_$SG56A9Ibn3nM#5}MsO5?N@1wT3>H8m= zVjp9Awg0c#80uHTe66X;$8qg-Y}}Z@iAn3xAJ}qte0PRVoFIj&>LJnRp>K(4cMT2I zDk)UyUY(fOLu+!>ug#r-qyu(*tkqAE>l`Z-$N;LYkmvnF6E9c<& zp{mB&Zn6WNY?2&HE5=LT^6ie&#o3A|dRh(bbw4np@m|E4hvc#On^%(KM#8zxw>Axs zQ~N+QeFxzq1cq4E)Q&!GrLS^m`RlV=yRdE%Tr}CCEgQ@`No@V7Qg?R62`NB??ny10 z{JFyQE^cFa2(qd3|zCU1hP8 z{f|287yactsZwd%_pUC6%$s(_wzN36iaFGTsHmtpU@shV0(Gd#lUKgx1(#H|A*(`VZPY!Z(p$zQL_A%h>3WG-K}zZbXkv&=+6p&pjf^76r5F&VkRi+9T#?7l3LbAFLL^+4`cVw^0< zDXN!F7R%CzN`lW}C0Ci0?ES}fug^dD`4{%@QxM+i7jbGjFq-l5Wm88qQF+3en$=qn z;Jv;U3o_h`)u%581_$9Qu!usZ}clG*K#klmMJOGrKFl!wmACn?4gwqG}$A5 zSd?#Q=$QE7*o?|x6Oe3lrY2c4v zZ?^0qB1)qJc!6~hx{`}yCoYD;GzVD_8BPz`yg&DGv`+-dhBpK`FdJoPxty0yS2Z&F z7BB~{&=#v4QwUYEzG|S%?H^#SQy~_}-F=?Rd#+S|h{*BVc(2L=> z{$sSWP4iWjUD`uoN8j(#P~AE3wI&DOtk0U{NJL%p$#AHcT54)wMJ;!;e%nzrwPL z3J9{B5!D1l0F#!eeTX$E9iFq&5=uPJ&mUAj4C^%wnWGM00KCcsa(wRWbO5awqe$S$ z^NA>DXI9oh!C$?C59C%qd{h*wy=5mCJw3htbBNT2WtYOKL;Fu&;)7!$=GAcN#mEdN z!o-49?Xag9AmDC{XE#a!6Hj+!US%Q8i6WcQ?ZA2|L4JK_{#nJ?-3@k(Fk?RsGelBgm=_IN-7>B7!}de4uH%C zX%Kj8Iq>g8g95^#J^qQNB8U9dkaDWV3dCkOFc@&g%1gyB&jVIJ<^R$rhdHDE{lvQk zYJhVep5Ayl*tCbu`4Z}2S>X*uKV_Iy&Od^PVLh`Wa zNHHna4emWs%owGi%K&hw2Jk-!j> zjFQsm-o4h3H~J@eD4vtPp^`fWhq&f}-u=KIF~r669d|8c8AyaNOqbdL%TBbLeNcG9 z=>QRZj~_o)H~Drk_-TwM(2>=Z75qG6irJ{Qi!4Pb9`X8S2i&&7<)Zi-nt6;_$?P+iFw7>kJkzdjG?=@yocy z9xgWKP8Drn_$Wr#29&YG#qLNN=7~&>zp13 z>8zIr3Z-1ZNX3&De?2Jbj^8eTKb8513+eVZg7dYY>QyJebszU!#avrw%Bdr+} z&-J)|?PMns;f*ZS9vK|$oNU|^3jRyb@7;xnNM@9&T0dQNZ!Q*<;frH5Fi+I4f4z6- zvnP&On3NLb?LrX)?Y4wQsszMNZ@9 zkJuO;8+!-q8&&$KV|%?mAKLo_p^uyt**a4?sw|{WOV1D7e=ToFEsl>eDnLinz!iIa z5zKf~Bc|h?`)g_v7MH!!yq(ABV%SK`iHRDl7lehuW=TqlI(zM002M^A6yzfYryBTU zU+Hk3+XW(!Xm2De-TY(YHYtx#I`plB+9q1RWz>UIY4 zb3xZHfK$Y2!+U6i_;IDK72n=nk2%N@i9}!p&DVb_`SXy@1ULrgIe6QWQsfz*Wv2r_ z6l|?dDO^?Ty)>j%vrKb_MIck7OGtn!=(>d5elMNCkgfWbH+uSSe6}n^YYGEbpO`uc zZ;R+^m?Q%;=mwS)oRA})e=ytTogjUt2bH^?-jn2IA`vbQuDj&qpv6&`WIuZ2$e{3# zz9m~F+$gyEpCc#J@KBOxW%5kNkpqeKI8nRE>gS7}V?RB&$QFl;%`?ciRz%GE*Y*%~ zyVnJbJg{O^!QyQ^@{9nz@V%4dxc73Z_W@F}D7BgPSnU>6NEuAMu zMwpm;{URbvjE?;i;?X?N^+3@OG)2^NdZ zjp=8oJ({AQKCI7qT|MaL3+k4Tv)1s;b4?8mq_vC$|CI!Y-K^us=PM@xhNx*0yp^>I zznhpkPGB&d`xQ&ID$50Dr>#wA5Y2g_lRrn0UyCxCA=&m&;BgZcvc@62g)PrDoJ&7dK9Sz7U}dNmb8E;CFWxCxw(HeQNOFS3bbI?Ks*7lwL>{o%Ighz$W|&VQz>9jw|p3g&WfAO5ORRM98T*ax|^I zvok3e{;OR`pdjB5sS94BUr|edGO|@c!~0QV>MD)%M=%OdSn%`n9~NzW=ZS`9acSv{ z8}XA89J|qRr~by4U)hT{9DrKmu&2L+)>>yT*1>}&wm!wbH%9t53n)sM* zRon1YAyd|<=MN&SorEcD#6qA(s(j?lDqT0m$eiil|sVc2vcl-wEUea?ZY!;8E z4gEE{9S@LDcPabdH3}v|&B19YgKWUvG-BW%BJc~LuFt7>rx1*YA_t;aFK=(9ras8g zke$NnR|%gSJyTN|;?b7m00@rYb%EtyQ4yv?IWSrZK8XxC(nAef@I{=LKIx->_pTKv z9uRzh1(L;g8svmAL{NVFHn!zFPT8yfJq8x$k3QYlII%54fN(^a9boX=9NiBHEg?Bs zFOy`Rx-2sj@ zG3wM;QWh8(-MK5*el7qwH7p(Kqr~^AzxxiEEd(yer$A^>Yp<3A(&~l=$$aI1l{Bnpcx$_B(?}Y5 z;4{=WJlwTnY|F~|#N<~IZT#@tjJ}SBwLz?nF;SxVJ_U7Y33+EHwo0}NN7SAL%pvep zmWA}{)vac^$ZZcL{wik<6Mv8{&MJCiZt(T=H3azH>~cq^8yzDi=^@{Bj3$qQ^l_HX zkJ%j8u&tg%hSeA1Dm?;O1K;m*~AKclf( zVUOb~aake!*nds@(bl1Pf~CF$J8`4a}TY2I$vGsoW1CF z+8Dlb=juuo)RbBPK<;Qn^83{MLYkzc!FfdnIp`ZR?Y0v|WL_bBnv{z44{wzeM-aaD zt85tbc>MvvxN28d{YM)KFF~uk_=gYJ($1p9U0=Qo7nV^7*d7u6W;ZpfIeW6J{bTNs z=^2Hus?S@6kM1IhNdEmBC*S=~P2{{;pw@JedQj*F2V>ko9d-TAeQJ%FQa63=YUKPP zB1nxHfA4E%O3S!H+fuE7R}1(J4F5}3==mY96Jz+Y{uMS&ORJVKUZD+4+_IwdY3 zsUkb9f ztnd!)eX%9c2BM4g4FdX0WGa5197zcauk_8+e6N9mPpJq^sQ%=d3&fff`NjALZ<_|m zZ>`+fTVe+P<^sHaCdS&j-*WFY_Umo;kP_0pmvDO+FsEkO+)~TK9eD0+L{9Ms8o|}N zav4zfmCzQQxzW%vHQj68R9xJhAUC9?wZQw;yg`HH?=_ay{R^QWxD}$mi>N_~K=yf< zJsT(xPtU-R9iTp2VLbY3TUSx+*)fdOXYO9PHr;dDO)@!&v$(5&VQRamM_c|r&Z4f< zDM?1D_FZyr-+rxu&||h|oUl2iu&l3xI-jOp0^!(Z(^d=n9V)6V*0pWU6XcX*dEFOY3Y-IYmGmM+{PGX2qjWYwHANUpnpg>f8303E!HJ zdE&7x$oo*VmK`TiqWj+W=MIs0J-v>{pKnXjANfslir`K8I-Bf0qr^vKZN^wWl$G^$ zw(Hivfi@Y#&_U@~`4`(%{@N1{JZ1JTYOp(&#tn2+MJ>tq+W0kTe@^Nt_1^MF7g=sJ zkga?4`N!J4NxS*gBA=DW+LDEos>^j$<-b;y--Ml`^E=_&r`MOLii6qwHyvg^`aXn4 z-XZbky_;PhMw9lc^oE6}-+el!_rcq=wa9t$IH8Mq;b>pMm%o1T0-or^!aUP_W!cPb zeZ`ml$NMKlX&`@u)W+)_ONztDdsi=_(BGx4f$KkINE~a^-5MBv2=U#IXz9LS*7IE? z?o1>Az%2$^GULKR-XdoNp(SYt=>j;QG&k9KDv7$rdZBEQesz za5<{2C)};&uCzp&1YRQ%-e+j&gq78*NPMCa)o!Ej-)Fsf$EvF6>%)(VYQf4XOgP7B z!$xTXdZy5llFsuk5Jo+%cZJ_e+@s5*;jYrETq7}{o2*t83aKdd9D`iMRVdwuqh zls)Zsy;d?_l|@ui)6npP_hjbWuzAYH)eqh&Midelw3PjlN@q(genFC0QwOC`*-yTM z^-#FD`TmTF;X5d6sH9|7OLb5zSyJ+2Q(N(GiT|xjVL|cwhDJ=zw7oS=er5fKMfav@ z`(&q^zJDES@vDuLt*X7KkdL3gvs^haFwl9VA#3Nf(JvP{30vD&B_)h&^lrLSJ)!fL zbr<_eh1XWgavjeOT8c`Jh6-R7?lC&*R8p*6UKeUD^I!B6C06Uoe{xSE7=6o|nv?AP zxQtH7`+eqJI&CKJ=S&LoLw56dZ>y!R=%};{*BXo+gV)HmH%$J1{9k~A^#5m_&bvK1 zt^Fogg7}=Yv;u2eIyJX}vRvftFCMErRJ+Z`e$t&g`|jLX4!D{UKWQo1ew56nBk7u{ zl;s#iVj3K&9SYaX^lTSP z0|Q?P*j*xHF%YlExToj*Rfw(B8e6N1%{vMm5em}G&Ae5lw$ps#y8@K+d-tWRF8UKc zdA06zwCzpiI%fGtA{&yli-&p+iAtj1x^i%W*T$;9v%P@XO9dw=9Gu72mfy0EHKq<0Jc?Y%zty|@wWlBo&+?@S^18#?` z%+2$TSAR`7&Rb^GQ}FpY(^RkdiC5{FR{cLhO2H?-KgbC(ML_u8{YGZNUU?UzPYRBR zaveO#Qn&5X7WUH!-u(xje!f@lwCZ+K@v*|S*jI)I2>}T;1qCrJEjXXXUS2i9TMCht zKV;s98oP-~U4B&ky7J(Gtp=Sp!c0?vPtw@CzKF?$b6-*K+QopYV6uG&O<7sDYWDZ@ z+#j}7LOkWATFh0a8LHbqYNg9Z^G2NnKo}@zMay&8$?B}v@PJq^ZA6^xu8@shUhk;0 zyOfzfB_G&A$s!w%!ATc$CI1F1kzRp%z?@D`wr><`Nu4V3)j7Ny;6=GWNITr`M zUcG9GlY+AxjP8eYboEsfG+5G77~H@mFqW3~?lkX}CE*Et8SG2>hiucp)$h9D?%N;H z3x{vE$s;d{wdYdAzgo6ctN;wB2nE24(yVMYX=#-zU7npg=ZD-Eqp$XMd`ygc{~pun z=`VZoIyW}fOjnn3sCcTrS0`^yjgkZ>)84pIBy60`$DeCsp|-Ln?B$tQtl)CDc4xiS z{e*KY1FCA*+(Z}76%BzmWIV0aSo-u@(7X95O)S;4U%r%;)pQHqZA&E^9Hhv%4a&`@ z5)d#xE|PD|hR3&wd*v{1N4#O07BuHzf+*?gb$39|clnn0@7|0J?tz-|`x-p|`t<$h z?QTe2yJo_kGvL$qjp4thf>()ODl6%^cOlRBq9$eh5M@fE2l+UTioob@FO*dRdzPUv+Oz3oZ8IN)A z(hfV5_DFSMrC37RwJ9vL$6Y$%Qq7z}|3+*6QC zO->Cd`N~NFG~-BYfwxtRHs%_zj;Rf4a^3$O%(Bwb=$7+@G0TG)I4?)F2#$#7*Fehf zf>Y&U97*{2nK^OiA9?|C->KbOIpd<>Jalk zO#PYFlB}&>s0IP#e_M zlhs;|BBsZF&eir?A@Zfcw1g>}k54`1RA{`%>=xj+fZ@gBNaI~hP!YgteP^TW(!7PJ zXi3E7$A7@m@4*8X7a>iV|6*^~vTnu5bRp51#OQ%7WM-44Ld}|Z44RN|g7_kx4 zVtSX9l0vd)Poj1ojLG2jz5w1aBtsZZ!J+0OOiDzF{VLJz$Buu5?leFDsA#HI;YAFW zFmS_UgxS9e!A4Gt9r_E7YdARoaqKVkVEh7hcCUsav+_I4LqOE(+{&~Yz9Z*9N%Lu% z!d<5I4#kNJ+ivY-u={?$bSE!2Pr&yp&Mn_QeBg9sc2rPUGc=gG$MAT53lrQw<^V_=52_u+O~MSc{l zVz5}*7iraFT?JM_N?r19BZi_>@eR)b6!Ub8B2)rz_l(0m!A%ejh92d>z2^rbmluCgtouSGuw%bG&)PW zt+iX`{x(JD)7cg)g`q+aL&R+vDS|)`z4Y<%SuZ9@?^wa%0ZfNRDpk9an56M)fDmd|>i7-)-0HO)aqK&D(QR5q3C*UUzkFvrT?)XOah+7+2omNmOK-zPzGmNkv6<(8o3W4YZSG_xgIBm%5Qyf-5~p zHtWxeb9@Cf0BWv#HPgjgIN4TkS_d7V9?6!cPKnqv6A@)K{G9WY>}`FsA=Gw2z}Ksm z!jt~%H!lxvlKuAtuPen4i#!tVt-p}Zs@>D<+bNnKdI0NkXaAJ?h0OZe+Cj_QiJyLX z?Ru9GE4+e7O=jGw!bbCz>t+E;PuZ1MVe|9WxP2U4DGWS;R;EjDd5&N}@7+qlZE(o( zvfx?5%K%=87`)tTva@ZhLns4s?|4^Y?FMBOKc55$x`K1_^6lFKC>ro5ojKyhxs4&f z89d)OIe)_EG9=g%q=o0t2L=!CBNgJCeS7spc9lp<*W1$m+Uokb!b7PJ^?zP|JnvQ# zS6*t=$HHUMQ*vwjLozu>jq04tOe((nw{>*~KtIQl2c2hkA;^)S8SNuwLDW3Khrciz znhj}LS?z3-4&eFRP`6^X3jv}rm<5fEjo^@geE_dk!xTQurE|f=29*Kgk4Ye&-mP$Z zFHKG>%EOo8>dO{@_&HOgX(I@4(T8Y4Ef}Ngg4rr&_;e1+yiT^8z;qne#KJI-hfOC` z=WCAQ#?%5tBIDmUFSK5`aIg1+p;|}DqfWLuAT{dgIiyB8P1}g5ui|h5dM(s}3AKVj zvU@-IO`ySQYMsAEeepSw!U-3ubxa)F2WKx|zKm5h&uaKUK(%JB<>1!lQgQ2EOXxN+ z-$ncZz7vyi{b(EagETG>Y9q;>9@`uvBS>zDY|tuNmngb+)rEE|SLSts-2`$z1BG3Y z&szp3B0AD%TXW%CCoWni_#dDqitxAi_1M-V9I05(!E7CcA8F;S#CSlI+i$Rn)-)^ zioI#rNtKRuuQ9jR-B43+a=Rsee75mr=E>1`<_=Lr| zBxE&Zt&@ow7$m^2z?b$4oGj17AC!&~S61ZIO&l(iZcp!WS^V^Sf1j=@&svJ({wc@s zvxn`?RMR4(`!jT>nHM*#uqzYgxw(yD!y z+%w{pWD_pX)==rmcVayKE;ARWNuXer>2f+Zu?ZdgTN$?*tq}kyTcx~r`1_A3mfB^TcaKABU^LUoDy_*+A2N7 zqpIG$V|(+)L0K6)EDM8p*`t`jL8U!=zRk|w`=jIjeDM|M3zex=b)>QJjs+VRRMgd9 zhxI&vbS#lq+Utd;{H>q|zkHWomdS_4FxTn>nNDCVKkJ8P>aZnQ6lFLxhv-s*j@l7M$L9 zg()iP)lhAkhnJCrFUeK?YPh2_C@E!?m6=>iNldGkj8)?2e=c?;N8g-lmwZeQ%ejk- zF{Mv;?%1XJ;-u-snA*~|74I9IL~@ZeINVrJsr=;$;SwM&8*+-u{dj~*>OBNMi8v(L}8^R>g(-46p~ zt{J_BhDm1QW@$H=l@a$ShU?UQsM-$XwAh|qL7axmqMGAYiuxb5&hyZ$2@(dXBDI|L z;nPdS_081cjz=HZ%M>_Cb<1I3^aMIuqUIX*S>{Fjs1UqC6!`M)s7gt3&&8Y{={$8_)Y$lxt*$?THC~yd``jc6pmv5Z$y+5ck!X

    4cDDYO)ji_&2 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 literal 136288 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 - ) -} - export function HogFunctionTest(): JSX.Element { const { logicProps } = useValues(hogFunctionConfigurationLogic) const { @@ -85,6 +70,7 @@ export function HogFunctionTest(): JSX.Element { savedGlobals, testInvocation, testResultMode, + sortedTestsResult, } = useValues(hogFunctionTestLogic(logicProps)) const { submitTestInvocation, @@ -242,7 +228,7 @@ export function HogFunctionTest(): JSX.Element { {testResult.status === 'success' ? 'Success' : 'Error'} - {type === 'transformation' ? ( + {type === 'transformation' && testResult.status === 'success' ? ( <>
    Transformation result @@ -262,15 +248,9 @@ export function HogFunctionTest(): JSX.Element { { + const properties = result.properties ?? {} + properties.$ip = properties.$ip ?? '89.160.20.129' + return { + event: result.event, + uuid: result.uuid, + distinct_id: result.distinct_id, + timestamp: result.timestamp, + properties, + } +} + +const convertFromTransformationEvent = (result: HogTransformationEvent): Record => { + return { + event: result.event, + uuid: result.uuid, + distinct_id: result.distinct_id, + timestamp: result.timestamp, + properties: result.properties, + } } export const hogFunctionTestLogic = kea([ @@ -61,6 +84,7 @@ export const hogFunctionTestLogic = kea([ saveGlobals: (name: string, globals: HogFunctionInvocationGlobals) => ({ name, globals }), deleteSavedGlobals: (index: number) => ({ index }), setTestResultMode: (mode: 'raw' | 'diff') => ({ mode }), + receiveExampleGlobals: (globals: HogFunctionInvocationGlobals | null) => ({ globals }), }), reducers({ expanded: [ @@ -95,10 +119,24 @@ export const hogFunctionTestLogic = kea([ }), listeners(({ values, actions }) => ({ loadSampleGlobalsSuccess: () => { - actions.setTestInvocationValue('globals', JSON.stringify(values.sampleGlobals, null, 2)) + actions.receiveExampleGlobals(values.sampleGlobals) }, setSampleGlobals: ({ sampleGlobals }) => { - actions.setTestInvocationValue('globals', JSON.stringify(sampleGlobals, null, 2)) + actions.receiveExampleGlobals(sampleGlobals) + }, + + receiveExampleGlobals: ({ globals }) => { + if (!globals) { + return + } + + if (values.type === 'transformation') { + const event = convertToTransformationEvent(globals.event) + // Strip down to just the real values + actions.setTestInvocationValue('globals', JSON.stringify(event, null, 2)) + } else { + actions.setTestInvocationValue('globals', JSON.stringify(globals, null, 2)) + } }, })), @@ -123,10 +161,18 @@ export const hogFunctionTestLogic = kea([ return } - const globals = tryJsonParse(data.globals) + const parsedData = tryJsonParse(data.globals) const configuration = sanitizeConfiguration(values.configuration) as Record configuration.template_id = values.templateId + // Transformations have a simpler UI just showing the event so we need to map it back to the event + const globals = + values.type === 'transformation' + ? { + event: parsedData, + } + : parsedData + try { const res = await api.hogFunctions.createTestInvocation(props.id ?? 'new', { globals, @@ -134,6 +180,11 @@ export const hogFunctionTestLogic = kea([ configuration, }) + // Modify the result to match better our globals format + if (values.type === 'transformation' && res.result) { + res.result = convertFromTransformationEvent(res.result) + } + actions.setTestResult(res) } catch (e) { lemonToast.error(`An unexpected server error occurred while trying to testing the function. ${e}`) @@ -142,30 +193,39 @@ export const hogFunctionTestLogic = kea([ }, })), + selectors(() => ({ + sortedTestsResult: [ + (s) => [s.configuration, s.testResult, s.testInvocation], + ( + configuration, + testResult, + testInvocation + ): { + input: string + output: string + } | null => { + if (!testResult || configuration.type !== 'transformation') { + return null + } + + const input = JSON.parse(testInvocation.globals) + + // To provide a nice diffing experience, we need to sort the json result so that the keys are in the same order + // as the input. + const sortedResult = JSON.parse(JSON.stringify(testResult.result)) + const sortedInput = JSON.parse(JSON.stringify(input)) + + return { + input: JSON.stringify(sortedInput, null, 2), + output: JSON.stringify(sortedResult, null, 2), + } + }, + ], + })), + afterMount(({ actions, values }) => { - if (values.type === 'email') { - const email = { - from: 'me@example.com', - to: 'you@example.com', - subject: 'Hello', - html: 'hello world', - } - actions.setTestInvocationValue( - 'globals', - JSON.stringify({ email, person: values.exampleInvocationGlobals.person }, null, 2) - ) - } else if (values.type === 'broadcast') { - actions.setTestInvocationValue( - 'globals', - JSON.stringify({ person: values.exampleInvocationGlobals.person }, null, 2) - ) - } else if (values.type === 'transformation') { - actions.setTestInvocationValue( - 'globals', - JSON.stringify({ event: values.exampleInvocationGlobals.event }, null, 2) - ) - } else { - actions.setTestInvocationValue('globals', '{/* Please wait, fetching a real event. */}') + if (values.type === 'transformation') { + actions.receiveExampleGlobals(values.exampleInvocationGlobals) } }), ]) diff --git a/frontend/src/types.ts b/frontend/src/types.ts index 469796bbb91cf..af4eba3ab181d 100644 --- a/frontend/src/types.ts +++ b/frontend/src/types.ts @@ -4779,6 +4779,13 @@ export type HogFunctionInvocationGlobals = { > } +export type HogFunctionTestInvocationResult = { + status: 'success' | 'error' + logs: LogEntry[] + result: any + errors?: string[] +} + export type AppMetricsV2Response = { labels: string[] series: { diff --git a/plugin-server/src/cdp/cdp-api.ts b/plugin-server/src/cdp/cdp-api.ts index 278afecb379b7..b490beca8bdde 100644 --- a/plugin-server/src/cdp/cdp-api.ts +++ b/plugin-server/src/cdp/cdp-api.ts @@ -110,7 +110,7 @@ export class CdpApi { status.info('⚡️', 'Received invocation', { id, team_id, body: req.body }) - if (!globals) { + if (!globals || !globals.event) { res.status(400).json({ error: 'Missing event' }) return } From d820d002d2d0bd032568a24b56c84078ebcbfd65 Mon Sep 17 00:00:00 2001 From: Ben White Date: Thu, 30 Jan 2025 12:30:22 +0100 Subject: [PATCH 120/163] Fixes --- plugin-server/src/ingestion/ingestion-consumer.test.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/plugin-server/src/ingestion/ingestion-consumer.test.ts b/plugin-server/src/ingestion/ingestion-consumer.test.ts index 4de471713e0ac..abc16201f89e4 100644 --- a/plugin-server/src/ingestion/ingestion-consumer.test.ts +++ b/plugin-server/src/ingestion/ingestion-consumer.test.ts @@ -323,6 +323,11 @@ describe('IngestionConsumer', () => { }) describe('event batching', () => { + beforeEach(async () => { + ingester = new IngestionConsumer(hub) + await ingester.start() + }) + it('should batch events based on the distinct_id', async () => { const messages = createKafkaMessages([ createEvent({ distinct_id: 'distinct-id-1' }), From e824c70e3d8cbacd54c2fdfcac560f2584e7c49e Mon Sep 17 00:00:00 2001 From: Ben White Date: Thu, 30 Jan 2025 12:31:09 +0100 Subject: [PATCH 121/163] feat: Expose CDP templates via nodejs (#28033) --- ...-pipeline-node-new-hog-function--light.png | Bin 131245 -> 133981 bytes frontend/public/transformations/geoip.png | Bin 0 -> 15661 bytes .../public/transformations/user-agent.png | Bin 5792 -> 3575 bytes .../hogfunctions/HogFunctionConfiguration.tsx | 7 +- mypy-baseline.txt | 19 +- plugin-server/src/cdp/cdp-api.ts | 6 + .../language-url-splitter-app/template.ts | 2 +- .../property-filter-plugin/template.ts | 2 +- .../semver-flattener-plugin/template.ts | 2 +- .../user-agent-plugin/template.ts | 2 +- .../_transformations/geoip/geoip.template.ts | 2 +- plugin-server/src/cdp/templates/index.ts | 36 ++ posthog/api/hog_function.py | 5 +- posthog/api/hog_function_template.py | 81 ++- .../test/__data__/hog_function_templates.json | 547 ++++++++++++++++++ posthog/api/test/test_hog_function.py | 14 + .../api/test/test_hog_function_templates.py | 17 +- posthog/cdp/templates/__init__.py | 11 - .../_transformations/template_pass_through.py | 18 - .../cdp/templates/hog_function_template.py | 8 +- .../test/__snapshots__/test_web_goals.ambr | 164 +++--- .../__snapshots__/test_web_stats_table.ambr | 24 +- posthog/models/hog_functions/hog_function.py | 14 +- posthog/plugins/plugin_server_api.py | 4 + 24 files changed, 831 insertions(+), 154 deletions(-) create mode 100644 frontend/public/transformations/geoip.png create mode 100644 posthog/api/test/__data__/hog_function_templates.json delete mode 100644 posthog/cdp/templates/_transformations/template_pass_through.py diff --git a/frontend/__snapshots__/scenes-app-pipeline--pipeline-node-new-hog-function--light.png b/frontend/__snapshots__/scenes-app-pipeline--pipeline-node-new-hog-function--light.png index 770be28c1a0e5bce7ad96f0334461d95938c4846..1eaddf28cc8a7986b098c7ab8e43bc60a5e95757 100644 GIT binary patch delta 93739 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 >+ao((kO~I(a;lY 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 diff --git a/frontend/public/transformations/geoip.png b/frontend/public/transformations/geoip.png new file mode 100644 index 0000000000000000000000000000000000000000..45ab21b98ec71eb40680112f42a565db7c56ff34 GIT binary patch literal 15661 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 By?Fot literal 5792 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 } - const isLegacyPlugin = hogFunction?.template?.id?.startsWith('plugin-') + const isLegacyPlugin = (template?.id || hogFunction?.template?.id)?.startsWith('plugin-') const headerButtons = ( <> @@ -167,7 +167,7 @@ export function HogFunctionConfiguration({ const showFilters = displayOptions.showFilters ?? - ['destination', 'internal_destination', 'site_destination', 'broadcast', 'transformation'].includes(type) + ['destination', 'internal_destination', 'site_destination', 'broadcast'].includes(type) const showExpectedVolume = displayOptions.showExpectedVolume ?? ['destination', 'site_destination'].includes(type) const showStatus = displayOptions.showStatus ?? ['destination', 'internal_destination', 'email', 'transformation'].includes(type) @@ -266,8 +266,7 @@ export function HogFunctionConfiguration({ {isLegacyPlugin ? ( - This destination is one of our legacy plugins. It will be deprecated and you - should instead upgrade + This is part of our legacy plugins and will eventually be deprecated. ) : hogFunction?.template && !hogFunction.template.id.startsWith('template-blank-') ? ( ] = ...") [var-annotated] -posthog/temporal/tests/batch_exports/test_snowflake_batch_export_workflow.py:0: error: Need type annotation for "_execute_async_calls" (hint: "_execute_async_calls: list[] = ...") [var-annotated] -posthog/temporal/tests/batch_exports/test_snowflake_batch_export_workflow.py:0: error: Need type annotation for "_cursors" (hint: "_cursors: list[] = ...") [var-annotated] -posthog/temporal/tests/batch_exports/test_snowflake_batch_export_workflow.py:0: error: List item 0 has incompatible type "tuple[str, str, int, int, int, int, str, int]"; expected "tuple[str, str, int, int, str, str, str, str]" [list-item] +posthog/temporal/tests/batch_exports/test_run_updates.py:0: error: Unused "type: ignore" comment [unused-ignore] +posthog/temporal/tests/batch_exports/test_run_updates.py:0: error: Unused "type: ignore" comment [unused-ignore] +posthog/temporal/tests/batch_exports/test_run_updates.py:0: error: Unused "type: ignore" comment [unused-ignore] +posthog/temporal/tests/batch_exports/test_run_updates.py:0: error: Unused "type: ignore" comment [unused-ignore] +posthog/temporal/tests/batch_exports/test_batch_exports.py:0: error: TypedDict key must be a string literal; expected one of ("_timestamp", "created_at", "distinct_id", "elements", "elements_chain", ...) [literal-required] posthog/api/test/test_capture.py:0: error: Statement is unreachable [unreachable] posthog/api/test/test_capture.py:0: error: Incompatible return value type (got "_MonkeyPatchedWSGIResponse", expected "HttpResponse") [return-value] posthog/api/test/test_capture.py:0: error: Module has no attribute "utc" [attr-defined] @@ -803,6 +798,10 @@ posthog/api/test/test_capture.py:0: error: Dict entry 0 has incompatible type "s posthog/api/test/test_capture.py:0: error: Dict entry 0 has incompatible type "str": "float"; expected "str": "int" [dict-item] posthog/api/test/test_capture.py:0: error: Dict entry 0 has incompatible type "str": "float"; expected "str": "int" [dict-item] posthog/api/test/test_capture.py:0: error: Dict entry 0 has incompatible type "str": "float"; expected "str": "int" [dict-item] +posthog/temporal/tests/batch_exports/test_snowflake_batch_export_workflow.py:0: error: Need type annotation for "_execute_calls" (hint: "_execute_calls: list[] = ...") [var-annotated] +posthog/temporal/tests/batch_exports/test_snowflake_batch_export_workflow.py:0: error: Need type annotation for "_execute_async_calls" (hint: "_execute_async_calls: list[] = ...") [var-annotated] +posthog/temporal/tests/batch_exports/test_snowflake_batch_export_workflow.py:0: error: Need type annotation for "_cursors" (hint: "_cursors: list[] = ...") [var-annotated] +posthog/temporal/tests/batch_exports/test_snowflake_batch_export_workflow.py:0: error: List item 0 has incompatible type "tuple[str, str, int, int, int, int, str, int]"; expected "tuple[str, str, int, int, str, str, str, str]" [list-item] posthog/temporal/tests/batch_exports/test_redshift_batch_export_workflow.py:0: error: Incompatible types in assignment (expression has type "str | int", variable has type "int") [assignment] posthog/api/test/batch_exports/conftest.py:0: error: Signature of "run" incompatible with supertype "Worker" [override] posthog/api/test/batch_exports/conftest.py:0: note: Superclass: diff --git a/plugin-server/src/cdp/cdp-api.ts b/plugin-server/src/cdp/cdp-api.ts index 859f1f898d2e9..a2c4839f1e946 100644 --- a/plugin-server/src/cdp/cdp-api.ts +++ b/plugin-server/src/cdp/cdp-api.ts @@ -8,6 +8,7 @@ import { FetchExecutorService } from './services/fetch-executor.service' import { HogExecutorService, MAX_ASYNC_STEPS } from './services/hog-executor.service' import { HogFunctionManagerService } from './services/hog-function-manager.service' import { HogWatcherService, HogWatcherState } from './services/hog-watcher.service' +import { HOG_FUNCTION_TEMPLATES } from './templates' import { HogFunctionInvocationResult, HogFunctionQueueParametersFetchRequest, HogFunctionType, LogEntry } from './types' export class CdpApi { @@ -42,10 +43,15 @@ export class CdpApi { router.post('/api/projects/:team_id/hog_functions/:id/invocations', asyncHandler(this.postFunctionInvocation)) router.get('/api/projects/:team_id/hog_functions/:id/status', asyncHandler(this.getFunctionStatus())) router.patch('/api/projects/:team_id/hog_functions/:id/status', asyncHandler(this.patchFunctionStatus())) + router.get('/api/hog_function_templates', this.getHogFunctionTemplates) return router } + private getHogFunctionTemplates = (req: express.Request, res: express.Response): void => { + res.json(HOG_FUNCTION_TEMPLATES) + } + private getFunctionStatus = () => async (req: express.Request, res: express.Response): Promise => { diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/language-url-splitter-app/template.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/language-url-splitter-app/template.ts index ab786cf78c13d..26c423e6ff281 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/language-url-splitter-app/template.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/language-url-splitter-app/template.ts @@ -6,7 +6,7 @@ export const template: HogFunctionTemplate = { id: 'plugin-language-url-splitter-app', name: 'Language URL splitter', description: 'Splits the language from the URL', - icon_url: 'https://raw.githubusercontent.com/posthog/language-url-splitter-app/main/logo.png', + icon_url: '/static/hedgehog/builder-hog-01.png', category: ['Transformation'], hog: `return event`, inputs_schema: [ diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/property-filter-plugin/template.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/property-filter-plugin/template.ts index 4f5d7d64754fa..4ed18296873a2 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/property-filter-plugin/template.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/property-filter-plugin/template.ts @@ -6,7 +6,7 @@ export const template: HogFunctionTemplate = { id: 'plugin-property-filter-plugin', name: 'Property Filter', description: 'This plugin will set all configured properties to null inside an ingested event.', - icon_url: 'https://raw.githubusercontent.com/posthog/posthog-property-filter-plugin/main/logo.png', + icon_url: 'https://raw.githubusercontent.com/posthog/property-filter-plugin/dev/logo.png', category: ['Transformation'], hog: `return event`, inputs_schema: [ diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/semver-flattener-plugin/template.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/semver-flattener-plugin/template.ts index 1b6acc7cac708..b90fedceb287d 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/semver-flattener-plugin/template.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/semver-flattener-plugin/template.ts @@ -6,7 +6,7 @@ export const template: HogFunctionTemplate = { id: 'plugin-semver-flattener-plugin', name: 'SemVer Flattener', description: 'This plugin will flatten semver versions in the specified properties.', - icon_url: 'https://raw.githubusercontent.com/posthog/posthog-semver-flattener-plugin/main/logo.png', + icon_url: '/static/transformations/semver-flattener.png', category: ['Transformation'], hog: `return event`, inputs_schema: [ diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/user-agent-plugin/template.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/user-agent-plugin/template.ts index 313c7d85afa59..cb177efbc3384 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/user-agent-plugin/template.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/user-agent-plugin/template.ts @@ -7,7 +7,7 @@ export const template: HogFunctionTemplate = { name: 'User Agent Populator', description: "Enhances events with user agent details. User Agent plugin allows you to populate events with the $browser, $browser_version for PostHog Clients that don't typically populate these properties", - icon_url: 'https://raw.githubusercontent.com/posthog/useragent-plugin/main/logo.png', + icon_url: '/static/transformations/user-agent.png', category: ['Transformation'], hog: `return event`, inputs_schema: [ diff --git a/plugin-server/src/cdp/templates/_transformations/geoip/geoip.template.ts b/plugin-server/src/cdp/templates/_transformations/geoip/geoip.template.ts index 4f95ceef54413..cc11f03b89575 100644 --- a/plugin-server/src/cdp/templates/_transformations/geoip/geoip.template.ts +++ b/plugin-server/src/cdp/templates/_transformations/geoip/geoip.template.ts @@ -6,7 +6,7 @@ export const template: HogFunctionTemplate = { id: 'template-geoip', name: 'GeoIP', description: 'Adds geoip data to the event', - icon_url: '/static/hedgehog/builder-hog-01.png', + icon_url: '/static/transformations/geoip.png', category: ['Custom'], hog: ` // Define the properties to be added to the event diff --git a/plugin-server/src/cdp/templates/index.ts b/plugin-server/src/cdp/templates/index.ts index e69de29bb2d1d..9ae554aaac68e 100644 --- a/plugin-server/src/cdp/templates/index.ts +++ b/plugin-server/src/cdp/templates/index.ts @@ -0,0 +1,36 @@ +import { template as downsamplingPlugin } from '../legacy-plugins/_transformations/downsampling-plugin/template' +import { template as languageUrlSplitterTemplate } from '../legacy-plugins/_transformations/language-url-splitter-app/template' +import { template as posthogAppUrlParametersToEventPropertiesTemplate } from '../legacy-plugins/_transformations/posthog-app-url-parameters-to-event-properties/template' +import { template as posthogFilterOutTemplate } from '../legacy-plugins/_transformations/posthog-filter-out-plugin/template' +import { template as posthogUrlNormalizerTemplate } from '../legacy-plugins/_transformations/posthog-url-normalizer-plugin/template' +import { template as propertyFilterTemplate } from '../legacy-plugins/_transformations/property-filter-plugin/template' +import { template as semverFlattenerTemplate } from '../legacy-plugins/_transformations/semver-flattener-plugin/template' +import { template as taxonomyTemplate } from '../legacy-plugins/_transformations/taxonomy-plugin/template' +import { template as timestampParserTemplate } from '../legacy-plugins/_transformations/timestamp-parser-plugin/template' +import { template as userAgentTemplate } from '../legacy-plugins/_transformations/user-agent-plugin/template' +import { template as webhookTemplate } from './_destinations/webhook/webhook.template' +import { template as defaultTransformationTemplate } from './_transformations/default/default.template' +import { template as geoipTemplate } from './_transformations/geoip/geoip.template' +import { HogFunctionTemplate } from './types' + +export const HOG_FUNCTION_TEMPLATES_DESTINATIONS: HogFunctionTemplate[] = [webhookTemplate] + +export const HOG_FUNCTION_TEMPLATES_TRANSFORMATIONS: HogFunctionTemplate[] = [ + defaultTransformationTemplate, + geoipTemplate, + downsamplingPlugin, + languageUrlSplitterTemplate, + posthogAppUrlParametersToEventPropertiesTemplate, + posthogFilterOutTemplate, + posthogUrlNormalizerTemplate, + propertyFilterTemplate, + semverFlattenerTemplate, + taxonomyTemplate, + timestampParserTemplate, + userAgentTemplate, +] + +export const HOG_FUNCTION_TEMPLATES: HogFunctionTemplate[] = [ + ...HOG_FUNCTION_TEMPLATES_DESTINATIONS, + ...HOG_FUNCTION_TEMPLATES_TRANSFORMATIONS, +] diff --git a/posthog/api/hog_function.py b/posthog/api/hog_function.py index 30648a2164b04..c7e3ca2ca54a2 100644 --- a/posthog/api/hog_function.py +++ b/posthog/api/hog_function.py @@ -13,14 +13,13 @@ from posthog.api.app_metrics2 import AppMetricsMixin from posthog.api.forbid_destroy_model import ForbidDestroyModel -from posthog.api.hog_function_template import HogFunctionTemplateSerializer +from posthog.api.hog_function_template import HogFunctionTemplateSerializer, HogFunctionTemplates from posthog.api.log_entries import LogEntryMixin from posthog.api.routing import TeamAndOrgViewSetMixin from posthog.api.shared import UserBasicSerializer from posthog.cdp.filters import compile_filters_bytecode, compile_filters_expr from posthog.cdp.services.icons import CDPIconsService -from posthog.cdp.templates import HOG_FUNCTION_TEMPLATES_BY_ID from posthog.cdp.templates._internal.template_legacy_plugin import create_legacy_plugin_template from posthog.cdp.validation import compile_hog, generate_template_bytecode, validate_inputs, validate_inputs_schema from posthog.cdp.site_functions import get_transpiled_function @@ -156,7 +155,7 @@ def validate(self, attrs): is_create = self.context.get("view") and self.context["view"].action == "create" template_id = attrs.get("template_id", instance.template_id if instance else None) - template = HOG_FUNCTION_TEMPLATES_BY_ID.get(template_id, None) + template = HogFunctionTemplates.template(template_id) if template_id else None if template_id and template_id.startswith("plugin-"): template = create_legacy_plugin_template(template_id) diff --git a/posthog/api/hog_function_template.py b/posthog/api/hog_function_template.py index a8a24145a02cf..5015b5731dc86 100644 --- a/posthog/api/hog_function_template.py +++ b/posthog/api/hog_function_template.py @@ -1,3 +1,5 @@ +from datetime import datetime, timedelta +from posthoganalytics import capture_exception import structlog from django_filters.rest_framework import DjangoFilterBackend from rest_framework import viewsets, permissions @@ -5,13 +7,15 @@ from rest_framework.response import Response from rest_framework.exceptions import NotFound -from posthog.cdp.templates import HOG_FUNCTION_SUB_TEMPLATES, HOG_FUNCTION_TEMPLATES, ALL_HOG_FUNCTION_TEMPLATES_BY_ID +from posthog.cdp.templates import HOG_FUNCTION_TEMPLATES from posthog.cdp.templates.hog_function_template import ( HogFunctionMapping, HogFunctionMappingTemplate, HogFunctionTemplate, HogFunctionSubTemplate, + derive_sub_templates, ) +from posthog.plugins.plugin_server_api import get_hog_function_templates from rest_framework_dataclasses.serializers import DataclassSerializer @@ -42,6 +46,77 @@ class Meta: dataclass = HogFunctionTemplate +class HogFunctionTemplates: + _cache_until: datetime | None = None + _cached_templates: list[HogFunctionTemplate] = [] + _cached_templates_by_id: dict[str, HogFunctionTemplate] = {} + _cached_sub_templates: list[HogFunctionTemplate] = [] + _cached_sub_templates_by_id: dict[str, HogFunctionTemplate] = {} + + @classmethod + def templates(cls): + cls._load_templates() + return cls._cached_templates + + @classmethod + def sub_templates(cls): + cls._load_templates() + return cls._cached_sub_templates + + @classmethod + def template(cls, template_id: str): + cls._load_templates() + return cls._cached_templates_by_id.get(template_id, cls._cached_sub_templates_by_id.get(template_id)) + + @classmethod + def _load_templates(cls): + if cls._cache_until and datetime.now() < cls._cache_until: + return + + # First we load and convert all nodejs templates to python templates + nodejs_templates: list[HogFunctionTemplate] = [] + + try: + response = get_hog_function_templates() + + if response.status_code != 200: + raise Exception("Failed to fetch hog function templates from the node service") + + nodejs_templates_json = response.json() + for template_data in nodejs_templates_json: + try: + serializer = HogFunctionTemplateSerializer(data=template_data) + serializer.is_valid(raise_exception=True) + template = serializer.save() + nodejs_templates.append(template) + except Exception as e: + logger.error( + "Failed to convert template", + template_id=template_data.get("id"), + error=str(e), + exc_info=True, + ) + capture_exception(e) + raise + except Exception as e: + capture_exception(e) + # Continue on so as not to block the user + + templates = [ + *HOG_FUNCTION_TEMPLATES, + *nodejs_templates, + ] + sub_templates = derive_sub_templates(templates=templates) + + # If we failed to get the templates, we cache for 30 seconds to avoid hammering the node service + # If we got the templates, we cache for 5 minutes as these change infrequently + cls._cache_until = datetime.now() + timedelta(seconds=30 if not nodejs_templates else 300) + cls._cached_templates = templates + cls._cached_sub_templates = sub_templates + cls._cached_templates_by_id = {template.id: template for template in templates} + cls._cached_sub_templates_by_id = {template.id: template for template in sub_templates} + + # NOTE: There is nothing currently private about these values class PublicHogFunctionTemplateViewSet(viewsets.GenericViewSet): filter_backends = [DjangoFilterBackend] @@ -59,7 +134,7 @@ def list(self, request: Request, *args, **kwargs): elif "types" in request.GET: types = self.request.GET.get("types", "destination").split(",") - templates_list = HOG_FUNCTION_SUB_TEMPLATES if sub_template_id else HOG_FUNCTION_TEMPLATES + templates_list = HogFunctionTemplates.sub_templates() if sub_template_id else HogFunctionTemplates.templates() matching_templates = [] @@ -81,7 +156,7 @@ def list(self, request: Request, *args, **kwargs): return self.get_paginated_response(serializer.data) def retrieve(self, request: Request, *args, **kwargs): - item = ALL_HOG_FUNCTION_TEMPLATES_BY_ID.get(kwargs["pk"], None) + item = HogFunctionTemplates.template(kwargs["pk"]) if not item: raise NotFound(f"Template with id {kwargs['pk']} not found.") diff --git a/posthog/api/test/__data__/hog_function_templates.json b/posthog/api/test/__data__/hog_function_templates.json new file mode 100644 index 0000000000000..ad0dc299dddef --- /dev/null +++ b/posthog/api/test/__data__/hog_function_templates.json @@ -0,0 +1,547 @@ +[ + { + "status": "beta", + "type": "destination", + "id": "template-webhook", + "name": "HTTP Webhook", + "description": "Sends a webhook templated by the incoming event data", + "icon_url": "/static/posthog-icon.svg", + "category": ["Custom"], + "hog": "\nlet payload := {\n 'headers': inputs.headers,\n 'body': inputs.body,\n 'method': inputs.method\n}\n\nif (inputs.debug) {\n print('Request', inputs.url, payload)\n}\n\nlet res := fetch(inputs.url, payload);\n\nif (inputs.debug) {\n print('Response', res.status, res.body);\n}\n", + "inputs_schema": [ + { + "key": "url", + "type": "string", + "label": "Webhook URL", + "secret": false, + "required": true + }, + { + "key": "method", + "type": "choice", + "label": "Method", + "secret": false, + "choices": [ + { + "label": "POST", + "value": "POST" + }, + { + "label": "PUT", + "value": "PUT" + }, + { + "label": "PATCH", + "value": "PATCH" + }, + { + "label": "GET", + "value": "GET" + }, + { + "label": "DELETE", + "value": "DELETE" + } + ], + "default": "POST", + "required": false + }, + { + "key": "body", + "type": "json", + "label": "JSON Body", + "default": { + "event": "{event}", + "person": "{person}" + }, + "secret": false, + "required": false + }, + { + "key": "headers", + "type": "dictionary", + "label": "Headers", + "secret": false, + "required": false, + "default": { + "Content-Type": "application/json" + } + }, + { + "key": "debug", + "type": "boolean", + "label": "Log responses", + "description": "Logs the response of http calls for debugging.", + "secret": false, + "required": false, + "default": false + } + ], + "sub_templates": [ + { + "id": "early-access-feature-enrollment", + "name": "HTTP Webhook on feature enrollment", + "filters": { + "events": [ + { + "id": "$feature_enrollment_update", + "type": "events" + } + ] + } + }, + { + "id": "survey-response", + "name": "HTTP Webhook on survey response", + "filters": { + "events": [ + { + "id": "survey sent", + "type": "events", + "properties": [ + { + "key": "$survey_response", + "type": "event", + "value": "is_set", + "operator": "is_set" + } + ] + } + ] + } + }, + { + "id": "activity-log", + "name": "HTTP Webhook on team activity", + "filters": { + "events": [ + { + "id": "$activity_log_entry_created", + "type": "events" + } + ] + }, + "type": "internal_destination" + } + ] + }, + { + "status": "alpha", + "type": "transformation", + "id": "template-blank-transformation", + "name": "Custom transformation", + "description": "This is a starter template for custom transformations", + "icon_url": "/static/hedgehog/builder-hog-01.png", + "category": ["Custom"], + "hog": "\n// This is a blank template for custom transformations\n// The function receives 'event' as a global object and expects it to be returned\n// If you return null then the event will be discarded\nreturn event\n ", + "inputs_schema": [] + }, + { + "status": "beta", + "type": "transformation", + "id": "template-geoip", + "name": "GeoIP", + "description": "Adds geoip data to the event", + "icon_url": "/static/hedgehog/builder-hog-01.png", + "category": ["Custom"], + "hog": "\n// Define the properties to be added to the event\nlet geoipProperties := {\n 'city_name': null,\n 'city_confidence': null,\n 'subdivision_2_name': null,\n 'subdivision_2_code': null,\n 'subdivision_1_name': null,\n 'subdivision_1_code': null,\n 'country_name': null,\n 'country_code': null,\n 'continent_name': null,\n 'continent_code': null,\n 'postal_code': null,\n 'latitude': null,\n 'longitude': null,\n 'accuracy_radius': null,\n 'time_zone': null\n}\n// Check if the event has an IP address l\nif (event.properties?.$geoip_disable or empty(event.properties?.$ip)) {\n print('geoip disabled or no ip', event.properties, event.properties?.$ip)\n return event\n}\nlet ip := event.properties.$ip\nif (ip == '127.0.0.1') {\n print('spoofing ip for local development', ip)\n ip := '89.160.20.129'\n}\nlet response := geoipLookup(ip)\nif (not response) {\n print('geoip lookup failed for ip', ip)\n return event\n}\nlet location := {}\nif (response.city) {\n location['city_name'] := response.city.names?.en\n}\nif (response.country) {\n location['country_name'] := response.country.names?.en\n location['country_code'] := response.country.isoCode\n}\nif (response.continent) {\n location['continent_name'] := response.continent.names?.en\n location['continent_code'] := response.continent.code\n}\nif (response.postal) {\n location['postal_code'] := response.postal.code\n}\nif (response.location) {\n location['latitude'] := response.location?.latitude\n location['longitude'] := response.location?.longitude\n location['accuracy_radius'] := response.location?.accuracyRadius\n location['time_zone'] := response.location?.timeZone\n}\nif (response.subdivisions) {\n for (let index, subdivision in response.subdivisions) {\n location[f'subdivision_{index + 1}_code'] := subdivision.isoCode\n location[f'subdivision_{index + 1}_name'] := subdivision.names?.en\n }\n}\nprint('geoip location data for ip:', location) \nlet returnEvent := event\nreturnEvent.properties := returnEvent.properties ?? {}\nreturnEvent.properties.$set := returnEvent.properties.$set ?? {}\nreturnEvent.properties.$set_once := returnEvent.properties.$set_once ?? {}\nfor (let key, value in geoipProperties) {\n if (value != null) {\n returnEvent.properties.$set[f'$geoip_{key}'] := value\n returnEvent.properties.$set_once[f'$initial_geoip_{key}'] := value\n }\n returnEvent.properties.$set[f'$geoip_{key}'] := value\n returnEvent.properties.$set_once[f'$initial_geoip_{key}'] := value\n}\nfor (let key, value in location) {\n returnEvent.properties[f'$geoip_{key}'] := value\n returnEvent.properties.$set[f'$geoip_{key}'] := value\n returnEvent.properties.$set_once[f'$initial_geoip_{key}'] := value\n}\nreturn returnEvent\n ", + "inputs_schema": [] + }, + { + "status": "alpha", + "type": "transformation", + "id": "plugin-downsampling-plugin", + "name": "Downsample", + "description": "Reduces event volume coming into PostHog", + "icon_url": "https://raw.githubusercontent.com/posthog/downsampling-plugin/main/logo.png", + "category": ["Custom"], + "hog": "return event", + "inputs_schema": [ + { + "type": "string", + "key": "percentage", + "label": "% of events to keep", + "default": "100", + "required": false + }, + { + "type": "choice", + "key": "samplingMethod", + "label": "Sampling method", + "choices": [ + { + "value": "Random sampling", + "label": "Random sampling" + }, + { + "value": "Distinct ID aware sampling", + "label": "Distinct ID aware sampling" + } + ], + "default": "Distinct ID aware sampling", + "required": false + } + ] + }, + { + "status": "alpha", + "type": "transformation", + "id": "plugin-language-url-splitter-app", + "name": "Language URL splitter", + "description": "Splits the language from the URL", + "icon_url": "https://raw.githubusercontent.com/posthog/language-url-splitter-app/main/logo.png", + "category": ["Transformation"], + "hog": "return event", + "inputs_schema": [ + { + "key": "pattern", + "label": "Pattern", + "type": "string", + "default": "^/([a-z]{2})(?=/|#|\\?|$)", + "description": "Initialized with `const regexp = new RegExp($pattern)`", + "required": true + }, + { + "key": "matchGroup", + "label": "Match group", + "type": "string", + "default": "1", + "description": "Used in: `const value = regexp.match($pathname)[$matchGroup]`", + "required": true + }, + { + "key": "property", + "label": "Property", + "type": "string", + "default": "locale", + "description": "Name of the event property we will store the matched value in", + "required": true + }, + { + "key": "replacePattern", + "label": "Replacement pattern", + "type": "string", + "default": "^(/[a-z]{2})(/|(?=/|#|\\?|$))", + "description": "Initialized with `new RegExp($pattern)`, leave empty to disable path cleanup.", + "required": true + }, + { + "key": "replaceKey", + "label": "Replacement key", + "type": "string", + "default": "$pathname", + "description": "Where to store the updated path. Keep as `$pathname` to override.", + "required": true + }, + { + "key": "replaceValue", + "label": "Replacement value", + "type": "string", + "default": "/", + "description": "`properties[key] = $pathname.replace(pattern, value)`", + "required": true + } + ] + }, + { + "status": "alpha", + "type": "transformation", + "id": "plugin-posthog-app-url-parameters-to-event-properties", + "name": "URL parameters to event properties", + "description": "Converts URL query parameters to event properties", + "icon_url": "https://raw.githubusercontent.com/posthog/posthog-app-url-parameters-to-event-properties/main/logo.png", + "category": ["Transformation"], + "hog": "return event", + "inputs_schema": [ + { + "key": "parameters", + "label": "URL query parameters to convert", + "type": "string", + "default": "", + "description": "Comma separated list of URL query parameters to capture. Leaving this blank will capture nothing." + }, + { + "key": "prefix", + "label": "Prefix", + "type": "string", + "default": "", + "description": "Add a prefix to the property name e.g. set it to 'prefix_' to get followerId -\u003E prefix_followerId" + }, + { + "key": "suffix", + "label": "Suffix", + "type": "string", + "default": "", + "description": "Add a suffix to the property name e.g. set it to '_suffix' to get followerId -\u003E followerId_suffix" + }, + { + "key": "ignoreCase", + "label": "Ignore the case of URL parameters", + "type": "choice", + "choices": [ + { + "value": "true", + "label": "true" + }, + { + "value": "false", + "label": "false" + } + ], + "default": "false", + "description": "Ignores the case of parameters e.g. when set to true than followerId would match FollowerId, followerID, FoLlOwErId and similar" + }, + { + "key": "setAsUserProperties", + "label": "Add to user properties", + "type": "choice", + "choices": [ + { + "value": "true", + "label": "true" + }, + { + "value": "false", + "label": "false" + } + ], + "default": "false", + "description": "Additionally adds the property to the user properties" + }, + { + "key": "setAsInitialUserProperties", + "label": "Add to user initial properties", + "type": "choice", + "choices": [ + { + "value": "true", + "label": "true" + }, + { + "value": "false", + "label": "false" + } + ], + "default": "false", + "description": "Additionally adds the property to the user initial properties. This will add a prefix of 'initial_' before the already fully composed property e.g. initial_prefix_followerId_suffix" + }, + { + "key": "alwaysJson", + "label": "Always JSON stringify the property data", + "type": "choice", + "choices": [ + { + "value": "true", + "label": "true" + }, + { + "value": "false", + "label": "false" + } + ], + "default": "false", + "description": "If set, always store the resulting data as a JSON array. (Otherwise, single parameters get stored as-is, and multi-value parameters get stored as a JSON array.)" + } + ] + }, + { + "status": "alpha", + "type": "transformation", + "id": "plugin-posthog-filter-out-plugin", + "name": "Filter Out Plugin", + "description": "Filter out events where property values satisfy the given condition", + "icon_url": "https://raw.githubusercontent.com/posthog/posthog-filter-out-plugin/main/logo.png", + "category": ["Transformation"], + "hog": "return event", + "inputs_schema": [ + { + "key": "filters", + "label": "Filters to apply", + "type": "string", + "description": "A JSON file containing an array of filters to apply. See the README for more information.", + "required": false + }, + { + "key": "eventsToDrop", + "label": "Events to filter out", + "type": "string", + "description": "A comma-separated list of event names to filter out (e.g. $pageview,$autocapture)", + "required": false + }, + { + "key": "keepUndefinedProperties", + "label": "Keep event if any of the filtered properties are undefined?", + "type": "choice", + "choices": [ + { + "value": "Yes", + "label": "Yes" + }, + { + "value": "No", + "label": "No" + } + ], + "default": "No" + } + ] + }, + { + "status": "alpha", + "type": "transformation", + "id": "plugin-posthog-url-normalizer-plugin", + "name": "URL Normalizer", + "description": "Normalize the format of urls in your application allowing you to more easily compare them in insights.", + "icon_url": "https://raw.githubusercontent.com/posthog/posthog-url-normalizer-plugin/main/logo.png", + "category": ["Transformation"], + "hog": "return event", + "inputs_schema": [] + }, + { + "status": "alpha", + "type": "transformation", + "id": "plugin-property-filter-plugin", + "name": "Property Filter", + "description": "This plugin will set all configured properties to null inside an ingested event.", + "icon_url": "https://raw.githubusercontent.com/posthog/posthog-property-filter-plugin/main/logo.png", + "category": ["Transformation"], + "hog": "return event", + "inputs_schema": [ + { + "key": "properties", + "label": "Properties to filter out", + "type": "string", + "description": "A comma-separated list of properties to filter out (e.g. $ip, $current_url)", + "default": "", + "required": true + } + ] + }, + { + "status": "alpha", + "type": "transformation", + "id": "plugin-semver-flattener-plugin", + "name": "SemVer Flattener", + "description": "This plugin will flatten semver versions in the specified properties.", + "icon_url": "https://raw.githubusercontent.com/posthog/semver-flattener-plugin/main/logo.png", + "category": ["Transformation"], + "hog": "return event", + "inputs_schema": [ + { + "key": "properties", + "label": "comma separated properties to explode version number from", + "type": "string", + "description": "my_version_number,app_version", + "default": "", + "required": true + } + ] + }, + { + "status": "alpha", + "type": "transformation", + "id": "plugin-taxonomy-plugin", + "name": "Taxonomy", + "description": "Standardize your event names into a single pattern.", + "icon_url": "https://raw.githubusercontent.com/posthog/taxonomy-plugin/main/logo.png", + "category": ["Transformation"], + "hog": "return event", + "inputs_schema": [ + { + "key": "defaultNamingConvention", + "label": "Select your default naming pattern", + "type": "choice", + "choices": [ + { + "value": "camelCase", + "label": "camelCase" + }, + { + "value": "PascalCase", + "label": "PascalCase" + }, + { + "value": "snake_case", + "label": "snake_case" + }, + { + "value": "kebab-case", + "label": "kebab-case" + }, + { + "value": "spaces in between", + "label": "spaces in between" + } + ], + "default": "camelCase", + "required": true + } + ] + }, + { + "status": "alpha", + "type": "transformation", + "id": "plugin-timestamp-parser-plugin", + "name": "Timestamp Parser", + "description": "Parse your event timestamps into useful date properties.", + "icon_url": "https://raw.githubusercontent.com/posthog/timestamp-parser-plugin/main/logo.png", + "category": ["Transformation"], + "hog": "return event", + "inputs_schema": [] + }, + { + "status": "alpha", + "type": "transformation", + "id": "plugin-user-agent-plugin", + "name": "User Agent Populator", + "description": "Enhances events with user agent details. User Agent plugin allows you to populate events with the $browser, $browser_version for PostHog Clients that don't typically populate these properties", + "icon_url": "https://raw.githubusercontent.com/posthog/useragent-plugin/main/logo.png", + "category": ["Transformation"], + "hog": "return event", + "inputs_schema": [ + { + "key": "overrideUserAgentDetails", + "label": "Can override existing browser related properties of event?", + "type": "string", + "description": "If the ingested event already have $browser $browser_version properties in combination with $useragent the $browser, $browser_version properties will be re-populated with the value of $useragent", + "default": "false", + "required": false + }, + { + "key": "enableSegmentAnalyticsJs", + "label": "Automatically read segment_userAgent property, automatically sent by Segment via analytics.js?", + "type": "choice", + "description": "Segment's analytics.js library automatically sends a useragent property that Posthog sees as segment_userAgent. Enabling this causes this plugin to parse that property", + "choices": [ + { + "value": "false", + "label": "false" + }, + { + "value": "true", + "label": "true" + } + ], + "default": "false", + "required": false + }, + { + "key": "debugMode", + "type": "choice", + "description": "Enable debug mode to log when the plugin is unable to extract values from the user agent", + "choices": [ + { + "value": "false", + "label": "false" + }, + { + "value": "true", + "label": "true" + } + ], + "default": "false", + "required": false + } + ] + } +] diff --git a/posthog/api/test/test_hog_function.py b/posthog/api/test/test_hog_function.py index 790bb2fa641c4..9fa5da11b0764 100644 --- a/posthog/api/test/test_hog_function.py +++ b/posthog/api/test/test_hog_function.py @@ -8,6 +8,8 @@ from rest_framework import status from common.hogvm.python.operation import HOGQL_BYTECODE_VERSION +from posthog.api.test.test_hog_function_templates import MOCK_NODE_TEMPLATES +from posthog.api.hog_function_template import HogFunctionTemplates from posthog.constants import AvailableFeature from posthog.models.action.action import Action from posthog.models.hog_functions.hog_function import DEFAULT_STATE, HogFunction @@ -71,6 +73,13 @@ def get_db_field_value(field, model_id): class TestHogFunctionAPIWithoutAvailableFeature(ClickhouseTestMixin, APIBaseTest, QueryMatchingTest): + def setUp(self): + super().setUp() + with patch("posthog.api.hog_function_template.get_hog_function_templates") as mock_get_templates: + mock_get_templates.return_value.status_code = 200 + mock_get_templates.return_value.json.return_value = MOCK_NODE_TEMPLATES + HogFunctionTemplates._load_templates() # Cache templates to simplify tests + def _create_slack_function(self, data: Optional[dict] = None): payload = { "name": "Slack", @@ -173,6 +182,11 @@ def setUp(self): ] self.organization.save() + with patch("posthog.api.hog_function_template.get_hog_function_templates") as mock_get_templates: + mock_get_templates.return_value.status_code = 200 + mock_get_templates.return_value.json.return_value = MOCK_NODE_TEMPLATES + HogFunctionTemplates._load_templates() # Cache templates to simplify tests + def _get_function_activity( self, function_id: Optional[int] = None, diff --git a/posthog/api/test/test_hog_function_templates.py b/posthog/api/test/test_hog_function_templates.py index c24e8fb9ba37b..d0883e265b069 100644 --- a/posthog/api/test/test_hog_function_templates.py +++ b/posthog/api/test/test_hog_function_templates.py @@ -1,11 +1,18 @@ -from unittest.mock import ANY +import json +import os +from unittest.mock import ANY, patch from inline_snapshot import snapshot from rest_framework import status +from posthog.api.hog_function_template import HogFunctionTemplates from posthog.cdp.templates.hog_function_template import derive_sub_templates from posthog.test.base import APIBaseTest, ClickhouseTestMixin, QueryMatchingTest from posthog.cdp.templates.slack.template_slack import template +MOCK_NODE_TEMPLATES = json.loads( + open(os.path.join(os.path.dirname(__file__), "__data__/hog_function_templates.json")).read() +) + # NOTE: We check this as a sanity check given that this is a public API so we want to explicitly define what is exposed EXPECTED_FIRST_RESULT = { "sub_templates": ANY, @@ -42,6 +49,14 @@ def test_derive_sub_templates(self): class TestHogFunctionTemplates(ClickhouseTestMixin, APIBaseTest, QueryMatchingTest): + def setUp(self): + super().setUp() + + with patch("posthog.api.hog_function_template.get_hog_function_templates") as mock_get_templates: + mock_get_templates.return_value.status_code = 200 + mock_get_templates.return_value.json.return_value = MOCK_NODE_TEMPLATES + HogFunctionTemplates._load_templates() # Cache templates to simplify tests + def test_list_function_templates(self): response = self.client.get("/api/projects/@current/hog_function_templates/") diff --git a/posthog/cdp/templates/__init__.py b/posthog/cdp/templates/__init__.py index e1b732eab312b..ca81f8915585e 100644 --- a/posthog/cdp/templates/__init__.py +++ b/posthog/cdp/templates/__init__.py @@ -52,7 +52,6 @@ from ._internal.template_blank import blank_site_destination, blank_site_app from .snapchat_ads.template_snapchat_ads import template as snapchat_ads from .snapchat_ads.template_pixel import template_snapchat_pixel as snapchat_pixel -from ._transformations.template_pass_through import template as pass_through_transformation HOG_FUNCTION_TEMPLATES = [ @@ -108,18 +107,10 @@ hogdesk, notification_bar, pineapple_mode, - pass_through_transformation, debug_posthog, ] -# This is a list of sub templates that are generated by merging the subtemplate with it's template -HOG_FUNCTION_SUB_TEMPLATES = derive_sub_templates(HOG_FUNCTION_TEMPLATES) - -HOG_FUNCTION_TEMPLATES_BY_ID = {template.id: template for template in HOG_FUNCTION_TEMPLATES} -HOG_FUNCTION_SUB_TEMPLATES_BY_ID = {template.id: template for template in HOG_FUNCTION_SUB_TEMPLATES} -ALL_HOG_FUNCTION_TEMPLATES_BY_ID = {**HOG_FUNCTION_TEMPLATES_BY_ID, **HOG_FUNCTION_SUB_TEMPLATES_BY_ID} - HOG_FUNCTION_MIGRATORS = { TemplateCustomerioMigrator.plugin_url: TemplateCustomerioMigrator, TemplateIntercomMigrator.plugin_url: TemplateIntercomMigrator, @@ -133,5 +124,3 @@ TemplateLoopsMigrator.plugin_url: TemplateLoopsMigrator, TemplateAvoMigrator.plugin_url: TemplateAvoMigrator, } - -__all__ = ["HOG_FUNCTION_TEMPLATES", "HOG_FUNCTION_TEMPLATES_BY_ID", "ALL_HOG_FUNCTION_TEMPLATES_BY_ID"] diff --git a/posthog/cdp/templates/_transformations/template_pass_through.py b/posthog/cdp/templates/_transformations/template_pass_through.py deleted file mode 100644 index 5a4e88e003d31..0000000000000 --- a/posthog/cdp/templates/_transformations/template_pass_through.py +++ /dev/null @@ -1,18 +0,0 @@ -from posthog.cdp.templates.hog_function_template import HogFunctionTemplate - -template: HogFunctionTemplate = HogFunctionTemplate( - status="alpha", - type="transformation", - id="template-blank-transformation", - name="Custom transformation", - description="This is a starter template for custom transformations", - icon_url="/static/hedgehog/builder-hog-01.png", - category=["Custom"], - hog=""" -// This is a blank template for custom transformations -// The function receives `event` as a global object and expects it to be returned -// If you return null then the event will be discarded -return event -""".strip(), - inputs_schema=[], -) diff --git a/posthog/cdp/templates/hog_function_template.py b/posthog/cdp/templates/hog_function_template.py index f76deacc3d4e4..132b36fa3abb0 100644 --- a/posthog/cdp/templates/hog_function_template.py +++ b/posthog/cdp/templates/hog_function_template.py @@ -1,5 +1,5 @@ import dataclasses -from typing import Literal, Optional, get_args, TYPE_CHECKING +from typing import Literal, Optional, TYPE_CHECKING if TYPE_CHECKING: @@ -10,7 +10,6 @@ SubTemplateId = Literal["early-access-feature-enrollment", "survey-response", "activity-log"] -SUB_TEMPLATE_ID: tuple[SubTemplateId, ...] = get_args(SubTemplateId) HogFunctionTemplateType = Literal[ "destination", @@ -83,6 +82,11 @@ def migrate(cls, obj: PluginConfig) -> dict: def derive_sub_templates(templates: list[HogFunctionTemplate]) -> list[HogFunctionTemplate]: + """ + Given a list of templates, derive the sub templates from them. + Sub templates just override certain params of the parent template. + This allows the API to filter for templates based on a SubTemplateId such as ones designed for surveys. + """ sub_templates = [] for template in templates: for sub_template in template.sub_templates or []: diff --git a/posthog/hogql_queries/web_analytics/test/__snapshots__/test_web_goals.ambr b/posthog/hogql_queries/web_analytics/test/__snapshots__/test_web_goals.ambr index baf646431c4e8..d2b78d71f03d1 100644 --- a/posthog/hogql_queries/web_analytics/test/__snapshots__/test_web_goals.ambr +++ b/posthog/hogql_queries/web_analytics/test/__snapshots__/test_web_goals.ambr @@ -1,8 +1,8 @@ # serializer version: 1 # name: TestWebGoalsQueryRunner.test_dont_show_deleted_actions ''' - SELECT uniqIf(person_id, and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC'))), 0))) AS current_total_people, - uniqIf(person_id, and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0))) AS previous_total_people, + SELECT uniqIf(person_id, and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC'))), 0))) AS current_total_people, + uniqIf(person_id, and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0))) AS previous_total_people, 'Contacted Sales' AS action_name_0, tuple(sum(action_current_count_0), sum(action_previous_count_0)) AS action_total_0, tuple(uniq(action_current_person_id_0), uniq(action_previous_person_id_0)) AS action_uniques_0, @@ -12,14 +12,14 @@ FROM (SELECT any(if(not(empty(events__override.distinct_id)), events__override.person_id, events.person_id)) AS person_id, min(events__session.`$start_timestamp`) AS start_timestamp, - countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))))) AS action_current_count_0, - countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_0, + countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))))) AS action_current_count_0, + countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_0, if(ifNull(greater(action_current_count_0, 0), 0), person_id, NULL) AS action_current_person_id_0, if(ifNull(greater(action_previous_count_0, 0), 0), person_id, NULL) AS action_previous_person_id_0, countIf(and(and(equals(events.event, '$pageview'), ifNull(match(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', ''), 'https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'), isNull(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', '')) - and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))))) AS action_current_count_1, + and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))))) AS action_current_count_1, countIf(and(and(equals(events.event, '$pageview'), ifNull(match(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', ''), 'https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'), isNull(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', '')) - and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_1, + and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_1, if(ifNull(greater(action_current_count_1, 0), 0), person_id, NULL) AS action_current_person_id_1, if(ifNull(greater(action_previous_count_1, 0), 0), person_id, NULL) AS action_previous_person_id_1 FROM events @@ -27,7 +27,7 @@ (SELECT min(toTimeZone(raw_sessions.min_timestamp, 'UTC')) AS `$start_timestamp`, raw_sessions.session_id_v7 AS session_id_v7 FROM raw_sessions - WHERE and(equals(raw_sessions.team_id, 99999), or(and(ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC'))), 0)), and(ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0)))) + WHERE and(equals(raw_sessions.team_id, 99999), or(and(ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC'))), 0)), and(ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0)))) GROUP BY raw_sessions.session_id_v7, raw_sessions.session_id_v7) AS events__session ON equals(toUInt128(accurateCastOrNull(events.`$session_id`, 'UUID')), events__session.session_id_v7) LEFT OUTER JOIN @@ -38,9 +38,9 @@ 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 events__override ON equals(events.distinct_id, events__override.distinct_id) WHERE and(equals(events.team_id, 99999), and(isNotNull(events.`$session_id`), or(equals(events.event, '$pageview'), equals(events.event, '$screen'), or(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(equals(events.event, '$pageview'), ifNull(match(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', ''), 'https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'), isNull(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', '')) - and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))))), or(and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))))), 1, 1)) + and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))))), or(and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))))), 1, 1)) GROUP BY events.`$session_id`) - WHERE or(and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC'))), 0)), and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0))) + WHERE or(and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC'))), 0)), and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0))) LIMIT 100 SETTINGS readonly=2, max_execution_time=60, allow_experimental_object_type=1, @@ -52,8 +52,8 @@ # --- # name: TestWebGoalsQueryRunner.test_many_users_and_actions ''' - SELECT uniqIf(person_id, and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC'))), 0))) AS current_total_people, - uniqIf(person_id, and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0))) AS previous_total_people, + SELECT uniqIf(person_id, and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC'))), 0))) AS current_total_people, + uniqIf(person_id, and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0))) AS previous_total_people, 'Contacted Sales' AS action_name_0, tuple(sum(action_current_count_0), sum(action_previous_count_0)) AS action_total_0, tuple(uniq(action_current_person_id_0), uniq(action_previous_person_id_0)) AS action_uniques_0, @@ -66,18 +66,18 @@ FROM (SELECT any(if(not(empty(events__override.distinct_id)), events__override.person_id, events.person_id)) AS person_id, min(events__session.`$start_timestamp`) AS start_timestamp, - countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))))) AS action_current_count_0, - countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_0, + countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))))) AS action_current_count_0, + countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_0, if(ifNull(greater(action_current_count_0, 0), 0), person_id, NULL) AS action_current_person_id_0, if(ifNull(greater(action_previous_count_0, 0), 0), person_id, NULL) AS action_previous_person_id_0, countIf(and(and(equals(events.event, '$pageview'), ifNull(match(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', ''), 'https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'), isNull(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', '')) - and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))))) AS action_current_count_1, + and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))))) AS action_current_count_1, countIf(and(and(equals(events.event, '$pageview'), ifNull(match(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', ''), 'https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'), isNull(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', '')) - and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_1, + and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_1, if(ifNull(greater(action_current_count_1, 0), 0), person_id, NULL) AS action_current_person_id_1, if(ifNull(greater(action_previous_count_1, 0), 0), person_id, NULL) AS action_previous_person_id_1, - countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))))) AS action_current_count_2, - countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_2, + countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))))) AS action_current_count_2, + countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_2, if(ifNull(greater(action_current_count_2, 0), 0), person_id, NULL) AS action_current_person_id_2, if(ifNull(greater(action_previous_count_2, 0), 0), person_id, NULL) AS action_previous_person_id_2 FROM events @@ -85,7 +85,7 @@ (SELECT min(toTimeZone(raw_sessions.min_timestamp, 'UTC')) AS `$start_timestamp`, raw_sessions.session_id_v7 AS session_id_v7 FROM raw_sessions - WHERE and(equals(raw_sessions.team_id, 99999), or(and(ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC'))), 0)), and(ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0)))) + WHERE and(equals(raw_sessions.team_id, 99999), or(and(ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC'))), 0)), and(ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0)))) GROUP BY raw_sessions.session_id_v7, raw_sessions.session_id_v7) AS events__session ON equals(toUInt128(accurateCastOrNull(events.`$session_id`, 'UUID')), events__session.session_id_v7) LEFT OUTER JOIN @@ -96,9 +96,9 @@ 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 events__override ON equals(events.distinct_id, events__override.distinct_id) WHERE and(equals(events.team_id, 99999), and(isNotNull(events.`$session_id`), or(equals(events.event, '$pageview'), equals(events.event, '$screen'), or(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(equals(events.event, '$pageview'), ifNull(match(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', ''), 'https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'), isNull(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', '')) - and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)))), or(and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))))), 1, 1)) + and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)))), or(and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))))), 1, 1)) GROUP BY events.`$session_id`) - WHERE or(and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC'))), 0)), and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0))) + WHERE or(and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC'))), 0)), and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0))) LIMIT 100 SETTINGS readonly=2, max_execution_time=60, allow_experimental_object_type=1, @@ -110,7 +110,7 @@ # --- # name: TestWebGoalsQueryRunner.test_no_comparison ''' - SELECT uniqIf(person_id, and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC'))), 0))) AS current_total_people, + SELECT uniqIf(person_id, and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC'))), 0))) AS current_total_people, uniqIf(person_id, 0) AS previous_total_people, 'Contacted Sales' AS action_name_0, tuple(sum(action_current_count_0), NULL) AS action_total_0, @@ -124,17 +124,17 @@ FROM (SELECT any(if(not(empty(events__override.distinct_id)), events__override.person_id, events.person_id)) AS person_id, min(events__session.`$start_timestamp`) AS start_timestamp, - countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))))) AS action_current_count_0, + countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))))) AS action_current_count_0, countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), 0)) AS action_previous_count_0, if(ifNull(greater(action_current_count_0, 0), 0), person_id, NULL) AS action_current_person_id_0, if(ifNull(greater(action_previous_count_0, 0), 0), person_id, NULL) AS action_previous_person_id_0, countIf(and(and(equals(events.event, '$pageview'), ifNull(match(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', ''), 'https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'), isNull(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', '')) - and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))))) AS action_current_count_1, + and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))))) AS action_current_count_1, countIf(and(and(equals(events.event, '$pageview'), ifNull(match(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', ''), 'https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'), isNull(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', '')) and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), 0)) AS action_previous_count_1, if(ifNull(greater(action_current_count_1, 0), 0), person_id, NULL) AS action_current_person_id_1, if(ifNull(greater(action_previous_count_1, 0), 0), person_id, NULL) AS action_previous_person_id_1, - countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))))) AS action_current_count_2, + countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))))) AS action_current_count_2, countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)), 0)) AS action_previous_count_2, if(ifNull(greater(action_current_count_2, 0), 0), person_id, NULL) AS action_current_person_id_2, if(ifNull(greater(action_previous_count_2, 0), 0), person_id, NULL) AS action_previous_person_id_2 @@ -143,7 +143,7 @@ (SELECT min(toTimeZone(raw_sessions.min_timestamp, 'UTC')) AS `$start_timestamp`, raw_sessions.session_id_v7 AS session_id_v7 FROM raw_sessions - WHERE and(equals(raw_sessions.team_id, 99999), ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC'))), 0)) + WHERE and(equals(raw_sessions.team_id, 99999), ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC'))), 0)) GROUP BY raw_sessions.session_id_v7, raw_sessions.session_id_v7) AS events__session ON equals(toUInt128(accurateCastOrNull(events.`$session_id`, 'UUID')), events__session.session_id_v7) LEFT OUTER JOIN @@ -154,9 +154,9 @@ 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 events__override ON equals(events.distinct_id, events__override.distinct_id) WHERE and(equals(events.team_id, 99999), and(isNotNull(events.`$session_id`), or(equals(events.event, '$pageview'), equals(events.event, '$screen'), or(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(equals(events.event, '$pageview'), ifNull(match(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', ''), 'https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'), isNull(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', '')) - and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)))), or(and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))), 0), 1, 1)) + and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)))), or(and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))), 0), 1, 1)) GROUP BY events.`$session_id`) - WHERE or(and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC'))), 0)), 0) + WHERE or(and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC'))), 0)), 0) LIMIT 100 SETTINGS readonly=2, max_execution_time=60, allow_experimental_object_type=1, @@ -168,8 +168,8 @@ # --- # name: TestWebGoalsQueryRunner.test_no_crash_when_no_data_and_some_actions ''' - SELECT uniqIf(person_id, and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC'))), 0))) AS current_total_people, - uniqIf(person_id, and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0))) AS previous_total_people, + SELECT uniqIf(person_id, and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC'))), 0))) AS current_total_people, + uniqIf(person_id, and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0))) AS previous_total_people, 'Contacted Sales' AS action_name_0, tuple(sum(action_current_count_0), sum(action_previous_count_0)) AS action_total_0, tuple(uniq(action_current_person_id_0), uniq(action_previous_person_id_0)) AS action_uniques_0, @@ -182,18 +182,18 @@ FROM (SELECT any(if(not(empty(events__override.distinct_id)), events__override.person_id, events.person_id)) AS person_id, min(events__session.`$start_timestamp`) AS start_timestamp, - countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))))) AS action_current_count_0, - countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_0, + countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))))) AS action_current_count_0, + countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_0, if(ifNull(greater(action_current_count_0, 0), 0), person_id, NULL) AS action_current_person_id_0, if(ifNull(greater(action_previous_count_0, 0), 0), person_id, NULL) AS action_previous_person_id_0, countIf(and(and(equals(events.event, '$pageview'), ifNull(match(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', ''), 'https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'), isNull(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', '')) - and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))))) AS action_current_count_1, + and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))))) AS action_current_count_1, countIf(and(and(equals(events.event, '$pageview'), ifNull(match(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', ''), 'https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'), isNull(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', '')) - and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_1, + and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_1, if(ifNull(greater(action_current_count_1, 0), 0), person_id, NULL) AS action_current_person_id_1, if(ifNull(greater(action_previous_count_1, 0), 0), person_id, NULL) AS action_previous_person_id_1, - countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))))) AS action_current_count_2, - countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_2, + countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))))) AS action_current_count_2, + countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_2, if(ifNull(greater(action_current_count_2, 0), 0), person_id, NULL) AS action_current_person_id_2, if(ifNull(greater(action_previous_count_2, 0), 0), person_id, NULL) AS action_previous_person_id_2 FROM events @@ -201,7 +201,7 @@ (SELECT min(toTimeZone(raw_sessions.min_timestamp, 'UTC')) AS `$start_timestamp`, raw_sessions.session_id_v7 AS session_id_v7 FROM raw_sessions - WHERE and(equals(raw_sessions.team_id, 99999), or(and(ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC'))), 0)), and(ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0)))) + WHERE and(equals(raw_sessions.team_id, 99999), or(and(ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC'))), 0)), and(ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0)))) GROUP BY raw_sessions.session_id_v7, raw_sessions.session_id_v7) AS events__session ON equals(toUInt128(accurateCastOrNull(events.`$session_id`, 'UUID')), events__session.session_id_v7) LEFT OUTER JOIN @@ -212,9 +212,9 @@ 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 events__override ON equals(events.distinct_id, events__override.distinct_id) WHERE and(equals(events.team_id, 99999), and(isNotNull(events.`$session_id`), or(equals(events.event, '$pageview'), equals(events.event, '$screen'), or(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(equals(events.event, '$pageview'), ifNull(match(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', ''), 'https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'), isNull(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', '')) - and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)))), or(and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))))), 1, 1)) + and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)))), or(and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))))), 1, 1)) GROUP BY events.`$session_id`) - WHERE or(and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC'))), 0)), and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0))) + WHERE or(and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC'))), 0)), and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0))) LIMIT 100 SETTINGS readonly=2, max_execution_time=60, allow_experimental_object_type=1, @@ -226,8 +226,8 @@ # --- # name: TestWebGoalsQueryRunner.test_one_user_one_action ''' - SELECT uniqIf(person_id, and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC'))), 0))) AS current_total_people, - uniqIf(person_id, and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0))) AS previous_total_people, + SELECT uniqIf(person_id, and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC'))), 0))) AS current_total_people, + uniqIf(person_id, and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0))) AS previous_total_people, 'Contacted Sales' AS action_name_0, tuple(sum(action_current_count_0), sum(action_previous_count_0)) AS action_total_0, tuple(uniq(action_current_person_id_0), uniq(action_previous_person_id_0)) AS action_uniques_0, @@ -240,18 +240,18 @@ FROM (SELECT any(if(not(empty(events__override.distinct_id)), events__override.person_id, events.person_id)) AS person_id, min(events__session.`$start_timestamp`) AS start_timestamp, - countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))))) AS action_current_count_0, - countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_0, + countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))))) AS action_current_count_0, + countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_0, if(ifNull(greater(action_current_count_0, 0), 0), person_id, NULL) AS action_current_person_id_0, if(ifNull(greater(action_previous_count_0, 0), 0), person_id, NULL) AS action_previous_person_id_0, countIf(and(and(equals(events.event, '$pageview'), ifNull(match(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', ''), 'https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'), isNull(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', '')) - and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))))) AS action_current_count_1, + and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))))) AS action_current_count_1, countIf(and(and(equals(events.event, '$pageview'), ifNull(match(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', ''), 'https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'), isNull(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', '')) - and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_1, + and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_1, if(ifNull(greater(action_current_count_1, 0), 0), person_id, NULL) AS action_current_person_id_1, if(ifNull(greater(action_previous_count_1, 0), 0), person_id, NULL) AS action_previous_person_id_1, - countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))))) AS action_current_count_2, - countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_2, + countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))))) AS action_current_count_2, + countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_2, if(ifNull(greater(action_current_count_2, 0), 0), person_id, NULL) AS action_current_person_id_2, if(ifNull(greater(action_previous_count_2, 0), 0), person_id, NULL) AS action_previous_person_id_2 FROM events @@ -259,7 +259,7 @@ (SELECT min(toTimeZone(raw_sessions.min_timestamp, 'UTC')) AS `$start_timestamp`, raw_sessions.session_id_v7 AS session_id_v7 FROM raw_sessions - WHERE and(equals(raw_sessions.team_id, 99999), or(and(ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC'))), 0)), and(ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0)))) + WHERE and(equals(raw_sessions.team_id, 99999), or(and(ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC'))), 0)), and(ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0)))) GROUP BY raw_sessions.session_id_v7, raw_sessions.session_id_v7) AS events__session ON equals(toUInt128(accurateCastOrNull(events.`$session_id`, 'UUID')), events__session.session_id_v7) LEFT OUTER JOIN @@ -270,9 +270,9 @@ 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 events__override ON equals(events.distinct_id, events__override.distinct_id) WHERE and(equals(events.team_id, 99999), and(isNotNull(events.`$session_id`), or(equals(events.event, '$pageview'), equals(events.event, '$screen'), or(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(equals(events.event, '$pageview'), ifNull(match(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', ''), 'https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'), isNull(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', '')) - and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)))), or(and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))))), 1, 1)) + and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)))), or(and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))))), 1, 1)) GROUP BY events.`$session_id`) - WHERE or(and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC'))), 0)), and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0))) + WHERE or(and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC'))), 0)), and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0))) LIMIT 100 SETTINGS readonly=2, max_execution_time=60, allow_experimental_object_type=1, @@ -284,8 +284,8 @@ # --- # name: TestWebGoalsQueryRunner.test_one_user_two_different_actions ''' - SELECT uniqIf(person_id, and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC'))), 0))) AS current_total_people, - uniqIf(person_id, and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0))) AS previous_total_people, + SELECT uniqIf(person_id, and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC'))), 0))) AS current_total_people, + uniqIf(person_id, and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0))) AS previous_total_people, 'Contacted Sales' AS action_name_0, tuple(sum(action_current_count_0), sum(action_previous_count_0)) AS action_total_0, tuple(uniq(action_current_person_id_0), uniq(action_previous_person_id_0)) AS action_uniques_0, @@ -298,18 +298,18 @@ FROM (SELECT any(if(not(empty(events__override.distinct_id)), events__override.person_id, events.person_id)) AS person_id, min(events__session.`$start_timestamp`) AS start_timestamp, - countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))))) AS action_current_count_0, - countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_0, + countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))))) AS action_current_count_0, + countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_0, if(ifNull(greater(action_current_count_0, 0), 0), person_id, NULL) AS action_current_person_id_0, if(ifNull(greater(action_previous_count_0, 0), 0), person_id, NULL) AS action_previous_person_id_0, countIf(and(and(equals(events.event, '$pageview'), ifNull(match(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', ''), 'https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'), isNull(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', '')) - and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))))) AS action_current_count_1, + and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))))) AS action_current_count_1, countIf(and(and(equals(events.event, '$pageview'), ifNull(match(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', ''), 'https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'), isNull(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', '')) - and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_1, + and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_1, if(ifNull(greater(action_current_count_1, 0), 0), person_id, NULL) AS action_current_person_id_1, if(ifNull(greater(action_previous_count_1, 0), 0), person_id, NULL) AS action_previous_person_id_1, - countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))))) AS action_current_count_2, - countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_2, + countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))))) AS action_current_count_2, + countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_2, if(ifNull(greater(action_current_count_2, 0), 0), person_id, NULL) AS action_current_person_id_2, if(ifNull(greater(action_previous_count_2, 0), 0), person_id, NULL) AS action_previous_person_id_2 FROM events @@ -317,7 +317,7 @@ (SELECT min(toTimeZone(raw_sessions.min_timestamp, 'UTC')) AS `$start_timestamp`, raw_sessions.session_id_v7 AS session_id_v7 FROM raw_sessions - WHERE and(equals(raw_sessions.team_id, 99999), or(and(ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC'))), 0)), and(ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0)))) + WHERE and(equals(raw_sessions.team_id, 99999), or(and(ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC'))), 0)), and(ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0)))) GROUP BY raw_sessions.session_id_v7, raw_sessions.session_id_v7) AS events__session ON equals(toUInt128(accurateCastOrNull(events.`$session_id`, 'UUID')), events__session.session_id_v7) LEFT OUTER JOIN @@ -328,9 +328,9 @@ 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 events__override ON equals(events.distinct_id, events__override.distinct_id) WHERE and(equals(events.team_id, 99999), and(isNotNull(events.`$session_id`), or(equals(events.event, '$pageview'), equals(events.event, '$screen'), or(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(equals(events.event, '$pageview'), ifNull(match(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', ''), 'https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'), isNull(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', '')) - and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)))), or(and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))))), 1, 1)) + and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)))), or(and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))))), 1, 1)) GROUP BY events.`$session_id`) - WHERE or(and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC'))), 0)), and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0))) + WHERE or(and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC'))), 0)), and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0))) LIMIT 100 SETTINGS readonly=2, max_execution_time=60, allow_experimental_object_type=1, @@ -342,8 +342,8 @@ # --- # name: TestWebGoalsQueryRunner.test_one_user_two_similar_actions_across_sessions ''' - SELECT uniqIf(person_id, and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC'))), 0))) AS current_total_people, - uniqIf(person_id, and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0))) AS previous_total_people, + SELECT uniqIf(person_id, and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC'))), 0))) AS current_total_people, + uniqIf(person_id, and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0))) AS previous_total_people, 'Contacted Sales' AS action_name_0, tuple(sum(action_current_count_0), sum(action_previous_count_0)) AS action_total_0, tuple(uniq(action_current_person_id_0), uniq(action_previous_person_id_0)) AS action_uniques_0, @@ -356,18 +356,18 @@ FROM (SELECT any(if(not(empty(events__override.distinct_id)), events__override.person_id, events.person_id)) AS person_id, min(events__session.`$start_timestamp`) AS start_timestamp, - countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))))) AS action_current_count_0, - countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_0, + countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))))) AS action_current_count_0, + countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_0, if(ifNull(greater(action_current_count_0, 0), 0), person_id, NULL) AS action_current_person_id_0, if(ifNull(greater(action_previous_count_0, 0), 0), person_id, NULL) AS action_previous_person_id_0, countIf(and(and(equals(events.event, '$pageview'), ifNull(match(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', ''), 'https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'), isNull(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', '')) - and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))))) AS action_current_count_1, + and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))))) AS action_current_count_1, countIf(and(and(equals(events.event, '$pageview'), ifNull(match(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', ''), 'https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'), isNull(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', '')) - and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_1, + and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_1, if(ifNull(greater(action_current_count_1, 0), 0), person_id, NULL) AS action_current_person_id_1, if(ifNull(greater(action_previous_count_1, 0), 0), person_id, NULL) AS action_previous_person_id_1, - countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))))) AS action_current_count_2, - countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_2, + countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))))) AS action_current_count_2, + countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_2, if(ifNull(greater(action_current_count_2, 0), 0), person_id, NULL) AS action_current_person_id_2, if(ifNull(greater(action_previous_count_2, 0), 0), person_id, NULL) AS action_previous_person_id_2 FROM events @@ -375,7 +375,7 @@ (SELECT min(toTimeZone(raw_sessions.min_timestamp, 'UTC')) AS `$start_timestamp`, raw_sessions.session_id_v7 AS session_id_v7 FROM raw_sessions - WHERE and(equals(raw_sessions.team_id, 99999), or(and(ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC'))), 0)), and(ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0)))) + WHERE and(equals(raw_sessions.team_id, 99999), or(and(ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC'))), 0)), and(ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0)))) GROUP BY raw_sessions.session_id_v7, raw_sessions.session_id_v7) AS events__session ON equals(toUInt128(accurateCastOrNull(events.`$session_id`, 'UUID')), events__session.session_id_v7) LEFT OUTER JOIN @@ -386,9 +386,9 @@ 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 events__override ON equals(events.distinct_id, events__override.distinct_id) WHERE and(equals(events.team_id, 99999), and(isNotNull(events.`$session_id`), or(equals(events.event, '$pageview'), equals(events.event, '$screen'), or(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(equals(events.event, '$pageview'), ifNull(match(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', ''), 'https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'), isNull(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', '')) - and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)))), or(and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))))), 1, 1)) + and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)))), or(and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))))), 1, 1)) GROUP BY events.`$session_id`) - WHERE or(and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC'))), 0)), and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0))) + WHERE or(and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC'))), 0)), and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0))) LIMIT 100 SETTINGS readonly=2, max_execution_time=60, allow_experimental_object_type=1, @@ -400,8 +400,8 @@ # --- # name: TestWebGoalsQueryRunner.test_one_users_one_action_each ''' - SELECT uniqIf(person_id, and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC'))), 0))) AS current_total_people, - uniqIf(person_id, and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0))) AS previous_total_people, + SELECT uniqIf(person_id, and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC'))), 0))) AS current_total_people, + uniqIf(person_id, and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0))) AS previous_total_people, 'Contacted Sales' AS action_name_0, tuple(sum(action_current_count_0), sum(action_previous_count_0)) AS action_total_0, tuple(uniq(action_current_person_id_0), uniq(action_previous_person_id_0)) AS action_uniques_0, @@ -414,18 +414,18 @@ FROM (SELECT any(if(not(empty(events__override.distinct_id)), events__override.person_id, events.person_id)) AS person_id, min(events__session.`$start_timestamp`) AS start_timestamp, - countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))))) AS action_current_count_0, - countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_0, + countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))))) AS action_current_count_0, + countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_0, if(ifNull(greater(action_current_count_0, 0), 0), person_id, NULL) AS action_current_person_id_0, if(ifNull(greater(action_previous_count_0, 0), 0), person_id, NULL) AS action_previous_person_id_0, countIf(and(and(equals(events.event, '$pageview'), ifNull(match(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', ''), 'https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'), isNull(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', '')) - and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))))) AS action_current_count_1, + and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))))) AS action_current_count_1, countIf(and(and(equals(events.event, '$pageview'), ifNull(match(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', ''), 'https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'), isNull(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', '')) - and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_1, + and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_1, if(ifNull(greater(action_current_count_1, 0), 0), person_id, NULL) AS action_current_person_id_1, if(ifNull(greater(action_previous_count_1, 0), 0), person_id, NULL) AS action_previous_person_id_1, - countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))))) AS action_current_count_2, - countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_2, + countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))))) AS action_current_count_2, + countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_2, if(ifNull(greater(action_current_count_2, 0), 0), person_id, NULL) AS action_current_person_id_2, if(ifNull(greater(action_previous_count_2, 0), 0), person_id, NULL) AS action_previous_person_id_2 FROM events @@ -433,7 +433,7 @@ (SELECT min(toTimeZone(raw_sessions.min_timestamp, 'UTC')) AS `$start_timestamp`, raw_sessions.session_id_v7 AS session_id_v7 FROM raw_sessions - WHERE and(equals(raw_sessions.team_id, 99999), or(and(ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC'))), 0)), and(ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0)))) + WHERE and(equals(raw_sessions.team_id, 99999), or(and(ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC'))), 0)), and(ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0)))) GROUP BY raw_sessions.session_id_v7, raw_sessions.session_id_v7) AS events__session ON equals(toUInt128(accurateCastOrNull(events.`$session_id`, 'UUID')), events__session.session_id_v7) LEFT OUTER JOIN @@ -444,9 +444,9 @@ 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 events__override ON equals(events.distinct_id, events__override.distinct_id) WHERE and(equals(events.team_id, 99999), and(isNotNull(events.`$session_id`), or(equals(events.event, '$pageview'), equals(events.event, '$screen'), or(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(equals(events.event, '$pageview'), ifNull(match(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', ''), 'https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'), isNull(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', '')) - and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)))), or(and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))))), 1, 1)) + and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)))), or(and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))))), 1, 1)) GROUP BY events.`$session_id`) - WHERE or(and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC'))), 0)), and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0))) + WHERE or(and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC'))), 0)), and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0))) LIMIT 100 SETTINGS readonly=2, max_execution_time=60, allow_experimental_object_type=1, diff --git a/posthog/hogql_queries/web_analytics/test/__snapshots__/test_web_stats_table.ambr b/posthog/hogql_queries/web_analytics/test/__snapshots__/test_web_stats_table.ambr index 8cf0e55b54bdf..197dd4761b6e6 100644 --- a/posthog/hogql_queries/web_analytics/test/__snapshots__/test_web_stats_table.ambr +++ b/posthog/hogql_queries/web_analytics/test/__snapshots__/test_web_stats_table.ambr @@ -497,7 +497,7 @@ min(toTimeZone(raw_sessions.min_timestamp, 'UTC')) AS `$start_timestamp`, raw_sessions.session_id_v7 AS session_id_v7 FROM raw_sessions - WHERE and(equals(raw_sessions.team_id, 99999), ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-07-30 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC'))), 0)) + WHERE and(equals(raw_sessions.team_id, 99999), ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-07-30 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC'))), 0)) GROUP BY raw_sessions.session_id_v7, raw_sessions.session_id_v7) AS events__session ON equals(toUInt128(accurateCastOrNull(events.`$session_id`, 'UUID')), events__session.session_id_v7) LEFT OUTER JOIN @@ -507,7 +507,7 @@ WHERE equals(person_distinct_id_overrides.team_id, 99999) 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 events__override ON equals(events.distinct_id, events__override.distinct_id) - WHERE and(equals(events.team_id, 99999), and(or(and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-07-30 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))), 0), or(equals(events.event, '$pageview'), equals(events.event, '$screen')), ifNull(in(if(not(empty(events__override.distinct_id)), events__override.person_id, events.person_id), + WHERE and(equals(events.team_id, 99999), and(or(and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-07-30 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))), 0), or(equals(events.event, '$pageview'), equals(events.event, '$screen')), ifNull(in(if(not(empty(events__override.distinct_id)), events__override.person_id, events.person_id), (SELECT cohortpeople.person_id AS person_id FROM cohortpeople WHERE and(equals(cohortpeople.team_id, 99999), equals(cohortpeople.cohort_id, 99999), equals(cohortpeople.version, 0)))), 0), isNotNull(breakdown_value))) @@ -1457,7 +1457,7 @@ min(toTimeZone(raw_sessions.min_timestamp, 'UTC')) AS `$start_timestamp`, raw_sessions.session_id_v7 AS session_id_v7 FROM raw_sessions - WHERE and(equals(raw_sessions.team_id, 99999), ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-07-30 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC'))), 0)) + WHERE and(equals(raw_sessions.team_id, 99999), ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-07-30 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC'))), 0)) GROUP BY raw_sessions.session_id_v7, raw_sessions.session_id_v7) AS events__session ON equals(toUInt128(accurateCastOrNull(events.`$session_id`, 'UUID')), events__session.session_id_v7) LEFT OUTER JOIN @@ -1477,7 +1477,7 @@ WHERE equals(person.team_id, 99999) GROUP BY person.id HAVING and(ifNull(equals(argMax(person.is_deleted, person.version), 0), 0), ifNull(less(argMax(toTimeZone(person.created_at, 'UTC'), person.version), plus(now64(6, 'UTC'), toIntervalDay(1))), 0)))), 0)) SETTINGS optimize_aggregation_in_order=1) AS events__person ON equals(if(not(empty(events__override.distinct_id)), events__override.person_id, events.person_id), events__person.id) - WHERE and(equals(events.team_id, 99999), and(or(and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-07-30 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))), 0), or(equals(events.event, '$pageview'), equals(events.event, '$screen')), ifNull(notILike(events__person.properties___email, '%@posthog.com%'), 1), isNotNull(breakdown_value))) + WHERE and(equals(events.team_id, 99999), and(or(and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-07-30 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))), 0), or(equals(events.event, '$pageview'), equals(events.event, '$screen')), ifNull(notILike(events__person.properties___email, '%@posthog.com%'), 1), isNotNull(breakdown_value))) GROUP BY session_id, breakdown_value) GROUP BY `context.columns.breakdown_value` @@ -2799,7 +2799,7 @@ min(toTimeZone(raw_sessions.min_timestamp, 'UTC')) AS `$start_timestamp`, raw_sessions.session_id_v7 AS session_id_v7 FROM raw_sessions - WHERE and(equals(raw_sessions.team_id, 99999), ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2019-02-17 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC'))), 0)) + WHERE and(equals(raw_sessions.team_id, 99999), ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2019-02-17 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC'))), 0)) GROUP BY raw_sessions.session_id_v7, raw_sessions.session_id_v7) AS events__session ON equals(toUInt128(accurateCastOrNull(events.`$session_id`, 'UUID')), events__session.session_id_v7) LEFT OUTER JOIN @@ -2809,7 +2809,7 @@ WHERE equals(person_distinct_id_overrides.team_id, 99999) 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 events__override ON equals(events.distinct_id, events__override.distinct_id) - WHERE and(equals(events.team_id, 99999), and(or(and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2019-02-17 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))), 0), or(equals(events.event, '$pageview'), equals(events.event, '$screen')), 1, isNotNull(breakdown_value))) + WHERE and(equals(events.team_id, 99999), and(or(and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2019-02-17 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))), 0), or(equals(events.event, '$pageview'), equals(events.event, '$screen')), 1, isNotNull(breakdown_value))) GROUP BY session_id, breakdown_value) GROUP BY `context.columns.breakdown_value` @@ -2845,7 +2845,7 @@ min(toTimeZone(raw_sessions.min_timestamp, 'UTC')) AS `$start_timestamp`, raw_sessions.session_id_v7 AS session_id_v7 FROM raw_sessions - WHERE and(equals(raw_sessions.team_id, 99999), ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-07-15 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC'))), 0)) + WHERE and(equals(raw_sessions.team_id, 99999), ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-07-15 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC'))), 0)) GROUP BY raw_sessions.session_id_v7, raw_sessions.session_id_v7) AS events__session ON equals(toUInt128(accurateCastOrNull(events.`$session_id`, 'UUID')), events__session.session_id_v7) LEFT OUTER JOIN @@ -2855,7 +2855,7 @@ WHERE equals(person_distinct_id_overrides.team_id, 99999) 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 events__override ON equals(events.distinct_id, events__override.distinct_id) - WHERE and(equals(events.team_id, 99999), and(or(and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-07-15 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))), 0), or(equals(events.event, '$pageview'), equals(events.event, '$screen')), 1, isNotNull(breakdown_value))) + WHERE and(equals(events.team_id, 99999), and(or(and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-07-15 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))), 0), or(equals(events.event, '$pageview'), equals(events.event, '$screen')), 1, isNotNull(breakdown_value))) GROUP BY session_id, breakdown_value) GROUP BY `context.columns.breakdown_value` @@ -2902,7 +2902,7 @@ min(toTimeZone(raw_sessions.min_timestamp, 'UTC')) AS `$start_timestamp`, raw_sessions.session_id_v7 AS session_id_v7 FROM raw_sessions - WHERE and(equals(raw_sessions.team_id, 99999), ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2019-02-17 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC'))), 0)) + WHERE and(equals(raw_sessions.team_id, 99999), ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2019-02-17 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC'))), 0)) GROUP BY raw_sessions.session_id_v7, raw_sessions.session_id_v7) AS events__session ON equals(toUInt128(accurateCastOrNull(events.`$session_id`, 'UUID')), events__session.session_id_v7) LEFT OUTER JOIN @@ -2912,7 +2912,7 @@ WHERE equals(person_distinct_id_overrides.team_id, 99999) 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 events__override ON equals(events.distinct_id, events__override.distinct_id) - WHERE and(equals(events.team_id, 99999), and(or(and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2019-02-17 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))), 0), or(equals(events.event, '$pageview'), equals(events.event, '$screen')), 1, isNotNull(breakdown_value))) + WHERE and(equals(events.team_id, 99999), and(or(and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2019-02-17 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))), 0), or(equals(events.event, '$pageview'), equals(events.event, '$screen')), 1, isNotNull(breakdown_value))) GROUP BY session_id, breakdown_value) GROUP BY `context.columns.breakdown_value` @@ -2959,7 +2959,7 @@ min(toTimeZone(raw_sessions.min_timestamp, 'UTC')) AS `$start_timestamp`, raw_sessions.session_id_v7 AS session_id_v7 FROM raw_sessions - WHERE and(equals(raw_sessions.team_id, 99999), ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-07-30 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC'))), 0)) + WHERE and(equals(raw_sessions.team_id, 99999), ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-07-30 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC'))), 0)) GROUP BY raw_sessions.session_id_v7, raw_sessions.session_id_v7) AS events__session ON equals(toUInt128(accurateCastOrNull(events.`$session_id`, 'UUID')), events__session.session_id_v7) LEFT OUTER JOIN @@ -2969,7 +2969,7 @@ WHERE equals(person_distinct_id_overrides.team_id, 99999) 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 events__override ON equals(events.distinct_id, events__override.distinct_id) - WHERE and(equals(events.team_id, 99999), and(or(and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-07-30 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))), 0), or(equals(events.event, '$pageview'), equals(events.event, '$screen')), 1, isNotNull(breakdown_value))) + WHERE and(equals(events.team_id, 99999), and(or(and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-07-30 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))), 0), or(equals(events.event, '$pageview'), equals(events.event, '$screen')), 1, isNotNull(breakdown_value))) GROUP BY session_id, breakdown_value) GROUP BY `context.columns.breakdown_value` diff --git a/posthog/models/hog_functions/hog_function.py b/posthog/models/hog_functions/hog_function.py index 18fcf7582b870..a4d0d515fedf4 100644 --- a/posthog/models/hog_functions/hog_function.py +++ b/posthog/models/hog_functions/hog_function.py @@ -96,12 +96,20 @@ class Meta: @property def template(self) -> Optional[HogFunctionTemplate]: - from posthog.cdp.templates import ALL_HOG_FUNCTION_TEMPLATES_BY_ID + from posthog.api.hog_function_template import HogFunctionTemplates - if self.template_id and self.template_id.startswith("plugin-"): + if not self.template_id: + return None + + template = HogFunctionTemplates.template(self.template_id) + + if template: + return template + + if self.template_id.startswith("plugin-"): return create_legacy_plugin_template(self.template_id) - return ALL_HOG_FUNCTION_TEMPLATES_BY_ID.get(self.template_id, None) + return None @property def filter_action_ids(self) -> list[int]: diff --git a/posthog/plugins/plugin_server_api.py b/posthog/plugins/plugin_server_api.py index ef6b312ba874c..40c322bcf70a3 100644 --- a/posthog/plugins/plugin_server_api.py +++ b/posthog/plugins/plugin_server_api.py @@ -90,3 +90,7 @@ def patch_hog_function_status(team_id: int, hog_function_id: UUIDT, state: int) CDP_FUNCTION_EXECUTOR_API_URL + f"/api/projects/{team_id}/hog_functions/{hog_function_id}/status", json={"state": state}, ) + + +def get_hog_function_templates() -> requests.Response: + return requests.get(CDP_FUNCTION_EXECUTOR_API_URL + f"/api/hog_function_templates") From fb45a648a5e149b3238b69e680c48e68dcbd3812 Mon Sep 17 00:00:00 2001 From: Ben White Date: Thu, 30 Jan 2025 12:40:07 +0100 Subject: [PATCH 122/163] Fix comments --- frontend/src/lib/monaco/CodeEditor.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/lib/monaco/CodeEditor.tsx b/frontend/src/lib/monaco/CodeEditor.tsx index 5e7ed67d9127e..f2c0d1f36faf1 100644 --- a/frontend/src/lib/monaco/CodeEditor.tsx +++ b/frontend/src/lib/monaco/CodeEditor.tsx @@ -206,7 +206,6 @@ export function CodeEditor({ }, []) const editorOptions: editor.IStandaloneEditorConstructionOptions = { - // :TRICKY: We need to declare all options here, as omitting something will carry its value from one to another. minimap: { enabled: false, }, @@ -269,6 +268,7 @@ export function CodeEditor({ } if (originalValue) { + // If originalValue is provided, we render a diff editor instead return ( Date: Thu, 30 Jan 2025 12:42:08 +0100 Subject: [PATCH 123/163] Fixes --- .../pipeline/hogfunctions/hogFunctionTestLogic.tsx | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/frontend/src/scenes/pipeline/hogfunctions/hogFunctionTestLogic.tsx b/frontend/src/scenes/pipeline/hogfunctions/hogFunctionTestLogic.tsx index 4cab695c1eb69..2adbb54b57ca8 100644 --- a/frontend/src/scenes/pipeline/hogfunctions/hogFunctionTestLogic.tsx +++ b/frontend/src/scenes/pipeline/hogfunctions/hogFunctionTestLogic.tsx @@ -210,14 +210,9 @@ export const hogFunctionTestLogic = kea([ const input = JSON.parse(testInvocation.globals) - // To provide a nice diffing experience, we need to sort the json result so that the keys are in the same order - // as the input. - const sortedResult = JSON.parse(JSON.stringify(testResult.result)) - const sortedInput = JSON.parse(JSON.stringify(input)) - return { - input: JSON.stringify(sortedInput, null, 2), - output: JSON.stringify(sortedResult, null, 2), + input: JSON.stringify(input, null, 2), + output: JSON.stringify(testResult.result, null, 2), } }, ], From a437013a281d268619ec6ce22cf6064a0a4ffdc0 Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 30 Jan 2025 11:45:10 +0000 Subject: [PATCH 124/163] Update UI snapshots for `chromium` (1) --- ...uccess--second-recording-in-list--dark.png | Bin 118722 -> 119473 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/frontend/__snapshots__/replay-player-success--second-recording-in-list--dark.png b/frontend/__snapshots__/replay-player-success--second-recording-in-list--dark.png index 397bb37bd08f74b083c4f147a14e88d2107cef81..28a50e2da4c9d1e69eed239c0c31fb196eda9a80 100644 GIT binary patch delta 27742 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 c{7LS Date: Thu, 30 Jan 2025 12:57:29 +0100 Subject: [PATCH 125/163] Fixes --- .../pipeline/hogfunctions/HogFunctionTest.tsx | 87 +++++++++++-------- .../hogfunctions/hogFunctionTestLogic.tsx | 9 +- 2 files changed, 55 insertions(+), 41 deletions(-) diff --git a/frontend/src/scenes/pipeline/hogfunctions/HogFunctionTest.tsx b/frontend/src/scenes/pipeline/hogfunctions/HogFunctionTest.tsx index fa5cf5274dd14..a06f74a82410b 100644 --- a/frontend/src/scenes/pipeline/hogfunctions/HogFunctionTest.tsx +++ b/frontend/src/scenes/pipeline/hogfunctions/HogFunctionTest.tsx @@ -233,47 +233,58 @@ export function HogFunctionTest(): JSX.Element {

    Below you can see the event after the transformation has been applied.

    {testResult.result ? ( - + <> + {!sortedTestsResult?.hasDiff && ( + + The event was unmodified by the transformation. + + )} + + ) : ( The event was dropped by the transformation. If this is expected then diff --git a/frontend/src/scenes/pipeline/hogfunctions/hogFunctionTestLogic.tsx b/frontend/src/scenes/pipeline/hogfunctions/hogFunctionTestLogic.tsx index 2adbb54b57ca8..96dcb27c23925 100644 --- a/frontend/src/scenes/pipeline/hogfunctions/hogFunctionTestLogic.tsx +++ b/frontend/src/scenes/pipeline/hogfunctions/hogFunctionTestLogic.tsx @@ -203,16 +203,19 @@ export const hogFunctionTestLogic = kea([ ): { input: string output: string + hasDiff: boolean } | null => { if (!testResult || configuration.type !== 'transformation') { return null } - const input = JSON.parse(testInvocation.globals) + const input = JSON.stringify(JSON.parse(testInvocation.globals), null, 2) + const output = JSON.stringify(testResult.result, null, 2) return { - input: JSON.stringify(input, null, 2), - output: JSON.stringify(testResult.result, null, 2), + input, + output, + hasDiff: input !== output, } }, ], From 5ecfec925428e077b8d551f97e9961c9d97eda2d Mon Sep 17 00:00:00 2001 From: Ben White Date: Thu, 30 Jan 2025 13:16:02 +0100 Subject: [PATCH 126/163] Fix --- .../hogfunctions/hogFunctionTestLogic.tsx | 2 +- plugin-server/src/cdp/cdp-api.test.ts | 67 ------------------- plugin-server/src/cdp/cdp-api.ts | 1 + .../_transformations/geoip/geoip.template.ts | 2 +- .../test/__data__/hog_function_templates.json | 2 +- 5 files changed, 4 insertions(+), 70 deletions(-) diff --git a/frontend/src/scenes/pipeline/hogfunctions/hogFunctionTestLogic.tsx b/frontend/src/scenes/pipeline/hogfunctions/hogFunctionTestLogic.tsx index 96dcb27c23925..5e76f43f35598 100644 --- a/frontend/src/scenes/pipeline/hogfunctions/hogFunctionTestLogic.tsx +++ b/frontend/src/scenes/pipeline/hogfunctions/hogFunctionTestLogic.tsx @@ -187,7 +187,7 @@ export const hogFunctionTestLogic = kea([ actions.setTestResult(res) } catch (e) { - lemonToast.error(`An unexpected server error occurred while trying to testing the function. ${e}`) + lemonToast.error(`An unexpected server error occurred while testing the function. ${e}`) } }, }, diff --git a/plugin-server/src/cdp/cdp-api.test.ts b/plugin-server/src/cdp/cdp-api.test.ts index 7c10879e6ea62..ae7d1d8a4500b 100644 --- a/plugin-server/src/cdp/cdp-api.test.ts +++ b/plugin-server/src/cdp/cdp-api.test.ts @@ -51,8 +51,6 @@ jest.mock('../../src/utils/fetch', () => { const mockFetch: jest.Mock = require('../../src/utils/fetch').trackedFetch -jest.setTimeout(1000) - describe('CDP API', () => { let hub: Hub let team: Team @@ -447,71 +445,6 @@ describe('CDP API', () => { }) }) - it('handles mappings', async () => { - const hogFunction = await insertHogFunction({ - ...HOG_EXAMPLES.simple_fetch, - ...HOG_INPUTS_EXAMPLES.simple_fetch, - ...HOG_FILTERS_EXAMPLES.no_filters, - mappings: [ - { - // Filters for pageview or autocapture - ...HOG_FILTERS_EXAMPLES.pageview_or_autocapture_filter, - }, - { - // No filters so should match all events - ...HOG_FILTERS_EXAMPLES.no_filters, - }, - { - // Broken filters so shouldn't match - ...HOG_FILTERS_EXAMPLES.broken_filters, - }, - ], - }) - - const res = await supertest(app) - .post(`/api/projects/${hogFunction.team_id}/hog_functions/${hogFunction.id}/invocations`) - .send({ globals, mock_async_functions: true }) - - expect(res.status).toEqual(200) - - const minimalLogs = res.body.logs.map((log) => ({ - level: log.level, - message: log.message, - })) - - expect(minimalLogs).toMatchObject([ - { level: 'info', message: 'Mapping trigger not matching filters was ignored.' }, - { - level: 'error', - message: - 'Error filtering event b3a1fe86-b10c-43cc-acaf-d208977608d0: Invalid HogQL bytecode, stack is empty, can not pop', - }, - { level: 'debug', message: 'Executing function' }, - { - level: 'debug', - message: - "Suspending function due to async function call 'fetch'. Payload: 2110 bytes. Event: b3a1fe86-b10c-43cc-acaf-d208977608d0", - }, - { - level: 'info', - message: "Async function 'fetch' was mocked with arguments:", - }, - { - level: 'info', - message: expect.stringContaining('fetch({'), - }, - { level: 'debug', message: 'Resuming function' }, - { - level: 'info', - message: 'Fetch response:, {"status":200,"body":{}}', - }, - { - level: 'debug', - message: expect.stringContaining('Function completed in '), - }, - ]) - }) - describe('transformations', () => { let configuration: HogFunctionType diff --git a/plugin-server/src/cdp/cdp-api.ts b/plugin-server/src/cdp/cdp-api.ts index b490beca8bdde..fdacd5a4556c4 100644 --- a/plugin-server/src/cdp/cdp-api.ts +++ b/plugin-server/src/cdp/cdp-api.ts @@ -45,6 +45,7 @@ export class CdpApi { } isHealthy() { + // NOTE: There isn't really anything to check for here so we are just always healthy return true } diff --git a/plugin-server/src/cdp/templates/_transformations/geoip/geoip.template.ts b/plugin-server/src/cdp/templates/_transformations/geoip/geoip.template.ts index 8202e9c566e5f..92fc3f25bab56 100644 --- a/plugin-server/src/cdp/templates/_transformations/geoip/geoip.template.ts +++ b/plugin-server/src/cdp/templates/_transformations/geoip/geoip.template.ts @@ -27,7 +27,7 @@ let geoipProperties := { 'accuracy_radius': null, 'time_zone': null } -// Check if the event has an IP address l +// Check if the event has an IP address if (event.properties?.$geoip_disable or empty(event.properties?.$ip)) { print('geoip disabled or no ip.') return event diff --git a/posthog/api/test/__data__/hog_function_templates.json b/posthog/api/test/__data__/hog_function_templates.json index ad0dc299dddef..34dfb989d8d98 100644 --- a/posthog/api/test/__data__/hog_function_templates.json +++ b/posthog/api/test/__data__/hog_function_templates.json @@ -144,7 +144,7 @@ "description": "Adds geoip data to the event", "icon_url": "/static/hedgehog/builder-hog-01.png", "category": ["Custom"], - "hog": "\n// Define the properties to be added to the event\nlet geoipProperties := {\n 'city_name': null,\n 'city_confidence': null,\n 'subdivision_2_name': null,\n 'subdivision_2_code': null,\n 'subdivision_1_name': null,\n 'subdivision_1_code': null,\n 'country_name': null,\n 'country_code': null,\n 'continent_name': null,\n 'continent_code': null,\n 'postal_code': null,\n 'latitude': null,\n 'longitude': null,\n 'accuracy_radius': null,\n 'time_zone': null\n}\n// Check if the event has an IP address l\nif (event.properties?.$geoip_disable or empty(event.properties?.$ip)) {\n print('geoip disabled or no ip', event.properties, event.properties?.$ip)\n return event\n}\nlet ip := event.properties.$ip\nif (ip == '127.0.0.1') {\n print('spoofing ip for local development', ip)\n ip := '89.160.20.129'\n}\nlet response := geoipLookup(ip)\nif (not response) {\n print('geoip lookup failed for ip', ip)\n return event\n}\nlet location := {}\nif (response.city) {\n location['city_name'] := response.city.names?.en\n}\nif (response.country) {\n location['country_name'] := response.country.names?.en\n location['country_code'] := response.country.isoCode\n}\nif (response.continent) {\n location['continent_name'] := response.continent.names?.en\n location['continent_code'] := response.continent.code\n}\nif (response.postal) {\n location['postal_code'] := response.postal.code\n}\nif (response.location) {\n location['latitude'] := response.location?.latitude\n location['longitude'] := response.location?.longitude\n location['accuracy_radius'] := response.location?.accuracyRadius\n location['time_zone'] := response.location?.timeZone\n}\nif (response.subdivisions) {\n for (let index, subdivision in response.subdivisions) {\n location[f'subdivision_{index + 1}_code'] := subdivision.isoCode\n location[f'subdivision_{index + 1}_name'] := subdivision.names?.en\n }\n}\nprint('geoip location data for ip:', location) \nlet returnEvent := event\nreturnEvent.properties := returnEvent.properties ?? {}\nreturnEvent.properties.$set := returnEvent.properties.$set ?? {}\nreturnEvent.properties.$set_once := returnEvent.properties.$set_once ?? {}\nfor (let key, value in geoipProperties) {\n if (value != null) {\n returnEvent.properties.$set[f'$geoip_{key}'] := value\n returnEvent.properties.$set_once[f'$initial_geoip_{key}'] := value\n }\n returnEvent.properties.$set[f'$geoip_{key}'] := value\n returnEvent.properties.$set_once[f'$initial_geoip_{key}'] := value\n}\nfor (let key, value in location) {\n returnEvent.properties[f'$geoip_{key}'] := value\n returnEvent.properties.$set[f'$geoip_{key}'] := value\n returnEvent.properties.$set_once[f'$initial_geoip_{key}'] := value\n}\nreturn returnEvent\n ", + "hog": "\n// Define the properties to be added to the event\nlet geoipProperties := {\n 'city_name': null,\n 'city_confidence': null,\n 'subdivision_2_name': null,\n 'subdivision_2_code': null,\n 'subdivision_1_name': null,\n 'subdivision_1_code': null,\n 'country_name': null,\n 'country_code': null,\n 'continent_name': null,\n 'continent_code': null,\n 'postal_code': null,\n 'latitude': null,\n 'longitude': null,\n 'accuracy_radius': null,\n 'time_zone': null\n}\n// Check if the event has an IP address\nif (event.properties?.$geoip_disable or empty(event.properties?.$ip)) {\n print('geoip disabled or no ip', event.properties, event.properties?.$ip)\n return event\n}\nlet ip := event.properties.$ip\nif (ip == '127.0.0.1') {\n print('spoofing ip for local development', ip)\n ip := '89.160.20.129'\n}\nlet response := geoipLookup(ip)\nif (not response) {\n print('geoip lookup failed for ip', ip)\n return event\n}\nlet location := {}\nif (response.city) {\n location['city_name'] := response.city.names?.en\n}\nif (response.country) {\n location['country_name'] := response.country.names?.en\n location['country_code'] := response.country.isoCode\n}\nif (response.continent) {\n location['continent_name'] := response.continent.names?.en\n location['continent_code'] := response.continent.code\n}\nif (response.postal) {\n location['postal_code'] := response.postal.code\n}\nif (response.location) {\n location['latitude'] := response.location?.latitude\n location['longitude'] := response.location?.longitude\n location['accuracy_radius'] := response.location?.accuracyRadius\n location['time_zone'] := response.location?.timeZone\n}\nif (response.subdivisions) {\n for (let index, subdivision in response.subdivisions) {\n location[f'subdivision_{index + 1}_code'] := subdivision.isoCode\n location[f'subdivision_{index + 1}_name'] := subdivision.names?.en\n }\n}\nprint('geoip location data for ip:', location) \nlet returnEvent := event\nreturnEvent.properties := returnEvent.properties ?? {}\nreturnEvent.properties.$set := returnEvent.properties.$set ?? {}\nreturnEvent.properties.$set_once := returnEvent.properties.$set_once ?? {}\nfor (let key, value in geoipProperties) {\n if (value != null) {\n returnEvent.properties.$set[f'$geoip_{key}'] := value\n returnEvent.properties.$set_once[f'$initial_geoip_{key}'] := value\n }\n returnEvent.properties.$set[f'$geoip_{key}'] := value\n returnEvent.properties.$set_once[f'$initial_geoip_{key}'] := value\n}\nfor (let key, value in location) {\n returnEvent.properties[f'$geoip_{key}'] := value\n returnEvent.properties.$set[f'$geoip_{key}'] := value\n returnEvent.properties.$set_once[f'$initial_geoip_{key}'] := value\n}\nreturn returnEvent\n ", "inputs_schema": [] }, { From 0d08ffe87f16fa46488885b888ed65a816ada4a0 Mon Sep 17 00:00:00 2001 From: Ben White Date: Thu, 30 Jan 2025 13:49:50 +0100 Subject: [PATCH 127/163] Fixes --- .../hog-transformer.service.test.ts | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/plugin-server/src/cdp/hog-transformations/hog-transformer.service.test.ts b/plugin-server/src/cdp/hog-transformations/hog-transformer.service.test.ts index dbc983ff10360..c6ac1925eb6dd 100644 --- a/plugin-server/src/cdp/hog-transformations/hog-transformer.service.test.ts +++ b/plugin-server/src/cdp/hog-transformations/hog-transformer.service.test.ts @@ -191,7 +191,7 @@ describe('HogTransformer', () => { await hogTransformer['hogFunctionManager'].reloadAllHogFunctions() - const createHogFunctionInvocationSpy = jest.spyOn(hogTransformer as any, 'createHogFunctionInvocation') + const executeHogFunctionSpy = jest.spyOn(hogTransformer as any, 'executeHogFunction') const event: PluginEvent = { ip: '89.160.20.129', @@ -207,10 +207,10 @@ describe('HogTransformer', () => { await hogTransformer.transformEvent(event) - expect(createHogFunctionInvocationSpy).toHaveBeenCalledTimes(3) - expect(createHogFunctionInvocationSpy.mock.calls[0][1]).toMatchObject({ execution_order: 1 }) - expect(createHogFunctionInvocationSpy.mock.calls[1][1]).toMatchObject({ execution_order: 2 }) - expect(createHogFunctionInvocationSpy.mock.calls[2][1]).toMatchObject({ execution_order: 3 }) + expect(executeHogFunctionSpy).toHaveBeenCalledTimes(3) + expect(executeHogFunctionSpy.mock.calls[0][0]).toMatchObject({ execution_order: 1 }) + expect(executeHogFunctionSpy.mock.calls[1][0]).toMatchObject({ execution_order: 2 }) + expect(executeHogFunctionSpy.mock.calls[2][0]).toMatchObject({ execution_order: 3 }) expect(event.properties?.test_property).toEqual('test_value') }) @@ -270,7 +270,7 @@ describe('HogTransformer', () => { await hogTransformer['hogFunctionManager'].reloadAllHogFunctions() - const createHogFunctionInvocationSpy = jest.spyOn(hogTransformer as any, 'createHogFunctionInvocation') + const executeHogFunctionSpy = jest.spyOn(hogTransformer as any, 'executeHogFunction') const event: PluginEvent = { ip: '89.160.20.129', @@ -291,7 +291,7 @@ describe('HogTransformer', () => { * Second call is the deleting the test property * hence the result is null */ - expect(createHogFunctionInvocationSpy).toHaveBeenCalledTimes(2) + expect(executeHogFunctionSpy).toHaveBeenCalledTimes(2) expect(result?.event?.properties?.test_property).toEqual(null) }) it('should execute tranformation without execution_order last', async () => { @@ -369,7 +369,7 @@ describe('HogTransformer', () => { await insertHogFunction(hub.db.postgres, teamId, firstTransformationFunction) await hogTransformer['hogFunctionManager'].reloadAllHogFunctions() - const createHogFunctionInvocationSpy = jest.spyOn(hogTransformer as any, 'createHogFunctionInvocation') + const executeHogFunctionSpy = jest.spyOn(hogTransformer as any, 'executeHogFunction') const event: PluginEvent = { ip: '89.160.20.129', @@ -384,10 +384,10 @@ describe('HogTransformer', () => { } await hogTransformer.transformEvent(event) - expect(createHogFunctionInvocationSpy).toHaveBeenCalledTimes(3) - expect(createHogFunctionInvocationSpy.mock.calls[0][1]).toMatchObject({ execution_order: 1 }) - expect(createHogFunctionInvocationSpy.mock.calls[1][1]).toMatchObject({ execution_order: 2 }) - expect(createHogFunctionInvocationSpy.mock.calls[2][1]).toMatchObject({ execution_order: null }) + expect(executeHogFunctionSpy).toHaveBeenCalledTimes(3) + expect(executeHogFunctionSpy.mock.calls[0][0]).toMatchObject({ execution_order: 1 }) + expect(executeHogFunctionSpy.mock.calls[1][0]).toMatchObject({ execution_order: 2 }) + expect(executeHogFunctionSpy.mock.calls[2][0]).toMatchObject({ execution_order: null }) }) }) From b21018c82f9cb055aa5c0bcc50bc184a816f68ec Mon Sep 17 00:00:00 2001 From: Ben White Date: Thu, 30 Jan 2025 15:33:05 +0100 Subject: [PATCH 128/163] Fixees --- .../index.ts | 32 + .../template.ts | 14 + .../drop-events-on-property-plugin/index.ts | 21 + .../plugin.json | 26 + .../template.ts | 31 + .../flatten-properties-plugin/index.test.ts | 196 ++ .../flatten-properties-plugin/index.ts | 62 + .../flatten-properties-plugin/plugin.json | 23 + .../flatten-properties-plugin/template.ts | 31 + .../plugin-advanced-geoip/index.test.ts | 154 ++ .../plugin-advanced-geoip/index.ts | 99 + .../plugin-advanced-geoip/plugin.json | 24 + .../plugin-advanced-geoip/template.ts | 35 + .../__tests__/index_agent.js | 507 ++++ .../__tests__/index_agent_installer.js | 53 + .../__tests__/index_blog.js | 264 +++ .../__tests__/index_cloud.js | 361 +++ .../__tests__/index_community.js | 224 ++ .../__tests__/index_learn.js | 264 +++ .../__tests__/index_staging.js | 97 + .../__tests__/index_testing.js | 97 + .../__tests__/index_website.js | 238 ++ .../plugin-netdata-event-processing/index.js | 2036 +++++++++++++++++ .../interaction_detail_agent.js | 202 ++ .../interaction_type_agent.js | 144 ++ .../plugin.json | 8 + .../process.js | 167 ++ .../process_elements_agent.js | 416 ++++ .../process_elements_agent_installer.js | 7 + .../process_elements_blog.js | 109 + .../process_elements_cloud.js | 221 ++ .../process_elements_community.js | 104 + .../process_elements_learn.js | 109 + .../process_elements_staging.js | 130 ++ .../process_elements_testing.js | 130 ++ .../process_elements_website.js | 104 + .../process_properties_agent.js | 97 + .../process_properties_agent_installer.js | 22 + .../process_properties_blog.js | 8 + .../process_properties_cloud.js | 9 + .../process_properties_community.js | 8 + .../process_properties_learn.js | 8 + .../process_properties_staging.js | 8 + .../process_properties_testing.js | 8 + .../process_properties_website.js | 8 + .../plugin-netdata-event-processing/utils.js | 54 + .../plugin-stonly-UTM-Extractor/index.ts | 41 + .../plugin-stonly-UTM-Extractor/template.ts | 14 + .../posthog-app-unduplicator/index.test.ts | 111 + .../posthog-app-unduplicator/index.ts | 72 + .../posthog-app-unduplicator/plugin.json | 16 + .../posthog-app-unduplicator/template.ts | 25 + .../posthog-plugin-geoip/index.test.ts | 136 ++ .../posthog-plugin-geoip/index.ts | 114 + .../posthog-plugin-geoip/plugin.json | 8 + .../posthog-plugin-geoip/template.ts | 14 + .../posthog-route-censor-plugin/dist.js | 624 +++++ .../posthog-route-censor-plugin/index.ts | 21 + .../posthog-route-censor-plugin/plugin.json | 43 + .../posthog-route-censor-plugin/template.ts | 47 + 60 files changed, 8256 insertions(+) create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/Plugin-Stonly-Clean-Campaign-Name/index.ts create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/Plugin-Stonly-Clean-Campaign-Name/template.ts create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/drop-events-on-property-plugin/index.ts create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/drop-events-on-property-plugin/plugin.json create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/drop-events-on-property-plugin/template.ts create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/flatten-properties-plugin/index.test.ts create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/flatten-properties-plugin/index.ts create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/flatten-properties-plugin/plugin.json create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/flatten-properties-plugin/template.ts create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/plugin-advanced-geoip/index.test.ts create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/plugin-advanced-geoip/index.ts create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/plugin-advanced-geoip/plugin.json create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/plugin-advanced-geoip/template.ts create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/__tests__/index_agent.js create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/__tests__/index_agent_installer.js create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/__tests__/index_blog.js create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/__tests__/index_cloud.js create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/__tests__/index_community.js create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/__tests__/index_learn.js create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/__tests__/index_staging.js create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/__tests__/index_testing.js create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/__tests__/index_website.js create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/index.js create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/interaction_detail_agent.js create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/interaction_type_agent.js create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/plugin.json create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process.js create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_elements_agent.js create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_elements_agent_installer.js create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_elements_blog.js create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_elements_cloud.js create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_elements_community.js create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_elements_learn.js create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_elements_staging.js create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_elements_testing.js create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_elements_website.js create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_properties_agent.js create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_properties_agent_installer.js create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_properties_blog.js create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_properties_cloud.js create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_properties_community.js create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_properties_learn.js create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_properties_staging.js create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_properties_testing.js create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_properties_website.js create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/utils.js create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/plugin-stonly-UTM-Extractor/index.ts create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/plugin-stonly-UTM-Extractor/template.ts create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/posthog-app-unduplicator/index.test.ts create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/posthog-app-unduplicator/index.ts create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/posthog-app-unduplicator/plugin.json create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/posthog-app-unduplicator/template.ts create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/posthog-plugin-geoip/index.test.ts create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/posthog-plugin-geoip/index.ts create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/posthog-plugin-geoip/plugin.json create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/posthog-plugin-geoip/template.ts create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/posthog-route-censor-plugin/dist.js create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/posthog-route-censor-plugin/index.ts create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/posthog-route-censor-plugin/plugin.json create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/posthog-route-censor-plugin/template.ts diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/Plugin-Stonly-Clean-Campaign-Name/index.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/Plugin-Stonly-Clean-Campaign-Name/index.ts new file mode 100644 index 0000000000000..ac1efa5e5ff9d --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/Plugin-Stonly-Clean-Campaign-Name/index.ts @@ -0,0 +1,32 @@ +import { PluginEvent } from '@posthog/plugin-scaffold' + +import { LegacyTransformationPluginMeta } from '../../types' + +const cleanUtmCampain = (utmCampaign: string) => { + return utmCampaign + .replace('com-', 'comp-') + .replace('compp-', 'comp-') + .split('_')[0] + .split('-') + .slice(0, 2) + .join(' ') +} + +export function processEvent(event: PluginEvent, _: LegacyTransformationPluginMeta) { + // Some events (such as $identify) don't have properties + if (event.properties && event.properties['utm_campaign']) { + const cleanCampaign = cleanUtmCampain(event.properties['utm_campaign']) + if (event.properties['$set']) { + event.properties['$set']['campaign'] = cleanCampaign + } else { + event.properties['$set'] = { campaign: cleanCampaign } + } + if (event.properties['$set_once']) { + event.properties['$set_once']['initial_campaign'] = cleanCampaign + } else { + event.properties['$set_once'] = { initial_campaign: cleanCampaign } + } + } + // Return the event to be ingested, or return null to discard + return event +} diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/Plugin-Stonly-Clean-Campaign-Name/template.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/Plugin-Stonly-Clean-Campaign-Name/template.ts new file mode 100644 index 0000000000000..000be7a45e70e --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/Plugin-Stonly-Clean-Campaign-Name/template.ts @@ -0,0 +1,14 @@ +import { HogFunctionTemplate } from '../../../templates/types' + +// NOTE: This is a deprecated plugin and should never be shown to new users +export const template: HogFunctionTemplate = { + status: 'alpha', + type: 'transformation', + id: 'plugin-stonly-clean-campaign-name', + name: 'Clean Campaign Name', + description: '', + icon_url: '/static/hedgehog/builder-hog-01.png', + category: ['Custom'], + hog: `return event`, + inputs_schema: [], +} diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/drop-events-on-property-plugin/index.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/drop-events-on-property-plugin/index.ts new file mode 100644 index 0000000000000..7fc9b55aadea3 --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/drop-events-on-property-plugin/index.ts @@ -0,0 +1,21 @@ +// Learn more about plugins at: https://posthog.com/docs/plugins/build/overview + +import { PluginEvent } from '@posthog/plugin-scaffold' + +import { LegacyTransformationPluginMeta } from '../../types' + +// Processes each event, optionally transforming it +export function processEvent(event: PluginEvent, { config }: LegacyTransformationPluginMeta) { + // Some events (such as $identify) don't have properties + if (event.properties && event.properties[config.property_key]) { + if (!config.property_values || config.property_values == '') { + return null + } + const values = config.property_values.split(',') + if (values.indexOf(event.properties[config.property_key]) > -1) { + return null + } + } + // Return the event to be ingested, or return null to discard + return event +} diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/drop-events-on-property-plugin/plugin.json b/plugin-server/src/cdp/legacy-plugins/_transformations/drop-events-on-property-plugin/plugin.json new file mode 100644 index 0000000000000..2558d024fd062 --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/drop-events-on-property-plugin/plugin.json @@ -0,0 +1,26 @@ +{ + "name": "Drop Events Based On Property", + "url": "https://github.com/posthog/drop-events-on-property-plugin", + "description": "This plugin will drop any events that have a specific key. If you supply a value, it will drop any event with the combination of they key and the value. You will not be billed for any events that this plugin drops.", + "main": "index.js", + "posthogVersion": ">= 1.25.0", + "config": [ + { + "markdown": "Configure the key and optional value to filter events on" + }, + { + "key": "property_key", + "hint": "Which property key to filter on. If you do not specify a value, all events with this key will be dropped.", + "name": "Property key to filter on", + "type": "string", + "required": true + }, + { + "key": "property_values", + "hint": "Which value to match to drop events. Split multiple values by comma to filter.", + "name": "Property value to filter on", + "type": "string", + "required": false + } + ] +} diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/drop-events-on-property-plugin/template.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/drop-events-on-property-plugin/template.ts new file mode 100644 index 0000000000000..888df9d96b302 --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/drop-events-on-property-plugin/template.ts @@ -0,0 +1,31 @@ +import { HogFunctionTemplate } from '../../../templates/types' + +// NOTE: This is a deprecated plugin and should never be shown to new users +export const template: HogFunctionTemplate = { + status: 'alpha', + type: 'transformation', + id: 'plugin-drop-events-on-property-plugin', + name: 'Drop Events Based On Property', + description: + 'This plugin will drop any events that have a specific key. If you supply a value, it will drop any event with the combination of they key and the value. You will not be billed for any events that this plugin drops.', + icon_url: 'https://raw.githubusercontent.com/posthog/drop-events-on-property-plugin/main/logo.png', + category: ['Custom'], + hog: `return event`, + inputs_schema: [ + { + key: 'property_key', + description: + 'Which property key to filter on. If you do not specify a value, all events with this key will be dropped.', + label: 'Property key to filter on', + type: 'string', + required: true, + }, + { + key: 'property_values', + description: 'Which value to match to drop events. Split multiple values by comma to filter.', + label: 'Property value to filter on', + type: 'string', + required: false, + }, + ], +} diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/flatten-properties-plugin/index.test.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/flatten-properties-plugin/index.test.ts new file mode 100644 index 0000000000000..df3038467a8e9 --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/flatten-properties-plugin/index.test.ts @@ -0,0 +1,196 @@ +const { createEvent } = require('@posthog/plugin-scaffold/test/utils.js') +const { processEvent } = require('./index') + +const nestedEventProperties = { + a: { + b: { + c: { + d: { + e: { + f: 'nested under e', + }, + z: 'nested under d', + }, + z: 'nested under c', + }, + z: 'nested under b', + }, + z: 'nested under a', + }, + w: { + array: [{ z: 'nested in w array' }], + }, + x: 'not nested', + y: 'not nested either', +} + +describe('the property flattener', () => { + test('flattens all nested properties', async () => { + const event = createEvent({ event: 'test', properties: nestedEventProperties }) + + const eventsOutput = await processEvent(event, { config: { separator: '__' } }) + + const expectedProperties = { + a: nestedEventProperties.a, + w: nestedEventProperties.w, + x: 'not nested', + y: 'not nested either', + a__b__c__d__e__f: 'nested under e', + a__b__c__d__z: 'nested under d', + a__b__c__z: 'nested under c', + a__b__z: 'nested under b', + a__z: 'nested under a', + w__array__0__z: 'nested in w array', + } + + expect(eventsOutput).toEqual(createEvent({ event: 'test', properties: expectedProperties })) + }) + + test('autocapture is ignored', async () => { + const event = createEvent({ + event: '$autocapture', + properties: { + $elements: [ + { tag_name: 'span', nth_child: 1 }, + { tag_name: 'div', nth_child: 1 }, + ], + $elements_chain: 'span:nth_child="1";div:nth_child="1"', + }, + }) + + const eventsOutput = await processEvent(event, { config: { separator: '__' } }) + + const expectedProperties = { + $elements: [ + { tag_name: 'span', nth_child: 1 }, + { tag_name: 'div', nth_child: 1 }, + ], + $elements_chain: 'span:nth_child="1";div:nth_child="1"', + } + + expect(eventsOutput).toEqual(createEvent({ event: '$autocapture', properties: expectedProperties })) + }) + + test('organization usage report is ignored because it causes very many flattened properties', async () => { + const event = createEvent({ + event: 'organization usage report', + properties: { + any: [{ nested: 'property' }], + }, + }) + + const eventsOutput = await processEvent(event, { config: { separator: '__' } }) + + const expectedProperties = { + any: [{ nested: 'property' }], + } + + expect(eventsOutput).toEqual( + createEvent({ event: 'organization usage report', properties: expectedProperties }) + ) + }) + + test('set and set once', async () => { + const event = createEvent({ + event: '$identify', + properties: { + $set: { + example: { + company_size: 20, + category: ['a', 'b'], + }, + }, + $set_once: { + example: { + company_size: 20, + category: ['a', 'b'], + }, + }, + }, + }) + + const eventsOutput = await processEvent(event, { config: { separator: '__' } }) + + const expectedProperties = { + $set: { + example: { + company_size: 20, + category: ['a', 'b'], + }, + example__company_size: 20, + example__category__0: 'a', + example__category__1: 'b', + }, + $set_once: { + example: { + company_size: 20, + category: ['a', 'b'], + }, + example__company_size: 20, + example__category__0: 'a', + example__category__1: 'b', + }, + } + + expect(eventsOutput.properties).toEqual(expectedProperties) + }) + + test('$group_set', async () => { + const event = createEvent({ + event: '$groupidentify', + properties: { + $group_set: { + a: 'shopify.com', + ads: { + 'facebook-ads': true, + 'google-ads': true, + 'tiktok-ads': false, + 'pinterest-ads': false, + 'snapchat-ads': false, + fairing: false, + slack: false, + }, + pixel: true, + pixel_settings: { + allow_auto_install: true, + }, + currency: 'USD', + timezone: 'XXX', + }, + }, + }) + + const eventsOutput = await processEvent(event, { config: { separator: '__' } }) + + const expectedProperties = { + $group_set: { + a: 'shopify.com', + ads: { + 'facebook-ads': true, + 'google-ads': true, + 'tiktok-ads': false, + 'pinterest-ads': false, + 'snapchat-ads': false, + fairing: false, + slack: false, + }, + pixel: true, + pixel_settings: { + allow_auto_install: true, + }, + currency: 'USD', + timezone: 'XXX', + 'ads__facebook-ads': true, + ads__fairing: false, + 'ads__google-ads': true, + 'ads__pinterest-ads': false, + ads__slack: false, + 'ads__snapchat-ads': false, + 'ads__tiktok-ads': false, + pixel_settings__allow_auto_install: true, + }, + } + + expect(eventsOutput.properties).toEqual(expectedProperties) + }) +}) diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/flatten-properties-plugin/index.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/flatten-properties-plugin/index.ts new file mode 100644 index 0000000000000..f23309a8bce04 --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/flatten-properties-plugin/index.ts @@ -0,0 +1,62 @@ +import { PluginEvent } from '@posthog/plugin-scaffold' + +import { LegacyTransformationPluginMeta } from '../../types' + +/** + * Some events will always create very large numbers of flattened properties + * This is undesirable since a large enough number of properties for a particular team can slow down the property filter in the UI + * If the problem property has a unique enough name it can be added to the propertyDenyList + * If not (or if many properties for a given event are problematic) then the event can be added here + */ +const eventDenyList = ['$autocapture', 'organization usage report'] + +function processEvent(event: PluginEvent, { config }: LegacyTransformationPluginMeta) { + try { + if (!eventDenyList.includes(event.event) && event.properties) { + event.properties = flattenProperties(event.properties, config.separator) + } + } catch (e) { + throw e + } + return event +} + +const propertyDenyList = [ + '$elements', + '$elements_chain', + '$groups', + '$active_feature_flags', + '$heatmap_data', + '$web_vitals_data', +] + +const flattenProperties = (props: Record, sep: string, nestedChain: string[] = []) => { + let newProps: Record = {} + for (const [key, value] of Object.entries(props)) { + if (propertyDenyList.includes(key)) { + // Hide 'internal' properties used in event processing + } else if (key === '$set') { + newProps = { ...newProps, $set: { ...props[key], ...flattenProperties(props[key], sep) } } + } else if (key === '$set_once') { + newProps = { ...newProps, $set_once: { ...props[key], ...flattenProperties(props[key], sep) } } + } else if (key === '$group_set') { + newProps = { ...newProps, $group_set: { ...props[key], ...flattenProperties(props[key], sep) } } + } else if (Array.isArray(value)) { + newProps = { ...newProps, ...flattenProperties(props[key], sep, [...nestedChain, key]) } + } else if (value !== null && typeof value === 'object' && Object.keys(value).length > 0) { + newProps = { ...newProps, ...flattenProperties(props[key], sep, [...nestedChain, key]) } + } else { + if (nestedChain.length > 0) { + newProps[nestedChain.join(sep) + `${sep}${key}`] = value + } + } + } + if (nestedChain.length > 0) { + return { ...newProps } + } + return { ...props, ...newProps } +} + +module.exports = { + processEvent, +} diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/flatten-properties-plugin/plugin.json b/plugin-server/src/cdp/legacy-plugins/_transformations/flatten-properties-plugin/plugin.json new file mode 100644 index 0000000000000..59c0a7613bc78 --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/flatten-properties-plugin/plugin.json @@ -0,0 +1,23 @@ +{ + "name": "Property Flattener Plugin", + "url": "https://github.com/PostHog/property-flattener-plugin", + "description": "Flatten event properties to easily access them in filters.", + "main": "index.js", + "config": [ + { + "key": "separator", + "hint": "For example, to access the value of 'b' in a: { b: 1 } with separator '__', you can do 'a__b'", + "name": "Select a separator format for accessing your nested properties", + "type": "choice", + "choices": [ + "__", + ".", + ">", + "/" + ], + "order": 1, + "default": "__", + "required": true + } + ] +} diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/flatten-properties-plugin/template.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/flatten-properties-plugin/template.ts new file mode 100644 index 0000000000000..496eca9d051a5 --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/flatten-properties-plugin/template.ts @@ -0,0 +1,31 @@ +import { HogFunctionTemplate } from '../../../templates/types' + +// NOTE: This is a deprecated plugin and should never be shown to new users +export const template: HogFunctionTemplate = { + status: 'alpha', + type: 'transformation', + id: 'plugin-flatten-properties-plugin', + name: 'Flatten Properties', + description: + 'This plugin will flatten all nested properties into a single property. You will not be billed for any events that this plugin drops.', + icon_url: 'https://raw.githubusercontent.com/posthog/flatten-properties-plugin/main/logo.png', + category: ['Custom'], + hog: `return event`, + inputs_schema: [ + { + key: 'separator', + description: + "For example, to access the value of 'b' in a: { b: 1 } with separator '__', you can do 'a__b'", + label: 'Select a separator format for accessing your nested properties', + type: 'choice', + choices: [ + { value: '__', label: '__' }, + { value: '.', label: '.' }, + { value: '>', label: '>' }, + { value: '/', label: '/' }, + ], + default: '__', + required: true, + }, + ], +} diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-advanced-geoip/index.test.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-advanced-geoip/index.test.ts new file mode 100644 index 0000000000000..82dac6c6de6a3 --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-advanced-geoip/index.test.ts @@ -0,0 +1,154 @@ +import { PluginEvent } from '@posthog/plugin-scaffold' +import { createPageview, resetMeta } from '@posthog/plugin-scaffold/test/utils' + +import { LegacyTransformationPluginMeta } from '../../types' +import { processEvent } from './index' + +const defaultMeta = { + config: { + discardIp: 'true', + discardLibs: 'posthog-node', + }, +} + +const createGeoIPPageview = (): PluginEvent => { + const event = createPageview() + + const setGeoIPProps = { + $geoip_city_name: 'Ashburn', + $geoip_country_name: 'United States', + $geoip_country_code: 'US', + $geoip_continent_name: 'North America', + $geoip_continent_code: 'NA', + $geoip_postal_code: '20149', + $geoip_latitude: 39.0469, + $geoip_longitude: -77.4903, + $geoip_time_zone: 'America/New_York', + $geoip_subdivision_1_code: 'VA', + $geoip_subdivision_1_name: 'Virginia', + } + + const setOnceGeoIPProps = { + $initial_geoip_city_name: 'Ashburn', + $initial_geoip_country_name: 'United States', + $initial_geoip_country_code: 'US', + $initial_geoip_continent_name: 'North America', + $initial_geoip_continent_code: 'NA', + $initial_geoip_postal_code: '20149', + $initial_geoip_latitude: 39.0469, + $initial_geoip_longitude: -77.4903, + $initial_geoip_time_zone: 'America/New_York', + $initial_geoip_subdivision_1_code: 'VA', + $initial_geoip_subdivision_1_name: 'Virginia', + } + + const properties = { + ...event.properties, + $ip: '13.106.122.3', + $plugins_succeeded: ['GeoIP (8199)', 'Unduplicates (8303)'], + $lib: 'posthog-node', + $set: setGeoIPProps, + $set_once: setOnceGeoIPProps, + } + return { + ...event, + ip: '13.106.122.3', + properties, + $set: setGeoIPProps, + $set_once: setOnceGeoIPProps, + } +} + +const helperVerifyGeoIPIsEmpty = (event: PluginEvent): void => { + // event properties + expect(event!.properties!.$geoip_city_name).toEqual(undefined) + expect(event!.properties!.$geoip_country_name).toEqual(undefined) + expect(event!.properties!.$geoip_country_code).toEqual(undefined) + + // $set + expect(event!.$set!.$geoip_city_name).toEqual(undefined) + expect(event!.$set!.$geoip_country_name).toEqual(undefined) + expect(event!.$set!.$geoip_country_code).toEqual(undefined) + expect(event!.$set!.$geoip_continent_name).toEqual(undefined) + expect(event!.$set!.$geoip_continent_code).toEqual(undefined) + expect(event!.$set!.$geoip_postal_code).toEqual(undefined) + expect(event!.$set!.$geoip_latitude).toEqual(undefined) + expect(event!.$set!.$geoip_longitude).toEqual(undefined) + expect(event!.$set!.$geoip_time_zone).toEqual(undefined) + expect(event!.$set!.$geoip_subdivision_1_code).toEqual(undefined) + expect(event!.$set!.$geoip_subdivision_1_name).toEqual(undefined) + + // $set_once + expect(event!.$set_once!.$initial_geoip_city_name).toEqual(undefined) + expect(event!.$set_once!.$initial_geoip_country_name).toEqual(undefined) + expect(event!.$set_once!.$initial_geoip_country_code).toEqual(undefined) + expect(event!.$set_once!.$initial_geoip_latitude).toEqual(undefined) + expect(event!.$set_once!.$initial_geoip_longitude).toEqual(undefined) + + // $set on event props + expect(event!.properties!.$set!.$geoip_city_name).toEqual(undefined) + expect(event!.properties!.$set!.$geoip_country_name).toEqual(undefined) + expect(event!.properties!.$set!.$geoip_country_code).toEqual(undefined) + + // $set_once on event props + expect(event!.properties!.$set_once!.$initial_geoip_city_name).toEqual(undefined) + expect(event!.properties!.$set_once!.$initial_geoip_country_name).toEqual(undefined) + expect(event!.properties!.$set_once!.$initial_geoip_country_code).toEqual(undefined) +} + +describe('discard IP', () => { + test('IP is discarded', () => { + const meta = resetMeta(defaultMeta) as LegacyTransformationPluginMeta + const event = processEvent(createGeoIPPageview(), meta) + expect(event?.ip).toEqual(null) + expect(event?.properties?.$ip).toEqual(undefined) + }) + test('IP is not discarded if not enabled', () => { + const meta = resetMeta({ + config: { ...defaultMeta.config, discardIp: 'false' }, + }) as LegacyTransformationPluginMeta + const event = processEvent(createGeoIPPageview(), meta) + expect(event?.ip).toEqual('13.106.122.3') + expect(event?.properties?.$ip).toEqual('13.106.122.3') + }) + test('IP is not discarded if GeoIP not processed', () => { + const meta = resetMeta() as LegacyTransformationPluginMeta + const preprocessedEvent = createGeoIPPageview() + preprocessedEvent.properties = { ...preprocessedEvent.properties, $plugins_succeeded: ['Unduplicates (8303)'] } + const event = processEvent(preprocessedEvent, meta) + expect(event?.ip).toEqual('13.106.122.3') + expect(event?.properties?.$ip).toEqual('13.106.122.3') + }) +}) + +describe('$lib ignore', () => { + test('ignores GeoIP from $lib', () => { + const meta = resetMeta(defaultMeta) as LegacyTransformationPluginMeta + const event = processEvent(createGeoIPPageview(), meta) + helperVerifyGeoIPIsEmpty(event!) + expect(Object.keys(event!.$set!).length).toEqual(0) // Ensure this is not sending properties even as undefined (that overwrites the user properties) + expect(Object.keys(event!.$set_once!).length).toEqual(0) // Ensure this is not sending properties even as undefined (that overwrites the user properties) + }) + + test('ignores GeoIP from $lib CSV', () => { + const meta = resetMeta({ + config: { ...defaultMeta.config, discardLibs: 'posthog-ios,posthog-android,posthog-node' }, + }) as LegacyTransformationPluginMeta + const event = processEvent(createGeoIPPageview(), meta) + helperVerifyGeoIPIsEmpty(event!) + }) + + test('keeps GeoIP if $lib is not on ignore list', () => { + const meta = resetMeta() as LegacyTransformationPluginMeta + const preprocessedEvent = createGeoIPPageview() + preprocessedEvent.properties!.$lib = 'posthog-swift' + const event = processEvent(preprocessedEvent, meta) + expect(event!.$set!.$geoip_city_name).toEqual('Ashburn') + expect(event!.$set!.$geoip_country_name).toEqual('United States') + expect(event!.$set!.$geoip_country_code).toEqual('US') + + expect(event!.$set_once!.$initial_geoip_city_name).toEqual('Ashburn') + expect(event!.$set_once!.$initial_geoip_country_name).toEqual('United States') + expect(event!.$set_once!.$initial_geoip_country_code).toEqual('US') + }) +}) diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-advanced-geoip/index.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-advanced-geoip/index.ts new file mode 100644 index 0000000000000..4d7ab9c3886c5 --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-advanced-geoip/index.ts @@ -0,0 +1,99 @@ +import { PluginEvent } from '@posthog/plugin-scaffold' + +import { LegacyTransformationPluginMeta } from '../../types' + +const geoIpProps = [ + '$geoip_city_name', + '$geoip_country_name', + '$geoip_country_code', + '$geoip_continent_name', + '$geoip_continent_code', + '$geoip_postal_code', + '$geoip_latitude', + '$geoip_longitude', + '$geoip_time_zone', + '$geoip_subdivision_1_code', + '$geoip_subdivision_1_name', + '$geoip_subdivision_2_code', + '$geoip_subdivision_2_name', + '$geoip_subdivision_3_code', + '$geoip_subdivision_3_name', +] + +const geoIpInitialProps = [ + '$initial_geoip_city_name', + '$initial_geoip_country_name', + '$initial_geoip_country_code', + '$initial_geoip_continent_name', + '$initial_geoip_continent_code', + '$initial_geoip_postal_code', + '$initial_geoip_latitude', + '$initial_geoip_longitude', + '$initial_geoip_time_zone', + '$initial_geoip_subdivision_1_code', + '$initial_geoip_subdivision_1_name', + '$initial_geoip_subdivision_2_code', + '$initial_geoip_subdivision_2_name', + '$initial_geoip_subdivision_3_code', + '$initial_geoip_subdivision_3_name', +] + +// interface AppInterface { +// config: { +// discardIp: 'true' | 'false' +// discardLibs: string +// } +// } + +const GEO_IP_PLUGIN = /^GeoIP \(\d+\)$/ + +export const processEvent = (event: PluginEvent, { config, logger }: LegacyTransformationPluginMeta) => { + const parsedLibs = config.discardLibs?.split(',').map((val: string) => val.toLowerCase().trim()) + + if (parsedLibs && event.properties?.$lib && parsedLibs.includes(event.properties?.$lib)) { + // Event comes from a `$lib` that should be ignored + logger.log( + `Discarding GeoIP properties from ${event.uuid || event.event} as event comes from ignored $lib: ${ + event.properties?.$lib + }.` + ) + for (const prop of geoIpProps) { + // We need to handle both `$set` and `properties.$set` as they are both used in different contexts + if (event.$set) { + delete event.$set[prop] + } + + if (event.properties.$set) { + delete event.properties.$set[prop] + } + delete event.properties[prop] + } + + for (const prop of geoIpInitialProps) { + if (event.$set_once) { + delete event.$set_once[prop] + } + + if (event.properties.$set_once) { + delete event.properties.$set_once[prop] + } + } + } + + if (config.discardIp === 'true') { + if ( + Array.isArray(event.properties?.$plugins_succeeded) && + event.properties?.$plugins_succeeded.find((val: string) => val.toString().match(GEO_IP_PLUGIN)) + ) { + event.properties.$ip = undefined + event.ip = null + logger.log(`IP discarded for event ${event.uuid || event.event}.`) + } else { + logger.warn(`Could not discard IP for event ${event.uuid || event.event} as GeoIP has not been processed.`) + } + } + + logger.log(`Finished processing ${event.uuid || event.event}.`) + + return event +} diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-advanced-geoip/plugin.json b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-advanced-geoip/plugin.json new file mode 100644 index 0000000000000..c18536f149b01 --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-advanced-geoip/plugin.json @@ -0,0 +1,24 @@ +{ + "name": "Advanced GeoIP", + "url": "https://github.com/paolodamico/posthog-app-advanced-geoip", + "description": "Advanced GeoIP filtering app for PostHog", + "main": "index.ts", + "posthogVersion": ">=1.25.0", + "config": [ + { + "key": "discardIp", + "name": "Discard IP addresses after GeoIP?", + "type": "choice", + "choices": ["true", "false"], + "hint": "Whether IP addresses should be discarded after doing GeoIP lookup.", + "required": true + }, + { + "key": "discardLibs", + "name": "Discard GeoIP for libraries", + "type": "string", + "hint": "Comma-separated list of libraries ($lib) for which GeoIP should be ignored (e.g. `posthog-node,posthog-python`)", + "required": false + } + ] +} diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-advanced-geoip/template.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-advanced-geoip/template.ts new file mode 100644 index 0000000000000..a1cba0a69c8c3 --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-advanced-geoip/template.ts @@ -0,0 +1,35 @@ +import { HogFunctionTemplate } from '../../../templates/types' + +// NOTE: This is a deprecated plugin and should never be shown to new users +export const template: HogFunctionTemplate = { + status: 'alpha', + type: 'transformation', + id: 'plugin-advanced-geoip', + name: 'Advanced GeoIP', + description: + 'This plugin will add advanced geoip properties to your events. You will not be billed for any events that this plugin drops.', + icon_url: 'https://raw.githubusercontent.com/posthog/advanced-geoip-plugin/main/logo.png', + category: ['Custom'], + hog: `return event`, + inputs_schema: [ + { + key: 'discardIp', + label: 'Discard IP addresses after GeoIP?', + type: 'choice', + choices: [ + { value: 'true', label: 'true' }, + { value: 'false', label: 'false' }, + ], + description: 'Whether IP addresses should be discarded after doing GeoIP lookup.', + required: true, + }, + { + key: 'discardLibs', + label: 'Discard GeoIP for libraries', + type: 'string', + description: + 'Comma-separated list of libraries ($lib) for which GeoIP should be ignored (e.g. `posthog-node,posthog-python`)', + required: false, + }, + ], +} diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/__tests__/index_agent.js b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/__tests__/index_agent.js new file mode 100644 index 0000000000000..aa893c415224a --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/__tests__/index_agent.js @@ -0,0 +1,507 @@ +const { + createEvent, + createIdentify, + createPageview, + createCache, + getMeta, + resetMeta, + clone, +} = require('posthog-plugins/test/utils.js') +const { setupPlugin, processEvent } = require('../index') + +const netdataPluginVersion = '0.0.15' + +beforeEach(() => { + resetMeta({ + config: { + netdata_version: 'v1.41.0', + }, + }) +}) + +test('setupPlugin', async () => { + expect(getMeta().config.netdata_version).toEqual('v1.41.0') + await setupPlugin(getMeta()) + expect(getMeta().global.setupDone).toEqual(true) +}) + +// test has_alarms_critical +test('has_alarms_critical', async () => { + const event = createEvent({ event: 'test event', properties: { "$current_url":"agent backend", "alarms_critical": 1 } }) + const eventCopy = await processEvent(clone(event), getMeta()) + expect(eventCopy).toEqual({ + ...event, + properties: { + ...event.properties, + has_alarms_critical: true, + netdata_posthog_plugin_version: netdataPluginVersion, + interaction_type: 'other', + interaction_detail: '', + interaction_token: 'other|', + event_ph: 'test event', + event_source: 'agent', + }, + }) +}) + +// test has_alarms_warning +test('has_alarms_warning', async () => { + const event = createEvent({ event: 'test event', properties: { "$current_url":"agent backend", "alarms_warning": 0 } }) + const eventCopy = await processEvent(clone(event), getMeta()) + expect(eventCopy).toEqual({ + ...event, + properties: { + ...event.properties, + has_alarms_warning: false, + netdata_posthog_plugin_version: netdataPluginVersion, + interaction_type: 'other', + interaction_detail: '', + interaction_token: 'other|', + event_ph: 'test event', + event_source: 'agent', + }, + }) +}) + +// test netdata_buildinfo +test('netdata_buildinfo', async () => { + const event = createEvent({ event: 'test event', properties: { "$current_url":"agent backend", "netdata_buildinfo": "JSON-C|dbengine|Native HTTPS|LWS v3.2.2" } }) + const eventCopy = await processEvent(clone(event), getMeta()) + expect(eventCopy).toEqual({ + ...event, + properties: { + ...event.properties, + netdata_buildinfo_json_c: true, + netdata_buildinfo_dbengine: true, + netdata_buildinfo_native_https: true, + netdata_buildinfo_lws_v3_2_2: true, + netdata_posthog_plugin_version: netdataPluginVersion, + interaction_type: 'other', + interaction_detail: '', + interaction_token: 'other|', + event_ph: 'test event', + event_source: 'agent', + }, + }) +}) + +// test host_collectors +test('host_collectors', async () => { + const event = createEvent({ + event: 'test event', + properties: { + "$current_url":"agent backend", + "host_collectors": [ + { + "plugin": "python.d.plugin", + "module": "dockerhub" + }, + { + "plugin": "apps.plugin", + "module": "" + }, + { + "plugin": "proc.plugin", + "module": "/proc/diskstats" + }, + { + "plugin": "proc.plugin", + "module": "/proc/softirqs" + }, + ] + } + }) + const eventCopy = await processEvent(clone(event), getMeta()) + expect(eventCopy).toEqual({ + ...event, + properties: { + ...event.properties, + host_collector_plugin_python_d_plugin: true, + host_collector_plugin_apps_plugin: true, + host_collector_plugin_proc_plugin: true, + host_collector_module_dockerhub: true, + host_collector_module_proc_diskstats: true, + host_collector_module_proc_softirqs: true, + netdata_posthog_plugin_version: netdataPluginVersion, + interaction_type: 'other', + interaction_detail: '', + interaction_token: 'other|', + event_ph: 'test event', + event_source: 'agent', + }, + }) +}) + +// test host_collectors null +test('host_collectors_null', async () => { + const event = createEvent({ + event: 'test event', + properties: { + "$current_url":"agent backend", + "host_collectors": [null] + } + }) + const eventCopy = await processEvent(clone(event), getMeta()) + expect(eventCopy).toEqual({ + ...event, + properties: { + ...event.properties, + netdata_posthog_plugin_version: netdataPluginVersion, + interaction_type: 'other', + interaction_detail: '', + interaction_token: 'other|', + event_ph: 'test event', + event_source: 'agent', + }, + }) +}) + +// test netdata_machine_guid +test('netdata_machine_guid', async () => { + const event = createEvent({ event: 'test event', properties: { "$current_url":"agent backend", "netdata_machine_guid": "" } }) + const eventCopy = await processEvent(clone(event), getMeta()) + expect(eventCopy).toEqual({ + ...event, + properties: { + "$current_url":"agent backend", + netdata_machine_guid: 'empty', + netdata_machine_guid_is_empty: true, + netdata_posthog_plugin_version: netdataPluginVersion, + interaction_type: 'other', + interaction_detail: '', + interaction_token: 'other|', + event_ph: 'test event', + event_source: 'agent', + }, + }) +}) + +// test netdata_machine_guid +test('netdata_machine_guid', async () => { + const event = createEvent({ event: 'test event', properties: { "$current_url":"agent backend", "netdata_machine_guid": "123" } }) + const eventCopy = await processEvent(clone(event), getMeta()) + expect(eventCopy).toEqual({ + ...event, + properties: { + "$current_url":"agent backend", + netdata_machine_guid: '123', + netdata_machine_guid_is_empty: false, + netdata_posthog_plugin_version: netdataPluginVersion, + interaction_type: 'other', + interaction_detail: '', + interaction_token: 'other|', + event_ph: 'test event', + event_source: 'agent', + }, + }) +}) + +// test netdata_person_id +test('netdata_person_id', async () => { + const event = createEvent({ event: 'test event', properties: { "$current_url":"agent backend", "netdata_person_id": "" } }) + const eventCopy = await processEvent(clone(event), getMeta()) + expect(eventCopy).toEqual({ + ...event, + properties: { + "$current_url":"agent backend", + netdata_person_id: 'empty', + netdata_person_id_is_empty: true, + netdata_posthog_plugin_version: netdataPluginVersion, + interaction_type: 'other', + interaction_detail: '', + interaction_token: 'other|', + event_ph: 'test event', + event_source: 'agent', + }, + }) +}) + +// test netdata_person_id +test('netdata_person_id', async () => { + const event = createEvent({ event: 'test event', properties: { "$current_url":"agent backend", "netdata_person_id": "123" } }) + const eventCopy = await processEvent(clone(event), getMeta()) + expect(eventCopy).toEqual({ + ...event, + properties: { + "$current_url":"agent backend", + netdata_person_id: '123', + netdata_person_id_is_empty: false, + netdata_posthog_plugin_version: netdataPluginVersion, + interaction_type: 'other', + interaction_detail: '', + interaction_token: 'other|', + event_ph: 'test event', + event_source: 'agent', + }, + }) +}) + +// test distinct_id +test('distinct_id', async () => { + const event = createEvent({ event: 'test event', properties: { "$current_url":"agent backend", "distinct_id": "" } }) + const eventCopy = await processEvent(clone(event), getMeta()) + expect(eventCopy).toEqual({ + ...event, + properties: { + distinct_id: 'empty', + $current_url: 'agent backend', + distinct_id_is_empty: true, + netdata_posthog_plugin_version: netdataPluginVersion, + interaction_type: 'other', + interaction_detail: '', + interaction_token: 'other|', + event_ph: 'test event', + event_source: 'agent', + }, + }) +}) + +// test distinct_id +test('distinct_id', async () => { + const event = createEvent({ event: 'test event', properties: { "distinct_id": "123", "$current_url": "agent backend" } }) + const eventCopy = await processEvent(clone(event), getMeta()) + expect(eventCopy).toEqual({ + ...event, + properties: { + distinct_id: '123', + $current_url: 'agent backend', + distinct_id_is_empty: false, + netdata_posthog_plugin_version: netdataPluginVersion, + interaction_type: 'other', + interaction_detail: '', + interaction_token: 'other|', + event_ph: 'test event', + event_source: 'agent', + }, + }) +}) + + +// test data_testid +test('data_testid', async () => { + const eventExample = { + "event": "$autocapture", + "distinct_id": "dev-test", + "$current_url": "agent backend", + "properties": { + "$current_url": "agent backend", + "$elements": [ + { + "attr__data-testid": "date-picker::click-quick-selector::::21600", + }, + { + "attr__href": "#menu_web_log_nginx" + }, + { + "event": null, + "text": null, + "tag_name": "div", + "attr_class": [ + "bjKBDB", + "styled__ShortPick-sc-1yj3701-6" + ], + "href": null, + "attr_id": null, + "nth_child": 1, + "nth_of_type": 1, + "attributes": { + "attr__class": "styled__ShortPick-sc-1yj3701-6 bjKBDB" + }, + "order": 1 + }, + { + "event": null, + "text": "unshared", + "tag_name": "span", + "attr_class": [ + "chart-legend-bottomstyled__DimensionLabel-ltgk2z-9", + "iMmOhf" + ], + "href": null, + "attr_id": null, + "nth_child": 2, + "nth_of_type": 1, + "attributes": { + "attr__class": "chart-legend-bottomstyled__DimensionLabel-ltgk2z-9 iMmOhf" + }, + "order": 0 + }, + { + "$el_text": "unshared" + }, + { + "event": null, + "text": "unshared", + "tag_name": "span", + "attr_class": [ + "chart-legend-bottomstyled__DimensionLabel-ltgk2z-9", + "iMmOhf" + ], + "href": null, + "attr_id": null, + "nth_child": 2, + "nth_of_type": 1, + "attributes": { + "attr__class": "chart-legend-bottomstyled__DimensionLabel-ltgk2z-9 iMmOhf" + }, + "order": 0 + + }, + { + "attr__data-id": "newyork_netdata_rocks_mem_ksm", + "attr__data-legend-position": "bottom", + "attr__data-netdata": "mem.ksm", + } + ] + } + } + const event = createEvent(eventExample) + const eventCopy = await processEvent(clone(event), getMeta()) + expect(eventCopy['properties']['el_data_testid']).toEqual("date-picker::click-quick-selector::::21600") + expect(eventCopy['properties']['el_href_menu']).toEqual("#menu_web_log_nginx") + expect(eventCopy['properties']['el_text']).toEqual("unshared") + expect(eventCopy['properties']['el_data_netdata']).toEqual("mem.ksm") + expect(eventCopy['properties']['interaction_type']).toEqual("menu") +}) + +// test menu +test('menu', async () => { + const event = createEvent({ event: 'test event', properties: {"$current_url": "agent backend", "$elements":[{"attr__href": "#menu_system_submenu_cpu"}]} }) + const eventCopy = await processEvent(clone(event), getMeta()) + expect(eventCopy).toEqual({ + ...event, + properties: { + ...event.properties, + el_href: '#menu_system_submenu_cpu', + el_href_menu: '#menu_system_submenu_cpu', + el_menu: 'system', + el_submenu: 'cpu', + netdata_posthog_plugin_version: netdataPluginVersion, + interaction_type: 'submenu', + interaction_detail: '#menu_system_submenu_cpu', + interaction_token: 'submenu|#menu_system_submenu_cpu', + event_ph: 'test event', + event_source: 'agent', + }, + }) +}) + +test('processEvent does not crash with identify', async () => { + // create a random event + const event0 = createIdentify() + + // must clone the event since `processEvent` will mutate it otherwise + const event1 = await processEvent(clone(event0), getMeta()) + expect(event1).toEqual(event0) +}) + +// test config_https_available +test('config_https_available', async () => { + const event = createEvent({ event: 'test event', properties: { "config_https_available": true, "$current_url": "agent backend" } }) + const eventCopy = await processEvent(clone(event), getMeta()) + expect(eventCopy).toEqual({ + ...event, + properties: { + ...event.properties, + config_https_available: true, + netdata_posthog_plugin_version: netdataPluginVersion, + interaction_type: 'other', + interaction_detail: '', + interaction_token: 'other|', + event_ph: 'test event', + event_source: 'agent', + }, + }) +}) + +// test event_source +test('event_source', async () => { + const event = createEvent({ event: 'test event', properties: { "$current_url": "http://london3.my-netdata.io/#menu_dockerhub_submenu_status;after=-420;before=0;theme=slate" } }) + const eventCopy = await processEvent(clone(event), getMeta()) + expect(eventCopy['properties']['event_source']).toEqual("agent") + }) + +// test event_source_agent_backend +test('event_source_agent_backend', async () => { + const eventExample = { + "event": "$pageview", + "distinct_id": "dev-test", + "properties": { + "$current_url": "agent backend", + } + } + const event = createEvent(eventExample) + const eventCopy = await processEvent(clone(event), getMeta()) + expect(eventCopy['properties']['event_source']).toEqual("agent") +}) + +// test event_source_agent_frontend +test('event_source_agent_frontend', async () => { + const eventExample = { + "event": "$pageview", + "distinct_id": "dev-test", + "properties": { + "$current_url": "agent dashboard", + } + } + const event = createEvent(eventExample) + const eventCopy = await processEvent(clone(event), getMeta()) + expect(eventCopy['properties']['event_source']).toEqual("agent") +}) + +// test el_name +test('el_name', async () => { + const eventExample = { + "event": "$autocapture", + "distinct_id": "dev-test", + "properties": { + "$current_url": "agent dashboard", + "$elements": [ + { + "attr__foo": "foo" + }, + { + "attr__bar": "bar", + }, + { + "attr__data-id": "newyork_netdata_rocks_mem_ksm", + "attr__data-legend-position": "bottom", + "attr__data-netdata": "mem.ksm", + "attr__name": "foo", + } + ] + } + } + const event = createEvent(eventExample) + const eventCopy = await processEvent(clone(event), getMeta()) + expect(eventCopy['properties']['el_name']).toEqual("foo") +}) + +// test el_class +test('el_class', async () => { + const eventExample = { + "event": "$autocapture", + "distinct_id": "dev-test", + "properties": { + "$current_url": "agent dashboard", + "$elements": [ + { + "attr__foo": "foo" + }, + { + "attr__bar": "bar", + "attributes": { + "attr__aria-label": "my_aria_label", + "attr__class": "my_att_class" + }, + }, + { + "attr__class": "my_class" + } + ] + } + } + const event = createEvent(eventExample) + const eventCopy = await processEvent(clone(event), getMeta()) + expect(eventCopy['properties']['el_class']).toEqual("my_class") +}) diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/__tests__/index_agent_installer.js b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/__tests__/index_agent_installer.js new file mode 100644 index 0000000000000..0c10c6c679ada --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/__tests__/index_agent_installer.js @@ -0,0 +1,53 @@ +const { + createEvent, + createIdentify, + createPageview, + createCache, + getMeta, + resetMeta, + clone, +} = require('posthog-plugins/test/utils.js') +const { setupPlugin, processEvent } = require('../index') + +const netdataPluginVersion = '0.0.15' + +beforeEach(() => { + resetMeta({ + config: { + netdata_version: 'v1.41.0', + }, + }) +}) + +test('setupPlugin', async () => { + expect(getMeta().config.netdata_version).toEqual('v1.41.0') + await setupPlugin(getMeta()) + expect(getMeta().global.setupDone).toEqual(true) +}) + +// test event_source +test('event_source', async () => { + const event = createEvent({ event: 'test event', properties: { "$current_url":"agent installer"} }) + const eventCopy = await processEvent(clone(event), getMeta()) + expect(eventCopy['properties']['event_source']).toEqual("agent installer") +}) + +// test install_options_easy +test('install_options_easy', async () => { + const event = createEvent({ + event: 'test event', properties: { + "$current_url":"agent installer", + "install_options": "--dont-start-it --dont-wait --claim-token 3gciBd6HYGp7Z2v2fJDd6meraFoUT4QpVYcHTE253KujJPStNQhXi9cicTgEEc_mNiQNxAYtHlZNpC1a2NQz57fV6aZaa2vPvyPYw9hsv_SOfzfWxMdQ6L-PPOyM9e9N2HAVp7E --claim-rooms 22ff1e07-8e9c-41ad-b141-5bd95fbf95d1 --claim-url https://app.netdata.cloud --foo" + } + }) + const eventCopy = await processEvent(clone(event), getMeta()) + expect(eventCopy['properties']['event_source']).toEqual("agent installer") + expect(eventCopy['properties']['opt_dont_start_it']).toEqual('') + expect(eventCopy['properties']['opt_dont_wait']).toEqual('') + expect(eventCopy['properties']['opt_claim_token']).toEqual('3gciBd6HYGp7Z2v2fJDd6meraFoUT4QpVYcHTE253KujJPStNQhXi9cicTgEEc_mNiQNxAYtHlZNpC1a2NQz57fV6aZaa2vPvyPYw9hsv_SOfzfWxMdQ6L-PPOyM9e9N2HAVp7E') + expect(eventCopy['properties']['opt_claim_url']).toEqual('https://app.netdata.cloud') + expect(eventCopy['properties']['opt_foo']).toEqual('') +}) + + + diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/__tests__/index_blog.js b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/__tests__/index_blog.js new file mode 100644 index 0000000000000..3f2bab17e8726 --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/__tests__/index_blog.js @@ -0,0 +1,264 @@ +const { + createEvent, + createIdentify, + createPageview, + createCache, + getMeta, + resetMeta, + clone, +} = require('posthog-plugins/test/utils.js') +const { setupPlugin, processEvent } = require('../index') + +const netdataPluginVersion = '0.0.15' + +beforeEach(() => { + resetMeta({ + config: { + netdata_version: 'v1.41.0', + }, + }) +}) + +test('setupPlugin', async () => { + expect(getMeta().config.netdata_version).toEqual('v1.41.0') + await setupPlugin(getMeta()) + expect(getMeta().global.setupDone).toEqual(true) +}) + +// test data_testid +test('data_testid', async () => { + const eventExample = { + "event": "$autocapture", + "distinct_id": "dev-test", + "properties": { + "$current_url": "https://blog.netdata.cloud/", + "$elements": [ + { + "attr__data-testid": "date-picker::click-quick-selector::::21600", + }, + { + "attr__href": "#menu_web_log_nginx" + }, + { + "event": null, + "text": null, + "tag_name": "div", + "attr_class": [ + "bjKBDB", + "styled__ShortPick-sc-1yj3701-6" + ], + "href": null, + "attr_id": null, + "nth_child": 1, + "nth_of_type": 1, + "attributes": { + "attr__class": "styled__ShortPick-sc-1yj3701-6 bjKBDB" + }, + "order": 1 + }, + { + "event": null, + "text": "unshared", + "tag_name": "span", + "attr_class": [ + "chart-legend-bottomstyled__DimensionLabel-ltgk2z-9", + "iMmOhf" + ], + "href": null, + "attr_id": null, + "nth_child": 2, + "nth_of_type": 1, + "attributes": { + "attr__class": "chart-legend-bottomstyled__DimensionLabel-ltgk2z-9 iMmOhf" + }, + "order": 0 + }, + { + "$el_text": "unshared" + }, + { + "event": null, + "text": "unshared", + "tag_name": "span", + "attr_class": [ + "chart-legend-bottomstyled__DimensionLabel-ltgk2z-9", + "iMmOhf" + ], + "href": null, + "attr_id": null, + "nth_child": 2, + "nth_of_type": 1, + "attributes": { + "attr__class": "chart-legend-bottomstyled__DimensionLabel-ltgk2z-9 iMmOhf" + }, + "order": 0 + + }, + { + "attr__data-testid": "newyork_netdata_rocks_mem_ksm", + "attr__data-legend-position": "bottom", + "attr__data-netdata": "mem.ksm", + } + ] + } + } + const event = createEvent(eventExample) + const eventCopy = await processEvent(clone(event), getMeta()) + expect(eventCopy['properties']['event_source']).toEqual("blog") + expect(eventCopy['properties']['el_data_testid']).toEqual("date-picker::click-quick-selector::::21600") +}) + +// test data_ga +test('data_ga', async () => { + const eventExample = { + "event": "$autocapture", + "distinct_id": "dev-test", + "properties": { + "$current_url": "https://blog.netdata.cloud/", + "$elements": [ + { + "attr__data-ga": "date-picker::click-quick-selector::::21600" + }, + { + "attr__data-ga": "#menu_web_log_nginx", + }, + { + "$el_text": "unshared" + }, + { + "attr__data-id": "newyork_netdata_rocks_mem_ksm", + "attr__data-legend-position": "bottom", + "attr__data-netdata": "mem.ksm", + } + ] + } + } + const event = createEvent(eventExample) + const eventCopy = await processEvent(clone(event), getMeta()) + expect(eventCopy['properties']['event_source']).toEqual("blog") + expect(eventCopy['properties']['el_data_ga']).toEqual("date-picker::click-quick-selector::::21600") + expect(eventCopy['properties']['el_data_ga_0']).toEqual("date-picker") + expect(eventCopy['properties']['el_data_ga_1']).toEqual("click-quick-selector") + expect(eventCopy['properties']['el_data_ga_2']).toEqual("") + expect(eventCopy['properties']['el_text']).toEqual("unshared") +}) + +test('processEvent does not crash with identify', async () => { + // create a random event + const event0 = createIdentify() + + // must clone the event since `processEvent` will mutate it otherwise + const event1 = await processEvent(clone(event0), getMeta()) + expect(event1).toEqual(event0) +}) + +// test event_source_blog +test('event_source_blog', async () => { + const eventExample = { + "event": "$pageview", + "distinct_id": "dev-test", + "properties": { + "$current_url": "https://blog.netdata.cloud/", + } + } + const event = createEvent(eventExample) + const eventCopy = await processEvent(clone(event), getMeta()) + expect(eventCopy['properties']['event_source']).toEqual("blog") +}) + +// test el_name +test('el_name', async () => { + const eventExample = { + "event": "$autocapture", + "distinct_id": "dev-test", + "properties": { + "$current_url": "https://blog.netdata.cloud/", + "$elements": [ + { + "attr__foo": "foo" + }, + { + "attr__bar": "bar", + }, + { + "attr__data-id": "newyork_netdata_rocks_mem_ksm", + "attr__data-legend-position": "bottom", + "attr__data-netdata": "mem.ksm", + "attr__name": "foo", + } + ] + } + } + const event = createEvent(eventExample) + const eventCopy = await processEvent(clone(event), getMeta()) + expect(eventCopy['properties']['el_name']).toEqual("foo") +}) + +// test el_aria_label +test('el_aria_label', async () => { + const eventExample = { + "event": "$autocapture", + "distinct_id": "dev-test", + "properties": { + "$current_url": "https://blog.netdata.cloud/", + "$elements": [ + { + "attr__foo": "foo" + }, + { + "attr__bar": "bar", + "attributes": { + "attr__aria-label": "Docs pages navigation", + "attr__class": "pagination-nav docusaurus-mt-lg" + }, + } + ] + } + } + const event = createEvent(eventExample) + const eventCopy = await processEvent(clone(event), getMeta()) + expect(eventCopy['properties']['el_aria_label']).toEqual("Docs pages navigation") +}) + +// test el_class +test('el_class', async () => { + const eventExample = { + "event": "$autocapture", + "distinct_id": "dev-test", + "properties": { + "$current_url": "https://blog.netdata.cloud/", + "$elements": [ + { + "attr__foo": "foo" + }, + { + "attr__bar": "bar", + "attributes": { + "attr__aria-label": "Docs pages navigation", + "attr__class": "my_att_class" + }, + }, + { + "attr__class": "my_class" + } + ] + } + } + const event = createEvent(eventExample) + const eventCopy = await processEvent(clone(event), getMeta()) + expect(eventCopy['properties']['el_class']).toEqual("my_class") +}) + +// test event_source_blog_preview +test('event_source_blog_preview', async () => { + const eventExample = { + "event": "$pageview", + "distinct_id": "dev-test", + "properties": { + "$current_url": "https://deploy-preview-168--netdata-blog.netlify.app", + } + } + const event = createEvent(eventExample) + const eventCopy = await processEvent(clone(event), getMeta()) + expect(eventCopy['properties']['event_source']).toEqual("blog_preview") +}) diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/__tests__/index_cloud.js b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/__tests__/index_cloud.js new file mode 100644 index 0000000000000..ac59449931783 --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/__tests__/index_cloud.js @@ -0,0 +1,361 @@ +const { + createEvent, + createIdentify, + createPageview, + createCache, + getMeta, + resetMeta, + clone, +} = require('posthog-plugins/test/utils.js') +const { setupPlugin, processEvent } = require('../index') + +const netdataPluginVersion = '0.0.15' + +beforeEach(() => { + resetMeta({ + config: { + netdata_version: 'v1.41.0', + }, + }) +}) + +test('setupPlugin', async () => { + expect(getMeta().config.netdata_version).toEqual('v1.41.0') + await setupPlugin(getMeta()) + expect(getMeta().global.setupDone).toEqual(true) +}) + +// test data_testid +test('data_testid', async () => { + const eventExample = { + "event": "$autocapture", + "distinct_id": "dev-test", + "properties": { + "$current_url": "https://app.netdata.cloud/", + "$elements": [ + { + "attr__data-testid": "date-picker::click-quick-selector::::21600", + }, + { + "attr__href": "#menu_web_log_nginx" + }, + { + "event": null, + "text": null, + "tag_name": "div", + "attr_class": [ + "bjKBDB", + "styled__ShortPick-sc-1yj3701-6" + ], + "href": null, + "attr_id": null, + "nth_child": 1, + "nth_of_type": 1, + "attributes": { + "attr__class": "styled__ShortPick-sc-1yj3701-6 bjKBDB" + }, + "order": 1 + }, + { + "event": null, + "text": "unshared", + "tag_name": "span", + "attr_class": [ + "chart-legend-bottomstyled__DimensionLabel-ltgk2z-9", + "iMmOhf" + ], + "href": null, + "attr_id": null, + "nth_child": 2, + "nth_of_type": 1, + "attributes": { + "attr__class": "chart-legend-bottomstyled__DimensionLabel-ltgk2z-9 iMmOhf" + }, + "order": 0 + }, + { + "$el_text": "unshared" + }, + { + "event": null, + "text": "unshared", + "tag_name": "span", + "attr_class": [ + "chart-legend-bottomstyled__DimensionLabel-ltgk2z-9", + "iMmOhf" + ], + "href": null, + "attr_id": null, + "nth_child": 2, + "nth_of_type": 1, + "attributes": { + "attr__class": "chart-legend-bottomstyled__DimensionLabel-ltgk2z-9 iMmOhf" + }, + "order": 0 + + }, + { + "attr__data-testid": "newyork_netdata_rocks_mem_ksm", + "attr__data-legend-position": "bottom", + "attr__data-netdata": "mem.ksm", + } + ] + } + } + const event = createEvent(eventExample) + const eventCopy = await processEvent(clone(event), getMeta()) + expect(eventCopy['properties']['event_source']).toEqual("cloud") + expect(eventCopy['properties']['el_data_testid']).toEqual("date-picker::click-quick-selector::::21600") +}) + +// test data_ga +test('data_ga', async () => { + const eventExample = { + "event": "$autocapture", + "distinct_id": "dev-test", + "properties": { + "$current_url": "https://app.netdata.cloud/", + "$elements": [ + { + "attr__data-ga": "some-category::some-action::some-label::some-value", + }, + { + "attr__data-ga": "#menu_web_log_nginx", + }, + { + "$el_text": "unshared" + }, + { + "attr__data-ga": "date-picker::click-quick-selector::::21600", + "attr__data-id": "newyork_netdata_rocks_mem_ksm", + "attr__data-legend-position": "bottom", + "attr__data-netdata": "mem.ksm", + } + ] + } + } + const event = createEvent(eventExample) + const eventCopy = await processEvent(clone(event), getMeta()) + expect(eventCopy['properties']['event_source']).toEqual("cloud") + expect(eventCopy['properties']['el_data_ga']).toEqual("some-category::some-action::some-label::some-value") + expect(eventCopy['properties']['el_data_ga_inner']).toEqual("some-category::some-action::some-label::some-value") + expect(eventCopy['properties']['el_data_ga_outer']).toEqual("date-picker::click-quick-selector::::21600") + expect(eventCopy['properties']['el_data_ga_0']).toEqual("some-category") + expect(eventCopy['properties']['el_data_ga_1']).toEqual("some-action") + expect(eventCopy['properties']['el_data_ga_2']).toEqual("some-label") + expect(eventCopy['properties']['event_category']).toEqual("some-category") + expect(eventCopy['properties']['event_action']).toEqual("some-action") + expect(eventCopy['properties']['event_label']).toEqual("some-label") + expect(eventCopy['properties']['event_value']).toEqual("some-value") + expect(eventCopy['properties']['el_text']).toEqual("unshared") +}) + +test('processEvent does not crash with identify', async () => { + // create a random event + const event0 = createIdentify() + + // must clone the event since `processEvent` will mutate it otherwise + const event1 = await processEvent(clone(event0), getMeta()) + expect(event1).toEqual(event0) +}) + +// test event_source_cloud +test('event_source_cloud', async () => { + const eventExample = { + "event": "$pageview", + "distinct_id": "dev-test", + "properties": { + "$current_url": "https://app.netdata.cloud/", + } + } + const event = createEvent(eventExample) + const eventCopy = await processEvent(clone(event), getMeta()) + expect(eventCopy['properties']['event_source']).toEqual("cloud") +}) + +// test event_source_cloud_identify +test('event_source_cloud_identify', async () => { + const eventExample = { + "event": "$identify", + "distinct_id": "dev-test", + "properties": { + } + } + const event = createEvent(eventExample) + const eventCopy = await processEvent(clone(event), getMeta()) + expect(eventCopy['properties']['event_source']).toEqual("cloud") + expect(eventCopy['properties']['event_ph']).toEqual("$identify") +}) + +// test data_track +test('data_track', async () => { + const eventExample = { + "event": "$autocapture", + "distinct_id": "dev-test", + "properties": { + "$current_url": "https://app.netdata.cloud/", + "$elements": [ + { + "attr__data-track": "foobar" + }, + { + "attr__data-track": "date-picker::click-quick-selector::::21600" + }, + { + "$el_text": "unshared" + }, + { + "attr__data-id": "newyork_netdata_rocks_mem_ksm", + "attr__data-legend-position": "bottom", + "attr__data-netdata": "mem.ksm", + } + ] + } + } + const event = createEvent(eventExample) + const eventCopy = await processEvent(clone(event), getMeta()) + expect(eventCopy['properties']['event_source']).toEqual("cloud") + expect(eventCopy['properties']['el_data_track_outer']).toEqual("date-picker::click-quick-selector::::21600") + expect(eventCopy['properties']['el_data_track_outer_0']).toEqual("date-picker") + expect(eventCopy['properties']['el_data_track_outer_1']).toEqual("click-quick-selector") + expect(eventCopy['properties']['el_data_track_outer_2']).toEqual("") + expect(eventCopy['properties']['el_text']).toEqual("unshared") + expect(eventCopy['properties']['el_data_track_outer']).toEqual("date-picker::click-quick-selector::::21600") + expect(eventCopy['properties']['el_data_track_inner']).toEqual("foobar") +}) + +// test el_name +test('el_name', async () => { + const eventExample = { + "event": "$autocapture", + "distinct_id": "dev-test", + "properties": { + "$current_url": "https://app.netdata.cloud/", + "$elements": [ + { + "attr__foo": "foo" + }, + { + "attr__bar": "bar", + }, + { + "attr__data-id": "newyork_netdata_rocks_mem_ksm", + "attr__data-legend-position": "bottom", + "attr__data-netdata": "mem.ksm", + "attr__name": "foo", + } + ] + } + } + const event = createEvent(eventExample) + const eventCopy = await processEvent(clone(event), getMeta()) + expect(eventCopy['properties']['el_name']).toEqual("foo") +}) + +// test pathname +test('pathname', async () => { + const eventExample = { + "event": "$pageview", + "distinct_id": "dev-test", + "properties": { + "$current_url": "https://app.netdata.cloud/a/b/c/d", + "$pathname": "/a/b/c/d" + } + } + const event = createEvent(eventExample) + const eventCopy = await processEvent(clone(event), getMeta()) + expect(eventCopy['properties']['pathname_1']).toEqual("a") + expect(eventCopy['properties']['pathname_2']).toEqual("b") + expect(eventCopy['properties']['pathname_3']).toEqual("c") + expect(eventCopy['properties']['pathname_4']).toEqual("d") + expect(eventCopy['properties']['event_source']).toEqual("cloud") +}) + +// test pathname +test('pathname_real', async () => { + const eventExample = { + "event": "$pageview", + "distinct_id": "dev-test", + "properties": { + "$current_url": "https://app.netdata.cloud/account/sso-agent?id=e6bfbf32-e89f-11ec-a180-233f485cb8df", + "$pathname": "/account/sso-agent?id=e6bfbf32-e89f-11ec-a180-233f485cb8df" + } + } + const event = createEvent(eventExample) + const eventCopy = await processEvent(clone(event), getMeta()) + expect(eventCopy['properties']['pathname_1']).toEqual("account") + expect(eventCopy['properties']['pathname_2']).toEqual("sso-agent?id=e6bfbf32-e89f-11ec-a180-233f485cb8df") + expect(eventCopy['properties']['event_source']).toEqual("cloud") + +}) + +// test el_class +test('el_class', async () => { + const eventExample = { + "event": "$autocapture", + "distinct_id": "dev-test", + "properties": { + "$current_url": "https://app.netdata.cloud/", + "$elements": [ + { + "attr__foo": "foo" + }, + { + "attr__bar": "bar", + "attributes": { + "attr__aria-label": "my_aria_label", + "attr__class": "my_att_class" + }, + }, + { + "attr__class": "my_class" + } + ] + } + } + const event = createEvent(eventExample) + const eventCopy = await processEvent(clone(event), getMeta()) + expect(eventCopy['properties']['el_class']).toEqual("my_class") +}) + +// test cloud_agent_19999 +test('cloud_agent_19999', async () => { + const eventExample = { + "event": "$pageview", + "distinct_id": "dev-test", + "properties": { + "$current_url": "https://10.10.10.10:19999/spaces/foo", + } + } + const event = createEvent(eventExample) + const eventCopy = await processEvent(clone(event), getMeta()) + expect(eventCopy['properties']['event_source']).toEqual("cloud_agent") +}) + +// test cloud_agent_spaces +test('cloud_agent_spaces', async () => { + const eventExample = { + "event": "$pageview", + "distinct_id": "dev-test", + "properties": { + "$current_url": "https://some.netdata/spaces/foo", + } + } + const event = createEvent(eventExample) + const eventCopy = await processEvent(clone(event), getMeta()) + expect(eventCopy['properties']['event_source']).toEqual("cloud_agent") +}) + +// test cloud_spaces +test('cloud_agent_spaces', async () => { + const eventExample = { + "event": "$pageview", + "distinct_id": "dev-test", + "properties": { + "$current_url": "https://app.netdata.cloud/spaces/foobar", + } + } + const event = createEvent(eventExample) + const eventCopy = await processEvent(clone(event), getMeta()) + expect(eventCopy['properties']['event_source']).toEqual("cloud") +}) diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/__tests__/index_community.js b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/__tests__/index_community.js new file mode 100644 index 0000000000000..6d086b0b31186 --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/__tests__/index_community.js @@ -0,0 +1,224 @@ +const { + createEvent, + createIdentify, + createPageview, + createCache, + getMeta, + resetMeta, + clone, +} = require('posthog-plugins/test/utils.js') +const { setupPlugin, processEvent } = require('../index') + +const netdataPluginVersion = '0.0.15' + +beforeEach(() => { + resetMeta({ + config: { + netdata_version: 'v1.41.0', + }, + }) +}) + +test('setupPlugin', async () => { + expect(getMeta().config.netdata_version).toEqual('v1.41.0') + await setupPlugin(getMeta()) + expect(getMeta().global.setupDone).toEqual(true) +}) + +// test data_testid +test('data_testid', async () => { + const eventExample = { + "event": "$autocapture", + "distinct_id": "dev-test", + "properties": { + "$current_url": "https://community.netdata.cloud/", + "$elements": [ + { + "attr__data-testid": "date-picker::click-quick-selector::::21600", + }, + { + "attr__href": "#menu_web_log_nginx" + }, + { + "event": null, + "text": null, + "tag_name": "div", + "attr_class": [ + "bjKBDB", + "styled__ShortPick-sc-1yj3701-6" + ], + "href": null, + "attr_id": null, + "nth_child": 1, + "nth_of_type": 1, + "attributes": { + "attr__class": "styled__ShortPick-sc-1yj3701-6 bjKBDB" + }, + "order": 1 + }, + { + "event": null, + "text": "unshared", + "tag_name": "span", + "attr_class": [ + "chart-legend-bottomstyled__DimensionLabel-ltgk2z-9", + "iMmOhf" + ], + "href": null, + "attr_id": null, + "nth_child": 2, + "nth_of_type": 1, + "attributes": { + "attr__class": "chart-legend-bottomstyled__DimensionLabel-ltgk2z-9 iMmOhf" + }, + "order": 0 + }, + { + "$el_text": "unshared" + }, + { + "event": null, + "text": "unshared", + "tag_name": "span", + "attr_class": [ + "chart-legend-bottomstyled__DimensionLabel-ltgk2z-9", + "iMmOhf" + ], + "href": null, + "attr_id": null, + "nth_child": 2, + "nth_of_type": 1, + "attributes": { + "attr__class": "chart-legend-bottomstyled__DimensionLabel-ltgk2z-9 iMmOhf" + }, + "order": 0 + + }, + { + "attr__data-testid": "newyork_netdata_rocks_mem_ksm", + "attr__data-legend-position": "bottom", + "attr__data-netdata": "mem.ksm", + } + ] + } + } + const event = createEvent(eventExample) + const eventCopy = await processEvent(clone(event), getMeta()) + expect(eventCopy['properties']['event_source']).toEqual("community") + expect(eventCopy['properties']['el_data_testid']).toEqual("date-picker::click-quick-selector::::21600") +}) + +// test data_ga +test('data_ga', async () => { + const eventExample = { + "event": "$autocapture", + "distinct_id": "dev-test", + "properties": { + "$current_url": "https://community.netdata.cloud/", + "$elements": [ + { + "attr__data-ga": "date-picker::click-quick-selector::::21600" + }, + { + "attr__data-ga": "#menu_web_log_nginx", + }, + { + "$el_text": "unshared" + }, + { + "attr__data-id": "newyork_netdata_rocks_mem_ksm", + "attr__data-legend-position": "bottom", + "attr__data-netdata": "mem.ksm", + } + ] + } + } + const event = createEvent(eventExample) + const eventCopy = await processEvent(clone(event), getMeta()) + expect(eventCopy['properties']['event_source']).toEqual("community") + expect(eventCopy['properties']['el_data_ga']).toEqual("date-picker::click-quick-selector::::21600") + expect(eventCopy['properties']['el_data_ga_0']).toEqual("date-picker") + expect(eventCopy['properties']['el_data_ga_1']).toEqual("click-quick-selector") + expect(eventCopy['properties']['el_data_ga_2']).toEqual("") + expect(eventCopy['properties']['el_text']).toEqual("unshared") +}) + +test('processEvent does not crash with identify', async () => { + // create a random event + const event0 = createIdentify() + + // must clone the event since `processEvent` will mutate it otherwise + const event1 = await processEvent(clone(event0), getMeta()) + expect(event1).toEqual(event0) +}) + +// test event_source_community +test('event_source_community', async () => { + const eventExample = { + "event": "$pageview", + "distinct_id": "dev-test", + "properties": { + "$current_url": "https://community.netdata.cloud/", + } + } + const event = createEvent(eventExample) + const eventCopy = await processEvent(clone(event), getMeta()) + expect(eventCopy['properties']['event_source']).toEqual("community") +}) + +// test el_name +test('el_name', async () => { + const eventExample = { + "event": "$autocapture", + "distinct_id": "dev-test", + "properties": { + "$current_url": "https://community.netdata.cloud/", + "$elements": [ + { + "attr__foo": "foo" + }, + { + "attr__bar": "bar", + }, + { + "attr__data-id": "newyork_netdata_rocks_mem_ksm", + "attr__data-legend-position": "bottom", + "attr__data-netdata": "mem.ksm", + "attr__name": "foo", + } + ] + } + } + const event = createEvent(eventExample) + const eventCopy = await processEvent(clone(event), getMeta()) + expect(eventCopy['properties']['el_name']).toEqual("foo") +}) + +// test el_class +test('el_class', async () => { + const eventExample = { + "event": "$autocapture", + "distinct_id": "dev-test", + "properties": { + "$current_url": "https://community.netdata.cloud/", + "$elements": [ + { + "attr__foo": "foo" + }, + { + "attr__bar": "bar", + "attributes": { + "attr__aria-label": "my_aria_label", + "attr__class": "my_att_class" + }, + }, + { + "attr__class": "my_class" + } + ] + } + } + const event = createEvent(eventExample) + const eventCopy = await processEvent(clone(event), getMeta()) + expect(eventCopy['properties']['el_class']).toEqual("my_class") +}) diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/__tests__/index_learn.js b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/__tests__/index_learn.js new file mode 100644 index 0000000000000..25bfca3bfd9a5 --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/__tests__/index_learn.js @@ -0,0 +1,264 @@ +const { + createEvent, + createIdentify, + createPageview, + createCache, + getMeta, + resetMeta, + clone, +} = require('posthog-plugins/test/utils.js') +const { setupPlugin, processEvent } = require('../index') + +const netdataPluginVersion = '0.0.15' + +beforeEach(() => { + resetMeta({ + config: { + netdata_version: 'v1.41.0', + }, + }) +}) + +test('setupPlugin', async () => { + expect(getMeta().config.netdata_version).toEqual('v1.41.0') + await setupPlugin(getMeta()) + expect(getMeta().global.setupDone).toEqual(true) +}) + +// test data_testid +test('data_testid', async () => { + const eventExample = { + "event": "$autocapture", + "distinct_id": "dev-test", + "properties": { + "$current_url": "https://learn.netdata.cloud/", + "$elements": [ + { + "attr__data-testid": "date-picker::click-quick-selector::::21600", + }, + { + "attr__href": "#menu_web_log_nginx" + }, + { + "event": null, + "text": null, + "tag_name": "div", + "attr_class": [ + "bjKBDB", + "styled__ShortPick-sc-1yj3701-6" + ], + "href": null, + "attr_id": null, + "nth_child": 1, + "nth_of_type": 1, + "attributes": { + "attr__class": "styled__ShortPick-sc-1yj3701-6 bjKBDB" + }, + "order": 1 + }, + { + "event": null, + "text": "unshared", + "tag_name": "span", + "attr_class": [ + "chart-legend-bottomstyled__DimensionLabel-ltgk2z-9", + "iMmOhf" + ], + "href": null, + "attr_id": null, + "nth_child": 2, + "nth_of_type": 1, + "attributes": { + "attr__class": "chart-legend-bottomstyled__DimensionLabel-ltgk2z-9 iMmOhf" + }, + "order": 0 + }, + { + "$el_text": "unshared" + }, + { + "event": null, + "text": "unshared", + "tag_name": "span", + "attr_class": [ + "chart-legend-bottomstyled__DimensionLabel-ltgk2z-9", + "iMmOhf" + ], + "href": null, + "attr_id": null, + "nth_child": 2, + "nth_of_type": 1, + "attributes": { + "attr__class": "chart-legend-bottomstyled__DimensionLabel-ltgk2z-9 iMmOhf" + }, + "order": 0 + + }, + { + "attr__data-testid": "newyork_netdata_rocks_mem_ksm", + "attr__data-legend-position": "bottom", + "attr__data-netdata": "mem.ksm", + } + ] + } + } + const event = createEvent(eventExample) + const eventCopy = await processEvent(clone(event), getMeta()) + expect(eventCopy['properties']['event_source']).toEqual("learn") + expect(eventCopy['properties']['el_data_testid']).toEqual("date-picker::click-quick-selector::::21600") +}) + +// test data_ga +test('data_ga', async () => { + const eventExample = { + "event": "$autocapture", + "distinct_id": "dev-test", + "properties": { + "$current_url": "https://learn.netdata.cloud/", + "$elements": [ + { + "attr__data-ga": "date-picker::click-quick-selector::::21600" + }, + { + "attr__data-ga": "#menu_web_log_nginx", + }, + { + "$el_text": "unshared" + }, + { + "attr__data-id": "newyork_netdata_rocks_mem_ksm", + "attr__data-legend-position": "bottom", + "attr__data-netdata": "mem.ksm", + } + ] + } + } + const event = createEvent(eventExample) + const eventCopy = await processEvent(clone(event), getMeta()) + expect(eventCopy['properties']['event_source']).toEqual("learn") + expect(eventCopy['properties']['el_data_ga']).toEqual("date-picker::click-quick-selector::::21600") + expect(eventCopy['properties']['el_data_ga_0']).toEqual("date-picker") + expect(eventCopy['properties']['el_data_ga_1']).toEqual("click-quick-selector") + expect(eventCopy['properties']['el_data_ga_2']).toEqual("") + expect(eventCopy['properties']['el_text']).toEqual("unshared") +}) + +test('processEvent does not crash with identify', async () => { + // create a random event + const event0 = createIdentify() + + // must clone the event since `processEvent` will mutate it otherwise + const event1 = await processEvent(clone(event0), getMeta()) + expect(event1).toEqual(event0) +}) + +// test event_source_learn +test('event_source_learn', async () => { + const eventExample = { + "event": "$pageview", + "distinct_id": "dev-test", + "properties": { + "$current_url": "https://learn.netdata.cloud/", + } + } + const event = createEvent(eventExample) + const eventCopy = await processEvent(clone(event), getMeta()) + expect(eventCopy['properties']['event_source']).toEqual("learn") +}) + +// test el_name +test('el_name', async () => { + const eventExample = { + "event": "$autocapture", + "distinct_id": "dev-test", + "properties": { + "$current_url": "https://learn.netdata.cloud/", + "$elements": [ + { + "attr__foo": "foo" + }, + { + "attr__bar": "bar", + }, + { + "attr__data-id": "newyork_netdata_rocks_mem_ksm", + "attr__data-legend-position": "bottom", + "attr__data-netdata": "mem.ksm", + "attr__name": "foo", + } + ] + } + } + const event = createEvent(eventExample) + const eventCopy = await processEvent(clone(event), getMeta()) + expect(eventCopy['properties']['el_name']).toEqual("foo") +}) + +// test el_aria_label +test('el_aria_label', async () => { + const eventExample = { + "event": "$autocapture", + "distinct_id": "dev-test", + "properties": { + "$current_url": "https://learn.netdata.cloud/", + "$elements": [ + { + "attr__foo": "foo" + }, + { + "attr__bar": "bar", + "attributes": { + "attr__aria-label": "Docs pages navigation", + "attr__class": "pagination-nav docusaurus-mt-lg" + }, + } + ] + } + } + const event = createEvent(eventExample) + const eventCopy = await processEvent(clone(event), getMeta()) + expect(eventCopy['properties']['el_aria_label']).toEqual("Docs pages navigation") +}) + +// test el_class +test('el_class', async () => { + const eventExample = { + "event": "$autocapture", + "distinct_id": "dev-test", + "properties": { + "$current_url": "https://learn.netdata.cloud/", + "$elements": [ + { + "attr__foo": "foo" + }, + { + "attr__bar": "bar", + "attributes": { + "attr__aria-label": "Docs pages navigation", + "attr__class": "my_att_class" + }, + }, + { + "attr__class": "my_class" + } + ] + } + } + const event = createEvent(eventExample) + const eventCopy = await processEvent(clone(event), getMeta()) + expect(eventCopy['properties']['el_class']).toEqual("my_class") +}) + +// test event_source_learn_preview +test('event_source_learn_preview', async () => { + const eventExample = { + "event": "$pageview", + "distinct_id": "dev-test", + "properties": { + "$current_url": "https://deploy-preview-1058--netdata-docusaurus.netlify.app/", + } + } + const event = createEvent(eventExample) + const eventCopy = await processEvent(clone(event), getMeta()) + expect(eventCopy['properties']['event_source']).toEqual("learn_preview") +}) diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/__tests__/index_staging.js b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/__tests__/index_staging.js new file mode 100644 index 0000000000000..3f2a4213f2f16 --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/__tests__/index_staging.js @@ -0,0 +1,97 @@ +const { + createEvent, + createIdentify, + createPageview, + createCache, + getMeta, + resetMeta, + clone, +} = require('posthog-plugins/test/utils.js') +const { setupPlugin, processEvent } = require('../index') + +const netdataPluginVersion = '0.0.15' + +beforeEach(() => { + resetMeta({ + config: { + netdata_version: 'v1.29.2', + }, + }) +}) + +test('setupPlugin', async () => { + expect(getMeta().config.netdata_version).toEqual('v1.29.2') + await setupPlugin(getMeta()) + expect(getMeta().global.setupDone).toEqual(true) +}) + +// test event_source_staging +test('event_source_staging', async () => { + const eventExample = { + "event": "$pageview", + "distinct_id": "dev-test", + "properties": { + "$current_url": "https://staging.netdata.cloud/", + } + } + const event = createEvent(eventExample) + const eventCopy = await processEvent(clone(event), getMeta()) + expect(eventCopy['properties']['event_source']).toEqual("staging") +}) + +// test el_name +test('el_name', async () => { + const eventExample = { + "event": "$autocapture", + "distinct_id": "dev-test", + "properties": { + "$current_url": "https://staging.netdata.cloud/", + "$elements": [ + { + "attr__foo": "foo" + }, + { + "attr__bar": "bar", + }, + { + "attr__data-id": "newyork_netdata_rocks_mem_ksm", + "attr__data-legend-position": "bottom", + "attr__data-netdata": "mem.ksm", + "attr__name": "foo", + } + ] + } + } + const event = createEvent(eventExample) + const eventCopy = await processEvent(clone(event), getMeta()) + expect(eventCopy['properties']['el_name']).toEqual("foo") +}) + +// test el_class +test('el_class', async () => { + const eventExample = { + "event": "$autocapture", + "distinct_id": "dev-test", + "properties": { + "$current_url": "https://staging.netdata.cloud/", + "$elements": [ + { + "attr__foo": "foo" + }, + { + "attr__bar": "bar", + "attributes": { + "attr__aria-label": "my_aria_label", + "attr__class": "my_att_class" + }, + }, + { + "attr__class": "my_class" + } + ] + } + } + const event = createEvent(eventExample) + const eventCopy = await processEvent(clone(event), getMeta()) + expect(eventCopy['properties']['el_class']).toEqual("my_class") +}) diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/__tests__/index_testing.js b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/__tests__/index_testing.js new file mode 100644 index 0000000000000..baa6752da9150 --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/__tests__/index_testing.js @@ -0,0 +1,97 @@ +const { + createEvent, + createIdentify, + createPageview, + createCache, + getMeta, + resetMeta, + clone, +} = require('posthog-plugins/test/utils.js') +const { setupPlugin, processEvent } = require('../index') + +const netdataPluginVersion = '0.0.15' + +beforeEach(() => { + resetMeta({ + config: { + netdata_version: 'v1.29.2', + }, + }) +}) + +test('setupPlugin', async () => { + expect(getMeta().config.netdata_version).toEqual('v1.29.2') + await setupPlugin(getMeta()) + expect(getMeta().global.setupDone).toEqual(true) +}) + +// test event_source_testing +test('event_source_testing', async () => { + const eventExample = { + "event": "$pageview", + "distinct_id": "dev-test", + "properties": { + "$current_url": "https://testing.netdata.cloud/", + } + } + const event = createEvent(eventExample) + const eventCopy = await processEvent(clone(event), getMeta()) + expect(eventCopy['properties']['event_source']).toEqual("testing") +}) + +// test el_name +test('el_name', async () => { + const eventExample = { + "event": "$autocapture", + "distinct_id": "dev-test", + "properties": { + "$current_url": "https://testing.netdata.cloud/", + "$elements": [ + { + "attr__foo": "foo" + }, + { + "attr__bar": "bar", + }, + { + "attr__data-id": "newyork_netdata_rocks_mem_ksm", + "attr__data-legend-position": "bottom", + "attr__data-netdata": "mem.ksm", + "attr__name": "foo", + } + ] + } + } + const event = createEvent(eventExample) + const eventCopy = await processEvent(clone(event), getMeta()) + expect(eventCopy['properties']['el_name']).toEqual("foo") +}) + +// test el_class +test('el_class', async () => { + const eventExample = { + "event": "$autocapture", + "distinct_id": "dev-test", + "properties": { + "$current_url": "https://testing.netdata.cloud/", + "$elements": [ + { + "attr__foo": "foo" + }, + { + "attr__bar": "bar", + "attributes": { + "attr__aria-label": "my_aria_label", + "attr__class": "my_att_class" + }, + }, + { + "attr__class": "my_class" + } + ] + } + } + const event = createEvent(eventExample) + const eventCopy = await processEvent(clone(event), getMeta()) + expect(eventCopy['properties']['el_class']).toEqual("my_class") +}) diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/__tests__/index_website.js b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/__tests__/index_website.js new file mode 100644 index 0000000000000..841bc44b5d0bd --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/__tests__/index_website.js @@ -0,0 +1,238 @@ +const { + createEvent, + createIdentify, + createPageview, + createCache, + getMeta, + resetMeta, + clone, +} = require('posthog-plugins/test/utils.js') +const { setupPlugin, processEvent } = require('../index') + +const netdataPluginVersion = '0.0.15' + +beforeEach(() => { + resetMeta({ + config: { + netdata_version: 'v1.29.2', + }, + }) +}) + +test('setupPlugin', async () => { + expect(getMeta().config.netdata_version).toEqual('v1.29.2') + await setupPlugin(getMeta()) + expect(getMeta().global.setupDone).toEqual(true) +}) + +// test data_testid +test('data_testid', async () => { + const eventExample = { + "event": "$autocapture", + "distinct_id": "dev-test", + "properties": { + "$current_url": "https://www.netdata.cloud/", + "$elements": [ + { + "attr__data-testid": "date-picker::click-quick-selector::::21600", + }, + { + "attr__href": "#menu_web_log_nginx" + }, + { + "event": null, + "text": null, + "tag_name": "div", + "attr_class": [ + "bjKBDB", + "styled__ShortPick-sc-1yj3701-6" + ], + "href": null, + "attr_id": null, + "nth_child": 1, + "nth_of_type": 1, + "attributes": { + "attr__class": "styled__ShortPick-sc-1yj3701-6 bjKBDB" + }, + "order": 1 + }, + { + "event": null, + "text": "unshared", + "tag_name": "span", + "attr_class": [ + "chart-legend-bottomstyled__DimensionLabel-ltgk2z-9", + "iMmOhf" + ], + "href": null, + "attr_id": null, + "nth_child": 2, + "nth_of_type": 1, + "attributes": { + "attr__class": "chart-legend-bottomstyled__DimensionLabel-ltgk2z-9 iMmOhf" + }, + "order": 0 + }, + { + "$el_text": "unshared" + }, + { + "event": null, + "text": "unshared", + "tag_name": "span", + "attr_class": [ + "chart-legend-bottomstyled__DimensionLabel-ltgk2z-9", + "iMmOhf" + ], + "href": null, + "attr_id": null, + "nth_child": 2, + "nth_of_type": 1, + "attributes": { + "attr__class": "chart-legend-bottomstyled__DimensionLabel-ltgk2z-9 iMmOhf" + }, + "order": 0 + + }, + { + "attr__data-testid": "newyork_netdata_rocks_mem_ksm", + "attr__data-legend-position": "bottom", + "attr__data-netdata": "mem.ksm", + } + ] + } + } + const event = createEvent(eventExample) + const eventCopy = await processEvent(clone(event), getMeta()) + expect(eventCopy['properties']['event_source']).toEqual("website") + expect(eventCopy['properties']['el_data_testid']).toEqual("date-picker::click-quick-selector::::21600") +}) + +// test data_ga +test('data_ga', async () => { + const eventExample = { + "event": "$autocapture", + "distinct_id": "dev-test", + "properties": { + "$current_url": "https://www.netdata.cloud/", + "$elements": [ + { + "attr__data-ga": "date-picker::click-quick-selector::::21600" + }, + { + "attr__data-ga": "#menu_web_log_nginx", + }, + { + "$el_text": "unshared" + }, + { + "attr__data-id": "newyork_netdata_rocks_mem_ksm", + "attr__data-legend-position": "bottom", + "attr__data-netdata": "mem.ksm", + } + ] + } + } + const event = createEvent(eventExample) + const eventCopy = await processEvent(clone(event), getMeta()) + expect(eventCopy['properties']['event_source']).toEqual("website") + expect(eventCopy['properties']['el_data_ga']).toEqual("date-picker::click-quick-selector::::21600") + expect(eventCopy['properties']['el_data_ga_0']).toEqual("date-picker") + expect(eventCopy['properties']['el_data_ga_1']).toEqual("click-quick-selector") + expect(eventCopy['properties']['el_data_ga_2']).toEqual("") + expect(eventCopy['properties']['el_text']).toEqual("unshared") +}) + +test('processEvent does not crash with identify', async () => { + // create a random event + const event0 = createIdentify() + + // must clone the event since `processEvent` will mutate it otherwise + const event1 = await processEvent(clone(event0), getMeta()) + expect(event1).toEqual(event0) +}) + +// test event_source_website +test('event_source_website', async () => { + const eventExample = { + "event": "$pageview", + "distinct_id": "dev-test", + "properties": { + "$current_url": "https://www.netdata.cloud/", + } + } + const event = createEvent(eventExample) + const eventCopy = await processEvent(clone(event), getMeta()) + expect(eventCopy['properties']['event_source']).toEqual("website") +}) + +// test el_name +test('el_name', async () => { + const eventExample = { + "event": "$autocapture", + "distinct_id": "dev-test", + "properties": { + "$current_url": "https://www.netdata.cloud/", + "$elements": [ + { + "attr__foo": "foo" + }, + { + "attr__bar": "bar", + }, + { + "attr__data-id": "newyork_netdata_rocks_mem_ksm", + "attr__data-legend-position": "bottom", + "attr__data-netdata": "mem.ksm", + "attr__name": "foo", + } + ] + } + } + const event = createEvent(eventExample) + const eventCopy = await processEvent(clone(event), getMeta()) + expect(eventCopy['properties']['el_name']).toEqual("foo") +}) + +// test el_class +test('el_class', async () => { + const eventExample = { + "event": "$autocapture", + "distinct_id": "dev-test", + "properties": { + "$current_url": "https://www.netdata.cloud/", + "$elements": [ + { + "attr__foo": "foo" + }, + { + "attr__bar": "bar", + "attributes": { + "attr__aria-label": "my_aria_label", + "attr__class": "my_att_class" + }, + }, + { + "attr__class": "my_class" + } + ] + } + } + const event = createEvent(eventExample) + const eventCopy = await processEvent(clone(event), getMeta()) + expect(eventCopy['properties']['el_class']).toEqual("my_class") +}) + +// test event_source_website_preview +test('event_source_website_preview', async () => { + const eventExample = { + "event": "$pageview", + "distinct_id": "dev-test", + "properties": { + "$current_url": "https://deploy-preview-167--netdata-website.netlify.app/", + } + } + const event = createEvent(eventExample) + const eventCopy = await processEvent(clone(event), getMeta()) + expect(eventCopy['properties']['event_source']).toEqual("website_preview") +}) diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/index.js b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/index.js new file mode 100644 index 0000000000000..caf12a4c998f7 --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/index.js @@ -0,0 +1,2036 @@ +function cleanPropertyName(k) { + return k + // convert to lower case + .toLowerCase() + // remove leading slash + .replace(/^\//, "") + // replace all slashes and dots with _ + .replace(/\/|\.|-| /g, "_") + ; +} + +function isStringDDMMYYYYHHMM(dt){ + var reDate = /^((0?[1-9]|[12][0-9]|3[01])[- /.](0?[1-9]|1[012])[- /.](19|20)?[0-9]{2}[ ][012][0-9][:][0-9]{2})*$/; + return reDate.test(dt); +} + +function isDemo(url) { + if ( + (url.includes('://london.my-netdata.io')) + || + (url.includes('://london3.my-netdata.io')) + || + (url.includes('://cdn77.my-netdata.io')) + || + (url.includes('://octopuscs.my-netdata.io')) + || + (url.includes('://bangalore.my-netdata.io')) + || + (url.includes('://frankfurt.my-netdata.io')) + || + (url.includes('://newyork.my-netdata.io')) + || + (url.includes('://sanfrancisco.my-netdata.io')) + || + (url.includes('://singapore.my-netdata.io')) + || + (url.includes('://toronto.my-netdata.io')) + ){ + return true + } else { + return false + } +} + +function splitPathName(event) { + if (event.properties['$pathname']) { + event.properties["$pathname"].split("/").forEach((pathname, index) => { + if ((pathname !== "") && (pathname !== null)){ + event.properties[`pathname_${index}`] = pathname; + } + }); + } + return event +} + +function processElementsAgent(event) { + // extract properties from elements + if (event.properties['$elements']) { + + // process each element, last (outermost) first. + event.properties['$elements'].slice().forEach((element) => { + + // el_data_testid_outer + if ('attr__data-testid' in element) { + event.properties['el_data_testid_outer'] = element['attr__data-testid']; + + // el_data_testid_outer_0 + if (element['attr__data-testid'].includes('::')) { + arr = element['attr__data-testid'].split('::'); + event.properties['el_data_testid_outer_0'] = arr[0]; + event.properties['el_data_testid_outer_1'] = arr[1]; + event.properties['el_data_testid_outer_2'] = arr[2]; + event.properties['el_data_testid_outer_3'] = arr[3]; + event.properties['el_data_testid_outer_4'] = arr[4]; + } + + } + + // el_data_ga_outer + if ('attr__data-ga' in element) { + event.properties['el_data_ga_outer'] = element['attr__data-ga']; + + // el_data_ga_outer_0 + if (element['attr__data-ga'].includes('::')) { + arr = element['attr__data-ga'].split('::'); + event.properties['el_data_ga_outer_0'] = arr[0]; + event.properties['el_data_ga_outer_1'] = arr[1]; + event.properties['el_data_ga_outer_2'] = arr[2]; + event.properties['el_data_ga_outer_3'] = arr[3]; + event.properties['el_data_ga_outer_4'] = arr[4]; + } + + } + + // el_data_track_outer + if ('attr__data-track' in element) { + event.properties['el_data_track_outer'] = element['attr__data-track']; + + // el_data_track_outer_0 + if (element['attr__data-track'].includes('::')) { + arr = element['attr__data-track'].split('::'); + event.properties['el_data_track_outer_0'] = arr[0]; + event.properties['el_data_track_outer_1'] = arr[1]; + event.properties['el_data_track_outer_2'] = arr[2]; + event.properties['el_data_track_outer_3'] = arr[3]; + event.properties['el_data_track_outer_4'] = arr[4]; + } + + } + + }); + + // process each element, reverse to use posthog order as preference + event.properties['$elements'].slice().reverse().forEach((element) => { + + // el_data_testid + if ('attr__data-testid' in element) { + event.properties['el_data_testid'] = element['attr__data-testid']; + + // el_data_testid_0 + if (element['attr__data-testid'].includes('::')) { + arr = element['attr__data-testid'].split('::'); + event.properties['el_data_testid_0'] = arr[0]; + event.properties['el_data_testid_1'] = arr[1]; + event.properties['el_data_testid_2'] = arr[2]; + event.properties['el_data_testid_3'] = arr[3]; + event.properties['el_data_testid_4'] = arr[4]; + } + + } + + // el_data_ga + if ('attr__data-ga' in element) { + event.properties['el_data_ga'] = element['attr__data-ga']; + + // el_data_ga_0 + if (element['attr__data-ga'].includes('::')) { + arr = element['attr__data-ga'].split('::'); + event.properties['el_data_ga_0'] = arr[0]; + event.properties['el_data_ga_1'] = arr[1]; + event.properties['el_data_ga_2'] = arr[2]; + event.properties['el_data_ga_3'] = arr[3]; + event.properties['el_data_ga_4'] = arr[4]; + } + + } + + // el_data_testid_inner + if ('attr__data-testid' in element) { + event.properties['el_data_testid_inner'] = element['attr__data-testid']; + + // el_data_testid_inner_0 + if (element['attr__data-testid'].includes('::')) { + arr = element['attr__data-testid'].split('::'); + event.properties['el_data_testid_inner_0'] = arr[0]; + event.properties['el_data_testid_inner_1'] = arr[1]; + event.properties['el_data_testid_inner_2'] = arr[2]; + event.properties['el_data_testid_inner_3'] = arr[3]; + event.properties['el_data_testid_inner_4'] = arr[4]; + } + + } + + // el_data_ga_inner + if ('attr__data-ga' in element) { + event.properties['el_data_ga_inner'] = element['attr__data-ga']; + + // el_data_ga_inner_0 + if (element['attr__data-ga'].includes('::')) { + arr = element['attr__data-ga'].split('::'); + event.properties['el_data_ga_inner_0'] = arr[0]; + event.properties['el_data_ga_inner_1'] = arr[1]; + event.properties['el_data_ga_inner_2'] = arr[2]; + event.properties['el_data_ga_inner_3'] = arr[3]; + event.properties['el_data_ga_inner_4'] = arr[4]; + } + + } + + // el_data_track_inner + if ('attr__data-track' in element) { + event.properties['el_data_track_inner'] = element['attr__data-track']; + + // el_data_track_inner_0 + if (element['attr__data-track'].includes('::')) { + arr = element['attr__data-track'].split('::'); + event.properties['el_data_track_inner_0'] = arr[0]; + event.properties['el_data_track_inner_1'] = arr[1]; + event.properties['el_data_track_inner_2'] = arr[2]; + event.properties['el_data_track_inner_3'] = arr[3]; + event.properties['el_data_track_inner_4'] = arr[4]; + } + + } + + // el_id_menu + if ('attr__href' in element && element['attr__href'] !== null && element['attr__href'].substring(0,5) === '#menu') { + event.properties['el_href_menu'] = element['attr__href']; + event.properties['el_menu'] = element['attr__href'].split('_submenu')[0].replace('#menu_', ''); + if (element['attr__href'].includes('_submenu_')) { + event.properties['el_submenu'] = element['attr__href'].split('_submenu_')[1]; + } else { + event.properties['el_submenu'] = ''; + } + } + + // el_href + if ('attr__href' in element && element['attr__href'] !== null) { + event.properties['el_href'] = element['attr__href']; + } else if ('href' in element && element['href'] !== null) { + event.properties['el_href'] = element['href']; + } else if ('$href' in element && element['$href'] !== null) { + event.properties['el_href'] = element['$href']; + } + + // el_onclick + if ('attr__onclick' in element && element['attr__onclick'] !== null) { + event.properties['el_onclick'] = element['attr__onclick']; + } + + // el_id + if ('attr__id' in element && element['attr__id'] !== null) { + event.properties['el_id'] = element['attr__id']; + } + + // el_name + if ('attr__name' in element && element['attr__name'] !== null) { + event.properties['el_name'] = element['attr__name']; + } + + // el_title + if ('attr__title' in element && element['attr__title'] !== null) { + event.properties['el_title'] = element['attr__title']; + } + + // el_text + if ('$el_text' in element && element['$el_text'] !== null && element['$el_text'] !== '') { + event.properties['el_text'] = element['$el_text']; + + // el_text_datetime + if (element['$el_text'].includes('/20') && isStringDDMMYYYYHHMM(element['$el_text'])) { + dtStr = element['$el_text']; + dtStrClean = dtStr.substring(6,10).concat( + '-',dtStr.substring(3,5),'-',dtStr.substring(0,2),' ',dtStr.substring(11,16) + ); + event.properties['el_text_datetime'] = dtStrClean; + } + + } else if ('text' in element && element['text'] !== null && element['text'] !== '') { + event.properties['el_text'] = element['text']; + } + + // el_data_netdata + if ('attr__data-netdata' in element && element['attr__data-netdata'] !== null) { + event.properties['el_data_netdata'] = element['attr__data-netdata']; + } + + // el_data_target + if ('attr__data-target' in element && element['attr__data-target'] !== null && element['attr__data-target'] !== '#sidebar') { + event.properties['el_data_target'] = element['attr__data-target']; + + // el_data_target_updatemodal + if ('attr__data-target' in element && element['attr__data-target'] !== null && element['attr__data-target'] === '#updateModal') { + event.properties['el_data_target_updatemodal'] = true; + } + + } + + // el_data_id + if ('attr__data-id' in element && element['attr__data-id'] !== null) { + event.properties['el_data_id'] = element['attr__data-id']; + } + + // el_data_original_title + if ('attr__data-original-title' in element && element['attr__data-original-title'] !== null) { + event.properties['el_data_original_title'] = element['attr__data-original-title']; + } + + // el_data_toggle + if ('attr__data-toggle' in element && element['attr__data-toggle'] !== null) { + event.properties['el_data_toggle'] = element['attr__data-toggle']; + } + + // el_data-legend-position + if ('attr__data-legend-position' in element && element['attr__data-legend-position'] !== null) { + event.properties['el_data_legend_position'] = element['attr__data-legend-position']; + } + + // el_aria_controls + if ('attr__aria-controls' in element && element['attr__aria-controls'] !== null) { + event.properties['el_aria_controls'] = element['attr__aria-controls']; + } + + // el_aria_labelledby + if ('attr__aria-labelledby' in element && element['attr__aria-labelledby'] !== null) { + event.properties['el_aria_labelledby'] = element['attr__aria-labelledby']; + } + + // el_class_netdata_legend_toolbox + if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'] === 'netdata-legend-toolbox') { + event.properties['el_class_netdata_legend_toolbox'] = true; + } + + // el_class_fa_play + if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('fa-play')) { + event.properties['el_class_fa_play'] = true; + } + + // el_class_fa_backward + if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('fa-backward')) { + event.properties['el_class_fa_backward'] = true; + } + + // el_class_fa_forward + if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('fa-forward')) { + event.properties['el_class_fa_forward'] = true; + } + + // el_class_fa_plus + if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('fa-plus')) { + event.properties['el_class_fa_plus'] = true; + } + + // el_class_fa_minus + if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('fa-minus')) { + event.properties['el_class_fa_minus'] = true; + } + + // el_class_fa_sort + if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('fa-sort')) { + event.properties['el_class_fa_sort'] = true; + } + + // el_class_navbar_highlight_content + if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('navbar-highlight-content')) { + event.properties['el_class_navbar_highlight_content'] = true; + } + + // el_class_datepickercontainer + if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('DatePickerContainer')) { + event.properties['el_class_datepickercontainer'] = true; + } + + // el_class_startendcontainer + if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('StartEndContainer')) { + event.properties['el_class_startendcontainer'] = true; + } + + // el_class_pickerbtnarea + if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('PickerBtnArea')) { + event.properties['el_class_pickerbtnarea'] = true; + } + + // el_class_pickerbox + if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('PickerBox')) { + event.properties['el_class_pickerbox'] = true; + } + + // el_class_collapsablesection + if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('CollapsableSection')) { + event.properties['el_class_collapsablesection'] = true; + } + + // el_class_signinbutton + if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('SignInButton')) { + event.properties['el_class_signinbutton'] = true; + } + + // el_class_documentation_container + if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('documentation__Container')) { + event.properties['el_class_documentation_container'] = true; + } + + // el_class_utilitysection + if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('UtilitySection')) { + event.properties['el_class_utilitysection'] = true; + } + + // el_class_success + if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('success')) { + event.properties['el_class_success'] = true; + } + + // el_class_warning + if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('warning')) { + event.properties['el_class_warning'] = true; + } + + // el_class_danger + if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('danger')) { + event.properties['el_class_danger'] = true; + } + + // el_class_info + if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'] === 'info') { + event.properties['el_class_info'] = true; + } + + // el_class_pagination + if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('pagination')) { + event.properties['el_class_pagination'] = true; + } + + // el_class_page_number + if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('page-number')) { + event.properties['el_class_page_number'] = true; + } + + // el_class_export + if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('export')) { + event.properties['el_class_export'] = true; + } + + // el_class_netdata_chartblock_container + if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('netdata-chartblock-container')) { + event.properties['el_class_netdata_chartblock_container'] = true; + } + + // el_class_netdata_reset_button + if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('netdata-reset-button')) { + event.properties['el_class_netdata_reset_button'] = true; + } + + // el_class_netdata_legend_toolbox_button + if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('netdata-legend-toolbox-button')) { + event.properties['el_class_netdata_legend_toolbox_button'] = true; + } + + // el_class_netdata_legend_resize_handler + if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('netdata-legend-resize-handler')) { + event.properties['el_class_netdata_legend_resize_handler'] = true; + } + + // el_class_calendarday + if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('CalendarDay')) { + event.properties['el_class_calendarday'] = true; + } + + // el_class_daypicker + if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('DayPicker')) { + event.properties['el_class_daypicker'] = true; + } + + // el_class_daterangepicker + if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('DateRangePicker')) { + event.properties['el_class_daterangepicker'] = true; + } + + // el_id_date_picker_root + if ('attr__id' in element && element['attr__id'] !== null && element['attr__id'].includes('date-picker-root')) { + event.properties['el_id_date_picker_root'] = true; + } + + // el_id_updatemodal + if ('attr__id' in element && element['attr__id'] !== null && element['attr__id'].includes('updateModal')) { + event.properties['el_id_updatemodal'] = true; + } + + // el_class + if ('attr__class' in element && element['attr__class'] !== null) { + event.properties['el_class'] = element['attr__class']; + } + + }); + + } + + return event +} + +function getInteractionTypeAgent(event) { + + if (['$pageview', '$pageleave', '$identify', 'agent backend'].includes(event.event)) { + + return event.event.replace('$', '').replace(' ', '_') + + // menu + } else if (event.properties.hasOwnProperty('el_href_menu')) { + + return event.properties['el_href_menu'].includes('submenu') ? 'submenu' : 'menu' + + // chart_toolbox + } else if ( + event.properties.hasOwnProperty('el_class_netdata_legend_resize_handler') || + event.properties.hasOwnProperty('el_class_netdata_legend_toolbox') + ) { + + return 'chart_toolbox' + + // chart_dim + } else if ( + event.properties.hasOwnProperty('el_data_netdata') && + event.properties.hasOwnProperty('el_id') && + ( + event.properties.hasOwnProperty('el_text') || event.properties.hasOwnProperty('el_title') + ) && + event.properties['el_id'].startsWith('chart_') + ) { + + return 'chart_dim' + + // date_picker + } else if ( + event.properties['el_id'] === 'date-picker-root' || + ( + event.properties.hasOwnProperty('el_data_testid') && + event.properties['el_data_testid'].startsWith('date-picker') + ) || + event.properties.hasOwnProperty('el_class_daterangepicker') + ) { + + return 'date_picker' + + // hamburger + } else if ( + event.properties.hasOwnProperty('el_class_collapsablesection') || + event.properties['el_title'] === 'hamburger' + ) { + + return 'hamburger' + + // update + } else if ( + event.properties.hasOwnProperty('el_data_target_updatemodal') || + event.properties.hasOwnProperty('el_id_updatemodal') + ) { + + return 'update' + + // help + } else if ( + ['Need Help?', 'question'].includes(event.properties['el_title']) || + event.properties['el_data_testid'] === 'documentation-help-close' || + event.properties.hasOwnProperty('el_class_documentation_container') + ) { + + return 'help' + + // load_snapshot + } else if ( + event.properties['el_data_target'] === '#loadSnapshotModal' || + event.properties['el_id'] === 'loadSnapshotDragAndDrop' || + event.properties['el_id'] === 'loadSnapshotSelectFiles' || + event.properties['el_id'] === 'loadSnapshotModal' + ) { + + return 'load_snapshot' + + // save_snapshot + } else if ( + event.properties['el_data_target'] === '#saveSnapshotModal' || + event.properties['el_id'] === 'saveSnapshotResolutionSlider' || + event.properties['el_id'] === 'saveSnapshotExport' || + event.properties['el_id'] === 'saveSnapshotModal' || + event.properties['el_id'] === 'hiddenDownloadLinks' + ) { + + return 'save_snapshot' + + // print + } else if ( + event.properties['el_data_target'] === '#printPreflightModal' || + event.properties['el_onclick'] === 'return printPreflight(),!1' + ) { + + return 'print' + + // alarms + } else if ( + event.properties['el_data_target'] === '#alarmsModal' || + ['#alarms_all', '#alarms_log', '#alarms_active'].includes(event.properties['el_href']) || + event.properties['el_id'] === 'alarms_log_table' || + event.properties['el_id'] === 'alarms_log' || + event.properties['el_id'] === 'alarmsModal' || + event.properties['el_aria_labelledby'] === 'alarmsModalLabel' + ) { + + return 'alarms' + + // settings + } else if ( + event.properties['el_data_target'] === '#optionsModal' || + event.properties['el_id'] === 'optionsModal' || + event.properties['el_aria_labelledby'] === 'optionsModalLabel' + ) { + + return 'settings' + + // cloud + } else if (event.properties.hasOwnProperty('el_class_signinbutton')) { + + return 'cloud' + + // highlight + } else if (event.properties['el_id'] === 'navbar-highlight-content') { + + return 'highlight' + + // add_charts + } else if (event.properties['el_text'] === 'Add more charts') { + + return 'add_charts' + + // add_alarms + } else if (event.properties['el_text'] === 'Add more alarms') { + + return 'add_alarms' + + } else { + + return 'other' + + } +} + +function getInteractionDetailAgent(event) { + + // menu + if (['menu', 'submenu'].includes(event.properties['interaction_type'])) { + + return event.properties['el_href_menu'] + + // chart_toolbox + } else if (event.properties['interaction_type'] === 'chart_toolbox') { + + if (event.properties.hasOwnProperty('el_class_fa_minus')) { + return 'zoom_out' + } else if (event.properties.hasOwnProperty('el_class_fa_plus')) { + return 'zoom_in' + } else if (event.properties.hasOwnProperty('el_class_fa_backward')) { + return 'scroll_backward' + } else if (event.properties.hasOwnProperty('el_class_fa_forward')) { + return 'scroll_forward' + } else if (event.properties.hasOwnProperty('el_class_fa_sort')) { + return 'resize' + } else if (event.properties.hasOwnProperty('el_class_fa_play')) { + return 'play' + } else { + return 'other' + } + + // chart_dim + } else if (event.properties['interaction_type'] === 'chart_dim') { + + if ( + event.properties.hasOwnProperty('el_id') && + event.properties.hasOwnProperty('el_text') + ) { + return event.properties['el_data_netdata'].concat('.',event.properties['el_text']) + } else if ( + event.properties.hasOwnProperty('el_id') && + event.properties.hasOwnProperty('el_title') + ) { + return event.properties['el_data_netdata'].concat('.',event.properties['el_title']) + } else { + return 'other' + } + + // date_picker + } else if (event.properties['interaction_type'] === 'date_picker') { + + if (event.properties['el_id'] === 'date-picker-root') { + return 'open' + } else if ( + event.properties.hasOwnProperty('el_data_testid') && + event.properties['el_data_testid'].startsWith('date-picker') + ) { + if (event.properties['el_data_testid'].includes('click-quick-selector')) { + return event.properties['el_data_testid_1'].concat(' ',event.properties['el_data_testid_3']) + } else { + return event.properties['el_data_testid_1'] + } + } else if (event.properties['el_id'] === 'month_right') { + return 'month_right' + } else if (event.properties['el_id'] === 'month_left') { + return 'month_left' + } else if (event.properties.hasOwnProperty('el_class_daterangepicker')) { + return 'date_range' + } else { + return 'other' + } + + // update + } else if (event.properties['interaction_type'] === 'update') { + + if (event.properties['el_title'] === 'update') { + return 'open' + } else if (event.properties['el_text'] === 'Check Now') { + return 'check' + } else if (event.properties['el_text'] === 'Close') { + return 'close' + } else { + return 'other' + } + + // highlight + } else if (event.properties['interaction_type'] === 'highlight') { + + if (event.properties['el_onclick'] === 'urlOptions.clearHighlight();') { + return 'clear' + } else { + return 'other' + } + + // settings + } else if (event.properties['interaction_type'] === 'settings') { + + if (event.properties['el_id'] === 'root') { + return 'open' + } else if (event.properties['el_text'] === 'Close') { + return 'close' + } else if (event.properties['el_data_toggle'] === 'tab') { + return 'tab' + } else if (event.properties['el_data_toggle'] === 'toggle') { + return 'toggle' + } else { + return 'other' + } + + // alarms + } else if (event.properties['interaction_type'] === 'alarms') { + + if ( + event.properties.hasOwnProperty('el_href') && + event.properties['el_href'].includes('#alarm_all_') + ) { + return event.properties['el_text'] + } else if (event.properties.hasOwnProperty('el_class_page_number')) { + return 'page_number' + } else if (event.properties['el_id'] === 'root') { + return 'open' + } else if ( + event.properties['el_text'] === 'Active' || + event.properties['el_id'] === 'alarms_active' + ) { + return 'active' + } else if (event.properties['el_text'] === 'Log') { + return 'log' + } else if (event.properties['el_text'] === 'All') { + return 'all' + } else if ( + event.properties.hasOwnProperty('el_class_warning') && + event.properties.hasOwnProperty('el_text') + ) { + if ( + event.properties['el_text'].includes(':') || + event.properties['el_text'].includes('%') + ) { + return 'warn' + } else { + return 'warn__'.concat(event.properties['el_text']) + } + } else if ( + event.properties.hasOwnProperty('el_class_success') && + event.properties.hasOwnProperty('el_text') + ) { + if ( + event.properties['el_text'].includes(':') || + event.properties['el_text'].includes('%') + ) { + return 'norm' + } else { + return 'norm__'.concat(event.properties['el_text']) + } + } else if ( + event.properties.hasOwnProperty('el_class_danger') && + event.properties.hasOwnProperty('el_text') + ) { + if ( + event.properties['el_text'].includes(':') || + event.properties['el_text'].includes('%') + ) { + return 'crit' + } else { + return 'crit__'.concat(event.properties['el_text']) + } + } else if ( + event.properties.hasOwnProperty('el_class_info') && + event.properties.hasOwnProperty('el_text') + ) { + if ( + event.properties['el_text'].includes(':') || + event.properties['el_text'].includes('%') + ) { + return 'undef' + } else { + return 'undef__'.concat(event.properties['el_text']) + } + } else if ( + event.properties['el_text'] === 'Close' || + event.properties['el_text'] === '×' + ) { + return 'close' + } else if ( + event.properties['el_title'] === 'Refresh' && + event.properties['el_id'] === 'alarms_log' + ) { + return 'refresh_log' + } else { + return 'other' + } + + // cloud + } else if (event.properties['interaction_type'] === 'cloud') { + + if (event.properties['el_text'] === 'Sign In to Cloud') { + return 'sign_in' + } else { + return 'other' + } + + } else { + + return '' + + } +} + +function processPropertiesAgent(event) { + + event = splitPathName(event); + + // has_alarms_critical + if (typeof event.properties['alarms_critical'] === 'number') { + event.properties['has_alarms_critical'] = event.properties['alarms_critical'] > 0; + } + + // has_alarms_warning + if (typeof event.properties['alarms_warning'] === 'number') { + event.properties['has_alarms_warning'] = event.properties['alarms_warning'] > 0; + } + + // add attribute for each build info flag + if (event.properties['netdata_buildinfo']) { + [...new Set(event.properties['netdata_buildinfo'].split('|'))].forEach((buildInfo) => { + if ((buildInfo !== "") && (buildInfo !== null)){ + event.properties[`netdata_buildinfo_${cleanPropertyName(buildInfo)}`] = true; + } + }); + } + + // add attribute for each host collector + if (event.properties['host_collectors']) { + + // only process if not empty + if (event.properties['host_collectors'][0] != null) { + + // make set for both plugins and modules present + let plugins = [...new Set(event.properties['host_collectors'].map(a => a.plugin))]; + let modules = [...new Set(event.properties['host_collectors'].map(a => a.module))]; + + // add flag for each plugin + plugins.forEach((plugin) => { + if ((plugin !== "") && (plugin !== null)){ + event.properties[`host_collector_plugin_${cleanPropertyName(plugin)}`] = true; + } + }); + + // add flag for each module + modules.forEach((module) => { + if ((module !== "") && (module !== null)){ + event.properties[`host_collector_module_${cleanPropertyName(module)}`] = true; + } + }); + + } + } + + // check if netdata_machine_guid property exists + if (typeof event.properties['netdata_machine_guid'] === 'string') { + // flag if empty string + if (event.properties['netdata_machine_guid']==='') { + event.properties['netdata_machine_guid'] = 'empty'; + event.properties['netdata_machine_guid_is_empty'] = true; + } else { + event.properties['netdata_machine_guid_is_empty'] = false; + } + } + + // check if netdata_machine_guid property exists + if (typeof event.properties['netdata_person_id'] === 'string') { + // flag if empty string + if (event.properties['netdata_person_id']==='') { + event.properties['netdata_person_id'] = 'empty'; + event.properties['netdata_person_id_is_empty'] = true; + } else { + event.properties['netdata_person_id_is_empty'] = false; + } + } + + // check if $distinct_id property exists + if (typeof event.properties['distinct_id'] === 'string') { + // flag if empty string + if (event.properties['distinct_id']==='') { + event.properties['distinct_id'] = 'empty'; + event.properties['distinct_id_is_empty'] = true; + } else { + event.properties['distinct_id_is_empty'] = false; + } + } + + // interaction_type + event.properties['interaction_type'] = getInteractionTypeAgent(event); + event.properties['interaction_detail'] = getInteractionDetailAgent(event); + event.properties['interaction_token'] = event.properties['interaction_type'].concat('|',event.properties['interaction_detail']); + //if (event.event === '$autocapture' && event.properties.hasOwnProperty('interaction_token')) { + // event.event = event.properties['interaction_token'] + //} + + return event +} + +function processElementsAgentInstaller(event) { + + // placeholder for now + + return event +} + +function processPropertiesAgentInstaller(event) { + + // only process if install_options not empty + if (event.properties['install_options'] != null) { + + // make set for install options + let installOptions = [...new Set((event.properties['install_options'] + ' ').split('--'))]; + + // make flag for each option + installOptions.forEach((installOption) => { + if ((installOption !== "") && (installOption !== null)){ + let installOptionKV = installOption.split(' '); + event.properties[`opt_${cleanPropertyName(installOptionKV[0])}`] = installOptionKV[1]; + } + }); + + } + + return event +} + +function processElementsCloud(event) { + // extract properties from elements + if (event.properties['$elements']) { + + // process each element, last (outermost) first. + event.properties['$elements'].slice().forEach((element) => { + + // el_data_testid_outer + if ('attr__data-testid' in element) { + event.properties['el_data_testid_outer'] = element['attr__data-testid']; + + // el_data_testid_outer_0 + if (element['attr__data-testid'].includes('::')) { + arr = element['attr__data-testid'].split('::'); + event.properties['el_data_testid_outer_0'] = arr[0]; + event.properties['el_data_testid_outer_1'] = arr[1]; + event.properties['el_data_testid_outer_2'] = arr[2]; + event.properties['el_data_testid_outer_3'] = arr[3]; + event.properties['el_data_testid_outer_4'] = arr[4]; + } + + } + + // el_data_ga_outer + if ('attr__data-ga' in element) { + event.properties['el_data_ga_outer'] = element['attr__data-ga']; + + // el_data_ga_outer_0 + if (element['attr__data-ga'].includes('::')) { + arr = element['attr__data-ga'].split('::'); + event.properties['el_data_ga_outer_0'] = arr[0]; + event.properties['el_data_ga_outer_1'] = arr[1]; + event.properties['el_data_ga_outer_2'] = arr[2]; + event.properties['el_data_ga_outer_3'] = arr[3]; + event.properties['el_data_ga_outer_4'] = arr[4]; + } + + } + + // el_data_track_outer + if ('attr__data-track' in element) { + event.properties['el_data_track_outer'] = element['attr__data-track']; + + // el_data_track_outer_0 + if (element['attr__data-track'].includes('::')) { + arr = element['attr__data-track'].split('::'); + event.properties['el_data_track_outer_0'] = arr[0]; + event.properties['el_data_track_outer_1'] = arr[1]; + event.properties['el_data_track_outer_2'] = arr[2]; + event.properties['el_data_track_outer_3'] = arr[3]; + event.properties['el_data_track_outer_4'] = arr[4]; + } + + } + + }); + + // process each element, reverse to use posthog order as preference + event.properties['$elements'].slice().reverse().forEach((element) => { + + // el_data_testid + if ('attr__data-testid' in element) { + event.properties['el_data_testid'] = element['attr__data-testid']; + + // el_data_testid_0 + if (element['attr__data-testid'].includes('::')) { + arr = element['attr__data-testid'].split('::'); + event.properties['el_data_testid_0'] = arr[0]; + event.properties['el_data_testid_1'] = arr[1]; + event.properties['el_data_testid_2'] = arr[2]; + event.properties['el_data_testid_3'] = arr[3]; + event.properties['el_data_testid_4'] = arr[4]; + } + + } + + // el_data_ga + if ('attr__data-ga' in element) { + event.properties['el_data_ga'] = element['attr__data-ga']; + + // el_data_ga_0 + if (element['attr__data-ga'].includes('::')) { + arr = element['attr__data-ga'].split('::'); + event.properties['el_data_ga_0'] = arr[0]; + event.properties['el_data_ga_1'] = arr[1]; + event.properties['el_data_ga_2'] = arr[2]; + event.properties['el_data_ga_3'] = arr[3]; + event.properties['el_data_ga_4'] = arr[4]; + + // give nice names in posthog + event.properties['event_category'] = arr[0]; + event.properties['event_action'] = arr[1]; + event.properties['event_label'] = arr[2]; + event.properties['event_value'] = arr[3]; + } + + } + + // el_data_testid_inner + if ('attr__data-testid' in element) { + event.properties['el_data_testid_inner'] = element['attr__data-testid']; + + // el_data_testid_inner_0 + if (element['attr__data-testid'].includes('::')) { + arr = element['attr__data-testid'].split('::'); + event.properties['el_data_testid_inner_0'] = arr[0]; + event.properties['el_data_testid_inner_1'] = arr[1]; + event.properties['el_data_testid_inner_2'] = arr[2]; + event.properties['el_data_testid_inner_3'] = arr[3]; + event.properties['el_data_testid_inner_4'] = arr[4]; + } + + } + + // el_data_ga_inner + if ('attr__data-ga' in element) { + event.properties['el_data_ga_inner'] = element['attr__data-ga']; + + // el_data_ga_inner_0 + if (element['attr__data-ga'].includes('::')) { + arr = element['attr__data-ga'].split('::'); + event.properties['el_data_ga_inner_0'] = arr[0]; + event.properties['el_data_ga_inner_1'] = arr[1]; + event.properties['el_data_ga_inner_2'] = arr[2]; + event.properties['el_data_ga_inner_3'] = arr[3]; + event.properties['el_data_ga_inner_4'] = arr[4]; + } + + } + + // el_data_track_inner + if ('attr__data-track' in element) { + event.properties['el_data_track_inner'] = element['attr__data-track']; + + // el_data_track_inner_0 + if (element['attr__data-track'].includes('::')) { + arr = element['attr__data-track'].split('::'); + event.properties['el_data_track_inner_0'] = arr[0]; + event.properties['el_data_track_inner_1'] = arr[1]; + event.properties['el_data_track_inner_2'] = arr[2]; + event.properties['el_data_track_inner_3'] = arr[3]; + event.properties['el_data_track_inner_4'] = arr[4]; + } + + } + + // el_href + if ('attr__href' in element && element['attr__href'] !== null) { + event.properties['el_href'] = element['attr__href']; + } else if ('href' in element && element['href'] !== null) { + event.properties['el_href'] = element['href']; + } else if ('$href' in element && element['$href'] !== null) { + event.properties['el_href'] = element['$href']; + } + + // el_onclick + if ('attr__onclick' in element && element['attr__onclick'] !== null) { + event.properties['el_onclick'] = element['attr__onclick']; + } + + // el_id + if ('attr__id' in element && element['attr__id'] !== null) { + event.properties['el_id'] = element['attr__id']; + } + + // el_name + if ('attr__name' in element && element['attr__name'] !== null) { + event.properties['el_name'] = element['attr__name']; + } + + // el_title + if ('attr__title' in element && element['attr__title'] !== null) { + event.properties['el_title'] = element['attr__title']; + } + + // el_text + if ('$el_text' in element && element['$el_text'] !== null && element['$el_text'] !== '') { + event.properties['el_text'] = element['$el_text']; + + } else if ('text' in element && element['text'] !== null && element['text'] !== '') { + event.properties['el_text'] = element['text']; + } + + // el_data_menuid + if ('attr__data-menuid' in element && element['attr__data-menuid'] !== null) { + event.properties['el_data_menuid'] = element['attr__data-menuid']; + } + + // el_data_submenuid + if ('attr__data-submenuid' in element && element['attr__data-submenuid'] !== null) { + event.properties['el_data_submenuid'] = element['attr__data-submenuid']; + } + + // el_data_chartid + if ('attr__data-chartid' in element && element['attr__data-chartid'] !== null) { + event.properties['el_data_chartid'] = element['attr__data-chartid']; + } + + // el_id_menu + if ('attr__id' in element && element['attr__id'] !== null && element['attr__id'].substring(0,5) === 'menu_') { + event.properties['el_id_menu'] = element['attr__id']; + event.properties['el_menu'] = element['attr__id'].split('_submenu')[0].replace('menu_', ''); + if (element['attr__id'].includes('_submenu_')) { + event.properties['el_submenu'] = element['attr__id'].split('_submenu_')[1]; + } else { + event.properties['el_submenu'] = ''; + } + } + + // el_class + if ('attr__class' in element && element['attr__class'] !== null) { + event.properties['el_class'] = element['attr__class']; + } + + }); + + } + + return event +} + +function processPropertiesCloud(event) { + + event = splitPathName(event); + + return event + +} + +function processElementsStaging(event) { + // extract properties from elements + if (event.properties['$elements']) { + + // process each element, reverse to use posthog order as preference + event.properties['$elements'].slice().reverse().forEach((element) => { + + // el_data_testid + if ('attr__data-testid' in element) { + event.properties['el_data_testid'] = element['attr__data-testid']; + + // el_data_testid_0 + if (element['attr__data-testid'].includes('::')) { + arr = element['attr__data-testid'].split('::'); + event.properties['el_data_testid_0'] = arr[0]; + event.properties['el_data_testid_1'] = arr[1]; + event.properties['el_data_testid_2'] = arr[2]; + event.properties['el_data_testid_3'] = arr[3]; + event.properties['el_data_testid_4'] = arr[4]; + } + + } + + // el_data_ga + if ('attr__data-ga' in element) { + event.properties['el_data_ga'] = element['attr__data-ga']; + + // el_data_ga_0 + if (element['attr__data-ga'].includes('::')) { + arr = element['attr__data-ga'].split('::'); + event.properties['el_data_ga_0'] = arr[0]; + event.properties['el_data_ga_1'] = arr[1]; + event.properties['el_data_ga_2'] = arr[2]; + event.properties['el_data_ga_3'] = arr[3]; + event.properties['el_data_ga_4'] = arr[4]; + } + + } + + // el_data_track + if ('attr__data-track' in element) { + event.properties['el_data_track'] = element['attr__data-track']; + + // el_data_track_0 + if (element['attr__data-track'].includes('::')) { + arr = element['attr__data-track'].split('::'); + event.properties['el_data_track_0'] = arr[0]; + event.properties['el_data_track_1'] = arr[1]; + event.properties['el_data_track_2'] = arr[2]; + event.properties['el_data_track_3'] = arr[3]; + event.properties['el_data_track_4'] = arr[4]; + } + + } + + // el_href + if ('attr__href' in element && element['attr__href'] !== null) { + event.properties['el_href'] = element['attr__href']; + } else if ('href' in element && element['href'] !== null) { + event.properties['el_href'] = element['href']; + } else if ('$href' in element && element['$href'] !== null) { + event.properties['el_href'] = element['$href']; + } + + // el_onclick + if ('attr__onclick' in element && element['attr__onclick'] !== null) { + event.properties['el_onclick'] = element['attr__onclick']; + } + + // el_id + if ('attr__id' in element && element['attr__id'] !== null) { + event.properties['el_id'] = element['attr__id']; + } + + // el_name + if ('attr__name' in element && element['attr__name'] !== null) { + event.properties['el_name'] = element['attr__name']; + } + + // el_title + if ('attr__title' in element && element['attr__title'] !== null) { + event.properties['el_title'] = element['attr__title']; + } + + // el_text + if ('$el_text' in element && element['$el_text'] !== null && element['$el_text'] !== '') { + event.properties['el_text'] = element['$el_text']; + + } else if ('text' in element && element['text'] !== null && element['text'] !== '') { + event.properties['el_text'] = element['text']; + } + + // el_data_menuid + if ('attr__data-menuid' in element && element['attr__data-menuid'] !== null) { + event.properties['el_data_menuid'] = element['attr__data-menuid']; + } + + // el_data_submenuid + if ('attr__data-submenuid' in element && element['attr__data-submenuid'] !== null) { + event.properties['el_data_submenuid'] = element['attr__data-submenuid']; + } + + // el_data_chartid + if ('attr__data-chartid' in element && element['attr__data-chartid'] !== null) { + event.properties['el_data_chartid'] = element['attr__data-chartid']; + } + + // el_id_menu + if ('attr__id' in element && element['attr__id'] !== null && element['attr__id'].substring(0,5) === 'menu_') { + event.properties['el_id_menu'] = element['attr__id']; + event.properties['el_menu'] = element['attr__id'].split('_submenu')[0].replace('menu_', ''); + if (element['attr__id'].includes('_submenu_')) { + event.properties['el_submenu'] = element['attr__id'].split('_submenu_')[1]; + } else { + event.properties['el_submenu'] = ''; + } + } + + // el_class + if ('attr__class' in element && element['attr__class'] !== null) { + event.properties['el_class'] = element['attr__class']; + } + + }); + + } + + return event +} + +function processPropertiesStaging(event) { + + event = splitPathName(event); + + return event +} + +function processElementsTesting(event) { + // extract properties from elements + if (event.properties['$elements']) { + + // process each element, reverse to use posthog order as preference + event.properties['$elements'].slice().reverse().forEach((element) => { + + // el_data_testid + if ('attr__data-testid' in element) { + event.properties['el_data_testid'] = element['attr__data-testid']; + + // el_data_testid_0 + if (element['attr__data-testid'].includes('::')) { + arr = element['attr__data-testid'].split('::'); + event.properties['el_data_testid_0'] = arr[0]; + event.properties['el_data_testid_1'] = arr[1]; + event.properties['el_data_testid_2'] = arr[2]; + event.properties['el_data_testid_3'] = arr[3]; + event.properties['el_data_testid_4'] = arr[4]; + } + + } + + // el_data_ga + if ('attr__data-ga' in element) { + event.properties['el_data_ga'] = element['attr__data-ga']; + + // el_data_ga_0 + if (element['attr__data-ga'].includes('::')) { + arr = element['attr__data-ga'].split('::'); + event.properties['el_data_ga_0'] = arr[0]; + event.properties['el_data_ga_1'] = arr[1]; + event.properties['el_data_ga_2'] = arr[2]; + event.properties['el_data_ga_3'] = arr[3]; + event.properties['el_data_ga_4'] = arr[4]; + } + + } + + // el_data_track + if ('attr__data-track' in element) { + event.properties['el_data_track'] = element['attr__data-track']; + + // el_data_track_0 + if (element['attr__data-track'].includes('::')) { + arr = element['attr__data-track'].split('::'); + event.properties['el_data_track_0'] = arr[0]; + event.properties['el_data_track_1'] = arr[1]; + event.properties['el_data_track_2'] = arr[2]; + event.properties['el_data_track_3'] = arr[3]; + event.properties['el_data_track_4'] = arr[4]; + } + + } + + // el_href + if ('attr__href' in element && element['attr__href'] !== null) { + event.properties['el_href'] = element['attr__href']; + } else if ('href' in element && element['href'] !== null) { + event.properties['el_href'] = element['href']; + } else if ('$href' in element && element['$href'] !== null) { + event.properties['el_href'] = element['$href']; + } + + // el_onclick + if ('attr__onclick' in element && element['attr__onclick'] !== null) { + event.properties['el_onclick'] = element['attr__onclick']; + } + + // el_id + if ('attr__id' in element && element['attr__id'] !== null) { + event.properties['el_id'] = element['attr__id']; + } + + // el_name + if ('attr__name' in element && element['attr__name'] !== null) { + event.properties['el_name'] = element['attr__name']; + } + + // el_title + if ('attr__title' in element && element['attr__title'] !== null) { + event.properties['el_title'] = element['attr__title']; + } + + // el_text + if ('$el_text' in element && element['$el_text'] !== null && element['$el_text'] !== '') { + event.properties['el_text'] = element['$el_text']; + + } else if ('text' in element && element['text'] !== null && element['text'] !== '') { + event.properties['el_text'] = element['text']; + } + + // el_data_menuid + if ('attr__data-menuid' in element && element['attr__data-menuid'] !== null) { + event.properties['el_data_menuid'] = element['attr__data-menuid']; + } + + // el_data_submenuid + if ('attr__data-submenuid' in element && element['attr__data-submenuid'] !== null) { + event.properties['el_data_submenuid'] = element['attr__data-submenuid']; + } + + // el_data_chartid + if ('attr__data-chartid' in element && element['attr__data-chartid'] !== null) { + event.properties['el_data_chartid'] = element['attr__data-chartid']; + } + + // el_id_menu + if ('attr__id' in element && element['attr__id'] !== null && element['attr__id'].substring(0,5) === 'menu_') { + event.properties['el_id_menu'] = element['attr__id']; + event.properties['el_menu'] = element['attr__id'].split('_submenu')[0].replace('menu_', ''); + if (element['attr__id'].includes('_submenu_')) { + event.properties['el_submenu'] = element['attr__id'].split('_submenu_')[1]; + } else { + event.properties['el_submenu'] = ''; + } + } + + // el_class + if ('attr__class' in element && element['attr__class'] !== null) { + event.properties['el_class'] = element['attr__class']; + } + + }); + + } + + return event +} + +function processPropertiesTesting(event) { + + event = splitPathName(event); + + return event +} + +function processElementsWebsite(event) { + // extract properties from elements + if (event.properties['$elements']) { + + // process each element, reverse to use posthog order as preference + event.properties['$elements'].slice().reverse().forEach((element) => { + + // el_data_testid + if ('attr__data-testid' in element) { + event.properties['el_data_testid'] = element['attr__data-testid']; + + // el_data_testid_0 + if (element['attr__data-testid'].includes('::')) { + arr = element['attr__data-testid'].split('::'); + event.properties['el_data_testid_0'] = arr[0]; + event.properties['el_data_testid_1'] = arr[1]; + event.properties['el_data_testid_2'] = arr[2]; + event.properties['el_data_testid_3'] = arr[3]; + event.properties['el_data_testid_4'] = arr[4]; + } + + } + + // el_data_ga + if ('attr__data-ga' in element) { + event.properties['el_data_ga'] = element['attr__data-ga']; + + // el_data_ga_0 + if (element['attr__data-ga'].includes('::')) { + arr = element['attr__data-ga'].split('::'); + event.properties['el_data_ga_0'] = arr[0]; + event.properties['el_data_ga_1'] = arr[1]; + event.properties['el_data_ga_2'] = arr[2]; + event.properties['el_data_ga_3'] = arr[3]; + event.properties['el_data_ga_4'] = arr[4]; + } + + } + + // el_data_track + if ('attr__data-track' in element) { + event.properties['el_data_track'] = element['attr__data-track']; + + // el_data_track_0 + if (element['attr__data-track'].includes('::')) { + arr = element['attr__data-track'].split('::'); + event.properties['el_data_track_0'] = arr[0]; + event.properties['el_data_track_1'] = arr[1]; + event.properties['el_data_track_2'] = arr[2]; + event.properties['el_data_track_3'] = arr[3]; + event.properties['el_data_track_4'] = arr[4]; + } + + } + + // el_href + if ('attr__href' in element && element['attr__href'] !== null) { + event.properties['el_href'] = element['attr__href']; + } else if ('href' in element && element['href'] !== null) { + event.properties['el_href'] = element['href']; + } else if ('$href' in element && element['$href'] !== null) { + event.properties['el_href'] = element['$href']; + } + + // el_onclick + if ('attr__onclick' in element && element['attr__onclick'] !== null) { + event.properties['el_onclick'] = element['attr__onclick']; + } + + // el_id + if ('attr__id' in element && element['attr__id'] !== null) { + event.properties['el_id'] = element['attr__id']; + } + + // el_name + if ('attr__name' in element && element['attr__name'] !== null) { + event.properties['el_name'] = element['attr__name']; + } + + // el_title + if ('attr__title' in element && element['attr__title'] !== null) { + event.properties['el_title'] = element['attr__title']; + } + + // el_text + if ('$el_text' in element && element['$el_text'] !== null && element['$el_text'] !== '') { + event.properties['el_text'] = element['$el_text']; + + } else if ('text' in element && element['text'] !== null && element['text'] !== '') { + event.properties['el_text'] = element['text']; + } + + // el_class + if ('attr__class' in element && element['attr__class'] !== null) { + event.properties['el_class'] = element['attr__class']; + } + + }); + + } + + return event +} + +function processPropertiesWebsite(event) { + + event = splitPathName(event); + + return event +} + +function processElementsLearn(event) { + // extract properties from elements + if (event.properties['$elements']) { + + // process each element, reverse to use posthog order as preference + event.properties['$elements'].slice().reverse().forEach((element) => { + + // el_data_testid + if ('attr__data-testid' in element) { + event.properties['el_data_testid'] = element['attr__data-testid']; + + // el_data_testid_0 + if (element['attr__data-testid'].includes('::')) { + arr = element['attr__data-testid'].split('::'); + event.properties['el_data_testid_0'] = arr[0]; + event.properties['el_data_testid_1'] = arr[1]; + event.properties['el_data_testid_2'] = arr[2]; + event.properties['el_data_testid_3'] = arr[3]; + event.properties['el_data_testid_4'] = arr[4]; + } + + } + + // el_data_ga + if ('attr__data-ga' in element) { + event.properties['el_data_ga'] = element['attr__data-ga']; + + // el_data_ga_0 + if (element['attr__data-ga'].includes('::')) { + arr = element['attr__data-ga'].split('::'); + event.properties['el_data_ga_0'] = arr[0]; + event.properties['el_data_ga_1'] = arr[1]; + event.properties['el_data_ga_2'] = arr[2]; + event.properties['el_data_ga_3'] = arr[3]; + event.properties['el_data_ga_4'] = arr[4]; + } + + } + + // el_data_track + if ('attr__data-track' in element) { + event.properties['el_data_track'] = element['attr__data-track']; + + // el_data_track_0 + if (element['attr__data-track'].includes('::')) { + arr = element['attr__data-track'].split('::'); + event.properties['el_data_track_0'] = arr[0]; + event.properties['el_data_track_1'] = arr[1]; + event.properties['el_data_track_2'] = arr[2]; + event.properties['el_data_track_3'] = arr[3]; + event.properties['el_data_track_4'] = arr[4]; + } + + } + + // el_href + if ('attr__href' in element && element['attr__href'] !== null) { + event.properties['el_href'] = element['attr__href']; + } else if ('href' in element && element['href'] !== null) { + event.properties['el_href'] = element['href']; + } else if ('$href' in element && element['$href'] !== null) { + event.properties['el_href'] = element['$href']; + } + + // el_onclick + if ('attr__onclick' in element && element['attr__onclick'] !== null) { + event.properties['el_onclick'] = element['attr__onclick']; + } + + // el_id + if ('attr__id' in element && element['attr__id'] !== null) { + event.properties['el_id'] = element['attr__id']; + } + + // el_name + if ('attr__name' in element && element['attr__name'] !== null) { + event.properties['el_name'] = element['attr__name']; + } + + // el_title + if ('attr__title' in element && element['attr__title'] !== null) { + event.properties['el_title'] = element['attr__title']; + } + + // el_text + if ('$el_text' in element && element['$el_text'] !== null && element['$el_text'] !== '') { + event.properties['el_text'] = element['$el_text']; + + } else if ('text' in element && element['text'] !== null && element['text'] !== '') { + event.properties['el_text'] = element['text']; + } + + // el_class + if ('attr__class' in element && element['attr__class'] !== null) { + event.properties['el_class'] = element['attr__class']; + } + + // el_aria_label + if ('attributes' in element && element['attributes'] !== null && 'attr__aria-label' in element['attributes']) { + event.properties['el_aria_label'] = element['attributes']['attr__aria-label']; + } + + }); + + } + + return event +} + +function processPropertiesLearn(event) { + + event = splitPathName(event); + + return event +} + +function processElementsCommunity(event) { + // extract properties from elements + if (event.properties['$elements']) { + + // process each element, reverse to use posthog order as preference + event.properties['$elements'].slice().reverse().forEach((element) => { + + // el_data_testid + if ('attr__data-testid' in element) { + event.properties['el_data_testid'] = element['attr__data-testid']; + + // el_data_testid_0 + if (element['attr__data-testid'].includes('::')) { + arr = element['attr__data-testid'].split('::'); + event.properties['el_data_testid_0'] = arr[0]; + event.properties['el_data_testid_1'] = arr[1]; + event.properties['el_data_testid_2'] = arr[2]; + event.properties['el_data_testid_3'] = arr[3]; + event.properties['el_data_testid_4'] = arr[4]; + } + + } + + // el_data_ga + if ('attr__data-ga' in element) { + event.properties['el_data_ga'] = element['attr__data-ga']; + + // el_data_ga_0 + if (element['attr__data-ga'].includes('::')) { + arr = element['attr__data-ga'].split('::'); + event.properties['el_data_ga_0'] = arr[0]; + event.properties['el_data_ga_1'] = arr[1]; + event.properties['el_data_ga_2'] = arr[2]; + event.properties['el_data_ga_3'] = arr[3]; + event.properties['el_data_ga_4'] = arr[4]; + } + + } + + // el_data_track + if ('attr__data-track' in element) { + event.properties['el_data_track'] = element['attr__data-track']; + + // el_data_track_0 + if (element['attr__data-track'].includes('::')) { + arr = element['attr__data-track'].split('::'); + event.properties['el_data_track_0'] = arr[0]; + event.properties['el_data_track_1'] = arr[1]; + event.properties['el_data_track_2'] = arr[2]; + event.properties['el_data_track_3'] = arr[3]; + event.properties['el_data_track_4'] = arr[4]; + } + + } + + // el_href + if ('attr__href' in element && element['attr__href'] !== null) { + event.properties['el_href'] = element['attr__href']; + } else if ('href' in element && element['href'] !== null) { + event.properties['el_href'] = element['href']; + } else if ('$href' in element && element['$href'] !== null) { + event.properties['el_href'] = element['$href']; + } + + // el_onclick + if ('attr__onclick' in element && element['attr__onclick'] !== null) { + event.properties['el_onclick'] = element['attr__onclick']; + } + + // el_id + if ('attr__id' in element && element['attr__id'] !== null) { + event.properties['el_id'] = element['attr__id']; + } + + // el_name + if ('attr__name' in element && element['attr__name'] !== null) { + event.properties['el_name'] = element['attr__name']; + } + + // el_title + if ('attr__title' in element && element['attr__title'] !== null) { + event.properties['el_title'] = element['attr__title']; + } + + // el_text + if ('$el_text' in element && element['$el_text'] !== null && element['$el_text'] !== '') { + event.properties['el_text'] = element['$el_text']; + + } else if ('text' in element && element['text'] !== null && element['text'] !== '') { + event.properties['el_text'] = element['text']; + } + + // el_class + if ('attr__class' in element && element['attr__class'] !== null) { + event.properties['el_class'] = element['attr__class']; + } + + }); + + } + + return event +} + +function processPropertiesCommunity(event) { + + event = splitPathName(event); + + return event +} + +function processElementsBlog(event) { + // extract properties from elements + if (event.properties['$elements']) { + + // process each element, reverse to use posthog order as preference + event.properties['$elements'].slice().reverse().forEach((element) => { + + // el_data_testid + if ('attr__data-testid' in element) { + event.properties['el_data_testid'] = element['attr__data-testid']; + + // el_data_testid_0 + if (element['attr__data-testid'].includes('::')) { + arr = element['attr__data-testid'].split('::'); + event.properties['el_data_testid_0'] = arr[0]; + event.properties['el_data_testid_1'] = arr[1]; + event.properties['el_data_testid_2'] = arr[2]; + event.properties['el_data_testid_3'] = arr[3]; + event.properties['el_data_testid_4'] = arr[4]; + } + + } + + // el_data_ga + if ('attr__data-ga' in element) { + event.properties['el_data_ga'] = element['attr__data-ga']; + + // el_data_ga_0 + if (element['attr__data-ga'].includes('::')) { + arr = element['attr__data-ga'].split('::'); + event.properties['el_data_ga_0'] = arr[0]; + event.properties['el_data_ga_1'] = arr[1]; + event.properties['el_data_ga_2'] = arr[2]; + event.properties['el_data_ga_3'] = arr[3]; + event.properties['el_data_ga_4'] = arr[4]; + } + + } + + // el_data_track + if ('attr__data-track' in element) { + event.properties['el_data_track'] = element['attr__data-track']; + + // el_data_track_0 + if (element['attr__data-track'].includes('::')) { + arr = element['attr__data-track'].split('::'); + event.properties['el_data_track_0'] = arr[0]; + event.properties['el_data_track_1'] = arr[1]; + event.properties['el_data_track_2'] = arr[2]; + event.properties['el_data_track_3'] = arr[3]; + event.properties['el_data_track_4'] = arr[4]; + } + + } + + // el_href + if ('attr__href' in element && element['attr__href'] !== null) { + event.properties['el_href'] = element['attr__href']; + } else if ('href' in element && element['href'] !== null) { + event.properties['el_href'] = element['href']; + } else if ('$href' in element && element['$href'] !== null) { + event.properties['el_href'] = element['$href']; + } + + // el_onclick + if ('attr__onclick' in element && element['attr__onclick'] !== null) { + event.properties['el_onclick'] = element['attr__onclick']; + } + + // el_id + if ('attr__id' in element && element['attr__id'] !== null) { + event.properties['el_id'] = element['attr__id']; + } + + // el_name + if ('attr__name' in element && element['attr__name'] !== null) { + event.properties['el_name'] = element['attr__name']; + } + + // el_title + if ('attr__title' in element && element['attr__title'] !== null) { + event.properties['el_title'] = element['attr__title']; + } + + // el_text + if ('$el_text' in element && element['$el_text'] !== null && element['$el_text'] !== '') { + event.properties['el_text'] = element['$el_text']; + + } else if ('text' in element && element['text'] !== null && element['text'] !== '') { + event.properties['el_text'] = element['text']; + } + + // el_class + if ('attr__class' in element && element['attr__class'] !== null) { + event.properties['el_class'] = element['attr__class']; + } + + // el_aria_label + if ('attributes' in element && element['attributes'] !== null && 'attr__aria-label' in element['attributes']) { + event.properties['el_aria_label'] = element['attributes']['attr__aria-label']; + } + + }); + + } + + return event +} + +function processPropertiesBlog(event) { + + event = splitPathName(event); + + return event +} + +//import URL from 'url'; + +const netdataPluginVersion = '0.0.15'; + +async function setupPlugin({ config, global }) { + //console.log("Setting up the plugin!") + //console.log(config) + global.setupDone = true; +} + +async function processEvent(event, { config, cache }) { + + if (event.properties) { + + event.properties['event_ph'] = event.event; + event.properties['netdata_posthog_plugin_version'] = netdataPluginVersion; + + // determine processing based on url + if ('$current_url' in event.properties) { + + // try extract specific url params + //if (event.properties['$current_url'].startsWith('http')) { + // const urlParams = new URL(event.properties['$current_url']).searchParams + // if (event.properties['$current_url'].includes('utm_source')) event.properties['url_param_utm_source'] = urlParams.get('utm_source'); + //} + + if ( + (['agent dashboard', 'agent backend'].includes(event.properties['$current_url'])) + || + (isDemo(event.properties['$current_url'])) + || + (event.properties['$current_url'].startsWith('https://netdata.corp.app.netdata.cloud')) + ) { + + event.properties['event_source'] = 'agent'; + event = processElementsAgent(event); + event = processPropertiesAgent(event); + + } else if (['agent installer'].includes(event.properties['$current_url'])) { + + event.properties['event_source'] = 'agent installer'; + event = processElementsAgentInstaller(event); + event = processPropertiesAgentInstaller(event); + + } else if (event.properties['$current_url'].startsWith('https://www.netdata.cloud')) { + + event.properties['event_source'] = 'website'; + event = processElementsWebsite(event); + event = processPropertiesWebsite(event); + + } else if (event.properties['$current_url'].includes('netdata-website.netlify.app')) + { + + event.properties['event_source'] = 'website_preview'; + event = processElementsWebsite(event); + event = processPropertiesWebsite(event); + + } else if (event.properties['$current_url'].startsWith('https://learn.netdata.cloud')) { + + event.properties['event_source'] = 'learn'; + event = processElementsLearn(event); + event = processPropertiesLearn(event); + + } else if (event.properties['$current_url'].includes('netdata-docusaurus.netlify.app')) { + + event.properties['event_source'] = 'learn_preview'; + event = processElementsLearn(event); + event = processPropertiesLearn(event); + + } else if (event.properties['$current_url'].startsWith('https://blog.netdata.cloud')) { + + event.properties['event_source'] = 'blog'; + event = processElementsBlog(event); + event = processPropertiesBlog(event); + + } else if (event.properties['$current_url'].includes('netdata-blog.netlify.app')) { + + event.properties['event_source'] = 'blog_preview'; + event = processElementsBlog(event); + event = processPropertiesBlog(event); + + } else if (event.properties['$current_url'].startsWith('https://community.netdata.cloud')) { + + event.properties['event_source'] = 'community'; + event = processElementsCommunity(event); + event = processPropertiesCommunity(event); + + } else if (event.properties['$current_url'].startsWith('https://staging.netdata.cloud')) { + + event.properties['event_source'] = 'staging'; + event = processElementsStaging(event); + event = processPropertiesStaging(event); + + } else if (event.properties['$current_url'].startsWith('https://testing.netdata.cloud')) { + + event.properties['event_source'] = 'testing'; + event = processElementsTesting(event); + event = processPropertiesTesting(event); + + } else if (event.properties['$current_url'].startsWith('https://app.netdata.cloud') + || event.properties['$current_url'].includes(':19999/spaces/') + || event.properties['$current_url'].includes('/spaces/') + ) { + + if (event.properties['$current_url'].startsWith('https://app.netdata.cloud')) { + event.properties['event_source'] = 'cloud'; + } else { + event.properties['event_source'] = 'cloud_agent'; + } + + event = processElementsCloud(event); + event = processPropertiesCloud(event); + + } else { + + event.properties['event_source'] = 'unknown'; + + } + + } else if (event.properties['event_ph'] === '$identify') { + + event.properties['event_source'] = 'cloud'; + event = processElementsCloud(event); + event = processPropertiesCloud(event); + + } else { + + event.properties['event_source'] = 'unknown'; + + } + } + + return event +} + +module.exports = { + setupPlugin, + processEvent, +}; diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/interaction_detail_agent.js b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/interaction_detail_agent.js new file mode 100644 index 0000000000000..3aded613f26ff --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/interaction_detail_agent.js @@ -0,0 +1,202 @@ +export function getInteractionDetailAgent(event) { + + // menu + if (['menu', 'submenu'].includes(event.properties['interaction_type'])) { + + return event.properties['el_href_menu'] + + // chart_toolbox + } else if (event.properties['interaction_type'] === 'chart_toolbox') { + + if (event.properties.hasOwnProperty('el_class_fa_minus')) { + return 'zoom_out' + } else if (event.properties.hasOwnProperty('el_class_fa_plus')) { + return 'zoom_in' + } else if (event.properties.hasOwnProperty('el_class_fa_backward')) { + return 'scroll_backward' + } else if (event.properties.hasOwnProperty('el_class_fa_forward')) { + return 'scroll_forward' + } else if (event.properties.hasOwnProperty('el_class_fa_sort')) { + return 'resize' + } else if (event.properties.hasOwnProperty('el_class_fa_play')) { + return 'play' + } else { + return 'other' + } + + // chart_dim + } else if (event.properties['interaction_type'] === 'chart_dim') { + + if ( + event.properties.hasOwnProperty('el_id') && + event.properties.hasOwnProperty('el_text') + ) { + return event.properties['el_data_netdata'].concat('.',event.properties['el_text']) + } else if ( + event.properties.hasOwnProperty('el_id') && + event.properties.hasOwnProperty('el_title') + ) { + return event.properties['el_data_netdata'].concat('.',event.properties['el_title']) + } else { + return 'other' + } + + // date_picker + } else if (event.properties['interaction_type'] === 'date_picker') { + + if (event.properties['el_id'] === 'date-picker-root') { + return 'open' + } else if ( + event.properties.hasOwnProperty('el_data_testid') && + event.properties['el_data_testid'].startsWith('date-picker') + ) { + if (event.properties['el_data_testid'].includes('click-quick-selector')) { + return event.properties['el_data_testid_1'].concat(' ',event.properties['el_data_testid_3']) + } else { + return event.properties['el_data_testid_1'] + } + } else if (event.properties['el_id'] === 'month_right') { + return 'month_right' + } else if (event.properties['el_id'] === 'month_left') { + return 'month_left' + } else if (event.properties.hasOwnProperty('el_class_daterangepicker')) { + return 'date_range' + } else { + return 'other' + } + + // update + } else if (event.properties['interaction_type'] === 'update') { + + if (event.properties['el_title'] === 'update') { + return 'open' + } else if (event.properties['el_text'] === 'Check Now') { + return 'check' + } else if (event.properties['el_text'] === 'Close') { + return 'close' + } else { + return 'other' + } + + // highlight + } else if (event.properties['interaction_type'] === 'highlight') { + + if (event.properties['el_onclick'] === 'urlOptions.clearHighlight();') { + return 'clear' + } else { + return 'other' + } + + // settings + } else if (event.properties['interaction_type'] === 'settings') { + + if (event.properties['el_id'] === 'root') { + return 'open' + } else if (event.properties['el_text'] === 'Close') { + return 'close' + } else if (event.properties['el_data_toggle'] === 'tab') { + return 'tab' + } else if (event.properties['el_data_toggle'] === 'toggle') { + return 'toggle' + } else { + return 'other' + } + + // alarms + } else if (event.properties['interaction_type'] === 'alarms') { + + if ( + event.properties.hasOwnProperty('el_href') && + event.properties['el_href'].includes('#alarm_all_') + ) { + return event.properties['el_text'] + } else if (event.properties.hasOwnProperty('el_class_page_number')) { + return 'page_number' + } else if (event.properties['el_id'] === 'root') { + return 'open' + } else if ( + event.properties['el_text'] === 'Active' || + event.properties['el_id'] === 'alarms_active' + ) { + return 'active' + } else if (event.properties['el_text'] === 'Log') { + return 'log' + } else if (event.properties['el_text'] === 'All') { + return 'all' + } else if ( + event.properties.hasOwnProperty('el_class_warning') && + event.properties.hasOwnProperty('el_text') + ) { + if ( + event.properties['el_text'].includes(':') || + event.properties['el_text'].includes('%') + ) { + return 'warn' + } else { + return 'warn__'.concat(event.properties['el_text']) + } + } else if ( + event.properties.hasOwnProperty('el_class_success') && + event.properties.hasOwnProperty('el_text') + ) { + if ( + event.properties['el_text'].includes(':') || + event.properties['el_text'].includes('%') + ) { + return 'norm' + } else { + return 'norm__'.concat(event.properties['el_text']) + } + } else if ( + event.properties.hasOwnProperty('el_class_danger') && + event.properties.hasOwnProperty('el_text') + ) { + if ( + event.properties['el_text'].includes(':') || + event.properties['el_text'].includes('%') + ) { + return 'crit' + } else { + return 'crit__'.concat(event.properties['el_text']) + } + } else if ( + event.properties.hasOwnProperty('el_class_info') && + event.properties.hasOwnProperty('el_text') + ) { + if ( + event.properties['el_text'].includes(':') || + event.properties['el_text'].includes('%') + ) { + return 'undef' + } else { + return 'undef__'.concat(event.properties['el_text']) + } + } else if ( + event.properties['el_text'] === 'Close' || + event.properties['el_text'] === '×' + ) { + return 'close' + } else if ( + event.properties['el_title'] === 'Refresh' && + event.properties['el_id'] === 'alarms_log' + ) { + return 'refresh_log' + } else { + return 'other' + } + + // cloud + } else if (event.properties['interaction_type'] === 'cloud') { + + if (event.properties['el_text'] === 'Sign In to Cloud') { + return 'sign_in' + } else { + return 'other' + } + + } else { + + return '' + + } +} \ No newline at end of file diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/interaction_type_agent.js b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/interaction_type_agent.js new file mode 100644 index 0000000000000..d01cbad6a3cc8 --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/interaction_type_agent.js @@ -0,0 +1,144 @@ +export function getInteractionTypeAgent(event) { + + if (['$pageview', '$pageleave', '$identify', 'agent backend'].includes(event.event)) { + + return event.event.replace('$', '').replace(' ', '_') + + // menu + } else if (event.properties.hasOwnProperty('el_href_menu')) { + + return event.properties['el_href_menu'].includes('submenu') ? 'submenu' : 'menu' + + // chart_toolbox + } else if ( + event.properties.hasOwnProperty('el_class_netdata_legend_resize_handler') || + event.properties.hasOwnProperty('el_class_netdata_legend_toolbox') + ) { + + return 'chart_toolbox' + + // chart_dim + } else if ( + event.properties.hasOwnProperty('el_data_netdata') && + event.properties.hasOwnProperty('el_id') && + ( + event.properties.hasOwnProperty('el_text') || event.properties.hasOwnProperty('el_title') + ) && + event.properties['el_id'].startsWith('chart_') + ) { + + return 'chart_dim' + + // date_picker + } else if ( + event.properties['el_id'] === 'date-picker-root' || + ( + event.properties.hasOwnProperty('el_data_testid') && + event.properties['el_data_testid'].startsWith('date-picker') + ) || + event.properties.hasOwnProperty('el_class_daterangepicker') + ) { + + return 'date_picker' + + // hamburger + } else if ( + event.properties.hasOwnProperty('el_class_collapsablesection') || + event.properties['el_title'] === 'hamburger' + ) { + + return 'hamburger' + + // update + } else if ( + event.properties.hasOwnProperty('el_data_target_updatemodal') || + event.properties.hasOwnProperty('el_id_updatemodal') + ) { + + return 'update' + + // help + } else if ( + ['Need Help?', 'question'].includes(event.properties['el_title']) || + event.properties['el_data_testid'] === 'documentation-help-close' || + event.properties.hasOwnProperty('el_class_documentation_container') + ) { + + return 'help' + + // load_snapshot + } else if ( + event.properties['el_data_target'] === '#loadSnapshotModal' || + event.properties['el_id'] === 'loadSnapshotDragAndDrop' || + event.properties['el_id'] === 'loadSnapshotSelectFiles' || + event.properties['el_id'] === 'loadSnapshotModal' + ) { + + return 'load_snapshot' + + // save_snapshot + } else if ( + event.properties['el_data_target'] === '#saveSnapshotModal' || + event.properties['el_id'] === 'saveSnapshotResolutionSlider' || + event.properties['el_id'] === 'saveSnapshotExport' || + event.properties['el_id'] === 'saveSnapshotModal' || + event.properties['el_id'] === 'hiddenDownloadLinks' + ) { + + return 'save_snapshot' + + // print + } else if ( + event.properties['el_data_target'] === '#printPreflightModal' || + event.properties['el_onclick'] === 'return printPreflight(),!1' + ) { + + return 'print' + + // alarms + } else if ( + event.properties['el_data_target'] === '#alarmsModal' || + ['#alarms_all', '#alarms_log', '#alarms_active'].includes(event.properties['el_href']) || + event.properties['el_id'] === 'alarms_log_table' || + event.properties['el_id'] === 'alarms_log' || + event.properties['el_id'] === 'alarmsModal' || + event.properties['el_aria_labelledby'] === 'alarmsModalLabel' + ) { + + return 'alarms' + + // settings + } else if ( + event.properties['el_data_target'] === '#optionsModal' || + event.properties['el_id'] === 'optionsModal' || + event.properties['el_aria_labelledby'] === 'optionsModalLabel' + ) { + + return 'settings' + + // cloud + } else if (event.properties.hasOwnProperty('el_class_signinbutton')) { + + return 'cloud' + + // highlight + } else if (event.properties['el_id'] === 'navbar-highlight-content') { + + return 'highlight' + + // add_charts + } else if (event.properties['el_text'] === 'Add more charts') { + + return 'add_charts' + + // add_alarms + } else if (event.properties['el_text'] === 'Add more alarms') { + + return 'add_alarms' + + } else { + + return 'other' + + } +} \ No newline at end of file diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/plugin.json b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/plugin.json new file mode 100644 index 0000000000000..474ecf072af2d --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/plugin.json @@ -0,0 +1,8 @@ +{ + "name": "PostHog Netdata Event Processing", + "url": "https://github.com/andrewm4894/posthog-netdata-event-processing", + "description": "A Posthog plugin to do some data processing on our Posthog events as they arrive.", + "main": "index.js", + "config": [ + ] +} diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process.js b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process.js new file mode 100644 index 0000000000000..4b9f27bf2bed8 --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process.js @@ -0,0 +1,167 @@ +import { processElementsAgent } from './process_elements_agent'; +import { processPropertiesAgent } from './process_properties_agent'; + +import { processElementsAgentInstaller } from './process_elements_agent_installer'; +import { processPropertiesAgentInstaller } from './process_properties_agent_installer'; + +import { processElementsCloud } from './process_elements_cloud'; +import { processPropertiesCloud } from './process_properties_cloud'; + +import { processElementsStaging } from './process_elements_staging'; +import { processPropertiesStaging } from './process_properties_staging'; + +import { processElementsTesting } from './process_elements_testing'; +import { processPropertiesTesting } from './process_properties_testing'; + +import { processElementsWebsite } from './process_elements_website'; +import { processPropertiesWebsite } from './process_properties_website'; + +import { processElementsLearn } from './process_elements_learn'; +import { processPropertiesLearn } from './process_properties_learn'; + +import { processElementsCommunity } from './process_elements_community'; +import { processPropertiesCommunity } from './process_properties_community'; + +import { processElementsBlog } from './process_elements_blog'; +import { processPropertiesBlog } from './process_properties_blog'; + +import { isDemo } from "./utils"; +//import URL from 'url'; + +const netdataPluginVersion = '0.0.15' + +async function setupPlugin({ config, global }) { + //console.log("Setting up the plugin!") + //console.log(config) + global.setupDone = true +} + +async function processEvent(event, { config, cache }) { + + if (event.properties) { + + event.properties['event_ph'] = event.event + event.properties['netdata_posthog_plugin_version'] = netdataPluginVersion + + // determine processing based on url + if ('$current_url' in event.properties) { + + // try extract specific url params + //if (event.properties['$current_url'].startsWith('http')) { + // const urlParams = new URL(event.properties['$current_url']).searchParams + // if (event.properties['$current_url'].includes('utm_source')) event.properties['url_param_utm_source'] = urlParams.get('utm_source'); + //} + + if ( + (['agent dashboard', 'agent backend'].includes(event.properties['$current_url'])) + || + (isDemo(event.properties['$current_url'])) + || + (event.properties['$current_url'].startsWith('https://netdata.corp.app.netdata.cloud')) + ) { + + event.properties['event_source'] = 'agent' + event = processElementsAgent(event) + event = processPropertiesAgent(event) + + } else if (['agent installer'].includes(event.properties['$current_url'])) { + + event.properties['event_source'] = 'agent installer' + event = processElementsAgentInstaller(event) + event = processPropertiesAgentInstaller(event) + + } else if (event.properties['$current_url'].startsWith('https://www.netdata.cloud')) { + + event.properties['event_source'] = 'website' + event = processElementsWebsite(event) + event = processPropertiesWebsite(event) + + } else if (event.properties['$current_url'].includes('netdata-website.netlify.app')) + { + + event.properties['event_source'] = 'website_preview' + event = processElementsWebsite(event) + event = processPropertiesWebsite(event) + + } else if (event.properties['$current_url'].startsWith('https://learn.netdata.cloud')) { + + event.properties['event_source'] = 'learn' + event = processElementsLearn(event) + event = processPropertiesLearn(event) + + } else if (event.properties['$current_url'].includes('netdata-docusaurus.netlify.app')) { + + event.properties['event_source'] = 'learn_preview' + event = processElementsLearn(event) + event = processPropertiesLearn(event) + + } else if (event.properties['$current_url'].startsWith('https://blog.netdata.cloud')) { + + event.properties['event_source'] = 'blog' + event = processElementsBlog(event) + event = processPropertiesBlog(event) + + } else if (event.properties['$current_url'].includes('netdata-blog.netlify.app')) { + + event.properties['event_source'] = 'blog_preview' + event = processElementsBlog(event) + event = processPropertiesBlog(event) + + } else if (event.properties['$current_url'].startsWith('https://community.netdata.cloud')) { + + event.properties['event_source'] = 'community' + event = processElementsCommunity(event) + event = processPropertiesCommunity(event) + + } else if (event.properties['$current_url'].startsWith('https://staging.netdata.cloud')) { + + event.properties['event_source'] = 'staging' + event = processElementsStaging(event) + event = processPropertiesStaging(event) + + } else if (event.properties['$current_url'].startsWith('https://testing.netdata.cloud')) { + + event.properties['event_source'] = 'testing' + event = processElementsTesting(event) + event = processPropertiesTesting(event) + + } else if (event.properties['$current_url'].startsWith('https://app.netdata.cloud') + || event.properties['$current_url'].includes(':19999/spaces/') + || event.properties['$current_url'].includes('/spaces/') + ) { + + if (event.properties['$current_url'].startsWith('https://app.netdata.cloud')) { + event.properties['event_source'] = 'cloud'; + } else { + event.properties['event_source'] = 'cloud_agent'; + } + + event = processElementsCloud(event) + event = processPropertiesCloud(event) + + } else { + + event.properties['event_source'] = 'unknown' + + } + + } else if (event.properties['event_ph'] === '$identify') { + + event.properties['event_source'] = 'cloud' + event = processElementsCloud(event) + event = processPropertiesCloud(event) + + } else { + + event.properties['event_source'] = 'unknown' + + } + } + + return event +} + +module.exports = { + setupPlugin, + processEvent, +} diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_elements_agent.js b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_elements_agent.js new file mode 100644 index 0000000000000..fba6d57cd0e8c --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_elements_agent.js @@ -0,0 +1,416 @@ +import {isStringDDMMYYYYHHMM} from "./utils"; + +export function processElementsAgent(event) { + // extract properties from elements + if (event.properties['$elements']) { + + // process each element, last (outermost) first. + event.properties['$elements'].slice().forEach((element) => { + + // el_data_testid_outer + if ('attr__data-testid' in element) { + event.properties['el_data_testid_outer'] = element['attr__data-testid'] + + // el_data_testid_outer_0 + if (element['attr__data-testid'].includes('::')) { + arr = element['attr__data-testid'].split('::') + event.properties['el_data_testid_outer_0'] = arr[0] + event.properties['el_data_testid_outer_1'] = arr[1] + event.properties['el_data_testid_outer_2'] = arr[2] + event.properties['el_data_testid_outer_3'] = arr[3] + event.properties['el_data_testid_outer_4'] = arr[4] + } + + } + + // el_data_ga_outer + if ('attr__data-ga' in element) { + event.properties['el_data_ga_outer'] = element['attr__data-ga'] + + // el_data_ga_outer_0 + if (element['attr__data-ga'].includes('::')) { + arr = element['attr__data-ga'].split('::') + event.properties['el_data_ga_outer_0'] = arr[0] + event.properties['el_data_ga_outer_1'] = arr[1] + event.properties['el_data_ga_outer_2'] = arr[2] + event.properties['el_data_ga_outer_3'] = arr[3] + event.properties['el_data_ga_outer_4'] = arr[4] + } + + } + + // el_data_track_outer + if ('attr__data-track' in element) { + event.properties['el_data_track_outer'] = element['attr__data-track'] + + // el_data_track_outer_0 + if (element['attr__data-track'].includes('::')) { + arr = element['attr__data-track'].split('::') + event.properties['el_data_track_outer_0'] = arr[0] + event.properties['el_data_track_outer_1'] = arr[1] + event.properties['el_data_track_outer_2'] = arr[2] + event.properties['el_data_track_outer_3'] = arr[3] + event.properties['el_data_track_outer_4'] = arr[4] + } + + } + + }) + + // process each element, reverse to use posthog order as preference + event.properties['$elements'].slice().reverse().forEach((element) => { + + // el_data_testid + if ('attr__data-testid' in element) { + event.properties['el_data_testid'] = element['attr__data-testid'] + + // el_data_testid_0 + if (element['attr__data-testid'].includes('::')) { + arr = element['attr__data-testid'].split('::') + event.properties['el_data_testid_0'] = arr[0] + event.properties['el_data_testid_1'] = arr[1] + event.properties['el_data_testid_2'] = arr[2] + event.properties['el_data_testid_3'] = arr[3] + event.properties['el_data_testid_4'] = arr[4] + } + + } + + // el_data_ga + if ('attr__data-ga' in element) { + event.properties['el_data_ga'] = element['attr__data-ga'] + + // el_data_ga_0 + if (element['attr__data-ga'].includes('::')) { + arr = element['attr__data-ga'].split('::') + event.properties['el_data_ga_0'] = arr[0] + event.properties['el_data_ga_1'] = arr[1] + event.properties['el_data_ga_2'] = arr[2] + event.properties['el_data_ga_3'] = arr[3] + event.properties['el_data_ga_4'] = arr[4] + } + + } + + // el_data_testid_inner + if ('attr__data-testid' in element) { + event.properties['el_data_testid_inner'] = element['attr__data-testid'] + + // el_data_testid_inner_0 + if (element['attr__data-testid'].includes('::')) { + arr = element['attr__data-testid'].split('::') + event.properties['el_data_testid_inner_0'] = arr[0] + event.properties['el_data_testid_inner_1'] = arr[1] + event.properties['el_data_testid_inner_2'] = arr[2] + event.properties['el_data_testid_inner_3'] = arr[3] + event.properties['el_data_testid_inner_4'] = arr[4] + } + + } + + // el_data_ga_inner + if ('attr__data-ga' in element) { + event.properties['el_data_ga_inner'] = element['attr__data-ga'] + + // el_data_ga_inner_0 + if (element['attr__data-ga'].includes('::')) { + arr = element['attr__data-ga'].split('::') + event.properties['el_data_ga_inner_0'] = arr[0] + event.properties['el_data_ga_inner_1'] = arr[1] + event.properties['el_data_ga_inner_2'] = arr[2] + event.properties['el_data_ga_inner_3'] = arr[3] + event.properties['el_data_ga_inner_4'] = arr[4] + } + + } + + // el_data_track_inner + if ('attr__data-track' in element) { + event.properties['el_data_track_inner'] = element['attr__data-track'] + + // el_data_track_inner_0 + if (element['attr__data-track'].includes('::')) { + arr = element['attr__data-track'].split('::') + event.properties['el_data_track_inner_0'] = arr[0] + event.properties['el_data_track_inner_1'] = arr[1] + event.properties['el_data_track_inner_2'] = arr[2] + event.properties['el_data_track_inner_3'] = arr[3] + event.properties['el_data_track_inner_4'] = arr[4] + } + + } + + // el_id_menu + if ('attr__href' in element && element['attr__href'] !== null && element['attr__href'].substring(0,5) === '#menu') { + event.properties['el_href_menu'] = element['attr__href'] + event.properties['el_menu'] = element['attr__href'].split('_submenu')[0].replace('#menu_', '') + if (element['attr__href'].includes('_submenu_')) { + event.properties['el_submenu'] = element['attr__href'].split('_submenu_')[1] + } else { + event.properties['el_submenu'] = '' + } + } + + // el_href + if ('attr__href' in element && element['attr__href'] !== null) { + event.properties['el_href'] = element['attr__href'] + } else if ('href' in element && element['href'] !== null) { + event.properties['el_href'] = element['href'] + } else if ('$href' in element && element['$href'] !== null) { + event.properties['el_href'] = element['$href'] + } + + // el_onclick + if ('attr__onclick' in element && element['attr__onclick'] !== null) { + event.properties['el_onclick'] = element['attr__onclick'] + } + + // el_id + if ('attr__id' in element && element['attr__id'] !== null) { + event.properties['el_id'] = element['attr__id'] + } + + // el_name + if ('attr__name' in element && element['attr__name'] !== null) { + event.properties['el_name'] = element['attr__name'] + } + + // el_title + if ('attr__title' in element && element['attr__title'] !== null) { + event.properties['el_title'] = element['attr__title'] + } + + // el_text + if ('$el_text' in element && element['$el_text'] !== null && element['$el_text'] !== '') { + event.properties['el_text'] = element['$el_text'] + + // el_text_datetime + if (element['$el_text'].includes('/20') && isStringDDMMYYYYHHMM(element['$el_text'])) { + dtStr = element['$el_text'] + dtStrClean = dtStr.substring(6,10).concat( + '-',dtStr.substring(3,5),'-',dtStr.substring(0,2),' ',dtStr.substring(11,16) + ) + event.properties['el_text_datetime'] = dtStrClean + } + + } else if ('text' in element && element['text'] !== null && element['text'] !== '') { + event.properties['el_text'] = element['text'] + } + + // el_data_netdata + if ('attr__data-netdata' in element && element['attr__data-netdata'] !== null) { + event.properties['el_data_netdata'] = element['attr__data-netdata'] + } + + // el_data_target + if ('attr__data-target' in element && element['attr__data-target'] !== null && element['attr__data-target'] !== '#sidebar') { + event.properties['el_data_target'] = element['attr__data-target'] + + // el_data_target_updatemodal + if ('attr__data-target' in element && element['attr__data-target'] !== null && element['attr__data-target'] === '#updateModal') { + event.properties['el_data_target_updatemodal'] = true + } + + } + + // el_data_id + if ('attr__data-id' in element && element['attr__data-id'] !== null) { + event.properties['el_data_id'] = element['attr__data-id'] + } + + // el_data_original_title + if ('attr__data-original-title' in element && element['attr__data-original-title'] !== null) { + event.properties['el_data_original_title'] = element['attr__data-original-title'] + } + + // el_data_toggle + if ('attr__data-toggle' in element && element['attr__data-toggle'] !== null) { + event.properties['el_data_toggle'] = element['attr__data-toggle'] + } + + // el_data-legend-position + if ('attr__data-legend-position' in element && element['attr__data-legend-position'] !== null) { + event.properties['el_data_legend_position'] = element['attr__data-legend-position'] + } + + // el_aria_controls + if ('attr__aria-controls' in element && element['attr__aria-controls'] !== null) { + event.properties['el_aria_controls'] = element['attr__aria-controls'] + } + + // el_aria_labelledby + if ('attr__aria-labelledby' in element && element['attr__aria-labelledby'] !== null) { + event.properties['el_aria_labelledby'] = element['attr__aria-labelledby'] + } + + // el_class_netdata_legend_toolbox + if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'] === 'netdata-legend-toolbox') { + event.properties['el_class_netdata_legend_toolbox'] = true + } + + // el_class_fa_play + if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('fa-play')) { + event.properties['el_class_fa_play'] = true + } + + // el_class_fa_backward + if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('fa-backward')) { + event.properties['el_class_fa_backward'] = true + } + + // el_class_fa_forward + if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('fa-forward')) { + event.properties['el_class_fa_forward'] = true + } + + // el_class_fa_plus + if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('fa-plus')) { + event.properties['el_class_fa_plus'] = true + } + + // el_class_fa_minus + if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('fa-minus')) { + event.properties['el_class_fa_minus'] = true + } + + // el_class_fa_sort + if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('fa-sort')) { + event.properties['el_class_fa_sort'] = true + } + + // el_class_navbar_highlight_content + if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('navbar-highlight-content')) { + event.properties['el_class_navbar_highlight_content'] = true + } + + // el_class_datepickercontainer + if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('DatePickerContainer')) { + event.properties['el_class_datepickercontainer'] = true + } + + // el_class_startendcontainer + if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('StartEndContainer')) { + event.properties['el_class_startendcontainer'] = true + } + + // el_class_pickerbtnarea + if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('PickerBtnArea')) { + event.properties['el_class_pickerbtnarea'] = true + } + + // el_class_pickerbox + if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('PickerBox')) { + event.properties['el_class_pickerbox'] = true + } + + // el_class_collapsablesection + if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('CollapsableSection')) { + event.properties['el_class_collapsablesection'] = true + } + + // el_class_signinbutton + if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('SignInButton')) { + event.properties['el_class_signinbutton'] = true + } + + // el_class_documentation_container + if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('documentation__Container')) { + event.properties['el_class_documentation_container'] = true + } + + // el_class_utilitysection + if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('UtilitySection')) { + event.properties['el_class_utilitysection'] = true + } + + // el_class_success + if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('success')) { + event.properties['el_class_success'] = true + } + + // el_class_warning + if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('warning')) { + event.properties['el_class_warning'] = true + } + + // el_class_danger + if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('danger')) { + event.properties['el_class_danger'] = true + } + + // el_class_info + if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'] === 'info') { + event.properties['el_class_info'] = true + } + + // el_class_pagination + if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('pagination')) { + event.properties['el_class_pagination'] = true + } + + // el_class_page_number + if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('page-number')) { + event.properties['el_class_page_number'] = true + } + + // el_class_export + if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('export')) { + event.properties['el_class_export'] = true + } + + // el_class_netdata_chartblock_container + if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('netdata-chartblock-container')) { + event.properties['el_class_netdata_chartblock_container'] = true + } + + // el_class_netdata_reset_button + if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('netdata-reset-button')) { + event.properties['el_class_netdata_reset_button'] = true + } + + // el_class_netdata_legend_toolbox_button + if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('netdata-legend-toolbox-button')) { + event.properties['el_class_netdata_legend_toolbox_button'] = true + } + + // el_class_netdata_legend_resize_handler + if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('netdata-legend-resize-handler')) { + event.properties['el_class_netdata_legend_resize_handler'] = true + } + + // el_class_calendarday + if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('CalendarDay')) { + event.properties['el_class_calendarday'] = true + } + + // el_class_daypicker + if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('DayPicker')) { + event.properties['el_class_daypicker'] = true + } + + // el_class_daterangepicker + if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('DateRangePicker')) { + event.properties['el_class_daterangepicker'] = true + } + + // el_id_date_picker_root + if ('attr__id' in element && element['attr__id'] !== null && element['attr__id'].includes('date-picker-root')) { + event.properties['el_id_date_picker_root'] = true + } + + // el_id_updatemodal + if ('attr__id' in element && element['attr__id'] !== null && element['attr__id'].includes('updateModal')) { + event.properties['el_id_updatemodal'] = true + } + + // el_class + if ('attr__class' in element && element['attr__class'] !== null) { + event.properties['el_class'] = element['attr__class'] + } + + }) + + } + + return event +} \ No newline at end of file diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_elements_agent_installer.js b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_elements_agent_installer.js new file mode 100644 index 0000000000000..84f67000abb8c --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_elements_agent_installer.js @@ -0,0 +1,7 @@ + +export function processElementsAgentInstaller(event) { + + // placeholder for now + + return event +} \ No newline at end of file diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_elements_blog.js b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_elements_blog.js new file mode 100644 index 0000000000000..f7da935ac326c --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_elements_blog.js @@ -0,0 +1,109 @@ + +export function processElementsBlog(event) { + // extract properties from elements + if (event.properties['$elements']) { + + // process each element, reverse to use posthog order as preference + event.properties['$elements'].slice().reverse().forEach((element) => { + + // el_data_testid + if ('attr__data-testid' in element) { + event.properties['el_data_testid'] = element['attr__data-testid'] + + // el_data_testid_0 + if (element['attr__data-testid'].includes('::')) { + arr = element['attr__data-testid'].split('::') + event.properties['el_data_testid_0'] = arr[0] + event.properties['el_data_testid_1'] = arr[1] + event.properties['el_data_testid_2'] = arr[2] + event.properties['el_data_testid_3'] = arr[3] + event.properties['el_data_testid_4'] = arr[4] + } + + } + + // el_data_ga + if ('attr__data-ga' in element) { + event.properties['el_data_ga'] = element['attr__data-ga'] + + // el_data_ga_0 + if (element['attr__data-ga'].includes('::')) { + arr = element['attr__data-ga'].split('::') + event.properties['el_data_ga_0'] = arr[0] + event.properties['el_data_ga_1'] = arr[1] + event.properties['el_data_ga_2'] = arr[2] + event.properties['el_data_ga_3'] = arr[3] + event.properties['el_data_ga_4'] = arr[4] + } + + } + + // el_data_track + if ('attr__data-track' in element) { + event.properties['el_data_track'] = element['attr__data-track'] + + // el_data_track_0 + if (element['attr__data-track'].includes('::')) { + arr = element['attr__data-track'].split('::') + event.properties['el_data_track_0'] = arr[0] + event.properties['el_data_track_1'] = arr[1] + event.properties['el_data_track_2'] = arr[2] + event.properties['el_data_track_3'] = arr[3] + event.properties['el_data_track_4'] = arr[4] + } + + } + + // el_href + if ('attr__href' in element && element['attr__href'] !== null) { + event.properties['el_href'] = element['attr__href'] + } else if ('href' in element && element['href'] !== null) { + event.properties['el_href'] = element['href'] + } else if ('$href' in element && element['$href'] !== null) { + event.properties['el_href'] = element['$href'] + } + + // el_onclick + if ('attr__onclick' in element && element['attr__onclick'] !== null) { + event.properties['el_onclick'] = element['attr__onclick'] + } + + // el_id + if ('attr__id' in element && element['attr__id'] !== null) { + event.properties['el_id'] = element['attr__id'] + } + + // el_name + if ('attr__name' in element && element['attr__name'] !== null) { + event.properties['el_name'] = element['attr__name'] + } + + // el_title + if ('attr__title' in element && element['attr__title'] !== null) { + event.properties['el_title'] = element['attr__title'] + } + + // el_text + if ('$el_text' in element && element['$el_text'] !== null && element['$el_text'] !== '') { + event.properties['el_text'] = element['$el_text'] + + } else if ('text' in element && element['text'] !== null && element['text'] !== '') { + event.properties['el_text'] = element['text'] + } + + // el_class + if ('attr__class' in element && element['attr__class'] !== null) { + event.properties['el_class'] = element['attr__class'] + } + + // el_aria_label + if ('attributes' in element && element['attributes'] !== null && 'attr__aria-label' in element['attributes']) { + event.properties['el_aria_label'] = element['attributes']['attr__aria-label'] + } + + }) + + } + + return event +} diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_elements_cloud.js b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_elements_cloud.js new file mode 100644 index 0000000000000..7e342eb7a514e --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_elements_cloud.js @@ -0,0 +1,221 @@ + +export function processElementsCloud(event) { + // extract properties from elements + if (event.properties['$elements']) { + + // process each element, last (outermost) first. + event.properties['$elements'].slice().forEach((element) => { + + // el_data_testid_outer + if ('attr__data-testid' in element) { + event.properties['el_data_testid_outer'] = element['attr__data-testid'] + + // el_data_testid_outer_0 + if (element['attr__data-testid'].includes('::')) { + arr = element['attr__data-testid'].split('::') + event.properties['el_data_testid_outer_0'] = arr[0] + event.properties['el_data_testid_outer_1'] = arr[1] + event.properties['el_data_testid_outer_2'] = arr[2] + event.properties['el_data_testid_outer_3'] = arr[3] + event.properties['el_data_testid_outer_4'] = arr[4] + } + + } + + // el_data_ga_outer + if ('attr__data-ga' in element) { + event.properties['el_data_ga_outer'] = element['attr__data-ga'] + + // el_data_ga_outer_0 + if (element['attr__data-ga'].includes('::')) { + arr = element['attr__data-ga'].split('::') + event.properties['el_data_ga_outer_0'] = arr[0] + event.properties['el_data_ga_outer_1'] = arr[1] + event.properties['el_data_ga_outer_2'] = arr[2] + event.properties['el_data_ga_outer_3'] = arr[3] + event.properties['el_data_ga_outer_4'] = arr[4] + } + + } + + // el_data_track_outer + if ('attr__data-track' in element) { + event.properties['el_data_track_outer'] = element['attr__data-track'] + + // el_data_track_outer_0 + if (element['attr__data-track'].includes('::')) { + arr = element['attr__data-track'].split('::') + event.properties['el_data_track_outer_0'] = arr[0] + event.properties['el_data_track_outer_1'] = arr[1] + event.properties['el_data_track_outer_2'] = arr[2] + event.properties['el_data_track_outer_3'] = arr[3] + event.properties['el_data_track_outer_4'] = arr[4] + } + + } + + }) + + // process each element, reverse to use posthog order as preference + event.properties['$elements'].slice().reverse().forEach((element) => { + + // el_data_testid + if ('attr__data-testid' in element) { + event.properties['el_data_testid'] = element['attr__data-testid'] + + // el_data_testid_0 + if (element['attr__data-testid'].includes('::')) { + arr = element['attr__data-testid'].split('::') + event.properties['el_data_testid_0'] = arr[0] + event.properties['el_data_testid_1'] = arr[1] + event.properties['el_data_testid_2'] = arr[2] + event.properties['el_data_testid_3'] = arr[3] + event.properties['el_data_testid_4'] = arr[4] + } + + } + + // el_data_ga + if ('attr__data-ga' in element) { + event.properties['el_data_ga'] = element['attr__data-ga'] + + // el_data_ga_0 + if (element['attr__data-ga'].includes('::')) { + arr = element['attr__data-ga'].split('::') + event.properties['el_data_ga_0'] = arr[0] + event.properties['el_data_ga_1'] = arr[1] + event.properties['el_data_ga_2'] = arr[2] + event.properties['el_data_ga_3'] = arr[3] + event.properties['el_data_ga_4'] = arr[4] + + // give nice names in posthog + event.properties['event_category'] = arr[0] + event.properties['event_action'] = arr[1] + event.properties['event_label'] = arr[2] + event.properties['event_value'] = arr[3] + } + + } + + // el_data_testid_inner + if ('attr__data-testid' in element) { + event.properties['el_data_testid_inner'] = element['attr__data-testid'] + + // el_data_testid_inner_0 + if (element['attr__data-testid'].includes('::')) { + arr = element['attr__data-testid'].split('::') + event.properties['el_data_testid_inner_0'] = arr[0] + event.properties['el_data_testid_inner_1'] = arr[1] + event.properties['el_data_testid_inner_2'] = arr[2] + event.properties['el_data_testid_inner_3'] = arr[3] + event.properties['el_data_testid_inner_4'] = arr[4] + } + + } + + // el_data_ga_inner + if ('attr__data-ga' in element) { + event.properties['el_data_ga_inner'] = element['attr__data-ga'] + + // el_data_ga_inner_0 + if (element['attr__data-ga'].includes('::')) { + arr = element['attr__data-ga'].split('::') + event.properties['el_data_ga_inner_0'] = arr[0] + event.properties['el_data_ga_inner_1'] = arr[1] + event.properties['el_data_ga_inner_2'] = arr[2] + event.properties['el_data_ga_inner_3'] = arr[3] + event.properties['el_data_ga_inner_4'] = arr[4] + } + + } + + // el_data_track_inner + if ('attr__data-track' in element) { + event.properties['el_data_track_inner'] = element['attr__data-track'] + + // el_data_track_inner_0 + if (element['attr__data-track'].includes('::')) { + arr = element['attr__data-track'].split('::') + event.properties['el_data_track_inner_0'] = arr[0] + event.properties['el_data_track_inner_1'] = arr[1] + event.properties['el_data_track_inner_2'] = arr[2] + event.properties['el_data_track_inner_3'] = arr[3] + event.properties['el_data_track_inner_4'] = arr[4] + } + + } + + // el_href + if ('attr__href' in element && element['attr__href'] !== null) { + event.properties['el_href'] = element['attr__href'] + } else if ('href' in element && element['href'] !== null) { + event.properties['el_href'] = element['href'] + } else if ('$href' in element && element['$href'] !== null) { + event.properties['el_href'] = element['$href'] + } + + // el_onclick + if ('attr__onclick' in element && element['attr__onclick'] !== null) { + event.properties['el_onclick'] = element['attr__onclick'] + } + + // el_id + if ('attr__id' in element && element['attr__id'] !== null) { + event.properties['el_id'] = element['attr__id'] + } + + // el_name + if ('attr__name' in element && element['attr__name'] !== null) { + event.properties['el_name'] = element['attr__name'] + } + + // el_title + if ('attr__title' in element && element['attr__title'] !== null) { + event.properties['el_title'] = element['attr__title'] + } + + // el_text + if ('$el_text' in element && element['$el_text'] !== null && element['$el_text'] !== '') { + event.properties['el_text'] = element['$el_text'] + + } else if ('text' in element && element['text'] !== null && element['text'] !== '') { + event.properties['el_text'] = element['text'] + } + + // el_data_menuid + if ('attr__data-menuid' in element && element['attr__data-menuid'] !== null) { + event.properties['el_data_menuid'] = element['attr__data-menuid'] + } + + // el_data_submenuid + if ('attr__data-submenuid' in element && element['attr__data-submenuid'] !== null) { + event.properties['el_data_submenuid'] = element['attr__data-submenuid'] + } + + // el_data_chartid + if ('attr__data-chartid' in element && element['attr__data-chartid'] !== null) { + event.properties['el_data_chartid'] = element['attr__data-chartid'] + } + + // el_id_menu + if ('attr__id' in element && element['attr__id'] !== null && element['attr__id'].substring(0,5) === 'menu_') { + event.properties['el_id_menu'] = element['attr__id'] + event.properties['el_menu'] = element['attr__id'].split('_submenu')[0].replace('menu_', '') + if (element['attr__id'].includes('_submenu_')) { + event.properties['el_submenu'] = element['attr__id'].split('_submenu_')[1] + } else { + event.properties['el_submenu'] = '' + } + } + + // el_class + if ('attr__class' in element && element['attr__class'] !== null) { + event.properties['el_class'] = element['attr__class'] + } + + }) + + } + + return event +} \ No newline at end of file diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_elements_community.js b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_elements_community.js new file mode 100644 index 0000000000000..7a1435a75e674 --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_elements_community.js @@ -0,0 +1,104 @@ + +export function processElementsCommunity(event) { + // extract properties from elements + if (event.properties['$elements']) { + + // process each element, reverse to use posthog order as preference + event.properties['$elements'].slice().reverse().forEach((element) => { + + // el_data_testid + if ('attr__data-testid' in element) { + event.properties['el_data_testid'] = element['attr__data-testid'] + + // el_data_testid_0 + if (element['attr__data-testid'].includes('::')) { + arr = element['attr__data-testid'].split('::') + event.properties['el_data_testid_0'] = arr[0] + event.properties['el_data_testid_1'] = arr[1] + event.properties['el_data_testid_2'] = arr[2] + event.properties['el_data_testid_3'] = arr[3] + event.properties['el_data_testid_4'] = arr[4] + } + + } + + // el_data_ga + if ('attr__data-ga' in element) { + event.properties['el_data_ga'] = element['attr__data-ga'] + + // el_data_ga_0 + if (element['attr__data-ga'].includes('::')) { + arr = element['attr__data-ga'].split('::') + event.properties['el_data_ga_0'] = arr[0] + event.properties['el_data_ga_1'] = arr[1] + event.properties['el_data_ga_2'] = arr[2] + event.properties['el_data_ga_3'] = arr[3] + event.properties['el_data_ga_4'] = arr[4] + } + + } + + // el_data_track + if ('attr__data-track' in element) { + event.properties['el_data_track'] = element['attr__data-track'] + + // el_data_track_0 + if (element['attr__data-track'].includes('::')) { + arr = element['attr__data-track'].split('::') + event.properties['el_data_track_0'] = arr[0] + event.properties['el_data_track_1'] = arr[1] + event.properties['el_data_track_2'] = arr[2] + event.properties['el_data_track_3'] = arr[3] + event.properties['el_data_track_4'] = arr[4] + } + + } + + // el_href + if ('attr__href' in element && element['attr__href'] !== null) { + event.properties['el_href'] = element['attr__href'] + } else if ('href' in element && element['href'] !== null) { + event.properties['el_href'] = element['href'] + } else if ('$href' in element && element['$href'] !== null) { + event.properties['el_href'] = element['$href'] + } + + // el_onclick + if ('attr__onclick' in element && element['attr__onclick'] !== null) { + event.properties['el_onclick'] = element['attr__onclick'] + } + + // el_id + if ('attr__id' in element && element['attr__id'] !== null) { + event.properties['el_id'] = element['attr__id'] + } + + // el_name + if ('attr__name' in element && element['attr__name'] !== null) { + event.properties['el_name'] = element['attr__name'] + } + + // el_title + if ('attr__title' in element && element['attr__title'] !== null) { + event.properties['el_title'] = element['attr__title'] + } + + // el_text + if ('$el_text' in element && element['$el_text'] !== null && element['$el_text'] !== '') { + event.properties['el_text'] = element['$el_text'] + + } else if ('text' in element && element['text'] !== null && element['text'] !== '') { + event.properties['el_text'] = element['text'] + } + + // el_class + if ('attr__class' in element && element['attr__class'] !== null) { + event.properties['el_class'] = element['attr__class'] + } + + }) + + } + + return event +} \ No newline at end of file diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_elements_learn.js b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_elements_learn.js new file mode 100644 index 0000000000000..26d536cb78f5b --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_elements_learn.js @@ -0,0 +1,109 @@ + +export function processElementsLearn(event) { + // extract properties from elements + if (event.properties['$elements']) { + + // process each element, reverse to use posthog order as preference + event.properties['$elements'].slice().reverse().forEach((element) => { + + // el_data_testid + if ('attr__data-testid' in element) { + event.properties['el_data_testid'] = element['attr__data-testid'] + + // el_data_testid_0 + if (element['attr__data-testid'].includes('::')) { + arr = element['attr__data-testid'].split('::') + event.properties['el_data_testid_0'] = arr[0] + event.properties['el_data_testid_1'] = arr[1] + event.properties['el_data_testid_2'] = arr[2] + event.properties['el_data_testid_3'] = arr[3] + event.properties['el_data_testid_4'] = arr[4] + } + + } + + // el_data_ga + if ('attr__data-ga' in element) { + event.properties['el_data_ga'] = element['attr__data-ga'] + + // el_data_ga_0 + if (element['attr__data-ga'].includes('::')) { + arr = element['attr__data-ga'].split('::') + event.properties['el_data_ga_0'] = arr[0] + event.properties['el_data_ga_1'] = arr[1] + event.properties['el_data_ga_2'] = arr[2] + event.properties['el_data_ga_3'] = arr[3] + event.properties['el_data_ga_4'] = arr[4] + } + + } + + // el_data_track + if ('attr__data-track' in element) { + event.properties['el_data_track'] = element['attr__data-track'] + + // el_data_track_0 + if (element['attr__data-track'].includes('::')) { + arr = element['attr__data-track'].split('::') + event.properties['el_data_track_0'] = arr[0] + event.properties['el_data_track_1'] = arr[1] + event.properties['el_data_track_2'] = arr[2] + event.properties['el_data_track_3'] = arr[3] + event.properties['el_data_track_4'] = arr[4] + } + + } + + // el_href + if ('attr__href' in element && element['attr__href'] !== null) { + event.properties['el_href'] = element['attr__href'] + } else if ('href' in element && element['href'] !== null) { + event.properties['el_href'] = element['href'] + } else if ('$href' in element && element['$href'] !== null) { + event.properties['el_href'] = element['$href'] + } + + // el_onclick + if ('attr__onclick' in element && element['attr__onclick'] !== null) { + event.properties['el_onclick'] = element['attr__onclick'] + } + + // el_id + if ('attr__id' in element && element['attr__id'] !== null) { + event.properties['el_id'] = element['attr__id'] + } + + // el_name + if ('attr__name' in element && element['attr__name'] !== null) { + event.properties['el_name'] = element['attr__name'] + } + + // el_title + if ('attr__title' in element && element['attr__title'] !== null) { + event.properties['el_title'] = element['attr__title'] + } + + // el_text + if ('$el_text' in element && element['$el_text'] !== null && element['$el_text'] !== '') { + event.properties['el_text'] = element['$el_text'] + + } else if ('text' in element && element['text'] !== null && element['text'] !== '') { + event.properties['el_text'] = element['text'] + } + + // el_class + if ('attr__class' in element && element['attr__class'] !== null) { + event.properties['el_class'] = element['attr__class'] + } + + // el_aria_label + if ('attributes' in element && element['attributes'] !== null && 'attr__aria-label' in element['attributes']) { + event.properties['el_aria_label'] = element['attributes']['attr__aria-label'] + } + + }) + + } + + return event +} \ No newline at end of file diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_elements_staging.js b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_elements_staging.js new file mode 100644 index 0000000000000..bb0cf9f80df64 --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_elements_staging.js @@ -0,0 +1,130 @@ + +export function processElementsStaging(event) { + // extract properties from elements + if (event.properties['$elements']) { + + // process each element, reverse to use posthog order as preference + event.properties['$elements'].slice().reverse().forEach((element) => { + + // el_data_testid + if ('attr__data-testid' in element) { + event.properties['el_data_testid'] = element['attr__data-testid'] + + // el_data_testid_0 + if (element['attr__data-testid'].includes('::')) { + arr = element['attr__data-testid'].split('::') + event.properties['el_data_testid_0'] = arr[0] + event.properties['el_data_testid_1'] = arr[1] + event.properties['el_data_testid_2'] = arr[2] + event.properties['el_data_testid_3'] = arr[3] + event.properties['el_data_testid_4'] = arr[4] + } + + } + + // el_data_ga + if ('attr__data-ga' in element) { + event.properties['el_data_ga'] = element['attr__data-ga'] + + // el_data_ga_0 + if (element['attr__data-ga'].includes('::')) { + arr = element['attr__data-ga'].split('::') + event.properties['el_data_ga_0'] = arr[0] + event.properties['el_data_ga_1'] = arr[1] + event.properties['el_data_ga_2'] = arr[2] + event.properties['el_data_ga_3'] = arr[3] + event.properties['el_data_ga_4'] = arr[4] + } + + } + + // el_data_track + if ('attr__data-track' in element) { + event.properties['el_data_track'] = element['attr__data-track'] + + // el_data_track_0 + if (element['attr__data-track'].includes('::')) { + arr = element['attr__data-track'].split('::') + event.properties['el_data_track_0'] = arr[0] + event.properties['el_data_track_1'] = arr[1] + event.properties['el_data_track_2'] = arr[2] + event.properties['el_data_track_3'] = arr[3] + event.properties['el_data_track_4'] = arr[4] + } + + } + + // el_href + if ('attr__href' in element && element['attr__href'] !== null) { + event.properties['el_href'] = element['attr__href'] + } else if ('href' in element && element['href'] !== null) { + event.properties['el_href'] = element['href'] + } else if ('$href' in element && element['$href'] !== null) { + event.properties['el_href'] = element['$href'] + } + + // el_onclick + if ('attr__onclick' in element && element['attr__onclick'] !== null) { + event.properties['el_onclick'] = element['attr__onclick'] + } + + // el_id + if ('attr__id' in element && element['attr__id'] !== null) { + event.properties['el_id'] = element['attr__id'] + } + + // el_name + if ('attr__name' in element && element['attr__name'] !== null) { + event.properties['el_name'] = element['attr__name'] + } + + // el_title + if ('attr__title' in element && element['attr__title'] !== null) { + event.properties['el_title'] = element['attr__title'] + } + + // el_text + if ('$el_text' in element && element['$el_text'] !== null && element['$el_text'] !== '') { + event.properties['el_text'] = element['$el_text'] + + } else if ('text' in element && element['text'] !== null && element['text'] !== '') { + event.properties['el_text'] = element['text'] + } + + // el_data_menuid + if ('attr__data-menuid' in element && element['attr__data-menuid'] !== null) { + event.properties['el_data_menuid'] = element['attr__data-menuid'] + } + + // el_data_submenuid + if ('attr__data-submenuid' in element && element['attr__data-submenuid'] !== null) { + event.properties['el_data_submenuid'] = element['attr__data-submenuid'] + } + + // el_data_chartid + if ('attr__data-chartid' in element && element['attr__data-chartid'] !== null) { + event.properties['el_data_chartid'] = element['attr__data-chartid'] + } + + // el_id_menu + if ('attr__id' in element && element['attr__id'] !== null && element['attr__id'].substring(0,5) === 'menu_') { + event.properties['el_id_menu'] = element['attr__id'] + event.properties['el_menu'] = element['attr__id'].split('_submenu')[0].replace('menu_', '') + if (element['attr__id'].includes('_submenu_')) { + event.properties['el_submenu'] = element['attr__id'].split('_submenu_')[1] + } else { + event.properties['el_submenu'] = '' + } + } + + // el_class + if ('attr__class' in element && element['attr__class'] !== null) { + event.properties['el_class'] = element['attr__class'] + } + + }) + + } + + return event +} \ No newline at end of file diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_elements_testing.js b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_elements_testing.js new file mode 100644 index 0000000000000..75909102a45e5 --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_elements_testing.js @@ -0,0 +1,130 @@ + +export function processElementsTesting(event) { + // extract properties from elements + if (event.properties['$elements']) { + + // process each element, reverse to use posthog order as preference + event.properties['$elements'].slice().reverse().forEach((element) => { + + // el_data_testid + if ('attr__data-testid' in element) { + event.properties['el_data_testid'] = element['attr__data-testid'] + + // el_data_testid_0 + if (element['attr__data-testid'].includes('::')) { + arr = element['attr__data-testid'].split('::') + event.properties['el_data_testid_0'] = arr[0] + event.properties['el_data_testid_1'] = arr[1] + event.properties['el_data_testid_2'] = arr[2] + event.properties['el_data_testid_3'] = arr[3] + event.properties['el_data_testid_4'] = arr[4] + } + + } + + // el_data_ga + if ('attr__data-ga' in element) { + event.properties['el_data_ga'] = element['attr__data-ga'] + + // el_data_ga_0 + if (element['attr__data-ga'].includes('::')) { + arr = element['attr__data-ga'].split('::') + event.properties['el_data_ga_0'] = arr[0] + event.properties['el_data_ga_1'] = arr[1] + event.properties['el_data_ga_2'] = arr[2] + event.properties['el_data_ga_3'] = arr[3] + event.properties['el_data_ga_4'] = arr[4] + } + + } + + // el_data_track + if ('attr__data-track' in element) { + event.properties['el_data_track'] = element['attr__data-track'] + + // el_data_track_0 + if (element['attr__data-track'].includes('::')) { + arr = element['attr__data-track'].split('::') + event.properties['el_data_track_0'] = arr[0] + event.properties['el_data_track_1'] = arr[1] + event.properties['el_data_track_2'] = arr[2] + event.properties['el_data_track_3'] = arr[3] + event.properties['el_data_track_4'] = arr[4] + } + + } + + // el_href + if ('attr__href' in element && element['attr__href'] !== null) { + event.properties['el_href'] = element['attr__href'] + } else if ('href' in element && element['href'] !== null) { + event.properties['el_href'] = element['href'] + } else if ('$href' in element && element['$href'] !== null) { + event.properties['el_href'] = element['$href'] + } + + // el_onclick + if ('attr__onclick' in element && element['attr__onclick'] !== null) { + event.properties['el_onclick'] = element['attr__onclick'] + } + + // el_id + if ('attr__id' in element && element['attr__id'] !== null) { + event.properties['el_id'] = element['attr__id'] + } + + // el_name + if ('attr__name' in element && element['attr__name'] !== null) { + event.properties['el_name'] = element['attr__name'] + } + + // el_title + if ('attr__title' in element && element['attr__title'] !== null) { + event.properties['el_title'] = element['attr__title'] + } + + // el_text + if ('$el_text' in element && element['$el_text'] !== null && element['$el_text'] !== '') { + event.properties['el_text'] = element['$el_text'] + + } else if ('text' in element && element['text'] !== null && element['text'] !== '') { + event.properties['el_text'] = element['text'] + } + + // el_data_menuid + if ('attr__data-menuid' in element && element['attr__data-menuid'] !== null) { + event.properties['el_data_menuid'] = element['attr__data-menuid'] + } + + // el_data_submenuid + if ('attr__data-submenuid' in element && element['attr__data-submenuid'] !== null) { + event.properties['el_data_submenuid'] = element['attr__data-submenuid'] + } + + // el_data_chartid + if ('attr__data-chartid' in element && element['attr__data-chartid'] !== null) { + event.properties['el_data_chartid'] = element['attr__data-chartid'] + } + + // el_id_menu + if ('attr__id' in element && element['attr__id'] !== null && element['attr__id'].substring(0,5) === 'menu_') { + event.properties['el_id_menu'] = element['attr__id'] + event.properties['el_menu'] = element['attr__id'].split('_submenu')[0].replace('menu_', '') + if (element['attr__id'].includes('_submenu_')) { + event.properties['el_submenu'] = element['attr__id'].split('_submenu_')[1] + } else { + event.properties['el_submenu'] = '' + } + } + + // el_class + if ('attr__class' in element && element['attr__class'] !== null) { + event.properties['el_class'] = element['attr__class'] + } + + }) + + } + + return event +} \ No newline at end of file diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_elements_website.js b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_elements_website.js new file mode 100644 index 0000000000000..ec7da7a19d655 --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_elements_website.js @@ -0,0 +1,104 @@ + +export function processElementsWebsite(event) { + // extract properties from elements + if (event.properties['$elements']) { + + // process each element, reverse to use posthog order as preference + event.properties['$elements'].slice().reverse().forEach((element) => { + + // el_data_testid + if ('attr__data-testid' in element) { + event.properties['el_data_testid'] = element['attr__data-testid'] + + // el_data_testid_0 + if (element['attr__data-testid'].includes('::')) { + arr = element['attr__data-testid'].split('::') + event.properties['el_data_testid_0'] = arr[0] + event.properties['el_data_testid_1'] = arr[1] + event.properties['el_data_testid_2'] = arr[2] + event.properties['el_data_testid_3'] = arr[3] + event.properties['el_data_testid_4'] = arr[4] + } + + } + + // el_data_ga + if ('attr__data-ga' in element) { + event.properties['el_data_ga'] = element['attr__data-ga'] + + // el_data_ga_0 + if (element['attr__data-ga'].includes('::')) { + arr = element['attr__data-ga'].split('::') + event.properties['el_data_ga_0'] = arr[0] + event.properties['el_data_ga_1'] = arr[1] + event.properties['el_data_ga_2'] = arr[2] + event.properties['el_data_ga_3'] = arr[3] + event.properties['el_data_ga_4'] = arr[4] + } + + } + + // el_data_track + if ('attr__data-track' in element) { + event.properties['el_data_track'] = element['attr__data-track'] + + // el_data_track_0 + if (element['attr__data-track'].includes('::')) { + arr = element['attr__data-track'].split('::') + event.properties['el_data_track_0'] = arr[0] + event.properties['el_data_track_1'] = arr[1] + event.properties['el_data_track_2'] = arr[2] + event.properties['el_data_track_3'] = arr[3] + event.properties['el_data_track_4'] = arr[4] + } + + } + + // el_href + if ('attr__href' in element && element['attr__href'] !== null) { + event.properties['el_href'] = element['attr__href'] + } else if ('href' in element && element['href'] !== null) { + event.properties['el_href'] = element['href'] + } else if ('$href' in element && element['$href'] !== null) { + event.properties['el_href'] = element['$href'] + } + + // el_onclick + if ('attr__onclick' in element && element['attr__onclick'] !== null) { + event.properties['el_onclick'] = element['attr__onclick'] + } + + // el_id + if ('attr__id' in element && element['attr__id'] !== null) { + event.properties['el_id'] = element['attr__id'] + } + + // el_name + if ('attr__name' in element && element['attr__name'] !== null) { + event.properties['el_name'] = element['attr__name'] + } + + // el_title + if ('attr__title' in element && element['attr__title'] !== null) { + event.properties['el_title'] = element['attr__title'] + } + + // el_text + if ('$el_text' in element && element['$el_text'] !== null && element['$el_text'] !== '') { + event.properties['el_text'] = element['$el_text'] + + } else if ('text' in element && element['text'] !== null && element['text'] !== '') { + event.properties['el_text'] = element['text'] + } + + // el_class + if ('attr__class' in element && element['attr__class'] !== null) { + event.properties['el_class'] = element['attr__class'] + } + + }) + + } + + return event +} \ No newline at end of file diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_properties_agent.js b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_properties_agent.js new file mode 100644 index 0000000000000..b72000cdd2907 --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_properties_agent.js @@ -0,0 +1,97 @@ +import {cleanPropertyName, splitPathName} from "./utils"; +import {getInteractionTypeAgent} from "./interaction_type_agent"; +import {getInteractionDetailAgent} from "./interaction_detail_agent"; + +export function processPropertiesAgent(event) { + + event = splitPathName(event) + + // has_alarms_critical + if (typeof event.properties['alarms_critical'] === 'number') { + event.properties['has_alarms_critical'] = event.properties['alarms_critical'] > 0 + } + + // has_alarms_warning + if (typeof event.properties['alarms_warning'] === 'number') { + event.properties['has_alarms_warning'] = event.properties['alarms_warning'] > 0 + } + + // add attribute for each build info flag + if (event.properties['netdata_buildinfo']) { + [...new Set(event.properties['netdata_buildinfo'].split('|'))].forEach((buildInfo) => { + if ((buildInfo !== "") && (buildInfo !== null)){ + event.properties[`netdata_buildinfo_${cleanPropertyName(buildInfo)}`] = true + } + }) + } + + // add attribute for each host collector + if (event.properties['host_collectors']) { + + // only process if not empty + if (event.properties['host_collectors'][0] != null) { + + // make set for both plugins and modules present + let plugins = [...new Set(event.properties['host_collectors'].map(a => a.plugin))]; + let modules = [...new Set(event.properties['host_collectors'].map(a => a.module))]; + + // add flag for each plugin + plugins.forEach((plugin) => { + if ((plugin !== "") && (plugin !== null)){ + event.properties[`host_collector_plugin_${cleanPropertyName(plugin)}`] = true + } + }) + + // add flag for each module + modules.forEach((module) => { + if ((module !== "") && (module !== null)){ + event.properties[`host_collector_module_${cleanPropertyName(module)}`] = true + } + }) + + } + } + + // check if netdata_machine_guid property exists + if (typeof event.properties['netdata_machine_guid'] === 'string') { + // flag if empty string + if (event.properties['netdata_machine_guid']==='') { + event.properties['netdata_machine_guid'] = 'empty' + event.properties['netdata_machine_guid_is_empty'] = true + } else { + event.properties['netdata_machine_guid_is_empty'] = false + } + } + + // check if netdata_machine_guid property exists + if (typeof event.properties['netdata_person_id'] === 'string') { + // flag if empty string + if (event.properties['netdata_person_id']==='') { + event.properties['netdata_person_id'] = 'empty' + event.properties['netdata_person_id_is_empty'] = true + } else { + event.properties['netdata_person_id_is_empty'] = false + } + } + + // check if $distinct_id property exists + if (typeof event.properties['distinct_id'] === 'string') { + // flag if empty string + if (event.properties['distinct_id']==='') { + event.properties['distinct_id'] = 'empty' + event.properties['distinct_id_is_empty'] = true + } else { + event.properties['distinct_id_is_empty'] = false + } + } + + // interaction_type + event.properties['interaction_type'] = getInteractionTypeAgent(event) + event.properties['interaction_detail'] = getInteractionDetailAgent(event) + event.properties['interaction_token'] = event.properties['interaction_type'].concat('|',event.properties['interaction_detail']) + //if (event.event === '$autocapture' && event.properties.hasOwnProperty('interaction_token')) { + // event.event = event.properties['interaction_token'] + //} + + return event +} \ No newline at end of file diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_properties_agent_installer.js b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_properties_agent_installer.js new file mode 100644 index 0000000000000..dd982dc8cca66 --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_properties_agent_installer.js @@ -0,0 +1,22 @@ +import {cleanPropertyName} from "./utils"; + +export function processPropertiesAgentInstaller(event) { + + // only process if install_options not empty + if (event.properties['install_options'] != null) { + + // make set for install options + let installOptions = [...new Set((event.properties['install_options'] + ' ').split('--'))]; + + // make flag for each option + installOptions.forEach((installOption) => { + if ((installOption !== "") && (installOption !== null)){ + let installOptionKV = installOption.split(' ') + event.properties[`opt_${cleanPropertyName(installOptionKV[0])}`] = installOptionKV[1] + } + }) + + } + + return event +} \ No newline at end of file diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_properties_blog.js b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_properties_blog.js new file mode 100644 index 0000000000000..5044c73781864 --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_properties_blog.js @@ -0,0 +1,8 @@ +import {splitPathName} from "./utils"; + +export function processPropertiesBlog(event) { + + event = splitPathName(event) + + return event +} diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_properties_cloud.js b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_properties_cloud.js new file mode 100644 index 0000000000000..973ec98dc78b1 --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_properties_cloud.js @@ -0,0 +1,9 @@ +import {splitPathName} from "./utils"; + +export function processPropertiesCloud(event) { + + event = splitPathName(event) + + return event + +} \ No newline at end of file diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_properties_community.js b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_properties_community.js new file mode 100644 index 0000000000000..bab5443d4682e --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_properties_community.js @@ -0,0 +1,8 @@ +import {splitPathName} from "./utils"; + +export function processPropertiesCommunity(event) { + + event = splitPathName(event) + + return event +} \ No newline at end of file diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_properties_learn.js b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_properties_learn.js new file mode 100644 index 0000000000000..849a1654c91c2 --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_properties_learn.js @@ -0,0 +1,8 @@ +import {splitPathName} from "./utils"; + +export function processPropertiesLearn(event) { + + event = splitPathName(event) + + return event +} \ No newline at end of file diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_properties_staging.js b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_properties_staging.js new file mode 100644 index 0000000000000..3aab1ea2c407c --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_properties_staging.js @@ -0,0 +1,8 @@ +import {splitPathName} from "./utils"; + +export function processPropertiesStaging(event) { + + event = splitPathName(event) + + return event +} \ No newline at end of file diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_properties_testing.js b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_properties_testing.js new file mode 100644 index 0000000000000..6bbb964d6600a --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_properties_testing.js @@ -0,0 +1,8 @@ +import {splitPathName} from "./utils"; + +export function processPropertiesTesting(event) { + + event = splitPathName(event) + + return event +} \ No newline at end of file diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_properties_website.js b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_properties_website.js new file mode 100644 index 0000000000000..28fa9cedee745 --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_properties_website.js @@ -0,0 +1,8 @@ +import {splitPathName} from "./utils"; + +export function processPropertiesWebsite(event) { + + event = splitPathName(event) + + return event +} \ No newline at end of file diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/utils.js b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/utils.js new file mode 100644 index 0000000000000..b043931a99c84 --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/utils.js @@ -0,0 +1,54 @@ +export function cleanPropertyName(k) { + return k + // convert to lower case + .toLowerCase() + // remove leading slash + .replace(/^\//, "") + // replace all slashes and dots with _ + .replace(/\/|\.|-| /g, "_") + ; +} + +export function isStringDDMMYYYYHHMM(dt){ + var reDate = /^((0?[1-9]|[12][0-9]|3[01])[- /.](0?[1-9]|1[012])[- /.](19|20)?[0-9]{2}[ ][012][0-9][:][0-9]{2})*$/; + return reDate.test(dt); +} + +export function isDemo(url) { + if ( + (url.includes('://london.my-netdata.io')) + || + (url.includes('://london3.my-netdata.io')) + || + (url.includes('://cdn77.my-netdata.io')) + || + (url.includes('://octopuscs.my-netdata.io')) + || + (url.includes('://bangalore.my-netdata.io')) + || + (url.includes('://frankfurt.my-netdata.io')) + || + (url.includes('://newyork.my-netdata.io')) + || + (url.includes('://sanfrancisco.my-netdata.io')) + || + (url.includes('://singapore.my-netdata.io')) + || + (url.includes('://toronto.my-netdata.io')) + ){ + return true + } else { + return false + } +} + +export function splitPathName(event) { + if (event.properties['$pathname']) { + event.properties["$pathname"].split("/").forEach((pathname, index) => { + if ((pathname !== "") && (pathname !== null)){ + event.properties[`pathname_${index}`] = pathname + } + }) + } + return event +} \ No newline at end of file diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-stonly-UTM-Extractor/index.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-stonly-UTM-Extractor/index.ts new file mode 100644 index 0000000000000..5acdbf53615a6 --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-stonly-UTM-Extractor/index.ts @@ -0,0 +1,41 @@ +import { PluginEvent } from '@posthog/plugin-scaffold' +import { URL } from 'url' + +import { LegacyTransformationPluginMeta } from '../../types' +// Processes each event, optionally transforming it + +const extractUtmFromUrl = (urlString: string, utm: string) => { + const url = new URL(urlString) + return url.searchParams.get(utm) +} + +const utmProperties = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_content', 'utm_term'] + +const urlProperties = ['current_url', '$current_url', 'signUpPageConversion', 'signUpPageLanding', 'firstPageViewed'] + +export function processEvent(event: PluginEvent, _: LegacyTransformationPluginMeta) { + // Some events (such as $identify) don't have properties + let didAddUtms = false + if (event.properties) { + for (const urlProp of urlProperties) { + if (event.properties[urlProp] !== undefined && event.properties[urlProp].includes('utm')) { + for (const utmProp of utmProperties) { + // Only change events that have an URL and no UTMs set (mostly segment) + if (!event.properties[utmProp]) { + const extractedUtm = extractUtmFromUrl(event.properties[urlProp], utmProp) + if (extractedUtm) { + event.properties[utmProp] = extractedUtm + didAddUtms = true + } + } + } + } + // Only take UTMS from the first available URL, then return + if (didAddUtms) { + return event + } + } + } + // Return the event to be ingested, or return null to discard + return event +} diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-stonly-UTM-Extractor/template.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-stonly-UTM-Extractor/template.ts new file mode 100644 index 0000000000000..d0efb73c87293 --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-stonly-UTM-Extractor/template.ts @@ -0,0 +1,14 @@ +import { HogFunctionTemplate } from '../../../templates/types' + +// NOTE: This is a deprecated plugin and should never be shown to new users +export const template: HogFunctionTemplate = { + status: 'alpha', + type: 'transformation', + id: 'plugin-stonly-utm-extractor', + name: 'UTM Extractor', + description: '', + icon_url: '/static/hedgehog/builder-hog-01.png', + category: ['Custom'], + hog: `return event`, + inputs_schema: [], +} diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-app-unduplicator/index.test.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-app-unduplicator/index.test.ts new file mode 100644 index 0000000000000..6b6873cf610b7 --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-app-unduplicator/index.test.ts @@ -0,0 +1,111 @@ +import { createPageview, resetMeta } from '@posthog/plugin-scaffold/test/utils' + +import { LegacyTransformationPluginMeta } from '../../types' +import { processEvent } from './index' + +const defaultMeta = { + config: { + dedupMode: 'Event and Timestamp', + }, +} + +describe('`Event and Timestamp` mode', () => { + test('event UUID is properly generated', () => { + const meta = resetMeta() as LegacyTransformationPluginMeta + const event = processEvent({ ...createPageview(), timestamp: '2020-02-02T23:59:59.999999Z' }, meta) + expect(event?.uuid).toEqual('1b2b7e1a-c059-5116-a6d2-eb1c1dd793bc') + }) + test('same key properties produces the same UUID', () => { + const meta = resetMeta() as LegacyTransformationPluginMeta + const event1 = processEvent( + { ...createPageview(), event: 'myPageview', timestamp: '2020-05-02T20:59:59.999999Z', ignoreMe: 'yes' }, + meta + ) + const event2 = processEvent( + { + ...createPageview(), + event: 'myPageview', + timestamp: '2020-05-02T20:59:59.999999Z', + differentProperty: 'indeed', + }, + meta + ) + expect(event1?.uuid).toBeTruthy() + expect(event1?.uuid).toEqual(event2?.uuid) + }) + test('different key properties produces a different UUID', () => { + const meta = resetMeta() as LegacyTransformationPluginMeta + const event1 = processEvent({ ...createPageview(), timestamp: '2020-05-02T20:59:59.999999Z' }, meta) + const event2 = processEvent( + { + ...createPageview(), + timestamp: '2020-05-02T20:59:59.999888Z', // note milliseconds are different + }, + meta + ) + expect(event1?.uuid).toBeTruthy() + expect(event1?.uuid).not.toEqual(event2?.uuid) + }) +}) + +describe('`All Properties` mode', () => { + test('event UUID is properly generated (all props)', () => { + const meta = resetMeta({ + config: { ...defaultMeta.config, dedupMode: 'All Properties' }, + }) as LegacyTransformationPluginMeta + const event = processEvent({ ...createPageview(), timestamp: '2020-02-02T23:59:59.999999Z' }, meta) + expect(event?.uuid).toEqual('5a4e6d35-a9e4-50e2-9d97-4f7cc04e8b30') + }) + test('same key properties produces the same UUID (all props)', () => { + const meta = resetMeta({ + config: { ...defaultMeta.config, dedupMode: 'All Properties' }, + }) as LegacyTransformationPluginMeta + const event1 = processEvent( + { + ...createPageview(), + event: 'myPageview', + timestamp: '2020-05-02T20:59:59.999999Z', + properties: { + ...createPageview().properties, + customProp1: true, + customProp2: 'lgtm!', + }, + }, + meta + ) + const event2 = processEvent( + { + ...createPageview(), + event: 'myPageview', + timestamp: '2020-05-02T20:59:59.999999Z', + properties: { + ...createPageview().properties, + customProp1: true, + customProp2: 'lgtm!', + }, + }, + meta + ) + expect(event1?.uuid).toBeTruthy() + expect(event1?.uuid).toEqual(event2?.uuid) + }) + test('different properties produce a different UUID (all props)', () => { + const meta = resetMeta({ + config: { ...defaultMeta.config, dedupMode: 'All Properties' }, + }) as LegacyTransformationPluginMeta + const event1 = processEvent( + { ...createPageview(), timestamp: '2020-05-02T20:59:59.999999Z', properties: { customProp: '2' } }, + meta + ) + const event2 = processEvent( + { + ...createPageview(), + timestamp: '2020-05-02T20:59:59.999999Z', + properties: { customProp: '1' }, + }, + meta + ) + expect(event1?.uuid).toBeTruthy() + expect(event1?.uuid).not.toEqual(event2?.uuid) + }) +}) diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-app-unduplicator/index.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-app-unduplicator/index.ts new file mode 100644 index 0000000000000..bf86e62b57c84 --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-app-unduplicator/index.ts @@ -0,0 +1,72 @@ +import { PluginEvent } from '@posthog/plugin-scaffold' +import { createHash } from 'crypto' + +import { LegacyTransformationPluginMeta } from '../../types' + +// From UUID Namespace RFC (https://datatracker.ietf.org/doc/html/rfc4122) +const NAMESPACE_OID = '6ba7b812-9dad-11d1-80b4-00c04fd430c8' + +const byteToHex: string[] = [] + +for (let i = 0; i < 256; ++i) { + byteToHex.push((i + 0x100).toString(16).slice(1)) +} + +function stringifyUUID(arr: Buffer) { + // Forked from https://github.com/uuidjs/uuid (MIT) + // Copyright (c) 2010-2020 Robert Kieffer and other contributors + return ( + byteToHex[arr[0]] + + byteToHex[arr[1]] + + byteToHex[arr[2]] + + byteToHex[arr[3]] + + '-' + + byteToHex[arr[4]] + + byteToHex[arr[5]] + + '-' + + byteToHex[arr[6]] + + byteToHex[arr[7]] + + '-' + + byteToHex[arr[8]] + + byteToHex[arr[9]] + + '-' + + byteToHex[arr[10]] + + byteToHex[arr[11]] + + byteToHex[arr[12]] + + byteToHex[arr[13]] + + byteToHex[arr[14]] + + byteToHex[arr[15]] + ).toLowerCase() +} + +export function processEvent(event: PluginEvent, { config }: LegacyTransformationPluginMeta) { + if (!event.timestamp) { + // posthog-js sends events without a timestamp, but with an offset and a UUID. + // Because the UUID is generated by the SDK, we can silently ignore these events. + // For other SDKs, log an info log with the library name. + const lib = event.properties?.$lib || 'unknown' + if (lib !== 'web') { + console.info( + `Received event from "${lib}" without a timestamp, the event will not be processed because deduping will not work.` + ) + } + + return event + } + + // Create a hash of the relevant properties of the event + const stringifiedProps = config.dedupMode === 'All Properties' ? `_${JSON.stringify(event.properties)}` : '' + const hash = createHash('sha1') + const eventKeyBuffer = hash + .update( + `${NAMESPACE_OID}_${event.team_id}_${event.distinct_id}_${event.event}_${event.timestamp}${stringifiedProps}` + ) + .digest() + + // Convert to UUID v5 spec + eventKeyBuffer[6] = (eventKeyBuffer[6] & 0x0f) | 0x50 + eventKeyBuffer[8] = (eventKeyBuffer[8] & 0x3f) | 0x80 + + event.uuid = stringifyUUID(eventKeyBuffer) + return event +} diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-app-unduplicator/plugin.json b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-app-unduplicator/plugin.json new file mode 100644 index 0000000000000..2ab0263f60168 --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-app-unduplicator/plugin.json @@ -0,0 +1,16 @@ +{ + "name": "Unduplicates", + "url": "https://github.com/paolodamico/posthog-plugin-unduplicates", + "description": "Prevent duplicates in your data when ingesting.", + "main": "index.ts", + "posthogVersion": ">=1.25.0", + "config": [ + { + "key": "dedupMode", + "name": "Dedup Mode", + "type": "choice", + "required": true, + "choices": ["Event and Timestamp", "All Properties"] + } + ] +} diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-app-unduplicator/template.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-app-unduplicator/template.ts new file mode 100644 index 0000000000000..3920a9bfed28c --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-app-unduplicator/template.ts @@ -0,0 +1,25 @@ +import { HogFunctionTemplate } from '../../../templates/types' + +// NOTE: This is a deprecated plugin and should never be shown to new users +export const template: HogFunctionTemplate = { + status: 'alpha', + type: 'transformation', + id: 'plugin-posthog-app-unduplicator', + name: 'PostHog App Unduplicator', + description: 'Prevent duplicates in your data when ingesting.', + icon_url: '/static/hedgehog/builder-hog-01.png', + category: ['Custom'], + hog: `return event`, + inputs_schema: [ + { + key: 'dedupMode', + label: 'Dedup Mode', + type: 'choice', + required: true, + choices: [ + { value: 'Event and Timestamp', label: 'Event and Timestamp' }, + { value: 'All Properties', label: 'All Properties' }, + ], + }, + ], +} diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-plugin-geoip/index.test.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-plugin-geoip/index.test.ts new file mode 100644 index 0000000000000..f4189b0745dc4 --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-plugin-geoip/index.test.ts @@ -0,0 +1,136 @@ +import { City, Reader } from '@maxmind/geoip2-node' +import { createPageview, resetMeta } from '@posthog/plugin-scaffold/test/utils' +import { join } from 'path' + +import { LegacyTransformationPluginMeta } from '../../types' +import { processEvent } from './index' + +const DEFAULT_MMDB_FILE_NAME = 'GeoLite2-City-Test.mmdb' + +async function resetMetaWithMmdb( + transformResult = (res: City) => res as Record, + file = DEFAULT_MMDB_FILE_NAME +): Promise { + const mmdb = await Reader.open(join(__dirname, file)) + return resetMeta({ + geoip: { + locate: (ipAddress: string) => { + const res = mmdb.city(ipAddress) + return transformResult(res) + }, + }, + }) as LegacyTransformationPluginMeta +} + +test('event is enriched with IP location', async () => { + const event = processEvent({ ...createPageview(), ip: '89.160.20.129' }, await resetMetaWithMmdb()) + expect(event!.properties).toEqual( + expect.objectContaining({ + $geoip_city_name: 'Linköping', + $geoip_country_name: 'Sweden', + $geoip_country_code: 'SE', + $geoip_continent_name: 'Europe', + $geoip_continent_code: 'EU', + $geoip_latitude: 58.4167, + $geoip_longitude: 15.6167, + $geoip_accuracy_radius: 76, + $geoip_time_zone: 'Europe/Stockholm', + $geoip_subdivision_1_code: 'E', + $geoip_subdivision_1_name: 'Östergötland County', + }) + ) +}) + +test('person is enriched with IP location', async () => { + const event = processEvent({ ...createPageview(), ip: '89.160.20.129' }, await resetMetaWithMmdb()) + expect(event!.properties!.$set).toEqual( + expect.objectContaining({ + $geoip_city_name: 'Linköping', + $geoip_country_name: 'Sweden', + $geoip_country_code: 'SE', + $geoip_continent_name: 'Europe', + $geoip_continent_code: 'EU', + $geoip_latitude: 58.4167, + $geoip_longitude: 15.6167, + $geoip_time_zone: 'Europe/Stockholm', + $geoip_subdivision_1_code: 'E', + $geoip_subdivision_1_name: 'Östergötland County', + }) + ) + expect(event!.properties!.$set_once).toEqual( + expect.objectContaining({ + $initial_geoip_city_name: 'Linköping', + $initial_geoip_country_name: 'Sweden', + $initial_geoip_country_code: 'SE', + $initial_geoip_continent_name: 'Europe', + $initial_geoip_continent_code: 'EU', + $initial_geoip_latitude: 58.4167, + $initial_geoip_longitude: 15.6167, + $initial_geoip_time_zone: 'Europe/Stockholm', + $initial_geoip_subdivision_1_code: 'E', + $initial_geoip_subdivision_1_name: 'Östergötland County', + }) + ) +}) + +test('person props default to null if no values present', async () => { + const removeCityNameFromLookupResult = (res: City) => { + const { city, ...remainingResult } = res + return remainingResult + } + const event = processEvent( + { ...createPageview(), ip: '89.160.20.129' }, + await resetMetaWithMmdb(removeCityNameFromLookupResult) + ) + expect(event!.properties!.$set).toMatchInlineSnapshot(` + Object { + "$geoip_accuracy_radius": 76, + "$geoip_city_confidence": null, + "$geoip_city_name": null, + "$geoip_continent_code": "EU", + "$geoip_continent_name": "Europe", + "$geoip_country_code": "SE", + "$geoip_country_name": "Sweden", + "$geoip_latitude": 58.4167, + "$geoip_longitude": 15.6167, + "$geoip_postal_code": null, + "$geoip_subdivision_1_code": "E", + "$geoip_subdivision_1_name": "Östergötland County", + "$geoip_subdivision_2_code": null, + "$geoip_subdivision_2_name": null, + "$geoip_time_zone": "Europe/Stockholm", + } + `) + expect(event!.properties!.$set_once).toMatchInlineSnapshot(` + Object { + "$initial_geoip_accuracy_radius": 76, + "$initial_geoip_city_confidence": null, + "$initial_geoip_city_name": null, + "$initial_geoip_continent_code": "EU", + "$initial_geoip_continent_name": "Europe", + "$initial_geoip_country_code": "SE", + "$initial_geoip_country_name": "Sweden", + "$initial_geoip_latitude": 58.4167, + "$initial_geoip_longitude": 15.6167, + "$initial_geoip_postal_code": null, + "$initial_geoip_subdivision_1_code": "E", + "$initial_geoip_subdivision_1_name": "Östergötland County", + "$initial_geoip_subdivision_2_code": null, + "$initial_geoip_subdivision_2_name": null, + "$initial_geoip_time_zone": "Europe/Stockholm", + } + `) +}) + +test('error is thrown if meta.geoip is not provided', async () => { + expect.assertions(1) + await expect(async () => processEvent({ ...createPageview(), ip: '89.160.20.129' }, resetMeta())).rejects.toEqual( + new Error('This PostHog version does not have GeoIP capabilities! Upgrade to PostHog 1.24.0 or later') + ) +}) + +test('event is skipped using $geoip_disable', async () => { + const testEvent = { ...createPageview(), ip: '89.160.20.129', properties: { $geoip_disable: true } } + const processedEvent = processEvent(JSON.parse(JSON.stringify(testEvent)), await resetMetaWithMmdb()) + expect(testEvent).toEqual(processedEvent) +}) diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-plugin-geoip/index.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-plugin-geoip/index.ts new file mode 100644 index 0000000000000..02d83bdd53748 --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-plugin-geoip/index.ts @@ -0,0 +1,114 @@ +import { PluginEvent } from '@posthog/plugin-scaffold' + +import { LegacyTransformationPluginMeta } from '../../types' + +const props = { + city_name: null, + city_confidence: null, + subdivision_2_name: null, + subdivision_2_code: null, + subdivision_1_name: null, + subdivision_1_code: null, + country_name: null, + country_code: null, + continent_name: null, + continent_code: null, + postal_code: null, + latitude: null, + longitude: null, + accuracy_radius: null, + time_zone: null, +} + +const defaultLocationSetProps = Object.entries(props).reduce((acc, [key]) => { + acc[`$geoip_${key}`] = null + return acc +}, {} as Record) + +const defaultLocationSetOnceProps = Object.entries(props).reduce((acc, [key]) => { + acc[`$initial_geoip_${key}`] = null + return acc +}, {} as Record) + +export const processEvent = (event: PluginEvent, { geoip }: LegacyTransformationPluginMeta) => { + if (!geoip) { + throw new Error('This PostHog version does not have GeoIP capabilities! Upgrade to PostHog 1.24.0 or later') + } + let ip = event.properties?.$ip || event.ip + if (ip && !event.properties?.$geoip_disable) { + ip = String(ip) + if (ip === '127.0.0.1') { + ip = '13.106.122.3' // Spoofing an Australian IP address for local development + } + const response = geoip.locate(ip) + if (response) { + const location: Record = {} + if (response.city) { + location['city_name'] = response.city.names?.en + if (typeof response.city.confidence === 'number') { + // NOTE: Confidence is part of the enterprise maxmind DB, not typically installed + location['city_confidence'] = response.city.confidence + } + } + if (response.country) { + location['country_name'] = response.country.names?.en + location['country_code'] = response.country.isoCode + if (typeof response.country.confidence === 'number') { + // NOTE: Confidence is part of the enterprise maxmind DB, not typically installed + location['country_confidence'] = response.country.confidence ?? null + } + } + if (response.continent) { + location['continent_name'] = response.continent.names?.en + location['continent_code'] = response.continent.code + } + if (response.postal) { + location['postal_code'] = response.postal.code + if (typeof response.postal.confidence === 'number') { + // NOTE: Confidence is part of the enterprise maxmind DB, not typically installed + location['postal_code_confidence'] = response.postal.confidence ?? null + } + } + if (response.location) { + location['latitude'] = response.location?.latitude + location['longitude'] = response.location?.longitude + location['accuracy_radius'] = response.location?.accuracyRadius + location['time_zone'] = response.location?.timeZone + } + if (response.subdivisions) { + for (const [index, subdivision] of response.subdivisions.entries()) { + location[`subdivision_${index + 1}_code`] = subdivision.isoCode + location[`subdivision_${index + 1}_name`] = subdivision.names?.en + + if (typeof subdivision.confidence === 'number') { + // NOTE: Confidence is part of the enterprise maxmind DB, not typically installed + location[`subdivision_${index + 1}_confidence`] = subdivision.confidence ?? null + } + } + } + + if (!event.properties) { + event.properties = {} + } + + if (!event.properties.$set) { + event.properties.$set = {} + } + if (!event.properties.$set_once) { + event.properties.$set_once = {} + } + event.properties.$set = { ...defaultLocationSetProps, ...(event.properties.$set ?? {}) } + event.properties.$set_once = { + ...defaultLocationSetOnceProps, + ...(event.properties.$set_once ?? {}), + } + + for (const [key, value] of Object.entries(location)) { + event.properties[`$geoip_${key}`] = value + event.properties.$set![`$geoip_${key}`] = value + event.properties.$set_once![`$initial_geoip_${key}`] = value + } + } + return event + } +} diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-plugin-geoip/plugin.json b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-plugin-geoip/plugin.json new file mode 100644 index 0000000000000..723251ca4a6cf --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-plugin-geoip/plugin.json @@ -0,0 +1,8 @@ +{ + "name": "GeoIP", + "url": "https://github.com/PostHog/posthog-plugin-geoip", + "description": "Enrich PostHog events and persons with IP location data", + "main": "index.ts", + "posthogVersion": ">=1.24.0", + "stateless": true +} diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-plugin-geoip/template.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-plugin-geoip/template.ts new file mode 100644 index 0000000000000..4126b8ce1b6d7 --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-plugin-geoip/template.ts @@ -0,0 +1,14 @@ +import { HogFunctionTemplate } from '../../../templates/types' + +// NOTE: This is a deprecated plugin and should never be shown to new users +export const template: HogFunctionTemplate = { + status: 'alpha', + type: 'transformation', + id: 'plugin-posthog-plugin-geoip', + name: 'GeoIP', + description: '', + icon_url: '/static/transformations/geoip.png', + category: ['Custom'], + hog: `return event`, + inputs_schema: [], +} diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-route-censor-plugin/dist.js b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-route-censor-plugin/dist.js new file mode 100644 index 0000000000000..ace8ee11b7cc7 --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-route-censor-plugin/dist.js @@ -0,0 +1,624 @@ +'use strict' + +const checkIsValidHttpUrl = (str) => { + try { + const url = new URL(str) + return url.protocol === 'http:' || url.protocol === 'https:' + } catch (err) { + return false + } +} + +/** + * @remix-run/router v1.5.0 + * + * Copyright (c) Remix Software Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE.md file in the root directory of this source tree. + * + * @license MIT + */ + +//////////////////////////////////////////////////////////////////////////////// +//#region Types and Constants +//////////////////////////////////////////////////////////////////////////////// + +/** + * Actions represent the type of change to a location value. + */ +var Action +;(function (Action) { + /** + * A POP indicates a change to an arbitrary index in the history stack, such + * as a back or forward navigation. It does not describe the direction of the + * navigation, only that the current index changed. + * + * Note: This is the default action for newly created history objects. + */ + Action['Pop'] = 'POP' + /** + * A PUSH indicates a new entry being added to the history stack, such as when + * a link is clicked and a new page loads. When this happens, all subsequent + * entries in the stack are lost. + */ + + Action['Push'] = 'PUSH' + /** + * A REPLACE indicates the entry at the current index in the history stack + * being replaced by a new one. + */ + + Action['Replace'] = 'REPLACE' +})(Action || (Action = {})) +function invariant(value, message) { + if (value === false || value === null || typeof value === 'undefined') { + throw new Error(message) + } +} +function warning(cond, message) { + if (!cond) { + try { + // Welcome to debugging history! + // + // This error is thrown as a convenience so you can more easily + // find the source for a warning that appears in the console by + // enabling "pause on exceptions" in your JavaScript debugger. + throw new Error(message) + } catch (e) {} + } +} +/** + * Parses a string URL path into its separate pathname, search, and hash components. + */ + +function parsePath(path) { + let parsedPath = {} + + if (path) { + let hashIndex = path.indexOf('#') + + if (hashIndex >= 0) { + parsedPath.hash = path.substr(hashIndex) + path = path.substr(0, hashIndex) + } + + let searchIndex = path.indexOf('?') + + if (searchIndex >= 0) { + parsedPath.search = path.substr(searchIndex) + path = path.substr(0, searchIndex) + } + + if (path) { + parsedPath.pathname = path + } + } + + return parsedPath +} + +var ResultType +;(function (ResultType) { + ResultType['data'] = 'data' + ResultType['deferred'] = 'deferred' + ResultType['redirect'] = 'redirect' + ResultType['error'] = 'error' +})(ResultType || (ResultType = {})) +/** + * Matches the given routes to a location and returns the match data. + * + * @see https://reactrouter.com/utils/match-routes + */ + +function matchRoutes(routes, locationArg, basename) { + if (basename === void 0) { + basename = '/' + } + + let location = typeof locationArg === 'string' ? parsePath(locationArg) : locationArg + let pathname = stripBasename(location.pathname || '/', basename) + + if (pathname == null) { + return null + } + + let branches = flattenRoutes(routes) + rankRouteBranches(branches) + let matches = null + + for (let i = 0; matches == null && i < branches.length; ++i) { + matches = matchRouteBranch( + branches[i], // Incoming pathnames are generally encoded from either window.location + // or from router.navigate, but we want to match against the unencoded + // paths in the route definitions. Memory router locations won't be + // encoded here but there also shouldn't be anything to decode so this + // should be a safe operation. This avoids needing matchRoutes to be + // history-aware. + safelyDecodeURI(pathname) + ) + } + + return matches +} + +function flattenRoutes(routes, branches, parentsMeta, parentPath) { + if (branches === void 0) { + branches = [] + } + + if (parentsMeta === void 0) { + parentsMeta = [] + } + + if (parentPath === void 0) { + parentPath = '' + } + + let flattenRoute = (route, index, relativePath) => { + let meta = { + relativePath: relativePath === undefined ? route.path || '' : relativePath, + caseSensitive: route.caseSensitive === true, + childrenIndex: index, + route, + } + + if (meta.relativePath.startsWith('/')) { + invariant( + meta.relativePath.startsWith(parentPath), + 'Absolute route path "' + + meta.relativePath + + '" nested under path ' + + ('"' + parentPath + '" is not valid. An absolute child route path ') + + 'must start with the combined path of all its parent routes.' + ) + meta.relativePath = meta.relativePath.slice(parentPath.length) + } + + let path = joinPaths([parentPath, meta.relativePath]) + let routesMeta = parentsMeta.concat(meta) // Add the children before adding this route to the array so we traverse the + // route tree depth-first and child routes appear before their parents in + // the "flattened" version. + + if (route.children && route.children.length > 0) { + invariant( + // Our types know better, but runtime JS may not! + route.index !== true, + 'Index routes must not have child routes. Please remove ' + + ('all child routes from route path "' + path + '".') + ) + flattenRoutes(route.children, branches, routesMeta, path) + } // Routes without a path shouldn't ever match by themselves unless they are + // index routes, so don't add them to the list of possible branches. + + if (route.path == null && !route.index) { + return + } + + branches.push({ + path, + score: computeScore(path, route.index), + routesMeta, + }) + } + + routes.forEach((route, index) => { + var _route$path + + // coarse-grain check for optional params + if (route.path === '' || !((_route$path = route.path) != null && _route$path.includes('?'))) { + flattenRoute(route, index) + } else { + for (let exploded of explodeOptionalSegments(route.path)) { + flattenRoute(route, index, exploded) + } + } + }) + return branches +} +/** + * Computes all combinations of optional path segments for a given path, + * excluding combinations that are ambiguous and of lower priority. + * + * For example, `/one/:two?/three/:four?/:five?` explodes to: + * - `/one/three` + * - `/one/:two/three` + * - `/one/three/:four` + * - `/one/three/:five` + * - `/one/:two/three/:four` + * - `/one/:two/three/:five` + * - `/one/three/:four/:five` + * - `/one/:two/three/:four/:five` + */ + +function explodeOptionalSegments(path) { + let segments = path.split('/') + if (segments.length === 0) { + return [] + } + let [first, ...rest] = segments // Optional path segments are denoted by a trailing `?` + + let isOptional = first.endsWith('?') // Compute the corresponding required segment: `foo?` -> `foo` + + let required = first.replace(/\?$/, '') + + if (rest.length === 0) { + // Intepret empty string as omitting an optional segment + // `["one", "", "three"]` corresponds to omitting `:two` from `/one/:two?/three` -> `/one/three` + return isOptional ? [required, ''] : [required] + } + + let restExploded = explodeOptionalSegments(rest.join('/')) + let result = [] // All child paths with the prefix. Do this for all children before the + // optional version for all children so we get consistent ordering where the + // parent optional aspect is preferred as required. Otherwise, we can get + // child sections interspersed where deeper optional segments are higher than + // parent optional segments, where for example, /:two would explodes _earlier_ + // then /:one. By always including the parent as required _for all children_ + // first, we avoid this issue + + result.push(...restExploded.map((subpath) => (subpath === '' ? required : [required, subpath].join('/')))) // Then if this is an optional value, add all child versions without + + if (isOptional) { + result.push(...restExploded) + } // for absolute paths, ensure `/` instead of empty segment + + return result.map((exploded) => (path.startsWith('/') && exploded === '' ? '/' : exploded)) +} + +function rankRouteBranches(branches) { + branches.sort((a, b) => + a.score !== b.score + ? b.score - a.score // Higher score first + : compareIndexes( + a.routesMeta.map((meta) => meta.childrenIndex), + b.routesMeta.map((meta) => meta.childrenIndex) + ) + ) +} + +const paramRe = /^:\w+$/ +const dynamicSegmentValue = 3 +const indexRouteValue = 2 +const emptySegmentValue = 1 +const staticSegmentValue = 10 +const splatPenalty = -2 + +const isSplat = (s) => s === '*' + +function computeScore(path, index) { + let segments = path.split('/') + let initialScore = segments.length + + if (segments.some(isSplat)) { + initialScore += splatPenalty + } + + if (index) { + initialScore += indexRouteValue + } + + return segments + .filter((s) => !isSplat(s)) + .reduce( + (score, segment) => + score + + (paramRe.test(segment) ? dynamicSegmentValue : segment === '' ? emptySegmentValue : staticSegmentValue), + initialScore + ) +} + +function compareIndexes(a, b) { + let siblings = a.length === b.length && a.slice(0, -1).every((n, i) => n === b[i]) + return siblings // If two routes are siblings, we should try to match the earlier sibling + ? // first. This allows people to have fine-grained control over the matching + // behavior by simply putting routes with identical paths in the order they + // want them tried. + a[a.length - 1] - b[b.length - 1] // Otherwise, it doesn't really make sense to rank non-siblings by index, + : // so they sort equally. + 0 +} + +function matchRouteBranch(branch, pathname) { + let { routesMeta } = branch + let matchedParams = {} + let matchedPathname = '/' + let matches = [] + + for (let i = 0; i < routesMeta.length; ++i) { + let meta = routesMeta[i] + let end = i === routesMeta.length - 1 + let remainingPathname = matchedPathname === '/' ? pathname : pathname.slice(matchedPathname.length) || '/' + let match = matchPath( + { + path: meta.relativePath, + caseSensitive: meta.caseSensitive, + end, + }, + remainingPathname + ) + if (!match) { + return null + } + Object.assign(matchedParams, match.params) + let route = meta.route + matches.push({ + // TODO: Can this as be avoided? + params: matchedParams, + pathname: joinPaths([matchedPathname, match.pathname]), + pathnameBase: normalizePathname(joinPaths([matchedPathname, match.pathnameBase])), + route, + }) + + if (match.pathnameBase !== '/') { + matchedPathname = joinPaths([matchedPathname, match.pathnameBase]) + } + } + + return matches +} +/** + * Performs pattern matching on a URL pathname and returns information about + * the match. + * + * @see https://reactrouter.com/utils/match-path + */ + +function matchPath(pattern, pathname) { + if (typeof pattern === 'string') { + pattern = { + path: pattern, + caseSensitive: false, + end: true, + } + } + + let [matcher, paramNames] = compilePath(pattern.path, pattern.caseSensitive, pattern.end) + let match = pathname.match(matcher) + if (!match) { + return null + } + let matchedPathname = match[0] + let pathnameBase = matchedPathname.replace(/(.)\/+$/, '$1') + let captureGroups = match.slice(1) + let params = paramNames.reduce((memo, paramName, index) => { + // We need to compute the pathnameBase here using the raw splat value + // instead of using params["*"] later because it will be decoded then + if (paramName === '*') { + let splatValue = captureGroups[index] || '' + pathnameBase = matchedPathname.slice(0, matchedPathname.length - splatValue.length).replace(/(.)\/+$/, '$1') + } + + memo[paramName] = safelyDecodeURIComponent(captureGroups[index] || '', paramName) + return memo + }, {}) + return { + params, + pathname: matchedPathname, + pathnameBase, + pattern, + } +} + +function compilePath(path, caseSensitive, end) { + if (caseSensitive === void 0) { + caseSensitive = false + } + + if (end === void 0) { + end = true + } + + warning( + path === '*' || !path.endsWith('*') || path.endsWith('/*'), + 'Route path "' + + path + + '" will be treated as if it were ' + + ('"' + path.replace(/\*$/, '/*') + '" because the `*` character must ') + + 'always follow a `/` in the pattern. To get rid of this warning, ' + + ('please change the route path to "' + path.replace(/\*$/, '/*') + '".') + ) + let paramNames = [] + let regexpSource = + '^' + + path + .replace(/\/*\*?$/, '') // Ignore trailing / and /*, we'll handle it below + .replace(/^\/*/, '/') // Make sure it has a leading / + .replace(/[\\.*+^$?{}|()[\]]/g, '\\$&') // Escape special regex chars + .replace(/\/:(\w+)/g, (_, paramName) => { + paramNames.push(paramName) + return '/([^\\/]+)' + }) + + if (path.endsWith('*')) { + paramNames.push('*') + regexpSource += + path === '*' || path === '/*' + ? '(.*)$' // Already matched the initial /, just match the rest + : '(?:\\/(.+)|\\/*)$' // Don't include the / in params["*"] + } else if (end) { + // When matching to the end, ignore trailing slashes + regexpSource += '\\/*$' + } else if (path !== '' && path !== '/') { + // If our path is non-empty and contains anything beyond an initial slash, + // then we have _some_ form of path in our regex so we should expect to + // match only if we find the end of this path segment. Look for an optional + // non-captured trailing slash (to match a portion of the URL) or the end + // of the path (if we've matched to the end). We used to do this with a + // word boundary but that gives false positives on routes like + // /user-preferences since `-` counts as a word boundary. + regexpSource += '(?:(?=\\/|$))' + } else { + } + + let matcher = new RegExp(regexpSource, caseSensitive ? undefined : 'i') + return [matcher, paramNames] +} + +function safelyDecodeURI(value) { + try { + return decodeURI(value) + } catch (error) { + warning( + false, + 'The URL path "' + + value + + '" could not be decoded because it is is a ' + + 'malformed URL segment. This is probably due to a bad percent ' + + ('encoding (' + error + ').') + ) + return value + } +} + +function safelyDecodeURIComponent(value, paramName) { + try { + return decodeURIComponent(value) + } catch (error) { + warning( + false, + 'The value for the URL param "' + + paramName + + '" will not be decoded because' + + (' the string "' + value + '" is a malformed URL segment. This is probably') + + (' due to a bad percent encoding (' + error + ').') + ) + return value + } +} +/** + * @private + */ + +function stripBasename(pathname, basename) { + if (basename === '/') { + return pathname + } + + if (!pathname.toLowerCase().startsWith(basename.toLowerCase())) { + return null + } // We want to leave trailing slash behavior in the user's control, so if they + // specify a basename with a trailing slash, we should support it + + let startIndex = basename.endsWith('/') ? basename.length - 1 : basename.length + let nextChar = pathname.charAt(startIndex) + + if (nextChar && nextChar !== '/') { + // pathname does not start with basename/ + return null + } + + return pathname.slice(startIndex) || '/' +} +/** + * @private + */ + +const joinPaths = (paths) => paths.join('/').replace(/\/\/+/g, '/') +/** + * @private + */ + +const normalizePathname = (pathname) => pathname.replace(/\/+$/, '').replace(/^\/*/, '/') + +const validMutationMethodsArr = ['post', 'put', 'patch', 'delete'] +;['get', ...validMutationMethodsArr] + +/** + * Take a URL path and applies the react router v6 route matching algorithm + * to censor portions of the URL + * + * @param path URL path to censor + * @returns same URL path, but with all included variables censored + */ +const censorUrlPath = (path, routes) => { + if (typeof path !== 'string') { + return path + } + // If no routes, then just censor all paths to be safe. + if (typeof routes === 'undefined') { + return '/:censoredFullPath' + } + // Find matches with URL path using React Router's parsing algoritm. + const matches = matchRoutes(routes, path) + // If no matches, then no need to censor anything. + if (!matches?.length) { + return path + } + let censoredPath = path + // Check each match, if the variable is in the "includes" array, then censor it. + matches.forEach((match) => { + match.route.include.forEach((variableToCensor) => { + const value = match.params[variableToCensor] + if (!value) { + return + } + censoredPath = censoredPath.replace(value, `:${variableToCensor}`) + }) + }) + return censoredPath +} + +/** + * Removes addresses and hashes from URLs stored in posthog properties. + * + * @param properties Full list of properties passed into the event. + * @param propertiesToAnonymize List of properties that should be anonymized. + * @returns The anonymized list of properties. + */ +const censorProperties = (properties, routes, propertiesToAnonymize) => { + if (!properties) { + return {} + } + const censoredProperties = {} + propertiesToAnonymize.forEach((propertyKey) => { + const propertyValue = properties[propertyKey] + if (!propertyValue || typeof propertyValue !== 'string') { + return + } + const isValidUrl = checkIsValidHttpUrl(propertyValue) + // For full URLs, first parse out the path. + if (isValidUrl) { + const url = new URL(propertyValue) + const censoredPath = censorUrlPath(url.pathname, routes) + // Piece back together the URL but with the censored path. + censoredProperties[propertyKey] = `${url.origin}${censoredPath}` + return + } + // Otherwise assume the propertyValue is a url path (instead of the full url) and we can censor it directly. + censoredProperties[propertyKey] = censorUrlPath(propertyValue, routes) + }) + return censoredProperties +} + +/** + * Runs on every event + * + * @param event PostHog event + * @param meta metadata defined in the plugin.json + * @returns modified event + */ +const processEvent = (event, { global }) => { + // If we don't have routes to censor, then just return the input event. + if (!global.routes?.length) { + return event + } + return { + ...event, + properties: { + ...event.properties, + ...censorProperties(event.properties, global.routes, global.properties), + }, + $set: { + ...event.$set, + ...censorProperties(event.$set, global.routes, global.setProperties), + }, + $set_once: { + ...event.$set_once, + ...censorProperties(event.$set_once, global.routes, global.setOnceProperties), + }, + } +} + +exports.processEvent = processEvent +exports.setupPlugin = setupPlugin diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-route-censor-plugin/index.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-route-censor-plugin/index.ts new file mode 100644 index 0000000000000..d73ff44c43921 --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-route-censor-plugin/index.ts @@ -0,0 +1,21 @@ +import { LegacyTransformationPlugin, LegacyTransformationPluginMeta } from '../../types' +import { processEvent } from './dist' +import metadata from './plugin.json' + +// NOTE: The dist.js is a compiled version of the plugin as it is has external dependencies that it inlines +// It is mostly untouched other than removing console logs and moving the setup code here + +const setupPlugin = ({ global, config, logger }: LegacyTransformationPluginMeta) => { + global.properties = config.properties.split(',') + global.setProperties = config.set_properties.split(',') + global.setOnceProperties = config.set_once_properties.split(',') + global.routes = typeof config.routes === 'string' ? JSON.parse(config.routes) : config.routes + logger.debug('Plugin set up with global config: ', JSON.stringify(global, null, 2)) +} + +export const posthogRouteCensorPlugin: LegacyTransformationPlugin = { + id: 'posthog-route-censor-plugin', + metadata: metadata as any, + processEvent, + setupPlugin, +} diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-route-censor-plugin/plugin.json b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-route-censor-plugin/plugin.json new file mode 100644 index 0000000000000..17089005c3953 --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-route-censor-plugin/plugin.json @@ -0,0 +1,43 @@ +{ + "name": "Route Censor", + "description": "Removes segments of URLs based on route patterns.", + "url": "https://github.com/ava-labs/posthog-route-censor-plugin", + "main": "dist/index.js", + "config": [ + { + "markdown": "Removes segments of URLs based on route patterns. See [Github Repo](https://github.com/ava-labs/posthog-route-censor-plugin) for more details." + }, + { + "key": "routes", + "name": "List of routes following the React Router route patterns.", + "markdown": "[Example Here](https://github.com/ava-labs/posthog-route-censor-plugin/blob/main/src/assets/exampleRoutes.json). See package [README](https://github.com/ava-labs/posthog-route-censor-plugin) for more details.", + "type": "attachment", + "hint": "See README for more details and example.", + "required": true + }, + { + "key": "properties", + "name": "List of properties to censor", + "type": "string", + "default": "$current_url,$referrer,$pathname,$initial_current_url,initial_pathname,initial_referrer", + "hint": "Separate properties with commas, without using spaces, like so: `foo,bar,$baz`", + "required": false + }, + { + "key": "set_properties", + "name": "List of $set properties to censor", + "type": "string", + "default": "$initial_current_url,$initial_pathname,$initial_referrer", + "hint": "Separate properties with commas, without using spaces, like so: `foo,bar,$baz`", + "required": false + }, + { + "key": "set_once_properties", + "name": "List of $set_once properties to censor", + "type": "string", + "default": "$initial_current_url,$initial_pathname,$initial_referrer", + "hint": "Separate properties with commas, without using spaces, like so: `foo,bar,$baz`", + "required": false + } + ] +} diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-route-censor-plugin/template.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-route-censor-plugin/template.ts new file mode 100644 index 0000000000000..cb0e3eaf048d9 --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-route-censor-plugin/template.ts @@ -0,0 +1,47 @@ +import { HogFunctionTemplate } from '../../../templates/types' + +// NOTE: This is a deprecated plugin and should never be shown to new users +export const template: HogFunctionTemplate = { + status: 'alpha', + type: 'transformation', + id: 'plugin-posthog-route-censor-plugin', + name: 'Route Censor', + description: 'Removes segments of URLs based on route patterns.', + icon_url: '/static/hedgehog/builder-hog-01.png', + category: ['Custom'], + hog: `return event`, + inputs_schema: [ + { + key: 'routes', + label: 'List of routes following the React Router route patterns.', + description: + '[Example Here](https://github.com/ava-labs/posthog-route-censor-plugin/blob/main/src/assets/exampleRoutes.json). See package [README](https://github.com/ava-labs/posthog-route-censor-plugin) for more details.', + type: 'string', + required: true, + }, + { + key: 'properties', + label: 'List of properties to censor', + type: 'string', + default: '$current_url,$referrer,$pathname,$initial_current_url,initial_pathname,initial_referrer', + description: 'Separate properties with commas, without using spaces, like so: `foo,bar,$baz`', + required: false, + }, + { + key: 'set_properties', + label: 'List of $set properties to censor', + type: 'string', + default: '$initial_current_url,$initial_pathname,$initial_referrer', + description: 'Separate properties with commas, without using spaces, like so: `foo,bar,$baz`', + required: false, + }, + { + key: 'set_once_properties', + label: 'List of $set_once properties to censor', + type: 'string', + default: '$initial_current_url,$initial_pathname,$initial_referrer', + description: 'Separate properties with commas, without using spaces, like so: `foo,bar,$baz`', + required: false, + }, + ], +} From ed541dd34f4f8feed41031db3486d4aaf10070a3 Mon Sep 17 00:00:00 2001 From: Ben White Date: Thu, 30 Jan 2025 15:41:39 +0100 Subject: [PATCH 129/163] Fixes --- .../_destinations/customerio/index.ts | 2 -- .../_destinations/intercom/index.ts | 2 -- .../index.ts | 8 ++++++-- .../downsampling-plugin/index.ts | 2 -- .../drop-events-on-property-plugin/index.ts | 7 ++++++- .../flatten-properties-plugin/index.ts | 8 ++++---- .../language-url-splitter-app/index.ts | 2 -- .../plugin-advanced-geoip/index.ts | 7 ++++++- .../plugin-stonly-UTM-Extractor/index.ts | 7 ++++++- .../posthog-app-unduplicator/index.ts | 7 ++++++- .../index.ts | 2 -- .../posthog-filter-out-plugin/index.ts | 2 -- .../posthog-plugin-geoip/index.ts | 7 ++++++- .../posthog-route-censor-plugin/index.ts | 2 -- .../posthog-url-normalizer-plugin/index.ts | 2 -- .../property-filter-plugin/index.ts | 2 -- .../semver-flattener-plugin/index.ts | 2 -- .../_transformations/taxonomy-plugin/index.ts | 2 -- .../timestamp-parser-plugin/index.ts | 2 -- .../user-agent-plugin/index.ts | 2 -- plugin-server/src/cdp/legacy-plugins/index.ts | 19 +++++++++++++++++++ plugin-server/src/cdp/legacy-plugins/types.ts | 10 +--------- 22 files changed, 60 insertions(+), 46 deletions(-) diff --git a/plugin-server/src/cdp/legacy-plugins/_destinations/customerio/index.ts b/plugin-server/src/cdp/legacy-plugins/_destinations/customerio/index.ts index 7099602c87f11..04c69ed4d70bd 100644 --- a/plugin-server/src/cdp/legacy-plugins/_destinations/customerio/index.ts +++ b/plugin-server/src/cdp/legacy-plugins/_destinations/customerio/index.ts @@ -4,7 +4,6 @@ import { RetryError } from '@posthog/plugin-scaffold' import { Response } from '~/src/utils/fetch' import { LegacyDestinationPlugin, LegacyDestinationPluginMeta } from '../../types' -import metadata from './plugin.json' const DEFAULT_HOST = 'track.customer.io' const DEFAULT_SEND_EVENTS_FROM_ANONYMOUS_USERS = 'Send all events' @@ -257,7 +256,6 @@ function getEmailFromEvent(event: ProcessedPluginEvent): string | null { export const customerioPlugin: LegacyDestinationPlugin = { id: 'customerio-plugin', - metadata: metadata as any, setupPlugin: setupPlugin as any, onEvent, } diff --git a/plugin-server/src/cdp/legacy-plugins/_destinations/intercom/index.ts b/plugin-server/src/cdp/legacy-plugins/_destinations/intercom/index.ts index fd7138cdd8062..d49e187652219 100644 --- a/plugin-server/src/cdp/legacy-plugins/_destinations/intercom/index.ts +++ b/plugin-server/src/cdp/legacy-plugins/_destinations/intercom/index.ts @@ -3,7 +3,6 @@ import { ProcessedPluginEvent, RetryError } from '@posthog/plugin-scaffold' import { Response } from '~/src/utils/fetch' import { LegacyDestinationPlugin, LegacyDestinationPluginMeta } from '../../types' -import metadata from './plugin.json' type IntercomMeta = LegacyDestinationPluginMeta & { global: { @@ -191,7 +190,6 @@ function getTimestamp(meta: IntercomMeta, event: ProcessedPluginEvent): number { export const intercomPlugin: LegacyDestinationPlugin = { id: 'posthog-intercom-plugin', - metadata: metadata as any, onEvent, setupPlugin: () => Promise.resolve(), } diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/Plugin-Stonly-Clean-Campaign-Name/index.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/Plugin-Stonly-Clean-Campaign-Name/index.ts index ac1efa5e5ff9d..00912ed93cc8f 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/Plugin-Stonly-Clean-Campaign-Name/index.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/Plugin-Stonly-Clean-Campaign-Name/index.ts @@ -1,7 +1,6 @@ import { PluginEvent } from '@posthog/plugin-scaffold' -import { LegacyTransformationPluginMeta } from '../../types' - +import { LegacyTransformationPlugin, LegacyTransformationPluginMeta } from '../../types' const cleanUtmCampain = (utmCampaign: string) => { return utmCampaign .replace('com-', 'comp-') @@ -30,3 +29,8 @@ export function processEvent(event: PluginEvent, _: LegacyTransformationPluginMe // Return the event to be ingested, or return null to discard return event } + +export const pluginStonlyCleanCampaignName: LegacyTransformationPlugin = { + id: 'plugin-stonly-clean-campaign-name', + processEvent, +} diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/downsampling-plugin/index.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/downsampling-plugin/index.ts index 640f3ff06ac64..fcb9756998580 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/downsampling-plugin/index.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/downsampling-plugin/index.ts @@ -2,7 +2,6 @@ import { PluginEvent } from '@posthog/plugin-scaffold' import { createHash } from 'crypto' import { LegacyTransformationPlugin, LegacyTransformationPluginMeta } from '../../types' -import metadata from './plugin.json' export function setupPlugin({ config, global }: LegacyTransformationPluginMeta) { const percentage = parseFloat(config.percentage) @@ -39,7 +38,6 @@ export function processEvent(event: PluginEvent, { global }: LegacyTransformatio export const downsamplingPlugin: LegacyTransformationPlugin = { id: 'downsampling-plugin', - metadata: metadata as any, processEvent, setupPlugin, } diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/drop-events-on-property-plugin/index.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/drop-events-on-property-plugin/index.ts index 7fc9b55aadea3..f60ba936447a5 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/drop-events-on-property-plugin/index.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/drop-events-on-property-plugin/index.ts @@ -2,7 +2,7 @@ import { PluginEvent } from '@posthog/plugin-scaffold' -import { LegacyTransformationPluginMeta } from '../../types' +import { LegacyTransformationPlugin, LegacyTransformationPluginMeta } from '../../types' // Processes each event, optionally transforming it export function processEvent(event: PluginEvent, { config }: LegacyTransformationPluginMeta) { @@ -19,3 +19,8 @@ export function processEvent(event: PluginEvent, { config }: LegacyTransformatio // Return the event to be ingested, or return null to discard return event } + +export const dropEventsOnPropertyPlugin: LegacyTransformationPlugin = { + id: 'drop-events-on-property-plugin', + processEvent, +} diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/flatten-properties-plugin/index.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/flatten-properties-plugin/index.ts index f23309a8bce04..1f1725d0472e1 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/flatten-properties-plugin/index.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/flatten-properties-plugin/index.ts @@ -1,7 +1,6 @@ import { PluginEvent } from '@posthog/plugin-scaffold' -import { LegacyTransformationPluginMeta } from '../../types' - +import { LegacyTransformationPlugin, LegacyTransformationPluginMeta } from '../../types' /** * Some events will always create very large numbers of flattened properties * This is undesirable since a large enough number of properties for a particular team can slow down the property filter in the UI @@ -10,7 +9,7 @@ import { LegacyTransformationPluginMeta } from '../../types' */ const eventDenyList = ['$autocapture', 'organization usage report'] -function processEvent(event: PluginEvent, { config }: LegacyTransformationPluginMeta) { +export function processEvent(event: PluginEvent, { config }: LegacyTransformationPluginMeta) { try { if (!eventDenyList.includes(event.event) && event.properties) { event.properties = flattenProperties(event.properties, config.separator) @@ -57,6 +56,7 @@ const flattenProperties = (props: Record, sep: string, nestedChain: return { ...props, ...newProps } } -module.exports = { +export const flattenPropertiesPlugin: LegacyTransformationPlugin = { + id: 'flatten-properties-plugin', processEvent, } diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/language-url-splitter-app/index.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/language-url-splitter-app/index.ts index 776b895ef9533..9c750e9c003eb 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/language-url-splitter-app/index.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/language-url-splitter-app/index.ts @@ -1,7 +1,6 @@ import { PluginEvent } from '@posthog/plugin-scaffold' import { LegacyTransformationPlugin, LegacyTransformationPluginMeta } from '../../types' -import metadata from './plugin.json' export function processEvent(event: PluginEvent, { config }: LegacyTransformationPluginMeta) { const { pattern, matchGroup, property, replacePattern, replaceKey, replaceValue } = config @@ -21,6 +20,5 @@ export function processEvent(event: PluginEvent, { config }: LegacyTransformatio export const languageUrlSplitterApp: LegacyTransformationPlugin = { id: 'language-url-splitter-app', - metadata: metadata as any, processEvent, } diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-advanced-geoip/index.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-advanced-geoip/index.ts index 4d7ab9c3886c5..8515e60c1f280 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-advanced-geoip/index.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-advanced-geoip/index.ts @@ -1,6 +1,6 @@ import { PluginEvent } from '@posthog/plugin-scaffold' -import { LegacyTransformationPluginMeta } from '../../types' +import { LegacyTransformationPlugin, LegacyTransformationPluginMeta } from '../../types' const geoIpProps = [ '$geoip_city_name', @@ -97,3 +97,8 @@ export const processEvent = (event: PluginEvent, { config, logger }: LegacyTrans return event } + +export const pluginAdvancedGeoip: LegacyTransformationPlugin = { + id: 'plugin-advanced-geoip', + processEvent, +} diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-stonly-UTM-Extractor/index.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-stonly-UTM-Extractor/index.ts index 5acdbf53615a6..dfd5ac033916b 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-stonly-UTM-Extractor/index.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-stonly-UTM-Extractor/index.ts @@ -1,7 +1,7 @@ import { PluginEvent } from '@posthog/plugin-scaffold' import { URL } from 'url' -import { LegacyTransformationPluginMeta } from '../../types' +import { LegacyTransformationPlugin, LegacyTransformationPluginMeta } from '../../types' // Processes each event, optionally transforming it const extractUtmFromUrl = (urlString: string, utm: string) => { @@ -39,3 +39,8 @@ export function processEvent(event: PluginEvent, _: LegacyTransformationPluginMe // Return the event to be ingested, or return null to discard return event } + +export const pluginStonlyUtmExtractor: LegacyTransformationPlugin = { + id: 'plugin-stonly-utm-extractor', + processEvent, +} diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-app-unduplicator/index.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-app-unduplicator/index.ts index bf86e62b57c84..88236b5c2e145 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-app-unduplicator/index.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-app-unduplicator/index.ts @@ -1,7 +1,7 @@ import { PluginEvent } from '@posthog/plugin-scaffold' import { createHash } from 'crypto' -import { LegacyTransformationPluginMeta } from '../../types' +import { LegacyTransformationPlugin, LegacyTransformationPluginMeta } from '../../types' // From UUID Namespace RFC (https://datatracker.ietf.org/doc/html/rfc4122) const NAMESPACE_OID = '6ba7b812-9dad-11d1-80b4-00c04fd430c8' @@ -70,3 +70,8 @@ export function processEvent(event: PluginEvent, { config }: LegacyTransformatio event.uuid = stringifyUUID(eventKeyBuffer) return event } + +export const posthogAppUnduplicator: LegacyTransformationPlugin = { + id: 'posthog-app-unduplicator', + processEvent, +} diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-app-url-parameters-to-event-properties/index.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-app-url-parameters-to-event-properties/index.ts index 804b1e15a2f4a..affa7c067f22e 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-app-url-parameters-to-event-properties/index.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-app-url-parameters-to-event-properties/index.ts @@ -2,7 +2,6 @@ import { PluginEvent } from '@posthog/plugin-scaffold' import { URLSearchParams } from 'url' import { LegacyTransformationPlugin, LegacyTransformationPluginMeta } from '../../types' -import metadata from './plugin.json' export type PluginConfig = { ignoreCase: 'true' | 'false' @@ -87,7 +86,6 @@ export const processEvent = (event: PluginEvent, meta: LocalMeta): PluginEvent = export const posthogAppUrlParametersToEventPropertiesPlugin: LegacyTransformationPlugin = { id: 'posthog-app-url-parameters-to-event-properties', - metadata: metadata as any, processEvent, setupPlugin: setupPlugin as any, } diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-filter-out-plugin/index.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-filter-out-plugin/index.ts index da677bb5539e7..852f92f774506 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-filter-out-plugin/index.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-filter-out-plugin/index.ts @@ -1,7 +1,6 @@ import { Meta, PluginAttachment, PluginEvent } from '@posthog/plugin-scaffold' import { LegacyTransformationPlugin, LegacyTransformationPluginMeta } from '../../types' -import metadata from './plugin.json' export interface Filter { property: string @@ -129,7 +128,6 @@ const parseFiltersAndMigrate = (filters: Filter[][] | Filter[]): Filter[][] => { export const posthogFilterOutPlugin: LegacyTransformationPlugin = { id: 'posthog-filter-out-plugin', - metadata: metadata as any, processEvent, setupPlugin, } diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-plugin-geoip/index.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-plugin-geoip/index.ts index 02d83bdd53748..6dc6cbb87b9c0 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-plugin-geoip/index.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-plugin-geoip/index.ts @@ -1,6 +1,6 @@ import { PluginEvent } from '@posthog/plugin-scaffold' -import { LegacyTransformationPluginMeta } from '../../types' +import { LegacyTransformationPlugin, LegacyTransformationPluginMeta } from '../../types' const props = { city_name: null, @@ -112,3 +112,8 @@ export const processEvent = (event: PluginEvent, { geoip }: LegacyTransformation return event } } + +export const posthogPluginGeoip: LegacyTransformationPlugin = { + id: 'posthog-plugin-geoip', + processEvent, +} diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-route-censor-plugin/index.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-route-censor-plugin/index.ts index d73ff44c43921..5c26102cb3ae8 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-route-censor-plugin/index.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-route-censor-plugin/index.ts @@ -1,6 +1,5 @@ import { LegacyTransformationPlugin, LegacyTransformationPluginMeta } from '../../types' import { processEvent } from './dist' -import metadata from './plugin.json' // NOTE: The dist.js is a compiled version of the plugin as it is has external dependencies that it inlines // It is mostly untouched other than removing console logs and moving the setup code here @@ -15,7 +14,6 @@ const setupPlugin = ({ global, config, logger }: LegacyTransformationPluginMeta) export const posthogRouteCensorPlugin: LegacyTransformationPlugin = { id: 'posthog-route-censor-plugin', - metadata: metadata as any, processEvent, setupPlugin, } diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-url-normalizer-plugin/index.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-url-normalizer-plugin/index.ts index b9512e3aee046..2b19017399127 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-url-normalizer-plugin/index.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-url-normalizer-plugin/index.ts @@ -2,7 +2,6 @@ import { PluginEvent } from '@posthog/plugin-scaffold' import { LegacyTransformationPluginMeta } from '../../types' import { LegacyTransformationPlugin } from '../../types' -import metadata from './plugin.json' function normalizeUrl(url: string): string { try { @@ -29,6 +28,5 @@ export function processEvent(event: PluginEvent, { logger }: LegacyTransformatio export const posthogUrlNormalizerPlugin: LegacyTransformationPlugin = { id: 'posthog-url-normalizer-plugin', - metadata: metadata as any, processEvent, } diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/property-filter-plugin/index.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/property-filter-plugin/index.ts index f98ecc7c81dee..9a63154851b2a 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/property-filter-plugin/index.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/property-filter-plugin/index.ts @@ -1,7 +1,6 @@ import { PluginEvent } from '@posthog/plugin-scaffold' import { LegacyTransformationPlugin, LegacyTransformationPluginMeta } from '../../types' -import metadata from './plugin.json' export function setupPlugin({ config, global }: LegacyTransformationPluginMeta) { global.propertiesToFilter = config.properties.split(',') @@ -36,7 +35,6 @@ export function processEvent(event: PluginEvent, { global }: LegacyTransformatio export const propertyFilterPlugin: LegacyTransformationPlugin = { id: 'property-filter-plugin', - metadata: metadata as any, processEvent, setupPlugin, } diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/semver-flattener-plugin/index.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/semver-flattener-plugin/index.ts index 94b98c6ef630b..dc67495ffa76c 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/semver-flattener-plugin/index.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/semver-flattener-plugin/index.ts @@ -1,7 +1,6 @@ import { PluginEvent } from '@posthog/plugin-scaffold' import { LegacyTransformationPlugin, LegacyTransformationPluginMeta } from '../../types' -import metadata from './plugin.json' interface VersionParts { major: number @@ -58,6 +57,5 @@ export function processEvent(event: PluginEvent, meta: LegacyTransformationPlugi export const semverFlattenerPlugin: LegacyTransformationPlugin = { id: 'semver-flattener-plugin', - metadata: metadata as any, processEvent, } diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/taxonomy-plugin/index.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/taxonomy-plugin/index.ts index 32dc3e2b0df9b..95ddb35d9ad0f 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/taxonomy-plugin/index.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/taxonomy-plugin/index.ts @@ -1,7 +1,6 @@ import { PluginEvent } from '@posthog/plugin-scaffold' import { LegacyTransformationPlugin, LegacyTransformationPluginMeta } from '../../types' -import metadata from './plugin.json' type Transformation = { name: string @@ -82,6 +81,5 @@ const standardizeName = (name: string, desiredPattern: Transformation) => { export const taxonomyPlugin: LegacyTransformationPlugin = { id: 'taxonomy-plugin', - metadata: metadata as any, processEvent, } diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/timestamp-parser-plugin/index.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/timestamp-parser-plugin/index.ts index eef40f78baf0a..365b47eb3025a 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/timestamp-parser-plugin/index.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/timestamp-parser-plugin/index.ts @@ -1,7 +1,6 @@ import { PluginEvent } from '@posthog/plugin-scaffold' import { LegacyTransformationPlugin, LegacyTransformationPluginMeta } from '../../types' -import metadata from './plugin.json' function processEvent(event: PluginEvent, _meta: LegacyTransformationPluginMeta) { if (event.properties && event['timestamp'] && !isNaN(event['timestamp'] as any)) { @@ -20,6 +19,5 @@ function processEvent(event: PluginEvent, _meta: LegacyTransformationPluginMeta) export const timestampParserPlugin: LegacyTransformationPlugin = { id: 'timestamp-parser-plugin', - metadata: metadata as any, processEvent, } diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/user-agent-plugin/index.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/user-agent-plugin/index.ts index 65467458f5e8f..eab524d160807 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/user-agent-plugin/index.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/user-agent-plugin/index.ts @@ -2,7 +2,6 @@ import { PluginEvent } from '@posthog/plugin-scaffold' import { detect } from 'detect-browser' import { LegacyTransformationPlugin, LegacyTransformationPluginMeta } from '../../types' -import metadata from './plugin.json' export type UserAgentMeta = LegacyTransformationPluginMeta & { config: { @@ -163,6 +162,5 @@ function detectDeviceType(userAgent: string) { export const userAgentPlugin: LegacyTransformationPlugin = { id: 'user-agent-plugin', - metadata: metadata as any, processEvent, } diff --git a/plugin-server/src/cdp/legacy-plugins/index.ts b/plugin-server/src/cdp/legacy-plugins/index.ts index b4dfa84a140f0..7de4ce7975348 100644 --- a/plugin-server/src/cdp/legacy-plugins/index.ts +++ b/plugin-server/src/cdp/legacy-plugins/index.ts @@ -1,9 +1,17 @@ import { customerioPlugin } from './_destinations/customerio' import { intercomPlugin } from './_destinations/intercom' import { downsamplingPlugin } from './_transformations/downsampling-plugin' +import { dropEventsOnPropertyPlugin } from './_transformations/drop-events-on-property-plugin' +import { flattenPropertiesPlugin } from './_transformations/flatten-properties-plugin' import { languageUrlSplitterApp } from './_transformations/language-url-splitter-app' +import { pluginAdvancedGeoip } from './_transformations/plugin-advanced-geoip' +import { pluginStonlyCleanCampaignName } from './_transformations/Plugin-Stonly-Clean-Campaign-Name' +import { pluginStonlyUtmExtractor } from './_transformations/plugin-stonly-UTM-Extractor' +import { posthogAppUnduplicator } from './_transformations/posthog-app-unduplicator' import { posthogAppUrlParametersToEventPropertiesPlugin } from './_transformations/posthog-app-url-parameters-to-event-properties' import { posthogFilterOutPlugin } from './_transformations/posthog-filter-out-plugin' +import { posthogPluginGeoip } from './_transformations/posthog-plugin-geoip' +import { posthogRouteCensorPlugin } from './_transformations/posthog-route-censor-plugin' import { posthogUrlNormalizerPlugin } from './_transformations/posthog-url-normalizer-plugin' import { propertyFilterPlugin } from './_transformations/property-filter-plugin' import { semverFlattenerPlugin } from './_transformations/semver-flattener-plugin' @@ -28,3 +36,14 @@ export const TRANSFORMATION_PLUGINS_BY_ID = { [timestampParserPlugin.id]: timestampParserPlugin, [userAgentPlugin.id]: userAgentPlugin, } + +export const DEPRECATED_TRANSFORMATION_PLUGINS_BY_ID = { + [dropEventsOnPropertyPlugin.id]: dropEventsOnPropertyPlugin, + [flattenPropertiesPlugin.id]: flattenPropertiesPlugin, + [pluginAdvancedGeoip.id]: pluginAdvancedGeoip, + [pluginStonlyCleanCampaignName.id]: pluginStonlyCleanCampaignName, + [pluginStonlyUtmExtractor.id]: pluginStonlyUtmExtractor, + [posthogAppUnduplicator.id]: posthogAppUnduplicator, + [posthogPluginGeoip.id]: posthogPluginGeoip, + [posthogRouteCensorPlugin.id]: posthogRouteCensorPlugin, +} diff --git a/plugin-server/src/cdp/legacy-plugins/types.ts b/plugin-server/src/cdp/legacy-plugins/types.ts index a6a15b6031c65..662fc302e2000 100644 --- a/plugin-server/src/cdp/legacy-plugins/types.ts +++ b/plugin-server/src/cdp/legacy-plugins/types.ts @@ -1,4 +1,4 @@ -import { PluginConfigSchema, PluginEvent, ProcessedPluginEvent } from '@posthog/plugin-scaffold' +import { PluginEvent, ProcessedPluginEvent } from '@posthog/plugin-scaffold' import { Response, trackedFetch } from '~/src/utils/fetch' @@ -21,20 +21,12 @@ export type LegacyDestinationPluginMeta = LegacyTransformationPluginMeta & { export type LegacyDestinationPlugin = { id: string - metadata: { - name: string - config: PluginConfigSchema[] - } onEvent(event: ProcessedPluginEvent, meta: LegacyDestinationPluginMeta): Promise setupPlugin?: (meta: LegacyDestinationPluginMeta) => Promise } export type LegacyTransformationPlugin = { id: string - metadata: { - name: string - config: PluginConfigSchema[] - } processEvent(event: PluginEvent, meta: LegacyTransformationPluginMeta): PluginEvent | undefined | null setupPlugin?: (meta: LegacyTransformationPluginMeta) => void } From 2d9f8355accda5fdc260c475a74905efbdd1d5cd Mon Sep 17 00:00:00 2001 From: Ben White Date: Thu, 30 Jan 2025 15:42:20 +0100 Subject: [PATCH 130/163] Remove the stonly thing --- .../__tests__/index_agent.js | 507 ---- .../__tests__/index_agent_installer.js | 53 - .../__tests__/index_blog.js | 264 --- .../__tests__/index_cloud.js | 361 --- .../__tests__/index_community.js | 224 -- .../__tests__/index_learn.js | 264 --- .../__tests__/index_staging.js | 97 - .../__tests__/index_testing.js | 97 - .../__tests__/index_website.js | 238 -- .../plugin-netdata-event-processing/index.js | 2036 ----------------- .../interaction_detail_agent.js | 202 -- .../interaction_type_agent.js | 144 -- .../plugin.json | 8 - .../process.js | 167 -- .../process_elements_agent.js | 416 ---- .../process_elements_agent_installer.js | 7 - .../process_elements_blog.js | 109 - .../process_elements_cloud.js | 221 -- .../process_elements_community.js | 104 - .../process_elements_learn.js | 109 - .../process_elements_staging.js | 130 -- .../process_elements_testing.js | 130 -- .../process_elements_website.js | 104 - .../process_properties_agent.js | 97 - .../process_properties_agent_installer.js | 22 - .../process_properties_blog.js | 8 - .../process_properties_cloud.js | 9 - .../process_properties_community.js | 8 - .../process_properties_learn.js | 8 - .../process_properties_staging.js | 8 - .../process_properties_testing.js | 8 - .../process_properties_website.js | 8 - .../plugin-netdata-event-processing/utils.js | 54 - 33 files changed, 6222 deletions(-) delete mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/__tests__/index_agent.js delete mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/__tests__/index_agent_installer.js delete mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/__tests__/index_blog.js delete mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/__tests__/index_cloud.js delete mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/__tests__/index_community.js delete mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/__tests__/index_learn.js delete mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/__tests__/index_staging.js delete mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/__tests__/index_testing.js delete mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/__tests__/index_website.js delete mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/index.js delete mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/interaction_detail_agent.js delete mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/interaction_type_agent.js delete mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/plugin.json delete mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process.js delete mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_elements_agent.js delete mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_elements_agent_installer.js delete mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_elements_blog.js delete mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_elements_cloud.js delete mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_elements_community.js delete mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_elements_learn.js delete mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_elements_staging.js delete mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_elements_testing.js delete mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_elements_website.js delete mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_properties_agent.js delete mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_properties_agent_installer.js delete mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_properties_blog.js delete mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_properties_cloud.js delete mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_properties_community.js delete mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_properties_learn.js delete mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_properties_staging.js delete mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_properties_testing.js delete mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_properties_website.js delete mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/utils.js diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/__tests__/index_agent.js b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/__tests__/index_agent.js deleted file mode 100644 index aa893c415224a..0000000000000 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/__tests__/index_agent.js +++ /dev/null @@ -1,507 +0,0 @@ -const { - createEvent, - createIdentify, - createPageview, - createCache, - getMeta, - resetMeta, - clone, -} = require('posthog-plugins/test/utils.js') -const { setupPlugin, processEvent } = require('../index') - -const netdataPluginVersion = '0.0.15' - -beforeEach(() => { - resetMeta({ - config: { - netdata_version: 'v1.41.0', - }, - }) -}) - -test('setupPlugin', async () => { - expect(getMeta().config.netdata_version).toEqual('v1.41.0') - await setupPlugin(getMeta()) - expect(getMeta().global.setupDone).toEqual(true) -}) - -// test has_alarms_critical -test('has_alarms_critical', async () => { - const event = createEvent({ event: 'test event', properties: { "$current_url":"agent backend", "alarms_critical": 1 } }) - const eventCopy = await processEvent(clone(event), getMeta()) - expect(eventCopy).toEqual({ - ...event, - properties: { - ...event.properties, - has_alarms_critical: true, - netdata_posthog_plugin_version: netdataPluginVersion, - interaction_type: 'other', - interaction_detail: '', - interaction_token: 'other|', - event_ph: 'test event', - event_source: 'agent', - }, - }) -}) - -// test has_alarms_warning -test('has_alarms_warning', async () => { - const event = createEvent({ event: 'test event', properties: { "$current_url":"agent backend", "alarms_warning": 0 } }) - const eventCopy = await processEvent(clone(event), getMeta()) - expect(eventCopy).toEqual({ - ...event, - properties: { - ...event.properties, - has_alarms_warning: false, - netdata_posthog_plugin_version: netdataPluginVersion, - interaction_type: 'other', - interaction_detail: '', - interaction_token: 'other|', - event_ph: 'test event', - event_source: 'agent', - }, - }) -}) - -// test netdata_buildinfo -test('netdata_buildinfo', async () => { - const event = createEvent({ event: 'test event', properties: { "$current_url":"agent backend", "netdata_buildinfo": "JSON-C|dbengine|Native HTTPS|LWS v3.2.2" } }) - const eventCopy = await processEvent(clone(event), getMeta()) - expect(eventCopy).toEqual({ - ...event, - properties: { - ...event.properties, - netdata_buildinfo_json_c: true, - netdata_buildinfo_dbengine: true, - netdata_buildinfo_native_https: true, - netdata_buildinfo_lws_v3_2_2: true, - netdata_posthog_plugin_version: netdataPluginVersion, - interaction_type: 'other', - interaction_detail: '', - interaction_token: 'other|', - event_ph: 'test event', - event_source: 'agent', - }, - }) -}) - -// test host_collectors -test('host_collectors', async () => { - const event = createEvent({ - event: 'test event', - properties: { - "$current_url":"agent backend", - "host_collectors": [ - { - "plugin": "python.d.plugin", - "module": "dockerhub" - }, - { - "plugin": "apps.plugin", - "module": "" - }, - { - "plugin": "proc.plugin", - "module": "/proc/diskstats" - }, - { - "plugin": "proc.plugin", - "module": "/proc/softirqs" - }, - ] - } - }) - const eventCopy = await processEvent(clone(event), getMeta()) - expect(eventCopy).toEqual({ - ...event, - properties: { - ...event.properties, - host_collector_plugin_python_d_plugin: true, - host_collector_plugin_apps_plugin: true, - host_collector_plugin_proc_plugin: true, - host_collector_module_dockerhub: true, - host_collector_module_proc_diskstats: true, - host_collector_module_proc_softirqs: true, - netdata_posthog_plugin_version: netdataPluginVersion, - interaction_type: 'other', - interaction_detail: '', - interaction_token: 'other|', - event_ph: 'test event', - event_source: 'agent', - }, - }) -}) - -// test host_collectors null -test('host_collectors_null', async () => { - const event = createEvent({ - event: 'test event', - properties: { - "$current_url":"agent backend", - "host_collectors": [null] - } - }) - const eventCopy = await processEvent(clone(event), getMeta()) - expect(eventCopy).toEqual({ - ...event, - properties: { - ...event.properties, - netdata_posthog_plugin_version: netdataPluginVersion, - interaction_type: 'other', - interaction_detail: '', - interaction_token: 'other|', - event_ph: 'test event', - event_source: 'agent', - }, - }) -}) - -// test netdata_machine_guid -test('netdata_machine_guid', async () => { - const event = createEvent({ event: 'test event', properties: { "$current_url":"agent backend", "netdata_machine_guid": "" } }) - const eventCopy = await processEvent(clone(event), getMeta()) - expect(eventCopy).toEqual({ - ...event, - properties: { - "$current_url":"agent backend", - netdata_machine_guid: 'empty', - netdata_machine_guid_is_empty: true, - netdata_posthog_plugin_version: netdataPluginVersion, - interaction_type: 'other', - interaction_detail: '', - interaction_token: 'other|', - event_ph: 'test event', - event_source: 'agent', - }, - }) -}) - -// test netdata_machine_guid -test('netdata_machine_guid', async () => { - const event = createEvent({ event: 'test event', properties: { "$current_url":"agent backend", "netdata_machine_guid": "123" } }) - const eventCopy = await processEvent(clone(event), getMeta()) - expect(eventCopy).toEqual({ - ...event, - properties: { - "$current_url":"agent backend", - netdata_machine_guid: '123', - netdata_machine_guid_is_empty: false, - netdata_posthog_plugin_version: netdataPluginVersion, - interaction_type: 'other', - interaction_detail: '', - interaction_token: 'other|', - event_ph: 'test event', - event_source: 'agent', - }, - }) -}) - -// test netdata_person_id -test('netdata_person_id', async () => { - const event = createEvent({ event: 'test event', properties: { "$current_url":"agent backend", "netdata_person_id": "" } }) - const eventCopy = await processEvent(clone(event), getMeta()) - expect(eventCopy).toEqual({ - ...event, - properties: { - "$current_url":"agent backend", - netdata_person_id: 'empty', - netdata_person_id_is_empty: true, - netdata_posthog_plugin_version: netdataPluginVersion, - interaction_type: 'other', - interaction_detail: '', - interaction_token: 'other|', - event_ph: 'test event', - event_source: 'agent', - }, - }) -}) - -// test netdata_person_id -test('netdata_person_id', async () => { - const event = createEvent({ event: 'test event', properties: { "$current_url":"agent backend", "netdata_person_id": "123" } }) - const eventCopy = await processEvent(clone(event), getMeta()) - expect(eventCopy).toEqual({ - ...event, - properties: { - "$current_url":"agent backend", - netdata_person_id: '123', - netdata_person_id_is_empty: false, - netdata_posthog_plugin_version: netdataPluginVersion, - interaction_type: 'other', - interaction_detail: '', - interaction_token: 'other|', - event_ph: 'test event', - event_source: 'agent', - }, - }) -}) - -// test distinct_id -test('distinct_id', async () => { - const event = createEvent({ event: 'test event', properties: { "$current_url":"agent backend", "distinct_id": "" } }) - const eventCopy = await processEvent(clone(event), getMeta()) - expect(eventCopy).toEqual({ - ...event, - properties: { - distinct_id: 'empty', - $current_url: 'agent backend', - distinct_id_is_empty: true, - netdata_posthog_plugin_version: netdataPluginVersion, - interaction_type: 'other', - interaction_detail: '', - interaction_token: 'other|', - event_ph: 'test event', - event_source: 'agent', - }, - }) -}) - -// test distinct_id -test('distinct_id', async () => { - const event = createEvent({ event: 'test event', properties: { "distinct_id": "123", "$current_url": "agent backend" } }) - const eventCopy = await processEvent(clone(event), getMeta()) - expect(eventCopy).toEqual({ - ...event, - properties: { - distinct_id: '123', - $current_url: 'agent backend', - distinct_id_is_empty: false, - netdata_posthog_plugin_version: netdataPluginVersion, - interaction_type: 'other', - interaction_detail: '', - interaction_token: 'other|', - event_ph: 'test event', - event_source: 'agent', - }, - }) -}) - - -// test data_testid -test('data_testid', async () => { - const eventExample = { - "event": "$autocapture", - "distinct_id": "dev-test", - "$current_url": "agent backend", - "properties": { - "$current_url": "agent backend", - "$elements": [ - { - "attr__data-testid": "date-picker::click-quick-selector::::21600", - }, - { - "attr__href": "#menu_web_log_nginx" - }, - { - "event": null, - "text": null, - "tag_name": "div", - "attr_class": [ - "bjKBDB", - "styled__ShortPick-sc-1yj3701-6" - ], - "href": null, - "attr_id": null, - "nth_child": 1, - "nth_of_type": 1, - "attributes": { - "attr__class": "styled__ShortPick-sc-1yj3701-6 bjKBDB" - }, - "order": 1 - }, - { - "event": null, - "text": "unshared", - "tag_name": "span", - "attr_class": [ - "chart-legend-bottomstyled__DimensionLabel-ltgk2z-9", - "iMmOhf" - ], - "href": null, - "attr_id": null, - "nth_child": 2, - "nth_of_type": 1, - "attributes": { - "attr__class": "chart-legend-bottomstyled__DimensionLabel-ltgk2z-9 iMmOhf" - }, - "order": 0 - }, - { - "$el_text": "unshared" - }, - { - "event": null, - "text": "unshared", - "tag_name": "span", - "attr_class": [ - "chart-legend-bottomstyled__DimensionLabel-ltgk2z-9", - "iMmOhf" - ], - "href": null, - "attr_id": null, - "nth_child": 2, - "nth_of_type": 1, - "attributes": { - "attr__class": "chart-legend-bottomstyled__DimensionLabel-ltgk2z-9 iMmOhf" - }, - "order": 0 - - }, - { - "attr__data-id": "newyork_netdata_rocks_mem_ksm", - "attr__data-legend-position": "bottom", - "attr__data-netdata": "mem.ksm", - } - ] - } - } - const event = createEvent(eventExample) - const eventCopy = await processEvent(clone(event), getMeta()) - expect(eventCopy['properties']['el_data_testid']).toEqual("date-picker::click-quick-selector::::21600") - expect(eventCopy['properties']['el_href_menu']).toEqual("#menu_web_log_nginx") - expect(eventCopy['properties']['el_text']).toEqual("unshared") - expect(eventCopy['properties']['el_data_netdata']).toEqual("mem.ksm") - expect(eventCopy['properties']['interaction_type']).toEqual("menu") -}) - -// test menu -test('menu', async () => { - const event = createEvent({ event: 'test event', properties: {"$current_url": "agent backend", "$elements":[{"attr__href": "#menu_system_submenu_cpu"}]} }) - const eventCopy = await processEvent(clone(event), getMeta()) - expect(eventCopy).toEqual({ - ...event, - properties: { - ...event.properties, - el_href: '#menu_system_submenu_cpu', - el_href_menu: '#menu_system_submenu_cpu', - el_menu: 'system', - el_submenu: 'cpu', - netdata_posthog_plugin_version: netdataPluginVersion, - interaction_type: 'submenu', - interaction_detail: '#menu_system_submenu_cpu', - interaction_token: 'submenu|#menu_system_submenu_cpu', - event_ph: 'test event', - event_source: 'agent', - }, - }) -}) - -test('processEvent does not crash with identify', async () => { - // create a random event - const event0 = createIdentify() - - // must clone the event since `processEvent` will mutate it otherwise - const event1 = await processEvent(clone(event0), getMeta()) - expect(event1).toEqual(event0) -}) - -// test config_https_available -test('config_https_available', async () => { - const event = createEvent({ event: 'test event', properties: { "config_https_available": true, "$current_url": "agent backend" } }) - const eventCopy = await processEvent(clone(event), getMeta()) - expect(eventCopy).toEqual({ - ...event, - properties: { - ...event.properties, - config_https_available: true, - netdata_posthog_plugin_version: netdataPluginVersion, - interaction_type: 'other', - interaction_detail: '', - interaction_token: 'other|', - event_ph: 'test event', - event_source: 'agent', - }, - }) -}) - -// test event_source -test('event_source', async () => { - const event = createEvent({ event: 'test event', properties: { "$current_url": "http://london3.my-netdata.io/#menu_dockerhub_submenu_status;after=-420;before=0;theme=slate" } }) - const eventCopy = await processEvent(clone(event), getMeta()) - expect(eventCopy['properties']['event_source']).toEqual("agent") - }) - -// test event_source_agent_backend -test('event_source_agent_backend', async () => { - const eventExample = { - "event": "$pageview", - "distinct_id": "dev-test", - "properties": { - "$current_url": "agent backend", - } - } - const event = createEvent(eventExample) - const eventCopy = await processEvent(clone(event), getMeta()) - expect(eventCopy['properties']['event_source']).toEqual("agent") -}) - -// test event_source_agent_frontend -test('event_source_agent_frontend', async () => { - const eventExample = { - "event": "$pageview", - "distinct_id": "dev-test", - "properties": { - "$current_url": "agent dashboard", - } - } - const event = createEvent(eventExample) - const eventCopy = await processEvent(clone(event), getMeta()) - expect(eventCopy['properties']['event_source']).toEqual("agent") -}) - -// test el_name -test('el_name', async () => { - const eventExample = { - "event": "$autocapture", - "distinct_id": "dev-test", - "properties": { - "$current_url": "agent dashboard", - "$elements": [ - { - "attr__foo": "foo" - }, - { - "attr__bar": "bar", - }, - { - "attr__data-id": "newyork_netdata_rocks_mem_ksm", - "attr__data-legend-position": "bottom", - "attr__data-netdata": "mem.ksm", - "attr__name": "foo", - } - ] - } - } - const event = createEvent(eventExample) - const eventCopy = await processEvent(clone(event), getMeta()) - expect(eventCopy['properties']['el_name']).toEqual("foo") -}) - -// test el_class -test('el_class', async () => { - const eventExample = { - "event": "$autocapture", - "distinct_id": "dev-test", - "properties": { - "$current_url": "agent dashboard", - "$elements": [ - { - "attr__foo": "foo" - }, - { - "attr__bar": "bar", - "attributes": { - "attr__aria-label": "my_aria_label", - "attr__class": "my_att_class" - }, - }, - { - "attr__class": "my_class" - } - ] - } - } - const event = createEvent(eventExample) - const eventCopy = await processEvent(clone(event), getMeta()) - expect(eventCopy['properties']['el_class']).toEqual("my_class") -}) diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/__tests__/index_agent_installer.js b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/__tests__/index_agent_installer.js deleted file mode 100644 index 0c10c6c679ada..0000000000000 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/__tests__/index_agent_installer.js +++ /dev/null @@ -1,53 +0,0 @@ -const { - createEvent, - createIdentify, - createPageview, - createCache, - getMeta, - resetMeta, - clone, -} = require('posthog-plugins/test/utils.js') -const { setupPlugin, processEvent } = require('../index') - -const netdataPluginVersion = '0.0.15' - -beforeEach(() => { - resetMeta({ - config: { - netdata_version: 'v1.41.0', - }, - }) -}) - -test('setupPlugin', async () => { - expect(getMeta().config.netdata_version).toEqual('v1.41.0') - await setupPlugin(getMeta()) - expect(getMeta().global.setupDone).toEqual(true) -}) - -// test event_source -test('event_source', async () => { - const event = createEvent({ event: 'test event', properties: { "$current_url":"agent installer"} }) - const eventCopy = await processEvent(clone(event), getMeta()) - expect(eventCopy['properties']['event_source']).toEqual("agent installer") -}) - -// test install_options_easy -test('install_options_easy', async () => { - const event = createEvent({ - event: 'test event', properties: { - "$current_url":"agent installer", - "install_options": "--dont-start-it --dont-wait --claim-token 3gciBd6HYGp7Z2v2fJDd6meraFoUT4QpVYcHTE253KujJPStNQhXi9cicTgEEc_mNiQNxAYtHlZNpC1a2NQz57fV6aZaa2vPvyPYw9hsv_SOfzfWxMdQ6L-PPOyM9e9N2HAVp7E --claim-rooms 22ff1e07-8e9c-41ad-b141-5bd95fbf95d1 --claim-url https://app.netdata.cloud --foo" - } - }) - const eventCopy = await processEvent(clone(event), getMeta()) - expect(eventCopy['properties']['event_source']).toEqual("agent installer") - expect(eventCopy['properties']['opt_dont_start_it']).toEqual('') - expect(eventCopy['properties']['opt_dont_wait']).toEqual('') - expect(eventCopy['properties']['opt_claim_token']).toEqual('3gciBd6HYGp7Z2v2fJDd6meraFoUT4QpVYcHTE253KujJPStNQhXi9cicTgEEc_mNiQNxAYtHlZNpC1a2NQz57fV6aZaa2vPvyPYw9hsv_SOfzfWxMdQ6L-PPOyM9e9N2HAVp7E') - expect(eventCopy['properties']['opt_claim_url']).toEqual('https://app.netdata.cloud') - expect(eventCopy['properties']['opt_foo']).toEqual('') -}) - - - diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/__tests__/index_blog.js b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/__tests__/index_blog.js deleted file mode 100644 index 3f2bab17e8726..0000000000000 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/__tests__/index_blog.js +++ /dev/null @@ -1,264 +0,0 @@ -const { - createEvent, - createIdentify, - createPageview, - createCache, - getMeta, - resetMeta, - clone, -} = require('posthog-plugins/test/utils.js') -const { setupPlugin, processEvent } = require('../index') - -const netdataPluginVersion = '0.0.15' - -beforeEach(() => { - resetMeta({ - config: { - netdata_version: 'v1.41.0', - }, - }) -}) - -test('setupPlugin', async () => { - expect(getMeta().config.netdata_version).toEqual('v1.41.0') - await setupPlugin(getMeta()) - expect(getMeta().global.setupDone).toEqual(true) -}) - -// test data_testid -test('data_testid', async () => { - const eventExample = { - "event": "$autocapture", - "distinct_id": "dev-test", - "properties": { - "$current_url": "https://blog.netdata.cloud/", - "$elements": [ - { - "attr__data-testid": "date-picker::click-quick-selector::::21600", - }, - { - "attr__href": "#menu_web_log_nginx" - }, - { - "event": null, - "text": null, - "tag_name": "div", - "attr_class": [ - "bjKBDB", - "styled__ShortPick-sc-1yj3701-6" - ], - "href": null, - "attr_id": null, - "nth_child": 1, - "nth_of_type": 1, - "attributes": { - "attr__class": "styled__ShortPick-sc-1yj3701-6 bjKBDB" - }, - "order": 1 - }, - { - "event": null, - "text": "unshared", - "tag_name": "span", - "attr_class": [ - "chart-legend-bottomstyled__DimensionLabel-ltgk2z-9", - "iMmOhf" - ], - "href": null, - "attr_id": null, - "nth_child": 2, - "nth_of_type": 1, - "attributes": { - "attr__class": "chart-legend-bottomstyled__DimensionLabel-ltgk2z-9 iMmOhf" - }, - "order": 0 - }, - { - "$el_text": "unshared" - }, - { - "event": null, - "text": "unshared", - "tag_name": "span", - "attr_class": [ - "chart-legend-bottomstyled__DimensionLabel-ltgk2z-9", - "iMmOhf" - ], - "href": null, - "attr_id": null, - "nth_child": 2, - "nth_of_type": 1, - "attributes": { - "attr__class": "chart-legend-bottomstyled__DimensionLabel-ltgk2z-9 iMmOhf" - }, - "order": 0 - - }, - { - "attr__data-testid": "newyork_netdata_rocks_mem_ksm", - "attr__data-legend-position": "bottom", - "attr__data-netdata": "mem.ksm", - } - ] - } - } - const event = createEvent(eventExample) - const eventCopy = await processEvent(clone(event), getMeta()) - expect(eventCopy['properties']['event_source']).toEqual("blog") - expect(eventCopy['properties']['el_data_testid']).toEqual("date-picker::click-quick-selector::::21600") -}) - -// test data_ga -test('data_ga', async () => { - const eventExample = { - "event": "$autocapture", - "distinct_id": "dev-test", - "properties": { - "$current_url": "https://blog.netdata.cloud/", - "$elements": [ - { - "attr__data-ga": "date-picker::click-quick-selector::::21600" - }, - { - "attr__data-ga": "#menu_web_log_nginx", - }, - { - "$el_text": "unshared" - }, - { - "attr__data-id": "newyork_netdata_rocks_mem_ksm", - "attr__data-legend-position": "bottom", - "attr__data-netdata": "mem.ksm", - } - ] - } - } - const event = createEvent(eventExample) - const eventCopy = await processEvent(clone(event), getMeta()) - expect(eventCopy['properties']['event_source']).toEqual("blog") - expect(eventCopy['properties']['el_data_ga']).toEqual("date-picker::click-quick-selector::::21600") - expect(eventCopy['properties']['el_data_ga_0']).toEqual("date-picker") - expect(eventCopy['properties']['el_data_ga_1']).toEqual("click-quick-selector") - expect(eventCopy['properties']['el_data_ga_2']).toEqual("") - expect(eventCopy['properties']['el_text']).toEqual("unshared") -}) - -test('processEvent does not crash with identify', async () => { - // create a random event - const event0 = createIdentify() - - // must clone the event since `processEvent` will mutate it otherwise - const event1 = await processEvent(clone(event0), getMeta()) - expect(event1).toEqual(event0) -}) - -// test event_source_blog -test('event_source_blog', async () => { - const eventExample = { - "event": "$pageview", - "distinct_id": "dev-test", - "properties": { - "$current_url": "https://blog.netdata.cloud/", - } - } - const event = createEvent(eventExample) - const eventCopy = await processEvent(clone(event), getMeta()) - expect(eventCopy['properties']['event_source']).toEqual("blog") -}) - -// test el_name -test('el_name', async () => { - const eventExample = { - "event": "$autocapture", - "distinct_id": "dev-test", - "properties": { - "$current_url": "https://blog.netdata.cloud/", - "$elements": [ - { - "attr__foo": "foo" - }, - { - "attr__bar": "bar", - }, - { - "attr__data-id": "newyork_netdata_rocks_mem_ksm", - "attr__data-legend-position": "bottom", - "attr__data-netdata": "mem.ksm", - "attr__name": "foo", - } - ] - } - } - const event = createEvent(eventExample) - const eventCopy = await processEvent(clone(event), getMeta()) - expect(eventCopy['properties']['el_name']).toEqual("foo") -}) - -// test el_aria_label -test('el_aria_label', async () => { - const eventExample = { - "event": "$autocapture", - "distinct_id": "dev-test", - "properties": { - "$current_url": "https://blog.netdata.cloud/", - "$elements": [ - { - "attr__foo": "foo" - }, - { - "attr__bar": "bar", - "attributes": { - "attr__aria-label": "Docs pages navigation", - "attr__class": "pagination-nav docusaurus-mt-lg" - }, - } - ] - } - } - const event = createEvent(eventExample) - const eventCopy = await processEvent(clone(event), getMeta()) - expect(eventCopy['properties']['el_aria_label']).toEqual("Docs pages navigation") -}) - -// test el_class -test('el_class', async () => { - const eventExample = { - "event": "$autocapture", - "distinct_id": "dev-test", - "properties": { - "$current_url": "https://blog.netdata.cloud/", - "$elements": [ - { - "attr__foo": "foo" - }, - { - "attr__bar": "bar", - "attributes": { - "attr__aria-label": "Docs pages navigation", - "attr__class": "my_att_class" - }, - }, - { - "attr__class": "my_class" - } - ] - } - } - const event = createEvent(eventExample) - const eventCopy = await processEvent(clone(event), getMeta()) - expect(eventCopy['properties']['el_class']).toEqual("my_class") -}) - -// test event_source_blog_preview -test('event_source_blog_preview', async () => { - const eventExample = { - "event": "$pageview", - "distinct_id": "dev-test", - "properties": { - "$current_url": "https://deploy-preview-168--netdata-blog.netlify.app", - } - } - const event = createEvent(eventExample) - const eventCopy = await processEvent(clone(event), getMeta()) - expect(eventCopy['properties']['event_source']).toEqual("blog_preview") -}) diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/__tests__/index_cloud.js b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/__tests__/index_cloud.js deleted file mode 100644 index ac59449931783..0000000000000 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/__tests__/index_cloud.js +++ /dev/null @@ -1,361 +0,0 @@ -const { - createEvent, - createIdentify, - createPageview, - createCache, - getMeta, - resetMeta, - clone, -} = require('posthog-plugins/test/utils.js') -const { setupPlugin, processEvent } = require('../index') - -const netdataPluginVersion = '0.0.15' - -beforeEach(() => { - resetMeta({ - config: { - netdata_version: 'v1.41.0', - }, - }) -}) - -test('setupPlugin', async () => { - expect(getMeta().config.netdata_version).toEqual('v1.41.0') - await setupPlugin(getMeta()) - expect(getMeta().global.setupDone).toEqual(true) -}) - -// test data_testid -test('data_testid', async () => { - const eventExample = { - "event": "$autocapture", - "distinct_id": "dev-test", - "properties": { - "$current_url": "https://app.netdata.cloud/", - "$elements": [ - { - "attr__data-testid": "date-picker::click-quick-selector::::21600", - }, - { - "attr__href": "#menu_web_log_nginx" - }, - { - "event": null, - "text": null, - "tag_name": "div", - "attr_class": [ - "bjKBDB", - "styled__ShortPick-sc-1yj3701-6" - ], - "href": null, - "attr_id": null, - "nth_child": 1, - "nth_of_type": 1, - "attributes": { - "attr__class": "styled__ShortPick-sc-1yj3701-6 bjKBDB" - }, - "order": 1 - }, - { - "event": null, - "text": "unshared", - "tag_name": "span", - "attr_class": [ - "chart-legend-bottomstyled__DimensionLabel-ltgk2z-9", - "iMmOhf" - ], - "href": null, - "attr_id": null, - "nth_child": 2, - "nth_of_type": 1, - "attributes": { - "attr__class": "chart-legend-bottomstyled__DimensionLabel-ltgk2z-9 iMmOhf" - }, - "order": 0 - }, - { - "$el_text": "unshared" - }, - { - "event": null, - "text": "unshared", - "tag_name": "span", - "attr_class": [ - "chart-legend-bottomstyled__DimensionLabel-ltgk2z-9", - "iMmOhf" - ], - "href": null, - "attr_id": null, - "nth_child": 2, - "nth_of_type": 1, - "attributes": { - "attr__class": "chart-legend-bottomstyled__DimensionLabel-ltgk2z-9 iMmOhf" - }, - "order": 0 - - }, - { - "attr__data-testid": "newyork_netdata_rocks_mem_ksm", - "attr__data-legend-position": "bottom", - "attr__data-netdata": "mem.ksm", - } - ] - } - } - const event = createEvent(eventExample) - const eventCopy = await processEvent(clone(event), getMeta()) - expect(eventCopy['properties']['event_source']).toEqual("cloud") - expect(eventCopy['properties']['el_data_testid']).toEqual("date-picker::click-quick-selector::::21600") -}) - -// test data_ga -test('data_ga', async () => { - const eventExample = { - "event": "$autocapture", - "distinct_id": "dev-test", - "properties": { - "$current_url": "https://app.netdata.cloud/", - "$elements": [ - { - "attr__data-ga": "some-category::some-action::some-label::some-value", - }, - { - "attr__data-ga": "#menu_web_log_nginx", - }, - { - "$el_text": "unshared" - }, - { - "attr__data-ga": "date-picker::click-quick-selector::::21600", - "attr__data-id": "newyork_netdata_rocks_mem_ksm", - "attr__data-legend-position": "bottom", - "attr__data-netdata": "mem.ksm", - } - ] - } - } - const event = createEvent(eventExample) - const eventCopy = await processEvent(clone(event), getMeta()) - expect(eventCopy['properties']['event_source']).toEqual("cloud") - expect(eventCopy['properties']['el_data_ga']).toEqual("some-category::some-action::some-label::some-value") - expect(eventCopy['properties']['el_data_ga_inner']).toEqual("some-category::some-action::some-label::some-value") - expect(eventCopy['properties']['el_data_ga_outer']).toEqual("date-picker::click-quick-selector::::21600") - expect(eventCopy['properties']['el_data_ga_0']).toEqual("some-category") - expect(eventCopy['properties']['el_data_ga_1']).toEqual("some-action") - expect(eventCopy['properties']['el_data_ga_2']).toEqual("some-label") - expect(eventCopy['properties']['event_category']).toEqual("some-category") - expect(eventCopy['properties']['event_action']).toEqual("some-action") - expect(eventCopy['properties']['event_label']).toEqual("some-label") - expect(eventCopy['properties']['event_value']).toEqual("some-value") - expect(eventCopy['properties']['el_text']).toEqual("unshared") -}) - -test('processEvent does not crash with identify', async () => { - // create a random event - const event0 = createIdentify() - - // must clone the event since `processEvent` will mutate it otherwise - const event1 = await processEvent(clone(event0), getMeta()) - expect(event1).toEqual(event0) -}) - -// test event_source_cloud -test('event_source_cloud', async () => { - const eventExample = { - "event": "$pageview", - "distinct_id": "dev-test", - "properties": { - "$current_url": "https://app.netdata.cloud/", - } - } - const event = createEvent(eventExample) - const eventCopy = await processEvent(clone(event), getMeta()) - expect(eventCopy['properties']['event_source']).toEqual("cloud") -}) - -// test event_source_cloud_identify -test('event_source_cloud_identify', async () => { - const eventExample = { - "event": "$identify", - "distinct_id": "dev-test", - "properties": { - } - } - const event = createEvent(eventExample) - const eventCopy = await processEvent(clone(event), getMeta()) - expect(eventCopy['properties']['event_source']).toEqual("cloud") - expect(eventCopy['properties']['event_ph']).toEqual("$identify") -}) - -// test data_track -test('data_track', async () => { - const eventExample = { - "event": "$autocapture", - "distinct_id": "dev-test", - "properties": { - "$current_url": "https://app.netdata.cloud/", - "$elements": [ - { - "attr__data-track": "foobar" - }, - { - "attr__data-track": "date-picker::click-quick-selector::::21600" - }, - { - "$el_text": "unshared" - }, - { - "attr__data-id": "newyork_netdata_rocks_mem_ksm", - "attr__data-legend-position": "bottom", - "attr__data-netdata": "mem.ksm", - } - ] - } - } - const event = createEvent(eventExample) - const eventCopy = await processEvent(clone(event), getMeta()) - expect(eventCopy['properties']['event_source']).toEqual("cloud") - expect(eventCopy['properties']['el_data_track_outer']).toEqual("date-picker::click-quick-selector::::21600") - expect(eventCopy['properties']['el_data_track_outer_0']).toEqual("date-picker") - expect(eventCopy['properties']['el_data_track_outer_1']).toEqual("click-quick-selector") - expect(eventCopy['properties']['el_data_track_outer_2']).toEqual("") - expect(eventCopy['properties']['el_text']).toEqual("unshared") - expect(eventCopy['properties']['el_data_track_outer']).toEqual("date-picker::click-quick-selector::::21600") - expect(eventCopy['properties']['el_data_track_inner']).toEqual("foobar") -}) - -// test el_name -test('el_name', async () => { - const eventExample = { - "event": "$autocapture", - "distinct_id": "dev-test", - "properties": { - "$current_url": "https://app.netdata.cloud/", - "$elements": [ - { - "attr__foo": "foo" - }, - { - "attr__bar": "bar", - }, - { - "attr__data-id": "newyork_netdata_rocks_mem_ksm", - "attr__data-legend-position": "bottom", - "attr__data-netdata": "mem.ksm", - "attr__name": "foo", - } - ] - } - } - const event = createEvent(eventExample) - const eventCopy = await processEvent(clone(event), getMeta()) - expect(eventCopy['properties']['el_name']).toEqual("foo") -}) - -// test pathname -test('pathname', async () => { - const eventExample = { - "event": "$pageview", - "distinct_id": "dev-test", - "properties": { - "$current_url": "https://app.netdata.cloud/a/b/c/d", - "$pathname": "/a/b/c/d" - } - } - const event = createEvent(eventExample) - const eventCopy = await processEvent(clone(event), getMeta()) - expect(eventCopy['properties']['pathname_1']).toEqual("a") - expect(eventCopy['properties']['pathname_2']).toEqual("b") - expect(eventCopy['properties']['pathname_3']).toEqual("c") - expect(eventCopy['properties']['pathname_4']).toEqual("d") - expect(eventCopy['properties']['event_source']).toEqual("cloud") -}) - -// test pathname -test('pathname_real', async () => { - const eventExample = { - "event": "$pageview", - "distinct_id": "dev-test", - "properties": { - "$current_url": "https://app.netdata.cloud/account/sso-agent?id=e6bfbf32-e89f-11ec-a180-233f485cb8df", - "$pathname": "/account/sso-agent?id=e6bfbf32-e89f-11ec-a180-233f485cb8df" - } - } - const event = createEvent(eventExample) - const eventCopy = await processEvent(clone(event), getMeta()) - expect(eventCopy['properties']['pathname_1']).toEqual("account") - expect(eventCopy['properties']['pathname_2']).toEqual("sso-agent?id=e6bfbf32-e89f-11ec-a180-233f485cb8df") - expect(eventCopy['properties']['event_source']).toEqual("cloud") - -}) - -// test el_class -test('el_class', async () => { - const eventExample = { - "event": "$autocapture", - "distinct_id": "dev-test", - "properties": { - "$current_url": "https://app.netdata.cloud/", - "$elements": [ - { - "attr__foo": "foo" - }, - { - "attr__bar": "bar", - "attributes": { - "attr__aria-label": "my_aria_label", - "attr__class": "my_att_class" - }, - }, - { - "attr__class": "my_class" - } - ] - } - } - const event = createEvent(eventExample) - const eventCopy = await processEvent(clone(event), getMeta()) - expect(eventCopy['properties']['el_class']).toEqual("my_class") -}) - -// test cloud_agent_19999 -test('cloud_agent_19999', async () => { - const eventExample = { - "event": "$pageview", - "distinct_id": "dev-test", - "properties": { - "$current_url": "https://10.10.10.10:19999/spaces/foo", - } - } - const event = createEvent(eventExample) - const eventCopy = await processEvent(clone(event), getMeta()) - expect(eventCopy['properties']['event_source']).toEqual("cloud_agent") -}) - -// test cloud_agent_spaces -test('cloud_agent_spaces', async () => { - const eventExample = { - "event": "$pageview", - "distinct_id": "dev-test", - "properties": { - "$current_url": "https://some.netdata/spaces/foo", - } - } - const event = createEvent(eventExample) - const eventCopy = await processEvent(clone(event), getMeta()) - expect(eventCopy['properties']['event_source']).toEqual("cloud_agent") -}) - -// test cloud_spaces -test('cloud_agent_spaces', async () => { - const eventExample = { - "event": "$pageview", - "distinct_id": "dev-test", - "properties": { - "$current_url": "https://app.netdata.cloud/spaces/foobar", - } - } - const event = createEvent(eventExample) - const eventCopy = await processEvent(clone(event), getMeta()) - expect(eventCopy['properties']['event_source']).toEqual("cloud") -}) diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/__tests__/index_community.js b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/__tests__/index_community.js deleted file mode 100644 index 6d086b0b31186..0000000000000 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/__tests__/index_community.js +++ /dev/null @@ -1,224 +0,0 @@ -const { - createEvent, - createIdentify, - createPageview, - createCache, - getMeta, - resetMeta, - clone, -} = require('posthog-plugins/test/utils.js') -const { setupPlugin, processEvent } = require('../index') - -const netdataPluginVersion = '0.0.15' - -beforeEach(() => { - resetMeta({ - config: { - netdata_version: 'v1.41.0', - }, - }) -}) - -test('setupPlugin', async () => { - expect(getMeta().config.netdata_version).toEqual('v1.41.0') - await setupPlugin(getMeta()) - expect(getMeta().global.setupDone).toEqual(true) -}) - -// test data_testid -test('data_testid', async () => { - const eventExample = { - "event": "$autocapture", - "distinct_id": "dev-test", - "properties": { - "$current_url": "https://community.netdata.cloud/", - "$elements": [ - { - "attr__data-testid": "date-picker::click-quick-selector::::21600", - }, - { - "attr__href": "#menu_web_log_nginx" - }, - { - "event": null, - "text": null, - "tag_name": "div", - "attr_class": [ - "bjKBDB", - "styled__ShortPick-sc-1yj3701-6" - ], - "href": null, - "attr_id": null, - "nth_child": 1, - "nth_of_type": 1, - "attributes": { - "attr__class": "styled__ShortPick-sc-1yj3701-6 bjKBDB" - }, - "order": 1 - }, - { - "event": null, - "text": "unshared", - "tag_name": "span", - "attr_class": [ - "chart-legend-bottomstyled__DimensionLabel-ltgk2z-9", - "iMmOhf" - ], - "href": null, - "attr_id": null, - "nth_child": 2, - "nth_of_type": 1, - "attributes": { - "attr__class": "chart-legend-bottomstyled__DimensionLabel-ltgk2z-9 iMmOhf" - }, - "order": 0 - }, - { - "$el_text": "unshared" - }, - { - "event": null, - "text": "unshared", - "tag_name": "span", - "attr_class": [ - "chart-legend-bottomstyled__DimensionLabel-ltgk2z-9", - "iMmOhf" - ], - "href": null, - "attr_id": null, - "nth_child": 2, - "nth_of_type": 1, - "attributes": { - "attr__class": "chart-legend-bottomstyled__DimensionLabel-ltgk2z-9 iMmOhf" - }, - "order": 0 - - }, - { - "attr__data-testid": "newyork_netdata_rocks_mem_ksm", - "attr__data-legend-position": "bottom", - "attr__data-netdata": "mem.ksm", - } - ] - } - } - const event = createEvent(eventExample) - const eventCopy = await processEvent(clone(event), getMeta()) - expect(eventCopy['properties']['event_source']).toEqual("community") - expect(eventCopy['properties']['el_data_testid']).toEqual("date-picker::click-quick-selector::::21600") -}) - -// test data_ga -test('data_ga', async () => { - const eventExample = { - "event": "$autocapture", - "distinct_id": "dev-test", - "properties": { - "$current_url": "https://community.netdata.cloud/", - "$elements": [ - { - "attr__data-ga": "date-picker::click-quick-selector::::21600" - }, - { - "attr__data-ga": "#menu_web_log_nginx", - }, - { - "$el_text": "unshared" - }, - { - "attr__data-id": "newyork_netdata_rocks_mem_ksm", - "attr__data-legend-position": "bottom", - "attr__data-netdata": "mem.ksm", - } - ] - } - } - const event = createEvent(eventExample) - const eventCopy = await processEvent(clone(event), getMeta()) - expect(eventCopy['properties']['event_source']).toEqual("community") - expect(eventCopy['properties']['el_data_ga']).toEqual("date-picker::click-quick-selector::::21600") - expect(eventCopy['properties']['el_data_ga_0']).toEqual("date-picker") - expect(eventCopy['properties']['el_data_ga_1']).toEqual("click-quick-selector") - expect(eventCopy['properties']['el_data_ga_2']).toEqual("") - expect(eventCopy['properties']['el_text']).toEqual("unshared") -}) - -test('processEvent does not crash with identify', async () => { - // create a random event - const event0 = createIdentify() - - // must clone the event since `processEvent` will mutate it otherwise - const event1 = await processEvent(clone(event0), getMeta()) - expect(event1).toEqual(event0) -}) - -// test event_source_community -test('event_source_community', async () => { - const eventExample = { - "event": "$pageview", - "distinct_id": "dev-test", - "properties": { - "$current_url": "https://community.netdata.cloud/", - } - } - const event = createEvent(eventExample) - const eventCopy = await processEvent(clone(event), getMeta()) - expect(eventCopy['properties']['event_source']).toEqual("community") -}) - -// test el_name -test('el_name', async () => { - const eventExample = { - "event": "$autocapture", - "distinct_id": "dev-test", - "properties": { - "$current_url": "https://community.netdata.cloud/", - "$elements": [ - { - "attr__foo": "foo" - }, - { - "attr__bar": "bar", - }, - { - "attr__data-id": "newyork_netdata_rocks_mem_ksm", - "attr__data-legend-position": "bottom", - "attr__data-netdata": "mem.ksm", - "attr__name": "foo", - } - ] - } - } - const event = createEvent(eventExample) - const eventCopy = await processEvent(clone(event), getMeta()) - expect(eventCopy['properties']['el_name']).toEqual("foo") -}) - -// test el_class -test('el_class', async () => { - const eventExample = { - "event": "$autocapture", - "distinct_id": "dev-test", - "properties": { - "$current_url": "https://community.netdata.cloud/", - "$elements": [ - { - "attr__foo": "foo" - }, - { - "attr__bar": "bar", - "attributes": { - "attr__aria-label": "my_aria_label", - "attr__class": "my_att_class" - }, - }, - { - "attr__class": "my_class" - } - ] - } - } - const event = createEvent(eventExample) - const eventCopy = await processEvent(clone(event), getMeta()) - expect(eventCopy['properties']['el_class']).toEqual("my_class") -}) diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/__tests__/index_learn.js b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/__tests__/index_learn.js deleted file mode 100644 index 25bfca3bfd9a5..0000000000000 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/__tests__/index_learn.js +++ /dev/null @@ -1,264 +0,0 @@ -const { - createEvent, - createIdentify, - createPageview, - createCache, - getMeta, - resetMeta, - clone, -} = require('posthog-plugins/test/utils.js') -const { setupPlugin, processEvent } = require('../index') - -const netdataPluginVersion = '0.0.15' - -beforeEach(() => { - resetMeta({ - config: { - netdata_version: 'v1.41.0', - }, - }) -}) - -test('setupPlugin', async () => { - expect(getMeta().config.netdata_version).toEqual('v1.41.0') - await setupPlugin(getMeta()) - expect(getMeta().global.setupDone).toEqual(true) -}) - -// test data_testid -test('data_testid', async () => { - const eventExample = { - "event": "$autocapture", - "distinct_id": "dev-test", - "properties": { - "$current_url": "https://learn.netdata.cloud/", - "$elements": [ - { - "attr__data-testid": "date-picker::click-quick-selector::::21600", - }, - { - "attr__href": "#menu_web_log_nginx" - }, - { - "event": null, - "text": null, - "tag_name": "div", - "attr_class": [ - "bjKBDB", - "styled__ShortPick-sc-1yj3701-6" - ], - "href": null, - "attr_id": null, - "nth_child": 1, - "nth_of_type": 1, - "attributes": { - "attr__class": "styled__ShortPick-sc-1yj3701-6 bjKBDB" - }, - "order": 1 - }, - { - "event": null, - "text": "unshared", - "tag_name": "span", - "attr_class": [ - "chart-legend-bottomstyled__DimensionLabel-ltgk2z-9", - "iMmOhf" - ], - "href": null, - "attr_id": null, - "nth_child": 2, - "nth_of_type": 1, - "attributes": { - "attr__class": "chart-legend-bottomstyled__DimensionLabel-ltgk2z-9 iMmOhf" - }, - "order": 0 - }, - { - "$el_text": "unshared" - }, - { - "event": null, - "text": "unshared", - "tag_name": "span", - "attr_class": [ - "chart-legend-bottomstyled__DimensionLabel-ltgk2z-9", - "iMmOhf" - ], - "href": null, - "attr_id": null, - "nth_child": 2, - "nth_of_type": 1, - "attributes": { - "attr__class": "chart-legend-bottomstyled__DimensionLabel-ltgk2z-9 iMmOhf" - }, - "order": 0 - - }, - { - "attr__data-testid": "newyork_netdata_rocks_mem_ksm", - "attr__data-legend-position": "bottom", - "attr__data-netdata": "mem.ksm", - } - ] - } - } - const event = createEvent(eventExample) - const eventCopy = await processEvent(clone(event), getMeta()) - expect(eventCopy['properties']['event_source']).toEqual("learn") - expect(eventCopy['properties']['el_data_testid']).toEqual("date-picker::click-quick-selector::::21600") -}) - -// test data_ga -test('data_ga', async () => { - const eventExample = { - "event": "$autocapture", - "distinct_id": "dev-test", - "properties": { - "$current_url": "https://learn.netdata.cloud/", - "$elements": [ - { - "attr__data-ga": "date-picker::click-quick-selector::::21600" - }, - { - "attr__data-ga": "#menu_web_log_nginx", - }, - { - "$el_text": "unshared" - }, - { - "attr__data-id": "newyork_netdata_rocks_mem_ksm", - "attr__data-legend-position": "bottom", - "attr__data-netdata": "mem.ksm", - } - ] - } - } - const event = createEvent(eventExample) - const eventCopy = await processEvent(clone(event), getMeta()) - expect(eventCopy['properties']['event_source']).toEqual("learn") - expect(eventCopy['properties']['el_data_ga']).toEqual("date-picker::click-quick-selector::::21600") - expect(eventCopy['properties']['el_data_ga_0']).toEqual("date-picker") - expect(eventCopy['properties']['el_data_ga_1']).toEqual("click-quick-selector") - expect(eventCopy['properties']['el_data_ga_2']).toEqual("") - expect(eventCopy['properties']['el_text']).toEqual("unshared") -}) - -test('processEvent does not crash with identify', async () => { - // create a random event - const event0 = createIdentify() - - // must clone the event since `processEvent` will mutate it otherwise - const event1 = await processEvent(clone(event0), getMeta()) - expect(event1).toEqual(event0) -}) - -// test event_source_learn -test('event_source_learn', async () => { - const eventExample = { - "event": "$pageview", - "distinct_id": "dev-test", - "properties": { - "$current_url": "https://learn.netdata.cloud/", - } - } - const event = createEvent(eventExample) - const eventCopy = await processEvent(clone(event), getMeta()) - expect(eventCopy['properties']['event_source']).toEqual("learn") -}) - -// test el_name -test('el_name', async () => { - const eventExample = { - "event": "$autocapture", - "distinct_id": "dev-test", - "properties": { - "$current_url": "https://learn.netdata.cloud/", - "$elements": [ - { - "attr__foo": "foo" - }, - { - "attr__bar": "bar", - }, - { - "attr__data-id": "newyork_netdata_rocks_mem_ksm", - "attr__data-legend-position": "bottom", - "attr__data-netdata": "mem.ksm", - "attr__name": "foo", - } - ] - } - } - const event = createEvent(eventExample) - const eventCopy = await processEvent(clone(event), getMeta()) - expect(eventCopy['properties']['el_name']).toEqual("foo") -}) - -// test el_aria_label -test('el_aria_label', async () => { - const eventExample = { - "event": "$autocapture", - "distinct_id": "dev-test", - "properties": { - "$current_url": "https://learn.netdata.cloud/", - "$elements": [ - { - "attr__foo": "foo" - }, - { - "attr__bar": "bar", - "attributes": { - "attr__aria-label": "Docs pages navigation", - "attr__class": "pagination-nav docusaurus-mt-lg" - }, - } - ] - } - } - const event = createEvent(eventExample) - const eventCopy = await processEvent(clone(event), getMeta()) - expect(eventCopy['properties']['el_aria_label']).toEqual("Docs pages navigation") -}) - -// test el_class -test('el_class', async () => { - const eventExample = { - "event": "$autocapture", - "distinct_id": "dev-test", - "properties": { - "$current_url": "https://learn.netdata.cloud/", - "$elements": [ - { - "attr__foo": "foo" - }, - { - "attr__bar": "bar", - "attributes": { - "attr__aria-label": "Docs pages navigation", - "attr__class": "my_att_class" - }, - }, - { - "attr__class": "my_class" - } - ] - } - } - const event = createEvent(eventExample) - const eventCopy = await processEvent(clone(event), getMeta()) - expect(eventCopy['properties']['el_class']).toEqual("my_class") -}) - -// test event_source_learn_preview -test('event_source_learn_preview', async () => { - const eventExample = { - "event": "$pageview", - "distinct_id": "dev-test", - "properties": { - "$current_url": "https://deploy-preview-1058--netdata-docusaurus.netlify.app/", - } - } - const event = createEvent(eventExample) - const eventCopy = await processEvent(clone(event), getMeta()) - expect(eventCopy['properties']['event_source']).toEqual("learn_preview") -}) diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/__tests__/index_staging.js b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/__tests__/index_staging.js deleted file mode 100644 index 3f2a4213f2f16..0000000000000 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/__tests__/index_staging.js +++ /dev/null @@ -1,97 +0,0 @@ -const { - createEvent, - createIdentify, - createPageview, - createCache, - getMeta, - resetMeta, - clone, -} = require('posthog-plugins/test/utils.js') -const { setupPlugin, processEvent } = require('../index') - -const netdataPluginVersion = '0.0.15' - -beforeEach(() => { - resetMeta({ - config: { - netdata_version: 'v1.29.2', - }, - }) -}) - -test('setupPlugin', async () => { - expect(getMeta().config.netdata_version).toEqual('v1.29.2') - await setupPlugin(getMeta()) - expect(getMeta().global.setupDone).toEqual(true) -}) - -// test event_source_staging -test('event_source_staging', async () => { - const eventExample = { - "event": "$pageview", - "distinct_id": "dev-test", - "properties": { - "$current_url": "https://staging.netdata.cloud/", - } - } - const event = createEvent(eventExample) - const eventCopy = await processEvent(clone(event), getMeta()) - expect(eventCopy['properties']['event_source']).toEqual("staging") -}) - -// test el_name -test('el_name', async () => { - const eventExample = { - "event": "$autocapture", - "distinct_id": "dev-test", - "properties": { - "$current_url": "https://staging.netdata.cloud/", - "$elements": [ - { - "attr__foo": "foo" - }, - { - "attr__bar": "bar", - }, - { - "attr__data-id": "newyork_netdata_rocks_mem_ksm", - "attr__data-legend-position": "bottom", - "attr__data-netdata": "mem.ksm", - "attr__name": "foo", - } - ] - } - } - const event = createEvent(eventExample) - const eventCopy = await processEvent(clone(event), getMeta()) - expect(eventCopy['properties']['el_name']).toEqual("foo") -}) - -// test el_class -test('el_class', async () => { - const eventExample = { - "event": "$autocapture", - "distinct_id": "dev-test", - "properties": { - "$current_url": "https://staging.netdata.cloud/", - "$elements": [ - { - "attr__foo": "foo" - }, - { - "attr__bar": "bar", - "attributes": { - "attr__aria-label": "my_aria_label", - "attr__class": "my_att_class" - }, - }, - { - "attr__class": "my_class" - } - ] - } - } - const event = createEvent(eventExample) - const eventCopy = await processEvent(clone(event), getMeta()) - expect(eventCopy['properties']['el_class']).toEqual("my_class") -}) diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/__tests__/index_testing.js b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/__tests__/index_testing.js deleted file mode 100644 index baa6752da9150..0000000000000 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/__tests__/index_testing.js +++ /dev/null @@ -1,97 +0,0 @@ -const { - createEvent, - createIdentify, - createPageview, - createCache, - getMeta, - resetMeta, - clone, -} = require('posthog-plugins/test/utils.js') -const { setupPlugin, processEvent } = require('../index') - -const netdataPluginVersion = '0.0.15' - -beforeEach(() => { - resetMeta({ - config: { - netdata_version: 'v1.29.2', - }, - }) -}) - -test('setupPlugin', async () => { - expect(getMeta().config.netdata_version).toEqual('v1.29.2') - await setupPlugin(getMeta()) - expect(getMeta().global.setupDone).toEqual(true) -}) - -// test event_source_testing -test('event_source_testing', async () => { - const eventExample = { - "event": "$pageview", - "distinct_id": "dev-test", - "properties": { - "$current_url": "https://testing.netdata.cloud/", - } - } - const event = createEvent(eventExample) - const eventCopy = await processEvent(clone(event), getMeta()) - expect(eventCopy['properties']['event_source']).toEqual("testing") -}) - -// test el_name -test('el_name', async () => { - const eventExample = { - "event": "$autocapture", - "distinct_id": "dev-test", - "properties": { - "$current_url": "https://testing.netdata.cloud/", - "$elements": [ - { - "attr__foo": "foo" - }, - { - "attr__bar": "bar", - }, - { - "attr__data-id": "newyork_netdata_rocks_mem_ksm", - "attr__data-legend-position": "bottom", - "attr__data-netdata": "mem.ksm", - "attr__name": "foo", - } - ] - } - } - const event = createEvent(eventExample) - const eventCopy = await processEvent(clone(event), getMeta()) - expect(eventCopy['properties']['el_name']).toEqual("foo") -}) - -// test el_class -test('el_class', async () => { - const eventExample = { - "event": "$autocapture", - "distinct_id": "dev-test", - "properties": { - "$current_url": "https://testing.netdata.cloud/", - "$elements": [ - { - "attr__foo": "foo" - }, - { - "attr__bar": "bar", - "attributes": { - "attr__aria-label": "my_aria_label", - "attr__class": "my_att_class" - }, - }, - { - "attr__class": "my_class" - } - ] - } - } - const event = createEvent(eventExample) - const eventCopy = await processEvent(clone(event), getMeta()) - expect(eventCopy['properties']['el_class']).toEqual("my_class") -}) diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/__tests__/index_website.js b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/__tests__/index_website.js deleted file mode 100644 index 841bc44b5d0bd..0000000000000 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/__tests__/index_website.js +++ /dev/null @@ -1,238 +0,0 @@ -const { - createEvent, - createIdentify, - createPageview, - createCache, - getMeta, - resetMeta, - clone, -} = require('posthog-plugins/test/utils.js') -const { setupPlugin, processEvent } = require('../index') - -const netdataPluginVersion = '0.0.15' - -beforeEach(() => { - resetMeta({ - config: { - netdata_version: 'v1.29.2', - }, - }) -}) - -test('setupPlugin', async () => { - expect(getMeta().config.netdata_version).toEqual('v1.29.2') - await setupPlugin(getMeta()) - expect(getMeta().global.setupDone).toEqual(true) -}) - -// test data_testid -test('data_testid', async () => { - const eventExample = { - "event": "$autocapture", - "distinct_id": "dev-test", - "properties": { - "$current_url": "https://www.netdata.cloud/", - "$elements": [ - { - "attr__data-testid": "date-picker::click-quick-selector::::21600", - }, - { - "attr__href": "#menu_web_log_nginx" - }, - { - "event": null, - "text": null, - "tag_name": "div", - "attr_class": [ - "bjKBDB", - "styled__ShortPick-sc-1yj3701-6" - ], - "href": null, - "attr_id": null, - "nth_child": 1, - "nth_of_type": 1, - "attributes": { - "attr__class": "styled__ShortPick-sc-1yj3701-6 bjKBDB" - }, - "order": 1 - }, - { - "event": null, - "text": "unshared", - "tag_name": "span", - "attr_class": [ - "chart-legend-bottomstyled__DimensionLabel-ltgk2z-9", - "iMmOhf" - ], - "href": null, - "attr_id": null, - "nth_child": 2, - "nth_of_type": 1, - "attributes": { - "attr__class": "chart-legend-bottomstyled__DimensionLabel-ltgk2z-9 iMmOhf" - }, - "order": 0 - }, - { - "$el_text": "unshared" - }, - { - "event": null, - "text": "unshared", - "tag_name": "span", - "attr_class": [ - "chart-legend-bottomstyled__DimensionLabel-ltgk2z-9", - "iMmOhf" - ], - "href": null, - "attr_id": null, - "nth_child": 2, - "nth_of_type": 1, - "attributes": { - "attr__class": "chart-legend-bottomstyled__DimensionLabel-ltgk2z-9 iMmOhf" - }, - "order": 0 - - }, - { - "attr__data-testid": "newyork_netdata_rocks_mem_ksm", - "attr__data-legend-position": "bottom", - "attr__data-netdata": "mem.ksm", - } - ] - } - } - const event = createEvent(eventExample) - const eventCopy = await processEvent(clone(event), getMeta()) - expect(eventCopy['properties']['event_source']).toEqual("website") - expect(eventCopy['properties']['el_data_testid']).toEqual("date-picker::click-quick-selector::::21600") -}) - -// test data_ga -test('data_ga', async () => { - const eventExample = { - "event": "$autocapture", - "distinct_id": "dev-test", - "properties": { - "$current_url": "https://www.netdata.cloud/", - "$elements": [ - { - "attr__data-ga": "date-picker::click-quick-selector::::21600" - }, - { - "attr__data-ga": "#menu_web_log_nginx", - }, - { - "$el_text": "unshared" - }, - { - "attr__data-id": "newyork_netdata_rocks_mem_ksm", - "attr__data-legend-position": "bottom", - "attr__data-netdata": "mem.ksm", - } - ] - } - } - const event = createEvent(eventExample) - const eventCopy = await processEvent(clone(event), getMeta()) - expect(eventCopy['properties']['event_source']).toEqual("website") - expect(eventCopy['properties']['el_data_ga']).toEqual("date-picker::click-quick-selector::::21600") - expect(eventCopy['properties']['el_data_ga_0']).toEqual("date-picker") - expect(eventCopy['properties']['el_data_ga_1']).toEqual("click-quick-selector") - expect(eventCopy['properties']['el_data_ga_2']).toEqual("") - expect(eventCopy['properties']['el_text']).toEqual("unshared") -}) - -test('processEvent does not crash with identify', async () => { - // create a random event - const event0 = createIdentify() - - // must clone the event since `processEvent` will mutate it otherwise - const event1 = await processEvent(clone(event0), getMeta()) - expect(event1).toEqual(event0) -}) - -// test event_source_website -test('event_source_website', async () => { - const eventExample = { - "event": "$pageview", - "distinct_id": "dev-test", - "properties": { - "$current_url": "https://www.netdata.cloud/", - } - } - const event = createEvent(eventExample) - const eventCopy = await processEvent(clone(event), getMeta()) - expect(eventCopy['properties']['event_source']).toEqual("website") -}) - -// test el_name -test('el_name', async () => { - const eventExample = { - "event": "$autocapture", - "distinct_id": "dev-test", - "properties": { - "$current_url": "https://www.netdata.cloud/", - "$elements": [ - { - "attr__foo": "foo" - }, - { - "attr__bar": "bar", - }, - { - "attr__data-id": "newyork_netdata_rocks_mem_ksm", - "attr__data-legend-position": "bottom", - "attr__data-netdata": "mem.ksm", - "attr__name": "foo", - } - ] - } - } - const event = createEvent(eventExample) - const eventCopy = await processEvent(clone(event), getMeta()) - expect(eventCopy['properties']['el_name']).toEqual("foo") -}) - -// test el_class -test('el_class', async () => { - const eventExample = { - "event": "$autocapture", - "distinct_id": "dev-test", - "properties": { - "$current_url": "https://www.netdata.cloud/", - "$elements": [ - { - "attr__foo": "foo" - }, - { - "attr__bar": "bar", - "attributes": { - "attr__aria-label": "my_aria_label", - "attr__class": "my_att_class" - }, - }, - { - "attr__class": "my_class" - } - ] - } - } - const event = createEvent(eventExample) - const eventCopy = await processEvent(clone(event), getMeta()) - expect(eventCopy['properties']['el_class']).toEqual("my_class") -}) - -// test event_source_website_preview -test('event_source_website_preview', async () => { - const eventExample = { - "event": "$pageview", - "distinct_id": "dev-test", - "properties": { - "$current_url": "https://deploy-preview-167--netdata-website.netlify.app/", - } - } - const event = createEvent(eventExample) - const eventCopy = await processEvent(clone(event), getMeta()) - expect(eventCopy['properties']['event_source']).toEqual("website_preview") -}) diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/index.js b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/index.js deleted file mode 100644 index caf12a4c998f7..0000000000000 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/index.js +++ /dev/null @@ -1,2036 +0,0 @@ -function cleanPropertyName(k) { - return k - // convert to lower case - .toLowerCase() - // remove leading slash - .replace(/^\//, "") - // replace all slashes and dots with _ - .replace(/\/|\.|-| /g, "_") - ; -} - -function isStringDDMMYYYYHHMM(dt){ - var reDate = /^((0?[1-9]|[12][0-9]|3[01])[- /.](0?[1-9]|1[012])[- /.](19|20)?[0-9]{2}[ ][012][0-9][:][0-9]{2})*$/; - return reDate.test(dt); -} - -function isDemo(url) { - if ( - (url.includes('://london.my-netdata.io')) - || - (url.includes('://london3.my-netdata.io')) - || - (url.includes('://cdn77.my-netdata.io')) - || - (url.includes('://octopuscs.my-netdata.io')) - || - (url.includes('://bangalore.my-netdata.io')) - || - (url.includes('://frankfurt.my-netdata.io')) - || - (url.includes('://newyork.my-netdata.io')) - || - (url.includes('://sanfrancisco.my-netdata.io')) - || - (url.includes('://singapore.my-netdata.io')) - || - (url.includes('://toronto.my-netdata.io')) - ){ - return true - } else { - return false - } -} - -function splitPathName(event) { - if (event.properties['$pathname']) { - event.properties["$pathname"].split("/").forEach((pathname, index) => { - if ((pathname !== "") && (pathname !== null)){ - event.properties[`pathname_${index}`] = pathname; - } - }); - } - return event -} - -function processElementsAgent(event) { - // extract properties from elements - if (event.properties['$elements']) { - - // process each element, last (outermost) first. - event.properties['$elements'].slice().forEach((element) => { - - // el_data_testid_outer - if ('attr__data-testid' in element) { - event.properties['el_data_testid_outer'] = element['attr__data-testid']; - - // el_data_testid_outer_0 - if (element['attr__data-testid'].includes('::')) { - arr = element['attr__data-testid'].split('::'); - event.properties['el_data_testid_outer_0'] = arr[0]; - event.properties['el_data_testid_outer_1'] = arr[1]; - event.properties['el_data_testid_outer_2'] = arr[2]; - event.properties['el_data_testid_outer_3'] = arr[3]; - event.properties['el_data_testid_outer_4'] = arr[4]; - } - - } - - // el_data_ga_outer - if ('attr__data-ga' in element) { - event.properties['el_data_ga_outer'] = element['attr__data-ga']; - - // el_data_ga_outer_0 - if (element['attr__data-ga'].includes('::')) { - arr = element['attr__data-ga'].split('::'); - event.properties['el_data_ga_outer_0'] = arr[0]; - event.properties['el_data_ga_outer_1'] = arr[1]; - event.properties['el_data_ga_outer_2'] = arr[2]; - event.properties['el_data_ga_outer_3'] = arr[3]; - event.properties['el_data_ga_outer_4'] = arr[4]; - } - - } - - // el_data_track_outer - if ('attr__data-track' in element) { - event.properties['el_data_track_outer'] = element['attr__data-track']; - - // el_data_track_outer_0 - if (element['attr__data-track'].includes('::')) { - arr = element['attr__data-track'].split('::'); - event.properties['el_data_track_outer_0'] = arr[0]; - event.properties['el_data_track_outer_1'] = arr[1]; - event.properties['el_data_track_outer_2'] = arr[2]; - event.properties['el_data_track_outer_3'] = arr[3]; - event.properties['el_data_track_outer_4'] = arr[4]; - } - - } - - }); - - // process each element, reverse to use posthog order as preference - event.properties['$elements'].slice().reverse().forEach((element) => { - - // el_data_testid - if ('attr__data-testid' in element) { - event.properties['el_data_testid'] = element['attr__data-testid']; - - // el_data_testid_0 - if (element['attr__data-testid'].includes('::')) { - arr = element['attr__data-testid'].split('::'); - event.properties['el_data_testid_0'] = arr[0]; - event.properties['el_data_testid_1'] = arr[1]; - event.properties['el_data_testid_2'] = arr[2]; - event.properties['el_data_testid_3'] = arr[3]; - event.properties['el_data_testid_4'] = arr[4]; - } - - } - - // el_data_ga - if ('attr__data-ga' in element) { - event.properties['el_data_ga'] = element['attr__data-ga']; - - // el_data_ga_0 - if (element['attr__data-ga'].includes('::')) { - arr = element['attr__data-ga'].split('::'); - event.properties['el_data_ga_0'] = arr[0]; - event.properties['el_data_ga_1'] = arr[1]; - event.properties['el_data_ga_2'] = arr[2]; - event.properties['el_data_ga_3'] = arr[3]; - event.properties['el_data_ga_4'] = arr[4]; - } - - } - - // el_data_testid_inner - if ('attr__data-testid' in element) { - event.properties['el_data_testid_inner'] = element['attr__data-testid']; - - // el_data_testid_inner_0 - if (element['attr__data-testid'].includes('::')) { - arr = element['attr__data-testid'].split('::'); - event.properties['el_data_testid_inner_0'] = arr[0]; - event.properties['el_data_testid_inner_1'] = arr[1]; - event.properties['el_data_testid_inner_2'] = arr[2]; - event.properties['el_data_testid_inner_3'] = arr[3]; - event.properties['el_data_testid_inner_4'] = arr[4]; - } - - } - - // el_data_ga_inner - if ('attr__data-ga' in element) { - event.properties['el_data_ga_inner'] = element['attr__data-ga']; - - // el_data_ga_inner_0 - if (element['attr__data-ga'].includes('::')) { - arr = element['attr__data-ga'].split('::'); - event.properties['el_data_ga_inner_0'] = arr[0]; - event.properties['el_data_ga_inner_1'] = arr[1]; - event.properties['el_data_ga_inner_2'] = arr[2]; - event.properties['el_data_ga_inner_3'] = arr[3]; - event.properties['el_data_ga_inner_4'] = arr[4]; - } - - } - - // el_data_track_inner - if ('attr__data-track' in element) { - event.properties['el_data_track_inner'] = element['attr__data-track']; - - // el_data_track_inner_0 - if (element['attr__data-track'].includes('::')) { - arr = element['attr__data-track'].split('::'); - event.properties['el_data_track_inner_0'] = arr[0]; - event.properties['el_data_track_inner_1'] = arr[1]; - event.properties['el_data_track_inner_2'] = arr[2]; - event.properties['el_data_track_inner_3'] = arr[3]; - event.properties['el_data_track_inner_4'] = arr[4]; - } - - } - - // el_id_menu - if ('attr__href' in element && element['attr__href'] !== null && element['attr__href'].substring(0,5) === '#menu') { - event.properties['el_href_menu'] = element['attr__href']; - event.properties['el_menu'] = element['attr__href'].split('_submenu')[0].replace('#menu_', ''); - if (element['attr__href'].includes('_submenu_')) { - event.properties['el_submenu'] = element['attr__href'].split('_submenu_')[1]; - } else { - event.properties['el_submenu'] = ''; - } - } - - // el_href - if ('attr__href' in element && element['attr__href'] !== null) { - event.properties['el_href'] = element['attr__href']; - } else if ('href' in element && element['href'] !== null) { - event.properties['el_href'] = element['href']; - } else if ('$href' in element && element['$href'] !== null) { - event.properties['el_href'] = element['$href']; - } - - // el_onclick - if ('attr__onclick' in element && element['attr__onclick'] !== null) { - event.properties['el_onclick'] = element['attr__onclick']; - } - - // el_id - if ('attr__id' in element && element['attr__id'] !== null) { - event.properties['el_id'] = element['attr__id']; - } - - // el_name - if ('attr__name' in element && element['attr__name'] !== null) { - event.properties['el_name'] = element['attr__name']; - } - - // el_title - if ('attr__title' in element && element['attr__title'] !== null) { - event.properties['el_title'] = element['attr__title']; - } - - // el_text - if ('$el_text' in element && element['$el_text'] !== null && element['$el_text'] !== '') { - event.properties['el_text'] = element['$el_text']; - - // el_text_datetime - if (element['$el_text'].includes('/20') && isStringDDMMYYYYHHMM(element['$el_text'])) { - dtStr = element['$el_text']; - dtStrClean = dtStr.substring(6,10).concat( - '-',dtStr.substring(3,5),'-',dtStr.substring(0,2),' ',dtStr.substring(11,16) - ); - event.properties['el_text_datetime'] = dtStrClean; - } - - } else if ('text' in element && element['text'] !== null && element['text'] !== '') { - event.properties['el_text'] = element['text']; - } - - // el_data_netdata - if ('attr__data-netdata' in element && element['attr__data-netdata'] !== null) { - event.properties['el_data_netdata'] = element['attr__data-netdata']; - } - - // el_data_target - if ('attr__data-target' in element && element['attr__data-target'] !== null && element['attr__data-target'] !== '#sidebar') { - event.properties['el_data_target'] = element['attr__data-target']; - - // el_data_target_updatemodal - if ('attr__data-target' in element && element['attr__data-target'] !== null && element['attr__data-target'] === '#updateModal') { - event.properties['el_data_target_updatemodal'] = true; - } - - } - - // el_data_id - if ('attr__data-id' in element && element['attr__data-id'] !== null) { - event.properties['el_data_id'] = element['attr__data-id']; - } - - // el_data_original_title - if ('attr__data-original-title' in element && element['attr__data-original-title'] !== null) { - event.properties['el_data_original_title'] = element['attr__data-original-title']; - } - - // el_data_toggle - if ('attr__data-toggle' in element && element['attr__data-toggle'] !== null) { - event.properties['el_data_toggle'] = element['attr__data-toggle']; - } - - // el_data-legend-position - if ('attr__data-legend-position' in element && element['attr__data-legend-position'] !== null) { - event.properties['el_data_legend_position'] = element['attr__data-legend-position']; - } - - // el_aria_controls - if ('attr__aria-controls' in element && element['attr__aria-controls'] !== null) { - event.properties['el_aria_controls'] = element['attr__aria-controls']; - } - - // el_aria_labelledby - if ('attr__aria-labelledby' in element && element['attr__aria-labelledby'] !== null) { - event.properties['el_aria_labelledby'] = element['attr__aria-labelledby']; - } - - // el_class_netdata_legend_toolbox - if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'] === 'netdata-legend-toolbox') { - event.properties['el_class_netdata_legend_toolbox'] = true; - } - - // el_class_fa_play - if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('fa-play')) { - event.properties['el_class_fa_play'] = true; - } - - // el_class_fa_backward - if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('fa-backward')) { - event.properties['el_class_fa_backward'] = true; - } - - // el_class_fa_forward - if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('fa-forward')) { - event.properties['el_class_fa_forward'] = true; - } - - // el_class_fa_plus - if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('fa-plus')) { - event.properties['el_class_fa_plus'] = true; - } - - // el_class_fa_minus - if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('fa-minus')) { - event.properties['el_class_fa_minus'] = true; - } - - // el_class_fa_sort - if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('fa-sort')) { - event.properties['el_class_fa_sort'] = true; - } - - // el_class_navbar_highlight_content - if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('navbar-highlight-content')) { - event.properties['el_class_navbar_highlight_content'] = true; - } - - // el_class_datepickercontainer - if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('DatePickerContainer')) { - event.properties['el_class_datepickercontainer'] = true; - } - - // el_class_startendcontainer - if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('StartEndContainer')) { - event.properties['el_class_startendcontainer'] = true; - } - - // el_class_pickerbtnarea - if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('PickerBtnArea')) { - event.properties['el_class_pickerbtnarea'] = true; - } - - // el_class_pickerbox - if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('PickerBox')) { - event.properties['el_class_pickerbox'] = true; - } - - // el_class_collapsablesection - if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('CollapsableSection')) { - event.properties['el_class_collapsablesection'] = true; - } - - // el_class_signinbutton - if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('SignInButton')) { - event.properties['el_class_signinbutton'] = true; - } - - // el_class_documentation_container - if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('documentation__Container')) { - event.properties['el_class_documentation_container'] = true; - } - - // el_class_utilitysection - if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('UtilitySection')) { - event.properties['el_class_utilitysection'] = true; - } - - // el_class_success - if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('success')) { - event.properties['el_class_success'] = true; - } - - // el_class_warning - if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('warning')) { - event.properties['el_class_warning'] = true; - } - - // el_class_danger - if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('danger')) { - event.properties['el_class_danger'] = true; - } - - // el_class_info - if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'] === 'info') { - event.properties['el_class_info'] = true; - } - - // el_class_pagination - if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('pagination')) { - event.properties['el_class_pagination'] = true; - } - - // el_class_page_number - if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('page-number')) { - event.properties['el_class_page_number'] = true; - } - - // el_class_export - if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('export')) { - event.properties['el_class_export'] = true; - } - - // el_class_netdata_chartblock_container - if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('netdata-chartblock-container')) { - event.properties['el_class_netdata_chartblock_container'] = true; - } - - // el_class_netdata_reset_button - if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('netdata-reset-button')) { - event.properties['el_class_netdata_reset_button'] = true; - } - - // el_class_netdata_legend_toolbox_button - if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('netdata-legend-toolbox-button')) { - event.properties['el_class_netdata_legend_toolbox_button'] = true; - } - - // el_class_netdata_legend_resize_handler - if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('netdata-legend-resize-handler')) { - event.properties['el_class_netdata_legend_resize_handler'] = true; - } - - // el_class_calendarday - if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('CalendarDay')) { - event.properties['el_class_calendarday'] = true; - } - - // el_class_daypicker - if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('DayPicker')) { - event.properties['el_class_daypicker'] = true; - } - - // el_class_daterangepicker - if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('DateRangePicker')) { - event.properties['el_class_daterangepicker'] = true; - } - - // el_id_date_picker_root - if ('attr__id' in element && element['attr__id'] !== null && element['attr__id'].includes('date-picker-root')) { - event.properties['el_id_date_picker_root'] = true; - } - - // el_id_updatemodal - if ('attr__id' in element && element['attr__id'] !== null && element['attr__id'].includes('updateModal')) { - event.properties['el_id_updatemodal'] = true; - } - - // el_class - if ('attr__class' in element && element['attr__class'] !== null) { - event.properties['el_class'] = element['attr__class']; - } - - }); - - } - - return event -} - -function getInteractionTypeAgent(event) { - - if (['$pageview', '$pageleave', '$identify', 'agent backend'].includes(event.event)) { - - return event.event.replace('$', '').replace(' ', '_') - - // menu - } else if (event.properties.hasOwnProperty('el_href_menu')) { - - return event.properties['el_href_menu'].includes('submenu') ? 'submenu' : 'menu' - - // chart_toolbox - } else if ( - event.properties.hasOwnProperty('el_class_netdata_legend_resize_handler') || - event.properties.hasOwnProperty('el_class_netdata_legend_toolbox') - ) { - - return 'chart_toolbox' - - // chart_dim - } else if ( - event.properties.hasOwnProperty('el_data_netdata') && - event.properties.hasOwnProperty('el_id') && - ( - event.properties.hasOwnProperty('el_text') || event.properties.hasOwnProperty('el_title') - ) && - event.properties['el_id'].startsWith('chart_') - ) { - - return 'chart_dim' - - // date_picker - } else if ( - event.properties['el_id'] === 'date-picker-root' || - ( - event.properties.hasOwnProperty('el_data_testid') && - event.properties['el_data_testid'].startsWith('date-picker') - ) || - event.properties.hasOwnProperty('el_class_daterangepicker') - ) { - - return 'date_picker' - - // hamburger - } else if ( - event.properties.hasOwnProperty('el_class_collapsablesection') || - event.properties['el_title'] === 'hamburger' - ) { - - return 'hamburger' - - // update - } else if ( - event.properties.hasOwnProperty('el_data_target_updatemodal') || - event.properties.hasOwnProperty('el_id_updatemodal') - ) { - - return 'update' - - // help - } else if ( - ['Need Help?', 'question'].includes(event.properties['el_title']) || - event.properties['el_data_testid'] === 'documentation-help-close' || - event.properties.hasOwnProperty('el_class_documentation_container') - ) { - - return 'help' - - // load_snapshot - } else if ( - event.properties['el_data_target'] === '#loadSnapshotModal' || - event.properties['el_id'] === 'loadSnapshotDragAndDrop' || - event.properties['el_id'] === 'loadSnapshotSelectFiles' || - event.properties['el_id'] === 'loadSnapshotModal' - ) { - - return 'load_snapshot' - - // save_snapshot - } else if ( - event.properties['el_data_target'] === '#saveSnapshotModal' || - event.properties['el_id'] === 'saveSnapshotResolutionSlider' || - event.properties['el_id'] === 'saveSnapshotExport' || - event.properties['el_id'] === 'saveSnapshotModal' || - event.properties['el_id'] === 'hiddenDownloadLinks' - ) { - - return 'save_snapshot' - - // print - } else if ( - event.properties['el_data_target'] === '#printPreflightModal' || - event.properties['el_onclick'] === 'return printPreflight(),!1' - ) { - - return 'print' - - // alarms - } else if ( - event.properties['el_data_target'] === '#alarmsModal' || - ['#alarms_all', '#alarms_log', '#alarms_active'].includes(event.properties['el_href']) || - event.properties['el_id'] === 'alarms_log_table' || - event.properties['el_id'] === 'alarms_log' || - event.properties['el_id'] === 'alarmsModal' || - event.properties['el_aria_labelledby'] === 'alarmsModalLabel' - ) { - - return 'alarms' - - // settings - } else if ( - event.properties['el_data_target'] === '#optionsModal' || - event.properties['el_id'] === 'optionsModal' || - event.properties['el_aria_labelledby'] === 'optionsModalLabel' - ) { - - return 'settings' - - // cloud - } else if (event.properties.hasOwnProperty('el_class_signinbutton')) { - - return 'cloud' - - // highlight - } else if (event.properties['el_id'] === 'navbar-highlight-content') { - - return 'highlight' - - // add_charts - } else if (event.properties['el_text'] === 'Add more charts') { - - return 'add_charts' - - // add_alarms - } else if (event.properties['el_text'] === 'Add more alarms') { - - return 'add_alarms' - - } else { - - return 'other' - - } -} - -function getInteractionDetailAgent(event) { - - // menu - if (['menu', 'submenu'].includes(event.properties['interaction_type'])) { - - return event.properties['el_href_menu'] - - // chart_toolbox - } else if (event.properties['interaction_type'] === 'chart_toolbox') { - - if (event.properties.hasOwnProperty('el_class_fa_minus')) { - return 'zoom_out' - } else if (event.properties.hasOwnProperty('el_class_fa_plus')) { - return 'zoom_in' - } else if (event.properties.hasOwnProperty('el_class_fa_backward')) { - return 'scroll_backward' - } else if (event.properties.hasOwnProperty('el_class_fa_forward')) { - return 'scroll_forward' - } else if (event.properties.hasOwnProperty('el_class_fa_sort')) { - return 'resize' - } else if (event.properties.hasOwnProperty('el_class_fa_play')) { - return 'play' - } else { - return 'other' - } - - // chart_dim - } else if (event.properties['interaction_type'] === 'chart_dim') { - - if ( - event.properties.hasOwnProperty('el_id') && - event.properties.hasOwnProperty('el_text') - ) { - return event.properties['el_data_netdata'].concat('.',event.properties['el_text']) - } else if ( - event.properties.hasOwnProperty('el_id') && - event.properties.hasOwnProperty('el_title') - ) { - return event.properties['el_data_netdata'].concat('.',event.properties['el_title']) - } else { - return 'other' - } - - // date_picker - } else if (event.properties['interaction_type'] === 'date_picker') { - - if (event.properties['el_id'] === 'date-picker-root') { - return 'open' - } else if ( - event.properties.hasOwnProperty('el_data_testid') && - event.properties['el_data_testid'].startsWith('date-picker') - ) { - if (event.properties['el_data_testid'].includes('click-quick-selector')) { - return event.properties['el_data_testid_1'].concat(' ',event.properties['el_data_testid_3']) - } else { - return event.properties['el_data_testid_1'] - } - } else if (event.properties['el_id'] === 'month_right') { - return 'month_right' - } else if (event.properties['el_id'] === 'month_left') { - return 'month_left' - } else if (event.properties.hasOwnProperty('el_class_daterangepicker')) { - return 'date_range' - } else { - return 'other' - } - - // update - } else if (event.properties['interaction_type'] === 'update') { - - if (event.properties['el_title'] === 'update') { - return 'open' - } else if (event.properties['el_text'] === 'Check Now') { - return 'check' - } else if (event.properties['el_text'] === 'Close') { - return 'close' - } else { - return 'other' - } - - // highlight - } else if (event.properties['interaction_type'] === 'highlight') { - - if (event.properties['el_onclick'] === 'urlOptions.clearHighlight();') { - return 'clear' - } else { - return 'other' - } - - // settings - } else if (event.properties['interaction_type'] === 'settings') { - - if (event.properties['el_id'] === 'root') { - return 'open' - } else if (event.properties['el_text'] === 'Close') { - return 'close' - } else if (event.properties['el_data_toggle'] === 'tab') { - return 'tab' - } else if (event.properties['el_data_toggle'] === 'toggle') { - return 'toggle' - } else { - return 'other' - } - - // alarms - } else if (event.properties['interaction_type'] === 'alarms') { - - if ( - event.properties.hasOwnProperty('el_href') && - event.properties['el_href'].includes('#alarm_all_') - ) { - return event.properties['el_text'] - } else if (event.properties.hasOwnProperty('el_class_page_number')) { - return 'page_number' - } else if (event.properties['el_id'] === 'root') { - return 'open' - } else if ( - event.properties['el_text'] === 'Active' || - event.properties['el_id'] === 'alarms_active' - ) { - return 'active' - } else if (event.properties['el_text'] === 'Log') { - return 'log' - } else if (event.properties['el_text'] === 'All') { - return 'all' - } else if ( - event.properties.hasOwnProperty('el_class_warning') && - event.properties.hasOwnProperty('el_text') - ) { - if ( - event.properties['el_text'].includes(':') || - event.properties['el_text'].includes('%') - ) { - return 'warn' - } else { - return 'warn__'.concat(event.properties['el_text']) - } - } else if ( - event.properties.hasOwnProperty('el_class_success') && - event.properties.hasOwnProperty('el_text') - ) { - if ( - event.properties['el_text'].includes(':') || - event.properties['el_text'].includes('%') - ) { - return 'norm' - } else { - return 'norm__'.concat(event.properties['el_text']) - } - } else if ( - event.properties.hasOwnProperty('el_class_danger') && - event.properties.hasOwnProperty('el_text') - ) { - if ( - event.properties['el_text'].includes(':') || - event.properties['el_text'].includes('%') - ) { - return 'crit' - } else { - return 'crit__'.concat(event.properties['el_text']) - } - } else if ( - event.properties.hasOwnProperty('el_class_info') && - event.properties.hasOwnProperty('el_text') - ) { - if ( - event.properties['el_text'].includes(':') || - event.properties['el_text'].includes('%') - ) { - return 'undef' - } else { - return 'undef__'.concat(event.properties['el_text']) - } - } else if ( - event.properties['el_text'] === 'Close' || - event.properties['el_text'] === '×' - ) { - return 'close' - } else if ( - event.properties['el_title'] === 'Refresh' && - event.properties['el_id'] === 'alarms_log' - ) { - return 'refresh_log' - } else { - return 'other' - } - - // cloud - } else if (event.properties['interaction_type'] === 'cloud') { - - if (event.properties['el_text'] === 'Sign In to Cloud') { - return 'sign_in' - } else { - return 'other' - } - - } else { - - return '' - - } -} - -function processPropertiesAgent(event) { - - event = splitPathName(event); - - // has_alarms_critical - if (typeof event.properties['alarms_critical'] === 'number') { - event.properties['has_alarms_critical'] = event.properties['alarms_critical'] > 0; - } - - // has_alarms_warning - if (typeof event.properties['alarms_warning'] === 'number') { - event.properties['has_alarms_warning'] = event.properties['alarms_warning'] > 0; - } - - // add attribute for each build info flag - if (event.properties['netdata_buildinfo']) { - [...new Set(event.properties['netdata_buildinfo'].split('|'))].forEach((buildInfo) => { - if ((buildInfo !== "") && (buildInfo !== null)){ - event.properties[`netdata_buildinfo_${cleanPropertyName(buildInfo)}`] = true; - } - }); - } - - // add attribute for each host collector - if (event.properties['host_collectors']) { - - // only process if not empty - if (event.properties['host_collectors'][0] != null) { - - // make set for both plugins and modules present - let plugins = [...new Set(event.properties['host_collectors'].map(a => a.plugin))]; - let modules = [...new Set(event.properties['host_collectors'].map(a => a.module))]; - - // add flag for each plugin - plugins.forEach((plugin) => { - if ((plugin !== "") && (plugin !== null)){ - event.properties[`host_collector_plugin_${cleanPropertyName(plugin)}`] = true; - } - }); - - // add flag for each module - modules.forEach((module) => { - if ((module !== "") && (module !== null)){ - event.properties[`host_collector_module_${cleanPropertyName(module)}`] = true; - } - }); - - } - } - - // check if netdata_machine_guid property exists - if (typeof event.properties['netdata_machine_guid'] === 'string') { - // flag if empty string - if (event.properties['netdata_machine_guid']==='') { - event.properties['netdata_machine_guid'] = 'empty'; - event.properties['netdata_machine_guid_is_empty'] = true; - } else { - event.properties['netdata_machine_guid_is_empty'] = false; - } - } - - // check if netdata_machine_guid property exists - if (typeof event.properties['netdata_person_id'] === 'string') { - // flag if empty string - if (event.properties['netdata_person_id']==='') { - event.properties['netdata_person_id'] = 'empty'; - event.properties['netdata_person_id_is_empty'] = true; - } else { - event.properties['netdata_person_id_is_empty'] = false; - } - } - - // check if $distinct_id property exists - if (typeof event.properties['distinct_id'] === 'string') { - // flag if empty string - if (event.properties['distinct_id']==='') { - event.properties['distinct_id'] = 'empty'; - event.properties['distinct_id_is_empty'] = true; - } else { - event.properties['distinct_id_is_empty'] = false; - } - } - - // interaction_type - event.properties['interaction_type'] = getInteractionTypeAgent(event); - event.properties['interaction_detail'] = getInteractionDetailAgent(event); - event.properties['interaction_token'] = event.properties['interaction_type'].concat('|',event.properties['interaction_detail']); - //if (event.event === '$autocapture' && event.properties.hasOwnProperty('interaction_token')) { - // event.event = event.properties['interaction_token'] - //} - - return event -} - -function processElementsAgentInstaller(event) { - - // placeholder for now - - return event -} - -function processPropertiesAgentInstaller(event) { - - // only process if install_options not empty - if (event.properties['install_options'] != null) { - - // make set for install options - let installOptions = [...new Set((event.properties['install_options'] + ' ').split('--'))]; - - // make flag for each option - installOptions.forEach((installOption) => { - if ((installOption !== "") && (installOption !== null)){ - let installOptionKV = installOption.split(' '); - event.properties[`opt_${cleanPropertyName(installOptionKV[0])}`] = installOptionKV[1]; - } - }); - - } - - return event -} - -function processElementsCloud(event) { - // extract properties from elements - if (event.properties['$elements']) { - - // process each element, last (outermost) first. - event.properties['$elements'].slice().forEach((element) => { - - // el_data_testid_outer - if ('attr__data-testid' in element) { - event.properties['el_data_testid_outer'] = element['attr__data-testid']; - - // el_data_testid_outer_0 - if (element['attr__data-testid'].includes('::')) { - arr = element['attr__data-testid'].split('::'); - event.properties['el_data_testid_outer_0'] = arr[0]; - event.properties['el_data_testid_outer_1'] = arr[1]; - event.properties['el_data_testid_outer_2'] = arr[2]; - event.properties['el_data_testid_outer_3'] = arr[3]; - event.properties['el_data_testid_outer_4'] = arr[4]; - } - - } - - // el_data_ga_outer - if ('attr__data-ga' in element) { - event.properties['el_data_ga_outer'] = element['attr__data-ga']; - - // el_data_ga_outer_0 - if (element['attr__data-ga'].includes('::')) { - arr = element['attr__data-ga'].split('::'); - event.properties['el_data_ga_outer_0'] = arr[0]; - event.properties['el_data_ga_outer_1'] = arr[1]; - event.properties['el_data_ga_outer_2'] = arr[2]; - event.properties['el_data_ga_outer_3'] = arr[3]; - event.properties['el_data_ga_outer_4'] = arr[4]; - } - - } - - // el_data_track_outer - if ('attr__data-track' in element) { - event.properties['el_data_track_outer'] = element['attr__data-track']; - - // el_data_track_outer_0 - if (element['attr__data-track'].includes('::')) { - arr = element['attr__data-track'].split('::'); - event.properties['el_data_track_outer_0'] = arr[0]; - event.properties['el_data_track_outer_1'] = arr[1]; - event.properties['el_data_track_outer_2'] = arr[2]; - event.properties['el_data_track_outer_3'] = arr[3]; - event.properties['el_data_track_outer_4'] = arr[4]; - } - - } - - }); - - // process each element, reverse to use posthog order as preference - event.properties['$elements'].slice().reverse().forEach((element) => { - - // el_data_testid - if ('attr__data-testid' in element) { - event.properties['el_data_testid'] = element['attr__data-testid']; - - // el_data_testid_0 - if (element['attr__data-testid'].includes('::')) { - arr = element['attr__data-testid'].split('::'); - event.properties['el_data_testid_0'] = arr[0]; - event.properties['el_data_testid_1'] = arr[1]; - event.properties['el_data_testid_2'] = arr[2]; - event.properties['el_data_testid_3'] = arr[3]; - event.properties['el_data_testid_4'] = arr[4]; - } - - } - - // el_data_ga - if ('attr__data-ga' in element) { - event.properties['el_data_ga'] = element['attr__data-ga']; - - // el_data_ga_0 - if (element['attr__data-ga'].includes('::')) { - arr = element['attr__data-ga'].split('::'); - event.properties['el_data_ga_0'] = arr[0]; - event.properties['el_data_ga_1'] = arr[1]; - event.properties['el_data_ga_2'] = arr[2]; - event.properties['el_data_ga_3'] = arr[3]; - event.properties['el_data_ga_4'] = arr[4]; - - // give nice names in posthog - event.properties['event_category'] = arr[0]; - event.properties['event_action'] = arr[1]; - event.properties['event_label'] = arr[2]; - event.properties['event_value'] = arr[3]; - } - - } - - // el_data_testid_inner - if ('attr__data-testid' in element) { - event.properties['el_data_testid_inner'] = element['attr__data-testid']; - - // el_data_testid_inner_0 - if (element['attr__data-testid'].includes('::')) { - arr = element['attr__data-testid'].split('::'); - event.properties['el_data_testid_inner_0'] = arr[0]; - event.properties['el_data_testid_inner_1'] = arr[1]; - event.properties['el_data_testid_inner_2'] = arr[2]; - event.properties['el_data_testid_inner_3'] = arr[3]; - event.properties['el_data_testid_inner_4'] = arr[4]; - } - - } - - // el_data_ga_inner - if ('attr__data-ga' in element) { - event.properties['el_data_ga_inner'] = element['attr__data-ga']; - - // el_data_ga_inner_0 - if (element['attr__data-ga'].includes('::')) { - arr = element['attr__data-ga'].split('::'); - event.properties['el_data_ga_inner_0'] = arr[0]; - event.properties['el_data_ga_inner_1'] = arr[1]; - event.properties['el_data_ga_inner_2'] = arr[2]; - event.properties['el_data_ga_inner_3'] = arr[3]; - event.properties['el_data_ga_inner_4'] = arr[4]; - } - - } - - // el_data_track_inner - if ('attr__data-track' in element) { - event.properties['el_data_track_inner'] = element['attr__data-track']; - - // el_data_track_inner_0 - if (element['attr__data-track'].includes('::')) { - arr = element['attr__data-track'].split('::'); - event.properties['el_data_track_inner_0'] = arr[0]; - event.properties['el_data_track_inner_1'] = arr[1]; - event.properties['el_data_track_inner_2'] = arr[2]; - event.properties['el_data_track_inner_3'] = arr[3]; - event.properties['el_data_track_inner_4'] = arr[4]; - } - - } - - // el_href - if ('attr__href' in element && element['attr__href'] !== null) { - event.properties['el_href'] = element['attr__href']; - } else if ('href' in element && element['href'] !== null) { - event.properties['el_href'] = element['href']; - } else if ('$href' in element && element['$href'] !== null) { - event.properties['el_href'] = element['$href']; - } - - // el_onclick - if ('attr__onclick' in element && element['attr__onclick'] !== null) { - event.properties['el_onclick'] = element['attr__onclick']; - } - - // el_id - if ('attr__id' in element && element['attr__id'] !== null) { - event.properties['el_id'] = element['attr__id']; - } - - // el_name - if ('attr__name' in element && element['attr__name'] !== null) { - event.properties['el_name'] = element['attr__name']; - } - - // el_title - if ('attr__title' in element && element['attr__title'] !== null) { - event.properties['el_title'] = element['attr__title']; - } - - // el_text - if ('$el_text' in element && element['$el_text'] !== null && element['$el_text'] !== '') { - event.properties['el_text'] = element['$el_text']; - - } else if ('text' in element && element['text'] !== null && element['text'] !== '') { - event.properties['el_text'] = element['text']; - } - - // el_data_menuid - if ('attr__data-menuid' in element && element['attr__data-menuid'] !== null) { - event.properties['el_data_menuid'] = element['attr__data-menuid']; - } - - // el_data_submenuid - if ('attr__data-submenuid' in element && element['attr__data-submenuid'] !== null) { - event.properties['el_data_submenuid'] = element['attr__data-submenuid']; - } - - // el_data_chartid - if ('attr__data-chartid' in element && element['attr__data-chartid'] !== null) { - event.properties['el_data_chartid'] = element['attr__data-chartid']; - } - - // el_id_menu - if ('attr__id' in element && element['attr__id'] !== null && element['attr__id'].substring(0,5) === 'menu_') { - event.properties['el_id_menu'] = element['attr__id']; - event.properties['el_menu'] = element['attr__id'].split('_submenu')[0].replace('menu_', ''); - if (element['attr__id'].includes('_submenu_')) { - event.properties['el_submenu'] = element['attr__id'].split('_submenu_')[1]; - } else { - event.properties['el_submenu'] = ''; - } - } - - // el_class - if ('attr__class' in element && element['attr__class'] !== null) { - event.properties['el_class'] = element['attr__class']; - } - - }); - - } - - return event -} - -function processPropertiesCloud(event) { - - event = splitPathName(event); - - return event - -} - -function processElementsStaging(event) { - // extract properties from elements - if (event.properties['$elements']) { - - // process each element, reverse to use posthog order as preference - event.properties['$elements'].slice().reverse().forEach((element) => { - - // el_data_testid - if ('attr__data-testid' in element) { - event.properties['el_data_testid'] = element['attr__data-testid']; - - // el_data_testid_0 - if (element['attr__data-testid'].includes('::')) { - arr = element['attr__data-testid'].split('::'); - event.properties['el_data_testid_0'] = arr[0]; - event.properties['el_data_testid_1'] = arr[1]; - event.properties['el_data_testid_2'] = arr[2]; - event.properties['el_data_testid_3'] = arr[3]; - event.properties['el_data_testid_4'] = arr[4]; - } - - } - - // el_data_ga - if ('attr__data-ga' in element) { - event.properties['el_data_ga'] = element['attr__data-ga']; - - // el_data_ga_0 - if (element['attr__data-ga'].includes('::')) { - arr = element['attr__data-ga'].split('::'); - event.properties['el_data_ga_0'] = arr[0]; - event.properties['el_data_ga_1'] = arr[1]; - event.properties['el_data_ga_2'] = arr[2]; - event.properties['el_data_ga_3'] = arr[3]; - event.properties['el_data_ga_4'] = arr[4]; - } - - } - - // el_data_track - if ('attr__data-track' in element) { - event.properties['el_data_track'] = element['attr__data-track']; - - // el_data_track_0 - if (element['attr__data-track'].includes('::')) { - arr = element['attr__data-track'].split('::'); - event.properties['el_data_track_0'] = arr[0]; - event.properties['el_data_track_1'] = arr[1]; - event.properties['el_data_track_2'] = arr[2]; - event.properties['el_data_track_3'] = arr[3]; - event.properties['el_data_track_4'] = arr[4]; - } - - } - - // el_href - if ('attr__href' in element && element['attr__href'] !== null) { - event.properties['el_href'] = element['attr__href']; - } else if ('href' in element && element['href'] !== null) { - event.properties['el_href'] = element['href']; - } else if ('$href' in element && element['$href'] !== null) { - event.properties['el_href'] = element['$href']; - } - - // el_onclick - if ('attr__onclick' in element && element['attr__onclick'] !== null) { - event.properties['el_onclick'] = element['attr__onclick']; - } - - // el_id - if ('attr__id' in element && element['attr__id'] !== null) { - event.properties['el_id'] = element['attr__id']; - } - - // el_name - if ('attr__name' in element && element['attr__name'] !== null) { - event.properties['el_name'] = element['attr__name']; - } - - // el_title - if ('attr__title' in element && element['attr__title'] !== null) { - event.properties['el_title'] = element['attr__title']; - } - - // el_text - if ('$el_text' in element && element['$el_text'] !== null && element['$el_text'] !== '') { - event.properties['el_text'] = element['$el_text']; - - } else if ('text' in element && element['text'] !== null && element['text'] !== '') { - event.properties['el_text'] = element['text']; - } - - // el_data_menuid - if ('attr__data-menuid' in element && element['attr__data-menuid'] !== null) { - event.properties['el_data_menuid'] = element['attr__data-menuid']; - } - - // el_data_submenuid - if ('attr__data-submenuid' in element && element['attr__data-submenuid'] !== null) { - event.properties['el_data_submenuid'] = element['attr__data-submenuid']; - } - - // el_data_chartid - if ('attr__data-chartid' in element && element['attr__data-chartid'] !== null) { - event.properties['el_data_chartid'] = element['attr__data-chartid']; - } - - // el_id_menu - if ('attr__id' in element && element['attr__id'] !== null && element['attr__id'].substring(0,5) === 'menu_') { - event.properties['el_id_menu'] = element['attr__id']; - event.properties['el_menu'] = element['attr__id'].split('_submenu')[0].replace('menu_', ''); - if (element['attr__id'].includes('_submenu_')) { - event.properties['el_submenu'] = element['attr__id'].split('_submenu_')[1]; - } else { - event.properties['el_submenu'] = ''; - } - } - - // el_class - if ('attr__class' in element && element['attr__class'] !== null) { - event.properties['el_class'] = element['attr__class']; - } - - }); - - } - - return event -} - -function processPropertiesStaging(event) { - - event = splitPathName(event); - - return event -} - -function processElementsTesting(event) { - // extract properties from elements - if (event.properties['$elements']) { - - // process each element, reverse to use posthog order as preference - event.properties['$elements'].slice().reverse().forEach((element) => { - - // el_data_testid - if ('attr__data-testid' in element) { - event.properties['el_data_testid'] = element['attr__data-testid']; - - // el_data_testid_0 - if (element['attr__data-testid'].includes('::')) { - arr = element['attr__data-testid'].split('::'); - event.properties['el_data_testid_0'] = arr[0]; - event.properties['el_data_testid_1'] = arr[1]; - event.properties['el_data_testid_2'] = arr[2]; - event.properties['el_data_testid_3'] = arr[3]; - event.properties['el_data_testid_4'] = arr[4]; - } - - } - - // el_data_ga - if ('attr__data-ga' in element) { - event.properties['el_data_ga'] = element['attr__data-ga']; - - // el_data_ga_0 - if (element['attr__data-ga'].includes('::')) { - arr = element['attr__data-ga'].split('::'); - event.properties['el_data_ga_0'] = arr[0]; - event.properties['el_data_ga_1'] = arr[1]; - event.properties['el_data_ga_2'] = arr[2]; - event.properties['el_data_ga_3'] = arr[3]; - event.properties['el_data_ga_4'] = arr[4]; - } - - } - - // el_data_track - if ('attr__data-track' in element) { - event.properties['el_data_track'] = element['attr__data-track']; - - // el_data_track_0 - if (element['attr__data-track'].includes('::')) { - arr = element['attr__data-track'].split('::'); - event.properties['el_data_track_0'] = arr[0]; - event.properties['el_data_track_1'] = arr[1]; - event.properties['el_data_track_2'] = arr[2]; - event.properties['el_data_track_3'] = arr[3]; - event.properties['el_data_track_4'] = arr[4]; - } - - } - - // el_href - if ('attr__href' in element && element['attr__href'] !== null) { - event.properties['el_href'] = element['attr__href']; - } else if ('href' in element && element['href'] !== null) { - event.properties['el_href'] = element['href']; - } else if ('$href' in element && element['$href'] !== null) { - event.properties['el_href'] = element['$href']; - } - - // el_onclick - if ('attr__onclick' in element && element['attr__onclick'] !== null) { - event.properties['el_onclick'] = element['attr__onclick']; - } - - // el_id - if ('attr__id' in element && element['attr__id'] !== null) { - event.properties['el_id'] = element['attr__id']; - } - - // el_name - if ('attr__name' in element && element['attr__name'] !== null) { - event.properties['el_name'] = element['attr__name']; - } - - // el_title - if ('attr__title' in element && element['attr__title'] !== null) { - event.properties['el_title'] = element['attr__title']; - } - - // el_text - if ('$el_text' in element && element['$el_text'] !== null && element['$el_text'] !== '') { - event.properties['el_text'] = element['$el_text']; - - } else if ('text' in element && element['text'] !== null && element['text'] !== '') { - event.properties['el_text'] = element['text']; - } - - // el_data_menuid - if ('attr__data-menuid' in element && element['attr__data-menuid'] !== null) { - event.properties['el_data_menuid'] = element['attr__data-menuid']; - } - - // el_data_submenuid - if ('attr__data-submenuid' in element && element['attr__data-submenuid'] !== null) { - event.properties['el_data_submenuid'] = element['attr__data-submenuid']; - } - - // el_data_chartid - if ('attr__data-chartid' in element && element['attr__data-chartid'] !== null) { - event.properties['el_data_chartid'] = element['attr__data-chartid']; - } - - // el_id_menu - if ('attr__id' in element && element['attr__id'] !== null && element['attr__id'].substring(0,5) === 'menu_') { - event.properties['el_id_menu'] = element['attr__id']; - event.properties['el_menu'] = element['attr__id'].split('_submenu')[0].replace('menu_', ''); - if (element['attr__id'].includes('_submenu_')) { - event.properties['el_submenu'] = element['attr__id'].split('_submenu_')[1]; - } else { - event.properties['el_submenu'] = ''; - } - } - - // el_class - if ('attr__class' in element && element['attr__class'] !== null) { - event.properties['el_class'] = element['attr__class']; - } - - }); - - } - - return event -} - -function processPropertiesTesting(event) { - - event = splitPathName(event); - - return event -} - -function processElementsWebsite(event) { - // extract properties from elements - if (event.properties['$elements']) { - - // process each element, reverse to use posthog order as preference - event.properties['$elements'].slice().reverse().forEach((element) => { - - // el_data_testid - if ('attr__data-testid' in element) { - event.properties['el_data_testid'] = element['attr__data-testid']; - - // el_data_testid_0 - if (element['attr__data-testid'].includes('::')) { - arr = element['attr__data-testid'].split('::'); - event.properties['el_data_testid_0'] = arr[0]; - event.properties['el_data_testid_1'] = arr[1]; - event.properties['el_data_testid_2'] = arr[2]; - event.properties['el_data_testid_3'] = arr[3]; - event.properties['el_data_testid_4'] = arr[4]; - } - - } - - // el_data_ga - if ('attr__data-ga' in element) { - event.properties['el_data_ga'] = element['attr__data-ga']; - - // el_data_ga_0 - if (element['attr__data-ga'].includes('::')) { - arr = element['attr__data-ga'].split('::'); - event.properties['el_data_ga_0'] = arr[0]; - event.properties['el_data_ga_1'] = arr[1]; - event.properties['el_data_ga_2'] = arr[2]; - event.properties['el_data_ga_3'] = arr[3]; - event.properties['el_data_ga_4'] = arr[4]; - } - - } - - // el_data_track - if ('attr__data-track' in element) { - event.properties['el_data_track'] = element['attr__data-track']; - - // el_data_track_0 - if (element['attr__data-track'].includes('::')) { - arr = element['attr__data-track'].split('::'); - event.properties['el_data_track_0'] = arr[0]; - event.properties['el_data_track_1'] = arr[1]; - event.properties['el_data_track_2'] = arr[2]; - event.properties['el_data_track_3'] = arr[3]; - event.properties['el_data_track_4'] = arr[4]; - } - - } - - // el_href - if ('attr__href' in element && element['attr__href'] !== null) { - event.properties['el_href'] = element['attr__href']; - } else if ('href' in element && element['href'] !== null) { - event.properties['el_href'] = element['href']; - } else if ('$href' in element && element['$href'] !== null) { - event.properties['el_href'] = element['$href']; - } - - // el_onclick - if ('attr__onclick' in element && element['attr__onclick'] !== null) { - event.properties['el_onclick'] = element['attr__onclick']; - } - - // el_id - if ('attr__id' in element && element['attr__id'] !== null) { - event.properties['el_id'] = element['attr__id']; - } - - // el_name - if ('attr__name' in element && element['attr__name'] !== null) { - event.properties['el_name'] = element['attr__name']; - } - - // el_title - if ('attr__title' in element && element['attr__title'] !== null) { - event.properties['el_title'] = element['attr__title']; - } - - // el_text - if ('$el_text' in element && element['$el_text'] !== null && element['$el_text'] !== '') { - event.properties['el_text'] = element['$el_text']; - - } else if ('text' in element && element['text'] !== null && element['text'] !== '') { - event.properties['el_text'] = element['text']; - } - - // el_class - if ('attr__class' in element && element['attr__class'] !== null) { - event.properties['el_class'] = element['attr__class']; - } - - }); - - } - - return event -} - -function processPropertiesWebsite(event) { - - event = splitPathName(event); - - return event -} - -function processElementsLearn(event) { - // extract properties from elements - if (event.properties['$elements']) { - - // process each element, reverse to use posthog order as preference - event.properties['$elements'].slice().reverse().forEach((element) => { - - // el_data_testid - if ('attr__data-testid' in element) { - event.properties['el_data_testid'] = element['attr__data-testid']; - - // el_data_testid_0 - if (element['attr__data-testid'].includes('::')) { - arr = element['attr__data-testid'].split('::'); - event.properties['el_data_testid_0'] = arr[0]; - event.properties['el_data_testid_1'] = arr[1]; - event.properties['el_data_testid_2'] = arr[2]; - event.properties['el_data_testid_3'] = arr[3]; - event.properties['el_data_testid_4'] = arr[4]; - } - - } - - // el_data_ga - if ('attr__data-ga' in element) { - event.properties['el_data_ga'] = element['attr__data-ga']; - - // el_data_ga_0 - if (element['attr__data-ga'].includes('::')) { - arr = element['attr__data-ga'].split('::'); - event.properties['el_data_ga_0'] = arr[0]; - event.properties['el_data_ga_1'] = arr[1]; - event.properties['el_data_ga_2'] = arr[2]; - event.properties['el_data_ga_3'] = arr[3]; - event.properties['el_data_ga_4'] = arr[4]; - } - - } - - // el_data_track - if ('attr__data-track' in element) { - event.properties['el_data_track'] = element['attr__data-track']; - - // el_data_track_0 - if (element['attr__data-track'].includes('::')) { - arr = element['attr__data-track'].split('::'); - event.properties['el_data_track_0'] = arr[0]; - event.properties['el_data_track_1'] = arr[1]; - event.properties['el_data_track_2'] = arr[2]; - event.properties['el_data_track_3'] = arr[3]; - event.properties['el_data_track_4'] = arr[4]; - } - - } - - // el_href - if ('attr__href' in element && element['attr__href'] !== null) { - event.properties['el_href'] = element['attr__href']; - } else if ('href' in element && element['href'] !== null) { - event.properties['el_href'] = element['href']; - } else if ('$href' in element && element['$href'] !== null) { - event.properties['el_href'] = element['$href']; - } - - // el_onclick - if ('attr__onclick' in element && element['attr__onclick'] !== null) { - event.properties['el_onclick'] = element['attr__onclick']; - } - - // el_id - if ('attr__id' in element && element['attr__id'] !== null) { - event.properties['el_id'] = element['attr__id']; - } - - // el_name - if ('attr__name' in element && element['attr__name'] !== null) { - event.properties['el_name'] = element['attr__name']; - } - - // el_title - if ('attr__title' in element && element['attr__title'] !== null) { - event.properties['el_title'] = element['attr__title']; - } - - // el_text - if ('$el_text' in element && element['$el_text'] !== null && element['$el_text'] !== '') { - event.properties['el_text'] = element['$el_text']; - - } else if ('text' in element && element['text'] !== null && element['text'] !== '') { - event.properties['el_text'] = element['text']; - } - - // el_class - if ('attr__class' in element && element['attr__class'] !== null) { - event.properties['el_class'] = element['attr__class']; - } - - // el_aria_label - if ('attributes' in element && element['attributes'] !== null && 'attr__aria-label' in element['attributes']) { - event.properties['el_aria_label'] = element['attributes']['attr__aria-label']; - } - - }); - - } - - return event -} - -function processPropertiesLearn(event) { - - event = splitPathName(event); - - return event -} - -function processElementsCommunity(event) { - // extract properties from elements - if (event.properties['$elements']) { - - // process each element, reverse to use posthog order as preference - event.properties['$elements'].slice().reverse().forEach((element) => { - - // el_data_testid - if ('attr__data-testid' in element) { - event.properties['el_data_testid'] = element['attr__data-testid']; - - // el_data_testid_0 - if (element['attr__data-testid'].includes('::')) { - arr = element['attr__data-testid'].split('::'); - event.properties['el_data_testid_0'] = arr[0]; - event.properties['el_data_testid_1'] = arr[1]; - event.properties['el_data_testid_2'] = arr[2]; - event.properties['el_data_testid_3'] = arr[3]; - event.properties['el_data_testid_4'] = arr[4]; - } - - } - - // el_data_ga - if ('attr__data-ga' in element) { - event.properties['el_data_ga'] = element['attr__data-ga']; - - // el_data_ga_0 - if (element['attr__data-ga'].includes('::')) { - arr = element['attr__data-ga'].split('::'); - event.properties['el_data_ga_0'] = arr[0]; - event.properties['el_data_ga_1'] = arr[1]; - event.properties['el_data_ga_2'] = arr[2]; - event.properties['el_data_ga_3'] = arr[3]; - event.properties['el_data_ga_4'] = arr[4]; - } - - } - - // el_data_track - if ('attr__data-track' in element) { - event.properties['el_data_track'] = element['attr__data-track']; - - // el_data_track_0 - if (element['attr__data-track'].includes('::')) { - arr = element['attr__data-track'].split('::'); - event.properties['el_data_track_0'] = arr[0]; - event.properties['el_data_track_1'] = arr[1]; - event.properties['el_data_track_2'] = arr[2]; - event.properties['el_data_track_3'] = arr[3]; - event.properties['el_data_track_4'] = arr[4]; - } - - } - - // el_href - if ('attr__href' in element && element['attr__href'] !== null) { - event.properties['el_href'] = element['attr__href']; - } else if ('href' in element && element['href'] !== null) { - event.properties['el_href'] = element['href']; - } else if ('$href' in element && element['$href'] !== null) { - event.properties['el_href'] = element['$href']; - } - - // el_onclick - if ('attr__onclick' in element && element['attr__onclick'] !== null) { - event.properties['el_onclick'] = element['attr__onclick']; - } - - // el_id - if ('attr__id' in element && element['attr__id'] !== null) { - event.properties['el_id'] = element['attr__id']; - } - - // el_name - if ('attr__name' in element && element['attr__name'] !== null) { - event.properties['el_name'] = element['attr__name']; - } - - // el_title - if ('attr__title' in element && element['attr__title'] !== null) { - event.properties['el_title'] = element['attr__title']; - } - - // el_text - if ('$el_text' in element && element['$el_text'] !== null && element['$el_text'] !== '') { - event.properties['el_text'] = element['$el_text']; - - } else if ('text' in element && element['text'] !== null && element['text'] !== '') { - event.properties['el_text'] = element['text']; - } - - // el_class - if ('attr__class' in element && element['attr__class'] !== null) { - event.properties['el_class'] = element['attr__class']; - } - - }); - - } - - return event -} - -function processPropertiesCommunity(event) { - - event = splitPathName(event); - - return event -} - -function processElementsBlog(event) { - // extract properties from elements - if (event.properties['$elements']) { - - // process each element, reverse to use posthog order as preference - event.properties['$elements'].slice().reverse().forEach((element) => { - - // el_data_testid - if ('attr__data-testid' in element) { - event.properties['el_data_testid'] = element['attr__data-testid']; - - // el_data_testid_0 - if (element['attr__data-testid'].includes('::')) { - arr = element['attr__data-testid'].split('::'); - event.properties['el_data_testid_0'] = arr[0]; - event.properties['el_data_testid_1'] = arr[1]; - event.properties['el_data_testid_2'] = arr[2]; - event.properties['el_data_testid_3'] = arr[3]; - event.properties['el_data_testid_4'] = arr[4]; - } - - } - - // el_data_ga - if ('attr__data-ga' in element) { - event.properties['el_data_ga'] = element['attr__data-ga']; - - // el_data_ga_0 - if (element['attr__data-ga'].includes('::')) { - arr = element['attr__data-ga'].split('::'); - event.properties['el_data_ga_0'] = arr[0]; - event.properties['el_data_ga_1'] = arr[1]; - event.properties['el_data_ga_2'] = arr[2]; - event.properties['el_data_ga_3'] = arr[3]; - event.properties['el_data_ga_4'] = arr[4]; - } - - } - - // el_data_track - if ('attr__data-track' in element) { - event.properties['el_data_track'] = element['attr__data-track']; - - // el_data_track_0 - if (element['attr__data-track'].includes('::')) { - arr = element['attr__data-track'].split('::'); - event.properties['el_data_track_0'] = arr[0]; - event.properties['el_data_track_1'] = arr[1]; - event.properties['el_data_track_2'] = arr[2]; - event.properties['el_data_track_3'] = arr[3]; - event.properties['el_data_track_4'] = arr[4]; - } - - } - - // el_href - if ('attr__href' in element && element['attr__href'] !== null) { - event.properties['el_href'] = element['attr__href']; - } else if ('href' in element && element['href'] !== null) { - event.properties['el_href'] = element['href']; - } else if ('$href' in element && element['$href'] !== null) { - event.properties['el_href'] = element['$href']; - } - - // el_onclick - if ('attr__onclick' in element && element['attr__onclick'] !== null) { - event.properties['el_onclick'] = element['attr__onclick']; - } - - // el_id - if ('attr__id' in element && element['attr__id'] !== null) { - event.properties['el_id'] = element['attr__id']; - } - - // el_name - if ('attr__name' in element && element['attr__name'] !== null) { - event.properties['el_name'] = element['attr__name']; - } - - // el_title - if ('attr__title' in element && element['attr__title'] !== null) { - event.properties['el_title'] = element['attr__title']; - } - - // el_text - if ('$el_text' in element && element['$el_text'] !== null && element['$el_text'] !== '') { - event.properties['el_text'] = element['$el_text']; - - } else if ('text' in element && element['text'] !== null && element['text'] !== '') { - event.properties['el_text'] = element['text']; - } - - // el_class - if ('attr__class' in element && element['attr__class'] !== null) { - event.properties['el_class'] = element['attr__class']; - } - - // el_aria_label - if ('attributes' in element && element['attributes'] !== null && 'attr__aria-label' in element['attributes']) { - event.properties['el_aria_label'] = element['attributes']['attr__aria-label']; - } - - }); - - } - - return event -} - -function processPropertiesBlog(event) { - - event = splitPathName(event); - - return event -} - -//import URL from 'url'; - -const netdataPluginVersion = '0.0.15'; - -async function setupPlugin({ config, global }) { - //console.log("Setting up the plugin!") - //console.log(config) - global.setupDone = true; -} - -async function processEvent(event, { config, cache }) { - - if (event.properties) { - - event.properties['event_ph'] = event.event; - event.properties['netdata_posthog_plugin_version'] = netdataPluginVersion; - - // determine processing based on url - if ('$current_url' in event.properties) { - - // try extract specific url params - //if (event.properties['$current_url'].startsWith('http')) { - // const urlParams = new URL(event.properties['$current_url']).searchParams - // if (event.properties['$current_url'].includes('utm_source')) event.properties['url_param_utm_source'] = urlParams.get('utm_source'); - //} - - if ( - (['agent dashboard', 'agent backend'].includes(event.properties['$current_url'])) - || - (isDemo(event.properties['$current_url'])) - || - (event.properties['$current_url'].startsWith('https://netdata.corp.app.netdata.cloud')) - ) { - - event.properties['event_source'] = 'agent'; - event = processElementsAgent(event); - event = processPropertiesAgent(event); - - } else if (['agent installer'].includes(event.properties['$current_url'])) { - - event.properties['event_source'] = 'agent installer'; - event = processElementsAgentInstaller(event); - event = processPropertiesAgentInstaller(event); - - } else if (event.properties['$current_url'].startsWith('https://www.netdata.cloud')) { - - event.properties['event_source'] = 'website'; - event = processElementsWebsite(event); - event = processPropertiesWebsite(event); - - } else if (event.properties['$current_url'].includes('netdata-website.netlify.app')) - { - - event.properties['event_source'] = 'website_preview'; - event = processElementsWebsite(event); - event = processPropertiesWebsite(event); - - } else if (event.properties['$current_url'].startsWith('https://learn.netdata.cloud')) { - - event.properties['event_source'] = 'learn'; - event = processElementsLearn(event); - event = processPropertiesLearn(event); - - } else if (event.properties['$current_url'].includes('netdata-docusaurus.netlify.app')) { - - event.properties['event_source'] = 'learn_preview'; - event = processElementsLearn(event); - event = processPropertiesLearn(event); - - } else if (event.properties['$current_url'].startsWith('https://blog.netdata.cloud')) { - - event.properties['event_source'] = 'blog'; - event = processElementsBlog(event); - event = processPropertiesBlog(event); - - } else if (event.properties['$current_url'].includes('netdata-blog.netlify.app')) { - - event.properties['event_source'] = 'blog_preview'; - event = processElementsBlog(event); - event = processPropertiesBlog(event); - - } else if (event.properties['$current_url'].startsWith('https://community.netdata.cloud')) { - - event.properties['event_source'] = 'community'; - event = processElementsCommunity(event); - event = processPropertiesCommunity(event); - - } else if (event.properties['$current_url'].startsWith('https://staging.netdata.cloud')) { - - event.properties['event_source'] = 'staging'; - event = processElementsStaging(event); - event = processPropertiesStaging(event); - - } else if (event.properties['$current_url'].startsWith('https://testing.netdata.cloud')) { - - event.properties['event_source'] = 'testing'; - event = processElementsTesting(event); - event = processPropertiesTesting(event); - - } else if (event.properties['$current_url'].startsWith('https://app.netdata.cloud') - || event.properties['$current_url'].includes(':19999/spaces/') - || event.properties['$current_url'].includes('/spaces/') - ) { - - if (event.properties['$current_url'].startsWith('https://app.netdata.cloud')) { - event.properties['event_source'] = 'cloud'; - } else { - event.properties['event_source'] = 'cloud_agent'; - } - - event = processElementsCloud(event); - event = processPropertiesCloud(event); - - } else { - - event.properties['event_source'] = 'unknown'; - - } - - } else if (event.properties['event_ph'] === '$identify') { - - event.properties['event_source'] = 'cloud'; - event = processElementsCloud(event); - event = processPropertiesCloud(event); - - } else { - - event.properties['event_source'] = 'unknown'; - - } - } - - return event -} - -module.exports = { - setupPlugin, - processEvent, -}; diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/interaction_detail_agent.js b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/interaction_detail_agent.js deleted file mode 100644 index 3aded613f26ff..0000000000000 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/interaction_detail_agent.js +++ /dev/null @@ -1,202 +0,0 @@ -export function getInteractionDetailAgent(event) { - - // menu - if (['menu', 'submenu'].includes(event.properties['interaction_type'])) { - - return event.properties['el_href_menu'] - - // chart_toolbox - } else if (event.properties['interaction_type'] === 'chart_toolbox') { - - if (event.properties.hasOwnProperty('el_class_fa_minus')) { - return 'zoom_out' - } else if (event.properties.hasOwnProperty('el_class_fa_plus')) { - return 'zoom_in' - } else if (event.properties.hasOwnProperty('el_class_fa_backward')) { - return 'scroll_backward' - } else if (event.properties.hasOwnProperty('el_class_fa_forward')) { - return 'scroll_forward' - } else if (event.properties.hasOwnProperty('el_class_fa_sort')) { - return 'resize' - } else if (event.properties.hasOwnProperty('el_class_fa_play')) { - return 'play' - } else { - return 'other' - } - - // chart_dim - } else if (event.properties['interaction_type'] === 'chart_dim') { - - if ( - event.properties.hasOwnProperty('el_id') && - event.properties.hasOwnProperty('el_text') - ) { - return event.properties['el_data_netdata'].concat('.',event.properties['el_text']) - } else if ( - event.properties.hasOwnProperty('el_id') && - event.properties.hasOwnProperty('el_title') - ) { - return event.properties['el_data_netdata'].concat('.',event.properties['el_title']) - } else { - return 'other' - } - - // date_picker - } else if (event.properties['interaction_type'] === 'date_picker') { - - if (event.properties['el_id'] === 'date-picker-root') { - return 'open' - } else if ( - event.properties.hasOwnProperty('el_data_testid') && - event.properties['el_data_testid'].startsWith('date-picker') - ) { - if (event.properties['el_data_testid'].includes('click-quick-selector')) { - return event.properties['el_data_testid_1'].concat(' ',event.properties['el_data_testid_3']) - } else { - return event.properties['el_data_testid_1'] - } - } else if (event.properties['el_id'] === 'month_right') { - return 'month_right' - } else if (event.properties['el_id'] === 'month_left') { - return 'month_left' - } else if (event.properties.hasOwnProperty('el_class_daterangepicker')) { - return 'date_range' - } else { - return 'other' - } - - // update - } else if (event.properties['interaction_type'] === 'update') { - - if (event.properties['el_title'] === 'update') { - return 'open' - } else if (event.properties['el_text'] === 'Check Now') { - return 'check' - } else if (event.properties['el_text'] === 'Close') { - return 'close' - } else { - return 'other' - } - - // highlight - } else if (event.properties['interaction_type'] === 'highlight') { - - if (event.properties['el_onclick'] === 'urlOptions.clearHighlight();') { - return 'clear' - } else { - return 'other' - } - - // settings - } else if (event.properties['interaction_type'] === 'settings') { - - if (event.properties['el_id'] === 'root') { - return 'open' - } else if (event.properties['el_text'] === 'Close') { - return 'close' - } else if (event.properties['el_data_toggle'] === 'tab') { - return 'tab' - } else if (event.properties['el_data_toggle'] === 'toggle') { - return 'toggle' - } else { - return 'other' - } - - // alarms - } else if (event.properties['interaction_type'] === 'alarms') { - - if ( - event.properties.hasOwnProperty('el_href') && - event.properties['el_href'].includes('#alarm_all_') - ) { - return event.properties['el_text'] - } else if (event.properties.hasOwnProperty('el_class_page_number')) { - return 'page_number' - } else if (event.properties['el_id'] === 'root') { - return 'open' - } else if ( - event.properties['el_text'] === 'Active' || - event.properties['el_id'] === 'alarms_active' - ) { - return 'active' - } else if (event.properties['el_text'] === 'Log') { - return 'log' - } else if (event.properties['el_text'] === 'All') { - return 'all' - } else if ( - event.properties.hasOwnProperty('el_class_warning') && - event.properties.hasOwnProperty('el_text') - ) { - if ( - event.properties['el_text'].includes(':') || - event.properties['el_text'].includes('%') - ) { - return 'warn' - } else { - return 'warn__'.concat(event.properties['el_text']) - } - } else if ( - event.properties.hasOwnProperty('el_class_success') && - event.properties.hasOwnProperty('el_text') - ) { - if ( - event.properties['el_text'].includes(':') || - event.properties['el_text'].includes('%') - ) { - return 'norm' - } else { - return 'norm__'.concat(event.properties['el_text']) - } - } else if ( - event.properties.hasOwnProperty('el_class_danger') && - event.properties.hasOwnProperty('el_text') - ) { - if ( - event.properties['el_text'].includes(':') || - event.properties['el_text'].includes('%') - ) { - return 'crit' - } else { - return 'crit__'.concat(event.properties['el_text']) - } - } else if ( - event.properties.hasOwnProperty('el_class_info') && - event.properties.hasOwnProperty('el_text') - ) { - if ( - event.properties['el_text'].includes(':') || - event.properties['el_text'].includes('%') - ) { - return 'undef' - } else { - return 'undef__'.concat(event.properties['el_text']) - } - } else if ( - event.properties['el_text'] === 'Close' || - event.properties['el_text'] === '×' - ) { - return 'close' - } else if ( - event.properties['el_title'] === 'Refresh' && - event.properties['el_id'] === 'alarms_log' - ) { - return 'refresh_log' - } else { - return 'other' - } - - // cloud - } else if (event.properties['interaction_type'] === 'cloud') { - - if (event.properties['el_text'] === 'Sign In to Cloud') { - return 'sign_in' - } else { - return 'other' - } - - } else { - - return '' - - } -} \ No newline at end of file diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/interaction_type_agent.js b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/interaction_type_agent.js deleted file mode 100644 index d01cbad6a3cc8..0000000000000 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/interaction_type_agent.js +++ /dev/null @@ -1,144 +0,0 @@ -export function getInteractionTypeAgent(event) { - - if (['$pageview', '$pageleave', '$identify', 'agent backend'].includes(event.event)) { - - return event.event.replace('$', '').replace(' ', '_') - - // menu - } else if (event.properties.hasOwnProperty('el_href_menu')) { - - return event.properties['el_href_menu'].includes('submenu') ? 'submenu' : 'menu' - - // chart_toolbox - } else if ( - event.properties.hasOwnProperty('el_class_netdata_legend_resize_handler') || - event.properties.hasOwnProperty('el_class_netdata_legend_toolbox') - ) { - - return 'chart_toolbox' - - // chart_dim - } else if ( - event.properties.hasOwnProperty('el_data_netdata') && - event.properties.hasOwnProperty('el_id') && - ( - event.properties.hasOwnProperty('el_text') || event.properties.hasOwnProperty('el_title') - ) && - event.properties['el_id'].startsWith('chart_') - ) { - - return 'chart_dim' - - // date_picker - } else if ( - event.properties['el_id'] === 'date-picker-root' || - ( - event.properties.hasOwnProperty('el_data_testid') && - event.properties['el_data_testid'].startsWith('date-picker') - ) || - event.properties.hasOwnProperty('el_class_daterangepicker') - ) { - - return 'date_picker' - - // hamburger - } else if ( - event.properties.hasOwnProperty('el_class_collapsablesection') || - event.properties['el_title'] === 'hamburger' - ) { - - return 'hamburger' - - // update - } else if ( - event.properties.hasOwnProperty('el_data_target_updatemodal') || - event.properties.hasOwnProperty('el_id_updatemodal') - ) { - - return 'update' - - // help - } else if ( - ['Need Help?', 'question'].includes(event.properties['el_title']) || - event.properties['el_data_testid'] === 'documentation-help-close' || - event.properties.hasOwnProperty('el_class_documentation_container') - ) { - - return 'help' - - // load_snapshot - } else if ( - event.properties['el_data_target'] === '#loadSnapshotModal' || - event.properties['el_id'] === 'loadSnapshotDragAndDrop' || - event.properties['el_id'] === 'loadSnapshotSelectFiles' || - event.properties['el_id'] === 'loadSnapshotModal' - ) { - - return 'load_snapshot' - - // save_snapshot - } else if ( - event.properties['el_data_target'] === '#saveSnapshotModal' || - event.properties['el_id'] === 'saveSnapshotResolutionSlider' || - event.properties['el_id'] === 'saveSnapshotExport' || - event.properties['el_id'] === 'saveSnapshotModal' || - event.properties['el_id'] === 'hiddenDownloadLinks' - ) { - - return 'save_snapshot' - - // print - } else if ( - event.properties['el_data_target'] === '#printPreflightModal' || - event.properties['el_onclick'] === 'return printPreflight(),!1' - ) { - - return 'print' - - // alarms - } else if ( - event.properties['el_data_target'] === '#alarmsModal' || - ['#alarms_all', '#alarms_log', '#alarms_active'].includes(event.properties['el_href']) || - event.properties['el_id'] === 'alarms_log_table' || - event.properties['el_id'] === 'alarms_log' || - event.properties['el_id'] === 'alarmsModal' || - event.properties['el_aria_labelledby'] === 'alarmsModalLabel' - ) { - - return 'alarms' - - // settings - } else if ( - event.properties['el_data_target'] === '#optionsModal' || - event.properties['el_id'] === 'optionsModal' || - event.properties['el_aria_labelledby'] === 'optionsModalLabel' - ) { - - return 'settings' - - // cloud - } else if (event.properties.hasOwnProperty('el_class_signinbutton')) { - - return 'cloud' - - // highlight - } else if (event.properties['el_id'] === 'navbar-highlight-content') { - - return 'highlight' - - // add_charts - } else if (event.properties['el_text'] === 'Add more charts') { - - return 'add_charts' - - // add_alarms - } else if (event.properties['el_text'] === 'Add more alarms') { - - return 'add_alarms' - - } else { - - return 'other' - - } -} \ No newline at end of file diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/plugin.json b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/plugin.json deleted file mode 100644 index 474ecf072af2d..0000000000000 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/plugin.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "PostHog Netdata Event Processing", - "url": "https://github.com/andrewm4894/posthog-netdata-event-processing", - "description": "A Posthog plugin to do some data processing on our Posthog events as they arrive.", - "main": "index.js", - "config": [ - ] -} diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process.js b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process.js deleted file mode 100644 index 4b9f27bf2bed8..0000000000000 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process.js +++ /dev/null @@ -1,167 +0,0 @@ -import { processElementsAgent } from './process_elements_agent'; -import { processPropertiesAgent } from './process_properties_agent'; - -import { processElementsAgentInstaller } from './process_elements_agent_installer'; -import { processPropertiesAgentInstaller } from './process_properties_agent_installer'; - -import { processElementsCloud } from './process_elements_cloud'; -import { processPropertiesCloud } from './process_properties_cloud'; - -import { processElementsStaging } from './process_elements_staging'; -import { processPropertiesStaging } from './process_properties_staging'; - -import { processElementsTesting } from './process_elements_testing'; -import { processPropertiesTesting } from './process_properties_testing'; - -import { processElementsWebsite } from './process_elements_website'; -import { processPropertiesWebsite } from './process_properties_website'; - -import { processElementsLearn } from './process_elements_learn'; -import { processPropertiesLearn } from './process_properties_learn'; - -import { processElementsCommunity } from './process_elements_community'; -import { processPropertiesCommunity } from './process_properties_community'; - -import { processElementsBlog } from './process_elements_blog'; -import { processPropertiesBlog } from './process_properties_blog'; - -import { isDemo } from "./utils"; -//import URL from 'url'; - -const netdataPluginVersion = '0.0.15' - -async function setupPlugin({ config, global }) { - //console.log("Setting up the plugin!") - //console.log(config) - global.setupDone = true -} - -async function processEvent(event, { config, cache }) { - - if (event.properties) { - - event.properties['event_ph'] = event.event - event.properties['netdata_posthog_plugin_version'] = netdataPluginVersion - - // determine processing based on url - if ('$current_url' in event.properties) { - - // try extract specific url params - //if (event.properties['$current_url'].startsWith('http')) { - // const urlParams = new URL(event.properties['$current_url']).searchParams - // if (event.properties['$current_url'].includes('utm_source')) event.properties['url_param_utm_source'] = urlParams.get('utm_source'); - //} - - if ( - (['agent dashboard', 'agent backend'].includes(event.properties['$current_url'])) - || - (isDemo(event.properties['$current_url'])) - || - (event.properties['$current_url'].startsWith('https://netdata.corp.app.netdata.cloud')) - ) { - - event.properties['event_source'] = 'agent' - event = processElementsAgent(event) - event = processPropertiesAgent(event) - - } else if (['agent installer'].includes(event.properties['$current_url'])) { - - event.properties['event_source'] = 'agent installer' - event = processElementsAgentInstaller(event) - event = processPropertiesAgentInstaller(event) - - } else if (event.properties['$current_url'].startsWith('https://www.netdata.cloud')) { - - event.properties['event_source'] = 'website' - event = processElementsWebsite(event) - event = processPropertiesWebsite(event) - - } else if (event.properties['$current_url'].includes('netdata-website.netlify.app')) - { - - event.properties['event_source'] = 'website_preview' - event = processElementsWebsite(event) - event = processPropertiesWebsite(event) - - } else if (event.properties['$current_url'].startsWith('https://learn.netdata.cloud')) { - - event.properties['event_source'] = 'learn' - event = processElementsLearn(event) - event = processPropertiesLearn(event) - - } else if (event.properties['$current_url'].includes('netdata-docusaurus.netlify.app')) { - - event.properties['event_source'] = 'learn_preview' - event = processElementsLearn(event) - event = processPropertiesLearn(event) - - } else if (event.properties['$current_url'].startsWith('https://blog.netdata.cloud')) { - - event.properties['event_source'] = 'blog' - event = processElementsBlog(event) - event = processPropertiesBlog(event) - - } else if (event.properties['$current_url'].includes('netdata-blog.netlify.app')) { - - event.properties['event_source'] = 'blog_preview' - event = processElementsBlog(event) - event = processPropertiesBlog(event) - - } else if (event.properties['$current_url'].startsWith('https://community.netdata.cloud')) { - - event.properties['event_source'] = 'community' - event = processElementsCommunity(event) - event = processPropertiesCommunity(event) - - } else if (event.properties['$current_url'].startsWith('https://staging.netdata.cloud')) { - - event.properties['event_source'] = 'staging' - event = processElementsStaging(event) - event = processPropertiesStaging(event) - - } else if (event.properties['$current_url'].startsWith('https://testing.netdata.cloud')) { - - event.properties['event_source'] = 'testing' - event = processElementsTesting(event) - event = processPropertiesTesting(event) - - } else if (event.properties['$current_url'].startsWith('https://app.netdata.cloud') - || event.properties['$current_url'].includes(':19999/spaces/') - || event.properties['$current_url'].includes('/spaces/') - ) { - - if (event.properties['$current_url'].startsWith('https://app.netdata.cloud')) { - event.properties['event_source'] = 'cloud'; - } else { - event.properties['event_source'] = 'cloud_agent'; - } - - event = processElementsCloud(event) - event = processPropertiesCloud(event) - - } else { - - event.properties['event_source'] = 'unknown' - - } - - } else if (event.properties['event_ph'] === '$identify') { - - event.properties['event_source'] = 'cloud' - event = processElementsCloud(event) - event = processPropertiesCloud(event) - - } else { - - event.properties['event_source'] = 'unknown' - - } - } - - return event -} - -module.exports = { - setupPlugin, - processEvent, -} diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_elements_agent.js b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_elements_agent.js deleted file mode 100644 index fba6d57cd0e8c..0000000000000 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_elements_agent.js +++ /dev/null @@ -1,416 +0,0 @@ -import {isStringDDMMYYYYHHMM} from "./utils"; - -export function processElementsAgent(event) { - // extract properties from elements - if (event.properties['$elements']) { - - // process each element, last (outermost) first. - event.properties['$elements'].slice().forEach((element) => { - - // el_data_testid_outer - if ('attr__data-testid' in element) { - event.properties['el_data_testid_outer'] = element['attr__data-testid'] - - // el_data_testid_outer_0 - if (element['attr__data-testid'].includes('::')) { - arr = element['attr__data-testid'].split('::') - event.properties['el_data_testid_outer_0'] = arr[0] - event.properties['el_data_testid_outer_1'] = arr[1] - event.properties['el_data_testid_outer_2'] = arr[2] - event.properties['el_data_testid_outer_3'] = arr[3] - event.properties['el_data_testid_outer_4'] = arr[4] - } - - } - - // el_data_ga_outer - if ('attr__data-ga' in element) { - event.properties['el_data_ga_outer'] = element['attr__data-ga'] - - // el_data_ga_outer_0 - if (element['attr__data-ga'].includes('::')) { - arr = element['attr__data-ga'].split('::') - event.properties['el_data_ga_outer_0'] = arr[0] - event.properties['el_data_ga_outer_1'] = arr[1] - event.properties['el_data_ga_outer_2'] = arr[2] - event.properties['el_data_ga_outer_3'] = arr[3] - event.properties['el_data_ga_outer_4'] = arr[4] - } - - } - - // el_data_track_outer - if ('attr__data-track' in element) { - event.properties['el_data_track_outer'] = element['attr__data-track'] - - // el_data_track_outer_0 - if (element['attr__data-track'].includes('::')) { - arr = element['attr__data-track'].split('::') - event.properties['el_data_track_outer_0'] = arr[0] - event.properties['el_data_track_outer_1'] = arr[1] - event.properties['el_data_track_outer_2'] = arr[2] - event.properties['el_data_track_outer_3'] = arr[3] - event.properties['el_data_track_outer_4'] = arr[4] - } - - } - - }) - - // process each element, reverse to use posthog order as preference - event.properties['$elements'].slice().reverse().forEach((element) => { - - // el_data_testid - if ('attr__data-testid' in element) { - event.properties['el_data_testid'] = element['attr__data-testid'] - - // el_data_testid_0 - if (element['attr__data-testid'].includes('::')) { - arr = element['attr__data-testid'].split('::') - event.properties['el_data_testid_0'] = arr[0] - event.properties['el_data_testid_1'] = arr[1] - event.properties['el_data_testid_2'] = arr[2] - event.properties['el_data_testid_3'] = arr[3] - event.properties['el_data_testid_4'] = arr[4] - } - - } - - // el_data_ga - if ('attr__data-ga' in element) { - event.properties['el_data_ga'] = element['attr__data-ga'] - - // el_data_ga_0 - if (element['attr__data-ga'].includes('::')) { - arr = element['attr__data-ga'].split('::') - event.properties['el_data_ga_0'] = arr[0] - event.properties['el_data_ga_1'] = arr[1] - event.properties['el_data_ga_2'] = arr[2] - event.properties['el_data_ga_3'] = arr[3] - event.properties['el_data_ga_4'] = arr[4] - } - - } - - // el_data_testid_inner - if ('attr__data-testid' in element) { - event.properties['el_data_testid_inner'] = element['attr__data-testid'] - - // el_data_testid_inner_0 - if (element['attr__data-testid'].includes('::')) { - arr = element['attr__data-testid'].split('::') - event.properties['el_data_testid_inner_0'] = arr[0] - event.properties['el_data_testid_inner_1'] = arr[1] - event.properties['el_data_testid_inner_2'] = arr[2] - event.properties['el_data_testid_inner_3'] = arr[3] - event.properties['el_data_testid_inner_4'] = arr[4] - } - - } - - // el_data_ga_inner - if ('attr__data-ga' in element) { - event.properties['el_data_ga_inner'] = element['attr__data-ga'] - - // el_data_ga_inner_0 - if (element['attr__data-ga'].includes('::')) { - arr = element['attr__data-ga'].split('::') - event.properties['el_data_ga_inner_0'] = arr[0] - event.properties['el_data_ga_inner_1'] = arr[1] - event.properties['el_data_ga_inner_2'] = arr[2] - event.properties['el_data_ga_inner_3'] = arr[3] - event.properties['el_data_ga_inner_4'] = arr[4] - } - - } - - // el_data_track_inner - if ('attr__data-track' in element) { - event.properties['el_data_track_inner'] = element['attr__data-track'] - - // el_data_track_inner_0 - if (element['attr__data-track'].includes('::')) { - arr = element['attr__data-track'].split('::') - event.properties['el_data_track_inner_0'] = arr[0] - event.properties['el_data_track_inner_1'] = arr[1] - event.properties['el_data_track_inner_2'] = arr[2] - event.properties['el_data_track_inner_3'] = arr[3] - event.properties['el_data_track_inner_4'] = arr[4] - } - - } - - // el_id_menu - if ('attr__href' in element && element['attr__href'] !== null && element['attr__href'].substring(0,5) === '#menu') { - event.properties['el_href_menu'] = element['attr__href'] - event.properties['el_menu'] = element['attr__href'].split('_submenu')[0].replace('#menu_', '') - if (element['attr__href'].includes('_submenu_')) { - event.properties['el_submenu'] = element['attr__href'].split('_submenu_')[1] - } else { - event.properties['el_submenu'] = '' - } - } - - // el_href - if ('attr__href' in element && element['attr__href'] !== null) { - event.properties['el_href'] = element['attr__href'] - } else if ('href' in element && element['href'] !== null) { - event.properties['el_href'] = element['href'] - } else if ('$href' in element && element['$href'] !== null) { - event.properties['el_href'] = element['$href'] - } - - // el_onclick - if ('attr__onclick' in element && element['attr__onclick'] !== null) { - event.properties['el_onclick'] = element['attr__onclick'] - } - - // el_id - if ('attr__id' in element && element['attr__id'] !== null) { - event.properties['el_id'] = element['attr__id'] - } - - // el_name - if ('attr__name' in element && element['attr__name'] !== null) { - event.properties['el_name'] = element['attr__name'] - } - - // el_title - if ('attr__title' in element && element['attr__title'] !== null) { - event.properties['el_title'] = element['attr__title'] - } - - // el_text - if ('$el_text' in element && element['$el_text'] !== null && element['$el_text'] !== '') { - event.properties['el_text'] = element['$el_text'] - - // el_text_datetime - if (element['$el_text'].includes('/20') && isStringDDMMYYYYHHMM(element['$el_text'])) { - dtStr = element['$el_text'] - dtStrClean = dtStr.substring(6,10).concat( - '-',dtStr.substring(3,5),'-',dtStr.substring(0,2),' ',dtStr.substring(11,16) - ) - event.properties['el_text_datetime'] = dtStrClean - } - - } else if ('text' in element && element['text'] !== null && element['text'] !== '') { - event.properties['el_text'] = element['text'] - } - - // el_data_netdata - if ('attr__data-netdata' in element && element['attr__data-netdata'] !== null) { - event.properties['el_data_netdata'] = element['attr__data-netdata'] - } - - // el_data_target - if ('attr__data-target' in element && element['attr__data-target'] !== null && element['attr__data-target'] !== '#sidebar') { - event.properties['el_data_target'] = element['attr__data-target'] - - // el_data_target_updatemodal - if ('attr__data-target' in element && element['attr__data-target'] !== null && element['attr__data-target'] === '#updateModal') { - event.properties['el_data_target_updatemodal'] = true - } - - } - - // el_data_id - if ('attr__data-id' in element && element['attr__data-id'] !== null) { - event.properties['el_data_id'] = element['attr__data-id'] - } - - // el_data_original_title - if ('attr__data-original-title' in element && element['attr__data-original-title'] !== null) { - event.properties['el_data_original_title'] = element['attr__data-original-title'] - } - - // el_data_toggle - if ('attr__data-toggle' in element && element['attr__data-toggle'] !== null) { - event.properties['el_data_toggle'] = element['attr__data-toggle'] - } - - // el_data-legend-position - if ('attr__data-legend-position' in element && element['attr__data-legend-position'] !== null) { - event.properties['el_data_legend_position'] = element['attr__data-legend-position'] - } - - // el_aria_controls - if ('attr__aria-controls' in element && element['attr__aria-controls'] !== null) { - event.properties['el_aria_controls'] = element['attr__aria-controls'] - } - - // el_aria_labelledby - if ('attr__aria-labelledby' in element && element['attr__aria-labelledby'] !== null) { - event.properties['el_aria_labelledby'] = element['attr__aria-labelledby'] - } - - // el_class_netdata_legend_toolbox - if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'] === 'netdata-legend-toolbox') { - event.properties['el_class_netdata_legend_toolbox'] = true - } - - // el_class_fa_play - if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('fa-play')) { - event.properties['el_class_fa_play'] = true - } - - // el_class_fa_backward - if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('fa-backward')) { - event.properties['el_class_fa_backward'] = true - } - - // el_class_fa_forward - if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('fa-forward')) { - event.properties['el_class_fa_forward'] = true - } - - // el_class_fa_plus - if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('fa-plus')) { - event.properties['el_class_fa_plus'] = true - } - - // el_class_fa_minus - if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('fa-minus')) { - event.properties['el_class_fa_minus'] = true - } - - // el_class_fa_sort - if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('fa-sort')) { - event.properties['el_class_fa_sort'] = true - } - - // el_class_navbar_highlight_content - if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('navbar-highlight-content')) { - event.properties['el_class_navbar_highlight_content'] = true - } - - // el_class_datepickercontainer - if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('DatePickerContainer')) { - event.properties['el_class_datepickercontainer'] = true - } - - // el_class_startendcontainer - if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('StartEndContainer')) { - event.properties['el_class_startendcontainer'] = true - } - - // el_class_pickerbtnarea - if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('PickerBtnArea')) { - event.properties['el_class_pickerbtnarea'] = true - } - - // el_class_pickerbox - if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('PickerBox')) { - event.properties['el_class_pickerbox'] = true - } - - // el_class_collapsablesection - if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('CollapsableSection')) { - event.properties['el_class_collapsablesection'] = true - } - - // el_class_signinbutton - if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('SignInButton')) { - event.properties['el_class_signinbutton'] = true - } - - // el_class_documentation_container - if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('documentation__Container')) { - event.properties['el_class_documentation_container'] = true - } - - // el_class_utilitysection - if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('UtilitySection')) { - event.properties['el_class_utilitysection'] = true - } - - // el_class_success - if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('success')) { - event.properties['el_class_success'] = true - } - - // el_class_warning - if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('warning')) { - event.properties['el_class_warning'] = true - } - - // el_class_danger - if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('danger')) { - event.properties['el_class_danger'] = true - } - - // el_class_info - if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'] === 'info') { - event.properties['el_class_info'] = true - } - - // el_class_pagination - if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('pagination')) { - event.properties['el_class_pagination'] = true - } - - // el_class_page_number - if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('page-number')) { - event.properties['el_class_page_number'] = true - } - - // el_class_export - if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('export')) { - event.properties['el_class_export'] = true - } - - // el_class_netdata_chartblock_container - if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('netdata-chartblock-container')) { - event.properties['el_class_netdata_chartblock_container'] = true - } - - // el_class_netdata_reset_button - if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('netdata-reset-button')) { - event.properties['el_class_netdata_reset_button'] = true - } - - // el_class_netdata_legend_toolbox_button - if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('netdata-legend-toolbox-button')) { - event.properties['el_class_netdata_legend_toolbox_button'] = true - } - - // el_class_netdata_legend_resize_handler - if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('netdata-legend-resize-handler')) { - event.properties['el_class_netdata_legend_resize_handler'] = true - } - - // el_class_calendarday - if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('CalendarDay')) { - event.properties['el_class_calendarday'] = true - } - - // el_class_daypicker - if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('DayPicker')) { - event.properties['el_class_daypicker'] = true - } - - // el_class_daterangepicker - if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'].includes('DateRangePicker')) { - event.properties['el_class_daterangepicker'] = true - } - - // el_id_date_picker_root - if ('attr__id' in element && element['attr__id'] !== null && element['attr__id'].includes('date-picker-root')) { - event.properties['el_id_date_picker_root'] = true - } - - // el_id_updatemodal - if ('attr__id' in element && element['attr__id'] !== null && element['attr__id'].includes('updateModal')) { - event.properties['el_id_updatemodal'] = true - } - - // el_class - if ('attr__class' in element && element['attr__class'] !== null) { - event.properties['el_class'] = element['attr__class'] - } - - }) - - } - - return event -} \ No newline at end of file diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_elements_agent_installer.js b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_elements_agent_installer.js deleted file mode 100644 index 84f67000abb8c..0000000000000 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_elements_agent_installer.js +++ /dev/null @@ -1,7 +0,0 @@ - -export function processElementsAgentInstaller(event) { - - // placeholder for now - - return event -} \ No newline at end of file diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_elements_blog.js b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_elements_blog.js deleted file mode 100644 index f7da935ac326c..0000000000000 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_elements_blog.js +++ /dev/null @@ -1,109 +0,0 @@ - -export function processElementsBlog(event) { - // extract properties from elements - if (event.properties['$elements']) { - - // process each element, reverse to use posthog order as preference - event.properties['$elements'].slice().reverse().forEach((element) => { - - // el_data_testid - if ('attr__data-testid' in element) { - event.properties['el_data_testid'] = element['attr__data-testid'] - - // el_data_testid_0 - if (element['attr__data-testid'].includes('::')) { - arr = element['attr__data-testid'].split('::') - event.properties['el_data_testid_0'] = arr[0] - event.properties['el_data_testid_1'] = arr[1] - event.properties['el_data_testid_2'] = arr[2] - event.properties['el_data_testid_3'] = arr[3] - event.properties['el_data_testid_4'] = arr[4] - } - - } - - // el_data_ga - if ('attr__data-ga' in element) { - event.properties['el_data_ga'] = element['attr__data-ga'] - - // el_data_ga_0 - if (element['attr__data-ga'].includes('::')) { - arr = element['attr__data-ga'].split('::') - event.properties['el_data_ga_0'] = arr[0] - event.properties['el_data_ga_1'] = arr[1] - event.properties['el_data_ga_2'] = arr[2] - event.properties['el_data_ga_3'] = arr[3] - event.properties['el_data_ga_4'] = arr[4] - } - - } - - // el_data_track - if ('attr__data-track' in element) { - event.properties['el_data_track'] = element['attr__data-track'] - - // el_data_track_0 - if (element['attr__data-track'].includes('::')) { - arr = element['attr__data-track'].split('::') - event.properties['el_data_track_0'] = arr[0] - event.properties['el_data_track_1'] = arr[1] - event.properties['el_data_track_2'] = arr[2] - event.properties['el_data_track_3'] = arr[3] - event.properties['el_data_track_4'] = arr[4] - } - - } - - // el_href - if ('attr__href' in element && element['attr__href'] !== null) { - event.properties['el_href'] = element['attr__href'] - } else if ('href' in element && element['href'] !== null) { - event.properties['el_href'] = element['href'] - } else if ('$href' in element && element['$href'] !== null) { - event.properties['el_href'] = element['$href'] - } - - // el_onclick - if ('attr__onclick' in element && element['attr__onclick'] !== null) { - event.properties['el_onclick'] = element['attr__onclick'] - } - - // el_id - if ('attr__id' in element && element['attr__id'] !== null) { - event.properties['el_id'] = element['attr__id'] - } - - // el_name - if ('attr__name' in element && element['attr__name'] !== null) { - event.properties['el_name'] = element['attr__name'] - } - - // el_title - if ('attr__title' in element && element['attr__title'] !== null) { - event.properties['el_title'] = element['attr__title'] - } - - // el_text - if ('$el_text' in element && element['$el_text'] !== null && element['$el_text'] !== '') { - event.properties['el_text'] = element['$el_text'] - - } else if ('text' in element && element['text'] !== null && element['text'] !== '') { - event.properties['el_text'] = element['text'] - } - - // el_class - if ('attr__class' in element && element['attr__class'] !== null) { - event.properties['el_class'] = element['attr__class'] - } - - // el_aria_label - if ('attributes' in element && element['attributes'] !== null && 'attr__aria-label' in element['attributes']) { - event.properties['el_aria_label'] = element['attributes']['attr__aria-label'] - } - - }) - - } - - return event -} diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_elements_cloud.js b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_elements_cloud.js deleted file mode 100644 index 7e342eb7a514e..0000000000000 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_elements_cloud.js +++ /dev/null @@ -1,221 +0,0 @@ - -export function processElementsCloud(event) { - // extract properties from elements - if (event.properties['$elements']) { - - // process each element, last (outermost) first. - event.properties['$elements'].slice().forEach((element) => { - - // el_data_testid_outer - if ('attr__data-testid' in element) { - event.properties['el_data_testid_outer'] = element['attr__data-testid'] - - // el_data_testid_outer_0 - if (element['attr__data-testid'].includes('::')) { - arr = element['attr__data-testid'].split('::') - event.properties['el_data_testid_outer_0'] = arr[0] - event.properties['el_data_testid_outer_1'] = arr[1] - event.properties['el_data_testid_outer_2'] = arr[2] - event.properties['el_data_testid_outer_3'] = arr[3] - event.properties['el_data_testid_outer_4'] = arr[4] - } - - } - - // el_data_ga_outer - if ('attr__data-ga' in element) { - event.properties['el_data_ga_outer'] = element['attr__data-ga'] - - // el_data_ga_outer_0 - if (element['attr__data-ga'].includes('::')) { - arr = element['attr__data-ga'].split('::') - event.properties['el_data_ga_outer_0'] = arr[0] - event.properties['el_data_ga_outer_1'] = arr[1] - event.properties['el_data_ga_outer_2'] = arr[2] - event.properties['el_data_ga_outer_3'] = arr[3] - event.properties['el_data_ga_outer_4'] = arr[4] - } - - } - - // el_data_track_outer - if ('attr__data-track' in element) { - event.properties['el_data_track_outer'] = element['attr__data-track'] - - // el_data_track_outer_0 - if (element['attr__data-track'].includes('::')) { - arr = element['attr__data-track'].split('::') - event.properties['el_data_track_outer_0'] = arr[0] - event.properties['el_data_track_outer_1'] = arr[1] - event.properties['el_data_track_outer_2'] = arr[2] - event.properties['el_data_track_outer_3'] = arr[3] - event.properties['el_data_track_outer_4'] = arr[4] - } - - } - - }) - - // process each element, reverse to use posthog order as preference - event.properties['$elements'].slice().reverse().forEach((element) => { - - // el_data_testid - if ('attr__data-testid' in element) { - event.properties['el_data_testid'] = element['attr__data-testid'] - - // el_data_testid_0 - if (element['attr__data-testid'].includes('::')) { - arr = element['attr__data-testid'].split('::') - event.properties['el_data_testid_0'] = arr[0] - event.properties['el_data_testid_1'] = arr[1] - event.properties['el_data_testid_2'] = arr[2] - event.properties['el_data_testid_3'] = arr[3] - event.properties['el_data_testid_4'] = arr[4] - } - - } - - // el_data_ga - if ('attr__data-ga' in element) { - event.properties['el_data_ga'] = element['attr__data-ga'] - - // el_data_ga_0 - if (element['attr__data-ga'].includes('::')) { - arr = element['attr__data-ga'].split('::') - event.properties['el_data_ga_0'] = arr[0] - event.properties['el_data_ga_1'] = arr[1] - event.properties['el_data_ga_2'] = arr[2] - event.properties['el_data_ga_3'] = arr[3] - event.properties['el_data_ga_4'] = arr[4] - - // give nice names in posthog - event.properties['event_category'] = arr[0] - event.properties['event_action'] = arr[1] - event.properties['event_label'] = arr[2] - event.properties['event_value'] = arr[3] - } - - } - - // el_data_testid_inner - if ('attr__data-testid' in element) { - event.properties['el_data_testid_inner'] = element['attr__data-testid'] - - // el_data_testid_inner_0 - if (element['attr__data-testid'].includes('::')) { - arr = element['attr__data-testid'].split('::') - event.properties['el_data_testid_inner_0'] = arr[0] - event.properties['el_data_testid_inner_1'] = arr[1] - event.properties['el_data_testid_inner_2'] = arr[2] - event.properties['el_data_testid_inner_3'] = arr[3] - event.properties['el_data_testid_inner_4'] = arr[4] - } - - } - - // el_data_ga_inner - if ('attr__data-ga' in element) { - event.properties['el_data_ga_inner'] = element['attr__data-ga'] - - // el_data_ga_inner_0 - if (element['attr__data-ga'].includes('::')) { - arr = element['attr__data-ga'].split('::') - event.properties['el_data_ga_inner_0'] = arr[0] - event.properties['el_data_ga_inner_1'] = arr[1] - event.properties['el_data_ga_inner_2'] = arr[2] - event.properties['el_data_ga_inner_3'] = arr[3] - event.properties['el_data_ga_inner_4'] = arr[4] - } - - } - - // el_data_track_inner - if ('attr__data-track' in element) { - event.properties['el_data_track_inner'] = element['attr__data-track'] - - // el_data_track_inner_0 - if (element['attr__data-track'].includes('::')) { - arr = element['attr__data-track'].split('::') - event.properties['el_data_track_inner_0'] = arr[0] - event.properties['el_data_track_inner_1'] = arr[1] - event.properties['el_data_track_inner_2'] = arr[2] - event.properties['el_data_track_inner_3'] = arr[3] - event.properties['el_data_track_inner_4'] = arr[4] - } - - } - - // el_href - if ('attr__href' in element && element['attr__href'] !== null) { - event.properties['el_href'] = element['attr__href'] - } else if ('href' in element && element['href'] !== null) { - event.properties['el_href'] = element['href'] - } else if ('$href' in element && element['$href'] !== null) { - event.properties['el_href'] = element['$href'] - } - - // el_onclick - if ('attr__onclick' in element && element['attr__onclick'] !== null) { - event.properties['el_onclick'] = element['attr__onclick'] - } - - // el_id - if ('attr__id' in element && element['attr__id'] !== null) { - event.properties['el_id'] = element['attr__id'] - } - - // el_name - if ('attr__name' in element && element['attr__name'] !== null) { - event.properties['el_name'] = element['attr__name'] - } - - // el_title - if ('attr__title' in element && element['attr__title'] !== null) { - event.properties['el_title'] = element['attr__title'] - } - - // el_text - if ('$el_text' in element && element['$el_text'] !== null && element['$el_text'] !== '') { - event.properties['el_text'] = element['$el_text'] - - } else if ('text' in element && element['text'] !== null && element['text'] !== '') { - event.properties['el_text'] = element['text'] - } - - // el_data_menuid - if ('attr__data-menuid' in element && element['attr__data-menuid'] !== null) { - event.properties['el_data_menuid'] = element['attr__data-menuid'] - } - - // el_data_submenuid - if ('attr__data-submenuid' in element && element['attr__data-submenuid'] !== null) { - event.properties['el_data_submenuid'] = element['attr__data-submenuid'] - } - - // el_data_chartid - if ('attr__data-chartid' in element && element['attr__data-chartid'] !== null) { - event.properties['el_data_chartid'] = element['attr__data-chartid'] - } - - // el_id_menu - if ('attr__id' in element && element['attr__id'] !== null && element['attr__id'].substring(0,5) === 'menu_') { - event.properties['el_id_menu'] = element['attr__id'] - event.properties['el_menu'] = element['attr__id'].split('_submenu')[0].replace('menu_', '') - if (element['attr__id'].includes('_submenu_')) { - event.properties['el_submenu'] = element['attr__id'].split('_submenu_')[1] - } else { - event.properties['el_submenu'] = '' - } - } - - // el_class - if ('attr__class' in element && element['attr__class'] !== null) { - event.properties['el_class'] = element['attr__class'] - } - - }) - - } - - return event -} \ No newline at end of file diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_elements_community.js b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_elements_community.js deleted file mode 100644 index 7a1435a75e674..0000000000000 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_elements_community.js +++ /dev/null @@ -1,104 +0,0 @@ - -export function processElementsCommunity(event) { - // extract properties from elements - if (event.properties['$elements']) { - - // process each element, reverse to use posthog order as preference - event.properties['$elements'].slice().reverse().forEach((element) => { - - // el_data_testid - if ('attr__data-testid' in element) { - event.properties['el_data_testid'] = element['attr__data-testid'] - - // el_data_testid_0 - if (element['attr__data-testid'].includes('::')) { - arr = element['attr__data-testid'].split('::') - event.properties['el_data_testid_0'] = arr[0] - event.properties['el_data_testid_1'] = arr[1] - event.properties['el_data_testid_2'] = arr[2] - event.properties['el_data_testid_3'] = arr[3] - event.properties['el_data_testid_4'] = arr[4] - } - - } - - // el_data_ga - if ('attr__data-ga' in element) { - event.properties['el_data_ga'] = element['attr__data-ga'] - - // el_data_ga_0 - if (element['attr__data-ga'].includes('::')) { - arr = element['attr__data-ga'].split('::') - event.properties['el_data_ga_0'] = arr[0] - event.properties['el_data_ga_1'] = arr[1] - event.properties['el_data_ga_2'] = arr[2] - event.properties['el_data_ga_3'] = arr[3] - event.properties['el_data_ga_4'] = arr[4] - } - - } - - // el_data_track - if ('attr__data-track' in element) { - event.properties['el_data_track'] = element['attr__data-track'] - - // el_data_track_0 - if (element['attr__data-track'].includes('::')) { - arr = element['attr__data-track'].split('::') - event.properties['el_data_track_0'] = arr[0] - event.properties['el_data_track_1'] = arr[1] - event.properties['el_data_track_2'] = arr[2] - event.properties['el_data_track_3'] = arr[3] - event.properties['el_data_track_4'] = arr[4] - } - - } - - // el_href - if ('attr__href' in element && element['attr__href'] !== null) { - event.properties['el_href'] = element['attr__href'] - } else if ('href' in element && element['href'] !== null) { - event.properties['el_href'] = element['href'] - } else if ('$href' in element && element['$href'] !== null) { - event.properties['el_href'] = element['$href'] - } - - // el_onclick - if ('attr__onclick' in element && element['attr__onclick'] !== null) { - event.properties['el_onclick'] = element['attr__onclick'] - } - - // el_id - if ('attr__id' in element && element['attr__id'] !== null) { - event.properties['el_id'] = element['attr__id'] - } - - // el_name - if ('attr__name' in element && element['attr__name'] !== null) { - event.properties['el_name'] = element['attr__name'] - } - - // el_title - if ('attr__title' in element && element['attr__title'] !== null) { - event.properties['el_title'] = element['attr__title'] - } - - // el_text - if ('$el_text' in element && element['$el_text'] !== null && element['$el_text'] !== '') { - event.properties['el_text'] = element['$el_text'] - - } else if ('text' in element && element['text'] !== null && element['text'] !== '') { - event.properties['el_text'] = element['text'] - } - - // el_class - if ('attr__class' in element && element['attr__class'] !== null) { - event.properties['el_class'] = element['attr__class'] - } - - }) - - } - - return event -} \ No newline at end of file diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_elements_learn.js b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_elements_learn.js deleted file mode 100644 index 26d536cb78f5b..0000000000000 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_elements_learn.js +++ /dev/null @@ -1,109 +0,0 @@ - -export function processElementsLearn(event) { - // extract properties from elements - if (event.properties['$elements']) { - - // process each element, reverse to use posthog order as preference - event.properties['$elements'].slice().reverse().forEach((element) => { - - // el_data_testid - if ('attr__data-testid' in element) { - event.properties['el_data_testid'] = element['attr__data-testid'] - - // el_data_testid_0 - if (element['attr__data-testid'].includes('::')) { - arr = element['attr__data-testid'].split('::') - event.properties['el_data_testid_0'] = arr[0] - event.properties['el_data_testid_1'] = arr[1] - event.properties['el_data_testid_2'] = arr[2] - event.properties['el_data_testid_3'] = arr[3] - event.properties['el_data_testid_4'] = arr[4] - } - - } - - // el_data_ga - if ('attr__data-ga' in element) { - event.properties['el_data_ga'] = element['attr__data-ga'] - - // el_data_ga_0 - if (element['attr__data-ga'].includes('::')) { - arr = element['attr__data-ga'].split('::') - event.properties['el_data_ga_0'] = arr[0] - event.properties['el_data_ga_1'] = arr[1] - event.properties['el_data_ga_2'] = arr[2] - event.properties['el_data_ga_3'] = arr[3] - event.properties['el_data_ga_4'] = arr[4] - } - - } - - // el_data_track - if ('attr__data-track' in element) { - event.properties['el_data_track'] = element['attr__data-track'] - - // el_data_track_0 - if (element['attr__data-track'].includes('::')) { - arr = element['attr__data-track'].split('::') - event.properties['el_data_track_0'] = arr[0] - event.properties['el_data_track_1'] = arr[1] - event.properties['el_data_track_2'] = arr[2] - event.properties['el_data_track_3'] = arr[3] - event.properties['el_data_track_4'] = arr[4] - } - - } - - // el_href - if ('attr__href' in element && element['attr__href'] !== null) { - event.properties['el_href'] = element['attr__href'] - } else if ('href' in element && element['href'] !== null) { - event.properties['el_href'] = element['href'] - } else if ('$href' in element && element['$href'] !== null) { - event.properties['el_href'] = element['$href'] - } - - // el_onclick - if ('attr__onclick' in element && element['attr__onclick'] !== null) { - event.properties['el_onclick'] = element['attr__onclick'] - } - - // el_id - if ('attr__id' in element && element['attr__id'] !== null) { - event.properties['el_id'] = element['attr__id'] - } - - // el_name - if ('attr__name' in element && element['attr__name'] !== null) { - event.properties['el_name'] = element['attr__name'] - } - - // el_title - if ('attr__title' in element && element['attr__title'] !== null) { - event.properties['el_title'] = element['attr__title'] - } - - // el_text - if ('$el_text' in element && element['$el_text'] !== null && element['$el_text'] !== '') { - event.properties['el_text'] = element['$el_text'] - - } else if ('text' in element && element['text'] !== null && element['text'] !== '') { - event.properties['el_text'] = element['text'] - } - - // el_class - if ('attr__class' in element && element['attr__class'] !== null) { - event.properties['el_class'] = element['attr__class'] - } - - // el_aria_label - if ('attributes' in element && element['attributes'] !== null && 'attr__aria-label' in element['attributes']) { - event.properties['el_aria_label'] = element['attributes']['attr__aria-label'] - } - - }) - - } - - return event -} \ No newline at end of file diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_elements_staging.js b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_elements_staging.js deleted file mode 100644 index bb0cf9f80df64..0000000000000 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_elements_staging.js +++ /dev/null @@ -1,130 +0,0 @@ - -export function processElementsStaging(event) { - // extract properties from elements - if (event.properties['$elements']) { - - // process each element, reverse to use posthog order as preference - event.properties['$elements'].slice().reverse().forEach((element) => { - - // el_data_testid - if ('attr__data-testid' in element) { - event.properties['el_data_testid'] = element['attr__data-testid'] - - // el_data_testid_0 - if (element['attr__data-testid'].includes('::')) { - arr = element['attr__data-testid'].split('::') - event.properties['el_data_testid_0'] = arr[0] - event.properties['el_data_testid_1'] = arr[1] - event.properties['el_data_testid_2'] = arr[2] - event.properties['el_data_testid_3'] = arr[3] - event.properties['el_data_testid_4'] = arr[4] - } - - } - - // el_data_ga - if ('attr__data-ga' in element) { - event.properties['el_data_ga'] = element['attr__data-ga'] - - // el_data_ga_0 - if (element['attr__data-ga'].includes('::')) { - arr = element['attr__data-ga'].split('::') - event.properties['el_data_ga_0'] = arr[0] - event.properties['el_data_ga_1'] = arr[1] - event.properties['el_data_ga_2'] = arr[2] - event.properties['el_data_ga_3'] = arr[3] - event.properties['el_data_ga_4'] = arr[4] - } - - } - - // el_data_track - if ('attr__data-track' in element) { - event.properties['el_data_track'] = element['attr__data-track'] - - // el_data_track_0 - if (element['attr__data-track'].includes('::')) { - arr = element['attr__data-track'].split('::') - event.properties['el_data_track_0'] = arr[0] - event.properties['el_data_track_1'] = arr[1] - event.properties['el_data_track_2'] = arr[2] - event.properties['el_data_track_3'] = arr[3] - event.properties['el_data_track_4'] = arr[4] - } - - } - - // el_href - if ('attr__href' in element && element['attr__href'] !== null) { - event.properties['el_href'] = element['attr__href'] - } else if ('href' in element && element['href'] !== null) { - event.properties['el_href'] = element['href'] - } else if ('$href' in element && element['$href'] !== null) { - event.properties['el_href'] = element['$href'] - } - - // el_onclick - if ('attr__onclick' in element && element['attr__onclick'] !== null) { - event.properties['el_onclick'] = element['attr__onclick'] - } - - // el_id - if ('attr__id' in element && element['attr__id'] !== null) { - event.properties['el_id'] = element['attr__id'] - } - - // el_name - if ('attr__name' in element && element['attr__name'] !== null) { - event.properties['el_name'] = element['attr__name'] - } - - // el_title - if ('attr__title' in element && element['attr__title'] !== null) { - event.properties['el_title'] = element['attr__title'] - } - - // el_text - if ('$el_text' in element && element['$el_text'] !== null && element['$el_text'] !== '') { - event.properties['el_text'] = element['$el_text'] - - } else if ('text' in element && element['text'] !== null && element['text'] !== '') { - event.properties['el_text'] = element['text'] - } - - // el_data_menuid - if ('attr__data-menuid' in element && element['attr__data-menuid'] !== null) { - event.properties['el_data_menuid'] = element['attr__data-menuid'] - } - - // el_data_submenuid - if ('attr__data-submenuid' in element && element['attr__data-submenuid'] !== null) { - event.properties['el_data_submenuid'] = element['attr__data-submenuid'] - } - - // el_data_chartid - if ('attr__data-chartid' in element && element['attr__data-chartid'] !== null) { - event.properties['el_data_chartid'] = element['attr__data-chartid'] - } - - // el_id_menu - if ('attr__id' in element && element['attr__id'] !== null && element['attr__id'].substring(0,5) === 'menu_') { - event.properties['el_id_menu'] = element['attr__id'] - event.properties['el_menu'] = element['attr__id'].split('_submenu')[0].replace('menu_', '') - if (element['attr__id'].includes('_submenu_')) { - event.properties['el_submenu'] = element['attr__id'].split('_submenu_')[1] - } else { - event.properties['el_submenu'] = '' - } - } - - // el_class - if ('attr__class' in element && element['attr__class'] !== null) { - event.properties['el_class'] = element['attr__class'] - } - - }) - - } - - return event -} \ No newline at end of file diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_elements_testing.js b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_elements_testing.js deleted file mode 100644 index 75909102a45e5..0000000000000 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_elements_testing.js +++ /dev/null @@ -1,130 +0,0 @@ - -export function processElementsTesting(event) { - // extract properties from elements - if (event.properties['$elements']) { - - // process each element, reverse to use posthog order as preference - event.properties['$elements'].slice().reverse().forEach((element) => { - - // el_data_testid - if ('attr__data-testid' in element) { - event.properties['el_data_testid'] = element['attr__data-testid'] - - // el_data_testid_0 - if (element['attr__data-testid'].includes('::')) { - arr = element['attr__data-testid'].split('::') - event.properties['el_data_testid_0'] = arr[0] - event.properties['el_data_testid_1'] = arr[1] - event.properties['el_data_testid_2'] = arr[2] - event.properties['el_data_testid_3'] = arr[3] - event.properties['el_data_testid_4'] = arr[4] - } - - } - - // el_data_ga - if ('attr__data-ga' in element) { - event.properties['el_data_ga'] = element['attr__data-ga'] - - // el_data_ga_0 - if (element['attr__data-ga'].includes('::')) { - arr = element['attr__data-ga'].split('::') - event.properties['el_data_ga_0'] = arr[0] - event.properties['el_data_ga_1'] = arr[1] - event.properties['el_data_ga_2'] = arr[2] - event.properties['el_data_ga_3'] = arr[3] - event.properties['el_data_ga_4'] = arr[4] - } - - } - - // el_data_track - if ('attr__data-track' in element) { - event.properties['el_data_track'] = element['attr__data-track'] - - // el_data_track_0 - if (element['attr__data-track'].includes('::')) { - arr = element['attr__data-track'].split('::') - event.properties['el_data_track_0'] = arr[0] - event.properties['el_data_track_1'] = arr[1] - event.properties['el_data_track_2'] = arr[2] - event.properties['el_data_track_3'] = arr[3] - event.properties['el_data_track_4'] = arr[4] - } - - } - - // el_href - if ('attr__href' in element && element['attr__href'] !== null) { - event.properties['el_href'] = element['attr__href'] - } else if ('href' in element && element['href'] !== null) { - event.properties['el_href'] = element['href'] - } else if ('$href' in element && element['$href'] !== null) { - event.properties['el_href'] = element['$href'] - } - - // el_onclick - if ('attr__onclick' in element && element['attr__onclick'] !== null) { - event.properties['el_onclick'] = element['attr__onclick'] - } - - // el_id - if ('attr__id' in element && element['attr__id'] !== null) { - event.properties['el_id'] = element['attr__id'] - } - - // el_name - if ('attr__name' in element && element['attr__name'] !== null) { - event.properties['el_name'] = element['attr__name'] - } - - // el_title - if ('attr__title' in element && element['attr__title'] !== null) { - event.properties['el_title'] = element['attr__title'] - } - - // el_text - if ('$el_text' in element && element['$el_text'] !== null && element['$el_text'] !== '') { - event.properties['el_text'] = element['$el_text'] - - } else if ('text' in element && element['text'] !== null && element['text'] !== '') { - event.properties['el_text'] = element['text'] - } - - // el_data_menuid - if ('attr__data-menuid' in element && element['attr__data-menuid'] !== null) { - event.properties['el_data_menuid'] = element['attr__data-menuid'] - } - - // el_data_submenuid - if ('attr__data-submenuid' in element && element['attr__data-submenuid'] !== null) { - event.properties['el_data_submenuid'] = element['attr__data-submenuid'] - } - - // el_data_chartid - if ('attr__data-chartid' in element && element['attr__data-chartid'] !== null) { - event.properties['el_data_chartid'] = element['attr__data-chartid'] - } - - // el_id_menu - if ('attr__id' in element && element['attr__id'] !== null && element['attr__id'].substring(0,5) === 'menu_') { - event.properties['el_id_menu'] = element['attr__id'] - event.properties['el_menu'] = element['attr__id'].split('_submenu')[0].replace('menu_', '') - if (element['attr__id'].includes('_submenu_')) { - event.properties['el_submenu'] = element['attr__id'].split('_submenu_')[1] - } else { - event.properties['el_submenu'] = '' - } - } - - // el_class - if ('attr__class' in element && element['attr__class'] !== null) { - event.properties['el_class'] = element['attr__class'] - } - - }) - - } - - return event -} \ No newline at end of file diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_elements_website.js b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_elements_website.js deleted file mode 100644 index ec7da7a19d655..0000000000000 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_elements_website.js +++ /dev/null @@ -1,104 +0,0 @@ - -export function processElementsWebsite(event) { - // extract properties from elements - if (event.properties['$elements']) { - - // process each element, reverse to use posthog order as preference - event.properties['$elements'].slice().reverse().forEach((element) => { - - // el_data_testid - if ('attr__data-testid' in element) { - event.properties['el_data_testid'] = element['attr__data-testid'] - - // el_data_testid_0 - if (element['attr__data-testid'].includes('::')) { - arr = element['attr__data-testid'].split('::') - event.properties['el_data_testid_0'] = arr[0] - event.properties['el_data_testid_1'] = arr[1] - event.properties['el_data_testid_2'] = arr[2] - event.properties['el_data_testid_3'] = arr[3] - event.properties['el_data_testid_4'] = arr[4] - } - - } - - // el_data_ga - if ('attr__data-ga' in element) { - event.properties['el_data_ga'] = element['attr__data-ga'] - - // el_data_ga_0 - if (element['attr__data-ga'].includes('::')) { - arr = element['attr__data-ga'].split('::') - event.properties['el_data_ga_0'] = arr[0] - event.properties['el_data_ga_1'] = arr[1] - event.properties['el_data_ga_2'] = arr[2] - event.properties['el_data_ga_3'] = arr[3] - event.properties['el_data_ga_4'] = arr[4] - } - - } - - // el_data_track - if ('attr__data-track' in element) { - event.properties['el_data_track'] = element['attr__data-track'] - - // el_data_track_0 - if (element['attr__data-track'].includes('::')) { - arr = element['attr__data-track'].split('::') - event.properties['el_data_track_0'] = arr[0] - event.properties['el_data_track_1'] = arr[1] - event.properties['el_data_track_2'] = arr[2] - event.properties['el_data_track_3'] = arr[3] - event.properties['el_data_track_4'] = arr[4] - } - - } - - // el_href - if ('attr__href' in element && element['attr__href'] !== null) { - event.properties['el_href'] = element['attr__href'] - } else if ('href' in element && element['href'] !== null) { - event.properties['el_href'] = element['href'] - } else if ('$href' in element && element['$href'] !== null) { - event.properties['el_href'] = element['$href'] - } - - // el_onclick - if ('attr__onclick' in element && element['attr__onclick'] !== null) { - event.properties['el_onclick'] = element['attr__onclick'] - } - - // el_id - if ('attr__id' in element && element['attr__id'] !== null) { - event.properties['el_id'] = element['attr__id'] - } - - // el_name - if ('attr__name' in element && element['attr__name'] !== null) { - event.properties['el_name'] = element['attr__name'] - } - - // el_title - if ('attr__title' in element && element['attr__title'] !== null) { - event.properties['el_title'] = element['attr__title'] - } - - // el_text - if ('$el_text' in element && element['$el_text'] !== null && element['$el_text'] !== '') { - event.properties['el_text'] = element['$el_text'] - - } else if ('text' in element && element['text'] !== null && element['text'] !== '') { - event.properties['el_text'] = element['text'] - } - - // el_class - if ('attr__class' in element && element['attr__class'] !== null) { - event.properties['el_class'] = element['attr__class'] - } - - }) - - } - - return event -} \ No newline at end of file diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_properties_agent.js b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_properties_agent.js deleted file mode 100644 index b72000cdd2907..0000000000000 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_properties_agent.js +++ /dev/null @@ -1,97 +0,0 @@ -import {cleanPropertyName, splitPathName} from "./utils"; -import {getInteractionTypeAgent} from "./interaction_type_agent"; -import {getInteractionDetailAgent} from "./interaction_detail_agent"; - -export function processPropertiesAgent(event) { - - event = splitPathName(event) - - // has_alarms_critical - if (typeof event.properties['alarms_critical'] === 'number') { - event.properties['has_alarms_critical'] = event.properties['alarms_critical'] > 0 - } - - // has_alarms_warning - if (typeof event.properties['alarms_warning'] === 'number') { - event.properties['has_alarms_warning'] = event.properties['alarms_warning'] > 0 - } - - // add attribute for each build info flag - if (event.properties['netdata_buildinfo']) { - [...new Set(event.properties['netdata_buildinfo'].split('|'))].forEach((buildInfo) => { - if ((buildInfo !== "") && (buildInfo !== null)){ - event.properties[`netdata_buildinfo_${cleanPropertyName(buildInfo)}`] = true - } - }) - } - - // add attribute for each host collector - if (event.properties['host_collectors']) { - - // only process if not empty - if (event.properties['host_collectors'][0] != null) { - - // make set for both plugins and modules present - let plugins = [...new Set(event.properties['host_collectors'].map(a => a.plugin))]; - let modules = [...new Set(event.properties['host_collectors'].map(a => a.module))]; - - // add flag for each plugin - plugins.forEach((plugin) => { - if ((plugin !== "") && (plugin !== null)){ - event.properties[`host_collector_plugin_${cleanPropertyName(plugin)}`] = true - } - }) - - // add flag for each module - modules.forEach((module) => { - if ((module !== "") && (module !== null)){ - event.properties[`host_collector_module_${cleanPropertyName(module)}`] = true - } - }) - - } - } - - // check if netdata_machine_guid property exists - if (typeof event.properties['netdata_machine_guid'] === 'string') { - // flag if empty string - if (event.properties['netdata_machine_guid']==='') { - event.properties['netdata_machine_guid'] = 'empty' - event.properties['netdata_machine_guid_is_empty'] = true - } else { - event.properties['netdata_machine_guid_is_empty'] = false - } - } - - // check if netdata_machine_guid property exists - if (typeof event.properties['netdata_person_id'] === 'string') { - // flag if empty string - if (event.properties['netdata_person_id']==='') { - event.properties['netdata_person_id'] = 'empty' - event.properties['netdata_person_id_is_empty'] = true - } else { - event.properties['netdata_person_id_is_empty'] = false - } - } - - // check if $distinct_id property exists - if (typeof event.properties['distinct_id'] === 'string') { - // flag if empty string - if (event.properties['distinct_id']==='') { - event.properties['distinct_id'] = 'empty' - event.properties['distinct_id_is_empty'] = true - } else { - event.properties['distinct_id_is_empty'] = false - } - } - - // interaction_type - event.properties['interaction_type'] = getInteractionTypeAgent(event) - event.properties['interaction_detail'] = getInteractionDetailAgent(event) - event.properties['interaction_token'] = event.properties['interaction_type'].concat('|',event.properties['interaction_detail']) - //if (event.event === '$autocapture' && event.properties.hasOwnProperty('interaction_token')) { - // event.event = event.properties['interaction_token'] - //} - - return event -} \ No newline at end of file diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_properties_agent_installer.js b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_properties_agent_installer.js deleted file mode 100644 index dd982dc8cca66..0000000000000 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_properties_agent_installer.js +++ /dev/null @@ -1,22 +0,0 @@ -import {cleanPropertyName} from "./utils"; - -export function processPropertiesAgentInstaller(event) { - - // only process if install_options not empty - if (event.properties['install_options'] != null) { - - // make set for install options - let installOptions = [...new Set((event.properties['install_options'] + ' ').split('--'))]; - - // make flag for each option - installOptions.forEach((installOption) => { - if ((installOption !== "") && (installOption !== null)){ - let installOptionKV = installOption.split(' ') - event.properties[`opt_${cleanPropertyName(installOptionKV[0])}`] = installOptionKV[1] - } - }) - - } - - return event -} \ No newline at end of file diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_properties_blog.js b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_properties_blog.js deleted file mode 100644 index 5044c73781864..0000000000000 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_properties_blog.js +++ /dev/null @@ -1,8 +0,0 @@ -import {splitPathName} from "./utils"; - -export function processPropertiesBlog(event) { - - event = splitPathName(event) - - return event -} diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_properties_cloud.js b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_properties_cloud.js deleted file mode 100644 index 973ec98dc78b1..0000000000000 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_properties_cloud.js +++ /dev/null @@ -1,9 +0,0 @@ -import {splitPathName} from "./utils"; - -export function processPropertiesCloud(event) { - - event = splitPathName(event) - - return event - -} \ No newline at end of file diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_properties_community.js b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_properties_community.js deleted file mode 100644 index bab5443d4682e..0000000000000 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_properties_community.js +++ /dev/null @@ -1,8 +0,0 @@ -import {splitPathName} from "./utils"; - -export function processPropertiesCommunity(event) { - - event = splitPathName(event) - - return event -} \ No newline at end of file diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_properties_learn.js b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_properties_learn.js deleted file mode 100644 index 849a1654c91c2..0000000000000 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_properties_learn.js +++ /dev/null @@ -1,8 +0,0 @@ -import {splitPathName} from "./utils"; - -export function processPropertiesLearn(event) { - - event = splitPathName(event) - - return event -} \ No newline at end of file diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_properties_staging.js b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_properties_staging.js deleted file mode 100644 index 3aab1ea2c407c..0000000000000 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_properties_staging.js +++ /dev/null @@ -1,8 +0,0 @@ -import {splitPathName} from "./utils"; - -export function processPropertiesStaging(event) { - - event = splitPathName(event) - - return event -} \ No newline at end of file diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_properties_testing.js b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_properties_testing.js deleted file mode 100644 index 6bbb964d6600a..0000000000000 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_properties_testing.js +++ /dev/null @@ -1,8 +0,0 @@ -import {splitPathName} from "./utils"; - -export function processPropertiesTesting(event) { - - event = splitPathName(event) - - return event -} \ No newline at end of file diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_properties_website.js b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_properties_website.js deleted file mode 100644 index 28fa9cedee745..0000000000000 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/process_properties_website.js +++ /dev/null @@ -1,8 +0,0 @@ -import {splitPathName} from "./utils"; - -export function processPropertiesWebsite(event) { - - event = splitPathName(event) - - return event -} \ No newline at end of file diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/utils.js b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/utils.js deleted file mode 100644 index b043931a99c84..0000000000000 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/utils.js +++ /dev/null @@ -1,54 +0,0 @@ -export function cleanPropertyName(k) { - return k - // convert to lower case - .toLowerCase() - // remove leading slash - .replace(/^\//, "") - // replace all slashes and dots with _ - .replace(/\/|\.|-| /g, "_") - ; -} - -export function isStringDDMMYYYYHHMM(dt){ - var reDate = /^((0?[1-9]|[12][0-9]|3[01])[- /.](0?[1-9]|1[012])[- /.](19|20)?[0-9]{2}[ ][012][0-9][:][0-9]{2})*$/; - return reDate.test(dt); -} - -export function isDemo(url) { - if ( - (url.includes('://london.my-netdata.io')) - || - (url.includes('://london3.my-netdata.io')) - || - (url.includes('://cdn77.my-netdata.io')) - || - (url.includes('://octopuscs.my-netdata.io')) - || - (url.includes('://bangalore.my-netdata.io')) - || - (url.includes('://frankfurt.my-netdata.io')) - || - (url.includes('://newyork.my-netdata.io')) - || - (url.includes('://sanfrancisco.my-netdata.io')) - || - (url.includes('://singapore.my-netdata.io')) - || - (url.includes('://toronto.my-netdata.io')) - ){ - return true - } else { - return false - } -} - -export function splitPathName(event) { - if (event.properties['$pathname']) { - event.properties["$pathname"].split("/").forEach((pathname, index) => { - if ((pathname !== "") && (pathname !== null)){ - event.properties[`pathname_${index}`] = pathname - } - }) - } - return event -} \ No newline at end of file From 1baa053d81a0e0796ace3e88ed026647be46ce06 Mon Sep 17 00:00:00 2001 From: Ben White Date: Thu, 30 Jan 2025 15:47:27 +0100 Subject: [PATCH 131/163] Fix up logic --- .../plugin-netdata-event-processing/dist.js | 2000 +++++++++++++++++ .../plugin-netdata-event-processing/index.ts | 7 + .../plugin.json | 7 + plugin-server/src/cdp/legacy-plugins/index.ts | 2 + 4 files changed, 2016 insertions(+) create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/dist.js create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/index.ts create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/plugin.json diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/dist.js b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/dist.js new file mode 100644 index 0000000000000..0450214929c79 --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/dist.js @@ -0,0 +1,2000 @@ +function cleanPropertyName(k) { + return ( + k + // convert to lower case + .toLowerCase() + // remove leading slash + .replace(/^\//, '') + // replace all slashes and dots with _ + .replace(/\/|\.|-| /g, '_') + ) +} + +function isStringDDMMYYYYHHMM(dt) { + var reDate = /^((0?[1-9]|[12][0-9]|3[01])[- /.](0?[1-9]|1[012])[- /.](19|20)?[0-9]{2}[ ][012][0-9][:][0-9]{2})*$/ + return reDate.test(dt) +} + +function isDemo(url) { + if ( + url.includes('://london.my-netdata.io') || + url.includes('://london3.my-netdata.io') || + url.includes('://cdn77.my-netdata.io') || + url.includes('://octopuscs.my-netdata.io') || + url.includes('://bangalore.my-netdata.io') || + url.includes('://frankfurt.my-netdata.io') || + url.includes('://newyork.my-netdata.io') || + url.includes('://sanfrancisco.my-netdata.io') || + url.includes('://singapore.my-netdata.io') || + url.includes('://toronto.my-netdata.io') + ) { + return true + } else { + return false + } +} + +function splitPathName(event) { + if (event.properties['$pathname']) { + event.properties['$pathname'].split('/').forEach((pathname, index) => { + if (pathname !== '' && pathname !== null) { + event.properties[`pathname_${index}`] = pathname + } + }) + } + return event +} + +function processElementsAgent(event) { + // extract properties from elements + if (event.properties['$elements']) { + // process each element, last (outermost) first. + event.properties['$elements'].slice().forEach((element) => { + // el_data_testid_outer + if ('attr__data-testid' in element) { + event.properties['el_data_testid_outer'] = element['attr__data-testid'] + + // el_data_testid_outer_0 + if (element['attr__data-testid'].includes('::')) { + arr = element['attr__data-testid'].split('::') + event.properties['el_data_testid_outer_0'] = arr[0] + event.properties['el_data_testid_outer_1'] = arr[1] + event.properties['el_data_testid_outer_2'] = arr[2] + event.properties['el_data_testid_outer_3'] = arr[3] + event.properties['el_data_testid_outer_4'] = arr[4] + } + } + + // el_data_ga_outer + if ('attr__data-ga' in element) { + event.properties['el_data_ga_outer'] = element['attr__data-ga'] + + // el_data_ga_outer_0 + if (element['attr__data-ga'].includes('::')) { + arr = element['attr__data-ga'].split('::') + event.properties['el_data_ga_outer_0'] = arr[0] + event.properties['el_data_ga_outer_1'] = arr[1] + event.properties['el_data_ga_outer_2'] = arr[2] + event.properties['el_data_ga_outer_3'] = arr[3] + event.properties['el_data_ga_outer_4'] = arr[4] + } + } + + // el_data_track_outer + if ('attr__data-track' in element) { + event.properties['el_data_track_outer'] = element['attr__data-track'] + + // el_data_track_outer_0 + if (element['attr__data-track'].includes('::')) { + arr = element['attr__data-track'].split('::') + event.properties['el_data_track_outer_0'] = arr[0] + event.properties['el_data_track_outer_1'] = arr[1] + event.properties['el_data_track_outer_2'] = arr[2] + event.properties['el_data_track_outer_3'] = arr[3] + event.properties['el_data_track_outer_4'] = arr[4] + } + } + }) + + // process each element, reverse to use posthog order as preference + event.properties['$elements'] + .slice() + .reverse() + .forEach((element) => { + // el_data_testid + if ('attr__data-testid' in element) { + event.properties['el_data_testid'] = element['attr__data-testid'] + + // el_data_testid_0 + if (element['attr__data-testid'].includes('::')) { + arr = element['attr__data-testid'].split('::') + event.properties['el_data_testid_0'] = arr[0] + event.properties['el_data_testid_1'] = arr[1] + event.properties['el_data_testid_2'] = arr[2] + event.properties['el_data_testid_3'] = arr[3] + event.properties['el_data_testid_4'] = arr[4] + } + } + + // el_data_ga + if ('attr__data-ga' in element) { + event.properties['el_data_ga'] = element['attr__data-ga'] + + // el_data_ga_0 + if (element['attr__data-ga'].includes('::')) { + arr = element['attr__data-ga'].split('::') + event.properties['el_data_ga_0'] = arr[0] + event.properties['el_data_ga_1'] = arr[1] + event.properties['el_data_ga_2'] = arr[2] + event.properties['el_data_ga_3'] = arr[3] + event.properties['el_data_ga_4'] = arr[4] + } + } + + // el_data_testid_inner + if ('attr__data-testid' in element) { + event.properties['el_data_testid_inner'] = element['attr__data-testid'] + + // el_data_testid_inner_0 + if (element['attr__data-testid'].includes('::')) { + arr = element['attr__data-testid'].split('::') + event.properties['el_data_testid_inner_0'] = arr[0] + event.properties['el_data_testid_inner_1'] = arr[1] + event.properties['el_data_testid_inner_2'] = arr[2] + event.properties['el_data_testid_inner_3'] = arr[3] + event.properties['el_data_testid_inner_4'] = arr[4] + } + } + + // el_data_ga_inner + if ('attr__data-ga' in element) { + event.properties['el_data_ga_inner'] = element['attr__data-ga'] + + // el_data_ga_inner_0 + if (element['attr__data-ga'].includes('::')) { + arr = element['attr__data-ga'].split('::') + event.properties['el_data_ga_inner_0'] = arr[0] + event.properties['el_data_ga_inner_1'] = arr[1] + event.properties['el_data_ga_inner_2'] = arr[2] + event.properties['el_data_ga_inner_3'] = arr[3] + event.properties['el_data_ga_inner_4'] = arr[4] + } + } + + // el_data_track_inner + if ('attr__data-track' in element) { + event.properties['el_data_track_inner'] = element['attr__data-track'] + + // el_data_track_inner_0 + if (element['attr__data-track'].includes('::')) { + arr = element['attr__data-track'].split('::') + event.properties['el_data_track_inner_0'] = arr[0] + event.properties['el_data_track_inner_1'] = arr[1] + event.properties['el_data_track_inner_2'] = arr[2] + event.properties['el_data_track_inner_3'] = arr[3] + event.properties['el_data_track_inner_4'] = arr[4] + } + } + + // el_id_menu + if ( + 'attr__href' in element && + element['attr__href'] !== null && + element['attr__href'].substring(0, 5) === '#menu' + ) { + event.properties['el_href_menu'] = element['attr__href'] + event.properties['el_menu'] = element['attr__href'].split('_submenu')[0].replace('#menu_', '') + if (element['attr__href'].includes('_submenu_')) { + event.properties['el_submenu'] = element['attr__href'].split('_submenu_')[1] + } else { + event.properties['el_submenu'] = '' + } + } + + // el_href + if ('attr__href' in element && element['attr__href'] !== null) { + event.properties['el_href'] = element['attr__href'] + } else if ('href' in element && element['href'] !== null) { + event.properties['el_href'] = element['href'] + } else if ('$href' in element && element['$href'] !== null) { + event.properties['el_href'] = element['$href'] + } + + // el_onclick + if ('attr__onclick' in element && element['attr__onclick'] !== null) { + event.properties['el_onclick'] = element['attr__onclick'] + } + + // el_id + if ('attr__id' in element && element['attr__id'] !== null) { + event.properties['el_id'] = element['attr__id'] + } + + // el_name + if ('attr__name' in element && element['attr__name'] !== null) { + event.properties['el_name'] = element['attr__name'] + } + + // el_title + if ('attr__title' in element && element['attr__title'] !== null) { + event.properties['el_title'] = element['attr__title'] + } + + // el_text + if ('$el_text' in element && element['$el_text'] !== null && element['$el_text'] !== '') { + event.properties['el_text'] = element['$el_text'] + + // el_text_datetime + if (element['$el_text'].includes('/20') && isStringDDMMYYYYHHMM(element['$el_text'])) { + dtStr = element['$el_text'] + dtStrClean = dtStr + .substring(6, 10) + .concat( + '-', + dtStr.substring(3, 5), + '-', + dtStr.substring(0, 2), + ' ', + dtStr.substring(11, 16) + ) + event.properties['el_text_datetime'] = dtStrClean + } + } else if ('text' in element && element['text'] !== null && element['text'] !== '') { + event.properties['el_text'] = element['text'] + } + + // el_data_netdata + if ('attr__data-netdata' in element && element['attr__data-netdata'] !== null) { + event.properties['el_data_netdata'] = element['attr__data-netdata'] + } + + // el_data_target + if ( + 'attr__data-target' in element && + element['attr__data-target'] !== null && + element['attr__data-target'] !== '#sidebar' + ) { + event.properties['el_data_target'] = element['attr__data-target'] + + // el_data_target_updatemodal + if ( + 'attr__data-target' in element && + element['attr__data-target'] !== null && + element['attr__data-target'] === '#updateModal' + ) { + event.properties['el_data_target_updatemodal'] = true + } + } + + // el_data_id + if ('attr__data-id' in element && element['attr__data-id'] !== null) { + event.properties['el_data_id'] = element['attr__data-id'] + } + + // el_data_original_title + if ('attr__data-original-title' in element && element['attr__data-original-title'] !== null) { + event.properties['el_data_original_title'] = element['attr__data-original-title'] + } + + // el_data_toggle + if ('attr__data-toggle' in element && element['attr__data-toggle'] !== null) { + event.properties['el_data_toggle'] = element['attr__data-toggle'] + } + + // el_data-legend-position + if ('attr__data-legend-position' in element && element['attr__data-legend-position'] !== null) { + event.properties['el_data_legend_position'] = element['attr__data-legend-position'] + } + + // el_aria_controls + if ('attr__aria-controls' in element && element['attr__aria-controls'] !== null) { + event.properties['el_aria_controls'] = element['attr__aria-controls'] + } + + // el_aria_labelledby + if ('attr__aria-labelledby' in element && element['attr__aria-labelledby'] !== null) { + event.properties['el_aria_labelledby'] = element['attr__aria-labelledby'] + } + + // el_class_netdata_legend_toolbox + if ( + 'attr__class' in element && + element['attr__class'] !== null && + element['attr__class'] === 'netdata-legend-toolbox' + ) { + event.properties['el_class_netdata_legend_toolbox'] = true + } + + // el_class_fa_play + if ( + 'attr__class' in element && + element['attr__class'] !== null && + element['attr__class'].includes('fa-play') + ) { + event.properties['el_class_fa_play'] = true + } + + // el_class_fa_backward + if ( + 'attr__class' in element && + element['attr__class'] !== null && + element['attr__class'].includes('fa-backward') + ) { + event.properties['el_class_fa_backward'] = true + } + + // el_class_fa_forward + if ( + 'attr__class' in element && + element['attr__class'] !== null && + element['attr__class'].includes('fa-forward') + ) { + event.properties['el_class_fa_forward'] = true + } + + // el_class_fa_plus + if ( + 'attr__class' in element && + element['attr__class'] !== null && + element['attr__class'].includes('fa-plus') + ) { + event.properties['el_class_fa_plus'] = true + } + + // el_class_fa_minus + if ( + 'attr__class' in element && + element['attr__class'] !== null && + element['attr__class'].includes('fa-minus') + ) { + event.properties['el_class_fa_minus'] = true + } + + // el_class_fa_sort + if ( + 'attr__class' in element && + element['attr__class'] !== null && + element['attr__class'].includes('fa-sort') + ) { + event.properties['el_class_fa_sort'] = true + } + + // el_class_navbar_highlight_content + if ( + 'attr__class' in element && + element['attr__class'] !== null && + element['attr__class'].includes('navbar-highlight-content') + ) { + event.properties['el_class_navbar_highlight_content'] = true + } + + // el_class_datepickercontainer + if ( + 'attr__class' in element && + element['attr__class'] !== null && + element['attr__class'].includes('DatePickerContainer') + ) { + event.properties['el_class_datepickercontainer'] = true + } + + // el_class_startendcontainer + if ( + 'attr__class' in element && + element['attr__class'] !== null && + element['attr__class'].includes('StartEndContainer') + ) { + event.properties['el_class_startendcontainer'] = true + } + + // el_class_pickerbtnarea + if ( + 'attr__class' in element && + element['attr__class'] !== null && + element['attr__class'].includes('PickerBtnArea') + ) { + event.properties['el_class_pickerbtnarea'] = true + } + + // el_class_pickerbox + if ( + 'attr__class' in element && + element['attr__class'] !== null && + element['attr__class'].includes('PickerBox') + ) { + event.properties['el_class_pickerbox'] = true + } + + // el_class_collapsablesection + if ( + 'attr__class' in element && + element['attr__class'] !== null && + element['attr__class'].includes('CollapsableSection') + ) { + event.properties['el_class_collapsablesection'] = true + } + + // el_class_signinbutton + if ( + 'attr__class' in element && + element['attr__class'] !== null && + element['attr__class'].includes('SignInButton') + ) { + event.properties['el_class_signinbutton'] = true + } + + // el_class_documentation_container + if ( + 'attr__class' in element && + element['attr__class'] !== null && + element['attr__class'].includes('documentation__Container') + ) { + event.properties['el_class_documentation_container'] = true + } + + // el_class_utilitysection + if ( + 'attr__class' in element && + element['attr__class'] !== null && + element['attr__class'].includes('UtilitySection') + ) { + event.properties['el_class_utilitysection'] = true + } + + // el_class_success + if ( + 'attr__class' in element && + element['attr__class'] !== null && + element['attr__class'].includes('success') + ) { + event.properties['el_class_success'] = true + } + + // el_class_warning + if ( + 'attr__class' in element && + element['attr__class'] !== null && + element['attr__class'].includes('warning') + ) { + event.properties['el_class_warning'] = true + } + + // el_class_danger + if ( + 'attr__class' in element && + element['attr__class'] !== null && + element['attr__class'].includes('danger') + ) { + event.properties['el_class_danger'] = true + } + + // el_class_info + if ('attr__class' in element && element['attr__class'] !== null && element['attr__class'] === 'info') { + event.properties['el_class_info'] = true + } + + // el_class_pagination + if ( + 'attr__class' in element && + element['attr__class'] !== null && + element['attr__class'].includes('pagination') + ) { + event.properties['el_class_pagination'] = true + } + + // el_class_page_number + if ( + 'attr__class' in element && + element['attr__class'] !== null && + element['attr__class'].includes('page-number') + ) { + event.properties['el_class_page_number'] = true + } + + // el_class_export + if ( + 'attr__class' in element && + element['attr__class'] !== null && + element['attr__class'].includes('export') + ) { + event.properties['el_class_export'] = true + } + + // el_class_netdata_chartblock_container + if ( + 'attr__class' in element && + element['attr__class'] !== null && + element['attr__class'].includes('netdata-chartblock-container') + ) { + event.properties['el_class_netdata_chartblock_container'] = true + } + + // el_class_netdata_reset_button + if ( + 'attr__class' in element && + element['attr__class'] !== null && + element['attr__class'].includes('netdata-reset-button') + ) { + event.properties['el_class_netdata_reset_button'] = true + } + + // el_class_netdata_legend_toolbox_button + if ( + 'attr__class' in element && + element['attr__class'] !== null && + element['attr__class'].includes('netdata-legend-toolbox-button') + ) { + event.properties['el_class_netdata_legend_toolbox_button'] = true + } + + // el_class_netdata_legend_resize_handler + if ( + 'attr__class' in element && + element['attr__class'] !== null && + element['attr__class'].includes('netdata-legend-resize-handler') + ) { + event.properties['el_class_netdata_legend_resize_handler'] = true + } + + // el_class_calendarday + if ( + 'attr__class' in element && + element['attr__class'] !== null && + element['attr__class'].includes('CalendarDay') + ) { + event.properties['el_class_calendarday'] = true + } + + // el_class_daypicker + if ( + 'attr__class' in element && + element['attr__class'] !== null && + element['attr__class'].includes('DayPicker') + ) { + event.properties['el_class_daypicker'] = true + } + + // el_class_daterangepicker + if ( + 'attr__class' in element && + element['attr__class'] !== null && + element['attr__class'].includes('DateRangePicker') + ) { + event.properties['el_class_daterangepicker'] = true + } + + // el_id_date_picker_root + if ( + 'attr__id' in element && + element['attr__id'] !== null && + element['attr__id'].includes('date-picker-root') + ) { + event.properties['el_id_date_picker_root'] = true + } + + // el_id_updatemodal + if ( + 'attr__id' in element && + element['attr__id'] !== null && + element['attr__id'].includes('updateModal') + ) { + event.properties['el_id_updatemodal'] = true + } + + // el_class + if ('attr__class' in element && element['attr__class'] !== null) { + event.properties['el_class'] = element['attr__class'] + } + }) + } + + return event +} + +function getInteractionTypeAgent(event) { + if (['$pageview', '$pageleave', '$identify', 'agent backend'].includes(event.event)) { + return event.event.replace('$', '').replace(' ', '_') + + // menu + } else if (event.properties.hasOwnProperty('el_href_menu')) { + return event.properties['el_href_menu'].includes('submenu') ? 'submenu' : 'menu' + + // chart_toolbox + } else if ( + event.properties.hasOwnProperty('el_class_netdata_legend_resize_handler') || + event.properties.hasOwnProperty('el_class_netdata_legend_toolbox') + ) { + return 'chart_toolbox' + + // chart_dim + } else if ( + event.properties.hasOwnProperty('el_data_netdata') && + event.properties.hasOwnProperty('el_id') && + (event.properties.hasOwnProperty('el_text') || event.properties.hasOwnProperty('el_title')) && + event.properties['el_id'].startsWith('chart_') + ) { + return 'chart_dim' + + // date_picker + } else if ( + event.properties['el_id'] === 'date-picker-root' || + (event.properties.hasOwnProperty('el_data_testid') && + event.properties['el_data_testid'].startsWith('date-picker')) || + event.properties.hasOwnProperty('el_class_daterangepicker') + ) { + return 'date_picker' + + // hamburger + } else if ( + event.properties.hasOwnProperty('el_class_collapsablesection') || + event.properties['el_title'] === 'hamburger' + ) { + return 'hamburger' + + // update + } else if ( + event.properties.hasOwnProperty('el_data_target_updatemodal') || + event.properties.hasOwnProperty('el_id_updatemodal') + ) { + return 'update' + + // help + } else if ( + ['Need Help?', 'question'].includes(event.properties['el_title']) || + event.properties['el_data_testid'] === 'documentation-help-close' || + event.properties.hasOwnProperty('el_class_documentation_container') + ) { + return 'help' + + // load_snapshot + } else if ( + event.properties['el_data_target'] === '#loadSnapshotModal' || + event.properties['el_id'] === 'loadSnapshotDragAndDrop' || + event.properties['el_id'] === 'loadSnapshotSelectFiles' || + event.properties['el_id'] === 'loadSnapshotModal' + ) { + return 'load_snapshot' + + // save_snapshot + } else if ( + event.properties['el_data_target'] === '#saveSnapshotModal' || + event.properties['el_id'] === 'saveSnapshotResolutionSlider' || + event.properties['el_id'] === 'saveSnapshotExport' || + event.properties['el_id'] === 'saveSnapshotModal' || + event.properties['el_id'] === 'hiddenDownloadLinks' + ) { + return 'save_snapshot' + + // print + } else if ( + event.properties['el_data_target'] === '#printPreflightModal' || + event.properties['el_onclick'] === 'return printPreflight(),!1' + ) { + return 'print' + + // alarms + } else if ( + event.properties['el_data_target'] === '#alarmsModal' || + ['#alarms_all', '#alarms_log', '#alarms_active'].includes(event.properties['el_href']) || + event.properties['el_id'] === 'alarms_log_table' || + event.properties['el_id'] === 'alarms_log' || + event.properties['el_id'] === 'alarmsModal' || + event.properties['el_aria_labelledby'] === 'alarmsModalLabel' + ) { + return 'alarms' + + // settings + } else if ( + event.properties['el_data_target'] === '#optionsModal' || + event.properties['el_id'] === 'optionsModal' || + event.properties['el_aria_labelledby'] === 'optionsModalLabel' + ) { + return 'settings' + + // cloud + } else if (event.properties.hasOwnProperty('el_class_signinbutton')) { + return 'cloud' + + // highlight + } else if (event.properties['el_id'] === 'navbar-highlight-content') { + return 'highlight' + + // add_charts + } else if (event.properties['el_text'] === 'Add more charts') { + return 'add_charts' + + // add_alarms + } else if (event.properties['el_text'] === 'Add more alarms') { + return 'add_alarms' + } else { + return 'other' + } +} + +function getInteractionDetailAgent(event) { + // menu + if (['menu', 'submenu'].includes(event.properties['interaction_type'])) { + return event.properties['el_href_menu'] + + // chart_toolbox + } else if (event.properties['interaction_type'] === 'chart_toolbox') { + if (event.properties.hasOwnProperty('el_class_fa_minus')) { + return 'zoom_out' + } else if (event.properties.hasOwnProperty('el_class_fa_plus')) { + return 'zoom_in' + } else if (event.properties.hasOwnProperty('el_class_fa_backward')) { + return 'scroll_backward' + } else if (event.properties.hasOwnProperty('el_class_fa_forward')) { + return 'scroll_forward' + } else if (event.properties.hasOwnProperty('el_class_fa_sort')) { + return 'resize' + } else if (event.properties.hasOwnProperty('el_class_fa_play')) { + return 'play' + } else { + return 'other' + } + + // chart_dim + } else if (event.properties['interaction_type'] === 'chart_dim') { + if (event.properties.hasOwnProperty('el_id') && event.properties.hasOwnProperty('el_text')) { + return event.properties['el_data_netdata'].concat('.', event.properties['el_text']) + } else if (event.properties.hasOwnProperty('el_id') && event.properties.hasOwnProperty('el_title')) { + return event.properties['el_data_netdata'].concat('.', event.properties['el_title']) + } else { + return 'other' + } + + // date_picker + } else if (event.properties['interaction_type'] === 'date_picker') { + if (event.properties['el_id'] === 'date-picker-root') { + return 'open' + } else if ( + event.properties.hasOwnProperty('el_data_testid') && + event.properties['el_data_testid'].startsWith('date-picker') + ) { + if (event.properties['el_data_testid'].includes('click-quick-selector')) { + return event.properties['el_data_testid_1'].concat(' ', event.properties['el_data_testid_3']) + } else { + return event.properties['el_data_testid_1'] + } + } else if (event.properties['el_id'] === 'month_right') { + return 'month_right' + } else if (event.properties['el_id'] === 'month_left') { + return 'month_left' + } else if (event.properties.hasOwnProperty('el_class_daterangepicker')) { + return 'date_range' + } else { + return 'other' + } + + // update + } else if (event.properties['interaction_type'] === 'update') { + if (event.properties['el_title'] === 'update') { + return 'open' + } else if (event.properties['el_text'] === 'Check Now') { + return 'check' + } else if (event.properties['el_text'] === 'Close') { + return 'close' + } else { + return 'other' + } + + // highlight + } else if (event.properties['interaction_type'] === 'highlight') { + if (event.properties['el_onclick'] === 'urlOptions.clearHighlight();') { + return 'clear' + } else { + return 'other' + } + + // settings + } else if (event.properties['interaction_type'] === 'settings') { + if (event.properties['el_id'] === 'root') { + return 'open' + } else if (event.properties['el_text'] === 'Close') { + return 'close' + } else if (event.properties['el_data_toggle'] === 'tab') { + return 'tab' + } else if (event.properties['el_data_toggle'] === 'toggle') { + return 'toggle' + } else { + return 'other' + } + + // alarms + } else if (event.properties['interaction_type'] === 'alarms') { + if (event.properties.hasOwnProperty('el_href') && event.properties['el_href'].includes('#alarm_all_')) { + return event.properties['el_text'] + } else if (event.properties.hasOwnProperty('el_class_page_number')) { + return 'page_number' + } else if (event.properties['el_id'] === 'root') { + return 'open' + } else if (event.properties['el_text'] === 'Active' || event.properties['el_id'] === 'alarms_active') { + return 'active' + } else if (event.properties['el_text'] === 'Log') { + return 'log' + } else if (event.properties['el_text'] === 'All') { + return 'all' + } else if (event.properties.hasOwnProperty('el_class_warning') && event.properties.hasOwnProperty('el_text')) { + if (event.properties['el_text'].includes(':') || event.properties['el_text'].includes('%')) { + return 'warn' + } else { + return 'warn__'.concat(event.properties['el_text']) + } + } else if (event.properties.hasOwnProperty('el_class_success') && event.properties.hasOwnProperty('el_text')) { + if (event.properties['el_text'].includes(':') || event.properties['el_text'].includes('%')) { + return 'norm' + } else { + return 'norm__'.concat(event.properties['el_text']) + } + } else if (event.properties.hasOwnProperty('el_class_danger') && event.properties.hasOwnProperty('el_text')) { + if (event.properties['el_text'].includes(':') || event.properties['el_text'].includes('%')) { + return 'crit' + } else { + return 'crit__'.concat(event.properties['el_text']) + } + } else if (event.properties.hasOwnProperty('el_class_info') && event.properties.hasOwnProperty('el_text')) { + if (event.properties['el_text'].includes(':') || event.properties['el_text'].includes('%')) { + return 'undef' + } else { + return 'undef__'.concat(event.properties['el_text']) + } + } else if (event.properties['el_text'] === 'Close' || event.properties['el_text'] === '×') { + return 'close' + } else if (event.properties['el_title'] === 'Refresh' && event.properties['el_id'] === 'alarms_log') { + return 'refresh_log' + } else { + return 'other' + } + + // cloud + } else if (event.properties['interaction_type'] === 'cloud') { + if (event.properties['el_text'] === 'Sign In to Cloud') { + return 'sign_in' + } else { + return 'other' + } + } else { + return '' + } +} + +function processPropertiesAgent(event) { + event = splitPathName(event) + + // has_alarms_critical + if (typeof event.properties['alarms_critical'] === 'number') { + event.properties['has_alarms_critical'] = event.properties['alarms_critical'] > 0 + } + + // has_alarms_warning + if (typeof event.properties['alarms_warning'] === 'number') { + event.properties['has_alarms_warning'] = event.properties['alarms_warning'] > 0 + } + + // add attribute for each build info flag + if (event.properties['netdata_buildinfo']) { + ;[...new Set(event.properties['netdata_buildinfo'].split('|'))].forEach((buildInfo) => { + if (buildInfo !== '' && buildInfo !== null) { + event.properties[`netdata_buildinfo_${cleanPropertyName(buildInfo)}`] = true + } + }) + } + + // add attribute for each host collector + if (event.properties['host_collectors']) { + // only process if not empty + if (event.properties['host_collectors'][0] != null) { + // make set for both plugins and modules present + let plugins = [...new Set(event.properties['host_collectors'].map((a) => a.plugin))] + let modules = [...new Set(event.properties['host_collectors'].map((a) => a.module))] + + // add flag for each plugin + plugins.forEach((plugin) => { + if (plugin !== '' && plugin !== null) { + event.properties[`host_collector_plugin_${cleanPropertyName(plugin)}`] = true + } + }) + + // add flag for each module + modules.forEach((module) => { + if (module !== '' && module !== null) { + event.properties[`host_collector_module_${cleanPropertyName(module)}`] = true + } + }) + } + } + + // check if netdata_machine_guid property exists + if (typeof event.properties['netdata_machine_guid'] === 'string') { + // flag if empty string + if (event.properties['netdata_machine_guid'] === '') { + event.properties['netdata_machine_guid'] = 'empty' + event.properties['netdata_machine_guid_is_empty'] = true + } else { + event.properties['netdata_machine_guid_is_empty'] = false + } + } + + // check if netdata_machine_guid property exists + if (typeof event.properties['netdata_person_id'] === 'string') { + // flag if empty string + if (event.properties['netdata_person_id'] === '') { + event.properties['netdata_person_id'] = 'empty' + event.properties['netdata_person_id_is_empty'] = true + } else { + event.properties['netdata_person_id_is_empty'] = false + } + } + + // check if $distinct_id property exists + if (typeof event.properties['distinct_id'] === 'string') { + // flag if empty string + if (event.properties['distinct_id'] === '') { + event.properties['distinct_id'] = 'empty' + event.properties['distinct_id_is_empty'] = true + } else { + event.properties['distinct_id_is_empty'] = false + } + } + + // interaction_type + event.properties['interaction_type'] = getInteractionTypeAgent(event) + event.properties['interaction_detail'] = getInteractionDetailAgent(event) + event.properties['interaction_token'] = event.properties['interaction_type'].concat( + '|', + event.properties['interaction_detail'] + ) + //if (event.event === '$autocapture' && event.properties.hasOwnProperty('interaction_token')) { + // event.event = event.properties['interaction_token'] + //} + + return event +} + +function processElementsAgentInstaller(event) { + // placeholder for now + + return event +} + +function processPropertiesAgentInstaller(event) { + // only process if install_options not empty + if (event.properties['install_options'] != null) { + // make set for install options + let installOptions = [...new Set((event.properties['install_options'] + ' ').split('--'))] + + // make flag for each option + installOptions.forEach((installOption) => { + if (installOption !== '' && installOption !== null) { + let installOptionKV = installOption.split(' ') + event.properties[`opt_${cleanPropertyName(installOptionKV[0])}`] = installOptionKV[1] + } + }) + } + + return event +} + +function processElementsCloud(event) { + // extract properties from elements + if (event.properties['$elements']) { + // process each element, last (outermost) first. + event.properties['$elements'].slice().forEach((element) => { + // el_data_testid_outer + if ('attr__data-testid' in element) { + event.properties['el_data_testid_outer'] = element['attr__data-testid'] + + // el_data_testid_outer_0 + if (element['attr__data-testid'].includes('::')) { + arr = element['attr__data-testid'].split('::') + event.properties['el_data_testid_outer_0'] = arr[0] + event.properties['el_data_testid_outer_1'] = arr[1] + event.properties['el_data_testid_outer_2'] = arr[2] + event.properties['el_data_testid_outer_3'] = arr[3] + event.properties['el_data_testid_outer_4'] = arr[4] + } + } + + // el_data_ga_outer + if ('attr__data-ga' in element) { + event.properties['el_data_ga_outer'] = element['attr__data-ga'] + + // el_data_ga_outer_0 + if (element['attr__data-ga'].includes('::')) { + arr = element['attr__data-ga'].split('::') + event.properties['el_data_ga_outer_0'] = arr[0] + event.properties['el_data_ga_outer_1'] = arr[1] + event.properties['el_data_ga_outer_2'] = arr[2] + event.properties['el_data_ga_outer_3'] = arr[3] + event.properties['el_data_ga_outer_4'] = arr[4] + } + } + + // el_data_track_outer + if ('attr__data-track' in element) { + event.properties['el_data_track_outer'] = element['attr__data-track'] + + // el_data_track_outer_0 + if (element['attr__data-track'].includes('::')) { + arr = element['attr__data-track'].split('::') + event.properties['el_data_track_outer_0'] = arr[0] + event.properties['el_data_track_outer_1'] = arr[1] + event.properties['el_data_track_outer_2'] = arr[2] + event.properties['el_data_track_outer_3'] = arr[3] + event.properties['el_data_track_outer_4'] = arr[4] + } + } + }) + + // process each element, reverse to use posthog order as preference + event.properties['$elements'] + .slice() + .reverse() + .forEach((element) => { + // el_data_testid + if ('attr__data-testid' in element) { + event.properties['el_data_testid'] = element['attr__data-testid'] + + // el_data_testid_0 + if (element['attr__data-testid'].includes('::')) { + arr = element['attr__data-testid'].split('::') + event.properties['el_data_testid_0'] = arr[0] + event.properties['el_data_testid_1'] = arr[1] + event.properties['el_data_testid_2'] = arr[2] + event.properties['el_data_testid_3'] = arr[3] + event.properties['el_data_testid_4'] = arr[4] + } + } + + // el_data_ga + if ('attr__data-ga' in element) { + event.properties['el_data_ga'] = element['attr__data-ga'] + + // el_data_ga_0 + if (element['attr__data-ga'].includes('::')) { + arr = element['attr__data-ga'].split('::') + event.properties['el_data_ga_0'] = arr[0] + event.properties['el_data_ga_1'] = arr[1] + event.properties['el_data_ga_2'] = arr[2] + event.properties['el_data_ga_3'] = arr[3] + event.properties['el_data_ga_4'] = arr[4] + + // give nice names in posthog + event.properties['event_category'] = arr[0] + event.properties['event_action'] = arr[1] + event.properties['event_label'] = arr[2] + event.properties['event_value'] = arr[3] + } + } + + // el_data_testid_inner + if ('attr__data-testid' in element) { + event.properties['el_data_testid_inner'] = element['attr__data-testid'] + + // el_data_testid_inner_0 + if (element['attr__data-testid'].includes('::')) { + arr = element['attr__data-testid'].split('::') + event.properties['el_data_testid_inner_0'] = arr[0] + event.properties['el_data_testid_inner_1'] = arr[1] + event.properties['el_data_testid_inner_2'] = arr[2] + event.properties['el_data_testid_inner_3'] = arr[3] + event.properties['el_data_testid_inner_4'] = arr[4] + } + } + + // el_data_ga_inner + if ('attr__data-ga' in element) { + event.properties['el_data_ga_inner'] = element['attr__data-ga'] + + // el_data_ga_inner_0 + if (element['attr__data-ga'].includes('::')) { + arr = element['attr__data-ga'].split('::') + event.properties['el_data_ga_inner_0'] = arr[0] + event.properties['el_data_ga_inner_1'] = arr[1] + event.properties['el_data_ga_inner_2'] = arr[2] + event.properties['el_data_ga_inner_3'] = arr[3] + event.properties['el_data_ga_inner_4'] = arr[4] + } + } + + // el_data_track_inner + if ('attr__data-track' in element) { + event.properties['el_data_track_inner'] = element['attr__data-track'] + + // el_data_track_inner_0 + if (element['attr__data-track'].includes('::')) { + arr = element['attr__data-track'].split('::') + event.properties['el_data_track_inner_0'] = arr[0] + event.properties['el_data_track_inner_1'] = arr[1] + event.properties['el_data_track_inner_2'] = arr[2] + event.properties['el_data_track_inner_3'] = arr[3] + event.properties['el_data_track_inner_4'] = arr[4] + } + } + + // el_href + if ('attr__href' in element && element['attr__href'] !== null) { + event.properties['el_href'] = element['attr__href'] + } else if ('href' in element && element['href'] !== null) { + event.properties['el_href'] = element['href'] + } else if ('$href' in element && element['$href'] !== null) { + event.properties['el_href'] = element['$href'] + } + + // el_onclick + if ('attr__onclick' in element && element['attr__onclick'] !== null) { + event.properties['el_onclick'] = element['attr__onclick'] + } + + // el_id + if ('attr__id' in element && element['attr__id'] !== null) { + event.properties['el_id'] = element['attr__id'] + } + + // el_name + if ('attr__name' in element && element['attr__name'] !== null) { + event.properties['el_name'] = element['attr__name'] + } + + // el_title + if ('attr__title' in element && element['attr__title'] !== null) { + event.properties['el_title'] = element['attr__title'] + } + + // el_text + if ('$el_text' in element && element['$el_text'] !== null && element['$el_text'] !== '') { + event.properties['el_text'] = element['$el_text'] + } else if ('text' in element && element['text'] !== null && element['text'] !== '') { + event.properties['el_text'] = element['text'] + } + + // el_data_menuid + if ('attr__data-menuid' in element && element['attr__data-menuid'] !== null) { + event.properties['el_data_menuid'] = element['attr__data-menuid'] + } + + // el_data_submenuid + if ('attr__data-submenuid' in element && element['attr__data-submenuid'] !== null) { + event.properties['el_data_submenuid'] = element['attr__data-submenuid'] + } + + // el_data_chartid + if ('attr__data-chartid' in element && element['attr__data-chartid'] !== null) { + event.properties['el_data_chartid'] = element['attr__data-chartid'] + } + + // el_id_menu + if ( + 'attr__id' in element && + element['attr__id'] !== null && + element['attr__id'].substring(0, 5) === 'menu_' + ) { + event.properties['el_id_menu'] = element['attr__id'] + event.properties['el_menu'] = element['attr__id'].split('_submenu')[0].replace('menu_', '') + if (element['attr__id'].includes('_submenu_')) { + event.properties['el_submenu'] = element['attr__id'].split('_submenu_')[1] + } else { + event.properties['el_submenu'] = '' + } + } + + // el_class + if ('attr__class' in element && element['attr__class'] !== null) { + event.properties['el_class'] = element['attr__class'] + } + }) + } + + return event +} + +function processPropertiesCloud(event) { + event = splitPathName(event) + + return event +} + +function processElementsStaging(event) { + // extract properties from elements + if (event.properties['$elements']) { + // process each element, reverse to use posthog order as preference + event.properties['$elements'] + .slice() + .reverse() + .forEach((element) => { + // el_data_testid + if ('attr__data-testid' in element) { + event.properties['el_data_testid'] = element['attr__data-testid'] + + // el_data_testid_0 + if (element['attr__data-testid'].includes('::')) { + arr = element['attr__data-testid'].split('::') + event.properties['el_data_testid_0'] = arr[0] + event.properties['el_data_testid_1'] = arr[1] + event.properties['el_data_testid_2'] = arr[2] + event.properties['el_data_testid_3'] = arr[3] + event.properties['el_data_testid_4'] = arr[4] + } + } + + // el_data_ga + if ('attr__data-ga' in element) { + event.properties['el_data_ga'] = element['attr__data-ga'] + + // el_data_ga_0 + if (element['attr__data-ga'].includes('::')) { + arr = element['attr__data-ga'].split('::') + event.properties['el_data_ga_0'] = arr[0] + event.properties['el_data_ga_1'] = arr[1] + event.properties['el_data_ga_2'] = arr[2] + event.properties['el_data_ga_3'] = arr[3] + event.properties['el_data_ga_4'] = arr[4] + } + } + + // el_data_track + if ('attr__data-track' in element) { + event.properties['el_data_track'] = element['attr__data-track'] + + // el_data_track_0 + if (element['attr__data-track'].includes('::')) { + arr = element['attr__data-track'].split('::') + event.properties['el_data_track_0'] = arr[0] + event.properties['el_data_track_1'] = arr[1] + event.properties['el_data_track_2'] = arr[2] + event.properties['el_data_track_3'] = arr[3] + event.properties['el_data_track_4'] = arr[4] + } + } + + // el_href + if ('attr__href' in element && element['attr__href'] !== null) { + event.properties['el_href'] = element['attr__href'] + } else if ('href' in element && element['href'] !== null) { + event.properties['el_href'] = element['href'] + } else if ('$href' in element && element['$href'] !== null) { + event.properties['el_href'] = element['$href'] + } + + // el_onclick + if ('attr__onclick' in element && element['attr__onclick'] !== null) { + event.properties['el_onclick'] = element['attr__onclick'] + } + + // el_id + if ('attr__id' in element && element['attr__id'] !== null) { + event.properties['el_id'] = element['attr__id'] + } + + // el_name + if ('attr__name' in element && element['attr__name'] !== null) { + event.properties['el_name'] = element['attr__name'] + } + + // el_title + if ('attr__title' in element && element['attr__title'] !== null) { + event.properties['el_title'] = element['attr__title'] + } + + // el_text + if ('$el_text' in element && element['$el_text'] !== null && element['$el_text'] !== '') { + event.properties['el_text'] = element['$el_text'] + } else if ('text' in element && element['text'] !== null && element['text'] !== '') { + event.properties['el_text'] = element['text'] + } + + // el_data_menuid + if ('attr__data-menuid' in element && element['attr__data-menuid'] !== null) { + event.properties['el_data_menuid'] = element['attr__data-menuid'] + } + + // el_data_submenuid + if ('attr__data-submenuid' in element && element['attr__data-submenuid'] !== null) { + event.properties['el_data_submenuid'] = element['attr__data-submenuid'] + } + + // el_data_chartid + if ('attr__data-chartid' in element && element['attr__data-chartid'] !== null) { + event.properties['el_data_chartid'] = element['attr__data-chartid'] + } + + // el_id_menu + if ( + 'attr__id' in element && + element['attr__id'] !== null && + element['attr__id'].substring(0, 5) === 'menu_' + ) { + event.properties['el_id_menu'] = element['attr__id'] + event.properties['el_menu'] = element['attr__id'].split('_submenu')[0].replace('menu_', '') + if (element['attr__id'].includes('_submenu_')) { + event.properties['el_submenu'] = element['attr__id'].split('_submenu_')[1] + } else { + event.properties['el_submenu'] = '' + } + } + + // el_class + if ('attr__class' in element && element['attr__class'] !== null) { + event.properties['el_class'] = element['attr__class'] + } + }) + } + + return event +} + +function processPropertiesStaging(event) { + event = splitPathName(event) + + return event +} + +function processElementsTesting(event) { + // extract properties from elements + if (event.properties['$elements']) { + // process each element, reverse to use posthog order as preference + event.properties['$elements'] + .slice() + .reverse() + .forEach((element) => { + // el_data_testid + if ('attr__data-testid' in element) { + event.properties['el_data_testid'] = element['attr__data-testid'] + + // el_data_testid_0 + if (element['attr__data-testid'].includes('::')) { + arr = element['attr__data-testid'].split('::') + event.properties['el_data_testid_0'] = arr[0] + event.properties['el_data_testid_1'] = arr[1] + event.properties['el_data_testid_2'] = arr[2] + event.properties['el_data_testid_3'] = arr[3] + event.properties['el_data_testid_4'] = arr[4] + } + } + + // el_data_ga + if ('attr__data-ga' in element) { + event.properties['el_data_ga'] = element['attr__data-ga'] + + // el_data_ga_0 + if (element['attr__data-ga'].includes('::')) { + arr = element['attr__data-ga'].split('::') + event.properties['el_data_ga_0'] = arr[0] + event.properties['el_data_ga_1'] = arr[1] + event.properties['el_data_ga_2'] = arr[2] + event.properties['el_data_ga_3'] = arr[3] + event.properties['el_data_ga_4'] = arr[4] + } + } + + // el_data_track + if ('attr__data-track' in element) { + event.properties['el_data_track'] = element['attr__data-track'] + + // el_data_track_0 + if (element['attr__data-track'].includes('::')) { + arr = element['attr__data-track'].split('::') + event.properties['el_data_track_0'] = arr[0] + event.properties['el_data_track_1'] = arr[1] + event.properties['el_data_track_2'] = arr[2] + event.properties['el_data_track_3'] = arr[3] + event.properties['el_data_track_4'] = arr[4] + } + } + + // el_href + if ('attr__href' in element && element['attr__href'] !== null) { + event.properties['el_href'] = element['attr__href'] + } else if ('href' in element && element['href'] !== null) { + event.properties['el_href'] = element['href'] + } else if ('$href' in element && element['$href'] !== null) { + event.properties['el_href'] = element['$href'] + } + + // el_onclick + if ('attr__onclick' in element && element['attr__onclick'] !== null) { + event.properties['el_onclick'] = element['attr__onclick'] + } + + // el_id + if ('attr__id' in element && element['attr__id'] !== null) { + event.properties['el_id'] = element['attr__id'] + } + + // el_name + if ('attr__name' in element && element['attr__name'] !== null) { + event.properties['el_name'] = element['attr__name'] + } + + // el_title + if ('attr__title' in element && element['attr__title'] !== null) { + event.properties['el_title'] = element['attr__title'] + } + + // el_text + if ('$el_text' in element && element['$el_text'] !== null && element['$el_text'] !== '') { + event.properties['el_text'] = element['$el_text'] + } else if ('text' in element && element['text'] !== null && element['text'] !== '') { + event.properties['el_text'] = element['text'] + } + + // el_data_menuid + if ('attr__data-menuid' in element && element['attr__data-menuid'] !== null) { + event.properties['el_data_menuid'] = element['attr__data-menuid'] + } + + // el_data_submenuid + if ('attr__data-submenuid' in element && element['attr__data-submenuid'] !== null) { + event.properties['el_data_submenuid'] = element['attr__data-submenuid'] + } + + // el_data_chartid + if ('attr__data-chartid' in element && element['attr__data-chartid'] !== null) { + event.properties['el_data_chartid'] = element['attr__data-chartid'] + } + + // el_id_menu + if ( + 'attr__id' in element && + element['attr__id'] !== null && + element['attr__id'].substring(0, 5) === 'menu_' + ) { + event.properties['el_id_menu'] = element['attr__id'] + event.properties['el_menu'] = element['attr__id'].split('_submenu')[0].replace('menu_', '') + if (element['attr__id'].includes('_submenu_')) { + event.properties['el_submenu'] = element['attr__id'].split('_submenu_')[1] + } else { + event.properties['el_submenu'] = '' + } + } + + // el_class + if ('attr__class' in element && element['attr__class'] !== null) { + event.properties['el_class'] = element['attr__class'] + } + }) + } + + return event +} + +function processPropertiesTesting(event) { + event = splitPathName(event) + + return event +} + +function processElementsWebsite(event) { + // extract properties from elements + if (event.properties['$elements']) { + // process each element, reverse to use posthog order as preference + event.properties['$elements'] + .slice() + .reverse() + .forEach((element) => { + // el_data_testid + if ('attr__data-testid' in element) { + event.properties['el_data_testid'] = element['attr__data-testid'] + + // el_data_testid_0 + if (element['attr__data-testid'].includes('::')) { + arr = element['attr__data-testid'].split('::') + event.properties['el_data_testid_0'] = arr[0] + event.properties['el_data_testid_1'] = arr[1] + event.properties['el_data_testid_2'] = arr[2] + event.properties['el_data_testid_3'] = arr[3] + event.properties['el_data_testid_4'] = arr[4] + } + } + + // el_data_ga + if ('attr__data-ga' in element) { + event.properties['el_data_ga'] = element['attr__data-ga'] + + // el_data_ga_0 + if (element['attr__data-ga'].includes('::')) { + arr = element['attr__data-ga'].split('::') + event.properties['el_data_ga_0'] = arr[0] + event.properties['el_data_ga_1'] = arr[1] + event.properties['el_data_ga_2'] = arr[2] + event.properties['el_data_ga_3'] = arr[3] + event.properties['el_data_ga_4'] = arr[4] + } + } + + // el_data_track + if ('attr__data-track' in element) { + event.properties['el_data_track'] = element['attr__data-track'] + + // el_data_track_0 + if (element['attr__data-track'].includes('::')) { + arr = element['attr__data-track'].split('::') + event.properties['el_data_track_0'] = arr[0] + event.properties['el_data_track_1'] = arr[1] + event.properties['el_data_track_2'] = arr[2] + event.properties['el_data_track_3'] = arr[3] + event.properties['el_data_track_4'] = arr[4] + } + } + + // el_href + if ('attr__href' in element && element['attr__href'] !== null) { + event.properties['el_href'] = element['attr__href'] + } else if ('href' in element && element['href'] !== null) { + event.properties['el_href'] = element['href'] + } else if ('$href' in element && element['$href'] !== null) { + event.properties['el_href'] = element['$href'] + } + + // el_onclick + if ('attr__onclick' in element && element['attr__onclick'] !== null) { + event.properties['el_onclick'] = element['attr__onclick'] + } + + // el_id + if ('attr__id' in element && element['attr__id'] !== null) { + event.properties['el_id'] = element['attr__id'] + } + + // el_name + if ('attr__name' in element && element['attr__name'] !== null) { + event.properties['el_name'] = element['attr__name'] + } + + // el_title + if ('attr__title' in element && element['attr__title'] !== null) { + event.properties['el_title'] = element['attr__title'] + } + + // el_text + if ('$el_text' in element && element['$el_text'] !== null && element['$el_text'] !== '') { + event.properties['el_text'] = element['$el_text'] + } else if ('text' in element && element['text'] !== null && element['text'] !== '') { + event.properties['el_text'] = element['text'] + } + + // el_class + if ('attr__class' in element && element['attr__class'] !== null) { + event.properties['el_class'] = element['attr__class'] + } + }) + } + + return event +} + +function processPropertiesWebsite(event) { + event = splitPathName(event) + + return event +} + +function processElementsLearn(event) { + // extract properties from elements + if (event.properties['$elements']) { + // process each element, reverse to use posthog order as preference + event.properties['$elements'] + .slice() + .reverse() + .forEach((element) => { + // el_data_testid + if ('attr__data-testid' in element) { + event.properties['el_data_testid'] = element['attr__data-testid'] + + // el_data_testid_0 + if (element['attr__data-testid'].includes('::')) { + arr = element['attr__data-testid'].split('::') + event.properties['el_data_testid_0'] = arr[0] + event.properties['el_data_testid_1'] = arr[1] + event.properties['el_data_testid_2'] = arr[2] + event.properties['el_data_testid_3'] = arr[3] + event.properties['el_data_testid_4'] = arr[4] + } + } + + // el_data_ga + if ('attr__data-ga' in element) { + event.properties['el_data_ga'] = element['attr__data-ga'] + + // el_data_ga_0 + if (element['attr__data-ga'].includes('::')) { + arr = element['attr__data-ga'].split('::') + event.properties['el_data_ga_0'] = arr[0] + event.properties['el_data_ga_1'] = arr[1] + event.properties['el_data_ga_2'] = arr[2] + event.properties['el_data_ga_3'] = arr[3] + event.properties['el_data_ga_4'] = arr[4] + } + } + + // el_data_track + if ('attr__data-track' in element) { + event.properties['el_data_track'] = element['attr__data-track'] + + // el_data_track_0 + if (element['attr__data-track'].includes('::')) { + arr = element['attr__data-track'].split('::') + event.properties['el_data_track_0'] = arr[0] + event.properties['el_data_track_1'] = arr[1] + event.properties['el_data_track_2'] = arr[2] + event.properties['el_data_track_3'] = arr[3] + event.properties['el_data_track_4'] = arr[4] + } + } + + // el_href + if ('attr__href' in element && element['attr__href'] !== null) { + event.properties['el_href'] = element['attr__href'] + } else if ('href' in element && element['href'] !== null) { + event.properties['el_href'] = element['href'] + } else if ('$href' in element && element['$href'] !== null) { + event.properties['el_href'] = element['$href'] + } + + // el_onclick + if ('attr__onclick' in element && element['attr__onclick'] !== null) { + event.properties['el_onclick'] = element['attr__onclick'] + } + + // el_id + if ('attr__id' in element && element['attr__id'] !== null) { + event.properties['el_id'] = element['attr__id'] + } + + // el_name + if ('attr__name' in element && element['attr__name'] !== null) { + event.properties['el_name'] = element['attr__name'] + } + + // el_title + if ('attr__title' in element && element['attr__title'] !== null) { + event.properties['el_title'] = element['attr__title'] + } + + // el_text + if ('$el_text' in element && element['$el_text'] !== null && element['$el_text'] !== '') { + event.properties['el_text'] = element['$el_text'] + } else if ('text' in element && element['text'] !== null && element['text'] !== '') { + event.properties['el_text'] = element['text'] + } + + // el_class + if ('attr__class' in element && element['attr__class'] !== null) { + event.properties['el_class'] = element['attr__class'] + } + + // el_aria_label + if ( + 'attributes' in element && + element['attributes'] !== null && + 'attr__aria-label' in element['attributes'] + ) { + event.properties['el_aria_label'] = element['attributes']['attr__aria-label'] + } + }) + } + + return event +} + +function processPropertiesLearn(event) { + event = splitPathName(event) + + return event +} + +function processElementsCommunity(event) { + // extract properties from elements + if (event.properties['$elements']) { + // process each element, reverse to use posthog order as preference + event.properties['$elements'] + .slice() + .reverse() + .forEach((element) => { + // el_data_testid + if ('attr__data-testid' in element) { + event.properties['el_data_testid'] = element['attr__data-testid'] + + // el_data_testid_0 + if (element['attr__data-testid'].includes('::')) { + arr = element['attr__data-testid'].split('::') + event.properties['el_data_testid_0'] = arr[0] + event.properties['el_data_testid_1'] = arr[1] + event.properties['el_data_testid_2'] = arr[2] + event.properties['el_data_testid_3'] = arr[3] + event.properties['el_data_testid_4'] = arr[4] + } + } + + // el_data_ga + if ('attr__data-ga' in element) { + event.properties['el_data_ga'] = element['attr__data-ga'] + + // el_data_ga_0 + if (element['attr__data-ga'].includes('::')) { + arr = element['attr__data-ga'].split('::') + event.properties['el_data_ga_0'] = arr[0] + event.properties['el_data_ga_1'] = arr[1] + event.properties['el_data_ga_2'] = arr[2] + event.properties['el_data_ga_3'] = arr[3] + event.properties['el_data_ga_4'] = arr[4] + } + } + + // el_data_track + if ('attr__data-track' in element) { + event.properties['el_data_track'] = element['attr__data-track'] + + // el_data_track_0 + if (element['attr__data-track'].includes('::')) { + arr = element['attr__data-track'].split('::') + event.properties['el_data_track_0'] = arr[0] + event.properties['el_data_track_1'] = arr[1] + event.properties['el_data_track_2'] = arr[2] + event.properties['el_data_track_3'] = arr[3] + event.properties['el_data_track_4'] = arr[4] + } + } + + // el_href + if ('attr__href' in element && element['attr__href'] !== null) { + event.properties['el_href'] = element['attr__href'] + } else if ('href' in element && element['href'] !== null) { + event.properties['el_href'] = element['href'] + } else if ('$href' in element && element['$href'] !== null) { + event.properties['el_href'] = element['$href'] + } + + // el_onclick + if ('attr__onclick' in element && element['attr__onclick'] !== null) { + event.properties['el_onclick'] = element['attr__onclick'] + } + + // el_id + if ('attr__id' in element && element['attr__id'] !== null) { + event.properties['el_id'] = element['attr__id'] + } + + // el_name + if ('attr__name' in element && element['attr__name'] !== null) { + event.properties['el_name'] = element['attr__name'] + } + + // el_title + if ('attr__title' in element && element['attr__title'] !== null) { + event.properties['el_title'] = element['attr__title'] + } + + // el_text + if ('$el_text' in element && element['$el_text'] !== null && element['$el_text'] !== '') { + event.properties['el_text'] = element['$el_text'] + } else if ('text' in element && element['text'] !== null && element['text'] !== '') { + event.properties['el_text'] = element['text'] + } + + // el_class + if ('attr__class' in element && element['attr__class'] !== null) { + event.properties['el_class'] = element['attr__class'] + } + }) + } + + return event +} + +function processPropertiesCommunity(event) { + event = splitPathName(event) + + return event +} + +function processElementsBlog(event) { + // extract properties from elements + if (event.properties['$elements']) { + // process each element, reverse to use posthog order as preference + event.properties['$elements'] + .slice() + .reverse() + .forEach((element) => { + // el_data_testid + if ('attr__data-testid' in element) { + event.properties['el_data_testid'] = element['attr__data-testid'] + + // el_data_testid_0 + if (element['attr__data-testid'].includes('::')) { + arr = element['attr__data-testid'].split('::') + event.properties['el_data_testid_0'] = arr[0] + event.properties['el_data_testid_1'] = arr[1] + event.properties['el_data_testid_2'] = arr[2] + event.properties['el_data_testid_3'] = arr[3] + event.properties['el_data_testid_4'] = arr[4] + } + } + + // el_data_ga + if ('attr__data-ga' in element) { + event.properties['el_data_ga'] = element['attr__data-ga'] + + // el_data_ga_0 + if (element['attr__data-ga'].includes('::')) { + arr = element['attr__data-ga'].split('::') + event.properties['el_data_ga_0'] = arr[0] + event.properties['el_data_ga_1'] = arr[1] + event.properties['el_data_ga_2'] = arr[2] + event.properties['el_data_ga_3'] = arr[3] + event.properties['el_data_ga_4'] = arr[4] + } + } + + // el_data_track + if ('attr__data-track' in element) { + event.properties['el_data_track'] = element['attr__data-track'] + + // el_data_track_0 + if (element['attr__data-track'].includes('::')) { + arr = element['attr__data-track'].split('::') + event.properties['el_data_track_0'] = arr[0] + event.properties['el_data_track_1'] = arr[1] + event.properties['el_data_track_2'] = arr[2] + event.properties['el_data_track_3'] = arr[3] + event.properties['el_data_track_4'] = arr[4] + } + } + + // el_href + if ('attr__href' in element && element['attr__href'] !== null) { + event.properties['el_href'] = element['attr__href'] + } else if ('href' in element && element['href'] !== null) { + event.properties['el_href'] = element['href'] + } else if ('$href' in element && element['$href'] !== null) { + event.properties['el_href'] = element['$href'] + } + + // el_onclick + if ('attr__onclick' in element && element['attr__onclick'] !== null) { + event.properties['el_onclick'] = element['attr__onclick'] + } + + // el_id + if ('attr__id' in element && element['attr__id'] !== null) { + event.properties['el_id'] = element['attr__id'] + } + + // el_name + if ('attr__name' in element && element['attr__name'] !== null) { + event.properties['el_name'] = element['attr__name'] + } + + // el_title + if ('attr__title' in element && element['attr__title'] !== null) { + event.properties['el_title'] = element['attr__title'] + } + + // el_text + if ('$el_text' in element && element['$el_text'] !== null && element['$el_text'] !== '') { + event.properties['el_text'] = element['$el_text'] + } else if ('text' in element && element['text'] !== null && element['text'] !== '') { + event.properties['el_text'] = element['text'] + } + + // el_class + if ('attr__class' in element && element['attr__class'] !== null) { + event.properties['el_class'] = element['attr__class'] + } + + // el_aria_label + if ( + 'attributes' in element && + element['attributes'] !== null && + 'attr__aria-label' in element['attributes'] + ) { + event.properties['el_aria_label'] = element['attributes']['attr__aria-label'] + } + }) + } + + return event +} + +function processPropertiesBlog(event) { + event = splitPathName(event) + + return event +} + +//import URL from 'url'; + +const netdataPluginVersion = '0.0.15' + +function processEvent(event) { + if (event.properties) { + event.properties['event_ph'] = event.event + event.properties['netdata_posthog_plugin_version'] = netdataPluginVersion + + // determine processing based on url + if ('$current_url' in event.properties) { + // try extract specific url params + //if (event.properties['$current_url'].startsWith('http')) { + // const urlParams = new URL(event.properties['$current_url']).searchParams + // if (event.properties['$current_url'].includes('utm_source')) event.properties['url_param_utm_source'] = urlParams.get('utm_source'); + //} + + if ( + ['agent dashboard', 'agent backend'].includes(event.properties['$current_url']) || + isDemo(event.properties['$current_url']) || + event.properties['$current_url'].startsWith('https://netdata.corp.app.netdata.cloud') + ) { + event.properties['event_source'] = 'agent' + event = processElementsAgent(event) + event = processPropertiesAgent(event) + } else if (['agent installer'].includes(event.properties['$current_url'])) { + event.properties['event_source'] = 'agent installer' + event = processElementsAgentInstaller(event) + event = processPropertiesAgentInstaller(event) + } else if (event.properties['$current_url'].startsWith('https://www.netdata.cloud')) { + event.properties['event_source'] = 'website' + event = processElementsWebsite(event) + event = processPropertiesWebsite(event) + } else if (event.properties['$current_url'].includes('netdata-website.netlify.app')) { + event.properties['event_source'] = 'website_preview' + event = processElementsWebsite(event) + event = processPropertiesWebsite(event) + } else if (event.properties['$current_url'].startsWith('https://learn.netdata.cloud')) { + event.properties['event_source'] = 'learn' + event = processElementsLearn(event) + event = processPropertiesLearn(event) + } else if (event.properties['$current_url'].includes('netdata-docusaurus.netlify.app')) { + event.properties['event_source'] = 'learn_preview' + event = processElementsLearn(event) + event = processPropertiesLearn(event) + } else if (event.properties['$current_url'].startsWith('https://blog.netdata.cloud')) { + event.properties['event_source'] = 'blog' + event = processElementsBlog(event) + event = processPropertiesBlog(event) + } else if (event.properties['$current_url'].includes('netdata-blog.netlify.app')) { + event.properties['event_source'] = 'blog_preview' + event = processElementsBlog(event) + event = processPropertiesBlog(event) + } else if (event.properties['$current_url'].startsWith('https://community.netdata.cloud')) { + event.properties['event_source'] = 'community' + event = processElementsCommunity(event) + event = processPropertiesCommunity(event) + } else if (event.properties['$current_url'].startsWith('https://staging.netdata.cloud')) { + event.properties['event_source'] = 'staging' + event = processElementsStaging(event) + event = processPropertiesStaging(event) + } else if (event.properties['$current_url'].startsWith('https://testing.netdata.cloud')) { + event.properties['event_source'] = 'testing' + event = processElementsTesting(event) + event = processPropertiesTesting(event) + } else if ( + event.properties['$current_url'].startsWith('https://app.netdata.cloud') || + event.properties['$current_url'].includes(':19999/spaces/') || + event.properties['$current_url'].includes('/spaces/') + ) { + if (event.properties['$current_url'].startsWith('https://app.netdata.cloud')) { + event.properties['event_source'] = 'cloud' + } else { + event.properties['event_source'] = 'cloud_agent' + } + + event = processElementsCloud(event) + event = processPropertiesCloud(event) + } else { + event.properties['event_source'] = 'unknown' + } + } else if (event.properties['event_ph'] === '$identify') { + event.properties['event_source'] = 'cloud' + event = processElementsCloud(event) + event = processPropertiesCloud(event) + } else { + event.properties['event_source'] = 'unknown' + } + } + + return event +} + +module.exports = { + processEvent, +} diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/index.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/index.ts new file mode 100644 index 0000000000000..19cd7687fe66c --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/index.ts @@ -0,0 +1,7 @@ +import { LegacyTransformationPlugin } from '../../types' +import { processEvent } from './dist' + +export const posthogNetdataEventProcessingPlugin: LegacyTransformationPlugin = { + id: 'posthog-plugin-netdata-event-processing-plugin', + processEvent: processEvent as any, +} diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/plugin.json b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/plugin.json new file mode 100644 index 0000000000000..95a0e5d5f1f8a --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/plugin.json @@ -0,0 +1,7 @@ +{ + "name": "PostHog Netdata Event Processing", + "url": "https://github.com/andrewm4894/posthog-netdata-event-processing", + "description": "A Posthog plugin to do some data processing on our Posthog events as they arrive.", + "main": "index.js", + "config": [] +} diff --git a/plugin-server/src/cdp/legacy-plugins/index.ts b/plugin-server/src/cdp/legacy-plugins/index.ts index 7de4ce7975348..73c6e235afe0a 100644 --- a/plugin-server/src/cdp/legacy-plugins/index.ts +++ b/plugin-server/src/cdp/legacy-plugins/index.ts @@ -5,6 +5,7 @@ import { dropEventsOnPropertyPlugin } from './_transformations/drop-events-on-pr import { flattenPropertiesPlugin } from './_transformations/flatten-properties-plugin' import { languageUrlSplitterApp } from './_transformations/language-url-splitter-app' import { pluginAdvancedGeoip } from './_transformations/plugin-advanced-geoip' +import { posthogNetdataEventProcessingPlugin } from './_transformations/plugin-netdata-event-processing' import { pluginStonlyCleanCampaignName } from './_transformations/Plugin-Stonly-Clean-Campaign-Name' import { pluginStonlyUtmExtractor } from './_transformations/plugin-stonly-UTM-Extractor' import { posthogAppUnduplicator } from './_transformations/posthog-app-unduplicator' @@ -46,4 +47,5 @@ export const DEPRECATED_TRANSFORMATION_PLUGINS_BY_ID = { [posthogAppUnduplicator.id]: posthogAppUnduplicator, [posthogPluginGeoip.id]: posthogPluginGeoip, [posthogRouteCensorPlugin.id]: posthogRouteCensorPlugin, + [posthogNetdataEventProcessingPlugin.id]: posthogNetdataEventProcessingPlugin, } From a1a5856bc71fe3672fade9503762f037846e6736 Mon Sep 17 00:00:00 2001 From: Ben White Date: Thu, 30 Jan 2025 15:59:11 +0100 Subject: [PATCH 132/163] Inlined al old plugins --- .../plugin-advanced-geoip/index.test.ts | 115 +++++---- .../posthog-plugin-geoip/index.test.ts | 243 +++++++++--------- .../posthog-plugin-geoip/index.ts | 2 +- .../src/cdp/templates/test/test-helpers.ts | 40 +-- 4 files changed, 206 insertions(+), 194 deletions(-) diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-advanced-geoip/index.test.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-advanced-geoip/index.test.ts index 82dac6c6de6a3..e63fe6afbef1c 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-advanced-geoip/index.test.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-advanced-geoip/index.test.ts @@ -9,6 +9,10 @@ const defaultMeta = { discardIp: 'true', discardLibs: 'posthog-node', }, + logger: { + log: jest.fn(), + warn: jest.fn(), + }, } const createGeoIPPageview = (): PluginEvent => { @@ -96,59 +100,66 @@ const helperVerifyGeoIPIsEmpty = (event: PluginEvent): void => { expect(event!.properties!.$set_once!.$initial_geoip_country_code).toEqual(undefined) } -describe('discard IP', () => { - test('IP is discarded', () => { - const meta = resetMeta(defaultMeta) as LegacyTransformationPluginMeta - const event = processEvent(createGeoIPPageview(), meta) - expect(event?.ip).toEqual(null) - expect(event?.properties?.$ip).toEqual(undefined) - }) - test('IP is not discarded if not enabled', () => { - const meta = resetMeta({ - config: { ...defaultMeta.config, discardIp: 'false' }, - }) as LegacyTransformationPluginMeta - const event = processEvent(createGeoIPPageview(), meta) - expect(event?.ip).toEqual('13.106.122.3') - expect(event?.properties?.$ip).toEqual('13.106.122.3') - }) - test('IP is not discarded if GeoIP not processed', () => { - const meta = resetMeta() as LegacyTransformationPluginMeta - const preprocessedEvent = createGeoIPPageview() - preprocessedEvent.properties = { ...preprocessedEvent.properties, $plugins_succeeded: ['Unduplicates (8303)'] } - const event = processEvent(preprocessedEvent, meta) - expect(event?.ip).toEqual('13.106.122.3') - expect(event?.properties?.$ip).toEqual('13.106.122.3') - }) -}) - -describe('$lib ignore', () => { - test('ignores GeoIP from $lib', () => { - const meta = resetMeta(defaultMeta) as LegacyTransformationPluginMeta - const event = processEvent(createGeoIPPageview(), meta) - helperVerifyGeoIPIsEmpty(event!) - expect(Object.keys(event!.$set!).length).toEqual(0) // Ensure this is not sending properties even as undefined (that overwrites the user properties) - expect(Object.keys(event!.$set_once!).length).toEqual(0) // Ensure this is not sending properties even as undefined (that overwrites the user properties) - }) - - test('ignores GeoIP from $lib CSV', () => { - const meta = resetMeta({ - config: { ...defaultMeta.config, discardLibs: 'posthog-ios,posthog-android,posthog-node' }, - }) as LegacyTransformationPluginMeta - const event = processEvent(createGeoIPPageview(), meta) - helperVerifyGeoIPIsEmpty(event!) +describe('plugin-advanced-geoip', () => { + describe('discard IP', () => { + test('IP is discarded', () => { + const meta = resetMeta(defaultMeta) as LegacyTransformationPluginMeta + const event = processEvent(createGeoIPPageview(), meta) + expect(event?.ip).toEqual(null) + expect(event?.properties?.$ip).toEqual(undefined) + }) + test('IP is not discarded if not enabled', () => { + const meta = resetMeta({ + ...defaultMeta, + config: { ...defaultMeta.config, discardIp: 'false' }, + }) as LegacyTransformationPluginMeta + const event = processEvent(createGeoIPPageview(), meta) + expect(event?.ip).toEqual('13.106.122.3') + expect(event?.properties?.$ip).toEqual('13.106.122.3') + }) + test('IP is not discarded if GeoIP not processed', () => { + const meta = resetMeta(defaultMeta) as LegacyTransformationPluginMeta + const preprocessedEvent = createGeoIPPageview() + preprocessedEvent.properties = { + ...preprocessedEvent.properties, + $plugins_succeeded: ['Unduplicates (8303)'], + } + const event = processEvent(preprocessedEvent, meta) + expect(event?.ip).toEqual('13.106.122.3') + expect(event?.properties?.$ip).toEqual('13.106.122.3') + }) }) - test('keeps GeoIP if $lib is not on ignore list', () => { - const meta = resetMeta() as LegacyTransformationPluginMeta - const preprocessedEvent = createGeoIPPageview() - preprocessedEvent.properties!.$lib = 'posthog-swift' - const event = processEvent(preprocessedEvent, meta) - expect(event!.$set!.$geoip_city_name).toEqual('Ashburn') - expect(event!.$set!.$geoip_country_name).toEqual('United States') - expect(event!.$set!.$geoip_country_code).toEqual('US') - - expect(event!.$set_once!.$initial_geoip_city_name).toEqual('Ashburn') - expect(event!.$set_once!.$initial_geoip_country_name).toEqual('United States') - expect(event!.$set_once!.$initial_geoip_country_code).toEqual('US') + describe('$lib ignore', () => { + test('ignores GeoIP from $lib', () => { + const meta = resetMeta(defaultMeta) as LegacyTransformationPluginMeta + const event = processEvent(createGeoIPPageview(), meta) + helperVerifyGeoIPIsEmpty(event!) + expect(Object.keys(event!.$set!).length).toEqual(0) // Ensure this is not sending properties even as undefined (that overwrites the user properties) + expect(Object.keys(event!.$set_once!).length).toEqual(0) // Ensure this is not sending properties even as undefined (that overwrites the user properties) + }) + + test('ignores GeoIP from $lib CSV', () => { + const meta = resetMeta({ + ...defaultMeta, + config: { ...defaultMeta.config, discardLibs: 'posthog-ios,posthog-android,posthog-node' }, + }) as LegacyTransformationPluginMeta + const event = processEvent(createGeoIPPageview(), meta) + helperVerifyGeoIPIsEmpty(event!) + }) + + test('keeps GeoIP if $lib is not on ignore list', () => { + const meta = resetMeta(defaultMeta) as LegacyTransformationPluginMeta + const preprocessedEvent = createGeoIPPageview() + preprocessedEvent.properties!.$lib = 'posthog-swift' + const event = processEvent(preprocessedEvent, meta) + expect(event!.$set!.$geoip_city_name).toEqual('Ashburn') + expect(event!.$set!.$geoip_country_name).toEqual('United States') + expect(event!.$set!.$geoip_country_code).toEqual('US') + + expect(event!.$set_once!.$initial_geoip_city_name).toEqual('Ashburn') + expect(event!.$set_once!.$initial_geoip_country_name).toEqual('United States') + expect(event!.$set_once!.$initial_geoip_country_code).toEqual('US') + }) }) }) diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-plugin-geoip/index.test.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-plugin-geoip/index.test.ts index f4189b0745dc4..a21375b7205e2 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-plugin-geoip/index.test.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-plugin-geoip/index.test.ts @@ -1,136 +1,133 @@ -import { City, Reader } from '@maxmind/geoip2-node' +import { City } from '@maxmind/geoip2-node' import { createPageview, resetMeta } from '@posthog/plugin-scaffold/test/utils' -import { join } from 'path' +import { loadTestMMDB } from '../../../templates/test/test-helpers' import { LegacyTransformationPluginMeta } from '../../types' import { processEvent } from './index' -const DEFAULT_MMDB_FILE_NAME = 'GeoLite2-City-Test.mmdb' +describe('posthog-plugin-geoip', () => { + const mmdb = loadTestMMDB() -async function resetMetaWithMmdb( - transformResult = (res: City) => res as Record, - file = DEFAULT_MMDB_FILE_NAME -): Promise { - const mmdb = await Reader.open(join(__dirname, file)) - return resetMeta({ - geoip: { - locate: (ipAddress: string) => { - const res = mmdb.city(ipAddress) - return transformResult(res) + function resetMetaWithMmdb( + transformResult = (res: City) => res as Record + ): LegacyTransformationPluginMeta { + return resetMeta({ + geoip: { + locate: (ipAddress: string) => { + const res = mmdb.city(ipAddress) + return transformResult(res) + }, }, - }, - }) as LegacyTransformationPluginMeta -} + logger: { + log: jest.fn(), + error: jest.fn(), + }, + }) as LegacyTransformationPluginMeta + } -test('event is enriched with IP location', async () => { - const event = processEvent({ ...createPageview(), ip: '89.160.20.129' }, await resetMetaWithMmdb()) - expect(event!.properties).toEqual( - expect.objectContaining({ - $geoip_city_name: 'Linköping', - $geoip_country_name: 'Sweden', - $geoip_country_code: 'SE', - $geoip_continent_name: 'Europe', - $geoip_continent_code: 'EU', - $geoip_latitude: 58.4167, - $geoip_longitude: 15.6167, - $geoip_accuracy_radius: 76, - $geoip_time_zone: 'Europe/Stockholm', - $geoip_subdivision_1_code: 'E', - $geoip_subdivision_1_name: 'Östergötland County', - }) - ) -}) + test('event is enriched with IP location', () => { + const event = processEvent({ ...createPageview(), ip: '89.160.20.129' }, resetMetaWithMmdb()) + expect(event!.properties).toEqual( + expect.objectContaining({ + $geoip_city_name: 'Linköping', + $geoip_country_name: 'Sweden', + $geoip_country_code: 'SE', + $geoip_continent_name: 'Europe', + $geoip_continent_code: 'EU', + $geoip_latitude: 58.4167, + $geoip_longitude: 15.6167, + $geoip_accuracy_radius: 76, + $geoip_time_zone: 'Europe/Stockholm', + $geoip_subdivision_1_code: 'E', + $geoip_subdivision_1_name: 'Östergötland County', + }) + ) + }) -test('person is enriched with IP location', async () => { - const event = processEvent({ ...createPageview(), ip: '89.160.20.129' }, await resetMetaWithMmdb()) - expect(event!.properties!.$set).toEqual( - expect.objectContaining({ - $geoip_city_name: 'Linköping', - $geoip_country_name: 'Sweden', - $geoip_country_code: 'SE', - $geoip_continent_name: 'Europe', - $geoip_continent_code: 'EU', - $geoip_latitude: 58.4167, - $geoip_longitude: 15.6167, - $geoip_time_zone: 'Europe/Stockholm', - $geoip_subdivision_1_code: 'E', - $geoip_subdivision_1_name: 'Östergötland County', - }) - ) - expect(event!.properties!.$set_once).toEqual( - expect.objectContaining({ - $initial_geoip_city_name: 'Linköping', - $initial_geoip_country_name: 'Sweden', - $initial_geoip_country_code: 'SE', - $initial_geoip_continent_name: 'Europe', - $initial_geoip_continent_code: 'EU', - $initial_geoip_latitude: 58.4167, - $initial_geoip_longitude: 15.6167, - $initial_geoip_time_zone: 'Europe/Stockholm', - $initial_geoip_subdivision_1_code: 'E', - $initial_geoip_subdivision_1_name: 'Östergötland County', - }) - ) -}) + test('person is enriched with IP location', () => { + const event = processEvent({ ...createPageview(), ip: '89.160.20.129' }, resetMetaWithMmdb()) + expect(event!.properties!.$set).toEqual( + expect.objectContaining({ + $geoip_city_name: 'Linköping', + $geoip_country_name: 'Sweden', + $geoip_country_code: 'SE', + $geoip_continent_name: 'Europe', + $geoip_continent_code: 'EU', + $geoip_latitude: 58.4167, + $geoip_longitude: 15.6167, + $geoip_time_zone: 'Europe/Stockholm', + $geoip_subdivision_1_code: 'E', + $geoip_subdivision_1_name: 'Östergötland County', + }) + ) + expect(event!.properties!.$set_once).toEqual( + expect.objectContaining({ + $initial_geoip_city_name: 'Linköping', + $initial_geoip_country_name: 'Sweden', + $initial_geoip_country_code: 'SE', + $initial_geoip_continent_name: 'Europe', + $initial_geoip_continent_code: 'EU', + $initial_geoip_latitude: 58.4167, + $initial_geoip_longitude: 15.6167, + $initial_geoip_time_zone: 'Europe/Stockholm', + $initial_geoip_subdivision_1_code: 'E', + $initial_geoip_subdivision_1_name: 'Östergötland County', + }) + ) + }) -test('person props default to null if no values present', async () => { - const removeCityNameFromLookupResult = (res: City) => { - const { city, ...remainingResult } = res - return remainingResult - } - const event = processEvent( - { ...createPageview(), ip: '89.160.20.129' }, - await resetMetaWithMmdb(removeCityNameFromLookupResult) - ) - expect(event!.properties!.$set).toMatchInlineSnapshot(` - Object { - "$geoip_accuracy_radius": 76, - "$geoip_city_confidence": null, - "$geoip_city_name": null, - "$geoip_continent_code": "EU", - "$geoip_continent_name": "Europe", - "$geoip_country_code": "SE", - "$geoip_country_name": "Sweden", - "$geoip_latitude": 58.4167, - "$geoip_longitude": 15.6167, - "$geoip_postal_code": null, - "$geoip_subdivision_1_code": "E", - "$geoip_subdivision_1_name": "Östergötland County", - "$geoip_subdivision_2_code": null, - "$geoip_subdivision_2_name": null, - "$geoip_time_zone": "Europe/Stockholm", + test('person props default to null if no values present', () => { + const removeCityNameFromLookupResult = (res: City) => { + const { city, ...remainingResult } = res + return remainingResult } - `) - expect(event!.properties!.$set_once).toMatchInlineSnapshot(` - Object { - "$initial_geoip_accuracy_radius": 76, - "$initial_geoip_city_confidence": null, - "$initial_geoip_city_name": null, - "$initial_geoip_continent_code": "EU", - "$initial_geoip_continent_name": "Europe", - "$initial_geoip_country_code": "SE", - "$initial_geoip_country_name": "Sweden", - "$initial_geoip_latitude": 58.4167, - "$initial_geoip_longitude": 15.6167, - "$initial_geoip_postal_code": null, - "$initial_geoip_subdivision_1_code": "E", - "$initial_geoip_subdivision_1_name": "Östergötland County", - "$initial_geoip_subdivision_2_code": null, - "$initial_geoip_subdivision_2_name": null, - "$initial_geoip_time_zone": "Europe/Stockholm", - } - `) -}) - -test('error is thrown if meta.geoip is not provided', async () => { - expect.assertions(1) - await expect(async () => processEvent({ ...createPageview(), ip: '89.160.20.129' }, resetMeta())).rejects.toEqual( - new Error('This PostHog version does not have GeoIP capabilities! Upgrade to PostHog 1.24.0 or later') - ) -}) + const event = processEvent( + { ...createPageview(), ip: '89.160.20.129' }, + resetMetaWithMmdb(removeCityNameFromLookupResult) + ) + expect(event!.properties!.$set).toMatchInlineSnapshot(` + { + "$geoip_accuracy_radius": 76, + "$geoip_city_confidence": null, + "$geoip_city_name": null, + "$geoip_continent_code": "EU", + "$geoip_continent_name": "Europe", + "$geoip_country_code": "SE", + "$geoip_country_name": "Sweden", + "$geoip_latitude": 58.4167, + "$geoip_longitude": 15.6167, + "$geoip_postal_code": null, + "$geoip_subdivision_1_code": "E", + "$geoip_subdivision_1_name": "Östergötland County", + "$geoip_subdivision_2_code": null, + "$geoip_subdivision_2_name": null, + "$geoip_time_zone": "Europe/Stockholm", + } + `) + expect(event!.properties!.$set_once).toMatchInlineSnapshot(` + { + "$initial_geoip_accuracy_radius": 76, + "$initial_geoip_city_confidence": null, + "$initial_geoip_city_name": null, + "$initial_geoip_continent_code": "EU", + "$initial_geoip_continent_name": "Europe", + "$initial_geoip_country_code": "SE", + "$initial_geoip_country_name": "Sweden", + "$initial_geoip_latitude": 58.4167, + "$initial_geoip_longitude": 15.6167, + "$initial_geoip_postal_code": null, + "$initial_geoip_subdivision_1_code": "E", + "$initial_geoip_subdivision_1_name": "Östergötland County", + "$initial_geoip_subdivision_2_code": null, + "$initial_geoip_subdivision_2_name": null, + "$initial_geoip_time_zone": "Europe/Stockholm", + } + `) + }) -test('event is skipped using $geoip_disable', async () => { - const testEvent = { ...createPageview(), ip: '89.160.20.129', properties: { $geoip_disable: true } } - const processedEvent = processEvent(JSON.parse(JSON.stringify(testEvent)), await resetMetaWithMmdb()) - expect(testEvent).toEqual(processedEvent) + test('event is skipped using $geoip_disable', () => { + const testEvent = { ...createPageview(), ip: '89.160.20.129', properties: { $geoip_disable: true } } + const processedEvent = processEvent(JSON.parse(JSON.stringify(testEvent)), resetMetaWithMmdb()) + expect(testEvent).toEqual(processedEvent) + }) }) diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-plugin-geoip/index.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-plugin-geoip/index.ts index 6dc6cbb87b9c0..c95698d542ee3 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-plugin-geoip/index.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-plugin-geoip/index.ts @@ -109,8 +109,8 @@ export const processEvent = (event: PluginEvent, { geoip }: LegacyTransformation event.properties.$set_once![`$initial_geoip_${key}`] = value } } - return event } + return event } export const posthogPluginGeoip: LegacyTransformationPlugin = { diff --git a/plugin-server/src/cdp/templates/test/test-helpers.ts b/plugin-server/src/cdp/templates/test/test-helpers.ts index 5af94348706d0..2abc2265cceba 100644 --- a/plugin-server/src/cdp/templates/test/test-helpers.ts +++ b/plugin-server/src/cdp/templates/test/test-helpers.ts @@ -23,6 +23,18 @@ export type DeepPartialHogFunctionInvocationGlobals = { source?: Partial } +export function loadTestMMDB() { + try { + const mmdbBrotliContents = readFileSync(join(__dirname, '../../../../tests/assets/GeoLite2-City-Test.mmdb.br')) + const mmdbBuffer = brotliDecompressSync(mmdbBrotliContents) + const mmdb = Reader.openBuffer(mmdbBuffer) + + return mmdb + } catch (error) { + throw new Error(`Failed to load MMDB file: ${error}`) + } +} + export class TemplateTester { public template: HogFunctionTemplateCompiled private executor: HogExecutorService @@ -55,24 +67,16 @@ export class TemplateTester { this.mockHub = { mmdb: undefined } as any if (!skipMMDB) { - try { - const mmdbBrotliContents = readFileSync( - join(__dirname, '../../../../tests/assets/GeoLite2-City-Test.mmdb.br') - ) - const mmdbBuffer = brotliDecompressSync(mmdbBrotliContents) - const mmdb = Reader.openBuffer(mmdbBuffer) - - this.mockHub.mmdb = transformResult - ? ({ - city: (ipAddress: string) => { - const res = mmdb.city(ipAddress) - return transformResult(res) - }, - } as unknown as ReaderModel) - : mmdb - } catch (error) { - throw new Error(`Failed to load MMDB file: ${error}`) - } + const mmdb = loadTestMMDB() + + this.mockHub.mmdb = transformResult + ? ({ + city: (ipAddress: string) => { + const res = mmdb.city(ipAddress) + return transformResult(res) + }, + } as unknown as ReaderModel) + : mmdb } const mockHogFunctionManager = {} as any From 9a16d053a84e429933fbc770a7fca5e21dd7eb9c Mon Sep 17 00:00:00 2001 From: Ben White Date: Thu, 30 Jan 2025 16:30:18 +0100 Subject: [PATCH 133/163] Fixes --- .../ph-shotgun-processevent-app/index.test.ts | 109 + .../ph-shotgun-processevent-app/index.ts | 193 + .../ph-shotgun-processevent-app/plugin.json | 12 + .../ph-shotgun-processevent-app/template.ts | 13 + .../index.test.ts | 187 + .../index.ts | 3188 +++++++++++++++++ .../plugin.json | 16 + .../template.ts | 23 + plugin-server/src/cdp/legacy-plugins/index.ts | 4 + plugin-server/src/cdp/legacy-plugins/types.ts | 8 +- 10 files changed, 3752 insertions(+), 1 deletion(-) create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/ph-shotgun-processevent-app/index.test.ts create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/ph-shotgun-processevent-app/index.ts create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/ph-shotgun-processevent-app/plugin.json create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/ph-shotgun-processevent-app/template.ts create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/posthog-plugin-snowplow-referer-parser/index.test.ts create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/posthog-plugin-snowplow-referer-parser/index.ts create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/posthog-plugin-snowplow-referer-parser/plugin.json create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/posthog-plugin-snowplow-referer-parser/template.ts diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/ph-shotgun-processevent-app/index.test.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/ph-shotgun-processevent-app/index.test.ts new file mode 100644 index 0000000000000..ce4430d79d016 --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/ph-shotgun-processevent-app/index.test.ts @@ -0,0 +1,109 @@ +import { PluginEvent, PluginInput, PluginMeta } from '@posthog/plugin-scaffold' + +import { processEvent } from './index' + +/** + * Given a url, construct a page view event. + * + * @param $current_url The current url of the page view + * @returns A new PostHog page view event + */ +function buildEventWithName(eventName: string): PluginEvent { + const event: PluginEvent = { + distinct_id: 'distinct_id', + ip: '1.2.3.4', + site_url: 'test.com', + team_id: 0, + now: '2022-06-17T20:21:31.778000+00:00', + event: eventName, + uuid: '01817354-06bb-0000-d31c-2c4eed374100', + } + + return event +} + +function getMeta(): PluginMeta { + return {} as unknown as PluginMeta +} + +describe('ph-shotgun-processevent-app', () => { + it('should rename the event if in the list', () => { + const sourceEvent = buildEventWithName('Community Management Dashboard Displayed') + + const processedEvent = processEvent(sourceEvent, getMeta()) + + expect(processedEvent?.event).toEqual('Analytics Community Overview Tab Displayed') + }) + + it('should rename the event if in the list', () => { + const sourceEvent = buildEventWithName('Community Management Insights Overview Tab Displayed') + + const processedEvent = processEvent(sourceEvent, getMeta()) + + expect(processedEvent?.event).toEqual('Analytics Community Overview Tab Displayed') + }) + + it('should rename the event if in the list', () => { + const sourceEvent = buildEventWithName('Community Management Contacts Imported') + + const processedEvent = processEvent(sourceEvent, getMeta()) + + expect(processedEvent?.event).toEqual('Marketing Contacts Imported') + }) + + it('should rename the event if in the list', () => { + const sourceEvent = buildEventWithName('Marketing Organizer Page Tab Displayed') + + const processedEvent = processEvent(sourceEvent, getMeta()) + + expect(processedEvent?.event).toEqual('My Page Tab Displayed') + }) + + it('should rename the event if in the list', () => { + const sourceEvent = buildEventWithName('Settings') + + const processedEvent = processEvent(sourceEvent, getMeta()) + + expect(processedEvent?.event).toEqual('Profile Screen Settings Tapped') + }) + + it('should rename the event if in the list', () => { + const sourceEvent = buildEventWithName('Log Out') + + const processedEvent = processEvent(sourceEvent, getMeta()) + + expect(processedEvent?.event).toEqual('Logged Out') + }) + + it('should rename the event if in the list', () => { + const sourceEvent = buildEventWithName('Music Library Sync Completed') + + const processedEvent = processEvent(sourceEvent, getMeta()) + + expect(processedEvent?.event).toEqual('Music Library Sync Completed') + }) + + it('should rename the event if in the list', () => { + const sourceEvent = buildEventWithName('Non Existing Event') + + const processedEvent = processEvent(sourceEvent, getMeta()) + + expect(processedEvent?.event).toEqual('Non Existing Event') + }) + + it('should rename the event if in the list', () => { + const sourceEvent = buildEventWithName('KYB Completed') + + const processedEvent = processEvent(sourceEvent, getMeta()) + + expect(processedEvent?.event).toEqual('KYB Completed') + }) + + it('should rename the event if in the list', () => { + const sourceEvent = buildEventWithName('Reward Score Explanation Sheet Displayed') + + const processedEvent = processEvent(sourceEvent, getMeta()) + + expect(processedEvent?.event).toEqual('Reward Score Explanation Sheet Displayed') + }) +}) diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/ph-shotgun-processevent-app/index.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/ph-shotgun-processevent-app/index.ts new file mode 100644 index 0000000000000..3dbee9af91a42 --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/ph-shotgun-processevent-app/index.ts @@ -0,0 +1,193 @@ +import { PluginEvent } from '@posthog/plugin-scaffold' + +import { LegacyPluginMeta, LegacyTransformationPlugin } from '../../types' + +export function processEvent(event: PluginEvent, _: LegacyPluginMeta) { + switch (event.event) { + case 'Community Management Dashboard Displayed': + case 'Community Management Insights Overview Tab Displayed': + event.event = 'Analytics Community Overview Tab Displayed' + break + case 'Community Management Insights Sociodemographic Tab Displayed': + event.event = 'Analytics Community Sociodemographic Tab Displayed' + break + case 'Community Management Insights Purchase Behavior Tab Displayed': + event.event = 'Analytics Community Purchase Behavior Tab Displayed' + break + case 'Community Management Insights Music Tastes Tab Displayed': + event.event = 'Analytics Community Musical Tastes Tab Displayed' + break + case 'Community Management Contact Filter Button Tapped': + event.event = 'Marketing Contacts Filters Button Tapped' + break + case 'Community Management Contact Filters Applied': + event.event = 'Marketing Contacts Filters Applied' + break + case 'Community Management Contacts Exported': + event.event = 'Marketing Contacts Exported' + break + case 'Community Management Contacts Imported': + event.event = 'Marketing Contacts Imported' + break + case 'Community Management Contacts Tab Displayed': + event.event = 'Marketing Contacts Tab Displayed' + break + case 'Community Management Segment CSV Export Button Tapped': + event.event = 'Marketing Segment Exported' + break + case 'Community Management Segment Created': + event.event = 'Marketing Segment Created' + break + case 'Community Management Segment Displayed': + event.event = 'Marketing Segment Displayed' + break + case 'Community Management Segments List Displayed': + event.event = 'Marketing Segments Tab Displayed' + break + case 'Community Management Organizer Page Form Displayed': + case 'Marketing Organizer Page Tab Displayed': + case 'Marketing Organizer Widget Tab Displayed': + event.event = 'My Page Tab Displayed' + break + case 'Organizer Page Updated': + event.event = 'My Page Edited' + break + case 'App Open': + event.event = 'App Foregrounded' + break + case 'Open Website': + event.event = 'Website Opened' + break + case 'Signup': + case 'SignupGuest': + event.event = 'Sign Up Completed' + break + case 'Log In': + event.event = 'Log In Completed' + break + case 'Tap Areas': + event.event = 'Home Screen Area Tapped' + break + case 'Switch Area': + event.event = 'Area Switched' + break + case 'Search': + event.event = 'Content Searched' + break + case 'Tap Event': + event.event = 'Event Tapped' + break + case 'Event Page Viewed': + event.event = 'Event Page Displayed' + break + case 'Organizer Page Viewed': + case 'Organizer Screen Displayed': + event.event = 'Organizer Page Displayed' + break + case 'Artist Page Viewed': + case 'Artist Screen Displayed': + event.event = 'Artist Page Displayed' + break + case 'Shotgun': + event.event = 'Event Page Available Tickets Tapped' + break + case 'Event Screen Interest Button Tapped': + event.event = 'Event Interest Activated' + break + case 'Event Screen Uninterest Button Tapped': + event.event = 'Event Interest Deactivated' + break + case 'Share Event': + case 'Send Event': + event.event = 'Event Shared' + break + case 'Add Ticket To Basket': + event.event = 'Basket Item Added' + break + case 'Tap Coupon': + event.event = 'Basket Add Coupon Tapped' + break + case 'Add Coupon': + event.event = 'Coupon Added' + break + case 'Remove Ticket From Basket': + event.event = 'Basket Item Removed' + break + case 'Validate Basket': + case 'Pressed Order': + event.event = 'Basket Validated' + break + case 'Purchase': + event.event = 'Payment Validated' + break + case 'Get Printed Tickets': + case 'Download Ticket': + event.event = 'Order Confirmation Download Tickets Button Tapped' + break + case 'Show User Event Tickets': + event.event = 'Ticket List Screen Displayed' + break + case 'Resell Ticket': + event.event = 'Ticket Screen Resell Tapped' + break + case 'WaitingListDisplayed': + event.event = 'Waiting List Displayed' + break + case 'Transfer Ticket': + event.event = 'Ticket Screen Transfer Tapped' + break + case 'Select Resell Mode': + event.event = 'Resell Ticket Screen Mode Selected' + break + case 'Confirm Resell Ticket': + event.event = 'Ticket Resell Validated' + break + case 'Select Receiver': + event.event = 'Transfer Screen Receiver Selected' + break + case 'Tap Newsfeed': + event.event = 'Notification Center Tapped' + break + case 'Tap Newsfeed Item': + event.event = 'Notification Center Item Tapped' + break + case 'Tap Chloe': + case 'Help Tapped': + event.event = 'Contact Support Clicked' + break + case 'Help CTA Clicked': + event.event = 'Contact Support Clicked' + break + case 'Music Library Sync BottomSheet Displayed': + event.event = 'Music Library Sync Form Displayed' + break + case 'Music Library Sync Streaming Account Tapped': + event.event = 'Music Library Sync Form Sync Button Tapped' + break + case 'Music Library Sync Success': + event.event = 'Music Library Sync Completed' + break + case 'Settings': + case 'Settings Screen Displayed': + event.event = 'Profile Screen Settings Tapped' + break + case 'Tap Score': + event.event = 'Profile Screen Score Tapped' + break + case 'Add Payment Solution': + event.event = 'Payment Methods Screen Add Method Tapped' + break + case 'Log Out': + event.event = 'Logged Out' + break + default: + break + } + + return event +} + +export const phShotgunProcessEventApp: LegacyTransformationPlugin = { + id: 'ph-shotgun-processevent-app', + processEvent, +} diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/ph-shotgun-processevent-app/plugin.json b/plugin-server/src/cdp/legacy-plugins/_transformations/ph-shotgun-processevent-app/plugin.json new file mode 100644 index 0000000000000..f605379af3296 --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/ph-shotgun-processevent-app/plugin.json @@ -0,0 +1,12 @@ +{ + "name": "ph-shotgun-processevent-app", + "url": "https://github.com/lamothearthur/ph-shotgun-processevent-app", + "description": "A custom app to rename specific event names during the migration of Shotgun events from US to EU", + "main": "index.ts", + "posthogVersion": ">= 1.25.0", + "config": [ + { + "markdown": "Process and modify specific event properties as they are ingested by PostHog" + } + ] +} diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/ph-shotgun-processevent-app/template.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/ph-shotgun-processevent-app/template.ts new file mode 100644 index 0000000000000..6205f0a8f488c --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/ph-shotgun-processevent-app/template.ts @@ -0,0 +1,13 @@ +import { HogFunctionTemplate } from '~/src/cdp/templates/types' + +export const template: HogFunctionTemplate = { + status: 'alpha', + type: 'transformation', + id: 'plugin-ph-shotgun-processevent-app', + name: 'Shotgun Process Event App', + description: 'Process Shotgun events', + icon_url: '/static/hedgehog/builder-hog-01.png', + category: ['Transformation'], + hog: `return event`, + inputs_schema: [], +} diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-plugin-snowplow-referer-parser/index.test.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-plugin-snowplow-referer-parser/index.test.ts new file mode 100644 index 0000000000000..bb2d81c6f3ab6 --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-plugin-snowplow-referer-parser/index.test.ts @@ -0,0 +1,187 @@ +import { processEvent } from "."; + +const demoEvents = [ + { + event: "$pageview", + properties: { + $os: "Linux", + $browser: "Safari", + $device_type: "Desktop", + $current_url: + "https://office.getjoan.com/device/shareable/1009dea9-5f95-4896-95ec-a12d3397b080", + $host: "office.getjoan.com", + $pathname: "/device/shareable/1009dea9-5f95-4896-95ec-a12d3397b080", + $browser_version: 16, + $screen_height: 1080, + $screen_width: 1920, + $viewport_height: 1080, + $viewport_width: 1920, + $lib: "web", + $lib_version: "1.30.0", + $insert_id: "cp1wsa5ddrwuittb", + $time: 1671459506.901, + distinct_id: "18528f8e529264-066928753d330c8-c6a5a43-fa00-18528f8e52bb52", + $device_id: "18528f8e529264-066928753d330c8-c6a5a43-fa00-18528f8e52bb52", + $referrer: "$direct", + $referring_domain: "$direct", + $active_feature_flags: [], + token: "phc_gE7SWBNBgFbA4eQ154KPXebyB8KyLJuypR8jg1DSo9Z", + $session_id: "18528f8e5361b6d-07f85cfe92fe198-c6a5a43-fa00-18528f8e53714dd", + $window_id: "1852ac00ad0a8d-03577d57d3a7a08-c6a5a43-1fa400-1852ac00ad159b", + $set_once: { + $initial_os: "Linux", + $initial_browser: "Safari", + $initial_device_type: "Desktop", + $initial_current_url: + "https://office.getjoan.com/device/shareable/1009dea9-5f95-4896-95ec-a12d3397b080", + $initial_pathname: + "/device/shareable/1009dea9-5f95-4896-95ec-a12d3397b080", + $initial_browser_version: 16, + $initial_referrer: "$direct", + $initial_referring_domain: "$direct", + }, + $geoip_city_name: "Brussels", + $geoip_country_name: "Belgium", + $geoip_country_code: "BE", + $geoip_continent_name: "Europe", + $geoip_continent_code: "EU", + $geoip_postal_code: "1000", + $geoip_latitude: 50.8534, + $geoip_longitude: 4.347, + $geoip_time_zone: "Europe/Brussels", + $geoip_subdivision_1_code: "BRU", + $geoip_subdivision_1_name: "Brussels Capital", + }, + timestamp: "2022-12-19T14:18:26.902Z", + uuid: "01852ac0-0e96-0000-b3b1-d6c1b135c103", + distinct_id: "18528f8e529264-066928753d330c8-c6a5a43-fa00-18528f8e52bb52", + ip: "84.198.172.247", + site_url: "https://appdata.vnct.xyz", + team_id: 2, + now: "2022-12-19T14:18:27.859614+00:00", + sent_at: "2022-12-19T14:18:26.905000+00:00", + token: "phc_gE7SWBNBgFbA4eQ154KPXebyB8KyLJuypR8jg1DSo9Z", + }, + { + event: "SOME/custom_event", + properties: { + $os: "Linux", + $browser: "Safari", + $device_type: "Desktop", + $current_url: + "https://office.getjoan.com/device/shareable/1009dea9-5f95-4896-95ec-a12d3397b080", + $host: "office.getjoan.com", + $pathname: "/device/shareable/1009dea9-5f95-4896-95ec-a12d3397b080", + $browser_version: 16, + $screen_height: 1080, + $screen_width: 1920, + $viewport_height: 1080, + $viewport_width: 1920, + $lib: "web", + $lib_version: "1.30.0", + $insert_id: "cp1wsa5ddrwuittb", + $time: 1671459506.901, + distinct_id: "18528f8e529264-066928753d330c8-c6a5a43-fa00-18528f8e52bb52", + $device_id: "18528f8e529264-066928753d330c8-c6a5a43-fa00-18528f8e52bb52", + $referrer: "$direct", + $referring_domain: "$direct", + $active_feature_flags: [], + token: "phc_gE7SWBNBgFbA4eQ154KPXebyB8KyLJuypR8jg1DSo9Z", + $session_id: "18528f8e5361b6d-07f85cfe92fe198-c6a5a43-fa00-18528f8e53714dd", + $window_id: "1852ac00ad0a8d-03577d57d3a7a08-c6a5a43-1fa400-1852ac00ad159b", + $set_once: { + $initial_os: "Linux", + $initial_browser: "Safari", + $initial_device_type: "Desktop", + $initial_current_url: + "https://office.getjoan.com/device/shareable/1009dea9-5f95-4896-95ec-a12d3397b080", + $initial_pathname: + "/device/shareable/1009dea9-5f95-4896-95ec-a12d3397b080", + $initial_browser_version: 16, + $initial_referrer: "$direct", + $initial_referring_domain: "$direct", + }, + $geoip_city_name: "Brussels", + $geoip_country_name: "Belgium", + $geoip_country_code: "BE", + $geoip_continent_name: "Europe", + $geoip_continent_code: "EU", + $geoip_postal_code: "1000", + $geoip_latitude: 50.8534, + $geoip_longitude: 4.347, + $geoip_time_zone: "Europe/Brussels", + $geoip_subdivision_1_code: "BRU", + $geoip_subdivision_1_name: "Brussels Capital", + }, + timestamp: "2022-12-19T14:18:26.902Z", + uuid: "01852ac0-0e96-0000-b3b1-d6c1b135c103", + distinct_id: "18528f8e529264-066928753d330c8-c6a5a43-fa00-18528f8e52bb52", + ip: "84.198.172.247", + site_url: "https://appdata.vnct.xyz", + team_id: 2, + now: "2022-12-19T14:18:27.859614+00:00", + sent_at: "2022-12-19T14:18:26.905000+00:00", + token: "phc_gE7SWBNBgFbA4eQ154KPXebyB8KyLJuypR8jg1DSo9Z", + } +] + +const demoMeta = { + cache: {}, + config: { name: "world" }, + attachments: {}, + storage: {}, + geoip: {}, + jobs: {}, + utils: { cursor: {} }, + global: {}, +} + + +test("test processEvent translates utm links correctly revised", () => { + const utmURL = + "https://www.reddit.com/r/blender/comments/zmomxw/stable_diffusion_can_texture_your_entire_scene/?utm_source=share&utm_medium=android_app&utm_name=androidcss&utm_term=2&utm_content=share_button" + + + demoEvents.forEach(demoEvent => { + const utmEvent = { + ...demoEvent, + properties: { ...demoEvent.properties, $current_url: utmURL }, + } + + const processedUTMEvent = processEvent(utmEvent) + + expect(processedUTMEvent.properties?.referrer_parser).toBe("utm") + + const googleURL = `https://www.google.com/search?q='joan+6+pro'` + + const referrerEvent = { + ...demoEvent, + properties: { + ...demoEvent.properties, + $referrer: googleURL, + $referring_domain: "google.com", + }, + } + + const processedReferrerEvent = processEvent(referrerEvent) + + expect(processedReferrerEvent.properties?.referrer_parser).toBe("snowplow") + + const joanURL = `https://office.getjoan.com/settings'` + + const directEvent = { + ...demoEvent, + properties: { + ...demoEvent.properties, + $referrer: joanURL, + $referring_domain: "office.getjoan.com", + }, + } + + const processedDirectEvent = processEvent(directEvent) + + expect(processedDirectEvent.properties?.referrer_parser).toBe( + "direct_and_own_domains", + ) + }) +}) diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-plugin-snowplow-referer-parser/index.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-plugin-snowplow-referer-parser/index.ts new file mode 100644 index 0000000000000..aab08585b55de --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-plugin-snowplow-referer-parser/index.ts @@ -0,0 +1,3188 @@ +import { PluginEvent } from '@posthog/plugin-scaffold' + +import { LegacyTransformationPlugin, LegacyTransformationPluginMeta } from '../../types' + +export function processEvent(event: PluginEvent, { logger }: LegacyTransformationPluginMeta) { + const props = event.properties + + if (typeof props === 'undefined') { + return event + } + + logger.debug( + `(Event: ${event.event}) URL: ${props.$current_url}, Referrer: ${props.$referrer}, Referring domain: ${props.$referring_domain}` + ) + + // UTM tags + + if (props.$current_url.indexOf('utm_') > -1) { + const medium = getParameterByName('utm_medium', props.$current_url) + const source = getParameterByName('utm_source', props.$current_url) + const term = getParameterByName('utm_term', props.$current_url) + const campaign = getParameterByName('utm_campaign', props.$current_url) + const content = getParameterByName('utm_content', props.$current_url) + + logger.debug( + `(UTM) Medium: ${medium}, Source: ${source}, Term: ${term}, Campaign: ${campaign}, Content: ${content}` + ) + + return { + ...event, + properties: { + ...event.properties, + medium: medium?.toLowerCase(), + source: source?.toLowerCase(), + term: term?.toLowerCase(), + referrer_parser: 'utm', + $set: { + medium: medium?.toLowerCase(), + source: source?.toLowerCase(), + campaign: campaign?.toLowerCase(), + content: content?.toLowerCase(), + term: term?.toLowerCase(), + referrer_parser: 'utm', + }, + $set_once: { + initial_medium: medium?.toLowerCase(), + initial_source: source?.toLowerCase(), + initial_campaign: campaign?.toLowerCase(), + initial_content: content?.toLowerCase(), + initial_term: term?.toLowerCase(), + initial_referrer_parser: 'utm', + }, + }, + } + } + + // Direct + + if (props.$referrer === '$direct' || isReferrerFromDirectDomain(props.$referrer)) { + logger.debug(`(DIRECT) Source: direct`) + + return { + ...event, + properties: { + ...event.properties, + source: 'direct', + referrer_parser: 'direct_and_own_domains', + $set: { + source: 'direct', + referrer_parser: 'direct_and_own_domains', + }, + $set_once: { + initial_source: 'direct', + initial_referrer_parser: 'direct_and_own_domains', + initial_medium: 'undefined', + initial_campaign: 'undefined', + initial_content: 'undefined', + initial_term: 'undefined', + }, + }, + } + } + + // Snowplow parser + + const referrerData = getMediumAndSourceFromReferrer(props.$referring_domain) + const searchQuery = getSearchQueryFromUrl(props.$referrer, referrerData.queryParams) + + logger.debug(`(SNOWPLOW) Medium: ${referrerData.medium}, Source: ${referrerData.source}, Term: ${searchQuery}`) + + return { + ...event, + properties: { + ...event.properties, + medium: referrerData.medium.toLowerCase(), + source: referrerData.source.toLowerCase(), + term: searchQuery, + referrer_parser: 'snowplow', + $set: { + medium: referrerData.medium.toLowerCase(), + source: referrerData.source.toLowerCase(), + term: searchQuery, + referrer_parser: 'snowplow', + }, + $set_once: { + initial_medium: referrerData.medium.toLowerCase(), + initial_source: referrerData.source.toLowerCase(), + initial_term: searchQuery, + initial_referrer_parser: 'snowplow', + initial_campaign: 'undefined', + initial_content: 'undefined', + }, + }, + } +} + +function getParameterByName(name: string, url: string) { + name = name.replace(/[\[\]]/g, '\\$&') + const regex = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)'), + results = regex.exec(url) + if (!results) { + return null + } + if (!results[2]) { + return '' + } + return decodeURIComponent(results[2].replace(/\+/g, ' ')) +} + +// type ReferrerData = { +// medium: string +// source: string +// queryParams: string[] +// } + +function getMediumAndSourceFromReferrer(domain: string) { + let medium = 'unknown' + let source = 'unknown' + let queryParams: string[] = [] + + for (const med of Object.keys(referrers)) { + for (const src of Object.keys(referrers[med])) { + if (referrers[med][src].domains.includes(domain)) { + medium = med.toLowerCase() + source = src.toLowerCase() + queryParams = referrers[med][src].parameters || [] + } + } + } + + return { medium, source, queryParams } +} + +function getSearchQueryFromUrl(url: string, queryParams: string[]) { + let searchQuery = '' + + if (!Array.isArray(queryParams)) { + return searchQuery + } + + for (const param of queryParams) { + const searchString = getParameterByName(param, url) + if (searchString !== null) { + searchQuery = searchString + } + } + + return searchQuery +} + +function isReferrerFromDirectDomain(referrer: string) { + let isDirect = false + + for (const domain of directReferrerDomains) { + if (referrer.indexOf(domain) > -1) { + isDirect = true + } + } + + return isDirect +} + +const referrers: Record> = { + unknown: { + Outbrain: { + domains: ['paid.outbrain.com'], + }, + Google: { + domains: [ + 'support.google.com', + 'developers.google.com', + 'maps.google.com', + 'accounts.google.com', + 'drive.google.com', + 'sites.google.com', + 'groups.google.com', + 'groups.google.co.uk', + 'news.google.co.uk', + ], + }, + Taboola: { + domains: ['trc.taboola.com', 'api.taboola.com'], + }, + 'Yahoo!': { + domains: [ + 'finance.yahoo.com', + 'news.yahoo.com', + 'eurosport.yahoo.com', + 'sports.yahoo.com', + 'astrology.yahoo.com', + 'travel.yahoo.com', + 'answers.yahoo.com', + 'screen.yahoo.com', + 'weather.yahoo.com', + 'messenger.yahoo.com', + 'games.yahoo.com', + 'shopping.yahoo.net', + 'movies.yahoo.com', + 'cars.yahoo.com', + 'lifestyle.yahoo.com', + 'omg.yahoo.com', + 'match.yahoo.net', + ], + }, + }, + search: { + TalkTalk: { + domains: ['www.talktalk.co.uk'], + parameters: ['query'], + }, + '1.cz': { + domains: ['1.cz'], + parameters: ['q'], + }, + Softonic: { + domains: ['search.softonic.com'], + parameters: ['q'], + }, + GAIS: { + domains: ['gais.cs.ccu.edu.tw'], + parameters: ['q'], + }, + Freecause: { + domains: ['search.freecause.com'], + parameters: ['p'], + }, + '360.cn': { + domains: ['so.360.cn', 'www.so.com'], + parameters: ['q'], + }, + RPMFind: { + domains: ['rpmfind.net', 'fr2.rpmfind.net'], + parameters: ['query'], + }, + Comcast: { + domains: ['serach.comcast.net'], + parameters: ['q'], + }, + Voila: { + domains: ['search.ke.voila.fr', 'www.lemoteur.fr'], + parameters: ['rdata', 'kw'], + }, + Nifty: { + domains: ['search.nifty.com'], + parameters: ['q'], + }, + Atlas: { + domains: ['searchatlas.centrum.cz'], + parameters: ['q'], + }, + 'Lo.st': { + domains: ['lo.st'], + parameters: ['x_query'], + }, + DasTelefonbuch: { + domains: ['www1.dastelefonbuch.de'], + parameters: ['kw'], + }, + Fireball: { + domains: ['www.fireball.de'], + parameters: ['q'], + }, + '1und1': { + domains: ['search.1und1.de'], + parameters: ['su'], + }, + Virgilio: { + domains: [ + 'ricerca.virgilio.it', + 'ricercaimmagini.virgilio.it', + 'ricercavideo.virgilio.it', + 'ricercanews.virgilio.it', + 'mobile.virgilio.it', + ], + parameters: ['qs'], + }, + 'Web.nl': { + domains: ['www.web.nl'], + parameters: ['zoekwoord'], + }, + Plazoo: { + domains: ['www.plazoo.com'], + parameters: ['q'], + }, + 'Goyellow.de': { + domains: ['www.goyellow.de'], + parameters: ['MDN'], + }, + AOL: { + domains: [ + 'search.aol.com', + 'search.aol.it', + 'aolsearch.aol.com', + 'aolsearch.com', + 'www.aolrecherche.aol.fr', + 'www.aolrecherches.aol.fr', + 'www.aolimages.aol.fr', + 'aim.search.aol.com', + 'www.recherche.aol.fr', + 'find.web.aol.com', + 'recherche.aol.ca', + 'aolsearch.aol.co.uk', + 'search.aol.co.uk', + 'aolrecherche.aol.fr', + 'sucheaol.aol.de', + 'suche.aol.de', + 'suche.aolsvc.de', + 'aolbusqueda.aol.com.mx', + 'alicesuche.aol.de', + 'alicesuchet.aol.de', + 'suchet2.aol.de', + 'search.hp.my.aol.com.au', + 'search.hp.my.aol.de', + 'search.hp.my.aol.it', + 'search-intl.netscape.com', + ], + parameters: ['q', 'query'], + }, + Acoon: { + domains: ['www.acoon.de'], + parameters: ['begriff'], + }, + Free: { + domains: ['search.free.fr', 'search1-2.free.fr', 'search1-1.free.fr'], + parameters: ['q'], + }, + 'Apollo Latvia': { + domains: ['apollo.lv/portal/search/'], + parameters: ['q'], + }, + HighBeam: { + domains: ['www.highbeam.com'], + parameters: ['q'], + }, + 'I-play': { + domains: ['start.iplay.com'], + parameters: ['q'], + }, + FriendFeed: { + domains: ['friendfeed.com'], + parameters: ['q'], + }, + Yasni: { + domains: ['www.yasni.de', 'www.yasni.com', 'www.yasni.co.uk', 'www.yasni.ch', 'www.yasni.at'], + parameters: ['query'], + }, + Gigablast: { + domains: ['www.gigablast.com', 'dir.gigablast.com'], + parameters: ['q'], + }, + arama: { + domains: ['arama.com'], + parameters: ['q'], + }, + Fixsuche: { + domains: ['www.fixsuche.de'], + parameters: ['q'], + }, + Apontador: { + domains: ['apontador.com.br', 'www.apontador.com.br'], + parameters: ['q'], + }, + 'Search.com': { + domains: ['www.search.com'], + parameters: ['q'], + }, + Monstercrawler: { + domains: ['www.monstercrawler.com'], + parameters: ['qry'], + }, + 'Google Images': { + domains: [ + 'google.ac/imgres', + 'google.ad/imgres', + 'google.ae/imgres', + 'google.am/imgres', + 'google.as/imgres', + 'google.at/imgres', + 'google.az/imgres', + 'google.ba/imgres', + 'google.be/imgres', + 'google.bf/imgres', + 'google.bg/imgres', + 'google.bi/imgres', + 'google.bj/imgres', + 'google.bs/imgres', + 'google.by/imgres', + 'google.ca/imgres', + 'google.cat/imgres', + 'google.cc/imgres', + 'google.cd/imgres', + 'google.cf/imgres', + 'google.cg/imgres', + 'google.ch/imgres', + 'google.ci/imgres', + 'google.cl/imgres', + 'google.cm/imgres', + 'google.cn/imgres', + 'google.co.bw/imgres', + 'google.co.ck/imgres', + 'google.co.cr/imgres', + 'google.co.id/imgres', + 'google.co.il/imgres', + 'google.co.in/imgres', + 'google.co.jp/imgres', + 'google.co.ke/imgres', + 'google.co.kr/imgres', + 'google.co.ls/imgres', + 'google.co.ma/imgres', + 'google.co.mz/imgres', + 'google.co.nz/imgres', + 'google.co.th/imgres', + 'google.co.tz/imgres', + 'google.co.ug/imgres', + 'google.co.uk/imgres', + 'google.co.uz/imgres', + 'google.co.ve/imgres', + 'google.co.vi/imgres', + 'google.co.za/imgres', + 'google.co.zm/imgres', + 'google.co.zw/imgres', + 'google.com/imgres', + 'google.com.af/imgres', + 'google.com.ag/imgres', + 'google.com.ai/imgres', + 'google.com.ar/imgres', + 'google.com.au/imgres', + 'google.com.bd/imgres', + 'google.com.bh/imgres', + 'google.com.bn/imgres', + 'google.com.bo/imgres', + 'google.com.br/imgres', + 'google.com.by/imgres', + 'google.com.bz/imgres', + 'google.com.co/imgres', + 'google.com.cu/imgres', + 'google.com.cy/imgres', + 'google.com.do/imgres', + 'google.com.ec/imgres', + 'google.com.eg/imgres', + 'google.com.et/imgres', + 'google.com.fj/imgres', + 'google.com.gh/imgres', + 'google.com.gi/imgres', + 'google.com.gt/imgres', + 'google.com.hk/imgres', + 'google.com.jm/imgres', + 'google.com.kh/imgres', + 'google.com.kw/imgres', + 'google.com.lb/imgres', + 'google.com.lc/imgres', + 'google.com.ly/imgres', + 'google.com.mt/imgres', + 'google.com.mx/imgres', + 'google.com.my/imgres', + 'google.com.na/imgres', + 'google.com.nf/imgres', + 'google.com.ng/imgres', + 'google.com.ni/imgres', + 'google.com.np/imgres', + 'google.com.om/imgres', + 'google.com.pa/imgres', + 'google.com.pe/imgres', + 'google.com.ph/imgres', + 'google.com.pk/imgres', + 'google.com.pr/imgres', + 'google.com.py/imgres', + 'google.com.qa/imgres', + 'google.com.sa/imgres', + 'google.com.sb/imgres', + 'google.com.sg/imgres', + 'google.com.sl/imgres', + 'google.com.sv/imgres', + 'google.com.tj/imgres', + 'google.com.tn/imgres', + 'google.com.tr/imgres', + 'google.com.tw/imgres', + 'google.com.ua/imgres', + 'google.com.uy/imgres', + 'google.com.vc/imgres', + 'google.com.vn/imgres', + 'google.cv/imgres', + 'google.cz/imgres', + 'google.de/imgres', + 'google.dj/imgres', + 'google.dk/imgres', + 'google.dm/imgres', + 'google.dz/imgres', + 'google.ee/imgres', + 'google.es/imgres', + 'google.fi/imgres', + 'google.fm/imgres', + 'google.fr/imgres', + 'google.ga/imgres', + 'google.gd/imgres', + 'google.ge/imgres', + 'google.gf/imgres', + 'google.gg/imgres', + 'google.gl/imgres', + 'google.gm/imgres', + 'google.gp/imgres', + 'google.gr/imgres', + 'google.gy/imgres', + 'google.hn/imgres', + 'google.hr/imgres', + 'google.ht/imgres', + 'google.hu/imgres', + 'google.ie/imgres', + 'google.im/imgres', + 'google.io/imgres', + 'google.iq/imgres', + 'google.is/imgres', + 'google.it/imgres', + 'google.it.ao/imgres', + 'google.je/imgres', + 'google.jo/imgres', + 'google.kg/imgres', + 'google.ki/imgres', + 'google.kz/imgres', + 'google.la/imgres', + 'google.li/imgres', + 'google.lk/imgres', + 'google.lt/imgres', + 'google.lu/imgres', + 'google.lv/imgres', + 'google.md/imgres', + 'google.me/imgres', + 'google.mg/imgres', + 'google.mk/imgres', + 'google.ml/imgres', + 'google.mn/imgres', + 'google.ms/imgres', + 'google.mu/imgres', + 'google.mv/imgres', + 'google.mw/imgres', + 'google.ne/imgres', + 'google.nl/imgres', + 'google.no/imgres', + 'google.nr/imgres', + 'google.nu/imgres', + 'google.pl/imgres', + 'google.pn/imgres', + 'google.ps/imgres', + 'google.pt/imgres', + 'google.ro/imgres', + 'google.rs/imgres', + 'google.ru/imgres', + 'google.rw/imgres', + 'google.sc/imgres', + 'google.se/imgres', + 'google.sh/imgres', + 'google.si/imgres', + 'google.sk/imgres', + 'google.sm/imgres', + 'google.sn/imgres', + 'google.so/imgres', + 'google.st/imgres', + 'google.td/imgres', + 'google.tg/imgres', + 'google.tk/imgres', + 'google.tl/imgres', + 'google.tm/imgres', + 'google.to/imgres', + 'google.tt/imgres', + 'google.us/imgres', + 'google.vg/imgres', + 'google.vu/imgres', + 'images.google.ws', + 'images.google.ac', + 'images.google.ad', + 'images.google.ae', + 'images.google.am', + 'images.google.as', + 'images.google.at', + 'images.google.az', + 'images.google.ba', + 'images.google.be', + 'images.google.bf', + 'images.google.bg', + 'images.google.bi', + 'images.google.bj', + 'images.google.bs', + 'images.google.by', + 'images.google.ca', + 'images.google.cat', + 'images.google.cc', + 'images.google.cd', + 'images.google.cf', + 'images.google.cg', + 'images.google.ch', + 'images.google.ci', + 'images.google.cl', + 'images.google.cm', + 'images.google.cn', + 'images.google.co.bw', + 'images.google.co.ck', + 'images.google.co.cr', + 'images.google.co.id', + 'images.google.co.il', + 'images.google.co.in', + 'images.google.co.jp', + 'images.google.co.ke', + 'images.google.co.kr', + 'images.google.co.ls', + 'images.google.co.ma', + 'images.google.co.mz', + 'images.google.co.nz', + 'images.google.co.th', + 'images.google.co.tz', + 'images.google.co.ug', + 'images.google.co.uk', + 'images.google.co.uz', + 'images.google.co.ve', + 'images.google.co.vi', + 'images.google.co.za', + 'images.google.co.zm', + 'images.google.co.zw', + 'images.google.com', + 'images.google.com.af', + 'images.google.com.ag', + 'images.google.com.ai', + 'images.google.com.ar', + 'images.google.com.au', + 'images.google.com.bd', + 'images.google.com.bh', + 'images.google.com.bn', + 'images.google.com.bo', + 'images.google.com.br', + 'images.google.com.by', + 'images.google.com.bz', + 'images.google.com.co', + 'images.google.com.cu', + 'images.google.com.cy', + 'images.google.com.do', + 'images.google.com.ec', + 'images.google.com.eg', + 'images.google.com.et', + 'images.google.com.fj', + 'images.google.com.gh', + 'images.google.com.gi', + 'images.google.com.gt', + 'images.google.com.hk', + 'images.google.com.jm', + 'images.google.com.kh', + 'images.google.com.kh', + 'images.google.com.kw', + 'images.google.com.lb', + 'images.google.com.lc', + 'images.google.com.ly', + 'images.google.com.mt', + 'images.google.com.mx', + 'images.google.com.my', + 'images.google.com.na', + 'images.google.com.nf', + 'images.google.com.ng', + 'images.google.com.ni', + 'images.google.com.np', + 'images.google.com.om', + 'images.google.com.pa', + 'images.google.com.pe', + 'images.google.com.ph', + 'images.google.com.pk', + 'images.google.com.pr', + 'images.google.com.py', + 'images.google.com.qa', + 'images.google.com.sa', + 'images.google.com.sb', + 'images.google.com.sg', + 'images.google.com.sl', + 'images.google.com.sv', + 'images.google.com.tj', + 'images.google.com.tn', + 'images.google.com.tr', + 'images.google.com.tw', + 'images.google.com.ua', + 'images.google.com.uy', + 'images.google.com.vc', + 'images.google.com.vn', + 'images.google.cv', + 'images.google.cz', + 'images.google.de', + 'images.google.dj', + 'images.google.dk', + 'images.google.dm', + 'images.google.dz', + 'images.google.ee', + 'images.google.es', + 'images.google.fi', + 'images.google.fm', + 'images.google.fr', + 'images.google.ga', + 'images.google.gd', + 'images.google.ge', + 'images.google.gf', + 'images.google.gg', + 'images.google.gl', + 'images.google.gm', + 'images.google.gp', + 'images.google.gr', + 'images.google.gy', + 'images.google.hn', + 'images.google.hr', + 'images.google.ht', + 'images.google.hu', + 'images.google.ie', + 'images.google.im', + 'images.google.io', + 'images.google.iq', + 'images.google.is', + 'images.google.it', + 'images.google.it.ao', + 'images.google.je', + 'images.google.jo', + 'images.google.kg', + 'images.google.ki', + 'images.google.kz', + 'images.google.la', + 'images.google.li', + 'images.google.lk', + 'images.google.lt', + 'images.google.lu', + 'images.google.lv', + 'images.google.md', + 'images.google.me', + 'images.google.mg', + 'images.google.mk', + 'images.google.ml', + 'images.google.mn', + 'images.google.ms', + 'images.google.mu', + 'images.google.mv', + 'images.google.mw', + 'images.google.ne', + 'images.google.nl', + 'images.google.no', + 'images.google.nr', + 'images.google.nu', + 'images.google.pl', + 'images.google.pn', + 'images.google.ps', + 'images.google.pt', + 'images.google.ro', + 'images.google.rs', + 'images.google.ru', + 'images.google.rw', + 'images.google.sc', + 'images.google.se', + 'images.google.sh', + 'images.google.si', + 'images.google.sk', + 'images.google.sm', + 'images.google.sn', + 'images.google.so', + 'images.google.st', + 'images.google.td', + 'images.google.tg', + 'images.google.tk', + 'images.google.tl', + 'images.google.tm', + 'images.google.to', + 'images.google.tt', + 'images.google.us', + 'images.google.vg', + 'images.google.vu', + 'images.google.ws', + ], + parameters: ['q'], + }, + 'ABCs\u00f8k': { + domains: ['abcsolk.no', 'verden.abcsok.no'], + parameters: ['q'], + }, + 'Google Product Search': { + domains: [ + 'google.ac/products', + 'google.ad/products', + 'google.ae/products', + 'google.am/products', + 'google.as/products', + 'google.at/products', + 'google.az/products', + 'google.ba/products', + 'google.be/products', + 'google.bf/products', + 'google.bg/products', + 'google.bi/products', + 'google.bj/products', + 'google.bs/products', + 'google.by/products', + 'google.ca/products', + 'google.cat/products', + 'google.cc/products', + 'google.cd/products', + 'google.cf/products', + 'google.cg/products', + 'google.ch/products', + 'google.ci/products', + 'google.cl/products', + 'google.cm/products', + 'google.cn/products', + 'google.co.bw/products', + 'google.co.ck/products', + 'google.co.cr/products', + 'google.co.id/products', + 'google.co.il/products', + 'google.co.in/products', + 'google.co.jp/products', + 'google.co.ke/products', + 'google.co.kr/products', + 'google.co.ls/products', + 'google.co.ma/products', + 'google.co.mz/products', + 'google.co.nz/products', + 'google.co.th/products', + 'google.co.tz/products', + 'google.co.ug/products', + 'google.co.uk/products', + 'google.co.uz/products', + 'google.co.ve/products', + 'google.co.vi/products', + 'google.co.za/products', + 'google.co.zm/products', + 'google.co.zw/products', + 'google.com/products', + 'google.com.af/products', + 'google.com.ag/products', + 'google.com.ai/products', + 'google.com.ar/products', + 'google.com.au/products', + 'google.com.bd/products', + 'google.com.bh/products', + 'google.com.bn/products', + 'google.com.bo/products', + 'google.com.br/products', + 'google.com.by/products', + 'google.com.bz/products', + 'google.com.co/products', + 'google.com.cu/products', + 'google.com.cy/products', + 'google.com.do/products', + 'google.com.ec/products', + 'google.com.eg/products', + 'google.com.et/products', + 'google.com.fj/products', + 'google.com.gh/products', + 'google.com.gi/products', + 'google.com.gt/products', + 'google.com.hk/products', + 'google.com.jm/products', + 'google.com.kh/products', + 'google.com.kh/products', + 'google.com.kw/products', + 'google.com.lb/products', + 'google.com.lc/products', + 'google.com.ly/products', + 'google.com.mt/products', + 'google.com.mx/products', + 'google.com.my/products', + 'google.com.na/products', + 'google.com.nf/products', + 'google.com.ng/products', + 'google.com.ni/products', + 'google.com.np/products', + 'google.com.om/products', + 'google.com.pa/products', + 'google.com.pe/products', + 'google.com.ph/products', + 'google.com.pk/products', + 'google.com.pr/products', + 'google.com.py/products', + 'google.com.qa/products', + 'google.com.sa/products', + 'google.com.sb/products', + 'google.com.sg/products', + 'google.com.sl/products', + 'google.com.sv/products', + 'google.com.tj/products', + 'google.com.tn/products', + 'google.com.tr/products', + 'google.com.tw/products', + 'google.com.ua/products', + 'google.com.uy/products', + 'google.com.vc/products', + 'google.com.vn/products', + 'google.cv/products', + 'google.cz/products', + 'google.de/products', + 'google.dj/products', + 'google.dk/products', + 'google.dm/products', + 'google.dz/products', + 'google.ee/products', + 'google.es/products', + 'google.fi/products', + 'google.fm/products', + 'google.fr/products', + 'google.ga/products', + 'google.gd/products', + 'google.ge/products', + 'google.gf/products', + 'google.gg/products', + 'google.gl/products', + 'google.gm/products', + 'google.gp/products', + 'google.gr/products', + 'google.gy/products', + 'google.hn/products', + 'google.hr/products', + 'google.ht/products', + 'google.hu/products', + 'google.ie/products', + 'google.im/products', + 'google.io/products', + 'google.iq/products', + 'google.is/products', + 'google.it/products', + 'google.it.ao/products', + 'google.je/products', + 'google.jo/products', + 'google.kg/products', + 'google.ki/products', + 'google.kz/products', + 'google.la/products', + 'google.li/products', + 'google.lk/products', + 'google.lt/products', + 'google.lu/products', + 'google.lv/products', + 'google.md/products', + 'google.me/products', + 'google.mg/products', + 'google.mk/products', + 'google.ml/products', + 'google.mn/products', + 'google.ms/products', + 'google.mu/products', + 'google.mv/products', + 'google.mw/products', + 'google.ne/products', + 'google.nl/products', + 'google.no/products', + 'google.nr/products', + 'google.nu/products', + 'google.pl/products', + 'google.pn/products', + 'google.ps/products', + 'google.pt/products', + 'google.ro/products', + 'google.rs/products', + 'google.ru/products', + 'google.rw/products', + 'google.sc/products', + 'google.se/products', + 'google.sh/products', + 'google.si/products', + 'google.sk/products', + 'google.sm/products', + 'google.sn/products', + 'google.so/products', + 'google.st/products', + 'google.td/products', + 'google.tg/products', + 'google.tk/products', + 'google.tl/products', + 'google.tm/products', + 'google.to/products', + 'google.tt/products', + 'google.us/products', + 'google.vg/products', + 'google.vu/products', + 'google.ws/products', + 'www.google.ac/products', + 'www.google.ad/products', + 'www.google.ae/products', + 'www.google.am/products', + 'www.google.as/products', + 'www.google.at/products', + 'www.google.az/products', + 'www.google.ba/products', + 'www.google.be/products', + 'www.google.bf/products', + 'www.google.bg/products', + 'www.google.bi/products', + 'www.google.bj/products', + 'www.google.bs/products', + 'www.google.by/products', + 'www.google.ca/products', + 'www.google.cat/products', + 'www.google.cc/products', + 'www.google.cd/products', + 'www.google.cf/products', + 'www.google.cg/products', + 'www.google.ch/products', + 'www.google.ci/products', + 'www.google.cl/products', + 'www.google.cm/products', + 'www.google.cn/products', + 'www.google.co.bw/products', + 'www.google.co.ck/products', + 'www.google.co.cr/products', + 'www.google.co.id/products', + 'www.google.co.il/products', + 'www.google.co.in/products', + 'www.google.co.jp/products', + 'www.google.co.ke/products', + 'www.google.co.kr/products', + 'www.google.co.ls/products', + 'www.google.co.ma/products', + 'www.google.co.mz/products', + 'www.google.co.nz/products', + 'www.google.co.th/products', + 'www.google.co.tz/products', + 'www.google.co.ug/products', + 'www.google.co.uk/products', + 'www.google.co.uz/products', + 'www.google.co.ve/products', + 'www.google.co.vi/products', + 'www.google.co.za/products', + 'www.google.co.zm/products', + 'www.google.co.zw/products', + 'www.google.com/products', + 'www.google.com.af/products', + 'www.google.com.ag/products', + 'www.google.com.ai/products', + 'www.google.com.ar/products', + 'www.google.com.au/products', + 'www.google.com.bd/products', + 'www.google.com.bh/products', + 'www.google.com.bn/products', + 'www.google.com.bo/products', + 'www.google.com.br/products', + 'www.google.com.by/products', + 'www.google.com.bz/products', + 'www.google.com.co/products', + 'www.google.com.cu/products', + 'www.google.com.cy/products', + 'www.google.com.do/products', + 'www.google.com.ec/products', + 'www.google.com.eg/products', + 'www.google.com.et/products', + 'www.google.com.fj/products', + 'www.google.com.gh/products', + 'www.google.com.gi/products', + 'www.google.com.gt/products', + 'www.google.com.hk/products', + 'www.google.com.jm/products', + 'www.google.com.kh/products', + 'www.google.com.kh/products', + 'www.google.com.kw/products', + 'www.google.com.lb/products', + 'www.google.com.lc/products', + 'www.google.com.ly/products', + 'www.google.com.mt/products', + 'www.google.com.mx/products', + 'www.google.com.my/products', + 'www.google.com.na/products', + 'www.google.com.nf/products', + 'www.google.com.ng/products', + 'www.google.com.ni/products', + 'www.google.com.np/products', + 'www.google.com.om/products', + 'www.google.com.pa/products', + 'www.google.com.pe/products', + 'www.google.com.ph/products', + 'www.google.com.pk/products', + 'www.google.com.pr/products', + 'www.google.com.py/products', + 'www.google.com.qa/products', + 'www.google.com.sa/products', + 'www.google.com.sb/products', + 'www.google.com.sg/products', + 'www.google.com.sl/products', + 'www.google.com.sv/products', + 'www.google.com.tj/products', + 'www.google.com.tn/products', + 'www.google.com.tr/products', + 'www.google.com.tw/products', + 'www.google.com.ua/products', + 'www.google.com.uy/products', + 'www.google.com.vc/products', + 'www.google.com.vn/products', + 'www.google.cv/products', + 'www.google.cz/products', + 'www.google.de/products', + 'www.google.dj/products', + 'www.google.dk/products', + 'www.google.dm/products', + 'www.google.dz/products', + 'www.google.ee/products', + 'www.google.es/products', + 'www.google.fi/products', + 'www.google.fm/products', + 'www.google.fr/products', + 'www.google.ga/products', + 'www.google.gd/products', + 'www.google.ge/products', + 'www.google.gf/products', + 'www.google.gg/products', + 'www.google.gl/products', + 'www.google.gm/products', + 'www.google.gp/products', + 'www.google.gr/products', + 'www.google.gy/products', + 'www.google.hn/products', + 'www.google.hr/products', + 'www.google.ht/products', + 'www.google.hu/products', + 'www.google.ie/products', + 'www.google.im/products', + 'www.google.io/products', + 'www.google.iq/products', + 'www.google.is/products', + 'www.google.it/products', + 'www.google.it.ao/products', + 'www.google.je/products', + 'www.google.jo/products', + 'www.google.kg/products', + 'www.google.ki/products', + 'www.google.kz/products', + 'www.google.la/products', + 'www.google.li/products', + 'www.google.lk/products', + 'www.google.lt/products', + 'www.google.lu/products', + 'www.google.lv/products', + 'www.google.md/products', + 'www.google.me/products', + 'www.google.mg/products', + 'www.google.mk/products', + 'www.google.ml/products', + 'www.google.mn/products', + 'www.google.ms/products', + 'www.google.mu/products', + 'www.google.mv/products', + 'www.google.mw/products', + 'www.google.ne/products', + 'www.google.nl/products', + 'www.google.no/products', + 'www.google.nr/products', + 'www.google.nu/products', + 'www.google.pl/products', + 'www.google.pn/products', + 'www.google.ps/products', + 'www.google.pt/products', + 'www.google.ro/products', + 'www.google.rs/products', + 'www.google.ru/products', + 'www.google.rw/products', + 'www.google.sc/products', + 'www.google.se/products', + 'www.google.sh/products', + 'www.google.si/products', + 'www.google.sk/products', + 'www.google.sm/products', + 'www.google.sn/products', + 'www.google.so/products', + 'www.google.st/products', + 'www.google.td/products', + 'www.google.tg/products', + 'www.google.tk/products', + 'www.google.tl/products', + 'www.google.tm/products', + 'www.google.to/products', + 'www.google.tt/products', + 'www.google.us/products', + 'www.google.vg/products', + 'www.google.vu/products', + 'www.google.ws/products', + ], + parameters: ['q'], + }, + DasOertliche: { + domains: ['www.dasoertliche.de'], + parameters: ['kw'], + }, + InfoSpace: { + domains: [ + 'infospace.com', + 'dogpile.com', + 'www.dogpile.com', + 'metacrawler.com', + 'webfetch.com', + 'webcrawler.com', + 'search.kiwee.com', + 'isearch.babylon.com', + 'start.facemoods.com', + 'search.magnetic.com', + 'search.searchcompletion.com', + 'clusty.com', + ], + parameters: ['q', 's'], + }, + Weborama: { + domains: ['www.weborama.com'], + parameters: ['QUERY'], + }, + Bluewin: { + domains: ['search.bluewin.ch'], + parameters: ['searchTerm'], + }, + Neti: { + domains: ['www.neti.ee'], + parameters: ['query'], + }, + Winamp: { + domains: ['search.winamp.com'], + parameters: ['q'], + }, + Nigma: { + domains: ['nigma.ru'], + parameters: ['s'], + }, + 'Yahoo! Images': { + domains: ['image.yahoo.cn', 'images.search.yahoo.com'], + parameters: ['p', 'q'], + }, + Exalead: { + domains: ['www.exalead.fr', 'www.exalead.com'], + parameters: ['q'], + }, + Teoma: { + domains: ['www.teoma.com'], + parameters: ['q'], + }, + Needtofind: { + domains: ['ko.search.need2find.com'], + parameters: ['searchfor'], + }, + Looksmart: { + domains: ['www.looksmart.com'], + parameters: ['key'], + }, + 'Wirtualna Polska': { + domains: ['szukaj.wp.pl'], + parameters: ['szukaj'], + }, + Toolbarhome: { + domains: ['www.toolbarhome.com', 'vshare.toolbarhome.com'], + parameters: ['q'], + }, + Searchalot: { + domains: ['searchalot.com'], + parameters: ['q'], + }, + Yandex: { + domains: [ + 'yandex.ru', + 'yandex.ua', + 'yandex.com', + 'yandex.by', + 'www.yandex.ru', + 'www.yandex.ua', + 'www.yandex.com', + 'www.yandex.by', + ], + parameters: ['text'], + }, + 'canoe.ca': { + domains: ['web.canoe.ca'], + parameters: ['q'], + }, + Compuserve: { + domains: ['websearch.cs.com'], + parameters: ['query'], + }, + Blogdigger: { + domains: ['www.blogdigger.com'], + parameters: ['q'], + }, + Startpagina: { + domains: ['startgoogle.startpagina.nl'], + parameters: ['q'], + }, + eo: { + domains: ['eo.st'], + parameters: ['x_query'], + }, + Zhongsou: { + domains: ['p.zhongsou.com'], + parameters: ['w'], + }, + 'La Toile Du Quebec Via Google': { + domains: ['www.toile.com', 'web.toile.com'], + parameters: ['q'], + }, + Paperball: { + domains: ['www.paperball.de'], + parameters: ['q'], + }, + 'Jungle Spider': { + domains: ['www.jungle-spider.de'], + parameters: ['q'], + }, + PeoplePC: { + domains: ['search.peoplepc.com'], + parameters: ['q'], + }, + 'MetaCrawler.de': { + domains: ['s1.metacrawler.de', 's2.metacrawler.de', 's3.metacrawler.de'], + parameters: ['qry'], + }, + Orange: { + domains: ['busca.orange.es', 'search.orange.co.uk'], + parameters: ['q'], + }, + 'Gule Sider': { + domains: ['www.gulesider.no'], + parameters: ['q'], + }, + Francite: { + domains: ['recherche.francite.com'], + parameters: ['name'], + }, + 'Ask Toolbar': { + domains: ['search.tb.ask.com'], + parameters: ['searchfor'], + }, + 'Trusted-Search': { + domains: ['www.trusted--search.com'], + parameters: ['w'], + }, + goo: { + domains: ['search.goo.ne.jp', 'ocnsearch.goo.ne.jp'], + parameters: ['MT'], + }, + 'Fast Browser Search': { + domains: ['www.fastbrowsersearch.com'], + parameters: ['q'], + }, + Blogpulse: { + domains: ['www.blogpulse.com'], + parameters: ['query'], + }, + Volny: { + domains: ['web.volny.cz'], + parameters: ['search'], + }, + Icerockeet: { + domains: ['blogs.icerocket.com'], + parameters: ['q'], + }, + Terra: { + domains: ['buscador.terra.es', 'buscador.terra.cl', 'buscador.terra.com.br'], + parameters: ['query'], + }, + Amazon: { + domains: ['amazon.com', 'www.amazon.com'], + parameters: ['keywords'], + }, + Onet: { + domains: ['szukaj.onet.pl'], + parameters: ['qt'], + }, + Digg: { + domains: ['digg.com'], + parameters: ['s'], + }, + Abacho: { + domains: [ + 'www.abacho.de', + 'www.abacho.com', + 'www.abacho.co.uk', + 'www.se.abacho.com', + 'www.tr.abacho.com', + 'www.abacho.at', + 'www.abacho.fr', + 'www.abacho.es', + 'www.abacho.ch', + 'www.abacho.it', + ], + parameters: ['q'], + }, + maailm: { + domains: ['www.maailm.com'], + parameters: ['tekst'], + }, + Flix: { + domains: ['www.flix.de'], + parameters: ['keyword'], + }, + Suchnase: { + domains: ['www.suchnase.de'], + parameters: ['q'], + }, + Freenet: { + domains: ['suche.freenet.de'], + parameters: ['query', 'Keywords'], + }, + 'Poisk.ru': { + domains: ['www.plazoo.com'], + parameters: ['q'], + }, + Sharelook: { + domains: ['www.sharelook.fr'], + parameters: ['keyword'], + }, + Najdi: { + domains: ['www.najdi.si'], + parameters: ['q'], + }, + Picsearch: { + domains: ['www.picsearch.com'], + parameters: ['q'], + }, + 'Mail.ru': { + domains: ['go.mail.ru'], + parameters: ['q'], + }, + Alexa: { + domains: ['alexa.com', 'search.toolbars.alexa.com'], + parameters: ['q'], + }, + Metager: { + domains: ['meta.rrzn.uni-hannover.de', 'www.metager.de'], + parameters: ['eingabe'], + }, + Technorati: { + domains: ['technorati.com'], + parameters: ['q'], + }, + Globososo: { + domains: ['searches.globososo.com', 'search.globososo.com'], + parameters: ['q'], + }, + WWW: { + domains: ['search.www.ee'], + parameters: ['query'], + }, + 'Trouvez.com': { + domains: ['www.trouvez.com'], + parameters: ['query'], + }, + IXquick: { + domains: [ + 'ixquick.com', + 'www.eu.ixquick.com', + 'ixquick.de', + 'www.ixquick.de', + 'us.ixquick.com', + 's1.us.ixquick.com', + 's2.us.ixquick.com', + 's3.us.ixquick.com', + 's4.us.ixquick.com', + 's5.us.ixquick.com', + 'eu.ixquick.com', + 's8-eu.ixquick.com', + 's1-eu.ixquick.de', + ], + parameters: ['query'], + }, + 'Naver Images': { + domains: ['image.search.naver.com', 'imagesearch.naver.com'], + parameters: ['query'], + }, + Zapmeta: { + domains: ['www.zapmeta.com', 'www.zapmeta.nl', 'www.zapmeta.de', 'uk.zapmeta.com'], + parameters: ['q', 'query'], + }, + Yippy: { + domains: ['search.yippy.com'], + parameters: ['q', 'query'], + }, + Gomeo: { + domains: ['www.gomeo.com'], + parameters: ['Keywords'], + }, + Walhello: { + domains: ['www.walhello.info', 'www.walhello.com', 'www.walhello.de', 'www.walhello.nl'], + parameters: ['key'], + }, + Meta: { + domains: ['meta.ua'], + parameters: ['q'], + }, + Skynet: { + domains: ['www.skynet.be'], + parameters: ['q'], + }, + Searchy: { + domains: ['www.searchy.co.uk'], + parameters: ['q'], + }, + Findwide: { + domains: ['search.findwide.com'], + parameters: ['k'], + }, + WebSearch: { + domains: ['www.websearch.com'], + parameters: ['qkw', 'q'], + }, + Rambler: { + domains: ['nova.rambler.ru'], + parameters: ['query', 'words'], + }, + Latne: { + domains: ['www.latne.lv'], + parameters: ['q'], + }, + MySearch: { + domains: [ + 'www.mysearch.com', + 'ms114.mysearch.com', + 'ms146.mysearch.com', + 'kf.mysearch.myway.com', + 'ki.mysearch.myway.com', + 'search.myway.com', + 'search.mywebsearch.com', + ], + parameters: ['searchfor', 'searchFor'], + }, + Cuil: { + domains: ['www.cuil.com'], + parameters: ['q'], + }, + Tixuma: { + domains: ['www.tixuma.de'], + parameters: ['sc'], + }, + Sapo: { + domains: ['pesquisa.sapo.pt'], + parameters: ['q'], + }, + Gnadenmeer: { + domains: ['www.gnadenmeer.de'], + parameters: ['keyword'], + }, + Arcor: { + domains: ['www.arcor.de'], + parameters: ['Keywords'], + }, + Naver: { + domains: ['search.naver.com'], + parameters: ['query'], + }, + Zoeken: { + domains: ['www.zoeken.nl'], + parameters: ['q'], + }, + Startsiden: { + domains: ['www.startsiden.no'], + parameters: ['q'], + }, + Yam: { + domains: ['search.yam.com'], + parameters: ['k'], + }, + Eniro: { + domains: ['www.eniro.se'], + parameters: ['q', 'search_word'], + }, + APOLL07: { + domains: ['apollo7.de'], + parameters: ['query'], + }, + Biglobe: { + domains: ['cgi.search.biglobe.ne.jp'], + parameters: ['q'], + }, + Mozbot: { + domains: ['www.mozbot.fr', 'www.mozbot.co.uk', 'www.mozbot.com'], + parameters: ['q'], + }, + ICQ: { + domains: ['www.icq.com', 'search.icq.com'], + parameters: ['q'], + }, + Baidu: { + domains: [ + 'www.baidu.com', + 'www1.baidu.com', + 'zhidao.baidu.com', + 'tieba.baidu.com', + 'news.baidu.com', + 'web.gougou.com', + ], + parameters: ['wd', 'word', 'kw', 'k'], + }, + Conduit: { + domains: ['search.conduit.com'], + parameters: ['q'], + }, + Vindex: { + domains: ['www.vindex.nl', 'search.vindex.nl'], + parameters: ['search_for'], + }, + Babylon: { + domains: ['search.babylon.com', 'searchassist.babylon.com'], + parameters: ['q'], + }, + TrovaRapido: { + domains: ['www.trovarapido.com'], + parameters: ['q'], + }, + 'Suchmaschine.com': { + domains: ['www.suchmaschine.com'], + parameters: ['suchstr'], + }, + Lycos: { + domains: ['search.lycos.com', 'www.lycos.com', 'lycos.com'], + parameters: ['query'], + }, + Vinden: { + domains: ['www.vinden.nl'], + parameters: ['q'], + }, + Altavista: { + domains: [ + 'www.altavista.com', + 'search.altavista.com', + 'listings.altavista.com', + 'altavista.de', + 'altavista.fr', + 'be-nl.altavista.com', + 'be-fr.altavista.com', + ], + parameters: ['q'], + }, + dmoz: { + domains: ['dmoz.org', 'editors.dmoz.org'], + parameters: ['q'], + }, + Ecosia: { + domains: ['ecosia.org'], + parameters: ['q'], + }, + Maxwebsearch: { + domains: ['maxwebsearch.com'], + parameters: ['query'], + }, + Euroseek: { + domains: ['www.euroseek.com'], + parameters: ['string'], + }, + Bing: { + domains: [ + 'bing.com', + 'www.bing.com', + 'msnbc.msn.com', + 'dizionario.it.msn.com', + 'cc.bingj.com', + 'm.bing.com', + ], + parameters: ['q', 'Q'], + }, + 'X-recherche': { + domains: ['www.x-recherche.com'], + parameters: ['MOTS'], + }, + 'Yandex Images': { + domains: ['images.yandex.ru', 'images.yandex.ua', 'images.yandex.com'], + parameters: ['text'], + }, + GMX: { + domains: ['suche.gmx.net'], + parameters: ['su'], + }, + 'Daemon search': { + domains: ['daemon-search.com', 'my.daemon-search.com'], + parameters: ['q'], + }, + 'Jungle Key': { + domains: ['junglekey.com', 'junglekey.fr'], + parameters: ['query'], + }, + Firstfind: { + domains: ['www.firstsfind.com'], + parameters: ['qry'], + }, + Crawler: { + domains: ['www.crawler.com'], + parameters: ['q'], + }, + Holmes: { + domains: ['holmes.ge'], + parameters: ['q'], + }, + Charter: { + domains: ['www.charter.net'], + parameters: ['q'], + }, + Ilse: { + domains: ['www.ilse.nl'], + parameters: ['search_for'], + }, + earthlink: { + domains: ['search.earthlink.net'], + parameters: ['q'], + }, + Qualigo: { + domains: ['www.qualigo.at', 'www.qualigo.ch', 'www.qualigo.de', 'www.qualigo.nl'], + parameters: ['q'], + }, + 'El Mundo': { + domains: ['ariadna.elmundo.es'], + parameters: ['q'], + }, + Metager2: { + domains: ['metager2.de'], + parameters: ['q'], + }, + Forestle: { + domains: ['forestle.org', 'www.forestle.org', 'forestle.mobi'], + parameters: ['q'], + }, + 'Search.ch': { + domains: ['www.search.ch'], + parameters: ['q'], + }, + Meinestadt: { + domains: ['www.meinestadt.de'], + parameters: ['words'], + }, + Freshweather: { + domains: ['www.fresh-weather.com'], + parameters: ['q'], + }, + AllTheWeb: { + domains: ['www.alltheweb.com'], + parameters: ['q'], + }, + Snapdo: { + domains: ['search.snapdo.com'], + parameters: ['q'], + }, + Zoek: { + domains: ['www3.zoek.nl'], + parameters: ['q'], + }, + Daum: { + domains: ['search.daum.net'], + parameters: ['q'], + }, + Marktplaats: { + domains: ['www.marktplaats.nl'], + parameters: ['query'], + }, + 'suche.info': { + domains: ['suche.info'], + parameters: ['q'], + }, + 'Google News': { + domains: [ + 'news.google.ac', + 'news.google.ad', + 'news.google.ae', + 'news.google.am', + 'news.google.as', + 'news.google.at', + 'news.google.az', + 'news.google.ba', + 'news.google.be', + 'news.google.bf', + 'news.google.bg', + 'news.google.bi', + 'news.google.bj', + 'news.google.bs', + 'news.google.by', + 'news.google.ca', + 'news.google.cat', + 'news.google.cc', + 'news.google.cd', + 'news.google.cf', + 'news.google.cg', + 'news.google.ch', + 'news.google.ci', + 'news.google.cl', + 'news.google.cm', + 'news.google.cn', + 'news.google.co.bw', + 'news.google.co.ck', + 'news.google.co.cr', + 'news.google.co.id', + 'news.google.co.il', + 'news.google.co.in', + 'news.google.co.jp', + 'news.google.co.ke', + 'news.google.co.kr', + 'news.google.co.ls', + 'news.google.co.ma', + 'news.google.co.mz', + 'news.google.co.nz', + 'news.google.co.th', + 'news.google.co.tz', + 'news.google.co.ug', + 'news.google.co.uk', + 'news.google.co.uz', + 'news.google.co.ve', + 'news.google.co.vi', + 'news.google.co.za', + 'news.google.co.zm', + 'news.google.co.zw', + 'news.google.com', + 'news.google.com.af', + 'news.google.com.ag', + 'news.google.com.ai', + 'news.google.com.ar', + 'news.google.com.au', + 'news.google.com.bd', + 'news.google.com.bh', + 'news.google.com.bn', + 'news.google.com.bo', + 'news.google.com.br', + 'news.google.com.by', + 'news.google.com.bz', + 'news.google.com.co', + 'news.google.com.cu', + 'news.google.com.cy', + 'news.google.com.do', + 'news.google.com.ec', + 'news.google.com.eg', + 'news.google.com.et', + 'news.google.com.fj', + 'news.google.com.gh', + 'news.google.com.gi', + 'news.google.com.gt', + 'news.google.com.hk', + 'news.google.com.jm', + 'news.google.com.kh', + 'news.google.com.kh', + 'news.google.com.kw', + 'news.google.com.lb', + 'news.google.com.lc', + 'news.google.com.ly', + 'news.google.com.mt', + 'news.google.com.mx', + 'news.google.com.my', + 'news.google.com.na', + 'news.google.com.nf', + 'news.google.com.ng', + 'news.google.com.ni', + 'news.google.com.np', + 'news.google.com.om', + 'news.google.com.pa', + 'news.google.com.pe', + 'news.google.com.ph', + 'news.google.com.pk', + 'news.google.com.pr', + 'news.google.com.py', + 'news.google.com.qa', + 'news.google.com.sa', + 'news.google.com.sb', + 'news.google.com.sg', + 'news.google.com.sl', + 'news.google.com.sv', + 'news.google.com.tj', + 'news.google.com.tn', + 'news.google.com.tr', + 'news.google.com.tw', + 'news.google.com.ua', + 'news.google.com.uy', + 'news.google.com.vc', + 'news.google.com.vn', + 'news.google.cv', + 'news.google.cz', + 'news.google.de', + 'news.google.dj', + 'news.google.dk', + 'news.google.dm', + 'news.google.dz', + 'news.google.ee', + 'news.google.es', + 'news.google.fi', + 'news.google.fm', + 'news.google.fr', + 'news.google.ga', + 'news.google.gd', + 'news.google.ge', + 'news.google.gf', + 'news.google.gg', + 'news.google.gl', + 'news.google.gm', + 'news.google.gp', + 'news.google.gr', + 'news.google.gy', + 'news.google.hn', + 'news.google.hr', + 'news.google.ht', + 'news.google.hu', + 'news.google.ie', + 'news.google.im', + 'news.google.io', + 'news.google.iq', + 'news.google.is', + 'news.google.it', + 'news.google.it.ao', + 'news.google.je', + 'news.google.jo', + 'news.google.kg', + 'news.google.ki', + 'news.google.kz', + 'news.google.la', + 'news.google.li', + 'news.google.lk', + 'news.google.lt', + 'news.google.lu', + 'news.google.lv', + 'news.google.md', + 'news.google.me', + 'news.google.mg', + 'news.google.mk', + 'news.google.ml', + 'news.google.mn', + 'news.google.ms', + 'news.google.mu', + 'news.google.mv', + 'news.google.mw', + 'news.google.ne', + 'news.google.nl', + 'news.google.no', + 'news.google.nr', + 'news.google.nu', + 'news.google.pl', + 'news.google.pn', + 'news.google.ps', + 'news.google.pt', + 'news.google.ro', + 'news.google.rs', + 'news.google.ru', + 'news.google.rw', + 'news.google.sc', + 'news.google.se', + 'news.google.sh', + 'news.google.si', + 'news.google.sk', + 'news.google.sm', + 'news.google.sn', + 'news.google.so', + 'news.google.st', + 'news.google.td', + 'news.google.tg', + 'news.google.tk', + 'news.google.tl', + 'news.google.tm', + 'news.google.to', + 'news.google.tt', + 'news.google.us', + 'news.google.vg', + 'news.google.vu', + 'news.google.ws', + ], + parameters: ['q'], + }, + Zoohoo: { + domains: ['zoohoo.cz'], + parameters: ['q'], + }, + Seznam: { + domains: ['search.seznam.cz'], + parameters: ['q'], + }, + 'Online.no': { + domains: ['online.no'], + parameters: ['q'], + }, + Eurip: { + domains: ['www.eurip.com'], + parameters: ['q'], + }, + 'all.by': { + domains: ['all.by'], + parameters: ['query'], + }, + 'Road Runner Search': { + domains: ['search.rr.com'], + parameters: ['q'], + }, + 'Opplysningen 1881': { + domains: ['www.1881.no'], + parameters: ['Query'], + }, + YouGoo: { + domains: ['www.yougoo.fr'], + parameters: ['q'], + }, + 'Bing Images': { + domains: ['bing.com/images/search', 'www.bing.com/images/search'], + parameters: ['q', 'Q'], + }, + Geona: { + domains: ['geona.net'], + parameters: ['q'], + }, + Nate: { + domains: ['search.nate.com'], + parameters: ['q'], + }, + DuckDuckGo: { + domains: ['duckduckgo.com'], + parameters: ['q'], + }, + Hotbot: { + domains: ['www.hotbot.com'], + parameters: ['query'], + }, + Kvasir: { + domains: ['www.kvasir.no'], + parameters: ['q'], + }, + Austronaut: { + domains: ['www2.austronaut.at', 'www1.astronaut.at'], + parameters: ['q'], + }, + Excite: { + domains: [ + 'search.excite.it', + 'search.excite.fr', + 'search.excite.de', + 'search.excite.co.uk', + 'serach.excite.es', + 'search.excite.nl', + 'msxml.excite.com', + 'www.excite.co.jp', + ], + parameters: ['q', 'search'], + }, + qip: { + domains: ['search.qip.ru'], + parameters: ['query'], + }, + 'Certified-Toolbar': { + domains: ['search.certified-toolbar.com'], + parameters: ['q'], + }, + 'Yahoo!': { + domains: [ + 'search.yahoo.com', + 'yahoo.com', + 'ar.search.yahoo.com', + 'ar.yahoo.com', + 'au.search.yahoo.com', + 'au.yahoo.com', + 'br.search.yahoo.com', + 'br.yahoo.com', + 'cade.searchde.yahoo.com', + 'cade.yahoo.com', + 'chinese.searchinese.yahoo.com', + 'chinese.yahoo.com', + 'cn.search.yahoo.com', + 'cn.yahoo.com', + 'de.search.yahoo.com', + 'de.yahoo.com', + 'dk.search.yahoo.com', + 'dk.yahoo.com', + 'es.search.yahoo.com', + 'es.yahoo.com', + 'espanol.searchpanol.yahoo.com', + 'espanol.searchpanol.yahoo.com', + 'espanol.yahoo.com', + 'espanol.yahoo.com', + 'fr.search.yahoo.com', + 'fr.yahoo.com', + 'ie.search.yahoo.com', + 'ie.yahoo.com', + 'it.search.yahoo.com', + 'it.yahoo.com', + 'kr.search.yahoo.com', + 'kr.yahoo.com', + 'mx.search.yahoo.com', + 'mx.yahoo.com', + 'no.search.yahoo.com', + 'no.yahoo.com', + 'nz.search.yahoo.com', + 'nz.yahoo.com', + 'one.cn.yahoo.com', + 'one.searchn.yahoo.com', + 'qc.search.yahoo.com', + 'qc.search.yahoo.com', + 'qc.search.yahoo.com', + 'qc.yahoo.com', + 'qc.yahoo.com', + 'se.search.yahoo.com', + 'se.search.yahoo.com', + 'se.yahoo.com', + 'search.searcharch.yahoo.com', + 'search.yahoo.com', + 'uk.search.yahoo.com', + 'uk.yahoo.com', + 'www.yahoo.co.jp', + 'search.yahoo.co.jp', + 'www.cercato.it', + 'search.offerbox.com', + 'ys.mirostart.com', + ], + parameters: ['p', 'q'], + }, + 'URL.ORGanizier': { + domains: ['www.url.org'], + parameters: ['q'], + }, + Witch: { + domains: ['www.witch.de'], + parameters: ['search'], + }, + 'Mister Wong': { + domains: ['www.mister-wong.com', 'www.mister-wong.de'], + parameters: ['Keywords'], + }, + Aport: { + domains: ['sm.aport.ru'], + parameters: ['r'], + }, + 'Web.de': { + domains: ['suche.web.de'], + parameters: ['su'], + }, + Ask: { + domains: [ + 'ask.com', + 'www.ask.com', + 'web.ask.com', + 'int.ask.com', + 'mws.ask.com', + 'uk.ask.com', + 'images.ask.com', + 'ask.reference.com', + 'www.askkids.com', + 'iwon.ask.com', + 'www.ask.co.uk', + 'www.qbyrd.com', + 'search-results.com', + 'uk.search-results.com', + 'www.search-results.com', + 'int.search-results.com', + ], + parameters: ['q'], + }, + Centrum: { + domains: ['serach.centrum.cz', 'morfeo.centrum.cz'], + parameters: ['q'], + }, + Everyclick: { + domains: ['www.everyclick.com'], + parameters: ['keyword'], + }, + 'Google Video': { + domains: ['video.google.com'], + parameters: ['q'], + }, + Delfi: { + domains: ['otsing.delfi.ee'], + parameters: ['q'], + }, + blekko: { + domains: ['blekko.com'], + parameters: ['q'], + }, + Jyxo: { + domains: ['jyxo.1188.cz'], + parameters: ['q'], + }, + Kataweb: { + domains: ['www.kataweb.it'], + parameters: ['q'], + }, + 'uol.com.br': { + domains: ['busca.uol.com.br'], + parameters: ['q'], + }, + Arianna: { + domains: ['arianna.libero.it', 'www.arianna.com'], + parameters: ['query'], + }, + Mamma: { + domains: ['www.mamma.com', 'mamma75.mamma.com'], + parameters: ['query'], + }, + Yatedo: { + domains: ['www.yatedo.com', 'www.yatedo.fr'], + parameters: ['q'], + }, + Twingly: { + domains: ['www.twingly.com'], + parameters: ['q'], + }, + 'Delfi latvia': { + domains: ['smart.delfi.lv'], + parameters: ['q'], + }, + PriceRunner: { + domains: ['www.pricerunner.co.uk'], + parameters: ['q'], + }, + Rakuten: { + domains: ['websearch.rakuten.co.jp'], + parameters: ['qt'], + }, + Google: { + domains: [ + 'www.google.com', + 'www.google.ac', + 'www.google.ad', + 'www.google.com.af', + 'www.google.com.ag', + 'www.google.com.ai', + 'www.google.am', + 'www.google.it.ao', + 'www.google.com.ar', + 'www.google.as', + 'www.google.at', + 'www.google.com.au', + 'www.google.az', + 'www.google.ba', + 'www.google.com.bd', + 'www.google.be', + 'www.google.bf', + 'www.google.bg', + 'www.google.com.bh', + 'www.google.bi', + 'www.google.bj', + 'www.google.com.bn', + 'www.google.com.bo', + 'www.google.com.br', + 'www.google.bs', + 'www.google.co.bw', + 'www.google.com.by', + 'www.google.by', + 'www.google.com.bz', + 'www.google.ca', + 'www.google.com.kh', + 'www.google.cc', + 'www.google.cd', + 'www.google.cf', + 'www.google.cat', + 'www.google.cg', + 'www.google.ch', + 'www.google.ci', + 'www.google.co.ck', + 'www.google.cl', + 'www.google.cm', + 'www.google.cn', + 'www.google.com.co', + 'www.google.co.cr', + 'www.google.com.cu', + 'www.google.cv', + 'www.google.com.cy', + 'www.google.cz', + 'www.google.de', + 'www.google.dj', + 'www.google.dk', + 'www.google.dm', + 'www.google.com.do', + 'www.google.dz', + 'www.google.com.ec', + 'www.google.ee', + 'www.google.com.eg', + 'www.google.es', + 'www.google.com.et', + 'www.google.fi', + 'www.google.com.fj', + 'www.google.fm', + 'www.google.fr', + 'www.google.ga', + 'www.google.gd', + 'www.google.ge', + 'www.google.gf', + 'www.google.gg', + 'www.google.com.gh', + 'www.google.com.gi', + 'www.google.gl', + 'www.google.gm', + 'www.google.gp', + 'www.google.gr', + 'www.google.com.gt', + 'www.google.gy', + 'www.google.com.hk', + 'www.google.hn', + 'www.google.hr', + 'www.google.ht', + 'www.google.hu', + 'www.google.co.id', + 'www.google.iq', + 'www.google.ie', + 'www.google.co.il', + 'www.google.im', + 'www.google.co.in', + 'www.google.io', + 'www.google.is', + 'www.google.it', + 'www.google.je', + 'www.google.com.jm', + 'www.google.jo', + 'www.google.co.jp', + 'www.google.co.ke', + 'www.google.com.kh', + 'www.google.ki', + 'www.google.kg', + 'www.google.co.kr', + 'www.google.com.kw', + 'www.google.kz', + 'www.google.la', + 'www.google.com.lb', + 'www.google.com.lc', + 'www.google.li', + 'www.google.lk', + 'www.google.co.ls', + 'www.google.lt', + 'www.google.lu', + 'www.google.lv', + 'www.google.com.ly', + 'www.google.co.ma', + 'www.google.md', + 'www.google.me', + 'www.google.mg', + 'www.google.mk', + 'www.google.ml', + 'www.google.mn', + 'www.google.ms', + 'www.google.com.mt', + 'www.google.mu', + 'www.google.mv', + 'www.google.mw', + 'www.google.com.mx', + 'www.google.com.my', + 'www.google.co.mz', + 'www.google.com.na', + 'www.google.ne', + 'www.google.com.nf', + 'www.google.com.ng', + 'www.google.com.ni', + 'www.google.nl', + 'www.google.no', + 'www.google.com.np', + 'www.google.nr', + 'www.google.nu', + 'www.google.co.nz', + 'www.google.com.om', + 'www.google.com.pa', + 'www.google.com.pe', + 'www.google.com.ph', + 'www.google.com.pk', + 'www.google.pl', + 'www.google.pn', + 'www.google.com.pr', + 'www.google.ps', + 'www.google.pt', + 'www.google.com.py', + 'www.google.com.qa', + 'www.google.ro', + 'www.google.rs', + 'www.google.ru', + 'www.google.rw', + 'www.google.com.sa', + 'www.google.com.sb', + 'www.google.sc', + 'www.google.se', + 'www.google.com.sg', + 'www.google.sh', + 'www.google.si', + 'www.google.sk', + 'www.google.com.sl', + 'www.google.sn', + 'www.google.sm', + 'www.google.so', + 'www.google.st', + 'www.google.com.sv', + 'www.google.td', + 'www.google.tg', + 'www.google.co.th', + 'www.google.com.tj', + 'www.google.tk', + 'www.google.tl', + 'www.google.tm', + 'www.google.to', + 'www.google.com.tn', + 'www.google.com.tr', + 'www.google.tt', + 'www.google.com.tw', + 'www.google.co.tz', + 'www.google.com.ua', + 'www.google.co.ug', + 'www.google.ae', + 'www.google.co.uk', + 'www.google.us', + 'www.google.com.uy', + 'www.google.co.uz', + 'www.google.com.vc', + 'www.google.co.ve', + 'www.google.vg', + 'www.google.co.vi', + 'www.google.com.vn', + 'www.google.vu', + 'www.google.ws', + 'www.google.co.za', + 'www.google.co.zm', + 'www.google.co.zw', + 'google.com', + 'google.ac', + 'google.ad', + 'google.com.af', + 'google.com.ag', + 'google.com.ai', + 'google.am', + 'google.it.ao', + 'google.com.ar', + 'google.as', + 'google.at', + 'google.com.au', + 'google.az', + 'google.ba', + 'google.com.bd', + 'google.be', + 'google.bf', + 'google.bg', + 'google.com.bh', + 'google.bi', + 'google.bj', + 'google.com.bn', + 'google.com.bo', + 'google.com.br', + 'google.bs', + 'google.co.bw', + 'google.com.by', + 'google.by', + 'google.com.bz', + 'google.ca', + 'google.com.kh', + 'google.cc', + 'google.cd', + 'google.cf', + 'google.cat', + 'google.cg', + 'google.ch', + 'google.ci', + 'google.co.ck', + 'google.cl', + 'google.cm', + 'google.cn', + 'google.com.co', + 'google.co.cr', + 'google.com.cu', + 'google.cv', + 'google.com.cy', + 'google.cz', + 'google.de', + 'google.dj', + 'google.dk', + 'google.dm', + 'google.com.do', + 'google.dz', + 'google.com.ec', + 'google.ee', + 'google.com.eg', + 'google.es', + 'google.com.et', + 'google.fi', + 'google.com.fj', + 'google.fm', + 'google.fr', + 'google.ga', + 'google.gd', + 'google.ge', + 'google.gf', + 'google.gg', + 'google.com.gh', + 'google.com.gi', + 'google.gl', + 'google.gm', + 'google.gp', + 'google.gr', + 'google.com.gt', + 'google.gy', + 'google.com.hk', + 'google.hn', + 'google.hr', + 'google.ht', + 'google.hu', + 'google.co.id', + 'google.iq', + 'google.ie', + 'google.co.il', + 'google.im', + 'google.co.in', + 'google.io', + 'google.is', + 'google.it', + 'google.je', + 'google.com.jm', + 'google.jo', + 'google.co.jp', + 'google.co.ke', + 'google.com.kh', + 'google.ki', + 'google.kg', + 'google.co.kr', + 'google.com.kw', + 'google.kz', + 'google.la', + 'google.com.lb', + 'google.com.lc', + 'google.li', + 'google.lk', + 'google.co.ls', + 'google.lt', + 'google.lu', + 'google.lv', + 'google.com.ly', + 'google.co.ma', + 'google.md', + 'google.me', + 'google.mg', + 'google.mk', + 'google.ml', + 'google.mn', + 'google.ms', + 'google.com.mt', + 'google.mu', + 'google.mv', + 'google.mw', + 'google.com.mx', + 'google.com.my', + 'google.co.mz', + 'google.com.na', + 'google.ne', + 'google.com.nf', + 'google.com.ng', + 'google.com.ni', + 'google.nl', + 'google.no', + 'google.com.np', + 'google.nr', + 'google.nu', + 'google.co.nz', + 'google.com.om', + 'google.com.pa', + 'google.com.pe', + 'google.com.ph', + 'google.com.pk', + 'google.pl', + 'google.pn', + 'google.com.pr', + 'google.ps', + 'google.pt', + 'google.com.py', + 'google.com.qa', + 'google.ro', + 'google.rs', + 'google.ru', + 'google.rw', + 'google.com.sa', + 'google.com.sb', + 'google.sc', + 'google.se', + 'google.com.sg', + 'google.sh', + 'google.si', + 'google.sk', + 'google.com.sl', + 'google.sn', + 'google.sm', + 'google.so', + 'google.st', + 'google.com.sv', + 'google.td', + 'google.tg', + 'google.co.th', + 'google.com.tj', + 'google.tk', + 'google.tl', + 'google.tm', + 'google.to', + 'google.com.tn', + 'google.com.tr', + 'google.tt', + 'google.com.tw', + 'google.co.tz', + 'google.com.ua', + 'google.co.ug', + 'google.ae', + 'google.co.uk', + 'google.us', + 'google.com.uy', + 'google.co.uz', + 'google.com.vc', + 'google.co.ve', + 'google.vg', + 'google.co.vi', + 'google.com.vn', + 'google.vu', + 'google.ws', + 'google.co.za', + 'google.co.zm', + 'google.co.zw', + 'search.avg.com', + 'isearch.avg.com', + 'www.cnn.com', + 'darkoogle.com', + 'search.darkoogle.com', + 'search.foxtab.com', + 'www.gooofullsearch.com', + 'search.hiyo.com', + 'search.incredimail.com', + 'search1.incredimail.com', + 'search2.incredimail.com', + 'search3.incredimail.com', + 'search4.incredimail.com', + 'search.incredibar.com', + 'search.sweetim.com', + 'www.fastweb.it', + 'search.juno.com', + 'find.tdc.dk', + 'searchresults.verizon.com', + 'search.walla.co.il', + 'search.alot.com', + 'www.googleearth.de', + 'www.googleearth.fr', + 'webcache.googleusercontent.com', + 'encrypted.google.com', + 'googlesyndicatedsearch.com', + ], + parameters: ['q', 'query', 'Keywords'], + }, + 'Google Blogsearch': { + domains: [ + 'blogsearch.google.ac', + 'blogsearch.google.ad', + 'blogsearch.google.ae', + 'blogsearch.google.am', + 'blogsearch.google.as', + 'blogsearch.google.at', + 'blogsearch.google.az', + 'blogsearch.google.ba', + 'blogsearch.google.be', + 'blogsearch.google.bf', + 'blogsearch.google.bg', + 'blogsearch.google.bi', + 'blogsearch.google.bj', + 'blogsearch.google.bs', + 'blogsearch.google.by', + 'blogsearch.google.ca', + 'blogsearch.google.cat', + 'blogsearch.google.cc', + 'blogsearch.google.cd', + 'blogsearch.google.cf', + 'blogsearch.google.cg', + 'blogsearch.google.ch', + 'blogsearch.google.ci', + 'blogsearch.google.cl', + 'blogsearch.google.cm', + 'blogsearch.google.cn', + 'blogsearch.google.co.bw', + 'blogsearch.google.co.ck', + 'blogsearch.google.co.cr', + 'blogsearch.google.co.id', + 'blogsearch.google.co.il', + 'blogsearch.google.co.in', + 'blogsearch.google.co.jp', + 'blogsearch.google.co.ke', + 'blogsearch.google.co.kr', + 'blogsearch.google.co.ls', + 'blogsearch.google.co.ma', + 'blogsearch.google.co.mz', + 'blogsearch.google.co.nz', + 'blogsearch.google.co.th', + 'blogsearch.google.co.tz', + 'blogsearch.google.co.ug', + 'blogsearch.google.co.uk', + 'blogsearch.google.co.uz', + 'blogsearch.google.co.ve', + 'blogsearch.google.co.vi', + 'blogsearch.google.co.za', + 'blogsearch.google.co.zm', + 'blogsearch.google.co.zw', + 'blogsearch.google.com', + 'blogsearch.google.com.af', + 'blogsearch.google.com.ag', + 'blogsearch.google.com.ai', + 'blogsearch.google.com.ar', + 'blogsearch.google.com.au', + 'blogsearch.google.com.bd', + 'blogsearch.google.com.bh', + 'blogsearch.google.com.bn', + 'blogsearch.google.com.bo', + 'blogsearch.google.com.br', + 'blogsearch.google.com.by', + 'blogsearch.google.com.bz', + 'blogsearch.google.com.co', + 'blogsearch.google.com.cu', + 'blogsearch.google.com.cy', + 'blogsearch.google.com.do', + 'blogsearch.google.com.ec', + 'blogsearch.google.com.eg', + 'blogsearch.google.com.et', + 'blogsearch.google.com.fj', + 'blogsearch.google.com.gh', + 'blogsearch.google.com.gi', + 'blogsearch.google.com.gt', + 'blogsearch.google.com.hk', + 'blogsearch.google.com.jm', + 'blogsearch.google.com.kh', + 'blogsearch.google.com.kh', + 'blogsearch.google.com.kw', + 'blogsearch.google.com.lb', + 'blogsearch.google.com.lc', + 'blogsearch.google.com.ly', + 'blogsearch.google.com.mt', + 'blogsearch.google.com.mx', + 'blogsearch.google.com.my', + 'blogsearch.google.com.na', + 'blogsearch.google.com.nf', + 'blogsearch.google.com.ng', + 'blogsearch.google.com.ni', + 'blogsearch.google.com.np', + 'blogsearch.google.com.om', + 'blogsearch.google.com.pa', + 'blogsearch.google.com.pe', + 'blogsearch.google.com.ph', + 'blogsearch.google.com.pk', + 'blogsearch.google.com.pr', + 'blogsearch.google.com.py', + 'blogsearch.google.com.qa', + 'blogsearch.google.com.sa', + 'blogsearch.google.com.sb', + 'blogsearch.google.com.sg', + 'blogsearch.google.com.sl', + 'blogsearch.google.com.sv', + 'blogsearch.google.com.tj', + 'blogsearch.google.com.tn', + 'blogsearch.google.com.tr', + 'blogsearch.google.com.tw', + 'blogsearch.google.com.ua', + 'blogsearch.google.com.uy', + 'blogsearch.google.com.vc', + 'blogsearch.google.com.vn', + 'blogsearch.google.cv', + 'blogsearch.google.cz', + 'blogsearch.google.de', + 'blogsearch.google.dj', + 'blogsearch.google.dk', + 'blogsearch.google.dm', + 'blogsearch.google.dz', + 'blogsearch.google.ee', + 'blogsearch.google.es', + 'blogsearch.google.fi', + 'blogsearch.google.fm', + 'blogsearch.google.fr', + 'blogsearch.google.ga', + 'blogsearch.google.gd', + 'blogsearch.google.ge', + 'blogsearch.google.gf', + 'blogsearch.google.gg', + 'blogsearch.google.gl', + 'blogsearch.google.gm', + 'blogsearch.google.gp', + 'blogsearch.google.gr', + 'blogsearch.google.gy', + 'blogsearch.google.hn', + 'blogsearch.google.hr', + 'blogsearch.google.ht', + 'blogsearch.google.hu', + 'blogsearch.google.ie', + 'blogsearch.google.im', + 'blogsearch.google.io', + 'blogsearch.google.iq', + 'blogsearch.google.is', + 'blogsearch.google.it', + 'blogsearch.google.it.ao', + 'blogsearch.google.je', + 'blogsearch.google.jo', + 'blogsearch.google.kg', + 'blogsearch.google.ki', + 'blogsearch.google.kz', + 'blogsearch.google.la', + 'blogsearch.google.li', + 'blogsearch.google.lk', + 'blogsearch.google.lt', + 'blogsearch.google.lu', + 'blogsearch.google.lv', + 'blogsearch.google.md', + 'blogsearch.google.me', + 'blogsearch.google.mg', + 'blogsearch.google.mk', + 'blogsearch.google.ml', + 'blogsearch.google.mn', + 'blogsearch.google.ms', + 'blogsearch.google.mu', + 'blogsearch.google.mv', + 'blogsearch.google.mw', + 'blogsearch.google.ne', + 'blogsearch.google.nl', + 'blogsearch.google.no', + 'blogsearch.google.nr', + 'blogsearch.google.nu', + 'blogsearch.google.pl', + 'blogsearch.google.pn', + 'blogsearch.google.ps', + 'blogsearch.google.pt', + 'blogsearch.google.ro', + 'blogsearch.google.rs', + 'blogsearch.google.ru', + 'blogsearch.google.rw', + 'blogsearch.google.sc', + 'blogsearch.google.se', + 'blogsearch.google.sh', + 'blogsearch.google.si', + 'blogsearch.google.sk', + 'blogsearch.google.sm', + 'blogsearch.google.sn', + 'blogsearch.google.so', + 'blogsearch.google.st', + 'blogsearch.google.td', + 'blogsearch.google.tg', + 'blogsearch.google.tk', + 'blogsearch.google.tl', + 'blogsearch.google.tm', + 'blogsearch.google.to', + 'blogsearch.google.tt', + 'blogsearch.google.us', + 'blogsearch.google.vg', + 'blogsearch.google.vu', + 'blogsearch.google.ws', + ], + parameters: ['q'], + }, + 'Hooseek.com': { + domains: ['www.hooseek.com'], + parameters: ['recherche'], + }, + Dalesearch: { + domains: ['www.dalesearch.com'], + parameters: ['q'], + }, + 'Alice Adsl': { + domains: ['rechercher.aliceadsl.fr'], + parameters: ['q'], + }, + 'T-Online': { + domains: ['suche.t-online.de', 'brisbane.t-online.de', 'navigationshilfe.t-online.de'], + parameters: ['q'], + }, + 'soso.com': { + domains: ['www.soso.com'], + parameters: ['w'], + }, + Sogou: { + domains: ['www.sougou.com'], + parameters: ['query'], + }, + 'Hit-Parade': { + domains: ['req.-hit-parade.com', 'class.hit-parade.com', 'www.hit-parade.com'], + parameters: ['p7'], + }, + SearchCanvas: { + domains: ['www.searchcanvas.com'], + parameters: ['q'], + }, + Interia: { + domains: ['www.google.interia.pl'], + parameters: ['q'], + }, + Genieo: { + domains: ['search.genieo.com'], + parameters: ['q'], + }, + Tiscali: { + domains: ['search.tiscali.it', 'search-dyn.tiscali.it', 'hledani.tiscali.cz'], + parameters: ['q', 'key'], + }, + Clix: { + domains: ['pesquisa.clix.pt'], + parameters: ['question'], + }, + }, + email: { + Bigpond: { + domains: ['webmail.bigpond.com', 'webmail2.bigpond.com'], + }, + 'Naver Mail': { + domains: ['mail.naver.com'], + }, + 'Optus Zoo': { + domains: ['webmail.optuszoo.com.au'], + }, + 'Seznam Mail': { + domains: ['email.seznam.cz'], + }, + '126 Mail': { + domains: ['mail.126.com'], + }, + 'Outlook.com': { + domains: ['mail.live.com'], + }, + 'AOL Mail': { + domains: ['mail.aol.com'], + }, + 'Daum Mail': { + domains: ['mail2.daum.net'], + }, + 'Yahoo! Mail': { + domains: ['mail.yahoo.net', 'mail.yahoo.com', 'mail.yahoo.co.uk', 'mail.yahoo.co.jp'], + }, + '163 Mail': { + domains: ['mail.163.com'], + }, + 'Orange Webmail': { + domains: ['orange.fr/webmail'], + }, + 'QQ Mail': { + domains: ['mail.qq.com'], + }, + 'Mynet Mail': { + domains: ['mail.mynet.com'], + }, + Gmail: { + domains: ['mail.google.com'], + }, + }, + social: { + hi5: { + domains: ['hi5.com'], + }, + Friendster: { + domains: ['friendster.com'], + }, + Weibo: { + domains: ['weibo.com', 't.cn'], + }, + Xanga: { + domains: ['xanga.com'], + }, + Myspace: { + domains: ['myspace.com'], + }, + Buzznet: { + domains: ['wayn.com'], + }, + MyLife: { + domains: ['mylife.ru'], + }, + Flickr: { + domains: ['flickr.com'], + }, + 'Sonico.com': { + domains: ['sonico.com'], + }, + Odnoklassniki: { + domains: ['odnoklassniki.ru'], + }, + GitHub: { + domains: ['github.com'], + }, + Classmates: { + domains: ['classmates.com'], + }, + 'Friends Reunited': { + domains: ['friendsreunited.com'], + }, + Renren: { + domains: ['renren.com'], + }, + Quora: { + domains: ['quora.com'], + }, + 'Gaia Online': { + domains: ['gaiaonline.com'], + }, + Netlog: { + domains: ['netlog.com'], + }, + Orkut: { + domains: ['orkut.com'], + }, + MyHeritage: { + domains: ['myheritage.com'], + }, + Multiply: { + domains: ['multiply.com'], + }, + myYearbook: { + domains: ['myyearbook.com'], + }, + WeeWorld: { + domains: ['weeworld.com'], + }, + Vimeo: { + domains: ['vimeo.com'], + }, + 'Eksi Sozluk': { + domains: ['Sozluk.com', 'sourtimes.org'], + }, + Geni: { + domains: ['geni.com'], + }, + 'Uludag Sozluk': { + domains: ['uludagsozluk.com', 'ulusozluk.com'], + }, + SourceForge: { + domains: ['sourceforge.net'], + }, + Plaxo: { + domains: ['plaxo.com'], + }, + 'Taringa!': { + domains: ['taringa.net'], + }, + Tagged: { + domains: ['login.tagged.com'], + }, + XING: { + domains: ['xing.com'], + }, + Instagram: { + domains: ['instagram.com'], + }, + Vkontakte: { + domains: ['vk.com', 'vkontakte.ru'], + }, + Twitter: { + domains: ['twitter.com', 't.co'], + }, + 'vKruguDruzei.ru': { + domains: ['vkrugudruzei.ru'], + }, + Donanimhaber: { + domains: ['donanimhaber.com'], + }, + WAYN: { + domains: ['wayn.com'], + }, + Tuenti: { + domains: ['tuenti.com'], + }, + 'Mail.ru': { + domains: ['my.mail.ru'], + }, + Badoo: { + domains: ['badoo.com'], + }, + Instela: { + domains: ['instela.com'], + }, + Habbo: { + domains: ['habbo.com'], + }, + Pinterest: { + domains: ['pinterest.com'], + }, + LinkedIn: { + domains: ['linkedin.com', 'lnkd.in'], + }, + Foursquare: { + domains: ['foursquare.com'], + }, + Flixster: { + domains: ['flixster.com'], + }, + 'Windows Live Spaces': { + domains: ['login.live.com'], + }, + BlackPlanet: { + domains: ['blackplanet.com'], + }, + Cyworld: { + domains: ['global.cyworld.com'], + }, + Pocket: { + domains: ['itusozluk.com'], + }, + Skyrock: { + domains: ['skyrock.com'], + }, + Facebook: { + domains: ['facebook.com', 'fb.me', 'm.facebook.com', 'l.facebook.com', 'lm.facebook.com'], + }, + Disqus: { + domains: ['redirect.disqus.com', 'disq.us', 'disqus.com'], + }, + StudiVZ: { + domains: ['studivz.net'], + }, + Fotolog: { + domains: ['fotolog.com'], + }, + 'Google+': { + domains: ['url.google.com', 'plus.google.com'], + }, + 'Nasza-klasa.pl': { + domains: ['nk.pl'], + }, + Qzone: { + domains: ['qzone.qq.com'], + }, + Douban: { + domains: ['douban.com'], + }, + Bebo: { + domains: ['bebo.com'], + }, + Youtube: { + domains: ['youtube.com', 'youtu.be'], + }, + Reddit: { + domains: ['reddit.com'], + }, + 'Identi.ca': { + domains: ['identi.ca'], + }, + StackOverflow: { + domains: ['stackoverflow.com'], + }, + Mixi: { + domains: ['mixi.jp'], + }, + StumbleUpon: { + domains: ['stumbleupon.com'], + }, + 'Inci Sozluk': { + domains: ['inci.sozlukspot.com', 'incisozluk.com', 'incisozluk.cc'], + }, + Viadeo: { + domains: ['viadeo.com'], + }, + 'Last.fm': { + domains: ['lastfm.ru'], + }, + LiveJournal: { + domains: ['livejournal.ru'], + }, + Tumblr: { + domains: ['tumblr.com'], + }, + 'Hacker News': { + domains: ['news.ycombinator.com'], + }, + 'Hocam.com': { + domains: ['hocam.com'], + }, + Delicious: { + domains: ['delicious.com'], + }, + Hyves: { + domains: ['hyves.nl'], + }, + 'Paper.li': { + domains: ['paper.li'], + }, + 'MoiKrug.ru': { + domains: ['moikrug.ru'], + }, + }, + review: { + Capterra: { + domains: ['capterra.com'], + }, + G2: { + domains: ['g2.com'], + }, + SoftwarAadvice: { + domains: ['softwareadvice.com'], + }, + GetApp: { + domains: ['getapp.com'], + }, + }, +} + +const directReferrerDomains = [ + 'getjoan.com', + 'offer.getjoan.com', + 'office.getjoan.com', + 'portal.getjoan.com', + 'blog.getjoan.com', + 'support.getjoan.com', +] + +export const posthogSnowplowRefererParser: LegacyTransformationPlugin = { + id: 'plugin-posthog-plugin-snowplow-referer-parser', + processEvent, +} diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-plugin-snowplow-referer-parser/plugin.json b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-plugin-snowplow-referer-parser/plugin.json new file mode 100644 index 0000000000000..af844c3a3a0a6 --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-plugin-snowplow-referer-parser/plugin.json @@ -0,0 +1,16 @@ +{ + "name": "UTM Referrer", + "config": [ + { + "markdown": "Specify your config here" + }, + { + "key": "internal_domains", + "name": "Internal domains (comma delimited)", + "type": "string", + "hint": "Consider these domains as direct referrers. Example: `example.com,blog.example.com`", + "default": "", + "required": false + } + ] +} diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-plugin-snowplow-referer-parser/template.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-plugin-snowplow-referer-parser/template.ts new file mode 100644 index 0000000000000..4021ed2182bfe --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-plugin-snowplow-referer-parser/template.ts @@ -0,0 +1,23 @@ +import { HogFunctionTemplate } from '../../../templates/types' + +// NOTE: This is a deprecated plugin and should never be shown to new users +export const template: HogFunctionTemplate = { + status: 'alpha', + type: 'transformation', + id: 'plugin-posthog-snowplow-referer-parser', + name: 'UTM Referrer', + description: '', + icon_url: '/static/hedgehog/builder-hog-01.png', + category: ['Custom'], + hog: `return event`, + inputs_schema: [ + { + key: 'internal_domains', + label: 'Internal domains (comma delimited)', + type: 'string', + description: 'Consider these domains as direct referrers. Example: `example.com,blog.example.com`', + default: '', + required: false, + }, + ], +} diff --git a/plugin-server/src/cdp/legacy-plugins/index.ts b/plugin-server/src/cdp/legacy-plugins/index.ts index 73c6e235afe0a..d10c861a004d2 100644 --- a/plugin-server/src/cdp/legacy-plugins/index.ts +++ b/plugin-server/src/cdp/legacy-plugins/index.ts @@ -4,6 +4,7 @@ import { downsamplingPlugin } from './_transformations/downsampling-plugin' import { dropEventsOnPropertyPlugin } from './_transformations/drop-events-on-property-plugin' import { flattenPropertiesPlugin } from './_transformations/flatten-properties-plugin' import { languageUrlSplitterApp } from './_transformations/language-url-splitter-app' +import { phShotgunProcessEventApp } from './_transformations/ph-shotgun-processevent-app' import { pluginAdvancedGeoip } from './_transformations/plugin-advanced-geoip' import { posthogNetdataEventProcessingPlugin } from './_transformations/plugin-netdata-event-processing' import { pluginStonlyCleanCampaignName } from './_transformations/Plugin-Stonly-Clean-Campaign-Name' @@ -12,6 +13,7 @@ import { posthogAppUnduplicator } from './_transformations/posthog-app-unduplica import { posthogAppUrlParametersToEventPropertiesPlugin } from './_transformations/posthog-app-url-parameters-to-event-properties' import { posthogFilterOutPlugin } from './_transformations/posthog-filter-out-plugin' import { posthogPluginGeoip } from './_transformations/posthog-plugin-geoip' +import { posthogSnowplowRefererParser } from './_transformations/posthog-plugin-snowplow-referer-parser' import { posthogRouteCensorPlugin } from './_transformations/posthog-route-censor-plugin' import { posthogUrlNormalizerPlugin } from './_transformations/posthog-url-normalizer-plugin' import { propertyFilterPlugin } from './_transformations/property-filter-plugin' @@ -48,4 +50,6 @@ export const DEPRECATED_TRANSFORMATION_PLUGINS_BY_ID = { [posthogPluginGeoip.id]: posthogPluginGeoip, [posthogRouteCensorPlugin.id]: posthogRouteCensorPlugin, [posthogNetdataEventProcessingPlugin.id]: posthogNetdataEventProcessingPlugin, + [phShotgunProcessEventApp.id]: phShotgunProcessEventApp, + [posthogSnowplowRefererParser.id]: posthogSnowplowRefererParser, } diff --git a/plugin-server/src/cdp/legacy-plugins/types.ts b/plugin-server/src/cdp/legacy-plugins/types.ts index 662fc302e2000..f230e15c4bbaf 100644 --- a/plugin-server/src/cdp/legacy-plugins/types.ts +++ b/plugin-server/src/cdp/legacy-plugins/types.ts @@ -9,12 +9,18 @@ export type LegacyPluginLogger = { error: (...args: any[]) => void } -export type LegacyTransformationPluginMeta = { +export type LegacyPluginMeta = { config: Record global: Record logger: LegacyPluginLogger } +export type LegacyTransformationPluginMeta = LegacyPluginMeta & { + geoip: { + locate: (ipAddress: string) => Record + } +} + export type LegacyDestinationPluginMeta = LegacyTransformationPluginMeta & { fetch: (...args: Parameters) => Promise } From 22d475fa0a36241a1f3980b99968316a653d75ee Mon Sep 17 00:00:00 2001 From: Ben White Date: Thu, 30 Jan 2025 16:35:08 +0100 Subject: [PATCH 134/163] Fixes --- .../index.test.ts | 235 +++++++++--------- 1 file changed, 114 insertions(+), 121 deletions(-) diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-plugin-snowplow-referer-parser/index.test.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-plugin-snowplow-referer-parser/index.test.ts index bb2d81c6f3ab6..b58563e6981e5 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-plugin-snowplow-referer-parser/index.test.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-plugin-snowplow-referer-parser/index.test.ts @@ -1,187 +1,180 @@ -import { processEvent } from "."; +import { LegacyTransformationPluginMeta } from '../../types' +import { processEvent } from '.' const demoEvents = [ { - event: "$pageview", + event: '$pageview', properties: { - $os: "Linux", - $browser: "Safari", - $device_type: "Desktop", - $current_url: - "https://office.getjoan.com/device/shareable/1009dea9-5f95-4896-95ec-a12d3397b080", - $host: "office.getjoan.com", - $pathname: "/device/shareable/1009dea9-5f95-4896-95ec-a12d3397b080", + $os: 'Linux', + $browser: 'Safari', + $device_type: 'Desktop', + $current_url: 'https://office.getjoan.com/device/shareable/1009dea9-5f95-4896-95ec-a12d3397b080', + $host: 'office.getjoan.com', + $pathname: '/device/shareable/1009dea9-5f95-4896-95ec-a12d3397b080', $browser_version: 16, $screen_height: 1080, $screen_width: 1920, $viewport_height: 1080, $viewport_width: 1920, - $lib: "web", - $lib_version: "1.30.0", - $insert_id: "cp1wsa5ddrwuittb", + $lib: 'web', + $lib_version: '1.30.0', + $insert_id: 'cp1wsa5ddrwuittb', $time: 1671459506.901, - distinct_id: "18528f8e529264-066928753d330c8-c6a5a43-fa00-18528f8e52bb52", - $device_id: "18528f8e529264-066928753d330c8-c6a5a43-fa00-18528f8e52bb52", - $referrer: "$direct", - $referring_domain: "$direct", + distinct_id: '18528f8e529264-066928753d330c8-c6a5a43-fa00-18528f8e52bb52', + $device_id: '18528f8e529264-066928753d330c8-c6a5a43-fa00-18528f8e52bb52', + $referrer: '$direct', + $referring_domain: '$direct', $active_feature_flags: [], - token: "phc_gE7SWBNBgFbA4eQ154KPXebyB8KyLJuypR8jg1DSo9Z", - $session_id: "18528f8e5361b6d-07f85cfe92fe198-c6a5a43-fa00-18528f8e53714dd", - $window_id: "1852ac00ad0a8d-03577d57d3a7a08-c6a5a43-1fa400-1852ac00ad159b", + token: 'phc_gE7SWBNBgFbA4eQ154KPXebyB8KyLJuypR8jg1DSo9Z', + $session_id: '18528f8e5361b6d-07f85cfe92fe198-c6a5a43-fa00-18528f8e53714dd', + $window_id: '1852ac00ad0a8d-03577d57d3a7a08-c6a5a43-1fa400-1852ac00ad159b', $set_once: { - $initial_os: "Linux", - $initial_browser: "Safari", - $initial_device_type: "Desktop", + $initial_os: 'Linux', + $initial_browser: 'Safari', + $initial_device_type: 'Desktop', $initial_current_url: - "https://office.getjoan.com/device/shareable/1009dea9-5f95-4896-95ec-a12d3397b080", - $initial_pathname: - "/device/shareable/1009dea9-5f95-4896-95ec-a12d3397b080", + 'https://office.getjoan.com/device/shareable/1009dea9-5f95-4896-95ec-a12d3397b080', + $initial_pathname: '/device/shareable/1009dea9-5f95-4896-95ec-a12d3397b080', $initial_browser_version: 16, - $initial_referrer: "$direct", - $initial_referring_domain: "$direct", + $initial_referrer: '$direct', + $initial_referring_domain: '$direct', }, - $geoip_city_name: "Brussels", - $geoip_country_name: "Belgium", - $geoip_country_code: "BE", - $geoip_continent_name: "Europe", - $geoip_continent_code: "EU", - $geoip_postal_code: "1000", + $geoip_city_name: 'Brussels', + $geoip_country_name: 'Belgium', + $geoip_country_code: 'BE', + $geoip_continent_name: 'Europe', + $geoip_continent_code: 'EU', + $geoip_postal_code: '1000', $geoip_latitude: 50.8534, $geoip_longitude: 4.347, - $geoip_time_zone: "Europe/Brussels", - $geoip_subdivision_1_code: "BRU", - $geoip_subdivision_1_name: "Brussels Capital", + $geoip_time_zone: 'Europe/Brussels', + $geoip_subdivision_1_code: 'BRU', + $geoip_subdivision_1_name: 'Brussels Capital', }, - timestamp: "2022-12-19T14:18:26.902Z", - uuid: "01852ac0-0e96-0000-b3b1-d6c1b135c103", - distinct_id: "18528f8e529264-066928753d330c8-c6a5a43-fa00-18528f8e52bb52", - ip: "84.198.172.247", - site_url: "https://appdata.vnct.xyz", + timestamp: '2022-12-19T14:18:26.902Z', + uuid: '01852ac0-0e96-0000-b3b1-d6c1b135c103', + distinct_id: '18528f8e529264-066928753d330c8-c6a5a43-fa00-18528f8e52bb52', + ip: '84.198.172.247', + site_url: 'https://appdata.vnct.xyz', team_id: 2, - now: "2022-12-19T14:18:27.859614+00:00", - sent_at: "2022-12-19T14:18:26.905000+00:00", - token: "phc_gE7SWBNBgFbA4eQ154KPXebyB8KyLJuypR8jg1DSo9Z", + now: '2022-12-19T14:18:27.859614+00:00', + sent_at: '2022-12-19T14:18:26.905000+00:00', + token: 'phc_gE7SWBNBgFbA4eQ154KPXebyB8KyLJuypR8jg1DSo9Z', }, { - event: "SOME/custom_event", + event: 'SOME/custom_event', properties: { - $os: "Linux", - $browser: "Safari", - $device_type: "Desktop", - $current_url: - "https://office.getjoan.com/device/shareable/1009dea9-5f95-4896-95ec-a12d3397b080", - $host: "office.getjoan.com", - $pathname: "/device/shareable/1009dea9-5f95-4896-95ec-a12d3397b080", + $os: 'Linux', + $browser: 'Safari', + $device_type: 'Desktop', + $current_url: 'https://office.getjoan.com/device/shareable/1009dea9-5f95-4896-95ec-a12d3397b080', + $host: 'office.getjoan.com', + $pathname: '/device/shareable/1009dea9-5f95-4896-95ec-a12d3397b080', $browser_version: 16, $screen_height: 1080, $screen_width: 1920, $viewport_height: 1080, $viewport_width: 1920, - $lib: "web", - $lib_version: "1.30.0", - $insert_id: "cp1wsa5ddrwuittb", + $lib: 'web', + $lib_version: '1.30.0', + $insert_id: 'cp1wsa5ddrwuittb', $time: 1671459506.901, - distinct_id: "18528f8e529264-066928753d330c8-c6a5a43-fa00-18528f8e52bb52", - $device_id: "18528f8e529264-066928753d330c8-c6a5a43-fa00-18528f8e52bb52", - $referrer: "$direct", - $referring_domain: "$direct", + distinct_id: '18528f8e529264-066928753d330c8-c6a5a43-fa00-18528f8e52bb52', + $device_id: '18528f8e529264-066928753d330c8-c6a5a43-fa00-18528f8e52bb52', + $referrer: '$direct', + $referring_domain: '$direct', $active_feature_flags: [], - token: "phc_gE7SWBNBgFbA4eQ154KPXebyB8KyLJuypR8jg1DSo9Z", - $session_id: "18528f8e5361b6d-07f85cfe92fe198-c6a5a43-fa00-18528f8e53714dd", - $window_id: "1852ac00ad0a8d-03577d57d3a7a08-c6a5a43-1fa400-1852ac00ad159b", + token: 'phc_gE7SWBNBgFbA4eQ154KPXebyB8KyLJuypR8jg1DSo9Z', + $session_id: '18528f8e5361b6d-07f85cfe92fe198-c6a5a43-fa00-18528f8e53714dd', + $window_id: '1852ac00ad0a8d-03577d57d3a7a08-c6a5a43-1fa400-1852ac00ad159b', $set_once: { - $initial_os: "Linux", - $initial_browser: "Safari", - $initial_device_type: "Desktop", + $initial_os: 'Linux', + $initial_browser: 'Safari', + $initial_device_type: 'Desktop', $initial_current_url: - "https://office.getjoan.com/device/shareable/1009dea9-5f95-4896-95ec-a12d3397b080", - $initial_pathname: - "/device/shareable/1009dea9-5f95-4896-95ec-a12d3397b080", + 'https://office.getjoan.com/device/shareable/1009dea9-5f95-4896-95ec-a12d3397b080', + $initial_pathname: '/device/shareable/1009dea9-5f95-4896-95ec-a12d3397b080', $initial_browser_version: 16, - $initial_referrer: "$direct", - $initial_referring_domain: "$direct", + $initial_referrer: '$direct', + $initial_referring_domain: '$direct', }, - $geoip_city_name: "Brussels", - $geoip_country_name: "Belgium", - $geoip_country_code: "BE", - $geoip_continent_name: "Europe", - $geoip_continent_code: "EU", - $geoip_postal_code: "1000", + $geoip_city_name: 'Brussels', + $geoip_country_name: 'Belgium', + $geoip_country_code: 'BE', + $geoip_continent_name: 'Europe', + $geoip_continent_code: 'EU', + $geoip_postal_code: '1000', $geoip_latitude: 50.8534, $geoip_longitude: 4.347, - $geoip_time_zone: "Europe/Brussels", - $geoip_subdivision_1_code: "BRU", - $geoip_subdivision_1_name: "Brussels Capital", + $geoip_time_zone: 'Europe/Brussels', + $geoip_subdivision_1_code: 'BRU', + $geoip_subdivision_1_name: 'Brussels Capital', }, - timestamp: "2022-12-19T14:18:26.902Z", - uuid: "01852ac0-0e96-0000-b3b1-d6c1b135c103", - distinct_id: "18528f8e529264-066928753d330c8-c6a5a43-fa00-18528f8e52bb52", - ip: "84.198.172.247", - site_url: "https://appdata.vnct.xyz", + timestamp: '2022-12-19T14:18:26.902Z', + uuid: '01852ac0-0e96-0000-b3b1-d6c1b135c103', + distinct_id: '18528f8e529264-066928753d330c8-c6a5a43-fa00-18528f8e52bb52', + ip: '84.198.172.247', + site_url: 'https://appdata.vnct.xyz', team_id: 2, - now: "2022-12-19T14:18:27.859614+00:00", - sent_at: "2022-12-19T14:18:26.905000+00:00", - token: "phc_gE7SWBNBgFbA4eQ154KPXebyB8KyLJuypR8jg1DSo9Z", - } + now: '2022-12-19T14:18:27.859614+00:00', + sent_at: '2022-12-19T14:18:26.905000+00:00', + token: 'phc_gE7SWBNBgFbA4eQ154KPXebyB8KyLJuypR8jg1DSo9Z', + }, ] -const demoMeta = { - cache: {}, - config: { name: "world" }, - attachments: {}, - storage: {}, - geoip: {}, - jobs: {}, - utils: { cursor: {} }, +const demoMeta: any = { + config: { name: 'world' }, global: {}, + logger: { + debug: () => {}, + log: () => {}, + error: () => {}, + warn: () => {}, + }, } - -test("test processEvent translates utm links correctly revised", () => { +test('test processEvent translates utm links correctly revised', () => { const utmURL = - "https://www.reddit.com/r/blender/comments/zmomxw/stable_diffusion_can_texture_your_entire_scene/?utm_source=share&utm_medium=android_app&utm_name=androidcss&utm_term=2&utm_content=share_button" + 'https://www.reddit.com/r/blender/comments/zmomxw/stable_diffusion_can_texture_your_entire_scene/?utm_source=share&utm_medium=android_app&utm_name=androidcss&utm_term=2&utm_content=share_button' - - demoEvents.forEach(demoEvent => { + demoEvents.forEach((demoEvent) => { const utmEvent = { ...demoEvent, properties: { ...demoEvent.properties, $current_url: utmURL }, } - - const processedUTMEvent = processEvent(utmEvent) - - expect(processedUTMEvent.properties?.referrer_parser).toBe("utm") - + + const processedUTMEvent = processEvent(utmEvent, demoMeta) + + expect(processedUTMEvent.properties?.referrer_parser).toBe('utm') + const googleURL = `https://www.google.com/search?q='joan+6+pro'` - + const referrerEvent = { ...demoEvent, properties: { ...demoEvent.properties, $referrer: googleURL, - $referring_domain: "google.com", + $referring_domain: 'google.com', }, } - - const processedReferrerEvent = processEvent(referrerEvent) - - expect(processedReferrerEvent.properties?.referrer_parser).toBe("snowplow") - + + const processedReferrerEvent = processEvent(referrerEvent, demoMeta) + + expect(processedReferrerEvent.properties?.referrer_parser).toBe('snowplow') + const joanURL = `https://office.getjoan.com/settings'` - + const directEvent = { ...demoEvent, properties: { ...demoEvent.properties, $referrer: joanURL, - $referring_domain: "office.getjoan.com", + $referring_domain: 'office.getjoan.com', }, } - - const processedDirectEvent = processEvent(directEvent) - - expect(processedDirectEvent.properties?.referrer_parser).toBe( - "direct_and_own_domains", - ) + + const processedDirectEvent = processEvent(directEvent, demoMeta) + + expect(processedDirectEvent.properties?.referrer_parser).toBe('direct_and_own_domains') }) }) From d1261cbe964a3509dd34b4f8d187f434465141d1 Mon Sep 17 00:00:00 2001 From: Ben White Date: Thu, 30 Jan 2025 16:37:47 +0100 Subject: [PATCH 135/163] Fixes --- .../cdp-cyclotron-plugins-worker.consumer.ts | 2 +- .../hog-transformer.service.ts | 2 +- plugin-server/src/cdp/legacy-plugins/types.ts | 2 +- .../services/legacy-plugin-executor.service.ts | 15 +++++++++++++++ .../cdp/services/legacy-plugin-executor.test.ts | 2 +- 5 files changed, 19 insertions(+), 4 deletions(-) diff --git a/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.ts b/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.ts index 665c215acd962..36edf264ab82b 100644 --- a/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.ts +++ b/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.consumer.ts @@ -15,7 +15,7 @@ export class CdpCyclotronWorkerPlugins extends CdpCyclotronWorker { constructor(hub: Hub) { super(hub) - this.pluginExecutor = new LegacyPluginExecutorService() + this.pluginExecutor = new LegacyPluginExecutorService(hub) } public async processInvocations(invocations: HogFunctionInvocation[]): Promise { diff --git a/plugin-server/src/cdp/hog-transformations/hog-transformer.service.ts b/plugin-server/src/cdp/hog-transformations/hog-transformer.service.ts index 39574e9e3266d..cd86050122689 100644 --- a/plugin-server/src/cdp/hog-transformations/hog-transformer.service.ts +++ b/plugin-server/src/cdp/hog-transformations/hog-transformer.service.ts @@ -39,7 +39,7 @@ export class HogTransformerService { this.hub = hub this.hogFunctionManager = new HogFunctionManagerService(hub) this.hogExecutor = new HogExecutorService(hub, this.hogFunctionManager) - this.pluginExecutor = new LegacyPluginExecutorService() + this.pluginExecutor = new LegacyPluginExecutorService(hub) } private getTransformationFunctions() { diff --git a/plugin-server/src/cdp/legacy-plugins/types.ts b/plugin-server/src/cdp/legacy-plugins/types.ts index f230e15c4bbaf..69ba04310318a 100644 --- a/plugin-server/src/cdp/legacy-plugins/types.ts +++ b/plugin-server/src/cdp/legacy-plugins/types.ts @@ -17,7 +17,7 @@ export type LegacyPluginMeta = { export type LegacyTransformationPluginMeta = LegacyPluginMeta & { geoip: { - locate: (ipAddress: string) => Record + locate: (ipAddress: string) => Record | null } } diff --git a/plugin-server/src/cdp/services/legacy-plugin-executor.service.ts b/plugin-server/src/cdp/services/legacy-plugin-executor.service.ts index c98e59a23978a..ab13920d97439 100644 --- a/plugin-server/src/cdp/services/legacy-plugin-executor.service.ts +++ b/plugin-server/src/cdp/services/legacy-plugin-executor.service.ts @@ -2,6 +2,8 @@ import { PluginEvent, ProcessedPluginEvent, RetryError } from '@posthog/plugin-s import { DateTime } from 'luxon' import { Histogram } from 'prom-client' +import { Hub } from '~/src/types' + import { Response, trackedFetch } from '../../utils/fetch' import { status } from '../../utils/status' import { DESTINATION_PLUGINS_BY_ID, TRANSFORMATION_PLUGINS_BY_ID } from '../legacy-plugins' @@ -32,6 +34,7 @@ export type PluginState = { * NOTE: This is a consumer to take care of legacy plugins. */ export class LegacyPluginExecutorService { + constructor(private hub: Hub) {} private pluginState: Record = {} public async fetch(...args: Parameters): Promise { @@ -108,6 +111,18 @@ export class LegacyPluginExecutorService { config: invocation.globals.inputs, global: {}, logger: logger, + geoip: { + locate: (ipAddress: string): Record | null => { + if (!this.hub.mmdb) { + return null + } + try { + return this.hub.mmdb.city(ipAddress) + } catch { + return null + } + }, + }, } let setupPromise = Promise.resolve() diff --git a/plugin-server/src/cdp/services/legacy-plugin-executor.test.ts b/plugin-server/src/cdp/services/legacy-plugin-executor.test.ts index c6b05bb58ee85..f41973abf554b 100644 --- a/plugin-server/src/cdp/services/legacy-plugin-executor.test.ts +++ b/plugin-server/src/cdp/services/legacy-plugin-executor.test.ts @@ -33,7 +33,7 @@ describe('LegacyPluginExecutorService', () => { beforeEach(async () => { hub = await createHub() await resetTestDatabase() - service = new LegacyPluginExecutorService() + service = new LegacyPluginExecutorService(hub) team = await getFirstTeam(hub) fn = createHogFunction({ From 535f7cb647a12cfcd38f8e5fbdf3ba8d7605b6ed Mon Sep 17 00:00:00 2001 From: Ben White Date: Thu, 30 Jan 2025 16:52:31 +0100 Subject: [PATCH 136/163] Fixes --- .../posthog-anonymization/index.ts | 7 ++ .../posthog-anonymization/plugin.json | 20 ++++++ .../src/encodePrivateField.ts | 6 ++ .../src/normalizePath.ts | 25 +++++++ .../posthog-anonymization/src/processEvent.ts | 40 +++++++++++ .../posthog-anonymization/template.ts | 29 ++++++++ .../tests/encodePrivateField.test.ts | 9 +++ .../tests/normalizePath.test.ts | 45 ++++++++++++ .../tests/processEvent.test.ts | 68 +++++++++++++++++++ 9 files changed, 249 insertions(+) create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/posthog-anonymization/index.ts create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/posthog-anonymization/plugin.json create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/posthog-anonymization/src/encodePrivateField.ts create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/posthog-anonymization/src/normalizePath.ts create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/posthog-anonymization/src/processEvent.ts create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/posthog-anonymization/template.ts create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/posthog-anonymization/tests/encodePrivateField.test.ts create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/posthog-anonymization/tests/normalizePath.test.ts create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/posthog-anonymization/tests/processEvent.test.ts diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-anonymization/index.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-anonymization/index.ts new file mode 100644 index 0000000000000..72225edd96878 --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-anonymization/index.ts @@ -0,0 +1,7 @@ +import { LegacyTransformationPlugin } from '../../types' +import { processEvent } from './src/processEvent' + +export const pluginPosthogAnonymization: LegacyTransformationPlugin = { + id: 'plugin-posthog-anonymization', + processEvent, +} diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-anonymization/plugin.json b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-anonymization/plugin.json new file mode 100644 index 0000000000000..1c33b5f643612 --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-anonymization/plugin.json @@ -0,0 +1,20 @@ +{ + "name": "Anonymization and normalisation", + "description": "Anonymization and normalisation", + "config": [ + { + "key": "salt", + "name": "The salt.", + "type": "string", + "required": true, + "secret": true + }, + { + "key": "privateFields", + "name": "The names of fields to be anonymized divided by a colon.", + "type": "string", + "default": "distinct_id,name,userid", + "required": true + } + ] +} diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-anonymization/src/encodePrivateField.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-anonymization/src/encodePrivateField.ts new file mode 100644 index 0000000000000..c8b126293ea48 --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-anonymization/src/encodePrivateField.ts @@ -0,0 +1,6 @@ +import { createHash } from "crypto"; + +export const encodePrivateField = (property: string, salt: string) => + createHash("sha256") + .update(property + salt) + .digest("hex"); diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-anonymization/src/normalizePath.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-anonymization/src/normalizePath.ts new file mode 100644 index 0000000000000..c5c6ed110ef5e --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-anonymization/src/normalizePath.ts @@ -0,0 +1,25 @@ +const hasCapitalLetter = (text: string): boolean => /^[A-Z](.*?)$/.test(text); + +const hasNumber = (text: string): boolean => /\d/.test(text); + +const normalizeIdInPath = (pathChunk: string): string => { + if (!hasNumber(pathChunk) && !hasCapitalLetter(pathChunk)) { + return pathChunk; + } + + return ":id"; +}; + +const removeHashSearchQuery = (path: string): string => path.split("?")[0]; + +export const normalizePath = (path = ""): string => { + const decodedPath = decodeURIComponent(path); + const myURL = new URL(decodedPath); + + const newHash = removeHashSearchQuery(myURL.hash) + .split("/") + .map(normalizeIdInPath) + .join("/"); + + return myURL.origin + myURL.pathname + newHash; +}; diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-anonymization/src/processEvent.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-anonymization/src/processEvent.ts new file mode 100644 index 0000000000000..4ab274eace844 --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-anonymization/src/processEvent.ts @@ -0,0 +1,40 @@ +import { PluginEvent } from '@posthog/plugin-scaffold' + +import { LegacyTransformationPluginMeta } from '../../../types' +import { encodePrivateField } from './encodePrivateField' +import { normalizePath } from './normalizePath' + +export function processEvent(event: PluginEvent, { config }: LegacyTransformationPluginMeta): PluginEvent { + if (!config.salt) { + return event + } + if (!config.privateFields) { + return event + } + + if (event.properties && event.properties['$current_url']) { + event.properties['$current_url'] = normalizePath(event.properties['$current_url']) + } + + if (event.properties && event.properties['$set'] && event.properties['$set']['$current_url']) { + event.properties['$set']['$current_url'] = normalizePath(event.properties['$set']['$current_url']) + } + + if (event.properties && event.properties['$set_once'] && event.properties['$set_once']['$initial_current_url']) { + event.properties['$set_once']['$initial_current_url'] = normalizePath( + event.properties['$set_once']['$initial_current_url'] + ) + } + + config.privateFields.split(',').forEach((privateField: string) => { + if (event.properties && privateField in event.properties) { + event.properties[privateField] = encodePrivateField(event.properties[privateField] as string, config.salt) + } + + if (privateField in event) { + ;(event as any)[privateField] = encodePrivateField((event as any)[privateField] as string, config.salt) + } + }) + + return event +} diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-anonymization/template.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-anonymization/template.ts new file mode 100644 index 0000000000000..eef275a99a14c --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-anonymization/template.ts @@ -0,0 +1,29 @@ +import { HogFunctionTemplate } from '../../../templates/types' + +// NOTE: This is a deprecated plugin and should never be shown to new users +export const template: HogFunctionTemplate = { + status: 'alpha', + type: 'transformation', + id: 'plugin-posthog-anonymization', + name: 'PostHog Anonymization', + description: 'Anonymize your data.', + icon_url: '/static/hedgehog/builder-hog-01.png', + category: ['Custom'], + hog: `return event`, + inputs_schema: [ + { + key: 'salt', + label: 'The salt.', + type: 'string', + required: true, + secret: true, + }, + { + key: 'privateFields', + label: 'The names of fields to be anonymized divided by a colon.', + type: 'string', + default: 'distinct_id,name,userid', + required: true, + }, + ], +} diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-anonymization/tests/encodePrivateField.test.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-anonymization/tests/encodePrivateField.test.ts new file mode 100644 index 0000000000000..f0b45c08fe480 --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-anonymization/tests/encodePrivateField.test.ts @@ -0,0 +1,9 @@ +import { encodePrivateField } from '../src/encodePrivateField' + +describe('encodePrivateField', () => { + it('returns encoded field when input is not empty string', () => { + const actual = encodePrivateField('user_id', '1234567890') + + expect(actual).toMatchInlineSnapshot(`"ae2897db84a09e2729c95f8d41a16b4abdfcbf5e9a59c792877b7ba86c17bd03"`) + }) +}) diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-anonymization/tests/normalizePath.test.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-anonymization/tests/normalizePath.test.ts new file mode 100644 index 0000000000000..2c3d79dbdf466 --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-anonymization/tests/normalizePath.test.ts @@ -0,0 +1,45 @@ +import { normalizePath } from '../src/normalizePath' + +describe('normalizePath', () => { + it('removes IDs from the URL Hash', () => { + const actual = normalizePath('https://example.com/example.html#accounts/ASD123/cards') + + expect(actual).toMatchInlineSnapshot(`"https://example.com/example.html#accounts/:id/cards"`) + }) + + it('removes get parameters from URL Hash', () => { + const actual = normalizePath('https://example.com/example.html#accounts/cards?test=foo') + + expect(actual).toMatchInlineSnapshot(`"https://example.com/example.html#accounts/cards"`) + }) + + it('removes IDs and get parameters from bulk transfer url hash', () => { + const actual = normalizePath( + 'https://example.com/example.html#/path/to/THE_THING/830baf73-2f70-4194-b18e-8900c0281f49?backUrl=foobar' + ) + + expect(actual).toMatchInlineSnapshot(`"https://example.com/example.html#/path/to/:id/:id"`) + }) + + it('keeps the domain intact when it contains numbers', () => { + const actual = normalizePath( + 'https://example.com/?at=c#/currentAccount/830baf73-2f70-4194-b18e-8900c0281f49/transactions' + ) + + expect(actual).toMatchInlineSnapshot(`"https://example.com/#/currentAccount/:id/transactions"`) + }) + + it('removes the query param, but keeps the path in the hash for normalization', () => { + const actual = normalizePath('https://example.com/index.html?at=c&lang=en#/overview') + + expect(actual).toMatchInlineSnapshot(`"https://example.com/index.html#/overview"`) + }) + + it('normalizes encoded URIs', () => { + const actual = normalizePath( + 'https%3A%2F%2Fexample.com%2F%3Fat%3Dc%23%2FcurrentAccount%2F830baf73-2f70-4194-b18e-8900c0281f49%2Ftransactions' + ) + + expect(actual).toMatchInlineSnapshot(`"https://example.com/#/currentAccount/:id/transactions"`) + }) +}) diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-anonymization/tests/processEvent.test.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-anonymization/tests/processEvent.test.ts new file mode 100644 index 0000000000000..a864b184f9b95 --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-anonymization/tests/processEvent.test.ts @@ -0,0 +1,68 @@ +import { PluginEvent } from '@posthog/plugin-scaffold' + +import { processEvent } from '../src/processEvent' + +describe('processEvent', () => { + it('normalizes the site_url and encodes the private fields', () => { + const siteUrl = + 'https://example.com/example.html#/path/to/resource/830baf73-2f70-4194-b18e-8900c0281f49?backUrl=return' + /** + * @type {Object} + */ + const myEvent: PluginEvent = { + event: 'visit', + ip: '1.1.1.1', + now: '', + team_id: 0, + uuid: '', + distinct_id: '105338229', + properties: { + userid: '105338229', + name: 'John Doe', + $current_url: siteUrl, + $set: { + $current_url: siteUrl, + }, + $set_once: { + $initial_current_url: siteUrl, + }, + }, + } as any + + const actual = processEvent(myEvent, { + config: { + salt: '1234567890', + privateFields: 'distinct_id,userid,name', + }, + global: {}, + logger: { + debug: () => {}, + error: () => {}, + log: () => {}, + warn: () => {}, + }, + } as any) + + expect(actual).toMatchInlineSnapshot(` + { + "distinct_id": "83f029dcb4f5e8f260f008d71e770627adb92aa050aae0c005adad81cc57747c", + "event": "visit", + "ip": "1.1.1.1", + "now": "", + "properties": { + "$current_url": "https://example.com/example.html#/path/to/resource/:id", + "$set": { + "$current_url": "https://example.com/example.html#/path/to/resource/:id", + }, + "$set_once": { + "$initial_current_url": "https://example.com/example.html#/path/to/resource/:id", + }, + "name": "473fc460b53fb3c256ca124bea47e5edd337a864ec963c03ed7adcd1402cb3e7", + "userid": "83f029dcb4f5e8f260f008d71e770627adb92aa050aae0c005adad81cc57747c", + }, + "team_id": 0, + "uuid": "", + } + `) + }) +}) From c42c997088d917c4cb953683305a9e59bd21cbfd Mon Sep 17 00:00:00 2001 From: Ben White Date: Thu, 30 Jan 2025 17:22:11 +0100 Subject: [PATCH 137/163] Adds a new step for comparing --- .../compareToHogTransformStep.ts | 94 +++++++++++++++++++ .../worker/ingestion/event-pipeline/runner.ts | 8 ++ 2 files changed, 102 insertions(+) create mode 100644 plugin-server/src/worker/ingestion/event-pipeline/compareToHogTransformStep.ts diff --git a/plugin-server/src/worker/ingestion/event-pipeline/compareToHogTransformStep.ts b/plugin-server/src/worker/ingestion/event-pipeline/compareToHogTransformStep.ts new file mode 100644 index 0000000000000..2b3be3f7d8e75 --- /dev/null +++ b/plugin-server/src/worker/ingestion/event-pipeline/compareToHogTransformStep.ts @@ -0,0 +1,94 @@ +import { PluginEvent } from '@posthog/plugin-scaffold' +import { Counter } from 'prom-client' + +import { cloneObject } from '~/src/utils/utils' + +import { HogTransformerService } from '../../../cdp/hog-transformations/hog-transformer.service' +import { status } from '../../../utils/status' + +type Diff = { + key: string + plugins: string + hog: string +} + +export const counterHogTransformationDiff = new Counter({ + name: 'hog_transformation_diff', + help: 'Whether the hog transformations produced the same event as the plugin', + labelNames: ['outcome'], // either same or diff +}) + +export const compareEvents = (pluginEvent: PluginEvent, hogEvent: PluginEvent): Diff | null => { + // Comparing objects is expensive so we will do this instead by iterating over the keys we care about + + if (pluginEvent.event !== hogEvent.event) { + return { key: 'event', plugins: pluginEvent.event, hog: hogEvent.event } + } + + if (pluginEvent.distinct_id !== hogEvent.distinct_id) { + return { key: 'distinct_id', plugins: pluginEvent.distinct_id, hog: hogEvent.distinct_id } + } + + const pluginProperties = Object.keys(pluginEvent.properties ?? {}).sort() + const hogProperties = Object.keys(hogEvent.properties ?? {}).sort() + + // Loosely compare the two events by comparing the properties + if (pluginProperties.length !== hogProperties.length) { + return { key: 'properties', plugins: pluginProperties.join(','), hog: hogProperties.join(',') } + } + + // Compare each property individually + const diffProperties = pluginProperties.filter((property) => { + const pluginValue = pluginEvent.properties?.[property] + const hogValue = hogEvent.properties?.[property] + + return JSON.stringify(pluginValue) === JSON.stringify(hogValue) + }) + + if (diffProperties.length > 0) { + return { key: 'properties', plugins: diffProperties.join(','), hog: diffProperties.join(',') } + } +} + +export async function compareToHogTransformStep( + hogTransformer: HogTransformerService | null, + prePluginsEvent: PluginEvent, + postPluginsEvent: PluginEvent | null +): Promise { + if (!hogTransformer) { + return + } + + try { + // TRICKY: We + const clonedEvent = cloneObject(prePluginsEvent) + const result = await hogTransformer.transformEvent(clonedEvent) + const hogEvent = result.event + + if (!hogEvent || !postPluginsEvent) { + if (!hogEvent && !postPluginsEvent) { + counterHogTransformationDiff.inc({ outcome: 'same' }) + } else if (!hogEvent && postPluginsEvent) { + status.warn('⚠️', 'Hog transformation produced no event but the plugin did') + counterHogTransformationDiff.inc({ outcome: 'diff' }) + } else if (hogEvent && !postPluginsEvent) { + status.warn('⚠️', 'Hog transformation produced an event but the plugin did not') + counterHogTransformationDiff.inc({ outcome: 'diff' }) + } + return + } + + const diff = compareEvents(postPluginsEvent, hogEvent) + if (diff) { + status.warn('⚠️', 'Hog transformation produced an event but the plugin did not', { + team_id: prePluginsEvent.team_id, + diff, + }) + counterHogTransformationDiff.inc({ outcome: 'diff' }) + } else { + counterHogTransformationDiff.inc({ outcome: 'same' }) + } + } catch (error) { + status.error('Error occured when comparing plugin event to hog transform', error) + } +} diff --git a/plugin-server/src/worker/ingestion/event-pipeline/runner.ts b/plugin-server/src/worker/ingestion/event-pipeline/runner.ts index c3032cc53835b..a85071d9e7e65 100644 --- a/plugin-server/src/worker/ingestion/event-pipeline/runner.ts +++ b/plugin-server/src/worker/ingestion/event-pipeline/runner.ts @@ -12,6 +12,7 @@ import { normalizeProcessPerson } from '../../../utils/event' import { status } from '../../../utils/status' import { EventsProcessor } from '../process-event' import { captureIngestionWarning, generateEventDeadLetterQueueMessage } from '../utils' +import { compareToHogTransformStep } from './compareToHogTransformStep' import { cookielessServerHashStep } from './cookielessServerHashStep' import { createEventStep } from './createEventStep' import { emitEventStep } from './emitEventStep' @@ -232,6 +233,13 @@ export class EventPipelineRunner { } const processedEvent = await this.runStep(pluginsProcessEventStep, [this, postCookielessEvent], event.team_id) + + await this.runStep( + compareToHogTransformStep, + [this.hogTransformer, postCookielessEvent, processedEvent], + event.team_id + ) + if (processedEvent == null) { // A plugin dropped the event. return this.registerLastStep('pluginsProcessEventStep', [postCookielessEvent], kafkaAcks) From 362b83a2091ed4ba21edb796c5d485065b311b6c Mon Sep 17 00:00:00 2001 From: Ben White Date: Thu, 30 Jan 2025 17:34:17 +0100 Subject: [PATCH 138/163] Added sampling --- .vscode/launch.json | 3 ++- plugin-server/src/config/config.ts | 1 + plugin-server/src/types.ts | 1 + .../event-pipeline/compareToHogTransformStep.ts | 9 +++++++-- .../src/worker/ingestion/event-pipeline/runner.ts | 11 ++++++++--- 5 files changed, 19 insertions(+), 6 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 77c5355224e69..6d71b4ad5e30f 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -155,7 +155,8 @@ "OBJECT_STORAGE_ENABLED": "True", "HOG_HOOK_URL": "http://localhost:3300/hoghook", "PLUGIN_SERVER_MODE": "all-v2", - "HOG_TRANSFORMATIONS_ENABLED": "True" + "HOG_TRANSFORMATIONS_ENABLED": "True", + "HOG_TRANSFORMATIONS_COMPARISON_PERCENTAGE": "1" }, "presentation": { "group": "main" diff --git a/plugin-server/src/config/config.ts b/plugin-server/src/config/config.ts index 8cfb75cc505fc..486a0895701f9 100644 --- a/plugin-server/src/config/config.ts +++ b/plugin-server/src/config/config.ts @@ -225,6 +225,7 @@ export function getDefaultConfig(): PluginsServerConfig { // Hog Transformations (Alpha) HOG_TRANSFORMATIONS_ENABLED: false, + HOG_TRANSFORMATIONS_COMPARISON_PERCENTAGE: 0, // Cookieless COOKIELESS_FORCE_STATELESS_MODE: false, diff --git a/plugin-server/src/types.ts b/plugin-server/src/types.ts index 6c02380a0139b..5625c5eb4100b 100644 --- a/plugin-server/src/types.ts +++ b/plugin-server/src/types.ts @@ -323,6 +323,7 @@ export interface PluginsServerConfig extends CdpConfig, IngestionConsumerConfig // HOG Transformations (Alpha feature) HOG_TRANSFORMATIONS_ENABLED: boolean + HOG_TRANSFORMATIONS_COMPARISON_PERCENTAGE: number | undefined SESSION_RECORDING_MAX_BATCH_SIZE_KB: number | undefined SESSION_RECORDING_MAX_BATCH_AGE_MS: number | undefined diff --git a/plugin-server/src/worker/ingestion/event-pipeline/compareToHogTransformStep.ts b/plugin-server/src/worker/ingestion/event-pipeline/compareToHogTransformStep.ts index 2b3be3f7d8e75..9555dc564ca6c 100644 --- a/plugin-server/src/worker/ingestion/event-pipeline/compareToHogTransformStep.ts +++ b/plugin-server/src/worker/ingestion/event-pipeline/compareToHogTransformStep.ts @@ -53,14 +53,19 @@ export const compareEvents = (pluginEvent: PluginEvent, hogEvent: PluginEvent): export async function compareToHogTransformStep( hogTransformer: HogTransformerService | null, prePluginsEvent: PluginEvent, - postPluginsEvent: PluginEvent | null + postPluginsEvent: PluginEvent | null, + samplePercentage?: number ): Promise { if (!hogTransformer) { return } + if (!samplePercentage || Math.random() > samplePercentage) { + return + } + try { - // TRICKY: We + // TRICKY: We really want to make sure that the other event is unaffected const clonedEvent = cloneObject(prePluginsEvent) const result = await hogTransformer.transformEvent(clonedEvent) const hogEvent = result.event diff --git a/plugin-server/src/worker/ingestion/event-pipeline/runner.ts b/plugin-server/src/worker/ingestion/event-pipeline/runner.ts index a85071d9e7e65..8233f99b63ebf 100644 --- a/plugin-server/src/worker/ingestion/event-pipeline/runner.ts +++ b/plugin-server/src/worker/ingestion/event-pipeline/runner.ts @@ -64,7 +64,7 @@ export class EventPipelineRunner { this.hub = hub this.originalEvent = event this.eventsProcessor = new EventsProcessor(hub) - this.hogTransformer = hub.HOG_TRANSFORMATIONS_ENABLED ? hogTransformer : null + this.hogTransformer = hogTransformer } isEventDisallowed(event: PipelineEvent): boolean { @@ -236,7 +236,12 @@ export class EventPipelineRunner { await this.runStep( compareToHogTransformStep, - [this.hogTransformer, postCookielessEvent, processedEvent], + [ + this.hogTransformer, + postCookielessEvent, + processedEvent, + this.hub.HOG_TRANSFORMATIONS_COMPARISON_PERCENTAGE, + ], event.team_id ) @@ -247,7 +252,7 @@ export class EventPipelineRunner { const { event: transformedEvent, messagePromises } = await this.runStep( transformEventStep, - [processedEvent, this.hogTransformer], + [processedEvent, this.hub.HOG_TRANSFORMATIONS_ENABLED ? this.hogTransformer : null], event.team_id ) From 5057a9bba4d47e5aabdef4bd65745cc833a486b6 Mon Sep 17 00:00:00 2001 From: Ben White Date: Thu, 30 Jan 2025 17:34:43 +0100 Subject: [PATCH 139/163] Fix --- .../ingestion/event-pipeline/compareToHogTransformStep.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugin-server/src/worker/ingestion/event-pipeline/compareToHogTransformStep.ts b/plugin-server/src/worker/ingestion/event-pipeline/compareToHogTransformStep.ts index 9555dc564ca6c..c7155a0cc7f51 100644 --- a/plugin-server/src/worker/ingestion/event-pipeline/compareToHogTransformStep.ts +++ b/plugin-server/src/worker/ingestion/event-pipeline/compareToHogTransformStep.ts @@ -48,6 +48,8 @@ export const compareEvents = (pluginEvent: PluginEvent, hogEvent: PluginEvent): if (diffProperties.length > 0) { return { key: 'properties', plugins: diffProperties.join(','), hog: diffProperties.join(',') } } + + return null } export async function compareToHogTransformStep( From 902c21edd127ec8e2f2989d2ad0df979ae656bdd Mon Sep 17 00:00:00 2001 From: Ben White Date: Thu, 30 Jan 2025 17:42:04 +0100 Subject: [PATCH 140/163] Fixes --- .../hog-transformer.service.ts | 38 +++++++++++++++---- .../event-pipeline/transformEventStep.ts | 4 +- 2 files changed, 32 insertions(+), 10 deletions(-) diff --git a/plugin-server/src/cdp/hog-transformations/hog-transformer.service.ts b/plugin-server/src/cdp/hog-transformations/hog-transformer.service.ts index 39574e9e3266d..5c75bf98e41d4 100644 --- a/plugin-server/src/cdp/hog-transformations/hog-transformer.service.ts +++ b/plugin-server/src/cdp/hog-transformations/hog-transformer.service.ts @@ -24,8 +24,12 @@ export const hogTransformationDroppedEvents = new Counter({ help: 'Indicates how many events are dropped by hog transformations', }) -export interface TransformationResult { +export interface TransformationResultPure { event: PluginEvent | null + invocationResults: HogFunctionInvocationResult[] +} + +export interface TransformationResult extends TransformationResultPure { messagePromises: Promise[] } @@ -164,20 +168,38 @@ export class HogTransformerService { return promises } - public transformEvent(event: PluginEvent): Promise { + public transformEventAndProduceMessages(event: PluginEvent): Promise { return runInstrumentedFunction({ - statsKey: `hogTransformer`, + statsKey: `hogTransformer.transformEventAndProduceMessages`, + func: async () => { + const transformationResult = await this.transformEvent(event) + const messagePromises: Promise[] = [] + + transformationResult.invocationResults.forEach((result) => { + messagePromises.push(...this.processInvocationResult(result)) + }) + + return { + ...transformationResult, + messagePromises, + } + }, + }) + } + + public transformEvent(event: PluginEvent): Promise { + return runInstrumentedFunction({ + statsKey: `hogTransformer.transformEvent`, func: async () => { const teamHogFunctions = this.hogFunctionManager.getTeamHogFunctions(event.team_id) - const messagePromises: Promise[] = [] + const results: HogFunctionInvocationResult[] = [] // For now, execute each transformation function in sequence for (const hogFunction of teamHogFunctions) { const result = await this.executeHogFunction(hogFunction, this.createInvocationGlobals(event)) - // Process results and collect promises - messagePromises.push(...this.processInvocationResult(result)) + results.push(result) if (result.error) { status.error('⚠️', 'Error in transformation', { @@ -194,7 +216,7 @@ export class HogTransformerService { hogTransformationDroppedEvents.inc() return { event: null, - messagePromises, + invocationResults: results, } } @@ -232,7 +254,7 @@ export class HogTransformerService { return { event, - messagePromises, + invocationResults: results, } }, }) diff --git a/plugin-server/src/worker/ingestion/event-pipeline/transformEventStep.ts b/plugin-server/src/worker/ingestion/event-pipeline/transformEventStep.ts index 5ede6c0577192..8af1746828095 100644 --- a/plugin-server/src/worker/ingestion/event-pipeline/transformEventStep.ts +++ b/plugin-server/src/worker/ingestion/event-pipeline/transformEventStep.ts @@ -7,7 +7,7 @@ export async function transformEventStep( hogTransformer: HogTransformerService | null ): Promise { if (!hogTransformer) { - return { event, messagePromises: [] } + return { event, invocationResults: [], messagePromises: [] } } - return hogTransformer.transformEvent(event) + return hogTransformer.transformEventAndProduceMessages(event) } From 045717aa4e2146c2c140194618d7d78a46688289 Mon Sep 17 00:00:00 2001 From: Ben White Date: Thu, 30 Jan 2025 17:45:03 +0100 Subject: [PATCH 141/163] Fixes --- .../hog-transformer.service.test.ts | 40 ++++++------------- 1 file changed, 13 insertions(+), 27 deletions(-) diff --git a/plugin-server/src/cdp/hog-transformations/hog-transformer.service.test.ts b/plugin-server/src/cdp/hog-transformations/hog-transformer.service.test.ts index c6ac1925eb6dd..a56c8e57bc1f9 100644 --- a/plugin-server/src/cdp/hog-transformations/hog-transformer.service.test.ts +++ b/plugin-server/src/cdp/hog-transformations/hog-transformer.service.test.ts @@ -425,15 +425,7 @@ describe('HogTransformer', () => { const event: PluginEvent = createPluginEvent({ event: 'drop-me', team_id: teamId }) const result = await hogTransformer.transformEvent(event) expect(executeSpy).toHaveBeenCalledTimes(1) - expect(result).toMatchInlineSnapshot(` - { - "event": null, - "messagePromises": [ - Promise {}, - Promise {}, - ], - } - `) + expect(result.event).toMatchInlineSnapshot(`null`) }) it('handles legacy plugin transformation to keep events', async () => { @@ -441,26 +433,20 @@ describe('HogTransformer', () => { const result = await hogTransformer.transformEvent(event) expect(executeSpy).toHaveBeenCalledTimes(1) - expect(result).toMatchInlineSnapshot(` + expect(result.event).toMatchInlineSnapshot(` { - "event": { - "distinct_id": "distinct-id", - "event": "keep-me", - "ip": "89.160.20.129", - "now": "2024-06-07T12:00:00.000Z", - "properties": { - "$current_url": "https://example.com", - "$ip": "89.160.20.129", - }, - "site_url": "http://localhost", - "team_id": ${teamId}, - "timestamp": "2024-01-01T00:00:00Z", - "uuid": "event-id", + "distinct_id": "distinct-id", + "event": "keep-me", + "ip": "89.160.20.129", + "now": "2024-06-07T12:00:00.000Z", + "properties": { + "$current_url": "https://example.com", + "$ip": "89.160.20.129", }, - "messagePromises": [ - Promise {}, - Promise {}, - ], + "site_url": "http://localhost", + "team_id": 386116330, + "timestamp": "2024-01-01T00:00:00Z", + "uuid": "event-id", } `) }) From d050bb965d8dcd7dd9054694243b239f43d93edc Mon Sep 17 00:00:00 2001 From: Ben White Date: Thu, 30 Jan 2025 17:50:11 +0100 Subject: [PATCH 142/163] Fixes --- .../hog-transformations/hog-transformer.service.test.ts | 8 ++++---- .../ingestion/event-pipeline/compareToHogTransformStep.ts | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/plugin-server/src/cdp/hog-transformations/hog-transformer.service.test.ts b/plugin-server/src/cdp/hog-transformations/hog-transformer.service.test.ts index a56c8e57bc1f9..2384f04b6ed4c 100644 --- a/plugin-server/src/cdp/hog-transformations/hog-transformer.service.test.ts +++ b/plugin-server/src/cdp/hog-transformations/hog-transformer.service.test.ts @@ -9,7 +9,7 @@ import { template as defaultTemplate } from '../../../src/cdp/templates/_transfo import { template as geoipTemplate } from '../../../src/cdp/templates/_transformations/geoip/geoip.template' import { compileHog } from '../../../src/cdp/templates/compiler' import { createHogFunction, insertHogFunction } from '../../../tests/cdp/fixtures' -import { createTeam, resetTestDatabase } from '../../../tests/helpers/sql' +import { createTeam, getFirstTeam, resetTestDatabase } from '../../../tests/helpers/sql' import { Hub } from '../../types' import { closeHub, createHub } from '../../utils/db/hub' import { HogFunctionTemplate } from '../templates/types' @@ -51,8 +51,8 @@ describe('HogTransformer', () => { await resetTestDatabase() // Create a team first before inserting hog functions - const team = await hub.db.fetchTeam(2) - teamId = await createTeam(hub.db.postgres, team!.organization_id) + const team = await getFirstTeam(hub) + teamId = team.id hub.mmdb = Reader.openBuffer(brotliDecompressSync(mmdbBrotliContents)) hogTransformer = new HogTransformerService(hub) @@ -444,7 +444,7 @@ describe('HogTransformer', () => { "$ip": "89.160.20.129", }, "site_url": "http://localhost", - "team_id": 386116330, + "team_id": 2, "timestamp": "2024-01-01T00:00:00Z", "uuid": "event-id", } diff --git a/plugin-server/src/worker/ingestion/event-pipeline/compareToHogTransformStep.ts b/plugin-server/src/worker/ingestion/event-pipeline/compareToHogTransformStep.ts index c7155a0cc7f51..3048804d6608a 100644 --- a/plugin-server/src/worker/ingestion/event-pipeline/compareToHogTransformStep.ts +++ b/plugin-server/src/worker/ingestion/event-pipeline/compareToHogTransformStep.ts @@ -87,7 +87,7 @@ export async function compareToHogTransformStep( const diff = compareEvents(postPluginsEvent, hogEvent) if (diff) { - status.warn('⚠️', 'Hog transformation produced an event but the plugin did not', { + status.warn('⚠️', 'Hog transformation was different from plugin', { team_id: prePluginsEvent.team_id, diff, }) From da108028fa45b1f0283f143a9ec693f24e7fd0b8 Mon Sep 17 00:00:00 2001 From: Ben White Date: Thu, 30 Jan 2025 17:50:30 +0100 Subject: [PATCH 143/163] Fixes --- .../src/cdp/hog-transformations/hog-transformer.service.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin-server/src/cdp/hog-transformations/hog-transformer.service.test.ts b/plugin-server/src/cdp/hog-transformations/hog-transformer.service.test.ts index 2384f04b6ed4c..112332ae4b9fe 100644 --- a/plugin-server/src/cdp/hog-transformations/hog-transformer.service.test.ts +++ b/plugin-server/src/cdp/hog-transformations/hog-transformer.service.test.ts @@ -9,7 +9,7 @@ import { template as defaultTemplate } from '../../../src/cdp/templates/_transfo import { template as geoipTemplate } from '../../../src/cdp/templates/_transformations/geoip/geoip.template' import { compileHog } from '../../../src/cdp/templates/compiler' import { createHogFunction, insertHogFunction } from '../../../tests/cdp/fixtures' -import { createTeam, getFirstTeam, resetTestDatabase } from '../../../tests/helpers/sql' +import { getFirstTeam, resetTestDatabase } from '../../../tests/helpers/sql' import { Hub } from '../../types' import { closeHub, createHub } from '../../utils/db/hub' import { HogFunctionTemplate } from '../templates/types' From bbb35d314757f79e03504679d6dbe1e8fb33dbf7 Mon Sep 17 00:00:00 2001 From: Ben White Date: Fri, 31 Jan 2025 09:43:21 +0100 Subject: [PATCH 144/163] Fixes --- .../compareToHogTransformStep.ts | 33 ++++++++----------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/plugin-server/src/worker/ingestion/event-pipeline/compareToHogTransformStep.ts b/plugin-server/src/worker/ingestion/event-pipeline/compareToHogTransformStep.ts index 3048804d6608a..a6317b4176d74 100644 --- a/plugin-server/src/worker/ingestion/event-pipeline/compareToHogTransformStep.ts +++ b/plugin-server/src/worker/ingestion/event-pipeline/compareToHogTransformStep.ts @@ -18,38 +18,31 @@ export const counterHogTransformationDiff = new Counter({ labelNames: ['outcome'], // either same or diff }) -export const compareEvents = (pluginEvent: PluginEvent, hogEvent: PluginEvent): Diff | null => { +export const compareEvents = (pluginEvent: PluginEvent, hogEvent: PluginEvent): Diff[] => { // Comparing objects is expensive so we will do this instead by iterating over the keys we care about if (pluginEvent.event !== hogEvent.event) { - return { key: 'event', plugins: pluginEvent.event, hog: hogEvent.event } + return [{ key: 'event', plugins: pluginEvent.event, hog: hogEvent.event }] } if (pluginEvent.distinct_id !== hogEvent.distinct_id) { - return { key: 'distinct_id', plugins: pluginEvent.distinct_id, hog: hogEvent.distinct_id } + return [{ key: 'distinct_id', plugins: pluginEvent.distinct_id, hog: hogEvent.distinct_id }] } const pluginProperties = Object.keys(pluginEvent.properties ?? {}).sort() - const hogProperties = Object.keys(hogEvent.properties ?? {}).sort() - - // Loosely compare the two events by comparing the properties - if (pluginProperties.length !== hogProperties.length) { - return { key: 'properties', plugins: pluginProperties.join(','), hog: hogProperties.join(',') } - } + const diffs: Diff[] = [] // Compare each property individually - const diffProperties = pluginProperties.filter((property) => { + pluginProperties.forEach((property) => { const pluginValue = pluginEvent.properties?.[property] const hogValue = hogEvent.properties?.[property] - return JSON.stringify(pluginValue) === JSON.stringify(hogValue) + if (JSON.stringify(pluginValue) !== JSON.stringify(hogValue)) { + diffs.push({ key: `properties.${property}`, plugins: pluginValue, hog: hogValue }) + } }) - if (diffProperties.length > 0) { - return { key: 'properties', plugins: diffProperties.join(','), hog: diffProperties.join(',') } - } - - return null + return diffs } export async function compareToHogTransformStep( @@ -74,6 +67,7 @@ export async function compareToHogTransformStep( if (!hogEvent || !postPluginsEvent) { if (!hogEvent && !postPluginsEvent) { + status.info('✅', 'Both plugin and hog transformation produced no event') counterHogTransformationDiff.inc({ outcome: 'same' }) } else if (!hogEvent && postPluginsEvent) { status.warn('⚠️', 'Hog transformation produced no event but the plugin did') @@ -85,14 +79,15 @@ export async function compareToHogTransformStep( return } - const diff = compareEvents(postPluginsEvent, hogEvent) - if (diff) { + const diffs = compareEvents(postPluginsEvent, hogEvent) + if (diffs.length > 0) { status.warn('⚠️', 'Hog transformation was different from plugin', { team_id: prePluginsEvent.team_id, - diff, + diffs, }) counterHogTransformationDiff.inc({ outcome: 'diff' }) } else { + status.info('✅', 'Both plugin and hog transformation produced the same event') counterHogTransformationDiff.inc({ outcome: 'same' }) } } catch (error) { From 2616b852525327afc5b29c1b4401b523e2e86547 Mon Sep 17 00:00:00 2001 From: Ben White Date: Fri, 31 Jan 2025 09:44:26 +0100 Subject: [PATCH 145/163] Fix --- .../flatten-properties-plugin/plugin.json | 7 +- .../src/encodePrivateField.ts | 8 +- .../src/normalizePath.ts | 31 ++++--- .../plugin.json | 28 +++---- .../posthog-route-censor-plugin/dist.js | 1 - .../posthog-route-censor-plugin/plugin.json | 82 +++++++++---------- 6 files changed, 74 insertions(+), 83 deletions(-) diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/flatten-properties-plugin/plugin.json b/plugin-server/src/cdp/legacy-plugins/_transformations/flatten-properties-plugin/plugin.json index 59c0a7613bc78..93c4afb644a35 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/flatten-properties-plugin/plugin.json +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/flatten-properties-plugin/plugin.json @@ -9,12 +9,7 @@ "hint": "For example, to access the value of 'b' in a: { b: 1 } with separator '__', you can do 'a__b'", "name": "Select a separator format for accessing your nested properties", "type": "choice", - "choices": [ - "__", - ".", - ">", - "/" - ], + "choices": ["__", ".", ">", "/"], "order": 1, "default": "__", "required": true diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-anonymization/src/encodePrivateField.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-anonymization/src/encodePrivateField.ts index c8b126293ea48..60d8c4230cdc5 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-anonymization/src/encodePrivateField.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-anonymization/src/encodePrivateField.ts @@ -1,6 +1,6 @@ -import { createHash } from "crypto"; +import { createHash } from 'crypto' export const encodePrivateField = (property: string, salt: string) => - createHash("sha256") - .update(property + salt) - .digest("hex"); + createHash('sha256') + .update(property + salt) + .digest('hex') diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-anonymization/src/normalizePath.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-anonymization/src/normalizePath.ts index c5c6ed110ef5e..2a25e1da98d5d 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-anonymization/src/normalizePath.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-anonymization/src/normalizePath.ts @@ -1,25 +1,22 @@ -const hasCapitalLetter = (text: string): boolean => /^[A-Z](.*?)$/.test(text); +const hasCapitalLetter = (text: string): boolean => /^[A-Z](.*?)$/.test(text) -const hasNumber = (text: string): boolean => /\d/.test(text); +const hasNumber = (text: string): boolean => /\d/.test(text) const normalizeIdInPath = (pathChunk: string): string => { - if (!hasNumber(pathChunk) && !hasCapitalLetter(pathChunk)) { - return pathChunk; - } + if (!hasNumber(pathChunk) && !hasCapitalLetter(pathChunk)) { + return pathChunk + } - return ":id"; -}; + return ':id' +} -const removeHashSearchQuery = (path: string): string => path.split("?")[0]; +const removeHashSearchQuery = (path: string): string => path.split('?')[0] -export const normalizePath = (path = ""): string => { - const decodedPath = decodeURIComponent(path); - const myURL = new URL(decodedPath); +export const normalizePath = (path = ''): string => { + const decodedPath = decodeURIComponent(path) + const myURL = new URL(decodedPath) - const newHash = removeHashSearchQuery(myURL.hash) - .split("/") - .map(normalizeIdInPath) - .join("/"); + const newHash = removeHashSearchQuery(myURL.hash).split('/').map(normalizeIdInPath).join('/') - return myURL.origin + myURL.pathname + newHash; -}; + return myURL.origin + myURL.pathname + newHash +} diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-plugin-snowplow-referer-parser/plugin.json b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-plugin-snowplow-referer-parser/plugin.json index af844c3a3a0a6..af7b0897b4759 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-plugin-snowplow-referer-parser/plugin.json +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-plugin-snowplow-referer-parser/plugin.json @@ -1,16 +1,16 @@ { - "name": "UTM Referrer", - "config": [ - { - "markdown": "Specify your config here" - }, - { - "key": "internal_domains", - "name": "Internal domains (comma delimited)", - "type": "string", - "hint": "Consider these domains as direct referrers. Example: `example.com,blog.example.com`", - "default": "", - "required": false - } - ] + "name": "UTM Referrer", + "config": [ + { + "markdown": "Specify your config here" + }, + { + "key": "internal_domains", + "name": "Internal domains (comma delimited)", + "type": "string", + "hint": "Consider these domains as direct referrers. Example: `example.com,blog.example.com`", + "default": "", + "required": false + } + ] } diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-route-censor-plugin/dist.js b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-route-censor-plugin/dist.js index ace8ee11b7cc7..732df4679928c 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-route-censor-plugin/dist.js +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-route-censor-plugin/dist.js @@ -621,4 +621,3 @@ const processEvent = (event, { global }) => { } exports.processEvent = processEvent -exports.setupPlugin = setupPlugin diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-route-censor-plugin/plugin.json b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-route-censor-plugin/plugin.json index 17089005c3953..8d7d6fcded52f 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-route-censor-plugin/plugin.json +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-route-censor-plugin/plugin.json @@ -1,43 +1,43 @@ { - "name": "Route Censor", - "description": "Removes segments of URLs based on route patterns.", - "url": "https://github.com/ava-labs/posthog-route-censor-plugin", - "main": "dist/index.js", - "config": [ - { - "markdown": "Removes segments of URLs based on route patterns. See [Github Repo](https://github.com/ava-labs/posthog-route-censor-plugin) for more details." - }, - { - "key": "routes", - "name": "List of routes following the React Router route patterns.", - "markdown": "[Example Here](https://github.com/ava-labs/posthog-route-censor-plugin/blob/main/src/assets/exampleRoutes.json). See package [README](https://github.com/ava-labs/posthog-route-censor-plugin) for more details.", - "type": "attachment", - "hint": "See README for more details and example.", - "required": true - }, - { - "key": "properties", - "name": "List of properties to censor", - "type": "string", - "default": "$current_url,$referrer,$pathname,$initial_current_url,initial_pathname,initial_referrer", - "hint": "Separate properties with commas, without using spaces, like so: `foo,bar,$baz`", - "required": false - }, - { - "key": "set_properties", - "name": "List of $set properties to censor", - "type": "string", - "default": "$initial_current_url,$initial_pathname,$initial_referrer", - "hint": "Separate properties with commas, without using spaces, like so: `foo,bar,$baz`", - "required": false - }, - { - "key": "set_once_properties", - "name": "List of $set_once properties to censor", - "type": "string", - "default": "$initial_current_url,$initial_pathname,$initial_referrer", - "hint": "Separate properties with commas, without using spaces, like so: `foo,bar,$baz`", - "required": false - } - ] + "name": "Route Censor", + "description": "Removes segments of URLs based on route patterns.", + "url": "https://github.com/ava-labs/posthog-route-censor-plugin", + "main": "dist/index.js", + "config": [ + { + "markdown": "Removes segments of URLs based on route patterns. See [Github Repo](https://github.com/ava-labs/posthog-route-censor-plugin) for more details." + }, + { + "key": "routes", + "name": "List of routes following the React Router route patterns.", + "markdown": "[Example Here](https://github.com/ava-labs/posthog-route-censor-plugin/blob/main/src/assets/exampleRoutes.json). See package [README](https://github.com/ava-labs/posthog-route-censor-plugin) for more details.", + "type": "attachment", + "hint": "See README for more details and example.", + "required": true + }, + { + "key": "properties", + "name": "List of properties to censor", + "type": "string", + "default": "$current_url,$referrer,$pathname,$initial_current_url,initial_pathname,initial_referrer", + "hint": "Separate properties with commas, without using spaces, like so: `foo,bar,$baz`", + "required": false + }, + { + "key": "set_properties", + "name": "List of $set properties to censor", + "type": "string", + "default": "$initial_current_url,$initial_pathname,$initial_referrer", + "hint": "Separate properties with commas, without using spaces, like so: `foo,bar,$baz`", + "required": false + }, + { + "key": "set_once_properties", + "name": "List of $set_once properties to censor", + "type": "string", + "default": "$initial_current_url,$initial_pathname,$initial_referrer", + "hint": "Separate properties with commas, without using spaces, like so: `foo,bar,$baz`", + "required": false + } + ] } From d6fed325d227285eb7b82fd425b578157a0f711e Mon Sep 17 00:00:00 2001 From: Ben White Date: Fri, 31 Jan 2025 10:00:10 +0100 Subject: [PATCH 146/163] Fixes --- posthog/cdp/migrations.py | 36 ++++++++++--------- .../migrate_plugins_to_hog_functions.py | 10 ++++-- 2 files changed, 27 insertions(+), 19 deletions(-) diff --git a/posthog/cdp/migrations.py b/posthog/cdp/migrations.py index 05339d98c1524..4ff5d01a7dfaa 100644 --- a/posthog/cdp/migrations.py +++ b/posthog/cdp/migrations.py @@ -9,9 +9,10 @@ # python manage.py migrate_plugins_to_hog_functions --dry-run --test-mode -def migrate_legacy_plugins(dry_run=True, team_ids=None, test_mode=True): +def migrate_legacy_plugins(dry_run=True, team_ids=None, test_mode=True, kind=str): # Get all legacy plugin_configs that are active with their attachments and global values # Plugins are huge (JS and assets) so we only grab the bits we really need + legacy_plugins = ( PluginConfig.objects.values( "id", @@ -22,15 +23,23 @@ def migrate_legacy_plugins(dry_run=True, team_ids=None, test_mode=True): "plugin__capabilities", "plugin__config_schema", "plugin__icon", + # Order by order asc but with nulls last ) .filter(enabled=True) - .filter( + .order_by("order") + ) + + if kind == "destination": + legacy_plugins = legacy_plugins.filter( Q(plugin__capabilities__methods__contains=["onEvent"]) | Q(plugin__capabilities__methods__contains=["composeWebhook"]) ) - # always filter out geoip as we are not migrating it this way - .exclude(plugin__name="GeoIP") - ) + elif kind == "transformation": + legacy_plugins = legacy_plugins.filter(plugin__capabilities__methods__contains=["processEvent"]) + else: + raise ValueError(f"Invalid kind: {kind}") + + print(legacy_plugins.query, legacy_plugins.count()) if team_ids: legacy_plugins = legacy_plugins.filter(team_id__in=team_ids) @@ -49,12 +58,6 @@ def migrate_legacy_plugins(dry_run=True, team_ids=None, test_mode=True): plugin_config["plugin__capabilities"], ) - methods = plugin_config["plugin__capabilities"].get("methods", []) - - if "onEvent" not in methods and "composeWebhook" not in methods: - print("Skipping plugin", plugin_config["plugin__name"], "as it doesn't have onEvent or composeWebhook") # noqa: T201 - continue - print("Attempting to migrate plugin", plugin_config) # noqa: T201 url: str = plugin_config["plugin__url"] or "" @@ -121,21 +124,20 @@ def migrate_legacy_plugins(dry_run=True, team_ids=None, test_mode=True): team = teams_by_id[plugin_config["team_id"]] serializer_context = {"team": team, "get_team": (lambda t=team: t)} - icon_url = plugin_config["plugin__icon"] - - if not icon_url: - icon_url = f"https://raw.githubusercontent.com/PostHog/{plugin_id}/main/logo.png" + icon_url = ( + plugin_config["plugin__icon"] or f"https://raw.githubusercontent.com/PostHog/{plugin_id}/main/logo.png" + ) data = { "template_id": f"plugin-{plugin_id}", - "type": "destination", + "type": kind, "name": plugin_name, "description": "This is a legacy destination migrated from our old plugin system.", "filters": {}, "inputs": inputs, "inputs_schema": inputs_schema, "enabled": True, - "icon_url": plugin_config["plugin__icon"], + "icon_url": icon_url, } print("Attempting to create hog function...") # noqa: T201 diff --git a/posthog/management/commands/migrate_plugins_to_hog_functions.py b/posthog/management/commands/migrate_plugins_to_hog_functions.py index ccc58b88604ab..279255f65bc14 100644 --- a/posthog/management/commands/migrate_plugins_to_hog_functions.py +++ b/posthog/management/commands/migrate_plugins_to_hog_functions.py @@ -16,12 +16,18 @@ def add_arguments(self, parser): parser.add_argument( "--test-mode", action="store_true", help="Whether to just copy as a test function rather than migrate" ) + parser.add_argument( + "--kind", + type=str, + help="Whether to migrate destinations or transformations", + choices=["destination", "transformation"], + ) def handle(self, *args, **options): dry_run = options["dry_run"] team_ids = options["team_ids"] test_mode = options["test_mode"] - + kind = options["kind"] print("Migrating plugins to hog functions", options) # noqa: T201 - migrate_legacy_plugins(dry_run=dry_run, team_ids=team_ids, test_mode=test_mode) + migrate_legacy_plugins(dry_run=dry_run, team_ids=team_ids, test_mode=test_mode, kind=kind) From b15e247d47d556d62c442e5e93f1d9de66034a4e Mon Sep 17 00:00:00 2001 From: Ben White Date: Fri, 31 Jan 2025 10:26:32 +0100 Subject: [PATCH 147/163] Support turning hog off --- posthog/cdp/migrations.py | 16 ++++++++++++---- posthog/cdp/test/test_validation.py | 16 ++++++++++++++++ posthog/cdp/validation.py | 6 ++++-- 3 files changed, 32 insertions(+), 6 deletions(-) diff --git a/posthog/cdp/migrations.py b/posthog/cdp/migrations.py index 4ff5d01a7dfaa..1161b895ec270 100644 --- a/posthog/cdp/migrations.py +++ b/posthog/cdp/migrations.py @@ -8,6 +8,11 @@ # python manage.py migrate_plugins_to_hog_functions --dry-run --test-mode +""" +from posthog.models.hog_functions.hog_function import HogFunction +HogFunction.objects.filter(type="transformation", name__contains="CDP-TEST-HIDDEN").delete() +""" + def migrate_legacy_plugins(dry_run=True, team_ids=None, test_mode=True, kind=str): # Get all legacy plugin_configs that are active with their attachments and global values @@ -89,6 +94,7 @@ def migrate_legacy_plugins(dry_run=True, team_ids=None, test_mode=True, kind=str "label": schema.get("name", schema["key"]), "secret": schema.get("secret", False), "required": schema.get("required", False), + "hog": False, } if schema.get("default"): @@ -115,11 +121,13 @@ def migrate_legacy_plugins(dry_run=True, team_ids=None, test_mode=True, kind=str for key, value in plugin_config["config"].items(): inputs[key] = {"value": value} - # Load all attachments for this plugin config - attachments = PluginAttachment.objects.filter(plugin_config_id=plugin_config["id"]) + if len(plugin_config["config"]) > 0: + # Load all attachments for this plugin config if there is some config + attachments = PluginAttachment.objects.filter(plugin_config_id=plugin_config["id"]) - for attachment in attachments: - inputs[attachment.key] = {"value": attachment.parse_contents()} + for attachment in attachments: + print("Attachment", attachment.key, attachment.parse_contents()) # noqa: T201 + inputs[attachment.key] = {"value": attachment.parse_contents()} team = teams_by_id[plugin_config["team_id"]] serializer_context = {"team": team, "get_team": (lambda t=team: t)} diff --git a/posthog/cdp/test/test_validation.py b/posthog/cdp/test/test_validation.py index aa5cc829d1a3c..fc71bee0d6054 100644 --- a/posthog/cdp/test/test_validation.py +++ b/posthog/cdp/test/test_validation.py @@ -292,3 +292,19 @@ def test_validate_inputs_with_extraneous_dependencies(self): validated = validate_inputs(inputs_schema, inputs) # Only A is present, so A=0 assert validated["A"]["order"] == 0 + + def test_validate_inputs_no_bytcode_if_not_hog(self): + # A depends on a non-existing input X + # This should ignore X since it's not defined. + # So no error, but A has no real dependencies that matter. + inputs_schema = [ + {"key": "A", "type": "string", "required": True, "hog": False}, + ] + inputs = { + "A": {"value": "{inputs.X} + A"}, + } + + validated = validate_inputs(inputs_schema, inputs) + assert validated["A"].get("bytecode") is None + assert validated["A"].get("transpiled") is None + assert validated["A"].get("value") == "{inputs.X} + A" diff --git a/posthog/cdp/validation.py b/posthog/cdp/validation.py index 255d34466fc28..56ddce38076b4 100644 --- a/posthog/cdp/validation.py +++ b/posthog/cdp/validation.py @@ -91,6 +91,8 @@ class InputsSchemaItemSerializer(serializers.Serializer): requires_field = serializers.CharField(required=False) integration_field = serializers.CharField(required=False) requiredScopes = serializers.CharField(required=False) + # Indicates if hog templating should be used for this input + hog = serializers.BooleanField(required=False, default=True) # TODO Validate choices if type=choice @@ -106,7 +108,6 @@ def to_representation(self, value): class InputsItemSerializer(serializers.Serializer): value = AnyInputField(required=False) bytecode = serializers.ListField(required=False, read_only=True) - # input_deps = serializers.ListField(required=False) order = serializers.IntegerField(required=False) transpiled = serializers.JSONField(required=False) @@ -148,7 +149,8 @@ def validate(self, attrs): raise serializers.ValidationError({"inputs": {name: f"Either 'text' or 'html' is required."}}) try: - if value: + if value and schema.get("hog"): + # If we have a value and hog templating is enabled, we need to transpile the value if item_type in ["string", "dictionary", "json", "email"]: if item_type == "email" and isinstance(value, dict): # We want to exclude the "design" property From 2de40c4046af97b0b9945e2579b687bb5bd5540c Mon Sep 17 00:00:00 2001 From: Ben White Date: Fri, 31 Jan 2025 11:19:30 +0100 Subject: [PATCH 148/163] Added templating var --- .../pipeline/hogfunctions/HogFunctionInputs.tsx | 17 +++++++++-------- frontend/src/types.ts | 1 + .../downsampling-plugin/template.ts | 2 ++ .../language-url-splitter-app/template.ts | 6 ++++++ .../template.ts | 7 +++++++ .../posthog-filter-out-plugin/template.ts | 3 +++ .../property-filter-plugin/template.ts | 1 + .../semver-flattener-plugin/template.ts | 1 + .../taxonomy-plugin/template.ts | 1 + .../user-agent-plugin/template.ts | 3 +++ plugin-server/src/cdp/types.ts | 1 + posthog/cdp/migrations.py | 17 ++++++++++++----- posthog/cdp/test/test_validation.py | 2 +- 13 files changed, 48 insertions(+), 14 deletions(-) diff --git a/frontend/src/scenes/pipeline/hogfunctions/HogFunctionInputs.tsx b/frontend/src/scenes/pipeline/hogfunctions/HogFunctionInputs.tsx index 8cbb21044fc1a..a6e99ef9b8d0e 100644 --- a/frontend/src/scenes/pipeline/hogfunctions/HogFunctionInputs.tsx +++ b/frontend/src/scenes/pipeline/hogfunctions/HogFunctionInputs.tsx @@ -126,7 +126,7 @@ function DictionaryField({ onChange, value }: { onChange?: (value: any) => void; /> { @@ -230,7 +230,7 @@ function HogFunctionInputSchemaControls({ return (
    -
    +
    ({ @@ -263,7 +263,7 @@ function HogFunctionInputSchemaControls({ Done
    -
    +
    } noPadding - className=" opacity-0 group-hover:opacity-100 p-1 transition-opacity" + className="p-1 transition-opacity opacity-0 group-hover:opacity-100" > Supports templating @@ -437,8 +438,8 @@ export function HogFunctionInputWithSchema({ )}
    {value?.secret ? ( -
    - +
    + This value is secret and is not displayed here. ) : ( -
    +
    Date: Fri, 31 Jan 2025 11:33:32 +0100 Subject: [PATCH 149/163] Fixes --- .../hogfunctions/HogFunctionInputs.tsx | 26 +++++++++++++++---- posthog/cdp/migrations.py | 2 +- posthog/cdp/validation.py | 4 +-- 3 files changed, 24 insertions(+), 8 deletions(-) diff --git a/frontend/src/scenes/pipeline/hogfunctions/HogFunctionInputs.tsx b/frontend/src/scenes/pipeline/hogfunctions/HogFunctionInputs.tsx index a6e99ef9b8d0e..ac73971ec4488 100644 --- a/frontend/src/scenes/pipeline/hogfunctions/HogFunctionInputs.tsx +++ b/frontend/src/scenes/pipeline/hogfunctions/HogFunctionInputs.tsx @@ -58,11 +58,12 @@ function JsonConfigField(props: { className?: string autoFocus?: boolean value?: string + templating?: boolean }): JSX.Element { const { globalsWithInputs } = useValues(hogFunctionConfigurationLogic) return ( props.onChange?.(v ?? '')} options={{ @@ -75,7 +76,7 @@ function JsonConfigField(props: { verticalScrollbarSize: 0, }, }} - globals={globalsWithInputs} + globals={props.templating ? globalsWithInputs : undefined} /> ) } @@ -96,8 +97,19 @@ function EmailTemplateField({ schema }: { schema: HogFunctionInputSchemaType }): ) } -function HogFunctionTemplateInput(props: Omit): JSX.Element { +function HogFunctionTemplateInput( + props: Omit & { + templating: boolean + onChange?: (value: string) => void + value?: string + } +): JSX.Element { const { globalsWithInputs } = useValues(hogFunctionConfigurationLogic) + + if (!props.templating) { + return + } + return } @@ -162,6 +174,7 @@ function DictionaryField({ onChange, value }: { onChange?: (value: any) => void; } export function HogFunctionInputRenderer({ value, onChange, schema, disabled }: HogFunctionInputProps): JSX.Element { + const templating = schema.templating ?? true switch (schema.type) { case 'string': return ( @@ -170,10 +183,13 @@ export function HogFunctionInputRenderer({ value, onChange, schema, disabled }: value={value} onChange={disabled ? () => {} : onChange} className="ph-no-capture" + templating={templating} /> ) case 'json': - return + return ( + + ) case 'choice': return ( ) case 'dictionary': - return + return case 'boolean': return onChange?.(checked)} disabled={disabled} /> case 'integration': diff --git a/posthog/cdp/migrations.py b/posthog/cdp/migrations.py index 01393ed78a3ee..8b802d124a91d 100644 --- a/posthog/cdp/migrations.py +++ b/posthog/cdp/migrations.py @@ -112,7 +112,6 @@ def migrate_legacy_plugins(dry_run=True, team_ids=None, test_mode=True, kind=str } for choice in schema["choices"] ] - input_schema["type"] = "string" elif schema["type"] == "attachment": input_schema["secret"] = schema["key"] == "googleCloudKeyJson" input_schema["type"] = "json" @@ -152,6 +151,7 @@ def migrate_legacy_plugins(dry_run=True, team_ids=None, test_mode=True, kind=str "inputs": inputs, "inputs_schema": inputs_schema, "enabled": True, + "hog": "return event", "icon_url": icon_url, } diff --git a/posthog/cdp/validation.py b/posthog/cdp/validation.py index 56ddce38076b4..383843e8d0c7b 100644 --- a/posthog/cdp/validation.py +++ b/posthog/cdp/validation.py @@ -92,7 +92,7 @@ class InputsSchemaItemSerializer(serializers.Serializer): integration_field = serializers.CharField(required=False) requiredScopes = serializers.CharField(required=False) # Indicates if hog templating should be used for this input - hog = serializers.BooleanField(required=False, default=True) + templating = serializers.BooleanField(required=False, default=True) # TODO Validate choices if type=choice @@ -149,7 +149,7 @@ def validate(self, attrs): raise serializers.ValidationError({"inputs": {name: f"Either 'text' or 'html' is required."}}) try: - if value and schema.get("hog"): + if value and schema.get("templating"): # If we have a value and hog templating is enabled, we need to transpile the value if item_type in ["string", "dictionary", "json", "email"]: if item_type == "email" and isinstance(value, dict): From da3c650f44822e247f7cd903299ff79667390df4 Mon Sep 17 00:00:00 2001 From: Ben White Date: Fri, 31 Jan 2025 11:37:47 +0100 Subject: [PATCH 150/163] Fixes --- posthog/cdp/migrations.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/posthog/cdp/migrations.py b/posthog/cdp/migrations.py index 8b802d124a91d..8701b84a00537 100644 --- a/posthog/cdp/migrations.py +++ b/posthog/cdp/migrations.py @@ -9,11 +9,6 @@ # python manage.py migrate_plugins_to_hog_functions --dry-run --test-mode -""" -from posthog.models.hog_functions.hog_function import HogFunction -HogFunction.objects.filter(type="transformation", name__contains="CDP-TEST-HIDDEN").delete() -""" - def migrate_legacy_plugins(dry_run=True, team_ids=None, test_mode=True, kind=str): # Get all legacy plugin_configs that are active with their attachments and global values From 372646b7acfd60671454149eb586597222b435a1 Mon Sep 17 00:00:00 2001 From: Ben White Date: Fri, 31 Jan 2025 11:52:01 +0100 Subject: [PATCH 151/163] Fixes --- .../drop-events-on-property-plugin/template.ts | 2 ++ .../_transformations/flatten-properties-plugin/template.ts | 1 + .../_transformations/plugin-advanced-geoip/template.ts | 2 ++ .../_transformations/posthog-anonymization/template.ts | 2 ++ .../_transformations/posthog-app-unduplicator/template.ts | 1 + .../posthog-plugin-snowplow-referer-parser/template.ts | 1 + .../_transformations/posthog-route-censor-plugin/template.ts | 4 ++++ 7 files changed, 13 insertions(+) diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/drop-events-on-property-plugin/template.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/drop-events-on-property-plugin/template.ts index 888df9d96b302..7879b1085ed54 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/drop-events-on-property-plugin/template.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/drop-events-on-property-plugin/template.ts @@ -14,6 +14,7 @@ export const template: HogFunctionTemplate = { inputs_schema: [ { key: 'property_key', + templating: false, description: 'Which property key to filter on. If you do not specify a value, all events with this key will be dropped.', label: 'Property key to filter on', @@ -22,6 +23,7 @@ export const template: HogFunctionTemplate = { }, { key: 'property_values', + templating: false, description: 'Which value to match to drop events. Split multiple values by comma to filter.', label: 'Property value to filter on', type: 'string', diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/flatten-properties-plugin/template.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/flatten-properties-plugin/template.ts index 496eca9d051a5..1b39b2acef27d 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/flatten-properties-plugin/template.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/flatten-properties-plugin/template.ts @@ -14,6 +14,7 @@ export const template: HogFunctionTemplate = { inputs_schema: [ { key: 'separator', + templating: false, description: "For example, to access the value of 'b' in a: { b: 1 } with separator '__', you can do 'a__b'", label: 'Select a separator format for accessing your nested properties', diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-advanced-geoip/template.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-advanced-geoip/template.ts index a1cba0a69c8c3..697815906fefe 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-advanced-geoip/template.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-advanced-geoip/template.ts @@ -14,6 +14,7 @@ export const template: HogFunctionTemplate = { inputs_schema: [ { key: 'discardIp', + templating: false, label: 'Discard IP addresses after GeoIP?', type: 'choice', choices: [ @@ -25,6 +26,7 @@ export const template: HogFunctionTemplate = { }, { key: 'discardLibs', + templating: false, label: 'Discard GeoIP for libraries', type: 'string', description: diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-anonymization/template.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-anonymization/template.ts index eef275a99a14c..c2614a1efc642 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-anonymization/template.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-anonymization/template.ts @@ -13,6 +13,7 @@ export const template: HogFunctionTemplate = { inputs_schema: [ { key: 'salt', + templating: false, label: 'The salt.', type: 'string', required: true, @@ -20,6 +21,7 @@ export const template: HogFunctionTemplate = { }, { key: 'privateFields', + templating: false, label: 'The names of fields to be anonymized divided by a colon.', type: 'string', default: 'distinct_id,name,userid', diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-app-unduplicator/template.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-app-unduplicator/template.ts index 3920a9bfed28c..860251c0af3af 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-app-unduplicator/template.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-app-unduplicator/template.ts @@ -13,6 +13,7 @@ export const template: HogFunctionTemplate = { inputs_schema: [ { key: 'dedupMode', + templating: false, label: 'Dedup Mode', type: 'choice', required: true, diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-plugin-snowplow-referer-parser/template.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-plugin-snowplow-referer-parser/template.ts index 4021ed2182bfe..f3fea0bda0ed4 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-plugin-snowplow-referer-parser/template.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-plugin-snowplow-referer-parser/template.ts @@ -13,6 +13,7 @@ export const template: HogFunctionTemplate = { inputs_schema: [ { key: 'internal_domains', + templating: false, label: 'Internal domains (comma delimited)', type: 'string', description: 'Consider these domains as direct referrers. Example: `example.com,blog.example.com`', diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-route-censor-plugin/template.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-route-censor-plugin/template.ts index cb0e3eaf048d9..424dc481b0349 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-route-censor-plugin/template.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-route-censor-plugin/template.ts @@ -13,6 +13,7 @@ export const template: HogFunctionTemplate = { inputs_schema: [ { key: 'routes', + templating: false, label: 'List of routes following the React Router route patterns.', description: '[Example Here](https://github.com/ava-labs/posthog-route-censor-plugin/blob/main/src/assets/exampleRoutes.json). See package [README](https://github.com/ava-labs/posthog-route-censor-plugin) for more details.', @@ -21,6 +22,7 @@ export const template: HogFunctionTemplate = { }, { key: 'properties', + templating: false, label: 'List of properties to censor', type: 'string', default: '$current_url,$referrer,$pathname,$initial_current_url,initial_pathname,initial_referrer', @@ -29,6 +31,7 @@ export const template: HogFunctionTemplate = { }, { key: 'set_properties', + templating: false, label: 'List of $set properties to censor', type: 'string', default: '$initial_current_url,$initial_pathname,$initial_referrer', @@ -37,6 +40,7 @@ export const template: HogFunctionTemplate = { }, { key: 'set_once_properties', + templating: false, label: 'List of $set_once properties to censor', type: 'string', default: '$initial_current_url,$initial_pathname,$initial_referrer', From af0875ff9cb2153ef3df74aa06b45e99b9d96a4b Mon Sep 17 00:00:00 2001 From: Ben White Date: Fri, 31 Jan 2025 12:05:00 +0100 Subject: [PATCH 152/163] Fixes --- .../plugin.json | 2 +- .../template.ts | 14 ++++++++++ plugin-server/src/cdp/templates/index.ts | 26 +++++++++++++++++++ 3 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/template.ts diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/plugin.json b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/plugin.json index 95a0e5d5f1f8a..c2248c7e2c48d 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/plugin.json +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/plugin.json @@ -1,6 +1,6 @@ { "name": "PostHog Netdata Event Processing", - "url": "https://github.com/andrewm4894/posthog-netdata-event-processing", + "url": "https://github.com/posthog/plugin-netdata-event-processing", "description": "A Posthog plugin to do some data processing on our Posthog events as they arrive.", "main": "index.js", "config": [] diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/template.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/template.ts new file mode 100644 index 0000000000000..134bcff6e927f --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/template.ts @@ -0,0 +1,14 @@ +import { HogFunctionTemplate } from '../../../templates/types' + +// NOTE: This is a deprecated plugin and should never be shown to new users +export const template: HogFunctionTemplate = { + status: 'alpha', + type: 'transformation', + id: 'plugin-plugin-netdata-event-processing', + name: 'Netdata Event Processing', + description: '', + icon_url: '/static/hedgehog/builder-hog-01.png', + category: ['Custom'], + hog: `return event`, + inputs_schema: [], +} diff --git a/plugin-server/src/cdp/templates/index.ts b/plugin-server/src/cdp/templates/index.ts index 9ae554aaac68e..2d2bcc4f0b8fb 100644 --- a/plugin-server/src/cdp/templates/index.ts +++ b/plugin-server/src/cdp/templates/index.ts @@ -1,7 +1,18 @@ import { template as downsamplingPlugin } from '../legacy-plugins/_transformations/downsampling-plugin/template' +import { template as dropEventsOnPropertyPluginTemplate } from '../legacy-plugins/_transformations/drop-events-on-property-plugin/template' +import { template as flattenPropertiesPluginTemplate } from '../legacy-plugins/_transformations/flatten-properties-plugin/template' import { template as languageUrlSplitterTemplate } from '../legacy-plugins/_transformations/language-url-splitter-app/template' +import { template as phShotgunProcessEventAppTemplate } from '../legacy-plugins/_transformations/ph-shotgun-processevent-app/template' +import { template as pluginAdvancedGeoipTemplate } from '../legacy-plugins/_transformations/plugin-advanced-geoip/template' +import { template as posthogNetdataEventProcessingPluginTemplate } from '../legacy-plugins/_transformations/plugin-netdata-event-processing/template' +import { template as pluginStonlyCleanCampaignNameTemplate } from '../legacy-plugins/_transformations/Plugin-Stonly-Clean-Campaign-Name/template' +import { template as pluginStonlyUtmExtractorTemplate } from '../legacy-plugins/_transformations/plugin-stonly-UTM-Extractor/template' +import { template as posthogAppUnduplicatorTemplate } from '../legacy-plugins/_transformations/posthog-app-unduplicator/template' import { template as posthogAppUrlParametersToEventPropertiesTemplate } from '../legacy-plugins/_transformations/posthog-app-url-parameters-to-event-properties/template' import { template as posthogFilterOutTemplate } from '../legacy-plugins/_transformations/posthog-filter-out-plugin/template' +import { template as posthogPluginGeoipTemplate } from '../legacy-plugins/_transformations/posthog-plugin-geoip/template' +import { template as posthogSnowplowRefererParserTemplate } from '../legacy-plugins/_transformations/posthog-plugin-snowplow-referer-parser/template' +import { template as posthogRouteCensorPluginTemplate } from '../legacy-plugins/_transformations/posthog-route-censor-plugin/template' import { template as posthogUrlNormalizerTemplate } from '../legacy-plugins/_transformations/posthog-url-normalizer-plugin/template' import { template as propertyFilterTemplate } from '../legacy-plugins/_transformations/property-filter-plugin/template' import { template as semverFlattenerTemplate } from '../legacy-plugins/_transformations/semver-flattener-plugin/template' @@ -30,7 +41,22 @@ export const HOG_FUNCTION_TEMPLATES_TRANSFORMATIONS: HogFunctionTemplate[] = [ userAgentTemplate, ] +export const HOG_FUNCTION_TEMPLATES_TRANSFORMATIONS_DEPRECATED: HogFunctionTemplate[] = [ + dropEventsOnPropertyPluginTemplate, + flattenPropertiesPluginTemplate, + phShotgunProcessEventAppTemplate, + pluginAdvancedGeoipTemplate, + posthogNetdataEventProcessingPluginTemplate, + pluginStonlyCleanCampaignNameTemplate, + pluginStonlyUtmExtractorTemplate, + posthogAppUnduplicatorTemplate, + posthogPluginGeoipTemplate, + posthogSnowplowRefererParserTemplate, + posthogRouteCensorPluginTemplate, +] + export const HOG_FUNCTION_TEMPLATES: HogFunctionTemplate[] = [ ...HOG_FUNCTION_TEMPLATES_DESTINATIONS, ...HOG_FUNCTION_TEMPLATES_TRANSFORMATIONS, + ...HOG_FUNCTION_TEMPLATES_TRANSFORMATIONS_DEPRECATED, ] From 572a2bc10be3b76bd56507cd313216d18b3c6e3d Mon Sep 17 00:00:00 2001 From: Ben White Date: Fri, 31 Jan 2025 12:12:08 +0100 Subject: [PATCH 153/163] Fix --- posthog/cdp/migrations.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/posthog/cdp/migrations.py b/posthog/cdp/migrations.py index 8701b84a00537..e22d83aa9b785 100644 --- a/posthog/cdp/migrations.py +++ b/posthog/cdp/migrations.py @@ -40,8 +40,6 @@ def migrate_legacy_plugins(dry_run=True, team_ids=None, test_mode=True, kind=str else: raise ValueError(f"Invalid kind: {kind}") - print(legacy_plugins.query, legacy_plugins.count()) - if team_ids: legacy_plugins = legacy_plugins.filter(team_id__in=team_ids) From 961f2eaddb8ffcfa6a6f3150829e7d1eee785e43 Mon Sep 17 00:00:00 2001 From: Ben White Date: Fri, 31 Jan 2025 12:13:45 +0100 Subject: [PATCH 154/163] Fixes --- posthog/cdp/validation.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/posthog/cdp/validation.py b/posthog/cdp/validation.py index 383843e8d0c7b..96e048e934338 100644 --- a/posthog/cdp/validation.py +++ b/posthog/cdp/validation.py @@ -92,7 +92,7 @@ class InputsSchemaItemSerializer(serializers.Serializer): integration_field = serializers.CharField(required=False) requiredScopes = serializers.CharField(required=False) # Indicates if hog templating should be used for this input - templating = serializers.BooleanField(required=False, default=True) + templating = serializers.BooleanField(required=False) # TODO Validate choices if type=choice @@ -149,7 +149,7 @@ def validate(self, attrs): raise serializers.ValidationError({"inputs": {name: f"Either 'text' or 'html' is required."}}) try: - if value and schema.get("templating"): + if value and schema.get("templating", True): # If we have a value and hog templating is enabled, we need to transpile the value if item_type in ["string", "dictionary", "json", "email"]: if item_type == "email" and isinstance(value, dict): From 9fbd62afb0ce9a2c02a3656fa87ec7224af24398 Mon Sep 17 00:00:00 2001 From: Ben White Date: Fri, 31 Jan 2025 12:14:07 +0100 Subject: [PATCH 155/163] Fixes --- .../ingestion/event-pipeline/compareToHogTransformStep.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/plugin-server/src/worker/ingestion/event-pipeline/compareToHogTransformStep.ts b/plugin-server/src/worker/ingestion/event-pipeline/compareToHogTransformStep.ts index a6317b4176d74..40e17d4e870ed 100644 --- a/plugin-server/src/worker/ingestion/event-pipeline/compareToHogTransformStep.ts +++ b/plugin-server/src/worker/ingestion/event-pipeline/compareToHogTransformStep.ts @@ -1,10 +1,9 @@ import { PluginEvent } from '@posthog/plugin-scaffold' import { Counter } from 'prom-client' -import { cloneObject } from '~/src/utils/utils' - import { HogTransformerService } from '../../../cdp/hog-transformations/hog-transformer.service' import { status } from '../../../utils/status' +import { cloneObject } from '../../../utils/utils' type Diff = { key: string From 1047e5e9c5c00fa7bcb9de78ac297591651838af Mon Sep 17 00:00:00 2001 From: Ben White Date: Fri, 31 Jan 2025 12:16:02 +0100 Subject: [PATCH 156/163] Fixes --- .../src/worker/ingestion/event-pipeline/runner.ts | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/plugin-server/src/worker/ingestion/event-pipeline/runner.ts b/plugin-server/src/worker/ingestion/event-pipeline/runner.ts index 8233f99b63ebf..43e7043b01a1c 100644 --- a/plugin-server/src/worker/ingestion/event-pipeline/runner.ts +++ b/plugin-server/src/worker/ingestion/event-pipeline/runner.ts @@ -234,16 +234,17 @@ export class EventPipelineRunner { const processedEvent = await this.runStep(pluginsProcessEventStep, [this, postCookielessEvent], event.team_id) - await this.runStep( - compareToHogTransformStep, - [ + // NOTE: We don't use the step process here as we don't want it to interfere with other metrics + try { + await compareToHogTransformStep( this.hogTransformer, postCookielessEvent, processedEvent, - this.hub.HOG_TRANSFORMATIONS_COMPARISON_PERCENTAGE, - ], - event.team_id - ) + this.hub.HOG_TRANSFORMATIONS_COMPARISON_PERCENTAGE + ) + } catch (error) { + status.error('🔔', 'Error comparing to hog transform', { error }) + } if (processedEvent == null) { // A plugin dropped the event. From 0495a601e766fca67002d7db0031e07c5ae75023 Mon Sep 17 00:00:00 2001 From: Ben White Date: Fri, 31 Jan 2025 12:20:29 +0100 Subject: [PATCH 157/163] Fix --- .../ingestion/event-pipeline/compareToHogTransformStep.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin-server/src/worker/ingestion/event-pipeline/compareToHogTransformStep.ts b/plugin-server/src/worker/ingestion/event-pipeline/compareToHogTransformStep.ts index 40e17d4e870ed..64b454ce3fcc4 100644 --- a/plugin-server/src/worker/ingestion/event-pipeline/compareToHogTransformStep.ts +++ b/plugin-server/src/worker/ingestion/event-pipeline/compareToHogTransformStep.ts @@ -90,6 +90,6 @@ export async function compareToHogTransformStep( counterHogTransformationDiff.inc({ outcome: 'same' }) } } catch (error) { - status.error('Error occured when comparing plugin event to hog transform', error) + status.error('Error occurred when comparing plugin event to hog transform', error) } } From 9c211f0bf7be0cfeb07fd29971cf1b407ba52d80 Mon Sep 17 00:00:00 2001 From: Ben White Date: Fri, 31 Jan 2025 12:22:28 +0100 Subject: [PATCH 158/163] Fixes --- .../posthog-plugin-snowplow-referer-parser/index.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-plugin-snowplow-referer-parser/index.test.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-plugin-snowplow-referer-parser/index.test.ts index b58563e6981e5..ac41bac8c4087 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-plugin-snowplow-referer-parser/index.test.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-plugin-snowplow-referer-parser/index.test.ts @@ -1,4 +1,3 @@ -import { LegacyTransformationPluginMeta } from '../../types' import { processEvent } from '.' const demoEvents = [ From c62c8483488f0b3222831a2327343bca9a98f654 Mon Sep 17 00:00:00 2001 From: Ben White Date: Fri, 31 Jan 2025 15:29:15 +0100 Subject: [PATCH 159/163] Fixes --- .../src/cdp/consumers/cdp-cyclotron-plugins-worker.test.ts | 3 +++ .../cdp/legacy-plugins/_destinations/customerio/index.ts | 3 ++- .../src/cdp/legacy-plugins/_destinations/intercom/index.ts | 3 ++- .../Plugin-Stonly-Clean-Campaign-Name/index.ts | 3 +++ .../Plugin-Stonly-Clean-Campaign-Name/plugin.json | 6 ++++++ .../_transformations/downsampling-plugin/index.ts | 2 ++ .../drop-events-on-property-plugin/index.ts | 2 ++ .../_transformations/flatten-properties-plugin/index.ts | 2 ++ .../_transformations/language-url-splitter-app/index.ts | 2 ++ .../_transformations/ph-shotgun-processevent-app/index.ts | 4 +++- .../_transformations/plugin-advanced-geoip/index.ts | 2 ++ .../plugin-netdata-event-processing/index.ts | 3 ++- .../_transformations/plugin-stonly-UTM-Extractor/index.ts | 3 +++ .../plugin-stonly-UTM-Extractor/plugin.json | 6 ++++++ .../_transformations/posthog-anonymization/index.ts | 2 ++ .../_transformations/posthog-app-unduplicator/index.ts | 2 ++ .../posthog-app-url-parameters-to-event-properties/index.ts | 2 ++ .../_transformations/posthog-filter-out-plugin/index.ts | 2 ++ .../_transformations/posthog-plugin-geoip/index.ts | 2 ++ .../posthog-plugin-snowplow-referer-parser/index.ts | 2 ++ .../_transformations/posthog-route-censor-plugin/index.ts | 2 ++ .../_transformations/posthog-url-normalizer-plugin/index.ts | 5 +++-- .../_transformations/property-filter-plugin/index.ts | 2 ++ .../_transformations/semver-flattener-plugin/index.ts | 2 ++ .../_transformations/taxonomy-plugin/index.ts | 2 ++ .../_transformations/timestamp-parser-plugin/index.ts | 2 ++ .../_transformations/user-agent-plugin/index.ts | 3 +++ plugin-server/src/cdp/legacy-plugins/types.ts | 2 ++ .../src/cdp/services/legacy-plugin-executor.test.ts | 3 +++ 29 files changed, 73 insertions(+), 6 deletions(-) create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/Plugin-Stonly-Clean-Campaign-Name/plugin.json create mode 100644 plugin-server/src/cdp/legacy-plugins/_transformations/plugin-stonly-UTM-Extractor/plugin.json diff --git a/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.test.ts b/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.test.ts index 7d9a3743a76ba..d1a9bf95a610b 100644 --- a/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.test.ts +++ b/plugin-server/src/cdp/consumers/cdp-cyclotron-plugins-worker.test.ts @@ -127,6 +127,9 @@ describe('CdpCyclotronWorkerPlugins', () => { "useEuropeanDataStorage": "No", }, "fetch": [Function], + "geoip": { + "locate": [Function], + }, "global": {}, "logger": { "debug": [Function], diff --git a/plugin-server/src/cdp/legacy-plugins/_destinations/customerio/index.ts b/plugin-server/src/cdp/legacy-plugins/_destinations/customerio/index.ts index 04c69ed4d70bd..023a6d7e139b7 100644 --- a/plugin-server/src/cdp/legacy-plugins/_destinations/customerio/index.ts +++ b/plugin-server/src/cdp/legacy-plugins/_destinations/customerio/index.ts @@ -4,7 +4,7 @@ import { RetryError } from '@posthog/plugin-scaffold' import { Response } from '~/src/utils/fetch' import { LegacyDestinationPlugin, LegacyDestinationPluginMeta } from '../../types' - +import metadata from './plugin.json' const DEFAULT_HOST = 'track.customer.io' const DEFAULT_SEND_EVENTS_FROM_ANONYMOUS_USERS = 'Send all events' @@ -256,6 +256,7 @@ function getEmailFromEvent(event: ProcessedPluginEvent): string | null { export const customerioPlugin: LegacyDestinationPlugin = { id: 'customerio-plugin', + metadata, setupPlugin: setupPlugin as any, onEvent, } diff --git a/plugin-server/src/cdp/legacy-plugins/_destinations/intercom/index.ts b/plugin-server/src/cdp/legacy-plugins/_destinations/intercom/index.ts index d49e187652219..37ebc4189c5e7 100644 --- a/plugin-server/src/cdp/legacy-plugins/_destinations/intercom/index.ts +++ b/plugin-server/src/cdp/legacy-plugins/_destinations/intercom/index.ts @@ -3,7 +3,7 @@ import { ProcessedPluginEvent, RetryError } from '@posthog/plugin-scaffold' import { Response } from '~/src/utils/fetch' import { LegacyDestinationPlugin, LegacyDestinationPluginMeta } from '../../types' - +import metadata from './plugin.json' type IntercomMeta = LegacyDestinationPluginMeta & { global: { intercomUrl: string @@ -190,6 +190,7 @@ function getTimestamp(meta: IntercomMeta, event: ProcessedPluginEvent): number { export const intercomPlugin: LegacyDestinationPlugin = { id: 'posthog-intercom-plugin', + metadata, onEvent, setupPlugin: () => Promise.resolve(), } diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/Plugin-Stonly-Clean-Campaign-Name/index.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/Plugin-Stonly-Clean-Campaign-Name/index.ts index 00912ed93cc8f..d49763b7d7762 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/Plugin-Stonly-Clean-Campaign-Name/index.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/Plugin-Stonly-Clean-Campaign-Name/index.ts @@ -1,6 +1,8 @@ import { PluginEvent } from '@posthog/plugin-scaffold' import { LegacyTransformationPlugin, LegacyTransformationPluginMeta } from '../../types' +import metadata from './plugin.json' + const cleanUtmCampain = (utmCampaign: string) => { return utmCampaign .replace('com-', 'comp-') @@ -32,5 +34,6 @@ export function processEvent(event: PluginEvent, _: LegacyTransformationPluginMe export const pluginStonlyCleanCampaignName: LegacyTransformationPlugin = { id: 'plugin-stonly-clean-campaign-name', + metadata, processEvent, } diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/Plugin-Stonly-Clean-Campaign-Name/plugin.json b/plugin-server/src/cdp/legacy-plugins/_transformations/Plugin-Stonly-Clean-Campaign-Name/plugin.json new file mode 100644 index 0000000000000..10f5f7ca3fd58 --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/Plugin-Stonly-Clean-Campaign-Name/plugin.json @@ -0,0 +1,6 @@ +{ + "name": "Clean Campaign Name", + "url": "https://github.com/posthog/Plugin-Stonly-Clean-Campaign-Name", + "description": "", + "config": [] +} diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/downsampling-plugin/index.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/downsampling-plugin/index.ts index fcb9756998580..b5cbedc00db39 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/downsampling-plugin/index.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/downsampling-plugin/index.ts @@ -2,6 +2,7 @@ import { PluginEvent } from '@posthog/plugin-scaffold' import { createHash } from 'crypto' import { LegacyTransformationPlugin, LegacyTransformationPluginMeta } from '../../types' +import metadata from './plugin.json' export function setupPlugin({ config, global }: LegacyTransformationPluginMeta) { const percentage = parseFloat(config.percentage) @@ -38,6 +39,7 @@ export function processEvent(event: PluginEvent, { global }: LegacyTransformatio export const downsamplingPlugin: LegacyTransformationPlugin = { id: 'downsampling-plugin', + metadata, processEvent, setupPlugin, } diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/drop-events-on-property-plugin/index.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/drop-events-on-property-plugin/index.ts index f60ba936447a5..9e1aef2f90b89 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/drop-events-on-property-plugin/index.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/drop-events-on-property-plugin/index.ts @@ -3,6 +3,7 @@ import { PluginEvent } from '@posthog/plugin-scaffold' import { LegacyTransformationPlugin, LegacyTransformationPluginMeta } from '../../types' +import metadata from './plugin.json' // Processes each event, optionally transforming it export function processEvent(event: PluginEvent, { config }: LegacyTransformationPluginMeta) { @@ -22,5 +23,6 @@ export function processEvent(event: PluginEvent, { config }: LegacyTransformatio export const dropEventsOnPropertyPlugin: LegacyTransformationPlugin = { id: 'drop-events-on-property-plugin', + metadata, processEvent, } diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/flatten-properties-plugin/index.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/flatten-properties-plugin/index.ts index 1f1725d0472e1..0e0c95a5da24a 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/flatten-properties-plugin/index.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/flatten-properties-plugin/index.ts @@ -1,6 +1,7 @@ import { PluginEvent } from '@posthog/plugin-scaffold' import { LegacyTransformationPlugin, LegacyTransformationPluginMeta } from '../../types' +import metadata from './plugin.json' /** * Some events will always create very large numbers of flattened properties * This is undesirable since a large enough number of properties for a particular team can slow down the property filter in the UI @@ -58,5 +59,6 @@ const flattenProperties = (props: Record, sep: string, nestedChain: export const flattenPropertiesPlugin: LegacyTransformationPlugin = { id: 'flatten-properties-plugin', + metadata, processEvent, } diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/language-url-splitter-app/index.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/language-url-splitter-app/index.ts index 9c750e9c003eb..d204991de5631 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/language-url-splitter-app/index.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/language-url-splitter-app/index.ts @@ -1,6 +1,7 @@ import { PluginEvent } from '@posthog/plugin-scaffold' import { LegacyTransformationPlugin, LegacyTransformationPluginMeta } from '../../types' +import metadata from './plugin.json' export function processEvent(event: PluginEvent, { config }: LegacyTransformationPluginMeta) { const { pattern, matchGroup, property, replacePattern, replaceKey, replaceValue } = config @@ -20,5 +21,6 @@ export function processEvent(event: PluginEvent, { config }: LegacyTransformatio export const languageUrlSplitterApp: LegacyTransformationPlugin = { id: 'language-url-splitter-app', + metadata, processEvent, } diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/ph-shotgun-processevent-app/index.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/ph-shotgun-processevent-app/index.ts index 3dbee9af91a42..1188d710eae66 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/ph-shotgun-processevent-app/index.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/ph-shotgun-processevent-app/index.ts @@ -1,6 +1,7 @@ import { PluginEvent } from '@posthog/plugin-scaffold' -import { LegacyPluginMeta, LegacyTransformationPlugin } from '../../types' +import { LegacyTransformationPlugin, LegacyPluginMeta } from '../../types' +import metadata from './plugin.json' export function processEvent(event: PluginEvent, _: LegacyPluginMeta) { switch (event.event) { @@ -189,5 +190,6 @@ export function processEvent(event: PluginEvent, _: LegacyPluginMeta) { export const phShotgunProcessEventApp: LegacyTransformationPlugin = { id: 'ph-shotgun-processevent-app', + metadata, processEvent, } diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-advanced-geoip/index.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-advanced-geoip/index.ts index 8515e60c1f280..1c8e03928fe7f 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-advanced-geoip/index.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-advanced-geoip/index.ts @@ -1,6 +1,7 @@ import { PluginEvent } from '@posthog/plugin-scaffold' import { LegacyTransformationPlugin, LegacyTransformationPluginMeta } from '../../types' +import metadata from './plugin.json' const geoIpProps = [ '$geoip_city_name', @@ -100,5 +101,6 @@ export const processEvent = (event: PluginEvent, { config, logger }: LegacyTrans export const pluginAdvancedGeoip: LegacyTransformationPlugin = { id: 'plugin-advanced-geoip', + metadata, processEvent, } diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/index.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/index.ts index 19cd7687fe66c..ead5f6234dc3c 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/index.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-netdata-event-processing/index.ts @@ -1,7 +1,8 @@ import { LegacyTransformationPlugin } from '../../types' import { processEvent } from './dist' - +import metadata from './plugin.json' export const posthogNetdataEventProcessingPlugin: LegacyTransformationPlugin = { id: 'posthog-plugin-netdata-event-processing-plugin', + metadata, processEvent: processEvent as any, } diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-stonly-UTM-Extractor/index.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-stonly-UTM-Extractor/index.ts index dfd5ac033916b..babfc1d1b0ae5 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-stonly-UTM-Extractor/index.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-stonly-UTM-Extractor/index.ts @@ -2,6 +2,8 @@ import { PluginEvent } from '@posthog/plugin-scaffold' import { URL } from 'url' import { LegacyTransformationPlugin, LegacyTransformationPluginMeta } from '../../types' +import metadata from './plugin.json' + // Processes each event, optionally transforming it const extractUtmFromUrl = (urlString: string, utm: string) => { @@ -42,5 +44,6 @@ export function processEvent(event: PluginEvent, _: LegacyTransformationPluginMe export const pluginStonlyUtmExtractor: LegacyTransformationPlugin = { id: 'plugin-stonly-utm-extractor', + metadata, processEvent, } diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-stonly-UTM-Extractor/plugin.json b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-stonly-UTM-Extractor/plugin.json new file mode 100644 index 0000000000000..c272927adc6e9 --- /dev/null +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/plugin-stonly-UTM-Extractor/plugin.json @@ -0,0 +1,6 @@ +{ + "name": "UTM Extractor", + "url": "https://github.com/posthog/Plugin-Stonly-Clean-Campaign-Name", + "description": "", + "config": [] +} diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-anonymization/index.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-anonymization/index.ts index 72225edd96878..3ed23479c17d6 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-anonymization/index.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-anonymization/index.ts @@ -1,7 +1,9 @@ import { LegacyTransformationPlugin } from '../../types' +import metadata from './plugin.json' import { processEvent } from './src/processEvent' export const pluginPosthogAnonymization: LegacyTransformationPlugin = { id: 'plugin-posthog-anonymization', + metadata, processEvent, } diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-app-unduplicator/index.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-app-unduplicator/index.ts index 88236b5c2e145..442d6f50b74a2 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-app-unduplicator/index.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-app-unduplicator/index.ts @@ -2,6 +2,7 @@ import { PluginEvent } from '@posthog/plugin-scaffold' import { createHash } from 'crypto' import { LegacyTransformationPlugin, LegacyTransformationPluginMeta } from '../../types' +import metadata from './plugin.json' // From UUID Namespace RFC (https://datatracker.ietf.org/doc/html/rfc4122) const NAMESPACE_OID = '6ba7b812-9dad-11d1-80b4-00c04fd430c8' @@ -73,5 +74,6 @@ export function processEvent(event: PluginEvent, { config }: LegacyTransformatio export const posthogAppUnduplicator: LegacyTransformationPlugin = { id: 'posthog-app-unduplicator', + metadata, processEvent, } diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-app-url-parameters-to-event-properties/index.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-app-url-parameters-to-event-properties/index.ts index affa7c067f22e..c63a8a292f1df 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-app-url-parameters-to-event-properties/index.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-app-url-parameters-to-event-properties/index.ts @@ -2,6 +2,7 @@ import { PluginEvent } from '@posthog/plugin-scaffold' import { URLSearchParams } from 'url' import { LegacyTransformationPlugin, LegacyTransformationPluginMeta } from '../../types' +import metadata from './plugin.json' export type PluginConfig = { ignoreCase: 'true' | 'false' @@ -86,6 +87,7 @@ export const processEvent = (event: PluginEvent, meta: LocalMeta): PluginEvent = export const posthogAppUrlParametersToEventPropertiesPlugin: LegacyTransformationPlugin = { id: 'posthog-app-url-parameters-to-event-properties', + metadata, processEvent, setupPlugin: setupPlugin as any, } diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-filter-out-plugin/index.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-filter-out-plugin/index.ts index 852f92f774506..0e86ec87612ff 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-filter-out-plugin/index.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-filter-out-plugin/index.ts @@ -1,6 +1,7 @@ import { Meta, PluginAttachment, PluginEvent } from '@posthog/plugin-scaffold' import { LegacyTransformationPlugin, LegacyTransformationPluginMeta } from '../../types' +import metadata from './plugin.json' export interface Filter { property: string @@ -128,6 +129,7 @@ const parseFiltersAndMigrate = (filters: Filter[][] | Filter[]): Filter[][] => { export const posthogFilterOutPlugin: LegacyTransformationPlugin = { id: 'posthog-filter-out-plugin', + metadata, processEvent, setupPlugin, } diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-plugin-geoip/index.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-plugin-geoip/index.ts index c95698d542ee3..7d8c885c6f349 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-plugin-geoip/index.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-plugin-geoip/index.ts @@ -1,6 +1,7 @@ import { PluginEvent } from '@posthog/plugin-scaffold' import { LegacyTransformationPlugin, LegacyTransformationPluginMeta } from '../../types' +import metadata from './plugin.json' const props = { city_name: null, @@ -115,5 +116,6 @@ export const processEvent = (event: PluginEvent, { geoip }: LegacyTransformation export const posthogPluginGeoip: LegacyTransformationPlugin = { id: 'posthog-plugin-geoip', + metadata, processEvent, } diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-plugin-snowplow-referer-parser/index.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-plugin-snowplow-referer-parser/index.ts index aab08585b55de..99439be83d273 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-plugin-snowplow-referer-parser/index.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-plugin-snowplow-referer-parser/index.ts @@ -1,6 +1,7 @@ import { PluginEvent } from '@posthog/plugin-scaffold' import { LegacyTransformationPlugin, LegacyTransformationPluginMeta } from '../../types' +import metadata from './plugin.json' export function processEvent(event: PluginEvent, { logger }: LegacyTransformationPluginMeta) { const props = event.properties @@ -3184,5 +3185,6 @@ const directReferrerDomains = [ export const posthogSnowplowRefererParser: LegacyTransformationPlugin = { id: 'plugin-posthog-plugin-snowplow-referer-parser', + metadata, processEvent, } diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-route-censor-plugin/index.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-route-censor-plugin/index.ts index 5c26102cb3ae8..e373e6ee26a83 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-route-censor-plugin/index.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-route-censor-plugin/index.ts @@ -1,5 +1,6 @@ import { LegacyTransformationPlugin, LegacyTransformationPluginMeta } from '../../types' import { processEvent } from './dist' +import metadata from './plugin.json' // NOTE: The dist.js is a compiled version of the plugin as it is has external dependencies that it inlines // It is mostly untouched other than removing console logs and moving the setup code here @@ -14,6 +15,7 @@ const setupPlugin = ({ global, config, logger }: LegacyTransformationPluginMeta) export const posthogRouteCensorPlugin: LegacyTransformationPlugin = { id: 'posthog-route-censor-plugin', + metadata, processEvent, setupPlugin, } diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-url-normalizer-plugin/index.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-url-normalizer-plugin/index.ts index 2b19017399127..165dd88d59945 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-url-normalizer-plugin/index.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/posthog-url-normalizer-plugin/index.ts @@ -1,7 +1,7 @@ import { PluginEvent } from '@posthog/plugin-scaffold' -import { LegacyTransformationPluginMeta } from '../../types' -import { LegacyTransformationPlugin } from '../../types' +import { LegacyTransformationPlugin, LegacyTransformationPluginMeta } from '../../types' +import metadata from './plugin.json' function normalizeUrl(url: string): string { try { @@ -28,5 +28,6 @@ export function processEvent(event: PluginEvent, { logger }: LegacyTransformatio export const posthogUrlNormalizerPlugin: LegacyTransformationPlugin = { id: 'posthog-url-normalizer-plugin', + metadata, processEvent, } diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/property-filter-plugin/index.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/property-filter-plugin/index.ts index 9a63154851b2a..f67b3b988f899 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/property-filter-plugin/index.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/property-filter-plugin/index.ts @@ -1,6 +1,7 @@ import { PluginEvent } from '@posthog/plugin-scaffold' import { LegacyTransformationPlugin, LegacyTransformationPluginMeta } from '../../types' +import metadata from './plugin.json' export function setupPlugin({ config, global }: LegacyTransformationPluginMeta) { global.propertiesToFilter = config.properties.split(',') @@ -35,6 +36,7 @@ export function processEvent(event: PluginEvent, { global }: LegacyTransformatio export const propertyFilterPlugin: LegacyTransformationPlugin = { id: 'property-filter-plugin', + metadata, processEvent, setupPlugin, } diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/semver-flattener-plugin/index.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/semver-flattener-plugin/index.ts index dc67495ffa76c..fd100e97b895d 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/semver-flattener-plugin/index.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/semver-flattener-plugin/index.ts @@ -1,6 +1,7 @@ import { PluginEvent } from '@posthog/plugin-scaffold' import { LegacyTransformationPlugin, LegacyTransformationPluginMeta } from '../../types' +import metadata from './plugin.json' interface VersionParts { major: number @@ -57,5 +58,6 @@ export function processEvent(event: PluginEvent, meta: LegacyTransformationPlugi export const semverFlattenerPlugin: LegacyTransformationPlugin = { id: 'semver-flattener-plugin', + metadata, processEvent, } diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/taxonomy-plugin/index.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/taxonomy-plugin/index.ts index 95ddb35d9ad0f..c702ea8ff1630 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/taxonomy-plugin/index.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/taxonomy-plugin/index.ts @@ -1,6 +1,7 @@ import { PluginEvent } from '@posthog/plugin-scaffold' import { LegacyTransformationPlugin, LegacyTransformationPluginMeta } from '../../types' +import metadata from './plugin.json' type Transformation = { name: string @@ -81,5 +82,6 @@ const standardizeName = (name: string, desiredPattern: Transformation) => { export const taxonomyPlugin: LegacyTransformationPlugin = { id: 'taxonomy-plugin', + metadata, processEvent, } diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/timestamp-parser-plugin/index.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/timestamp-parser-plugin/index.ts index 365b47eb3025a..0c4cc2e9145b4 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/timestamp-parser-plugin/index.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/timestamp-parser-plugin/index.ts @@ -1,6 +1,7 @@ import { PluginEvent } from '@posthog/plugin-scaffold' import { LegacyTransformationPlugin, LegacyTransformationPluginMeta } from '../../types' +import metadata from './plugin.json' function processEvent(event: PluginEvent, _meta: LegacyTransformationPluginMeta) { if (event.properties && event['timestamp'] && !isNaN(event['timestamp'] as any)) { @@ -19,5 +20,6 @@ function processEvent(event: PluginEvent, _meta: LegacyTransformationPluginMeta) export const timestampParserPlugin: LegacyTransformationPlugin = { id: 'timestamp-parser-plugin', + metadata, processEvent, } diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/user-agent-plugin/index.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/user-agent-plugin/index.ts index eab524d160807..201aa97722368 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/user-agent-plugin/index.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/user-agent-plugin/index.ts @@ -2,6 +2,7 @@ import { PluginEvent } from '@posthog/plugin-scaffold' import { detect } from 'detect-browser' import { LegacyTransformationPlugin, LegacyTransformationPluginMeta } from '../../types' +import metadata from './plugin.json' export type UserAgentMeta = LegacyTransformationPluginMeta & { config: { @@ -162,5 +163,7 @@ function detectDeviceType(userAgent: string) { export const userAgentPlugin: LegacyTransformationPlugin = { id: 'user-agent-plugin', + metadata, processEvent, + setupPlugin, } diff --git a/plugin-server/src/cdp/legacy-plugins/types.ts b/plugin-server/src/cdp/legacy-plugins/types.ts index 69ba04310318a..72411e45027a3 100644 --- a/plugin-server/src/cdp/legacy-plugins/types.ts +++ b/plugin-server/src/cdp/legacy-plugins/types.ts @@ -27,12 +27,14 @@ export type LegacyDestinationPluginMeta = LegacyTransformationPluginMeta & { export type LegacyDestinationPlugin = { id: string + metadata: any onEvent(event: ProcessedPluginEvent, meta: LegacyDestinationPluginMeta): Promise setupPlugin?: (meta: LegacyDestinationPluginMeta) => Promise } export type LegacyTransformationPlugin = { id: string + metadata: any processEvent(event: PluginEvent, meta: LegacyTransformationPluginMeta): PluginEvent | undefined | null setupPlugin?: (meta: LegacyTransformationPluginMeta) => void } diff --git a/plugin-server/src/cdp/services/legacy-plugin-executor.test.ts b/plugin-server/src/cdp/services/legacy-plugin-executor.test.ts index f41973abf554b..1f192edc7fbf6 100644 --- a/plugin-server/src/cdp/services/legacy-plugin-executor.test.ts +++ b/plugin-server/src/cdp/services/legacy-plugin-executor.test.ts @@ -117,6 +117,9 @@ describe('LegacyPluginExecutorService', () => { "useEuropeanDataStorage": "No", }, "fetch": [Function], + "geoip": { + "locate": [Function], + }, "global": {}, "logger": { "debug": [Function], From 64bf5de055f4f2a1d35f280497225a7a44aaf55f Mon Sep 17 00:00:00 2001 From: Ben White Date: Fri, 31 Jan 2025 15:38:01 +0100 Subject: [PATCH 160/163] Fixes --- plugin-server/src/cdp/legacy-plugins/index.ts | 27 ++--- .../legacy-plugin-executor.test.ts.snap | 106 ++++++++++++++++++ .../services/legacy-plugin-executor.test.ts | 6 +- 3 files changed, 125 insertions(+), 14 deletions(-) diff --git a/plugin-server/src/cdp/legacy-plugins/index.ts b/plugin-server/src/cdp/legacy-plugins/index.ts index d10c861a004d2..a8b6f903b9f75 100644 --- a/plugin-server/src/cdp/legacy-plugins/index.ts +++ b/plugin-server/src/cdp/legacy-plugins/index.ts @@ -27,19 +27,6 @@ export const DESTINATION_PLUGINS_BY_ID = { [intercomPlugin.id]: intercomPlugin, } -export const TRANSFORMATION_PLUGINS_BY_ID = { - [downsamplingPlugin.id]: downsamplingPlugin, - [languageUrlSplitterApp.id]: languageUrlSplitterApp, - [posthogAppUrlParametersToEventPropertiesPlugin.id]: posthogAppUrlParametersToEventPropertiesPlugin, - [posthogFilterOutPlugin.id]: posthogFilterOutPlugin, - [posthogUrlNormalizerPlugin.id]: posthogUrlNormalizerPlugin, - [propertyFilterPlugin.id]: propertyFilterPlugin, - [semverFlattenerPlugin.id]: semverFlattenerPlugin, - [taxonomyPlugin.id]: taxonomyPlugin, - [timestampParserPlugin.id]: timestampParserPlugin, - [userAgentPlugin.id]: userAgentPlugin, -} - export const DEPRECATED_TRANSFORMATION_PLUGINS_BY_ID = { [dropEventsOnPropertyPlugin.id]: dropEventsOnPropertyPlugin, [flattenPropertiesPlugin.id]: flattenPropertiesPlugin, @@ -53,3 +40,17 @@ export const DEPRECATED_TRANSFORMATION_PLUGINS_BY_ID = { [phShotgunProcessEventApp.id]: phShotgunProcessEventApp, [posthogSnowplowRefererParser.id]: posthogSnowplowRefererParser, } + +export const TRANSFORMATION_PLUGINS_BY_ID = { + ...DEPRECATED_TRANSFORMATION_PLUGINS_BY_ID, + [downsamplingPlugin.id]: downsamplingPlugin, + [languageUrlSplitterApp.id]: languageUrlSplitterApp, + [posthogAppUrlParametersToEventPropertiesPlugin.id]: posthogAppUrlParametersToEventPropertiesPlugin, + [posthogFilterOutPlugin.id]: posthogFilterOutPlugin, + [posthogUrlNormalizerPlugin.id]: posthogUrlNormalizerPlugin, + [propertyFilterPlugin.id]: propertyFilterPlugin, + [semverFlattenerPlugin.id]: semverFlattenerPlugin, + [taxonomyPlugin.id]: taxonomyPlugin, + [timestampParserPlugin.id]: timestampParserPlugin, + [userAgentPlugin.id]: userAgentPlugin, +} diff --git a/plugin-server/src/cdp/services/__snapshots__/legacy-plugin-executor.test.ts.snap b/plugin-server/src/cdp/services/__snapshots__/legacy-plugin-executor.test.ts.snap index d92a16fa8d060..cbe29be82cbd6 100644 --- a/plugin-server/src/cdp/services/__snapshots__/legacy-plugin-executor.test.ts.snap +++ b/plugin-server/src/cdp/services/__snapshots__/legacy-plugin-executor.test.ts.snap @@ -19,6 +19,17 @@ exports[`LegacyPluginExecutorService smoke tests should run the destination plug ] `; +exports[`LegacyPluginExecutorService smoke tests should run the transformation plugin: { + name: 'plugin-posthog-plugin-snowplow-referer-parser', + plugin: [Object] +} 1`] = ` +[ + "Executing plugin plugin-posthog-plugin-snowplow-referer-parser", + "(Event: $pageview) URL: https://posthog.com, Referrer: undefined, Referring domain: undefined", + "Plugin execution failed: Cannot read properties of undefined (reading 'indexOf')", +] +`; + exports[`LegacyPluginExecutorService smoke tests should run the transformation plugin: { name: 'posthog-app-url-parameters-to-event-properties', plugin: [Object] @@ -29,6 +40,16 @@ exports[`LegacyPluginExecutorService smoke tests should run the transformation p ] `; +exports[`LegacyPluginExecutorService smoke tests should run the transformation plugin: { + name: 'posthog-plugin-netdata-event-processing-plugin', + plugin: [Object] +} 1`] = ` +[ + "Executing plugin posthog-plugin-netdata-event-processing-plugin", + "Execution successful", +] +`; + exports[`LegacyPluginExecutorService smoke tests should run the transformation plugin: { name: 'downsampling-plugin', plugin: [Object] } 1`] = ` [ "Executing plugin downsampling-plugin", @@ -36,6 +57,20 @@ exports[`LegacyPluginExecutorService smoke tests should run the transformation p ] `; +exports[`LegacyPluginExecutorService smoke tests should run the transformation plugin: { name: 'drop-events-on-property-plugin', plugin: [Object] } 1`] = ` +[ + "Executing plugin drop-events-on-property-plugin", + "Execution successful", +] +`; + +exports[`LegacyPluginExecutorService smoke tests should run the transformation plugin: { name: 'flatten-properties-plugin', plugin: [Object] } 1`] = ` +[ + "Executing plugin flatten-properties-plugin", + "Execution successful", +] +`; + exports[`LegacyPluginExecutorService smoke tests should run the transformation plugin: { name: 'language-url-splitter-app', plugin: [Object] } 1`] = ` [ "Executing plugin language-url-splitter-app", @@ -43,6 +78,43 @@ exports[`LegacyPluginExecutorService smoke tests should run the transformation p ] `; +exports[`LegacyPluginExecutorService smoke tests should run the transformation plugin: { name: 'ph-shotgun-processevent-app', plugin: [Object] } 1`] = ` +[ + "Executing plugin ph-shotgun-processevent-app", + "Execution successful", +] +`; + +exports[`LegacyPluginExecutorService smoke tests should run the transformation plugin: { name: 'plugin-advanced-geoip', plugin: [Object] } 1`] = ` +[ + "Executing plugin plugin-advanced-geoip", + "Could not discard IP for event b3a1fe86-b10c-43cc-acaf-d208977608d0 as GeoIP has not been processed.", + "Finished processing b3a1fe86-b10c-43cc-acaf-d208977608d0.", + "Execution successful", +] +`; + +exports[`LegacyPluginExecutorService smoke tests should run the transformation plugin: { name: 'plugin-stonly-clean-campaign-name', plugin: [Object] } 1`] = ` +[ + "Executing plugin plugin-stonly-clean-campaign-name", + "Execution successful", +] +`; + +exports[`LegacyPluginExecutorService smoke tests should run the transformation plugin: { name: 'plugin-stonly-utm-extractor', plugin: [Object] } 1`] = ` +[ + "Executing plugin plugin-stonly-utm-extractor", + "Execution successful", +] +`; + +exports[`LegacyPluginExecutorService smoke tests should run the transformation plugin: { name: 'posthog-app-unduplicator', plugin: [Object] } 1`] = ` +[ + "Executing plugin posthog-app-unduplicator", + "Execution successful", +] +`; + exports[`LegacyPluginExecutorService smoke tests should run the transformation plugin: { name: 'posthog-filter-out-plugin', plugin: [Object] } 1`] = ` [ "Executing plugin posthog-filter-out-plugin", @@ -50,6 +122,40 @@ exports[`LegacyPluginExecutorService smoke tests should run the transformation p ] `; +exports[`LegacyPluginExecutorService smoke tests should run the transformation plugin: { name: 'posthog-plugin-geoip', plugin: [Object] } 1`] = ` +[ + "Executing plugin posthog-plugin-geoip", + "Execution successful", +] +`; + +exports[`LegacyPluginExecutorService smoke tests should run the transformation plugin: { name: 'posthog-route-censor-plugin', plugin: [Object] } 1`] = ` +[ + "Executing plugin posthog-route-censor-plugin", + "Plugin set up with global config: , { + "properties": [ + "$current_url", + "$referrer", + "$pathname", + "$initial_current_url", + "initial_pathname", + "initial_referrer" + ], + "setProperties": [ + "$initial_current_url", + "$initial_pathname", + "$initial_referrer" + ], + "setOnceProperties": [ + "$initial_current_url", + "$initial_pathname", + "$initial_referrer" + ] +}", + "Execution successful", +] +`; + exports[`LegacyPluginExecutorService smoke tests should run the transformation plugin: { name: 'posthog-url-normalizer-plugin', plugin: [Object] } 1`] = ` [ "Executing plugin posthog-url-normalizer-plugin", diff --git a/plugin-server/src/cdp/services/legacy-plugin-executor.test.ts b/plugin-server/src/cdp/services/legacy-plugin-executor.test.ts index 1f192edc7fbf6..4aeb5a5f44370 100644 --- a/plugin-server/src/cdp/services/legacy-plugin-executor.test.ts +++ b/plugin-server/src/cdp/services/legacy-plugin-executor.test.ts @@ -11,7 +11,11 @@ import { getFirstTeam, resetTestDatabase } from '~/tests/helpers/sql' import { Hub, Team } from '../../types' import { closeHub, createHub } from '../../utils/db/hub' -import { DESTINATION_PLUGINS_BY_ID, TRANSFORMATION_PLUGINS_BY_ID } from '../legacy-plugins' +import { + DEPRECATED_TRANSFORMATION_PLUGINS_BY_ID, + DESTINATION_PLUGINS_BY_ID, + TRANSFORMATION_PLUGINS_BY_ID, +} from '../legacy-plugins' import { HogFunctionInvocationGlobalsWithInputs, HogFunctionType } from '../types' import { LegacyPluginExecutorService } from './legacy-plugin-executor.service' From 525458ded73d1f65fef3a2da62bd112ad9195c3d Mon Sep 17 00:00:00 2001 From: Ben White Date: Fri, 31 Jan 2025 15:47:25 +0100 Subject: [PATCH 161/163] Fixes --- .../_transformations/user-agent-plugin/index.ts | 2 +- plugin-server/src/cdp/legacy-plugins/index.ts | 2 ++ .../__snapshots__/legacy-plugin-executor.test.ts.snap | 7 +++++++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/user-agent-plugin/index.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/user-agent-plugin/index.ts index 201aa97722368..3bc727fc2ebec 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/user-agent-plugin/index.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/user-agent-plugin/index.ts @@ -165,5 +165,5 @@ export const userAgentPlugin: LegacyTransformationPlugin = { id: 'user-agent-plugin', metadata, processEvent, - setupPlugin, + setupPlugin: setupPlugin as (meta: LegacyTransformationPluginMeta) => void, } diff --git a/plugin-server/src/cdp/legacy-plugins/index.ts b/plugin-server/src/cdp/legacy-plugins/index.ts index a8b6f903b9f75..10e74fa066102 100644 --- a/plugin-server/src/cdp/legacy-plugins/index.ts +++ b/plugin-server/src/cdp/legacy-plugins/index.ts @@ -9,6 +9,7 @@ import { pluginAdvancedGeoip } from './_transformations/plugin-advanced-geoip' import { posthogNetdataEventProcessingPlugin } from './_transformations/plugin-netdata-event-processing' import { pluginStonlyCleanCampaignName } from './_transformations/Plugin-Stonly-Clean-Campaign-Name' import { pluginStonlyUtmExtractor } from './_transformations/plugin-stonly-UTM-Extractor' +import { pluginPosthogAnonymization } from './_transformations/posthog-anonymization' import { posthogAppUnduplicator } from './_transformations/posthog-app-unduplicator' import { posthogAppUrlParametersToEventPropertiesPlugin } from './_transformations/posthog-app-url-parameters-to-event-properties' import { posthogFilterOutPlugin } from './_transformations/posthog-filter-out-plugin' @@ -34,6 +35,7 @@ export const DEPRECATED_TRANSFORMATION_PLUGINS_BY_ID = { [pluginStonlyCleanCampaignName.id]: pluginStonlyCleanCampaignName, [pluginStonlyUtmExtractor.id]: pluginStonlyUtmExtractor, [posthogAppUnduplicator.id]: posthogAppUnduplicator, + [pluginPosthogAnonymization.id]: pluginPosthogAnonymization, [posthogPluginGeoip.id]: posthogPluginGeoip, [posthogRouteCensorPlugin.id]: posthogRouteCensorPlugin, [posthogNetdataEventProcessingPlugin.id]: posthogNetdataEventProcessingPlugin, diff --git a/plugin-server/src/cdp/services/__snapshots__/legacy-plugin-executor.test.ts.snap b/plugin-server/src/cdp/services/__snapshots__/legacy-plugin-executor.test.ts.snap index cbe29be82cbd6..4f5bebf62963c 100644 --- a/plugin-server/src/cdp/services/__snapshots__/legacy-plugin-executor.test.ts.snap +++ b/plugin-server/src/cdp/services/__snapshots__/legacy-plugin-executor.test.ts.snap @@ -94,6 +94,13 @@ exports[`LegacyPluginExecutorService smoke tests should run the transformation p ] `; +exports[`LegacyPluginExecutorService smoke tests should run the transformation plugin: { name: 'plugin-posthog-anonymization', plugin: [Object] } 1`] = ` +[ + "Executing plugin plugin-posthog-anonymization", + "Execution successful", +] +`; + exports[`LegacyPluginExecutorService smoke tests should run the transformation plugin: { name: 'plugin-stonly-clean-campaign-name', plugin: [Object] } 1`] = ` [ "Executing plugin plugin-stonly-clean-campaign-name", From bfaa806a43abae98e35809a2f404a6c054ff362a Mon Sep 17 00:00:00 2001 From: Ben White Date: Fri, 31 Jan 2025 16:21:22 +0100 Subject: [PATCH 162/163] Fix --- .../_transformations/ph-shotgun-processevent-app/index.ts | 2 +- .../src/cdp/services/legacy-plugin-executor.test.ts | 6 +----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/ph-shotgun-processevent-app/index.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/ph-shotgun-processevent-app/index.ts index 1188d710eae66..ce46817b55e40 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/ph-shotgun-processevent-app/index.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/ph-shotgun-processevent-app/index.ts @@ -1,6 +1,6 @@ import { PluginEvent } from '@posthog/plugin-scaffold' -import { LegacyTransformationPlugin, LegacyPluginMeta } from '../../types' +import { LegacyPluginMeta,LegacyTransformationPlugin } from '../../types' import metadata from './plugin.json' export function processEvent(event: PluginEvent, _: LegacyPluginMeta) { diff --git a/plugin-server/src/cdp/services/legacy-plugin-executor.test.ts b/plugin-server/src/cdp/services/legacy-plugin-executor.test.ts index 4aeb5a5f44370..1f192edc7fbf6 100644 --- a/plugin-server/src/cdp/services/legacy-plugin-executor.test.ts +++ b/plugin-server/src/cdp/services/legacy-plugin-executor.test.ts @@ -11,11 +11,7 @@ import { getFirstTeam, resetTestDatabase } from '~/tests/helpers/sql' import { Hub, Team } from '../../types' import { closeHub, createHub } from '../../utils/db/hub' -import { - DEPRECATED_TRANSFORMATION_PLUGINS_BY_ID, - DESTINATION_PLUGINS_BY_ID, - TRANSFORMATION_PLUGINS_BY_ID, -} from '../legacy-plugins' +import { DESTINATION_PLUGINS_BY_ID, TRANSFORMATION_PLUGINS_BY_ID } from '../legacy-plugins' import { HogFunctionInvocationGlobalsWithInputs, HogFunctionType } from '../types' import { LegacyPluginExecutorService } from './legacy-plugin-executor.service' From 342c543e04ada9035d294a0f5a37dd346a0efe18 Mon Sep 17 00:00:00 2001 From: Ben White Date: Fri, 31 Jan 2025 17:34:44 +0100 Subject: [PATCH 163/163] Fix --- .../_transformations/ph-shotgun-processevent-app/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin-server/src/cdp/legacy-plugins/_transformations/ph-shotgun-processevent-app/index.ts b/plugin-server/src/cdp/legacy-plugins/_transformations/ph-shotgun-processevent-app/index.ts index ce46817b55e40..a0db962a50aa3 100644 --- a/plugin-server/src/cdp/legacy-plugins/_transformations/ph-shotgun-processevent-app/index.ts +++ b/plugin-server/src/cdp/legacy-plugins/_transformations/ph-shotgun-processevent-app/index.ts @@ -1,6 +1,6 @@ import { PluginEvent } from '@posthog/plugin-scaffold' -import { LegacyPluginMeta,LegacyTransformationPlugin } from '../../types' +import { LegacyPluginMeta, LegacyTransformationPlugin } from '../../types' import metadata from './plugin.json' export function processEvent(event: PluginEvent, _: LegacyPluginMeta) {

    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 a*DlX36!-5*peQgg9XYJ0nW28}+CKm#`@I(c From 1a29f974d3c08349f33a3f1aec5801946b6b2490 Mon Sep 17 00:00:00 2001 From: Ben White Date: Thu, 30 Jan 2025 08:33:41 +0100 Subject: [PATCH 107/163] Fixed up caching --- posthog/api/hog_function_template.py | 1 + posthog/api/test/test_hog_function.py | 14 ++++++++++++++ posthog/api/test/test_hog_function_templates.py | 12 ++++++++---- 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/posthog/api/hog_function_template.py b/posthog/api/hog_function_template.py index b491fb4af26d8..b4e3b66289c91 100644 --- a/posthog/api/hog_function_template.py +++ b/posthog/api/hog_function_template.py @@ -99,6 +99,7 @@ def _load_templates(cls): ] sub_templates = derive_sub_templates(templates=templates) + cls._cached_at = datetime.now() cls._cached_templates = templates cls._cached_sub_templates = sub_templates cls._cached_templates_by_id = {template.id: template for template in templates} diff --git a/posthog/api/test/test_hog_function.py b/posthog/api/test/test_hog_function.py index 790bb2fa641c4..9fa5da11b0764 100644 --- a/posthog/api/test/test_hog_function.py +++ b/posthog/api/test/test_hog_function.py @@ -8,6 +8,8 @@ from rest_framework import status from common.hogvm.python.operation import HOGQL_BYTECODE_VERSION +from posthog.api.test.test_hog_function_templates import MOCK_NODE_TEMPLATES +from posthog.api.hog_function_template import HogFunctionTemplates from posthog.constants import AvailableFeature from posthog.models.action.action import Action from posthog.models.hog_functions.hog_function import DEFAULT_STATE, HogFunction @@ -71,6 +73,13 @@ def get_db_field_value(field, model_id): class TestHogFunctionAPIWithoutAvailableFeature(ClickhouseTestMixin, APIBaseTest, QueryMatchingTest): + def setUp(self): + super().setUp() + with patch("posthog.api.hog_function_template.get_hog_function_templates") as mock_get_templates: + mock_get_templates.return_value.status_code = 200 + mock_get_templates.return_value.json.return_value = MOCK_NODE_TEMPLATES + HogFunctionTemplates._load_templates() # Cache templates to simplify tests + def _create_slack_function(self, data: Optional[dict] = None): payload = { "name": "Slack", @@ -173,6 +182,11 @@ def setUp(self): ] self.organization.save() + with patch("posthog.api.hog_function_template.get_hog_function_templates") as mock_get_templates: + mock_get_templates.return_value.status_code = 200 + mock_get_templates.return_value.json.return_value = MOCK_NODE_TEMPLATES + HogFunctionTemplates._load_templates() # Cache templates to simplify tests + def _get_function_activity( self, function_id: Optional[int] = None, diff --git a/posthog/api/test/test_hog_function_templates.py b/posthog/api/test/test_hog_function_templates.py index 5271de0ce4c5f..46e25e18653ee 100644 --- a/posthog/api/test/test_hog_function_templates.py +++ b/posthog/api/test/test_hog_function_templates.py @@ -2,8 +2,10 @@ import os from unittest.mock import ANY, patch from inline_snapshot import snapshot +import pytest from rest_framework import status +from posthog.api.hog_function_template import HogFunctionTemplates from posthog.cdp.templates.hog_function_template import derive_sub_templates from posthog.test.base import APIBaseTest, ClickhouseTestMixin, QueryMatchingTest from posthog.cdp.templates.slack.template_slack import template @@ -48,11 +50,13 @@ def test_derive_sub_templates(self): class TestHogFunctionTemplates(ClickhouseTestMixin, APIBaseTest, QueryMatchingTest): - @patch("posthog.api.hog_function_template.get_hog_function_templates") - def setUp(self, mock_get_templates): + def setUp(self): super().setUp() - mock_get_templates.return_value.status_code = 200 - mock_get_templates.return_value.json.return_value = MOCK_NODE_TEMPLATES + + with patch("posthog.api.hog_function_template.get_hog_function_templates") as mock_get_templates: + mock_get_templates.return_value.status_code = 200 + mock_get_templates.return_value.json.return_value = MOCK_NODE_TEMPLATES + HogFunctionTemplates._load_templates() # Cache templates to simplify tests def test_list_function_templates(self): response = self.client.get("/api/projects/@current/hog_function_templates/") From 595a619b2a06280036a1e3f199bedda252400ca8 Mon Sep 17 00:00:00 2001 From: Ben White Date: Thu, 30 Jan 2025 08:39:00 +0100 Subject: [PATCH 108/163] fixes --- posthog/api/hog_function_template.py | 52 +++++++++++++++++----------- 1 file changed, 32 insertions(+), 20 deletions(-) diff --git a/posthog/api/hog_function_template.py b/posthog/api/hog_function_template.py index b4e3b66289c91..72b5d8f98e683 100644 --- a/posthog/api/hog_function_template.py +++ b/posthog/api/hog_function_template.py @@ -47,7 +47,7 @@ class Meta: class HogFunctionTemplates: - _cached_at: datetime | None = None + _cache_until: datetime | None = None _cached_templates: list[HogFunctionTemplate] = [] _cached_templates_by_id: dict[str, HogFunctionTemplate] = {} _cached_sub_templates: list[HogFunctionTemplate] = [] @@ -70,28 +70,38 @@ def template(cls, template_id: str): @classmethod def _load_templates(cls): - if cls._cached_at and datetime.now() - cls._cached_at < timedelta(minutes=1): + if cls._cache_until and datetime.now() < cls._cache_until: return # First we load and convert all nodejs templates to python templates - response = get_hog_function_templates() - if response.status_code != 200: - raise Exception("Failed to fetch hog function templates") - - nodejs_templates_json = response.json() nodejs_templates: list[HogFunctionTemplate] = [] - for template_data in nodejs_templates_json: - try: - serializer = HogFunctionTemplateSerializer(data=template_data) - serializer.is_valid(raise_exception=True) - template = serializer.save() - nodejs_templates.append(template) - except Exception as e: - logger.error( - "Failed to convert template", template_id=template_data.get("id"), error=str(e), exc_info=True - ) - capture_exception(e) - raise + + try: + response = get_hog_function_templates() + + if response.status_code != 200: + raise Exception("Failed to fetch hog function templates from the node service") + + nodejs_templates_json = response.json() + nodejs_templates: list[HogFunctionTemplate] = [] + for template_data in nodejs_templates_json: + try: + serializer = HogFunctionTemplateSerializer(data=template_data) + serializer.is_valid(raise_exception=True) + template = serializer.save() + nodejs_templates.append(template) + except Exception as e: + logger.error( + "Failed to convert template", + template_id=template_data.get("id"), + error=str(e), + exc_info=True, + ) + capture_exception(e) + raise + except Exception as e: + capture_exception(e) + # Continue on so as not to block the user templates = [ *HOG_FUNCTION_TEMPLATES, @@ -99,7 +109,9 @@ def _load_templates(cls): ] sub_templates = derive_sub_templates(templates=templates) - cls._cached_at = datetime.now() + # If we failed to get the templates, we cache for 30 seconds to avoid hammering the node service + # If we got the templates, we cache for 5 minutes as these change infrequently + cls._cache_until = datetime.now() + timedelta(seconds=30 if not nodejs_templates else 300) cls._cached_templates = templates cls._cached_sub_templates = sub_templates cls._cached_templates_by_id = {template.id: template for template in templates} From 166efe6a779e13b82a6c99d27eb0258027727210 Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 30 Jan 2025 07:56:29 +0000 Subject: [PATCH 109/163] Update query snapshots --- .../test/__snapshots__/test_web_goals.ambr | 164 +++++++++--------- .../__snapshots__/test_web_stats_table.ambr | 24 +-- 2 files changed, 94 insertions(+), 94 deletions(-) diff --git a/posthog/hogql_queries/web_analytics/test/__snapshots__/test_web_goals.ambr b/posthog/hogql_queries/web_analytics/test/__snapshots__/test_web_goals.ambr index baf646431c4e8..d2b78d71f03d1 100644 --- a/posthog/hogql_queries/web_analytics/test/__snapshots__/test_web_goals.ambr +++ b/posthog/hogql_queries/web_analytics/test/__snapshots__/test_web_goals.ambr @@ -1,8 +1,8 @@ # serializer version: 1 # name: TestWebGoalsQueryRunner.test_dont_show_deleted_actions ''' - SELECT uniqIf(person_id, and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC'))), 0))) AS current_total_people, - uniqIf(person_id, and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0))) AS previous_total_people, + SELECT uniqIf(person_id, and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC'))), 0))) AS current_total_people, + uniqIf(person_id, and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0))) AS previous_total_people, 'Contacted Sales' AS action_name_0, tuple(sum(action_current_count_0), sum(action_previous_count_0)) AS action_total_0, tuple(uniq(action_current_person_id_0), uniq(action_previous_person_id_0)) AS action_uniques_0, @@ -12,14 +12,14 @@ FROM (SELECT any(if(not(empty(events__override.distinct_id)), events__override.person_id, events.person_id)) AS person_id, min(events__session.`$start_timestamp`) AS start_timestamp, - countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))))) AS action_current_count_0, - countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_0, + countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))))) AS action_current_count_0, + countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_0, if(ifNull(greater(action_current_count_0, 0), 0), person_id, NULL) AS action_current_person_id_0, if(ifNull(greater(action_previous_count_0, 0), 0), person_id, NULL) AS action_previous_person_id_0, countIf(and(and(equals(events.event, '$pageview'), ifNull(match(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', ''), 'https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'), isNull(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', '')) - and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))))) AS action_current_count_1, + and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))))) AS action_current_count_1, countIf(and(and(equals(events.event, '$pageview'), ifNull(match(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', ''), 'https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'), isNull(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', '')) - and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_1, + and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_1, if(ifNull(greater(action_current_count_1, 0), 0), person_id, NULL) AS action_current_person_id_1, if(ifNull(greater(action_previous_count_1, 0), 0), person_id, NULL) AS action_previous_person_id_1 FROM events @@ -27,7 +27,7 @@ (SELECT min(toTimeZone(raw_sessions.min_timestamp, 'UTC')) AS `$start_timestamp`, raw_sessions.session_id_v7 AS session_id_v7 FROM raw_sessions - WHERE and(equals(raw_sessions.team_id, 99999), or(and(ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC'))), 0)), and(ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0)))) + WHERE and(equals(raw_sessions.team_id, 99999), or(and(ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC'))), 0)), and(ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0)))) GROUP BY raw_sessions.session_id_v7, raw_sessions.session_id_v7) AS events__session ON equals(toUInt128(accurateCastOrNull(events.`$session_id`, 'UUID')), events__session.session_id_v7) LEFT OUTER JOIN @@ -38,9 +38,9 @@ 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 events__override ON equals(events.distinct_id, events__override.distinct_id) WHERE and(equals(events.team_id, 99999), and(isNotNull(events.`$session_id`), or(equals(events.event, '$pageview'), equals(events.event, '$screen'), or(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(equals(events.event, '$pageview'), ifNull(match(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', ''), 'https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'), isNull(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', '')) - and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))))), or(and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))))), 1, 1)) + and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))))), or(and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))))), 1, 1)) GROUP BY events.`$session_id`) - WHERE or(and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC'))), 0)), and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0))) + WHERE or(and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC'))), 0)), and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0))) LIMIT 100 SETTINGS readonly=2, max_execution_time=60, allow_experimental_object_type=1, @@ -52,8 +52,8 @@ # --- # name: TestWebGoalsQueryRunner.test_many_users_and_actions ''' - SELECT uniqIf(person_id, and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC'))), 0))) AS current_total_people, - uniqIf(person_id, and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0))) AS previous_total_people, + SELECT uniqIf(person_id, and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC'))), 0))) AS current_total_people, + uniqIf(person_id, and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0))) AS previous_total_people, 'Contacted Sales' AS action_name_0, tuple(sum(action_current_count_0), sum(action_previous_count_0)) AS action_total_0, tuple(uniq(action_current_person_id_0), uniq(action_previous_person_id_0)) AS action_uniques_0, @@ -66,18 +66,18 @@ FROM (SELECT any(if(not(empty(events__override.distinct_id)), events__override.person_id, events.person_id)) AS person_id, min(events__session.`$start_timestamp`) AS start_timestamp, - countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))))) AS action_current_count_0, - countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_0, + countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))))) AS action_current_count_0, + countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_0, if(ifNull(greater(action_current_count_0, 0), 0), person_id, NULL) AS action_current_person_id_0, if(ifNull(greater(action_previous_count_0, 0), 0), person_id, NULL) AS action_previous_person_id_0, countIf(and(and(equals(events.event, '$pageview'), ifNull(match(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', ''), 'https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'), isNull(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', '')) - and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))))) AS action_current_count_1, + and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))))) AS action_current_count_1, countIf(and(and(equals(events.event, '$pageview'), ifNull(match(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', ''), 'https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'), isNull(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', '')) - and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_1, + and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_1, if(ifNull(greater(action_current_count_1, 0), 0), person_id, NULL) AS action_current_person_id_1, if(ifNull(greater(action_previous_count_1, 0), 0), person_id, NULL) AS action_previous_person_id_1, - countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))))) AS action_current_count_2, - countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_2, + countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))))) AS action_current_count_2, + countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_2, if(ifNull(greater(action_current_count_2, 0), 0), person_id, NULL) AS action_current_person_id_2, if(ifNull(greater(action_previous_count_2, 0), 0), person_id, NULL) AS action_previous_person_id_2 FROM events @@ -85,7 +85,7 @@ (SELECT min(toTimeZone(raw_sessions.min_timestamp, 'UTC')) AS `$start_timestamp`, raw_sessions.session_id_v7 AS session_id_v7 FROM raw_sessions - WHERE and(equals(raw_sessions.team_id, 99999), or(and(ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC'))), 0)), and(ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0)))) + WHERE and(equals(raw_sessions.team_id, 99999), or(and(ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC'))), 0)), and(ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0)))) GROUP BY raw_sessions.session_id_v7, raw_sessions.session_id_v7) AS events__session ON equals(toUInt128(accurateCastOrNull(events.`$session_id`, 'UUID')), events__session.session_id_v7) LEFT OUTER JOIN @@ -96,9 +96,9 @@ 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 events__override ON equals(events.distinct_id, events__override.distinct_id) WHERE and(equals(events.team_id, 99999), and(isNotNull(events.`$session_id`), or(equals(events.event, '$pageview'), equals(events.event, '$screen'), or(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(equals(events.event, '$pageview'), ifNull(match(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', ''), 'https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'), isNull(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', '')) - and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)))), or(and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))))), 1, 1)) + and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)))), or(and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))))), 1, 1)) GROUP BY events.`$session_id`) - WHERE or(and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC'))), 0)), and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0))) + WHERE or(and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC'))), 0)), and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0))) LIMIT 100 SETTINGS readonly=2, max_execution_time=60, allow_experimental_object_type=1, @@ -110,7 +110,7 @@ # --- # name: TestWebGoalsQueryRunner.test_no_comparison ''' - SELECT uniqIf(person_id, and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC'))), 0))) AS current_total_people, + SELECT uniqIf(person_id, and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC'))), 0))) AS current_total_people, uniqIf(person_id, 0) AS previous_total_people, 'Contacted Sales' AS action_name_0, tuple(sum(action_current_count_0), NULL) AS action_total_0, @@ -124,17 +124,17 @@ FROM (SELECT any(if(not(empty(events__override.distinct_id)), events__override.person_id, events.person_id)) AS person_id, min(events__session.`$start_timestamp`) AS start_timestamp, - countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))))) AS action_current_count_0, + countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))))) AS action_current_count_0, countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), 0)) AS action_previous_count_0, if(ifNull(greater(action_current_count_0, 0), 0), person_id, NULL) AS action_current_person_id_0, if(ifNull(greater(action_previous_count_0, 0), 0), person_id, NULL) AS action_previous_person_id_0, countIf(and(and(equals(events.event, '$pageview'), ifNull(match(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', ''), 'https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'), isNull(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', '')) - and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))))) AS action_current_count_1, + and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))))) AS action_current_count_1, countIf(and(and(equals(events.event, '$pageview'), ifNull(match(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', ''), 'https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'), isNull(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', '')) and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), 0)) AS action_previous_count_1, if(ifNull(greater(action_current_count_1, 0), 0), person_id, NULL) AS action_current_person_id_1, if(ifNull(greater(action_previous_count_1, 0), 0), person_id, NULL) AS action_previous_person_id_1, - countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))))) AS action_current_count_2, + countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))))) AS action_current_count_2, countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)), 0)) AS action_previous_count_2, if(ifNull(greater(action_current_count_2, 0), 0), person_id, NULL) AS action_current_person_id_2, if(ifNull(greater(action_previous_count_2, 0), 0), person_id, NULL) AS action_previous_person_id_2 @@ -143,7 +143,7 @@ (SELECT min(toTimeZone(raw_sessions.min_timestamp, 'UTC')) AS `$start_timestamp`, raw_sessions.session_id_v7 AS session_id_v7 FROM raw_sessions - WHERE and(equals(raw_sessions.team_id, 99999), ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC'))), 0)) + WHERE and(equals(raw_sessions.team_id, 99999), ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC'))), 0)) GROUP BY raw_sessions.session_id_v7, raw_sessions.session_id_v7) AS events__session ON equals(toUInt128(accurateCastOrNull(events.`$session_id`, 'UUID')), events__session.session_id_v7) LEFT OUTER JOIN @@ -154,9 +154,9 @@ 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 events__override ON equals(events.distinct_id, events__override.distinct_id) WHERE and(equals(events.team_id, 99999), and(isNotNull(events.`$session_id`), or(equals(events.event, '$pageview'), equals(events.event, '$screen'), or(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(equals(events.event, '$pageview'), ifNull(match(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', ''), 'https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'), isNull(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', '')) - and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)))), or(and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))), 0), 1, 1)) + and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)))), or(and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))), 0), 1, 1)) GROUP BY events.`$session_id`) - WHERE or(and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC'))), 0)), 0) + WHERE or(and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC'))), 0)), 0) LIMIT 100 SETTINGS readonly=2, max_execution_time=60, allow_experimental_object_type=1, @@ -168,8 +168,8 @@ # --- # name: TestWebGoalsQueryRunner.test_no_crash_when_no_data_and_some_actions ''' - SELECT uniqIf(person_id, and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC'))), 0))) AS current_total_people, - uniqIf(person_id, and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0))) AS previous_total_people, + SELECT uniqIf(person_id, and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC'))), 0))) AS current_total_people, + uniqIf(person_id, and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0))) AS previous_total_people, 'Contacted Sales' AS action_name_0, tuple(sum(action_current_count_0), sum(action_previous_count_0)) AS action_total_0, tuple(uniq(action_current_person_id_0), uniq(action_previous_person_id_0)) AS action_uniques_0, @@ -182,18 +182,18 @@ FROM (SELECT any(if(not(empty(events__override.distinct_id)), events__override.person_id, events.person_id)) AS person_id, min(events__session.`$start_timestamp`) AS start_timestamp, - countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))))) AS action_current_count_0, - countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_0, + countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))))) AS action_current_count_0, + countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_0, if(ifNull(greater(action_current_count_0, 0), 0), person_id, NULL) AS action_current_person_id_0, if(ifNull(greater(action_previous_count_0, 0), 0), person_id, NULL) AS action_previous_person_id_0, countIf(and(and(equals(events.event, '$pageview'), ifNull(match(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', ''), 'https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'), isNull(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', '')) - and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))))) AS action_current_count_1, + and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))))) AS action_current_count_1, countIf(and(and(equals(events.event, '$pageview'), ifNull(match(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', ''), 'https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'), isNull(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', '')) - and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_1, + and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_1, if(ifNull(greater(action_current_count_1, 0), 0), person_id, NULL) AS action_current_person_id_1, if(ifNull(greater(action_previous_count_1, 0), 0), person_id, NULL) AS action_previous_person_id_1, - countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))))) AS action_current_count_2, - countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_2, + countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))))) AS action_current_count_2, + countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_2, if(ifNull(greater(action_current_count_2, 0), 0), person_id, NULL) AS action_current_person_id_2, if(ifNull(greater(action_previous_count_2, 0), 0), person_id, NULL) AS action_previous_person_id_2 FROM events @@ -201,7 +201,7 @@ (SELECT min(toTimeZone(raw_sessions.min_timestamp, 'UTC')) AS `$start_timestamp`, raw_sessions.session_id_v7 AS session_id_v7 FROM raw_sessions - WHERE and(equals(raw_sessions.team_id, 99999), or(and(ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC'))), 0)), and(ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0)))) + WHERE and(equals(raw_sessions.team_id, 99999), or(and(ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC'))), 0)), and(ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0)))) GROUP BY raw_sessions.session_id_v7, raw_sessions.session_id_v7) AS events__session ON equals(toUInt128(accurateCastOrNull(events.`$session_id`, 'UUID')), events__session.session_id_v7) LEFT OUTER JOIN @@ -212,9 +212,9 @@ 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 events__override ON equals(events.distinct_id, events__override.distinct_id) WHERE and(equals(events.team_id, 99999), and(isNotNull(events.`$session_id`), or(equals(events.event, '$pageview'), equals(events.event, '$screen'), or(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(equals(events.event, '$pageview'), ifNull(match(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', ''), 'https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'), isNull(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', '')) - and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)))), or(and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))))), 1, 1)) + and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)))), or(and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))))), 1, 1)) GROUP BY events.`$session_id`) - WHERE or(and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC'))), 0)), and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0))) + WHERE or(and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC'))), 0)), and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0))) LIMIT 100 SETTINGS readonly=2, max_execution_time=60, allow_experimental_object_type=1, @@ -226,8 +226,8 @@ # --- # name: TestWebGoalsQueryRunner.test_one_user_one_action ''' - SELECT uniqIf(person_id, and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC'))), 0))) AS current_total_people, - uniqIf(person_id, and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0))) AS previous_total_people, + SELECT uniqIf(person_id, and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC'))), 0))) AS current_total_people, + uniqIf(person_id, and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0))) AS previous_total_people, 'Contacted Sales' AS action_name_0, tuple(sum(action_current_count_0), sum(action_previous_count_0)) AS action_total_0, tuple(uniq(action_current_person_id_0), uniq(action_previous_person_id_0)) AS action_uniques_0, @@ -240,18 +240,18 @@ FROM (SELECT any(if(not(empty(events__override.distinct_id)), events__override.person_id, events.person_id)) AS person_id, min(events__session.`$start_timestamp`) AS start_timestamp, - countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))))) AS action_current_count_0, - countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_0, + countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))))) AS action_current_count_0, + countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_0, if(ifNull(greater(action_current_count_0, 0), 0), person_id, NULL) AS action_current_person_id_0, if(ifNull(greater(action_previous_count_0, 0), 0), person_id, NULL) AS action_previous_person_id_0, countIf(and(and(equals(events.event, '$pageview'), ifNull(match(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', ''), 'https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'), isNull(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', '')) - and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))))) AS action_current_count_1, + and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))))) AS action_current_count_1, countIf(and(and(equals(events.event, '$pageview'), ifNull(match(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', ''), 'https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'), isNull(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', '')) - and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_1, + and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_1, if(ifNull(greater(action_current_count_1, 0), 0), person_id, NULL) AS action_current_person_id_1, if(ifNull(greater(action_previous_count_1, 0), 0), person_id, NULL) AS action_previous_person_id_1, - countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))))) AS action_current_count_2, - countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_2, + countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))))) AS action_current_count_2, + countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_2, if(ifNull(greater(action_current_count_2, 0), 0), person_id, NULL) AS action_current_person_id_2, if(ifNull(greater(action_previous_count_2, 0), 0), person_id, NULL) AS action_previous_person_id_2 FROM events @@ -259,7 +259,7 @@ (SELECT min(toTimeZone(raw_sessions.min_timestamp, 'UTC')) AS `$start_timestamp`, raw_sessions.session_id_v7 AS session_id_v7 FROM raw_sessions - WHERE and(equals(raw_sessions.team_id, 99999), or(and(ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC'))), 0)), and(ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0)))) + WHERE and(equals(raw_sessions.team_id, 99999), or(and(ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC'))), 0)), and(ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0)))) GROUP BY raw_sessions.session_id_v7, raw_sessions.session_id_v7) AS events__session ON equals(toUInt128(accurateCastOrNull(events.`$session_id`, 'UUID')), events__session.session_id_v7) LEFT OUTER JOIN @@ -270,9 +270,9 @@ 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 events__override ON equals(events.distinct_id, events__override.distinct_id) WHERE and(equals(events.team_id, 99999), and(isNotNull(events.`$session_id`), or(equals(events.event, '$pageview'), equals(events.event, '$screen'), or(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(equals(events.event, '$pageview'), ifNull(match(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', ''), 'https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'), isNull(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', '')) - and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)))), or(and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))))), 1, 1)) + and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)))), or(and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))))), 1, 1)) GROUP BY events.`$session_id`) - WHERE or(and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC'))), 0)), and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0))) + WHERE or(and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC'))), 0)), and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0))) LIMIT 100 SETTINGS readonly=2, max_execution_time=60, allow_experimental_object_type=1, @@ -284,8 +284,8 @@ # --- # name: TestWebGoalsQueryRunner.test_one_user_two_different_actions ''' - SELECT uniqIf(person_id, and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC'))), 0))) AS current_total_people, - uniqIf(person_id, and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0))) AS previous_total_people, + SELECT uniqIf(person_id, and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC'))), 0))) AS current_total_people, + uniqIf(person_id, and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0))) AS previous_total_people, 'Contacted Sales' AS action_name_0, tuple(sum(action_current_count_0), sum(action_previous_count_0)) AS action_total_0, tuple(uniq(action_current_person_id_0), uniq(action_previous_person_id_0)) AS action_uniques_0, @@ -298,18 +298,18 @@ FROM (SELECT any(if(not(empty(events__override.distinct_id)), events__override.person_id, events.person_id)) AS person_id, min(events__session.`$start_timestamp`) AS start_timestamp, - countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))))) AS action_current_count_0, - countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_0, + countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))))) AS action_current_count_0, + countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_0, if(ifNull(greater(action_current_count_0, 0), 0), person_id, NULL) AS action_current_person_id_0, if(ifNull(greater(action_previous_count_0, 0), 0), person_id, NULL) AS action_previous_person_id_0, countIf(and(and(equals(events.event, '$pageview'), ifNull(match(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', ''), 'https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'), isNull(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', '')) - and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))))) AS action_current_count_1, + and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))))) AS action_current_count_1, countIf(and(and(equals(events.event, '$pageview'), ifNull(match(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', ''), 'https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'), isNull(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', '')) - and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_1, + and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_1, if(ifNull(greater(action_current_count_1, 0), 0), person_id, NULL) AS action_current_person_id_1, if(ifNull(greater(action_previous_count_1, 0), 0), person_id, NULL) AS action_previous_person_id_1, - countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))))) AS action_current_count_2, - countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_2, + countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))))) AS action_current_count_2, + countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_2, if(ifNull(greater(action_current_count_2, 0), 0), person_id, NULL) AS action_current_person_id_2, if(ifNull(greater(action_previous_count_2, 0), 0), person_id, NULL) AS action_previous_person_id_2 FROM events @@ -317,7 +317,7 @@ (SELECT min(toTimeZone(raw_sessions.min_timestamp, 'UTC')) AS `$start_timestamp`, raw_sessions.session_id_v7 AS session_id_v7 FROM raw_sessions - WHERE and(equals(raw_sessions.team_id, 99999), or(and(ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC'))), 0)), and(ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0)))) + WHERE and(equals(raw_sessions.team_id, 99999), or(and(ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC'))), 0)), and(ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0)))) GROUP BY raw_sessions.session_id_v7, raw_sessions.session_id_v7) AS events__session ON equals(toUInt128(accurateCastOrNull(events.`$session_id`, 'UUID')), events__session.session_id_v7) LEFT OUTER JOIN @@ -328,9 +328,9 @@ 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 events__override ON equals(events.distinct_id, events__override.distinct_id) WHERE and(equals(events.team_id, 99999), and(isNotNull(events.`$session_id`), or(equals(events.event, '$pageview'), equals(events.event, '$screen'), or(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(equals(events.event, '$pageview'), ifNull(match(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', ''), 'https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'), isNull(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', '')) - and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)))), or(and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))))), 1, 1)) + and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)))), or(and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))))), 1, 1)) GROUP BY events.`$session_id`) - WHERE or(and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC'))), 0)), and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0))) + WHERE or(and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC'))), 0)), and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0))) LIMIT 100 SETTINGS readonly=2, max_execution_time=60, allow_experimental_object_type=1, @@ -342,8 +342,8 @@ # --- # name: TestWebGoalsQueryRunner.test_one_user_two_similar_actions_across_sessions ''' - SELECT uniqIf(person_id, and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC'))), 0))) AS current_total_people, - uniqIf(person_id, and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0))) AS previous_total_people, + SELECT uniqIf(person_id, and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC'))), 0))) AS current_total_people, + uniqIf(person_id, and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0))) AS previous_total_people, 'Contacted Sales' AS action_name_0, tuple(sum(action_current_count_0), sum(action_previous_count_0)) AS action_total_0, tuple(uniq(action_current_person_id_0), uniq(action_previous_person_id_0)) AS action_uniques_0, @@ -356,18 +356,18 @@ FROM (SELECT any(if(not(empty(events__override.distinct_id)), events__override.person_id, events.person_id)) AS person_id, min(events__session.`$start_timestamp`) AS start_timestamp, - countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))))) AS action_current_count_0, - countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_0, + countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))))) AS action_current_count_0, + countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_0, if(ifNull(greater(action_current_count_0, 0), 0), person_id, NULL) AS action_current_person_id_0, if(ifNull(greater(action_previous_count_0, 0), 0), person_id, NULL) AS action_previous_person_id_0, countIf(and(and(equals(events.event, '$pageview'), ifNull(match(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', ''), 'https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'), isNull(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', '')) - and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))))) AS action_current_count_1, + and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))))) AS action_current_count_1, countIf(and(and(equals(events.event, '$pageview'), ifNull(match(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', ''), 'https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'), isNull(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', '')) - and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_1, + and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_1, if(ifNull(greater(action_current_count_1, 0), 0), person_id, NULL) AS action_current_person_id_1, if(ifNull(greater(action_previous_count_1, 0), 0), person_id, NULL) AS action_previous_person_id_1, - countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))))) AS action_current_count_2, - countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_2, + countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))))) AS action_current_count_2, + countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_2, if(ifNull(greater(action_current_count_2, 0), 0), person_id, NULL) AS action_current_person_id_2, if(ifNull(greater(action_previous_count_2, 0), 0), person_id, NULL) AS action_previous_person_id_2 FROM events @@ -375,7 +375,7 @@ (SELECT min(toTimeZone(raw_sessions.min_timestamp, 'UTC')) AS `$start_timestamp`, raw_sessions.session_id_v7 AS session_id_v7 FROM raw_sessions - WHERE and(equals(raw_sessions.team_id, 99999), or(and(ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC'))), 0)), and(ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0)))) + WHERE and(equals(raw_sessions.team_id, 99999), or(and(ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC'))), 0)), and(ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0)))) GROUP BY raw_sessions.session_id_v7, raw_sessions.session_id_v7) AS events__session ON equals(toUInt128(accurateCastOrNull(events.`$session_id`, 'UUID')), events__session.session_id_v7) LEFT OUTER JOIN @@ -386,9 +386,9 @@ 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 events__override ON equals(events.distinct_id, events__override.distinct_id) WHERE and(equals(events.team_id, 99999), and(isNotNull(events.`$session_id`), or(equals(events.event, '$pageview'), equals(events.event, '$screen'), or(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(equals(events.event, '$pageview'), ifNull(match(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', ''), 'https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'), isNull(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', '')) - and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)))), or(and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))))), 1, 1)) + and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)))), or(and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))))), 1, 1)) GROUP BY events.`$session_id`) - WHERE or(and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC'))), 0)), and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0))) + WHERE or(and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC'))), 0)), and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0))) LIMIT 100 SETTINGS readonly=2, max_execution_time=60, allow_experimental_object_type=1, @@ -400,8 +400,8 @@ # --- # name: TestWebGoalsQueryRunner.test_one_users_one_action_each ''' - SELECT uniqIf(person_id, and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC'))), 0))) AS current_total_people, - uniqIf(person_id, and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0))) AS previous_total_people, + SELECT uniqIf(person_id, and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC'))), 0))) AS current_total_people, + uniqIf(person_id, and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0))) AS previous_total_people, 'Contacted Sales' AS action_name_0, tuple(sum(action_current_count_0), sum(action_previous_count_0)) AS action_total_0, tuple(uniq(action_current_person_id_0), uniq(action_previous_person_id_0)) AS action_uniques_0, @@ -414,18 +414,18 @@ FROM (SELECT any(if(not(empty(events__override.distinct_id)), events__override.person_id, events.person_id)) AS person_id, min(events__session.`$start_timestamp`) AS start_timestamp, - countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))))) AS action_current_count_0, - countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_0, + countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))))) AS action_current_count_0, + countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_0, if(ifNull(greater(action_current_count_0, 0), 0), person_id, NULL) AS action_current_person_id_0, if(ifNull(greater(action_previous_count_0, 0), 0), person_id, NULL) AS action_previous_person_id_0, countIf(and(and(equals(events.event, '$pageview'), ifNull(match(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', ''), 'https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'), isNull(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', '')) - and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))))) AS action_current_count_1, + and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))))) AS action_current_count_1, countIf(and(and(equals(events.event, '$pageview'), ifNull(match(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', ''), 'https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'), isNull(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', '')) - and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_1, + and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_1, if(ifNull(greater(action_current_count_1, 0), 0), person_id, NULL) AS action_current_person_id_1, if(ifNull(greater(action_previous_count_1, 0), 0), person_id, NULL) AS action_previous_person_id_1, - countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))))) AS action_current_count_2, - countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_2, + countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))))) AS action_current_count_2, + countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_2, if(ifNull(greater(action_current_count_2, 0), 0), person_id, NULL) AS action_current_person_id_2, if(ifNull(greater(action_previous_count_2, 0), 0), person_id, NULL) AS action_previous_person_id_2 FROM events @@ -433,7 +433,7 @@ (SELECT min(toTimeZone(raw_sessions.min_timestamp, 'UTC')) AS `$start_timestamp`, raw_sessions.session_id_v7 AS session_id_v7 FROM raw_sessions - WHERE and(equals(raw_sessions.team_id, 99999), or(and(ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC'))), 0)), and(ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0)))) + WHERE and(equals(raw_sessions.team_id, 99999), or(and(ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC'))), 0)), and(ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0)))) GROUP BY raw_sessions.session_id_v7, raw_sessions.session_id_v7) AS events__session ON equals(toUInt128(accurateCastOrNull(events.`$session_id`, 'UUID')), events__session.session_id_v7) LEFT OUTER JOIN @@ -444,9 +444,9 @@ 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 events__override ON equals(events.distinct_id, events__override.distinct_id) WHERE and(equals(events.team_id, 99999), and(isNotNull(events.`$session_id`), or(equals(events.event, '$pageview'), equals(events.event, '$screen'), or(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(equals(events.event, '$pageview'), ifNull(match(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', ''), 'https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'), isNull(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', '')) - and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)))), or(and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))))), 1, 1)) + and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)))), or(and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))))), 1, 1)) GROUP BY events.`$session_id`) - WHERE or(and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC'))), 0)), and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0))) + WHERE or(and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC'))), 0)), and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0))) LIMIT 100 SETTINGS readonly=2, max_execution_time=60, allow_experimental_object_type=1, diff --git a/posthog/hogql_queries/web_analytics/test/__snapshots__/test_web_stats_table.ambr b/posthog/hogql_queries/web_analytics/test/__snapshots__/test_web_stats_table.ambr index 8cf0e55b54bdf..197dd4761b6e6 100644 --- a/posthog/hogql_queries/web_analytics/test/__snapshots__/test_web_stats_table.ambr +++ b/posthog/hogql_queries/web_analytics/test/__snapshots__/test_web_stats_table.ambr @@ -497,7 +497,7 @@ min(toTimeZone(raw_sessions.min_timestamp, 'UTC')) AS `$start_timestamp`, raw_sessions.session_id_v7 AS session_id_v7 FROM raw_sessions - WHERE and(equals(raw_sessions.team_id, 99999), ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-07-30 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC'))), 0)) + WHERE and(equals(raw_sessions.team_id, 99999), ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-07-30 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC'))), 0)) GROUP BY raw_sessions.session_id_v7, raw_sessions.session_id_v7) AS events__session ON equals(toUInt128(accurateCastOrNull(events.`$session_id`, 'UUID')), events__session.session_id_v7) LEFT OUTER JOIN @@ -507,7 +507,7 @@ WHERE equals(person_distinct_id_overrides.team_id, 99999) 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 events__override ON equals(events.distinct_id, events__override.distinct_id) - WHERE and(equals(events.team_id, 99999), and(or(and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-07-30 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))), 0), or(equals(events.event, '$pageview'), equals(events.event, '$screen')), ifNull(in(if(not(empty(events__override.distinct_id)), events__override.person_id, events.person_id), + WHERE and(equals(events.team_id, 99999), and(or(and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-07-30 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))), 0), or(equals(events.event, '$pageview'), equals(events.event, '$screen')), ifNull(in(if(not(empty(events__override.distinct_id)), events__override.person_id, events.person_id), (SELECT cohortpeople.person_id AS person_id FROM cohortpeople WHERE and(equals(cohortpeople.team_id, 99999), equals(cohortpeople.cohort_id, 99999), equals(cohortpeople.version, 0)))), 0), isNotNull(breakdown_value))) @@ -1457,7 +1457,7 @@ min(toTimeZone(raw_sessions.min_timestamp, 'UTC')) AS `$start_timestamp`, raw_sessions.session_id_v7 AS session_id_v7 FROM raw_sessions - WHERE and(equals(raw_sessions.team_id, 99999), ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-07-30 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC'))), 0)) + WHERE and(equals(raw_sessions.team_id, 99999), ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-07-30 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC'))), 0)) GROUP BY raw_sessions.session_id_v7, raw_sessions.session_id_v7) AS events__session ON equals(toUInt128(accurateCastOrNull(events.`$session_id`, 'UUID')), events__session.session_id_v7) LEFT OUTER JOIN @@ -1477,7 +1477,7 @@ WHERE equals(person.team_id, 99999) GROUP BY person.id HAVING and(ifNull(equals(argMax(person.is_deleted, person.version), 0), 0), ifNull(less(argMax(toTimeZone(person.created_at, 'UTC'), person.version), plus(now64(6, 'UTC'), toIntervalDay(1))), 0)))), 0)) SETTINGS optimize_aggregation_in_order=1) AS events__person ON equals(if(not(empty(events__override.distinct_id)), events__override.person_id, events.person_id), events__person.id) - WHERE and(equals(events.team_id, 99999), and(or(and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-07-30 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))), 0), or(equals(events.event, '$pageview'), equals(events.event, '$screen')), ifNull(notILike(events__person.properties___email, '%@posthog.com%'), 1), isNotNull(breakdown_value))) + WHERE and(equals(events.team_id, 99999), and(or(and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-07-30 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))), 0), or(equals(events.event, '$pageview'), equals(events.event, '$screen')), ifNull(notILike(events__person.properties___email, '%@posthog.com%'), 1), isNotNull(breakdown_value))) GROUP BY session_id, breakdown_value) GROUP BY `context.columns.breakdown_value` @@ -2799,7 +2799,7 @@ min(toTimeZone(raw_sessions.min_timestamp, 'UTC')) AS `$start_timestamp`, raw_sessions.session_id_v7 AS session_id_v7 FROM raw_sessions - WHERE and(equals(raw_sessions.team_id, 99999), ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2019-02-17 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC'))), 0)) + WHERE and(equals(raw_sessions.team_id, 99999), ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2019-02-17 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC'))), 0)) GROUP BY raw_sessions.session_id_v7, raw_sessions.session_id_v7) AS events__session ON equals(toUInt128(accurateCastOrNull(events.`$session_id`, 'UUID')), events__session.session_id_v7) LEFT OUTER JOIN @@ -2809,7 +2809,7 @@ WHERE equals(person_distinct_id_overrides.team_id, 99999) 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 events__override ON equals(events.distinct_id, events__override.distinct_id) - WHERE and(equals(events.team_id, 99999), and(or(and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2019-02-17 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))), 0), or(equals(events.event, '$pageview'), equals(events.event, '$screen')), 1, isNotNull(breakdown_value))) + WHERE and(equals(events.team_id, 99999), and(or(and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2019-02-17 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))), 0), or(equals(events.event, '$pageview'), equals(events.event, '$screen')), 1, isNotNull(breakdown_value))) GROUP BY session_id, breakdown_value) GROUP BY `context.columns.breakdown_value` @@ -2845,7 +2845,7 @@ min(toTimeZone(raw_sessions.min_timestamp, 'UTC')) AS `$start_timestamp`, raw_sessions.session_id_v7 AS session_id_v7 FROM raw_sessions - WHERE and(equals(raw_sessions.team_id, 99999), ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-07-15 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC'))), 0)) + WHERE and(equals(raw_sessions.team_id, 99999), ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-07-15 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC'))), 0)) GROUP BY raw_sessions.session_id_v7, raw_sessions.session_id_v7) AS events__session ON equals(toUInt128(accurateCastOrNull(events.`$session_id`, 'UUID')), events__session.session_id_v7) LEFT OUTER JOIN @@ -2855,7 +2855,7 @@ WHERE equals(person_distinct_id_overrides.team_id, 99999) 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 events__override ON equals(events.distinct_id, events__override.distinct_id) - WHERE and(equals(events.team_id, 99999), and(or(and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-07-15 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))), 0), or(equals(events.event, '$pageview'), equals(events.event, '$screen')), 1, isNotNull(breakdown_value))) + WHERE and(equals(events.team_id, 99999), and(or(and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-07-15 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))), 0), or(equals(events.event, '$pageview'), equals(events.event, '$screen')), 1, isNotNull(breakdown_value))) GROUP BY session_id, breakdown_value) GROUP BY `context.columns.breakdown_value` @@ -2902,7 +2902,7 @@ min(toTimeZone(raw_sessions.min_timestamp, 'UTC')) AS `$start_timestamp`, raw_sessions.session_id_v7 AS session_id_v7 FROM raw_sessions - WHERE and(equals(raw_sessions.team_id, 99999), ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2019-02-17 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC'))), 0)) + WHERE and(equals(raw_sessions.team_id, 99999), ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2019-02-17 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC'))), 0)) GROUP BY raw_sessions.session_id_v7, raw_sessions.session_id_v7) AS events__session ON equals(toUInt128(accurateCastOrNull(events.`$session_id`, 'UUID')), events__session.session_id_v7) LEFT OUTER JOIN @@ -2912,7 +2912,7 @@ WHERE equals(person_distinct_id_overrides.team_id, 99999) 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 events__override ON equals(events.distinct_id, events__override.distinct_id) - WHERE and(equals(events.team_id, 99999), and(or(and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2019-02-17 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))), 0), or(equals(events.event, '$pageview'), equals(events.event, '$screen')), 1, isNotNull(breakdown_value))) + WHERE and(equals(events.team_id, 99999), and(or(and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2019-02-17 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))), 0), or(equals(events.event, '$pageview'), equals(events.event, '$screen')), 1, isNotNull(breakdown_value))) GROUP BY session_id, breakdown_value) GROUP BY `context.columns.breakdown_value` @@ -2959,7 +2959,7 @@ min(toTimeZone(raw_sessions.min_timestamp, 'UTC')) AS `$start_timestamp`, raw_sessions.session_id_v7 AS session_id_v7 FROM raw_sessions - WHERE and(equals(raw_sessions.team_id, 99999), ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-07-30 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC'))), 0)) + WHERE and(equals(raw_sessions.team_id, 99999), ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-07-30 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC'))), 0)) GROUP BY raw_sessions.session_id_v7, raw_sessions.session_id_v7) AS events__session ON equals(toUInt128(accurateCastOrNull(events.`$session_id`, 'UUID')), events__session.session_id_v7) LEFT OUTER JOIN @@ -2969,7 +2969,7 @@ WHERE equals(person_distinct_id_overrides.team_id, 99999) 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 events__override ON equals(events.distinct_id, events__override.distinct_id) - WHERE and(equals(events.team_id, 99999), and(or(and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-07-30 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))), 0), or(equals(events.event, '$pageview'), equals(events.event, '$screen')), 1, isNotNull(breakdown_value))) + WHERE and(equals(events.team_id, 99999), and(or(and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-07-30 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))), 0), or(equals(events.event, '$pageview'), equals(events.event, '$screen')), 1, isNotNull(breakdown_value))) GROUP BY session_id, breakdown_value) GROUP BY `context.columns.breakdown_value` From 5f908302a45b7fe5b68285646ce2681fa6fef98c Mon Sep 17 00:00:00 2001 From: Ben White Date: Thu, 30 Jan 2025 09:02:05 +0100 Subject: [PATCH 110/163] Fix --- plugin-server/src/cdp/cdp-api.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin-server/src/cdp/cdp-api.ts b/plugin-server/src/cdp/cdp-api.ts index 06019f0e5ba38..825a19794eae8 100644 --- a/plugin-server/src/cdp/cdp-api.ts +++ b/plugin-server/src/cdp/cdp-api.ts @@ -212,7 +212,7 @@ export class CdpApi { ], } } else { - response = await this.fetchExecutor!.executeLocally(invocation) + response = await this.fetchExecutor.executeLocally(invocation) } } else { response = this.hogExecutor.execute(invocation) From 965d632b4fb22137ac8d86dfc7573e57b0251b3b Mon Sep 17 00:00:00 2001 From: Ben White Date: Thu, 30 Jan 2025 09:02:59 +0100 Subject: [PATCH 111/163] Fix --- posthog/api/test/test_hog_function_templates.py | 1 - 1 file changed, 1 deletion(-) diff --git a/posthog/api/test/test_hog_function_templates.py b/posthog/api/test/test_hog_function_templates.py index 46e25e18653ee..d0883e265b069 100644 --- a/posthog/api/test/test_hog_function_templates.py +++ b/posthog/api/test/test_hog_function_templates.py @@ -2,7 +2,6 @@ import os from unittest.mock import ANY, patch from inline_snapshot import snapshot -import pytest from rest_framework import status from posthog.api.hog_function_template import HogFunctionTemplates From 34679cc97f2aa2af34091af88b369759d5e21176 Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 30 Jan 2025 08:10:20 +0000 Subject: [PATCH 112/163] Update query snapshots --- .../test/__snapshots__/test_web_goals.ambr | 164 +++++++++--------- .../__snapshots__/test_web_stats_table.ambr | 24 +-- 2 files changed, 94 insertions(+), 94 deletions(-) diff --git a/posthog/hogql_queries/web_analytics/test/__snapshots__/test_web_goals.ambr b/posthog/hogql_queries/web_analytics/test/__snapshots__/test_web_goals.ambr index baf646431c4e8..d2b78d71f03d1 100644 --- a/posthog/hogql_queries/web_analytics/test/__snapshots__/test_web_goals.ambr +++ b/posthog/hogql_queries/web_analytics/test/__snapshots__/test_web_goals.ambr @@ -1,8 +1,8 @@ # serializer version: 1 # name: TestWebGoalsQueryRunner.test_dont_show_deleted_actions ''' - SELECT uniqIf(person_id, and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC'))), 0))) AS current_total_people, - uniqIf(person_id, and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0))) AS previous_total_people, + SELECT uniqIf(person_id, and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC'))), 0))) AS current_total_people, + uniqIf(person_id, and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0))) AS previous_total_people, 'Contacted Sales' AS action_name_0, tuple(sum(action_current_count_0), sum(action_previous_count_0)) AS action_total_0, tuple(uniq(action_current_person_id_0), uniq(action_previous_person_id_0)) AS action_uniques_0, @@ -12,14 +12,14 @@ FROM (SELECT any(if(not(empty(events__override.distinct_id)), events__override.person_id, events.person_id)) AS person_id, min(events__session.`$start_timestamp`) AS start_timestamp, - countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))))) AS action_current_count_0, - countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_0, + countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))))) AS action_current_count_0, + countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_0, if(ifNull(greater(action_current_count_0, 0), 0), person_id, NULL) AS action_current_person_id_0, if(ifNull(greater(action_previous_count_0, 0), 0), person_id, NULL) AS action_previous_person_id_0, countIf(and(and(equals(events.event, '$pageview'), ifNull(match(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', ''), 'https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'), isNull(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', '')) - and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))))) AS action_current_count_1, + and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))))) AS action_current_count_1, countIf(and(and(equals(events.event, '$pageview'), ifNull(match(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', ''), 'https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'), isNull(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', '')) - and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_1, + and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_1, if(ifNull(greater(action_current_count_1, 0), 0), person_id, NULL) AS action_current_person_id_1, if(ifNull(greater(action_previous_count_1, 0), 0), person_id, NULL) AS action_previous_person_id_1 FROM events @@ -27,7 +27,7 @@ (SELECT min(toTimeZone(raw_sessions.min_timestamp, 'UTC')) AS `$start_timestamp`, raw_sessions.session_id_v7 AS session_id_v7 FROM raw_sessions - WHERE and(equals(raw_sessions.team_id, 99999), or(and(ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC'))), 0)), and(ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0)))) + WHERE and(equals(raw_sessions.team_id, 99999), or(and(ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC'))), 0)), and(ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0)))) GROUP BY raw_sessions.session_id_v7, raw_sessions.session_id_v7) AS events__session ON equals(toUInt128(accurateCastOrNull(events.`$session_id`, 'UUID')), events__session.session_id_v7) LEFT OUTER JOIN @@ -38,9 +38,9 @@ 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 events__override ON equals(events.distinct_id, events__override.distinct_id) WHERE and(equals(events.team_id, 99999), and(isNotNull(events.`$session_id`), or(equals(events.event, '$pageview'), equals(events.event, '$screen'), or(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(equals(events.event, '$pageview'), ifNull(match(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', ''), 'https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'), isNull(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', '')) - and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))))), or(and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))))), 1, 1)) + and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))))), or(and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))))), 1, 1)) GROUP BY events.`$session_id`) - WHERE or(and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC'))), 0)), and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0))) + WHERE or(and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC'))), 0)), and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0))) LIMIT 100 SETTINGS readonly=2, max_execution_time=60, allow_experimental_object_type=1, @@ -52,8 +52,8 @@ # --- # name: TestWebGoalsQueryRunner.test_many_users_and_actions ''' - SELECT uniqIf(person_id, and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC'))), 0))) AS current_total_people, - uniqIf(person_id, and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0))) AS previous_total_people, + SELECT uniqIf(person_id, and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC'))), 0))) AS current_total_people, + uniqIf(person_id, and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0))) AS previous_total_people, 'Contacted Sales' AS action_name_0, tuple(sum(action_current_count_0), sum(action_previous_count_0)) AS action_total_0, tuple(uniq(action_current_person_id_0), uniq(action_previous_person_id_0)) AS action_uniques_0, @@ -66,18 +66,18 @@ FROM (SELECT any(if(not(empty(events__override.distinct_id)), events__override.person_id, events.person_id)) AS person_id, min(events__session.`$start_timestamp`) AS start_timestamp, - countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))))) AS action_current_count_0, - countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_0, + countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))))) AS action_current_count_0, + countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_0, if(ifNull(greater(action_current_count_0, 0), 0), person_id, NULL) AS action_current_person_id_0, if(ifNull(greater(action_previous_count_0, 0), 0), person_id, NULL) AS action_previous_person_id_0, countIf(and(and(equals(events.event, '$pageview'), ifNull(match(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', ''), 'https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'), isNull(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', '')) - and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))))) AS action_current_count_1, + and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))))) AS action_current_count_1, countIf(and(and(equals(events.event, '$pageview'), ifNull(match(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', ''), 'https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'), isNull(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', '')) - and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_1, + and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_1, if(ifNull(greater(action_current_count_1, 0), 0), person_id, NULL) AS action_current_person_id_1, if(ifNull(greater(action_previous_count_1, 0), 0), person_id, NULL) AS action_previous_person_id_1, - countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))))) AS action_current_count_2, - countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_2, + countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))))) AS action_current_count_2, + countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_2, if(ifNull(greater(action_current_count_2, 0), 0), person_id, NULL) AS action_current_person_id_2, if(ifNull(greater(action_previous_count_2, 0), 0), person_id, NULL) AS action_previous_person_id_2 FROM events @@ -85,7 +85,7 @@ (SELECT min(toTimeZone(raw_sessions.min_timestamp, 'UTC')) AS `$start_timestamp`, raw_sessions.session_id_v7 AS session_id_v7 FROM raw_sessions - WHERE and(equals(raw_sessions.team_id, 99999), or(and(ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC'))), 0)), and(ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0)))) + WHERE and(equals(raw_sessions.team_id, 99999), or(and(ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC'))), 0)), and(ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0)))) GROUP BY raw_sessions.session_id_v7, raw_sessions.session_id_v7) AS events__session ON equals(toUInt128(accurateCastOrNull(events.`$session_id`, 'UUID')), events__session.session_id_v7) LEFT OUTER JOIN @@ -96,9 +96,9 @@ 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 events__override ON equals(events.distinct_id, events__override.distinct_id) WHERE and(equals(events.team_id, 99999), and(isNotNull(events.`$session_id`), or(equals(events.event, '$pageview'), equals(events.event, '$screen'), or(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(equals(events.event, '$pageview'), ifNull(match(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', ''), 'https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'), isNull(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', '')) - and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)))), or(and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))))), 1, 1)) + and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)))), or(and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))))), 1, 1)) GROUP BY events.`$session_id`) - WHERE or(and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC'))), 0)), and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0))) + WHERE or(and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC'))), 0)), and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0))) LIMIT 100 SETTINGS readonly=2, max_execution_time=60, allow_experimental_object_type=1, @@ -110,7 +110,7 @@ # --- # name: TestWebGoalsQueryRunner.test_no_comparison ''' - SELECT uniqIf(person_id, and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC'))), 0))) AS current_total_people, + SELECT uniqIf(person_id, and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC'))), 0))) AS current_total_people, uniqIf(person_id, 0) AS previous_total_people, 'Contacted Sales' AS action_name_0, tuple(sum(action_current_count_0), NULL) AS action_total_0, @@ -124,17 +124,17 @@ FROM (SELECT any(if(not(empty(events__override.distinct_id)), events__override.person_id, events.person_id)) AS person_id, min(events__session.`$start_timestamp`) AS start_timestamp, - countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))))) AS action_current_count_0, + countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))))) AS action_current_count_0, countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), 0)) AS action_previous_count_0, if(ifNull(greater(action_current_count_0, 0), 0), person_id, NULL) AS action_current_person_id_0, if(ifNull(greater(action_previous_count_0, 0), 0), person_id, NULL) AS action_previous_person_id_0, countIf(and(and(equals(events.event, '$pageview'), ifNull(match(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', ''), 'https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'), isNull(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', '')) - and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))))) AS action_current_count_1, + and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))))) AS action_current_count_1, countIf(and(and(equals(events.event, '$pageview'), ifNull(match(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', ''), 'https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'), isNull(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', '')) and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), 0)) AS action_previous_count_1, if(ifNull(greater(action_current_count_1, 0), 0), person_id, NULL) AS action_current_person_id_1, if(ifNull(greater(action_previous_count_1, 0), 0), person_id, NULL) AS action_previous_person_id_1, - countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))))) AS action_current_count_2, + countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))))) AS action_current_count_2, countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)), 0)) AS action_previous_count_2, if(ifNull(greater(action_current_count_2, 0), 0), person_id, NULL) AS action_current_person_id_2, if(ifNull(greater(action_previous_count_2, 0), 0), person_id, NULL) AS action_previous_person_id_2 @@ -143,7 +143,7 @@ (SELECT min(toTimeZone(raw_sessions.min_timestamp, 'UTC')) AS `$start_timestamp`, raw_sessions.session_id_v7 AS session_id_v7 FROM raw_sessions - WHERE and(equals(raw_sessions.team_id, 99999), ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC'))), 0)) + WHERE and(equals(raw_sessions.team_id, 99999), ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC'))), 0)) GROUP BY raw_sessions.session_id_v7, raw_sessions.session_id_v7) AS events__session ON equals(toUInt128(accurateCastOrNull(events.`$session_id`, 'UUID')), events__session.session_id_v7) LEFT OUTER JOIN @@ -154,9 +154,9 @@ 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 events__override ON equals(events.distinct_id, events__override.distinct_id) WHERE and(equals(events.team_id, 99999), and(isNotNull(events.`$session_id`), or(equals(events.event, '$pageview'), equals(events.event, '$screen'), or(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(equals(events.event, '$pageview'), ifNull(match(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', ''), 'https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'), isNull(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', '')) - and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)))), or(and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))), 0), 1, 1)) + and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)))), or(and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))), 0), 1, 1)) GROUP BY events.`$session_id`) - WHERE or(and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC'))), 0)), 0) + WHERE or(and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC'))), 0)), 0) LIMIT 100 SETTINGS readonly=2, max_execution_time=60, allow_experimental_object_type=1, @@ -168,8 +168,8 @@ # --- # name: TestWebGoalsQueryRunner.test_no_crash_when_no_data_and_some_actions ''' - SELECT uniqIf(person_id, and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC'))), 0))) AS current_total_people, - uniqIf(person_id, and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0))) AS previous_total_people, + SELECT uniqIf(person_id, and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC'))), 0))) AS current_total_people, + uniqIf(person_id, and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0))) AS previous_total_people, 'Contacted Sales' AS action_name_0, tuple(sum(action_current_count_0), sum(action_previous_count_0)) AS action_total_0, tuple(uniq(action_current_person_id_0), uniq(action_previous_person_id_0)) AS action_uniques_0, @@ -182,18 +182,18 @@ FROM (SELECT any(if(not(empty(events__override.distinct_id)), events__override.person_id, events.person_id)) AS person_id, min(events__session.`$start_timestamp`) AS start_timestamp, - countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))))) AS action_current_count_0, - countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_0, + countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))))) AS action_current_count_0, + countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_0, if(ifNull(greater(action_current_count_0, 0), 0), person_id, NULL) AS action_current_person_id_0, if(ifNull(greater(action_previous_count_0, 0), 0), person_id, NULL) AS action_previous_person_id_0, countIf(and(and(equals(events.event, '$pageview'), ifNull(match(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', ''), 'https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'), isNull(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', '')) - and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))))) AS action_current_count_1, + and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))))) AS action_current_count_1, countIf(and(and(equals(events.event, '$pageview'), ifNull(match(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', ''), 'https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'), isNull(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', '')) - and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_1, + and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_1, if(ifNull(greater(action_current_count_1, 0), 0), person_id, NULL) AS action_current_person_id_1, if(ifNull(greater(action_previous_count_1, 0), 0), person_id, NULL) AS action_previous_person_id_1, - countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))))) AS action_current_count_2, - countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_2, + countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))))) AS action_current_count_2, + countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_2, if(ifNull(greater(action_current_count_2, 0), 0), person_id, NULL) AS action_current_person_id_2, if(ifNull(greater(action_previous_count_2, 0), 0), person_id, NULL) AS action_previous_person_id_2 FROM events @@ -201,7 +201,7 @@ (SELECT min(toTimeZone(raw_sessions.min_timestamp, 'UTC')) AS `$start_timestamp`, raw_sessions.session_id_v7 AS session_id_v7 FROM raw_sessions - WHERE and(equals(raw_sessions.team_id, 99999), or(and(ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC'))), 0)), and(ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0)))) + WHERE and(equals(raw_sessions.team_id, 99999), or(and(ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC'))), 0)), and(ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0)))) GROUP BY raw_sessions.session_id_v7, raw_sessions.session_id_v7) AS events__session ON equals(toUInt128(accurateCastOrNull(events.`$session_id`, 'UUID')), events__session.session_id_v7) LEFT OUTER JOIN @@ -212,9 +212,9 @@ 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 events__override ON equals(events.distinct_id, events__override.distinct_id) WHERE and(equals(events.team_id, 99999), and(isNotNull(events.`$session_id`), or(equals(events.event, '$pageview'), equals(events.event, '$screen'), or(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(equals(events.event, '$pageview'), ifNull(match(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', ''), 'https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'), isNull(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', '')) - and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)))), or(and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))))), 1, 1)) + and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)))), or(and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))))), 1, 1)) GROUP BY events.`$session_id`) - WHERE or(and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC'))), 0)), and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0))) + WHERE or(and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC'))), 0)), and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0))) LIMIT 100 SETTINGS readonly=2, max_execution_time=60, allow_experimental_object_type=1, @@ -226,8 +226,8 @@ # --- # name: TestWebGoalsQueryRunner.test_one_user_one_action ''' - SELECT uniqIf(person_id, and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC'))), 0))) AS current_total_people, - uniqIf(person_id, and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0))) AS previous_total_people, + SELECT uniqIf(person_id, and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC'))), 0))) AS current_total_people, + uniqIf(person_id, and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0))) AS previous_total_people, 'Contacted Sales' AS action_name_0, tuple(sum(action_current_count_0), sum(action_previous_count_0)) AS action_total_0, tuple(uniq(action_current_person_id_0), uniq(action_previous_person_id_0)) AS action_uniques_0, @@ -240,18 +240,18 @@ FROM (SELECT any(if(not(empty(events__override.distinct_id)), events__override.person_id, events.person_id)) AS person_id, min(events__session.`$start_timestamp`) AS start_timestamp, - countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))))) AS action_current_count_0, - countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_0, + countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))))) AS action_current_count_0, + countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_0, if(ifNull(greater(action_current_count_0, 0), 0), person_id, NULL) AS action_current_person_id_0, if(ifNull(greater(action_previous_count_0, 0), 0), person_id, NULL) AS action_previous_person_id_0, countIf(and(and(equals(events.event, '$pageview'), ifNull(match(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', ''), 'https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'), isNull(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', '')) - and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))))) AS action_current_count_1, + and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))))) AS action_current_count_1, countIf(and(and(equals(events.event, '$pageview'), ifNull(match(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', ''), 'https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'), isNull(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', '')) - and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_1, + and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_1, if(ifNull(greater(action_current_count_1, 0), 0), person_id, NULL) AS action_current_person_id_1, if(ifNull(greater(action_previous_count_1, 0), 0), person_id, NULL) AS action_previous_person_id_1, - countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))))) AS action_current_count_2, - countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_2, + countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))))) AS action_current_count_2, + countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_2, if(ifNull(greater(action_current_count_2, 0), 0), person_id, NULL) AS action_current_person_id_2, if(ifNull(greater(action_previous_count_2, 0), 0), person_id, NULL) AS action_previous_person_id_2 FROM events @@ -259,7 +259,7 @@ (SELECT min(toTimeZone(raw_sessions.min_timestamp, 'UTC')) AS `$start_timestamp`, raw_sessions.session_id_v7 AS session_id_v7 FROM raw_sessions - WHERE and(equals(raw_sessions.team_id, 99999), or(and(ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC'))), 0)), and(ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0)))) + WHERE and(equals(raw_sessions.team_id, 99999), or(and(ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC'))), 0)), and(ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0)))) GROUP BY raw_sessions.session_id_v7, raw_sessions.session_id_v7) AS events__session ON equals(toUInt128(accurateCastOrNull(events.`$session_id`, 'UUID')), events__session.session_id_v7) LEFT OUTER JOIN @@ -270,9 +270,9 @@ 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 events__override ON equals(events.distinct_id, events__override.distinct_id) WHERE and(equals(events.team_id, 99999), and(isNotNull(events.`$session_id`), or(equals(events.event, '$pageview'), equals(events.event, '$screen'), or(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(equals(events.event, '$pageview'), ifNull(match(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', ''), 'https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'), isNull(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', '')) - and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)))), or(and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))))), 1, 1)) + and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)))), or(and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))))), 1, 1)) GROUP BY events.`$session_id`) - WHERE or(and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC'))), 0)), and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0))) + WHERE or(and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC'))), 0)), and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0))) LIMIT 100 SETTINGS readonly=2, max_execution_time=60, allow_experimental_object_type=1, @@ -284,8 +284,8 @@ # --- # name: TestWebGoalsQueryRunner.test_one_user_two_different_actions ''' - SELECT uniqIf(person_id, and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC'))), 0))) AS current_total_people, - uniqIf(person_id, and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0))) AS previous_total_people, + SELECT uniqIf(person_id, and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC'))), 0))) AS current_total_people, + uniqIf(person_id, and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0))) AS previous_total_people, 'Contacted Sales' AS action_name_0, tuple(sum(action_current_count_0), sum(action_previous_count_0)) AS action_total_0, tuple(uniq(action_current_person_id_0), uniq(action_previous_person_id_0)) AS action_uniques_0, @@ -298,18 +298,18 @@ FROM (SELECT any(if(not(empty(events__override.distinct_id)), events__override.person_id, events.person_id)) AS person_id, min(events__session.`$start_timestamp`) AS start_timestamp, - countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))))) AS action_current_count_0, - countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_0, + countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))))) AS action_current_count_0, + countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_0, if(ifNull(greater(action_current_count_0, 0), 0), person_id, NULL) AS action_current_person_id_0, if(ifNull(greater(action_previous_count_0, 0), 0), person_id, NULL) AS action_previous_person_id_0, countIf(and(and(equals(events.event, '$pageview'), ifNull(match(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', ''), 'https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'), isNull(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', '')) - and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))))) AS action_current_count_1, + and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))))) AS action_current_count_1, countIf(and(and(equals(events.event, '$pageview'), ifNull(match(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', ''), 'https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'), isNull(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', '')) - and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_1, + and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_1, if(ifNull(greater(action_current_count_1, 0), 0), person_id, NULL) AS action_current_person_id_1, if(ifNull(greater(action_previous_count_1, 0), 0), person_id, NULL) AS action_previous_person_id_1, - countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))))) AS action_current_count_2, - countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_2, + countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))))) AS action_current_count_2, + countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_2, if(ifNull(greater(action_current_count_2, 0), 0), person_id, NULL) AS action_current_person_id_2, if(ifNull(greater(action_previous_count_2, 0), 0), person_id, NULL) AS action_previous_person_id_2 FROM events @@ -317,7 +317,7 @@ (SELECT min(toTimeZone(raw_sessions.min_timestamp, 'UTC')) AS `$start_timestamp`, raw_sessions.session_id_v7 AS session_id_v7 FROM raw_sessions - WHERE and(equals(raw_sessions.team_id, 99999), or(and(ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC'))), 0)), and(ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0)))) + WHERE and(equals(raw_sessions.team_id, 99999), or(and(ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC'))), 0)), and(ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0)))) GROUP BY raw_sessions.session_id_v7, raw_sessions.session_id_v7) AS events__session ON equals(toUInt128(accurateCastOrNull(events.`$session_id`, 'UUID')), events__session.session_id_v7) LEFT OUTER JOIN @@ -328,9 +328,9 @@ 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 events__override ON equals(events.distinct_id, events__override.distinct_id) WHERE and(equals(events.team_id, 99999), and(isNotNull(events.`$session_id`), or(equals(events.event, '$pageview'), equals(events.event, '$screen'), or(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(equals(events.event, '$pageview'), ifNull(match(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', ''), 'https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'), isNull(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', '')) - and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)))), or(and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))))), 1, 1)) + and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)))), or(and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))))), 1, 1)) GROUP BY events.`$session_id`) - WHERE or(and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC'))), 0)), and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0))) + WHERE or(and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC'))), 0)), and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0))) LIMIT 100 SETTINGS readonly=2, max_execution_time=60, allow_experimental_object_type=1, @@ -342,8 +342,8 @@ # --- # name: TestWebGoalsQueryRunner.test_one_user_two_similar_actions_across_sessions ''' - SELECT uniqIf(person_id, and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC'))), 0))) AS current_total_people, - uniqIf(person_id, and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0))) AS previous_total_people, + SELECT uniqIf(person_id, and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC'))), 0))) AS current_total_people, + uniqIf(person_id, and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0))) AS previous_total_people, 'Contacted Sales' AS action_name_0, tuple(sum(action_current_count_0), sum(action_previous_count_0)) AS action_total_0, tuple(uniq(action_current_person_id_0), uniq(action_previous_person_id_0)) AS action_uniques_0, @@ -356,18 +356,18 @@ FROM (SELECT any(if(not(empty(events__override.distinct_id)), events__override.person_id, events.person_id)) AS person_id, min(events__session.`$start_timestamp`) AS start_timestamp, - countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))))) AS action_current_count_0, - countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_0, + countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))))) AS action_current_count_0, + countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_0, if(ifNull(greater(action_current_count_0, 0), 0), person_id, NULL) AS action_current_person_id_0, if(ifNull(greater(action_previous_count_0, 0), 0), person_id, NULL) AS action_previous_person_id_0, countIf(and(and(equals(events.event, '$pageview'), ifNull(match(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', ''), 'https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'), isNull(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', '')) - and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))))) AS action_current_count_1, + and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))))) AS action_current_count_1, countIf(and(and(equals(events.event, '$pageview'), ifNull(match(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', ''), 'https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'), isNull(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', '')) - and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_1, + and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_1, if(ifNull(greater(action_current_count_1, 0), 0), person_id, NULL) AS action_current_person_id_1, if(ifNull(greater(action_previous_count_1, 0), 0), person_id, NULL) AS action_previous_person_id_1, - countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))))) AS action_current_count_2, - countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_2, + countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))))) AS action_current_count_2, + countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_2, if(ifNull(greater(action_current_count_2, 0), 0), person_id, NULL) AS action_current_person_id_2, if(ifNull(greater(action_previous_count_2, 0), 0), person_id, NULL) AS action_previous_person_id_2 FROM events @@ -375,7 +375,7 @@ (SELECT min(toTimeZone(raw_sessions.min_timestamp, 'UTC')) AS `$start_timestamp`, raw_sessions.session_id_v7 AS session_id_v7 FROM raw_sessions - WHERE and(equals(raw_sessions.team_id, 99999), or(and(ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC'))), 0)), and(ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0)))) + WHERE and(equals(raw_sessions.team_id, 99999), or(and(ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC'))), 0)), and(ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0)))) GROUP BY raw_sessions.session_id_v7, raw_sessions.session_id_v7) AS events__session ON equals(toUInt128(accurateCastOrNull(events.`$session_id`, 'UUID')), events__session.session_id_v7) LEFT OUTER JOIN @@ -386,9 +386,9 @@ 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 events__override ON equals(events.distinct_id, events__override.distinct_id) WHERE and(equals(events.team_id, 99999), and(isNotNull(events.`$session_id`), or(equals(events.event, '$pageview'), equals(events.event, '$screen'), or(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(equals(events.event, '$pageview'), ifNull(match(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', ''), 'https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'), isNull(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', '')) - and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)))), or(and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))))), 1, 1)) + and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)))), or(and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))))), 1, 1)) GROUP BY events.`$session_id`) - WHERE or(and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC'))), 0)), and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0))) + WHERE or(and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC'))), 0)), and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0))) LIMIT 100 SETTINGS readonly=2, max_execution_time=60, allow_experimental_object_type=1, @@ -400,8 +400,8 @@ # --- # name: TestWebGoalsQueryRunner.test_one_users_one_action_each ''' - SELECT uniqIf(person_id, and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC'))), 0))) AS current_total_people, - uniqIf(person_id, and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0))) AS previous_total_people, + SELECT uniqIf(person_id, and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC'))), 0))) AS current_total_people, + uniqIf(person_id, and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0))) AS previous_total_people, 'Contacted Sales' AS action_name_0, tuple(sum(action_current_count_0), sum(action_previous_count_0)) AS action_total_0, tuple(uniq(action_current_person_id_0), uniq(action_previous_person_id_0)) AS action_uniques_0, @@ -414,18 +414,18 @@ FROM (SELECT any(if(not(empty(events__override.distinct_id)), events__override.person_id, events.person_id)) AS person_id, min(events__session.`$start_timestamp`) AS start_timestamp, - countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))))) AS action_current_count_0, - countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_0, + countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))))) AS action_current_count_0, + countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_0, if(ifNull(greater(action_current_count_0, 0), 0), person_id, NULL) AS action_current_person_id_0, if(ifNull(greater(action_previous_count_0, 0), 0), person_id, NULL) AS action_previous_person_id_0, countIf(and(and(equals(events.event, '$pageview'), ifNull(match(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', ''), 'https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'), isNull(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', '')) - and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))))) AS action_current_count_1, + and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))))) AS action_current_count_1, countIf(and(and(equals(events.event, '$pageview'), ifNull(match(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', ''), 'https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'), isNull(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', '')) - and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_1, + and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_1, if(ifNull(greater(action_current_count_1, 0), 0), person_id, NULL) AS action_current_person_id_1, if(ifNull(greater(action_previous_count_1, 0), 0), person_id, NULL) AS action_previous_person_id_1, - countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))))) AS action_current_count_2, - countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_2, + countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))))) AS action_current_count_2, + countIf(and(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC')))))) AS action_previous_count_2, if(ifNull(greater(action_current_count_2, 0), 0), person_id, NULL) AS action_current_person_id_2, if(ifNull(greater(action_previous_count_2, 0), 0), person_id, NULL) AS action_previous_person_id_2 FROM events @@ -433,7 +433,7 @@ (SELECT min(toTimeZone(raw_sessions.min_timestamp, 'UTC')) AS `$start_timestamp`, raw_sessions.session_id_v7 AS session_id_v7 FROM raw_sessions - WHERE and(equals(raw_sessions.team_id, 99999), or(and(ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC'))), 0)), and(ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0)))) + WHERE and(equals(raw_sessions.team_id, 99999), or(and(ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC'))), 0)), and(ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0)))) GROUP BY raw_sessions.session_id_v7, raw_sessions.session_id_v7) AS events__session ON equals(toUInt128(accurateCastOrNull(events.`$session_id`, 'UUID')), events__session.session_id_v7) LEFT OUTER JOIN @@ -444,9 +444,9 @@ 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 events__override ON equals(events.distinct_id, events__override.distinct_id) WHERE and(equals(events.team_id, 99999), and(isNotNull(events.`$session_id`), or(equals(events.event, '$pageview'), equals(events.event, '$screen'), or(and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Contacted Sales'), 0), events.elements_chain_texts)), and(equals(events.event, '$pageview'), ifNull(match(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', ''), 'https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'), isNull(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(events.properties, '$current_url'), ''), 'null'), '^"|"$', '')) - and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)))), or(and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))))), 1, 1)) + and isNull('https://(app|eu|us)\\.posthog\\.com/project/\\d+/web.*'))), and(equals(events.event, '$autocapture'), match(events.elements_chain, '(^|;)button(\\.|$|;|:)'), arrayExists(x -> ifNull(equals(x, 'Pay $10'), 0), events.elements_chain_texts)))), or(and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))), and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))))), 1, 1)) GROUP BY events.`$session_id`) - WHERE or(and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC'))), 0)), and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-03 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0))) + WHERE or(and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-11-01 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC'))), 0)), and(ifNull(greaterOrEquals(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-08-02 00:00:00', 6, 'UTC'))), 0), ifNull(less(start_timestamp, assumeNotNull(parseDateTime64BestEffortOrNull('2024-10-31 23:59:59', 6, 'UTC'))), 0))) LIMIT 100 SETTINGS readonly=2, max_execution_time=60, allow_experimental_object_type=1, diff --git a/posthog/hogql_queries/web_analytics/test/__snapshots__/test_web_stats_table.ambr b/posthog/hogql_queries/web_analytics/test/__snapshots__/test_web_stats_table.ambr index 8cf0e55b54bdf..197dd4761b6e6 100644 --- a/posthog/hogql_queries/web_analytics/test/__snapshots__/test_web_stats_table.ambr +++ b/posthog/hogql_queries/web_analytics/test/__snapshots__/test_web_stats_table.ambr @@ -497,7 +497,7 @@ min(toTimeZone(raw_sessions.min_timestamp, 'UTC')) AS `$start_timestamp`, raw_sessions.session_id_v7 AS session_id_v7 FROM raw_sessions - WHERE and(equals(raw_sessions.team_id, 99999), ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-07-30 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC'))), 0)) + WHERE and(equals(raw_sessions.team_id, 99999), ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-07-30 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC'))), 0)) GROUP BY raw_sessions.session_id_v7, raw_sessions.session_id_v7) AS events__session ON equals(toUInt128(accurateCastOrNull(events.`$session_id`, 'UUID')), events__session.session_id_v7) LEFT OUTER JOIN @@ -507,7 +507,7 @@ WHERE equals(person_distinct_id_overrides.team_id, 99999) 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 events__override ON equals(events.distinct_id, events__override.distinct_id) - WHERE and(equals(events.team_id, 99999), and(or(and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-07-30 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))), 0), or(equals(events.event, '$pageview'), equals(events.event, '$screen')), ifNull(in(if(not(empty(events__override.distinct_id)), events__override.person_id, events.person_id), + WHERE and(equals(events.team_id, 99999), and(or(and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-07-30 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))), 0), or(equals(events.event, '$pageview'), equals(events.event, '$screen')), ifNull(in(if(not(empty(events__override.distinct_id)), events__override.person_id, events.person_id), (SELECT cohortpeople.person_id AS person_id FROM cohortpeople WHERE and(equals(cohortpeople.team_id, 99999), equals(cohortpeople.cohort_id, 99999), equals(cohortpeople.version, 0)))), 0), isNotNull(breakdown_value))) @@ -1457,7 +1457,7 @@ min(toTimeZone(raw_sessions.min_timestamp, 'UTC')) AS `$start_timestamp`, raw_sessions.session_id_v7 AS session_id_v7 FROM raw_sessions - WHERE and(equals(raw_sessions.team_id, 99999), ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-07-30 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC'))), 0)) + WHERE and(equals(raw_sessions.team_id, 99999), ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-07-30 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC'))), 0)) GROUP BY raw_sessions.session_id_v7, raw_sessions.session_id_v7) AS events__session ON equals(toUInt128(accurateCastOrNull(events.`$session_id`, 'UUID')), events__session.session_id_v7) LEFT OUTER JOIN @@ -1477,7 +1477,7 @@ WHERE equals(person.team_id, 99999) GROUP BY person.id HAVING and(ifNull(equals(argMax(person.is_deleted, person.version), 0), 0), ifNull(less(argMax(toTimeZone(person.created_at, 'UTC'), person.version), plus(now64(6, 'UTC'), toIntervalDay(1))), 0)))), 0)) SETTINGS optimize_aggregation_in_order=1) AS events__person ON equals(if(not(empty(events__override.distinct_id)), events__override.person_id, events.person_id), events__person.id) - WHERE and(equals(events.team_id, 99999), and(or(and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-07-30 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))), 0), or(equals(events.event, '$pageview'), equals(events.event, '$screen')), ifNull(notILike(events__person.properties___email, '%@posthog.com%'), 1), isNotNull(breakdown_value))) + WHERE and(equals(events.team_id, 99999), and(or(and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-07-30 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))), 0), or(equals(events.event, '$pageview'), equals(events.event, '$screen')), ifNull(notILike(events__person.properties___email, '%@posthog.com%'), 1), isNotNull(breakdown_value))) GROUP BY session_id, breakdown_value) GROUP BY `context.columns.breakdown_value` @@ -2799,7 +2799,7 @@ min(toTimeZone(raw_sessions.min_timestamp, 'UTC')) AS `$start_timestamp`, raw_sessions.session_id_v7 AS session_id_v7 FROM raw_sessions - WHERE and(equals(raw_sessions.team_id, 99999), ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2019-02-17 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC'))), 0)) + WHERE and(equals(raw_sessions.team_id, 99999), ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2019-02-17 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC'))), 0)) GROUP BY raw_sessions.session_id_v7, raw_sessions.session_id_v7) AS events__session ON equals(toUInt128(accurateCastOrNull(events.`$session_id`, 'UUID')), events__session.session_id_v7) LEFT OUTER JOIN @@ -2809,7 +2809,7 @@ WHERE equals(person_distinct_id_overrides.team_id, 99999) 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 events__override ON equals(events.distinct_id, events__override.distinct_id) - WHERE and(equals(events.team_id, 99999), and(or(and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2019-02-17 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))), 0), or(equals(events.event, '$pageview'), equals(events.event, '$screen')), 1, isNotNull(breakdown_value))) + WHERE and(equals(events.team_id, 99999), and(or(and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2019-02-17 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))), 0), or(equals(events.event, '$pageview'), equals(events.event, '$screen')), 1, isNotNull(breakdown_value))) GROUP BY session_id, breakdown_value) GROUP BY `context.columns.breakdown_value` @@ -2845,7 +2845,7 @@ min(toTimeZone(raw_sessions.min_timestamp, 'UTC')) AS `$start_timestamp`, raw_sessions.session_id_v7 AS session_id_v7 FROM raw_sessions - WHERE and(equals(raw_sessions.team_id, 99999), ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-07-15 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC'))), 0)) + WHERE and(equals(raw_sessions.team_id, 99999), ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-07-15 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC'))), 0)) GROUP BY raw_sessions.session_id_v7, raw_sessions.session_id_v7) AS events__session ON equals(toUInt128(accurateCastOrNull(events.`$session_id`, 'UUID')), events__session.session_id_v7) LEFT OUTER JOIN @@ -2855,7 +2855,7 @@ WHERE equals(person_distinct_id_overrides.team_id, 99999) 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 events__override ON equals(events.distinct_id, events__override.distinct_id) - WHERE and(equals(events.team_id, 99999), and(or(and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-07-15 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))), 0), or(equals(events.event, '$pageview'), equals(events.event, '$screen')), 1, isNotNull(breakdown_value))) + WHERE and(equals(events.team_id, 99999), and(or(and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-07-15 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))), 0), or(equals(events.event, '$pageview'), equals(events.event, '$screen')), 1, isNotNull(breakdown_value))) GROUP BY session_id, breakdown_value) GROUP BY `context.columns.breakdown_value` @@ -2902,7 +2902,7 @@ min(toTimeZone(raw_sessions.min_timestamp, 'UTC')) AS `$start_timestamp`, raw_sessions.session_id_v7 AS session_id_v7 FROM raw_sessions - WHERE and(equals(raw_sessions.team_id, 99999), ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2019-02-17 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC'))), 0)) + WHERE and(equals(raw_sessions.team_id, 99999), ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2019-02-17 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC'))), 0)) GROUP BY raw_sessions.session_id_v7, raw_sessions.session_id_v7) AS events__session ON equals(toUInt128(accurateCastOrNull(events.`$session_id`, 'UUID')), events__session.session_id_v7) LEFT OUTER JOIN @@ -2912,7 +2912,7 @@ WHERE equals(person_distinct_id_overrides.team_id, 99999) 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 events__override ON equals(events.distinct_id, events__override.distinct_id) - WHERE and(equals(events.team_id, 99999), and(or(and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2019-02-17 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))), 0), or(equals(events.event, '$pageview'), equals(events.event, '$screen')), 1, isNotNull(breakdown_value))) + WHERE and(equals(events.team_id, 99999), and(or(and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2019-02-17 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))), 0), or(equals(events.event, '$pageview'), equals(events.event, '$screen')), 1, isNotNull(breakdown_value))) GROUP BY session_id, breakdown_value) GROUP BY `context.columns.breakdown_value` @@ -2959,7 +2959,7 @@ min(toTimeZone(raw_sessions.min_timestamp, 'UTC')) AS `$start_timestamp`, raw_sessions.session_id_v7 AS session_id_v7 FROM raw_sessions - WHERE and(equals(raw_sessions.team_id, 99999), ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-07-30 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC'))), 0)) + WHERE and(equals(raw_sessions.team_id, 99999), ifNull(greaterOrEquals(plus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2024-07-30 00:00:00', 6, 'UTC'))), 0), ifNull(lessOrEquals(minus(fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(raw_sessions.session_id_v7, 80)), 1000)), toIntervalDay(3)), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC'))), 0)) GROUP BY raw_sessions.session_id_v7, raw_sessions.session_id_v7) AS events__session ON equals(toUInt128(accurateCastOrNull(events.`$session_id`, 'UUID')), events__session.session_id_v7) LEFT OUTER JOIN @@ -2969,7 +2969,7 @@ WHERE equals(person_distinct_id_overrides.team_id, 99999) 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 events__override ON equals(events.distinct_id, events__override.distinct_id) - WHERE and(equals(events.team_id, 99999), and(or(and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-07-30 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-29 23:59:59', 6, 'UTC')))), 0), or(equals(events.event, '$pageview'), equals(events.event, '$screen')), 1, isNotNull(breakdown_value))) + WHERE and(equals(events.team_id, 99999), and(or(and(greaterOrEquals(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2024-07-30 00:00:00', 6, 'UTC'))), less(toTimeZone(events.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2025-01-30 23:59:59', 6, 'UTC')))), 0), or(equals(events.event, '$pageview'), equals(events.event, '$screen')), 1, isNotNull(breakdown_value))) GROUP BY session_id, breakdown_value) GROUP BY `context.columns.breakdown_value` From 25a6a10864c796f03453536c38cb026990018a25 Mon Sep 17 00:00:00 2001 From: Ben White Date: Thu, 30 Jan 2025 10:07:01 +0100 Subject: [PATCH 113/163] Fix --- posthog/api/hog_function_template.py | 1 - 1 file changed, 1 deletion(-) diff --git a/posthog/api/hog_function_template.py b/posthog/api/hog_function_template.py index 72b5d8f98e683..5015b5731dc86 100644 --- a/posthog/api/hog_function_template.py +++ b/posthog/api/hog_function_template.py @@ -83,7 +83,6 @@ def _load_templates(cls): raise Exception("Failed to fetch hog function templates from the node service") nodejs_templates_json = response.json() - nodejs_templates: list[HogFunctionTemplate] = [] for template_data in nodejs_templates_json: try: serializer = HogFunctionTemplateSerializer(data=template_data) From e81fc9389c72d4872d93f675f2216ccf2c68798e Mon Sep 17 00:00:00 2001 From: Ben White Date: Thu, 30 Jan 2025 10:09:22 +0100 Subject: [PATCH 114/163] Fixe --- posthog/cdp/templates/hog_function_template.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/posthog/cdp/templates/hog_function_template.py b/posthog/cdp/templates/hog_function_template.py index 7d69a4474aa3e..132b36fa3abb0 100644 --- a/posthog/cdp/templates/hog_function_template.py +++ b/posthog/cdp/templates/hog_function_template.py @@ -82,6 +82,11 @@ def migrate(cls, obj: PluginConfig) -> dict: def derive_sub_templates(templates: list[HogFunctionTemplate]) -> list[HogFunctionTemplate]: + """ + Given a list of templates, derive the sub templates from them. + Sub templates just override certain params of the parent template. + This allows the API to filter for templates based on a SubTemplateId such as ones designed for surveys. + """ sub_templates = [] for template in templates: for sub_template in template.sub_templates or []: From 7fdc6f0ff303c62ff4a67bb4d65d67c36f06c2e3 Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 30 Jan 2025 10:08:14 +0000 Subject: [PATCH 115/163] Update UI snapshots for `chromium` (1) --- ...-pipeline-node-new-hog-function--light.png | Bin 131245 -> 133981 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/frontend/__snapshots__/scenes-app-pipeline--pipeline-node-new-hog-function--light.png b/frontend/__snapshots__/scenes-app-pipeline--pipeline-node-new-hog-function--light.png index 770be28c1a0e5bce7ad96f0334461d95938c4846..1eaddf28cc8a7986b098c7ab8e43bc60a5e95757 100644 GIT binary patch delta 93739 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 >+ao((kO~I(a;lY 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 From 58f8932eaad2db6a4eababce414ffed9d307afb2 Mon Sep 17 00:00:00 2001 From: Ben White Date: Thu, 30 Jan 2025 11:15:59 +0100 Subject: [PATCH 116/163] Fixes --- plugin-server/src/cdp/cdp-api.test.ts | 582 ++++++++++++++++++++++++ plugin-server/src/cdp/cdp-api.ts | 19 +- plugin-server/tests/cdp/cdp-api.test.ts | 515 --------------------- 3 files changed, 595 insertions(+), 521 deletions(-) create mode 100644 plugin-server/src/cdp/cdp-api.test.ts delete mode 100644 plugin-server/tests/cdp/cdp-api.test.ts diff --git a/plugin-server/src/cdp/cdp-api.test.ts b/plugin-server/src/cdp/cdp-api.test.ts new file mode 100644 index 0000000000000..7c10879e6ea62 --- /dev/null +++ b/plugin-server/src/cdp/cdp-api.test.ts @@ -0,0 +1,582 @@ +import '../../tests/helpers/mocks/producer.mock' + +import express from 'express' +import supertest from 'supertest' + +import { HOG_EXAMPLES, HOG_FILTERS_EXAMPLES, HOG_INPUTS_EXAMPLES } from '../../tests/cdp/examples' +import { createHogFunction, insertHogFunction as _insertHogFunction } from '../../tests/cdp/fixtures' +import { getFirstTeam, resetTestDatabase } from '../../tests/helpers/sql' +import { Hub, Team } from '../types' +import { closeHub, createHub } from '../utils/db/hub' +import { CdpApi } from './cdp-api' +import { template as filterOutPluginTemplate } from './legacy-plugins/_transformations/posthog-filter-out-plugin/template' +import { HogFunctionInvocationGlobals, HogFunctionType } from './types' + +const mockConsumer = { + on: jest.fn(), + commitSync: jest.fn(), + commit: jest.fn(), + queryWatermarkOffsets: jest.fn(), + committed: jest.fn(), + assignments: jest.fn(), + isConnected: jest.fn(() => true), + getMetadata: jest.fn(), +} + +jest.mock('../../src/kafka/batch-consumer', () => { + return { + startBatchConsumer: jest.fn(() => + Promise.resolve({ + join: () => ({ + finally: jest.fn(), + }), + stop: jest.fn(), + consumer: mockConsumer, + }) + ), + } +}) + +jest.mock('../../src/utils/fetch', () => { + return { + trackedFetch: jest.fn(() => + Promise.resolve({ + status: 200, + text: () => Promise.resolve(JSON.stringify({ success: true })), + json: () => Promise.resolve({ success: true }), + }) + ), + } +}) + +const mockFetch: jest.Mock = require('../../src/utils/fetch').trackedFetch + +jest.setTimeout(1000) + +describe('CDP API', () => { + let hub: Hub + let team: Team + let app: express.Express + let api: CdpApi + let hogFunction: HogFunctionType + + const globals: Partial = { + groups: {}, + person: { + id: '123', + name: 'Jane Doe', + url: 'https://example.com/person/123', + properties: { + email: 'example@posthog.com', + }, + }, + event: { + uuid: 'b3a1fe86-b10c-43cc-acaf-d208977608d0', + event: '$pageview', + elements_chain: '', + distinct_id: '123', + timestamp: '2021-09-28T14:00:00Z', + url: 'https://example.com/events/b3a1fe86-b10c-43cc-acaf-d208977608d0/2021-09-28T14:00:00Z', + properties: { + $lib_version: '1.0.0', + }, + }, + } + + const insertHogFunction = async (hogFunction: Partial) => { + const item = await _insertHogFunction(hub.postgres, team.id, hogFunction) + // Trigger the reload that django would do + await api['hogFunctionManager'].reloadAllHogFunctions() + return item + } + + beforeEach(async () => { + await resetTestDatabase() + hub = await createHub() + hub.CDP_GOOGLE_ADWORDS_DEVELOPER_TOKEN = 'ADWORDS_TOKEN' + team = await getFirstTeam(hub) + + api = new CdpApi(hub) + await api.start() + app = express() + app.use(express.json()) + app.use('/', api.router()) + + mockFetch.mockClear() + + hogFunction = await insertHogFunction({ + ...HOG_EXAMPLES.simple_fetch, + ...HOG_INPUTS_EXAMPLES.simple_fetch, + ...HOG_FILTERS_EXAMPLES.no_filters, + }) + }) + + afterEach(async () => { + jest.setTimeout(10000) + await api.stop() + await closeHub(hub) + }) + + afterAll(() => { + jest.useRealTimers() + }) + + it('errors if missing hog function or team', async () => { + const res = await supertest(app) + .post(`/api/projects/${hogFunction.team_id}/hog_functions/missing/invocations`) + .send({ globals }) + + expect(res.status).toEqual(404) + }) + + it('errors if missing values', async () => { + const res = await supertest(app) + .post(`/api/projects/${hogFunction.team_id}/hog_functions/${hogFunction.id}/invocations`) + .send({}) + + expect(res.status).toEqual(400) + expect(res.body).toEqual({ + error: 'Missing event', + }) + }) + + it("does not error if hog function is 'new'", async () => { + const res = await supertest(app) + .post(`/api/projects/${hogFunction.team_id}/hog_functions/new/invocations`) + .send({ globals }) + + expect(res.status).toEqual(200) + }) + + it('can invoke a function via the API with mocks', async () => { + const res = await supertest(app) + .post(`/api/projects/${hogFunction.team_id}/hog_functions/${hogFunction.id}/invocations`) + .send({ globals, mock_async_functions: true }) + + expect(res.status).toEqual(200) + + expect(res.body).toMatchObject({ + errors: [], + logs: [ + { + level: 'debug', + message: 'Executing function', + }, + { + level: 'debug', + message: + "Suspending function due to async function call 'fetch'. Payload: 2110 bytes. Event: b3a1fe86-b10c-43cc-acaf-d208977608d0", + }, + { + level: 'info', + message: "Async function 'fetch' was mocked with arguments:", + }, + { + level: 'info', + message: expect.stringContaining('fetch({'), + }, + { + level: 'debug', + message: 'Resuming function', + }, + { + level: 'info', + message: 'Fetch response:, {"status":200,"body":{}}', + }, + { + level: 'debug', + message: expect.stringContaining('Function completed in '), + }, + ], + }) + }) + + it('can invoke a function via the API with real fetch', async () => { + mockFetch.mockImplementationOnce(() => + Promise.resolve({ + status: 201, + text: () => Promise.resolve(JSON.stringify({ real: true })), + headers: new Headers({ 'Content-Type': 'application/json' }), + }) + ) + const res = await supertest(app) + .post(`/api/projects/${hogFunction.team_id}/hog_functions/${hogFunction.id}/invocations`) + .send({ globals, mock_async_functions: false }) + + expect(res.status).toEqual(200) + expect(res.body).toMatchObject({ + errors: [], + logs: [ + { + level: 'debug', + message: 'Executing function', + }, + { + level: 'debug', + message: + "Suspending function due to async function call 'fetch'. Payload: 2110 bytes. Event: b3a1fe86-b10c-43cc-acaf-d208977608d0", + }, + { + level: 'debug', + message: 'Resuming function', + }, + { + level: 'info', + message: 'Fetch response:, {"status":201,"body":{"real":true}}', + }, + { + level: 'debug', + message: expect.stringContaining('Function completed in'), + }, + ], + }) + }) + + it('includes enriched values in the request', async () => { + mockFetch.mockImplementationOnce(() => { + return Promise.resolve({ + status: 201, + text: () => Promise.resolve(JSON.stringify({ real: true })), + headers: new Headers({ 'Content-Type': 'application/json' }), + }) + }) + + hogFunction = await insertHogFunction({ + ...HOG_EXAMPLES.simple_fetch, + ...HOG_INPUTS_EXAMPLES.simple_google_fetch, + ...HOG_FILTERS_EXAMPLES.no_filters, + }) + + const res = await supertest(app) + .post(`/api/projects/${hogFunction.team_id}/hog_functions/${hogFunction.id}/invocations`) + .send({ globals, mock_async_functions: false }) + + expect(mockFetch).toHaveBeenCalledWith( + 'https://googleads.googleapis.com/', + expect.objectContaining({ + headers: expect.objectContaining({ + 'developer-token': 'ADWORDS_TOKEN', + }), + }) + ) + + expect(res.status).toEqual(200) + expect(res.body).toMatchObject({ + logs: [ + { + level: 'debug', + message: 'Executing function', + }, + { + level: 'debug', + message: + "Suspending function due to async function call 'fetch'. Payload: 2108 bytes. Event: b3a1fe86-b10c-43cc-acaf-d208977608d0", + }, + { + level: 'debug', + message: 'Resuming function', + }, + { + level: 'info', + message: 'Fetch response:, {"status":201,"body":{"real":true}}', + }, + { + level: 'debug', + message: expect.stringContaining('Function completed in'), + }, + ], + }) + }) + + it('doesnt include enriched values in the mock response', async () => { + hogFunction = await insertHogFunction({ + ...HOG_EXAMPLES.simple_fetch, + ...HOG_INPUTS_EXAMPLES.simple_google_fetch, + ...HOG_FILTERS_EXAMPLES.no_filters, + }) + + const res = await supertest(app) + .post(`/api/projects/${hogFunction.team_id}/hog_functions/${hogFunction.id}/invocations`) + .send({ globals, mock_async_functions: true }) + + expect(res.status).toEqual(200) + expect(res.body).toMatchObject({ + logs: [ + { + level: 'debug', + message: 'Executing function', + }, + { + level: 'debug', + message: + "Suspending function due to async function call 'fetch'. Payload: 2108 bytes. Event: b3a1fe86-b10c-43cc-acaf-d208977608d0", + }, + { + level: 'info', + message: "Async function 'fetch' was mocked with arguments:", + }, + { + level: 'info', + message: expect.not.stringContaining('developer-token'), + }, + { + level: 'debug', + message: 'Resuming function', + }, + { + level: 'info', + message: 'Fetch response:, {"status":200,"body":{}}', + }, + { + level: 'debug', + message: expect.stringContaining('Function completed in '), + }, + ], + }) + }) + + it('handles mappings', async () => { + const hogFunction = await insertHogFunction({ + ...HOG_EXAMPLES.simple_fetch, + ...HOG_INPUTS_EXAMPLES.simple_fetch, + ...HOG_FILTERS_EXAMPLES.no_filters, + mappings: [ + { + // Filters for pageview or autocapture + ...HOG_FILTERS_EXAMPLES.pageview_or_autocapture_filter, + }, + { + // No filters so should match all events + ...HOG_FILTERS_EXAMPLES.no_filters, + }, + { + // Broken filters so shouldn't match + ...HOG_FILTERS_EXAMPLES.broken_filters, + }, + ], + }) + + const res = await supertest(app) + .post(`/api/projects/${hogFunction.team_id}/hog_functions/${hogFunction.id}/invocations`) + .send({ globals, mock_async_functions: true }) + + expect(res.status).toEqual(200) + + const minimalLogs = res.body.logs.map((log) => ({ + level: log.level, + message: log.message, + })) + + expect(minimalLogs).toMatchObject([ + { level: 'info', message: 'Mapping trigger not matching filters was ignored.' }, + { + level: 'error', + message: + 'Error filtering event b3a1fe86-b10c-43cc-acaf-d208977608d0: Invalid HogQL bytecode, stack is empty, can not pop', + }, + { level: 'debug', message: 'Executing function' }, + { + level: 'debug', + message: + "Suspending function due to async function call 'fetch'. Payload: 2110 bytes. Event: b3a1fe86-b10c-43cc-acaf-d208977608d0", + }, + { + level: 'info', + message: "Async function 'fetch' was mocked with arguments:", + }, + { + level: 'info', + message: expect.stringContaining('fetch({'), + }, + { level: 'debug', message: 'Resuming function' }, + { + level: 'info', + message: 'Fetch response:, {"status":200,"body":{}}', + }, + { + level: 'debug', + message: expect.stringContaining('Function completed in '), + }, + ]) + }) + + it('doesnt include enriched values in the mock response', async () => { + hogFunction = await insertHogFunction({ + ...HOG_EXAMPLES.simple_fetch, + ...HOG_INPUTS_EXAMPLES.simple_google_fetch, + ...HOG_FILTERS_EXAMPLES.no_filters, + }) + + const res = await supertest(app) + .post(`/api/projects/${hogFunction.team_id}/hog_functions/${hogFunction.id}/invocations`) + .send({ globals, mock_async_functions: true }) + + expect(res.status).toEqual(200) + expect(res.body).toMatchObject({ + logs: [ + { + level: 'debug', + message: 'Executing function', + }, + { + level: 'debug', + message: + "Suspending function due to async function call 'fetch'. Payload: 2108 bytes. Event: b3a1fe86-b10c-43cc-acaf-d208977608d0", + }, + { + level: 'info', + message: "Async function 'fetch' was mocked with arguments:", + }, + { + level: 'info', + message: expect.not.stringContaining('developer-token'), + }, + { + level: 'debug', + message: 'Resuming function', + }, + { + level: 'info', + message: 'Fetch response:, {"status":200,"body":{}}', + }, + { + level: 'debug', + message: expect.stringContaining('Function completed in '), + }, + ], + }) + }) + + it('handles mappings', async () => { + const hogFunction = await insertHogFunction({ + ...HOG_EXAMPLES.simple_fetch, + ...HOG_INPUTS_EXAMPLES.simple_fetch, + ...HOG_FILTERS_EXAMPLES.no_filters, + mappings: [ + { + // Filters for pageview or autocapture + ...HOG_FILTERS_EXAMPLES.pageview_or_autocapture_filter, + }, + { + // No filters so should match all events + ...HOG_FILTERS_EXAMPLES.no_filters, + }, + { + // Broken filters so shouldn't match + ...HOG_FILTERS_EXAMPLES.broken_filters, + }, + ], + }) + + const res = await supertest(app) + .post(`/api/projects/${hogFunction.team_id}/hog_functions/${hogFunction.id}/invocations`) + .send({ globals, mock_async_functions: true }) + + expect(res.status).toEqual(200) + + const minimalLogs = res.body.logs.map((log) => ({ + level: log.level, + message: log.message, + })) + + expect(minimalLogs).toMatchObject([ + { level: 'info', message: 'Mapping trigger not matching filters was ignored.' }, + { + level: 'error', + message: + 'Error filtering event b3a1fe86-b10c-43cc-acaf-d208977608d0: Invalid HogQL bytecode, stack is empty, can not pop', + }, + { level: 'debug', message: 'Executing function' }, + { + level: 'debug', + message: + "Suspending function due to async function call 'fetch'. Payload: 2110 bytes. Event: b3a1fe86-b10c-43cc-acaf-d208977608d0", + }, + { + level: 'info', + message: "Async function 'fetch' was mocked with arguments:", + }, + { + level: 'info', + message: expect.stringContaining('fetch({'), + }, + { level: 'debug', message: 'Resuming function' }, + { + level: 'info', + message: 'Fetch response:, {"status":200,"body":{}}', + }, + { + level: 'debug', + message: expect.stringContaining('Function completed in '), + }, + ]) + }) + + describe('transformations', () => { + let configuration: HogFunctionType + + beforeEach(() => { + configuration = createHogFunction({ + type: 'transformation', + name: filterOutPluginTemplate.name, + template_id: 'plugin-posthog-filter-out-plugin', + inputs: { + eventsToDrop: { + value: 'drop me', + }, + }, + team_id: team.id, + enabled: true, + hog: filterOutPluginTemplate.hog, + inputs_schema: filterOutPluginTemplate.inputs_schema, + }) + }) + + it('processes transformations and returns the result if not null', async () => { + const res = await supertest(app) + .post(`/api/projects/${hogFunction.team_id}/hog_functions/new/invocations`) + .send({ globals, mock_async_functions: true, configuration }) + + expect(res.status).toEqual(200) + + expect(res.body.logs.map((log) => log.message)).toMatchInlineSnapshot(` + [ + "Executing plugin posthog-filter-out-plugin", + "Execution successful", + ] + `) + + expect(res.body.result).toMatchInlineSnapshot(` + { + "distinct_id": "123", + "event": "$pageview", + "properties": { + "$lib_version": "1.0.0", + }, + "team_id": 2, + "timestamp": "2021-09-28T14:00:00Z", + "uuid": "b3a1fe86-b10c-43cc-acaf-d208977608d0", + } + `) + }) + + it('processes transformations and returns the result if null', async () => { + globals.event!.event = 'drop me' + + const res = await supertest(app) + .post(`/api/projects/${hogFunction.team_id}/hog_functions/new/invocations`) + .send({ globals, mock_async_functions: true, configuration }) + + expect(res.status).toEqual(200) + + expect(res.body.logs.map((log) => log.message)).toMatchInlineSnapshot(` + [ + "Executing plugin posthog-filter-out-plugin", + "Execution successful", + ] + `) + + expect(res.body.result).toMatchInlineSnapshot(`null`) + }) + }) +}) diff --git a/plugin-server/src/cdp/cdp-api.ts b/plugin-server/src/cdp/cdp-api.ts index 825a19794eae8..278afecb379b7 100644 --- a/plugin-server/src/cdp/cdp-api.ts +++ b/plugin-server/src/cdp/cdp-api.ts @@ -103,7 +103,7 @@ export class CdpApi { res.json(await this.hogWatcher.getState(id)) } - private postFunctionInvocation = async (req: express.Request, res: express.Response): Promise => { + private postFunctionInvocation = async (req: express.Request, res: express.Response): Promise => { try { const { id, team_id } = req.params const { globals, mock_async_functions, configuration } = req.body @@ -115,14 +115,21 @@ export class CdpApi { return } - const hogFunction = await this.hogFunctionManager.fetchHogFunction(req.params.id).catch(() => null) + const isNewFunction = req.params.id === 'new' + + const hogFunction = isNewFunction + ? null + : await this.hogFunctionManager.fetchHogFunction(req.params.id).catch(() => null) const team = await this.hub.teamManager.fetchTeam(parseInt(team_id)).catch(() => null) + if (!team) { + return res.status(404).json({ error: 'Team not found' }) + } + // NOTE: We allow the hog function to be null if it is a "new" hog function // The real security happens at the django layer so this is more of a sanity check - if (!team || (hogFunction && hogFunction.team_id !== team.id)) { - res.status(404).json({ error: 'Hog function not found' }) - return + if (!isNewFunction && (!hogFunction || hogFunction.team_id !== team.id)) { + return res.status(404).json({ error: 'Hog function not found' }) } // We use the provided config if given, otherwise the function's config @@ -232,7 +239,7 @@ export class CdpApi { compoundConfiguration.id = new UUIDT().toString() const response = await this.hogTransformer.executeHogFunction(compoundConfiguration, triggerGlobals) logs = logs.concat(response.logs) - result = response.execResult + result = response.execResult ?? null if (response.error) { errors.push(response.error) diff --git a/plugin-server/tests/cdp/cdp-api.test.ts b/plugin-server/tests/cdp/cdp-api.test.ts deleted file mode 100644 index 7fa020cb6aba1..0000000000000 --- a/plugin-server/tests/cdp/cdp-api.test.ts +++ /dev/null @@ -1,515 +0,0 @@ -import '../helpers/mocks/producer.mock' - -import express from 'express' -import supertest from 'supertest' - -import { CdpApi } from '../../src/cdp/cdp-api' -import { CdpInternalEventsConsumer } from '../../src/cdp/consumers/cdp-internal-event.consumer' -import { HogFunctionInvocationGlobals, HogFunctionType } from '../../src/cdp/types' -import { Hub, Team } from '../../src/types' -import { closeHub, createHub } from '../../src/utils/db/hub' -import { getFirstTeam, resetTestDatabase } from '../helpers/sql' -import { HOG_EXAMPLES, HOG_FILTERS_EXAMPLES, HOG_INPUTS_EXAMPLES } from './examples' -import { insertHogFunction as _insertHogFunction } from './fixtures' - -const mockConsumer = { - on: jest.fn(), - commitSync: jest.fn(), - commit: jest.fn(), - queryWatermarkOffsets: jest.fn(), - committed: jest.fn(), - assignments: jest.fn(), - isConnected: jest.fn(() => true), - getMetadata: jest.fn(), -} - -jest.mock('../../src/kafka/batch-consumer', () => { - return { - startBatchConsumer: jest.fn(() => - Promise.resolve({ - join: () => ({ - finally: jest.fn(), - }), - stop: jest.fn(), - consumer: mockConsumer, - }) - ), - } -}) - -jest.mock('../../src/utils/fetch', () => { - return { - trackedFetch: jest.fn(() => - Promise.resolve({ - status: 200, - text: () => Promise.resolve(JSON.stringify({ success: true })), - json: () => Promise.resolve({ success: true }), - }) - ), - } -}) - -const mockFetch: jest.Mock = require('../../src/utils/fetch').trackedFetch - -jest.setTimeout(1000) - -describe('CDP API', () => { - let processor: CdpInternalEventsConsumer - let hub: Hub - let team: Team - - const insertHogFunction = async (hogFunction: Partial) => { - const item = await _insertHogFunction(hub.postgres, team.id, hogFunction) - // Trigger the reload that django would do - await processor.hogFunctionManager.reloadAllHogFunctions() - return item - } - - beforeEach(async () => { - await resetTestDatabase() - hub = await createHub() - team = await getFirstTeam(hub) - - hub.CDP_GOOGLE_ADWORDS_DEVELOPER_TOKEN = 'ADWORDS_TOKEN' - - processor = new CdpInternalEventsConsumer(hub) - - await processor.start() - - mockFetch.mockClear() - }) - - afterEach(async () => { - jest.setTimeout(10000) - await processor.stop() - await closeHub(hub) - }) - - afterAll(() => { - jest.useRealTimers() - }) - - describe('API invocation', () => { - let app: express.Express - let hogFunction: HogFunctionType - - const globals: Partial = { - groups: {}, - person: { - id: '123', - name: 'Jane Doe', - url: 'https://example.com/person/123', - properties: { - email: 'example@posthog.com', - }, - }, - event: { - uuid: 'b3a1fe86-b10c-43cc-acaf-d208977608d0', - event: '$pageview', - elements_chain: '', - distinct_id: '123', - timestamp: '2021-09-28T14:00:00Z', - url: 'https://example.com/events/b3a1fe86-b10c-43cc-acaf-d208977608d0/2021-09-28T14:00:00Z', - properties: { - $lib_version: '1.0.0', - }, - }, - } - - beforeEach(async () => { - app = express() - app.use(express.json()) - const api = new CdpApi(hub, processor) - app.use('/', api.router()) - - hogFunction = await insertHogFunction({ - ...HOG_EXAMPLES.simple_fetch, - ...HOG_INPUTS_EXAMPLES.simple_fetch, - ...HOG_FILTERS_EXAMPLES.no_filters, - }) - }) - - it('errors if missing hog function or team', async () => { - const res = await supertest(app) - .post(`/api/projects/${hogFunction.team_id}/hog_functions/missing/invocations`) - .send({ globals }) - - expect(res.status).toEqual(404) - }) - - it('errors if missing values', async () => { - const res = await supertest(app) - .post(`/api/projects/${hogFunction.team_id}/hog_functions/${hogFunction.id}/invocations`) - .send({}) - - expect(res.status).toEqual(400) - expect(res.body).toEqual({ - error: 'Missing event', - }) - }) - - it('can invoke a function via the API with mocks', async () => { - const res = await supertest(app) - .post(`/api/projects/${hogFunction.team_id}/hog_functions/${hogFunction.id}/invocations`) - .send({ globals, mock_async_functions: true }) - - expect(res.status).toEqual(200) - console.log(res.body.logs[3].message) - expect(res.body).toMatchObject({ - errors: [], - logs: [ - { - level: 'debug', - message: 'Executing function', - }, - { - level: 'debug', - message: - "Suspending function due to async function call 'fetch'. Payload: 2110 bytes. Event: b3a1fe86-b10c-43cc-acaf-d208977608d0", - }, - { - level: 'info', - message: "Async function 'fetch' was mocked with arguments:", - }, - { - level: 'info', - message: expect.stringContaining('fetch({'), - }, - { - level: 'debug', - message: 'Resuming function', - }, - { - level: 'info', - message: 'Fetch response:, {"status":200,"body":{}}', - }, - { - level: 'debug', - message: expect.stringContaining('Function completed in '), - }, - ], - }) - }) - - it('can invoke a function via the API with real fetch', async () => { - mockFetch.mockImplementationOnce(() => - Promise.resolve({ - status: 201, - text: () => Promise.resolve(JSON.stringify({ real: true })), - headers: new Headers({ 'Content-Type': 'application/json' }), - }) - ) - const res = await supertest(app) - .post(`/api/projects/${hogFunction.team_id}/hog_functions/${hogFunction.id}/invocations`) - .send({ globals, mock_async_functions: false }) - - expect(res.status).toEqual(200) - expect(res.body).toMatchObject({ - errors: [], - logs: [ - { - level: 'debug', - message: 'Executing function', - }, - { - level: 'debug', - message: - "Suspending function due to async function call 'fetch'. Payload: 2110 bytes. Event: b3a1fe86-b10c-43cc-acaf-d208977608d0", - }, - { - level: 'debug', - message: 'Resuming function', - }, - { - level: 'info', - message: 'Fetch response:, {"status":201,"body":{"real":true}}', - }, - { - level: 'debug', - message: expect.stringContaining('Function completed in'), - }, - ], - }) - }) - - it('includes enriched values in the request', async () => { - mockFetch.mockImplementationOnce(() => { - return Promise.resolve({ - status: 201, - text: () => Promise.resolve(JSON.stringify({ real: true })), - headers: new Headers({ 'Content-Type': 'application/json' }), - }) - }) - - hogFunction = await insertHogFunction({ - ...HOG_EXAMPLES.simple_fetch, - ...HOG_INPUTS_EXAMPLES.simple_google_fetch, - ...HOG_FILTERS_EXAMPLES.no_filters, - }) - - const res = await supertest(app) - .post(`/api/projects/${hogFunction.team_id}/hog_functions/${hogFunction.id}/invocations`) - .send({ globals, mock_async_functions: false }) - - expect(mockFetch).toHaveBeenCalledWith( - 'https://googleads.googleapis.com/', - expect.objectContaining({ - headers: expect.objectContaining({ - 'developer-token': 'ADWORDS_TOKEN', - }), - }) - ) - - expect(res.status).toEqual(200) - expect(res.body).toMatchObject({ - logs: [ - { - level: 'debug', - message: 'Executing function', - }, - { - level: 'debug', - message: - "Suspending function due to async function call 'fetch'. Payload: 2108 bytes. Event: b3a1fe86-b10c-43cc-acaf-d208977608d0", - }, - { - level: 'debug', - message: 'Resuming function', - }, - { - level: 'info', - message: 'Fetch response:, {"status":201,"body":{"real":true}}', - }, - { - level: 'debug', - message: expect.stringContaining('Function completed in'), - }, - ], - }) - }) - - it('doesnt include enriched values in the mock response', async () => { - hogFunction = await insertHogFunction({ - ...HOG_EXAMPLES.simple_fetch, - ...HOG_INPUTS_EXAMPLES.simple_google_fetch, - ...HOG_FILTERS_EXAMPLES.no_filters, - }) - - const res = await supertest(app) - .post(`/api/projects/${hogFunction.team_id}/hog_functions/${hogFunction.id}/invocations`) - .send({ globals, mock_async_functions: true }) - - expect(res.status).toEqual(200) - expect(res.body).toMatchObject({ - logs: [ - { - level: 'debug', - message: 'Executing function', - }, - { - level: 'debug', - message: - "Suspending function due to async function call 'fetch'. Payload: 2108 bytes. Event: b3a1fe86-b10c-43cc-acaf-d208977608d0", - }, - { - level: 'info', - message: "Async function 'fetch' was mocked with arguments:", - }, - { - level: 'info', - message: expect.not.stringContaining('developer-token'), - }, - { - level: 'debug', - message: 'Resuming function', - }, - { - level: 'info', - message: 'Fetch response:, {"status":200,"body":{}}', - }, - { - level: 'debug', - message: expect.stringContaining('Function completed in '), - }, - ], - }) - }) - - it('handles mappings', async () => { - const hogFunction = await insertHogFunction({ - ...HOG_EXAMPLES.simple_fetch, - ...HOG_INPUTS_EXAMPLES.simple_fetch, - ...HOG_FILTERS_EXAMPLES.no_filters, - mappings: [ - { - // Filters for pageview or autocapture - ...HOG_FILTERS_EXAMPLES.pageview_or_autocapture_filter, - }, - { - // No filters so should match all events - ...HOG_FILTERS_EXAMPLES.no_filters, - }, - { - // Broken filters so shouldn't match - ...HOG_FILTERS_EXAMPLES.broken_filters, - }, - ], - }) - - const res = await supertest(app) - .post(`/api/projects/${hogFunction.team_id}/hog_functions/${hogFunction.id}/invocations`) - .send({ globals, mock_async_functions: true }) - - expect(res.status).toEqual(200) - - const minimalLogs = res.body.logs.map((log) => ({ - level: log.level, - message: log.message, - })) - - expect(minimalLogs).toMatchObject([ - { level: 'info', message: 'Mapping trigger not matching filters was ignored.' }, - { - level: 'error', - message: - 'Error filtering event b3a1fe86-b10c-43cc-acaf-d208977608d0: Invalid HogQL bytecode, stack is empty, can not pop', - }, - { level: 'debug', message: 'Executing function' }, - { - level: 'debug', - message: - "Suspending function due to async function call 'fetch'. Payload: 2110 bytes. Event: b3a1fe86-b10c-43cc-acaf-d208977608d0", - }, - { - level: 'info', - message: "Async function 'fetch' was mocked with arguments:", - }, - { - level: 'info', - message: expect.stringContaining('fetch({'), - }, - { level: 'debug', message: 'Resuming function' }, - { - level: 'info', - message: 'Fetch response:, {"status":200,"body":{}}', - }, - { - level: 'debug', - message: expect.stringContaining('Function completed in '), - }, - ]) - }) - - it('doesnt include enriched values in the mock response', async () => { - hogFunction = await insertHogFunction({ - ...HOG_EXAMPLES.simple_fetch, - ...HOG_INPUTS_EXAMPLES.simple_google_fetch, - ...HOG_FILTERS_EXAMPLES.no_filters, - }) - - const res = await supertest(app) - .post(`/api/projects/${hogFunction.team_id}/hog_functions/${hogFunction.id}/invocations`) - .send({ globals, mock_async_functions: true }) - - expect(res.status).toEqual(200) - expect(res.body).toMatchObject({ - logs: [ - { - level: 'debug', - message: 'Executing function', - }, - { - level: 'debug', - message: - "Suspending function due to async function call 'fetch'. Payload: 2108 bytes. Event: b3a1fe86-b10c-43cc-acaf-d208977608d0", - }, - { - level: 'info', - message: "Async function 'fetch' was mocked with arguments:", - }, - { - level: 'info', - message: expect.not.stringContaining('developer-token'), - }, - { - level: 'debug', - message: 'Resuming function', - }, - { - level: 'info', - message: 'Fetch response:, {"status":200,"body":{}}', - }, - { - level: 'debug', - message: expect.stringContaining('Function completed in '), - }, - ], - }) - }) - - it('handles mappings', async () => { - const hogFunction = await insertHogFunction({ - ...HOG_EXAMPLES.simple_fetch, - ...HOG_INPUTS_EXAMPLES.simple_fetch, - ...HOG_FILTERS_EXAMPLES.no_filters, - mappings: [ - { - // Filters for pageview or autocapture - ...HOG_FILTERS_EXAMPLES.pageview_or_autocapture_filter, - }, - { - // No filters so should match all events - ...HOG_FILTERS_EXAMPLES.no_filters, - }, - { - // Broken filters so shouldn't match - ...HOG_FILTERS_EXAMPLES.broken_filters, - }, - ], - }) - - const res = await supertest(app) - .post(`/api/projects/${hogFunction.team_id}/hog_functions/${hogFunction.id}/invocations`) - .send({ globals, mock_async_functions: true }) - - expect(res.status).toEqual(200) - - const minimalLogs = res.body.logs.map((log) => ({ - level: log.level, - message: log.message, - })) - - expect(minimalLogs).toMatchObject([ - { level: 'info', message: 'Mapping trigger not matching filters was ignored.' }, - { - level: 'error', - message: - 'Error filtering event b3a1fe86-b10c-43cc-acaf-d208977608d0: Invalid HogQL bytecode, stack is empty, can not pop', - }, - { level: 'debug', message: 'Executing function' }, - { - level: 'debug', - message: - "Suspending function due to async function call 'fetch'. Payload: 2110 bytes. Event: b3a1fe86-b10c-43cc-acaf-d208977608d0", - }, - { - level: 'info', - message: "Async function 'fetch' was mocked with arguments:", - }, - { - level: 'info', - message: expect.stringContaining('fetch({'), - }, - { level: 'debug', message: 'Resuming function' }, - { - level: 'info', - message: 'Fetch response:, {"status":200,"body":{}}', - }, - { - level: 'debug', - message: expect.stringContaining('Function completed in '), - }, - ]) - }) - }) -}) From 808edb86f7c0d6700d6d94ebeeefe26e50ef6195 Mon Sep 17 00:00:00 2001 From: Ben White Date: Thu, 30 Jan 2025 11:18:11 +0100 Subject: [PATCH 117/163] Fixes --- .../cdp/hog-transformations/hog-transformer.service.test.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/plugin-server/src/cdp/hog-transformations/hog-transformer.service.test.ts b/plugin-server/src/cdp/hog-transformations/hog-transformer.service.test.ts index 0c0cac890de60..63cf5fcfb3afc 100644 --- a/plugin-server/src/cdp/hog-transformations/hog-transformer.service.test.ts +++ b/plugin-server/src/cdp/hog-transformations/hog-transformer.service.test.ts @@ -4,8 +4,7 @@ import { readFileSync } from 'fs' import { join } from 'path' import { brotliDecompressSync } from 'zlib' -import { template as filterOutPluginTemplate } from '~/src/cdp/legacy-plugins/_transformations/posthog-filter-out-plugin/template' - +import { template as filterOutPluginTemplate } from '../../../src/cdp/legacy-plugins/_transformations/posthog-filter-out-plugin/template' import { template as defaultTemplate } from '../../../src/cdp/templates/_transformations/default/default.template' import { template as geoipTemplate } from '../../../src/cdp/templates/_transformations/geoip/geoip.template' import { compileHog } from '../../../src/cdp/templates/compiler' From a09a9de24d05bd396e473cc8d3a5c5e70082aad5 Mon Sep 17 00:00:00 2001 From: Ben White Date: Thu, 30 Jan 2025 11:22:06 +0100 Subject: [PATCH 118/163] Fixes --- .../hog-transformer.service.test.ts | 12 +++++++----- .../hog-transformations/hog-transformer.service.ts | 4 ++++ plugin-server/src/ingestion/ingestion-consumer.ts | 3 ++- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/plugin-server/src/cdp/hog-transformations/hog-transformer.service.test.ts b/plugin-server/src/cdp/hog-transformations/hog-transformer.service.test.ts index 0c0cac890de60..9baa2930fcb9e 100644 --- a/plugin-server/src/cdp/hog-transformations/hog-transformer.service.test.ts +++ b/plugin-server/src/cdp/hog-transformations/hog-transformer.service.test.ts @@ -57,10 +57,12 @@ describe('HogTransformer', () => { hub.mmdb = Reader.openBuffer(brotliDecompressSync(mmdbBrotliContents)) hogTransformer = new HogTransformerService(hub) + await hogTransformer.start() }) afterEach(async () => { await closeHub(hub) + await hogTransformer.stop() jest.spyOn(hogTransformer['pluginExecutor'], 'execute') }) @@ -81,7 +83,7 @@ describe('HogTransformer', () => { // Start the transformer after inserting functions because it is // starting the hogfunction manager which updates the cache - await hogTransformer.start() + await hogTransformer['hogFunctionManager'].reloadAllHogFunctions() const event: PluginEvent = createPluginEvent({}, teamId) const result = await hogTransformer.transformEvent(event) @@ -188,7 +190,7 @@ describe('HogTransformer', () => { await insertHogFunction(hub.db.postgres, teamId, defaultTransformationFunction) await insertHogFunction(hub.db.postgres, teamId, geoIpTransformationFunction) - await hogTransformer.start() + await hogTransformer['hogFunctionManager'].reloadAllHogFunctions() const createHogFunctionInvocationSpy = jest.spyOn(hogTransformer as any, 'createHogFunctionInvocation') @@ -267,7 +269,7 @@ describe('HogTransformer', () => { await insertHogFunction(hub.db.postgres, teamId, deletingTransformationFunction) await insertHogFunction(hub.db.postgres, teamId, addingTransformationFunction) - await hogTransformer.start() + await hogTransformer['hogFunctionManager'].reloadAllHogFunctions() const createHogFunctionInvocationSpy = jest.spyOn(hogTransformer as any, 'createHogFunctionInvocation') @@ -366,7 +368,7 @@ describe('HogTransformer', () => { await insertHogFunction(hub.db.postgres, teamId, thirdTransformationFunction) await insertHogFunction(hub.db.postgres, teamId, secondTransformationFunction) await insertHogFunction(hub.db.postgres, teamId, firstTransformationFunction) - await hogTransformer.start() + await hogTransformer['hogFunctionManager'].reloadAllHogFunctions() const createHogFunctionInvocationSpy = jest.spyOn(hogTransformer as any, 'createHogFunctionInvocation') @@ -410,7 +412,7 @@ describe('HogTransformer', () => { }) await insertHogFunction(hub.db.postgres, teamId, filterOutPlugin) - await hogTransformer.start() + await hogTransformer['hogFunctionManager'].reloadAllHogFunctions() // Set up the spy after hogTransformer is initialized executeSpy = jest.spyOn(hogTransformer['pluginExecutor'], 'execute') diff --git a/plugin-server/src/cdp/hog-transformations/hog-transformer.service.ts b/plugin-server/src/cdp/hog-transformations/hog-transformer.service.ts index 420f0af0d2cf2..8b8c5bedb5296 100644 --- a/plugin-server/src/cdp/hog-transformations/hog-transformer.service.ts +++ b/plugin-server/src/cdp/hog-transformations/hog-transformer.service.ts @@ -94,6 +94,10 @@ export class HogTransformerService { await this.hogFunctionManager.start(hogTypes) } + public async stop(): Promise { + await this.hogFunctionManager.stop() + } + private produceAppMetric(metric: HogFunctionAppMetric): Promise { const appMetric: AppMetric2Type = { app_source: 'hog_function', diff --git a/plugin-server/src/ingestion/ingestion-consumer.ts b/plugin-server/src/ingestion/ingestion-consumer.ts index f7d3ae81523a6..0fbf01db1d171 100644 --- a/plugin-server/src/ingestion/ingestion-consumer.ts +++ b/plugin-server/src/ingestion/ingestion-consumer.ts @@ -121,7 +121,8 @@ export class IngestionConsumer { await this.batchConsumer?.stop() status.info('🔁', `${this.name} - stopping kafka producer`) await this.kafkaProducer?.disconnect() - + status.info('🔁', `${this.name} - stopping hog transformer`) + await this.hogTransformer.stop() status.info('👍', `${this.name} - stopped!`) } From 0ba81fd87154a643a6f9947cee1449b0030ec36b Mon Sep 17 00:00:00 2001 From: Ben White Date: Thu, 30 Jan 2025 12:25:46 +0100 Subject: [PATCH 119/163] Fixes --- frontend/src/lib/api.ts | 3 +- .../pipeline/hogfunctions/HogFunctionTest.tsx | 28 +--- .../hogfunctions/hogFunctionTestLogic.tsx | 126 +++++++++++++----- frontend/src/types.ts | 7 + plugin-server/src/cdp/cdp-api.ts | 2 +- 5 files changed, 107 insertions(+), 59 deletions(-) diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts index 5e4a36632c665..d51b9ff1eb755 100644 --- a/frontend/src/lib/api.ts +++ b/frontend/src/lib/api.ts @@ -64,6 +64,7 @@ import { HogFunctionStatus, HogFunctionSubTemplateIdType, HogFunctionTemplateType, + HogFunctionTestInvocationResult, HogFunctionType, HogFunctionTypeType, InsightModel, @@ -1912,7 +1913,7 @@ const api = { mock_async_functions: boolean globals: any } - ): Promise { + ): Promise { return await new ApiRequest().hogFunction(id).withAction('invocations').create({ data }) }, diff --git a/frontend/src/scenes/pipeline/hogfunctions/HogFunctionTest.tsx b/frontend/src/scenes/pipeline/hogfunctions/HogFunctionTest.tsx index 369885c22808b..fa5cf5274dd14 100644 --- a/frontend/src/scenes/pipeline/hogfunctions/HogFunctionTest.tsx +++ b/frontend/src/scenes/pipeline/hogfunctions/HogFunctionTest.tsx @@ -58,21 +58,6 @@ const HogFunctionTestEditor = ({ ) } -export function HogFunctionTestPlaceholder({ - title, - description, -}: { - title?: string | JSX.Element - description?: string | JSX.Element -}): JSX.Element { - return ( -