fix: restore rule detail dependencies

This commit is contained in:
wren
2026-05-07 18:31:25 +08:00
parent add399e126
commit 5fc3a7a254
2 changed files with 67 additions and 15 deletions
+38 -10
View File
@@ -185,6 +185,28 @@ function fallbackDependencyOption(value: string, optionMap?: Map<string, Depende
}; };
} }
function resolveRuleDependencies(
rule: RuleSummary | undefined,
rulesById: Map<string, RuleSummary>,
visited = new Set<string>(),
): string[] {
if (!rule) return [];
const key = rule.ruleId || rule.id;
if (visited.has(key)) return [];
visited.add(key);
const merged = new Set<string>((rule.dependencies || []).filter(Boolean));
(rule.subRuleIds || []).forEach((ruleId) => {
const referenced = rulesById.get(ruleId);
resolveRuleDependencies(referenced, rulesById, visited).forEach((dependency) => {
if (dependency) merged.add(dependency);
});
});
return Array.from(merged);
}
function makeId(prefix: string): string { function makeId(prefix: string): string {
return `${prefix}-${Date.now()}`; return `${prefix}-${Date.now()}`;
} }
@@ -571,29 +593,36 @@ export default function RulesTestDetail() {
'ai_rule', 'ai_rule',
'rule_group' 'rule_group'
]), [rules]); ]), [rules]);
const rulesById = useMemo(() => new Map(rules.map(rule => [rule.ruleId || rule.id, rule])), [rules]);
const selectedDependencyOptions = useMemo(() => { const selectedDependencyOptions = useMemo(() => {
return ruleDraft.dependencies.map(value => dependencyOptionMap.get(value) || fallbackDependencyOption(value, dependencyOptionMap)); return ruleDraft.dependencies.map(value => dependencyOptionMap.get(value) || fallbackDependencyOption(value, dependencyOptionMap));
}, [dependencyOptionMap, ruleDraft.dependencies]); }, [dependencyOptionMap, ruleDraft.dependencies]);
const resolvedCurrentDependencies = useMemo(() => {
return resolveRuleDependencies(currentRule, rulesById);
}, [currentRule, rulesById]);
const currentRuleWithResolvedDependencies = useMemo(() => (
currentRule ? { ...currentRule, dependencies: resolvedCurrentDependencies } : undefined
), [currentRule, resolvedCurrentDependencies]);
const currentDependencyRows = useMemo(() => { const currentDependencyRows = useMemo(() => {
return (currentRule?.dependencies || []).map(value => dependencyOptionMap.get(value) || fallbackDependencyOption(value, dependencyOptionMap)); return resolvedCurrentDependencies.map(value => dependencyOptionMap.get(value) || fallbackDependencyOption(value, dependencyOptionMap));
}, [currentRule, dependencyOptionMap]); }, [dependencyOptionMap, resolvedCurrentDependencies]);
const currentRuleFields = useMemo( const currentRuleFields = useMemo(
() => fields.filter((field) => matchesCurrentRuleDependency(currentRule, [field.name])), () => fields.filter((field) => matchesCurrentRuleDependency(currentRuleWithResolvedDependencies, [field.name])),
[currentRule, fields], [currentRuleWithResolvedDependencies, fields],
); );
const currentRuleSubDocuments = useMemo( const currentRuleSubDocuments = useMemo(
() => subDocuments.filter((document) => matchesCurrentRuleDependency(currentRule, [document.name, document.id])), () => subDocuments.filter((document) => matchesCurrentRuleDependency(currentRuleWithResolvedDependencies, [document.name, document.id])),
[currentRule, subDocuments], [currentRuleWithResolvedDependencies, subDocuments],
); );
const currentRuleVisualElements = useMemo( const currentRuleVisualElements = useMemo(
() => visualElements.filter((item) => matchesCurrentRuleDependency(currentRule, [ () => visualElements.filter((item) => matchesCurrentRuleDependency(currentRuleWithResolvedDependencies, [
item.id, item.id,
item.name, item.name,
`visual.${item.id}`, `visual.${item.id}`,
`visual.${item.name || item.id}`, `visual.${item.name || item.id}`,
item.type, item.type,
])), ])),
[currentRule, visualElements], [currentRuleWithResolvedDependencies, visualElements],
); );
const dialogDependencyOptions = useMemo(() => { const dialogDependencyOptions = useMemo(() => {
const selectedValues = new Set(ruleDraft.dependencies); const selectedValues = new Set(ruleDraft.dependencies);
@@ -644,7 +673,6 @@ export default function RulesTestDetail() {
const fullYamlText = serializedYamlResult.yamlText; const fullYamlText = serializedYamlResult.yamlText;
const isSmartRuleDraft = ruleDraft.type === 'ai_rule' || ruleDraft.checkTypes.includes('ai'); const isSmartRuleDraft = ruleDraft.type === 'ai_rule' || ruleDraft.checkTypes.includes('ai');
const isRuleGroupDraft = ruleDraft.type === 'rule_group'; const isRuleGroupDraft = ruleDraft.type === 'rule_group';
const rulesById = useMemo(() => new Map(rules.map(rule => [rule.ruleId || rule.id, rule])), [rules]);
const saveButtonBusy = saveFetcher.state !== 'idle'; const saveButtonBusy = saveFetcher.state !== 'idle';
const latestDraftVersion = useMemo( const latestDraftVersion = useMemo(
() => versionItems.find((item) => !['published', 'rollback'].includes(item.status)), () => versionItems.find((item) => !['published', 'rollback'].includes(item.status)),
@@ -1362,7 +1390,7 @@ export default function RulesTestDetail() {
<Card <Card
className="ant-card" className="ant-card"
title={`依赖字段 (${currentRule.dependencies.length}项)`} title={`依赖字段 (${resolvedCurrentDependencies.length}项)`}
> >
<Table <Table
className="rules-test-table" className="rules-test-table"
+29 -5
View File
@@ -288,7 +288,7 @@ function parseRules(source: string): RuleSummary[] {
for (let index = start + 1; index < lines.length; index += 1) { for (let index = start + 1; index < lines.length; index += 1) {
const line = lines[index]; const line = lines[index];
const lineIndent = line.match(/^\s*/)?.[0].length || 0; const lineIndent = line.match(/^\s*/)?.[0].length || 0;
if (line.trim() && lineIndent <= baseIndent) { if (line.trim() && lineIndent <= baseIndent && !/^\s*-\s+/.test(line)) {
break; break;
} }
const match = line.match(/^\s*-\s+(.+)$/); const match = line.match(/^\s*-\s+(.+)$/);
@@ -313,7 +313,7 @@ function parseRules(source: string): RuleSummary[] {
values.push(stripYamlValue(match[1])); values.push(stripYamlValue(match[1]));
continue; continue;
} }
if (line.trim() && lineIndent <= indent) break; if (line.trim() && lineIndent <= indent && !/^\s*-\s+/.test(line)) break;
} }
return values; return values;
@@ -331,7 +331,7 @@ function parseRules(source: string): RuleSummary[] {
for (let index = start + 1; index < lines.length; index += 1) { for (let index = start + 1; index < lines.length; index += 1) {
const line = lines[index]; const line = lines[index];
const lineIndent = line.match(/^\s*/)?.[0].length || 0; const lineIndent = line.match(/^\s*/)?.[0].length || 0;
if (line.trim() && lineIndent <= baseIndent) { if (line.trim() && lineIndent <= baseIndent && !/^\s*-\s+/.test(line)) {
break; break;
} }
const match = line.match(/^\s*-\s+(.+)$/); const match = line.match(/^\s*-\s+(.+)$/);
@@ -342,6 +342,19 @@ function parseRules(source: string): RuleSummary[] {
return values; return values;
}; };
const readStageScalar = (block: string, key: string): string => stripYamlValue(block.match(new RegExp(`^\\s+${key}:\\s*(.+)$`, 'm'))?.[1] || ''); const readStageScalar = (block: string, key: string): string => stripYamlValue(block.match(new RegExp(`^\\s+${key}:\\s*(.+)$`, 'm'))?.[1] || '');
const readStagePairValues = (block: string): string[] => {
const lines = block.split('\n');
const values: string[] = [];
for (let index = 0; index < lines.length; index += 1) {
const match = lines[index].match(/^\s+(?:source|target|ref_field):\s*(.+)$/);
if (match) {
values.push(stripYamlValue(match[1]));
}
}
return values;
};
const summarizeStage = (stageBlock: string): string => { const summarizeStage = (stageBlock: string): string => {
const fields = readStageList(stageBlock, 'fields'); const fields = readStageList(stageBlock, 'fields');
const field = readStageScalar(stageBlock, 'field'); const field = readStageScalar(stageBlock, 'field');
@@ -376,11 +389,22 @@ function parseRules(source: string): RuleSummary[] {
const ruleId = stripYamlValue(ruleBlock.match(/^\s*-\s+rule_id:\s*(.+)$/m)?.[1] || ''); const ruleId = stripYamlValue(ruleBlock.match(/^\s*-\s+rule_id:\s*(.+)$/m)?.[1] || '');
const name = stripYamlValue(ruleBlock.match(/^\s+name:\s*(.+)$/m)?.[1] || '未命名规则'); const name = stripYamlValue(ruleBlock.match(/^\s+name:\s*(.+)$/m)?.[1] || '未命名规则');
const checkTypes = Array.from(new Set(Array.from(ruleBlock.matchAll(/^\s+(?:check|type):\s*(.+)$/gm)).map(match => stripYamlValue(match[1])))); const checkTypes = Array.from(new Set(Array.from(ruleBlock.matchAll(/^\s+(?:check|type):\s*(.+)$/gm)).map(match => stripYamlValue(match[1]))));
const stageDependencies = Array.from(ruleBlock.matchAll(/^\s+(?:field|number|chinese|left|right|left_field|right_field|target|element|seal_id|signature_id):\s*(.+)$/gm)) const stageDependencies = Array.from(ruleBlock.matchAll(/^\s+(?:field|number|chinese|left|right|left_field|right_field|target|element|seal_id|signature_id|ref_field):\s*(.+)$/gm))
.map(match => normalizeDependency(match[1])); .map(match => normalizeDependency(match[1]));
const stageFieldDependencies = splitBlocks(ruleBlock, /^\s{4,}-\s+id:\s*/)
.flatMap(stageBlock => [
...readStageList(stageBlock, 'fields'),
...readStagePairValues(stageBlock),
])
.map(normalizeDependency);
const prompts = readPrompts(ruleBlock); const prompts = readPrompts(ruleBlock);
const promptDependencies = readPromptDependencies(prompts).map(normalizeDependency); const promptDependencies = readPromptDependencies(prompts).map(normalizeDependency);
const dependencies = Array.from(new Set([...readExplicitDependencies(ruleBlock), ...stageDependencies, ...promptDependencies])); const dependencies = Array.from(new Set([
...readExplicitDependencies(ruleBlock),
...stageDependencies,
...stageFieldDependencies,
...promptDependencies,
]));
const scope = Array.from(new Set(Array.from(ruleBlock.matchAll(/^\s{4,}-\s*([^:\n]+)$/gm)).map(match => stripYamlValue(match[1])).filter(value => !/^\d+$/.test(value)))); const scope = Array.from(new Set(Array.from(ruleBlock.matchAll(/^\s{4,}-\s*([^:\n]+)$/gm)).map(match => stripYamlValue(match[1])).filter(value => !/^\d+$/.test(value))));
const subRules = readSubRules(ruleBlock); const subRules = readSubRules(ruleBlock);