diff --git a/ee/clickhouse/views/experiments.py b/ee/clickhouse/views/experiments.py index 6df24dc012cea..e71aa6f2aba6d 100644 --- a/ee/clickhouse/views/experiments.py +++ b/ee/clickhouse/views/experiments.py @@ -184,6 +184,7 @@ class Meta: "created_by", "created_at", "updated_at", + "type", ] read_only_fields = [ "id", @@ -193,6 +194,7 @@ class Meta: "feature_flag", "exposure_cohort", "holdout", + "type", ] def validate_parameters(self, value): diff --git a/frontend/src/layout/navigation-3000/sidebars/toolbar.ts b/frontend/src/layout/navigation-3000/sidebars/toolbar.ts index dfb417aaf6b3b..dec139cfc36ab 100644 --- a/frontend/src/layout/navigation-3000/sidebars/toolbar.ts +++ b/frontend/src/layout/navigation-3000/sidebars/toolbar.ts @@ -29,13 +29,13 @@ export const toolbarSidebarLogic = kea([ path(['layout', 'navigation-3000', 'sidebars', 'toolbarSidebarLogic']), connect(() => ({ values: [ - authorizedUrlListLogic({ actionId: null, type: AuthorizedUrlListType.TOOLBAR_URLS }), + authorizedUrlListLogic({ actionId: null, experimentId: null, type: AuthorizedUrlListType.TOOLBAR_URLS }), ['urlsKeyed', 'suggestionsLoading', 'launchUrl'], sceneLogic, ['activeScene', 'sceneParams'], ], actions: [ - authorizedUrlListLogic({ actionId: null, type: AuthorizedUrlListType.TOOLBAR_URLS }), + authorizedUrlListLogic({ actionId: null, experimentId: null, type: AuthorizedUrlListType.TOOLBAR_URLS }), ['addUrl', 'removeUrl', 'updateUrl'], ], })), @@ -50,6 +50,7 @@ export const toolbarSidebarLogic = kea([ onAdd: async (url) => { await authorizedUrlListLogic({ actionId: null, + experimentId: null, type: AuthorizedUrlListType.TOOLBAR_URLS, }).asyncActions.addUrl(url) }, diff --git a/frontend/src/lib/components/AuthorizedUrlList/AuthorizedUrlList.tsx b/frontend/src/lib/components/AuthorizedUrlList/AuthorizedUrlList.tsx index 1ce371cb6c060..cc41430709aed 100644 --- a/frontend/src/lib/components/AuthorizedUrlList/AuthorizedUrlList.tsx +++ b/frontend/src/lib/components/AuthorizedUrlList/AuthorizedUrlList.tsx @@ -45,8 +45,8 @@ function EmptyState({ ) : null } -function AuthorizedUrlForm({ actionId, type }: AuthorizedUrlListProps): JSX.Element { - const logic = authorizedUrlListLogic({ actionId: actionId ?? null, type }) +function AuthorizedUrlForm({ actionId, experimentId, type }: AuthorizedUrlListProps): JSX.Element { + const logic = authorizedUrlListLogic({ actionId: actionId ?? null, experimentId: experimentId ?? null, type }) const { isProposedUrlSubmitting } = useValues(logic) const { cancelProposingUrl } = useActions(logic) return ( @@ -78,15 +78,24 @@ function AuthorizedUrlForm({ actionId, type }: AuthorizedUrlListProps): JSX.Elem export interface AuthorizedUrlListProps { actionId?: number + experimentId?: number | 'new' + query?: string type: AuthorizedUrlListType } export function AuthorizedUrlList({ actionId, + experimentId, + query, type, addText = 'Add', }: AuthorizedUrlListProps & { addText?: string }): JSX.Element { - const logic = authorizedUrlListLogic({ actionId: actionId ?? null, type }) + const logic = authorizedUrlListLogic({ + experimentId: experimentId ?? null, + actionId: actionId ?? null, + type, + query, + }) const { urlsKeyed, suggestionsLoading, @@ -162,7 +171,7 @@ export function AuthorizedUrlList({ type === AuthorizedUrlListType.TOOLBAR_URLS ? launchUrl(keyedURL.url) : // other urls are simply opened directly - keyedURL.url + keyedURL.url + query } targetBlank tooltip={ diff --git a/frontend/src/lib/components/AuthorizedUrlList/authorizedUrlListLogic.test.ts b/frontend/src/lib/components/AuthorizedUrlList/authorizedUrlListLogic.test.ts index 6e9897b9e2693..f8cf41db5c728 100644 --- a/frontend/src/lib/components/AuthorizedUrlList/authorizedUrlListLogic.test.ts +++ b/frontend/src/lib/components/AuthorizedUrlList/authorizedUrlListLogic.test.ts @@ -35,6 +35,7 @@ describe('the authorized urls list logic', () => { logic = authorizedUrlListLogic({ type: AuthorizedUrlListType.TOOLBAR_URLS, actionId: null, + experimentId: null, }) logic.mount() }) @@ -119,6 +120,7 @@ describe('the authorized urls list logic', () => { logic = authorizedUrlListLogic({ type: AuthorizedUrlListType.RECORDING_DOMAINS, actionId: null, + experimentId: null, }) logic.mount() }) diff --git a/frontend/src/lib/components/AuthorizedUrlList/authorizedUrlListLogic.ts b/frontend/src/lib/components/AuthorizedUrlList/authorizedUrlListLogic.ts index 29225f3d7d4ec..59a4fda64bc6b 100644 --- a/frontend/src/lib/components/AuthorizedUrlList/authorizedUrlListLogic.ts +++ b/frontend/src/lib/components/AuthorizedUrlList/authorizedUrlListLogic.ts @@ -35,6 +35,7 @@ export interface ProposeNewUrlFormType { export enum AuthorizedUrlListType { TOOLBAR_URLS = 'TOOLBAR_URLS', RECORDING_DOMAINS = 'RECORDING_DOMAINS', + WEB_EXPERIMENTS = 'WEB_EXPERIMENTS', } /** @@ -90,12 +91,14 @@ export const validateProposedUrl = ( /** defaultIntent: whether to launch with empty intent (i.e. toolbar mode is default) */ export function appEditorUrl( appUrl: string, - options?: { actionId?: number | null; userIntent?: ToolbarUserIntent } + options?: { actionId?: number | null; experimentId?: number | null | 'new'; userIntent?: ToolbarUserIntent } ): string { // See https://github.com/PostHog/posthog-js/blob/f7119c/src/extensions/toolbar.ts#L52 for where these params // are passed. `appUrl` is an extra `redirect_to_site` param. const params: ToolbarParams & { appUrl: string } = { - userIntent: options?.userIntent ?? (options?.actionId ? 'edit-action' : 'add-action'), + userIntent: + options?.userIntent ?? + (options?.actionId ? 'edit-action' : options?.experimentId ? 'edit-experiment' : 'add-action'), // Make sure to pass the app url, otherwise the api_host will be used by // the toolbar, which isn't correct when used behind a reverse proxy as // we require e.g. SSO login to the app, which will not work when placed @@ -103,6 +106,7 @@ export function appEditorUrl( apiURL: apiHostOrigin(), appUrl, ...(options?.actionId ? { actionId: options.actionId } : {}), + ...(options?.experimentId ? { experimentId: options.experimentId } : {}), } return '/api/user/redirect_to_site/' + encodeParams(params, '?') } @@ -164,7 +168,9 @@ export interface KeyedAppUrl { export interface AuthorizedUrlListLogicProps { actionId: number | null + experimentId: number | null | 'new' type: AuthorizedUrlListType + query: string | undefined } export const authorizedUrlListLogic = kea([ path((key) => ['lib', 'components', 'AuthorizedUrlList', 'authorizedUrlListLogic', key]), @@ -372,11 +378,18 @@ export const authorizedUrlListLogic = kea([ }, ], launchUrl: [ - (_, p) => [p.actionId], - (actionId) => (url: string) => - appEditorUrl(url, { + (_, p) => [p.actionId, p.experimentId], + (actionId, experimentId) => (url: string) => { + if (experimentId) { + return appEditorUrl(url, { + experimentId, + }) + } + + return appEditorUrl(url, { actionId, - }), + }) + }, ], isAddUrlFormVisible: [(s) => [s.editUrlIndex], (editUrlIndex) => editUrlIndex === -1], onlyAllowDomains: [(_, p) => [p.type], (type) => type === AuthorizedUrlListType.RECORDING_DOMAINS], diff --git a/frontend/src/lib/components/IframedToolbarBrowser/iframedToolbarBrowserLogic.ts b/frontend/src/lib/components/IframedToolbarBrowser/iframedToolbarBrowserLogic.ts index 89797b60479dd..1e571dcdfda6c 100644 --- a/frontend/src/lib/components/IframedToolbarBrowser/iframedToolbarBrowserLogic.ts +++ b/frontend/src/lib/components/IframedToolbarBrowser/iframedToolbarBrowserLogic.ts @@ -45,13 +45,13 @@ export const iframedToolbarBrowserLogic = kea([ connect({ values: [ - authorizedUrlListLogic({ actionId: null, type: AuthorizedUrlListType.TOOLBAR_URLS }), + authorizedUrlListLogic({ actionId: null, experimentId: null, type: AuthorizedUrlListType.TOOLBAR_URLS }), ['urlsKeyed', 'checkUrlIsAuthorized'], teamLogic, ['currentTeam'], ], actions: [ - authorizedUrlListLogic({ actionId: null, type: AuthorizedUrlListType.TOOLBAR_URLS }), + authorizedUrlListLogic({ actionId: null, experimentId: null, type: AuthorizedUrlListType.TOOLBAR_URLS }), ['addUrl'], teamLogic, ['updateCurrentTeamSuccess'], diff --git a/frontend/src/scenes/experiments/ExperimentView/DistributionTable.tsx b/frontend/src/scenes/experiments/ExperimentView/DistributionTable.tsx index b7aa63829482b..bb07d8914cf41 100644 --- a/frontend/src/scenes/experiments/ExperimentView/DistributionTable.tsx +++ b/frontend/src/scenes/experiments/ExperimentView/DistributionTable.tsx @@ -1,8 +1,11 @@ import '../Experiment.scss' import { IconFlag } from '@posthog/icons' -import { LemonButton, LemonTable, LemonTableColumns } from '@posthog/lemon-ui' +import { LemonButton, LemonDialog, LemonTable, LemonTableColumns } from '@posthog/lemon-ui' import { useActions, useValues } from 'kea' +import { AuthorizedUrlList } from 'lib/components/AuthorizedUrlList/AuthorizedUrlList' +import { AuthorizedUrlListType } from 'lib/components/AuthorizedUrlList/authorizedUrlListLogic' +import { IconOpenInApp } from 'lib/lemon-ui/icons' import { sidePanelStateLogic } from '~/layout/navigation-3000/sidepanel/sidePanelStateLogic' import { MultivariateFlagVariant, SidePanelTab } from '~/types' @@ -16,9 +19,29 @@ export function DistributionTable(): JSX.Element { const { reportExperimentReleaseConditionsViewed } = useActions(experimentLogic) const { openSidePanel } = useActions(sidePanelStateLogic) + const onSelectElement = (variant: string): void => { + LemonDialog.open({ + title: 'Select a domain', + description: 'Choose the domain on which to preview this experiment variant', + content: ( + <> + = [ { - className: 'w-1/3', + className: className, key: 'key', title: 'Variant', render: function Key(_, item): JSX.Element { @@ -29,7 +52,7 @@ export function DistributionTable(): JSX.Element { }, }, { - className: 'w-1/3', + className: className, key: 'rollout_percentage', title: 'Rollout', render: function Key(_, item): JSX.Element { @@ -37,7 +60,7 @@ export function DistributionTable(): JSX.Element { }, }, { - className: 'w-1/3', + className: className, key: 'variant_screenshot', title: 'Screenshot', render: function Key(_, item): JSX.Element { @@ -50,6 +73,31 @@ export function DistributionTable(): JSX.Element { }, ] + if (experiment.type === 'web') { + columns.push({ + className: className, + key: 'preview_web_experiment', + title: 'Preview', + render: function Key(_, item): JSX.Element { + return ( +
+ { + e.preventDefault() + onSelectElement(item.key) + }} + sideIcon={} + > + Preview variant + +
+ ) + }, + }) + } + return (
diff --git a/frontend/src/scenes/experiments/ExperimentView/ExperimentView.tsx b/frontend/src/scenes/experiments/ExperimentView/ExperimentView.tsx index 1c8bf5bd5e71a..de54d1461014b 100644 --- a/frontend/src/scenes/experiments/ExperimentView/ExperimentView.tsx +++ b/frontend/src/scenes/experiments/ExperimentView/ExperimentView.tsx @@ -2,8 +2,7 @@ import '../Experiment.scss' import { LemonDivider } from '@posthog/lemon-ui' import { useActions, useValues } from 'kea' -import { FEATURE_FLAGS } from 'lib/constants' -import { featureFlagLogic } from 'lib/logic/featureFlagLogic' +import { WebExperimentImplementationDetails } from 'scenes/experiments/WebExperimentImplementationDetails' import { ExperimentImplementationDetails } from '../ExperimentImplementationDetails' import { experimentLogic } from '../experimentLogic' @@ -17,7 +16,6 @@ import { import { DataCollection } from './DataCollection' import { DistributionTable } from './DistributionTable' import { ExperimentExposureModal, ExperimentGoalModal, Goal } from './Goal' -import { HoldoutSelector } from './HoldoutSelector' import { Info } from './Info' import { Overview } from './Overview' import { ReleaseConditionsTable } from './ReleaseConditionsTable' @@ -27,7 +25,6 @@ import { SecondaryMetricsTable } from './SecondaryMetricsTable' export function ExperimentView(): JSX.Element { const { experiment, experimentLoading, experimentResultsLoading, experimentId, experimentResults } = useValues(experimentLogic) - const { featureFlags } = useValues(featureFlagLogic) const { updateExperimentSecondaryMetrics } = useActions(experimentLogic) @@ -51,7 +48,6 @@ export function ExperimentView(): JSX.Element {
- {featureFlags[FEATURE_FLAGS.EXPERIMENTS_HOLDOUTS] && }
@@ -65,14 +61,18 @@ export function ExperimentView(): JSX.Element {
- {featureFlags[FEATURE_FLAGS.EXPERIMENTS_HOLDOUTS] && }
- + {experiment.type === 'web' ? ( + + ) : ( + + )} + {experiment.start_date && (
diff --git a/frontend/src/scenes/experiments/WebExperimentImplementationDetails.tsx b/frontend/src/scenes/experiments/WebExperimentImplementationDetails.tsx new file mode 100644 index 0000000000000..71037c654ab42 --- /dev/null +++ b/frontend/src/scenes/experiments/WebExperimentImplementationDetails.tsx @@ -0,0 +1,53 @@ +import { AuthorizedUrlList } from 'lib/components/AuthorizedUrlList/AuthorizedUrlList' +import { AuthorizedUrlListType } from 'lib/components/AuthorizedUrlList/authorizedUrlListLogic' +import { IconOpenInApp } from 'lib/lemon-ui/icons' +import { LemonButton } from 'lib/lemon-ui/LemonButton' +import { LemonDialog } from 'lib/lemon-ui/LemonDialog' + +import { Experiment } from '~/types' + +interface WebExperimentImplementationDetails { + experiment: Partial | null +} +export function WebExperimentImplementationDetails({ experiment }: WebExperimentImplementationDetails): JSX.Element { + const onSelectElement = (): void => { + LemonDialog.open({ + title: 'Select a domain', + description: experiment?.id + ? 'Choose the domain on which to edit this experiment' + : 'Choose the domain on which to create this experiment', + content: ( + <> + + + ), + primaryButton: { + children: 'Close', + type: 'secondary', + }, + }) + } + + return ( + <> +
+

Implementation

+
+
+ This Web experiment's implementation should be edited on your website. +
+
+ } + > + Edit Web experiment on website + +
+
+
+ + ) +} diff --git a/frontend/src/scenes/heatmaps/heatmapsBrowserLogic.ts b/frontend/src/scenes/heatmaps/heatmapsBrowserLogic.ts index 902d9a639b72a..00e2f6c4aa8ad 100644 --- a/frontend/src/scenes/heatmaps/heatmapsBrowserLogic.ts +++ b/frontend/src/scenes/heatmaps/heatmapsBrowserLogic.ts @@ -34,7 +34,7 @@ export const heatmapsBrowserLogic = kea([ connect({ values: [ - authorizedUrlListLogic({ actionId: null, type: AuthorizedUrlListType.TOOLBAR_URLS }), + authorizedUrlListLogic({ actionId: null, experimentId: null, type: AuthorizedUrlListType.TOOLBAR_URLS }), ['urlsKeyed', 'checkUrlIsAuthorized'], ], }), diff --git a/frontend/src/scenes/onboarding/productAnalyticsSteps/DashboardTemplateConfigureStep.tsx b/frontend/src/scenes/onboarding/productAnalyticsSteps/DashboardTemplateConfigureStep.tsx index af0f24e4553a9..ccc1c4f9540b8 100644 --- a/frontend/src/scenes/onboarding/productAnalyticsSteps/DashboardTemplateConfigureStep.tsx +++ b/frontend/src/scenes/onboarding/productAnalyticsSteps/DashboardTemplateConfigureStep.tsx @@ -33,7 +33,9 @@ const UrlInput = ({ iframeRef }: { iframeRef: React.RefObject iframedToolbarBrowserLogic({ iframeRef, clearBrowserUrlOnUnmount: true }) ) const { combinedSnippetAndLiveEventsHosts } = useValues(sdksLogic) - const { addUrl } = useActions(authorizedUrlListLogic({ actionId: null, type: AuthorizedUrlListType.TOOLBAR_URLS })) + const { addUrl } = useActions( + authorizedUrlListLogic({ actionId: null, experimentId: null, type: AuthorizedUrlListType.TOOLBAR_URLS }) + ) const [inputValue, setInputValue] = useState(currentPath) useEffect(() => { diff --git a/frontend/src/scenes/session-recordings/SessionRecordings.tsx b/frontend/src/scenes/session-recordings/SessionRecordings.tsx index a38b088878c60..1865046fcbf42 100644 --- a/frontend/src/scenes/session-recordings/SessionRecordings.tsx +++ b/frontend/src/scenes/session-recordings/SessionRecordings.tsx @@ -134,6 +134,7 @@ function Warnings(): JSX.Element { const theAuthorizedUrlsLogic = authorizedUrlListLogic({ actionId: null, + experimentId: null, type: AuthorizedUrlListType.RECORDING_DOMAINS, }) const { suggestions, authorizedUrls } = useValues(theAuthorizedUrlsLogic) diff --git a/frontend/src/scenes/sites/Site.tsx b/frontend/src/scenes/sites/Site.tsx index 4e97669011639..57fe03e78e16f 100644 --- a/frontend/src/scenes/sites/Site.tsx +++ b/frontend/src/scenes/sites/Site.tsx @@ -14,7 +14,7 @@ export const scene: SceneExport = { export function Site({ url }: { url?: string } = {}): JSX.Element { const { launchUrl } = useValues( - authorizedUrlListLogic({ actionId: null, type: AuthorizedUrlListType.TOOLBAR_URLS }) + authorizedUrlListLogic({ actionId: null, experimentId: 'new', type: AuthorizedUrlListType.TOOLBAR_URLS }) ) const decodedUrl = decodeURIComponent(url || '') diff --git a/frontend/src/toolbar/bar/Toolbar.tsx b/frontend/src/toolbar/bar/Toolbar.tsx index 22100f1363bfb..1111676842b71 100644 --- a/frontend/src/toolbar/bar/Toolbar.tsx +++ b/frontend/src/toolbar/bar/Toolbar.tsx @@ -174,6 +174,10 @@ export function Toolbar(): JSX.Element | null { setVisibleMenu('actions') } + if (userIntent === 'add-experiment' || userIntent === 'edit-experiment') { + setVisibleMenu('experiments') + } + if (userIntent === 'heatmaps') { setVisibleMenu('heatmap') } diff --git a/frontend/src/toolbar/experiments/ExperimentsEditingToolbarMenu.tsx b/frontend/src/toolbar/experiments/ExperimentsEditingToolbarMenu.tsx index bdbafabc35ff9..3fa6f679b5dec 100644 --- a/frontend/src/toolbar/experiments/ExperimentsEditingToolbarMenu.tsx +++ b/frontend/src/toolbar/experiments/ExperimentsEditingToolbarMenu.tsx @@ -5,14 +5,16 @@ import { LemonBanner } from 'lib/lemon-ui/LemonBanner' import { LemonButton } from 'lib/lemon-ui/LemonButton' import { LemonCollapse } from 'lib/lemon-ui/LemonCollapse' import { LemonInput } from 'lib/lemon-ui/LemonInput' -import { useMemo } from 'react' +import { useEffect, useMemo } from 'react' import { ToolbarMenu } from '~/toolbar/bar/ToolbarMenu' +import { experimentsLogic } from '~/toolbar/experiments/experimentsLogic' import { experimentsTabLogic } from '~/toolbar/experiments/experimentsTabLogic' import { WebExperimentVariant } from '~/toolbar/experiments/WebExperimentVariant' import { WebExperimentVariantHeader } from '~/toolbar/experiments/WebExperimentVariantHeader' export const ExperimentsEditingToolbarMenu = (): JSX.Element => { + const { getExperiments } = useActions(experimentsLogic) const { selectedExperimentId, experimentForm, addVariantAvailable, selectedVariant, experimentFormErrors } = useValues(experimentsTabLogic) const { @@ -31,6 +33,10 @@ export const ExperimentsEditingToolbarMenu = (): JSX.Element => { } }, [selectedExperimentId, selectVariant, inspectForElementWithIndex]) + useEffect(() => { + getExperiments() + }, []) + return (
([ connect(() => ({ values: [ toolbarConfigLogic, - ['dataAttributes', 'apiURL', 'temporaryToken', 'buttonVisible', 'userIntent', 'dataAttributes'], + [ + 'dataAttributes', + 'apiURL', + 'temporaryToken', + 'buttonVisible', + 'userIntent', + 'dataAttributes', + 'experimentId', + ], experimentsLogic, ['allExperiments'], ], @@ -283,29 +291,27 @@ export const experimentsTabLogic = kea([ applyVariant: ({ variant }) => { if (values.experimentForm && values.experimentForm.variants) { const selectedVariant = values.experimentForm.variants[variant] - if (selectedVariant) { - selectedVariant.transforms.forEach((transform) => { - if (transform.selector) { - const elements = document.querySelectorAll(transform.selector) - elements.forEach((elements) => { - const htmlElement = elements as HTMLElement - if (htmlElement) { - if (transform.text) { - htmlElement.innerText = transform.text - } + selectedVariant?.transforms?.forEach((transform) => { + if (transform.selector) { + const elements = document.querySelectorAll(transform.selector) + elements.forEach((elements) => { + const htmlElement = elements as HTMLElement + if (htmlElement) { + if (transform.text) { + htmlElement.innerText = transform.text + } - if (transform.html) { - htmlElement.innerHTML = transform.html - } + if (transform.html) { + htmlElement.innerHTML = transform.html + } - if (transform.css) { - htmlElement.setAttribute('style', transform.css) - } + if (transform.css) { + htmlElement.setAttribute('style', transform.css) } - }) - } - }) - } + } + }) + } + }) } }, rebalanceRolloutPercentage: () => { @@ -345,13 +351,15 @@ export const experimentsTabLogic = kea([ if (values.experimentForm.variants) { const webVariant = values.experimentForm.variants[variant] if (webVariant) { - if (webVariant.transforms) { - webVariant.transforms.push({ - text: '', - html: '', - } as unknown as WebExperimentTransform) + if (webVariant.transforms == undefined) { + webVariant.transforms = [] } + webVariant.transforms.push({ + text: '', + html: '', + } as unknown as WebExperimentTransform) + actions.setExperimentFormValue('variants', values.experimentForm.variants) actions.selectVariant(variant) actions.inspectForElementWithIndex(variant, webVariant.transforms.length - 1) @@ -375,9 +383,9 @@ export const experimentsTabLogic = kea([ toolbarPosthogJS.capture('toolbar mode triggered', { mode: 'experiments', enabled: false }) }, [experimentsLogic.actionTypes.getExperimentsSuccess]: () => { - const { userIntent, selectedExperimentId } = values + const { userIntent, experimentId } = values if (userIntent === 'edit-experiment') { - actions.selectExperiment(selectedExperimentId) + actions.selectExperiment(experimentId) toolbarConfigLogic.actions.clearUserIntent() } else if (userIntent === 'add-experiment') { actions.newExperiment() diff --git a/frontend/src/toolbar/index.tsx b/frontend/src/toolbar/index.tsx index a5bdb7923fa1c..e5ae6fa344cc8 100644 --- a/frontend/src/toolbar/index.tsx +++ b/frontend/src/toolbar/index.tsx @@ -24,6 +24,7 @@ import { ToolbarParams } from '~/types' diff --git a/frontend/src/toolbar/toolbarConfigLogic.ts b/frontend/src/toolbar/toolbarConfigLogic.ts index 55ea5b53684c1..e31442b5b2743 100644 --- a/frontend/src/toolbar/toolbarConfigLogic.ts +++ b/frontend/src/toolbar/toolbarConfigLogic.ts @@ -30,6 +30,7 @@ export const toolbarConfigLogic = kea([ { logout: () => null, tokenExpired: () => null, authenticate: () => null }, ], actionId: [props.actionId || null, { logout: () => null, clearUserIntent: () => null }], + experimentId: [props.experimentId || null, { logout: () => null, clearUserIntent: () => null }], userIntent: [props.userIntent || null, { logout: () => null, clearUserIntent: () => null }], buttonVisible: [true, { showButton: () => true, hideButton: () => false, logout: () => false }], })), @@ -75,6 +76,7 @@ export const toolbarConfigLogic = kea([ ...values.props, temporaryToken: values.temporaryToken ?? undefined, actionId: values.actionId ?? undefined, + experimentId: values.experimentId ?? undefined, userIntent: values.userIntent ?? undefined, posthog: undefined, featureFlags: undefined, diff --git a/frontend/src/types.ts b/frontend/src/types.ts index 3d08ee1fb7512..57089edf1adb2 100644 --- a/frontend/src/types.ts +++ b/frontend/src/types.ts @@ -25,7 +25,6 @@ import type { PostHog, SupportedWebVitalsMetrics } from 'posthog-js' import { Layout } from 'react-grid-layout' import { LogLevel } from 'rrweb' import { BehavioralFilterKey, BehavioralFilterType } from 'scenes/cohorts/CohortFilters/types' -import { Holdout } from 'scenes/experiments/holdoutsLogic' import { AggregationAxisFormat } from 'scenes/insights/aggregationAxisFormat' import { JSONContent } from 'scenes/notebooks/Notebook/utils' import { Scene } from 'scenes/sceneTypes' @@ -41,7 +40,6 @@ import type { InsightVizNode, Node, QueryStatus, - RecordingOrder, RecordingsQuery, } from './queries/schema' import { NodeKind } from './queries/schema' @@ -516,7 +514,6 @@ export interface TeamType extends TeamBasicType { autocapture_web_vitals_opt_in?: boolean autocapture_web_vitals_allowed_metrics?: SupportedWebVitalsMetrics[] session_recording_url_trigger_config?: SessionReplayUrlTriggerConfig[] - session_recording_url_blocklist_config?: SessionReplayUrlTriggerConfig[] surveys_opt_in?: boolean heatmaps_opt_in?: boolean autocapture_exceptions_errors_to_ignore: string[] @@ -618,6 +615,7 @@ export interface ToolbarParams { token?: string /** public posthog-js token */ temporaryToken?: string /** private temporary user token */ actionId?: number + experimentId?: number userIntent?: ToolbarUserIntent source?: ToolbarSource toolbarVersion?: ToolbarVersion @@ -681,7 +679,6 @@ export enum ExperimentsTabs { All = 'all', Yours = 'yours', Archived = 'archived', - Holdouts = 'holdouts', } export enum ActivityTab { @@ -3236,8 +3233,9 @@ export interface Group { } export interface Experiment { - id: number | 'new' + id: number | 'new' | 'web' name: string + type?: string description?: string feature_flag_key: string feature_flag?: FeatureFlagBasicType @@ -3259,8 +3257,6 @@ export interface Experiment { created_at: string | null created_by: UserBasicType | null updated_at: string | null - holdout_id?: number | null - holdout?: Holdout } export interface FunnelExperimentVariant { @@ -4645,10 +4641,9 @@ export type ReplayTemplateType = { key: string name: string description: string - variables?: ReplayTemplateVariableType[] + variables: ReplayTemplateVariableType[] categories: ReplayTemplateCategory[] icon?: React.ReactNode - order?: RecordingOrder } export type ReplayTemplateCategory = 'B2B' | 'B2C' | 'More' diff --git a/posthog/api/user.py b/posthog/api/user.py index d3fe92437f206..92cfa08e01895 100644 --- a/posthog/api/user.py +++ b/posthog/api/user.py @@ -511,6 +511,7 @@ def redirect_to_site(request): "token": team.api_token, "temporaryToken": request.user.temporary_token, "actionId": request.GET.get("actionId"), + "experimentId": request.GET.get("experimentId"), "userIntent": request.GET.get("userIntent"), "toolbarVersion": "toolbar", "apiURL": request.build_absolute_uri("/")[:-1], diff --git a/posthog/api/web_experiment.py b/posthog/api/web_experiment.py index e4e03a5f9019a..5de34dfa79251 100644 --- a/posthog/api/web_experiment.py +++ b/posthog/api/web_experiment.py @@ -58,10 +58,8 @@ def validate(self, attrs): for name, variant in variants.items(): if variant.get("rollout_percentage") is None: raise ValidationError(f"Experiment variant '{name}' does not have any rollout percentage") - transforms = variant.get("transforms") - if transforms is None: - raise ValidationError(f"Experiment variant '{name}' does not have any element transforms") if name != "control": + transforms = variant.get("transforms", {}) for idx, transform in enumerate(transforms): if transform.get("selector") is None: raise ValidationError(