diff --git a/frontend/__snapshots__/scenes-other-billing--billing-with-credit-cta--light.png b/frontend/__snapshots__/scenes-other-billing--billing-with-credit-cta--light.png
index 444c0bb0cdc24..8633f30a452d7 100644
Binary files a/frontend/__snapshots__/scenes-other-billing--billing-with-credit-cta--light.png and b/frontend/__snapshots__/scenes-other-billing--billing-with-credit-cta--light.png differ
diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts
index f8adb51e539a5..d6760ce8d2abe 100644
--- a/frontend/src/lib/api.ts
+++ b/frontend/src/lib/api.ts
@@ -63,6 +63,7 @@ import {
HogFunctionStatus,
HogFunctionTemplateType,
HogFunctionType,
+ HogFunctionTypeType,
InsightModel,
IntegrationType,
ListOrganizationMembersParams,
@@ -1744,7 +1745,10 @@ const api = {
},
},
hogFunctions: {
- async list(params?: { filters?: any }): Promise> {
+ async list(params?: {
+ filters?: any
+ type?: HogFunctionTypeType
+ }): Promise> {
return await new ApiRequest().hogFunctions().withQueryString(params).get()
},
async get(id: HogFunctionType['id']): Promise {
@@ -1774,9 +1778,11 @@ const api = {
): Promise {
return await new ApiRequest().hogFunction(id).withAction('metrics/totals').withQueryString(params).get()
},
-
- async listTemplates(): Promise> {
- return await new ApiRequest().hogFunctionTemplates().get()
+ async listTemplates(type?: HogFunctionTypeType): Promise> {
+ return new ApiRequest()
+ .hogFunctionTemplates()
+ .withQueryString({ type: type ?? 'destination' })
+ .get()
},
async getTemplate(id: HogFunctionTemplateType['id']): Promise {
return await new ApiRequest().hogFunctionTemplate(id).get()
diff --git a/frontend/src/scenes/actions/ActionHogFunctions.tsx b/frontend/src/scenes/actions/ActionHogFunctions.tsx
index 056bdea24fd8e..6128a2c1d664c 100644
--- a/frontend/src/scenes/actions/ActionHogFunctions.tsx
+++ b/frontend/src/scenes/actions/ActionHogFunctions.tsx
@@ -35,6 +35,7 @@ export function ActionHogFunctions(): JSX.Element | null {
) : null}
Get notified via Slack, webhooks or more whenever this event is captured.
Notifications
Get notified when people opt in or out of your feature.
diff --git a/frontend/src/scenes/pipeline/destinations/destinationsLogic.tsx b/frontend/src/scenes/pipeline/destinations/destinationsLogic.tsx
index 3d13151059182..6f87bd5668ad8 100644
--- a/frontend/src/scenes/pipeline/destinations/destinationsLogic.tsx
+++ b/frontend/src/scenes/pipeline/destinations/destinationsLogic.tsx
@@ -166,7 +166,7 @@ export const pipelineDestinationsLogic = kea([
{
loadHogFunctions: async () => {
// TODO: Support pagination?
- return (await api.hogFunctions.list()).results
+ return (await api.hogFunctions.list({ type: 'destination' })).results
},
deleteNodeHogFunction: async ({ destination }) => {
diff --git a/frontend/src/scenes/pipeline/hogfunctions/HogFunctionConfiguration.tsx b/frontend/src/scenes/pipeline/hogfunctions/HogFunctionConfiguration.tsx
index e16f5cadedc7a..d0e60b03d8944 100644
--- a/frontend/src/scenes/pipeline/hogfunctions/HogFunctionConfiguration.tsx
+++ b/frontend/src/scenes/pipeline/hogfunctions/HogFunctionConfiguration.tsx
@@ -36,7 +36,12 @@ import { HogFunctionTest, HogFunctionTestPlaceholder } from './HogFunctionTest'
const EVENT_THRESHOLD_ALERT_LEVEL = 8000
-export function HogFunctionConfiguration({ templateId, id }: { templateId?: string; id?: string }): JSX.Element {
+export interface HogFunctionConfigurationProps {
+ templateId?: string | null
+ id?: string | null
+}
+
+export function HogFunctionConfiguration({ templateId, id }: HogFunctionConfigurationProps): JSX.Element {
const logicProps = { templateId, id }
const logic = hogFunctionConfigurationLogic(logicProps)
const {
diff --git a/frontend/src/scenes/pipeline/hogfunctions/hogFunctionConfigurationLogic.test.ts b/frontend/src/scenes/pipeline/hogfunctions/hogFunctionConfigurationLogic.test.ts
index 79cfaf648f8bc..3c9bef43c45d8 100644
--- a/frontend/src/scenes/pipeline/hogfunctions/hogFunctionConfigurationLogic.test.ts
+++ b/frontend/src/scenes/pipeline/hogfunctions/hogFunctionConfigurationLogic.test.ts
@@ -63,6 +63,7 @@ const HOG_TEMPLATE: HogFunctionTemplateType = {
],
status: 'beta',
id: 'template-webhook',
+ type: 'destination',
name: 'HTTP Webhook',
description: 'Sends a webhook templated by the incoming event data',
hog: "let res := fetch(inputs.url, {\n 'headers': inputs.headers,\n 'body': inputs.body,\n 'method': inputs.method\n});\n\nif (inputs.debug) {\n print('Response', res.status, res.body);\n}",
@@ -170,6 +171,7 @@ describe('hogFunctionConfigurationLogic', () => {
expect(logic.values.template).toEqual(HOG_TEMPLATE)
expect(logic.values.configuration).toEqual({
name: HOG_TEMPLATE.name,
+ type: HOG_TEMPLATE.type,
description: HOG_TEMPLATE.description,
inputs_schema: HOG_TEMPLATE.inputs_schema,
filters: null,
diff --git a/frontend/src/scenes/pipeline/hogfunctions/hogFunctionConfigurationLogic.tsx b/frontend/src/scenes/pipeline/hogfunctions/hogFunctionConfigurationLogic.tsx
index 45cc43ccdbbdf..08511bfbdc8f9 100644
--- a/frontend/src/scenes/pipeline/hogfunctions/hogFunctionConfigurationLogic.tsx
+++ b/frontend/src/scenes/pipeline/hogfunctions/hogFunctionConfigurationLogic.tsx
@@ -46,9 +46,9 @@ import { EmailTemplate } from './email-templater/emailTemplaterLogic'
import type { hogFunctionConfigurationLogicType } from './hogFunctionConfigurationLogicType'
export interface HogFunctionConfigurationLogicProps {
- templateId?: string
- subTemplateId?: string
- id?: string
+ templateId?: string | null
+ subTemplateId?: string | null
+ id?: string | null
}
export const EVENT_VOLUME_DAILY_WARNING_THRESHOLD = 1000
@@ -56,6 +56,7 @@ const UNSAVED_CONFIGURATION_TTL = 1000 * 60 * 5
const NEW_FUNCTION_TEMPLATE: HogFunctionTemplateType = {
id: 'new',
+ type: 'destination',
name: '',
description: '',
inputs_schema: [],
@@ -118,6 +119,7 @@ const templateToConfiguration = (
})
return {
+ type: template.type ?? 'destination',
name: subTemplate?.name ?? template.name,
description: subTemplate?.name ?? template.description,
inputs_schema: template.inputs_schema,
@@ -403,6 +405,10 @@ export const hogFunctionConfigurationLogic = kea ({
logicProps: [() => [(_, props) => props], (props): HogFunctionConfigurationLogicProps => props],
+ type: [
+ (s) => [s.configuration, s.hogFunction],
+ (configuration, hogFunction) => configuration?.type ?? hogFunction?.type ?? 'loading',
+ ],
hasAddon: [
(s) => [s.hasAvailableFeature],
(hasAvailableFeature) => {
diff --git a/frontend/src/scenes/pipeline/hogfunctions/list/LinkedHogFunctions.tsx b/frontend/src/scenes/pipeline/hogfunctions/list/LinkedHogFunctions.tsx
index 7403e1f63776d..976e1fda00d99 100644
--- a/frontend/src/scenes/pipeline/hogfunctions/list/LinkedHogFunctions.tsx
+++ b/frontend/src/scenes/pipeline/hogfunctions/list/LinkedHogFunctions.tsx
@@ -1,18 +1,20 @@
import { LemonButton } from '@posthog/lemon-ui'
import { useState } from 'react'
-import { HogFunctionFiltersType, HogFunctionSubTemplateIdType } from '~/types'
+import { HogFunctionFiltersType, HogFunctionSubTemplateIdType, HogFunctionTypeType } from '~/types'
import { HogFunctionList } from './HogFunctionsList'
import { HogFunctionTemplateList } from './HogFunctionTemplateList'
export type LinkedHogFunctionsProps = {
+ type: HogFunctionTypeType
filters: HogFunctionFiltersType
subTemplateId?: HogFunctionSubTemplateIdType
newDisabledReason?: string
}
export function LinkedHogFunctions({
+ type,
filters,
subTemplateId,
newDisabledReason,
@@ -22,6 +24,7 @@ export function LinkedHogFunctions({
return showNewDestination ? (
@@ -34,6 +37,7 @@ export function LinkedHogFunctions({
) : (
([
},
],
})),
- loaders(({ values, actions }) => ({
+ loaders(({ values, actions, props }) => ({
hogFunctions: [
[] as HogFunctionType[],
{
@@ -76,6 +77,7 @@ export const hogFunctionListLogic = kea([
return (
await api.hogFunctions.list({
filters: values.filters?.filters,
+ type: props.type,
})
).results
},
diff --git a/frontend/src/scenes/pipeline/hogfunctions/list/hogFunctionTemplateListLogic.tsx b/frontend/src/scenes/pipeline/hogfunctions/list/hogFunctionTemplateListLogic.tsx
index 079b56d659a7a..6facd97eb61ae 100644
--- a/frontend/src/scenes/pipeline/hogfunctions/list/hogFunctionTemplateListLogic.tsx
+++ b/frontend/src/scenes/pipeline/hogfunctions/list/hogFunctionTemplateListLogic.tsx
@@ -8,7 +8,7 @@ import { objectsEqual } from 'lib/utils'
import { pipelineAccessLogic } from 'scenes/pipeline/pipelineAccessLogic'
import { urls } from 'scenes/urls'
-import { HogFunctionTemplateType, PipelineStage } from '~/types'
+import { HogFunctionTemplateType, HogFunctionTypeType, PipelineStage } from '~/types'
import type { hogFunctionTemplateListLogicType } from './hogFunctionTemplateListLogicType'
@@ -22,6 +22,7 @@ export type HogFunctionTemplateListFilters = {
}
export type HogFunctionTemplateListLogicProps = {
+ type: HogFunctionTypeType
defaultFilters?: HogFunctionTemplateListFilters
forceFilters?: HogFunctionTemplateListFilters
syncFiltersWithUrl?: boolean
@@ -29,7 +30,7 @@ export type HogFunctionTemplateListLogicProps = {
export const hogFunctionTemplateListLogic = kea([
props({} as HogFunctionTemplateListLogicProps),
- key((props) => (props.syncFiltersWithUrl ? 'scene' : 'default')),
+ key((props) => `${props.syncFiltersWithUrl ? 'scene' : 'default'}/${props.type ?? 'destination'}`),
path((id) => ['scenes', 'pipeline', 'destinationsLogic', id]),
connect({
values: [pipelineAccessLogic, ['canEnableNewDestinations'], featureFlagLogic, ['featureFlags']],
@@ -53,12 +54,12 @@ export const hogFunctionTemplateListLogic = kea ({
+ loaders(({ props }) => ({
rawTemplates: [
[] as HogFunctionTemplateType[],
{
loadHogFunctionTemplates: async () => {
- return (await api.hogFunctions.listTemplates()).results
+ return (await api.hogFunctions.listTemplates(props.type)).results
},
},
],
diff --git a/frontend/src/scenes/surveys/SurveyView.tsx b/frontend/src/scenes/surveys/SurveyView.tsx
index 5b69762e9d487..2fdfb11802a68 100644
--- a/frontend/src/scenes/surveys/SurveyView.tsx
+++ b/frontend/src/scenes/surveys/SurveyView.tsx
@@ -403,6 +403,7 @@ export function SurveyView({ id }: { id: string }): JSX.Element {
Get notified whenever a survey result is submitted
Get notified whenever a survey result is submitted
& {
status: HogFunctionTemplateStatus
sub_templates?: HogFunctionSubTemplateType[]
diff --git a/latest_migrations.manifest b/latest_migrations.manifest
index 706c12ae7f1ef..4f5e422e17f82 100644
--- a/latest_migrations.manifest
+++ b/latest_migrations.manifest
@@ -5,7 +5,7 @@ contenttypes: 0002_remove_content_type_name
ee: 0016_rolemembership_organization_member
otp_static: 0002_throttling
otp_totp: 0002_auto_20190420_0723
-posthog: 0498_errortrackingissuefingerprint_and_more
+posthog: 0499_hog_function_type
sessions: 0001_initial
social_django: 0010_uid_db_index
two_factor: 0007_auto_20201201_1019
diff --git a/plugin-server/src/cdp/cdp-consumers.ts b/plugin-server/src/cdp/cdp-consumers.ts
index 4e9c4904d2c6b..04404d73cf3fc 100644
--- a/plugin-server/src/cdp/cdp-consumers.ts
+++ b/plugin-server/src/cdp/cdp-consumers.ts
@@ -548,7 +548,7 @@ export class CdpProcessedEventsConsumer extends CdpConsumerBase {
try {
const clickHouseEvent = JSON.parse(message.value!.toString()) as RawClickHouseEvent
- if (!this.hogFunctionManager.teamHasHogFunctions(clickHouseEvent.team_id)) {
+ if (!this.hogFunctionManager.teamHasHogDestinations(clickHouseEvent.team_id)) {
// No need to continue if the team doesn't have any functions
return
}
diff --git a/plugin-server/src/cdp/hog-executor.ts b/plugin-server/src/cdp/hog-executor.ts
index 079ecacfd538f..2e62a9a045a13 100644
--- a/plugin-server/src/cdp/hog-executor.ts
+++ b/plugin-server/src/cdp/hog-executor.ts
@@ -114,7 +114,7 @@ export class HogExecutor {
nonMatchingFunctions: HogFunctionType[]
erroredFunctions: HogFunctionType[]
} {
- const allFunctionsForTeam = this.hogFunctionManager.getTeamHogFunctions(event.project.id)
+ const allFunctionsForTeam = this.hogFunctionManager.getTeamHogDestinations(event.project.id)
const filtersGlobals = convertToHogFunctionFilterGlobal(event)
const nonMatchingFunctions: HogFunctionType[] = []
diff --git a/plugin-server/src/cdp/hog-function-manager.ts b/plugin-server/src/cdp/hog-function-manager.ts
index 2ae5fba957d05..a54be7d96a058 100644
--- a/plugin-server/src/cdp/hog-function-manager.ts
+++ b/plugin-server/src/cdp/hog-function-manager.ts
@@ -23,8 +23,11 @@ const HOG_FUNCTION_FIELDS = [
'filters',
'bytecode',
'masking',
+ 'type',
]
+const RELOAD_HOG_FUNCTION_TYPES = ['destination', 'email']
+
export class HogFunctionManager {
private started: boolean
private ready: boolean
@@ -93,6 +96,10 @@ export class HogFunctionManager {
.filter((x) => !!x) as HogFunctionType[]
}
+ public getTeamHogDestinations(teamId: Team['id']): HogFunctionType[] {
+ return this.getTeamHogFunctions(teamId).filter((x) => x.type === 'destination' || !x.type)
+ }
+
public getHogFunction(id: HogFunctionType['id']): HogFunctionType | undefined {
if (!this.ready) {
throw new Error('HogFunctionManager is not ready! Run HogFunctionManager.start() before this')
@@ -112,8 +119,8 @@ export class HogFunctionManager {
}
}
- public teamHasHogFunctions(teamId: Team['id']): boolean {
- return !!Object.keys(this.getTeamHogFunctions(teamId)).length
+ public teamHasHogDestinations(teamId: Team['id']): boolean {
+ return !!Object.keys(this.getTeamHogDestinations(teamId)).length
}
public async reloadAllHogFunctions(): Promise {
@@ -123,9 +130,9 @@ export class HogFunctionManager {
`
SELECT ${HOG_FUNCTION_FIELDS.join(', ')}
FROM posthog_hogfunction
- WHERE deleted = FALSE AND enabled = TRUE
+ WHERE deleted = FALSE AND enabled = TRUE AND (type is NULL or type = ANY($1))
`,
- [],
+ [RELOAD_HOG_FUNCTION_TYPES],
'fetchAllHogFunctions'
)
).rows
diff --git a/plugin-server/src/cdp/types.ts b/plugin-server/src/cdp/types.ts
index 19664b2f83e25..19cce8157a075 100644
--- a/plugin-server/src/cdp/types.ts
+++ b/plugin-server/src/cdp/types.ts
@@ -239,7 +239,7 @@ export type HogFunctionInvocationSerializedCompressed = {
// Mostly copied from frontend types
export type HogFunctionInputSchemaType = {
- type: 'string' | 'boolean' | 'dictionary' | 'choice' | 'json' | 'integration' | 'integration_field'
+ type: 'string' | 'boolean' | 'dictionary' | 'choice' | 'json' | 'integration' | 'integration_field' | 'email'
key: string
label?: string
choices?: { value: string; label: string }[]
@@ -252,8 +252,11 @@ export type HogFunctionInputSchemaType = {
integration_field?: 'slack_channel'
}
+export type HogFunctionTypeType = 'destination' | 'email' | 'sms' | 'push' | 'activity' | 'alert' | 'broadcast'
+
export type HogFunctionType = {
id: string
+ type: HogFunctionTypeType
team_id: number
name: string
enabled: boolean
diff --git a/plugin-server/tests/cdp/fixtures.ts b/plugin-server/tests/cdp/fixtures.ts
index e0b668559a9f1..efbf3d2a06f83 100644
--- a/plugin-server/tests/cdp/fixtures.ts
+++ b/plugin-server/tests/cdp/fixtures.ts
@@ -15,6 +15,7 @@ import { insertRow } from '../helpers/sql'
export const createHogFunction = (hogFunction: Partial) => {
const item: HogFunctionType = {
id: randomUUID(),
+ type: 'destination',
name: 'Hog Function',
team_id: 1,
enabled: true,
diff --git a/plugin-server/tests/cdp/hog-executor.test.ts b/plugin-server/tests/cdp/hog-executor.test.ts
index 96b722fb9afdb..f13ebd0e9c0a4 100644
--- a/plugin-server/tests/cdp/hog-executor.test.ts
+++ b/plugin-server/tests/cdp/hog-executor.test.ts
@@ -43,7 +43,7 @@ describe('Hog Executor', () => {
const mockFunctionManager = {
reloadAllHogFunctions: jest.fn(),
- getTeamHogFunctions: jest.fn(),
+ getTeamHogDestinations: jest.fn(),
getTeamHogFunction: jest.fn(),
}
@@ -64,7 +64,7 @@ describe('Hog Executor', () => {
...HOG_FILTERS_EXAMPLES.no_filters,
})
- mockFunctionManager.getTeamHogFunctions.mockReturnValue([hogFunction])
+ mockFunctionManager.getTeamHogDestinations.mockReturnValue([hogFunction])
mockFunctionManager.getTeamHogFunction.mockReturnValue(hogFunction)
})
@@ -223,7 +223,7 @@ describe('Hog Executor', () => {
...HOG_FILTERS_EXAMPLES.pageview_or_autocapture_filter,
})
- mockFunctionManager.getTeamHogFunctions.mockReturnValue([fn])
+ mockFunctionManager.getTeamHogDestinations.mockReturnValue([fn])
const resultsShouldntMatch = executor.findMatchingFunctions(createHogExecutionGlobals({ groups: {} }))
expect(resultsShouldntMatch.matchingFunctions).toHaveLength(0)
@@ -253,7 +253,7 @@ describe('Hog Executor', () => {
...HOG_INPUTS_EXAMPLES.simple_fetch,
...HOG_FILTERS_EXAMPLES.broken_filters,
})
- mockFunctionManager.getTeamHogFunctions.mockReturnValue([fn])
+ mockFunctionManager.getTeamHogDestinations.mockReturnValue([fn])
const resultsShouldMatch = executor.findMatchingFunctions(
createHogExecutionGlobals({
groups: {},
@@ -285,7 +285,7 @@ describe('Hog Executor', () => {
...HOG_FILTERS_EXAMPLES.elements_text_filter,
})
- mockFunctionManager.getTeamHogFunctions.mockReturnValue([fn])
+ mockFunctionManager.getTeamHogDestinations.mockReturnValue([fn])
const elementsChain = (buttonText: string) =>
`span.LemonButton__content:attr__class="LemonButton__content"nth-child="2"nth-of-type="2"text="${buttonText}";span.LemonButton__chrome:attr__class="LemonButton__chrome"nth-child="1"nth-of-type="1";button.LemonButton.LemonButton--has-icon.LemonButton--secondary.LemonButton--status-default:attr__class="LemonButton LemonButton--secondary LemonButton--status-default LemonButton--has-icon"attr__type="button"nth-child="1"nth-of-type="1"text="${buttonText}";div.flex.gap-4.items-center:attr__class="flex gap-4 items-center"nth-child="1"nth-of-type="1";div.flex.flex-wrap.gap-4.justify-between:attr__class="flex gap-4 justify-between flex-wrap"nth-child="3"nth-of-type="3";div.flex.flex-1.flex-col.gap-4.h-full.relative.w-full:attr__class="relative w-full flex flex-col gap-4 flex-1 h-full"nth-child="1"nth-of-type="1";div.LemonTabs__content:attr__class="LemonTabs__content"nth-child="2"nth-of-type="1";div.LemonTabs.LemonTabs--medium:attr__class="LemonTabs LemonTabs--medium"attr__style="--lemon-tabs-slider-width: 48px; --lemon-tabs-slider-offset: 0px;"nth-child="1"nth-of-type="1";div.Navigation3000__scene:attr__class="Navigation3000__scene"nth-child="2"nth-of-type="2";main:nth-child="2"nth-of-type="1";div.Navigation3000:attr__class="Navigation3000"nth-child="1"nth-of-type="1";div:attr__id="root"attr_id="root"nth-child="3"nth-of-type="1";body.overflow-hidden:attr__class="overflow-hidden"attr__theme="light"nth-child="2"nth-of-type="1"`
@@ -335,7 +335,7 @@ describe('Hog Executor', () => {
...HOG_FILTERS_EXAMPLES.elements_href_filter,
})
- mockFunctionManager.getTeamHogFunctions.mockReturnValue([fn])
+ mockFunctionManager.getTeamHogDestinations.mockReturnValue([fn])
const elementsChain = (link: string) =>
`span.LemonButton__content:attr__class="LemonButton__content"attr__href="${link}"href="${link}"nth-child="2"nth-of-type="2"text="Activity";span.LemonButton__chrome:attr__class="LemonButton__chrome"nth-child="1"nth-of-type="1";a.LemonButton.LemonButton--full-width.LemonButton--has-icon.LemonButton--secondary.LemonButton--status-alt.Link.NavbarButton:attr__class="Link LemonButton LemonButton--secondary LemonButton--status-alt LemonButton--full-width LemonButton--has-icon NavbarButton"attr__data-attr="menu-item-activity"attr__href="${link}"href="${link}"nth-child="1"nth-of-type="1"text="Activity";li.w-full:attr__class="w-full"nth-child="6"nth-of-type="6";ul:nth-child="1"nth-of-type="1";div.Navbar3000__top.ScrollableShadows__inner:attr__class="ScrollableShadows__inner Navbar3000__top"nth-child="1"nth-of-type="1";div.ScrollableShadows.ScrollableShadows--vertical:attr__class="ScrollableShadows ScrollableShadows--vertical"nth-child="1"nth-of-type="1";div.Navbar3000__content:attr__class="Navbar3000__content"nth-child="1"nth-of-type="1";nav.Navbar3000:attr__class="Navbar3000"nth-child="1"nth-of-type="1";div.Navigation3000:attr__class="Navigation3000"nth-child="1"nth-of-type="1";div:attr__id="root"attr_id="root"nth-child="3"nth-of-type="1";body.overflow-hidden:attr__class="overflow-hidden"attr__theme="light"nth-child="2"nth-of-type="1"`
@@ -385,7 +385,7 @@ describe('Hog Executor', () => {
...HOG_FILTERS_EXAMPLES.elements_tag_and_id_filter,
})
- mockFunctionManager.getTeamHogFunctions.mockReturnValue([fn])
+ mockFunctionManager.getTeamHogDestinations.mockReturnValue([fn])
const elementsChain = (id: string) =>
`a.Link.font-semibold.text-text-3000.text-xl:attr__class="Link font-semibold text-xl text-text-3000"attr__href="/project/1/dashboard/1"attr__id="${id}"attr_id="${id}"href="/project/1/dashboard/1"nth-child="1"nth-of-type="1"text="My App Dashboard";div.ProjectHomepage__dashboardheader__title:attr__class="ProjectHomepage__dashboardheader__title"nth-child="1"nth-of-type="1";div.ProjectHomepage__dashboardheader:attr__class="ProjectHomepage__dashboardheader"nth-child="2"nth-of-type="2";div.ProjectHomepage:attr__class="ProjectHomepage"nth-child="1"nth-of-type="1";div.Navigation3000__scene:attr__class="Navigation3000__scene"nth-child="2"nth-of-type="2";main:nth-child="2"nth-of-type="1";div.Navigation3000:attr__class="Navigation3000"nth-child="1"nth-of-type="1";div:attr__id="root"attr_id="root"nth-child="3"nth-of-type="1";body.overflow-hidden:attr__class="overflow-hidden"attr__theme="light"nth-child="2"nth-of-type="1"`
@@ -476,7 +476,7 @@ describe('Hog Executor', () => {
...HOG_FILTERS_EXAMPLES.no_filters,
})
- mockFunctionManager.getTeamHogFunctions.mockReturnValue([fn])
+ mockFunctionManager.getTeamHogDestinations.mockReturnValue([fn])
const result = executor.execute(createInvocation(fn))
expect(result.error).toContain('Execution timed out after 0.1 seconds. Performed ')
diff --git a/plugin-server/tests/cdp/hog-function-manager.test.ts b/plugin-server/tests/cdp/hog-function-manager.test.ts
index 818def2f5c8e4..9ae3e2c0e6e10 100644
--- a/plugin-server/tests/cdp/hog-function-manager.test.ts
+++ b/plugin-server/tests/cdp/hog-function-manager.test.ts
@@ -60,6 +60,24 @@ 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',
@@ -89,13 +107,14 @@ describe('HogFunctionManager', () => {
})
it('returns the hog functions', async () => {
- let items = manager.getTeamHogFunctions(teamId1)
+ let items = manager.getTeamHogDestinations(teamId1)
expect(items).toEqual([
{
id: hogFunctions[0].id,
team_id: teamId1,
name: 'Test Hog Function team 1',
+ type: 'destination',
enabled: true,
bytecode: {},
filters: null,
@@ -123,6 +142,10 @@ describe('HogFunctionManager', () => {
},
])
+ const allFunctions = manager.getTeamHogFunctions(teamId1)
+ expect(allFunctions.length).toEqual(2)
+ expect(allFunctions.map((f) => f.type).sort()).toEqual(['destination', 'email'])
+
await hub.db.postgres.query(
PostgresUse.COMMON_WRITE,
`UPDATE posthog_hogfunction SET name='Test Hog Function team 1 updated' WHERE id = $1`,
@@ -133,7 +156,7 @@ describe('HogFunctionManager', () => {
// This is normally dispatched by django
await manager.reloadHogFunctions(teamId1, [hogFunctions[0].id])
- items = manager.getTeamHogFunctions(teamId1)
+ items = manager.getTeamHogDestinations(teamId1)
expect(items).toMatchObject([
{
@@ -144,7 +167,7 @@ describe('HogFunctionManager', () => {
})
it('removes disabled functions', async () => {
- let items = manager.getTeamHogFunctions(teamId1)
+ let items = manager.getTeamHogDestinations(teamId1)
expect(items).toMatchObject([
{
@@ -162,14 +185,14 @@ describe('HogFunctionManager', () => {
// This is normally dispatched by django
await manager.reloadHogFunctions(teamId1, [hogFunctions[0].id])
- items = manager.getTeamHogFunctions(teamId1)
+ items = manager.getTeamHogDestinations(teamId1)
expect(items).toEqual([])
})
it('enriches integration inputs if found and belonging to the team', () => {
- const function1Inputs = manager.getTeamHogFunctions(teamId1)[0].inputs
- const function2Inputs = manager.getTeamHogFunctions(teamId2)[0].inputs
+ const function1Inputs = manager.getTeamHogDestinations(teamId1)[0].inputs
+ const function2Inputs = manager.getTeamHogDestinations(teamId2)[0].inputs
// Only the right team gets the integration inputs enriched
expect(function1Inputs).toEqual({
diff --git a/posthog/api/hog_function.py b/posthog/api/hog_function.py
index 881f6a79bd2b1..83f81ca2e63df 100644
--- a/posthog/api/hog_function.py
+++ b/posthog/api/hog_function.py
@@ -45,6 +45,7 @@ class Meta:
model = HogFunction
fields = [
"id",
+ "type",
"name",
"description",
"created_at",
@@ -82,6 +83,7 @@ class Meta:
model = HogFunction
fields = [
"id",
+ "type",
"name",
"description",
"created_at",
@@ -167,6 +169,9 @@ def validate(self, attrs):
if "hog" in attrs:
attrs["bytecode"] = compile_hog(attrs["hog"])
+ if "type" not in attrs:
+ attrs["type"] = "destination"
+
return super().validate(attrs)
def to_representation(self, data):
@@ -226,7 +231,8 @@ def get_serializer_class(self) -> type[BaseSerializer]:
def safely_get_queryset(self, queryset: QuerySet) -> QuerySet:
if self.action == "list":
- queryset = queryset.filter(deleted=False)
+ type = self.request.GET.get("type", "destination")
+ queryset = queryset.filter(deleted=False, type=type)
if self.request.GET.get("filters"):
try:
@@ -309,7 +315,7 @@ def perform_create(self, serializer):
item_id=serializer.instance.id,
scope="HogFunction",
activity="created",
- detail=Detail(name=serializer.instance.name, type=serializer.instance.type),
+ detail=Detail(name=serializer.instance.name, type=serializer.instance.type or "destination"),
)
def perform_update(self, serializer):
@@ -332,5 +338,7 @@ def perform_update(self, serializer):
item_id=instance_id,
scope="HogFunction",
activity="updated",
- detail=Detail(changes=changes, name=serializer.instance.name, type=serializer.instance.type),
+ detail=Detail(
+ changes=changes, name=serializer.instance.name, type=serializer.instance.type or "destination"
+ ),
)
diff --git a/posthog/api/hog_function_template.py b/posthog/api/hog_function_template.py
index c6dbd0a649add..befb986262ee2 100644
--- a/posthog/api/hog_function_template.py
+++ b/posthog/api/hog_function_template.py
@@ -33,8 +33,8 @@ class PublicHogFunctionTemplateViewSet(viewsets.GenericViewSet):
serializer_class = HogFunctionTemplateSerializer
def _get_templates(self):
- data = HOG_FUNCTION_TEMPLATES
- return data
+ type = self.request.GET.get("type", "destination")
+ return [item for item in HOG_FUNCTION_TEMPLATES if item.type == type]
def list(self, request: Request, *args, **kwargs):
page = self.paginate_queryset(self._get_templates())
diff --git a/posthog/api/test/__snapshots__/test_decide.ambr b/posthog/api/test/__snapshots__/test_decide.ambr
index 7e2825e51df8f..75b3f38264283 100644
--- a/posthog/api/test/__snapshots__/test_decide.ambr
+++ b/posthog/api/test/__snapshots__/test_decide.ambr
@@ -433,6 +433,7 @@
"posthog_hogfunction"."deleted",
"posthog_hogfunction"."updated_at",
"posthog_hogfunction"."enabled",
+ "posthog_hogfunction"."type",
"posthog_hogfunction"."icon_url",
"posthog_hogfunction"."hog",
"posthog_hogfunction"."bytecode",
@@ -536,6 +537,7 @@
"posthog_hogfunction"."deleted",
"posthog_hogfunction"."updated_at",
"posthog_hogfunction"."enabled",
+ "posthog_hogfunction"."type",
"posthog_hogfunction"."icon_url",
"posthog_hogfunction"."hog",
"posthog_hogfunction"."bytecode",
@@ -794,6 +796,7 @@
"posthog_hogfunction"."deleted",
"posthog_hogfunction"."updated_at",
"posthog_hogfunction"."enabled",
+ "posthog_hogfunction"."type",
"posthog_hogfunction"."icon_url",
"posthog_hogfunction"."hog",
"posthog_hogfunction"."bytecode",
@@ -1175,6 +1178,7 @@
"posthog_hogfunction"."deleted",
"posthog_hogfunction"."updated_at",
"posthog_hogfunction"."enabled",
+ "posthog_hogfunction"."type",
"posthog_hogfunction"."icon_url",
"posthog_hogfunction"."hog",
"posthog_hogfunction"."bytecode",
diff --git a/posthog/api/test/test_hog_function.py b/posthog/api/test/test_hog_function.py
index 0b845431b6591..7a9954318d896 100644
--- a/posthog/api/test/test_hog_function.py
+++ b/posthog/api/test/test_hog_function.py
@@ -19,6 +19,7 @@
EXAMPLE_FULL = {
"name": "HogHook",
"hog": "fetch(inputs.url, {\n 'headers': inputs.headers,\n 'body': inputs.payload,\n 'method': inputs.method\n});",
+ "type": "destination",
"inputs_schema": [
{"key": "url", "type": "string", "label": "Webhook URL", "required": True},
{"key": "payload", "type": "json", "label": "JSON Payload", "required": True},
@@ -164,6 +165,7 @@ def test_create_hog_function(self, *args):
assert response.json()["created_by"]["id"] == self.user.id
assert response.json() == {
"id": ANY,
+ "type": "destination",
"name": "Fetch URL",
"description": "Test description",
"created_at": ANY,
@@ -220,6 +222,7 @@ def test_creates_with_template_id(self, *args):
)
assert response.status_code == status.HTTP_201_CREATED, response.json()
assert response.json()["template"] == {
+ "type": "destination",
"name": template_webhook.name,
"description": template_webhook.description,
"id": template_webhook.id,
@@ -236,7 +239,12 @@ def test_creates_with_template_id(self, *args):
def test_deletes_via_update(self, *args):
response = self.client.post(
f"/api/projects/{self.team.id}/hog_functions/",
- data={"name": "Fetch URL", "description": "Test description", "hog": "fetch(inputs.url);"},
+ data={
+ "type": "destination",
+ "name": "Fetch URL",
+ "description": "Test description",
+ "hog": "fetch(inputs.url);",
+ },
)
assert response.status_code == status.HTTP_201_CREATED, response.json()
id = response.json()["id"]
@@ -925,3 +933,39 @@ def test_list_with_filters_filter(self, *args):
filters = {"actions": [{"id": f"{action2.id}"}]}
response = self.client.get(f"/api/projects/{self.team.id}/hog_functions/?filters={json.dumps(filters)}")
assert len(response.json()["results"]) == 1
+
+ def test_list_with_type_filter(self, *args):
+ response = self.client.post(
+ f"/api/projects/{self.team.id}/hog_functions/",
+ data={
+ **EXAMPLE_FULL,
+ "filters": {
+ "events": [{"id": "$pageview", "name": "$pageview", "type": "events", "order": 0}],
+ },
+ },
+ )
+ assert response.status_code == status.HTTP_201_CREATED, response.json()
+
+ response = self.client.get(f"/api/projects/{self.team.id}/hog_functions/")
+ assert len(response.json()["results"]) == 1
+
+ response = self.client.get(f"/api/projects/{self.team.id}/hog_functions/?type=destination")
+ assert len(response.json()["results"]) == 1
+
+ response = self.client.get(f"/api/projects/{self.team.id}/hog_functions/?type=email")
+ assert len(response.json()["results"]) == 0
+
+ response = self.client.post(
+ f"/api/projects/{self.team.id}/hog_functions/",
+ data={**EXAMPLE_FULL, "type": "email"},
+ )
+ assert response.status_code == status.HTTP_201_CREATED, response.json()
+
+ response = self.client.get(f"/api/projects/{self.team.id}/hog_functions/")
+ assert len(response.json()["results"]) == 1
+
+ response = self.client.get(f"/api/projects/{self.team.id}/hog_functions/?type=destination")
+ assert len(response.json()["results"]) == 1
+
+ response = self.client.get(f"/api/projects/{self.team.id}/hog_functions/?type=email")
+ assert len(response.json()["results"]) == 1
diff --git a/posthog/api/test/test_hog_function_templates.py b/posthog/api/test/test_hog_function_templates.py
index 1f6c57c36b1b8..cd9479a10b456 100644
--- a/posthog/api/test/test_hog_function_templates.py
+++ b/posthog/api/test/test_hog_function_templates.py
@@ -7,6 +7,7 @@
# 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,
+ "type": "destination",
"status": template.status,
"id": template.id,
"name": template.name,
@@ -28,6 +29,17 @@ def test_list_function_templates(self):
assert len(response.json()["results"]) > 5
assert response.json()["results"][0] == EXPECTED_FIRST_RESULT
+ def test_filter_function_templates(self):
+ response1 = self.client.get("/api/projects/@current/hog_function_templates/?type=notfound")
+ assert response1.status_code == status.HTTP_200_OK, response1.json()
+ assert len(response1.json()["results"]) == 0
+
+ response2 = self.client.get("/api/projects/@current/hog_function_templates/?type=destination")
+ response3 = self.client.get("/api/projects/@current/hog_function_templates/")
+
+ assert response2.json()["results"] == response3.json()["results"]
+ assert len(response2.json()["results"]) > 5
+
def test_public_list_function_templates(self):
self.client.logout()
response = self.client.get("/api/public_hog_function_templates/")
diff --git a/posthog/cdp/templates/activecampaign/template_activecampaign.py b/posthog/cdp/templates/activecampaign/template_activecampaign.py
index 1f79d51325574..e25e526017c66 100644
--- a/posthog/cdp/templates/activecampaign/template_activecampaign.py
+++ b/posthog/cdp/templates/activecampaign/template_activecampaign.py
@@ -3,6 +3,7 @@
template: HogFunctionTemplate = HogFunctionTemplate(
status="beta",
+ type="destination",
id="template-activecampaign",
name="ActiveCampaign",
description="Creates a new contact in ActiveCampaign whenever an event is triggered.",
diff --git a/posthog/cdp/templates/airtable/template_airtable.py b/posthog/cdp/templates/airtable/template_airtable.py
index f2e0ef2bc6133..436933057e2c7 100644
--- a/posthog/cdp/templates/airtable/template_airtable.py
+++ b/posthog/cdp/templates/airtable/template_airtable.py
@@ -3,6 +3,7 @@
template: HogFunctionTemplate = HogFunctionTemplate(
status="alpha",
+ type="destination",
id="template-airtable",
name="Airtable",
description="Creates Airtable records",
diff --git a/posthog/cdp/templates/attio/template_attio.py b/posthog/cdp/templates/attio/template_attio.py
index 8899ee01bff36..0ed851d9ed4d0 100644
--- a/posthog/cdp/templates/attio/template_attio.py
+++ b/posthog/cdp/templates/attio/template_attio.py
@@ -2,6 +2,7 @@
template: HogFunctionTemplate = HogFunctionTemplate(
status="beta",
+ type="destination",
id="template-attio",
name="Attio",
description="Update contacts in Attio",
diff --git a/posthog/cdp/templates/avo/template_avo.py b/posthog/cdp/templates/avo/template_avo.py
index 8045e3ce83351..073a819b108ac 100644
--- a/posthog/cdp/templates/avo/template_avo.py
+++ b/posthog/cdp/templates/avo/template_avo.py
@@ -5,6 +5,7 @@
template: HogFunctionTemplate = HogFunctionTemplate(
status="beta",
+ type="destination",
id="template-avo",
name="Avo",
description="Send events to Avo",
diff --git a/posthog/cdp/templates/aws_kinesis/template_aws_kinesis.py b/posthog/cdp/templates/aws_kinesis/template_aws_kinesis.py
index f7d7f4d36c603..96fdc76d403f8 100644
--- a/posthog/cdp/templates/aws_kinesis/template_aws_kinesis.py
+++ b/posthog/cdp/templates/aws_kinesis/template_aws_kinesis.py
@@ -3,6 +3,7 @@
template: HogFunctionTemplate = HogFunctionTemplate(
status="beta",
+ type="destination",
id="template-aws-kinesis",
name="AWS Kinesis",
description="Put data to an AWS Kinesis stream",
diff --git a/posthog/cdp/templates/braze/template_braze.py b/posthog/cdp/templates/braze/template_braze.py
index 72258f9db5cab..cd7f1ac733793 100644
--- a/posthog/cdp/templates/braze/template_braze.py
+++ b/posthog/cdp/templates/braze/template_braze.py
@@ -3,6 +3,7 @@
template: HogFunctionTemplate = HogFunctionTemplate(
status="beta",
+ type="destination",
id="template-braze",
name="Braze",
description="Send events to Braze",
diff --git a/posthog/cdp/templates/clearbit/template_clearbit.py b/posthog/cdp/templates/clearbit/template_clearbit.py
index bc9b3540bb146..415cc14255255 100644
--- a/posthog/cdp/templates/clearbit/template_clearbit.py
+++ b/posthog/cdp/templates/clearbit/template_clearbit.py
@@ -5,6 +5,7 @@
template: HogFunctionTemplate = HogFunctionTemplate(
status="alpha",
+ type="destination",
id="template-clearbit",
name="Clearbit",
description="Loads data from the Clearbit API and tracks an additional event with the enriched data if found. Once enriched, the person will not be enriched again.",
diff --git a/posthog/cdp/templates/customerio/template_customerio.py b/posthog/cdp/templates/customerio/template_customerio.py
index 25688ec9d92d8..68239b7de8e89 100644
--- a/posthog/cdp/templates/customerio/template_customerio.py
+++ b/posthog/cdp/templates/customerio/template_customerio.py
@@ -6,6 +6,7 @@
template: HogFunctionTemplate = HogFunctionTemplate(
status="beta",
+ type="destination",
id="template-customerio",
name="Customer.io",
description="Identify or track events against customers in Customer.io",
diff --git a/posthog/cdp/templates/discord/template_discord.py b/posthog/cdp/templates/discord/template_discord.py
index bca3f4604e216..fb8cb2bf50c64 100644
--- a/posthog/cdp/templates/discord/template_discord.py
+++ b/posthog/cdp/templates/discord/template_discord.py
@@ -2,6 +2,7 @@
template: HogFunctionTemplate = HogFunctionTemplate(
status="free",
+ type="destination",
id="template-discord",
name="Discord",
description="Sends a message to a discord channel",
diff --git a/posthog/cdp/templates/engage/template_engage.py b/posthog/cdp/templates/engage/template_engage.py
index 065cb3c9057bd..78d70d2e99a2f 100644
--- a/posthog/cdp/templates/engage/template_engage.py
+++ b/posthog/cdp/templates/engage/template_engage.py
@@ -4,6 +4,7 @@
template: HogFunctionTemplate = HogFunctionTemplate(
status="beta",
+ type="destination",
id="template-engage-so",
name="Engage.so",
description="Send events to Engage.so",
diff --git a/posthog/cdp/templates/gleap/template_gleap.py b/posthog/cdp/templates/gleap/template_gleap.py
index 1616c373594a6..8925c8b19b05b 100644
--- a/posthog/cdp/templates/gleap/template_gleap.py
+++ b/posthog/cdp/templates/gleap/template_gleap.py
@@ -2,6 +2,7 @@
template: HogFunctionTemplate = HogFunctionTemplate(
status="beta",
+ type="destination",
id="template-gleap",
name="Gleap",
description="Updates a contact in Gleap",
diff --git a/posthog/cdp/templates/google_ads/template_google_ads.py b/posthog/cdp/templates/google_ads/template_google_ads.py
index 1a658a9436ed2..e6496f50b6b52 100644
--- a/posthog/cdp/templates/google_ads/template_google_ads.py
+++ b/posthog/cdp/templates/google_ads/template_google_ads.py
@@ -2,6 +2,7 @@
template: HogFunctionTemplate = HogFunctionTemplate(
status="alpha",
+ type="destination",
id="template-google-ads",
name="Google Ads Conversions",
description="Send conversion events to Google Ads",
diff --git a/posthog/cdp/templates/google_cloud_storage/template_google_cloud_storage.py b/posthog/cdp/templates/google_cloud_storage/template_google_cloud_storage.py
index a24417b603b85..925b43945bc0a 100644
--- a/posthog/cdp/templates/google_cloud_storage/template_google_cloud_storage.py
+++ b/posthog/cdp/templates/google_cloud_storage/template_google_cloud_storage.py
@@ -8,6 +8,7 @@
template: HogFunctionTemplate = HogFunctionTemplate(
status="beta",
+ type="destination",
id="template-google-cloud-storage",
name="Google Cloud Storage",
description="Send data to GCS. This creates a file per event.",
diff --git a/posthog/cdp/templates/google_pubsub/template_google_pubsub.py b/posthog/cdp/templates/google_pubsub/template_google_pubsub.py
index 2c17dbb40d2e0..6ee1c56855490 100644
--- a/posthog/cdp/templates/google_pubsub/template_google_pubsub.py
+++ b/posthog/cdp/templates/google_pubsub/template_google_pubsub.py
@@ -8,6 +8,7 @@
template: HogFunctionTemplate = HogFunctionTemplate(
status="beta",
+ type="destination",
id="template-google-pubsub",
name="Google Pub/Sub",
description="Send data to a Google Pub/Sub topic",
diff --git a/posthog/cdp/templates/hog_function_template.py b/posthog/cdp/templates/hog_function_template.py
index dc41d27c87dae..f0a29fd7d2cff 100644
--- a/posthog/cdp/templates/hog_function_template.py
+++ b/posthog/cdp/templates/hog_function_template.py
@@ -26,6 +26,7 @@ class HogFunctionSubTemplate:
@dataclasses.dataclass(frozen=True)
class HogFunctionTemplate:
status: Literal["alpha", "beta", "stable", "free"]
+ type: Literal["destination", "shared", "email", "sms", "push", "broadcast", "activity", "alert"]
id: str
name: str
description: str
diff --git a/posthog/cdp/templates/hubspot/template_hubspot.py b/posthog/cdp/templates/hubspot/template_hubspot.py
index 78c0eb4dda73b..4cb08682a0e00 100644
--- a/posthog/cdp/templates/hubspot/template_hubspot.py
+++ b/posthog/cdp/templates/hubspot/template_hubspot.py
@@ -6,6 +6,7 @@
template: HogFunctionTemplate = HogFunctionTemplate(
status="beta",
+ type="destination",
id="template-hubspot",
name="Hubspot",
description="Creates a new contact in Hubspot whenever an event is triggered.",
diff --git a/posthog/cdp/templates/intercom/template_intercom.py b/posthog/cdp/templates/intercom/template_intercom.py
index 43bfcc0d80f37..069c231829231 100644
--- a/posthog/cdp/templates/intercom/template_intercom.py
+++ b/posthog/cdp/templates/intercom/template_intercom.py
@@ -5,6 +5,7 @@
template: HogFunctionTemplate = HogFunctionTemplate(
status="beta",
+ type="destination",
id="template-Intercom",
name="Intercom",
description="Send events and contact information to Intercom",
diff --git a/posthog/cdp/templates/klaviyo/template_klaviyo.py b/posthog/cdp/templates/klaviyo/template_klaviyo.py
index 805e1ad887226..6032632ae24e3 100644
--- a/posthog/cdp/templates/klaviyo/template_klaviyo.py
+++ b/posthog/cdp/templates/klaviyo/template_klaviyo.py
@@ -2,6 +2,7 @@
template_user: HogFunctionTemplate = HogFunctionTemplate(
status="beta",
+ type="destination",
id="template-klaviyo-user",
name="Klaviyo",
description="Updates a contact in Klaviyo",
@@ -143,6 +144,7 @@
template_event: HogFunctionTemplate = HogFunctionTemplate(
status="beta",
+ type="destination",
id="template-klaviyo-event",
name="Klaviyo",
description="Send events to Klaviyo",
diff --git a/posthog/cdp/templates/knock/template_knock.py b/posthog/cdp/templates/knock/template_knock.py
index 235f7d38a60ed..68c5bcccff40d 100644
--- a/posthog/cdp/templates/knock/template_knock.py
+++ b/posthog/cdp/templates/knock/template_knock.py
@@ -2,6 +2,7 @@
template: HogFunctionTemplate = HogFunctionTemplate(
status="beta",
+ type="destination",
id="template-knock",
name="Knock",
description="Send events to Knock",
diff --git a/posthog/cdp/templates/loops/template_loops.py b/posthog/cdp/templates/loops/template_loops.py
index 114bd3a6e98e5..6052eed186b36 100644
--- a/posthog/cdp/templates/loops/template_loops.py
+++ b/posthog/cdp/templates/loops/template_loops.py
@@ -4,6 +4,7 @@
template: HogFunctionTemplate = HogFunctionTemplate(
status="beta",
+ type="destination",
id="template-loops",
name="Loops",
description="Send events to Loops",
diff --git a/posthog/cdp/templates/mailchimp/template_mailchimp.py b/posthog/cdp/templates/mailchimp/template_mailchimp.py
index e12eab6dfc7d9..4ed3465e21e82 100644
--- a/posthog/cdp/templates/mailchimp/template_mailchimp.py
+++ b/posthog/cdp/templates/mailchimp/template_mailchimp.py
@@ -2,6 +2,7 @@
template: HogFunctionTemplate = HogFunctionTemplate(
status="beta",
+ type="destination",
id="template-mailchimp",
name="Mailchimp",
description="Updates a contact in Mailchimp and subscribes new ones.",
diff --git a/posthog/cdp/templates/mailgun/template_mailgun.py b/posthog/cdp/templates/mailgun/template_mailgun.py
index e220bf6535dce..9f7a91eea7a17 100644
--- a/posthog/cdp/templates/mailgun/template_mailgun.py
+++ b/posthog/cdp/templates/mailgun/template_mailgun.py
@@ -6,6 +6,7 @@
template_mailgun_send_email: HogFunctionTemplate = HogFunctionTemplate(
status="alpha",
+ type="destination",
id="template-mailgun-send-email",
name="Mailgun",
description="Send emails using the Mailgun HTTP API",
diff --git a/posthog/cdp/templates/mailjet/template_mailjet.py b/posthog/cdp/templates/mailjet/template_mailjet.py
index c35a17baf9c95..a089372daefb8 100644
--- a/posthog/cdp/templates/mailjet/template_mailjet.py
+++ b/posthog/cdp/templates/mailjet/template_mailjet.py
@@ -31,6 +31,7 @@
template_create_contact: HogFunctionTemplate = HogFunctionTemplate(
status="beta",
+ type="destination",
id="template-mailjet-create-contact",
name="Mailjet",
description="Add contacts to Mailjet",
@@ -81,6 +82,7 @@
template_update_contact_list: HogFunctionTemplate = HogFunctionTemplate(
status="beta",
+ type="destination",
id="template-mailjet-update-contact-list",
name="Mailjet",
description="Update a Mailjet contact list",
diff --git a/posthog/cdp/templates/meta_ads/template_meta_ads.py b/posthog/cdp/templates/meta_ads/template_meta_ads.py
index ad5b2fed1ff7c..146166b14946d 100644
--- a/posthog/cdp/templates/meta_ads/template_meta_ads.py
+++ b/posthog/cdp/templates/meta_ads/template_meta_ads.py
@@ -2,6 +2,7 @@
template: HogFunctionTemplate = HogFunctionTemplate(
status="alpha",
+ type="destination",
id="template-meta-ads",
name="Meta Ads Conversions",
description="Send conversion events to Meta Ads",
diff --git a/posthog/cdp/templates/microsoft_teams/template_microsoft_teams.py b/posthog/cdp/templates/microsoft_teams/template_microsoft_teams.py
index da8a1506f9d61..c1ce7afc5a909 100644
--- a/posthog/cdp/templates/microsoft_teams/template_microsoft_teams.py
+++ b/posthog/cdp/templates/microsoft_teams/template_microsoft_teams.py
@@ -2,6 +2,7 @@
template: HogFunctionTemplate = HogFunctionTemplate(
status="free",
+ type="destination",
id="template-microsoft-teams",
name="Microsoft Teams",
description="Sends a message to a Microsoft Teams channel",
diff --git a/posthog/cdp/templates/posthog/template_posthog.py b/posthog/cdp/templates/posthog/template_posthog.py
index a06876d382cee..fcd76879b8078 100644
--- a/posthog/cdp/templates/posthog/template_posthog.py
+++ b/posthog/cdp/templates/posthog/template_posthog.py
@@ -6,6 +6,7 @@
template: HogFunctionTemplate = HogFunctionTemplate(
status="beta",
+ type="destination",
id="template-posthog-replicator",
name="PostHog",
description="Send a copy of the incoming data in realtime to another PostHog instance",
diff --git a/posthog/cdp/templates/rudderstack/template_rudderstack.py b/posthog/cdp/templates/rudderstack/template_rudderstack.py
index b2c19d8fb7e68..fbc3ef2f65371 100644
--- a/posthog/cdp/templates/rudderstack/template_rudderstack.py
+++ b/posthog/cdp/templates/rudderstack/template_rudderstack.py
@@ -5,6 +5,7 @@
template: HogFunctionTemplate = HogFunctionTemplate(
status="alpha",
+ type="destination",
id="template-rudderstack",
name="RudderStack",
description="Send data to RudderStack",
diff --git a/posthog/cdp/templates/salesforce/template_salesforce.py b/posthog/cdp/templates/salesforce/template_salesforce.py
index 1b26558b3d48c..eedfd9980efb1 100644
--- a/posthog/cdp/templates/salesforce/template_salesforce.py
+++ b/posthog/cdp/templates/salesforce/template_salesforce.py
@@ -22,6 +22,7 @@
template_create: HogFunctionTemplate = HogFunctionTemplate(
status="alpha",
+ type="destination",
id="template-salesforce-create",
name="Salesforce",
description="Create objects in Salesforce",
@@ -108,6 +109,7 @@
template_update: HogFunctionTemplate = HogFunctionTemplate(
status="alpha",
+ type="destination",
id="template-salesforce-update",
name="Salesforce",
description="Update objects in Salesforce",
diff --git a/posthog/cdp/templates/sendgrid/template_sendgrid.py b/posthog/cdp/templates/sendgrid/template_sendgrid.py
index 96a0d07ccdf2e..c7a17ebc24516 100644
--- a/posthog/cdp/templates/sendgrid/template_sendgrid.py
+++ b/posthog/cdp/templates/sendgrid/template_sendgrid.py
@@ -7,6 +7,7 @@
template: HogFunctionTemplate = HogFunctionTemplate(
status="beta",
+ type="destination",
id="template-sendgrid",
name="Sendgrid",
description="Update marketing contacts in Sendgrid",
diff --git a/posthog/cdp/templates/slack/template_slack.py b/posthog/cdp/templates/slack/template_slack.py
index 970848e8de3e6..b3079485176c6 100644
--- a/posthog/cdp/templates/slack/template_slack.py
+++ b/posthog/cdp/templates/slack/template_slack.py
@@ -2,6 +2,7 @@
template: HogFunctionTemplate = HogFunctionTemplate(
status="free",
+ type="destination",
id="template-slack",
name="Slack",
description="Sends a message to a slack channel",
diff --git a/posthog/cdp/templates/webhook/template_webhook.py b/posthog/cdp/templates/webhook/template_webhook.py
index 23ee39cb542cb..f27822c5e0c24 100644
--- a/posthog/cdp/templates/webhook/template_webhook.py
+++ b/posthog/cdp/templates/webhook/template_webhook.py
@@ -3,6 +3,7 @@
template: HogFunctionTemplate = HogFunctionTemplate(
status="beta",
+ type="destination",
id="template-webhook",
name="HTTP Webhook",
description="Sends a webhook templated by the incoming event data",
diff --git a/posthog/cdp/templates/zapier/template_zapier.py b/posthog/cdp/templates/zapier/template_zapier.py
index 973fa2f276509..92d5de5916fbf 100644
--- a/posthog/cdp/templates/zapier/template_zapier.py
+++ b/posthog/cdp/templates/zapier/template_zapier.py
@@ -3,6 +3,7 @@
template: HogFunctionTemplate = HogFunctionTemplate(
status="free",
+ type="destination",
id="template-zapier",
name="Zapier",
description="Sends a webhook templated by the incoming event data",
diff --git a/posthog/cdp/templates/zendesk/template_zendesk.py b/posthog/cdp/templates/zendesk/template_zendesk.py
index 79184d3f607f0..3604c5d9a3964 100644
--- a/posthog/cdp/templates/zendesk/template_zendesk.py
+++ b/posthog/cdp/templates/zendesk/template_zendesk.py
@@ -4,6 +4,7 @@
template: HogFunctionTemplate = HogFunctionTemplate(
status="beta",
+ type="destination",
id="template-zendesk",
name="Zendesk",
description="Update contacts in Zendesk",
diff --git a/posthog/hogql/transforms/test/__snapshots__/test_in_cohort.ambr b/posthog/hogql/transforms/test/__snapshots__/test_in_cohort.ambr
index f018e96ef067a..06e46e0ca30b0 100644
--- a/posthog/hogql/transforms/test/__snapshots__/test_in_cohort.ambr
+++ b/posthog/hogql/transforms/test/__snapshots__/test_in_cohort.ambr
@@ -31,7 +31,7 @@
FROM events LEFT JOIN (
SELECT person_static_cohort.person_id AS cohort_person_id, 1 AS matched, person_static_cohort.cohort_id AS cohort_id
FROM person_static_cohort
- WHERE and(equals(person_static_cohort.team_id, 420), in(person_static_cohort.cohort_id, [4]))) AS __in_cohort ON equals(__in_cohort.cohort_person_id, events.person_id)
+ WHERE and(equals(person_static_cohort.team_id, 420), in(person_static_cohort.cohort_id, [6]))) AS __in_cohort ON equals(__in_cohort.cohort_person_id, events.person_id)
WHERE and(equals(events.team_id, 420), 1, ifNull(equals(__in_cohort.matched, 1), 0))
LIMIT 100
SETTINGS readonly=2, max_execution_time=60, allow_experimental_object_type=1, format_csv_allow_double_quotes=0, max_ast_elements=4000000, max_expanded_ast_elements=4000000, max_bytes_before_external_group_by=0
@@ -42,7 +42,7 @@
FROM events LEFT JOIN (
SELECT person_id AS cohort_person_id, 1 AS matched, cohort_id
FROM static_cohort_people
- WHERE in(cohort_id, [4])) AS __in_cohort ON equals(__in_cohort.cohort_person_id, person_id)
+ WHERE in(cohort_id, [6])) AS __in_cohort ON equals(__in_cohort.cohort_person_id, person_id)
WHERE and(1, equals(__in_cohort.matched, 1))
LIMIT 100
'''
@@ -55,7 +55,7 @@
FROM events LEFT JOIN (
SELECT person_static_cohort.person_id AS cohort_person_id, 1 AS matched, person_static_cohort.cohort_id AS cohort_id
FROM person_static_cohort
- WHERE and(equals(person_static_cohort.team_id, 420), in(person_static_cohort.cohort_id, [5]))) AS __in_cohort ON equals(__in_cohort.cohort_person_id, events.person_id)
+ WHERE and(equals(person_static_cohort.team_id, 420), in(person_static_cohort.cohort_id, [7]))) AS __in_cohort ON equals(__in_cohort.cohort_person_id, events.person_id)
WHERE and(equals(events.team_id, 420), 1, ifNull(equals(__in_cohort.matched, 1), 0))
LIMIT 100
SETTINGS readonly=2, max_execution_time=60, allow_experimental_object_type=1, format_csv_allow_double_quotes=0, max_ast_elements=4000000, max_expanded_ast_elements=4000000, max_bytes_before_external_group_by=0
@@ -66,7 +66,7 @@
FROM events LEFT JOIN (
SELECT person_id AS cohort_person_id, 1 AS matched, cohort_id
FROM static_cohort_people
- WHERE in(cohort_id, [5])) AS __in_cohort ON equals(__in_cohort.cohort_person_id, person_id)
+ WHERE in(cohort_id, [7])) AS __in_cohort ON equals(__in_cohort.cohort_person_id, person_id)
WHERE and(1, equals(__in_cohort.matched, 1))
LIMIT 100
'''
diff --git a/posthog/hogql_queries/insights/trends/test/__snapshots__/test_trends.ambr b/posthog/hogql_queries/insights/trends/test/__snapshots__/test_trends.ambr
index 6027f7ca7bb42..4ae57feb8cb96 100644
--- a/posthog/hogql_queries/insights/trends/test/__snapshots__/test_trends.ambr
+++ b/posthog/hogql_queries/insights/trends/test/__snapshots__/test_trends.ambr
@@ -851,14 +851,49 @@
# ---
# name: TestTrends.test_dau_with_breakdown_filtering_with_sampling.1
'''
- /* celery:posthog.tasks.tasks.sync_insight_caching_state */
- SELECT team_id,
- date_diff('second', max(timestamp), now()) AS age
- FROM events
- WHERE timestamp > date_sub(DAY, 3, now())
- AND timestamp < now()
- GROUP BY team_id
- ORDER BY age;
+ SELECT groupArray(1)(date)[1] AS date,
+ arrayFold((acc, x) -> arrayMap(i -> plus(acc[i], x[i]), range(1, plus(length(date), 1))), groupArray(ifNull(total, 0)), arrayWithConstant(length(date), reinterpretAsFloat64(0))) AS total,
+ if(ifNull(ifNull(greaterOrEquals(row_number, 25), 0), 0), '$$_posthog_breakdown_other_$$', breakdown_value) AS breakdown_value
+ FROM
+ (SELECT arrayMap(number -> plus(toStartOfDay(assumeNotNull(parseDateTime64BestEffortOrNull('2019-12-28 00:00:00', 6, 'UTC'))), toIntervalDay(number)), range(0, plus(coalesce(dateDiff('day', toStartOfDay(assumeNotNull(parseDateTime64BestEffortOrNull('2019-12-28 00:00:00', 6, 'UTC'))), toStartOfDay(assumeNotNull(parseDateTime64BestEffortOrNull('2020-01-04 23:59:59', 6, 'UTC'))))), 1))) AS date,
+ arrayMap(_match_date -> arraySum(arraySlice(groupArray(ifNull(count, 0)), indexOf(groupArray(day_start) AS _days_for_count, _match_date) AS _index, plus(minus(arrayLastIndex(x -> ifNull(equals(x, _match_date), isNull(x)
+ and isNull(_match_date)), _days_for_count), _index), 1))), date) AS total,
+ breakdown_value AS breakdown_value,
+ rowNumberInAllBlocks() AS row_number
+ FROM
+ (SELECT sum(total) AS count,
+ day_start AS day_start,
+ breakdown_value AS breakdown_value
+ FROM
+ (SELECT count(DISTINCT if(not(empty(e__override.distinct_id)), e__override.person_id, e.person_id)) AS total,
+ toStartOfDay(toTimeZone(e.timestamp, 'UTC')) AS day_start,
+ ifNull(nullIf(toString(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(e.properties, '$some_property'), ''), 'null'), '^"|"$', '')), ''), '$$_posthog_breakdown_null_$$') AS breakdown_value
+ FROM events AS e SAMPLE 1.0
+ LEFT OUTER JOIN
+ (SELECT argMax(person_distinct_id_overrides.person_id, person_distinct_id_overrides.version) AS person_id,
+ person_distinct_id_overrides.distinct_id AS distinct_id
+ FROM person_distinct_id_overrides
+ WHERE equals(person_distinct_id_overrides.team_id, 2)
+ GROUP BY person_distinct_id_overrides.distinct_id
+ HAVING ifNull(equals(argMax(person_distinct_id_overrides.is_deleted, person_distinct_id_overrides.version), 0), 0) SETTINGS optimize_aggregation_in_order=1) AS e__override ON equals(e.distinct_id, e__override.distinct_id)
+ WHERE and(equals(e.team_id, 2), greaterOrEquals(toTimeZone(e.timestamp, 'UTC'), toStartOfDay(assumeNotNull(parseDateTime64BestEffortOrNull('2019-12-28 00:00:00', 6, 'UTC')))), lessOrEquals(toTimeZone(e.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2020-01-04 23:59:59', 6, 'UTC'))), equals(e.event, 'sign up'))
+ GROUP BY day_start,
+ breakdown_value)
+ GROUP BY day_start,
+ breakdown_value
+ ORDER BY day_start ASC, breakdown_value ASC)
+ GROUP BY breakdown_value
+ ORDER BY if(ifNull(equals(breakdown_value, '$$_posthog_breakdown_other_$$'), 0), 2, if(ifNull(equals(breakdown_value, '$$_posthog_breakdown_null_$$'), 0), 1, 0)) ASC, arraySum(total) DESC, breakdown_value ASC)
+ WHERE isNotNull(breakdown_value)
+ GROUP BY breakdown_value
+ ORDER BY if(ifNull(equals(breakdown_value, '$$_posthog_breakdown_other_$$'), 0), 2, if(ifNull(equals(breakdown_value, '$$_posthog_breakdown_null_$$'), 0), 1, 0)) ASC, arraySum(total) DESC, breakdown_value ASC
+ LIMIT 50000 SETTINGS readonly=2,
+ max_execution_time=60,
+ allow_experimental_object_type=1,
+ format_csv_allow_double_quotes=0,
+ max_ast_elements=4000000,
+ max_expanded_ast_elements=4000000,
+ max_bytes_before_external_group_by=0
'''
# ---
# name: TestTrends.test_dau_with_breakdown_filtering_with_sampling.10
@@ -1075,38 +1110,143 @@
# ---
# name: TestTrends.test_dau_with_breakdown_filtering_with_sampling.2
'''
- /* celery:posthog.tasks.tasks.sync_insight_caching_state */
- SELECT team_id,
- date_diff('second', max(timestamp), now()) AS age
- FROM events
- WHERE timestamp > date_sub(DAY, 3, now())
- AND timestamp < now()
- GROUP BY team_id
- ORDER BY age;
+ SELECT groupArray(1)(date)[1] AS date,
+ arrayFold((acc, x) -> arrayMap(i -> plus(acc[i], x[i]), range(1, plus(length(date), 1))), groupArray(ifNull(total, 0)), arrayWithConstant(length(date), reinterpretAsFloat64(0))) AS total,
+ if(ifNull(ifNull(greaterOrEquals(row_number, 25), 0), 0), '$$_posthog_breakdown_other_$$', breakdown_value) AS breakdown_value
+ FROM
+ (SELECT arrayMap(number -> plus(toStartOfDay(assumeNotNull(parseDateTime64BestEffortOrNull('2019-12-28 00:00:00', 6, 'UTC'))), toIntervalDay(number)), range(0, plus(coalesce(dateDiff('day', toStartOfDay(assumeNotNull(parseDateTime64BestEffortOrNull('2019-12-28 00:00:00', 6, 'UTC'))), toStartOfDay(assumeNotNull(parseDateTime64BestEffortOrNull('2020-01-04 23:59:59', 6, 'UTC'))))), 1))) AS date,
+ arrayMap(_match_date -> arraySum(arraySlice(groupArray(ifNull(count, 0)), indexOf(groupArray(day_start) AS _days_for_count, _match_date) AS _index, plus(minus(arrayLastIndex(x -> ifNull(equals(x, _match_date), isNull(x)
+ and isNull(_match_date)), _days_for_count), _index), 1))), date) AS total,
+ breakdown_value AS breakdown_value,
+ rowNumberInAllBlocks() AS row_number
+ FROM
+ (SELECT sum(total) AS count,
+ day_start AS day_start,
+ breakdown_value AS breakdown_value
+ FROM
+ (SELECT count(DISTINCT if(not(empty(e__override.distinct_id)), e__override.person_id, e.person_id)) AS total,
+ toStartOfDay(toTimeZone(e.timestamp, 'UTC')) AS day_start,
+ ifNull(nullIf(toString(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(e.properties, '$some_property'), ''), 'null'), '^"|"$', '')), ''), '$$_posthog_breakdown_null_$$') AS breakdown_value
+ FROM events AS e SAMPLE 1.0
+ LEFT OUTER JOIN
+ (SELECT argMax(person_distinct_id_overrides.person_id, person_distinct_id_overrides.version) AS person_id,
+ person_distinct_id_overrides.distinct_id AS distinct_id
+ FROM person_distinct_id_overrides
+ WHERE equals(person_distinct_id_overrides.team_id, 2)
+ GROUP BY person_distinct_id_overrides.distinct_id
+ HAVING ifNull(equals(argMax(person_distinct_id_overrides.is_deleted, person_distinct_id_overrides.version), 0), 0) SETTINGS optimize_aggregation_in_order=1) AS e__override ON equals(e.distinct_id, e__override.distinct_id)
+ WHERE and(equals(e.team_id, 2), greaterOrEquals(toTimeZone(e.timestamp, 'UTC'), toStartOfDay(assumeNotNull(parseDateTime64BestEffortOrNull('2019-12-28 00:00:00', 6, 'UTC')))), lessOrEquals(toTimeZone(e.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2020-01-04 23:59:59', 6, 'UTC'))), equals(e.event, 'sign up'))
+ GROUP BY day_start,
+ breakdown_value)
+ GROUP BY day_start,
+ breakdown_value
+ ORDER BY day_start ASC, breakdown_value ASC)
+ GROUP BY breakdown_value
+ ORDER BY if(ifNull(equals(breakdown_value, '$$_posthog_breakdown_other_$$'), 0), 2, if(ifNull(equals(breakdown_value, '$$_posthog_breakdown_null_$$'), 0), 1, 0)) ASC, arraySum(total) DESC, breakdown_value ASC)
+ WHERE isNotNull(breakdown_value)
+ GROUP BY breakdown_value
+ ORDER BY if(ifNull(equals(breakdown_value, '$$_posthog_breakdown_other_$$'), 0), 2, if(ifNull(equals(breakdown_value, '$$_posthog_breakdown_null_$$'), 0), 1, 0)) ASC, arraySum(total) DESC, breakdown_value ASC
+ LIMIT 50000 SETTINGS readonly=2,
+ max_execution_time=60,
+ allow_experimental_object_type=1,
+ format_csv_allow_double_quotes=0,
+ max_ast_elements=4000000,
+ max_expanded_ast_elements=4000000,
+ max_bytes_before_external_group_by=0
'''
# ---
# name: TestTrends.test_dau_with_breakdown_filtering_with_sampling.3
'''
- /* celery:posthog.tasks.tasks.sync_insight_caching_state */
- SELECT team_id,
- date_diff('second', max(timestamp), now()) AS age
- FROM events
- WHERE timestamp > date_sub(DAY, 3, now())
- AND timestamp < now()
- GROUP BY team_id
- ORDER BY age;
+ SELECT groupArray(1)(date)[1] AS date,
+ arrayFold((acc, x) -> arrayMap(i -> plus(acc[i], x[i]), range(1, plus(length(date), 1))), groupArray(ifNull(total, 0)), arrayWithConstant(length(date), reinterpretAsFloat64(0))) AS total,
+ arrayMap(i -> if(ifNull(ifNull(greaterOrEquals(row_number, 25), 0), 0), '$$_posthog_breakdown_other_$$', i), breakdown_value) AS breakdown_value
+ FROM
+ (SELECT arrayMap(number -> plus(toStartOfDay(assumeNotNull(parseDateTime64BestEffortOrNull('2019-12-28 00:00:00', 6, 'UTC'))), toIntervalDay(number)), range(0, plus(coalesce(dateDiff('day', toStartOfDay(assumeNotNull(parseDateTime64BestEffortOrNull('2019-12-28 00:00:00', 6, 'UTC'))), toStartOfDay(assumeNotNull(parseDateTime64BestEffortOrNull('2020-01-04 23:59:59', 6, 'UTC'))))), 1))) AS date,
+ arrayMap(_match_date -> arraySum(arraySlice(groupArray(ifNull(count, 0)), indexOf(groupArray(day_start) AS _days_for_count, _match_date) AS _index, plus(minus(arrayLastIndex(x -> ifNull(equals(x, _match_date), isNull(x)
+ and isNull(_match_date)), _days_for_count), _index), 1))), date) AS total,
+ breakdown_value AS breakdown_value,
+ rowNumberInAllBlocks() AS row_number
+ FROM
+ (SELECT sum(total) AS count,
+ day_start AS day_start,
+ [ifNull(toString(breakdown_value_1), '$$_posthog_breakdown_null_$$')] AS breakdown_value
+ FROM
+ (SELECT count(DISTINCT if(not(empty(e__override.distinct_id)), e__override.person_id, e.person_id)) AS total,
+ toStartOfDay(toTimeZone(e.timestamp, 'UTC')) AS day_start,
+ ifNull(nullIf(toString(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(e.properties, '$some_property'), ''), 'null'), '^"|"$', '')), ''), '$$_posthog_breakdown_null_$$') AS breakdown_value_1
+ FROM events AS e SAMPLE 1.0
+ LEFT OUTER JOIN
+ (SELECT argMax(person_distinct_id_overrides.person_id, person_distinct_id_overrides.version) AS person_id,
+ person_distinct_id_overrides.distinct_id AS distinct_id
+ FROM person_distinct_id_overrides
+ WHERE equals(person_distinct_id_overrides.team_id, 2)
+ GROUP BY person_distinct_id_overrides.distinct_id
+ HAVING ifNull(equals(argMax(person_distinct_id_overrides.is_deleted, person_distinct_id_overrides.version), 0), 0) SETTINGS optimize_aggregation_in_order=1) AS e__override ON equals(e.distinct_id, e__override.distinct_id)
+ WHERE and(equals(e.team_id, 2), greaterOrEquals(toTimeZone(e.timestamp, 'UTC'), toStartOfDay(assumeNotNull(parseDateTime64BestEffortOrNull('2019-12-28 00:00:00', 6, 'UTC')))), lessOrEquals(toTimeZone(e.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2020-01-04 23:59:59', 6, 'UTC'))), equals(e.event, 'sign up'))
+ GROUP BY day_start,
+ breakdown_value_1)
+ GROUP BY day_start,
+ breakdown_value_1
+ ORDER BY day_start ASC, breakdown_value ASC)
+ GROUP BY breakdown_value
+ ORDER BY if(has(breakdown_value, '$$_posthog_breakdown_other_$$'), 2, if(has(breakdown_value, '$$_posthog_breakdown_null_$$'), 1, 0)) ASC, arraySum(total) DESC, breakdown_value ASC)
+ WHERE arrayExists(x -> isNotNull(x), breakdown_value)
+ GROUP BY breakdown_value
+ ORDER BY if(has(breakdown_value, '$$_posthog_breakdown_other_$$'), 2, if(has(breakdown_value, '$$_posthog_breakdown_null_$$'), 1, 0)) ASC, arraySum(total) DESC, breakdown_value ASC
+ LIMIT 50000 SETTINGS readonly=2,
+ max_execution_time=60,
+ allow_experimental_object_type=1,
+ format_csv_allow_double_quotes=0,
+ max_ast_elements=4000000,
+ max_expanded_ast_elements=4000000,
+ max_bytes_before_external_group_by=0
'''
# ---
# name: TestTrends.test_dau_with_breakdown_filtering_with_sampling.4
'''
- /* celery:posthog.tasks.tasks.sync_insight_caching_state */
- SELECT team_id,
- date_diff('second', max(timestamp), now()) AS age
- FROM events
- WHERE timestamp > date_sub(DAY, 3, now())
- AND timestamp < now()
- GROUP BY team_id
- ORDER BY age;
+ SELECT groupArray(1)(date)[1] AS date,
+ arrayFold((acc, x) -> arrayMap(i -> plus(acc[i], x[i]), range(1, plus(length(date), 1))), groupArray(ifNull(total, 0)), arrayWithConstant(length(date), reinterpretAsFloat64(0))) AS total,
+ arrayMap(i -> if(ifNull(ifNull(greaterOrEquals(row_number, 25), 0), 0), '$$_posthog_breakdown_other_$$', i), breakdown_value) AS breakdown_value
+ FROM
+ (SELECT arrayMap(number -> plus(toStartOfDay(assumeNotNull(parseDateTime64BestEffortOrNull('2019-12-28 00:00:00', 6, 'UTC'))), toIntervalDay(number)), range(0, plus(coalesce(dateDiff('day', toStartOfDay(assumeNotNull(parseDateTime64BestEffortOrNull('2019-12-28 00:00:00', 6, 'UTC'))), toStartOfDay(assumeNotNull(parseDateTime64BestEffortOrNull('2020-01-04 23:59:59', 6, 'UTC'))))), 1))) AS date,
+ arrayMap(_match_date -> arraySum(arraySlice(groupArray(ifNull(count, 0)), indexOf(groupArray(day_start) AS _days_for_count, _match_date) AS _index, plus(minus(arrayLastIndex(x -> ifNull(equals(x, _match_date), isNull(x)
+ and isNull(_match_date)), _days_for_count), _index), 1))), date) AS total,
+ breakdown_value AS breakdown_value,
+ rowNumberInAllBlocks() AS row_number
+ FROM
+ (SELECT sum(total) AS count,
+ day_start AS day_start,
+ [ifNull(toString(breakdown_value_1), '$$_posthog_breakdown_null_$$')] AS breakdown_value
+ FROM
+ (SELECT count(DISTINCT if(not(empty(e__override.distinct_id)), e__override.person_id, e.person_id)) AS total,
+ toStartOfDay(toTimeZone(e.timestamp, 'UTC')) AS day_start,
+ ifNull(nullIf(toString(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(e.properties, '$some_property'), ''), 'null'), '^"|"$', '')), ''), '$$_posthog_breakdown_null_$$') AS breakdown_value_1
+ FROM events AS e SAMPLE 1.0
+ LEFT OUTER JOIN
+ (SELECT argMax(person_distinct_id_overrides.person_id, person_distinct_id_overrides.version) AS person_id,
+ person_distinct_id_overrides.distinct_id AS distinct_id
+ FROM person_distinct_id_overrides
+ WHERE equals(person_distinct_id_overrides.team_id, 2)
+ GROUP BY person_distinct_id_overrides.distinct_id
+ HAVING ifNull(equals(argMax(person_distinct_id_overrides.is_deleted, person_distinct_id_overrides.version), 0), 0) SETTINGS optimize_aggregation_in_order=1) AS e__override ON equals(e.distinct_id, e__override.distinct_id)
+ WHERE and(equals(e.team_id, 2), greaterOrEquals(toTimeZone(e.timestamp, 'UTC'), toStartOfDay(assumeNotNull(parseDateTime64BestEffortOrNull('2019-12-28 00:00:00', 6, 'UTC')))), lessOrEquals(toTimeZone(e.timestamp, 'UTC'), assumeNotNull(parseDateTime64BestEffortOrNull('2020-01-04 23:59:59', 6, 'UTC'))), equals(e.event, 'sign up'))
+ GROUP BY day_start,
+ breakdown_value_1)
+ GROUP BY day_start,
+ breakdown_value_1
+ ORDER BY day_start ASC, breakdown_value ASC)
+ GROUP BY breakdown_value
+ ORDER BY if(has(breakdown_value, '$$_posthog_breakdown_other_$$'), 2, if(has(breakdown_value, '$$_posthog_breakdown_null_$$'), 1, 0)) ASC, arraySum(total) DESC, breakdown_value ASC)
+ WHERE arrayExists(x -> isNotNull(x), breakdown_value)
+ GROUP BY breakdown_value
+ ORDER BY if(has(breakdown_value, '$$_posthog_breakdown_other_$$'), 2, if(has(breakdown_value, '$$_posthog_breakdown_null_$$'), 1, 0)) ASC, arraySum(total) DESC, breakdown_value ASC
+ LIMIT 50000 SETTINGS readonly=2,
+ max_execution_time=60,
+ allow_experimental_object_type=1,
+ format_csv_allow_double_quotes=0,
+ max_ast_elements=4000000,
+ max_expanded_ast_elements=4000000,
+ max_bytes_before_external_group_by=0
'''
# ---
# name: TestTrends.test_dau_with_breakdown_filtering_with_sampling.5
diff --git a/posthog/migrations/0499_hog_function_type.py b/posthog/migrations/0499_hog_function_type.py
new file mode 100644
index 0000000000000..07e05b7185df7
--- /dev/null
+++ b/posthog/migrations/0499_hog_function_type.py
@@ -0,0 +1,31 @@
+# Generated by Django 4.2.15 on 2024-10-24 11:05
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ("posthog", "0498_errortrackingissuefingerprint_and_more"),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name="hogfunction",
+ name="type",
+ field=models.CharField(
+ blank=True,
+ choices=[
+ ("destination", "Destination"),
+ ("email", "Email"),
+ ("sms", "Sms"),
+ ("push", "Push"),
+ ("activity", "Activity"),
+ ("alert", "Alert"),
+ ("broadcast", "Broadcast"),
+ ],
+ max_length=24,
+ null=True,
+ ),
+ ),
+ migrations.RunSQL("UPDATE posthog_hogfunction SET type = 'destination' WHERE type IS NULL", "SELECT 1"),
+ ]
diff --git a/posthog/models/hog_functions/hog_function.py b/posthog/models/hog_functions/hog_function.py
index d9922a83d47f8..c2a1dfa63b042 100644
--- a/posthog/models/hog_functions/hog_function.py
+++ b/posthog/models/hog_functions/hog_function.py
@@ -31,6 +31,19 @@ class HogFunctionState(enum.Enum):
DISABLED_PERMANENTLY = 4
+class HogFunctionType(models.TextChoices):
+ DESTINATION = "destination"
+ EMAIL = "email"
+ SMS = "sms"
+ PUSH = "push"
+ ACTIVITY = "activity"
+ ALERT = "alert"
+ BROADCAST = "broadcast"
+
+
+TYPES_THAT_RELOAD_PLUGIN_SERVER = (HogFunctionType.DESTINATION, HogFunctionType.EMAIL)
+
+
class HogFunction(UUIDModel):
team = models.ForeignKey("Team", on_delete=models.CASCADE)
name = models.CharField(max_length=400, null=True, blank=True)
@@ -40,6 +53,7 @@ class HogFunction(UUIDModel):
deleted = models.BooleanField(default=False)
updated_at = models.DateTimeField(auto_now=True)
enabled = models.BooleanField(default=False)
+ type = models.CharField(max_length=24, choices=HogFunctionType.choices, null=True, blank=True)
icon_url = models.TextField(null=True, blank=True)
hog = models.TextField()
@@ -69,11 +83,6 @@ def filter_action_ids(self) -> list[int]:
_status: Optional[dict] = None
- @property
- def type(self) -> str:
- # Used in activity logs
- return "destination"
-
@property
def status(self) -> dict:
if not self.enabled:
@@ -145,7 +154,8 @@ def __str__(self):
@receiver(post_save, sender=HogFunction)
def hog_function_saved(sender, instance: HogFunction, created, **kwargs):
- reload_hog_functions_on_workers(team_id=instance.team_id, hog_function_ids=[str(instance.id)])
+ if instance.type is None or instance.type in TYPES_THAT_RELOAD_PLUGIN_SERVER:
+ reload_hog_functions_on_workers(team_id=instance.team_id, hog_function_ids=[str(instance.id)])
@receiver(post_save, sender=Action)