Skip to content

Semantic routing policy and Intelligent Model Routing #1236

Merged
PasanT9 merged 1 commit intowso2:mainfrom
pamaljayasinghe:semantic_routing
Mar 27, 2026
Merged

Semantic routing policy and Intelligent Model Routing #1236
PasanT9 merged 1 commit intowso2:mainfrom
pamaljayasinghe:semantic_routing

Conversation

@pamaljayasinghe
Copy link
Contributor

@pamaljayasinghe pamaljayasinghe commented Jan 9, 2026

PR Description

Purpose

Add frontend support for Semantic Routing and Intelligent Model Routing policies in the API Publisher portal.

Semantic Routing Policy

  • Enables routing requests to different AI/LLM models based on semantic similarity.
  • Users can configure:
    • A default model (fallback)
    • Multiple routing rules with utterances (keywords), score threshold, and target model/endpoint
    • Content path (JSONPath) for payload extraction
    • Separate production and sandbox configs
attach-model-semantic-model-routing aws-bedrock-semantic-model-routing-policy-configuration aws-bedrock-semantic-model-routing-policy-configuration

Intelligent Model Routing Policy

  • Enables context-aware routing to AI/LLM models using natural language rule descriptions.
  • Users can configure:
    • A default model (fallback)
    • Multiple routing rules with name, context description, and target model/endpoint
    • Content path (JSONPath) for payload extraction
    • Separate production and sandbox configs
attach-model-intelligent-model-routing intelligent-model-routing-policy-configuration aws-bedrock-intelligent-model-routing-policy-configuration

Changes

  • Added: SemanticRouting.tsx and IntelligentModelRouting.tsx custom policy forms
  • Modified: General.tsx to integrate new policy forms and validation
  • Updated: en.json with new i18n strings

@coderabbitai
Copy link

coderabbitai bot commented Jan 9, 2026

Note

Reviews paused

It 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 reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Two new custom policy components (IntelligentModelRouting and SemanticRouting) are introduced to configure semantic and model-based routing rules across production and sandbox environments. The General policy form is updated to render and validate these components. Localization keys are added to support the new UI elements and validation messaging.

Changes

Cohort / File(s) Summary
Localization Support
portals/publisher/src/main/webapp/site/public/locales/en.json
Added 43 new localization keys for IntelligentModelRouting and SemanticRouting policy configurations, including environment toggles, routing rules, model selection, content paths, and endpoint status messaging; also added Async Operation security keys.
Policy Form Integration
portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/AttachedPolicyForm/General.tsx
Integrated SemanticRouting and IntelligentModelRouting policy components into form rendering logic; added form validation state (isManualFormValid, showValidationErrors); extended save logic to validate manual-mode forms before persisting; updated button disabled state to reflect validation status.
Routing Policy Components
portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/CustomPolicies/IntelligentModelRouting.tsx, portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/CustomPolicies/SemanticRouting.tsx
Added two new policy configuration components featuring dual production/sandbox accordion sections with model selection, routing rule management, content path configuration, and validation. IntelligentModelRouting includes context-based routing rules; SemanticRouting includes utterance management and score thresholds. Both fetch endpoint and model lists, parse/serialize policy configs, and provide form validation feedback.

Sequence Diagram

sequenceDiagram
    participant User as User
    participant Form as General.tsx<br/>(Policy Form)
    participant Policy as SemanticRouting /<br/>IntelligentModelRouting
    participant API as Backend APIs
    participant State as Form State

    User->>Form: Select routing policy type
    Form->>Policy: Render component with props
    Note over Policy: Initialize from manualPolicyConfig
    Policy->>API: Fetch model list & endpoints
    API-->>Policy: Return models and endpoints
    Policy->>State: Parse & hydrate policy config
    User->>Policy: Configure routing rules, models, paths
    Policy->>Policy: Validate form (contentPath, rules, models)
    Policy->>State: Update isManualFormValid
    User->>Form: Click Save
    Form->>Form: Check isManualFormValid
    alt Form Valid
        Form->>Policy: Serialize config to string
        Policy-->>Form: Return serialized config
        Form->>State: Call setManualPolicyConfig
        Form->>API: Submit policy with config
        API-->>Form: Success
    else Form Invalid
        Form->>Form: Show validation errors
        Form->>User: Display error messages
    end
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 Routing rules now bloom and grow,
Through semantic paths they flow,
Models chosen with care and grace,
Production and sandbox find their place,
The form validates with gentle might,
Smart routing takes its flight! ✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately reflects the main changes: additions of semantic routing policy and intelligent model routing UI components with supporting localization and form integration.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

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

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 and usage tips.

@ashera96
Copy link
Contributor

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Jan 13, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 11

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
portals/publisher/src/main/webapp/site/public/locales/en.json (1)

1389-1442: Tighten consistency for new routing policy strings (casing + pluralization).

A few values look inconsistent (e.g., “Add Rule” vs “Add rule”, “Routing Rules” vs “Routing rule”). If these are user-facing labels/buttons, aligning casing/pluralization will make the two new policy UIs feel cohesive.

portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/AttachedPolicyForm/General.tsx (1)

136-148: Reset isManual/providerNotConfigured when policyObj changes (current code can get “stuck”).

Right now you only ever call setManual(true); if the same drawer instance is reused for a different policy, isManual may remain true and/or providerNotConfigured may remain true, breaking subsequent policy editing.

Proposed fix
 useEffect(() => {
-    if (
-        (policyObj && policyObj.name === 'modelRoundRobin') ||
-        (policyObj && policyObj.name === 'modelWeightedRoundRobin') ||
-        (policyObj && policyObj.name === 'modelFailover') ||
-        (policyObj && policyObj.name === 'semanticRouting') ||
-        (policyObj && policyObj.name === 'intelligentModelRouting')
-    ) {
-        setManual(true);
-    }
+    const manualPolicies = new Set([
+        'modelRoundRobin',
+        'modelWeightedRoundRobin',
+        'modelFailover',
+        'semanticRouting',
+        'intelligentModelRouting',
+    ]);
+    setManual(manualPolicies.has(policyObj?.name ?? ''));
+    // Avoid “sticky” disable if user switches away from routing policies.
+    setProviderNotConfigured(false);
 }, [policyObj]);
