From 3961160a5c699587d9a9c61b03576a75a4a4ee3b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 19 May 2026 09:42:23 +0000 Subject: [PATCH 1/6] Initial plan From 055b8822c885f6c7ff6bfbb20d69edbe8255a163 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 19 May 2026 09:50:10 +0000 Subject: [PATCH 2/6] Changes before error encountered Agent-Logs-Url: https://github.com/allandecastro/dataverse-erd-visualizer/sessions/949079dd-9f78-4701-89ea-9c169f57d75d --- src/components/ReactFlowERD.tsx | 6 ++++-- src/components/SidebarSettings.tsx | 23 +++++++++++++++++++++++ src/hooks/useERDState.ts | 1 + src/types/erdTypes.ts | 3 +++ 4 files changed, 31 insertions(+), 2 deletions(-) diff --git a/src/components/ReactFlowERD.tsx b/src/components/ReactFlowERD.tsx index 3ce2de2..f567adf 100644 --- a/src/components/ReactFlowERD.tsx +++ b/src/components/ReactFlowERD.tsx @@ -425,11 +425,13 @@ const ReactFlowERDInner = forwardRef(functio }, // Show different labels based on relationship type // N:N: Show intersection table name and cardinality badge - // 1:N/N:1: Show referencing attribute name + // 1:N/N:1: Show referencing attribute name (if showRelationshipLookupIds is enabled) label: rel.type === 'N:N' ? `[N:N] ${rel.intersectEntityName || rel.schemaName}` - : rel.referencingAttribute || '', + : colorSettings.showRelationshipLookupIds ?? true + ? rel.referencingAttribute || '' + : '', // Pass offset data for draggable edges data: { offset: edgeOffsets?.[rel.schemaName] ?? { x: 0, y: 0 }, diff --git a/src/components/SidebarSettings.tsx b/src/components/SidebarSettings.tsx index 7dee2a2..1f51cd2 100644 --- a/src/components/SidebarSettings.tsx +++ b/src/components/SidebarSettings.tsx @@ -240,6 +240,29 @@ export const SidebarSettings = memo(function SidebarSettings({ + {/* Show Relationship Lookup IDs Checkbox */} +
+ +
+ {/* Conditional Type Color Pickers */} {useRelationshipTypeColors && ( <> diff --git a/src/hooks/useERDState.ts b/src/hooks/useERDState.ts index c3da19a..d9151e3 100644 --- a/src/hooks/useERDState.ts +++ b/src/hooks/useERDState.ts @@ -104,6 +104,7 @@ export function useERDState({ entities, relationships }: UseERDStateProps) { manyToOneColor: '#06b6d4', manyToManyColor: '#8b5cf6', fieldLabelMode: 'displayName', + showRelationshipLookupIds: true, }); // Features diff --git a/src/types/erdTypes.ts b/src/types/erdTypes.ts index 5d29a63..e74d616 100644 --- a/src/types/erdTypes.ts +++ b/src/types/erdTypes.ts @@ -57,6 +57,9 @@ export interface ColorSettings { // Field label display mode fieldLabelMode: FieldLabelMode; + + // Relationship label display + showRelationshipLookupIds?: boolean; } /** Valid range for lineThickness */ From 1662a7172899088fab36acbe999bad2c418d90ce Mon Sep 17 00:00:00 2001 From: Allan De Castro <9749002+allandecastro@users.noreply.github.com> Date: Mon, 25 May 2026 09:56:23 +0000 Subject: [PATCH 3/6] Finalize hide lookup IDs feature: export, URL share state, tests, docs --- README.md | 3 +- src/App.tsx | 9 +++++ src/hooks/__tests__/useERDState.test.tsx | 1 + src/types/erdTypes.ts | 2 +- src/utils/__tests__/drawioExport.test.ts | 15 +++++++++ src/utils/__tests__/urlStateCodec.test.ts | 41 +++++++++++++++++++++++ src/utils/drawioExport.ts | 22 ++++++++---- src/utils/urlStateCodec.ts | 25 +++++++++++++- 8 files changed, 108 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index cc8041d..d1057ea 100644 --- a/README.md +++ b/README.md @@ -295,6 +295,7 @@ Visualize relationships with professional database notation styles: 2. Scroll to **Color Settings** section 3. Adjust **Line Notation Style**, **Line Stroke Style**, and **Line Thickness** 4. Enable **Color by Relationship Type** for type-specific colors +5. Toggle **Show Lookup IDs on Relationship Lines** to hide or show lookup field information All settings are automatically saved in snapshots and shareable URLs. @@ -618,5 +619,3 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file

Made with ❤️ for the Power Platform Community

- - diff --git a/src/App.tsx b/src/App.tsx index 821f357..8745300 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -53,6 +53,7 @@ import { decodeStateFromURL, expandCompactState, getFieldLabelModeFromCompact, + getShowRelationshipLookupIdsFromCompact, getShareBaseUrl, getStateHash, buildMinimalShareState, @@ -253,12 +254,16 @@ export default function ERDVisualizer({ const expandedState = expandCompactState(filteredState); const currentState = state.getSerializableState(); const urlFieldLabelMode = getFieldLabelModeFromCompact(filteredState) ?? 'displayName'; + const urlShowRelationshipLookupIds = getShowRelationshipLookupIdsFromCompact(filteredState); const mergedState = { ...currentState, ...expandedState, colorSettings: { ...currentState.colorSettings, fieldLabelMode: urlFieldLabelMode, + ...(urlShowRelationshipLookupIds !== undefined && { + showRelationshipLookupIds: urlShowRelationshipLookupIds, + }), }, }; state.restoreState(mergedState); @@ -273,12 +278,16 @@ export default function ERDVisualizer({ const expandedState = expandCompactState(decoded.state); const currentState = state.getSerializableState(); const urlFieldLabelMode = getFieldLabelModeFromCompact(decoded.state) ?? 'displayName'; + const urlShowRelationshipLookupIds = getShowRelationshipLookupIdsFromCompact(decoded.state); const mergedState = { ...currentState, ...expandedState, colorSettings: { ...currentState.colorSettings, fieldLabelMode: urlFieldLabelMode, + ...(urlShowRelationshipLookupIds !== undefined && { + showRelationshipLookupIds: urlShowRelationshipLookupIds, + }), }, }; state.restoreState(mergedState); diff --git a/src/hooks/__tests__/useERDState.test.tsx b/src/hooks/__tests__/useERDState.test.tsx index 9ee7d25..64431ee 100644 --- a/src/hooks/__tests__/useERDState.test.tsx +++ b/src/hooks/__tests__/useERDState.test.tsx @@ -437,6 +437,7 @@ describe('useERDState', () => { manyToOneColor: '#06b6d4', manyToManyColor: '#8b5cf6', fieldLabelMode: 'displayName', + showRelationshipLookupIds: true, }); }); diff --git a/src/types/erdTypes.ts b/src/types/erdTypes.ts index e74d616..6f5c836 100644 --- a/src/types/erdTypes.ts +++ b/src/types/erdTypes.ts @@ -85,7 +85,7 @@ export function parseColorSettingValue( if (Number.isNaN(parsed)) return LINE_THICKNESS_DEFAULT; return Math.max(LINE_THICKNESS_MIN, Math.min(LINE_THICKNESS_MAX, parsed)); } - if (key === 'useRelationshipTypeColors') { + if (key === 'useRelationshipTypeColors' || key === 'showRelationshipLookupIds') { return value === 'true'; } return value; diff --git a/src/utils/__tests__/drawioExport.test.ts b/src/utils/__tests__/drawioExport.test.ts index 492dde9..6689d9d 100644 --- a/src/utils/__tests__/drawioExport.test.ts +++ b/src/utils/__tests__/drawioExport.test.ts @@ -152,6 +152,21 @@ describe('drawioExport', () => { expect(text).toContain('N:1'); // Cardinality }); + it('should omit lookup IDs in relationship labels when disabled', async () => { + const blob = await exportToDrawio({ + ...baseOptions, + colorSettings: { + ...mockColorSettings, + showRelationshipLookupIds: false, + }, + }); + const text = await blobToText(blob); + + expect(text).toContain('contact_account'); + expect(text).not.toContain('parentcustomerid'); + expect(text).not.toContain('→'); + }); + it('should call progress callback during export', async () => { const progressCalls: Array<{ progress: number; message: string }> = []; diff --git a/src/utils/__tests__/urlStateCodec.test.ts b/src/utils/__tests__/urlStateCodec.test.ts index e3ceb61..3427d70 100644 --- a/src/utils/__tests__/urlStateCodec.test.ts +++ b/src/utils/__tests__/urlStateCodec.test.ts @@ -118,6 +118,19 @@ describe('urlStateCodec', () => { expect(decoded.state?.d).toBe(true); }); + it('should preserve showRelationshipLookupIds when false', () => { + const stateWithLookupIdsHidden = { + ...mockState, + showRelationshipLookupIds: false, + }; + + const encoded = encodeStateToURL(stateWithLookupIdsHidden); + const decoded = decodeStateFromURL(encoded); + + expect(decoded.success).toBe(true); + expect(decoded.state?.sl).toBe(false); + }); + it('should return error for invalid/corrupted URLs', () => { expect(decodeStateFromURL('invalid-base64').success).toBe(false); expect(decodeStateFromURL('').success).toBe(false); @@ -191,6 +204,16 @@ describe('urlStateCodec', () => { expect(expanded.isDarkMode).toBe(true); }); + it('should restore showRelationshipLookupIds from compact state', () => { + const compactWithLookupHidden: CompactState = { + ...compactState, + sl: false, + }; + + const expanded = expandCompactState(compactWithLookupHidden); + expect(expanded.colorSettings).toEqual({ showRelationshipLookupIds: false }); + }); + it('should handle multiple entities', () => { const multiEntityState: CompactState = { e: ['account', 'contact', 'opportunity'], @@ -903,6 +926,24 @@ describe('urlStateCodec', () => { expect(minimal).not.toHaveProperty('fieldLabelMode'); }); + it('should omit showRelationshipLookupIds when true (default)', () => { + const minimal = buildMinimalShareState(fullState); + expect(minimal).not.toHaveProperty('showRelationshipLookupIds'); + }); + + it('should include showRelationshipLookupIds when false', () => { + const stateWithHiddenIds = { + ...fullState, + colorSettings: { + ...fullState.colorSettings, + showRelationshipLookupIds: false, + }, + }; + const minimal = buildMinimalShareState(stateWithHiddenIds); + + expect(minimal.showRelationshipLookupIds).toBe(false); + }); + it('should produce a valid state for encodeStateToURL', () => { const minimal = buildMinimalShareState(fullState); diff --git a/src/utils/drawioExport.ts b/src/utils/drawioExport.ts index 277d32d..90a96dc 100644 --- a/src/utils/drawioExport.ts +++ b/src/utils/drawioExport.ts @@ -241,7 +241,10 @@ function calculateEntityHeight( * Format multi-line relationship label for connector * Escapes each component to prevent XML injection */ -function formatRelationshipLabel(relationship: EntityRelationship): string { +function formatRelationshipLabel( + relationship: EntityRelationship, + showLookupIds: boolean = true +): string { const lines: string[] = []; // Line 1: Cardinality (escaped) @@ -250,8 +253,8 @@ function formatRelationshipLabel(relationship: EntityRelationship): string { // Line 2: Schema name (escaped) lines.push(escapeXml(relationship.schemaName)); - // Line 3: Field mapping (if available, escape each part) - if (relationship.referencingAttribute && relationship.referencedAttribute) { + // Line 3: Field mapping (if available and enabled, escape each part) + if (showLookupIds && relationship.referencingAttribute && relationship.referencedAttribute) { lines.push( `${escapeXml(relationship.referencingAttribute)} → ${escapeXml(relationship.referencedAttribute)}` ); @@ -476,9 +479,10 @@ function generateConnectorCell( id: string, sourceId: string, targetId: string, - relationship: EntityRelationship + relationship: EntityRelationship, + showLookupIds: boolean = true ): string { - const label = formatRelationshipLabel(relationship); + const label = formatRelationshipLabel(relationship, showLookupIds); return ` @@ -600,7 +604,13 @@ function generateDrawioXml(options: DrawioExportOptions): string { if (sourceId && targetId) { const id = generateId('connector', index); - cells[cellIndex++] = generateConnectorCell(id, sourceId, targetId, rel); + cells[cellIndex++] = generateConnectorCell( + id, + sourceId, + targetId, + rel, + colorSettings.showRelationshipLookupIds ?? true + ); } // Report progress for relationships (80-95% range) diff --git a/src/utils/urlStateCodec.ts b/src/utils/urlStateCodec.ts index 5114eed..dac9ae1 100644 --- a/src/utils/urlStateCodec.ts +++ b/src/utils/urlStateCodec.ts @@ -35,6 +35,7 @@ export interface CompactState { gf?: string; // groupFilter (optional, only present when not 'all') sf?: Record; // selectedFields (optional, only present when non-empty) fo?: Record; // fieldOrder (optional, only present when non-empty) + sl?: boolean; // showRelationshipLookupIds (optional, only present when false) } /** @@ -94,6 +95,9 @@ export function buildMinimalShareState(state: SerializableState): MinimalShareSt state.colorSettings.fieldLabelMode !== 'displayName' && { fieldLabelMode: state.colorSettings.fieldLabelMode, }), + ...(state.colorSettings?.showRelationshipLookupIds === false && { + showRelationshipLookupIds: state.colorSettings.showRelationshipLookupIds, + }), ...(filteredFields && Object.keys(filteredFields).length > 0 && { selectedFields: filteredFields, @@ -149,6 +153,7 @@ export function encodeStateToURL(state: { entityColorOverrides?: Record; groupNames?: Record; fieldLabelMode?: FieldLabelMode; + showRelationshipLookupIds?: boolean; groupFilter?: string; selectedFields?: Record; fieldOrder?: Record; @@ -203,6 +208,11 @@ export function encodeStateToURL(state: { compactState.fo = state.fieldOrder; } + // Only include showRelationshipLookupIds if false (default is true) + if (state.showRelationshipLookupIds === false) { + compactState.sl = false; + } + // Serialize to JSON const json = JSON.stringify(compactState); @@ -292,9 +302,13 @@ export function expandCompactState(compact: CompactState): Partial Date: Mon, 25 May 2026 10:11:07 +0000 Subject: [PATCH 4/6] Fix lint error in useDataverseData effect --- src/hooks/useDataverseData.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/hooks/useDataverseData.ts b/src/hooks/useDataverseData.ts index 0dd4d3a..4767125 100644 --- a/src/hooks/useDataverseData.ts +++ b/src/hooks/useDataverseData.ts @@ -228,6 +228,9 @@ export function useDataverseData(options?: UseDataverseDataOptions): UseDatavers ); useEffect(() => { + // Fetch initial data on mount. This is intentionally done here to + // initialize hook state once and avoid duplicating the fetching logic. + // eslint-disable-next-line react-hooks/set-state-in-effect fetchData(); }, [fetchData]); From 35f5cb33bfe7ecedcfb4f87bcb54e6d8b11f4e7c Mon Sep 17 00:00:00 2001 From: Allan De Castro <9749002+allandecastro@users.noreply.github.com> Date: Mon, 25 May 2026 10:18:01 +0000 Subject: [PATCH 5/6] chore: format ReactFlowERD.tsx for Prettier --- src/components/ReactFlowERD.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ReactFlowERD.tsx b/src/components/ReactFlowERD.tsx index f567adf..9029928 100644 --- a/src/components/ReactFlowERD.tsx +++ b/src/components/ReactFlowERD.tsx @@ -429,7 +429,7 @@ const ReactFlowERDInner = forwardRef(functio label: rel.type === 'N:N' ? `[N:N] ${rel.intersectEntityName || rel.schemaName}` - : colorSettings.showRelationshipLookupIds ?? true + : (colorSettings.showRelationshipLookupIds ?? true) ? rel.referencingAttribute || '' : '', // Pass offset data for draggable edges From 0ff7b1470a30a159a8010a662d6c577fc64b7b6c Mon Sep 17 00:00:00 2001 From: Allan De Castro <9749002+allandecastro@users.noreply.github.com> Date: Mon, 25 May 2026 10:26:25 +0000 Subject: [PATCH 6/6] fix: allow partial colorSettings in URL state expansion --- src/utils/urlStateCodec.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/utils/urlStateCodec.ts b/src/utils/urlStateCodec.ts index dac9ae1..a684a24 100644 --- a/src/utils/urlStateCodec.ts +++ b/src/utils/urlStateCodec.ts @@ -5,7 +5,7 @@ import * as LZString from 'lz-string'; import type { EntityPosition } from '@/types'; -import type { LayoutMode, FieldLabelMode } from '@/types/erdTypes'; +import type { LayoutMode, FieldLabelMode, ColorSettings } from '@/types/erdTypes'; import type { SerializableState } from '@/types/snapshotTypes'; const CODEC_VERSION = '1.0.0'; @@ -281,7 +281,11 @@ export function decodeStateFromURL(hash: string): DecodeResult { * @param compact Compact state from URL * @returns Partial serializable state for restoreState() */ -export function expandCompactState(compact: CompactState): Partial { +type ExpandedURLState = Omit, 'colorSettings'> & { + colorSettings?: Partial; +}; + +export function expandCompactState(compact: CompactState): ExpandedURLState { return { selectedEntities: compact.e, entityPositions: expandPositions(compact.p),