Skip to content

Commit

Permalink
feat(bi): Added foundations of insight variables (#25146)
Browse files Browse the repository at this point in the history
Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
  • Loading branch information
Gilbert09 and github-actions[bot] authored Sep 25, 2024
1 parent 6f9ea30 commit 4ebdea6
Show file tree
Hide file tree
Showing 19 changed files with 594 additions and 8 deletions.
15 changes: 15 additions & 0 deletions frontend/src/lib/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { stringifiedFingerprint } from 'scenes/error-tracking/utils'
import { SavedSessionRecordingPlaylistsResult } from 'scenes/session-recordings/saved-playlists/savedSessionRecordingPlaylistsLogic'

import { getCurrentExporterData } from '~/exporter/exporterViewLogic'
import { Variable } from '~/queries/nodes/DataVisualization/types'
import {
AlertType,
AlertTypeWrite,
Expand Down Expand Up @@ -824,6 +825,11 @@ class ApiRequest {
return this.externalDataSchemas(teamId).addPathComponent(schemaId)
}

// Insight Variables
public insightVariables(teamId?: TeamType['id']): ApiRequest {
return this.projectsDetail(teamId).addPathComponent('insight_variables')
}

// ActivityLog
public activity_log(teamId?: TeamType['id']): ApiRequest {
return this.projectsDetail(teamId).addPathComponent('activity_log')
Expand Down Expand Up @@ -2205,6 +2211,15 @@ const api = {
},
},

insightVariables: {
async list(options?: ApiMethodOptions | undefined): Promise<PaginatedResponse<Variable>> {
return await new ApiRequest().insightVariables().get(options)
},
async create(data: Partial<any>): Promise<Variable> {
return await new ApiRequest().insightVariables().create({ data })
},
},

subscriptions: {
async get(subscriptionId: SubscriptionType['id']): Promise<SubscriptionType> {
return await new ApiRequest().subscription(subscriptionId).get()
Expand Down
1 change: 1 addition & 0 deletions frontend/src/lib/constants.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,7 @@ export const FEATURE_FLAGS = {
WEB_ANALYTICS_CONVERSION_GOALS: 'web-analytics-conversion-goals', // owner: @robbie-c
WEB_ANALYTICS_LAST_CLICK: 'web-analytics-last-click', // owner: @robbie-c
HEDGEHOG_SKIN_SPIDERHOG: 'hedgehog-skin-spiderhog', // owner: @benjackwhite
INSIGHT_VARIABLES: 'insight_variables', // owner: @Gilbert09 #team-data-warehouse
WEB_EXPERIMENTS: 'web-experiments', // owner: @team-feature-success
BIGQUERY_DWH: 'bigquery-dwh', // owner: @Gilbert09 #team-data-warehouse
REPLAY_DEFAULT_SORT_ORDER_EXPERIMENT: 'replay-order-by-experiment', // owner: #team-replay
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@ export const conditionalFormattingLogic = kea<conditionalFormattingLogicType>([
path(['queries', 'nodes', 'DataVisualization', 'Components', 'conditionalFormattingLogic']),
props({ rule: { id: '' }, key: '' } as ConditionalFormattingLogicProps),
connect({
values: [dataVisualizationLogic, ['query']],
actions: [dataVisualizationLogic, ['setQuery', 'updateConditionalFormattingRule']],
actions: [dataVisualizationLogic, ['updateConditionalFormattingRule']],
}),
actions({
selectColumn: (columnName: string) => ({ columnName }),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import {
LemonButton,
LemonInput,
LemonInputSelect,
LemonModal,
LemonSegmentedButton,
LemonSelect,
} from '@posthog/lemon-ui'
import { useActions, useValues } from 'kea'
import { LemonField } from 'lib/lemon-ui/LemonField'

import { Variable } from '../../types'
import { addVariableLogic } from './addVariableLogic'

const renderVariableSpecificFields = (
variable: Variable,
updateVariable: (variable: Variable) => void
): JSX.Element => {
if (variable.type === 'String') {
return (
<LemonField.Pure label="Default value" className="gap-1">
<LemonInput
placeholder="Default value"
value={variable.default_value}
onChange={(value) => updateVariable({ ...variable, default_value: value })}
/>
</LemonField.Pure>
)
}

if (variable.type === 'Number') {
return (
<LemonField.Pure label="Default value" className="gap-1">
<LemonInput
placeholder="Default value"
type="number"
value={variable.default_value}
onChange={(value) => updateVariable({ ...variable, default_value: value ?? 0 })}
/>
</LemonField.Pure>
)
}

if (variable.type === 'Boolean') {
return (
<LemonField.Pure label="Default value" className="gap-1">
<LemonSegmentedButton
className="w-full"
value={variable.default_value ? 'true' : 'false'}
onChange={(value) => updateVariable({ ...variable, default_value: value === 'true' })}
options={[
{
value: 'true',
label: 'true',
},
{
value: 'false',
label: 'false',
},
]}
/>
</LemonField.Pure>
)
}

if (variable.type === 'List') {
return (
<>
<LemonField.Pure label="Values" className="gap-1">
<LemonInputSelect
value={variable.values}
onChange={(value) => updateVariable({ ...variable, values: value })}
placeholder="Options..."
mode="multiple"
allowCustomValues={true}
options={[]}
/>
</LemonField.Pure>
<LemonField.Pure label="Default value" className="gap-1">
<LemonSelect
className="w-full"
placeholder="Select default value"
value={variable.default_value}
options={variable.values.map((n) => ({ label: n, value: n }))}
onChange={(value) => updateVariable({ ...variable, default_value: value ?? '' })}
allowClear
dropdownMaxContentWidth
/>
</LemonField.Pure>
</>
)
}

throw new Error(`Unsupported variable type: ${(variable as Variable).type}`)
}

export const NewVariableModal = (): JSX.Element => {
const { closeModal, updateVariable, save } = useActions(addVariableLogic)
const { isModalOpen, variable } = useValues(addVariableLogic)

return (
<LemonModal
title={`New ${variable.type} variable`}
isOpen={isModalOpen}
onClose={closeModal}
maxWidth="30rem"
footer={
<div className="flex flex-1 justify-end gap-2">
<LemonButton type="secondary" onClick={closeModal}>
Close
</LemonButton>
<LemonButton type="primary" onClick={() => save()}>
Save
</LemonButton>
</div>
}
>
<div className="gap-4 flex flex-col">
<LemonField.Pure label="Name" className="gap-1">
<LemonInput
placeholder="Name"
value={variable.name}
onChange={(value) => updateVariable({ ...variable, name: value })}
/>
</LemonField.Pure>
{renderVariableSpecificFields(variable, updateVariable)}
</div>
</LemonModal>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { IconPlus } from '@posthog/icons'
import { LemonButton, LemonMenu } from '@posthog/lemon-ui'
import { useActions, useValues } from 'kea'
import { FEATURE_FLAGS } from 'lib/constants'
import { featureFlagLogic } from 'lib/logic/featureFlagLogic'

import { dataVisualizationLogic } from '../../dataVisualizationLogic'
import { addVariableLogic } from './addVariableLogic'
import { NewVariableModal } from './NewVariableModal'
import { variablesLogic } from './variablesLogic'

export const Variables = (): JSX.Element => {
const { dataVisualizationProps, showEditingUI } = useValues(dataVisualizationLogic)

const { featureFlags } = useValues(featureFlagLogic)
const { openModal } = useActions(addVariableLogic)

const builtVariablesLogic = variablesLogic({ key: dataVisualizationProps.key })
const { variables, variablesLoading, variablesForInsight } = useValues(builtVariablesLogic)
const { addVariable } = useActions(builtVariablesLogic)

if (!featureFlags[FEATURE_FLAGS.INSIGHT_VARIABLES]) {
return <></>
}

return (
<>
<div className="flex gap-4 justify-between flex-wrap px-px">
{showEditingUI && (
<LemonMenu
items={[
{
title: 'New variable',
items: [
{
label: 'String',
onClick: () => openModal('String'),
},
{
label: 'Number',
onClick: () => openModal('Number'),
},
{
label: 'Boolean',
onClick: () => openModal('Boolean'),
},
{
label: 'List',
onClick: () => openModal('List'),
},
],
},
{
label: 'Existing variable',
items: variablesLoading
? [
{
label: 'Loading...',
onClick: () => {},
},
]
: variables.map((n) => ({
label: n.name,
onClick: () => addVariable(n.id),
})),
},
]}
>
<LemonButton type="secondary" sideIcon={<IconPlus />}>
Add variable
</LemonButton>
</LemonMenu>
)}
{variablesForInsight.map((n) => (
<div key={n.id}>{n.name}</div>
))}
</div>
<NewVariableModal />
</>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { actions, kea, path, reducers } from 'kea'
import { loaders } from 'kea-loaders'
import api from 'lib/api'

import { BooleanVariable, ListVariable, NumberVariable, StringVariable, Variable, VariableType } from '../../types'
import type { addVariableLogicType } from './addVariableLogicType'

const DEFAULT_VARIABLE: StringVariable = {
id: '',
type: 'String',
name: '',
default_value: '',
}

export const addVariableLogic = kea<addVariableLogicType>([
path(['queries', 'nodes', 'DataVisualization', 'Components', 'Variables', 'variableLogic']),
actions({
openModal: (variableType: VariableType) => ({ variableType }),
closeModal: true,
updateVariable: (variable: Variable) => ({ variable }),
}),
reducers({
variableType: [
'string' as VariableType,
{
openModal: (_, { variableType }) => variableType,
},
],
isModalOpen: [
false as boolean,
{
openModal: () => true,
closeModal: () => false,
},
],
variable: [
DEFAULT_VARIABLE as Variable,
{
openModal: (_, { variableType }) => {
if (variableType === 'String') {
return {
id: '',
type: 'String',
name: '',
default_value: '',
} as StringVariable
}

if (variableType === 'Number') {
return {
id: '',
type: 'Number',
name: '',
default_value: 0,
} as NumberVariable
}

if (variableType === 'Boolean') {
return {
id: '',
type: 'Boolean',
name: '',
default_value: false,
} as BooleanVariable
}

if (variableType === 'List') {
return {
id: '',
type: 'List',
name: '',
values: [],
default_value: '',
} as ListVariable
}

throw new Error(`Unsupported variable type ${variableType}`)
},
updateVariable: (state, { variable }) => ({ ...state, ...variable }),
closeModal: () => DEFAULT_VARIABLE,
},
],
}),
loaders(({ values }) => ({
savedVariable: [
null as null | Variable,
{
save: async () => {
return await api.insightVariables.create(values.variable)
},
},
],
})),
])
Loading

0 comments on commit 4ebdea6

Please sign in to comment.