Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(feature-mgmt): Set up success/failure/exposure metrics #27343

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions frontend/src/lib/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { ActivityLogItem } from 'lib/components/ActivityLog/humanizeActivity'
import { apiStatusLogic } from 'lib/logic/apiStatusLogic'
import { objectClean, toParams } from 'lib/utils'
import posthog from 'posthog-js'
import { NewFeatureForm } from 'scenes/feature-management/featureManagementEditLogic'
import { FeatureForm } from 'scenes/feature-management/featureManagementEditLogic'
import { RecordingComment } from 'scenes/session-recordings/player/inspector/playerInspectorLogic'
import { SavedSessionRecordingPlaylistsResult } from 'scenes/session-recordings/saved-playlists/savedSessionRecordingPlaylistsLogic'

Expand Down Expand Up @@ -1060,15 +1060,15 @@ const api = {
return await new ApiRequest().feature(id, teamId).get()
},
async create(
feature: NewFeatureForm,
feature: FeatureForm,
teamId: TeamType['id'] = ApiConfig.getCurrentTeamId()
): Promise<FeatureType> {
return await new ApiRequest().features(teamId).create({ data: feature })
},
async update(
feature: FeatureType,
teamId: TeamType['id'] = ApiConfig.getCurrentTeamId()
): Promise<FeatureType> {
): Promise<FeatureForm> {
return await new ApiRequest().feature(feature.id, teamId).update({ data: feature })
},
},
Expand Down
198 changes: 195 additions & 3 deletions frontend/src/scenes/feature-management/FeatureManagementEdit.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { LemonButton, LemonDivider, LemonInput, LemonTextArea } from '@posthog/lemon-ui'
import { IconExternal, IconPlusSmall, IconTrash } from '@posthog/icons'
import { LemonButton, LemonDivider, LemonInput, LemonLabel, LemonModal, LemonTextArea, Link } from '@posthog/lemon-ui'
import { useValues } from 'kea'
import { Form } from 'kea-forms'
import { router } from 'kea-router'
Expand All @@ -7,7 +8,12 @@
import { SceneExport } from 'scenes/sceneTypes'
import { urls } from 'scenes/urls'

import { InsightModel } from '~/types'

import { featureManagementEditLogic } from './featureManagementEditLogic'
import { Query } from '~/queries/Query/Query'
import { NodeKind } from '~/queries/schema'
import { commonActionFilterProps } from 'scenes/experiments/Metrics/Selectors'

export const scene: SceneExport = {
component: FeatureManagementEdit,
Expand All @@ -18,14 +24,14 @@
}

function FeatureManagementEdit(): JSX.Element {
const { props } = useValues(featureManagementEditLogic)
const { props, featureForm } = useValues(featureManagementEditLogic)

return (
<Form
id="feature-creation"
logic={featureManagementEditLogic}
props={props}
formKey="feature"
formKey="featureForm"
enableFormOnSubmit
className="space-y-4"
>
Expand Down Expand Up @@ -88,6 +94,68 @@
</div>
<LemonDivider />

<div className="flex flex-col space-y-4">
<div className="flex flex-col items-start space-y-2">
<LemonLabel>Success metrics (optional)</LemonLabel>
{featureForm.success_metrics.map((insight) => (
<FeatureManagementMetric
key={insight.short_id}
insight={insight}
onDelete={() => alert(`Delete metric ${insight.short_id}`)}
/>
))}
<LemonButton
type="secondary"
onClick={() => alert('Add metric')}
icon={<IconPlusSmall />}
size="xsmall"
data-attr="add-test-variant"
>
Add metric
</LemonButton>
</div>
<div className="flex flex-col items-start space-y-2">
<LemonLabel>Failure metrics (optional)</LemonLabel>
{featureForm.failure_metrics.map((insight) => (
<FeatureManagementMetric
key={insight.short_id}
insight={insight}
onDelete={() => alert(`Delete metric ${insight.short_id}`)}
/>
))}
<LemonButton
type="secondary"
onClick={() => alert('Add metric')}
icon={<IconPlusSmall />}
size="xsmall"
data-attr="add-test-variant"
>
Add metric
</LemonButton>
</div>
<div className="flex flex-col items-start space-y-2">
<LemonLabel>Exposure metrics (optional)</LemonLabel>
{featureForm.exposure_metrics.map((insight) => (
<FeatureManagementMetric
key={insight.short_id}
insight={insight}
onDelete={() => alert(`Delete metric ${insight.short_id}`)}
/>
))}
<LemonButton
type="secondary"
onClick={() => alert('Add metric')}
icon={<IconPlusSmall />}
size="xsmall"
data-attr="add-test-variant"
>
Add metric
</LemonButton>
</div>
</div>

<LemonDivider />

<div className="flex items-center gap-2 justify-end">
<LemonButton
data-attr="cancel-feature-flag"
Expand All @@ -103,3 +171,127 @@
</Form>
)
}

