Skip to content

feat(data-quality): migrate test case & bundle suite forms to react-hook-form#29784

Draft
ShaileshParmar11 wants to merge 34 commits into
mainfrom
dq-form-migration
Draft

feat(data-quality): migrate test case & bundle suite forms to react-hook-form#29784
ShaileshParmar11 wants to merge 34 commits into
mainfrom
dq-form-migration

Conversation

@ShaileshParmar11

Copy link
Copy Markdown
Contributor

What

Migrates the Data Quality Add Test Case and Add Bundle Suite forms from Ant Design to the react-hook-form + react-aria core-components stack (getField / FieldProp / HookForm).

  • Classic flow — right-side drawer, matching the legacy visual design (widths, field spacing, doc-panel behavior, helper text).
  • The migration preserves every legacy behavior: mount-time table fetch, on-change validation, doc-panel scroll-to-focused-field, reliable first-click dropdown open, tag/glossary data sources, custom-SQL layout, parameter-field rhythm, and the On-Demand scheduler card.

core-components is net-unchanged

Two intermediate commits (onLoadMore, an isDisabled field prop) touch the shared library, but they are fully reverted by the end of the branch — openmetadata-ui-core-components is byte-identical to main (0 diff). The form meets all its needs through existing primitives:

  • Table search is a single paged fetch (search-to-narrow) instead of infinite scroll.
  • Fields disable by passing react-aria's isDisabled through the existing {...rest} spread — no new public prop.
  • Test-type dropdown options are selected by role/display-name instead of a per-option test id.

Testing

  • Jest — form-body and field suites green.
  • Playwright — DQ specs migrated to the RHF drawer selectors (react-aria roles, inner-input fills); test-type options picked via getByRole('option').filter({ hasText }). Validated live: AddTestCaseNewFlow (table + column + pipeline + permission), TableLevelTests custom-SQL.

Notes

  • Draft; branch is currently a few commits behind main and can be rebased before marking ready.
  • Paired with the Collate PR that renders these same forms as centered modals in AskCollate AI mode.

🤖 Generated with Claude Code

ShaileshParmar11 and others added 30 commits July 6, 2026 10:09
…mponents RHF

Port of the reviewed dq-dashboard-form-migration branch onto main
(single squashed application; original bases were squash-merged):

- New RHF form stack: TestCaseFormBody/Drawer, BundleSuiteFormBody/Drawer,
  ParameterFields, TableDiffFields, TestCaseSchedulerSection, transforms,
  and ParameterFieldsUtils (param name sanitize/restore, payload
  normalization, select-item unwrapping).
- Hosts support variant="classic" (right drawer) and variant="ai"
  (centered modal via new AiFormModal + floating form hint).
- All OSS consumers migrated to the controlled-open hosts; legacy antd
  TestCaseFormV1 and BundleSuiteForm removed.
- core-components: FieldPropsMap gains isDisabled.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The classic drawer hosts passed no width, so SlideoutMenu fell back to
its narrow default and the side doc panel collapsed into a vertical
sliver. Match the legacy antd drawer sizing (was width=80%).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…form

The migrated body rendered flat fields, so the classic drawer lost the
old form's look: TestCaseFormV1.less keys everything on
.test-case-form-v1, which the new markup never carried.

- Body root now carries test-case-form-v1/drawer-mode so the kept
  stylesheet applies; sections wrapped as form-card-section cards
  (select-table, test-type, test-details, scheduler with its title).
- Description renders the RichTextEditor block editor again instead of
  the core TextArea fallback.
- Custom Query action right-aligned in the test-type card header.
- Plain-div card sections get the padding antd Card used to provide.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…e drawer

Scoped to .drawer-content-wrapper so the AI modal keeps design-system
defaults: medium gray labels, ~32px controls with 4px radius, no leading
search icon, and the Custom Query action overlaid on the Select Test
Type label row like the legacy absolute-positioned header.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- The SlideoutMenu overlay carried no z-index, letting the page table's
  sticky Actions column paint through the drawer; the hosts now tag the
  overlay (test-case-form-drawer / bundle-suite-form-drawer) and it gets
  z-index 40 — above page chrome, below the select popovers.
- Classic controls pinned to 32px to match the legacy antd sizing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ase form

The core getField TAG_SUGGESTION/GLOSSARY_TAG_SUGGESTION render a plain
Autocomplete with caller-supplied options, so both fields were empty.
Reuse the self-fetching common TagSuggestion (core Autocomplete based)
wrapped in RHF FormField, and teach it a tagType prop so the glossary
variant searches the glossaryTerm index and stamps source=Glossary.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… results

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…tion fields

