Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { toError } from '@sim/utils/errors'
import { generateId } from '@sim/utils/id'
import type { AddWorkflowGroupBodyInput } from '@/lib/api/contracts/tables'
import type { ColumnDefinition, WorkflowGroup, WorkflowGroupOutput } from '@/lib/table'
import { columnMatchesRef, getColumnId } from '@/lib/table/column-keys'
import { deriveOutputColumnName } from '@/lib/table/column-naming'
import { FieldError } from '@/app/workspace/[workspaceId]/tables/[tableId]/components/sidebar-fields'
import type { EnrichmentConfig as EnrichmentDef } from '@/enrichments/types'
Expand Down Expand Up @@ -49,7 +50,7 @@ function defaultColumnFor(
c.name.toLowerCase() === input.id.toLowerCase() ||
c.name.toLowerCase() === input.name.toLowerCase()
)
return match?.name ?? ''
return match ? getColumnId(match) : ''
}

/**
Expand All @@ -71,14 +72,23 @@ export function EnrichmentConfig({
const updateColumn = useUpdateColumn({ workspaceId, tableId })
const isEditing = Boolean(existingGroup)

/** Output column's persisted name (edit mode), used to detect renames. */
const originalOutputName = (outputId: string): string | undefined =>
existingGroup?.outputs.find((o) => o.outputId === outputId)?.columnName
/** Output's column (edit mode). Persisted `columnName` refs hold a stable
* column id (name for legacy groups), so resolve id-or-name to the column. */
const outputColumn = (outputId: string): ColumnDefinition | undefined => {
const ref = existingGroup?.outputs.find((o) => o.outputId === outputId)?.columnName
return ref === undefined ? undefined : allColumns.find((c) => columnMatchesRef(c, ref))
}

/** Output column's persisted display name (edit mode), used to detect renames. */
const originalOutputName = (outputId: string): string | undefined => outputColumn(outputId)?.name

