Skip to content

ENG-564: Node-based dataset editor with drill-down, CRUD, metadata editing, and YAML panel#7687

Draft
Linker44 wants to merge 18 commits intomainfrom
ENG-564-node-based-dataset-editor
Draft

ENG-564: Node-based dataset editor with drill-down, CRUD, metadata editing, and YAML panel#7687
Linker44 wants to merge 18 commits intomainfrom
ENG-564-node-based-dataset-editor

Conversation

@Linker44
Copy link
Contributor

Reopened from #7667

Ticket ENG-564

Description Of Changes

Replace the YAML-only dataset editor with an interactive node-based visual editor for SaaS connections using @xyflow/react. DB connections retain the existing YAML editor. The node editor supports drill-down navigation, CRUD operations, full fides_meta metadata editing, and a collapsible two-way YAML editor panel.

https://www.loom.com/share/8d3bb2ce183b446c9a53d6b57550c8d4

Code Changes

  • DatasetNodeEditor.tsx - Main ReactFlow component with drill-down state, breadcrumb navigation, add/delete/update handlers, highlight animation for new nodes, collapsible YAML editor panel with two-way sync (graph↔YAML)
  • DatasetNodeDetailPanel.tsx - Drawer for editing node metadata: description, data_categories, collapsible fides_meta sections for collections (after, erase_after, skip_processing) and fields (data_type, identity, primary_key, read_only, redact). Debounced updates (300ms). Delete with confirmation.
  • DatasetEditorSection.tsx - Conditional rendering: SaaS → DatasetNodeEditor, DB → Monaco YAML editor. Save handler branches accordingly.
  • AddNodeModal.tsx - Right-side Drawer for naming new collections/fields with uniqueness and format validation. In field mode, includes description, data categories, and collapsible fides_meta form.
  • FieldMetadataFormItems.tsx - Shared form components and helpers extracted to eliminate duplication between AddNodeModal and DatasetNodeDetailPanel. Includes DataCategoryTagSelect with taxonomy suggestions.
  • context/DatasetEditorActionsContext.tsx - Context providing add/delete callbacks to node components
  • context/DatasetTreeHoverContext.tsx - Hover highlighting with precomputed ancestor/descendant sets for performance
  • useDatasetGraph.ts - Converts Dataset to ReactFlow nodes/edges. Two views: overview (root→collections) and drill-down (collection→fields with nested support). Guards against malformed YAML data.
  • useDatasetNodeLayout.ts - Dagre LR layout with configurable spacing
  • nodes/DatasetRootNode.tsx - Dataset root node
  • nodes/DatasetCollectionNode.tsx - Collection node with "+" button (drill-down only), field count badge
  • nodes/DatasetFieldNode.tsx - Field node with "+" button for nested fields, data category count, protected badge
  • nodes/DatasetNodeHandle.tsx - Reusable LR handle component
  • nodes/DatasetNode.module.scss - Node styling, hover states, highlight animation, accessible focus-within for add buttons
  • edges/DatasetTreeEdge.tsx - Custom BezierEdge with hover-aware styling
  • test-datasets.tsx - Adjusted layout for full-height canvas
  • ConnectorParametersForm.tsx - Show "Edit dataset" button for SaaS connections

Steps to Confirm

  1. Navigate to /systems/configure/{id}/test-datasets for a SaaS connection
  2. Verify overview: dataset root → collection nodes (no fields visible)
  3. Click a collection → drills into fields with breadcrumb bar
  4. Click back chevron or dataset name → returns to overview
  5. In drill-down, hover a collection → "+" appears → add field with full metadata form
  6. Hover a field → "+" appears → add nested sub-field
  7. New nodes briefly highlight in yellow
  8. Click a field → drawer opens with description, data_categories, collapsible fides_meta
  9. Edit metadata → changes reflected in graph (debounced)
  10. Click "Delete field" → confirmation → field removed
  11. Click YAML button in toolbar → bottom panel opens with synced YAML
  12. Edit YAML → graph updates; edit graph → YAML updates
  13. Save → persists via API; Refresh → reloads from server
  14. Navigate to a DB connection → verify original YAML editor still works

