Skip to content

fix(webui): replace nested create routes with parent-view modals#2081

Merged
andrewazores merged 21 commits into
cryostatio:mainfrom
jtolentino1:fix/parent-view-create-modals
Mar 4, 2026
Merged

fix(webui): replace nested create routes with parent-view modals#2081
andrewazores merged 21 commits into
cryostatio:mainfrom
jtolentino1:fix/parent-view-create-modals

Conversation

@jtolentino1

@jtolentino1 jtolentino1 commented Feb 9, 2026

Copy link
Copy Markdown
Member

Welcome to Cryostat! 👋

Before contributing, make sure you have:

  • Read the contributing guidelines
  • Linked a relevant issue which this PR resolves
  • Linked any other relevant issues, PR's, or documentation, if any
  • Resolved all conflicts, if any
  • Rebased your branch PR on top of the latest upstream main branch
  • Attached at least one of the following labels to the PR: [chore, ci, docs, feat, fix, test]
  • Signed all commits using a GPG signature

To recreate commits with GPG signature git fetch upstream && git rebase --force --gpg-sign upstream/main


Fixes: #1284

Description of the change:

  1. Remove the nested create sub-routes (/recordings/create, /rules/create, /topology/create-custom-target) from routes.tsx. These child route definitions are replaced by PatternFly Modal components rendered directly within their parent views (Recordings.tsx, Rules.tsx, Topology.tsx). Each parent view manages a boolean state that is set to true when location.state.openCreateModal is present, and clears the state on modal close to prevent re-opening on re-render.

  2. Add embedded and onClose props to the three create components (CreateRecording, CreateRule, CreateTarget). When embedded is true, the component skips its page-level wrapper (TargetView or BreadcrumbPage) and returns just the form content, making it suitable for rendering inside a modal. The onClose prop is passed down to the inner form components (CustomRecordingForm, SnapshotRecordingForm, CreateRuleForm) as onExit, which they call instead of navigate('..') when provided.

  3. Add a modalPrefill Redux store slice (ModalPrefillSlice.ts) with modalPrefillSetIntent and modalPrefillClearIntent actions. The useModalFromLocationState hook checks location.state first, then falls back to the Redux modalPrefill store, so the modal opens and receives prefill data even when the OpenShift Console Router drops location.state during cross-page navigation. Cross-page call-sites (Dashboard, Events, Topology actions, CryostatLink) dispatch modalPrefillSetIntent to Redux before calling navigate(). Same-page call-sites (Create button on Recordings, Edit/Copy on Rules) use location.state only since it works reliably for same-page navigation.

  4. Update tests to remove references to the deleted sub-routes, render create components at the parent path instead, and assert navigation to the parent route. A globalThis.Request polyfill is added to test-setup.js, and the Mirage recording creation handler now includes jvmId in the notification message to support the modal's async table refresh.

Motivation for the change:

Navigating directly to the create sub-routes (e.g. by pasting /recordings/create into the browser) renders a blank page with a console error because the nested routes cannot properly initialize outside of their parent context. Replacing them with modals avoids this issue entirely, since the parent view is always the entry point.

How to manually test:

  1. Start the application with Mirage or against a live backend
  2. Navigate to Recordings, click "Create" in the toolbar, verify the modal opens and a recording can be created
  3. Navigate to Automated Rules, click "Create", verify the modal opens with the Match Expression Visualizer
  4. Navigate to Topology, click "Create Custom Target", verify the modal opens
  5. From Event Templates, use the "Create Recording..." kebab action on a template, verify it navigates to Recordings and opens the modal with the template pre-filled
image image image

@github-actions github-actions Bot added the needs-triage Needs thorough attention from code reviewers label Feb 9, 2026
@jtolentino1 jtolentino1 self-assigned this Feb 9, 2026
@jtolentino1 jtolentino1 added fix and removed needs-triage Needs thorough attention from code reviewers labels Feb 9, 2026
@jtolentino1 jtolentino1 marked this pull request as ready for review February 13, 2026 02:54

@andrewazores andrewazores left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Functionality looks and feels great!

When embedded is true

Are there cases where we still use these components with embedded={false}?

@jtolentino1

Copy link
Copy Markdown
Member Author

Functionality looks and feels great!

When embedded is true

Are there cases where we still use these components with embedded={false}?

Good catch. Now that the standalone create routes are removed, embedded is always true ... there's no code path that uses embedded={false} anymore. I'll simplify by removing the embedded prop and the conditional wrapper logic so the components always return just the form content.

@andrewazores

Copy link
Copy Markdown
Member

All looking good. I just had a thought though - we have some issues with passing state around using the React router when cryostat-web is running as part of the OpenShift Console plugin: cryostatio/cryostat-openshift-console-plugin#143

I guess this also means that with this change, the user will be able to navigate ex. from a Dashboard card or the Topology view to Recordings when they want to start a recording, but the modal will not automatically appear (and will continue to not be pre-populated if they manually open it)?

@jtolentino1

Copy link
Copy Markdown
Member Author

All looking good. I just had a thought though - we have some issues with passing state around using the React router when cryostat-web is running as part of the OpenShift Console plugin: cryostatio/cryostat-openshift-console-plugin#143

I guess this also means that with this change, the user will be able to navigate ex. from a Dashboard card or the Topology view to Recordings when they want to start a recording, but the modal will not automatically appear (and will continue to not be pre-populated if they manually open it)?

Hmm.. how about using a URL search param for the modal trigger and keep location.state for the pre-fill data as best-effort?

@andrewazores

Copy link
Copy Markdown
Member

I don't know how well URL search params work in the context of a console plugin, but it's worth giving it a shot.

@andrewazores

Copy link
Copy Markdown
Member

Check out cryostatio/cryostat-openshift-console-plugin#143 (comment) , I wrote down my thoughts on what the problem is. I could be wrong though, this was just my brain dump from what I understand is going on. If you're going to test out this PR in an OpenShift context anyway I encourage you to also test out my hypothesis about the router state being intercepted. If that is the case then it may not be so hard for us to solve on the Console Plugin side, and may not need to worry about the URL search params "fix" on this side.

@jtolentino1

Copy link
Copy Markdown
Member Author

Check out cryostatio/cryostat-openshift-console-plugin#143 (comment) , I wrote down my thoughts on what the problem is. I could be wrong though, this was just my brain dump from what I understand is going on. If you're going to test out this PR in an OpenShift context anyway I encourage you to also test out my hypothesis about the router state being intercepted. If that is the case then it may not be so hard for us to solve on the Console Plugin side, and may not need to worry about the URL search params "fix" on this side.

Okay so I tested this PR inside the OpenShift Console plugin and it lines up with the router-state interception issue from cryostatio/cryostat-openshift-console-plugin#143: initially only Automated Rules “Create” worked (it opens via local component state), while Recordings/Topology “Create” did nothing (they depended on location.state.openCreateModal, which wasn’t arriving). As a next step I tried my URL search param idea (?openCreateModal=1) as the modal-open trigger ... and with that in place all create modals open reliably in-console, but prefill is still dropped for flows like Rules → Edit and Events → Create Recording (so location.state is still not propagating). This makes me think the real fix is on the cryostat-openshift-console-plugin side (ensuring router state reaches the embedded Cryostat Web views), which would restore both modal open + prefill behavior and potentially eliminate the need for the URL search param workaround in cryostat-web.

@jtolentino1 jtolentino1 force-pushed the fix/parent-view-create-modals branch 2 times, most recently from d5e858d to 8302bb8 Compare February 27, 2026 17:19
@jtolentino1 jtolentino1 force-pushed the fix/parent-view-create-modals branch from 8302bb8 to b85f029 Compare February 27, 2026 17:23
…e-modals

# Conflicts:
#	src/app/CreateRecording/CreateRecording.tsx
#	src/app/routes.tsx
@andrewazores

Copy link
Copy Markdown
Member

@jtolentino1 rebase, please. I think this is ready for review now, right?

@jtolentino1

Copy link
Copy Markdown
Member Author

@jtolentino1 rebase, please. I think this is ready for review now, right?

no, not yet. I was testing this commit: b85f029 with cryostat-openshift-plugin and wasn't working just yet. actively working on a fix

@jtolentino1 jtolentino1 requested a review from andrewazores March 2, 2026 21:01
Comment thread src/app/Recordings/Recordings.tsx Outdated
@andrewazores

andrewazores commented Mar 2, 2026

Copy link
Copy Markdown
Member

Minor bug:

  1. Go to Events
  2. Pick a Template
  3. Click the overflow menu, Create Recording
  4. Fill in the modal/form and click Create
  5. A success notification appears and the table behind the modal appears to update, but the modal is not dismissed

Similarly, creating a JFR Metrics dashboard card and going through its recording creation flow does not dismiss the modal either. And the same with Target Analysis, from the Recordings view.

Directly creating a recording from the Recording view does dismiss the modal upon recording creation.

Comment thread src/app/Shared/Components/CryostatLink.tsx

@andrewazores andrewazores left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Looks great!

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] Subviews like /recordings/create are not accessible when visited directly

2 participants