const [inputMappings, setInputMappings] = useState<Record<string, string>>(() => {
if (existingGroup) {
const seed: Record<string, string> = {}
for (const m of existingGroup.inputMappings ?? []) seed[m.inputName] = m.columnName
for (const m of existingGroup.inputMappings ?? []) {
const col = allColumns.find((c) => columnMatchesRef(c, m.columnName))
seed[m.inputName] = col ? getColumnId(col) : m.columnName
}
return seed
}
const seed: Record<string, string> = {}
Expand All @@ -94,7 +104,9 @@ export function EnrichmentConfig({
const seed: Record<string, string> = {}
if (existingGroup) {
for (const o of existingGroup.outputs) {
if (o.outputId) seed[o.outputId] = o.columnName
if (!o.outputId) continue
const col = allColumns.find((c) => columnMatchesRef(c, o.columnName))
seed[o.outputId] = col?.name ?? o.columnName
}
return seed
}
Expand All @@ -111,7 +123,7 @@ export function EnrichmentConfig({
const [deps, setDeps] = useState<string[]>(() => existingGroup?.dependencies?.columns ?? [])
const [showValidation, setShowValidation] = useState(false)

const columnOptions = allColumns.map((c) => ({ label: c.name, value: c.name }))
const columnOptions = allColumns.map((c) => ({ label: c.name, value: getColumnId(c) }))
const missingRequired = enrichment.inputs.some((i) => i.required && !inputMappings[i.id])
const depsValid = !autoRun || deps.length > 0

Expand Down Expand Up @@ -162,10 +174,13 @@ export function EnrichmentConfig({
autoRun,
})
for (const o of enrichment.outputs) {
const original = originalOutputName(o.id)
const col = outputColumn(o.id)
const next = (outputNames[o.id] ?? '').trim()
if (original && next && next !== original) {
await updateColumn.mutateAsync({ columnName: original, updates: { name: next } })
if (col && next && next !== col.name) {
await updateColumn.mutateAsync({
columnName: getColumnId(col),
updates: { name: next },
})
}
}
toast.success(`Updated "${enrichment.name}"`)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,18 @@ import type { DisplayColumn } from '../types'
* surfaces stay in sync. */
const LIMITED_RUN_PRESETS = [10, 1000] as const

/** Labels for the table-scoped run items. With an active filter the run is
* scoped to matching rows, so the labels say "filtered rows" to make the
* narrowed target visible. Shared by both menu surfaces. */
function runMenuLabels(hasActiveFilter: boolean) {
const rows = hasActiveFilter ? 'filtered rows' : 'rows'
return {
all: `Run all ${rows}`,
incomplete: `Run empty ${rows}`,
limited: (max: number) => `Run ${max.toLocaleString()} empty ${rows}`,
}
}

interface ColumnOptionsMenuProps {
open: boolean
onOpenChange: (open: boolean) => void
Expand Down Expand Up @@ -65,6 +77,9 @@ interface ColumnOptionsMenuProps {
/** When set, surfaces a "Run N selected rows" item above Run all. */
onRunColumnSelected?: () => void
selectedRowCount?: number
/** Table-scoped run items honor the active filter; when true the labels say
* "filtered rows" so the narrowed scope is visible. */
hasActiveFilter?: boolean
/** When set, the menu surfaces a "View workflow" item that opens a popup
* preview of the configured workflow. */
onViewWorkflow?: () => void
Expand Down Expand Up @@ -97,12 +112,14 @@ export function ColumnOptionsMenu({
onRunColumnLimited,
onRunColumnSelected,
selectedRowCount = 0,
hasActiveFilter = false,
onViewWorkflow,
isPinned,
onPinToggle,
}: ColumnOptionsMenuProps) {
const showRunActions = Boolean(onRunColumnAll && onRunColumnIncomplete)
const showRunSelected = Boolean(onRunColumnSelected) && selectedRowCount > 0
const runLabels = runMenuLabels(hasActiveFilter)
return (
<DropdownMenu open={open} onOpenChange={onOpenChange}>
<DropdownMenuTrigger asChild>
Expand Down Expand Up @@ -140,15 +157,15 @@ export function ColumnOptionsMenu({
</DropdownMenuItem>
)}
<DropdownMenuItem onSelect={() => onRunColumnAll?.()}>
Run all rows
{runLabels.all}
</DropdownMenuItem>
<DropdownMenuItem onSelect={() => onRunColumnIncomplete?.()}>
Run empty rows
{runLabels.incomplete}
</DropdownMenuItem>
{onRunColumnLimited &&
LIMITED_RUN_PRESETS.map((max) => (
<DropdownMenuItem key={max} onSelect={() => onRunColumnLimited(max)}>
{`Run ${max.toLocaleString()} empty rows`}
{runLabels.limited(max)}
</DropdownMenuItem>
))}
</DropdownMenuSubContent>
Expand Down Expand Up @@ -221,6 +238,9 @@ interface WorkflowGroupMetaCellProps {
/** Row ids in the user's current multi-row selection; when non-empty the
* run menu adds a "Run N selected rows" option. */
selectedRowIds?: string[] | null
/** True when the grid has an active filter — table-scoped run items apply
* only to matching rows and are labeled "filtered rows". */
hasActiveFilter?: boolean
/** Opens a popup preview of the underlying workflow. */
onViewWorkflow?: (workflowId: string) => void
/** When set, the meta cell becomes draggable and forwards events through
Expand Down Expand Up @@ -267,6 +287,7 @@ export function WorkflowGroupMetaCell({
onDeleteColumn,
onDeleteGroup,
selectedRowIds,
hasActiveFilter = false,
onViewWorkflow,
onDragStart,
onDragOver,
Expand All @@ -292,6 +313,7 @@ export function WorkflowGroupMetaCell({
const didDragRef = useRef(false)

const selectedCount = selectedRowIds?.length ?? 0
const runLabels = runMenuLabels(hasActiveFilter)

function handleRunAll() {
if (groupId) onRunColumn?.(groupId, 'all')
Expand Down Expand Up @@ -443,11 +465,13 @@ export function WorkflowGroupMetaCell({
{`Run ${selectedCount} selected ${selectedCount === 1 ? 'row' : 'rows'}`}
</DropdownMenuItem>
)}
<DropdownMenuItem onSelect={handleRunAll}>Run all rows</DropdownMenuItem>
<DropdownMenuItem onSelect={handleRunIncomplete}>Run empty rows</DropdownMenuItem>
<DropdownMenuItem onSelect={handleRunAll}>{runLabels.all}</DropdownMenuItem>
<DropdownMenuItem onSelect={handleRunIncomplete}>
{runLabels.incomplete}
</DropdownMenuItem>
{LIMITED_RUN_PRESETS.map((max) => (
<DropdownMenuItem key={max} onSelect={() => handleRunLimited(max)}>
{`Run ${max.toLocaleString()} empty rows`}
{runLabels.limited(max)}
</DropdownMenuItem>
))}
</DropdownMenuContent>
Expand All @@ -470,6 +494,7 @@ export function WorkflowGroupMetaCell({
onRunColumnLimited={onRunColumn ? handleRunLimited : undefined}
onRunColumnSelected={onRunColumn && selectedCount > 0 ? handleRunSelected : undefined}
selectedRowCount={selectedCount}
hasActiveFilter={hasActiveFilter}
onViewWorkflow={onViewWorkflow ? () => onViewWorkflow(workflowId) : undefined}
isPinned={isPinned}
onPinToggle={onPinToggle}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -503,7 +503,10 @@ export function TableGrid({
rowIds?: string[],
limit?: RunLimit
) {
onRunColumn(groupId, runMode, rowIds, limit)
// Table-scoped runs (Run all / Run empty / Run N empty) honor the active
// filter; an explicit rowIds scope (Run selected) already names its rows.
const filter = rowIds ? undefined : (queryOptions.filter ?? undefined)
onRunColumn(groupId, runMode, rowIds, limit, filter)
}

const handleViewWorkflow = useCallback(
Expand Down Expand Up @@ -3643,6 +3646,7 @@ export function TableGrid({
onSelectGroup={handleGroupSelect}
onOpenConfig={() => handleConfigureWorkflowGroup(g.groupId)}
onRunColumn={userPermissions.canEdit ? handleRunColumn : undefined}
hasActiveFilter={Boolean(queryOptions.filter)}
selectedRowIds={selectedRowIds}
onInsertLeft={
userPermissions.canEdit ? handleInsertColumnLeft : undefined
Expand Down
Loading