Skip to content

Commit

Permalink
feat(onboarding-templates): create custom variable selector panel (#2…
Browse files Browse the repository at this point in the history
  • Loading branch information
raquelmsmith authored Sep 5, 2024
1 parent 20e65c6 commit 6f6a80a
Show file tree
Hide file tree
Showing 5 changed files with 267 additions and 8 deletions.
7 changes: 6 additions & 1 deletion frontend/src/lib/lemon-ui/LemonCollapse/LemonCollapse.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ interface LemonCollapsePanelProps {
onChange: (isExpanded: boolean) => void
className?: string
dataAttr?: string
onHeaderClick?: () => void
}

function LemonCollapsePanel({
Expand All @@ -106,13 +107,17 @@ function LemonCollapsePanel({
className,
dataAttr,
onChange,
onHeaderClick,
}: LemonCollapsePanelProps): JSX.Element {
const { height: contentHeight, ref: contentRef } = useResizeObserver({ box: 'border-box' })

return (
<div className="LemonCollapsePanel" aria-expanded={isExpanded}>
<LemonButton
onClick={() => onChange(!isExpanded)}
onClick={() => {
onHeaderClick && onHeaderClick()
onChange(!isExpanded)
}}
icon={isExpanded ? <IconCollapse /> : <IconExpand />}
className="LemonCollapsePanel__header"
{...(dataAttr ? { 'data-attr': dataAttr } : {})}
Expand Down
61 changes: 58 additions & 3 deletions frontend/src/scenes/dashboard/dashboardTemplateVariablesLogic.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { actions, kea, path, props, propsChanged, reducers } from 'kea'
import { actions, kea, listeners, path, props, propsChanged, reducers, selectors } from 'kea'
import { isEmptyObject } from 'lib/utils'

import { DashboardTemplateVariableType, FilterType, Optional } from '~/types'
Expand All @@ -24,6 +24,11 @@ export const dashboardTemplateVariablesLogic = kea<dashboardTemplateVariablesLog
variable_name: variableName,
filterGroup,
}),
setActiveVariableIndex: (index: number) => ({ index }),
incrementActiveVariableIndex: true,
possiblyIncrementActiveVariableIndex: true,
resetVariable: (variableId: string) => ({ variableId }),
goToNextUntouchedActiveVariableIndex: true,
}),
reducers({
variables: [
Expand All @@ -41,14 +46,64 @@ export const dashboardTemplateVariablesLogic = kea<dashboardTemplateVariablesLog
// TODO: handle actions as well as events
return state.map((v: DashboardTemplateVariableType) => {
if (v.name === variableName && filterGroup?.events?.length && filterGroup.events[0]) {
return { ...v, default: filterGroup.events[0] }
return { ...v, default: filterGroup.events[0], touched: true }
}
return v
return { ...v }
})
},
resetVariable: (state, { variableId }) => {
return state.map((v: DashboardTemplateVariableType) => {
if (v.id === variableId) {
return { ...v, default: FALLBACK_EVENT, touched: false }
}
return { ...v }
})
},
},
],
activeVariableIndex: [
0,
{
setActiveVariableIndex: (_, { index }) => index,
incrementActiveVariableIndex: (state) => state + 1,
},
],
}),
selectors(() => ({
activeVariable: [
(s) => [s.variables, s.activeVariableIndex],
(variables: DashboardTemplateVariableType[], activeVariableIndex: number) => {
return variables[activeVariableIndex]
},
],
allVariablesAreTouched: [
(s) => [s.variables],
(variables: DashboardTemplateVariableType[]) => {
return variables.every((v) => v.touched)
},
],
})),
listeners(({ actions, props, values }) => ({
possiblyIncrementActiveVariableIndex: () => {
if (props.variables.length > 0 && values.activeVariableIndex < props.variables.length - 1) {
actions.incrementActiveVariableIndex()
}
},
goToNextUntouchedActiveVariableIndex: () => {
let nextIndex = values.variables.findIndex((v, i) => !v.touched && i > values.activeVariableIndex)
if (nextIndex !== -1) {
actions.setActiveVariableIndex(nextIndex)
return
}
if (nextIndex == -1) {
nextIndex = values.variables.findIndex((v) => !v.touched)
if (nextIndex == -1) {
nextIndex = values.activeVariableIndex
}
}
actions.setActiveVariableIndex(nextIndex)
},
})),
propsChanged(({ actions, props }, oldProps) => {
if (props.variables !== oldProps.variables) {
actions.setVariables(props.variables)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import { IconArrowRight } from '@posthog/icons'
import { LemonButton, LemonCard, LemonSkeleton } from '@posthog/lemon-ui'
import { LemonButton, LemonCard, LemonSkeleton, Link } from '@posthog/lemon-ui'
import { useActions, useValues } from 'kea'
import { authorizedUrlListLogic, AuthorizedUrlListType } from 'lib/components/AuthorizedUrlList/authorizedUrlListLogic'
import { IframedToolbarBrowser } from 'lib/components/IframedToolbarBrowser/IframedToolbarBrowser'
import { iframedToolbarBrowserLogic } from 'lib/components/IframedToolbarBrowser/iframedToolbarBrowserLogic'
import { useRef, useState } from 'react'
import { DashboardTemplateVariables } from 'scenes/dashboard/DashboardTemplateVariables'
import { dashboardTemplateVariablesLogic } from 'scenes/dashboard/dashboardTemplateVariablesLogic'
import { newDashboardLogic } from 'scenes/dashboard/newDashboardLogic'

import { OnboardingStepKey } from '../onboardingLogic'
import { OnboardingStep } from '../OnboardingStep'
import { sdksLogic } from '../sdks/sdksLogic'
import { DashboardTemplateVariables } from './DashboardTemplateVariables'
import { onboardingTemplateConfigLogic } from './onboardingTemplateConfigLogic'

export const OnboardingDashboardTemplateConfigureStep = ({
Expand All @@ -23,11 +23,14 @@ export const OnboardingDashboardTemplateConfigureStep = ({
const { activeDashboardTemplate } = useValues(onboardingTemplateConfigLogic)
const { createDashboardFromTemplate } = useActions(newDashboardLogic)
const { isLoading } = useValues(newDashboardLogic)
const { variables } = useValues(dashboardTemplateVariablesLogic)
const { snippetHosts } = useValues(sdksLogic)
const { addUrl } = useActions(authorizedUrlListLogic({ actionId: null, type: AuthorizedUrlListType.TOOLBAR_URLS }))
const { setBrowserUrl } = useActions(iframedToolbarBrowserLogic({ iframeRef, clearBrowserUrlOnUnmount: true }))
const { browserUrl } = useValues(iframedToolbarBrowserLogic({ iframeRef, clearBrowserUrlOnUnmount: true }))
const theDashboardTemplateVariablesLogic = dashboardTemplateVariablesLogic({
variables: activeDashboardTemplate?.variables || [],
})
const { variables, allVariablesAreTouched } = useValues(theDashboardTemplateVariablesLogic)

const [isSubmitting, setIsSubmitting] = useState(false)

Expand Down Expand Up @@ -105,7 +108,15 @@ export const OnboardingDashboardTemplateConfigureStep = ({
)}
</div>
<div className="col-span-2">
<DashboardTemplateVariables />
<p>
For each action below, select an element on your site that indicates when that action is
taken, or enter a custom event name that you'll send using{' '}
<Link to="https://posthog.com/docs/product-analytics/capture-events">
<code>posthog.capture()</code>
</Link>{' '}
(no need to send it now) .
</p>
<DashboardTemplateVariables hasSelectedSite={!!browserUrl} />
<LemonButton
type="primary"
status="alt"
Expand All @@ -119,6 +130,7 @@ export const OnboardingDashboardTemplateConfigureStep = ({
fullWidth
center
className="mt-6"
disabledReason={!allVariablesAreTouched && 'Please select an event for each variable'}
>
Create dashboard
</LemonButton>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
import { IconCheckCircle, IconInfo, IconTrash } from '@posthog/icons'
import { LemonBanner, LemonButton, LemonCollapse, LemonInput, LemonLabel } from '@posthog/lemon-ui'
import { useActions, useValues } from 'kea'
import { useEffect, useState } from 'react'
import { dashboardTemplateVariablesLogic } from 'scenes/dashboard/dashboardTemplateVariablesLogic'
import { newDashboardLogic } from 'scenes/dashboard/newDashboardLogic'

import { DashboardTemplateVariableType } from '~/types'

function VariableSelector({
variable,
hasSelectedSite,
}: {
variable: DashboardTemplateVariableType
hasSelectedSite: boolean
}): JSX.Element {
const { activeDashboardTemplate } = useValues(newDashboardLogic)
const theDashboardTemplateVariablesLogic = dashboardTemplateVariablesLogic({
variables: activeDashboardTemplate?.variables || [],
})
const { setVariable, resetVariable, goToNextUntouchedActiveVariableIndex, incrementActiveVariableIndex } =
useActions(theDashboardTemplateVariablesLogic)
const { allVariablesAreTouched, variables, activeVariableIndex } = useValues(theDashboardTemplateVariablesLogic)
const [customEventName, setCustomEventName] = useState<string | null>(null)
const [showCustomEventField, setShowCustomEventField] = useState(false)

const FALLBACK_EVENT = {
id: '$other_event',
math: 'dau',
type: 'events',
}

return (
<div className="pl-7">
<div className="mb-2">
<p>
<IconInfo /> {variable.description}
</p>
</div>
{variable.touched && !customEventName && (
<div className="flex justify-between items-center bg-bg-3000-light p-2 pl-3 rounded mb-4">
<div>
<IconCheckCircle className="text-success font-bold" />{' '}
<span className="text-success font-bold">Selected</span>
<p className="italic text-muted mb-0">.md-invite-button</p>
</div>
<div>
<LemonButton
icon={<IconTrash />}
type="tertiary"
size="small"
onClick={() => resetVariable(variable.id)}
/>
</div>
</div>
)}
{showCustomEventField && (
<div className="mb-4">
<LemonLabel>Custom event name</LemonLabel>
<p>
Set the name that you'll use for a custom event (eg. a backend event) instead of selecting an
event from your site. You can change this later if needed.
</p>
<div className="flex gap-x-2 w-full">
<LemonInput
className="grow"
onChange={(v) => {
if (v) {
setCustomEventName(v)
setVariable(variable.name, {
events: [{ id: v, math: 'dau', type: 'events' }],
})
} else {
setCustomEventName(null)
resetVariable(variable.id)
}
}}
onBlur={() => {
if (customEventName) {
setVariable(variable.name, {
events: [{ id: customEventName, math: 'dau', type: 'events' }],
})
} else {
resetVariable(variable.id)
setShowCustomEventField(false)
}
}}
/>
<div>
<LemonButton
icon={<IconTrash />}
type="tertiary"
size="small"
onClick={() => {
resetVariable(variable.id)
setCustomEventName(null)
setShowCustomEventField(false)
}}
/>
</div>
</div>
</div>
)}
{!hasSelectedSite ? (
<LemonBanner type="warning">Please select a site to continue. </LemonBanner>
) : (
<div className="flex">
{variable.touched ? (
<>
{!allVariablesAreTouched ||
(allVariablesAreTouched && variables.length !== activeVariableIndex + 1) ? (
<LemonButton
type="primary"
status="alt"
onClick={() =>
!allVariablesAreTouched
? goToNextUntouchedActiveVariableIndex()
: variables.length !== activeVariableIndex + 1
? incrementActiveVariableIndex()
: null
}
>
Continue
</LemonButton>
) : null}
</>
) : (
<div className="flex gap-x-2">
<LemonButton
type="primary"
status="alt"
onClick={() => {
setShowCustomEventField(false)
setVariable(variable.name, { events: [FALLBACK_EVENT] })
}}
>
Select from site
</LemonButton>
<LemonButton type="secondary" onClick={() => setShowCustomEventField(true)}>
Use custom event
</LemonButton>
</div>
)}
</div>
)}
</div>
)
}

export function DashboardTemplateVariables({ hasSelectedSite }: { hasSelectedSite: boolean }): JSX.Element {
const { activeDashboardTemplate } = useValues(newDashboardLogic)
const theDashboardTemplateVariablesLogic = dashboardTemplateVariablesLogic({
variables: activeDashboardTemplate?.variables || [],
})
const { variables, activeVariableIndex } = useValues(theDashboardTemplateVariablesLogic)
const { setVariables, setActiveVariableIndex } = useActions(theDashboardTemplateVariablesLogic)

// TODO: onboarding-dashboard-templates: this is a hack, I'm not sure why it's not set properly initially.
useEffect(() => {
setVariables(activeDashboardTemplate?.variables || [])
}, [activeDashboardTemplate])

return (
<div className="mb-4 DashboardTemplateVariables max-w-192">
<LemonCollapse
activeKey={variables[activeVariableIndex]?.id}
panels={variables.map((v, i) => ({
key: v.id,
header: (
<div>
{v.name}
{v.touched && <IconCheckCircle className="text-success ml-2 text-base" />}
</div>
),
content: <VariableSelector variable={v} {...v} hasSelectedSite={hasSelectedSite} />,
className: 'p-4 bg-white',
onHeaderClick: () => {
setActiveVariableIndex(i)
},
}))}
embedded
size="small"
/>
</div>
)
}
1 change: 1 addition & 0 deletions frontend/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1800,6 +1800,7 @@ export interface DashboardTemplateVariableType {
type: 'event'
default: Record<string, JsonType>
required: boolean
touched?: boolean
}

export type DashboardLayoutSize = 'sm' | 'xs'
Expand Down

0 comments on commit 6f6a80a

Please sign in to comment.