Pre-Merge Checklist

  • Issue requirements met
  • All CI pipelines succeeded
  • CHANGELOG.md updated
  • UX feedback:
    • All UX related changes have been reviewed by a designer
  • Followup issues:
    • No followup issues
  • Database migrations:
    • No migrations
  • Documentation:
    • No documentation updates required

Linker44 added 15 commits March 13, 2026 12:15
Swap the Monaco YAML editor for an interactive React Flow graph where
each collection and field is a node. Clicking a node opens a drawer
to edit its metadata.

- Add DatasetNodeEditor with D3 hierarchy layout (LR tree)
- Add custom node components (root, collection, field) matching the
  taxonomy tree visual style with hover context
- Add DatasetTreeHoverContext for parent/child/inactive highlighting
- Add custom bezier edges that change color on hover
- Add DatasetNodeDetailPanel drawer for editing metadata
- Enable "Edit dataset" button for SaaS connectors
- Remove test results/logs sections for full-canvas layout
…diting to dataset node editor

- Add drill-down: overview shows root→collections, clicking a collection shows its fields
- Add breadcrumb navigation bar with back button when drilled into a collection
- Add/delete collections (via "+" on root node) and fields (via "+" on field nodes for nesting)
- Add AddNodeModal with uniqueness validation for naming new nodes
- Add DatasetEditorActionsContext to provide CRUD callbacks to node components
- Expand detail panel with collapsible fides_meta sections for both collections and fields
- Add delete button with confirmation dialog in detail panel
- Fix node overlap by constraining container width and increasing layout spacing
The node-based dataset editor should only be used for SaaS connections.
DB connectors retain the original Monaco YAML editor with reachability checks.
…down mode

- Add-field modal now includes description, data categories, and full
  fides_meta fields (matching the edit panel) so metadata can be set
  at creation time.
- Remove "+" button from dataset root node (overview mode).
- Show "+" on collection node only when it is the drill-down root.
DRY up duplicated code between AddNodeModal and DatasetNodeDetailPanel:
- FieldMetadataFormItems component for the 8 fides_meta form fields
- buildFieldMeta helper for converting form values to fides_meta
- DATA_TYPE_OPTIONS and REDACT_OPTIONS constants
Use useTaxonomies() to populate the data categories Select with
suggestions from the existing taxonomy, while still allowing free-form
tags input. Extracted as shared DataCategoryTagSelect component.
Only re-fit the viewport when the graph structure changes (node count
or focused collection), not when field metadata is edited.
When a collection or field is added, the new node gets a 1.5s yellow
highlight animation that fades out, making it easy to spot in the graph.
All 6 base types support the [] array suffix per parse_data_type_string
in the backend. Add all 12 variants to the dropdown.
- Replace centered Modal with right-side Drawer matching the edit panel
  style, with a "Create" button in the drawer header.
- Include array data type variants (e.g. string[], object[]) in the
  data type dropdown.
Adds a toggleable YAML code editor at the bottom of the node graph
with two-way sync: graph changes update the YAML and YAML edits
update the graph (debounced 500ms with error display).
- Hoist layout options to module constant to prevent useMemo busting
- Stabilize onMouseEnter with functional state update (removes dep)
- Precompute ancestor/descendant sets per hover instead of per-node
- Export ProtectedFieldsInfo from useDatasetGraph, remove duplicate
- Remove unused DatasetNodeData type export
- Remove no-op onMount from Monaco editor
- Add aria-label to add field/nested field buttons
- Show add buttons on focus-within (keyboard accessibility)
- Derive selectedNodeData from rawNodes via useMemo instead of storing
  a stale snapshot in state. Now always reflects the latest dataset.
- Debounce detail panel onValuesChange (300ms) to avoid triggering
  full dataset/graph recomputes on every keystroke.
- Guard against null/non-array fields and collections from incomplete
  YAML edits (e.g. bare "-" list items, partial typing).
…e editor forms

These advanced fields are better edited directly in YAML.
@Linker44 Linker44 requested a review from a team as a code owner March 18, 2026 14:42
@Linker44 Linker44 requested review from lucanovera and removed request for a team March 18, 2026 14:42
@vercel
Copy link
Contributor

