Skip to content

Conversation

@james-strauss-uwa
Copy link
Collaborator

@james-strauss-uwa james-strauss-uwa commented Dec 18, 2025

Added a 'changeable' flag to Field. This flag specifies whether the name (display text) of the Field can be changed. Many DALiuGE components will stop working if the name of certain fields is changed.

By default, changeable is true. But if the field is on a node that is part of a palette, then changeable is set to false. New fields added to Nodes by users will remain changeable.

I've added an icon in the "flags" column of the ParameterTable, to allow "expert" users to change the status of the flag. If "changeable" is false, then the input element in the "Attribute Name" column of the table is disabled.

I also fixed a related bug, where intermediate nodes, automatically inserted when the user drags an edge from Application to Application, are now more like the original Palette node, and not custom-modified to suit the edge.

Summary by Sourcery

Introduce a changeable flag on fields to control whether their display name can be edited and adjust node/edge handling and UI accordingly.

New Features:

  • Add a changeable flag to Field to indicate whether a field name can be modified and expose helpers to query and toggle it.
  • Allow expert users to toggle the changeable status from the node parameter table via new flag icons.
  • Support type-based port lookup across arbitrary field usages via a generalized findPortByMatchingType API.

Bug Fixes:

  • Ensure automatically inserted intermediary data nodes reuse appropriate ports and align more closely with their palette definitions instead of using custom ports.

Enhancements:

  • Propagate palette-awareness into Field creation so palette node fields and ports are non-changeable by default while user-added fields remain changeable.
  • Prevent renaming of non-changeable ports and fields in the parameter table by disabling the display-name input when locked.
  • Refine edge creation between applications to consider InputOutput ports alongside directional ports and to respect field changeability when synchronizing names.
  • Update DALiuGE branch node field names to clearer branchTrue/branchFalse identifiers and add a FILE_PATH field name constant.
  • Adjust palette node deserialization to mark nodes as palette nodes and widen the parameter table flags column to accommodate the new icon.

Tests:

  • Extend the Creating a Simple Graph end-to-end test to toggle the changeable flag before renaming a port.

@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Dec 18, 2025

Reviewer's Guide

Adds a runtime-only changeable flag to Field to control whether field/port display names can be edited, wires this through JSON deserialisation and the parameter table UI (including a new toggle icon), and adjusts intermediate data node creation and port matching so palette-derived nodes/ports behave more like their palette definitions and are no longer silently renamed when they should be immutable.

Sequence diagram for dragging an edge between applications with changeable-aware intermediate node

sequenceDiagram
    actor User
    participant Eagle
    participant SourceNode as Node_src
    participant SourcePort as Field_srcPort
    participant DestNode as Node_dest
    participant DestPort as Field_destPort
    participant InterTemplate as Node_paletteTemplate
    participant NewNode as Node_intermediary
    participant IOField as Field_inputOutput

    User->>Eagle: Drag edge from SourceNode to DestNode
    activate Eagle

    Eagle->>SourceNode: getPort()
    SourceNode-->>Eagle: SourcePort

    Eagle->>DestNode: findPortByMatchingType(type, [OutputPort|InputPort, InputOutput])
    DestNode-->>Eagle: DestPort

    alt intermediaryComponent exists
        Eagle->>InterTemplate: clone()
        InterTemplate-->>Eagle: intermediaryClone
        Eagle->>Eagle: generateNodeId()
        Eagle->>Eagle: addNodeToLogicalGraph(intermediaryClone, id, Default)
        Eagle-->>Eagle: [NewNode]
    else create default Data node
        Eagle->>Eagle: new Node("Data", ...)
        Eagle-->>Eagle: NewNode
    end

    Eagle->>NewNode: setPosition(x, y)
    Eagle->>NewNode: setName(SourcePort.getDisplayText())

    Eagle->>NewNode: findPortByMatchingType(SourcePort.getType(), [InputOutput])
    NewNode-->>Eagle: IOField or null

    alt IOField is null
        Eagle-->>User: Error notification "Unable to find suitable port"
    else IOField found
        Eagle->>IOField: isChangeable()
        alt IOField is changeable
            Eagle->>IOField: setDisplayText(SourcePort.getDisplayText())
            Eagle->>IOField: setDescription(SourcePort.getDescription())
        else IOField not changeable
            Eagle->>Eagle: Keep IOField name/description from palette
        end

        Eagle->>Eagle: create Edge(src, SourcePort, NewNode, IOField)
        Eagle->>Eagle: create Edge(NewNode, IOField, DestNode, DestPort)
        Eagle-->>User: Intermediate data node and edges created
    end
    deactivate Eagle
Loading

Class diagram for Field changeable flag and related Node/Eagle interactions