Clicking one TagSuggestion while another's popover is open cancels the
menu the click just opened (the closing popover's teardown races the
open). On focus, re-open via the trigger's ArrowDown handling, with a
follow-up check after the teardown window since the cancel can land
late.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… fields

Only a few fields reported focus to the doc panel. Replace the per-field
wiring with one focus-capture handler on the form body that resolves the
nearest root/<fieldName> id, matching the legacy antd form's form-level
onFocus behavior.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Show helper descriptions as label (?) tooltips instead of inline alert
  banners for test type, parameters, and compute row count
- Show only the selected test name in the test type trigger; the
  description stays in the dropdown options
- Follow the selected test type in the doc panel until it is cleared
- Match the legacy regular font weight on the Custom Query toggle

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… icons

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Extract the combobox re-open guard from TagSuggestion into a shared
ensureComboboxMenuOpen util and apply it to the table async select,
whose just-opened menu could be cancelled by the focus-driven doc panel
re-render.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ssion label

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Bind the custom query SQL editor to the form value; typed SQL now
  reaches params.sqlExpression and required validation can pass
- Render generic inputs for the remaining tableDiff parameters
  (where, threshold, caseSensitiveColumns)
- Render testCaseClassBase.createFormAdditionalFields via the core
  getField (contract migrated to core FieldProp) so distribution
  overrides like the dynamic assertion switch appear again
- Fall back to a text input for parameter data types without dedicated
  handling instead of rendering nothing
- Seed preselected test cases into the bundle suite form value so the
  create-from-selection flow can submit

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Validate table edit permission on selection when the user lacks
  global test case create rights, gating pipeline creation like the
  legacy form
- Funnel every drawer dismissal (cancel, X, Escape, programmatic)
  through one onClose so the parent is notified exactly once and the
  form resets; backdrop is inert like the legacy maskClosable drawer
- Fall back to the generated ingestion name when the pipeline name is
  cleared to an empty string
- Clear the selected column when switching back to table level
- Restore the rich text editor for the bundle suite description
- Restore the pipeline-deployed success toast for bundle suites
- Double the table search page size (legacy infinite scroll needs core
  Autocomplete support; tracked as follow-up)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Report focused parameter fields to the doc panel via the selected
  test type id, like the legacy param wrapper
- Show parameter descriptions as label tooltips on Array/Set lists
- Scroll to the first invalid field on Enter-key submits too
- Reset the form when the AI modal closes or submits
- Restore the legacy bundle suite drawer width (736px), footer test ids
  (submit-button/cancel-button, create-btn), and the optional pipeline
  description under the scheduler toggle

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Re-seed bundle suite form values on every open: the always-mounted
  drawer host froze useForm defaults before DataQualityTab's bulk
  selection provided initialValues, so create-from-selection could
  never submit; covered by a production-topology test
- Validate table permission at selection time (legacy antd change-time
  feedback) instead of first at submit
- Scroll to the first invalid field on bundle Enter-key and AI modal
  submits
- Drop the duplicate root/<testType> DOM id; the parameter wrapper
  reports doc panel focus via a handler like the legacy onClick wrapper

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The doc-panel focus wrapper made the parameters a single card child, so
the card's child spacing no longer applied between them.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…e scroll

Attaches a scroll listener to the options popover from the list box ref
and calls onLoadMore when scrolled near the end, so async selects can
page in more options like the legacy antd AsyncSelect.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Table search pages in more results on scroll (309 tables reachable
  again) via the new core Autocomplete onLoadMore
- Doc panel switches to the selected table's entity summary on
  selection (root/selected-entity), like the legacy option onclick
- Create button shows the legacy isCheckingPermissions loading state
- Clearing the table re-validates, clearing a stale permission error
- tableDiff parameters render in parameterDefinition order
- Test case drawer backdrop is dismissable again (legacy antd default;
  the bundle drawer keeps its legacy mask lock)
- Bundle form re-seeds when the seed content changes while open,
  closing the late-initialValues edge without wiping user edits on
  parent re-renders

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Permission-check spinner shows only on the Create button via a new
  additive submitLoading option, not as a full-body loader overlay
- loadMoreTables uses a ref-based in-flight latch instead of relying on
  render-flush timing
- Doc panel reverts to the generic table section when the table
  selection is cleared

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
react-hook-form ignores onChange(undefined), but ScheduleIntervalV1
signals On Demand with undefined — so the cron value never cleared and
the value-sync effect snapped the card back to Schedule. Store an empty
string in the form as the on-demand sentinel, convert to undefined at
the component and pipeline payload boundaries, and cover the RHF wiring
with a regression test using the real selection cards.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Select options accept an optional testId rendered as data-testid
  (core), and test type options carry the definition FQN like the
  legacy antd options
