Skip to content

Commit

Permalink
feat(cdp): improve testing interface (#27054)
Browse files Browse the repository at this point in the history
  • Loading branch information
mariusandra authored Dec 19, 2024
1 parent ca46d2d commit c434dab
Show file tree
Hide file tree
Showing 7 changed files with 171 additions and 55 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
19 changes: 12 additions & 7 deletions frontend/src/lib/lemon-ui/LemonButton/More.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ import { IconEllipsis } from '@posthog/icons'

import { PopoverProps } from '../Popover/Popover'
import { LemonButtonWithDropdown } from '.'
import { LemonButtonProps } from './LemonButton'
import { LemonButtonDropdown, LemonButtonProps } from './LemonButton'

export type MoreProps = Partial<Pick<PopoverProps, 'overlay' | 'placement'>> & LemonButtonProps
export type MoreProps = Partial<Pick<PopoverProps, 'overlay' | 'placement'>> &
LemonButtonProps & { dropdown?: Partial<LemonButtonDropdown> }

export function More({
overlay,
dropdown,
'data-attr': dataAttr,
placement = 'bottom-end',
...buttonProps
Expand All @@ -17,11 +19,14 @@ export function More({
aria-label="more"
data-attr={dataAttr ?? 'more-button'}
icon={<IconEllipsis />}
dropdown={{
placement: placement,
actionable: true,
overlay,
}}
dropdown={
{
placement: placement,
actionable: true,
...dropdown,
overlay,
} as LemonButtonDropdown
}
size="small"
{...buttonProps}
disabled={!overlay}
Expand Down
168 changes: 127 additions & 41 deletions frontend/src/scenes/pipeline/hogfunctions/HogFunctionTest.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@
import { TZLabel } from '@posthog/apps-common'
import { IconInfo, IconX } from '@posthog/icons'
import { LemonButton, LemonLabel, LemonSwitch, LemonTable, LemonTag, Tooltip } from '@posthog/lemon-ui'
import {
LemonButton,
LemonDivider,
LemonLabel,
LemonSwitch,
LemonTable,
LemonTag,
Spinner,
Tooltip,
} from '@posthog/lemon-ui'
import clsx from 'clsx'
import { useActions, useValues } from 'kea'
import { Form } from 'kea-forms'
import { More } from 'lib/lemon-ui/LemonButton/More'
import { LemonField } from 'lib/lemon-ui/LemonField'
import { CodeEditorResizeable } from 'lib/monaco/CodeEditorResizable'

Expand Down Expand Up @@ -62,11 +72,25 @@ export function HogFunctionTestPlaceholder({
}

export function HogFunctionTest(props: HogFunctionTestLogicProps): JSX.Element {
const { isTestInvocationSubmitting, testResult, expanded, sampleGlobalsLoading, sampleGlobalsError, type } =
useValues(hogFunctionTestLogic(props))
const { submitTestInvocation, setTestResult, toggleExpanded, loadSampleGlobals } = useActions(
hogFunctionTestLogic(props)
)
const {
isTestInvocationSubmitting,
testResult,
expanded,
sampleGlobalsLoading,
sampleGlobalsError,
type,
savedGlobals,
testInvocation,
} = useValues(hogFunctionTestLogic(props))
const {
submitTestInvocation,
setTestResult,
toggleExpanded,
loadSampleGlobals,
deleteSavedGlobals,
setSampleGlobals,
saveGlobals,
} = useActions(hogFunctionTestLogic(props))

return (
<Form logic={hogFunctionTestLogic} props={props} formKey="testInvocation" enableFormOnSubmit>
Expand All @@ -75,7 +99,10 @@ export function HogFunctionTest(props: HogFunctionTestLogicProps): JSX.Element {
>
<div className="flex items-center gap-2 justify-end">
<div className="flex-1 space-y-2">
<h2 className="mb-0">Testing</h2>
<h2 className="mb-0 flex gap-2 items-center">
<span>Testing</span>
{sampleGlobalsLoading ? <Spinner /> : null}
</h2>
{!expanded &&
(type === 'email' ? (
<p>Click here to test the provider with a sample e-mail</p>
Expand All @@ -87,7 +114,7 @@ export function HogFunctionTest(props: HogFunctionTestLogicProps): JSX.Element {
</div>

{!expanded ? (
<LemonButton type="secondary" onClick={() => toggleExpanded()}>
<LemonButton data-attr="expand-hog-testing" type="secondary" onClick={() => toggleExpanded()}>
Start testing
</LemonButton>
) : (
Expand All @@ -97,46 +124,100 @@ export function HogFunctionTest(props: HogFunctionTestLogicProps): JSX.Element {
type="primary"
onClick={() => setTestResult(null)}
loading={isTestInvocationSubmitting}
data-attr="clear-hog-test-result"
>
Clear test result
</LemonButton>
) : (
<>
<LemonButton
type="secondary"
onClick={loadSampleGlobals}
loading={sampleGlobalsLoading}
tooltip="Find the last event matching filters, and use it to populate the globals below."
>
Refresh globals
</LemonButton>
<LemonField name="mock_async_functions">
{({ value, onChange }) => (
<LemonSwitch
bordered
onChange={onChange}
checked={value}
label={
<Tooltip
title={
<>
When selected, async functions such as `fetch` will not
actually be called but instead will be mocked out with
the fetch content logged instead
</>
}
<More
dropdown={{ closeOnClickInside: false }}
overlay={
<>
<LemonField name="mock_async_functions">
{({ value, onChange }) => (
<LemonSwitch
onChange={(v) => onChange(!v)}
checked={!value}
data-attr="toggle-hog-test-mocking"
className="px-2 py-1"
label={
<Tooltip
title={
<>
When disabled, async functions such as
`fetch` will not be called. Instead they
will be mocked out and logged.
</>
}
>
<span className="flex gap-2">
Make real HTTP requests
<IconInfo className="text-lg" />
</span>
</Tooltip>
}
/>
)}
</LemonField>
<LemonDivider />
<LemonButton
fullWidth
onClick={loadSampleGlobals}
loading={sampleGlobalsLoading}
tooltip="Find the last event matching filters, and use it to populate the globals below."
>
Fetch new event
</LemonButton>
<LemonDivider />
{savedGlobals.map(({ name, globals }, index) => (
<div className="flex w-full justify-between" key={index}>
<LemonButton
data-attr="open-hog-test-data"
key={index}
onClick={() => setSampleGlobals(globals)}
fullWidth
className="flex-1"
>
{name}
</LemonButton>
<LemonButton
data-attr="delete-hog-test-data"
size="small"
icon={<IconX />}
onClick={() => deleteSavedGlobals(index)}
tooltip="Delete saved test data"
/>
</div>
))}
{testInvocation.globals && (
<LemonButton
fullWidth
data-attr="save-hog-test-data"
onClick={() => {
const name = prompt('Name this test data')
if (name) {
saveGlobals(name, JSON.parse(testInvocation.globals))
}
}}
disabledReason={(() => {
try {
JSON.parse(testInvocation.globals)
} catch (e) {
return 'Invalid globals JSON'
}
return undefined
})()}
>
<span className="flex gap-2">
Mock out HTTP requests
<IconInfo className="text-lg" />
</span>
</Tooltip>
}
/>
)}
</LemonField>
Save test data
</LemonButton>
)}
</>
}
/>
<LemonButton
type="primary"
data-attr="test-hog-function"
onClick={submitTestInvocation}
loading={isTestInvocationSubmitting}
>
Expand All @@ -145,7 +226,12 @@ export function HogFunctionTest(props: HogFunctionTestLogicProps): JSX.Element {
</>
)}

<LemonButton icon={<IconX />} onClick={() => toggleExpanded()} tooltip="Hide testing" />
<LemonButton
data-attr="hide-hog-testing"
icon={<IconX />}
onClick={() => toggleExpanded()}
tooltip="Hide testing"
/>
</>
)}
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { lemonToast } from '@posthog/lemon-ui'
import equal from 'fast-deep-equal'
import { actions, afterMount, connect, kea, key, listeners, path, props, reducers, selectors } from 'kea'
import { actions, afterMount, connect, isBreakpoint, kea, key, listeners, path, props, reducers, selectors } from 'kea'
import { forms } from 'kea-forms'
import { loaders } from 'kea-loaders'
import { beforeUnload, router } from 'kea-router'
Expand Down Expand Up @@ -231,8 +231,15 @@ export const hogFunctionConfigurationLogic = kea<hogFunctionConfigurationLogicTy
setUnsavedConfiguration: (configuration: HogFunctionConfigurationType | null) => ({ configuration }),
persistForUnload: true,
setSampleGlobalsError: (error) => ({ error }),
setSampleGlobals: (sampleGlobals: HogFunctionInvocationGlobals | null) => ({ sampleGlobals }),
}),
reducers(({ props }) => ({
sampleGlobals: [
null as HogFunctionInvocationGlobals | null,
{
setSampleGlobals: (_, { sampleGlobals }) => sampleGlobals,
},
],
showSource: [
// Show source by default for blank templates when creating a new function
!!(!props.id && props.templateId?.startsWith('template-blank-')),
Expand Down Expand Up @@ -440,7 +447,9 @@ export const hogFunctionConfigurationLogic = kea<hogFunctionConfigurationLogicTy
}
return globals
} catch (e: any) {
actions.setSampleGlobalsError(e.message ?? errorMessage)
if (!isBreakpoint(e)) {
actions.setSampleGlobalsError(e.message ?? errorMessage)
}
return values.exampleInvocationGlobals
}
},
Expand Down
21 changes: 18 additions & 3 deletions frontend/src/scenes/pipeline/hogfunctions/hogFunctionTestLogic.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ import { actions, afterMount, connect, kea, key, listeners, path, props, reducer
import { forms } from 'kea-forms'
import api from 'lib/api'
import { tryJsonParse } from 'lib/utils'
import { getCurrentTeamId } from 'lib/utils/getAppContext'

import { groupsModel } from '~/models/groupsModel'
import { LogEntry } from '~/types'
import { HogFunctionInvocationGlobals, LogEntry } from '~/types'

import { hogFunctionConfigurationLogic, sanitizeConfiguration } from './hogFunctionConfigurationLogic'
import type { hogFunctionTestLogicType } from './hogFunctionTestLogicType'
Expand Down Expand Up @@ -45,18 +46,20 @@ export const hogFunctionTestLogic = kea<hogFunctionTestLogicType>([
],
actions: [
hogFunctionConfigurationLogic({ id: props.id }),
['touchConfigurationField', 'loadSampleGlobalsSuccess', 'loadSampleGlobals'],
['touchConfigurationField', 'loadSampleGlobalsSuccess', 'loadSampleGlobals', 'setSampleGlobals'],
],
})),
actions({
setTestResult: (result: HogFunctionTestInvocationResult | null) => ({ result }),
toggleExpanded: (expanded?: boolean) => ({ expanded }),
saveGlobals: (name: string, globals: HogFunctionInvocationGlobals) => ({ name, globals }),
deleteSavedGlobals: (index: number) => ({ index }),
}),
reducers({
expanded: [
false as boolean,
{
toggleExpanded: (_, { expanded }) => (expanded === undefined ? !_ : expanded),
toggleExpanded: (state, { expanded }) => (expanded === undefined ? !state : expanded),
},
],

Expand All @@ -66,11 +69,23 @@ export const hogFunctionTestLogic = kea<hogFunctionTestLogicType>([
setTestResult: (_, { result }) => result,
},
],

savedGlobals: [
[] as { name: string; globals: HogFunctionInvocationGlobals }[],
{ persist: true, prefix: `${getCurrentTeamId()}__` },
{
saveGlobals: (state, { name, globals }) => [...state, { name, globals }],
deleteSavedGlobals: (state, { index }) => state.filter((_, i) => i !== index),
},
],
}),
listeners(({ values, actions }) => ({
loadSampleGlobalsSuccess: () => {
actions.setTestInvocationValue('globals', JSON.stringify(values.sampleGlobals, null, 2))
},
setSampleGlobals: ({ sampleGlobals }) => {
actions.setTestInvocationValue('globals', JSON.stringify(sampleGlobals, null, 2))
},
})),
forms(({ props, actions, values }) => ({
testInvocation: {
Expand Down
1 change: 1 addition & 0 deletions plugin-server/src/cdp/cdp-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ export class CdpApi {
id: team.id,
name: team.name,
url: `${this.hub.SITE_URL ?? 'http://localhost:8000'}/project/${team.id}`,
...globals.project,
},
},
compoundConfiguration,
Expand Down
4 changes: 2 additions & 2 deletions posthog/api/hog_function.py
Original file line number Diff line number Diff line change
Expand Up @@ -360,13 +360,13 @@ def invocations(self, request: Request, *args, **kwargs):
# Remove the team from the config
configuration.pop("team")

globals = serializer.validated_data["globals"]
hog_globals = serializer.validated_data["globals"]
mock_async_functions = serializer.validated_data["mock_async_functions"]

res = create_hog_invocation_test(
team_id=hog_function.team_id,
hog_function_id=hog_function.id,
globals=globals,
globals=hog_globals,
configuration=configuration,
mock_async_functions=mock_async_functions,
)
Expand Down

0 comments on commit c434dab

Please sign in to comment.