Skip to content

fix(launch): render object map values as JSON on relaunch - #922

Open
1fanwang wants to merge 3 commits into
flyteorg:masterfrom
1fanwang:fix-mapinput-object-render
Open

1fanwang wants to merge 3 commits into
flyteorg:masterfrom
1fanwang:fix-mapinput-object-render

Conversation

@1fanwang

@1fanwang 1fanwang commented Jun 21, 2026 •

Copy link
Copy Markdown
Contributor

Read then delete this section

This is a bug fix, so it takes the default patch version bump. Release version rules

TL;DR

Relaunching an execution that has a map input shows [object Object] in the value box for dict, nested map, union, file and dataset values, and 1,2 instead of [1,2] for a list. The form now shows them all as JSON text.

Maps of dicts, lists and nested maps also failed validation on an unedited relaunch, so every value had to be retyped. They now submit unchanged. Maps of unions, files and structured datasets keep submitting unchanged.

Type

  • Bug Fix
  • Feature
  • Plugin

Are all requirements met?

  • Code completed
  • Smoke tested
  • Unit tests added
  • Code documentation added
  • Any pending items have an associated Issue

Complete description

The relaunch form fills a map input from the values of the earlier execution. Dict, list and nested map values are now shown and kept as JSON text, which is what their validation expects. Union, file and structured dataset values are validated as structured values and reject text, so they stay structured and are only displayed as JSON.

Testing Done

The reproducer renders the real map input in jsdom. For each map type it builds a launched map literal, mounts the input with the value the form derives from that literal, validates what the input reports back with the map helpers, and converts it back to a literal. In the output, shown is the text in the value box, validation is the validator's answer, and sameLiteral says whether the rebuilt literal equals the launched one.

Save the source below as map_input_repro.cjs in a checkout with its dependencies installed, and run the commands from the repository root. Master is commit 73d8292.

git show 73d8292:packages/oss-console/src/components/Launch/LaunchForm/MapInput.tsx > packages/oss-console/src/components/Launch/LaunchForm/MapInput.base.tsx
NODE_ENV=test node map_input_repro.cjs MapInput.base 2>/dev/null
{"scenario":"map<str, union<str, int>>","shown":"[object Object]","validation":"ok","sameLiteral":true}
{"scenario":"map<str, dict>","shown":"[object Object]","validation":"Value's value is invalid","sameLiteral":true}
{"scenario":"map<str, list<int>>","shown":"1,2","validation":"Value's value is invalid","sameLiteral":true}
{"scenario":"map<str, map<str, str>>","shown":"[object Object]","validation":"Value's value is invalid","sameLiteral":true}
{"scenario":"map<str, file>","shown":"[object Object]","validation":"ok","sameLiteral":true}
{"scenario":"map<str, dataset>","shown":"[object Object]","validation":"ok","sameLiteral":true}

This branch:

NODE_ENV=test node map_input_repro.cjs MapInput 2>/dev/null
{"scenario":"map<str, union<str, int>>","shown":"{\"value\":\"hello\",\"typeDefinition\":{\"literalType\":{\"simple\":3,\"structure\":{\"tag\":\"str\"}},\"type\":\"STRING\"}}","validation":"ok","sameLiteral":true}
{"scenario":"map<str, dict>","shown":"{\"nested\":\"bar\"}","validation":"ok","sameLiteral":true}
{"scenario":"map<str, list<int>>","shown":"[1,2]","validation":"ok","sameLiteral":true}
{"scenario":"map<str, map<str, str>>","shown":"{\"inner\":\"v\"}","validation":"ok","sameLiteral":true}
{"scenario":"map<str, file>","shown":"{\"dimensionality\":0,\"format\":\"csv\",\"uri\":\"s3://bucket/key\"}","validation":"ok","sameLiteral":true}
{"scenario":"map<str, dataset>","shown":"{\"uri\":\"s3://bucket/df\",\"format\":\"parquet\"}","validation":"ok","sameLiteral":true}

On master the dict, list and nested map are rejected even though they would convert back to the launched literal. On this branch every map shows JSON, validates, and rebuilds the launched literal.

This runs in jsdom, not a browser, and I did not submit a relaunched execution to a live backend. On master the run also logs a React warning that an object was passed as the text value, and the commands drop stderr.

Reproducer source: map_input_repro.cjs
const { createRequire } = require('node:module');
const { isDeepStrictEqual } = require('node:util');

const load = createRequire(`${process.cwd()}/package.json`);
load('ts-node').register({
  transpileOnly: true,
  skipProject: true,
  compilerOptions: {
    module: 'CommonJS',
    moduleResolution: 'node',
    target: 'ES2020',
    jsx: 'react',
    esModuleInterop: true,
  },
});
const paths = load('tsconfig-paths');
const config = paths.loadConfig(process.cwd());
if (config.resultType !== 'success') throw new Error('tsconfig paths not found');
paths.register({ baseUrl: config.absoluteBaseUrl, paths: config.paths });

const { JSDOM } = load('jsdom');
const dom = new JSDOM('<!doctype html><html><body></body></html>', {
  url: 'http://localhost/',
  pretendToBeVisual: true,
});
global.window = dom.window;
global.document = dom.window.document;
Object.defineProperty(global, 'navigator', { value: dom.window.navigator, configurable: true });
global.HTMLElement = dom.window.HTMLElement;
global.getComputedStyle = dom.window.getComputedStyle;
global.requestAnimationFrame = dom.window.requestAnimationFrame.bind(dom.window);
global.cancelAnimationFrame = dom.window.cancelAnimationFrame.bind(dom.window);

