diff --git a/frontend/__snapshots__/scenes-app-insights--funnel-top-to-bottom-breakdown-edit--light.png b/frontend/__snapshots__/scenes-app-insights--funnel-top-to-bottom-breakdown-edit--light.png
index 0fa44c98b2c5b..6da90a8a2e7e1 100644
Binary files a/frontend/__snapshots__/scenes-app-insights--funnel-top-to-bottom-breakdown-edit--light.png and b/frontend/__snapshots__/scenes-app-insights--funnel-top-to-bottom-breakdown-edit--light.png differ
diff --git a/frontend/__snapshots__/scenes-app-pipeline--pipeline-landing-page--light.png b/frontend/__snapshots__/scenes-app-pipeline--pipeline-landing-page--light.png
index 7178f9989b67f..a131b2e67755b 100644
Binary files a/frontend/__snapshots__/scenes-app-pipeline--pipeline-landing-page--light.png and b/frontend/__snapshots__/scenes-app-pipeline--pipeline-landing-page--light.png differ
diff --git a/frontend/__snapshots__/scenes-app-pipeline--pipeline-landing-page-iff-legacy-sources--light.png b/frontend/__snapshots__/scenes-app-pipeline--pipeline-landing-page-iff-legacy-sources--light.png
index b215929f99da6..276e305964386 100644
Binary files a/frontend/__snapshots__/scenes-app-pipeline--pipeline-landing-page-iff-legacy-sources--light.png and b/frontend/__snapshots__/scenes-app-pipeline--pipeline-landing-page-iff-legacy-sources--light.png differ
diff --git a/frontend/__snapshots__/scenes-app-pipeline--pipeline-overview-page--light.png b/frontend/__snapshots__/scenes-app-pipeline--pipeline-overview-page--light.png
index 7178f9989b67f..a131b2e67755b 100644
Binary files a/frontend/__snapshots__/scenes-app-pipeline--pipeline-overview-page--light.png and b/frontend/__snapshots__/scenes-app-pipeline--pipeline-overview-page--light.png differ
diff --git a/frontend/src/lib/components/IframedToolbarBrowser/iframedToolbarBrowserLogic.ts b/frontend/src/lib/components/IframedToolbarBrowser/iframedToolbarBrowserLogic.ts
index 5792dbf59b716..36fba3da699b6 100644
--- a/frontend/src/lib/components/IframedToolbarBrowser/iframedToolbarBrowserLogic.ts
+++ b/frontend/src/lib/components/IframedToolbarBrowser/iframedToolbarBrowserLogic.ts
@@ -162,6 +162,9 @@ export const iframedToolbarBrowserLogic = kea([
currentFullUrl: [
(s) => [s.browserUrl, s.currentPath],
(browserUrl, currentPath) => {
+ if (!browserUrl) {
+ return null
+ }
return browserUrl + '/' + currentPath
},
],
@@ -325,6 +328,12 @@ export const iframedToolbarBrowserLogic = kea([
clearTimeout(cache.errorTimeout)
clearTimeout(cache.warnTimeout)
},
+ setIframeBanner: ({ banner }) => {
+ posthog.capture('in-app iFrame banner set', {
+ level: banner?.level,
+ message: banner?.message,
+ })
+ },
})),
afterMount(({ actions, values }) => {
diff --git a/frontend/src/scenes/dashboard/DashboardTemplateChooser.tsx b/frontend/src/scenes/dashboard/DashboardTemplateChooser.tsx
index b631392b38c9d..a2a320120ff15 100644
--- a/frontend/src/scenes/dashboard/DashboardTemplateChooser.tsx
+++ b/frontend/src/scenes/dashboard/DashboardTemplateChooser.tsx
@@ -13,12 +13,13 @@ import {
} from 'scenes/dashboard/dashboards/templates/dashboardTemplatesLogic'
import { newDashboardLogic } from 'scenes/dashboard/newDashboardLogic'
-import { DashboardTemplateType } from '~/types'
+import { DashboardTemplateType, TemplateAvailabilityContext } from '~/types'
export function DashboardTemplateChooser({
scope = 'default',
onItemClick,
redirectAfterCreation = true,
+ availabilityContexts,
}: DashboardTemplateProps): JSX.Element {
const templatesLogic = dashboardTemplatesLogic({ scope })
const { allTemplates, allTemplatesLoading } = useValues(templatesLogic)
@@ -35,61 +36,72 @@ export function DashboardTemplateChooser({
return (
- {
- if (isLoading) {
- return
- }
- setIsLoading(true)
- addDashboard({
- name: 'New Dashboard',
- show: true,
- })
- }}
- index={0}
- data-attr="create-dashboard-blank"
- />
+ {!availabilityContexts || availabilityContexts.includes(TemplateAvailabilityContext.GENERAL) ? (
+ {
+ if (isLoading) {
+ return
+ }
+ setIsLoading(true)
+ addDashboard({
+ name: 'New Dashboard',
+ show: true,
+ })
+ }}
+ index={0}
+ data-attr="create-dashboard-blank"
+ />
+ ) : null}
{allTemplatesLoading ? (
) : (
- allTemplates.map((template, index) => (
- {
- if (isLoading) {
- return
- }
- setIsLoading(true)
- // while we might receive templates from the external repository
- // we need to handle templates that don't have variables
- if ((template.variables || []).length === 0) {
- if (template.variables === null) {
- template.variables = []
+ allTemplates
+ .filter((template) => {
+ if (availabilityContexts) {
+ return availabilityContexts.some((context) =>
+ template.availability_contexts?.includes(context)
+ )
+ }
+ return true
+ })
+ .map((template, index) => (
+ {
+ if (isLoading) {
+ return
}
- createDashboardFromTemplate(
- template,
- template.variables || [],
- redirectAfterCreation
- )
- } else {
- if (!newDashboardModalVisible) {
- showVariableSelectModal(template)
+ setIsLoading(true)
+ // while we might receive templates from the external repository
+ // we need to handle templates that don't have variables
+ if ((template.variables || []).length === 0) {
+ if (template.variables === null) {
+ template.variables = []
+ }
+ createDashboardFromTemplate(
+ template,
+ template.variables || [],
+ redirectAfterCreation
+ )
} else {
- setActiveDashboardTemplate(template)
+ if (!newDashboardModalVisible) {
+ showVariableSelectModal(template)
+ } else {
+ setActiveDashboardTemplate(template)
+ }
}
- }
- onItemClick?.(template)
- }}
- index={index + 1}
- data-attr="create-dashboard-from-template"
- />
- ))
+ onItemClick?.(template)
+ }}
+ index={index + 1}
+ data-attr="create-dashboard-from-template"
+ />
+ ))
)}
diff --git a/frontend/src/scenes/dashboard/dashboardTemplateVariablesLogic.ts b/frontend/src/scenes/dashboard/dashboardTemplateVariablesLogic.ts
index de17c8108209a..506147c014565 100644
--- a/frontend/src/scenes/dashboard/dashboardTemplateVariablesLogic.ts
+++ b/frontend/src/scenes/dashboard/dashboardTemplateVariablesLogic.ts
@@ -40,6 +40,7 @@ export const dashboardTemplateVariablesLogic = kea ({ variableName, action }),
setVariableForPageview: (variableName: string, url: string) => ({ variableName, url }),
+ setVariableForScreenview: (variableName: string) => ({ variableName }),
setActiveVariableIndex: (index: number) => ({ index }),
incrementActiveVariableIndex: true,
possiblyIncrementActiveVariableIndex: true,
@@ -47,6 +48,7 @@ export const dashboardTemplateVariablesLogic = kea ({ isSelecting }),
setActiveVariableCustomEventName: (customEventName?: string | null) => ({ customEventName }),
+ maybeResetActiveVariableCustomEventName: true,
}),
reducers({
variables: [
@@ -194,12 +196,34 @@ export const dashboardTemplateVariablesLogic = kea {
+ const step: TemplateVariableStep = {
+ id: '$screenview',
+ math: BaseMathType.UniqueUsers,
+ type: EntityTypes.EVENTS,
+ order: 0,
+ name: '$screenview',
+ custom_name: variableName,
+ }
+ const filterGroup: FilterType = {
+ events: [step],
+ }
+ actions.setVariable(variableName, filterGroup)
+ actions.setIsCurrentlySelectingElement(false)
+ },
toolbarMessageReceived: ({ type, payload }) => {
if (type === PostHogAppToolbarEvent.PH_NEW_ACTION_CREATED) {
actions.setVariableFromAction(payload.action.name, payload.action as ActionType)
actions.disableElementSelector()
}
},
+ maybeResetActiveVariableCustomEventName: () => {
+ if (!values.activeVariable?.touched || !values.activeVariable?.default?.custom_event) {
+ actions.setActiveVariableCustomEventName(null)
+ } else if (values.activeVariable?.default?.custom_event) {
+ actions.setActiveVariableCustomEventName(values.activeVariable.default.id)
+ }
+ },
})),
propsChanged(({ actions, props }, oldProps) => {
if (props.variables !== oldProps.variables) {
diff --git a/frontend/src/scenes/dashboard/dashboards/templates/dashboardTemplatesLogic.tsx b/frontend/src/scenes/dashboard/dashboards/templates/dashboardTemplatesLogic.tsx
index c3c8a58c10817..3da908e2c3f27 100644
--- a/frontend/src/scenes/dashboard/dashboards/templates/dashboardTemplatesLogic.tsx
+++ b/frontend/src/scenes/dashboard/dashboards/templates/dashboardTemplatesLogic.tsx
@@ -3,7 +3,7 @@ import { loaders } from 'kea-loaders'
import api from 'lib/api'
import { featureFlagLogic } from 'lib/logic/featureFlagLogic'
-import { DashboardTemplateScope, DashboardTemplateType } from '~/types'
+import { DashboardTemplateScope, DashboardTemplateType, TemplateAvailabilityContext } from '~/types'
import type { dashboardTemplatesLogicType } from './dashboardTemplatesLogicType'
@@ -12,6 +12,7 @@ export interface DashboardTemplateProps {
scope?: 'default' | DashboardTemplateScope
onItemClick?: (template: DashboardTemplateType) => void
redirectAfterCreation?: boolean
+ availabilityContexts?: TemplateAvailabilityContext[]
}
export const dashboardTemplatesLogic = kea([
diff --git a/frontend/src/scenes/dashboard/newDashboardLogic.ts b/frontend/src/scenes/dashboard/newDashboardLogic.ts
index fb83b3ca15b70..44fa0252f5889 100644
--- a/frontend/src/scenes/dashboard/newDashboardLogic.ts
+++ b/frontend/src/scenes/dashboard/newDashboardLogic.ts
@@ -79,11 +79,13 @@ export const newDashboardLogic = kea([
createDashboardFromTemplate: (
template: DashboardTemplateType,
variables: DashboardTemplateVariableType[],
- redirectAfterCreation?: boolean
+ redirectAfterCreation?: boolean,
+ creationContext: string | null = null
) => ({
template,
variables,
redirectAfterCreation,
+ creationContext,
}),
submitNewDashboardSuccessWithResult: (result: DashboardType, variables?: DashboardTemplateVariableType[]) => ({
result,
@@ -178,7 +180,12 @@ export const newDashboardLogic = kea([
actions.clearActiveDashboardTemplate()
actions.resetNewDashboard()
},
- createDashboardFromTemplate: async ({ template, variables, redirectAfterCreation = true }) => {
+ createDashboardFromTemplate: async ({
+ template,
+ variables,
+ redirectAfterCreation = true,
+ creationContext = null,
+ }) => {
actions.setIsLoading(true)
const tiles = makeTilesUsingVariables(template.tiles, variables)
const dashboardJSON = {
@@ -189,7 +196,7 @@ export const newDashboardLogic = kea([
try {
const result: DashboardType = await api.create(
`api/projects/${teamLogic.values.currentTeamId}/dashboards/create_from_template_json`,
- { template: dashboardJSON }
+ { template: dashboardJSON, creation_context: creationContext }
)
actions.hideNewDashboardModal()
actions.resetNewDashboard()
diff --git a/frontend/src/scenes/onboarding/productAnalyticsSteps/DashboardTemplateConfigureStep.tsx b/frontend/src/scenes/onboarding/productAnalyticsSteps/DashboardTemplateConfigureStep.tsx
index 6b41d63cc00fe..c4f636e174bba 100644
--- a/frontend/src/scenes/onboarding/productAnalyticsSteps/DashboardTemplateConfigureStep.tsx
+++ b/frontend/src/scenes/onboarding/productAnalyticsSteps/DashboardTemplateConfigureStep.tsx
@@ -28,19 +28,12 @@ const UrlInput = ({ iframeRef }: { iframeRef: React.RefObject
const { setBrowserUrl, setInitialPath } = useActions(
iframedToolbarBrowserLogic({ iframeRef, clearBrowserUrlOnUnmount: true })
)
- const { browserUrl, currentPath, currentFullUrl } = useValues(
+ const { browserUrl, currentPath } = useValues(
iframedToolbarBrowserLogic({ iframeRef, clearBrowserUrlOnUnmount: true })
)
const { snippetHosts } = useValues(sdksLogic)
const { addUrl } = useActions(authorizedUrlListLogic({ actionId: null, type: AuthorizedUrlListType.TOOLBAR_URLS }))
const [inputValue, setInputValue] = useState(currentPath)
- const { activeDashboardTemplate } = useValues(newDashboardLogic)
- const theDashboardTemplateVariablesLogic = dashboardTemplateVariablesLogic({
- variables: activeDashboardTemplate?.variables || [],
- })
- const { setVariableForPageview, setActiveVariableCustomEventName } = useActions(theDashboardTemplateVariablesLogic)
- const { activeVariable } = useValues(theDashboardTemplateVariablesLogic)
- const { hideCustomEventField } = useActions(onboardingTemplateConfigLogic)
useEffect(() => {
setInputValue(currentPath)
@@ -87,18 +80,6 @@ const UrlInput = ({ iframeRef }: { iframeRef: React.RefObject
setInitialPath(inputValue || '')
}}
/>
- {
- setVariableForPageview(activeVariable.name, currentFullUrl)
- setActiveVariableCustomEventName(null)
- hideCustomEventField()
- }}
- >
- Select pageview
-
)
}
@@ -272,6 +253,7 @@ export const OnboardingDashboardTemplateConfigureStep = ({
{' '}
(no need to send it now) .
+ PS! These don't have to be perfect, you can fine-tune them later.
diff --git a/frontend/src/scenes/onboarding/productAnalyticsSteps/DashboardTemplateSelectStep.tsx b/frontend/src/scenes/onboarding/productAnalyticsSteps/DashboardTemplateSelectStep.tsx
index ce488672dd8d1..0c437a6594e83 100644
--- a/frontend/src/scenes/onboarding/productAnalyticsSteps/DashboardTemplateSelectStep.tsx
+++ b/frontend/src/scenes/onboarding/productAnalyticsSteps/DashboardTemplateSelectStep.tsx
@@ -4,6 +4,8 @@ import { useEffect } from 'react'
import { DashboardTemplateChooser } from 'scenes/dashboard/DashboardTemplateChooser'
import { newDashboardLogic } from 'scenes/dashboard/newDashboardLogic'
+import { TemplateAvailabilityContext } from '~/types'
+
import { onboardingLogic, OnboardingStepKey } from '../onboardingLogic'
import { OnboardingStep } from '../OnboardingStep'
import { onboardingTemplateConfigLogic } from './onboardingTemplateConfigLogic'
@@ -15,7 +17,7 @@ export const OnboardingDashboardTemplateSelectStep = ({
}): JSX.Element => {
const { goToNextStep } = useActions(onboardingLogic)
const { clearActiveDashboardTemplate } = useActions(newDashboardLogic)
- const { setDashboardCreatedDuringOnboarding } = useActions(onboardingTemplateConfigLogic)
+ const { setDashboardCreatedDuringOnboarding, reportTemplateSelected } = useActions(onboardingTemplateConfigLogic)
// TODO: this is hacky, find a better way to clear the active template when coming back to this screen
useEffect(() => {
@@ -44,12 +46,15 @@ export const OnboardingDashboardTemplateSelectStep = ({
{
+ // clear the saved dashboard so we don't skip the next step
setDashboardCreatedDuringOnboarding(null)
+ reportTemplateSelected(template)
if (template.variables?.length && template.variables.length > 0) {
goToNextStep()
}
}}
redirectAfterCreation={false}
+ availabilityContexts={[TemplateAvailabilityContext.ONBOARDING]}
/>
)
diff --git a/frontend/src/scenes/onboarding/productAnalyticsSteps/DashboardTemplateVariables.tsx b/frontend/src/scenes/onboarding/productAnalyticsSteps/DashboardTemplateVariables.tsx
index 074002255b63b..48736120c2ba6 100644
--- a/frontend/src/scenes/onboarding/productAnalyticsSteps/DashboardTemplateVariables.tsx
+++ b/frontend/src/scenes/onboarding/productAnalyticsSteps/DashboardTemplateVariables.tsx
@@ -1,5 +1,5 @@
import { IconCheckCircle, IconInfo, IconTarget, IconTrash } from '@posthog/icons'
-import { LemonBanner, LemonButton, LemonCollapse, LemonInput, LemonLabel, Spinner } from '@posthog/lemon-ui'
+import { LemonBanner, LemonButton, LemonCollapse, LemonInput, LemonLabel, LemonMenu, Spinner } from '@posthog/lemon-ui'
import { useActions, useValues } from 'kea'
import { iframedToolbarBrowserLogic } from 'lib/components/IframedToolbarBrowser/iframedToolbarBrowserLogic'
import { useEffect } from 'react'
@@ -25,6 +25,8 @@ function VariableSelector({
})
const {
setVariable,
+ setVariableForPageview,
+ setVariableForScreenview,
resetVariable,
goToNextUntouchedActiveVariableIndex,
incrementActiveVariableIndex,
@@ -43,6 +45,9 @@ function VariableSelector({
const { enableElementSelector, disableElementSelector, setNewActionName } = useActions(
iframedToolbarBrowserLogic({ iframeRef, clearBrowserUrlOnUnmount: true })
)
+ const { currentFullUrl, browserUrl, currentPath } = useValues(
+ iframedToolbarBrowserLogic({ iframeRef, clearBrowserUrlOnUnmount: true })
+ )
const variable: DashboardTemplateVariableType | undefined = variables.find((v) => v.name === variableName)
if (!variable) {
@@ -85,9 +90,15 @@ function VariableSelector({
Page URL: {variable.default.url || 'any url'}
>
+ ) : variable.default.type === EntityTypes.EVENTS &&
+ variable.default.name == '$screenview' ? (
+
+ Screenview:{' '}
+ {variable.default.properties?.[0].value || 'any screenview'}
+
) : variable.default.type === EntityTypes.EVENTS ? (
- Pageview URL:{' '}
+ Pageview URL contains:{' '}
{variable.default.properties?.[0].value || 'any url'}
) : null}
@@ -113,11 +124,12 @@ function VariableSelector({
{
if (v) {
setActiveVariableCustomEventName(v)
setVariable(variable.name, {
- events: [{ id: v, math: 'dau', type: 'events' }],
+ events: [{ id: v, math: 'dau', type: 'events', custom_event: true }],
})
} else {
setActiveVariableCustomEventName(null)
@@ -127,7 +139,14 @@ function VariableSelector({
onBlur={() => {
if (activeVariableCustomEventName) {
setVariable(variable.name, {
- events: [{ id: activeVariableCustomEventName, math: 'dau', type: 'events' }],
+ events: [
+ {
+ id: activeVariableCustomEventName,
+ math: 'dau',
+ type: 'events',
+ custom_event: true,
+ },
+ ],
})
} else {
resetVariable(variable.id)
@@ -161,13 +180,14 @@ function VariableSelector({
- !allVariablesAreTouched
+ onClick={() => {
+ customEventFieldShown && hideCustomEventField()
+ allVariablesAreTouched
? goToNextUntouchedActiveVariableIndex()
: variables.length !== activeVariableIndex + 1
? incrementActiveVariableIndex()
: null
- }
+ }}
>
Continue
@@ -207,6 +227,41 @@ function VariableSelector({
Select from site
)}
+
+ This pageview{' '}
+ {currentFullUrl ? (
+
+ {!currentPath ? browserUrl : '/' + currentPath}
+
+ ) : null}
+
+ ),
+ onClick: () => setVariableForPageview(variable.name, currentFullUrl || ''),
+ disabledReason: !currentFullUrl
+ ? 'Please select a site to use a specific pageview'
+ : undefined,
+ },
+ {
+ label: 'Any pageview',
+ onClick: () => setVariableForPageview(variable.name, browserUrl || ''),
+ },
+ {
+ label: 'Any screenview (mobile apps)',
+ onClick: () => setVariableForScreenview(variable.name),
+ },
+ ],
+ },
+ ]}
+ >
+ Use pageview
+
{
diff --git a/frontend/src/scenes/onboarding/productAnalyticsSteps/onboardingTemplateConfigLogic.ts b/frontend/src/scenes/onboarding/productAnalyticsSteps/onboardingTemplateConfigLogic.ts
index 51132bf69b9ca..6dffe47f5f3bc 100644
--- a/frontend/src/scenes/onboarding/productAnalyticsSteps/onboardingTemplateConfigLogic.ts
+++ b/frontend/src/scenes/onboarding/productAnalyticsSteps/onboardingTemplateConfigLogic.ts
@@ -1,9 +1,11 @@
import { actions, connect, kea, listeners, path, reducers } from 'kea'
import { urlToAction } from 'kea-router'
+import posthog from 'posthog-js'
+import { dashboardTemplateVariablesLogic } from 'scenes/dashboard/dashboardTemplateVariablesLogic'
import { newDashboardLogic } from 'scenes/dashboard/newDashboardLogic'
import { urls } from 'scenes/urls'
-import { DashboardType } from '~/types'
+import { DashboardTemplateType, DashboardType } from '~/types'
import { onboardingLogic, OnboardingStepKey } from '../onboardingLogic'
import type { onboardingTemplateConfigLogicType } from './onboardingTemplateConfigLogicType'
@@ -14,10 +16,17 @@ import type { onboardingTemplateConfigLogicType } from './onboardingTemplateConf
export const onboardingTemplateConfigLogic = kea([
path(['scenes', 'onboarding', 'productAnalyticsSteps', 'onboardingTemplateConfigLogic']),
connect({
- values: [newDashboardLogic, ['activeDashboardTemplate']],
+ values: [newDashboardLogic, ['activeDashboardTemplate'], dashboardTemplateVariablesLogic, ['activeVariable']],
actions: [
newDashboardLogic,
['submitNewDashboardSuccessWithResult', 'setIsLoading'],
+ dashboardTemplateVariablesLogic,
+ [
+ 'setActiveVariableIndex',
+ 'incrementActiveVariableIndex',
+ 'setActiveVariableCustomEventName',
+ 'maybeResetActiveVariableCustomEventName',
+ ],
onboardingLogic,
['goToPreviousStep', 'setOnCompleteOnboardingRedirectUrl'],
],
@@ -26,6 +35,7 @@ export const onboardingTemplateConfigLogic = kea ({ dashboard }),
showCustomEventField: true,
hideCustomEventField: true,
+ reportTemplateSelected: (template: DashboardTemplateType) => ({ template }),
}),
reducers({
dashboardCreatedDuringOnboarding: [
@@ -44,13 +54,42 @@ export const onboardingTemplateConfigLogic = kea ({
+ listeners(({ actions, values }) => ({
submitNewDashboardSuccessWithResult: ({ result, variables }) => {
if (result && variables?.length == 0) {
// dashbboard was created without variables, go to next step for success message
onboardingLogic.actions.goToNextStep()
}
actions.setOnCompleteOnboardingRedirectUrl(urls.dashboard(result.id))
+ posthog.capture('dashboard created during onboarding', {
+ dashboard_id: result.id,
+ creation_mode: result.creation_mode,
+ title: result.name,
+ has_variables: variables?.length ? variables?.length > 0 : false,
+ total_variables: variables?.length || 0,
+ variables: variables?.map((v) => v.name),
+ })
+ },
+ reportTemplateSelected: ({ template }) => {
+ posthog.capture('template selected during onboarding', {
+ template_id: template.id,
+ template_name: template.template_name,
+ variables: template.variables?.map((v) => v.name),
+ })
+ },
+ setActiveVariableIndex: () => {
+ actions.maybeResetActiveVariableCustomEventName()
+ },
+ incrementActiveVariableIndex: () => {
+ actions.maybeResetActiveVariableCustomEventName()
+ },
+ maybeResetActiveVariableCustomEventName: () => {
+ if (values.activeVariable.default?.custom_event) {
+ actions.showCustomEventField()
+ actions.setActiveVariableCustomEventName(values.activeVariable?.default?.id)
+ } else {
+ actions.hideCustomEventField()
+ }
},
})),
urlToAction(({ actions, values }) => ({
diff --git a/frontend/src/toolbar/actions/actionsTabLogic.tsx b/frontend/src/toolbar/actions/actionsTabLogic.tsx
index be2b0fa13f160..e9bde322fca0e 100644
--- a/frontend/src/toolbar/actions/actionsTabLogic.tsx
+++ b/frontend/src/toolbar/actions/actionsTabLogic.tsx
@@ -197,6 +197,7 @@ export const actionsTabLogic = kea([
const actionToSave = {
...formValues,
steps: formValues.steps?.map(stepToDatabaseFormat) || [],
+ creation_context: values.automaticActionCreationEnabled ? 'onboarding' : null,
}
const { apiURL, temporaryToken } = values
const { selectedActionId } = values
diff --git a/frontend/src/types.ts b/frontend/src/types.ts
index b8bb542b71625..173061faddd10 100644
--- a/frontend/src/types.ts
+++ b/frontend/src/types.ts
@@ -1795,6 +1795,11 @@ export interface DashboardType extends DashboardBasicType {
filters: DashboardFilter
}
+export enum TemplateAvailabilityContext {
+ GENERAL = 'general',
+ ONBOARDING = 'onboarding',
+}
+
export interface DashboardTemplateType {
id: string
team_id?: number
@@ -1807,6 +1812,7 @@ export interface DashboardTemplateType {
tags?: string[]
image_url?: string
scope?: DashboardTemplateScope
+ availability_contexts?: TemplateAvailabilityContext[]
}
export interface MonacoMarker {
@@ -2172,6 +2178,7 @@ export interface TemplateVariableStep {
url?: string | null
properties?: Record[]
custom_name?: string
+ custom_event?: boolean
}
export interface PropertiesTimelineFilterType {
diff --git a/latest_migrations.manifest b/latest_migrations.manifest
index d0fb78b374262..ae3ec24fd92dc 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: 0472_experiment_metrics
+posthog: 0473_dashboardtemplate_availability_contexts
sessions: 0001_initial
social_django: 0010_uid_db_index
two_factor: 0007_auto_20201201_1019
diff --git a/posthog/api/action.py b/posthog/api/action.py
index 4a669cef0d398..bd05b681e7c00 100644
--- a/posthog/api/action.py
+++ b/posthog/api/action.py
@@ -1,17 +1,15 @@
+from datetime import UTC, datetime
from typing import Any, cast
-from rest_framework import serializers, viewsets
from django.db.models import Count
-from rest_framework import request
+from rest_framework import request, serializers, viewsets
from rest_framework.response import Response
from rest_framework.settings import api_settings
from rest_framework_csv import renderers as csvrenderers
from posthog.api.routing import TeamAndOrgViewSetMixin
from posthog.api.shared import UserBasicSerializer
-from posthog.auth import (
- TemporaryTokenAuthentication,
-)
+from posthog.auth import TemporaryTokenAuthentication
from posthog.constants import TREND_FILTER_TYPE_EVENTS
from posthog.event_usage import report_user_action
from posthog.models import Action
@@ -19,7 +17,6 @@
from .forbid_destroy_model import ForbidDestroyModel
from .tagged_item import TaggedItemSerializerMixin, TaggedItemViewSetMixin
-from datetime import datetime, UTC
class ActionStepJSONSerializer(serializers.Serializer):
@@ -40,6 +37,7 @@ class ActionSerializer(TaggedItemSerializerMixin, serializers.HyperlinkedModelSe
created_by = UserBasicSerializer(read_only=True)
is_calculating = serializers.SerializerMethodField()
is_action = serializers.BooleanField(read_only=True, default=True)
+ creation_context = serializers.SerializerMethodField()
class Meta:
model = Action
@@ -60,6 +58,7 @@ class Meta:
"is_action",
"bytecode_error",
"pinned_at",
+ "creation_context",
]
read_only_fields = [
"team_id",
@@ -70,6 +69,9 @@ class Meta:
def get_is_calculating(self, action: Action) -> bool:
return False
+ def get_creation_context(self, obj):
+ return None
+
def validate(self, attrs):
instance = cast(Action, self.instance)
exclude_args = {}
@@ -96,13 +98,14 @@ def validate(self, attrs):
return attrs
def create(self, validated_data: Any) -> Any:
+ creation_context = self.context["request"].data.get("creation_context")
validated_data["created_by"] = self.context["request"].user
instance = super().create(validated_data)
report_user_action(
validated_data["created_by"],
"action created",
- instance.get_analytics_metadata(),
+ {**instance.get_analytics_metadata(), "creation_context": creation_context},
)
return instance
diff --git a/posthog/api/dashboards/dashboard.py b/posthog/api/dashboards/dashboard.py
index 7a0ab9b265cd1..5a15fe513a008 100644
--- a/posthog/api/dashboards/dashboard.py
+++ b/posthog/api/dashboards/dashboard.py
@@ -6,7 +6,6 @@
from django.shortcuts import get_object_or_404
from django.utils.timezone import now
from rest_framework import exceptions, serializers, viewsets
-from posthog.api.utils import action
from rest_framework.permissions import SAFE_METHODS, BasePermission
from rest_framework.request import Request
from rest_framework.response import Response
@@ -18,10 +17,11 @@
)
from posthog.api.forbid_destroy_model import ForbidDestroyModel
from posthog.api.insight import InsightSerializer, InsightViewSet
-from posthog.api.monitoring import monitor, Feature
+from posthog.api.monitoring import Feature, monitor
from posthog.api.routing import TeamAndOrgViewSetMixin
from posthog.api.shared import UserBasicSerializer
from posthog.api.tagged_item import TaggedItemSerializerMixin, TaggedItemViewSetMixin
+from posthog.api.utils import action
from posthog.event_usage import report_user_action
from posthog.helpers import create_dashboard_from_template
from posthog.helpers.dashboard_templates import create_from_template
@@ -517,6 +517,7 @@ def create_from_template_json(self, request: Request, *args: Any, **kwargs: Any)
try:
dashboard_template = DashboardTemplate(**request.data["template"])
+ creation_context = request.data.get("creation_context")
create_from_template(dashboard, dashboard_template)
report_user_action(
@@ -528,6 +529,7 @@ def create_from_template_json(self, request: Request, *args: Any, **kwargs: Any)
"template_key": dashboard_template.template_name,
"duplicated": False,
"dashboard_id": dashboard.pk,
+ "creation_context": creation_context,
},
)
except Exception:
diff --git a/posthog/api/dashboards/dashboard_templates.py b/posthog/api/dashboards/dashboard_templates.py
index 39941ff8b17fe..481c3f5363c34 100644
--- a/posthog/api/dashboards/dashboard_templates.py
+++ b/posthog/api/dashboards/dashboard_templates.py
@@ -6,13 +6,13 @@
from django.utils.decorators import method_decorator
from django.views.decorators.cache import cache_page
from rest_framework import request, response, serializers, viewsets
-from posthog.api.utils import action
from rest_framework.exceptions import ValidationError
from rest_framework.permissions import SAFE_METHODS, BasePermission
from rest_framework.request import Request
from posthog.api.forbid_destroy_model import ForbidDestroyModel
from posthog.api.routing import TeamAndOrgViewSetMixin
+from posthog.api.utils import action
from posthog.models.dashboard_templates import DashboardTemplate
logger = structlog.get_logger(__name__)
@@ -47,6 +47,7 @@ class Meta:
"image_url",
"team_id",
"scope",
+ "availability_contexts",
]
def create(self, validated_data: dict, *args, **kwargs) -> DashboardTemplate:
diff --git a/posthog/api/test/__snapshots__/test_api_docs.ambr b/posthog/api/test/__snapshots__/test_api_docs.ambr
index dcef18134fecd..359389b8d65fa 100644
--- a/posthog/api/test/__snapshots__/test_api_docs.ambr
+++ b/posthog/api/test/__snapshots__/test_api_docs.ambr
@@ -60,6 +60,7 @@
'/home/runner/work/posthog/posthog/ee/api/role.py: Warning [RoleViewSet > RoleSerializer]: unable to resolve type hint for function "get_associated_flags". Consider using a type hint or @extend_schema_field. Defaulting to string.',
'/home/runner/work/posthog/posthog/ee/api/role.py: Warning [RoleMembershipViewSet]: could not derive type of path parameter "organization_id" because model "ee.models.role.RoleMembership" contained no such field. Consider annotating parameter with @extend_schema. Defaulting to "string".',
'/home/runner/work/posthog/posthog/posthog/api/action.py: Warning [ActionViewSet]: could not derive type of path parameter "project_id" because model "posthog.models.action.action.Action" contained no such field. Consider annotating parameter with @extend_schema. Defaulting to "string".',
+ '/home/runner/work/posthog/posthog/posthog/api/action.py: Warning [ActionViewSet > ActionSerializer]: unable to resolve type hint for function "get_creation_context". Consider using a type hint or @extend_schema_field. Defaulting to string.',
'/home/runner/work/posthog/posthog/posthog/api/activity_log.py: Warning [ActivityLogViewSet]: could not derive type of path parameter "project_id" because model "posthog.models.activity_logging.activity_log.ActivityLog" contained no such field. Consider annotating parameter with @extend_schema. Defaulting to "string".',
'/home/runner/work/posthog/posthog/posthog/api/annotation.py: Warning [AnnotationsViewSet]: could not derive type of path parameter "project_id" because model "posthog.models.annotation.Annotation" contained no such field. Consider annotating parameter with @extend_schema. Defaulting to "string".',
'/home/runner/work/posthog/posthog/posthog/api/cohort.py: Warning [CohortViewSet]: could not derive type of path parameter "project_id" because model "posthog.models.cohort.cohort.Cohort" contained no such field. Consider annotating parameter with @extend_schema. Defaulting to "string".',
diff --git a/posthog/api/test/dashboards/test_dashboard.py b/posthog/api/test/dashboards/test_dashboard.py
index 359cff26345af..9b0625ec6fec4 100644
--- a/posthog/api/test/dashboards/test_dashboard.py
+++ b/posthog/api/test/dashboards/test_dashboard.py
@@ -1152,7 +1152,7 @@ def test_relations_on_insights_when_dashboards_were_deleted(self) -> None:
def test_create_from_template_json(self, mock_capture) -> None:
response = self.client.post(
f"/api/projects/{self.team.id}/dashboards/create_from_template_json",
- {"template": valid_template},
+ {"template": valid_template, "creation_context": "onboarding"},
)
self.assertEqual(response.status_code, 200, response.content)
@@ -1173,6 +1173,7 @@ def test_create_from_template_json(self, mock_capture) -> None:
"dashboard created",
{
"created_at": mock.ANY,
+ "creation_context": "onboarding",
"dashboard_id": dashboard["id"],
"duplicated": False,
"from_template": True,
diff --git a/posthog/api/test/test_action.py b/posthog/api/test/test_action.py
index f39a72db9b5cc..4228a653062f3 100644
--- a/posthog/api/test/test_action.py
+++ b/posthog/api/test/test_action.py
@@ -56,6 +56,7 @@ def test_create_action(self, patch_capture, *args):
"created_by": ANY,
"pinned_at": None,
"deleted": False,
+ "creation_context": None,
"is_calculating": False,
"last_calculated_at": ANY,
"team_id": self.team.id,
@@ -82,6 +83,7 @@ def test_create_action(self, patch_capture, *args):
"deleted": False,
"pinned": False,
"pinned_at": None,
+ "creation_context": None,
},
)
diff --git a/posthog/api/test/test_survey.py b/posthog/api/test/test_survey.py
index e5aa59dd4fd79..855e5171d3ce7 100644
--- a/posthog/api/test/test_survey.py
+++ b/posthog/api/test/test_survey.py
@@ -6,16 +6,14 @@
import pytest
from django.core.cache import cache
from django.test.client import Client
-
from freezegun.api import freeze_time
-from posthog.api.survey import nh3_clean_with_allow_list
-from posthog.models.cohort.cohort import Cohort
from nanoid import generate
from rest_framework import status
+from posthog.api.survey import nh3_clean_with_allow_list
from posthog.constants import AvailableFeature
-from posthog.models import FeatureFlag, Action
-
+from posthog.models import Action, FeatureFlag
+from posthog.models.cohort.cohort import Cohort
from posthog.models.feedback.survey import Survey
from posthog.test.base import (
APIBaseTest,
@@ -2555,6 +2553,7 @@ def test_list_surveys_with_actions(self):
"created_by": None,
"deleted": False,
"is_calculating": False,
+ "creation_context": None,
"last_calculated_at": ANY,
"team_id": self.team.id,
"is_action": True,
diff --git a/posthog/hogql/database/test/__snapshots__/test_database.ambr b/posthog/hogql/database/test/__snapshots__/test_database.ambr
index 6a8f5f45be0b1..ebd12e015c813 100644
--- a/posthog/hogql/database/test/__snapshots__/test_database.ambr
+++ b/posthog/hogql/database/test/__snapshots__/test_database.ambr
@@ -219,7 +219,7 @@
"pdi"
],
"hogql_value": "person",
- "id": null,
+ "id": "person",
"name": "person",
"schema_valid": true,
"table": "persons",
@@ -472,7 +472,7 @@
"person"
],
"hogql_value": "override",
- "id": null,
+ "id": "override",
"name": "override",
"schema_valid": true,
"table": "person_distinct_id_overrides",
diff --git a/posthog/migrations/0473_dashboardtemplate_availability_contexts.py b/posthog/migrations/0473_dashboardtemplate_availability_contexts.py
new file mode 100644
index 0000000000000..5d83c95e35e0c
--- /dev/null
+++ b/posthog/migrations/0473_dashboardtemplate_availability_contexts.py
@@ -0,0 +1,20 @@
+# Generated by Django 4.2.15 on 2024-09-17 19:02
+
+import django.contrib.postgres.fields
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ("posthog", "0472_experiment_metrics"),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name="dashboardtemplate",
+ name="availability_contexts",
+ field=django.contrib.postgres.fields.ArrayField(
+ base_field=models.CharField(max_length=255), blank=True, null=True, size=None
+ ),
+ ),
+ ]
diff --git a/posthog/models/dashboard_templates.py b/posthog/models/dashboard_templates.py
index bf9d6dc733eb2..312b0013a2058 100644
--- a/posthog/models/dashboard_templates.py
+++ b/posthog/models/dashboard_templates.py
@@ -37,6 +37,8 @@ class Scope(models.TextChoices):
# see https://stackoverflow.com/questions/417142/what-is-the-maximum-length-of-a-url-in-different-browsers
# but GitHub apparently is more likely 8kb https://stackoverflow.com/a/64565317
github_url = models.CharField(max_length=8201, null=True)
+ # where this template is available, e.g. "general" and/or "onboarding"
+ availability_contexts = ArrayField(models.CharField(max_length=255), blank=True, null=True)
class Meta:
constraints = [