🤖 Fix all issues with AI agents
In
@portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/AttachedPolicyForm/General.tsx:
- Around line 492-505: The save-button disabled logic ignores the global saving
state for manual policies and allows empty manual configs; update the disabled
expression so saving always disables the button (include saving independent of
isManual) and add a validation check that manualPolicyConfig is non-empty when
isManual (use the same manualPolicyConfig value passed to
SemanticRouting/IntelligentModelRouting, e.g.,
getValue(policySpec.policyAttributes[0]) or the manualPolicyConfig state), while
preserving the existing providerNotConfigured and the non-manual validation
branches; adjust the disabled prop wherever used (refer to
providerNotConfigured, isManual, saving, manualPolicyConfig) including the other
occurrence mentioned.
- Around line 258-261: The code assumes policySpec.policyAttributes[0] exists
which can throw if the array is empty or reordered; before assigning
updateCandidates[policySpec.policyAttributes[0].name] = manualPolicyConfig,
guard access by verifying policySpec.policyAttributes is defined and non-empty
and ideally locate the correct attribute by name (e.g., const attr =
policySpec.policyAttributes.find(a => a.name === expectedName) or fall back to
the first element), then use attr.name when setting updateCandidates; ensure
this check is applied in the block that handles policyObj values
(modelRoundRobin, modelWeightedRoundRobin, modelFailover, semanticRouting,
intelligentModelRouting) so you don’t dereference an undefined element.