function FeatureManagementMetric({ insight, onDelete }: { insight: InsightModel; onDelete: () => void }): JSX.Element {
return (
<div>
<div className="flex items-center gap-2">
<span>{insight.name}</span>

<Link to={urls.insightView(insight.short_id)}>
<LemonButton type="primary" icon={<IconExternal />}>
View metric
</LemonButton>
</Link>

<LemonButton type="secondary" icon={<IconTrash />} onClick={onDelete} />
</div>
</div>
)
}

function FeatureMetricModal({

Check failure on line 193 in frontend/src/scenes/feature-management/FeatureManagementEdit.tsx

View workflow job for this annotation

GitHub Actions / Code quality checks

'FeatureMetricModal' is declared but its value is never read.
isOpen,
onChange,

Check failure on line 195 in frontend/src/scenes/feature-management/FeatureManagementEdit.tsx

View workflow job for this annotation

GitHub Actions / Code quality checks

'onChange' is declared but its value is never read.
onClose,
}: {
isOpen: boolean
onChange: (metric: InsightModel) => void
onClose: () => void
}): JSX.Element {
const { experiment, experimentLoading } = useValues(experimentLogic({ experimentId }))

Check failure on line 202 in frontend/src/scenes/feature-management/FeatureManagementEdit.tsx

View workflow job for this annotation

GitHub Actions / Code quality checks

Cannot find name 'experimentLogic'. Did you mean 'experiment'?

Check failure on line 202 in frontend/src/scenes/feature-management/FeatureManagementEdit.tsx

View workflow job for this annotation

GitHub Actions / Code quality checks

Cannot find name 'experimentId'. Did you mean 'experiment'?
const { updateExperiment } = useActions(experimentLogic({ experimentId }))

Check failure on line 203 in frontend/src/scenes/feature-management/FeatureManagementEdit.tsx

View workflow job for this annotation

GitHub Actions / Code quality checks

Cannot find name 'useActions'.

Check failure on line 203 in frontend/src/scenes/feature-management/FeatureManagementEdit.tsx

View workflow job for this annotation

GitHub Actions / Code quality checks

Cannot find name 'experimentLogic'. Did you mean 'experiment'?

Check failure on line 203 in frontend/src/scenes/feature-management/FeatureManagementEdit.tsx

View workflow job for this annotation

GitHub Actions / Code quality checks

Cannot find name 'experimentId'. Did you mean 'experiment'?

return (
<LemonModal
isOpen={isOpen}
onClose={onClose}
width={1000}
title="Change feature metric"
footer={
<div className="flex items-center gap-2">
<LemonButton form="edit-experiment-exposure-form" type="secondary" onClick={onClose}>
Cancel
</LemonButton>
<LemonButton
form="edit-experiment-exposure-form"
onClick={() => {
updateExperiment({
metrics: experiment.metrics,
})
}}
type="primary"
loading={experimentLoading}
data-attr="create-annotation-submit"
>
Save
</LemonButton>
</div>
}
>
<PrimaryGoalTrendsExposure />

Check failure on line 232 in frontend/src/scenes/feature-management/FeatureManagementEdit.tsx

View workflow job for this annotation

GitHub Actions / Code quality checks

Cannot find name 'PrimaryGoalTrendsExposure'.
</LemonModal>
)
}

