fix: 修复工作流入口标题与提示词校验

- 支持系统入口标题清空占位与下游引用展示同步

- 降低字段编辑触发的节点图重算和无效渲染

- 在保存及执行前拦截空白的大模型用户提示词
This commit is contained in:
2026-08-10 23:15:41 +08:00
parent 6bfd440214
commit 1c0fbfa5ff
5 changed files with 482 additions and 58 deletions

View File

@@ -832,11 +832,36 @@ public class WorkflowCheckService {
continue; continue;
} }
if (workflowDatacenterContentService.isLlmNode(node.type)) { if (workflowDatacenterContentService.isLlmNode(node.type)) {
checkLlmUserPrompt(node, issues, issueKeys);
checkLlmQueryContext(node, parsed, issues, issueKeys); checkLlmQueryContext(node, parsed, issues, issueKeys);
} }
} }
} }
/**
* 校验大模型节点的用户提示词,避免空提示词进入运行时解析。
*
* @param node 大模型节点
* @param issues 问题列表
* @param issueKeys 问题去重键
*/
private void checkLlmUserPrompt(
NodeView node,
List<WorkflowCheckIssue> issues,
Set<String> issueKeys) {
String userPrompt = node.data == null ? null : node.data.getString("userPrompt");
if (!StringUtils.hasText(userPrompt)) {
addIssue(
issues,
issueKeys,
"LLM_USER_PROMPT_EMPTY",
"大模型节点的用户提示词不能为空",
node.id,
null,
node.name);
}
}
private void checkMakeFileNode(NodeView node, private void checkMakeFileNode(NodeView node,
List<WorkflowCheckIssue> issues, List<WorkflowCheckIssue> issues,
Set<String> issueKeys) { Set<String> issueKeys) {

View File

@@ -436,6 +436,41 @@ public class WorkflowCheckServiceTest {
assertHasCode(result, "SEARCH_DATASET_INVALID"); assertHasCode(result, "SEARCH_DATASET_INVALID");
} }
/**
* 验证保存阶段拒绝空白的大模型用户提示词。
*/
@Test
public void testSaveShouldBlockBlankLlmUserPrompt() throws Exception {
WorkflowCheckService service = newService(new HashMap<>());
JSONObject llmData = data("大模型");
llmData.put("userPrompt", " ");
String content = workflowJson(
array(node("llm-1", "llmNode", null, llmData)),
new JSONArray()
);
WorkflowCheckResult result = service.checkContent(content, WorkflowCheckStage.SAVE, null);
Assert.assertFalse(result.isPassed());
assertHasCode(result, "LLM_USER_PROMPT_EMPTY");
}
/**
* 验证保存阶段接受有效的大模型用户提示词。
*/
@Test
public void testSaveShouldPassNonBlankLlmUserPrompt() throws Exception {
WorkflowCheckService service = newService(new HashMap<>());
JSONObject llmData = data("大模型");
llmData.put("userPrompt", "{{start-1.user_input}}");
String content = workflowJson(
array(node("llm-1", "llmNode", null, llmData)),
new JSONArray()
);
WorkflowCheckResult result = service.checkContent(content, WorkflowCheckStage.SAVE, null);
Assert.assertTrue(result.isPassed());
}
@Test @Test
public void testPreExecuteShouldBlockMissingStartOrEnd() throws Exception { public void testPreExecuteShouldBlockMissingStartOrEnd() throws Exception {
WorkflowCheckService service = newService(new HashMap<>()); WorkflowCheckService service = newService(new HashMap<>());
@@ -453,6 +488,34 @@ public class WorkflowCheckServiceTest {
assertHasCode(result, "END_NODE_MISSING"); assertHasCode(result, "END_NODE_MISSING");
} }
/**
* 验证执行前校验拒绝空的大模型用户提示词。
*/
@Test
public void testPreExecuteShouldBlockEmptyLlmUserPrompt() throws Exception {
WorkflowCheckService service = newService(new HashMap<>());
JSONObject llmData = data("大模型");
llmData.put("userPrompt", "");
String content = workflowJson(
array(
node("s1", "startNode", null, data("开始")),
node("llm-1", "llmNode", null, llmData),
node("e1", "endNode", null, data("结束"))
),
array(
edge("edge-1", "s1", "llm-1"),
edge("edge-2", "llm-1", "e1")
)
);
WorkflowCheckResult result = service.checkContent(
content,
WorkflowCheckStage.PRE_EXECUTE,
BigInteger.ONE);
Assert.assertFalse(result.isPassed());
assertHasCode(result, "LLM_USER_PROMPT_EMPTY");
}
@Test @Test
public void testPreExecuteShouldPassForSourceOnlySearchDatasetNode() throws Exception { public void testPreExecuteShouldPassForSourceOnlySearchDatasetNode() throws Exception {
WorkflowCheckService service = newService(new HashMap<>()); WorkflowCheckService service = newService(new HashMap<>());

View File

@@ -52,9 +52,10 @@
return param.formType ? [param.formType] : []; return param.formType ? [param.formType] : [];
}); });
let parameterNameDraft = $state<string | null>(null); let parameterNameDraft = $state<string | null>(null);
let systemLabelDraft = $state<string | null>(null);
let displayParamName = $derived.by(() => { let displayParamName = $derived.by(() => {
if (isSystemStartParam) { if (isSystemStartParam) {
return '用户问题'; return systemLabelDraft ?? trimString(param.formLabel);
} }
return parameterNameDraft ?? param.name; return parameterNameDraft ?? param.name;
}); });
@@ -76,6 +77,8 @@
const applyStartFieldPatch = (fieldKey: string, patch: Record<string, any>) => { const applyStartFieldPatch = (fieldKey: string, patch: Record<string, any>) => {
const currentFieldId = trimString(param.id); const currentFieldId = trimString(param.id);
const shouldSyncReferences = Object.prototype.hasOwnProperty.call(patch, 'key')
|| Object.prototype.hasOwnProperty.call(patch, 'label');
store.updateNodes((nodes) => { store.updateNodes((nodes) => {
const edges = store.getEdges(); const edges = store.getEdges();
let nextFieldKey = fieldKey; let nextFieldKey = fieldKey;
@@ -98,6 +101,9 @@
} }
}; };
}); });
if (!shouldSyncReferences) {
return nextNodes;
}
return renameStartFieldReferencesInNodes( return renameStartFieldReferencesInNodes(
nextNodes, nextNodes,
edges, edges,
@@ -172,11 +178,11 @@
const updateName = (event: Event) => { const updateName = (event: Event) => {
const input = event.target as HTMLInputElement; const input = event.target as HTMLInputElement;
const newValue = input.value; const newValue = input.value;
parameterNameDraft = newValue;
if (isStartNodeInputParam) { if (isStartNodeInputParam) {
const normalizedValue = trimString(newValue); const normalizedValue = trimString(newValue);
if (!normalizedValue) { if (!normalizedValue) {
parameterNameError = '参数名不能为空'; parameterNameError = '参数名不能为空';
parameterNameDraft = newValue;
return; return;
} }
if (!isStartFormFieldKeyAvailable( if (!isStartFormFieldKeyAvailable(
@@ -185,13 +191,66 @@
normalizedValue normalizedValue
)) { )) {
parameterNameError = '参数名已存在'; parameterNameError = '参数名已存在';
parameterNameDraft = newValue;
return; return;
} }
} }
parameterNameError = '';
};
const updateVisibleName = (event: Event) => {
if (isSystemStartParam) {
systemLabelDraft = (event.target as HTMLInputElement).value;
return;
}
updateName(event);
};
const commitName = () => {
if (parameterNameDraft === null) {
return;
}
const draftValue = parameterNameDraft;
if (isStartNodeInputParam) {
const normalizedValue = trimString(draftValue);
if (!normalizedValue) {
parameterNameError = '参数名不能为空';
return;
}
if (!isStartFormFieldKeyAvailable(
node?.current?.data as Record<string, any>,
param.name || '',
normalizedValue
)) {
parameterNameError = '参数名已存在';
return;
}
parameterNameDraft = null; parameterNameDraft = null;
parameterNameError = ''; parameterNameError = '';
updateParameter('name', newValue); if (normalizedValue !== trimString(param.name)) {
updateParameter('name', normalizedValue);
}
return;
}
parameterNameDraft = null;
parameterNameError = '';
if (draftValue !== (param.name || '')) {
updateParameter('name', draftValue);
}
};
const commitVisibleName = () => {
if (!isSystemStartParam) {
commitName();
return;
}
if (systemLabelDraft === null) {
return;
}
const nextLabel = trimString(systemLabelDraft);
systemLabelDraft = null;
if (nextLabel !== trimString(param.formLabel)) {
updateParameter('formLabel', nextLabel);
}
}; };
const updateRequired = (event: Event) => { const updateRequired = (event: Event) => {
@@ -236,11 +295,13 @@
<div class="input-item input-item-name"> <div class="input-item input-item-name">
<Input style="width: 100%;" value={displayParamName} placeholder="请输入参数名称" <Input style="width: 100%;" value={displayParamName}
disabled={param.nameDisabled === true} placeholder={isSystemStartParam ? '用户问题' : '请输入参数名称'}
disabled={isSystemStartParam ? false : param.nameDisabled === true}
aria-invalid={parameterNameError ? 'true' : undefined} aria-invalid={parameterNameError ? 'true' : undefined}
aria-describedby={parameterNameError ? parameterNameErrorId : undefined} aria-describedby={parameterNameError ? parameterNameErrorId : undefined}
oninput={updateName} /> oninput={updateVisibleName}
onchange={commitVisibleName} />
{#if parameterNameError} {#if parameterNameError}
<div id={parameterNameErrorId} class="input-error" role="alert"> <div id={parameterNameErrorId} class="input-error" role="alert">
{parameterNameError} {parameterNameError}
@@ -296,7 +357,7 @@
数据标题: 数据标题:
<Textarea rows={1} style="width: 100%;" onchange={(event)=>{ <Textarea rows={1} style="width: 100%;" onchange={(event)=>{
updateParamByEvent('formLabel', event) updateParamByEvent('formLabel', event)
}} value={param.formLabel} /> }} value={param.formLabel} placeholder={isSystemStartParam ? '用户问题' : undefined} />
</div> </div>
<div class="input-more-item"> <div class="input-more-item">

View File

@@ -34,18 +34,57 @@ describe('workflow node fields', () => {
expect(parameters[0]?.name).toBe('user_input'); expect(parameters[0]?.name).toBe('user_input');
expect(parameters[0]?.systemReserved).toBe(true); expect(parameters[0]?.systemReserved).toBe(true);
expect(parameters[0]?.required).toBe(true); expect(parameters[0]?.required).toBe(true);
expect(parameters[0]?.formLabel).toBe('');
expect(parameters[0]?.displayName).toBe('流程开始 > 用户问题');
expect(initial.nodes[0]?.data?.startFormMeta).toMatchObject({ expect(initial.nodes[0]?.data?.startFormMeta).toMatchObject({
title: '开始问答', title: '开始问答',
submitText: '开始', submitText: '开始',
}); });
expect(initial.nodes[0]?.data?.startFormSchema?.[0]).toMatchObject({ expect(initial.nodes[0]?.data?.startFormSchema?.[0]).toMatchObject({
key: 'user_input', key: 'user_input',
label: '',
type: 'textarea', type: 'textarea',
systemReserved: true, systemReserved: true,
required: true, required: true,
}); });
}); });
it('preserves an empty system start title for the default placeholder', () => {
const initial = createInitialWorkflowData();
const startData = initial.nodes[0]?.data as Record<string, any>;
const customized = updateStartFormField(startData, 'user_input', {
label: '反洗钱',
});
const cleared = updateStartFormField(customized, 'user_input', {
label: '',
});
expect(customized.startFormSchema?.[0]?.label).toBe('反洗钱');
expect(cleared.startFormSchema?.[0]?.label).toBe('');
expect((cleared.parameters as any[])?.[0]?.formLabel).toBe('');
expect((cleared.parameters as any[])?.[0]?.displayName).toBe(
'流程开始 > 用户问题',
);
});
it('preserves an explicitly entered default or prefixed system title', () => {
const initial = createInitialWorkflowData();
const startData = initial.nodes[0]?.data as Record<string, any>;
const defaultTitle = updateStartFormField(startData, 'user_input', {
label: '用户问题',
});
const prefixedTitle = updateStartFormField(startData, 'user_input', {
label: '用户问题说明',
});
expect(defaultTitle.startFormSchema?.[0]?.label).toBe('用户问题');
expect((defaultTitle.parameters as any[])?.[0]?.formLabel).toBe('用户问题');
expect(prefixedTitle.startFormSchema?.[0]?.label).toBe('用户问题说明');
expect((prefixedTitle.parameters as any[])?.[0]?.formLabel).toBe(
'用户问题说明',
);
});
it('appends custom start form field into schema source of truth', () => { it('appends custom start form field into schema source of truth', () => {
const initial = createInitialWorkflowData(); const initial = createInitialWorkflowData();
const startNode = initial.nodes[0]!; const startNode = initial.nodes[0]!;
@@ -326,6 +365,129 @@ describe('workflow node fields', () => {
}); });
}); });
it('refreshes downstream reference labels when the system title changes', () => {
const initial = createInitialWorkflowData();
const customStartData = updateStartFormField(
initial.nodes[0]?.data as Record<string, any>,
'user_input',
{ label: '反洗钱' },
);
const clearedStartData = updateStartFormField(
customStartData,
'user_input',
{ label: '' },
);
const startNode: Node = {
id: 'start_1',
type: 'startNode',
position: { x: 0, y: 0 },
data: {
title: '开始节点',
...clearedStartData,
},
};
const llmNode: Node = {
id: 'llm_1',
type: 'llmNode',
position: { x: 120, y: 0 },
data: {
title: '大模型',
userPrompt: '',
parameters: [
{
id: 'param_1',
name: 'start_1.user_input',
ref: 'start_1.user_input',
refType: 'ref',
autoManaged: true,
formLabel: '开始节点 > 反洗钱',
displayName: '开始节点 > 反洗钱',
},
],
},
};
const endNode: Node = {
id: 'end_1',
type: 'endNode',
position: { x: 240, y: 0 },
data: {
title: '结束节点',
parameters: [
{
id: 'param_2',
name: 'start_1.user_input',
ref: 'start_1.user_input',
refType: 'ref',
formLabel: '开始节点 > 反洗钱',
displayName: '开始节点 > 反洗钱',
},
],
},
};
const edges: Edge[] = [
{ id: 'edge_1', source: 'start_1', target: 'llm_1' } as Edge,
{ id: 'edge_2', source: 'llm_1', target: 'end_1' } as Edge,
];
const nextNodes = renameStartFieldReferencesInNodes(
[startNode, llmNode, endNode],
edges,
'start_1',
'user_input',
'user_input',
);
const nextLlmNode = nextNodes.find((node) => node.id === 'llm_1')!;
const nextParameter = (nextLlmNode.data?.parameters as any[])?.[0];
const nextEndParameter = (
nextNodes.find((node) => node.id === 'end_1')?.data?.parameters as any[]
)?.[0];
expect(nextLlmNode.data?.userPrompt).toBe('');
expect(nextParameter?.name).toBe('start_1.user_input');
expect(nextParameter?.ref).toBe('start_1.user_input');
expect(nextParameter?.displayName).toBe('开始节点 > 用户问题');
expect(nextParameter?.formLabel).toBe('开始节点 > 用户问题');
expect(nextEndParameter?.displayName).toBe('开始节点 > 用户问题');
expect(nextEndParameter?.formLabel).toBe('开始节点 > 用户问题');
expect((nextLlmNode.data as any)?.[FIELD_BINDING_META_KEY]).toBeUndefined();
});
it('preserves node identities when a title sync has nothing to update', () => {
const initial = createInitialWorkflowData();
const startNode: Node = {
...initial.nodes[0]!,
id: 'start_1',
};
const untouchedNode: Node = {
id: 'code_1',
type: 'codeNode',
position: { x: 120, y: 0 },
data: {
title: '代码节点',
parameters: [
{
id: 'param_1',
name: 'manual_value',
displayName: '手工参数',
},
],
},
};
const nodes = [startNode, untouchedNode];
const nextNodes = renameStartFieldReferencesInNodes(
nodes,
[],
'start_1',
'user_input',
'user_input',
);
expect(nextNodes).toBe(nodes);
expect(nextNodes[0]).toBe(startNode);
expect(nextNodes[1]).toBe(untouchedNode);
});
it('preserves resource content type independently from input type', () => { it('preserves resource content type independently from input type', () => {
for (const contentType of ['image', 'video', 'audio', 'other'] as const) { for (const contentType of ['image', 'video', 'audio', 'other'] as const) {
const key = `preview_${contentType}`; const key = `preview_${contentType}`;

View File

@@ -184,6 +184,9 @@ function getNodeTitle(node?: Node | null) {
} }
function getParameterLabel(parameter?: Parameter | null) { function getParameterLabel(parameter?: Parameter | null) {
if (isSystemStartParameter(parameter)) {
return trimString(parameter?.formLabel) || SYSTEM_START_PARAM_LABEL;
}
return ( return (
asString(parameter?.formLabel).trim() || asString(parameter?.formLabel).trim() ||
asString(parameter?.displayName).trim() || asString(parameter?.displayName).trim() ||
@@ -415,6 +418,14 @@ function normalizeSystemStartFormType(value: unknown): 'input' | 'textarea' {
return trimString(value) === 'input' ? 'input' : 'textarea'; return trimString(value) === 'input' ? 'input' : 'textarea';
} }
function normalizeSystemStartFormLabel(value: unknown) {
return trimString(value);
}
function getSystemStartDisplayLabel(value: unknown) {
return normalizeSystemStartFormLabel(value) || SYSTEM_START_PARAM_LABEL;
}
function normalizeStartFormFieldUiType( function normalizeStartFormFieldUiType(
value: unknown, value: unknown,
fallback: StartFormFieldType = 'text', fallback: StartFormFieldType = 'text',
@@ -519,8 +530,8 @@ export function normalizeStartFormMeta(
function normalizeSystemStartParameter( function normalizeSystemStartParameter(
parameter?: Parameter | null, parameter?: Parameter | null,
): Parameter { ): Parameter {
const fallbackLabel = SYSTEM_START_PARAM_LABEL; const formLabel = normalizeSystemStartFormLabel(parameter?.formLabel);
const formLabel = trimString(parameter?.formLabel) || fallbackLabel; const displayLabel = getSystemStartDisplayLabel(formLabel);
const formType = normalizeSystemStartFormType(parameter?.formType); const formType = normalizeSystemStartFormType(parameter?.formType);
return ensureParameterId({ return ensureParameterId({
...cloneParameter(parameter || {}), ...cloneParameter(parameter || {}),
@@ -534,7 +545,7 @@ function normalizeSystemStartParameter(
formDescription: asString(parameter?.formDescription), formDescription: asString(parameter?.formDescription),
formPlaceholder: trimString(parameter?.formPlaceholder) || '请输入用户问题', formPlaceholder: trimString(parameter?.formPlaceholder) || '请输入用户问题',
defaultValue: asString(parameter?.defaultValue), defaultValue: asString(parameter?.defaultValue),
displayName: `流程开始 > ${formLabel}`, displayName: `流程开始 > ${displayLabel}`,
nameDisabled: true, nameDisabled: true,
dataTypeDisabled: true, dataTypeDisabled: true,
deleteDisabled: true, deleteDisabled: true,
@@ -637,8 +648,15 @@ function normalizeStartFormField(
const options = isOptionFieldType(type) const options = isOptionFieldType(type)
? ensureStringArray(field?.options || existingParameter?.enums) ? ensureStringArray(field?.options || existingParameter?.enums)
: []; : [];
const requestedLabel = const hasFieldLabel = Object.prototype.hasOwnProperty.call(
trimString(field?.label) || field || {},
'label',
);
const requestedLabel = isSystemField
? normalizeSystemStartFormLabel(
hasFieldLabel ? field?.label : existingParameter?.formLabel,
)
: trimString(field?.label) ||
trimString(existingParameter?.formLabel) || trimString(existingParameter?.formLabel) ||
trimString(existingParameter?.displayName) || trimString(existingParameter?.displayName) ||
key; key;
@@ -796,8 +814,7 @@ export function normalizeStartFormSchema(
{ {
id: trimString(systemParameter?.id), id: trimString(systemParameter?.id),
key: SYSTEM_START_PARAM_NAME, key: SYSTEM_START_PARAM_NAME,
label: label: normalizeSystemStartFormLabel(systemParameter?.formLabel),
trimString(systemParameter?.formLabel) || SYSTEM_START_PARAM_LABEL,
type: type:
normalizeSystemStartFormType(systemParameter?.formType) === 'input' normalizeSystemStartFormType(systemParameter?.formType) === 'input'
? 'text' ? 'text'
@@ -933,6 +950,7 @@ export function updateStartFormField(
!trimString(patch.label) && !trimString(patch.label) &&
patch.type != null && patch.type != null &&
isDefaultStartFormFieldLabel(field.label); isDefaultStartFormFieldLabel(field.label);
const hasLabelPatch = Object.prototype.hasOwnProperty.call(patch, 'label');
return { return {
...field, ...field,
...patch, ...patch,
@@ -947,7 +965,9 @@ export function updateStartFormField(
) )
: field.key), : field.key),
label: label:
trimString(patch.label) || field.systemReserved && hasLabelPatch
? normalizeSystemStartFormLabel(patch.label)
: trimString(patch.label) ||
(shouldAutoRenameLabel (shouldAutoRenameLabel
? getDefaultStartFormFieldLabel(nextType) ? getDefaultStartFormFieldLabel(nextType)
: field.label), : field.label),
@@ -1450,28 +1470,115 @@ function replaceStartFieldReferenceValue(
export function collectDownstreamNodeIds(rootNodeId: string, edges: Edge[]) { export function collectDownstreamNodeIds(rootNodeId: string, edges: Edge[]) {
const nodeIds = new Set<string>(); const nodeIds = new Set<string>();
const adjacency = new Map<string, string[]>();
for (const edge of edges) {
if (!edge.source || !edge.target || edge.sourceHandle === 'loop_handle') {
continue;
}
const targets = adjacency.get(edge.source);
if (targets) {
targets.push(edge.target);
} else {
adjacency.set(edge.source, [edge.target]);
}
}
const visit = (nodeId: string) => { const pendingNodeIds = [rootNodeId];
while (pendingNodeIds.length > 0) {
const nodeId = pendingNodeIds.pop();
if (!nodeId || nodeIds.has(nodeId)) { if (!nodeId || nodeIds.has(nodeId)) {
return; continue;
} }
nodeIds.add(nodeId); nodeIds.add(nodeId);
edges const targets = adjacency.get(nodeId) || [];
.filter( for (let index = targets.length - 1; index >= 0; index -= 1) {
(edge) => edge.source === nodeId && edge.sourceHandle !== 'loop_handle', pendingNodeIds.push(targets[index]!);
) }
.forEach((edge) => {
if (edge.target) {
visit(edge.target);
} }
});
};
visit(rootNodeId);
nodeIds.delete(rootNodeId); nodeIds.delete(rootNodeId);
return Array.from(nodeIds); return Array.from(nodeIds);
} }
function syncReferenceParameterLabels(
parameters: Parameter[],
refPath: string,
displayName: string,
): Parameter[] {
let changed = false;
const nextParameters = parameters.map((parameter) => {
const currentChildren = Array.isArray(parameter.children)
? parameter.children
: [];
const nextChildren = currentChildren.length
? syncReferenceParameterLabels(currentChildren, refPath, displayName)
: currentChildren;
const matchesReference =
(parameter.autoManaged === true || parameter.refType === 'ref') &&
(trimString(parameter.name) === refPath ||
trimString(parameter.ref) === refPath);
const labelChanged =
matchesReference &&
(parameter.displayName !== displayName ||
parameter.formLabel !== displayName);
const childrenChanged = nextChildren !== currentChildren;
if (!labelChanged && !childrenChanged) {
return parameter;
}
changed = true;
return {
...parameter,
...(labelChanged ? { displayName, formLabel: displayName } : {}),
...(childrenChanged ? { children: nextChildren } : {}),
};
});
return changed ? nextParameters : parameters;
}
function syncStartFieldReferenceLabelsInNodes(
nodes: Node[],
startNodeId: string,
fieldKey: string,
) {
const startNode = nodes.find((node) => node.id === startNodeId);
const startParameters = Array.isArray(startNode?.data?.parameters)
? (startNode.data.parameters as Parameter[])
: [];
const sourceParameter = findParameterByName(startParameters, fieldKey);
if (!startNode || !sourceParameter) {
return nodes;
}
const refPath = `${startNodeId}.${fieldKey}`;
const displayName = `${getNodeTitle(startNode)} > ${getReferenceParameterLabel(sourceParameter)}`;
let changed = false;
const nextNodes = nodes.map((node) => {
const parameters = Array.isArray(node.data?.parameters)
? (node.data.parameters as Parameter[])
: [];
if (parameters.length === 0) {
return node;
}
const nextParameters = syncReferenceParameterLabels(
parameters,
refPath,
displayName,
);
if (nextParameters === parameters) {
return node;
}
changed = true;
return {
...node,
data: {
...((node.data || {}) as Record<string, any>),
parameters: nextParameters,
},
};
});
return changed ? nextNodes : nodes;
}
export function renameStartFieldReferencesInNodes( export function renameStartFieldReferencesInNodes(
nodes: Node[], nodes: Node[],
edges: Edge[], edges: Edge[],
@@ -1482,19 +1589,16 @@ export function renameStartFieldReferencesInNodes(
const normalizedStartNodeId = trimString(startNodeId); const normalizedStartNodeId = trimString(startNodeId);
const normalizedCurrentKey = trimString(currentKey); const normalizedCurrentKey = trimString(currentKey);
const normalizedNextKey = trimString(nextKey); const normalizedNextKey = trimString(nextKey);
if ( if (!normalizedStartNodeId || !normalizedCurrentKey || !normalizedNextKey) {
!normalizedStartNodeId ||
!normalizedCurrentKey ||
!normalizedNextKey ||
normalizedCurrentKey === normalizedNextKey
) {
return nodes; return nodes;
} }
const oldRefPath = `${normalizedStartNodeId}.${normalizedCurrentKey}`; const oldRefPath = `${normalizedStartNodeId}.${normalizedCurrentKey}`;
const newRefPath = `${normalizedStartNodeId}.${normalizedNextKey}`; const newRefPath = `${normalizedStartNodeId}.${normalizedNextKey}`;
const nextNodes: Node[] = nodes.map((node) => { const keyChanged = normalizedCurrentKey !== normalizedNextKey;
const renamedNodes: Node[] = keyChanged
? nodes.map((node) => {
if (node.id === normalizedStartNodeId) { if (node.id === normalizedStartNodeId) {
return node; return node;
} }
@@ -1510,7 +1614,16 @@ export function renameStartFieldReferencesInNodes(
...node, ...node,
data: nextData ?? {}, data: nextData ?? {},
}; };
}); })
: nodes;
const nextNodes = syncStartFieldReferenceLabelsInNodes(
renamedNodes,
normalizedStartNodeId,
normalizedNextKey,
);
if (!keyChanged) {
return nextNodes;
}
const affectedNodeIds = collectDownstreamNodeIds( const affectedNodeIds = collectDownstreamNodeIds(
normalizedStartNodeId, normalizedStartNodeId,