Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(onboarding-templates): create actions from within the iframe #24825

Merged
merged 19 commits into from
Sep 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,10 @@ export function IframedToolbarBrowser({
iframeRef,
userIntent,
}: {
iframeRef?: React.MutableRefObject<HTMLIFrameElement | null>
iframeRef: React.MutableRefObject<HTMLIFrameElement | null>
userIntent: ToolbarUserIntent
}): JSX.Element | null {
const logic = iframedToolbarBrowserLogic()
const logic = iframedToolbarBrowserLogic({ iframeRef, userIntent: userIntent })

const { browserUrl } = useValues(logic)
const { onIframeLoad, setIframeWidth } = useActions(logic)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,14 @@ import { LemonBannerProps } from 'lib/lemon-ui/LemonBanner'
import posthog from 'posthog-js'
import { RefObject } from 'react'

import { ToolbarUserIntent } from '~/types'

import type { iframedToolbarBrowserLogicType } from './iframedToolbarBrowserLogicType'

export type IframedToolbarBrowserLogicProps = {
iframeRef: RefObject<HTMLIFrameElement | null>
clearBrowserUrlOnUnmount?: boolean
userIntent?: ToolbarUserIntent
}

export interface IFrameBanner {
Expand Down Expand Up @@ -50,9 +53,13 @@ export const iframedToolbarBrowserLogic = kea<iframedToolbarBrowserLogicType>([
setIframeBanner: (banner: IFrameBanner | null) => ({ banner }),
startTrackingLoading: true,
stopTrackingLoading: true,
enableElementSelector: true,
disableElementSelector: true,
setNewActionName: (name: string | null) => ({ name }),
toolbarMessageReceived: (type: PostHogAppToolbarEvent, payload: Record<string, any>) => ({ type, payload }),
}),

reducers({
reducers(({ props }) => ({
// they're called common filters in the toolbar because they're shared between heatmaps and clickmaps
// the name is continued here since they're passed down into the embedded iframe
commonFilters: [
Expand Down Expand Up @@ -87,7 +94,7 @@ export const iframedToolbarBrowserLogic = kea<iframedToolbarBrowserLogicType>([
],
browserUrl: [
null as string | null,
{ persist: true },
{ persist: props.userIntent == 'heatmaps' },
{
setBrowserUrl: (_, { url }) => url,
},
Expand All @@ -107,7 +114,7 @@ export const iframedToolbarBrowserLogic = kea<iframedToolbarBrowserLogicType>([
setIframeBanner: (_, { banner }) => banner,
},
],
}),
})),

selectors({
isBrowserUrlAuthorized: [
Expand Down Expand Up @@ -138,7 +145,7 @@ export const iframedToolbarBrowserLogic = kea<iframedToolbarBrowserLogicType>([
'*'
)
},

// heatmaps
patchHeatmapFilters: ({ filters }) => {
actions.sendToolbarMessage(PostHogAppToolbarEvent.PH_PATCH_HEATMAP_FILTERS, { filters })
},
Expand All @@ -159,6 +166,17 @@ export const iframedToolbarBrowserLogic = kea<iframedToolbarBrowserLogicType>([
actions.sendToolbarMessage(PostHogAppToolbarEvent.PH_HEATMAPS_COMMON_FILTERS, { commonFilters: filters })
},

// actions
enableElementSelector: () => {
actions.sendToolbarMessage(PostHogAppToolbarEvent.PH_ELEMENT_SELECTOR, { enabled: true })
},
disableElementSelector: () => {
actions.sendToolbarMessage(PostHogAppToolbarEvent.PH_ELEMENT_SELECTOR, { enabled: false })
},
setNewActionName: ({ name }) => {
actions.sendToolbarMessage(PostHogAppToolbarEvent.PH_NEW_ACTION_NAME, { name })
},

onIframeLoad: () => {
// we get this callback whether the iframe loaded successfully or not
// and don't get a signal if the load was successful, so we have to check
Expand All @@ -171,13 +189,20 @@ export const iframedToolbarBrowserLogic = kea<iframedToolbarBrowserLogicType>([
fixedPositionMode: values.heatmapFixedPositionMode,
commonFilters: values.commonFilters,
})
actions.sendToolbarMessage(PostHogAppToolbarEvent.PH_HEATMAPS_CONFIG, {
enabled: true,
})
switch (props.userIntent) {
case 'heatmaps':
actions.sendToolbarMessage(PostHogAppToolbarEvent.PH_HEATMAPS_CONFIG, {
enabled: true,
})
break
}
}

const onIframeMessage = (e: MessageEvent): void => {
const type: PostHogAppToolbarEvent = e?.data?.type
const payload = e?.data?.payload

actions.toolbarMessageReceived(type, payload)

if (!type || !type.startsWith('ph-')) {
return
Expand All @@ -195,14 +220,17 @@ export const iframedToolbarBrowserLogic = kea<iframedToolbarBrowserLogicType>([
case PostHogAppToolbarEvent.PH_TOOLBAR_INIT:
return init()
case PostHogAppToolbarEvent.PH_TOOLBAR_READY:
posthog.capture('in-app heatmap frame loaded', {
inapp_heatmap_page_url_visited: values.browserUrl,
inapp_heatmap_filters: values.heatmapFilters,
inapp_heatmap_color_palette: values.heatmapColorPalette,
inapp_heatmap_fixed_position_mode: values.heatmapFixedPositionMode,
})
// reset loading tracking - if we're e.g. slow this will avoid a flash of warning message
return actions.startTrackingLoading()
if (props.userIntent === 'heatmaps') {
posthog.capture('in-app heatmap frame loaded', {
inapp_heatmap_page_url_visited: values.browserUrl,
inapp_heatmap_filters: values.heatmapFilters,
inapp_heatmap_color_palette: values.heatmapColorPalette,
inapp_heatmap_fixed_position_mode: values.heatmapFixedPositionMode,
})
// reset loading tracking - if we're e.g. slow this will avoid a flash of warning message
return actions.startTrackingLoading()
}
return
case PostHogAppToolbarEvent.PH_TOOLBAR_HEATMAP_LOADING:
return actions.startTrackingLoading()
case PostHogAppToolbarEvent.PH_TOOLBAR_HEATMAP_LOADED:
Expand All @@ -223,6 +251,10 @@ export const iframedToolbarBrowserLogic = kea<iframedToolbarBrowserLogicType>([
actions.stopTrackingLoading()
actions.setIframeBanner({ level: 'error', message: 'The heatmap failed to load.' })
return
case PostHogAppToolbarEvent.PH_NEW_ACTION_CREATED:
actions.setNewActionName(null)
actions.disableElementSelector()
return
default:
console.warn(`[PostHog Heatmaps] Received unknown child window message: ${type}`)
}
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/lib/components/IframedToolbarBrowser/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ export enum PostHogAppToolbarEvent {
PH_TOOLBAR_HEATMAP_LOADING = 'ph-toolbar-heatmap-loading',
PH_TOOLBAR_HEATMAP_LOADED = 'ph-toolbar-heatmap-loaded',
PH_TOOLBAR_HEATMAP_FAILED = 'ph-toolbar-heatmap-failed',
PH_ELEMENT_SELECTOR = 'ph-element-selector',
PH_NEW_ACTION_NAME = 'ph-new-action-name',
PH_NEW_ACTION_CREATED = 'ph-new-action-created',
}

export const DEFAULT_HEATMAP_FILTERS: HeatmapFilters = {
Expand Down
75 changes: 70 additions & 5 deletions frontend/src/scenes/dashboard/dashboardTemplateVariablesLogic.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,18 @@
import { actions, kea, listeners, path, props, propsChanged, reducers, selectors } from 'kea'
import { actions, connect, kea, listeners, path, props, propsChanged, reducers, selectors } from 'kea'
import { iframedToolbarBrowserLogic } from 'lib/components/IframedToolbarBrowser/iframedToolbarBrowserLogic'
import { PostHogAppToolbarEvent } from 'lib/components/IframedToolbarBrowser/utils'
import { isEmptyObject } from 'lib/utils'

import { DashboardTemplateVariableType, FilterType, Optional } from '~/types'
import {
ActionType,
BaseMathType,
DashboardTemplateVariableType,
EntityType,
EntityTypes,
FilterType,
Optional,
TemplateVariableStep,
} from '~/types'

import type { dashboardTemplateVariablesLogicType } from './dashboardTemplateVariablesLogicType'

Expand All @@ -18,17 +29,22 @@ const FALLBACK_EVENT = {
export const dashboardTemplateVariablesLogic = kea<dashboardTemplateVariablesLogicType>([
path(['scenes', 'dashboard', 'DashboardTemplateVariablesLogic']),
props({ variables: [] } as DashboardTemplateVariablesLogicProps),
connect({
actions: [iframedToolbarBrowserLogic, ['toolbarMessageReceived', 'disableElementSelector']],
}),
actions({
setVariables: (variables: DashboardTemplateVariableType[]) => ({ variables }),
setVariable: (variableName: string, filterGroup: Optional<FilterType, 'type'>) => ({
variable_name: variableName,
filterGroup,
}),
setVariableFromAction: (variableName: string, action: ActionType) => ({ variableName, action }),
setActiveVariableIndex: (index: number) => ({ index }),
incrementActiveVariableIndex: true,
possiblyIncrementActiveVariableIndex: true,
resetVariable: (variableId: string) => ({ variableId }),
goToNextUntouchedActiveVariableIndex: true,
setIsCurrentlySelectingElement: (isSelecting: boolean) => ({ isSelecting }),
}),
reducers({
variables: [
Expand All @@ -43,10 +59,23 @@ export const dashboardTemplateVariablesLogic = kea<dashboardTemplateVariablesLog
})
},
setVariable: (state, { variable_name: variableName, filterGroup }): DashboardTemplateVariableType[] => {
// TODO: handle actions as well as events
// There is only one type with contents at a time
// So iterate through the types to find the first one with contents
const typeWithContents: EntityType = Object.keys(filterGroup).filter(
(group) => (filterGroup[group as EntityType] || [])?.length > 0
)?.[0] as EntityType

if (!typeWithContents) {
return state
}

return state.map((v: DashboardTemplateVariableType) => {
if (v.name === variableName && filterGroup?.events?.length && filterGroup.events[0]) {
return { ...v, default: filterGroup.events[0], touched: true }
if (
v.name === variableName &&
filterGroup?.[typeWithContents]?.length &&
filterGroup?.[typeWithContents]?.[0]
) {
return { ...v, default: filterGroup[typeWithContents]?.[0] || {}, touched: true }
}
return { ...v }
})
Expand All @@ -68,6 +97,12 @@ export const dashboardTemplateVariablesLogic = kea<dashboardTemplateVariablesLog
incrementActiveVariableIndex: (state) => state + 1,
},
],
isCurrentlySelectingElement: [
false as boolean,
{
setIsCurrentlySelectingElement: (_, { isSelecting }) => isSelecting,
},
],
}),
selectors(() => ({
activeVariable: [
Expand All @@ -82,6 +117,12 @@ export const dashboardTemplateVariablesLogic = kea<dashboardTemplateVariablesLog
return variables.every((v) => v.touched)
},
],
hasTouchedAnyVariable: [
(s) => [s.variables],
(variables: DashboardTemplateVariableType[]) => {
return variables.some((v) => v.touched)
},
],
})),
listeners(({ actions, props, values }) => ({
possiblyIncrementActiveVariableIndex: () => {
Expand All @@ -103,6 +144,30 @@ export const dashboardTemplateVariablesLogic = kea<dashboardTemplateVariablesLog
}
actions.setActiveVariableIndex(nextIndex)
},
setVariableFromAction: ({ variableName, action }) => {
const originalVariableName = variableName.replace(/\s-\s\d+/g, '')
const step: TemplateVariableStep = {
id: action.id.toString(),
math: BaseMathType.UniqueUsers,
name: action.name,
order: 0,
type: EntityTypes.ACTIONS,
selector: action.steps?.[0]?.selector,
href: action.steps?.[0]?.href,
url: action.steps?.[0]?.url,
}
const filterGroup: FilterType = {
actions: [step],
}
actions.setVariable(originalVariableName, 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()
}
},
})),
propsChanged(({ actions, props }, oldProps) => {
if (props.variables !== oldProps.variables) {
Expand Down
Loading
Loading