classDiagram
    class Field {
        -ko.Observable~Node~ node
        -ko.Observable~Map~ edges
        -ko.Observable~boolean~ changeable
        -ko.Observable~number~ inputX
        -ko.Observable~number~ inputY
        -ko.Observable~number~ outputX
        -ko.Observable~number~ outputY
        +isChangeable() boolean
        +toggleChangeable() Field
        +reset() Field
        +clone() Field
        +copyAttributesAndValuesTo(id FieldId, node Node) Field
        +fromOJSJson(data any, changeable boolean) Field$  
        +fromOJSJsonPort(data any, changeable boolean) Field$  
        +fromV4Json(data any, changeable boolean) Field$
    }

    class Node {
        +getFields() Field[]
        +getInputPorts() Field[]
        +getOutputPorts() Field[]
        +findPortByMatchingType(type string, usages Daliuge.FieldUsage[]) Field
        +setName(name string) void
        +setPosition(x number, y number) void
        +addField(field Field) void
        +fromV4Json(nodeData any, errorsWarnings any, isPaletteNode boolean) Node$ 
        +fromOJSJson(nodeData any, errorsWarnings any, isPaletteNode boolean) Node$
    }

    class Eagle {
        +addNodeToLogicalGraph(node Node, id NodeId, mode Eagle.AddNodeMode) Promise~Node[]~
        +logicalGraph() LogicalGraph
        +handleEdgeDropBetweenApplications() void
    }

    class ParameterTable {
        +getActiveColumnVisibility() any
        +getNodeLockedState(field Field) boolean
        +select(name string, column string, field Field, index number) void
        +requestAddField(field Field) void
    }

    class Daliuge_FieldUsage {
        <<enumeration>>
        InputPort
        OutputPort
        InputOutput
        NoPort
    }

    class Daliuge_FieldName {
        <<enumeration>>
        TRUE
        FALSE
        FILE_PATH
        GROUP_START
        GROUP_END
    }

    class Daliuge_Constants {
        +branchTrueField Field
        +branchFalseField Field
        +groupStartField Field
        +groupEndField Field
    }

    Field --> Node : node
    Field --> "*" Edge : edges
    Node "*" --> Field : fields
    Eagle --> Node : creates/uses
    ParameterTable --> Field : toggles changeable
    Node --> Daliuge_FieldUsage : uses in findPortByMatchingType
    Daliuge_Constants --> Field : palette fields
    Daliuge_Constants --> Daliuge_FieldName
    Daliuge_Constants --> Daliuge_FieldUsage
Loading

File-Level Changes

Change Details Files
Introduce a runtime-only changeable flag on Field and propagate it through cloning, copying, and JSON (OJS/V4) construction so palette-based fields/ports are non-changeable by default while user-added fields remain changeable.
  • Add changeable observable to Field, defaulting to true, with isChangeable and toggleChangeable accessors.
  • Reset changeable in Field.reset and copy it in clone, cloneShallow, and copyFrom so the flag is preserved across field duplication and ID changes.
  • Extend Field.fromOJSJson, Field.fromOJSJsonPort, and Field.fromV4Json to accept a changeable boolean and set it on the created field.
  • Update all Node.from*Json callers (OJS/V4, app fields, ports, local ports) to pass !isPaletteNode so palette nodes/ports become non-changeable while graph/user nodes stay changeable.
  • Update Palette.fromV4Json to call Node.fromV4Json with isPaletteNode=true to ensure palette nodes’ fields/ports are created as non-changeable.
src/Field.ts
src/Node.ts
src/Palette.ts
Update parameter table UI to respect the changeable flag and expose a toggle control for expert users, plus small layout tweaks.
  • Disable the Attribute Name input in node_parameter_table.html when either the node is locked or the bound field is not changeable (!$data.isChangeable()).
  • Add a new changeableFlag button in the flags column that toggles Field.changeable, showing different icons (label/label_off) and tooltips based on the flag value, only when component editing is allowed.
  • Increase .parameter_table_flags column width to accommodate the new flag icon button.
templates/node_parameter_table.html
static/tables.css
Ensure intermediate data nodes created when dragging edges between applications respect palette definitions, keep their IO ports, and only rename ports when allowed by changeable.
  • Change Node.findPortByMatchingType to accept a list of Daliuge.FieldUsage values and search across getFields() instead of separate input/output lists.
  • Update intermediate-node creation in Eagle to use Node.clone().setId(...) instead of Utils.duplicateNode, and to add the node via addNodeToLogicalGraph(...) while explicitly setting its position afterward.
  • Stop deleting and recreating ports on the intermediary node; instead, locate the existing InputOutput port that matches the source port type via the new findPortByMatchingType signature and error if none is found.
  • Guard all automatic renaming of ports (both when aligning src/dest node names and when configuring the intermediary IO port) with isChangeable() checks so non-changeable palette ports do not get renamed.
  • Adjust edge creation to use the found inputOutputPort rather than a newly created synthetic port.