In
@portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/CustomPolicies/IntelligentModelRouting.tsx:
- Around line 207-219: fetchModelList currently calls
JSON.parse(apiFromContext.subtypeConfiguration.configuration) without validation
which will throw if subtypeConfiguration or configuration is missing or invalid;
wrap the parse in a try/catch and/or use a safe check (e.g., optional chaining
to verify apiFromContext?.subtypeConfiguration?.configuration exists) before
calling JSON.parse, validate that the parsed object contains llmProviderId, and
only then call API.getLLMProviderModelList; on parse or validation failure, log
the error via console.error or process logger and return early, and keep the
existing .catch for the API call to handle network/response errors before
calling setModelList.
- Around line 269-279: The current useEffect builds configForBackend and then
corrupts valid JSON by doing JSON.stringify(...) followed by
jsonString.replace(/"/g, "'"); remove that replace and preserve the valid JSON
(i.e., call setManualPolicyConfig(JSON.stringify(configForBackend))); if the
backend truly requires single quotes instead of double quotes, instead encode
the JSON safely (e.g., Base64 encode the JSON string or use encodeURIComponent)
and have the backend decode it; update the useEffect around configForBackend,
JSON.stringify, and setManualPolicyConfig accordingly (or coordinate backend
decoding) rather than blindly replacing quotes.
- Around line 760-768: The production routing rules are being keyed by array
index in the map over config.production.routingrules (and rendered via
renderRuleCard), which breaks reconciliation when items are deleted; update the
RoutingRuleConfig model to include a unique id field (e.g., id: string), ensure
any code that creates/adds rules (the add rule handler) generates a stable
unique id for each new rule, then change the map to use rule.id as the React key
and pass that id as the key prop to the Paper returned by renderRuleCard; also
update handleProductionRuleDelete to locate and remove rules by id rather than
by index to avoid index-shift bugs.
- Around line 221-224: The useEffect that calls fetchModelList and
fetchEndpoints needs cleanup and correct dependencies: add apiFromContext.id and
apiFromContext.subtypeConfiguration to the effect dependency array and ensure
both async functions accept an AbortSignal (or check a mounted flag) so
in-flight requests are aborted on unmount; inside fetchModelList and
fetchEndpoints pass the AbortSignal to fetch/axios calls, catch abort errors,
and guard any setState calls (e.g., setModelList, setEndpoints) so they only run
when the request wasn’t aborted.
- Around line 653-673: The delete button Grid item in
IntelligentModelRouting.tsx is placed after the Grid container is closed so it
isn't a direct child of the Grid container; move the conditional block that
renders the IconButton (the Grid with data-testid='rule-delete' and its
IconButton/DeleteIcon children) so it is inside the surrounding Grid container
(or wrap it with a Grid container) that contains the other Grid items, ensuring
all Grid items are direct children of the same Grid container for correct
layout.

In
@portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/CustomPolicies/SemanticRouting.tsx:
- Around line 208-220: fetchModelList currently calls
JSON.parse(apiFromContext.subtypeConfiguration.configuration) without guarding
for missing or invalid JSON which can throw and crash the UI; update
fetchModelList to first verify apiFromContext.subtypeConfiguration.configuration
exists, wrap the JSON.parse in a try/catch (or use a safe parse helper) to
handle malformed JSON, only call API.getLLMProviderModelList when a valid
llmProviderId is extracted, and on error setModelList to an empty array or keep
prior state and log the parse/API error so the policy drawer won't crash
(references: fetchModelList, apiFromContext.subtypeConfiguration.configuration,
API.getLLMProviderModelList, setModelList).
- Around line 227-300: The current parsing/serialization in the useEffect
(reading manualPolicyConfig) and the serialization block that builds
configForBackend uses blind string replacements of quotes which corrupts
contentpath and utterances; remove the manual .replace(/\'/g, "'").replace(/'/g,
'"') and the JSON.stringify(...).replace(/"/g, "'") steps and instead keep
manualPolicyConfig as valid JSON (double-quoted) end-to-end or use a proper
serializer/escaper; update the parsing useEffect to
JSON.parse(manualPolicyConfig) (or JSON5.parse if you adopt JSON5) and update
the writer useEffect to setManualPolicyConfig(JSON.stringify(configForBackend))
(or JSON5.stringify) so contentpath and user strings are preserved, and
similarly remove quote normalization from normalizeContentPath and
handleContentPathUpdate; reference functions/vars: manualPolicyConfig,
setManualPolicyConfig, useEffect blocks that parse/serialize configForBackend,
normalizeContentPath, and handleContentPathUpdate.
🧹 Nitpick comments (3)
portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/CustomPolicies/SemanticRouting.tsx (2)

764-789: Switch + Accordion onChange likely double-toggles; stop propagation or pick one control path.

Clicking the Switch often triggers Accordion expansion as well; you’re handling both paths, which can cause redundant state updates (and double-resets when turning off).

Also applies to: 849-874


480-483: Consider localizing remaining hard-coded UI strings (loading + helper/placeholder text).

You added locale keys for much of the UI, but a few strings are still hard-coded (e.g., loading message, helperText, placeholders). Aligning these with en.json will keep i18n coverage complete.

Also applies to: 501-515, 683-684

portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/CustomPolicies/IntelligentModelRouting.tsx (1)

200-204: Silent error handling provides no feedback to users.

API errors are only logged to console. If endpoint fetching fails, users see an empty list with no explanation or retry option.

♻️ Consider adding error state
+const [fetchError, setFetchError] = useState<string | null>(null);

 const fetchEndpoints = () => {
     setLoading(true);
+    setFetchError(null);
     const endpointsPromise = API.getApiEndpoints(apiFromContext.id);
     endpointsPromise
         .then((response) => {
             // ... existing logic
         }).catch((error) => {
             console.error(error);
+            setFetchError('Failed to load endpoints. Please try again.');
         }).finally(() => {
             setLoading(false);
         });
 }

Then display the error in the UI with a retry button.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between eabc659 and 9b64b91.

📒 Files selected for processing (4)
  • portals/publisher/src/main/webapp/site/public/locales/en.json
  • portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/AttachedPolicyForm/General.tsx
  • portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/CustomPolicies/IntelligentModelRouting.tsx
  • portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/CustomPolicies/SemanticRouting.tsx
🧰 Additional context used
🧬 Code graph analysis (2)
portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/AttachedPolicyForm/General.tsx (1)
portals/admin/src/main/webapp/source/src/app/components/Base/Navigator.jsx (1)
  • policyObj (132-132)
portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/CustomPolicies/IntelligentModelRouting.tsx (1)
portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/CustomPolicies/Types.d.ts (1)
  • ModelVendor (35-38)
🔇 Additional comments (2)
portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/CustomPolicies/IntelligentModelRouting.tsx (2)

51-82: LGTM!

The interface definitions are well-structured and provide clear type safety for the routing configuration. The separation of EnvironmentConfig, RoutingRuleConfig, DefaultModelConfig, and ContentPathConfig promotes good modularity.


678-686: Good user feedback for missing LLM provider configuration.

The warning alert with documentation link provides helpful guidance when the LLM provider is not configured. The use of rel='noopener noreferrer' on the external link is a good security practice.

@sonarqubecloud
Copy link

Quality Gate Failed Quality Gate failed

Failed conditions
4 Security Hotspots
20.9% Duplication on New Code (required ≤ 3%)

See analysis details on SonarQube Cloud

@pamaljayasinghe pamaljayasinghe marked this pull request as ready for review January 16, 2026 10:15
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@portals/publisher/src/main/webapp/site/public/locales/en.json`:
- Around line 1428-1429: The string value for the localization key
"Apis.Details.Policies.CustomPolicies.SemanticRouting.add.route" currently reads
"Add rule"; update it to "Add Rule" to match the casing used by the
IntelligentModelRouting UI and ensure consistent routing UI text across the app.
♻️ Duplicate comments (4)
portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/CustomPolicies/IntelligentModelRouting.tsx (2)

231-241: Quote replacement still corrupts strings containing quotes.

The previous review flagged this pattern as corrupting user data (e.g., model names, contexts containing quotes). The comment was marked as addressed, but the code still uses .replace(/"/g, "'") on line 239.

If string values contain double quotes, this corrupts the JSON structure. If the backend truly requires single-quoted strings, consider:

  1. Coordinating with backend to accept standard JSON
  2. Using Base64 encoding for the payload
  3. At minimum, only escaping the outer structure while preserving inner string values

188-192: Parsing also uses quote replacement that can fail on edge cases.

Line 191 uses .replace(/'/g, '"') to convert single quotes back to double quotes before parsing. If user-entered values (rule names, contexts) contain apostrophes, the round-trip through .replace(/"/g, "'") (serialization) and .replace(/'/g, '"') (parsing) will corrupt them.

Example: A context like "User's input" becomes 'User's input' on serialization, then fails to parse or becomes malformed on the next load.

portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/CustomPolicies/SemanticRouting.tsx (2)

357-367: Content path normalization corrupts valid JSONPath expressions.

The normalizeContentPath function converts single quotes to double quotes, but JSONPath expressions commonly use single quotes for string literals (as shown in the placeholder at line 473: $.messages[?(@.role=='user')].content).

Converting 'user' to "user" may cause the JSONPath to fail at runtime depending on the parser implementation.


239-262: Same quote replacement issue as IntelligentModelRouting.

The serialization at line 260 uses .replace(/"/g, "'") which corrupts any user-entered strings containing double quotes (utterances, etc.).

🧹 Nitpick comments (4)
portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/CustomPolicies/IntelligentModelRouting.tsx (2)

243-259: Consider using stable IDs for rules.

The ID generation on line 245 uses Date.now() combined with a random suffix, which provides good uniqueness. However, Math.random().toString(36).substr(2, 9) uses the deprecated substr method.

♻️ Minor improvement: use substring instead of substr
 const handleAddProductionRule = () => {
     const newRule: RoutingRuleConfig = {
-        id: `rule-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
+        id: `rule-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`,
         name: '',
         context: '',
         model: '',
         endpointId: '',
     };

283-299: Same substr deprecation applies to sandbox rule ID generation.

♻️ Apply the same fix here
 const handleAddSandboxRule = () => {
     const newRule: RoutingRuleConfig = {
-        id: `rule-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
+        id: `rule-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`,
         name: '',
         context: '',
         model: '',
         endpointId: '',
     };
portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/CustomPolicies/SemanticRouting.tsx (2)

264-280: Same substr deprecation in route ID generation.

♻️ Use substring instead of substr
 const handleAddProductionRoute = () => {
     const newRoute: RoutingConfig = {
-        id: `route-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
+        id: `route-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`,
         model: '',
         endpointId: '',
         utterances: [],
         scorethreshold: '0.8',
     };

304-320: Same substr deprecation for sandbox route ID.

♻️ Use substring instead of substr
 const handleAddSandboxRoute = () => {
     const newRoute: RoutingConfig = {
-        id: `route-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
+        id: `route-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`,
         model: '',
         endpointId: '',
         utterances: [],
         scorethreshold: '0.8',
     };
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9b64b91 and 8a0fcbd.

📒 Files selected for processing (4)
  • portals/publisher/src/main/webapp/site/public/locales/en.json
  • portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/AttachedPolicyForm/General.tsx
  • portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/CustomPolicies/IntelligentModelRouting.tsx
  • portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/CustomPolicies/SemanticRouting.tsx
🧰 Additional context used
🧠 Learnings (5)
📓 Common learnings
Learnt from: pamaljayasinghe
Repo: wso2/apim-apps PR: 1236
File: portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/AttachedPolicyForm/General.tsx:257-260
Timestamp: 2026-01-16T10:16:10.352Z
Learning: In `portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/AttachedPolicyForm/General.tsx`, the built-in WSO2 LLM routing policies (modelRoundRobin, modelWeightedRoundRobin, modelFailover, semanticRouting, intelligentModelRouting) have guaranteed policy specs that always include at least one attribute in policySpec.policyAttributes, so accessing policySpec.policyAttributes[0] is safe without defensive guards for these specific policies.
Learnt from: pamaljayasinghe
Repo: wso2/apim-apps PR: 1236
File: portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/CustomPolicies/IntelligentModelRouting.tsx:221-224
Timestamp: 2026-01-16T07:33:18.136Z
Learning: In LLM policy components (ModelRoundRobin, ModelFailover, ModelWeightedRoundRobin, IntelligentModelRouting, SemanticRouting) in the APIM publisher portal, `useEffect(() => { fetchModelList(); fetchEndpoints(); }, []);` uses an empty dependency array for intentional mount-once fetching behavior. This is an established pattern across all policy components to avoid unnecessary re-fetches.
Learnt from: pamaljayasinghe
Repo: wso2/apim-apps PR: 1236
File: portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/CustomPolicies/IntelligentModelRouting.tsx:207-219
Timestamp: 2026-01-16T07:31:21.015Z
Learning: In LLM policy components (ModelRoundRobin, ModelFailover, ModelWeightedRoundRobin, IntelligentModelRouting, SemanticRouting) in the APIM publisher portal, JSON.parse(apiFromContext.subtypeConfiguration.configuration) is used without try-catch because parent components enforce that these components only render when the API has a valid LLM subtype configuration.
📚 Learning: 2026-01-16T10:16:10.352Z
Learnt from: pamaljayasinghe
Repo: wso2/apim-apps PR: 1236
File: portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/AttachedPolicyForm/General.tsx:257-260
Timestamp: 2026-01-16T10:16:10.352Z
Learning: In `portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/AttachedPolicyForm/General.tsx`, the built-in WSO2 LLM routing policies (modelRoundRobin, modelWeightedRoundRobin, modelFailover, semanticRouting, intelligentModelRouting) have guaranteed policy specs that always include at least one attribute in policySpec.policyAttributes, so accessing policySpec.policyAttributes[0] is safe without defensive guards for these specific policies.

Applied to files:

  • portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/AttachedPolicyForm/General.tsx
  • portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/CustomPolicies/SemanticRouting.tsx
  • portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/CustomPolicies/IntelligentModelRouting.tsx
  • portals/publisher/src/main/webapp/site/public/locales/en.json
📚 Learning: 2026-01-16T07:33:18.136Z
Learnt from: pamaljayasinghe
Repo: wso2/apim-apps PR: 1236
File: portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/CustomPolicies/IntelligentModelRouting.tsx:221-224
Timestamp: 2026-01-16T07:33:18.136Z
Learning: In LLM policy components (ModelRoundRobin, ModelFailover, ModelWeightedRoundRobin, IntelligentModelRouting, SemanticRouting) in the APIM publisher portal, `useEffect(() => { fetchModelList(); fetchEndpoints(); }, []);` uses an empty dependency array for intentional mount-once fetching behavior. This is an established pattern across all policy components to avoid unnecessary re-fetches.

Applied to files:

  • portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/AttachedPolicyForm/General.tsx
📚 Learning: 2026-01-16T07:31:21.015Z
Learnt from: pamaljayasinghe
Repo: wso2/apim-apps PR: 1236
File: portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/CustomPolicies/IntelligentModelRouting.tsx:207-219
Timestamp: 2026-01-16T07:31:21.015Z
Learning: In LLM policy components (ModelRoundRobin, ModelFailover, ModelWeightedRoundRobin, IntelligentModelRouting, SemanticRouting) in the APIM publisher portal, JSON.parse(apiFromContext.subtypeConfiguration.configuration) is used without try-catch because parent components enforce that these components only render when the API has a valid LLM subtype configuration.

Applied to files:

  • portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/AttachedPolicyForm/General.tsx
  • portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/CustomPolicies/SemanticRouting.tsx
  • portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/CustomPolicies/IntelligentModelRouting.tsx
📚 Learning: 2026-01-16T07:33:10.845Z
Learnt from: pamaljayasinghe
Repo: wso2/apim-apps PR: 1236
File: portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/CustomPolicies/IntelligentModelRouting.tsx:221-224
Timestamp: 2026-01-16T07:33:10.845Z
Learning: In policy components (ModelRoundRobin, ModelFailover, ModelWeightedRoundRobin, IntelligentModelRouting, SemanticRouting) within the APIM publisher portal, use useEffect(() => { fetchModelList(); fetchEndpoints(); }, []) to perform mount-once data fetching. This pattern should be applied across all TSX policy components to avoid unnecessary re-fetches. If a dependency-based re-fetch is needed, specify exact dependencies in the array.

Applied to files:

  • portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/CustomPolicies/SemanticRouting.tsx
  • portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/CustomPolicies/IntelligentModelRouting.tsx
🔇 Additional comments (11)
portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/CustomPolicies/IntelligentModelRouting.tsx (3)

100-122: Component structure and state initialization looks good.

The state setup with proper TypeScript interfaces, reasonable defaults, and clear separation of concerns (production vs sandbox, endpoints, model list, loading state, toggle flags) is well-organized.


495-644: Rule card rendering is well-structured.

The renderRuleCard function properly encapsulates the UI for each routing rule with:

  • Model and endpoint selection from fetched data
  • Proper form controls with labels and IDs
  • Delete button correctly placed inside the Grid container (addressed from past review)
  • Clean separation of change handlers per environment

646-828: Main render structure with accordions is well-implemented.

The Production and Sandbox accordions with:

  • Toggle switches for enabling/disabling
  • Proper alerts for missing models/endpoints
  • Dynamic rule rendering with unique keys (rule.id || fallback)
  • Content path configuration section

The UI structure is clear and follows MUI patterns correctly.

portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/CustomPolicies/SemanticRouting.tsx (4)

573-583: Score threshold validation is well-implemented.

The validation correctly:

  • Allows empty string (for clearing)
  • Validates numeric range 0-1
  • Prevents invalid input from being stored

657-702: Utterance ChipInput implementation looks good.

The utterance management with:

  • Add handler that trims and validates non-empty values
  • Delete handler using index lookup
  • Proper ChipInput component integration
  • Helpful placeholder and helper text

101-122: Component structure mirrors IntelligentModelRouting consistently.

The state setup follows the same established pattern as other LLM policy components, maintaining consistency across the codebase.


729-911: Main render structure is consistent with IntelligentModelRouting.

The accordion-based UI for Production/Sandbox environments with proper:

  • Toggle switches and state management
  • Alert messages for missing models/endpoints
  • Dynamic route rendering with unique keys
  • Content path configuration

The overall structure maintains consistency with the sibling component.

portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/AttachedPolicyForm/General.tsx (4)

48-49: New routing policy components imported cleanly.


137-145: Manual-policy detection now covers the new routing types.


257-259: Manual policy save mapping extended appropriately for new policies.


491-502: Semantic & Intelligent routing editors are wired into manual mode as expected.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (3)
portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/CustomPolicies/SemanticRouting.tsx (3)

172-176: validateForm missing from useEffect dependency array.

validateForm is re-created on every render and is not in the dep array. Since all values it closes over (config, productionEnabled, sandboxEnabled) are already listed, there is no stale-closure risk in practice, but react-hooks/exhaustive-deps will flag it. The cleanest fix is to inline the validation logic or wrap validateForm in useCallback with the same deps.

♻️ Option: inline or add to deps
  useEffect(() => {
      if (setIsFormValid) {
          setIsFormValid(validateForm());
      }
- }, [config, productionEnabled, sandboxEnabled, setIsFormValid]);
+ }, [config, productionEnabled, sandboxEnabled, setIsFormValid, validateForm]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/CustomPolicies/SemanticRouting.tsx`
around lines 172 - 176, The useEffect calls validateForm but does not include
validateForm in its dependency array, causing a linter warning; fix by
stabilizing validateForm or inlining the logic: either wrap the validateForm
function in useCallback with dependencies [config, productionEnabled,
sandboxEnabled] (so the function identity is stable) and then include
validateForm in the useEffect deps, or remove the helper and inline the
validation expression directly inside the useEffect that calls setIsFormValid
(keeping deps [config, productionEnabled, sandboxEnabled, setIsFormValid]);
reference validateForm, useEffect, setIsFormValid, config, productionEnabled,
sandboxEnabled when applying the change.

243-316: Bidirectional useEffect dependency creates unnecessary extra render cycles.

Effect A (line 243, deps [manualPolicyConfig]) calls setConfig; Effect B (line 293, deps [config, ...]) calls setManualPolicyConfig. Every user action triggers a 2-cycle ping-pong: the user's action updates config → Effect B writes a new string → the parent re-renders → Effect A parses it back (same values, new object reference) → Effect B fires again with the same string → React bails out via string equality.

A useRef guard prevents the redundant reverse-parse:

♻️ Proposed refactor
+    const isParsing = useRef(false);

     useEffect(() => {
         if (manualPolicyConfig !== '') {
             try {
+                isParsing.current = true;
                 const parsedConfig = JSON.parse(manualPolicyConfig);
                 // ... setConfig call ...
             } catch (error) { ... }
         }
     }, [manualPolicyConfig]);

     useEffect(() => {
+        if (isParsing.current) {
+            isParsing.current = false;
+            return;
+        }
         const configForBackend: any = { ... };
         setManualPolicyConfig(JSON.stringify(configForBackend));
     }, [config, setManualPolicyConfig]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/CustomPolicies/SemanticRouting.tsx`
around lines 243 - 316, The two useEffect hooks (one reading manualPolicyConfig
to call setConfig, the other serializing config to call setManualPolicyConfig)
cause a ping-pong render; add a useRef guard to skip the reverse-parse: create a
ref like skipParseRef, in the serialization effect (the one that builds
configForBackend and calls setManualPolicyConfig) set skipParseRef.current =
true immediately before calling setManualPolicyConfig, and in the parse effect
(the one that JSON.parse manualPolicyConfig and calls setConfig) check if
skipParseRef.current is true — if so, set it back to false and return early;
keep references to manualPolicyConfig, setConfig, config, setManualPolicyConfig
and the two useEffect blocks when applying this change.

320-320: substr is deprecated; replace with slice.

String.prototype.substr() is deprecated and is defined only in Annex B as normative-optional for non-browser runtimes; String.prototype.substring() or slice() should be used instead.

♻️ Proposed fix
- id: `route-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
+ id: `route-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`,

The same pattern appears at line 360 in handleAddSandboxRoute.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/CustomPolicies/SemanticRouting.tsx`
at line 320, Replace uses of the deprecated String.prototype.substr in the
id-generation expressions inside SemanticRouting (both in the handler that
creates routes and in handleAddSandboxRoute): change the substr(2, 9) call on
Math.random().toString(36) to use slice instead by computing slice(start,
start+length) (i.e., start=2 and length=9 → slice(2, 11)), updating both
occurrences so the generated id remains unchanged but avoids the deprecated API.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/CustomPolicies/SemanticRouting.tsx`:
- Around line 527-531: The TextField for contentpath in the SemanticRouting
component is marked required but the payload only includes contentpath when
non-empty (see config.contentpath check), so remove the misleading required
attribute on that TextField (or make it conditional) to avoid browser
validation; locate the TextField usage in SemanticRouting.tsx (the element
rendering the "contentpath" input) and delete the required prop (or set
required={!!someCondition} if you intend it to be required only in certain
modes).
- Around line 293-316: The effect building configForBackend should strip the
UI-only id from each route before sending to backend: when constructing
production and sandbox routes (config.production.routes and
config.sandbox.routes) map over each route and create a new object omitting the
id property (e.g., using object rest to exclude id or explicitly pick allowed
fields) so configForBackend.production.routes and
configForBackend.sandbox.routes contain route objects without the
client-generated id; keep the rest of the logic (contentpath handling,
JSON.stringify and quote-replacement) and then call setManualPolicyConfig with
the formatted string.

---

Duplicate comments:
In `@portals/publisher/src/main/webapp/site/public/locales/en.json`:
- Around line 1427-1429: The localization key
Apis.Details.Policies.CustomPolicies.SemanticRouting.add.route currently uses
"Add rule" and should be normalized to title case to match other routing UIs;
update the value for
Apis.Details.Policies.CustomPolicies.SemanticRouting.add.route from "Add rule"
to "Add Rule" in en.json so it matches IntelligentModelRouting and other
entries.

In
`@portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/CustomPolicies/SemanticRouting.tsx`:
- Around line 825-832: The component SemanticRouting shows the "No models
available" alert while fetchModelList is in-flight because fetchModelList never
toggles a loading flag; add a boolean state (e.g., modelsLoading) in the
component, set modelsLoading = true before calling fetchModelList and set it
false in finally (or both success/error branches), and update the render checks
that currently use modelList.length === 0 (the alert locations in the component)
to only show the warning when !modelsLoading && modelList.length === 0;
reference fetchModelList, modelList, and the SemanticRouting render where the
Alert is returned and apply the same guard to both alert occurrences.
- Line 246: Stop the quote-replacement round-trip: remove the manual replacement
of single quotes to double quotes in the JSON.parse call for manualPolicyConfig
(the line using JSON.parse(manualPolicyConfig.replace(/'/g, '"'))) and instead
parse manualPolicyConfig as standard JSON; likewise remove any serialization
code that converts double quotes to single quotes when writing
manualPolicyConfig (the serialize block mentioned around lines 314–315) so the
config remains valid JSON end-to-end. Also update normalizeContentPath to avoid
converting single quotes inside JSONPath expressions (do not replace "'" → '"'
within JSONPath handling), and ensure any error handling around JSON.parse fails
gracefully without resetting the entire form.
- Around line 126-169: The validateForm function currently omits checking
route.scorethreshold so a route with an empty threshold can pass; update
validateForm (inside the production loop over config.production.routes and the
sandbox loop over config.sandbox.routes) to also require a non-empty
scorethreshold (same validation logic as used by handleThresholdChange where ''
is considered invalid) for each route before returning true so both production
and sandbox routes fail validation when scorethreshold is empty.

---

Nitpick comments:
In
`@portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/CustomPolicies/SemanticRouting.tsx`:
- Around line 172-176: The useEffect calls validateForm but does not include
validateForm in its dependency array, causing a linter warning; fix by
stabilizing validateForm or inlining the logic: either wrap the validateForm
function in useCallback with dependencies [config, productionEnabled,
sandboxEnabled] (so the function identity is stable) and then include
validateForm in the useEffect deps, or remove the helper and inline the
validation expression directly inside the useEffect that calls setIsFormValid
(keeping deps [config, productionEnabled, sandboxEnabled, setIsFormValid]);
reference validateForm, useEffect, setIsFormValid, config, productionEnabled,
sandboxEnabled when applying the change.
- Around line 243-316: The two useEffect hooks (one reading manualPolicyConfig
to call setConfig, the other serializing config to call setManualPolicyConfig)
cause a ping-pong render; add a useRef guard to skip the reverse-parse: create a
ref like skipParseRef, in the serialization effect (the one that builds
configForBackend and calls setManualPolicyConfig) set skipParseRef.current =
true immediately before calling setManualPolicyConfig, and in the parse effect
(the one that JSON.parse manualPolicyConfig and calls setConfig) check if
skipParseRef.current is true — if so, set it back to false and return early;
keep references to manualPolicyConfig, setConfig, config, setManualPolicyConfig
and the two useEffect blocks when applying this change.
- Line 320: Replace uses of the deprecated String.prototype.substr in the
id-generation expressions inside SemanticRouting (both in the handler that
creates routes and in handleAddSandboxRoute): change the substr(2, 9) call on
Math.random().toString(36) to use slice instead by computing slice(start,
start+length) (i.e., start=2 and length=9 → slice(2, 11)), updating both
occurrences so the generated id remains unchanged but avoids the deprecated API.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

♻️ Duplicate comments (1)
portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/CustomPolicies/SemanticRouting.tsx (1)

308-311: ⚠️ Potential issue | 🟠 Major

Strip UI-only route id before serializing config for backend.

Line 310 serializes config directly, so client-generated id is sent in production.routes and sandbox.routes. This should be removed from backend payloads.

💡 Proposed fix
     useEffect(() => {
         isInternalUpdate.current = true;
-        setManualPolicyConfig(JSON.stringify(config).replace(/'/g, '\\u0027').replace(/"/g, "'"));
+        const configForBackend = {
+            ...config,
+            production: {
+                ...config.production,
+                routes: config.production.routes.map(({ id, ...rest }) => rest),
+            },
+            sandbox: {
+                ...config.sandbox,
+                routes: config.sandbox.routes.map(({ id, ...rest }) => rest),
+            },
+        };
+        setManualPolicyConfig(JSON.stringify(configForBackend).replace(/'/g, '\\u0027').replace(/"/g, "'"));
     }, [config, setManualPolicyConfig]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/CustomPolicies/SemanticRouting.tsx`
around lines 308 - 311, The current useEffect in SemanticRouting uses
JSON.stringify(config) which includes client-only route.id values; before
calling setManualPolicyConfig, create a sanitized copy of config (e.g., clone
config and for each route in production?.routes and sandbox?.routes remove the
id property) so the UI-only id is stripped, then JSON.stringify the
sanitizedConfig and perform the same character replacements and call
setManualPolicyConfig; keep the existing isInternalUpdate.current assignment and
dependency list (config, setManualPolicyConfig) and reference the same symbols
(useEffect, isInternalUpdate, setManualPolicyConfig, config, production.routes,
sandbox.routes) when implementing the sanitation.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/AttachedPolicyForm/General.tsx`:
- Around line 139-153: The effect in General's useEffect only sets manual mode
true for certain policyObj names but never resets it; update the effect that
watches policyObj to explicitly clear manual state and validation when the
incoming policyObj is not one of the manual types: inside the same useEffect
(referencing policyObj, setManual, and setIsManualFormValid) add an else branch
that calls setManual(false) and resets validation (e.g.,
setIsManualFormValid(true) or appropriate default) so stale
isManual/isManualFormValid values are cleared whenever policyObj changes.

In
`@portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/CustomPolicies/IntelligentModelRouting.tsx`:
- Around line 153-170: The validation currently only checks falsy values for
fields like rule.name and rule.context so whitespace-only strings pass; update
the checks in the loops over config.production.routingrules and
config.sandbox.routingrules to verify trimmed content (e.g., ensure rule.name
and rule.context exist and rule.name.trim().length > 0 and
rule.context.trim().length > 0) and likewise validate sandbox.defaultModel
fields if they can be whitespace; change the if conditions that use !rule.name /
!rule.context to trimmed-empty checks to prevent saving whitespace-only values.
- Around line 305-308: In useEffect where isInternalUpdate.current is set and
setManualPolicyConfig is called, strip UI-only RoutingRuleConfig.id values from
the config before serializing: create a copy of config (referenced as config)
and for each routing rule remove the id property, then JSON.stringify that
cleaned payload (instead of the raw config) and pass it to
setManualPolicyConfig; keep the existing escape replacements and preserve
isInternalUpdate.current behavior.

In
`@portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/CustomPolicies/SemanticRouting.tsx`:
- Around line 148-176: The current validateForm logic (used when
productionEnabled/sandboxEnabled checks run) misses validating each route's
score threshold; update the validation in the production and sandbox blocks (the
loops over config.production.routes and config.sandbox.routes inside
validateForm) to require route.scoreThreshold to be non-empty and within the
allowed bounds (e.g., between the configured min and max or a sensible range
such as 0 and 1); if route.scoreThreshold is missing or out of range, return
false so the form cannot be saved.

---

Duplicate comments:
In
`@portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/CustomPolicies/SemanticRouting.tsx`:
- Around line 308-311: The current useEffect in SemanticRouting uses
JSON.stringify(config) which includes client-only route.id values; before
calling setManualPolicyConfig, create a sanitized copy of config (e.g., clone
config and for each route in production?.routes and sandbox?.routes remove the
id property) so the UI-only id is stripped, then JSON.stringify the
sanitizedConfig and perform the same character replacements and call
setManualPolicyConfig; keep the existing isInternalUpdate.current assignment and
dependency list (config, setManualPolicyConfig) and reference the same symbols
(useEffect, isInternalUpdate, setManualPolicyConfig, config, production.routes,
sandbox.routes) when implementing the sanitation.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4f25f46a-5d2a-4cb4-989e-d5af8ace473e

📥 Commits

Reviewing files that changed from the base of the PR and between 670824e and 656da92.

📒 Files selected for processing (13)
  • .github/workflows/ui-test.yml
  • pom.xml
  • portals/admin/pom.xml
  • portals/admin/src/main/webapp/site/public/locales/en.json
  • portals/admin/src/main/webapp/source/src/app/components/KeyManagers/AddEditKeyManager.jsx
  • portals/devportal/pom.xml
  • portals/devportal/src/main/webapp/site/public/locales/en.json
  • portals/pom.xml
  • portals/publisher/pom.xml
  • portals/publisher/src/main/webapp/site/public/locales/en.json
  • portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/AttachedPolicyForm/General.tsx
  • portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/CustomPolicies/IntelligentModelRouting.tsx
  • portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/CustomPolicies/SemanticRouting.tsx
💤 Files with no reviewable changes (2)
  • portals/admin/src/main/webapp/source/src/app/components/KeyManagers/AddEditKeyManager.jsx
  • portals/admin/src/main/webapp/site/public/locales/en.json
✅ Files skipped from review due to trivial changes (1)
  • portals/publisher/pom.xml
🚧 Files skipped from review as they are similar to previous changes (1)
  • portals/publisher/src/main/webapp/site/public/locales/en.json

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (3)
portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/CustomPolicies/IntelligentModelRouting.tsx (2)

285-287: ⚠️ Potential issue | 🟠 Major

UI-only id field is serialized to backend.

The config object includes RoutingRuleConfig.id fields (used for React keys) which are UI-only. These should be stripped before serialization to avoid sending unnecessary data to the backend.

💡 Proposed fix
     useEffect(() => {
-        setManualPolicyConfig(JSON.stringify(config).replace(/"/g, '&quot;'));
+        const configForBackend = {
+            ...config,
+            production: {
+                ...config.production,
+                routingrules: config.production.routingrules.map(({ id, ...rest }) => rest),
+            },
+            sandbox: {
+                ...config.sandbox,
+                routingrules: config.sandbox.routingrules.map(({ id, ...rest }) => rest),
+            },
+        };
+        setManualPolicyConfig(JSON.stringify(configForBackend).replace(/"/g, '&quot;'));
     }, [config]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/CustomPolicies/IntelligentModelRouting.tsx`
around lines 285 - 287, The current useEffect serializes the whole config (in
useEffect -> setManualPolicyConfig) including UI-only RoutingRuleConfig.id
fields used as React keys; remove/strip any id properties from each routing rule
before calling setManualPolicyConfig so the serialized JSON sent to backend
omits those UI-only ids (operate on the config copy or map config.routingRules
to omit 'id' prior to JSON.stringify).

142-159: ⚠️ Potential issue | 🟡 Minor

Whitespace-only name/context values pass validation.

Lines 143 and 157 use falsy checks (!rule.name, !rule.context) which allow whitespace-only strings like " " to pass validation. Consider using .trim() for consistency with the contentPath validation at line 131.

💡 Proposed fix
             for (const rule of config.production.routingrules) {
-                if (!rule.model || !rule.endpointId || !rule.name || !rule.context) {
+                if (!rule.model || !rule.endpointId || !rule.name.trim() || !rule.context.trim()) {
                     return false;
                 }
             }
// ... similar change for sandbox at line 157
             for (const rule of config.sandbox.routingrules) {
-                if (!rule.model || !rule.endpointId || !rule.name || !rule.context) {
+                if (!rule.model || !rule.endpointId || !rule.name.trim() || !rule.context.trim()) {
                     return false;
                 }
             }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/CustomPolicies/IntelligentModelRouting.tsx`
around lines 142 - 159, The validation currently uses falsy checks for rule.name
and rule.context in the production and sandbox loops (inside the handling of
config.production.routingrules and config.sandbox.routingrules), which allows
whitespace-only strings to pass; update those checks to use .trim() (e.g.,
validate rule.name.trim() and rule.context.trim()) so that empty or
whitespace-only values are rejected; apply the same trimmed validation logic
where other string fields are validated (consistent with contentPath at line
131) for both the production and sandbox branches and return false when trimmed
values are empty.
portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/AttachedPolicyForm/General.tsx (1)

140-155: ⚠️ Potential issue | 🟠 Major

Manual-mode flags not reset when policyObj changes to non-manual policy.

The effect only enables manual mode when policyObj matches certain names but never resets isManual, isManualFormValid, or showValidationErrors when switching to a non-manual policy. This could leave stale state if the component receives a different policyObj.

💡 Proposed fix
     useEffect(() => {
+        const manualPolicies = [
+            'modelRoundRobin',
+            'modelWeightedRoundRobin',
+            'modelFailover',
+            'ContentBasedModelRouter',
+            'semanticRouting',
+            'intelligentModelRouting',
+        ];
+        const policyName = policyObj?.name ?? '';
+        const isManualPolicy = manualPolicies.includes(policyName);
+        
+        setManual(isManualPolicy);
+        setShowValidationErrors(false);
+        
-        if (
-            (policyObj && policyObj.name === 'modelRoundRobin') ||
-            ...
-        ) {
-            setManual(true);
-            if (policyObj.name === 'semanticRouting' || policyObj.name === 'intelligentModelRouting') {
-                setIsManualFormValid(false);
-            }
-        }
+        if (isManualPolicy) {
+            // Initialize form as invalid for policies that require validation
+            if (policyName === 'semanticRouting' || policyName === 'intelligentModelRouting') {
+                setIsManualFormValid(false);
+            } else {
+                setIsManualFormValid(true);
+            }
+        } else {
+            setIsManualFormValid(true);
+        }
     }, [policyObj]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/AttachedPolicyForm/General.tsx`
around lines 140 - 155, The useEffect watching policyObj only sets manual-mode
flags when policyObj matches manual policy names but never resets them when
switching to a non-manual policy; update the effect that references policyObj to
explicitly set setManual(false), setIsManualFormValid(true) (or appropriate
default), and setShowValidationErrors(false) in the else branch so
manual-related state is cleared when policyObj.name is not one of
'modelRoundRobin','modelWeightedRoundRobin','modelFailover','ContentBasedModelRouter','semanticRouting','intelligentModelRouting';
ensure you adjust the conditional around setIsManualFormValid to only set false
for semanticRouting/intelligentModelRouting and otherwise restore the default in
the else path.
🧹 Nitpick comments (1)
portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/CustomPolicies/IntelligentModelRouting.tsx (1)

453-459: Consider simplifying accordion toggle handler.

Creating a synthetic event object to call the toggle handler works but is indirect. A small refactor could improve clarity.

♻️ Suggested simplification
     const handleAccordionChange = (env: 'production' | 'sandbox') => (event: React.SyntheticEvent, expanded: boolean) => {
         if (env === 'production') {
-            handleProductionToggle({ target: { checked: expanded } } as React.ChangeEvent<HTMLInputElement>);
+            setProductionEnabled(expanded);
+            if (!expanded) {
+                setConfig(prev => ({
+                    ...prev,
+                    production: { defaultModel: { ...EMPTY_MODEL }, routingrules: [] },
+                }));
+            }
         } else {
-            handleSandboxToggle({ target: { checked: expanded } } as React.ChangeEvent<HTMLInputElement>);
+            setSandboxEnabled(expanded);
+            if (!expanded) {
+                setConfig(prev => ({
+                    ...prev,
+                    sandbox: { defaultModel: { ...EMPTY_MODEL }, routingrules: [] },
+                }));
+            }
         }
     };

Alternatively, extract the toggle logic into a shared helper to avoid duplication.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/CustomPolicies/IntelligentModelRouting.tsx`
around lines 453 - 459, The current handleAccordionChange builds synthetic React
events to call handleProductionToggle/handleSandboxToggle, which is indirect;
refactor by extracting the toggle behavior into a shared helper (e.g.,
toggleEnvironment) that accepts (env: 'production'|'sandbox', expanded: boolean)
and invokes the appropriate handler logic directly, or change
handleProductionToggle/handleSandboxToggle to accept a boolean and call them
directly from handleAccordionChange, replacing the synthetic event creation
while preserving existing behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In
`@portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/AttachedPolicyForm/General.tsx`:
- Around line 140-155: The useEffect watching policyObj only sets manual-mode
flags when policyObj matches manual policy names but never resets them when
switching to a non-manual policy; update the effect that references policyObj to
explicitly set setManual(false), setIsManualFormValid(true) (or appropriate
default), and setShowValidationErrors(false) in the else branch so
manual-related state is cleared when policyObj.name is not one of
'modelRoundRobin','modelWeightedRoundRobin','modelFailover','ContentBasedModelRouter','semanticRouting','intelligentModelRouting';
ensure you adjust the conditional around setIsManualFormValid to only set false
for semanticRouting/intelligentModelRouting and otherwise restore the default in
the else path.

In
`@portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/CustomPolicies/IntelligentModelRouting.tsx`:
- Around line 285-287: The current useEffect serializes the whole config (in
useEffect -> setManualPolicyConfig) including UI-only RoutingRuleConfig.id
fields used as React keys; remove/strip any id properties from each routing rule
before calling setManualPolicyConfig so the serialized JSON sent to backend
omits those UI-only ids (operate on the config copy or map config.routingRules
to omit 'id' prior to JSON.stringify).
- Around line 142-159: The validation currently uses falsy checks for rule.name
and rule.context in the production and sandbox loops (inside the handling of
config.production.routingrules and config.sandbox.routingrules), which allows
whitespace-only strings to pass; update those checks to use .trim() (e.g.,
validate rule.name.trim() and rule.context.trim()) so that empty or
whitespace-only values are rejected; apply the same trimmed validation logic
where other string fields are validated (consistent with contentPath at line
131) for both the production and sandbox branches and return false when trimmed
values are empty.

---

Nitpick comments:
In
`@portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/CustomPolicies/IntelligentModelRouting.tsx`:
- Around line 453-459: The current handleAccordionChange builds synthetic React
events to call handleProductionToggle/handleSandboxToggle, which is indirect;
refactor by extracting the toggle behavior into a shared helper (e.g.,
toggleEnvironment) that accepts (env: 'production'|'sandbox', expanded: boolean)
and invokes the appropriate handler logic directly, or change
handleProductionToggle/handleSandboxToggle to accept a boolean and call them
directly from handleAccordionChange, replacing the synthetic event creation
while preserving existing behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d1140cf4-faec-406b-8617-6452a2e1529f

📥 Commits

Reviewing files that changed from the base of the PR and between 656da92 and 9377340.

📒 Files selected for processing (4)
  • portals/publisher/src/main/webapp/site/public/locales/en.json
  • portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/AttachedPolicyForm/General.tsx
  • portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/CustomPolicies/IntelligentModelRouting.tsx
  • portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/CustomPolicies/SemanticRouting.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/CustomPolicies/SemanticRouting.tsx
  • portals/publisher/src/main/webapp/site/public/locales/en.json

@tharikaGitHub
Copy link
Member

Let's also try to refactor the 2 files IntelligentModelRouting.tsx‎ and SemanticRouting.tsx as they contain mostly common code. We can do this as an improvement.

tharikaGitHub
tharikaGitHub previously approved these changes Mar 10, 2026
tharikaGitHub
tharikaGitHub previously approved these changes Mar 13, 2026
@sonarqubecloud
Copy link

Quality Gate Failed Quality Gate failed

Failed conditions
4 Security Hotspots
39.5% Duplication on New Code (required ≤ 3%)

See analysis details on SonarQube Cloud

@PasanT9 PasanT9 merged commit 2950aad into wso2:main Mar 27, 2026
2 of 4 checks passed
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.

4 participants