Show the opposite trace endpoint

Why: The trace view selected an endpoint by lexical ID order and could present the current section as its own source.

Evidence: Focused presentation tests cover both selected directions of a source-to-section relationship.

Impact: Source and impact columns now identify the object opposite the selected node.

Refs: rdm-two-trees-latest-templates
This commit is contained in:
2026-10-14 17:42:45 +00:00
parent c9b55eb502
commit b921bef6a6
3 changed files with 27 additions and 5 deletions
+5 -4
View File
@@ -7,6 +7,7 @@ import { Chip, Collapsible, EmptyState, SectionLabel } from '@ui'
import { classifyRdmError } from '../rdmErrors' import { classifyRdmError } from '../rdmErrors'
import { import {
describeAuditEvent, describeTreeItem, humanizeKind, humanizeRelation, humanizeState, isTreeItemExpandable, describeAuditEvent, describeTreeItem, humanizeKind, humanizeRelation, humanizeState, isTreeItemExpandable,
relationOtherEndpoint,
type AuditEventViewInput, type AuditEventViewInput,
} from '../rdmAuditPresentation' } from '../rdmAuditPresentation'
import { store, useStore } from '../store' import { store, useStore } from '../store'
@@ -478,11 +479,11 @@ function TraceTab({ pid }: { pid: string }) {
<div className="rdm-audit-trace-cols"> <div className="rdm-audit-trace-cols">
<div className="rdm-audit-trace-col"> <div className="rdm-audit-trace-col">
<SectionLabel>来源依据({sources.length})</SectionLabel> <SectionLabel>来源依据({sources.length})</SectionLabel>
{sources.length === 0 ? <div className="empty-note">无来源</div> : sources.map((r, i) => <RelationRow key={i} r={r} />)} {sources.length === 0 ? <div className="empty-note">无来源</div> : sources.map((r, i) => <RelationRow key={i} r={r} selected={sel} />)}
</div> </div>
<div className="rdm-audit-trace-col"> <div className="rdm-audit-trace-col">
<SectionLabel>影响对象({impacts.length})</SectionLabel> <SectionLabel>影响对象({impacts.length})</SectionLabel>
{impacts.length === 0 ? <div className="empty-note">无影响</div> : impacts.map((r, i) => <RelationRow key={i} r={r} />)} {impacts.length === 0 ? <div className="empty-note">无影响</div> : impacts.map((r, i) => <RelationRow key={i} r={r} selected={sel} />)}
</div> </div>
</div> </div>
)} )}
@@ -491,8 +492,8 @@ function TraceTab({ pid }: { pid: string }) {
) )
} }
function RelationRow({ r }: { r: RelationEntry }) { function RelationRow({ r, selected }: { r: RelationEntry; selected: { kind: string; id: string } }) {
const other = r.from?.id && r.to?.id ? (r.from?.id < r.to?.id ? r.from : r.to) : (r.from || r.to) const other = relationOtherEndpoint(r, selected)
const readable = humanizeRelation(r) const readable = humanizeRelation(r)
return ( return (
<div className="rdm-audit-rel"> <div className="rdm-audit-rel">
+13 -1
View File
@@ -1,5 +1,8 @@
import { describe, expect, it } from 'vitest' import { describe, expect, it } from 'vitest'
import { describeAuditEvent, describeTreeItem, humanizeKind, humanizeRelation, humanizeState, isTreeItemExpandable } from './rdmAuditPresentation' import {
describeAuditEvent, describeTreeItem, humanizeKind, humanizeRelation, humanizeState,
isTreeItemExpandable, relationOtherEndpoint,
} from './rdmAuditPresentation'
describe('RDM audit presentation', () => { describe('RDM audit presentation', () => {
it('turns a structured document event into a readable action and impact', () => { it('turns a structured document event into a readable action and impact', () => {
@@ -74,4 +77,13 @@ describe('RDM audit presentation', () => {
title: '模板内容 3', title: '模板内容 3',
}) })
}) })
it('shows the endpoint opposite the selected object in a trace relationship', () => {
const relation = {
from: { kind: 'slice', id: 'slc-6' },
to: { kind: 'section', id: 'sec-2' },
}
expect(relationOtherEndpoint(relation, { kind: 'section', id: 'sec-2' })).toEqual(relation.from)
expect(relationOtherEndpoint(relation, { kind: 'slice', id: 'slc-6' })).toEqual(relation.to)
})
}) })
+9
View File
@@ -157,6 +157,15 @@ export function humanizeRelation(input: { layer?: string; type?: string; kind?:
} }
} }
export function relationOtherEndpoint<T extends { kind?: string; id?: string }>(
relation: { from?: T; to?: T },
selected: { kind: string; id: string },
): T | undefined {
if (relation.to?.kind === selected.kind && relation.to.id === selected.id) return relation.from
if (relation.from?.kind === selected.kind && relation.from.id === selected.id) return relation.to
return relation.from || relation.to
}
export function isTreeItemExpandable(item: { kind: string; hasChildren?: boolean; id?: string; title?: string }): boolean { export function isTreeItemExpandable(item: { kind: string; hasChildren?: boolean; id?: string; title?: string }): boolean {
return item.kind === 'doc' || item.hasChildren === true return item.kind === 'doc' || item.hasChildren === true
} }