function FeatureMetricPicker({ onChange }: { onChange: (something: any) => void }): JSX.Element {

Check failure on line 237 in frontend/src/scenes/feature-management/FeatureManagementEdit.tsx

View workflow job for this annotation

GitHub Actions / Code quality checks

'FeatureMetricPicker' is declared but its value is never read.
const hasFilters = (currentTeam?.test_account_filters || []).length > 0

if (!editingPrimaryMetricIndex && editingPrimaryMetricIndex !== 0) {
return <></>
}

const metricIdx = editingPrimaryMetricIndex
const currentMetric = experiment.metrics[metricIdx] as ExperimentTrendsQuery

return (
<>
<ActionFilter
bordered
filters={queryNodeToFilter(currentMetric.exposure_query as InsightQueryNode)}
setFilters={({ actions, events, data_warehouse }: Partial<FilterType>): void => {
const series = actionsAndEventsToSeries(
{ actions, events, data_warehouse } as any,
true,
MathAvailability.All
)

setTrendsExposureMetric({
metricIdx,
series,
})
}}
typeKey="experiment-metric"
buttonCopy="Add graph series"
showSeriesIndicator={true}
entitiesLimit={1}
showNumericalPropsOnly={true}
{...commonActionFilterProps}
/>
<div className="mt-4 space-y-4">
<TestAccountFilterSwitch
checked={hasFilters ? !!currentMetric.exposure_query?.filterTestAccounts : false}
onChange={(checked: boolean) => {
setTrendsExposureMetric({
metricIdx,
filterTestAccounts: checked,
})
}}
fullWidth
/>
</div>
<div className="mt-4">
<Query
query={{
kind: NodeKind.InsightVizNode,
source: currentMetric.exposure_query,
showTable: false,
showLastComputation: true,
showLastComputationRefresh: false,
}}
readOnly
/>
</div>
</>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import api from 'lib/api'
import { Scene } from 'scenes/sceneTypes'
import { urls } from 'scenes/urls'

import { Breadcrumb, FeatureType } from '~/types'
import { Breadcrumb, FeatureType, InsightModel } from '~/types'

import type { featureManagementEditLogicType } from './featureManagementEditLogicType'
import { featureManagementLogic } from './featureManagementLogic'
Expand All @@ -17,12 +17,22 @@ export interface FeatureLogicProps {
id: string
}

export type NewFeatureForm = Pick<FeatureType, 'key' | 'name' | 'description'>
export type FeatureForm = {
name: string
key: string
description: string
success_metrics: InsightModel[]
failure_metrics: InsightModel[]
exposure_metrics: InsightModel[]
}

const NEW_FEATURE: NewFeatureForm = {
const NEW_FEATURE: FeatureForm = {
key: '',
name: '',
description: '',
success_metrics: [],
failure_metrics: [],
exposure_metrics: [],
}

export const featureManagementEditLogic = kea<featureManagementEditLogicType>([
Expand All @@ -32,23 +42,23 @@ export const featureManagementEditLogic = kea<featureManagementEditLogicType>([
actions: [featureManagementLogic, ['loadFeatures']],
}),
loaders(({ props, actions }) => ({
feature: {
saveFeature: async (updatedFeature: NewFeatureForm | FeatureType) => {
featureForm: {
saveFeature: async (updatedFeature: FeatureForm): Promise<FeatureType> => {
let feature
if (props.id === 'new') {
feature = await api.features.create(updatedFeature)
} else {
feature = await api.features.update(updatedFeature as FeatureType)
feature = await api.features.update(updatedFeature)
}

// Reset the form after creation
actions.resetFeature()
return feature
return updatedFeature
},
},
})),
forms(({ actions }) => ({
feature: {
featureForm: {
defaults: { ...NEW_FEATURE },
// sync validation, will be shown as errors in the form
errors: ({ name }) => {
Expand All @@ -63,7 +73,7 @@ export const featureManagementEditLogic = kea<featureManagementEditLogicType>([
},
})),
reducers({
feature: [
featureForm: [
NEW_FEATURE,
{
setFeatureValue: (state, { name, value }) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ export const featureManagementLogic = kea<featureManagementLogicType>([
}),
listeners(({ actions, values }) => ({
loadFeaturesSuccess: ({ features }) => {
console.log('loadFeaturesSuccess', { features, active: values.activeFeatureId })
if (values.activeFeatureId === null && features.results.length > 0) {
actions.setActiveFeatureId(features.results[0].id)
}
Expand All @@ -84,7 +85,9 @@ export const featureManagementLogic = kea<featureManagementLogicType>([
}),
urlToAction(({ actions, values }) => ({
'/features/:id': ({ id }) => {
if (id && String(values.activeFeatureId) !== id && id !== 'new') {
console.log('urlToAction', { id })

if (id && String(values.activeFeatureId) !== id) {
actions.setActiveFeatureId(id)
}
},
Expand Down
Loading