vercel bot commented Mar 18, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
fides-plus-nightly Ready Ready Preview, Comment Mar 18, 2026 4:32pm
1 Skipped Deployment
Project Deployment Actions Updated (UTC)
fides-privacy-center Ignored Ignored Mar 18, 2026 4:32pm

Request Review

@Linker44 Linker44 self-assigned this Mar 18, 2026
@greptile-apps
Copy link
Contributor

greptile-apps bot commented Mar 18, 2026

Greptile Summary

This PR replaces the YAML-only dataset editor with a node-based visual editor (@xyflow/react) for SaaS connections, while preserving the existing Monaco YAML editor for DB connections. The implementation is well-structured with clear separation of concerns — context providers for hover state and editor actions, dedicated hooks for graph construction and layout, and a detail panel with debounced updates. The two-way YAML↔graph sync is handled cleanly using a changeSourceRef sentinel to avoid sync loops.

Key observations:

  • PR size exceeds policy limits: the PR touches 17 files with ~2,300 lines of changes, exceeding the allowed thresholds of 15 files and 500 lines. Consider splitting the work into smaller, focused PRs in future iterations.
  • Several flex layout containers use bare <div> elements with inline display: "flex" / flexDirection styles instead of the project's Flex component from fidesui — found in DatasetNodeEditor.tsx (lines 505–509, 562–567, 598–607) and DatasetNodeDetailPanel.tsx (line 174).
  • Single-character loop variable e is used in DatasetTreeHoverContext.tsx (lines 40, 59) where edge would be more descriptive.
  • The fitView setTimeout inside useEffect in DatasetNodeEditor.tsx (line 291) lacks a cleanup return, which can cause the stale timer to fire after re-renders or unmount.
  • DatasetNodeEditor.tsx is 688 lines, close to the 700-line component limit — consider extracting the YAML panel or breadcrumb toolbar into sub-components if it grows further.

Confidence Score: 3/5

  • Functionally correct but exceeds PR size policy; a few minor code style issues should be addressed before merging.
  • The implementation logic is sound — two-way YAML sync, drill-down navigation, CRUD operations, and the DB/SaaS branching all look correct. No runtime errors or security issues were found. The score is reduced primarily because the PR significantly exceeds the 15-file / 500-line size policy, and there are a handful of style-guideline violations (div vs Flex, short variable names, missing timer cleanup) that should be cleaned up per project conventions.
  • DatasetNodeEditor.tsx (div flex containers, missing setTimeout cleanup, approaching line limit) and DatasetTreeHoverContext.tsx (single-char variable names)

Important Files Changed

Filename Overview
clients/admin-ui/src/features/test-datasets/DatasetNodeEditor.tsx Main ReactFlow editor component (688 lines, approaching the 700-line limit). Contains drill-down navigation, YAML sync, and all CRUD handlers. Two minor style issues: several div flex containers should use the Flex component, and the fitView setTimeout lacks a cleanup return in its useEffect.
clients/admin-ui/src/features/test-datasets/DatasetNodeDetailPanel.tsx Drawer panel for editing collection/field metadata with debounced updates. Logic is correct; the Drawer title uses a bare div flex container that should be Flex.
clients/admin-ui/src/features/test-datasets/DatasetEditorSection.tsx Conditional rendering correctly branches SaaS to the node editor and DB to the YAML editor. Save/refresh handlers appropriately split logic. The isSaas wrapper div could use Flex instead.
clients/admin-ui/src/features/test-datasets/context/DatasetTreeHoverContext.tsx Hover context with precomputed ancestor/descendant sets. Efficient and well-structured. Single-character variable e is used in two forEach loops (lines 40 and 59) instead of the more descriptive edge.
clients/admin-ui/src/features/test-datasets/useDatasetGraph.ts Correctly converts a Dataset into ReactFlow nodes/edges for both overview and drill-down views. Guards against malformed data with Array.isArray and filter(Boolean). Logic is clean and well-separated.

Last reviewed commit: "Remove return_all_el..."

