Skip to content

feat(studio): Transform File Modal with preview schema output - #1032

Open
steramae-nvidia wants to merge 1 commit into
mainfrom
steramae/transform-preview
Open

feat(studio): Transform File Modal with preview schema output#1032
steramae-nvidia wants to merge 1 commit into
mainfrom
steramae/transform-preview

Conversation

@steramae-nvidia

@steramae-nvidia steramae-nvidia commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Signed-off-by: Sean Teramae steramae@nvidia.com

Summary by CodeRabbit

  • New Features
    • Added live before-and-after previews for file transformations, including row navigation and collapsible, copyable output.
    • Added flexible mapping fields supporting text inputs, selectable options, passthrough field properties, disabled states, and row removal.
    • File actions now consistently target the selected dataset when renaming, creating splits, or transforming files.
  • Bug Fixes
    • Improved dataset and filepath handling in rename and transformation workflows.
    • Added safer handling for transformed values and unavailable preview data.

Signed-off-by: Sean Teramae <steramae@nvidia.com>
@steramae-nvidia
steramae-nvidia requested review from a team as code owners July 31, 2026 22:29
@steramae-nvidia

Copy link
Copy Markdown
Contributor Author

@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The PR extracts reusable mapping rows, adds dataset-aware file action handling, and replaces transform schema output with a generated source-to-transformed preview.

Changes

Studio mapping and transform workflow

