Skip to content

Commit

Permalink
feat(experiments): edit flag from the sidebar (#25412)
Browse files Browse the repository at this point in the history
  • Loading branch information
jurajmajerik authored Oct 9, 2024
1 parent 0b76c16 commit 417725d
Show file tree
Hide file tree
Showing 13 changed files with 258 additions and 19 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 7 additions & 1 deletion frontend/src/layout/navigation-3000/sidepanel/SidePanel.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import './SidePanel.scss'

import { IconEllipsis, IconFeatures, IconGear, IconInfo, IconNotebook, IconSupport } from '@posthog/icons'
import { IconEllipsis, IconFeatures, IconFlag, IconGear, IconInfo, IconNotebook, IconSupport } from '@posthog/icons'
import { LemonButton, LemonMenu, LemonMenuItems, LemonModal } from '@posthog/lemon-ui'
import clsx from 'clsx'
import { useActions, useValues } from 'kea'
Expand All @@ -20,6 +20,7 @@ import { SidePanelActivation, SidePanelActivationIcon } from './panels/activatio
import { SidePanelActivity, SidePanelActivityIcon } from './panels/activity/SidePanelActivity'
import { SidePanelDiscussion, SidePanelDiscussionIcon } from './panels/discussion/SidePanelDiscussion'
import { SidePanelDocs } from './panels/SidePanelDocs'
import { SidePanelExperimentFeatureFlag } from './panels/SidePanelExperimentFeatureFlag'
import { SidePanelFeaturePreviews } from './panels/SidePanelFeaturePreviews'
import { SidePanelSettings } from './panels/SidePanelSettings'
import { SidePanelStatus, SidePanelStatusIcon } from './panels/SidePanelStatus'
Expand Down Expand Up @@ -87,6 +88,11 @@ export const SIDE_PANEL_TABS: Record<
Content: SidePanelStatus,
noModalSupport: true,
},
[SidePanelTab.ExperimentFeatureFlag]: {
label: 'Release conditions',
Icon: IconFlag,
Content: SidePanelExperimentFeatureFlag,
},
}

const DEFAULT_WIDTH = 512
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
import { LemonBanner, LemonButton, LemonDivider, LemonInput, LemonTable, Link, Spinner } from '@posthog/lemon-ui'
import { useActions, useValues } from 'kea'
import { router } from 'kea-router'
import { useEffect, useMemo } from 'react'
import { experimentLogic } from 'scenes/experiments/experimentLogic'
import { featureFlagLogic, FeatureFlagLogicProps } from 'scenes/feature-flags/featureFlagLogic'
import { FeatureFlagReleaseConditions } from 'scenes/feature-flags/FeatureFlagReleaseConditions'
import { urls } from 'scenes/urls'

import { sidePanelStateLogic } from '../sidePanelStateLogic'

