From 043c3e2a535c7de19dc7a7ed8477fe1ef5659193 Mon Sep 17 00:00:00 2001
From: Marius Andra
Date: Thu, 24 Oct 2024 13:57:36 +0200
Subject: [PATCH 01/20] feat(messaging): different types of hog functions
---
frontend/src/lib/api.ts | 14 +++++---
.../src/scenes/actions/ActionHogFunctions.tsx | 1 +
.../definition/DefinitionView.tsx | 1 +
.../EarlyAccessFeature.tsx | 1 +
.../destinations/destinationsLogic.tsx | 2 +-
.../hogfunctions/HogFunctionConfiguration.tsx | 7 +++-
.../hogFunctionConfigurationLogic.test.ts | 1 +
.../hogFunctionConfigurationLogic.tsx | 12 +++++--
.../hogfunctions/list/LinkedHogFunctions.tsx | 6 +++-
.../list/hogFunctionListLogic.tsx | 6 ++--
.../list/hogFunctionTemplateListLogic.tsx | 9 +++---
frontend/src/scenes/surveys/SurveyView.tsx | 1 +
frontend/src/scenes/surveys/Surveys.tsx | 1 +
frontend/src/types.ts | 13 +++++++-
latest_migrations.manifest | 2 +-
plugin-server/src/cdp/cdp-consumers.ts | 2 +-
plugin-server/src/cdp/hog-executor.ts | 2 +-
plugin-server/src/cdp/hog-function-manager.ts | 19 ++++++++---
posthog/api/hog_function.py | 11 +++++--
posthog/api/hog_function_template.py | 4 +--
.../activecampaign/template_activecampaign.py | 1 +
.../templates/airtable/template_airtable.py | 1 +
posthog/cdp/templates/attio/template_attio.py | 1 +
posthog/cdp/templates/avo/template_avo.py | 1 +
.../aws_kinesis/template_aws_kinesis.py | 1 +
posthog/cdp/templates/braze/template_braze.py | 1 +
.../templates/clearbit/template_clearbit.py | 1 +
.../customerio/template_customerio.py | 1 +
.../cdp/templates/discord/template_discord.py | 1 +
.../cdp/templates/engage/template_engage.py | 1 +
posthog/cdp/templates/gleap/template_gleap.py | 1 +
.../google_ads/template_google_ads.py | 1 +
.../template_google_cloud_storage.py | 1 +
.../google_pubsub/template_google_pubsub.py | 1 +
.../cdp/templates/hog_function_template.py | 1 +
.../cdp/templates/hubspot/template_hubspot.py | 1 +
.../templates/intercom/template_intercom.py | 1 +
.../cdp/templates/klaviyo/template_klaviyo.py | 2 ++
posthog/cdp/templates/knock/template_knock.py | 1 +
posthog/cdp/templates/loops/template_loops.py | 1 +
.../cdp/templates/mailgun/template_mailgun.py | 1 +
.../cdp/templates/mailjet/template_mailjet.py | 2 ++
.../templates/meta_ads/template_meta_ads.py | 1 +
.../template_microsoft_teams.py | 1 +
.../cdp/templates/posthog/template_posthog.py | 1 +
.../rudderstack/template_rudderstack.py | 1 +
.../salesforce/template_salesforce.py | 2 ++
.../templates/sendgrid/template_sendgrid.py | 1 +
posthog/cdp/templates/slack/template_slack.py | 1 +
.../cdp/templates/webhook/template_webhook.py | 1 +
.../cdp/templates/zapier/template_zapier.py | 1 +
.../cdp/templates/zendesk/template_zendesk.py | 1 +
posthog/migrations/0496_hog_function_type.py | 32 +++++++++++++++++++
posthog/models/hog_functions/hog_function.py | 23 +++++++++----
54 files changed, 170 insertions(+), 35 deletions(-)
create mode 100644 posthog/migrations/0496_hog_function_type.py
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..0f2cad47c8018 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}",
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 246737b656b4c..1336f7cc60701 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: 0495_alter_batchexportbackfill_start_at_and_more
+posthog: 0496_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..80733163863fc 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 FETCH_HOG_FUNCTION_TYPES = ['destination', 'email']
+
export class HogFunctionManager {
private started: boolean
private ready: boolean
@@ -93,6 +96,14 @@ 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 getTeamHogEmailProvider(teamId: Team['id']): HogFunctionType | undefined {
+ return this.getTeamHogFunctions(teamId).find((x) => x.type === 'email')
+ }
+
public getHogFunction(id: HogFunctionType['id']): HogFunctionType | undefined {
if (!this.ready) {
throw new Error('HogFunctionManager is not ready! Run HogFunctionManager.start() before this')
@@ -112,8 +123,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 +134,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))
`,
- [],
+ [FETCH_HOG_FUNCTION_TYPES],
'fetchAllHogFunctions'
)
).rows
diff --git a/posthog/api/hog_function.py b/posthog/api/hog_function.py
index d1dc2aca65860..6bc7b0e2bff13 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",
@@ -236,7 +238,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:
@@ -319,7 +322,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):
@@ -342,5 +345,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/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/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/migrations/0496_hog_function_type.py b/posthog/migrations/0496_hog_function_type.py
new file mode 100644
index 0000000000000..275cc804bd330
--- /dev/null
+++ b/posthog/migrations/0496_hog_function_type.py
@@ -0,0 +1,32 @@
+# Generated by Django 4.2.15 on 2024-10-24 11:05
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ("posthog", "0495_alter_batchexportbackfill_start_at_and_more"),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name="hogfunction",
+ name="type",
+ field=models.CharField(
+ blank=True,
+ choices=[
+ ("destination", "Destination"),
+ ("shared", "Shared"),
+ ("email", "Email"),
+ ("sms", "Sms"),
+ ("push", "Push"),
+ ("broadcast", "Broadcast"),
+ ("activity", "Activity"),
+ ("alert", "Alert"),
+ ],
+ max_length=24,
+ null=True,
+ ),
+ ),
+ migrations.RunSQL("UPDATE posthog_hogfunction SET type = 'destination' WHERE type IS NULL"),
+ ]
diff --git a/posthog/models/hog_functions/hog_function.py b/posthog/models/hog_functions/hog_function.py
index d9922a83d47f8..094014b988af5 100644
--- a/posthog/models/hog_functions/hog_function.py
+++ b/posthog/models/hog_functions/hog_function.py
@@ -31,6 +31,20 @@ class HogFunctionState(enum.Enum):
DISABLED_PERMANENTLY = 4
+class HogFunctionType(models.TextChoices):
+ DESTINATION = "destination"
+ SHARED = "shared"
+ EMAIL = "email"
+ SMS = "sms"
+ PUSH = "push"
+ BROADCAST = "broadcast"
+ ACTIVITY = "activity"
+ ALERT = "alert"
+
+
+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 +54,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 +84,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 +155,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)
From a66cce4c876302ae9af6a4cbbfeadb970ddd7c05 Mon Sep 17 00:00:00 2001
From: Marius Andra
Date: Thu, 24 Oct 2024 14:02:42 +0200
Subject: [PATCH 02/20] mailchimp type
---
posthog/cdp/templates/mailchimp/template_mailchimp.py | 1 +
1 file changed, 1 insertion(+)
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.",
From 324df0a1fd4938ee9faca6f752a01cb1c30d1d76 Mon Sep 17 00:00:00 2001
From: Marius Andra
Date: Thu, 24 Oct 2024 14:22:52 +0200
Subject: [PATCH 03/20] fix
---
plugin-server/src/cdp/hog-function-manager.ts | 4 ++--
plugin-server/src/cdp/types.ts | 11 +++++++++++
2 files changed, 13 insertions(+), 2 deletions(-)
diff --git a/plugin-server/src/cdp/hog-function-manager.ts b/plugin-server/src/cdp/hog-function-manager.ts
index 80733163863fc..c53ff71952ec2 100644
--- a/plugin-server/src/cdp/hog-function-manager.ts
+++ b/plugin-server/src/cdp/hog-function-manager.ts
@@ -26,7 +26,7 @@ const HOG_FUNCTION_FIELDS = [
'type',
]
-const FETCH_HOG_FUNCTION_TYPES = ['destination', 'email']
+const RELOAD_HOG_FUNCTION_TYPES = ['destination', 'email']
export class HogFunctionManager {
private started: boolean
@@ -136,7 +136,7 @@ export class HogFunctionManager {
FROM posthog_hogfunction
WHERE deleted = FALSE AND enabled = TRUE AND (type is NULL or type = ANY($1))
`,
- [FETCH_HOG_FUNCTION_TYPES],
+ [RELOAD_HOG_FUNCTION_TYPES],
'fetchAllHogFunctions'
)
).rows
diff --git a/plugin-server/src/cdp/types.ts b/plugin-server/src/cdp/types.ts
index 19664b2f83e25..b672aa0f6c773 100644
--- a/plugin-server/src/cdp/types.ts
+++ b/plugin-server/src/cdp/types.ts
@@ -252,8 +252,19 @@ export type HogFunctionInputSchemaType = {
integration_field?: 'slack_channel'
}
+export type HogFunctionTypeType =
+ | 'destination'
+ | 'shared'
+ | 'email'
+ | 'sms'
+ | 'push'
+ | 'broadcast'
+ | 'activity'
+ | 'alert'
+
export type HogFunctionType = {
id: string
+ type: HogFunctionTypeType
team_id: number
name: string
enabled: boolean
From dcb30a2639d0f74d104f7f8ed7e82afe70d989e1 Mon Sep 17 00:00:00 2001
From: Marius Andra
Date: Thu, 24 Oct 2024 14:23:51 +0200
Subject: [PATCH 04/20] test
---
.../pipeline/hogfunctions/hogFunctionConfigurationLogic.test.ts | 1 +
1 file changed, 1 insertion(+)
diff --git a/frontend/src/scenes/pipeline/hogfunctions/hogFunctionConfigurationLogic.test.ts b/frontend/src/scenes/pipeline/hogfunctions/hogFunctionConfigurationLogic.test.ts
index 0f2cad47c8018..3c9bef43c45d8 100644
--- a/frontend/src/scenes/pipeline/hogfunctions/hogFunctionConfigurationLogic.test.ts
+++ b/frontend/src/scenes/pipeline/hogfunctions/hogFunctionConfigurationLogic.test.ts
@@ -171,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,
From ffbb89a68929e4ce2699d6e5e20e1a2dc8ff2d30 Mon Sep 17 00:00:00 2001
From: Marius Andra
Date: Thu, 24 Oct 2024 14:26:29 +0200
Subject: [PATCH 05/20] more fixes
---
plugin-server/tests/cdp/fixtures.ts | 1 +
plugin-server/tests/cdp/hog-function-manager.test.ts | 1 +
2 files changed, 2 insertions(+)
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-function-manager.test.ts b/plugin-server/tests/cdp/hog-function-manager.test.ts
index 818def2f5c8e4..d63fa02dc3d54 100644
--- a/plugin-server/tests/cdp/hog-function-manager.test.ts
+++ b/plugin-server/tests/cdp/hog-function-manager.test.ts
@@ -96,6 +96,7 @@ describe('HogFunctionManager', () => {
id: hogFunctions[0].id,
team_id: teamId1,
name: 'Test Hog Function team 1',
+ type: 'destination',
enabled: true,
bytecode: {},
filters: null,
From 9b9696c21b7b2d2279b17d71ab5d670b08014e2b Mon Sep 17 00:00:00 2001
From: Marius Andra
Date: Thu, 24 Oct 2024 14:33:07 +0200
Subject: [PATCH 06/20] remove "shared"
---
frontend/src/types.ts | 10 +---------
plugin-server/src/cdp/types.ts | 10 +---------
posthog/migrations/0497_hog_function_type.py | 3 +--
posthog/models/hog_functions/hog_function.py | 3 +--
4 files changed, 4 insertions(+), 22 deletions(-)
diff --git a/frontend/src/types.ts b/frontend/src/types.ts
index bb7001d3cc59f..86a20ea4cd23b 100644
--- a/frontend/src/types.ts
+++ b/frontend/src/types.ts
@@ -4505,15 +4505,7 @@ export interface HogFunctionFiltersType {
bytecode_error?: string
}
-export type HogFunctionTypeType =
- | 'destination'
- | 'shared'
- | 'email'
- | 'sms'
- | 'push'
- | 'broadcast'
- | 'activity'
- | 'alert'
+export type HogFunctionTypeType = 'destination' | 'email' | 'sms' | 'push' | 'activity' | 'alert' | 'broadcast'
export type HogFunctionType = {
id: string
diff --git a/plugin-server/src/cdp/types.ts b/plugin-server/src/cdp/types.ts
index b672aa0f6c773..0b592e29af04c 100644
--- a/plugin-server/src/cdp/types.ts
+++ b/plugin-server/src/cdp/types.ts
@@ -252,15 +252,7 @@ export type HogFunctionInputSchemaType = {
integration_field?: 'slack_channel'
}
-export type HogFunctionTypeType =
- | 'destination'
- | 'shared'
- | 'email'
- | 'sms'
- | 'push'
- | 'broadcast'
- | 'activity'
- | 'alert'
+export type HogFunctionTypeType = 'destination' | 'email' | 'sms' | 'push' | 'activity' | 'alert' | 'broadcast'
export type HogFunctionType = {
id: string
diff --git a/posthog/migrations/0497_hog_function_type.py b/posthog/migrations/0497_hog_function_type.py
index ab0db9fe01565..149443a5acdcd 100644
--- a/posthog/migrations/0497_hog_function_type.py
+++ b/posthog/migrations/0497_hog_function_type.py
@@ -16,13 +16,12 @@ class Migration(migrations.Migration):
blank=True,
choices=[
("destination", "Destination"),
- ("shared", "Shared"),
("email", "Email"),
("sms", "Sms"),
("push", "Push"),
- ("broadcast", "Broadcast"),
("activity", "Activity"),
("alert", "Alert"),
+ ("broadcast", "Broadcast"),
],
max_length=24,
null=True,
diff --git a/posthog/models/hog_functions/hog_function.py b/posthog/models/hog_functions/hog_function.py
index 094014b988af5..c2a1dfa63b042 100644
--- a/posthog/models/hog_functions/hog_function.py
+++ b/posthog/models/hog_functions/hog_function.py
@@ -33,13 +33,12 @@ class HogFunctionState(enum.Enum):
class HogFunctionType(models.TextChoices):
DESTINATION = "destination"
- SHARED = "shared"
EMAIL = "email"
SMS = "sms"
PUSH = "push"
- BROADCAST = "broadcast"
ACTIVITY = "activity"
ALERT = "alert"
+ BROADCAST = "broadcast"
TYPES_THAT_RELOAD_PLUGIN_SERVER = (HogFunctionType.DESTINATION, HogFunctionType.EMAIL)
From 9b06f4db366f44934dad44a19c50c05ae1eb04e1 Mon Sep 17 00:00:00 2001
From: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
Date: Thu, 24 Oct 2024 12:36:12 +0000
Subject: [PATCH 07/20] Update UI snapshots for `chromium` (1)
---
...er--trends-line-insight-detailed--dark.png | Bin 21701 -> 21655 bytes
...r--trends-line-insight-detailed--light.png | Bin 21385 -> 21344 bytes
2 files changed, 0 insertions(+), 0 deletions(-)
diff --git a/frontend/__snapshots__/exporter-exporter--trends-line-insight-detailed--dark.png b/frontend/__snapshots__/exporter-exporter--trends-line-insight-detailed--dark.png
index b54d4facb0bfe8353e19863b17eee837e4aab6c0..e3e93b3dd9678360493d495ab090800a9f994b6f 100644
GIT binary patch
literal 21655
zcmeFZXIN9+w=Zh%fQrZ~RV*N&pdv(S1XQF;m#!c!G$BCfQ4x3*5S0=-QbMmn2q8dJ
z1cXqeLm&x7KnM_e3khfP?!E6lANF~keeT)k%YFXy10gGG%{AwkV~+6~zcJQ&`&dVf
z?F7$>ef##YsXw}}zi;26>3#bSEFL=mju;#a+XWx{;rePS`${_aX7=s7xKI7QvY~Iv
z92t>dxK{UdjQaI^_hFwIX^oGU9vr&=SnIJZT{`~9mH59d3*W+Z^#*8PSJ%1)%M^}(
zV%vP6YH;SSg2RuUx=qh#OUx~dK#xGgZ@i3kUkwsWZyzi;2)
zn*WsxT8GDOIQRZ6HLb9>vceP?p(Q~F$$G}dR%SK9YqQcPX^O{*@^R}4Ga_*!2__Q1z0<=(AdnZrp_Epby3#D%q$6oB|}WWFU4k<
zcb#8=u*&Mb47v0b7-13O)@BF)n*{d1kg$^VI-MCOxmriCkK&1QPzv}tk`dmfJ+#~^
z6~1#PV4b@H&)m0lXm8yD>z@~5ldVRI?V^Wa{Xa_+Odp$d`?q{~7T!_HR}uhe-&t*I
zGd&neOUnl9__8w#5x+qF(mXG{QfQPm1Y6oWQFn$jaeJloxz3EF#YRFcdF4)sB>s6%
zBC>WVJ}N%GL^4(0odUJ?_ilX0l}ui2y)C_`nsw+HMSW?pcI1~rv^=u(6RXmTST~l
z#*kXLLseO>>so(yxwLbCk#DunJZXkrxBDkccO+mJ+Das1Yi8E{erE)1(1$LgE8S6P
zwEp7G_99Ds|L^0r`Wh1yKIC@d^umwaVYH6{MrnOBMid=Lczsxo=OZc*D4EKdv5wc)r1k
zPQl#h3$?W{Op=qfoBi;EM%5$sI=+~h5p=%mgqDVAOv}z}ZR+U~qk_P3&jB83*xUey
z7;K5JbRC}B4fZ3m(|t(toWLvy1iAd=U`;otPc7PzwEN^7I>JTK3*_~}1j^6ZKIf^n
ztrs0B92-N7lkLgU#kG#@Ntl(X_D*e7T%1uG9Y_iorB3Uzr#+VKX^JA~N}I-aoVp4n
zn$tSFJWwHYZ?#@lYZ&)is=0uS1FNP;s%bhF2Uz!95%*hNjw+wUZRkMuqWqXghCO|B!R3KllKq
zq_r*D04^SaLvM+qbyDRPs1=N1cLHOI(Bw{}9oYABl)@Lp*v}-~bXz)(3!mU-pQA8z
zr*&&K%S)tS=iN8UebO5>)9>D~T+Pkd2!sw-v%>V5eSV4EU*
z5K%O^W!f$V=&v0UJ-R;k3Gl
z0JbvEw|{PkQ&`6e16$LP-TeOj^$|ASNIuz`qN2dBFN33aAqBqJ`B)KtMSB-=jkk`{
z%w}wN=7WfEg{{?x;zdAgU73wHk|0DP(Ye2JhR~$@r?1d~W?T&GFG$o+l6CfJOiJc&
zD{hm#e%*06XKmJ~(As~iueGz9c7$`^J|E0%-|}=Fd}4p9{)1{A3$MkjqsH>K&kK*@
zgPu6ggr&O--!StZ8`JKn6N1yZh8_mEUh69rObd%7Etg=m^}4#>bsmY2Ey8bRx@X6(
z&ksHW!%5Vd8mZ`<>o0bG_H69+;gf370*uWMxneSWcmDipI2x4y`B{UgJAp}ZZfMp`
z7{q8!D~GVRr+%LwaiV@hS$=uS`i@lC`BNpRmE2$4p~Psbay3JtP@O5Vv;_jydaL(o
z`1(9J9%+2Tyd#}33JxNA_0+Oso1)ViJ}@SPmn&D)?By!b8BXMWwOr^dT8jg0r13EU0g
zxM5xun6<`OpC@MpVBd0zxUVlyw9o#kw_LbOY$d`u#0$K6)4gch8}tF(O1*o%Z1B?f
z)o`|L8^S>bYXxm0W9Bf`HWb7VD06RSH(DvEm-jY{INxrLfhDG=6JV1CS2bBdV+;02
z;o>VBq(Uoob@gHrhJ+oSCg=BvP0?cq0N4ol5nM-DS!m_t?z}twwQeBF%*)JeN=dN2
z1{*Nr#A&89;;K^|-C3R14Mi8s)KbxmlAkg~$?Bq$C5z+$x?~`j8NctWmi@#^<=Sjt
zyLPNYg*UAiH%&**S6L6dL+7UZEbFQ9dn{cI0m?)`Y@vZlkB{ttwc+1M)hyW@i!L-N
zmD!Z%T`=g$H)saCP$%W?mY^W@;5BqF2y&zv>Ag@(k6$0k^s9)~Z_L$VtS(?om~~bGL7R#jiZ|`s
zs$qSFK^d;Y+Su8lEb}OyEi#^toj*zi7AUqmKR3;*X(rcDVWdQaTvK8xhmLKE`I+AdV7?
za?jDPAJ;%H35{>VRo0D7mYjq55xuk+}fyAFE-}dVW?3J
zwjCtoZl}Uka~vDP*mu5jlj=7|46LEMwfssiU%w99Ll28~sGV^Klc33>9ym%@C*|f$
zvkCJis;DQ2fS9)2XiJrsM0*W>mJFURoX(DYFTaow+a0kRxK+C%$tShmZeb*0R_VK%
zP>y?O`6b&C{1cN~Ya|;@$1zGdMgklb2=cq@sMhcYTGd3{L3)b4WIElbqlLnsqy0|!F8fv}O+z=ffhjJY|Ja4Agf$6X=+w&aOzDwu^
z4u<}Q8zUp59~vI<_?oQRfn`Na*a
zY?}|8@VAMMvH^8{90HWb}Fsh2Uem}meD4^u*2B)roo}_AY#{iN56%a;MO6)i>Qm%
ztu7bh@VDx=NxZ|}kenDDIUy(%iuUTlN)UstEJpU|;MS9NZ(P5AcbOQjoy@YDXo}L;
zfHE2-1$`ICV!N$(eKt;Zy3H$n!9R+HwfiyV5qQLKsc!l~O7Jv!BnoMu(VisT9+WMG
zUhUQ_onlR1%&o268Jf1*Y)#BHp{Tntfiyc3rKY1G4~^#>_F#+Wi(RE4_B`Wa(mO73
zE5Eh13sO)$FWD@$XnsZix%l^G9up7M_>`_e6af+uH;JE;;0T^y8^a-w`GJo;Q2qU))$ucmX&hVc4+fS*pPO6Y>{|AKv
zf8?ekms|zrWOG@~&S`QNWAxXZ37Ut!D`qC#ee1T7$RvFd8
zlqh70#Spx1s&Dw@Nm*U#hCyp3tj7Z{0|4$@$-5x(Vg<}&sbdqG!PZM<56H59g28K3
z67E>@vND^g)+5rLDZNwM5mMw~Cc`!^y7nWpMbU{+Vi~T$qU$r$#gW^Km!sV$3JLbmff3G-flDsQCOVxZU@(Vv*h9G^A`q78>IR(TWTzu|$a
z2JTT^wAh-Ugznv(pz97jW%J3MFNTm}tIw}aB4;NNK8XJkmC1!E@Nw;i`HD*Rb7|_*G#R2PpH5mnS
z4N=9_pSFU*cOmFil(}2rXuZ5(?e4-z6}?t!ZN?$ZvdU!(EYS!3ZIxk}Xz2Fn;()aR
zFow`c=aE}`T*qeM42cm?0fScAukBFQN(vK9OP_D0x;j{G8D||n>@99D@JnfhakBd=
z#6j3tZo+IV+n`f!;f(=YMhmz_^6k
zse#hk?d{sFlShynM}g(!m#-8A5Tph{btl)XnJ|BI<2p+zYa0O&z89rwmU0MEQ?9tE
zC~nal)t+#x!VFUjdYhI|NK=?B)|9BLt*pF*_F|SI5#&;&$r9$eE>!CJM(E|qq4vn8
z5>A?%79c(FW_|@$X~o+<>(Ttsn(9ugfhbc}*2qplVL!FcXUdOWiOVp6=~rHYR3O6J
z1SMLnJmK3b==@=BrSkOcfcZFpP-#tC!YdK!yPH7Z)Symg*2Ab4BWjLY%~pRc%
zP(~$i+D$q{t6=Av$>Ds3gtkB8CtsI1BZ}7`3YKp*Hd{<5l$Cu3HgtEs&&
za&FPHK51NH!&L8+TH(@%IYs6SHpGEe`=06zeHna)3*KwNFxM^h#yk?tXM?)?bb#=l
zoKaRb3f%4t2?H07NtA{6UyfG%h(F-hjHqT#n;Gqqg!s2~XMpHOr5^lOsKI|@y4JRe
z`vLE;wIQq$
z<&bT`pvv*`=J2_ETX4}+nhy=v&n0dQpmkaHb4w6oNf73Sk(=Z-{K^o-vwz4M|GS&c
z?zcRn=z9wP6LcSVGpO}2b(8eZU0>=tV~uIr5UT&LZy_~wU&aExagqo(X&>|ur2Z9qB{A#hfE%1`j6VdWDHeC;!
zeZE2J&am7it9RZfoz0zT!
zDfZ;|Sp9`v00ROx1vgG@cV}#EZQ0pPGJNJ1BhoF`>WGr<#XV?vuDlxo4WAAIL@>P@
zz2b_*w$P+ay52
z>H4JAb2MY0+krpwV)cpfH%^>9sjqRAuMFuHL#Ycu$NI1SNz+e)AiuSlRzUotLPHPl
z+4#32e8ZoEnQLgYCEkn0WE)8L*v`kH0sr8?Q_iNTNo-xGktR6^j5RRzpyB4lHYuID
z#pG!~1Xt*!lJT>^RiVt}Gzd^!p50X&C}=-Qty
z*Y0dlNy0LTRd;lbir@Wn%ho`HE7zRhy#!fVUVayW3YuB8^`E1<|Nhw(w;{9nMKkG+
zlY%?zwXz!DaL{gWbGq*~peQ@;GJDtO*DpYyjgR=P7-$^i5cr@^a!9W;uXIe)_uo!*
zfXHPR&pih*5N!Y$EdW<8114_#_Y3+#fArp0Mf4q;A9>o=Q
zuQo2z(FFN9&HRC&_sUYMblQ7Qc*f2%L
zcRXE38@S{}TVT|m0RJcNIj^AkW)lk>9a{EGr-5X+EEWce5#)VELVpglHxp1xdEi|+rD5e!dl@9diA$KXIp}-
zIC?c4N>T8cBeW7BDHHs6UwjQiOWEhdy4B7|HuDE~fjE+V7BMQN3VA=8zP5_`;zowq)K%sry1bT(*9ar1tvinQt@ELe!zNz7_z;l4G@=g
zP2kpYYjSU?H57dZL3usWu(iT%Xl3@R>{(+PwA?79&NJ6Ya&u}6Kzx_pyceV2mCZR+{&;kCcBsN3`l{wTeT`@(rQdC^+^S&*Ix}EB&l*`8+x@q08jI1m+gn?@{*~)a@OMHAHn-vdOZ43|Z
z`V8c(xYy5Ykk@H)9^=2ok;@S?JyiUk*QM4=%x_$pCM6a_pOu+8AQ}~r0leID)yebM
zT}YKJ^GH06L<`2xq>1SI0T(apYKD~?UsMj-=KG*ukn(=GI_Avfhit5ltDx&oCmY8a
z)T&@ZnU?rE$=cDKkq5XvD$=VEt%;IxC1u+Yr*Pqi*?8A!1K8SHmp^6G;_=4!J%Nw+
zE`vsZ+!-5Gg3xuD6jBWWE4+D~blN6-dx5Zb_L=|p${5ku2c6X*hB
z1$3Ywa;_^m=-rJLvNNo7JHT~dFYUHJG_SwKfTAIq3Tzi}AQu$J<6wU2cMk>*&m*2!
z41It78nr9y@;&+Tksyz!lW1i1(o`mTk4jkGDHCaq=DoE#(_90bdQH7o+n@)S+lCda
zH}57oR=yLLUrf)gtn`|zhQLk6rrbmo{8tCNySqoK8XySqoDn6I#0|H>vTx9Iq<{Bj
zm$fWyHMYALOI=#(Hh&i{QEZUAzS5~AJ7#@ZS7B^0wwu1YnF~TA95DfIZ7b!rN|UU+
z>$R37JuIC@*GY}nui5s^+dAZ?
zcm9d(>Gkq!%nj&IZ9r*`x(>B0j$MXmMgEnjZ-(dR%V$
zg{r_UH9tQiU@m64pgT(kIh0b&YhH|H#^}h>fN7KfsU4V~pL-p%V*~l3&)SXrLNbqr
zTwyc|2z~(pGPqX|+1s_)srk~c>OS%qYlzVct
z_mVJQCWa!xuKj~SMoT*nP*D0X^x-7Q9!SFI$
zlGQ4I?HPzL{%V!GKgbE-=cg6_NBQ9YMSAnUHRJrs6<`aWZy&*Ezc%?~o1uWPUtb0`
zVpPiQsLtz}f9CXmme&5iK8=>CZ=e687T|wB>;8Xzc>fyGf2sQ6U+eH+4+Q@=ic0<^
z)_;lhzpFs$U%K=!UHX?U0jTuwe0cDf`&-E3
z!t`{dqoMfReh{H2YPCLBns@)I6(^RcwS3g3qk{+B{|vZz?#DkDKU#Rx(!m33Fdr)M
z<>eiVntKf?i{0kfNjd*Zpju_~7rVH`Uu9nDO;^`oZY}={mXZ06j~yc)3D!C3fUp*-)y?w8J5HVerBTqOH?{$SzX}0?u8BHN0EH})
zwVV2GUGrED@4h$aiT
zP96klX;97^8>8z;4Nlh*mhzrmRTBuX8TC)PoxQ#ink+YO=VI3K9eQZ6sTF{@vCZ86Sqb^$J_G!Hk8p4ZC2%SW)YPI(y#|P=7uxKO481K
z+n`WVSU1>t=gnI#O{v5-Ku;6w+j&5R*9dDs9y@s;%@ql8o5ZTS*@zzuAZimp^%04X
zzFLZSe}OTT%lkIfMf0FM$OODYo!@0rj(QfRB05ajcw~Oo#bDjAb++=}v%a$}A~Nn2
z7a%XI;7QDM8q&idvRNmF!Bp~@OTPT@eMy2}_wioSz5XPf5l
z|0IRR5xuiK#xGxmTp!{FBxjnw9J4Y8a7Wu;LpUVOD(|Ab#tvJT>RsOLA6~d7Dmv6n
z^*1jMpnL_(FF&(8jHg%UbTBzab&p)xf1vgBep9iFyxf@=uPrE^#`H@R^7j|;Q>evN
zSpPyyzg}UT&tSi?ILwutS)A$k;ILuN?H|f7KHq+GPgsZm)k#j~{SE89(bJ#2OASS<6|2;Le2-CX^4Tnm>T2?@TimJW
zVlv|6$eR-u51%~wv-&MCf?i5WKh8#-rhFjMI&Q%y@8<#j;;#8aR`alaX6%FEi|m
z>kR5v-=FfXoK6j1IXa2Go>=mT1*(2SBBD_V<&k#!EVsPl43x@roJ=a1g=Nvg
z4sCe8C=+=zI6NF0c4(UW-0cyxOm_Aei0`kA%lW;;K60JrqZg842$KC&@N2VAkFsTD
zabfYpgM*t}TkA9Y`EG8Y-&ZY1qQV~T8r}}nyYi#in>N+Bc+2c=-&jM~kt0V&@QB@6
zjE%K*`sU_fPEOA2?}fd^^-7cj`_oTVo_dSZa5mXkaL~Uxysm@*{
zVJ^YcA2eEK;h!6Qt4hR)_cu2ObS1CQW@mMCQrSQOnlHuO{op4nYr`A(=+8`$`70J5-agbZ{CC*LL{eEN=-VHCv*+K+RVO^=oejN
z9LC?2-%gBdu`<0o8%gl*8XSBTwoR$Jg}9)rnWSLjJ3jBJXFP@e@N0BwqG{2$Hhp6u
zYv`(`eE{C#+_`h3Gc$Vn{^X{xVYDW<%HIgA8;79M>rw%zu{^G)0hcXF4Rm)6|Gs5PRQ4`rt#nx+?cu``2mOc3nT&G*u=SpVP**C5N57
zE`C+)_UqHjx4PG>ZX>^DR}lpR
zHEZeEsb`oZduZ-IDkJhZV(USg=qHb?GP49!&<%xJB*e%EuB1B(A2+Hv&ok<%(5V|2;Q@ou7zf5W!!
zfrI|t^iNkSxZd7}VMq;G;zB=q8bTVLajMjKuha%!Z3f>?>q`{;y5nZPJS8!@SDtg{
zj@zb-lu&kdw%1Z7Z*0iQ@@cXLX
zu-I$Sf&<47?fej0N=zK8s;VjvIWi)_y|uOd7Yyq0eembsi@F1@*-F2-jvdmZn8wB=
zBR3z3H3ZCk5c#`(?5?b={w}KJcam-ve`-of3LlQoEjo>ST!@V=N~_BL&YdjXt1;Y-
z*>5N2=MLdFcD~qbG4|Fb-*}+68jrXDbz8+`+I-9#@12oKO1{gdv#}7Y5qcZw35!)v
zW-{;cz3&oLJXQEn$$a}MKR^F*p)+TmsT+uR;6`>{e|N8P>p2%9)X(XXZ2H+ID5#d!
zI9~Q^)Q)~*?8V)~p(X^@RKRxKmq6&Ri*BP!zTncvt7sXSkq;lNI0O{D5cEuP6FwQG
zQt~;h=*@>@h`Z?T6V`(6!ov{6erH}q|2HZb&z}hJGtwNz&R?5I83_~=q(5s2v#3ew
zDRp;;9cNmZ-Zp=G-^|EpesRK%SqwmMo@*}eS{
zjcX+mB}*Z;z@pm7o_EdLFqM
z86UTw_Fm)5#R$#EYyL4cdWQI&C!N(MQ8k^ORUg~dA#Ok>ziH)%i`Aya%@55%%n9j=
z&h{@}{ED1WHyp7>uGHC6Vhamt&EhD7U1;EywO?b<@LLuVCKVPLk72@hXKq%?F;i#P
z7UUyVC9rL|JnZc3VE@6^(gjcF_05Kbg@N?w1*>frYV6%n1k@Ea)Rod7#vkHlUsE6IdF4LQT&Q-bB-!M
z^LrfL=LqCd#ZNkzTS<7r;cI_LeMBNnd9q=AT;JMS5V0YVCSW<~={Fg2G$}revgY{Y
z^Gob*B@I~jAh&g?Hy^0F*=JO$PaO&VM~AB39X?TgRbF1%+dFDXH{Wn}ZX~gVGJdz1
z%?TTC#8rhZ6)N&zg^@v({fa@&85R96a-~|{8(#X^wbz%Onn#|tW~mva2eBIEHRIiw
zKIR_kPLH47TdovMG8crFFlK4=JS2FctjlVtu#{jR7Wd|ZL2eHle<-h6DodyMv8rmw
z<3FDheVc5``S|z}_{>-7R1{B%R@$BVWR!@|6}cM826?oudcb)LBK4O@!XL@$9hK&*6^
zOY*nq%f9sH_aygUsXckhf;=&~pI8r;+S;C@zs%U!pO@aBp~(#~;YP_QdcpGx3#rJ(
z#eR3>-Qb=34;smCS#kRKkqwHg}AexLJtS6TG^a6
z42uIi
z7IZBx)xsWXz7;=X%R^63T}#XB`Ey?YJH35>Os_~<-|}qj?eCYleJSPi_~cB6-^;OQ
z&lCirTYd;6#R4P&eB0doJaBU=M~|AApqis;l>>DF8&;-6QQyi#jzy$sX+q>gaOHYm
ze8^1*is}7hrUcfC%qi0wyN5*2ov54MhB?E)reE-_q-{;xhlYj%qosfCuXeT1$D>*d
zoK_zw2i46o?RM3eE%p_t8_Z^N`i@|`gz4SB3oYucIXOWLj`&zBZle)DJBp@+L`Uz{
z7fj~PF`>PGL@QNSADwSy=6JyaW8vO|AX!Oiw19{p(kSTJ*xSB#1KoE7ITv
z##T)pfu1!xTzz+n+N@Q123{)jxVhO%Dgny;Wriy=RPI`&eA}4UsikHRm#P6!OhtjK4#Ou
zF#>2uU#f@*hzi-m4q}fjv9B7!zyZH#>_H?Ay%p7X@qM+h4!F%+8Nl0FPTu%xm*67;tlrlU{)B
zc_-R&vn(Scs^Y$V=VbTZ3lMTTbECBlIM*5mBT7KFc5zl(<CZ3+a6L7a7C#Twnk018{#+@0tBq(2&cEAl<
zasGv7GZK2{%o&+o49;}O#OBDVNnziqX~!q#Q^vk2{lUguo=2^toQ4L&OV|stmAp&X
zb1ne&siHsn_~=Sq)};gf>v^Tz>398LyRUXx=RQ6DbJM%^N_+YZ$zy?=k7mCn`~eYm
zj}97JdnY2JZ-#CtIiENlzk36(7F}SB{Ngs-+u~7v>&=JpPc#C{|K;`&FAtNkcJ+;;
zZd6A{J)iuw93-K>SM$-u)2H4u;F+Tn&C%Wui|>f?%cY2+#Lte~hI8lhpP2xT~J5LEUTwP}7+RtC#5H8m-<$XXen7eZ
ze%*auM|$3{MoXC9MW*)jsCHk&eTqShsR#>Qx%gQ`=zf5I=NiopXv?ctiWlCSITd{L
ztMU&i&a1ANB)SXhCf)S>UDg~eNT;l59tqJaj5`!?OR6wm{lfY4JX~C&=Oa2(@t+Z%
zC)g^CU97CWRSEilM`;y2993NBC-|ZBt*x1i66K@o2hjt>BOLER%+eK5oK;h^4dUd8
zsHhBy3=@xOx2CO=r;zJmHuVRD-}nX~PM&0IX>TW3551d#_4iDg`CUM+tzp#-gdaQ%
zL(mtjfk+0IIK$Zm(Nsa=9-vv?*eKkjowM`9U6ZS^cGWKKc+dp`fb#N#(#YF_m?X^0
zPS(e7V=tQCUznVj2wLf%x5RJs4-D{xwX@QWh8LFIOYmmjv#4+BLHVpR`lLS_i+uRLksb>hFB0VDCBd+82{}
zne9r^!NV?ocXcXc;sr2i*UxrPXW|Sb@=!drG=EX~T#K8?+<}%$8Fm}&|JHqx=243%
z(K^Eft9N796#52Ts?5l3Hi@H}>@aUQF&!Humw40YP$Ap5q_<8XeXmk5
z_JXRQG38=o2&6D+$zx_0=1_2~|1{`ko&$6v^W%!%Wve=^CrytAPoFEB9J||2@>-;0
z4k%!rTt6S);N<3JzZqe5r0QkKBC-++=D`ibA9JYCJ^808YVYnh600IVmRVhUN^I~*
z!hFl)`h0ne^Dg{ef&J^J?T5aZoywFEeD1ve*wFF^+}c+uP?ojV`cG!i?`4B)0b2e?Gc#GnPd7
zp?A}beXD;iAx_k{qd%T%ghb9S
zn($_Ur&-`|rthRD&O-T^UVhT@@<{_9=Jl8sQVk(;kVI+&MX2B@+9*@{;RIeO{~wFb
zR9#(ND=H-EqzaRKmrHx5AX(8l422qa9qLZ2bSQr!l_SCy`PNu0t~pr#V|={M4R_qT
zjQC#ykNUJu7bRYNQb(Tnd+aR;*rlcO^Potm$MUbS-gxoZ`SjyLw#|_md_0}gE5Joo
z?N(}DKQBd{;(X|S?ha8}L9wPIL8(}c0~x0gmu|heLJ1lVJM!vYnNPczHt+ceJ7;Gp
zdf#DPzYEynmouuiSd)c?%LF|QX|lziU4`0C!`Y^`jIx`EH!h+Q|i7ybC!
zt>+j40Ja!w^A)f)v+^f9<0m`YQ_^Z}r%M2J%C4#P2nz2Y5(_8ACYs)_Z>(6Ze>R?&vR*66#G>s4shr31la7O}aHtR);01?kE3Wf0_h(XAiKov>UG#sdwJPE)
z(;Lw!<)9TXw&g(B>@+!r-FkEDTxiB54|oo4Df4iyPWqM&(Zr&py(qB-KBcg`m2*Qs
z-;hy3Dz|-j_{eHtE3vx$u$Ca{k6uIR@84`Ze`Qs~XH4Gf5&6q?Pb%MavY4krW!)Dd
z#{HK-z_;vM3*oLcHZV_)^L{&4Vb5;7N`&Am+t&+fXg=H?quD%AL(<5#wJ{NUE4ovoU_!uSo^Fe5^nH*XS?t2Qbk~PaAtGJj=
z=m(JqUzw)m$vhQn2xlX&tds(BLc^}7BbJOzAzpklf`e~Z6HTmITKe#=?ukPLS{87d{%C0zR
zH!^h~=Xg7jC?$6L(tPZKsI%%nn&)W5JO4fSggjeM_nVo~r-FRvDxEv6LREy$M{LYP
zV^0gJVV*4)C90~WIk#V{h?p16V5NT=^0OV#q|x(yNEE?r-=lK6;~X4L9NIzbmPa;Ev?EejMLL@=Synfr5A+S~w7n2OJ^*jHjR?Ji6`4wUJJ=E24
zdE=sz6IEO{wH^7`9XcX9U8x_tM|Pmru2PkJ*X{(V6p|cIFlOfNXlZihgoG#x-7mhN
zmfvTIuaOFT*p}VUfk`7ggSdxx?!>yi`4C&Q$Db*to)eXf2qfKtJPA)teY#V9E2*;^
zK+YlT?q@S?KE64S!BA9G%rnRe+P*(eS&6WaC@n38Ci*17Tx=Vn-oB-j!?6|;C=GNt
zNAAN7y5_Nu#7O{zLBw(b(ah&lo^ZQna}07S5>ubS)=vD#JIS+T3A{N>+*C?z99KzyNdD4
z!1#MrZ_DQh*Zp`lV!s=wMy^Zcg0^${dCPn=H5Taf>1o0w%akx38;?d86aSXJTZdje#yla(nP<-aDAV)~L{$;L)Q;d3lrLc3Ce;y}T>dq)absr8UeEDKD9JpK=`8rs3GS0-R<7ajCi=|IkY7o6Et6-+Z
z0OQUNIBpQD0kY(AlmCSqGO8Kg^aVjOf&MBa+?&K}#x9q@SJ743B{7$a>$!P6^(HnL
z-%g>hdUTE31GXAhxNp;Fy8(x*jrpCDHPkvzTggpjm}&di%Q0k$$P|My;XCbGDmnqI^WQxYN2_2m03|3LCg$wpBq}yR30;$8V>W-jgu4OLQ)`>K_T`Ynr=mE%S;k{>V5EaihS7KUU>?Ey8gwa}5s~
z1YD6r&sB|82VH#NaH)vK%x$KVdQ3h*O$t!Li>B2p=H|Qs0r+QcRr4k_>{o}9-2tIANT?CVoyx0QKzAUO<^E+$Qdp?QWs#|DvW!DLk8igjs7vIFj#^=nD
z3WrErD~X&1^LCcwwXdG
z3liRrOGrd-6@;*JJ2+@t``lTpetg$;t$<6+sAq|)Gc`5EOS-!f|CR4QKtrjvb)alO
ze_JP%VVLpQsyZQmCIBOEuF71AZQ-n`#RDeIcjd?zFln7h}o9oJP`)emuiPB32
zh6Pc{Rc#4rcl^3HLC4<5@lG2rIt0-M3g)pfDAe>P#CgeJhTtjEcj|CbpJ1^iw!DA`rFL}R&C6a@_MyJ*
zY{wDDez1PPy*;`DU&RY)cAjP_H8g5P4PW;o#R6US1H^1IudGeNRMOPqp@}754fNSM
zp?xn+Gb*ITe>l56s#d+|^NZL$4QL?M1C1-=Fn_O)@+Vf2epsv4`lS!pwG_C2ei??>
zJ)|R5UQz0@EFd^gL{SL|rl=jl(z5RG0(ff>XroR&Ven%9{|MJ!=JrsZhV2!M?KSv+
z7UY=}suhdHu{5v!drq
z4Ck+fDNWo~wl=Ik>S>W{b~wbGhUIe{6arR;6z$xzRlDfyW_ze9Ek~0Icg93V-%bnN
z9C7EuG~EhdEWF)UX)|x`+=kOz9~KlSCP9>5a9)~~mlb4Y<|Wc)cJ6lW@10>gWA3#T
u?}nOm=Anrugh@j8kwE8`oE|AwBmc?obTrSl65970-^J^7(x=U=A
zsHmvu6ka}8r=q$5{ye)wdlr1i7psbck25an@=&V0zMJb*RDV(_Jb$X`nYfAa@YPyw
zY}j3e{+aV<1eHmOuvrJ=nLn-z*r0s!$I8NfF&Ws;+QedQH>_;!bEmR$Q!2#i-;MUR
z2{W;F1yWr!dDc;Z<)Se)`~5e+U)^jtGJG}Jt>3Svg($I_oM1DUl2f9cuv?pHF*1}9PTVLx1=%Bzh
zIwmIi+x2{->b#5$t9D$$$1{fI4%)Pthc8~-wH^CCyv{Lw5p-^sqWK{D>sPfDISY$J
z44+Q!)O(rDj?U>Sx8kAogDE%LkdTm3A@GmYPbHHC1qF?Z-R&iriZB*i4V-0Ce~XA@
zr>keWZH!|ZqRvoJHRzN$psF5ZYFb#V;2bJYXAHBobLm=Jr>mJj6Px_(!6CMzS(UX0
z_8wH0W}1BW?@yw0!Id|b_wV2D?3md0(Fa#_a`jtR{XugRABWamN3*obJ@{tdod2IP
z*nejG%^x4Vlj|L^?KWc>YECQJK|(jYk3U2RW=ptqV&uAtUUTy752)hv8})U8ay%#3)EM0b+!{0)Xt7n#{jZ-d?<$H$F6
z=(8(y#TVRW4%kUuAob!iMF=)?GCZb7(!D>jX&hwtg*?#3okNiekrytrkWX?wfH6v2
z>1KWRx58zUCD$*>YD{^q)^hvWG|@#2o?6Rt1(vs%6Jq6`}z
zRvTlvSvuKeIR)=}CY(ep{mW|ee-S^6eMS9f3}8uShk9=ArpE~O3L#;gQQERROj`NI
zwUl|1eua7JvIlorb_(|26^`{Uy5H^N%q67bNM|!?1k5J
zKqXdlMA$?fJh6TZb`u3AAs;JCpXMEVYh;dO@**dGneWFMvq{?G5|f0#Q&H_G3%gA|
zP4Xey0S@gy)GmPiM
zAf_wn{jw)bx5H)hw6(R@)23JB(m9`G_>?m?ok^;4oOqqslVempwsg@HO1mg~6OOB7
z*=k9jPIcFyf*cV;xDm2J2R1`@#N*H{zg~FVmJjXha~R0S{*>oB%w0|9D(d`Y<~>TQS0$wAlB%bxTMb9!xd{`&QcvFTzvyjrg~?HbRkcqvE!
zBL|~!$o6!&3{KPkX`_nQd;94!1)Cb9X)&gA6_LE8d{g!90oht9cj-4v9fN$@Ih(VT
zf@|Rr9Fl>Yy>O}6qThZz&we;fdGGQiV18z()nw5NTJGDoQ%q6
zQC#6A97>g#XESs8J`~QJHIg|sshe7=nF;>j8}V7y5uy9bqmSB}x!NK*N)~0t>Jq=<
zm~S2~Ec};q*;ApknVJ|67A%4~PkmWwd>{DvVnbgN{pJi^5Jxqj*bpb
zTqQSzgoODVVTI=B8RXD3Gnd&8}{z}k(H2NKJlplOsnm=wORpW>occbz=^Y(M%1K8ou(D3yl
zmN42_im1m<>AKvZm>d=>s1p4xE$GYCF13ovJ~O`ot|LVi!HBUiH`+smKxt=5eF5-#
zEK(_kAH|xFO|THZumg|&ss_fNb+|F!-PdPa>1^Km;$&-_ki{pYnnuAlG-vDNU*zsV
z*8c#(bC!%#3zs1;jo~Wa%>QD9HU~cGYzE^ySQ{jjXsGIIzm~Avp1F99H(#&VJ^iMy
zii$gl`cOXXrjfq>SUwCX7s?h>KkeQX6~-wd2CT83o^%tcTV%E#!2&~@1%P=H&?_p@
z8|GnUWmR@|p@{fH!#Pm~Wmam%+P#cTU`D#fXBOUQ3qyE$tKG6=Y{zr_Hnu$YWEb<;
zUr|vVvt=3!xXh18F>Et-v3o8=HYdyco_ccIKzh*JKi8ZcuHaB?Hfb&|UbluDxjmlB
zzFqvtyWYd~hOo^>HCpV#h3+nhyq;#sd~kO?7gXYhU-ez3m^Agw5J8D@rP#hFPo8*r
zg~*3xR`(tiyX0nRa0oNM?s4PJ*Up2fQg+JK)C{ZUjr@AAHr8|;
zgjmpZlxP@t6MFpSxT9Y-VKl9moU329iLSL>RpH?BS;$=O*0C${$D(Db-L{0C7M28z
zVYM%|rpgq;ic50%y>vMnrjLI|Z%mh~wujO99?u`AD(#1}y)#2T^QM51FQ#XS+pMhh
z;qv|K#9d1y%U{^uyVye6pu{RUF0RtzFrG*h%6o-Aog_HY55vsFG@50UZJ$7b#EI4V
zl2yEnPJ@ESn|?08BEm>&v7oeBJzYvnVsy_8d^u6RDkf3%Cw|yyWt2da-eL87mcb@S
z*6=*2a{ZX2Ny}Y{@Tw=_NSnoaQadA=BWaf58)LnSJY7TUIg)PFlnpK;`c1>SgUw>8P9@n9FiqS38%d%
z=UpUBQ2(~bezz*8isbGwWCk~l8O@IEubd;g(2&>KvGovX#6Z3y(ud-1
zi4TfVeS&Y%FSQ9|D)b)ZxkG-k3@)?0-v`UUMO<4nZTpg~T{~5mYv5R?1wy#ZNXO>X
zVS_#7xXgYU!PqCgU*ggyn?js-m$6=!^uRDeZ-}_hl&p*FF$+0NyHk3{<76yVXn$Jv
zbApg=SeEk+xA{as|FL7_bj8B@tRl6G?f&JvBYuP%v+aTmmmD+C2j01cM^GN-P
z3qCy55%=|+|8hcaec*~GR$a*E!j{eDCAsqUB{UFX
zcsrNZL;PwZoLswL^4NB2>8Kp7S?hf`G&Hmd^K@D(7$07D1(3jjLh1z}C08qZg$oXM
z0mws1Nr^(jdL;W2CWnfKa%+AV**ZQsS98yCx5jIK)^k5MC7hr=E=UuGd|2qKma8v%
z>ejDVn3-c>N$k)YAl-Jy2(XDeXgcY165NW5ypOgky+<+O>Hd*Djvy^MEVhXX+vXUS
zW|@aDb0e0!V;%kPEb_j>pmV!hK1Y>%ChF1_@}*dTe*2;02y`t-zs~I9#_`B{yd(%iwv?e{@omdQsBrp{a56|cNFR*m&5FcNQ++$zx!
z77=*{659@DX)$Uw(XCBDoZ_gWf-9VrwIO_o3uP0%!c8Q!&B~HnhPe@N;{Lq2aqgx{
zLv;uGYQyJn2>CaF0zQY5$BUWBJLL}a+>fmz6MTuHh@CM5ED!wR*L!??K8K~^eUPKg
zU@3($U`G;|f7#1Nz$artA4Vsp_Bfuj_
zudJQvJOH=p{j`_@mE&TRwgd3SFo}hoz9h*+9C=
zqrPgay;EtW%WA3PtXSo;mcM@^Ml0KPG*8rHJJun;w6rnDEFvl~S<+1(zyc``jKp$E
zxX0MWSwPpmq}sZ$i?K?>33w8H2B#R$%M62pmEFGmKGs>x=k0xrU)@R^NuB$5{YiR6
zQZzjh?y{pyImu>h!il|et`oN&2g^YfDZAL2o}47=qXGi_9j;}+?Esc2pP4+pVq9u$
zkdYJq$e`RtFGVi&guL5;btKtWm)Sc|76rHw_qqIh4}WZkReF~g;_nWy_^r|~3BAwr
zyjtN`E!K%I-bo)qsQ0a^Liv8(a+8z6YP+w>54fVrNk6i(+0O-IjTIu>Bc@T!-pgqoutp
zTu=R0J{}0s1f7%e;M^m!44y#7DVTN3(z;;FQ9ug5W;bRiK8^G;~X~v>xS-yXx)rtS-{JbnOK
zWXD|3^ZB(g!$&bTZ?at9i?hTj8NtWWe5wyuC{i0e!*v8TKf-#>l7YU8)Zy?E)=#SX+?I!KN4>wH;K)Nmh7iDRr*I*9%i6%vSnwSA%CNjbm<=($Jo
zVc3q_^r|a~PK5hlij=qaiJc|Z3QuN_H`uG(*JtjCA8ps8zgAA#m$;uhl>rJq0-Baw
z?>WQm#wYf!`-Hxyo}3a0vzarurTBUgvg!jAitDWAo=N!^B7sx)e#cush_2d-Nr()`
z`ZKM&5^z%6SGW%_ZaW*zfzsr)kNI>yk1E&ubIVc5QAvJ_`S3x2nA$nLc0TO)E6CdF
z=~nn2#*1%Gq3?yDT|p#N66bP3BOUpPBm3#Y%1U?5jvF_bioc*_Y3_?gp&-
zwVv}?o0s3Fw0~T8^#)L63zcm-%u@-b;draJ9=?h?ViD|OKUhQp;8W^2?hfFF`Ce@imlYCdDT`jH7IKJvca6@MM0p*rGR2TOh?FKe;n{6ePOGJ6X1h
zii%L$auQj!oJ>|-o*mrZ<7Woy88}pvCRYPp*N2|)@=6Ig?p83g
z^YU6hqs_0wwS5bhl_ix#b$1N`UPXFy{HduabQZJMQ!LbzB$cmlLg9
z(v%Z=kPu2!NIfi7F}i8F8$9r5-kU7Y@5Rwlj$5}JJ^3*3KvMJ=2q%kv>oo
zZ=9MLO2v~1Tv~=T%hcp5KsIlP61S-7FsrL9Gd{DBaTX@O)nAvVN+yMpRJc5vtcNXo
zW(rZ;4ax9%IsIh<6?=EKvvVfEp)cFuE+5Gfyz+G
z>~G8Jkw-E^v-Kh)8O)fKoLpbZlP8lPz?@Xkyi-u<45fJNgD7;Dm-plpHpRuMq&wDb
z;W+YUITo(~Wv+p~tEa~fwbx+gtw7FcYWl#~^!X%KETc*=?ungoXzGTr&2Po(5|rn;
zG*`(l39LiwpR0ce!iqWSJRA8WgUi!zSd#92U)vrJQ?-yFw8~o_k4^db{
zUEtj^et&}h+oWf$UbU7@c$Df}_4~J3W7CWN`ke(Ccy&Gac6760WxHx$6);hQO6O6R
zeuhbnc5qXJL$N_WRla`d10hEzwZTAb0Ruyq!)Q2p*uHvyQrroBVV1*lG(4#0c#l&$
z&}gZcRRi>}JcNaNsB+PQ`;E=YsdtP#W_{L7&Q)=`cGOctn4uEn3P-MYf1RMbsF;+I
zkM>8$(f0%dP#QF+J#LrK$euIQ)^&thJ?Z2h0l`VdrMJaLa>s~
zLL&kR%j5Duuy%6@Yvi%@!zpDJ`Ply;SDXR^5C%#Mh>s9#i-3gb>~^_;iSF2nOL%kF
zo)!9%jLNf5NJrk3o->d)v#^MXiQyQiLxtAY&7VF|&M@#Kn(g{@)AnsAmA`CFqQ$H2i!%aGNu|yYKoc;7B#J{Npu)hFC0@s)jqJTm^5w#tAjM&-Z%Xe^o
z8NF!+XijVEU2eq3Dv$1tj(ZSu6kwzO_)5q9<;EkM{aCNwQJs|~GZtn+$jU@iwL8RY
zCnY?HF|UGn0^{%PGcLB+deojYRNMYjs8$*@9cAmUS?^zKf3vMFKAJvHPp2NuZou=*tFt0OiWE)^Z4`-;Tr@e~XURs&>z1Y%0*1Jzk2k
z=0>C^`)_BaBfGo0Y{v7Hi_?_ZSC)XnG?trS7yWx>!?EUxBbvzA1mW~Nu5syqFuPjV
z&dC9_n!0l|2q%feX@L06*u-7y1AxN%z|VYR!bc?n7aDs6uSo&&E|eCJzo76&N3&Cf
z8sMxJg(!yYLDG+HBNib&br3}|HPYSI2K*OU=kko8w72CPxc?59-rHep3K6Om1}ZLN
z6O!V5G*L*o&o}M4QVFN{v&Z(ub3iNh^XieV3%jG)I#MP5-1%2fCH>`V(LeJAV0E_;
zJ1Yr}Zd*f7o;;-kYkzEWjEbInX+=!%{{1*95A@|{#&su-M?4~6(K%1&*2|QrVi}=M
zPMyI_d=DJdfiBJ1^dZ2jv-w5H-UGp2hk+ly{jjMlvTSE3S_%G60ibwxv7U9f?kr=|
zfJuJp@VatX*50Yd@6Bsy3B7r!k!Xsuk@p?DQ83e8}D-h|xOzx85vL$$8
zHMT0H`+X9xN}?}G$T9Je>B@4fKeo!2CR)g-*4vFlI0TGRkyFe%(!goDrlz&BG89W$
zeO5k0Xzx9e_Ue*%S?RMKEoyxv(sg5F@np5~?lf)DKT#&65I+p~U8(aJ#`8y#%on
zBlq>Yom%au+M;i^d3Vh6ikJhs#(M7|(8px`ykn!I={a!T_@OJ9S`5bWPxFHiWWVq?ZVO6_e3c+(KXpJj+(`0VaAJy8iI={Mc-QmjT)e1#
zq+)8F%kBlH*g8PD7@O|%CY{D!ZUk;?s_ya9x=v8tz|p|7NTx`>;QTsFP+VMG$lhL?
zpM?MZxeQ-kz{&NCE$qQg&>tj*?tY=~Vt#Ejx8;B!@x;4fEDtU?yE-UOcdB$hh$ifc
z>Y(te5aMPP7q8N+AC|<})Csz)((2ZH;5ORO9aTKriH^J?yFFow8P6XF=c81m{X
zMLPxRPBk5Odw@@Nddqaq?=0Qt3SjJea?;azt?divu1e__p`_S4ESUyuCaJjNrYW3a
zAj6ys+k`o-Be6BrZzjR^8C#v~X6x>v+!a53r3#1Cx9hQASfvE@xB-0x3M@BbX>()c
z!7PvyIwzdCcw$+3#RE8HyzRh=qOBbP)EU0X)gto%I<^tY(T0#J8-zgI@1y6Q@u8eU
z3e~2AE+pM->dBapYh;nV5e=0q+%vne^~}tIs(HgIb;DL5HO+dvtWCLXl-WuH8BYfB
zuGM@N?zVP$x|AEN81V_?KN`k~OK`ORYVE1uVpNX%8N^k&>UgAMDCs=%Jv)!wdB7?H
zcI(pnzk@&w|JEbK$ETEMEQ|kEjP$|<0GV|xOUritR}Bppn=d*b_8+v}cdxSqpcMW%
z^ul5PbWzlNv%Ev%jYqwryYsIY6uvc=lN$wL!U$-5
z(m)^R9BK`TO>}Sb0@2X5wJUm5SXh?ei7AED0t(?!b79;bUa?rY8k7SBYdw>B7?yA%
z((eFkH}*jtnzs4t8XC?#vUC`Y%OqNTW?Z-
z{$*}M3S>MF2K(l#vHc|udAhHLJNO9_(oU(G8`As9bm`M1Nw>12+nKPzgLM(vWP(Bz
zPvw)zNl}}T@AYryMf&S@AP~wjXwPHS1Vn*?qYSO=G0doKnQc^lDKR~_z6P?Q}r}P7W{f}CJ{|aUQPxSY%KHmKQ)s_A=wf~yh|2AX)S1bX!
z=D(?K{L5DVvekdBf&I%%{^ceA@{<2tBmUR=`2WcIFxzYp1B|nJ?j=CVZ&;?jQuPVl
zo49bwYpJGfXjFQu&Yx^1}`X;t*
zU`_~elLkt5zU&+|zJ5v%Yx9f*^MYWX`j<}o(7u_d1|`BpMMX=zMh~K=-uHrX4{Pi7
zFne<75mOLoqUp2=KZN+~&olWgDy;858dfG9=ZTF(@B5=a5^c)McRel^c--gX%T_mU
zr%=8TJ#!-!!Q*UwNI98032wGU@R`_ol;+DeB|Myqp1yz1A%d^`+U#gxl{32ECjz#G
z6FYkYcgx(|JX7;!Y8{5FM>87K74Lj>z9wbs8M?FbWh<}jn!M*VYTpp|?Z4TV1g4n4
z5Ncoi+ucx~xvOipr*f-GW{XJdOryD#%M=9PxE9`3VTHu?*wfN`%mE@f5e15z`v_m;
zA856zb}DbS%!`8ODXX69^{$<+`xlcAUs6Q)v@1LLbbid
zKBb1|7gwJ<_VK&yKG?Mr8Zq@5xjlXV1aC2E4a_2L3e1x9q$vUG&9a5xu;XQ@c#4iR
z+5@&VSRmEV;71+6?LzO-_49#7CC-(gXbK6mdKrdowRDv&^X^NtUPmNxv@f5YE2Y-M
zzGyEs7N+IoFPUUq_;wj+SF)fK_U_%goxgw>Sx@LyjEKNTFdS<{h2
zWn1J-O`E9_)_&=uriE6eGV^J$J3m7XGQf;v)>`yV*}}d3tgWqEbGzgnL(0S)66`#O
zDdY#@rQfcGvcFl?MM;Q(fZ7pVv9F4${-kassAslwB!{c#>bG$R9XO0zP~uB%at$k;
z_m;xjRKI`cS4>rpCC^0yQ6>ilbEQByXWBx--1})~UB)?1clq8%!KEcEHiKaHciig~
z+uT9H)a6(v^2XQps2~=Yr!lCTj1qUhTSj6aj|F0g*3@(ePN-h76$<^1qiDra;q&__1oJ!X#=HW)*_gUz<9u*b5
z!^2UOZPpz{a(h+zBr)9y+WWG%^CZ{54gHGO$^Dy0Kzd=R1<|2uNmjLNyV46OLS{
zI=^Whn4js?0V_A6xL>kXA!?=_>FEthw73!7CxFZxg
zPTKAFcMwY}SBp6Deh@OZ`09+$el;)e(NgkE53+l8zsS<13!w!@ifz(rVLFQ?HVw
z?@Lw*g}2vxVA1cN$%h07Pjh@xAY#ibIm2Tg``I+jRdfYWO8aDMQ+SlwW?Op?2N$!R
z1%+kJI^{4bpA(zg?E6r-A;~<}KXdL4H%v8X`cHEyqLG}VNv^Rcd2)^Hr=;Xa_L7z1
z=XaGGdR%GfkDKwrsC~_tU&~k*-aK
zVFEtKc!;#t;I)+k)nJ8l
zglUS3BUS4=%mTbqQXpwpVEh+%olsR?k9@;of<0Pxe!hQy<_HU^aIftcALo^oEz8Wj
zUq8pj-7?;8@aNn$=&^5de)!RrlzESi?GOU*TNfIttmf2txDhZC8650$aANw^)4Ir>
zfk((U-t(}`n2QVB>L{3zoehch=HY*A=Dlz|?p2ICPL)tw^oGL7#^!wgb_iRKD`shF
zYgjqu@2IH7p5M=zL)^B!xw-E=zEd&%rNyYT{?qd>b!KLmhEYdBJ2A_l7AN)0Z=J?^
zcXkfyr6wjO-d9zbH-DF0TK}ve7k0xvJx5w((fOs-4r15TOByY7f*Zz0@v{w9aa`jVoektq0Ug?b&Ue)~&at@7OnC^sIf^t1NESs2{SSlNCnbEJe!I^p-U
zCliFkn3!S*uvtvWQ>?!3mR~1I1xH#xSeIKE)t{^3EgoKZJig?$T-O(9c#``3g&ZTa
zZniEZD@*dwb!y~(l)miL#DpiYrE{!wq+3H{SS*LaU!fB*7QKXfmP;uTQ0=B2
z@sYL1n1%8RTQ^C_BI}no>_=!UUbNnItRZ(HE}FuL>z1`{DJds^K!t^isIRQe*29ou
zbbr%zDEF$}+Z@kVm6K!UHhT$`ZjMDZ_P}dK%+%MH`6eHCyEyN|;wX{j$>6$l<;tIk>rBxH7^WLH
zZVaTIDNwk^e2D`T+;5;k%mUL
zM?U`qWMcB-*>m3Nt6qQ1o8$;-djJBD?bz29BsyA0zZ
zx-4~=c+)g!d)ip;OeVc}J6`pg<#HM-o{8@7BQtlxv91ow36J|p9lt-N@f-w(6gP(7
zB{ikmj$aZba0tdOuB<4MVEoMDV!@e|>Z+=W%F3y!iGyv97qo+-GOu*p2%$DFdUp!1
zHa0$0&wR`)9!?Mu7l%EF4h%fMk=wECoN}w#T-a+b8FuSd)30B*?CdHo1;kt|)Mt%g
zU+#>OeE&DZ?xnAsTug4R{pOD|9$j4|U%wLO)NOp5YY
zvbxmVtB3O}d~%bQR~|!A&wNF~m#0L5<92NG)?l#|_wS!SzjjU?$fX~B7xpTAmHfD|
zPo=;To6i>mZ!pW}XL58?4z2cyy|~zJD8&CjC||;Fg}*ehdEOPfogTqA)JI;&Vy98F
z;mz}FNhT_f+?v9|!VnRkJ{7cBM^zd+82dMtPnTsI1ZXpV*3l{3YFr9X<;q8oZ2rzl-)`yj(ce{Q5uA{`Q{+Qx=2*M
zqod7dG=~Vh<89&ZtgNZg(bloCw(05w#Z)k&oxKCILfw&!ikuunBi(VS+jJt&1+I=(
zS5>qzgw^_`^9wpKie^5?hUr=QIK(|nXg1O9zQPT!+YT9Yh?hD4{;HK>%
zJ#jL85)xJ`U!FhM&YdOyNZwe+UBfiA{7nZoY(ijiqv=zT^)JU*795)5u}{>Pt{7(mmyltbZ9nYE?;)k!>
z&H6F(m(Q~$Wj%l3_U_%AQ}8g-oZmrunsfh#Crpy;Bb8=>Sdq-1jb%ccSgQCNOgB5-K{AN(qp5Cs}oo3MtTv9Rn4d$R2qFTal6DnZaV0QpY%Y<6_DV*??K>yxh+Sl(6?D5+=?1jNG3X0s!H)dG&mu7
zgoW;Ww3p+08McgtDfavY>O0EFEdPVfs4B-Ye^pYoFW#m*c-D?&{PQ&M55(l_<-xf-UY!v0?J{Aie&zrr+NXsnfX}|kRyuxsM>X3a@2b&8seYRNm
z?b=8^C`~Ysb>&}16e%dscJ_8#3adpyxj3Rs6)A7MEVLt`gRb{)N5u;criLVBhlD&;
zMJ-`47>1jYMO&aMDp5jD%_-kNJilV%DI(%WLo$Mqg(dd=qrtp9K}1Bu-&8BvqSj5z
z%S^~TmW)-h^z#F5Z_x_B2xq1FWjM91EI?bkoC|V5~6;>(l
z-|CsasjpC)b4s=PU&hBUa(OJ<;BZ_X#+C6hc2-TmFoDufhf{c-oZ#{9>kHoLH8nXk
z&{Xvc=MOeFXDVxmc?45$f`=SiT3A&+$e&~06xt&j
z_v?H5@W9NpT;Y!w@|KQ{1MB-D28KD84#^vH7zF_eC}oP1HIA-Lh+?Wk$jkQ-}C;N_#5vb9_CQZQQ+H
zw04D4m>?3kwZd!q>BDRP053V1kC>*Wh40_jIy!a|6QwbR!TqbS%B)pr9>_ap&n389
zx`&^Ue4Cq_Jf%6KI$u7n933AA>26|rT2w4JfSw-Aj%xCV^+>G;*57))x3TaeZAhY`
zathJ|HF3|l+F?-&%g=+zfl*S=q`)q73RN2B9Zs3rE8API=O%@FKqxbt1qBg3Io-}r
zQYNoG9(?rVG`Nf$^~&nv;3G&oF`vmHc+FuSWpFbxbs;J4TZWa*6nyTrQ)lR2aCLqU
zytB1+vMq1}yDCrX3|gb3
z0FBeqU9>3Fot&)9b+j>ZIx@~Tevhg?nXPh5U>vHgt{3PpJK#xKzhXg~$awX}K(T!I
zHjALeL#_uPS>0=mH*hoD{0rKVXXNvOjt+le`~YYfN!{%w4xyks%Sc5hdo@`StbP&MeVeU#)u9z9J&_f-n6FXDYJ{
zMbNZfDd5Fj;fwz{Y_&C(JN@N@`6_70+bfDIs31sjVeVJi0v!
zx0xBl>@H>Dh9xqE(T2~+|WXHr&ImTeE@tDL2d
zED3-#%=#SVg3{MdpB{=O4oTd}}oJA~%Fw66FsEaHlpy_Tj@nou-yf
zj<}9JeKb-6w#j5eb_WxaLV{Q54cW(!A6Lj9ZF|GUZPJt~aGg;
z{)fi#V?KK$>pvR)zL4Kr?JSzEioKU5HB_w>v&FTypYP@E%?K?)o1Li3S$M$fAEn4$
zH>_5n1*7@eX%>5+9ho;;$?F<<>5<{C$yIS{6x=6$!x0B)(!h9NkPD
z7?=yVYz)Ab3KW{C%daApO$idnDUd0T%9IKnQz1GD-@0YjXuB_5oY!_`EnhwJ`$*Bs
z5}ib~2z=B2t(LXHF^_I{n~byDYaU#ti`&D#wOGa?Q_CV<{%sDXKfqG(t6f{l9HyZs
zuX+?Bph19OXH)F_lvZ!cYf!g655M_{A3%}~AM`)tlD31N
z*~nW_o!1sj<*#t*8=NeS&(hoYaTwS6x>=|%kZ2e($`zuDvKCXh0ZV=N`ZW^$^+~1I
z75J3espn+I=dpye@qg~>3aa$--llWN^wdyIS4b`5M3H17EoCW@!B)%N5h5>>HZeDnm=Zj86;?K
zCMJ5nsR^3$>(+}~gFx>o?aLVq0B#~}Y?Lc5E)G8lmy!=g*l`3>Ovc
zt*!Z&ik7l@)RY%rYiXm`q;&dH*$rj%lj8d3-?fEuY%MPOZQznnJ~)sQHc)r@?E1IN
zh9XKI2-KD?C)jK7sNNKHo@ka2KXM?|AsSme`Bdw2Ej?DkYn5{z*vFJRPS;6~m1b9?
zo&E^@a-{T~QkDO*i?d)VtKRp*8$-p^$mV0?5DVjJjfS=Nf0~Gz+1jMhpMP)r@Wr3B
zdLP%%J{@5jNEVxB?-r{HOetB~E?V)zf%Zk%M%e~S)cXqt1;{9#2+*gBHf0Sxl^)n+hr1%hL9{%lS
zQasuFK+C0gsKC)XLo;i|!n{~9pr5wX#-5CctRBHNC1^KtL1lD)f%o;!b+0~C{
z4OHOGhtosy{FT#}8^H1Z@MIod{K=iyk0E(!@+@&FKlE(hdtW?Rxs5K{@3X!OEyi;E
zqj4(fq#nNup3e)cx{HszPZV84{V#HdeON))MGEwz+5g}d#-jiE7!4ZTz!578NB_^B
z$?4#HjS0M%aeDkP*~U^`#!FhtYOwvb3##v46PSrlNZ3f{i|saZhJI3?{Fa~-QFFoq
zl=L;S@LOA35z2BbnRHmrgSvFXZu9%s+kp(FrKP=HRYg8n*??c485g!t_I;ai#>U2%
zS!#3Dl3U;QCVrK8$fN)B^-T)N%*KYTw8o9e=x*aZ#(V{CV~uA`Hn~#$pq;xa2{R+V
zf0K!eOPS5`>iRk(Q$E+AV7&w2D~;?I;^#;2?mK*a+2Wg|%9k5Az^}7^uqNr!cEBk^
zj8-%^Hf{tuUkDr9`Lm5vqS+(ny8J-H=q2i~{L*5dNnTGeEz||CTDv+s$5Sv+p6;J_
zjW{m&u^NTed_20d^_x%YC-_YQTu(%VFel{OrDSH(c
z1A%ho^a7-AObhV7MeJ(zW;2s7C)neN)s)HW>g5~Ie_^YzFI%6^
zx*&SL#n$HK_g|&^t}ZUhPsmav>(nuH0wYkjyx+V!u(TB$33*;@p#q=BTh{9#vZtNXh={v6TFqQBIJIYo2F>zNKmPEjZx9
zDEGuaA>mgosw*pp#@Qktf=rD5ed)^$*@F$ZV@YW#H^S4)%Yp37p+)`54CvQBaC~lB
zn)Xktspy*3{{3}C1Sp*++2XshC!gqNUkPJ(otyg#1iUN^W|kr$Dj}$?!<2isQTN}y
z@p^7jdUm#wR&?^TuhmxVMQ%GWTgv-p0K}aui8Gb^WO7~4|6yt9&u6PDN$RRdwR`b*
zu6C}MxX8+)+020pW_)dCxGDYRA2z<06dP-)Y4#jr-P^rh74uA~N-|h1+Iq4>%31ZT
zAx4wq=H|zbAKbAh-q>!ClF8?St}QHP2*097-q+o`^v*le$WHaqA!X@HAD_ky3K5*-
z!dH{T)HZcKq(BoMp4J}9Ze(P%-+u&Ia<)hNZ}fs~A@{@hLqx=Ca?)MKomo=5FlTuA
zT&WkPs=w#e)k^`CBXSUMl6m(0Fm@?I$Kbbaog!qMXfvv|8`SCR@@rH4{mOjzjk*PF
za-)-?q+H}~g||m_ngP-0?dRV*uU{McKlRkL@^5rYRkzMrz03yIT9CZX&({@i@xnhp
zJ`~y5%dh9S1AtTPTGm@f$JNal{Mgp?2(sL9mQ}_EG4`NJ@q^l*#!=9kv_QmY39j5B
zFYg=;{dY<8tK3TqsXfnv!fk4pFc@Hdz+^H)0ublHdjTSbs~WF#c&cKu0pgy$FT^Ef
zF@lBvo|V922|^d^sdIq`Ql6AdAkS|M8%cL}I0oY`LkWv&%l*534%vZ1p)U
z#Y%(K?KXB!=Cf0#hTFRWUPQ*oGvVkgTOE(Uptk4y?iY@ml&kSle$00~ZX@nkx*NwW
zY_H!@^{<>s=ZHdz0y&!J3s*Mzn?rlw5{ZUBQ?sS9u`VwUJv{ue>dkbz>PT{JeV17g
zki<1IEi;axi3zQ{LJEw<{P#z!I^)&j_@9!4Ser&0)Zy@Z{D2!(5H*4N(*
z`Oq9}K5}j;@H?S@XlN<_ed8`Wr*X<_pjfqY;6}Y
z7-93h^MXO96M9vJx^`6389|;Zuaxx*n~!_ReBqToQr&P6UPmWs$Njyer=L0d1_wI~>1ltp5(
ztFf?mwIo&6wYymK^u#^p&(CS-IRU~K@-z9Y*|h9Dx<9nFwUw%_C#Ce^%)f_=1HeqK
zGc)r=cVXh=mr(UBEzfNQ>7
zTP`fQMfb5f3Wsv{@j2;-7>H7qS%eIGGV&??sN*Tu7zh5C#Iv=oo42EbH@n{9Tw+L%
z%bS#~607|?&Gud>p@((BTu6bS&R#cR=$!J6p#Q?m>^%4HLbaZ^g_3U0S-F1DQ$ZuG
zOB^s!QPJA^&6GB|!nUm7V5JJN8BUoh`4@6I$4kR841*(Got;JpD<9I*(ulU>wl+3Q
z%E~~S1?2ti)z50lqE=ckp~#na?Z#kS2aiSXAE?~5vAaG#GD5)P)yHupk}uyj+SXFx
zA=SU&r@J_U1_)V!gt9e_DyisBj4c*NG-&R<dSCy3>t4o1fdKT_Km8S8s1wHv#>exMYzqO4Qo0KPs#~u?
zIh~Y}IP+z$5%Wb(0Zy`Kd$$}r3DaazoRE;9$IpCd@leL;QmG+N6wnEK62Afyn>sp*
z`%W<0R6+;8x#2M({r&U%ctb%C?}cyoqj)pM-FM%%UGDD{6cS1g?6rAZx(&$yzhQUI
z^w;G}d4E7JUCL9w0pVfaM=HNcRX3{m{>XO4)xdxqU=-W7gPd-ik{Dld<)p%Jx;h1d
z^mpo7x_I_|0|?(B?SM_=fl~jKyNy(z#(y*BXL6eueN$XMc1qz@xzRoUE-^`}KmMyH
zP(;07qL_vc(9oYdxpyx!a6O5Ma|`H{lsfwlY_&QwF`=sii)DmNv;~6g^dLIr3J>i2
zs)?4*i$g+&S$UaiLQk_DLKS?*m{6)x?gEAfxL(x`obzP}D%O6=J>W?}a^-S{6
zzorl>CL61mYEXy9_2k@PVOhms;)gB!s1G(3WDXX?kVqd~+@Y;bNNXgBY*i;G8r>du
zu}M#EI~`^*AtDwcBbQJoBevD=xJ?(1o!Y}FXzyo_OP!tfp&xu?ywv4s@87GC^&a^&
zQ{!tY^K@XjoJ0e1py^>cm&-(=5^Ed=>uL4Q$zA)%sa;)#HegsMU;n43Skf^`Q$-~?
zFIq(CBK2Q2r%Z(I;_w7;CKro-!d^)KvU6*z5-TRT(p)De`zTq`{4I-kxt>UrMenO$
zG5ja*yo`)$^W5CeR|yZ68YUI1XU`xwOlQM+61?uMP_wOOa(
z(|4fF)#u~cVjYb8lo$3v!SYw>DL8An2f*1;>uP&AyM|B}#h%hA1)q%wpVzAzEUfYS
zIHmd1R_91DwipAcylY~gl`M@_`BwhTWIpi{c@s;VDj~b??d|zulkO^^$uu8}b~N>h
z2~njRV%l1ZZAVm8%pdb6q$7&G{$7-nh7X{n7<*1uCk`P#rtYj!Do0AZ9--xA__n5!~;8;hjWfsyc37
zn5thh6f&9hZJBmQKgoSQF&54K1MIW_baIJyt#jEKY}aF{ZoXQKk6s=nO(am
RdnN$nb5B=4mvv4FO#tji&>jE)
diff --git a/frontend/__snapshots__/exporter-exporter--trends-line-insight-detailed--light.png b/frontend/__snapshots__/exporter-exporter--trends-line-insight-detailed--light.png
index 332ea24ce308456a5c4dc18262bded8de8689748..d4377362d92701640737571d7d9f29c058ada487 100644
GIT binary patch
literal 21344
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