diff --git a/frontend/src/lib/lemon-ui/LemonCollapse/LemonCollapse.tsx b/frontend/src/lib/lemon-ui/LemonCollapse/LemonCollapse.tsx index 32b569226dcaa..ed3bd39277311 100644 --- a/frontend/src/lib/lemon-ui/LemonCollapse/LemonCollapse.tsx +++ b/frontend/src/lib/lemon-ui/LemonCollapse/LemonCollapse.tsx @@ -96,6 +96,7 @@ interface LemonCollapsePanelProps { onChange: (isExpanded: boolean) => void className?: string dataAttr?: string + onHeaderClick?: () => void } function LemonCollapsePanel({ @@ -106,13 +107,17 @@ function LemonCollapsePanel({ className, dataAttr, onChange, + onHeaderClick, }: LemonCollapsePanelProps): JSX.Element { const { height: contentHeight, ref: contentRef } = useResizeObserver({ box: 'border-box' }) return (
onChange(!isExpanded)} + onClick={() => { + onHeaderClick && onHeaderClick() + onChange(!isExpanded) + }} icon={isExpanded ? : } className="LemonCollapsePanel__header" {...(dataAttr ? { 'data-attr': dataAttr } : {})} diff --git a/frontend/src/scenes/dashboard/dashboardTemplateVariablesLogic.ts b/frontend/src/scenes/dashboard/dashboardTemplateVariablesLogic.ts index 39b45617e66ac..da23d22466d25 100644 --- a/frontend/src/scenes/dashboard/dashboardTemplateVariablesLogic.ts +++ b/frontend/src/scenes/dashboard/dashboardTemplateVariablesLogic.ts @@ -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' @@ -24,6 +24,11 @@ export const dashboardTemplateVariablesLogic = kea ({ index }), + incrementActiveVariableIndex: true, + possiblyIncrementActiveVariableIndex: true, + resetVariable: (variableId: string) => ({ variableId }), + goToNextUntouchedActiveVariableIndex: true, }), reducers({ variables: [ @@ -41,14 +46,64 @@ export const dashboardTemplateVariablesLogic = kea { 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) diff --git a/frontend/src/scenes/onboarding/productAnalyticsSteps/DashboardTemplateConfigureStep.tsx b/frontend/src/scenes/onboarding/productAnalyticsSteps/DashboardTemplateConfigureStep.tsx index 1f67ee2abd399..2bed8e8a07d2f 100644 --- a/frontend/src/scenes/onboarding/productAnalyticsSteps/DashboardTemplateConfigureStep.tsx +++ b/frontend/src/scenes/onboarding/productAnalyticsSteps/DashboardTemplateConfigureStep.tsx @@ -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 = ({ @@ -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) @@ -105,7 +108,15 @@ export const OnboardingDashboardTemplateConfigureStep = ({ )}
- +

+ 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{' '} + + posthog.capture() + {' '} + (no need to send it now) . +

+ Create dashboard diff --git a/frontend/src/scenes/onboarding/productAnalyticsSteps/DashboardTemplateVariables.tsx b/frontend/src/scenes/onboarding/productAnalyticsSteps/DashboardTemplateVariables.tsx new file mode 100644 index 0000000000000..e22f822eedcfe --- /dev/null +++ b/frontend/src/scenes/onboarding/productAnalyticsSteps/DashboardTemplateVariables.tsx @@ -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(null) + const [showCustomEventField, setShowCustomEventField] = useState(false) + + const FALLBACK_EVENT = { + id: '$other_event', + math: 'dau', + type: 'events', + } + + return ( +
+
+

+ {variable.description} +

+
+ {variable.touched && !customEventName && ( +
+
+ {' '} + Selected +

.md-invite-button

+
+
+ } + type="tertiary" + size="small" + onClick={() => resetVariable(variable.id)} + /> +
+
+ )} + {showCustomEventField && ( +
+ Custom event name +

+ 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. +

+
+ { + 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) + } + }} + /> +
+ } + type="tertiary" + size="small" + onClick={() => { + resetVariable(variable.id) + setCustomEventName(null) + setShowCustomEventField(false) + }} + /> +
+
+
+ )} + {!hasSelectedSite ? ( + Please select a site to continue. + ) : ( +
+ {variable.touched ? ( + <> + {!allVariablesAreTouched || + (allVariablesAreTouched && variables.length !== activeVariableIndex + 1) ? ( + + !allVariablesAreTouched + ? goToNextUntouchedActiveVariableIndex() + : variables.length !== activeVariableIndex + 1 + ? incrementActiveVariableIndex() + : null + } + > + Continue + + ) : null} + + ) : ( +
+ { + setShowCustomEventField(false) + setVariable(variable.name, { events: [FALLBACK_EVENT] }) + }} + > + Select from site + + setShowCustomEventField(true)}> + Use custom event + +
+ )} +
+ )} +
+ ) +} + +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 ( +
+ ({ + key: v.id, + header: ( +
+ {v.name} + {v.touched && } +
+ ), + content: , + className: 'p-4 bg-white', + onHeaderClick: () => { + setActiveVariableIndex(i) + }, + }))} + embedded + size="small" + /> +
+ ) +} diff --git a/frontend/src/types.ts b/frontend/src/types.ts index 61d6072f17d21..d1aaa92a6007f 100644 --- a/frontend/src/types.ts +++ b/frontend/src/types.ts @@ -1800,6 +1800,7 @@ export interface DashboardTemplateVariableType { type: 'event' default: Record required: boolean + touched?: boolean } export type DashboardLayoutSize = 'sm' | 'xs'