mirror of
https://github.com/simstudioai/sim.git
synced 2026-09-24 15:45:35 +08:00
fix(tables): enrichment sidebar column-id display + filter-scoped run menu (#5459)
* fix(tables): resolve column ids to display names in enrichment edit sidebar * feat(tables): scope group-header run menu to the active filter
This commit is contained in:
+25
-10
@@ -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'
|
||||
@@ -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) : ''
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -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> = {}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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}"`)
|
||||
|
||||
+31
-6
@@ -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
|
||||
@@ -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
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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
|
||||
@@ -267,6 +287,7 @@ export function WorkflowGroupMetaCell({
|
||||
onDeleteColumn,
|
||||
onDeleteGroup,
|
||||
selectedRowIds,
|
||||
hasActiveFilter = false,
|
||||
onViewWorkflow,
|
||||
onDragStart,
|
||||
onDragOver,
|
||||
@@ -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')
|
||||
@@ -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>
|
||||
@@ -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}
|
||||
|
||||
+5
-1
@@ -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(
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user