- Parameter fields keep the legacy antd form-item ids
  (testCaseFormV1_params_*) as stable selectors, including array rows
  and tableDiff fields
- The form body carries the test-case-form-v1 test id again

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ites

- Fetch tables on drawer mount like the legacy AsyncSelect, so options
  are ready before first focus
- Validate on change (RHF mode onChange), restoring antd's while-typing
  name and field validation feedback
- Enum parameters with optionValues render as a single select even when
  typed ARRAY (legacy precedence); the custom SQL strategy field was a
  free-text array row list
- tableDiff cross-list column exclusion compares select item ids, not
  item objects, so key columns disable in Use Columns again
- Restore the legacy scheduler-card test id

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
antd-era interaction patterns rewritten for the react-aria form:
dropdown popovers to role listbox/option picks, filter-typing to
click-to-open, heading-click dismissals to Escape, array add rows via
add-<param> testids, tag options to tag-option-<fqn>, wrapper testid
fills retargeted to inner inputs, validation-help assertions to
text-based checks. Edit-modal (unmigrated antd) selectors untouched.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The AI-mode modal's Cancel/Create buttons had no test ids, so the
shared submit helpers (and the AskCollate CTA flows) could not drive the
AI variant. Add configurable submitTestId/cancelTestId defaulting to
create-btn/cancel-btn to match the classic test case drawer; the bundle
suite drawer passes submit-button/cancel-button to match its own.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
FieldProp already exposes disabled; the added isDisabled duplicated it.
Remove isDisabled from the public type and map the single disabled prop
to react-aria's isDisabled convention inside the Autocomplete and Select
renderers, so one prop disables both the react-aria selects and the
legacy field components. DQ form fields updated to pass disabled.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ShaileshParmar11 and others added 2 commits July 6, 2026 21:17
Rework the form to need no changes to the shared component library:

- Revert the Autocomplete onLoadMore, SelectItemType.testId, and the
  disabled->isDisabled renderer mapping in core-components
- Table search is a single paged fetch again (search to narrow) instead
  of infinite scroll
- Fields disable by passing react-aria's isDisabled through the existing
  prop spread (no new public FieldProp), so no duplicate of disabled
- Test type options no longer carry a per-option test id

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The test-type dropdown options no longer carry a per-option test id
(the core-components change was reverted). Select them by their visible
display name via getByRole('option').filter({ hasText }) instead, the
same pattern the table and column option picks already use. Display
names are the test definitions' displayName values.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions github-actions Bot added safe to test Add this label to run secure Github workflows on PRs UI UI specific issues labels Jul 6, 2026
@ShaileshParmar11

Copy link
Copy Markdown
Contributor Author

Paired Collate PR: open-metadata/openmetadata-collate#4874

