Skip to content

LFX Mentorship: Feat: Week 2-3 - Implement TokenRatePolicy form view#575

Open
justin212407 wants to merge 1 commit into
Kuadrant:mainfrom
justin212407:Form-views-for-policy-creation-pages
Open

LFX Mentorship: Feat: Week 2-3 - Implement TokenRatePolicy form view#575
justin212407 wants to merge 1 commit into
Kuadrant:mainfrom
justin212407:Form-views-for-policy-creation-pages

Conversation

@justin212407

@justin212407 justin212407 commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Description

Adds a form-based creation/editing view to KuadrantTokenRateLimitPolicyCreatePage.tsx, which previously only rendered a static YAML editor with a hardcoded example resource. Users can now create and edit TokenRateLimitPolicy resources through a guided form interface without needing to know the CRD spec structure, while retaining the option to switch to a YAML view at any point.

This is the second deliverable in the Add form views for policy creation pages (#378) epic, following the same five-pillar architecture established in KuadrantDNSPolicyCreatePage.tsx and first implemented for OIDCPolicy in #545.

Type of change

  • [feat] New feature

Changes made

  • Replaced the static YAML-only editor in KuadrantTokenRateLimitPolicyCreatePage.tsx with a complete form view following the five-pillar architecture from KuadrantDNSPolicyCreatePage.tsx.

  • Added a Form/YAML toggle (PatternFly Radio buttons), with both views backed by the same underlying state.

  • Implemented createTokenRateLimitPolicy() to generate the complete Kubernetes resource from form state. This object is shared by both ResourceYAMLEditor and KuadrantCreateUpdate.

  • Implemented bidirectional YAML synchronization:

    • useEffect updates YAML whenever form fields change.
    • handleYAMLChange() parses edited YAML back into form state using js-yaml.
  • Added edit mode hydration:

    • Detects edit mode from the URL.
    • Uses useK8sWatchResource to load the existing resource.
    • Populates all form fields and configured limits.
    • Locks the resource name using formDisabled.
  • Built an inline limits UI using:

    • PatternFly Label and LabelGroup for displaying configured limits.
    • A local Modal for adding new limits with Limit Name, Limit Value, and Window fields.
  • Wired KuadrantCreateUpdate for both create and update flows with policyType="tokenratelimit".

  • Added src/components/tokenratelimitpolicy/useTokenRateLimitPolicyActions.tsx, providing:

    • Edit (form)
    • Edit labels
    • Edit annotations
    • Delete
  • Added the edit route:

    /k8s/ns/:ns/tokenratelimitpolicy/name/:name/edit
    

    pointing to KuadrantTokenRateLimitPolicyCreatePage in console-extensions.json.

  • Registered useTokenRateLimitPolicyActions as a console.action/resource-provider for kuadrant.io/v1alpha1/TokenRateLimitPolicy.

  • Exported useTokenRateLimitPolicyActions from package.json via exposedModules.

  • Added 7 new i18n keys to locales/en/plugin__kuadrant-console-plugin.json in alphabetical order.

Test plan

  • yarn lint passes (no changes after running)
  • yarn build passes
  • yarn i18n passes (no changes after running; commit updated locale files if needed)
  • Tested manually in OpenShift Console
  • Tested in both light and dark themes
  • Tested in all-namespaces and single namespace mode

Manual testing

  1. Navigate to:

    Create Policy → TokenRateLimitPolicy
    
  2. Verify the form renders with:

    • Policy Name
    • Target Gateway
    • Configured Limits
  3. Leave Policy Name empty and verify the Save button is disabled.

  4. Enter a policy name and select a gateway.

    Verify Save becomes enabled.

  5. Click Add Limit.

    Verify the modal opens with:

    • Limit Name
    • Limit
    • Window
  6. Leave all modal fields empty.

    Verify Add Limit is disabled.

  7. Fill all three fields.

    Verify Add Limit becomes enabled.

  8. Save the limit.

    Verify:

    • the modal closes
    • a label appears showing:
    <name>: <value> per <window>
    
  9. Remove a limit using the × button.

    Verify it disappears.

  10. Switch to YAML View.

    Verify the generated YAML contains:

    apiVersion: kuadrant.io/v1alpha1
    kind: TokenRateLimitPolicy
    metadata:
      name: <your-value>
      namespace: default
    
    spec:
      targetRef:
        group: gateway.networking.k8s.io
        kind: Gateway
        name: <your-value>
        namespace: <your-value>
    
      limits:
        <limit-name>:
          rates:
            - limit: <number>
              window: <string>
  11. Edit the YAML.

    Switch back to Form View and verify the form reflects the updated values.

  12. Submit the form.

    Verify the user is redirected to the TokenRateLimitPolicy list page.

  13. Open the kebab menu for an existing TokenRateLimitPolicy.

    Verify the following actions are available:

    • Edit
    • Edit labels
    • Edit annotations
    • Delete
  14. Click Edit.

    Verify:

    • navigation to the edit route
    • form fields are pre-populated
    • configured limits are restored
  15. Click Cancel.

    Verify the browser navigates back.

Screenshots

Screencast.From.2026-06-25.00-53-32.mp4

Review guidance

Suggested review order

  1. src/components/KuadrantTokenRateLimitPolicyCreatePage.tsx

    Main implementation.

    Compare against KuadrantDNSPolicyCreatePage.tsx for five-pillar architecture consistency.

    Key areas:

    • Local TokenRate, TokenLimitConfig, and TokenLimitMap interfaces
    • Inline limits UI and modal
    • createTokenRateLimitPolicy() factory
    • Bidirectional YAML synchronization
    • Edit mode hydration
  2. src/components/tokenratelimitpolicy/useTokenRateLimitPolicyActions.tsx

    New resource action hook following the same implementation pattern as useOIDCPolicyActions.tsx.

  3. console-extensions.json

    Review:

    • edit route
    • resource action provider registration
  4. package.json

    Verify useTokenRateLimitPolicyActions has been added to exposedModules.

  5. locales/en/plugin__kuadrant-console-plugin.json

    Confirm the seven new translation keys are alphabetically ordered.

Why a local modal?

The existing AddLimitModal uses:

{
  duration: number;
  limit: number;
  unit: string;
}

whereas TokenRateLimitPolicy expects:

{
  limit: number;
  window: string;
}

Rather than modifying a shared component used elsewhere, this PR introduces a self-contained modal that matches the TokenRateLimitPolicy schema, keeping the change isolated and avoiding regressions for existing RateLimitPolicy functionality.

Checklist

  • All user-facing strings use t() and have been added to locales/en/plugin__kuadrant-console-plugin.json
  • CSS classes are prefixed with kuadrant- (no bare .pf-* or .co-* selectors and no hex colors)
  • No console.log statements remain
  • Commits include a Signed-off-by line (git commit -s)

Summary by CodeRabbit

  • New Features

    • Added support for creating and editing TokenRateLimitPolicy resources from the console.
    • Introduced a form-based editor with YAML switching, limit management, and gateway selection.
    • Added resource actions for editing labels, annotations, deleting, and opening the form editor.
  • Documentation

    • Updated in-app text and labels for token-based rate limiting settings and policy descriptions.

Signed-off-by: justin212407 <charlesjustin2124@gmail.com>
@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown

Thanks for the contribution! The linked issue(s) are closed. Please link an open, triaged issue and reopen this PR.

@github-actions github-actions Bot closed this Jul 1, 2026
@coderabbitai

coderabbitai Bot commented Jul 1, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 37bfc46e-1f56-4f79-ab74-a72a9bec5fbe

📥 Commits

Reviewing files that changed from the base of the PR and between ddc8cd8 and 64ef77d.

📒 Files selected for processing (5)
  • console-extensions.json
  • locales/en/plugin__kuadrant-console-plugin.json
  • package.json
  • src/components/KuadrantTokenRateLimitPolicyCreatePage.tsx
  • src/components/tokenratelimitpolicy/useTokenRateLimitPolicyActions.tsx

📝 Walkthrough

Walkthrough

This PR adds a form/YAML toggle create-and-edit page for TokenRateLimitPolicy resources, a new actions hook exposing edit/delete/labels/annotations actions and a "form edit" navigation action, corresponding console extension route/action wiring, an exposed-module registration, and related translation entries.

Changes

TokenRateLimitPolicy Form Editing

Layer / File(s) Summary
Imports, types, and resource builder
src/components/KuadrantTokenRateLimitPolicyCreatePage.tsx
Expands imports for form/modal/routing/k8s helpers, defines limit-related interfaces, and adds createTokenRateLimitPolicy() to build the resource object from form state.
Edit-mode detection and state hydration
src/components/KuadrantTokenRateLimitPolicyCreatePage.tsx
Detects edit mode from the route path, watches the existing resource, and hydrates form state (name, gateway, limits, metadata) with the form disabled in edit mode.
YAML parsing and synchronisation
src/components/KuadrantTokenRateLimitPolicyCreatePage.tsx
Adds handleYAMLChange to parse YAML into form state and an effect to keep yamlInput synchronised with form state.
Form event handlers and limit CRUD
src/components/KuadrantTokenRateLimitPolicyCreatePage.tsx
Adds handlers for name updates, cancel, limit removal, and the Add Limit modal save/open logic, plus save/form-enabled validation flags.
Form/YAML toggle rendering and Add Limit modal
src/components/KuadrantTokenRateLimitPolicyCreatePage.tsx
Renders the radio-controlled form/YAML toggle, policy name input, GatewaySelect, configured-limits label list, KuadrantCreateUpdate, ResourceYAMLEditor, and the Add Limit modal.
TokenRateLimitPolicy actions hook
src/components/tokenratelimitpolicy/useTokenRateLimitPolicyActions.tsx
New hook computing access-review attributes and returning edit-labels/annotations, form-edit navigation, and delete actions.
Console extension wiring, exposed module, and translations
console-extensions.json, package.json, locales/en/plugin__kuadrant-console-plugin.json
Registers the edit route and action provider for TokenRateLimitPolicy, exposes the new actions hook module, and adds/removes related translation keys.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant ActionsMenu as useTokenRateLimitPolicyActions
    participant CreatePage as KuadrantTokenRateLimitPolicyCreatePage
    participant K8sAPI as Kubernetes API

    User->>ActionsMenu: Select "Edit via form"
    ActionsMenu->>CreatePage: Navigate to /tokenratelimitpolicy/name/:name/edit

    CreatePage->>K8sAPI: useK8sWatchResource(TokenRateLimitPolicy)
    K8sAPI-->>CreatePage: Existing resource data
    CreatePage->>CreatePage: Populate form state (name, gateway, limits)

    alt Form view
        User->>CreatePage: Edit fields / add limit
        CreatePage->>CreatePage: Update yamlInput to stay in sync
    else YAML view
        User->>CreatePage: Edit YAML
        CreatePage->>CreatePage: Parse YAML, update form state
    end

    User->>CreatePage: Save
    CreatePage->>K8sAPI: Submit updated TokenRateLimitPolicy resource
    K8sAPI-->>CreatePage: Confirmation / error
    CreatePage-->>User: Redirect or show result
Loading

Possibly related issues

Suggested reviewers: emmaaroche, eguzki, didierofrivia

Poem

A rabbit hops through YAML and form,
Toggling views to keep policies warm,
Limits added, gateways bound,
Edit mode humming with a watchful sound,
Hop, save, and cancel — all safe and sound! 🐇✨

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install failed. For unrecoverable errors, disable the tool in CodeRabbit configuration.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@emmaaroche emmaaroche reopened this Jul 1, 2026

@emmaaroche emmaaroche 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.

Nice work, left a few comments below 👍

Also one additional comment - The CRD spec shows rates is an array, supporting multiple rate windows per limit (like "1m" and "1h"). The modal currently only supports adding one rate per limit. We should update this to support multiple rates. See docs: https://docs.kuadrant.io/dev/kuadrant-operator/doc/overviews/token-rate-limiting/#multiple-time-windows

Comment on lines +206 to +216
const handleSaveLimit = () => {
if (newLimitName && newLimitValue !== '' && newLimitWindow) {
setLimits((prevLimits) => ({
...prevLimits,
[newLimitName]: { rates: [{ limit: Number(newLimitValue), window: newLimitWindow }] },
}));
setIsAddLimitOpen(false);
}
};

const isAddLimitSaveDisabled = !newLimitName || newLimitValue === '' || !newLimitWindow;

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.

When a user adds a limit with the same name twice (e.g., "global"), the second one silently overwrites the first without warning. This could be confusing since they won't know why their first configuration disappeared.

Adding duplicate detection would prevent this, example:

const isDuplicateName = !!limits[newLimitName];
const isAddLimitSaveDisabled =
  !newLimitName ||
  newLimitValue === '' ||
  !newLimitWindow ||
  isDuplicateName;

And show validation feedback:

<FormHelperText>
  <HelperText>
    <HelperTextItem variant={isDuplicateName ? 'error' : 'default'}>
      {isDuplicateName
        ? t('A limit with this name already exists')
        : t('Unique identifier for this rate limit')}
    </HelperTextItem>
  </HelperText>
</FormHelperText>

Comment on lines +364 to +379
<FormGroup label={t('Window')} isRequired fieldId="new-limit-window">
<TextInput
isRequired
type="text"
id="new-limit-window"
value={newLimitWindow}
onChange={(_event, value) => setNewLimitWindow(value)}
placeholder="e.g. 1h, 60s, 1440m"
/>
<FormHelperText>
<HelperText>
<HelperTextItem>
{t('Time window for the rate limit (e.g. 1h, 60s, 1440m)')}
</HelperTextItem>
</HelperText>
</FormHelperText>

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.

Window field accepts any string right now, so users can enter invalid formats like "1 hour" or "60" and only discover the error when the K8s API rejects it with a regex validation message.

The window field must follow Gateway API Duration format with pattern ^([0-9]{1,5}(h|m|s|ms)){1,4}$.

Valid formats: "1h", "60s", "500ms", "1h30m", "2h15m30s"
Invalid formats: "1 hour" (spaces), "1d" (days not supported), "1000000h" (max 5 digits per unit)

Adding client-side validation would catch these early with clear feedback instead of the API error when trying to create, example:

const windowPattern = /^([0-9]{1,5}(h|m|s|ms)){1,4}$/;
const isValidWindow = !newLimitWindow || windowPattern.test(newLimitWindow);

<TextInput
  validated={isValidWindow ? 'default' : 'error'}
/>
<FormHelperText>
  <HelperText>
    <HelperTextItem variant={isValidWindow ? 'default' : 'error'}>
      {isValidWindow
        ? t('Time window for the rate limit (e.g. 1h, 60s, 1440m)')
        : t('Format must be like: 1h, 60s, 500ms, or 1h30m')}
    </HelperTextItem>
  </HelperText>
</FormHelperText>

Comment on lines +347 to +355
<FormGroup label={t('Limit')} isRequired fieldId="new-limit-value">
<TextInput
isRequired
type="number"
id="new-limit-value"
value={newLimitValue}
onChange={(_event, value) => setNewLimitValue(value === '' ? '' : Number(value))}
placeholder={t('Limit value')}
/>

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.

The limit input field has a small clickable area where only the left portion where the placeholder text appears is responsive. The rest of the input box doesn't accept clicks/focus, which can create a confusing UX.

Image

@emmaaroche

Copy link
Copy Markdown
Member

FYI this PR will require a rebase once #457 is merged

@justin212407

Copy link
Copy Markdown
Contributor Author

FYI this PR will require a rebase once #457 is merged

I will make the changes and rebase before pushing the next commit.

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