Skip to content

Commit

Permalink
feat(onboarding-templates): create actions from within the iframe (#2…
Browse files Browse the repository at this point in the history
  • Loading branch information
raquelmsmith authored Sep 11, 2024
1 parent 4811cff commit b865cea
Show file tree
Hide file tree
Showing 15 changed files with 540 additions and 154 deletions.
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

0 comments on commit b865cea

Please sign in to comment.