Layer / File(s) Summary
Mapping row extraction
web/packages/common/src/components/form/MappingFields/*
MappingRow now owns controlled field rendering, passthrough props, labels, disabled state, and removal behavior. MappingFields delegates row rendering to it.
Dataset-aware file actions
web/packages/studio/src/components/FilesTable/FileQuickActions/index.tsx, web/packages/studio/src/components/FilesTable/RenameFileModal/index.tsx
File action modals receive datasetId. Rename submission resolves the dataset and passes its workspace to the mutation.
Preview output rendering
web/packages/studio/src/components/PreviewOutputPanel/index.tsx
PreviewOutputPanel renders collapsible, copyable source and modified values with optional pagination and syntax highlighting.
Transform preview integration
web/packages/studio/src/components/FilesTable/TransformFileModal/*
useTransformPreview parses rows, applies Handlebars mappings, and exposes navigation state. TransformFileModal uses resolved filepath metadata and renders TransformPreview.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant TransformFileModal
  participant useTransformPreview
  participant PreviewOutputPanel
  User->>TransformFileModal: open transform modal
  TransformFileModal->>useTransformPreview: pass file content and mappings
  useTransformPreview->>TransformFileModal: return source and transformed row
  TransformFileModal->>PreviewOutputPanel: render preview values
  User->>PreviewOutputPanel: navigate rows or copy output
Loading

Possibly related PRs

Suggested labels: feat

Suggested reviewers: htolentino-nvidia, aray12

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the Studio Transform File Modal and its preview schema output, which are the main changes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch steramae/transform-preview

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (2)
web/packages/common/src/components/form/MappingFields/types.ts (1)

13-14: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Type-only imports use value import syntax in both new files. Both files import symbols that appear only in type positions.

  • web/packages/common/src/components/form/MappingFields/types.ts#L13-L14: convert the ControlledCombobox and ControlledTextInput imports to import type.
  • web/packages/common/src/components/form/MappingFields/MappingRow.tsx#L23-L23: convert the Control and FieldValues import to import type.

As per coding guidelines: "Use import type for type-only imports in TypeScript".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/packages/common/src/components/form/MappingFields/types.ts` around lines
13 - 14, Update web/packages/common/src/components/form/MappingFields/types.ts
lines 13-14 to use type-only imports for ControlledCombobox and
ControlledTextInput, and update
web/packages/common/src/components/form/MappingFields/MappingRow.tsx line 23 to
use a type-only import for Control and FieldValues; no other changes are needed.

Source: Coding guidelines

web/packages/common/src/components/form/MappingFields/index.tsx (1)

194-201: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Drop the four assertions.

{} is already assignable to Partial<...>, so ?? infers the correct type. Type NO_OVERRIDES once instead.

♻️ Proposed change
-  const keyComboboxProps = (attributes?.keyCombobox ??
-    NO_OVERRIDES) as Partial<KeyValueComboboxPassthrough>;
-  const valueComboboxProps = (attributes?.valueCombobox ??
-    NO_OVERRIDES) as Partial<KeyValueComboboxPassthrough>;
-  const keyTextInputProps = (attributes?.keyTextInput ??
-    NO_OVERRIDES) as Partial<KeyValueTextInputPassthrough>;
-  const valueTextInputProps = (attributes?.valueTextInput ??
-    NO_OVERRIDES) as Partial<KeyValueTextInputPassthrough>;
+  const keyComboboxProps = attributes?.keyCombobox ?? NO_OVERRIDES;
+  const valueComboboxProps = attributes?.valueCombobox ?? NO_OVERRIDES;
+  const keyTextInputProps = attributes?.keyTextInput ?? NO_OVERRIDES;
+  const valueTextInputProps = attributes?.valueTextInput ?? NO_OVERRIDES;

With line 67:

const NO_OVERRIDES: Readonly<Record<string, never>> = {};

As per coding guidelines: "Use type assertions sparingly. Prefer type guards and narrowing in TypeScript".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/packages/common/src/components/form/MappingFields/index.tsx` around lines
194 - 201, Remove the four type assertions from the keyComboboxProps,
valueComboboxProps, keyTextInputProps, and valueTextInputProps initializations.
Type NO_OVERRIDES once as Readonly<Record<string, never>> near its declaration,
allowing each nullish-coalescing expression to infer the appropriate Partial
passthrough type.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@web/packages/studio/src/components/FilesTable/TransformFileModal/index.tsx`:
- Around line 64-65: Update the fileType derivation near resolvedFilepath so
filenames without a dot produce an empty file type instead of the full filename;
preserve extracting the final suffix for paths with extensions, ensuring
parseFileContent does not misclassify extensionless names based on substring
matches.

In
`@web/packages/studio/src/components/FilesTable/TransformFileModal/TransformPreview.tsx`:
- Around line 4-8: Use type-only imports for the specified symbols: in
web/packages/studio/src/components/FilesTable/TransformFileModal/TransformPreview.tsx
lines 4-8, import TransformFileFormFields, FC, and Control with import type; in
web/packages/studio/src/components/FilesTable/TransformFileModal/index.tsx lines
15-18, mark TransformFileFormFields as type while keeping transformFileSchema a
value import; and on line 25, mark ComponentProps and FC as type while keeping
useMemo as a value import.

In
`@web/packages/studio/src/components/FilesTable/TransformFileModal/useTransformPreview.ts`:
- Around line 21-46: Update the nested-path construction in the mapping loop so
each intermediate segment is verified to be a non-null object before assigning
into it; replace any existing primitive or null value with a fresh object before
reassigning current. Preserve existing object branches and ensure conflicting
mappings such as “user” followed by “user.name” complete without throwing.

In `@web/packages/studio/src/components/PreviewOutputPanel/index.tsx`:
- Around line 51-60: Add an aria-expanded attribute to the collapse toggle
Button, deriving its boolean value from the existing collapsed state so expanded
and collapsed states are accurately exposed to screen readers. Keep the current
onClick handler, icons, and aria-label unchanged.

---

Nitpick comments:
In `@web/packages/common/src/components/form/MappingFields/index.tsx`:
- Around line 194-201: Remove the four type assertions from the
keyComboboxProps, valueComboboxProps, keyTextInputProps, and valueTextInputProps
initializations. Type NO_OVERRIDES once as Readonly<Record<string, never>> near
its declaration, allowing each nullish-coalescing expression to infer the
appropriate Partial passthrough type.

In `@web/packages/common/src/components/form/MappingFields/types.ts`:
- Around line 13-14: Update
web/packages/common/src/components/form/MappingFields/types.ts lines 13-14 to
use type-only imports for ControlledCombobox and ControlledTextInput, and update
web/packages/common/src/components/form/MappingFields/MappingRow.tsx line 23 to
use a type-only import for Control and FieldValues; no other changes are needed.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 9791b903-8fcb-4256-bc16-d8344b32b4a4

📥 Commits

Reviewing files that changed from the base of the PR and between 8da2cd4 and 2cec860.

📒 Files selected for processing (9)
  • web/packages/common/src/components/form/MappingFields/MappingRow.tsx
  • web/packages/common/src/components/form/MappingFields/index.tsx
  • web/packages/common/src/components/form/MappingFields/types.ts
  • web/packages/studio/src/components/FilesTable/FileQuickActions/index.tsx
  • web/packages/studio/src/components/FilesTable/RenameFileModal/index.tsx
  • web/packages/studio/src/components/FilesTable/TransformFileModal/TransformPreview.tsx
  • web/packages/studio/src/components/FilesTable/TransformFileModal/index.tsx
  • web/packages/studio/src/components/FilesTable/TransformFileModal/useTransformPreview.ts
  • web/packages/studio/src/components/PreviewOutputPanel/index.tsx

Comment on lines +64 to +65
const resolvedFilepath = filepath ?? '';
const fileType = resolvedFilepath.split('.').at(-1) ?? '';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Handle filepaths without an extension.

resolvedFilepath.split('.').at(-1) returns the full filename when there is no dot in the name (for example, "README" yields "README" instead of ""). Downstream, fileType is checked with fileType?.includes('csv') in parseFileContent; a filename that happens to contain the substring "csv" without an extension would be misclassified as CSV.

🩹 Proposed fix
-  const resolvedFilepath = filepath ?? '';
-  const fileType = resolvedFilepath.split('.').at(-1) ?? '';
+  const resolvedFilepath = filepath ?? '';
+  const extensionParts = resolvedFilepath.split('.');
+  const fileType = extensionParts.length > 1 ? (extensionParts.at(-1) ?? '') : '';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const resolvedFilepath = filepath ?? '';
const fileType = resolvedFilepath.split('.').at(-1) ?? '';
const resolvedFilepath = filepath ?? '';
const extensionParts = resolvedFilepath.split('.');
const fileType = extensionParts.length > 1 ? (extensionParts.at(-1) ?? '') : '';
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/packages/studio/src/components/FilesTable/TransformFileModal/index.tsx`
around lines 64 - 65, Update the fileType derivation near resolvedFilepath so
filenames without a dot produce an empty file type instead of the full filename;
preserve extracting the final suffix for paths with extensions, ensuring
parseFileContent does not misclassify extensionless names based on substring
matches.

Comment on lines +4 to +8
import { TransformFileFormFields } from '@studio/components/FilesTable/TransformFileModal/types';
import { useTransformPreview } from '@studio/components/FilesTable/TransformFileModal/useTransformPreview';
import { PreviewOutputPanel } from '@studio/components/PreviewOutputPanel';
import { FC, useMemo } from 'react';
import { Control, useWatch } from 'react-hook-form';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Use import type for type-only symbols. Both files import type-only symbols as regular imports; the sibling file useTransformPreview.ts already does this correctly for the same symbol.

  • web/packages/studio/src/components/FilesTable/TransformFileModal/TransformPreview.tsx#L4-L8: mark TransformFileFormFields (line 4, only used in Control<TransformFileFormFields>), FC (line 7), and Control (line 8) as import type.
  • web/packages/studio/src/components/FilesTable/TransformFileModal/index.tsx#L15-L18: mark TransformFileFormFields as type within the combined import; transformFileSchema stays a value import.
  • web/packages/studio/src/components/FilesTable/TransformFileModal/index.tsx#L25-L25: mark ComponentProps and FC as type; useMemo stays a value import.
🔧 Proposed fixes
--- a/web/packages/studio/src/components/FilesTable/TransformFileModal/TransformPreview.tsx
-import { TransformFileFormFields } from '`@studio/components/FilesTable/TransformFileModal/types`';
+import type { TransformFileFormFields } from '`@studio/components/FilesTable/TransformFileModal/types`';
 import { useTransformPreview } from '`@studio/components/FilesTable/TransformFileModal/useTransformPreview`';
 import { PreviewOutputPanel } from '`@studio/components/PreviewOutputPanel`';
-import { FC, useMemo } from 'react';
-import { Control, useWatch } from 'react-hook-form';
+import { useMemo, type FC } from 'react';
+import { useWatch, type Control } from 'react-hook-form';
--- a/web/packages/studio/src/components/FilesTable/TransformFileModal/index.tsx
 import {
-  TransformFileFormFields,
+  type TransformFileFormFields,
   transformFileSchema,
 } from '`@studio/components/FilesTable/TransformFileModal/types`';
 ...
-import { ComponentProps, FC, useMemo } from 'react';
+import { useMemo, type ComponentProps, type FC } from 'react';

Based on coding guidelines: "Use import type for type-only imports in TypeScript."

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { TransformFileFormFields } from '@studio/components/FilesTable/TransformFileModal/types';
import { useTransformPreview } from '@studio/components/FilesTable/TransformFileModal/useTransformPreview';
import { PreviewOutputPanel } from '@studio/components/PreviewOutputPanel';
import { FC, useMemo } from 'react';
import { Control, useWatch } from 'react-hook-form';
import type { TransformFileFormFields } from '`@studio/components/FilesTable/TransformFileModal/types`';
import { useTransformPreview } from '`@studio/components/FilesTable/TransformFileModal/useTransformPreview`';
import { PreviewOutputPanel } from '`@studio/components/PreviewOutputPanel`';
import { useMemo, type FC } from 'react';
import { useWatch, type Control } from 'react-hook-form';
Suggested change
import { TransformFileFormFields } from '@studio/components/FilesTable/TransformFileModal/types';
import { useTransformPreview } from '@studio/components/FilesTable/TransformFileModal/useTransformPreview';
import { PreviewOutputPanel } from '@studio/components/PreviewOutputPanel';
import { FC, useMemo } from 'react';
import { Control, useWatch } from 'react-hook-form';
import {
type TransformFileFormFields,
transformFileSchema,
} from '`@studio/components/FilesTable/TransformFileModal/types`';
Suggested change
import { TransformFileFormFields } from '@studio/components/FilesTable/TransformFileModal/types';
import { useTransformPreview } from '@studio/components/FilesTable/TransformFileModal/useTransformPreview';
import { PreviewOutputPanel } from '@studio/components/PreviewOutputPanel';
import { FC, useMemo } from 'react';
import { Control, useWatch } from 'react-hook-form';
import { useMemo, type ComponentProps, type FC } from 'react';
📍 Affects 2 files
  • web/packages/studio/src/components/FilesTable/TransformFileModal/TransformPreview.tsx#L4-L8 (this comment)
  • web/packages/studio/src/components/FilesTable/TransformFileModal/index.tsx#L15-L18
  • web/packages/studio/src/components/FilesTable/TransformFileModal/index.tsx#L25-L25
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@web/packages/studio/src/components/FilesTable/TransformFileModal/TransformPreview.tsx`
around lines 4 - 8, Use type-only imports for the specified symbols: in
web/packages/studio/src/components/FilesTable/TransformFileModal/TransformPreview.tsx
lines 4-8, import TransformFileFormFields, FC, and Control with import type; in
web/packages/studio/src/components/FilesTable/TransformFileModal/index.tsx lines
15-18, mark TransformFileFormFields as type while keeping transformFileSchema a
value import; and on line 25, mark ComponentProps and FC as type while keeping
useMemo as a value import.

Source: Coding guidelines

Comment on lines +21 to +46
for (const { key, value } of mappings) {
if (!key.trim()) continue;

const template = Handlebars.compile(value ?? '');
const keyParts = key.split('.');
let current = newRow;

for (let i = 0; i < keyParts.length - 1; i++) {
const part = keyParts[i];
if (!(part in current)) current[part] = {};
current = current[part] as Record<string, unknown>;
}

const lastPart = keyParts[keyParts.length - 1];
const compiledValue = template(processedRow);

try {
if (compiledValue.trim().startsWith('[') || compiledValue.trim().startsWith('{')) {
current[lastPart] = JSON.parse(compiledValue);
} else {
current[lastPart] = compiledValue;
}
} catch {
current[lastPart] = compiledValue;
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Fix crash on conflicting nested mapping keys.

The loop reuses current[part] as the next nesting target without checking its type:

if (!(part in current)) current[part] = {};
current = current[part] as Record<string, unknown>;

If an earlier mapping already assigned current[part] to a non-object value (e.g. mapping key "user" set newRow.user to a string), then a later mapping key "user.name" reuses that string as current. Module code runs in strict mode, so current[lastPart] = ... on a primitive throws TypeError. This throw happens inside the afterRow useMemo, during render, and can crash the preview.

Two mapping rows with keys "user" and "user.name" are both valid free-text input in the mapping UI, so this is reachable, not theoretical.

🛠️ Proposed fix
     for (let i = 0; i < keyParts.length - 1; i++) {
       const part = keyParts[i];
-      if (!(part in current)) current[part] = {};
+      const existing = current[part];
+      if (typeof existing !== 'object' || existing === null || Array.isArray(existing)) {
+        current[part] = {};
+      }
       current = current[part] as Record<string, unknown>;
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@web/packages/studio/src/components/FilesTable/TransformFileModal/useTransformPreview.ts`
around lines 21 - 46, Update the nested-path construction in the mapping loop so
each intermediate segment is verified to be a non-null object before assigning
into it; replace any existing primitive or null value with a fresh object before
reassigning current. Preserve existing object branches and ensure conflicting
mappings such as “user” followed by “user.name” complete without throwing.

Comment on lines +51 to +60
<Button
size="small"
type="button"
kind="tertiary"
onClick={() => setCollapsed(!collapsed)}
className="shrink-0 text-secondary transition-colors hover:text-primary"
aria-label={collapsed ? 'Expand preview' : 'Collapse preview'}
>
{collapsed ? <ChevronRight size={14} /> : <ChevronDown size={14} />}
</Button>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Add aria-expanded to the collapse toggle.

The button toggles collapsed and sets aria-label, but it does not expose aria-expanded. Screen readers cannot tell whether the panel is expanded or collapsed.

♿ Proposed fix
         <Button
           size="small"
           type="button"
           kind="tertiary"
           onClick={() => setCollapsed(!collapsed)}
           className="shrink-0 text-secondary transition-colors hover:text-primary"
           aria-label={collapsed ? 'Expand preview' : 'Collapse preview'}
+          aria-expanded={!collapsed}
         >
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<Button
size="small"
type="button"
kind="tertiary"
onClick={() => setCollapsed(!collapsed)}
className="shrink-0 text-secondary transition-colors hover:text-primary"
aria-label={collapsed ? 'Expand preview' : 'Collapse preview'}
>
{collapsed ? <ChevronRight size={14} /> : <ChevronDown size={14} />}
</Button>
<Button
size="small"
type="button"
kind="tertiary"
onClick={() => setCollapsed(!collapsed)}
className="shrink-0 text-secondary transition-colors hover:text-primary"
aria-label={collapsed ? 'Expand preview' : 'Collapse preview'}
aria-expanded={!collapsed}
>
{collapsed ? <ChevronRight size={14} /> : <ChevronDown size={14} />}
</Button>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/packages/studio/src/components/PreviewOutputPanel/index.tsx` around lines
51 - 60, Add an aria-expanded attribute to the collapse toggle Button, deriving
its boolean value from the existing collapsed state so expanded and collapsed
states are accurately exposed to screen readers. Keep the current onClick
handler, icons, and aria-label unchanged.

@github-actions

Copy link
Copy Markdown
Contributor
Suite Lines Covered Line Rate Branch Rate
Unit Tests 29440/37448 78.6% 63.2%
Integration Tests 17382/36166 48.1% 20.6%

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant