mirror of
https://github.com/BillyOutlast/posthog.git
synced 2026-02-04 03:01:23 +01:00
feat(workflows): manual trigger run options (#40906)
Co-authored-by: meikel <meikel@posthog.com>
This commit is contained in:
@@ -163,6 +163,7 @@ export class CdpSourceWebhooksConsumer extends CdpConsumerBase {
|
||||
query,
|
||||
stringBody: req.rawBody ?? '',
|
||||
},
|
||||
variables: req.body.$variables || {},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -71,6 +71,7 @@ export class HogFunctionHandler implements ActionHandler {
|
||||
{
|
||||
event: invocation.state.event,
|
||||
person: invocation.person,
|
||||
variables: invocation.state.variables,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ import { HogExecutorService } from '../hog-executor.service'
|
||||
import { HogFunctionTemplateManagerService } from '../managers/hog-function-template-manager.service'
|
||||
import { RecipientsManagerService } from '../managers/recipients-manager.service'
|
||||
import { RecipientPreferencesService } from '../messaging/recipient-preferences.service'
|
||||
import { HogFlowExecutorService } from './hogflow-executor.service'
|
||||
import { HogFlowExecutorService, createHogFlowInvocation } from './hogflow-executor.service'
|
||||
import { HogFlowFunctionsService } from './hogflow-functions.service'
|
||||
|
||||
// Mock before importing fetch
|
||||
@@ -1075,4 +1075,60 @@ describe('Hogflow Executor', () => {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('variable merging', () => {
|
||||
it('merges default and provided variables correctly', () => {
|
||||
const hogFlow: HogFlow = new FixtureHogFlowBuilder()
|
||||
.withWorkflow({
|
||||
actions: {
|
||||
trigger: {
|
||||
type: 'trigger',
|
||||
config: {
|
||||
type: 'event',
|
||||
filters: {},
|
||||
},
|
||||
},
|
||||
|
||||
exit: {
|
||||
type: 'exit',
|
||||
config: {},
|
||||
},
|
||||
},
|
||||
edges: [{ from: 'trigger', to: 'exit', type: 'continue' }],
|
||||
})
|
||||
.build()
|
||||
|
||||
// Set variables directly with required fields
|
||||
hogFlow.variables = [
|
||||
{ key: 'foo', default: 'bar', type: 'string', label: 'foo' },
|
||||
{ key: 'baz', default: 123, type: 'number', label: 'baz' },
|
||||
{ key: 'overrideMe', default: 'defaultValue', type: 'string', label: 'overrideMe' },
|
||||
]
|
||||
|
||||
const globals = {
|
||||
event: {
|
||||
event: 'test',
|
||||
properties: {},
|
||||
url: '',
|
||||
distinct_id: '',
|
||||
timestamp: '',
|
||||
uuid: '',
|
||||
elements_chain: '',
|
||||
},
|
||||
project: { id: 1, name: 'Test Project', url: '' },
|
||||
person: { id: 'person_id', name: '', properties: {}, url: '' },
|
||||
variables: {
|
||||
overrideMe: 'customValue',
|
||||
extra: 'shouldBeIncluded',
|
||||
},
|
||||
}
|
||||
const invocation = createHogFlowInvocation(globals, hogFlow, {} as any)
|
||||
expect(invocation.state.variables).toEqual({
|
||||
foo: 'bar',
|
||||
baz: 123,
|
||||
overrideMe: 'customValue',
|
||||
extra: 'shouldBeIncluded',
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -37,21 +37,27 @@ export function createHogFlowInvocation(
|
||||
hogFlow: HogFlow,
|
||||
filterGlobals: HogFunctionFilterGlobals
|
||||
): CyclotronJobInvocationHogFlow {
|
||||
// Build default variables from hogFlow, then merge in any provided in globals.variables
|
||||
const defaultVariables =
|
||||
hogFlow.variables?.reduce(
|
||||
(acc, variable) => {
|
||||
acc[variable.key] = variable.default || null
|
||||
return acc
|
||||
},
|
||||
{} as Record<string, any>
|
||||
) || {}
|
||||
|
||||
const mergedVariables = {
|
||||
...defaultVariables,
|
||||
...(globals.variables || {}),
|
||||
}
|
||||
|
||||
return {
|
||||
id: new UUIDT().toString(),
|
||||
state: {
|
||||
event: globals.event,
|
||||
actionStepCount: 0,
|
||||
variables: {
|
||||
// Spread in any existing variables from hogflow so the default values are in place
|
||||
...hogFlow.variables?.reduce(
|
||||
(acc, variable) => {
|
||||
acc[variable.key] = variable.default || null
|
||||
return acc
|
||||
},
|
||||
{} as Record<string, any>
|
||||
),
|
||||
},
|
||||
variables: mergedVariables,
|
||||
},
|
||||
teamId: hogFlow.team_id,
|
||||
functionId: hogFlow.id, // TODO: Include version?
|
||||
|
||||
@@ -34,7 +34,7 @@ export const getPersonDisplayName = (team: Team, distinctId: string, properties:
|
||||
const customIdentifier: string =
|
||||
typeof propertyIdentifier !== 'string' ? JSON.stringify(propertyIdentifier) : propertyIdentifier
|
||||
|
||||
return (customIdentifier || distinctId)?.trim()
|
||||
return (customIdentifier || String(distinctId))?.trim()
|
||||
}
|
||||
|
||||
// that we can keep to as a contract
|
||||
|
||||
@@ -1,21 +1,41 @@
|
||||
import { useActions, useValues } from 'kea'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
|
||||
import { IconButton } from '@posthog/icons'
|
||||
import { LemonButton } from '@posthog/lemon-ui'
|
||||
import { LemonButton, LemonDivider } from '@posthog/lemon-ui'
|
||||
|
||||
import { SceneTitleSection } from '~/layout/scenes/components/SceneTitleSection'
|
||||
|
||||
import { HogFlowManualTriggerButton } from './hogflows/HogFlowManualTriggerButton'
|
||||
import { workflowLogic } from './workflowLogic'
|
||||
import { WorkflowSceneLogicProps } from './workflowSceneLogic'
|
||||
|
||||
export const WorkflowSceneHeader = (props: WorkflowSceneLogicProps = {}): JSX.Element => {
|
||||
const logic = workflowLogic(props)
|
||||
const { workflow, workflowChanged, isWorkflowSubmitting, workflowLoading, workflowHasErrors } = useValues(logic)
|
||||
const { saveWorkflowPartial, submitWorkflow, discardChanges, setWorkflowValue, triggerManualWorkflow } =
|
||||
useActions(logic)
|
||||
const { saveWorkflowPartial, submitWorkflow, discardChanges, setWorkflowValue } = useActions(logic)
|
||||
|
||||
const isSavedWorkflow = props.id && props.id !== 'new'
|
||||
const isManualWorkflow = workflow?.trigger?.type === 'manual'
|
||||
const [displayStatus, setDisplayStatus] = useState(workflow?.status)
|
||||
const [isTransitioning, setIsTransitioning] = useState(false)
|
||||
const prevStatusRef = useRef(workflow?.status)
|
||||
|
||||
useEffect(() => {
|
||||
// Only transition if status actually changed (not on initial mount)
|
||||
if (workflow?.status !== displayStatus && prevStatusRef.current !== undefined) {
|
||||
setIsTransitioning(true)
|
||||
const timer = setTimeout(() => {
|
||||
setDisplayStatus(workflow?.status)
|
||||
setIsTransitioning(false)
|
||||
}, 150)
|
||||
prevStatusRef.current = workflow?.status
|
||||
return () => clearTimeout(timer)
|
||||
} else if (workflow?.status !== displayStatus) {
|
||||
// On initial mount, just set it without transition
|
||||
setDisplayStatus(workflow?.status)
|
||||
prevStatusRef.current = workflow?.status
|
||||
}
|
||||
}, [workflow?.status, displayStatus])
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -26,52 +46,45 @@ export const WorkflowSceneHeader = (props: WorkflowSceneLogicProps = {}): JSX.El
|
||||
canEdit
|
||||
onNameChange={(name) => setWorkflowValue('name', name)}
|
||||
onDescriptionChange={(description) => setWorkflowValue('description', description)}
|
||||
isLoading={workflowLoading}
|
||||
isLoading={workflowLoading && !workflow}
|
||||
renameDebounceMs={200}
|
||||
actions={
|
||||
<>
|
||||
{isManualWorkflow && (
|
||||
<LemonButton
|
||||
type="primary"
|
||||
disabledReason={workflow?.status !== 'active' && 'Must enable workflow to use trigger'}
|
||||
icon={<IconButton />}
|
||||
tooltip="Triggers workflow immediately"
|
||||
onClick={triggerManualWorkflow}
|
||||
>
|
||||
Trigger
|
||||
</LemonButton>
|
||||
)}
|
||||
{isManualWorkflow && <HogFlowManualTriggerButton {...props} />}
|
||||
{isSavedWorkflow && (
|
||||
<>
|
||||
<LemonButton
|
||||
type="primary"
|
||||
type={displayStatus === 'active' ? 'primary' : 'secondary'}
|
||||
onClick={() =>
|
||||
saveWorkflowPartial({
|
||||
status: workflow?.status === 'draft' ? 'active' : 'draft',
|
||||
})
|
||||
}
|
||||
size="small"
|
||||
loading={workflowLoading}
|
||||
disabledReason={workflowChanged ? 'Save changes first' : undefined}
|
||||
className="transition-colors duration-300 ease-in-out"
|
||||
>
|
||||
{workflow?.status === 'draft' ? 'Enable' : 'Disable'}
|
||||
<span
|
||||
className={`inline-block transition-opacity duration-300 ease-in-out ${
|
||||
isTransitioning ? 'opacity-0' : 'opacity-100'
|
||||
}`}
|
||||
>
|
||||
{displayStatus === 'draft' ? 'Enable' : 'Disable'}
|
||||
</span>
|
||||
</LemonButton>
|
||||
<LemonDivider vertical />
|
||||
</>
|
||||
)}
|
||||
|
||||
{isSavedWorkflow && workflowChanged && (
|
||||
<>
|
||||
<LemonButton
|
||||
data-attr="discard-workflow-changes"
|
||||
type="secondary"
|
||||
onClick={() => discardChanges()}
|
||||
size="small"
|
||||
>
|
||||
Discard changes
|
||||
</LemonButton>
|
||||
</>
|
||||
{workflowChanged && (
|
||||
<LemonButton
|
||||
data-attr="discard-workflow-changes"
|
||||
type="secondary"
|
||||
onClick={() => discardChanges()}
|
||||
size="small"
|
||||
>
|
||||
Clear changes
|
||||
</LemonButton>
|
||||
)}
|
||||
|
||||
<LemonButton
|
||||
type="primary"
|
||||
size="small"
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
import { useActions, useValues } from 'kea'
|
||||
|
||||
import { IconChevronDown } from '@posthog/icons'
|
||||
import { LemonButton, LemonInput, Popover } from '@posthog/lemon-ui'
|
||||
|
||||
import { LemonField } from 'lib/lemon-ui/LemonField'
|
||||
|
||||
import { CyclotronJobInputSchemaType } from '~/types'
|
||||
|
||||
import { WorkflowLogicProps, workflowLogic } from '../workflowLogic'
|
||||
import { hogFlowManualTriggerButtonLogic } from './HogFlowManualTriggerButtonLogic'
|
||||
|
||||
const VariableInputsPopover = ({
|
||||
setPopoverVisible,
|
||||
props,
|
||||
}: {
|
||||
setPopoverVisible: (visible: boolean) => void
|
||||
props: WorkflowLogicProps
|
||||
}): JSX.Element => {
|
||||
const logic = hogFlowManualTriggerButtonLogic(props)
|
||||
const { workflow, variableValues, inputs } = useValues(logic)
|
||||
const { triggerManualWorkflow } = useActions(workflowLogic(props))
|
||||
const { setInput, clearInputs } = useActions(logic)
|
||||
|
||||
if (!workflow?.variables || workflow.variables.length === 0) {
|
||||
return (
|
||||
<div className="flex flex-col gap-3 p-3 min-w-80">
|
||||
<div className="pb-2 border-b">
|
||||
<h3 className="text-sm font-semibold">Configure variables</h3>
|
||||
</div>
|
||||
<div className="text-muted text-sm">No variables to configure.</div>
|
||||
<div className="flex justify-end border-t pt-3">
|
||||
<LemonButton
|
||||
type="primary"
|
||||
status="alt"
|
||||
onClick={() => {
|
||||
triggerManualWorkflow({})
|
||||
setPopoverVisible(false)
|
||||
clearInputs()
|
||||
}}
|
||||
data-attr="run-workflow-btn"
|
||||
>
|
||||
Run workflow
|
||||
</LemonButton>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4 p-3 min-w-80 max-w-96">
|
||||
<div className="pb-2 border-b">
|
||||
<h3 className="text-sm font-semibold">Configure variables</h3>
|
||||
<p className="text-xs text-muted mt-0.5">Set variable values or leave empty to use defaults</p>
|
||||
</div>
|
||||
<div className="flex flex-col gap-3">
|
||||
{workflow.variables.map((variable: CyclotronJobInputSchemaType) => {
|
||||
const inputValue = inputs[variable.key]
|
||||
const displayValue = inputValue ?? ''
|
||||
const hasDefault = variable.default !== undefined && variable.default !== ''
|
||||
|
||||
return (
|
||||
<LemonField.Pure key={variable.key} label={variable.label || variable.key}>
|
||||
{variable.type === 'number' ? (
|
||||
<LemonInput
|
||||
type="number"
|
||||
value={displayValue === '' ? undefined : Number(displayValue)}
|
||||
placeholder={hasDefault ? `Default: ${String(variable.default)}` : 'Enter value'}
|
||||
onChange={(value: number | undefined) => {
|
||||
setInput(variable.key, value !== undefined ? String(value) : '')
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<LemonInput
|
||||
type="text"
|
||||
value={displayValue}
|
||||
placeholder={hasDefault ? `Default: ${String(variable.default)}` : 'Enter value'}
|
||||
onChange={(value: string) => {
|
||||
setInput(variable.key, value)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</LemonField.Pure>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end border-t pt-3">
|
||||
<LemonButton
|
||||
type="primary"
|
||||
status="alt"
|
||||
onClick={() => {
|
||||
triggerManualWorkflow(variableValues)
|
||||
setPopoverVisible(false)
|
||||
clearInputs()
|
||||
}}
|
||||
data-attr="run-workflow-btn"
|
||||
>
|
||||
Run workflow
|
||||
</LemonButton>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const HogFlowManualTriggerButton = (props: WorkflowLogicProps = {}): JSX.Element => {
|
||||
const logic = hogFlowManualTriggerButtonLogic(props)
|
||||
const { workflow, workflowChanged } = useValues(workflowLogic(props))
|
||||
const { popoverVisible } = useValues(logic)
|
||||
const { setPopoverVisible } = useActions(logic)
|
||||
|
||||
const triggerButton = (
|
||||
<LemonButton
|
||||
type="primary"
|
||||
size="small"
|
||||
disabledReason={
|
||||
workflow?.status !== 'active'
|
||||
? 'Must enable workflow to use trigger'
|
||||
: workflowChanged
|
||||
? 'Save changes first'
|
||||
: undefined
|
||||
}
|
||||
sideIcon={<IconChevronDown className={`transition-transform ${popoverVisible ? 'rotate-180' : ''}`} />}
|
||||
tooltip="Triggers workflow immediately"
|
||||
onClick={() => setPopoverVisible(!popoverVisible)}
|
||||
>
|
||||
Trigger
|
||||
</LemonButton>
|
||||
)
|
||||
|
||||
return (
|
||||
<Popover
|
||||
visible={popoverVisible}
|
||||
placement="bottom-start"
|
||||
onClickOutside={() => setPopoverVisible(false)}
|
||||
overlay={<VariableInputsPopover setPopoverVisible={setPopoverVisible} props={props} />}
|
||||
>
|
||||
{triggerButton}
|
||||
</Popover>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
import { actions, connect, kea, path, props, reducers, selectors } from 'kea'
|
||||
|
||||
import { CyclotronJobInputSchemaType } from '~/types'
|
||||
|
||||
import { WorkflowLogicProps, workflowLogic } from '../workflowLogic'
|
||||
import type { hogFlowManualTriggerButtonLogicType } from './HogFlowManualTriggerButtonLogicType'
|
||||
|
||||
const parseValue = (value: string, variableType: string): any => {
|
||||
if (value === '') {
|
||||
return undefined
|
||||
}
|
||||
switch (variableType) {
|
||||
case 'number':
|
||||
const num = Number(value)
|
||||
return isNaN(num) ? value : num
|
||||
case 'boolean':
|
||||
if (value.toLowerCase() === 'true') {
|
||||
return true
|
||||
}
|
||||
if (value.toLowerCase() === 'false') {
|
||||
return false
|
||||
}
|
||||
return value
|
||||
default:
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
export const hogFlowManualTriggerButtonLogic = kea<hogFlowManualTriggerButtonLogicType>([
|
||||
path(['products', 'workflows', 'frontend', 'Workflows', 'hogflows', 'hogFlowManualTriggerButtonLogic']),
|
||||
props({} as WorkflowLogicProps),
|
||||
connect((props: WorkflowLogicProps) => ({
|
||||
values: [workflowLogic(props), ['workflow']],
|
||||
actions: [workflowLogic(props), ['triggerManualWorkflow']],
|
||||
})),
|
||||
actions({
|
||||
setInput: (key: string, value: string) => ({ key, value }),
|
||||
setPopoverVisible: (visible: boolean) => ({ visible }),
|
||||
clearInputs: () => ({}),
|
||||
}),
|
||||
reducers({
|
||||
inputs: [
|
||||
{} as Record<string, string>,
|
||||
{
|
||||
setInput: (state, { key, value }) => ({ ...state, [key]: value }),
|
||||
clearInputs: () => ({}),
|
||||
},
|
||||
],
|
||||
popoverVisible: [
|
||||
false,
|
||||
{
|
||||
setPopoverVisible: (_, { visible }) => visible,
|
||||
},
|
||||
],
|
||||
}),
|
||||
selectors({
|
||||
variableValues: [
|
||||
(s) => [s.inputs, s.workflow],
|
||||
(inputs: Record<string, string>, workflow: any): Record<string, any> => {
|
||||
if (!workflow?.variables) {
|
||||
return {}
|
||||
}
|
||||
return Object.fromEntries(
|
||||
workflow.variables.map((v: CyclotronJobInputSchemaType) => {
|
||||
const inputValue = inputs[v.key]
|
||||
if (inputValue !== undefined && inputValue !== '') {
|
||||
// Parse the string input to the correct type
|
||||
return [v.key, parseValue(inputValue, v.type)]
|
||||
}
|
||||
// Use default value as-is (preserve original type)
|
||||
return [v.key, v.default]
|
||||
})
|
||||
)
|
||||
},
|
||||
],
|
||||
}),
|
||||
])
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useActions, useValues } from 'kea'
|
||||
|
||||
import { IconCode, IconPlus, IconTrash } from '@posthog/icons'
|
||||
import { IconCode, IconCopy, IconPlus, IconTrash } from '@posthog/icons'
|
||||
import { LemonButton, LemonDialog, LemonInput, LemonLabel, Tooltip, lemonToast } from '@posthog/lemon-ui'
|
||||
|
||||
import { LemonField } from 'lib/lemon-ui/LemonField/LemonField'
|
||||
@@ -24,7 +24,9 @@ export function HogFlowEditorPanelVariables(): JSX.Element | null {
|
||||
|
||||
const editVariableKey = (idx: number, key: string): void => {
|
||||
const updatedVariables = [...(workflow?.variables || [])]
|
||||
updatedVariables[idx].key = key
|
||||
const sanitizedKey = key.replace(/\s+/g, '_')
|
||||
updatedVariables[idx].key = sanitizedKey
|
||||
updatedVariables[idx].label = sanitizedKey
|
||||
setWorkflowInfo({
|
||||
variables: updatedVariables,
|
||||
})
|
||||
@@ -96,14 +98,23 @@ export function HogFlowEditorPanelVariables(): JSX.Element | null {
|
||||
</LemonField.Pure>
|
||||
<LemonField.Pure label="Usage syntax">
|
||||
<Tooltip title={`{{ variables.${variable.key} }}`}>
|
||||
<code
|
||||
className="w-36 py-2 bg-primary-alt-highlight-light rounded-sm text-center text-xs truncate cursor-pointer"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
void navigator.clipboard.writeText(`{{ variables.${variable.key} }}`)
|
||||
lemonToast.success('Copied to clipboard')
|
||||
}}
|
||||
>{`{{ variables.${variable.key} }}`}</code>
|
||||
<span className="group relative">
|
||||
<code className="w-36 py-2 bg-primary-alt-highlight-light rounded-sm text-center text-xs truncate block">
|
||||
{`{{ variables.${variable.key} }}`}
|
||||
</code>
|
||||
<span className="absolute top-0 right-0 z-10 p-px opacity-0 transition-opacity group-hover:opacity-100">
|
||||
<LemonButton
|
||||
size="small"
|
||||
icon={<IconCopy />}
|
||||
className="bg-white/80"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
void navigator.clipboard.writeText(`{{ variables.${variable.key} }}`)
|
||||
lemonToast.success('Copied to clipboard')
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
</span>
|
||||
</Tooltip>
|
||||
</LemonField.Pure>
|
||||
<LemonButton
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useActions, useValues } from 'kea'
|
||||
import { Form } from 'kea-forms'
|
||||
import { useEffect } from 'react'
|
||||
|
||||
import { IconInfo, IconPlay, IconPlayFilled, IconRedo, IconTestTube } from '@posthog/icons'
|
||||
import { IconInfo, IconPlayFilled, IconRedo, IconTestTube } from '@posthog/icons'
|
||||
import {
|
||||
LemonBanner,
|
||||
LemonButton,
|
||||
@@ -72,7 +72,7 @@ export function HogFlowEditorPanelTest(): JSX.Element | null {
|
||||
|
||||
<p>Step through each action in your workflow and see how it behaves.</p>
|
||||
|
||||
<LemonButton type="primary" onClick={() => setSelectedNodeId(TRIGGER_NODE_ID)} icon={<IconPlay />}>
|
||||
<LemonButton type="primary" onClick={() => setSelectedNodeId(TRIGGER_NODE_ID)}>
|
||||
Start testing
|
||||
</LemonButton>
|
||||
</div>
|
||||
@@ -149,7 +149,6 @@ export function HogFlowEditorPanelTest(): JSX.Element | null {
|
||||
type="primary"
|
||||
data-attr="test-workflow-panel-new"
|
||||
onClick={() => submitTestInvocation()}
|
||||
icon={<IconPlay />}
|
||||
loading={isTestInvocationSubmitting}
|
||||
disabledReason={sampleGlobals ? undefined : 'Must load event to run test'}
|
||||
size="small"
|
||||
|
||||
@@ -259,10 +259,9 @@ function StepTriggerConfigurationManual(): JSX.Element {
|
||||
<p className="mb-0">
|
||||
This workflow can be triggered manually via{' '}
|
||||
<Tooltip title="It's up there on the top right ⤴︎">
|
||||
<span className="font-bold cursor-pointer">the trigger button</span>
|
||||
<span className="font-bold cursor-pointer">the trigger button on the top</span>
|
||||
</Tooltip>
|
||||
</p>
|
||||
<IconButton fontSize={24} />
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
|
||||
@@ -123,7 +123,7 @@ export const workflowLogic = kea<workflowLogicType>([
|
||||
// NOTE: This is a wrapper for setWorkflowValues, to get around some weird typegen issues
|
||||
setWorkflowInfo: (workflow: Partial<HogFlow>) => ({ workflow }),
|
||||
saveWorkflowPartial: (workflow: Partial<HogFlow>) => ({ workflow }),
|
||||
triggerManualWorkflow: true,
|
||||
triggerManualWorkflow: (variables: Record<string, any>) => ({ variables }),
|
||||
discardChanges: true,
|
||||
}),
|
||||
loaders(({ props, values }) => ({
|
||||
@@ -392,7 +392,7 @@ export const workflowLogic = kea<workflowLogicType>([
|
||||
|
||||
actions.setWorkflowValues({ edges: [...newEdges, ...edges] })
|
||||
},
|
||||
triggerManualWorkflow: async () => {
|
||||
triggerManualWorkflow: async ({ variables }) => {
|
||||
if (!values.workflow.id || values.workflow.id === 'new') {
|
||||
lemonToast.error('You need to save the workflow before triggering it manually.')
|
||||
return
|
||||
@@ -409,13 +409,14 @@ export const workflowLogic = kea<workflowLogicType>([
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
user_id: values.user?.id,
|
||||
user_id: String(values.user?.id),
|
||||
$variables: variables,
|
||||
}),
|
||||
credentials: 'omit',
|
||||
})
|
||||
} catch (e) {
|
||||
lemonToast.error('Error triggering workflow: ' + (e as Error).message)
|
||||
// return
|
||||
return
|
||||
}
|
||||
|
||||
lemonToast.success('Workflow triggered', {
|
||||
|
||||
@@ -142,7 +142,7 @@ export function WorkflowsScene(): JSX.Element {
|
||||
key: 'workflows',
|
||||
content: (
|
||||
<>
|
||||
<p>Create automated workflows workflows triggered by events</p>
|
||||
<p>Create and manage your workflows</p>
|
||||
<WorkflowsTable />
|
||||
</>
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user