33import { useMemo , useState } from 'react'
44import {
55 Button ,
6- ButtonGroup ,
7- ButtonGroupItem ,
86 ChipCombobox ,
97 ChipInput ,
108 type ComboboxOptionGroup ,
@@ -21,28 +19,20 @@ import {
2119import { ArrowLeft , ChevronDown , SquareArrowUpRight , X } from '@sim/emcn/icons'
2220import { toError } from '@sim/utils/errors'
2321import { generateId } from '@sim/utils/id'
24- import { useMutation , useQueryClient } from '@tanstack/react-query'
2522import { findValidationIssue , isValidationError } from '@/lib/api/client/errors'
26- import { requestJson } from '@/lib/api/client/request'
2723import type {
2824 AddWorkflowGroupBodyInput ,
2925 UpdateWorkflowGroupBodyInput ,
3026} from '@/lib/api/contracts/tables'
31- import {
32- putWorkflowNormalizedStateContract ,
33- type WorkflowStateContractInput ,
34- } from '@/lib/api/contracts/workflows'
3527import type {
3628 ColumnDefinition ,
3729 WorkflowGroup ,
3830 WorkflowGroupDependencies ,
39- WorkflowGroupDeploymentMode ,
4031 WorkflowGroupInputMapping ,
4132 WorkflowGroupOutput ,
4233} from '@/lib/table'
4334import { getColumnId } from '@/lib/table/column-keys'
4435import { columnTypeForLeaf , deriveOutputColumnName } from '@/lib/table/column-naming'
45- import { columnTypeById } from '@/lib/table/column-types'
4636import {
4737 type FlattenOutputsBlockInput ,
4838 type FlattenOutputsEdgeInput ,
@@ -58,12 +48,12 @@ import {
5848} from '@/app/workspace/[workspaceId]/tables/[tableId]/components/sidebar-fields'
5949import { PreviewWorkflow } from '@/app/workspace/[workspaceId]/w/components/preview'
6050import { BlockTile } from '@/blocks/block-tile'
51+ import { useDeployedWorkflowState } from '@/hooks/queries/deployments'
6152import {
6253 useAddWorkflowGroup ,
6354 useUpdateColumn ,
6455 useUpdateWorkflowGroup ,
6556} from '@/hooks/queries/tables'
66- import { useWorkflowState , workflowKeys } from '@/hooks/queries/workflows'
6757import type { WorkflowMetadata } from '@/stores/workflows/registry/types'
6858import { InputMappingSection } from './input-mapping-section'
6959import { RunSettingsSection } from './run-settings-section'
@@ -142,25 +132,6 @@ interface BlockOutputGroup {
142132 paths : string [ ]
143133}
144134
145- interface WorkflowStatePayload {
146- blocks : Record <
147- string ,
148- {
149- type : string
150- subBlocks ?: Record < string , { id ?: string ; type ?: string ; value ?: unknown } >
151- } & Record < string , unknown >
152- >
153- edges : unknown [ ]
154- loops : unknown
155- parallels : unknown
156- lastSaved ?: number
157- isDeployed ?: boolean
158- }
159-
160- function tableColumnTypeToInputType ( colType : ColumnDefinition [ 'type' ] | undefined ) : string {
161- return columnTypeById ( colType ) . workflowInputType
162- }
163-
164135/**
165136 * Right-edge sidebar for workflow group configuration. Three flows:
166137 * - create a new group (workflow + outputs + deps),
@@ -277,11 +248,6 @@ export function WorkflowSidebarBody({
277248 */
278249 const otherColumns = anchorIdx >= allColumns . length ? allColumns : allColumns . slice ( 0 , anchorIdx )
279250
280- // Used by the "missing workflow input" suggestion below — for edit-output
281- // we exclude the column being edited (you can't suggest it as its own
282- // input).
283- const anchorColumnName = config . mode === 'edit-output' ? config . columnName : null
284-
285251 // Every left-of-current column is a valid dep — workflow output columns
286252 // included. Exclude this group's own outputs (you can't depend on yourself).
287253 const ownOutputIds = new Set ( existingGroup ?. outputs . map ( ( o ) => o . columnName ) ?? [ ] )
@@ -312,11 +278,6 @@ export function WorkflowSidebarBody({
312278 const [ autoRun , setAutoRun ] = useState < boolean > ( ( ) =>
313279 existingGroup ? existingGroup . autoRun !== false : false
314280 )
315- // Which workflow state per-cell runs execute against. Defaults to `'live'`
316- // (the editable draft) for both new and pre-feature groups.
317- const [ deploymentMode , setDeploymentMode ] = useState < WorkflowGroupDeploymentMode > (
318- ( ) => existingGroup ?. deploymentMode ?? 'live'
319- )
320281 // Deps default to none selected. With auto-run on, at least one is required
321282 // (enforced via `depsValid` below); a legacy group with empty deps will
322283 // surface the error on first open until the user picks at least one column.
@@ -332,101 +293,21 @@ export function WorkflowSidebarBody({
332293 const [ showValidation , setShowValidation ] = useState ( false )
333294 const [ nameError , setNameError ] = useState < string | null > ( null )
334295
335- const workflowState = useWorkflowState ( selectedWorkflowId || undefined )
296+ const workflowState = useDeployedWorkflowState ( selectedWorkflowId || null )
336297
337- /** Resolves the unified Start block id and its current `inputFormat` field
338- * names. The "Add inputs" mutation only adds rows for table columns that
339- * aren't already represented in the start block. */
340- const startBlockInputs = useMemo < {
341- blockId : string | null
342- existingNames : Set < string >
343- existing : InputFormatField [ ]
344- } > ( ( ) => {
298+ /** Resolves Start-block inputs from the active deployment used by table runs. */
299+ const startBlockInputs = useMemo < InputFormatField [ ] > ( ( ) => {
345300 const blocks = ( workflowState . data as { blocks ?: Record < string , { type : string } > } | null )
346301 ?. blocks
347- if ( ! blocks ) return { blockId : null , existingNames : new Set ( ) , existing : [ ] }
302+ if ( ! blocks ) return [ ]
348303 const candidate = TriggerUtils . findStartBlock ( blocks , 'manual' )
349- if ( ! candidate ) return { blockId : null , existingNames : new Set ( ) , existing : [ ] }
304+ if ( ! candidate ) return [ ]
350305 const block = blocks [ candidate . blockId ] as
351306 | { subBlocks ?: Record < string , { value ?: unknown } > }
352307 | undefined
353- const existing = normalizeInputFormatValue ( block ?. subBlocks ?. inputFormat ?. value )
354- return {
355- blockId : candidate . blockId ,
356- existingNames : new Set ( existing . map ( ( f ) => f . name ) . filter ( ( n ) : n is string => ! ! n ) ) ,
357- existing,
358- }
308+ return normalizeInputFormatValue ( block ?. subBlocks ?. inputFormat ?. value )
359309 } , [ workflowState . data ] )
360310
361- const missingInputColumnNames = useMemo < string [ ] > ( ( ) => {
362- if ( ! startBlockInputs . blockId ) return [ ]
363- const anchor = anchorColumnName
364- return allColumns
365- . filter (
366- ( c ) =>
367- getColumnId ( c ) !== anchor &&
368- ! c . workflowGroupId &&
369- ! startBlockInputs . existingNames . has ( c . name )
370- )
371- . map ( ( c ) => c . name )
372- } , [ allColumns , anchorColumnName , startBlockInputs ] )
373-
374- const queryClient = useQueryClient ( )
375- const addInputsMutation = useMutation ( {
376- mutationFn : async ( ) => {
377- const wfId = selectedWorkflowId
378- const startBlockId = startBlockInputs . blockId
379- const state = workflowState . data as WorkflowStatePayload | null | undefined
380- if ( ! wfId || ! startBlockId || ! state || missingInputColumnNames . length === 0 ) {
381- throw new Error ( 'Nothing to add' )
382- }
383- const startBlock = state . blocks [ startBlockId ]
384- if ( ! startBlock ) throw new Error ( 'Start block missing from workflow' )
385-
386- const newFields : InputFormatField [ ] = missingInputColumnNames . map ( ( name ) => {
387- const col = allColumns . find ( ( c ) => c . name === name )
388- return {
389- id : generateId ( ) ,
390- name,
391- type : tableColumnTypeToInputType ( col ?. type ) ,
392- value : '' ,
393- collapsed : false ,
394- } as InputFormatField & { id : string ; collapsed : boolean }
395- } )
396-
397- const updatedSubBlock = {
398- ...( startBlock . subBlocks ?. inputFormat ?? { id : 'inputFormat' , type : 'input-format' } ) ,
399- value : [ ...startBlockInputs . existing , ...newFields ] ,
400- }
401- const updatedBlocks = {
402- ...state . blocks ,
403- [ startBlockId ] : {
404- ...startBlock ,
405- subBlocks : { ...startBlock . subBlocks , inputFormat : updatedSubBlock } ,
406- } ,
407- }
408-
409- const rawBody = {
410- blocks : updatedBlocks ,
411- edges : state . edges ,
412- loops : state . loops ,
413- parallels : state . parallels ,
414- lastSaved : state . lastSaved ?? Date . now ( ) ,
415- isDeployed : state . isDeployed ?? false ,
416- }
417- // double-cast-allowed: WorkflowStatePayload is the loose local view of
418- // useWorkflowState; round-trip back to the strict PUT body shape.
419- const body = rawBody as unknown as WorkflowStateContractInput
420- await requestJson ( putWorkflowNormalizedStateContract , { params : { id : wfId } , body } )
421- } ,
422- onError : ( err ) => {
423- toast . error ( toError ( err ) . message )
424- } ,
425- onSettled : ( ) => {
426- return queryClient . invalidateQueries ( { queryKey : workflowKeys . state ( selectedWorkflowId ) } )
427- } ,
428- } )
429-
430311 const blockOutputGroups = useMemo < BlockOutputGroup [ ] > ( ( ) => {
431312 const state = workflowState . data as
432313 | {
@@ -517,13 +398,13 @@ export function WorkflowSidebarBody({
517398 // Once the Start block's input fields resolve, auto-fill any field that has no
518399 // persisted mapping yet but matches a table column by name. Runs once; never
519400 // overrides a persisted or user-picked mapping.
520- if ( ! inputMappingsHydrated && startBlockInputs . existing . length > 0 ) {
401+ if ( ! inputMappingsHydrated && startBlockInputs . length > 0 ) {
521402 // Map a Start input field to the column sharing its name, storing the
522403 // column id (the value the dropdowns and persisted mappings key on).
523404 const idByColumnName = new Map ( depOptions . map ( ( c ) => [ c . name , getColumnId ( c ) ] ) )
524405 const next = { ...inputMappings }
525406 let changed = false
526- for ( const field of startBlockInputs . existing ) {
407+ for ( const field of startBlockInputs ) {
527408 if ( ! field . name || next [ field . name ] ) continue
528409 const colId = idByColumnName . get ( field . name )
529410 if ( colId ) {
@@ -676,7 +557,6 @@ export function WorkflowSidebarBody({
676557 outputs : fullOutputs ,
677558 ...( newOutputColumns . length > 0 ? { newOutputColumns } : { } ) ,
678559 inputMappings : inputMappingsList ,
679- deploymentMode,
680560 autoRun,
681561 } )
682562 toast . success ( `Saved "${ existingGroup . name ?? 'Workflow' } "` )
@@ -708,7 +588,6 @@ export function WorkflowSidebarBody({
708588 dependencies,
709589 outputs : groupOutputs ,
710590 inputMappings : inputMappingsList ,
711- deploymentMode,
712591 autoRun,
713592 }
714593 await addWorkflowGroup . mutateAsync ( { group, outputColumns : newOutputColumns } )
@@ -815,29 +694,6 @@ export function WorkflowSidebarBody({
815694 < div className = 'flex flex-col gap-[9.5px]' >
816695 < div className = 'flex min-w-0 items-center justify-between gap-2 pl-0.5' >
817696 < Label > Workflow preview</ Label >
818- { ! isEnrichment &&
819- startBlockInputs . blockId &&
820- missingInputColumnNames . length > 0 && (
821- < Tooltip . Root >
822- < Tooltip . Trigger asChild >
823- < Button
824- type = 'button'
825- variant = 'default'
826- size = 'sm'
827- className = 'flex-none whitespace-nowrap'
828- onClick = { ( ) => addInputsMutation . mutate ( ) }
829- disabled = { addInputsMutation . isPending }
830- >
831- { addInputsMutation . isPending
832- ? 'Adding…'
833- : `Add column inputs (${ missingInputColumnNames . length } )` }
834- </ Button >
835- </ Tooltip . Trigger >
836- < Tooltip . Content side = 'top' >
837- Adds { missingInputColumnNames . join ( ', ' ) } to the workflow's Start block
838- </ Tooltip . Content >
839- </ Tooltip . Root >
840- ) }
841697 </ div >
842698 < div className = 'relative h-[160px] overflow-hidden rounded-sm border border-[var(--border)]' >
843699 { workflowState . isLoading ? (
@@ -896,12 +752,16 @@ export function WorkflowSidebarBody({
896752 < div className = 'flex flex-col gap-[9.5px]' >
897753 < RequiredLabel > Workflow</ RequiredLabel >
898754 < ChipCombobox
899- options = { workflows ?. map ( ( wf ) => ( { label : wf . name , value : wf . id } ) ) ?? [ ] }
755+ options = {
756+ workflows
757+ ?. filter ( ( workflow ) => workflow . isDeployed )
758+ . map ( ( workflow ) => ( { label : workflow . name , value : workflow . id } ) ) ?? [ ]
759+ }
900760 value = { selectedWorkflowId }
901761 onChange = { ( v ) => setSelectedWorkflowId ( v ) }
902762 placeholder = 'Select a workflow'
903763 disabled = { ! workflows || workflows . length === 0 || isEditOutputMode || isEnrichment }
904- emptyMessage = 'No manual triggers configured '
764+ emptyMessage = 'No deployed workflows available '
905765 maxHeight = { 260 }
906766 searchable
907767 searchPlaceholder = 'Search workflows...'
@@ -993,25 +853,8 @@ export function WorkflowSidebarBody({
993853 </ div >
994854 { showAdvanced && (
995855 < >
996- { ! isEnrichment && (
997- < >
998- < div className = 'flex items-center justify-between pl-0.5' >
999- < Label > Workflow version</ Label >
1000- < ButtonGroup
1001- value = { deploymentMode }
1002- onValueChange = { ( v ) =>
1003- setDeploymentMode ( v === 'deployed' ? 'deployed' : 'live' )
1004- }
1005- >
1006- < ButtonGroupItem value = 'live' > Live</ ButtonGroupItem >
1007- < ButtonGroupItem value = 'deployed' > Deployed</ ButtonGroupItem >
1008- </ ButtonGroup >
1009- </ div >
1010- < FieldDivider />
1011- </ >
1012- ) }
1013856 < InputMappingSection
1014- inputFields = { startBlockInputs . existing }
857+ inputFields = { startBlockInputs }
1015858 columnOptions = { depOptions }
1016859 value = { inputMappings }
1017860 onChange = { setInputMappings }
0 commit comments