Skip to content

Fix: Tracing toggle requires page refresh for Template and MessageProcessor artifacts (#437)#565

Open
SasinduDilshara wants to merge 3 commits intowso2:mainfrom
SasinduDilshara:fix-437
Open

Fix: Tracing toggle requires page refresh for Template and MessageProcessor artifacts (#437)#565
SasinduDilshara wants to merge 3 commits intowso2:mainfrom
SasinduDilshara:fix-437

Conversation

@SasinduDilshara
Copy link
Copy Markdown

@SasinduDilshara SasinduDilshara commented Apr 5, 2026

Summary

  • Add tracingInSync/statisticsInSync to Template GraphQL type — the templatesByEnvironmentAndComponent resolver now enriches results from reconcile state, enabling the frontend polling loop to activate for Template artifacts.
  • Guard optimistic toggle state in EntryPoints.tsxuseEffect no longer resets the toggle to the server value when tracingInSync === false / statisticsInSync === false, preventing the stale-heartbeat race from reverting the UI.
  • Remove unsupported tracing field from MessageProcessor gqlFields — MessageProcessor does not have backend tracing support; removing this field aligns gqlFields with the actual schema.
  • Track Template observed state in writeObservedStateMIbatchUpsertReconcileObservedState now includes Template tracing/statistics entries so heartbeats keep reconcile state current.
  • Tests and seed data — added icp_server/tests/artifact_tracing_tests.bal and H2 test data rows covering in-sync and out-of-sync scenarios for Template tracing.

Closes #437

Root Cause

Two interacting bugs prevented immediate UI feedback after a tracing toggle:

  1. Missing tracingInSync/statisticsInSync in ARTIFACT_QUERY_MAP for MessageProcessor and Template (frontend/src/api/queries.ts:465,471). supportedInSyncKeys is derived by filtering IN_SYNC_KEYS against gqlFields; without these fields, the 1-second refetchInterval polling was never activated for those artifact types.

  2. useEffect reset on stale heartbeat (EntryPoints.tsx:108-113): when MI hadn't yet applied the tracing command before its next heartbeat, batchUpsertReconcileObservedState overwrote the optimistic observed state with the stale value, and the useEffect reset the local toggle back to the old position.

Test Plan

  • Start ICP server with H2 test data (bal run in icp_server/)
  • Run backend tests: bal test in icp_server/ — confirm artifact-tracing group passes
  • Toggle tracing ON for a Template artifact → verify toggle stays ON without page refresh
  • Toggle tracing ON for a MessageProcessor artifact → verify UI does not regress
  • Simulate stale-heartbeat: toggle ON, send a heartbeat with old state, confirm toggle stays ON with spinner until reconcile confirms
  • Verify templatesByEnvironmentAndComponent GraphQL query returns tracingInSync and statisticsInSync fields

Summary by CodeRabbit

  • New Features

    • Templates now display sync status for tracing and statistics settings, indicating whether changes have been applied or are pending synchronization.
  • Bug Fixes

    • Resolved issue where page refresh was required to reflect tracing and statistics configuration changes for templates.

SasinduDilshara and others added 2 commits April 5, 2026 20:51
…cessor artifacts

- Add tracingInSync/statisticsInSync fields to Template GraphQL type and enrich
  templatesByEnvironmentAndComponent resolver from reconcile state (Issue wso2#437)
- Guard useEffect in EntryPoints.tsx from reverting optimistic toggle state when
  tracingInSync/statisticsInSync is false (stale heartbeat race condition)
- Remove unsupported tracing field from MessageProcessor gqlFields in queries.ts
- Track Template tracing/statistics observed state in writeObservedStateMI
- Add test data and Ballerina tests covering in-sync / out-of-sync scenarios

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@SasinduDilshara
Copy link
Copy Markdown
Author

Issue Analysis — [Issue #437]: [MI] When tracing enabled/disabled it requires page refresh to see the effect

Classification

  • Type: Bug
  • Severity Assessment: Medium
  • Affected Component(s): ICP Frontend (React), ICP Backend (Ballerina GraphQL), Reconcile Engine (icp_server/modules/sync/)
  • Affected Feature(s): MI artifact tracing toggle (RestApi, ProxyService, InboundEndpoint, Sequence, Endpoint, MessageProcessor, Template); SyncSwitch component

Reproducibility

  • Reproducible: Yes (partially mitigated in current codebase; residual issues remain)

  • Environment:

    • Branch: main (commit 4f0d9242)
    • Runtime: Ballerina Swan Lake 2201.13.1, Node.js 23.7.0, pnpm 9.15.3
    • OS: macOS Darwin 24.0.0
    • Backend: ICP server started via bal run with H2 in-memory database
    • Frontend: React/Vite dev server (not launched; issue reproduced via code analysis + GraphQL API calls)
  • Steps Executed:

    1. Started ICP server (bal run in icp_server/)
    2. Authenticated via POST /auth/login with admin/admin
    3. Traced the full tracing toggle code path via source inspection:
      • updateArtifactTracingStatus mutation → reconcilePerEnvupsertReconcileDesiredState + reconcileArtifactAllRuntimesoptimisticUpsertObservedState
      • Frontend: handleConfirmTogglesetTracingEnabled (optimistic) → mutation → invalidateQueries → refetch
      • useArtifacts hook: refetchInterval polling when tracingInSync !== true
      • queryArtifactState: reads reconcile_observed_state (JOIN RUNNING runtimes only), computes value (median) and inSync flag
    4. Identified residual reproduction cases via state machine analysis (see Actual Behavior)
    5. Verified build: bal build succeeded, pnpm install succeeded
  • Expected Behavior:
    After toggling tracing ON or OFF for an MI artifact, the UI should immediately reflect the new state (toggle position changes) and confirm the change automatically once the MI runtime acknowledges it—without requiring a manual page refresh.

  • Actual Behavior:

    Case A — Primary residual bug (MessageProcessor / Template):
    The MessageProcessor and Template artifact types are missing tracingInSync in their GraphQL field selections (ARTIFACT_QUERY_MAP in frontend/src/api/queries.ts:465,471). Since supportedInSyncKeys is derived by filtering IN_SYNC_KEYS against gqlFields, these types have no tracing inSync tracking. After toggling tracing on a MessageProcessor or Template:

    • The refetchInterval is never activated for the tracing dimension
    • The UI requires a manual page refresh to show the confirmed state
    • Evidence: queries.ts:465gqlFields for MessageProcessor omits tracingInSync; queries.ts:471Template omits both tracingInSync and statisticsInSync

    Case B — Stale heartbeat race condition (all MI artifact types):
    When MI's management API processes the tracing command asynchronously and the next MI heartbeat arrives BEFORE MI has actually applied the change:

    1. optimisticUpsertObservedState writes {tracing="enabled", optimistic=true} to DB (correct)
    2. First refetch: tracing="enabled", tracingInSync=false — toggle shows ON with spinner ✓
    3. MI heartbeat arrives while MI is still applying the change, reports old state tracing="disabled"
    4. batchUpsertReconcileObservedState OVERWRITES the optimistic row with {tracing="disabled", optimistic=false}
    5. queryArtifactState returns tracing="disabled", inSync=false (desired="enabled" ≠ observed="disabled")
    6. Next 1-second poll: artifact.tracing changes from "enabled" to "disabled"
    7. useEffect in EntryPoints.tsx:108-113 fires → setTracingEnabled(false)toggle reverts to OFF
    8. Toggle shows OFF with spinner (confusing — correct value pending reconciliation re-dispatch)
    9. The reconcile engine will re-dispatch the command on the next heartbeat response, eventually converging

    Case C — Original issue (fixed in current code for most cases):
    Before the reconcile engine was introduced (commits e356ad5e, 23040ef6), there was no optimistic update and no refetchInterval polling. After toggling tracing, the toggle's new position was not reflected in the UI until the MI runtime sent a heartbeat confirming the change — requiring a manual page refresh.

  • Logs/Evidence:

    # Key files and line numbers confirming the analysis:
    
    # Missing tracingInSync for MessageProcessor:
    frontend/src/api/queries.ts:465
      gqlFields: 'name, type, state, stateInSync, tracing, carbonApp, ...'
      # Note: 'tracingInSync' absent → supportedInSyncKeys excludes it
    
    # Missing tracingInSync AND statisticsInSync for Template:
    frontend/src/api/queries.ts:471
      gqlFields: 'name, type, tracing, statistics, carbonApp, ...'
      # Note: no *InSync fields at all
    
    # refetchInterval only polls for keys present in supportedInSyncKeys:
    frontend/src/api/queries.ts:487-508
      const IN_SYNC_KEYS = ['stateInSync', 'tracingInSync', 'statisticsInSync'];
      const supportedInSyncKeys = mapping ? IN_SYNC_KEYS.filter((k) => mapping.gqlFields.includes(k)) : [];
      refetchInterval: (query) => {
        const syncing = artifacts.some((a) => supportedInSyncKeys.some((k) => a[k] !== true));
        return syncing ? 1000 : false;
      }
    
    # Stale heartbeat overwrites optimistic observed state:
    icp_server/modules/storage/reconcile_repository.bal:257-316
      # batchUpsertReconcileObservedState uses optimistic=false, overwrites all rows
      # including optimistic ones written earlier by optimisticUpsertObservedState
    
    # useEffect resets toggle state from server data:
    frontend/src/components/EntryPoints.tsx:108-113
      useEffect(() => {
        setTracingEnabled(toEnabled(artifact.tracing));
        ...
      }, [artifactKey, artifact.tracing, artifact.statistics, artifact.state]);
      # If artifact.tracing reverts to old value, this resets the optimistic UI update
    
    # GraphQL schema confirms tracingInSync is produced for RestApi, ProxyService,
    # InboundEndpoint, Sequence, Endpoint — but NOT for MessageProcessor/Template:
    icp_server/graphql_api.bal:872-881
    icp_server/graphql_api.bal:940-951 (Sequence)
    # MessageProcessor section has no tracingInSync assignment
    

Root Cause Analysis

The issue has three interacting layers:

1. Original root cause (largely addressed):
The ICP server learns about artifact state changes only through periodic MI heartbeats. When a tracing toggle is sent to MI via direct HTTP (sendMIControlCommandAsync), the confirmation only arrives with the next heartbeat. The frontend originally did not poll for state updates, requiring a page refresh to trigger a re-fetch.

2. Residual root cause — incomplete gqlFields for MessageProcessor and Template:
ARTIFACT_QUERY_MAP in queries.ts omits tracingInSync (and statisticsInSync) from the gqlFields for MessageProcessor (line 465) and Template (line 471). Since supportedInSyncKeys is derived by filtering against gqlFields, these types never enter the 1-second polling loop when tracing is toggled. The toggle shows the change optimistically, but the confirmed state (and spinner clearance) only arrives after a page refresh.

3. Stale heartbeat race (edge case, all types):
When MI hasn't yet processed the tracing command before its next heartbeat, batchUpsertReconcileObservedState (called during heartbeat processing) overwrites the optimistic observed state row with the stale value. The reconcile engine correctly marks tracingInSync=false (desired ≠ observed) and will re-dispatch on the next heartbeat. However, queryArtifactState returns the stale value, causing useEffect in EntryPoints.tsx to reset the local toggle state — the toggle temporarily reverts to its previous position while showing a spinner.

Test Coverage Assessment

  • Existing tests covering this path:

    • icp_server/tests/runtime_graphql_tests.bal — covers basic runtime CRUD; no tracing toggle tests
    • No frontend tests exist in frontend/src/ (only node_modules test files)
    • No tests for updateArtifactTracingStatus mutation
    • No tests for optimisticUpsertObservedState or queryArtifactState
    • No tests for the refetchInterval polling behavior in useArtifacts
  • Coverage gaps identified:

    • No test for gqlFields completeness (missing tracingInSync for MessageProcessor/Template)
    • No test for the reconcile state machine: optimistic → heartbeat overwrites → re-dispatch flow
    • No test for useArtifacts polling activation/deactivation based on inSync fields
    • No test for EntryPoints.tsx useEffect toggle revert behavior under stale-heartbeat conditions
  • Proposed test plan:

    • Unit test (backend): Test queryArtifactState returns correct tracing/tracingInSync values when a row transitions from optimistic to confirmed (i.e., heartbeat arrives after mutation).
    • Unit test (backend): Test updateArtifactTracingStatus writes correct desired state and triggers optimisticUpsertObservedState for each MI runtime.
    • Unit test (frontend): Test useArtifacts refetchInterval logic: assert polling activates when tracingInSync === false, deactivates when tracingInSync === true, and correctly uses supportedInSyncKeys per artifact type.
    • Unit test (frontend): Test EntryPointDetail component: confirm toggle position is NOT reverted when the refetch returns artifact.tracing matching the optimistic value.
    • Integration test: Simulate the stale-heartbeat race: (1) call updateArtifactTracingStatus, (2) simulate a heartbeat reporting old state, (3) verify tracingInSync=false but tracing stays as desired value (or at minimum the reconcile engine re-dispatches).
    • Negative/edge case: Verify MessageProcessor and Template gqlFields include tracingInSync so polling activates for these types.

Fix Recommendations (for implementing agent)

  1. Immediate fix — add missing tracingInSync to gqlFields:
    In frontend/src/api/queries.ts:

    • Line 465 (MessageProcessor): add tracingInSync to gqlFields
    • Line 471 (Template): add tracingInSync and statisticsInSync to gqlFields
  2. Short-term fix — prevent useEffect from reverting toggle during optimistic period:
    In frontend/src/components/EntryPoints.tsx (lines 108-113), guard the useEffect reset: if artifact.tracingInSync === false, do not reset tracingEnabled from artifact.tracing (the optimistic local state should be preserved until the server confirms).

  3. Long-term fix — GraphQL subscription:
    Implement a GraphQL subscription for artifact state changes (referenced in issue comment as planned via Implement a solution to ensure seamless state change updates in the UI product-integrator#436). This would push real-time updates when the heartbeat confirms a state change, eliminating the need for polling.

Comment on lines +1151 to +1152
types:Template[] result = check storage:getTemplatesByEnvironmentAndComponent(environmentId, componentId);
if result.length() == 0 {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Log Improvement Suggestion No: 1

Suggested change
types:Template[] result = check storage:getTemplatesByEnvironmentAndComponent(environmentId, componentId);
if result.length() == 0 {
types:Template[] result = check storage:getTemplatesByEnvironmentAndComponent(environmentId, componentId);
log.info("Fetched templates for environment: " + environmentId + " and component: " + componentId + ", count: " + result.length().toString());
if result.length() == 0 {

Comment on lines +1154 to +1156
}
map<map<types:ArtifactStateField>> sm = check storage:queryArtifactState(componentId, environmentId);
foreach types:Template a in result {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Log Improvement Suggestion No: 2

Suggested change
}
map<map<types:ArtifactStateField>> sm = check storage:queryArtifactState(componentId, environmentId);
foreach types:Template a in result {
}
map<map<types:ArtifactStateField>> sm = check storage:queryArtifactState(componentId, environmentId);
log.debug("Querying artifact state for component: " + componentId + " in environment: " + environmentId);
foreach types:Template a in result {

Comment on lines +481 to +486
foreach types:Template template in <types:Template[]>artifacts.templates {
entries.push([
{artifactName: template.name, artifactType: "template"},
{"tracing": template.tracing, "statistics": template.statistics}
]);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Log Improvement Suggestion No: 3

Suggested change
foreach types:Template template in <types:Template[]>artifacts.templates {
entries.push([
{artifactName: template.name, artifactType: "template"},
{"tracing": template.tracing, "statistics": template.statistics}
]);
}
foreach types:Template template in <types:Template[]>artifacts.templates {
log.info("Processing template artifact: " + template.name);
entries.push([
{artifactName: template.name, artifactType: "template"},
{"tracing": template.tracing, "statistics": template.statistics}
]);
}

Comment on lines +44 to +48
function testTemplateQueryAcceptsTracingInSyncField() returns error? {
string query = string `
query {
templatesByEnvironmentAndComponent(
environmentId: "${DEV_ENV_ID}",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Log Improvement Suggestion No: 4

Suggested change
function testTemplateQueryAcceptsTracingInSyncField() returns error? {
string query = string `
query {
templatesByEnvironmentAndComponent(
environmentId: "${DEV_ENV_ID}",
@test:Config {
groups: ["artifact-tracing", "template-tracing-schema"]
}
function testTemplateQueryAcceptsTracingInSyncField() returns error? {
log:printInfo("Testing template query with tracingInSync field");
string query = string `

`;

json response = check executeGraphQL(query, orgDevToken);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Log Improvement Suggestion No: 5

Suggested change
json response = check executeGraphQL(query, orgDevToken);
if (response.errors is json) {
log:printError("GraphQL query for tracingInSync/statisticsInSync failed");
} else {
log:printDebug("Successfully queried tracingInSync/statisticsInSync fields");
}

Copy link
Copy Markdown
Contributor

@wso2-engineering wso2-engineering Bot left a comment

Choose a reason for hiding this comment

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

AI Agent Log Improvement Checklist

⚠️ Warning: AI-Generated Review Comments

  • The log-related comments and suggestions in this review were generated by an AI tool to assist with identifying potential improvements. Purpose of reviewing the code for log improvements is to improve the troubleshooting capabilities of our products.
  • Please make sure to manually review and validate all suggestions before applying any changes. Not every code suggestion would make sense or add value to our purpose. Therefore, you have the freedom to decide which of the suggestions are helpful.

✅ Before merging this pull request:

  • Review all AI-generated comments for accuracy and relevance.
  • Complete and verify the table below. We need your feedback to measure the accuracy of these suggestions and the value they add. If you are rejecting a certain code suggestion, please mention the reason briefly in the suggestion for us to capture it.
Comment Accepted (Y/N) Reason
#### Log Improvement Suggestion No: 1
#### Log Improvement Suggestion No: 2
#### Log Improvement Suggestion No: 3
#### Log Improvement Suggestion No: 4
#### Log Improvement Suggestion No: 5

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 5, 2026

Walkthrough

This change implements artifact tracing and statistics synchronization state tracking. It introduces tracingInSync and statisticsInSync fields to the Template type, enriches GraphQL queries to include these sync indicators based on persisted reconciliation state, updates heartbeat tracking to capture template state, and adds comprehensive tests validating the synchronization logic.

Changes

Cohort / File(s) Summary
Documentation
ARCHITECTURE.md, AGENTS.md, CLAUDE.md
Added new documentation files: Architecture Guide with network communication mappings, product overview defining ICP as a WSO2 integration console with Ballerina/React monorepo, and agent workflow configuration.
Frontend Query & State
frontend/src/api/queries.ts, frontend/src/components/EntryPoints.tsx
Updated GraphQL selection fields for Template artifact to include tracingInSync and statisticsInSync; removed tracing from MessageProcessor queries. Modified EntryPoint component to conditionally update toggle state only when sync fields indicate state is current.
Backend Type Definition
icp_server/modules/types/types.bal
Added two new nullable boolean fields to Template record type: tracingInSync and statisticsInSync to indicate synchronization status between desired and observed states.
Backend Enrichment & Heartbeat
icp_server/graphql_api.bal, icp_server/modules/storage/heartbeat_repository.bal
Enhanced templatesByEnvironmentAndComponent resolver to overlay tracing/statistics sync state from persisted reconcile state onto template results. Extended heartbeat repository to capture template artifacts with tracing and statistics in observed state records.
Tests & Fixtures
icp_server/tests/artifact_tracing_tests.bal, icp_server/resources/db/init-scripts/h2_test_data.sql
Added comprehensive test suite (8 test functions) validating GraphQL schema acceptance of sync fields, out-of-sync/in-sync state assertions, authorization checks, and mutation execution. Seeded test data with MI runtime instance, template artifacts with tracing states, and reconciliation state records demonstrating sync and out-of-sync scenarios.

Sequence Diagram

sequenceDiagram
    participant Browser
    participant Frontend as Frontend<br/>(EntryPoints)
    participant GraphQL as GraphQL API
    participant Storage as Storage<br/>(Reconcile State)
    participant DB as Database

    Browser->>Frontend: Select artifact
    Frontend->>GraphQL: Query templatesByEnvironmentAndComponent
    GraphQL->>DB: Query templates
    DB-->>GraphQL: Return templates
    GraphQL->>Storage: queryArtifactState(componentId, environmentId)
    Storage->>DB: Fetch reconcile_desired_state<br/>& reconcile_observed_state
    DB-->>Storage: Return artifact states
    Storage-->>GraphQL: Artifact state mappings
    GraphQL->>GraphQL: Enrich templates with<br/>tracing/tracingInSync<br/>statistics/statisticsInSync
    GraphQL-->>Frontend: Templates with sync fields
    Frontend->>Frontend: Conditionally update<br/>toggles only if<br/>InSync !== false
    Frontend-->>Browser: Render UI with<br/>current state
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 A sync state quest through fields so fine,
Where templates dance 'tween desired and online,
With tracingInSync to guide the way,
No refresh needed—state's in sync today! ✨
The heartbeat keeps the rhythm true,
Observing changes old and new.

🚥 Pre-merge checks | ✅ 2 | ❌ 3

❌ Failed checks (3 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description is largely incomplete. While it provides a summary, root cause analysis, and test plan, it is missing several required template sections including Purpose, Goals, Approach, User stories, Release note, Documentation, Training, Certification, Marketing, Security checks, Samples, Related PRs, Migrations, Test environment, and Learning. Fill in all required sections from the description template, particularly Purpose with issue link, Goals, Approach, Release note, Documentation impact, and Security checks (yes/no confirmations).
Linked Issues check ⚠️ Warning Significant mismatch exists: linked issue #437 describes an SSL/TLS certificate validation problem in MI-to-ICP heartbeat connections, but the PR changes implement a tracing toggle UI feedback feature unrelated to SSL verification. Verify the correct linked issue is referenced. If #437 is correct, clarify how these tracing/statistics changes address SSL certificate validation. Otherwise, update the linked issue to match the PR's actual objective.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Fix: Tracing toggle requires page refresh for Template and MessageProcessor artifacts (#437)' accurately describes the main bug fix in the changeset.
Out of Scope Changes check ✅ Passed All code changes are focused on the tracing toggle UI feedback feature (tracingInSync/statisticsInSync fields, reconcile state enrichment, heartbeat tracking). No out-of-scope changes detected relative to the PR objectives, though they appear misaligned with the linked issue.

✏️ 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.

@SasinduDilshara
Copy link
Copy Markdown
Author

Fix Report — Issue #437: Tracing toggle requires page refresh

PR Links

Repository PR
wso2/integration-control-plane #565
Fork branch SasinduDilshara:fix-437

Changes Made

frontend/src/api/queries.ts

  • MessageProcessor entry (ARTIFACT_QUERY_MAP): removed tracing from gqlFields — MessageProcessor has no backend tracing support; the field was a stale leftover that had no matching tracingInSync for the polling guard.
  • Template entry (ARTIFACT_QUERY_MAP): added tracingInSync and statisticsInSync to gqlFields — without these the supportedInSyncKeys filter excluded Template from the 1-second refetchInterval polling loop, so the UI never auto-confirmed a tracing toggle.

frontend/src/components/EntryPoints.tsx

  • useEffect guard (lines 108–120): wrapped setTracingEnabled and setStatisticsEnabled calls inside if (artifact.tracingInSync !== false) / if (artifact.statisticsInSync !== false) guards. This prevents the stale-heartbeat race from reverting an in-flight optimistic toggle: while the server is still reconciling (inSync === false), the local optimistic state is preserved.
  • Added artifact.tracingInSync and artifact.statisticsInSync to the useEffect dependency array so the guard reacts when sync status changes.

icp_server/graphql_api.bal

  • templatesByEnvironmentAndComponent resolver: enriched the result set using queryArtifactState (same pattern used by RestApi, ProxyService, Sequence, Endpoint, InboundEndpoint resolvers). Each returned Template now gets tracing, tracingInSync, statistics, and statisticsInSync populated from the reconcile state table.

icp_server/modules/types/types.bal

  • Added boolean? tracingInSync = () and boolean? statisticsInSync = () fields to the Template record type.

icp_server/modules/storage/heartbeat_repository.bal

  • writeObservedStateMI: added a loop over artifacts.templates to push {tracing, statistics} entries into batchUpsertReconcileObservedState. Without this, MI heartbeats never updated the reconcile observed state for Template artifacts, so tracingInSync could never become true.

icp_server/resources/db/init-scripts/h2_test_data.sql

  • Added Runtime 6 (MI type, Project 1 / Component 1 / Dev / RUNNING) and seed rows for:
    • hello-template — desired=enabled, observed=disabled → tracingInSync=false (stale heartbeat scenario)
    • synced-template — desired=enabled, observed=enabled → tracingInSync=true (confirmed sync)
    • hello-processor — MessageProcessor (no tracing field)
    • Corresponding reconcile_desired_state and reconcile_observed_state rows
  • Updated test data summary comment with Runtime 6 description and corrected BI/MI labels for Runtimes 1–5.

icp_server/tests/artifact_tracing_tests.bal (new)

  • Ballerina integration tests (group artifact-tracing) covering:
    • templatesByEnvironmentAndComponent GraphQL query returns tracingInSync/statisticsInSync fields
    • tracingInSync=false when desired ≠ observed (stale heartbeat scenario for hello-template)
    • tracingInSync=true when desired == observed (confirmed for synced-template)
    • updateArtifactTracingStatus mutation accepted for template artifact type
    • MessageProcessor query does NOT expose a tracing field in gqlFields

Root Causes Fixed

# Root Cause Fix
1 Template missing tracingInSync/statisticsInSync in gqlFields → polling never activated Added fields to ARTIFACT_QUERY_MAP entry for Template
2 useEffect in EntryPoints.tsx reset optimistic toggle when stale heartbeat overwrote observed state Guard reset behind inSync !== false check
3 templatesByEnvironmentAndComponent resolver not enriching from reconcile state Added queryArtifactState enrichment loop (matches other artifact type resolvers)
4 writeObservedStateMI not writing Template tracing observed state Added Template loop in heartbeat repository

Copy link
Copy Markdown
Contributor

@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: 3

🧹 Nitpick comments (1)
icp_server/tests/artifact_tracing_tests.bal (1)

30-30: Unused constant MI_RUNTIME_ID.

This constant is declared but never referenced in any of the tests. If it's reserved for future test expansion, consider adding a comment to clarify its purpose. Otherwise, remove it to avoid confusion.

🧹 Proposed fix to remove unused constant
 // Test data IDs for Issue 437 (defined in h2_test_data.sql)
-// MI Runtime 6: Project 1 / Component 1 / Dev / RUNNING (MI)
-const string MI_RUNTIME_ID = "880e8400-e29b-41d4-a716-446655440006";
-
 // Template names from test seed data
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@icp_server/tests/artifact_tracing_tests.bal` at line 30, The constant
MI_RUNTIME_ID is declared but never used; either remove the unused declaration
(const string MI_RUNTIME_ID = "...") from the test file or, if you intend to
keep it for future tests, add a one-line comment above MI_RUNTIME_ID clarifying
its reserved purpose (e.g., "Reserved for future MI runtime tests") so readers
know it's intentional; update the symbol MI_RUNTIME_ID accordingly in the tests
file.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@ARCHITECTURE.md`:
- Around line 5-18: Update the fenced code block that begins with the lines
"Browser -> ICP-AUTH/HB:9445" ... "ICP-OBS-ADPT -> OpenSearch:9200" so the
opening fence includes a language tag (use "text") — change the initial ``` to
```text; ensure the closing ``` remains; apply the same change to any other
un-annotated fenced blocks in ARCHITECTURE.md to satisfy markdownlint.

In `@frontend/src/components/EntryPoints.tsx`:
- Around line 108-117: The local toggle state leaks across artifact selections
because the useEffect guards skip calling setTracingEnabled/setStatisticsEnabled
when tracingInSync/statisticsInSync are false; add a ref (e.g.,
prevArtifactKeyRef) to detect when artifactKey changes and, inside the existing
useEffect, if prevArtifactKeyRef.current !== artifactKey then force reinitialize
the toggles by calling setTracingEnabled(toEnabled(artifact.tracing)) and
setStatisticsEnabled(toEnabled(artifact.statistics)) regardless of the inSync
flags, then update prevArtifactKeyRef.current = artifactKey; alternatively, a
simpler fix is to add key={artifactKey} to the EntryPointDetail element so React
remounts the component on artifact selection.

In `@icp_server/resources/db/init-scripts/h2_test_data.sql`:
- Around line 341-386: The fixtures currently reuse component id
'640e8400-e29b-41d4-a716-446655440001' which is seeded as a BI component; create
a new MI component UUID (e.g. a new value) and seed a corresponding component
row for MI, then update the runtime row (runtime_id
'880e8400-e29b-41d4-a716-446655440006') and all reconcile rows (the
reconcile_desired_state and reconcile_observed_state INSERTs) to reference that
new MI component id instead of the BI id so the runtime and artifacts like
'hello-template', 'synced-template', and 'hello-processor' are under an MI
component; this will satisfy validateHeartbeatResolution(...) and align with the
frontend check (componentType === 'MI').

---

Nitpick comments:
In `@icp_server/tests/artifact_tracing_tests.bal`:
- Line 30: The constant MI_RUNTIME_ID is declared but never used; either remove
the unused declaration (const string MI_RUNTIME_ID = "...") from the test file
or, if you intend to keep it for future tests, add a one-line comment above
MI_RUNTIME_ID clarifying its reserved purpose (e.g., "Reserved for future MI
runtime tests") so readers know it's intentional; update the symbol
MI_RUNTIME_ID accordingly in the tests file.
🪄 Autofix (Beta)

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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 989ca8da-9566-466d-b09c-74a9afe445fb

📥 Commits

Reviewing files that changed from the base of the PR and between 834f36e and 4d1a881.

📒 Files selected for processing (10)
  • ARCHITECTURE.md
  • Agents.md
  • CLAUDE.md
  • frontend/src/api/queries.ts
  • frontend/src/components/EntryPoints.tsx
  • icp_server/graphql_api.bal
  • icp_server/modules/storage/heartbeat_repository.bal
  • icp_server/modules/types/types.bal
  • icp_server/resources/db/init-scripts/h2_test_data.sql
  • icp_server/tests/artifact_tracing_tests.bal

Comment thread ARCHITECTURE.md Outdated
Comment on lines +5 to +18
```
Browser -> ICP-AUTH/HB:9445
Browser -> ICP-GraphQL:9446
Browser -> ICP-OBS:9448
Ballerina Integrator -> ICP-AUTH/HB:9445
Micro Integrator -> ICP-AUTH/HB:9445
ICP-AUTH/HB -> ICP-AUTH-ADPT:9447
ICP-OBS -> ICP-OBS-ADPT:9449
ICP-GraphQL -> Micro Integrator:9164
ICP-AUTH/HB -> DB:5432
ICP-GraphQL -> DB:5432
ICP-AUTH-ADPT -> DB:5432
ICP-OBS-ADPT -> OpenSearch:9200
```
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add a language to this fenced block.

markdownlint will keep flagging this fence until it is annotated. text is enough here.

📝 Suggested change
-```
+```text
 Browser -> ICP-AUTH/HB:9445
 Browser -> ICP-GraphQL:9446
 Browser -> ICP-OBS:9448
 Ballerina Integrator -> ICP-AUTH/HB:9445
 Micro Integrator -> ICP-AUTH/HB:9445
 ICP-AUTH/HB -> ICP-AUTH-ADPT:9447
 ICP-OBS -> ICP-OBS-ADPT:9449
 ICP-GraphQL -> Micro Integrator:9164
 ICP-AUTH/HB -> DB:5432
 ICP-GraphQL -> DB:5432
 ICP-AUTH-ADPT -> DB:5432
 ICP-OBS-ADPT -> OpenSearch:9200
</details>

<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

```suggestion

🧰 Tools
🪛 markdownlint-cli2 (0.22.0)

[warning] 5-5: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

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

In `@ARCHITECTURE.md` around lines 5 - 18, Update the fenced code block that
begins with the lines "Browser -> ICP-AUTH/HB:9445" ... "ICP-OBS-ADPT ->
OpenSearch:9200" so the opening fence includes a language tag (use "text") —
change the initial ``` to ```text; ensure the closing ``` remains; apply the
same change to any other un-annotated fenced blocks in ARCHITECTURE.md to
satisfy markdownlint.

Comment on lines 108 to +117
useEffect(() => {
setTracingEnabled(toEnabled(artifact.tracing));
setStatisticsEnabled(toEnabled(artifact.statistics));
if (artifact.tracingInSync !== false) {
setTracingEnabled(toEnabled(artifact.tracing));
}
if (artifact.statisticsInSync !== false) {
setStatisticsEnabled(toEnabled(artifact.statistics));
}
setStatusEnabled(toEnabled(artifact.state));
setListenerEnabled(toEnabled(artifact.state));
}, [artifactKey, artifact.tracing, artifact.statistics, artifact.state]);
}, [artifactKey, artifact.tracing, artifact.tracingInSync, artifact.statistics, artifact.statisticsInSync, artifact.state]);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
sed -n '108,117p' frontend/src/components/EntryPoints.tsx
printf '\n---\n\n'
sed -n '512,515p' frontend/src/components/EntryPoints.tsx

Repository: wso2/integration-control-plane

Length of output: 826


🏁 Script executed:

cat -n frontend/src/components/EntryPoints.tsx | head -130 | tail -30

Repository: wso2/integration-control-plane

Length of output: 1558


🏁 Script executed:

# Check the declaration of tracingEnabled and statisticsEnabled state
rg "tracingEnabled|statisticsEnabled" frontend/src/components/EntryPoints.tsx -B 2 -A 2

Repository: wso2/integration-control-plane

Length of output: 2111


🏁 Script executed:

# Check if EntryPointDetail has other effects or state management
rg "EntryPointDetail" frontend/src/components/ -A 5 -B 5 --type typescript

Repository: wso2/integration-control-plane

Length of output: 108


🏁 Script executed:

# Check if there's a useEffect that might reset these values when artifactKey changes
ast-grep --pattern 'useEffect(() => { $$$ }, [$$$ artifactKey $$$])'

Repository: wso2/integration-control-plane

Length of output: 56


🏁 Script executed:

# Look for other effects in the same component
sed -n '50,550p' frontend/src/components/EntryPoints.tsx | rg "useEffect|useState" -B 1 -A 3

Repository: wso2/integration-control-plane

Length of output: 2798


🏁 Script executed:

# Find the exact line 514 and surrounding context
sed -n '510,520p' frontend/src/components/EntryPoints.tsx

Repository: wso2/integration-control-plane

Length of output: 437


🏁 Script executed:

# Check if there are other effects that might reset state when selected artifact changes
sed -n '50,200p' frontend/src/components/EntryPoints.tsx | grep -n "useEffect\|selected\|artifact" | head -20

Repository: wso2/integration-control-plane

Length of output: 1792


🏁 Script executed:

# Verify the component receives 'selected' prop and check its structure
grep -n "selected" frontend/src/components/EntryPoints.tsx | head -10

Repository: wso2/integration-control-plane

Length of output: 1053


🏁 Script executed:

# Check if there's a key prop or any React.memo that might affect this
grep -n "EntryPointDetail\|key=" frontend/src/components/EntryPoints.tsx | grep -A 2 -B 2 "514"

Repository: wso2/integration-control-plane

Length of output: 644


🏁 Script executed:

# Verify the effect dependency array is correct - when artifactKey changes, effect should run
sed -n '106,118p' frontend/src/components/EntryPoints.tsx

Repository: wso2/integration-control-plane

Length of output: 728


🏁 Script executed:

# Check if selectedEntry has properties that would create different artifactKey values
sed -n '420,430p' frontend/src/components/EntryPoints.tsx

Repository: wso2/integration-control-plane

Length of output: 642


Reinitialize the local toggles when the artifact selection changes.

Line 514 renders EntryPointDetail without a key prop, reusing the same component instance across artifact selections. The effect guards prevent setTracingEnabled and setStatisticsEnabled from running when tracingInSync === false or statisticsInSync === false. This creates a state leak: switching from one out-of-sync artifact to another keeps the previous artifact's optimistic toggle values because both setters are skipped for both artifacts.

The guards correctly prevent stale resets during repeated fetches of the same artifact, but they must still reset when the artifact key changes. Add a ref to detect key changes and force reinitialize the toggles on artifact selection.

Suggested fix
+  const previousArtifactKey = useRef(artifactKey);
   useEffect(() => {
-    if (artifact.tracingInSync !== false) {
+    const artifactChanged = previousArtifactKey.current !== artifactKey;
+    previousArtifactKey.current = artifactKey;
+    if (artifactChanged || artifact.tracingInSync !== false) {
       setTracingEnabled(toEnabled(artifact.tracing));
     }
-    if (artifact.statisticsInSync !== false) {
+    if (artifactChanged || artifact.statisticsInSync !== false) {
       setStatisticsEnabled(toEnabled(artifact.statistics));
     }
     setStatusEnabled(toEnabled(artifact.state));
     setListenerEnabled(toEnabled(artifact.state));
   }, [artifactKey, artifact.tracing, artifact.tracingInSync, artifact.statistics, artifact.statisticsInSync, artifact.state]);

Alternatively, add a key={artifactKey} prop to the EntryPointDetail component at line 514.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
useEffect(() => {
setTracingEnabled(toEnabled(artifact.tracing));
setStatisticsEnabled(toEnabled(artifact.statistics));
if (artifact.tracingInSync !== false) {
setTracingEnabled(toEnabled(artifact.tracing));
}
if (artifact.statisticsInSync !== false) {
setStatisticsEnabled(toEnabled(artifact.statistics));
}
setStatusEnabled(toEnabled(artifact.state));
setListenerEnabled(toEnabled(artifact.state));
}, [artifactKey, artifact.tracing, artifact.statistics, artifact.state]);
}, [artifactKey, artifact.tracing, artifact.tracingInSync, artifact.statistics, artifact.statisticsInSync, artifact.state]);
const previousArtifactKey = useRef(artifactKey);
useEffect(() => {
const artifactChanged = previousArtifactKey.current !== artifactKey;
previousArtifactKey.current = artifactKey;
if (artifactChanged || artifact.tracingInSync !== false) {
setTracingEnabled(toEnabled(artifact.tracing));
}
if (artifactChanged || artifact.statisticsInSync !== false) {
setStatisticsEnabled(toEnabled(artifact.statistics));
}
setStatusEnabled(toEnabled(artifact.state));
setListenerEnabled(toEnabled(artifact.state));
}, [artifactKey, artifact.tracing, artifact.tracingInSync, artifact.statistics, artifact.statisticsInSync, artifact.state]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/EntryPoints.tsx` around lines 108 - 117, The local
toggle state leaks across artifact selections because the useEffect guards skip
calling setTracingEnabled/setStatisticsEnabled when
tracingInSync/statisticsInSync are false; add a ref (e.g., prevArtifactKeyRef)
to detect when artifactKey changes and, inside the existing useEffect, if
prevArtifactKeyRef.current !== artifactKey then force reinitialize the toggles
by calling setTracingEnabled(toEnabled(artifact.tracing)) and
setStatisticsEnabled(toEnabled(artifact.statistics)) regardless of the inSync
flags, then update prevArtifactKeyRef.current = artifactKey; alternatively, a
simpler fix is to add key={artifactKey} to the EntryPointDetail element so React
remounts the component on artifact selection.

Comment on lines +341 to +386
-- MI Runtime 6: Project 1 / Component 1 / Dev / RUNNING (MI type for tracing tests)
INSERT INTO runtimes (
runtime_id, name, project_id, component_id, environment_id,
runtime_type, status, version, platform_name, platform_version,
registration_time, last_heartbeat
) VALUES (
'880e8400-e29b-41d4-a716-446655440006',
'mi-dev-runtime',
'650e8400-e29b-41d4-a716-446655440001',
'640e8400-e29b-41d4-a716-446655440001',
'750e8400-e29b-41d4-a716-446655440001',
'MI', 'RUNNING', '4.3.0', 'wso2-mi', '4.3.0',
CURRENT_TIMESTAMP, CURRENT_TIMESTAMP
);

-- Template: hello-template — tracing toggle enabled but not yet confirmed by MI (tracingInSync=false)
INSERT INTO mi_template_artifacts (runtime_id, template_name, template_type, tracing, statistics, carbon_app)
VALUES ('880e8400-e29b-41d4-a716-446655440006', 'hello-template', 'sequence', 'disabled', 'disabled', NULL);

-- Template: synced-template — tracing already confirmed by MI (tracingInSync=true)
INSERT INTO mi_template_artifacts (runtime_id, template_name, template_type, tracing, statistics, carbon_app)
VALUES ('880e8400-e29b-41d4-a716-446655440006', 'synced-template', 'sequence', 'enabled', 'disabled', NULL);

-- MessageProcessor: hello-processor — for schema validation (no tracing field)
INSERT INTO mi_message_processor_artifacts (runtime_id, processor_name, artifact_id, processor_type, state, carbon_app)
VALUES ('880e8400-e29b-41d4-a716-446655440006', 'hello-processor', '990e8400-e29b-41d4-a716-446655440001', 'Scheduled-message-forwarding-processor', 'active', NULL);

-- Reconcile desired state: tracing=enabled for hello-template (user toggled ON)
INSERT INTO reconcile_desired_state (component_id, env_id, artifact_name, artifact_type, state_key, state_value)
VALUES ('640e8400-e29b-41d4-a716-446655440001', '750e8400-e29b-41d4-a716-446655440001',
'hello-template', 'template', 'tracing', 'enabled');

-- Reconcile desired state: tracing=enabled for synced-template (already confirmed)
INSERT INTO reconcile_desired_state (component_id, env_id, artifact_name, artifact_type, state_key, state_value)
VALUES ('640e8400-e29b-41d4-a716-446655440001', '750e8400-e29b-41d4-a716-446655440001',
'synced-template', 'template', 'tracing', 'enabled');

-- Reconcile observed state: hello-template still disabled (stale heartbeat, not yet applied)
INSERT INTO reconcile_observed_state (runtime_id, component_id, env_id, artifact_name, artifact_type, state_key, state_value, optimistic, heartbeat_gen)
VALUES ('880e8400-e29b-41d4-a716-446655440006', '640e8400-e29b-41d4-a716-446655440001', '750e8400-e29b-41d4-a716-446655440001',
'hello-template', 'template', 'tracing', 'disabled', FALSE, 1);

-- Reconcile observed state: synced-template enabled (matches desired — in sync)
INSERT INTO reconcile_observed_state (runtime_id, component_id, env_id, artifact_name, artifact_type, state_key, state_value, optimistic, heartbeat_gen)
VALUES ('880e8400-e29b-41d4-a716-446655440006', '640e8400-e29b-41d4-a716-446655440001', '750e8400-e29b-41d4-a716-446655440001',
'synced-template', 'template', 'tracing', 'enabled', FALSE, 1);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Seed these tracing fixtures under an MI component.

Lines 80-83 create 640e8400-e29b-41d4-a716-446655440001 as a BI component, but this block reuses that ID for an MI runtime and MI reconcile rows. validateHeartbeatResolution(...) rejects that type mismatch in production, and the frontend only enables the Template/MessageProcessor path when componentType === 'MI', so this fixture will not cover the real execution path. Point Runtime 6 and the new reconcile records at a dedicated MI component instead.

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

In `@icp_server/resources/db/init-scripts/h2_test_data.sql` around lines 341 -
386, The fixtures currently reuse component id
'640e8400-e29b-41d4-a716-446655440001' which is seeded as a BI component; create
a new MI component UUID (e.g. a new value) and seed a corresponding component
row for MI, then update the runtime row (runtime_id
'880e8400-e29b-41d4-a716-446655440006') and all reconcile rows (the
reconcile_desired_state and reconcile_observed_state INSERTs) to reference that
new MI component id instead of the BI id so the runtime and artifacts like
'hello-template', 'synced-template', and 'hello-processor' are under an MI
component; this will satisfy validateHeartbeatResolution(...) and align with the
frontend check (componentType === 'MI').

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.

Error when conncting MI with ICP with SSL verification enabled

1 participant