@@ -19,17 +19,11 @@ import {
1919import { ArrowLeft , ChevronDown , SquareArrowUpRight , X } from '@sim/emcn/icons'
2020import { toError } from '@sim/utils/errors'
2121import { generateId } from '@sim/utils/id'
22- import { useMutation , useQueryClient } from '@tanstack/react-query'
2322import { findValidationIssue , isValidationError } from '@/lib/api/client/errors'
24- import { requestJson } from '@/lib/api/client/request'
2523import type {
2624 AddWorkflowGroupBodyInput ,
2725 UpdateWorkflowGroupBodyInput ,
2826} from '@/lib/api/contracts/tables'
29- import {
30- putWorkflowNormalizedStateContract ,
31- type WorkflowStateContractInput ,
32- } from '@/lib/api/contracts/workflows'
3327import type {
3428 ColumnDefinition ,
3529 WorkflowGroup ,
@@ -39,7 +33,6 @@ import type {
3933} from '@/lib/table'
4034import { getColumnId } from '@/lib/table/column-keys'
4135import { columnTypeForLeaf , deriveOutputColumnName } from '@/lib/table/column-naming'
42- import { columnTypeById } from '@/lib/table/column-types'
4336import {
4437 type FlattenOutputsBlockInput ,
4538 type FlattenOutputsEdgeInput ,
@@ -55,12 +48,12 @@ import {
5548} from '@/app/workspace/[workspaceId]/tables/[tableId]/components/sidebar-fields'
5649import { PreviewWorkflow } from '@/app/workspace/[workspaceId]/w/components/preview'
5750import { BlockTile } from '@/blocks/block-tile'
51+ import { useDeployedWorkflowState } from '@/hooks/queries/deployments'
5852import {
5953 useAddWorkflowGroup ,
6054 useUpdateColumn ,
6155 useUpdateWorkflowGroup ,
6256} from '@/hooks/queries/tables'
63- import { useWorkflowState , workflowKeys } from '@/hooks/queries/workflows'
6457import type { WorkflowMetadata } from '@/stores/workflows/registry/types'
6558import { InputMappingSection } from './input-mapping-section'
6659import { RunSettingsSection } from './run-settings-section'
@@ -139,25 +132,6 @@ interface BlockOutputGroup {
139132 paths : string [ ]
140133}
141134
142- interface WorkflowStatePayload {
143- blocks : Record <
144- string ,
145- {
146- type : string
147- subBlocks ?: Record < string , { id ?: string ; type ?: string ; value ?: unknown } >
148- } & Record < string , unknown >
149- >
150- edges : unknown [ ]
151- loops : unknown
152- parallels : unknown
153- lastSaved ?: number
154- isDeployed ?: boolean
155- }
156-
157- function tableColumnTypeToInputType ( colType : ColumnDefinition [ 'type' ] | undefined ) : string {
158- return columnTypeById ( colType ) . workflowInputType
159- }
160-
161135/**
162136 * Right-edge sidebar for workflow group configuration. Three flows:
163137 * - create a new group (workflow + outputs + deps),
@@ -274,11 +248,6 @@ export function WorkflowSidebarBody({
274248 */
275249 const otherColumns = anchorIdx >= allColumns . length ? allColumns : allColumns . slice ( 0 , anchorIdx )
276250
277- // Used by the "missing workflow input" suggestion below — for edit-output
278- // we exclude the column being edited (you can't suggest it as its own
279- // input).
280- const anchorColumnName = config . mode === 'edit-output' ? config . columnName : null
281-
282251 // Every left-of-current column is a valid dep — workflow output columns
283252 // included. Exclude this group's own outputs (you can't depend on yourself).
284253 const ownOutputIds = new Set ( existingGroup ?. outputs . map ( ( o ) => o . columnName ) ?? [ ] )
@@ -324,101 +293,21 @@ export function WorkflowSidebarBody({
324293 const [ showValidation , setShowValidation ] = useState ( false )
325294 const [ nameError , setNameError ] = useState < string | null > ( null )
326295
327- const workflowState = useWorkflowState ( selectedWorkflowId || undefined )
296+ const workflowState = useDeployedWorkflowState ( selectedWorkflowId || null )
328297
329- /** Resolves the unified Start block id and its current `inputFormat` field
330- * names. The "Add inputs" mutation only adds rows for table columns that
331- * aren't already represented in the start block. */
332- const startBlockInputs = useMemo < {
333- blockId : string | null
334- existingNames : Set < string >
335- existing : InputFormatField [ ]
336- } > ( ( ) => {
298+ /** Resolves Start-block inputs from the active deployment used by table runs. */
299+ const startBlockInputs = useMemo < InputFormatField [ ] > ( ( ) => {
337300 const blocks = ( workflowState . data as { blocks ?: Record < string , { type : string } > } | null )
338301 ?. blocks
339- if ( ! blocks ) return { blockId : null , existingNames : new Set ( ) , existing : [ ] }
302+ if ( ! blocks ) return [ ]
340303 const candidate = TriggerUtils . findStartBlock ( blocks , 'manual' )
341- if ( ! candidate ) return { blockId : null , existingNames : new Set ( ) , existing : [ ] }
304+ if ( ! candidate ) return [ ]
342305 const block = blocks [ candidate . blockId ] as
343306 | { subBlocks ?: Record < string , { value ?: unknown } > }
344307 | undefined
345- const existing = normalizeInputFormatValue ( block ?. subBlocks ?. inputFormat ?. value )
346- return {
347- blockId : candidate . blockId ,
348- existingNames : new Set ( existing . map ( ( f ) => f . name ) . filter ( ( n ) : n is string => ! ! n ) ) ,
349- existing,
350- }
308+ return normalizeInputFormatValue ( block ?. subBlocks ?. inputFormat ?. value )
351309 } , [ workflowState . data ] )
352310
353- const missingInputColumnNames = useMemo < string [ ] > ( ( ) => {
354- if ( ! startBlockInputs . blockId ) return [ ]
355- const anchor = anchorColumnName
356- return allColumns
357- . filter (
358- ( c ) =>
359- getColumnId ( c ) !== anchor &&
360- ! c . workflowGroupId &&
361- ! startBlockInputs . existingNames . has ( c . name )
362- )
363- . map ( ( c ) => c . name )
364- } , [ allColumns , anchorColumnName , startBlockInputs ] )
365-
366- const queryClient = useQueryClient ( )
367- const addInputsMutation = useMutation ( {
368- mutationFn : async ( ) => {
369- const wfId = selectedWorkflowId
370- const startBlockId = startBlockInputs . blockId
371- const state = workflowState . data as WorkflowStatePayload | null | undefined
372- if ( ! wfId || ! startBlockId || ! state || missingInputColumnNames . length === 0 ) {
373- throw new Error ( 'Nothing to add' )
374- }
375- const startBlock = state . blocks [ startBlockId ]
376- if ( ! startBlock ) throw new Error ( 'Start block missing from workflow' )
377-
378- const newFields : InputFormatField [ ] = missingInputColumnNames . map ( ( name ) => {
379- const col = allColumns . find ( ( c ) => c . name === name )
380- return {
381- id : generateId ( ) ,
382- name,
383- type : tableColumnTypeToInputType ( col ?. type ) ,
384- value : '' ,
385- collapsed : false ,
386- } as InputFormatField & { id : string ; collapsed : boolean }
387- } )
388-
389- const updatedSubBlock = {
390- ...( startBlock . subBlocks ?. inputFormat ?? { id : 'inputFormat' , type : 'input-format' } ) ,
391- value : [ ...startBlockInputs . existing , ...newFields ] ,
392- }
393- const updatedBlocks = {
394- ...state . blocks ,
395- [ startBlockId ] : {
396- ...startBlock ,
397- subBlocks : { ...startBlock . subBlocks , inputFormat : updatedSubBlock } ,
398- } ,
399- }
400-
401- const rawBody = {
402- blocks : updatedBlocks ,
403- edges : state . edges ,
404- loops : state . loops ,
405- parallels : state . parallels ,
406- lastSaved : state . lastSaved ?? Date . now ( ) ,
407- isDeployed : state . isDeployed ?? false ,
408- }
409- // double-cast-allowed: WorkflowStatePayload is the loose local view of
410- // useWorkflowState; round-trip back to the strict PUT body shape.
411- const body = rawBody as unknown as WorkflowStateContractInput
412- await requestJson ( putWorkflowNormalizedStateContract , { params : { id : wfId } , body } )
413- } ,
414- onError : ( err ) => {
415- toast . error ( toError ( err ) . message )
416- } ,
417- onSettled : ( ) => {
418- return queryClient . invalidateQueries ( { queryKey : workflowKeys . state ( selectedWorkflowId ) } )
419- } ,
420- } )
421-
422311 const blockOutputGroups = useMemo < BlockOutputGroup [ ] > ( ( ) => {
423312 const state = workflowState . data as
424313 | {
@@ -509,13 +398,13 @@ export function WorkflowSidebarBody({
509398 // Once the Start block's input fields resolve, auto-fill any field that has no
510399 // persisted mapping yet but matches a table column by name. Runs once; never
511400 // overrides a persisted or user-picked mapping.
512- if ( ! inputMappingsHydrated && startBlockInputs . existing . length > 0 ) {
401+ if ( ! inputMappingsHydrated && startBlockInputs . length > 0 ) {
513402 // Map a Start input field to the column sharing its name, storing the
514403 // column id (the value the dropdowns and persisted mappings key on).
515404 const idByColumnName = new Map ( depOptions . map ( ( c ) => [ c . name , getColumnId ( c ) ] ) )
516405 const next = { ...inputMappings }
517406 let changed = false
518- for ( const field of startBlockInputs . existing ) {
407+ for ( const field of startBlockInputs ) {
519408 if ( ! field . name || next [ field . name ] ) continue
520409 const colId = idByColumnName . get ( field . name )
521410 if ( colId ) {
@@ -805,29 +694,6 @@ export function WorkflowSidebarBody({
805694 < div className = 'flex flex-col gap-[9.5px]' >
806695 < div className = 'flex min-w-0 items-center justify-between gap-2 pl-0.5' >
807696 < Label > Workflow preview</ Label >
808- { ! isEnrichment &&
809- startBlockInputs . blockId &&
810- missingInputColumnNames . length > 0 && (
811- < Tooltip . Root >
812- < Tooltip . Trigger asChild >
813- < Button
814- type = 'button'
815- variant = 'default'
816- size = 'sm'
817- className = 'flex-none whitespace-nowrap'
818- onClick = { ( ) => addInputsMutation . mutate ( ) }
819- disabled = { addInputsMutation . isPending }
820- >
821- { addInputsMutation . isPending
822- ? 'Adding…'
823- : `Add column inputs (${ missingInputColumnNames . length } )` }
824- </ Button >
825- </ Tooltip . Trigger >
826- < Tooltip . Content side = 'top' >
827- Adds { missingInputColumnNames . join ( ', ' ) } to the workflow's Start block
828- </ Tooltip . Content >
829- </ Tooltip . Root >
830- ) }
831697 </ div >
832698 < div className = 'relative h-[160px] overflow-hidden rounded-sm border border-[var(--border)]' >
833699 { workflowState . isLoading ? (
@@ -886,12 +752,16 @@ export function WorkflowSidebarBody({
886752 < div className = 'flex flex-col gap-[9.5px]' >
887753 < RequiredLabel > Workflow</ RequiredLabel >
888754 < ChipCombobox
889- 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+ }
890760 value = { selectedWorkflowId }
891761 onChange = { ( v ) => setSelectedWorkflowId ( v ) }
892762 placeholder = 'Select a workflow'
893763 disabled = { ! workflows || workflows . length === 0 || isEditOutputMode || isEnrichment }
894- emptyMessage = 'No manual triggers configured '
764+ emptyMessage = 'No deployed workflows available '
895765 maxHeight = { 260 }
896766 searchable
897767 searchPlaceholder = 'Search workflows...'
@@ -984,7 +854,7 @@ export function WorkflowSidebarBody({
984854 { showAdvanced && (
985855 < >
986856 < InputMappingSection
987- inputFields = { startBlockInputs . existing }
857+ inputFields = { startBlockInputs }
988858 columnOptions = { depOptions }
989859 value = { inputMappings }
990860 onChange = { setInputMappings }
0 commit comments