const React = load('react');
const Long = load('long');
const { render } = load('@testing-library/react');
const { ThemeProvider } = load('@mui/material/styles');
const { muiTheme } = load('@clients/theme/Theme/muiTheme');
const Core = load('@clients/common/flyteidl/core').default;

const form = '@clients/oss-console/components/Launch/LaunchForm';
const { MapInput } = load(`${form}/${process.argv[2] || 'MapInput'}`);
const { getInputDefintionForLiteralType } = load(`${form}/utils`);
const { mapHelper } = load(`${form}/inputHelpers/map`);
const { InputType } = load(`${form}/types`);

const { SimpleType } = Core;
const strType = { simple: SimpleType.STRING, structure: { tag: 'str' } };
const intType = { simple: SimpleType.INTEGER, structure: { tag: 'int' } };
const str = (stringValue) => ({ scalar: { primitive: { stringValue } } });
const scenarios = {
  'map<str, union<str, int>>': {
    valueType: { unionType: { variants: [strType, intType] } },
    child: { scalar: { union: { value: str('hello'), type: strType } } },
  },
  'map<str, dict>': {
    valueType: { simple: SimpleType.STRUCT },
    child: { scalar: { generic: { fields: { nested: { stringValue: 'bar' } } } } },
  },
  'map<str, list<int>>': {
    valueType: { collectionType: { simple: SimpleType.INTEGER } },
    child: {
      collection: {
        literals: [1, 2].map((n) => ({ scalar: { primitive: { integer: Long.fromNumber(n) } } })),
      },
    },
  },
  'map<str, map<str, str>>': {
    valueType: { mapValueType: { simple: SimpleType.STRING } },
    child: { map: { literals: { inner: str('v') } } },
  },
  'map<str, file>': {
    valueType: { blob: { format: 'csv', dimensionality: 0 } },
    child: {
      scalar: {
        blob: { uri: 's3://bucket/key', metadata: { type: { format: 'csv', dimensionality: 0 } } },
      },
    },
  },
  'map<str, dataset>': {
    valueType: { structuredDatasetType: { format: 'parquet' } },
    child: {
      scalar: {
        structuredDataset: {
          uri: 's3://bucket/df',
          metadata: { structuredDatasetType: { format: 'parquet' } },
        },
      },
    },
  },
};

for (const [name, { valueType, child }] of Object.entries(scenarios)) {
  const typeDefinition = getInputDefintionForLiteralType({ mapValueType: valueType });
  if (typeDefinition.type !== InputType.Map) throw new Error('expected a map type');
  const launched = { map: { literals: { foo: child } } };

  let submitted;
  const props = {
    description: '',
    name: 'mapInput',
    label: 'Map Input',
    required: true,
    typeDefinition,
    value: mapHelper.fromLiteral(launched, typeDefinition),
    onChange: (value) => {
      submitted = value;
    },
  };
  const { container, unmount } = render(
    React.createElement(ThemeProvider, { theme: muiTheme }, React.createElement(MapInput, props)),
  );
  const shown = container.querySelector('.valueControl textarea, .valueControl input').value;

  let validation = 'ok';
  try {
    mapHelper.validate({ name: props.name, required: true, typeDefinition, value: submitted });
  } catch (e) {
    validation = e.message;
  }
  let sameLiteral;
  try {
    sameLiteral = isDeepStrictEqual(mapHelper.toLiteral({ typeDefinition, value: submitted }), launched);
  } catch (e) {
    sameLiteral = e.message;
  }
  console.log(JSON.stringify({ scenario: name, shown, validation, sameLiteral }));
  unmount();
}
dom.window.close();

Tracking Issue

N/A

Follow-up issue

N/A

@welcome

welcome Bot commented Jun 21, 2026

Copy link
Copy Markdown

Thank you for opening this pull request! 🙌

These tips will help get your PR across the finish line:

  • Most of the repos have a PR template; if not, fill it out to the best of your knowledge.
  • Sign off your commits (Reference: DCO Guide).

@1fanwang 1fanwang changed the title fix(launch): render object map values as JSON instead of crashing on relaunch fix(launch): render object map values as JSON on relaunch Aug 20, 2026
…relaunch

A map whose values are objects (e.g. Map[str, Struct]) parsed each value into the
string-typed value field, so the launch form bound an object straight into a text
input -- coerced to "[object Object]" and producing render errors on relaunch.
JSON-stringify object values at parse time so the field always receives a string.

Signed-off-by: 1fanwang <1fannnw@gmail.com>
The test name and assertions already carry the relaunch scenario. Repeating the same behavior in source and test comments adds noise without documenting another constraint.

Signed-off-by: 1fanwang <1fannnw@gmail.com>
@1fanwang
1fanwang force-pushed the fix-mapinput-object-render branch from a7c1cc5 to 2dea5d0 Compare August 24, 2026 10:14
Signed-off-by: 1fanwang <1fannnw@gmail.com>
@1fanwang

Copy link
Copy Markdown
Contributor Author

@Sovietaced could you review when you have time?

@Sovietaced

Copy link
Copy Markdown
Member

I am not familiar with this codebase but AI reviews shows a possible regression with union values.

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