export const SidePanelExperimentFeatureFlag = (): JSX.Element => {
const { closeSidePanel } = useActions(sidePanelStateLogic)
const { currentLocation } = useValues(router)

useEffect(() => {
// Side panel state is persisted in local storage, so we need to check if we're on the experiment page,
// otherwise close the side panel
const isExperimentPath = /^\/project\/[0-9]+\/experiments\/[0-9]+/.test(currentLocation.pathname)
if (!isExperimentPath) {
closeSidePanel()
}
}, [currentLocation, closeSidePanel])

// Retrieve experiment ID from URL
const experimentId = useMemo(() => {
const match = currentLocation.pathname.match(/\/experiments\/(\d+)/)
return match ? parseInt(match[1]) : null
}, [currentLocation.pathname])

const { experiment } = useValues(experimentLogic({ experimentId: experimentId ?? 'new' }))

const _featureFlagLogic = featureFlagLogic({ id: experiment.feature_flag?.id ?? null } as FeatureFlagLogicProps)
const { featureFlag, areVariantRolloutsValid, variantRolloutSum, featureFlagLoading } = useValues(_featureFlagLogic)
const { setFeatureFlagFilters, saveSidebarExperimentFeatureFlag, distributeVariantsEqually } =
useActions(_featureFlagLogic)

const variants = featureFlag?.filters?.multivariate?.variants || []

const handleRolloutPercentageChange = (index: number, value: number | undefined): void => {
if (!featureFlag?.filters?.multivariate || !value) {
return
}

const updatedVariants = featureFlag.filters.multivariate.variants.map((variant, i) =>
i === index ? { ...variant, rollout_percentage: value } : variant
)

const updatedFilters = {
...featureFlag.filters,
multivariate: { ...featureFlag.filters.multivariate, variants: updatedVariants },
}

setFeatureFlagFilters(updatedFilters, null)
}

if (featureFlagLoading || !featureFlag.id) {
return (
<div className="flex items-center justify-center h-full">
<Spinner className="text-3xl" />
</div>
)
}

return (
<div className="space-y-6 p-2">
<LemonBanner type="info">
<div className="space-y-3">
<div>
Adjusting variant distribution or user targeting may impact the validity of your results. Adjust
only if you're aware of how changes will affect your experiment.
</div>
<div>
For full feature flag settings, go to{' '}
<Link
target="_blank"
className="font-semibold"
to={experiment.feature_flag ? urls.featureFlag(experiment.feature_flag.id) : undefined}
>
{experiment.feature_flag?.key}
</Link>{' '}
.
</div>
</div>
</LemonBanner>
<div>
<h3 className="l3">Experiment variants</h3>
<LemonTable
dataSource={variants}
columns={[
{
title: 'Variant Key',
dataIndex: 'key',
key: 'key',
render: (value) => <span className="font-semibold">{value}</span>,
width: '50%',
},
{
title: (
<div className="flex items-center justify-between space-x-2">
<span>Rollout Percentage</span>
<LemonButton type="secondary" size="xsmall" onClick={distributeVariantsEqually}>
Redistribute
</LemonButton>
</div>
),
dataIndex: 'rollout_percentage',
key: 'rollout_percentage',
render: (_, record, index) => (
<LemonInput
type="number"
value={record.rollout_percentage}
onChange={(changedValue) => {
if (changedValue !== null) {
const valueInt =
changedValue !== undefined ? parseInt(changedValue.toString()) : 0
if (!isNaN(valueInt)) {
handleRolloutPercentageChange(index, changedValue)
}
}
}}
min={0}
max={100}
/>
),
},
]}
/>
{variants.length > 0 && !areVariantRolloutsValid && (
<p className="text-danger">
Percentage rollouts for variants must sum to 100 (currently {variantRolloutSum}
).
</p>
)}
</div>

<FeatureFlagReleaseConditions
id={`${experiment.feature_flag?.id}`}
filters={featureFlag?.filters ?? []}
onChange={setFeatureFlagFilters}
/>
<LemonDivider />
<div>
<LemonButton
className="-mt-4"
type="primary"
onClick={() => {
saveSidebarExperimentFeatureFlag(featureFlag)
}}
>
Save
</LemonButton>
</div>
</div>
)
}
28 changes: 26 additions & 2 deletions frontend/src/layout/navigation-3000/sidepanel/sidePanelLogic.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { connect, kea, path, selectors } from 'kea'
import { router } from 'kea-router'
import { FEATURE_FLAGS } from 'lib/constants'
import { featureFlagLogic } from 'lib/logic/featureFlagLogic'
import { preflightLogic } from 'scenes/PreflightCheck/preflightLogic'
Expand Down Expand Up @@ -39,6 +40,8 @@ export const sidePanelLogic = kea<sidePanelLogicType>([
['status'],
userLogic,
['hasAvailableFeature'],
router,
['currentLocation'],
],
actions: [sidePanelStateLogic, ['closeSidePanel', 'openSidePanel']],
}),
Expand All @@ -49,6 +52,7 @@ export const sidePanelLogic = kea<sidePanelLogicType>([
(isCloudOrDev, isReady, hasCompletedAllTasks, featureflags) => {
const tabs: SidePanelTab[] = []

tabs.push(SidePanelTab.ExperimentFeatureFlag)
tabs.push(SidePanelTab.Notebooks)
tabs.push(SidePanelTab.Docs)
if (isCloudOrDev) {
Expand All @@ -74,8 +78,24 @@ export const sidePanelLogic = kea<sidePanelLogicType>([
],

visibleTabs: [
(s) => [s.enabledTabs, s.selectedTab, s.sidePanelOpen, s.unreadCount, s.status, s.hasAvailableFeature],
(enabledTabs, selectedTab, sidePanelOpen, unreadCount, status, hasAvailableFeature): SidePanelTab[] => {
(s) => [
s.enabledTabs,
s.selectedTab,
s.sidePanelOpen,
s.unreadCount,
s.status,
s.hasAvailableFeature,
s.currentLocation,
],
(
enabledTabs,
selectedTab,
sidePanelOpen,
unreadCount,
status,
hasAvailableFeature,
currentLocation
): SidePanelTab[] => {
return enabledTabs.filter((tab) => {
if (tab === selectedTab && sidePanelOpen) {
return true
Expand All @@ -98,6 +118,10 @@ export const sidePanelLogic = kea<sidePanelLogicType>([
return false
}

if (tab === SidePanelTab.ExperimentFeatureFlag) {
return /^\/project\/[0-9]+\/experiments\/[0-9]+/.test(currentLocation.pathname)
}

return true
})
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
import '../Experiment.scss'

import { LemonTable, LemonTableColumns, Link } from '@posthog/lemon-ui'
import { useValues } from 'kea'
import { urls } from 'scenes/urls'
import { IconFlag } from '@posthog/icons'
import { LemonButton, LemonTable, LemonTableColumns } from '@posthog/lemon-ui'
import { useActions, useValues } from 'kea'

import { MultivariateFlagVariant } from '~/types'
import { sidePanelStateLogic } from '~/layout/navigation-3000/sidepanel/sidePanelStateLogic'
import { MultivariateFlagVariant, SidePanelTab } from '~/types'

import { experimentLogic } from '../experimentLogic'
import { VariantTag } from './components'
import { VariantScreenshot } from './VariantScreenshot'

export function DistributionTable(): JSX.Element {
const { experimentId, experiment, experimentResults } = useValues(experimentLogic)
const { openSidePanel } = useActions(sidePanelStateLogic)

const columns: LemonTableColumns<MultivariateFlagVariant> = [
{
Expand Down Expand Up @@ -56,13 +58,15 @@ export function DistributionTable(): JSX.Element {

<div className="w-1/2 flex flex-col justify-end">
<div className="ml-auto mb-2">
<Link
target="_blank"
<LemonButton
icon={<IconFlag />}
onClick={() => openSidePanel(SidePanelTab.ExperimentFeatureFlag)}
type="secondary"
size="xsmall"
className="font-semibold"
to={experiment.feature_flag ? urls.featureFlag(experiment.feature_flag.id) : undefined}
>
Manage distribution
</Link>
</LemonButton>
</div>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
import '../Experiment.scss'

import { LemonTable, LemonTableColumns, LemonTag, Link } from '@posthog/lemon-ui'
import { useValues } from 'kea'
import { urls } from 'scenes/urls'
import { IconFlag } from '@posthog/icons'
import { LemonButton, LemonTable, LemonTableColumns, LemonTag } from '@posthog/lemon-ui'
import { useActions, useValues } from 'kea'

import { sidePanelStateLogic } from '~/layout/navigation-3000/sidepanel/sidePanelStateLogic'
import { groupsModel } from '~/models/groupsModel'
import { FeatureFlagGroupType } from '~/types'
import { FeatureFlagGroupType, SidePanelTab } from '~/types'

import { experimentLogic } from '../experimentLogic'

export function ReleaseConditionsTable(): JSX.Element {
const { experiment } = useValues(experimentLogic)
const { aggregationLabel } = useValues(groupsModel)
const { openSidePanel } = useActions(sidePanelStateLogic)

const columns: LemonTableColumns<FeatureFlagGroupType> = [
{
Expand Down Expand Up @@ -61,13 +63,15 @@ export function ReleaseConditionsTable(): JSX.Element {

<div className="w-1/2 flex flex-col justify-end">
<div className="ml-auto mb-2">
<Link
target="_blank"
<LemonButton
icon={<IconFlag />}
onClick={() => openSidePanel(SidePanelTab.ExperimentFeatureFlag)}
type="secondary"
size="xsmall"
className="font-semibold"
to={experiment.feature_flag ? urls.featureFlag(experiment.feature_flag.id) : undefined}
>
Manage release conditions
</Link>
</LemonButton>
</div>
</div>
</div>
Expand Down
Loading

0 comments on commit 417725d

Please sign in to comment.