Comment on lines +174 to +176
<div style={{ display: "flex", alignItems: "center", gap: 8 }}>
<Typography.Text strong>{nodeData?.label}</Typography.Text>
<Tag
Copy link
Contributor

Choose a reason for hiding this comment

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

P2 Prefer Flex over div with inline flex styles

The Drawer title uses a <div> with display: "flex" inline styles. Per the project's semantic HTML guideline, Ant Design's Flex component should be used instead of <div> elements for flex containers.

Suggested change
<div style={{ display: "flex", alignItems: "center", gap: 8 }}>
<Typography.Text strong>{nodeData?.label}</Typography.Text>
<Tag
<Flex align="center" gap={8}>
<Typography.Text strong>{nodeData?.label}</Typography.Text>

The same pattern appears in the closing tag — change </div> to </Flex> and add Flex to the fidesui import.

Rule Used: Avoid using div elements when possible. Use sema... (source)

Learnt From
ethyca/fides#6763

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment on lines +505 to +509
<div
ref={reactFlowRef}
className="size-full"
style={{ display: "flex", flexDirection: "column" }}
>
Copy link
Contributor

Choose a reason for hiding this comment

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

P2 Use Flex instead of div for flex container

This outer wrapper uses display: "flex" and flexDirection: "column" via inline styles on a <div>. The project guideline prefers using Ant Design's Flex component over bare <div> elements for flex containers.

Suggested change
<div
ref={reactFlowRef}
className="size-full"
style={{ display: "flex", flexDirection: "column" }}
>
<Flex
ref={reactFlowRef}
className="size-full"
vertical
>

The same applies to the YAML panel container at line 598 (<div style={{ ..., display: "flex", flexDirection: "column" }}>) and the canvas wrapper at line 562 (<div style={{ flex: "1 1 auto", ... }}>). All three div flex containers can be replaced with Flex (setting vertical for column direction and using flex, style for sizing props). This pattern also appears in DatasetEditorSection.tsx at the isSaas branch wrapper (line ~283).

Rule Used: Avoid using div elements when possible. Use sema... (source)

Learnt From
ethyca/fides#6763

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

const buildAncestryMaps = (edges: Edge[]) => {
// parentOf: child → parent
const parentOf = new Map<string, string>();
edges.forEach((e) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

P2 Avoid single-character variable names

e is a 1-character variable name. The project convention requires full, descriptive names for variables. Rename to edge for clarity. The same applies on line 59 inside getDescendants.

Suggested change
edges.forEach((e) => {
edges.forEach((edge) => {

Line 59 should also be updated:

    edges.forEach((edge) => {
      if (!children.has(edge.source)) {
        children.set(edge.source, []);
      }
      children.get(edge.source)!.push(edge.target);
    });

Rule Used: Use full names for variables, not 1 to 2 character... (source)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment on lines +289 to +295
useEffect(() => {
if (nodeCount > 0) {
setTimeout(() => {
reactFlowInstance.fitView({ padding: 0.2 });
}, 150);
}
}, [nodeCount, focusedCollection, reactFlowInstance]);
Copy link
Contributor

Choose a reason for hiding this comment

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

P2 Missing setTimeout cleanup in useEffect

The setTimeout call for fitView is not cleaned up in the effect's return function. If the component unmounts or the effect re-runs within the 150 ms window, the stale timer will fire and call reactFlowInstance.fitView against a potentially stale closure. While fitView doesn't trigger React state updates (so there's no unmounted-component warning), it's best practice to always clear timers in useEffect cleanup.

Suggested change
useEffect(() => {
if (nodeCount > 0) {
setTimeout(() => {
reactFlowInstance.fitView({ padding: 0.2 });
}, 150);
}
}, [nodeCount, focusedCollection, reactFlowInstance]);
useEffect(() => {
if (nodeCount > 0) {
const timerId = setTimeout(() => {
reactFlowInstance.fitView({ padding: 0.2 });
}, 150);
return () => clearTimeout(timerId);
}
return undefined;
}, [nodeCount, focusedCollection, reactFlowInstance]);

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.

1 participant