@@ -134,11 +144,14 @@ const TagSuggestion: FC<TagSuggestionProps> = ({

const handleItemInserted = useCallback(

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Bug: TagSuggestion re-inserts already-selected tag as duplicate

In handleItemInserted, when the inserted key already exists in value, existingTag is found but the handler still calls onChange?.([...value, newTag]), appending the existing tag again. If react-aria's Autocomplete ever fires onItemInserted for an already-selected item (e.g. rapid re-selection), the tag array will contain duplicates, which then flow into normalizeTagLabels and the created test case payload. Consider guarding: if (existingTag) { return; } before appending, or dedupe by tagFQN before calling onChange.

Skip insertion when the tag is already selected.:

const handleItemInserted = useCallback(
  (key: string | number) => {
    if (String(key) === NO_DATA_OPTION_ID) {
      return;
    }
    if (value.some((tag) => tag.tagFQN === String(key))) {
      return; // already selected; avoid duplicate entry
    }
    const tagData = tagDataMap.current.get(String(key));
    const newTag: EntityTags = {
      tagFQN: String(key),
      source: tagType,
      name: tagData?.name,
      displayName: tagData?.displayName,
      description: tagData?.description,
      style: tagData?.style,
    };
    onChange?.([...value, newTag]);
    searchDebounced.cancel();
    fetchOptions('');
  },
  [value, onChange, searchDebounced, tagType]
);
  • Apply fix

Check the box to apply the fix or reply for a change | Was this helpful? React with 👍 / 👎

}) => {
const { t } = useTranslation();
const [options, setOptions] = useState<TagSelectItem[]>([]);
const tagDataMap = useRef<Map<string, TagLabel>>(new Map());

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Quality: tagDataMap is an unbounded Map cache without a size cap

The repo caching guideline requires caches to have explicit size caps (use lru-cache in TS, not a bare Map). tagDataMap in TagSuggestion is a useRef<Map<string, TagLabel>> that accumulates an entry for every option returned by every search/glossary fetch for the lifetime of the component and is never pruned. On a long-lived form with many searches this grows unbounded. Note TestCaseFormBody's tablesCache correctly caps at TABLES_CACHE_MAX_SIZE; apply the same bounded pattern (or an lru-cache) to tagDataMap.

Was this helpful? React with 👍 / 👎

Comment on lines +104 to +114
const seedSignature = JSON.stringify([
initialValues?.name ?? '',
initialValues?.description ?? '',
(initialValues?.testCases ?? []).map((tc) => tc.id),
]);

useEffect(() => {
if (open) {
form.reset(computeDefaultValues());
}
}, [open, seedSignature]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Quality: Bundle suite re-seed effect omits deps and relies on JSON.stringify signature

The re-seed effect in BundleSuiteFormDrawer intentionally depends only on [open, seedSignature] and excludes form/computeDefaultValues, and seedSignature is a JSON.stringify of name/description/testCase ids recomputed on every render. This works but is fragile: (1) it will trip react-hooks/exhaustive-deps and any future edit to computeDefaultValues closure that isn't reflected in seedSignature will silently use stale seed data; (2) JSON.stringify on every render is wasted work. Consider memoizing seedSignature with useMemo and adding an eslint-disable with an explanatory comment (already partially present) so the intent is enforced rather than incidental.

Was this helpful? React with 👍 / 👎

@gitar-bot

gitar-bot Bot commented Jul 6, 2026

Copy link
Copy Markdown
Code Review 👍 Approved with suggestions 0 resolved / 3 findings

Migrates Data Quality forms to react-hook-form while maintaining parity with legacy behaviors and selectors. Address the duplicate TagSuggestion insertion, implement an LRU cache for tagDataMap, and refine the Bundle suite re-seed effect dependencies.

💡 Bug: TagSuggestion re-inserts already-selected tag as duplicate

📄 openmetadata-ui/src/main/resources/ui/src/components/common/TagSuggestion/TagSuggestion.tsx:145-159

In handleItemInserted, when the inserted key already exists in value, existingTag is found but the handler still calls onChange?.([...value, newTag]), appending the existing tag again. If react-aria's Autocomplete ever fires onItemInserted for an already-selected item (e.g. rapid re-selection), the tag array will contain duplicates, which then flow into normalizeTagLabels and the created test case payload. Consider guarding: if (existingTag) { return; } before appending, or dedupe by tagFQN before calling onChange.

Skip insertion when the tag is already selected.
const handleItemInserted = useCallback(
  (key: string | number) => {
    if (String(key) === NO_DATA_OPTION_ID) {
      return;
    }
    if (value.some((tag) => tag.tagFQN === String(key))) {
      return; // already selected; avoid duplicate entry
    }
    const tagData = tagDataMap.current.get(String(key));
    const newTag: EntityTags = {
      tagFQN: String(key),
      source: tagType,
      name: tagData?.name,
      displayName: tagData?.displayName,
      description: tagData?.description,
      style: tagData?.style,
    };
    onChange?.([...value, newTag]);
    searchDebounced.cancel();
    fetchOptions('');
  },
  [value, onChange, searchDebounced, tagType]
);
💡 Quality: tagDataMap is an unbounded Map cache without a size cap

📄 openmetadata-ui/src/main/resources/ui/src/components/common/TagSuggestion/TagSuggestion.tsx:64 📄 openmetadata-ui/src/main/resources/ui/src/components/common/TagSuggestion/TagSuggestion.tsx:85-87 📄 openmetadata-ui/src/main/resources/ui/src/components/common/TagSuggestion/TagSuggestion.tsx:113-115

The repo caching guideline requires caches to have explicit size caps (use lru-cache in TS, not a bare Map). tagDataMap in TagSuggestion is a useRef<Map<string, TagLabel>> that accumulates an entry for every option returned by every search/glossary fetch for the lifetime of the component and is never pruned. On a long-lived form with many searches this grows unbounded. Note TestCaseFormBody's tablesCache correctly caps at TABLES_CACHE_MAX_SIZE; apply the same bounded pattern (or an lru-cache) to tagDataMap.

💡 Quality: Bundle suite re-seed effect omits deps and relies on JSON.stringify signature

📄 openmetadata-ui/src/main/resources/ui/src/components/DataQuality/BundleSuiteForm/BundleSuiteFormDrawer.tsx:104-114

The re-seed effect in BundleSuiteFormDrawer intentionally depends only on [open, seedSignature] and excludes form/computeDefaultValues, and seedSignature is a JSON.stringify of name/description/testCase ids recomputed on every render. This works but is fragile: (1) it will trip react-hooks/exhaustive-deps and any future edit to computeDefaultValues closure that isn't reflected in seedSignature will silently use stale seed data; (2) JSON.stringify on every render is wasted work. Consider memoizing seedSignature with useMemo and adding an eslint-disable with an explanatory comment (already partially present) so the intent is enforced rather than incidental.

🤖 Prompt for agents
Code Review: Migrates Data Quality forms to react-hook-form while maintaining parity with legacy behaviors and selectors. Address the duplicate TagSuggestion insertion, implement an LRU cache for tagDataMap, and refine the Bundle suite re-seed effect dependencies.

1. 💡 Bug: TagSuggestion re-inserts already-selected tag as duplicate
   Files: openmetadata-ui/src/main/resources/ui/src/components/common/TagSuggestion/TagSuggestion.tsx:145-159

   In `handleItemInserted`, when the inserted key already exists in `value`, `existingTag` is found but the handler still calls `onChange?.([...value, newTag])`, appending the existing tag again. If react-aria's Autocomplete ever fires `onItemInserted` for an already-selected item (e.g. rapid re-selection), the tag array will contain duplicates, which then flow into `normalizeTagLabels` and the created test case payload. Consider guarding: `if (existingTag) { return; }` before appending, or dedupe by `tagFQN` before calling `onChange`.

   Fix (Skip insertion when the tag is already selected.):
   const handleItemInserted = useCallback(
     (key: string | number) => {
       if (String(key) === NO_DATA_OPTION_ID) {
         return;
       }
       if (value.some((tag) => tag.tagFQN === String(key))) {
         return; // already selected; avoid duplicate entry
       }
       const tagData = tagDataMap.current.get(String(key));
       const newTag: EntityTags = {
         tagFQN: String(key),
         source: tagType,
         name: tagData?.name,
         displayName: tagData?.displayName,
         description: tagData?.description,
         style: tagData?.style,
       };
       onChange?.([...value, newTag]);
       searchDebounced.cancel();
       fetchOptions('');
     },
     [value, onChange, searchDebounced, tagType]
   );

2. 💡 Quality: tagDataMap is an unbounded Map cache without a size cap
   Files: openmetadata-ui/src/main/resources/ui/src/components/common/TagSuggestion/TagSuggestion.tsx:64, openmetadata-ui/src/main/resources/ui/src/components/common/TagSuggestion/TagSuggestion.tsx:85-87, openmetadata-ui/src/main/resources/ui/src/components/common/TagSuggestion/TagSuggestion.tsx:113-115

   The repo caching guideline requires caches to have explicit size caps (use `lru-cache` in TS, not a bare `Map`). `tagDataMap` in TagSuggestion is a `useRef<Map<string, TagLabel>>` that accumulates an entry for every option returned by every search/glossary fetch for the lifetime of the component and is never pruned. On a long-lived form with many searches this grows unbounded. Note `TestCaseFormBody`'s `tablesCache` correctly caps at `TABLES_CACHE_MAX_SIZE`; apply the same bounded pattern (or an `lru-cache`) to `tagDataMap`.

3. 💡 Quality: Bundle suite re-seed effect omits deps and relies on JSON.stringify signature
   Files: openmetadata-ui/src/main/resources/ui/src/components/DataQuality/BundleSuiteForm/BundleSuiteFormDrawer.tsx:104-114

   The re-seed effect in BundleSuiteFormDrawer intentionally depends only on `[open, seedSignature]` and excludes `form`/`computeDefaultValues`, and `seedSignature` is a `JSON.stringify` of name/description/testCase ids recomputed on every render. This works but is fragile: (1) it will trip `react-hooks/exhaustive-deps` and any future edit to `computeDefaultValues` closure that isn't reflected in `seedSignature` will silently use stale seed data; (2) `JSON.stringify` on every render is wasted work. Consider memoizing `seedSignature` with `useMemo` and adding an eslint-disable with an explanatory comment (already partially present) so the intent is enforced rather than incidental.

Options

Display: compact → Showing less information.

Comment with these commands to change the behavior for this request:

Compact
gitar display:verbose         

Was this helpful? React with 👍 / 👎 | Gitar

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

Labels

safe to test Add this label to run secure Github workflows on PRs UI UI specific issues

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant