feat(studio): add guardrail creation flow - #1115
Conversation
Adds a "Create Guardrail" button to the guardrails list header that
navigates to /guardrails/new, where a modal lets the user name the
guardrail and POST it to the API before redirecting to the detail route.
Client-side validation matches the backend name pattern
(^[a-z](?!.*--)[a-z0-9\-@.+_]{1,62}(?<!-)$) so invalid names are
caught before the API is called. Input attributes suppress browser
autocomplete and autocorrect for the identifier field.
Signed-off-by: Alex Ray <alray@nvidia.com>
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughChangesGuardrail creation
Sequence Diagram(s)sequenceDiagram
participant GuardrailsRoute
participant CreateGuardrailModal
participant MSWGuardrailHandler
participant mockGuardrailConfigs
GuardrailsRoute->>CreateGuardrailModal: Open creation modal
CreateGuardrailModal->>MSWGuardrailHandler: Submit name and description
MSWGuardrailHandler->>mockGuardrailConfigs: Store generated configuration
MSWGuardrailHandler-->>CreateGuardrailModal: Return created configuration with HTTP 201
CreateGuardrailModal->>GuardrailsRoute: Navigate to created guardrail
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
web/packages/studio/src/routes/utils.ts (1)
401-403: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueAdd an explicit return type.
Add
: stringto this exported route helper.As per coding guidelines, use explicit return types for public APIs and complex functions.
Proposed fix
-export const getGuardrailNewRoute = (workspace: string) => { +export const getGuardrailNewRoute = (workspace: string): string => {🤖 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/routes/utils.ts` around lines 401 - 403, Add the explicit string return type to the exported getGuardrailNewRoute function, preserving its existing generatePath implementation and behavior.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/mocks/handlers/guardrails.ts`:
- Line 149: Remove the duplicate body declaration in the request handler,
keeping a single parsed request body variable with the existing { name: string;
description?: string } type used by the handler.
In `@web/packages/studio/src/routes/guardrails/GuardrailNewRoute/index.tsx`:
- Around line 58-66: Update the FormModal invocation in GuardrailNewRoute to
pass disabled={isPending}, preventing cancel and close actions while guardrail
creation is pending; leave the existing submitDisabled and loading behavior
unchanged.
---
Nitpick comments:
In `@web/packages/studio/src/routes/utils.ts`:
- Around line 401-403: Add the explicit string return type to the exported
getGuardrailNewRoute function, preserving its existing generatePath
implementation and behavior.
🪄 Autofix
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: 48327e31-c0c9-428f-8c9f-b6f8aea5de03
📒 Files selected for processing (6)
web/packages/studio/src/constants/routes.tsweb/packages/studio/src/mocks/handlers/guardrails.tsweb/packages/studio/src/routes/groups/guardrailsRoutes.tsxweb/packages/studio/src/routes/guardrails/GuardrailNewRoute/index.tsxweb/packages/studio/src/routes/guardrails/GuardrailsRoute/index.tsxweb/packages/studio/src/routes/utils.ts
- Rename body to input in POST mock handler to avoid shadowing the PATCH
handler's body variable in the same file scope
- Add disabled={isPending} to FormModal so the user cannot close or
resubmit the modal while the create request is in flight
Signed-off-by: Alex Ray <alray@nvidia.com>
|
Avoids a naming conflict where a guardrail named 'new' would be unreachable via the UI. The ~ character is outside the backend name pattern so /~new can never collide with a real guardrail name. Signed-off-by: Alex Ray <alray@nvidia.com>
Drops the /~new sub-path entirely. The creation modal is now opened by local state in GuardrailsRoute rather than a child route, so there is no URL segment that can collide with a guardrail name and no history entry to skip past when navigating back from the detail page. Moves the component to GuardrailsRoute/CreateGuardrailModal, matching the CreateSecretModal placement under SecretsListRoute. Signed-off-by: Alex Ray <alray@nvidia.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
web/packages/studio/src/routes/guardrails/GuardrailsRoute/CreateGuardrailModal/index.tsx (1)
18-22: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueRename the schema constant.
Rename
schematoSCHEMA. Update its references.Proposed fix
-const schema = z.object({ +const SCHEMA = z.object({ name: entityNameSchema(), }); -type FormData = z.infer<typeof schema>; +type FormData = z.infer<typeof SCHEMA>;As per coding guidelines: “Use
SCREAMING_SNAKE_CASEfor constants.”🤖 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/routes/guardrails/GuardrailsRoute/CreateGuardrailModal/index.tsx` around lines 18 - 22, Rename the `schema` constant to `SCHEMA` in the guardrail modal and update all references, including the `FormData` type inference, while preserving its existing validation behavior.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/routes/guardrails/GuardrailsRoute/CreateGuardrailModal/index.tsx`:
- Around line 58-59: Update the onSubmit handler to catch only the createConfig
mutation rejection, assign the mutation error to errorText, and return before
continuing the success path so the rejected promise cannot escape FormModal. Add
a rejected-POST test for the guardrail creation flow that verifies no unhandled
rejection and visible error text.
---
Nitpick comments:
In
`@web/packages/studio/src/routes/guardrails/GuardrailsRoute/CreateGuardrailModal/index.tsx`:
- Around line 18-22: Rename the `schema` constant to `SCHEMA` in the guardrail
modal and update all references, including the `FormData` type inference, while
preserving its existing validation behavior.
🪄 Autofix
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: cdd49329-8fe1-4a04-9884-2c2b2df76c60
📒 Files selected for processing (2)
web/packages/studio/src/routes/guardrails/GuardrailsRoute/CreateGuardrailModal/index.tsxweb/packages/studio/src/routes/guardrails/GuardrailsRoute/index.tsx
…ection react-hook-form re-throws from its submit handler and FormModal drops the returned promise, so a rejected POST surfaced as an unhandled rejection. The user already saw the failure via errorText, but the rejection escaped and turned the test run red. Catch it and return, matching CreateSecretModal. Also renames the zod schema to createGuardrailFormSchema to match the naming used by sibling form modals, and adds tests covering the success path, the failed-create path, and client-side name validation. Signed-off-by: Alex Ray <alray@nvidia.com>
There was a problem hiding this comment.
🧹 Nitpick comments (2)
web/packages/studio/src/routes/guardrails/GuardrailsRoute/CreateGuardrailModal/index.test.tsx (2)
50-53: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick winAssert query invalidation after creation.
The success test checks navigation and
onClose, but notqueryClient.invalidateQueries. A regression can leave the guardrails list stale while this test passes. Return theQueryClientfromrenderModal, spy oninvalidateQueries, and assert the workspace config query key.🤖 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/routes/guardrails/GuardrailsRoute/CreateGuardrailModal/index.test.tsx` around lines 50 - 53, Update the success test around renderModal to expose its QueryClient, spy on queryClient.invalidateQueries, and assert it is called with the workspace configuration query key after guardrail creation. Preserve the existing navigation and onClose assertions.
41-43: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick winAssert the POST payload in both MSW handlers.
Both handlers return fixed responses without reading
request. The tests pass even ifCreateGuardrailModalsends the wrongname. Parse the request body and assert{ name: 'my-rail' }.Suggested assertion
- http.post(CONFIGS_URL, () => - HttpResponse.json({ name: 'my-rail' }, { status: 201 }) - ) + http.post(CONFIGS_URL, async ({ request }) => { + expect(await request.json()).toEqual({ name: 'my-rail' }); + return HttpResponse.json({ name: 'my-rail' }, { status: 201 }); + })Also applies to: 57-61
🤖 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/routes/guardrails/GuardrailsRoute/CreateGuardrailModal/index.test.tsx` around lines 41 - 43, Update both MSW POST handlers in the CreateGuardrailModal tests to accept the request, parse its JSON body, and assert that the payload equals { name: 'my-rail' } before returning the existing responses. Apply this to the handlers around both referenced test sections.
🤖 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.
Nitpick comments:
In
`@web/packages/studio/src/routes/guardrails/GuardrailsRoute/CreateGuardrailModal/index.test.tsx`:
- Around line 50-53: Update the success test around renderModal to expose its
QueryClient, spy on queryClient.invalidateQueries, and assert it is called with
the workspace configuration query key after guardrail creation. Preserve the
existing navigation and onClose assertions.
- Around line 41-43: Update both MSW POST handlers in the CreateGuardrailModal
tests to accept the request, parse its JSON body, and assert that the payload
equals { name: 'my-rail' } before returning the existing responses. Apply this
to the handlers around both referenced test sections.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Enterprise
Run ID: 328418d3-4887-429e-a46f-b0686b0048bf
📒 Files selected for processing (2)
web/packages/studio/src/routes/guardrails/GuardrailsRoute/CreateGuardrailModal/index.test.tsxweb/packages/studio/src/routes/guardrails/GuardrailsRoute/CreateGuardrailModal/index.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
- web/packages/studio/src/routes/guardrails/GuardrailsRoute/CreateGuardrailModal/index.tsx
Signed-off-by: Alex Ray <alray@nvidia.com>
…reation-flow Signed-off-by: Alex Ray <alray@nvidia.com>
…reation-flow Signed-off-by: Alex Ray <alray@nvidia.com>
Summary
Screen.Recording.2026-08-06.at.12.20.57.PM.mov
Adds guardrail creation to Studio.
entityNameSchemahelper, so invalid names are caught client-side before the request goes out and the field shows the same guidance as other entity-name inputsPOSThandler for local developmentThe modal is driven by local state in
GuardrailsRouterather than a route of its own. An earlier revision put it behind a/guardrails/newsub-path to make the flow linkable, but a guardrail literally namednewwould then be unreachable, and the extra history entry sat between the detail page and the list on back-navigation. Neither was worth the linkability for a transient two-field modal.It lives at
GuardrailsRoute/CreateGuardrailModal, matching howCreateSecretModalsits underSecretsListRoute.Test plan
Summary by CodeRabbit
New Features
Bug Fixes