src/Eagle.ts
src/Node.ts
Strengthen edge-drop source/destination port matching and update DALiuGE constants for branch ports and file path.
  • When resolving realDestPort during edge drop, call findPortByMatchingType with a usage array `[OutputPort
InputPort, InputOutput]depending on whether the source is an input, allowing matching IO ports as well.</li><li>AddFILE_PATHtoDaliuge.FieldNameenum and renamebranchYesField/branchNoFieldtobranchTrueField/branchFalseField, updating their uses in the Branch palette definition.</li><li>Update the Branch palette node’s fields` array to use the renamed branch fields.
Extend the simple graph e2e test to cover toggling the changeable flag before renaming a port.
  • After clicking the file’s input port in creatingASimpleGraph.spec.ts, click the .changeableFlag button in the highlighted row to set the port as changeable.
  • Then perform the existing port rename to testInput, implicitly asserting that the UI now allows the rename when the flag is enabled.
e2e/creatingASimpleGraph.spec.ts

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@james-strauss-uwa james-strauss-uwa self-assigned this Dec 18, 2025
Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey there - I've reviewed your changes - here's some feedback:

  • The new changeable flag is explicitly treated as runtime-only and is reset in reuse(), so any user toggles or palette-derived non-changeable state will not survive serialization/deserialization; consider whether this should be persisted or at least documented in code to avoid surprising behavior on reload.
  • In the intermediary data-node creation flow you now rely on addNodeToLogicalGraph(...)[0] and then call setPosition afterwards; if addNodeToLogicalGraph or downstream logic assumes the initial position argument, it may be cleaner to thread dataComponentPosition through rather than ignoring it and correcting later.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The new `changeable` flag is explicitly treated as runtime-only and is reset in `reuse()`, so any user toggles or palette-derived non-changeable state will not survive serialization/deserialization; consider whether this should be persisted or at least documented in code to avoid surprising behavior on reload.
- In the intermediary data-node creation flow you now rely on `addNodeToLogicalGraph(...)[0]` and then call `setPosition` afterwards; if `addNodeToLogicalGraph` or downstream logic assumes the initial position argument, it may be cleaner to thread `dataComponentPosition` through rather than ignoring it and correcting later.

## Individual Comments

### Comment 1
<location> `src/Eagle.ts:4577-4580` </location>
<code_context>
             } else {
-                intermediaryComponent = Utils.duplicateNode(intermediaryComponent);
+                //intermediaryComponent = Utils.duplicateNode(intermediaryComponent);
+                intermediaryComponent = intermediaryComponent.clone().setId(Utils.generateNodeId());
             }

</code_context>

<issue_to_address>
**issue (bug_risk):** Intermediary node ID is generated twice and may become inconsistent.

`intermediaryComponent` gets a new ID in `clone().setId(Utils.generateNodeId())`, and then a different ID is passed to `addNodeToLogicalGraph(intermediaryComponent, Utils.generateNodeId(), ...)`. Depending on how `setId` and `addNodeToLogicalGraph` work, this can desync the component’s internal ID from the graph’s ID or cause collisions. Consider generating the ID once and reusing it, or centralizing ID assignment in a single place.
</issue_to_address>

### Comment 2
<location> `src/Field.ts:483` </location>
<code_context>
         this.isEvent(false);
         this.node(null);
         this.edges().clear();
+        this.changeable(true);

         return this;
</code_context>

<issue_to_address>
**issue (bug_risk):** Resetting a field always makes it changeable, potentially overriding intended immutability.

`reset()` always sets `changeable` to `true`. If this is called on fields that should remain non-changeable (e.g., palette-derived/system fields), it will unexpectedly allow editing. Consider preserving the existing `changeable` value or providing a variant/parameter to control whether it should be restored.
</issue_to_address>

### Comment 3
<location> `src/Daliuge.ts:176` </location>
<code_context>
-    export const branchYesField = new Field(null, FieldName.TRUE, "", "", "The affirmative output from a branch node", false, DataType.Object, false, [], false, FieldType.Component, FieldUsage.OutputPort);
-    export const branchNoField  = new Field(null, FieldName.FALSE,  "", "", "he negative output from a branch node", false, DataType.Object, false, [], false, FieldType.Component, FieldUsage.OutputPort);
+    export const branchTrueField = new Field(null, FieldName.TRUE, "", "", "The affirmative output from a branch node", false, DataType.Object, false, [], false, FieldType.Component, FieldUsage.OutputPort);
+    export const branchFalseField  = new Field(null, FieldName.FALSE,  "", "", "he negative output from a branch node", false, DataType.Object, false, [], false, FieldType.Component, FieldUsage.OutputPort);

     export const dropClassField = new Field(null, FieldName.DROP_CLASS, "", "", "", false, DataType.String, false, [], false, FieldType.Component, FieldUsage.NoPort);
</code_context>

<issue_to_address>
**nitpick (typo):** Minor typo in branch false field description string.

The description for `branchFalseField` is missing the initial `T` (currently "he negative output from a branch node"). Please correct this user-facing text while you’re updating this line.

```suggestion
    export const branchFalseField  = new Field(null, FieldName.FALSE,  "", "", "The negative output from a branch node", false, DataType.Object, false, [], false, FieldType.Component, FieldUsage.OutputPort);
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants