From aeead4bc6c320613f27a53c8e895e5152ab2cb35 Mon Sep 17 00:00:00 2001 From: Lucio Lelii Date: Wed, 30 Sep 2026 08:23:55 +0200 Subject: [PATCH] feat(editor): name connected storage resources --- src/app/shared/nodes/generic-node/generic-node.css | 2 ++ src/app/shared/nodes/generic-node/generic-node.html | 4 ++-- src/app/shared/nodes/generic-node/generic-node.ts | 10 ++++++++++ src/app/shared/nodes/task-step-node/task-step-node.css | 2 ++ .../shared/nodes/task-step-node/task-step-node.html | 4 ++-- src/app/shared/nodes/task-step-node/task-step-node.ts | 10 ++++++++++ src/app/utilities/rete-editor.spec.ts | 3 ++- src/app/utilities/rete-editor.ts | 9 ++++++++- 8 files changed, 38 insertions(+), 6 deletions(-) diff --git a/src/app/shared/nodes/generic-node/generic-node.css b/src/app/shared/nodes/generic-node/generic-node.css index 267feb7..e091879 100644 --- a/src/app/shared/nodes/generic-node/generic-node.css +++ b/src/app/shared/nodes/generic-node/generic-node.css @@ -1082,6 +1082,8 @@ border-color: #14b8a6; background: linear-gradient(135deg, #ccfbf1 0%, #99f6e4 100%); box-shadow: inset 0 0 0 1px rgba(13, 148, 136, 0.14); + letter-spacing: 0.04em; + text-transform: none; } .llm-pill-resource--connected .bi { diff --git a/src/app/shared/nodes/generic-node/generic-node.html b/src/app/shared/nodes/generic-node/generic-node.html index 5225744..30dbcb9 100644 --- a/src/app/shared/nodes/generic-node/generic-node.html +++ b/src/app/shared/nodes/generic-node/generic-node.html @@ -231,7 +231,7 @@ refComponent class="llm-pill llm-pill-input llm-pill-resource" [class.llm-pill-resource--connected]="resourceLinkIsConnected" - [attr.title]="resourceLinkIsConnected ? 'Resource connected — drag another Resource here to replace it' : 'Drag a Resource here'" + [attr.title]="resourceLinkIsConnected ? 'Connected to ' + connectedResourceName + ' — drag another Resource here to replace it' : 'Drag a Resource here'" [data]="{ type: 'socket', side: 'input', @@ -243,7 +243,7 @@ [emit]="emit"> @if (resourceLinkIsConnected) { - Resource connected + Connected to {{ connectedResourceName }} } @else { Resource } diff --git a/src/app/shared/nodes/generic-node/generic-node.ts b/src/app/shared/nodes/generic-node/generic-node.ts index acb53a0..7b4150c 100644 --- a/src/app/shared/nodes/generic-node/generic-node.ts +++ b/src/app/shared/nodes/generic-node/generic-node.ts @@ -283,6 +283,16 @@ export class GenericNodeComponent implements OnDestroy { return typeof value === 'string' && value.trim().length > 0; } + /** The Resource node named by the handle, retained by the canvas when the link is drawn. */ + get connectedResourceName(): string { + const nodeData = this.data?.data as Record | undefined; + const name = nodeData?.['__connectedResourceName']; + if (typeof name === 'string' && name.trim()) return name.trim(); + const field = nodeData?.['__resourceRefField']; + const id = typeof field === 'string' ? nodeData?.['specificConfiguration']?.[field] : null; + return typeof id === 'string' && id.trim() ? id : 'Resource'; + } + get dependantOutput(): RenderedSocketPort | null { const output = this.data?.outputs?.[FLOW_DEPENDANT_PORT_KEY]; return output diff --git a/src/app/shared/nodes/task-step-node/task-step-node.css b/src/app/shared/nodes/task-step-node/task-step-node.css index 152f6be..236ca9f 100644 --- a/src/app/shared/nodes/task-step-node/task-step-node.css +++ b/src/app/shared/nodes/task-step-node/task-step-node.css @@ -885,6 +885,8 @@ button.llm-node-bias-summary:focus-visible { border-color: #14b8a6; background: linear-gradient(135deg, #ccfbf1 0%, #99f6e4 100%); box-shadow: inset 0 0 0 1px rgba(13, 148, 136, 0.14); + letter-spacing: 0.04em; + text-transform: none; } .llm-pill-resource--connected .bi { diff --git a/src/app/shared/nodes/task-step-node/task-step-node.html b/src/app/shared/nodes/task-step-node/task-step-node.html index ed6f727..b294d5d 100644 --- a/src/app/shared/nodes/task-step-node/task-step-node.html +++ b/src/app/shared/nodes/task-step-node/task-step-node.html @@ -259,7 +259,7 @@ refComponent class="llm-pill llm-pill-input llm-pill-resource" [class.llm-pill-resource--connected]="storageLinkIsConnected" - [attr.title]="storageLinkIsConnected ? 'Resource connected' : 'Resource'" + [attr.title]="storageLinkIsConnected ? 'Connected to ' + connectedResourceName : 'Resource'" [data]="{ type: 'socket', side: 'input', @@ -271,7 +271,7 @@ [emit]="emit"> @if (storageLinkIsConnected) { - Resource connected + Connected to {{ connectedResourceName }} } @else { Resource } diff --git a/src/app/shared/nodes/task-step-node/task-step-node.ts b/src/app/shared/nodes/task-step-node/task-step-node.ts index e176ce9..57863d0 100644 --- a/src/app/shared/nodes/task-step-node/task-step-node.ts +++ b/src/app/shared/nodes/task-step-node/task-step-node.ts @@ -181,6 +181,16 @@ export class TaskStepNodeComponent { return typeof value === 'string' && value.trim().length > 0; } + /** The Resource node named by the handle, retained by the canvas when the link is drawn. */ + get connectedResourceName(): string { + const nodeData = this.data?.data as Record | undefined; + const name = nodeData?.['__connectedResourceName']; + if (typeof name === 'string' && name.trim()) return name.trim(); + const field = nodeData?.['__resourceRefField']; + const id = typeof field === 'string' ? nodeData?.['specificConfiguration']?.[field] : null; + return typeof id === 'string' && id.trim() ? id : 'Resource'; + } + get dependantOutput(): { key: string; socket: ClassicPreset.Socket } | null { const output = this.data?.outputs?.[FLOW_DEPENDANT_PORT_KEY]; return output diff --git a/src/app/utilities/rete-editor.spec.ts b/src/app/utilities/rete-editor.spec.ts index 97abb85..3b37cfe 100644 --- a/src/app/utilities/rete-editor.spec.ts +++ b/src/app/utilities/rete-editor.spec.ts @@ -97,7 +97,7 @@ describe('the link between a resource and a step that uses it', () => { s3.data = { id: 's3', typeName: 'S3Storage', nodeFamily: 'resource' }; s3.addOutput(FLOW_RESOURCE_PORT_KEY, new ClassicPreset.Output(socket)); const postgres = new ClassicPreset.Node('PostgreSQL') as any; - postgres.data = { id: 'postgres', typeName: 'PostgresStorage', nodeFamily: 'resource' }; + postgres.data = { id: 'postgres', name: 'Analytics database', typeName: 'PostgresStorage', nodeFamily: 'resource' }; postgres.addOutput(FLOW_RESOURCE_PORT_KEY, new ClassicPreset.Output(socket)); const operation = new ClassicPreset.Node('Operation') as any; operation.data = { @@ -124,6 +124,7 @@ describe('the link between a resource and a step that uses it', () => { expect(operation.data.specificConfiguration).toMatchObject({ storageNode: 'postgres', resourceType: 'PostgresStorage', operationId: null, settings: {} }); + expect(operation.data.__connectedResourceName).toBe('Analytics database'); expect(area.update).toHaveBeenCalledWith('node', operation.id); }); diff --git a/src/app/utilities/rete-editor.ts b/src/app/utilities/rete-editor.ts index 2308348..71ff6b8 100644 --- a/src/app/utilities/rete-editor.ts +++ b/src/app/utilities/rete-editor.ts @@ -872,10 +872,17 @@ export async function syncResourceLinkPresentation( const resourceType = typeof source?.data?.typeName === 'string' && source.data.typeName.trim() ? source.data.typeName : null; + const resourceName = typeof source?.data?.name === 'string' && source.data.name.trim() + ? source.data.name + : typeof source?.data?.specificConfiguration?.name === 'string' && source.data.specificConfiguration.name.trim() + ? source.data.specificConfiguration.name + : resourceId; const configuration = targetData['specificConfiguration'] as Record | undefined; const resourceChanged = configuration?.[reference.field] !== resourceId; - if (!resourceChanged && configuration?.['resourceType'] === resourceType) return; + const nameChanged = targetData['__connectedResourceName'] !== resourceName; + if (!resourceChanged && configuration?.['resourceType'] === resourceType && !nameChanged) return; + targetData['__connectedResourceName'] = resourceName; targetData['specificConfiguration'] = { ...(configuration ?? {}), [reference.field]: resourceId,