Skip to content

Build logs revamp#217

Merged
akila-i merged 4 commits intoopenchoreo:mainfrom
akila-i:build-logs-revamp
Feb 10, 2026
Merged

Build logs revamp#217
akila-i merged 4 commits intoopenchoreo:mainfrom
akila-i:build-logs-revamp

Conversation

@akila-i
Copy link
Copy Markdown
Contributor

@akila-i akila-i commented Feb 5, 2026

Purpose

This PR implements frontend changes to retrieve component workflow run status and logs.

  • The status API is called when a particular workflow run is opened, which will return the steps and logsURL (which switches between the build plane and observability plane based on workflow run age and observability configuration).
  • The logs URL is then invoked to fetch logs of an expanded step. (running step is automatically expanded and kept short polling to provide live-updating logs experience. for terminated workflows, the last step/error step is auto expanded)
  • This enables users to access workflow run information and logs, regardless of whether the logs are available in the build plane (for short period of time) or have been archived to the observability plane.

Related: openchoreo/openchoreo#1474

Goals

Describe the solutions that this feature/fix will introduce to resolve the problems described above

Approach

Screenshot 2026-02-06 at 11 30 52 Screenshot 2026-02-06 at 11 31 05 Screenshot 2026-02-06 at 11 31 17

User stories

Summary of user stories addressed by this change>

Release note

Brief description of the new feature or bug fix as it will appear in the release notes

Documentation

Link(s) to product documentation that addresses the changes of this PR. If no doc impact, enter “N/A” plus brief explanation of why there’s no doc impact

Training

Link to the PR for changes to the training content in https://github.com/wso2/WSO2-Training, if applicable

Certification

Type “Sent” when you have provided new/updated certification questions, plus four answers for each question (correct answer highlighted in bold), based on this change. Certification questions/answers should be sent to certification@wso2.com and NOT pasted in this PR. If there is no impact on certification exams, type “N/A” and explain why.

Marketing

Link to drafts of marketing content that will describe and promote this feature, including product page changes, technical articles, blog posts, videos, etc., if applicable

Automation tests

  • Unit tests

    Code coverage information

  • Integration tests

    Details about the test cases and coverage

Security checks

Samples

Provide high-level details about the samples related to this feature

Related PRs

openchoreo/openchoreo#1747

Migrations (if applicable)

Describe migration steps and platforms on which migration has been tested

Test environment

List all JDK versions, operating systems, databases, and browser/versions on which this feature/fix was tested

Learning

Describe the research phase and any blog posts, patterns, libraries, or add-ons you used to solve the problem.

Summary by CodeRabbit

  • New Features

    • New API endpoints and client support to fetch component workflow run status and logs (per-step logs, query filters, and polling).
    • BuildLogs UI revamped: per-step accordion, real-time status polling, automatic step selection, and live log streaming with observability-aware messages.
    • Added a "succeeded" status display for build steps.
  • Chores

    • Updated OpenAPI/config metadata to reflect latest sync.

@akila-i akila-i marked this pull request as ready for review February 6, 2026 05:41
Copy link
Copy Markdown
Contributor

@stefinie123 stefinie123 left a comment

Choose a reason for hiding this comment

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

We need to update lastSyncCommit, lastSyncDate and lastSyncDescription in `https://github.com/openchoreo/backstage-plugins/blob/main/packages/openchoreo-client-node/openapi-config.json since we have updated the open-api.yaml

@akila-i
Copy link
Copy Markdown
Contributor Author

akila-i commented Feb 6, 2026

We need to update lastSyncCommit, lastSyncDate and lastSyncDescription in `https://github.com/openchoreo/backstage-plugins/blob/main/packages/openchoreo-client-node/openapi-config.json since we have updated the open-api.yaml

The changes are from the PR: openchoreo/openchoreo#1747. Will update the sync details once it is merged.

@akila-i akila-i force-pushed the build-logs-revamp branch 2 times, most recently from cc2903b to ec01125 Compare February 10, 2026 04:07
@akila-i
Copy link
Copy Markdown
Contributor Author

akila-i commented Feb 10, 2026

We need to update lastSyncCommit, lastSyncDate and lastSyncDescription in `https://github.com/openchoreo/backstage-plugins/blob/main/packages/openchoreo-client-node/openapi-config.json since we have updated the open-api.yaml

This is done

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Feb 10, 2026

📝 Walkthrough

Walkthrough

Adds workflow-run status and logs: OpenAPI schema and endpoint additions, backend router and WorkflowService methods for status/logs retrieval, client API methods, BuildLogs UI refactor with per-step polling/accordion, style updates, and common type exports.

Changes

Cohort / File(s) Summary
OpenAPI config & APIs
packages/openchoreo-client-node/openapi-config.json, packages/openchoreo-client-node/openapi/openchoreo-api.yaml, packages/openchoreo-client-node/openapi/openchoreo-observability-api.yaml
Updated sync metadata; added schemas ComponentWorkflowRunStatusResponse, WorkflowStepStatus, ComponentWorkflowRunLogEntry; new GET paths for workflow run status and logs; observability API moved to namespace-scoped logs and added NamespaceLogsRequest.
Backend router & service
plugins/openchoreo-ci-backend/src/router.ts, plugins/openchoreo-ci-backend/src/services/WorkflowService.ts
Router adds /workflow-run-status and /workflow-run-logs; WorkflowService adds getWorkflowRunStatus() and fetchWorkflowRunLogs() with live vs observer branching, log mapping, validation, and error handling.
Client API layer
plugins/openchoreo-ci/src/api/OpenChoreoCiClient.ts, plugins/openchoreo-ci/src/api/OpenChoreoCiClientApi.ts
Added fetchWorkflowRunStatus() and fetchWorkflowRunLogs() (discovery-based requests, query params, validation, error handling); fetchBuildLogsForBuild marked for deprecation in favor of new logs method.
UI components & styles
plugins/openchoreo-ci/src/components/BuildLogs/BuildLogs.tsx, plugins/openchoreo-ci/src/components/BuildLogs/styles.ts, plugins/openchoreo-ci/src/components/BuildStatusChip/BuildStatusChip.tsx
BuildLogs refactored into status-driven, per-step Accordion UI with polling for status and per-step logs, deduplication, loading/error states; styles add step-related keys; BuildStatusChip adds succeeded case.
Common types
plugins/openchoreo-common/src/index.ts
Exports added: WorkflowRunStatusResponse, WorkflowStepStatus; WorkloadType union expanded to include ManualTask, ScheduledTask, WebApplication.

Sequence Diagram(s)

sequenceDiagram
    participant UI as BuildLogs UI
    participant Client as OpenChoreoCiClient
    participant Backend as CI Backend Router
    participant Service as WorkflowService
    participant API as OpenChoreo API

    UI->>Client: fetchWorkflowRunStatus(build)
    Client->>Backend: GET /workflow-run-status
    Backend->>Service: getWorkflowRunStatus(...)
    Service->>API: GET /namespaces/.../workflow-runs/{runName}/status
    API-->>Service: ComponentWorkflowRunStatusResponse
    Service-->>Backend: WorkflowRunStatusResponse
    Backend-->>Client: status + logURL
    Client-->>UI: status, steps, logURL

    loop poll every 10s until terminal
      UI->>Client: fetchWorkflowRunStatus(build)
    end

    UI->>Client: fetchWorkflowRunLogs(namespace,project,component,run,hasLiveObservability,opts)
    Client->>Backend: GET /workflow-run-logs?step=...&sinceSeconds=...
    Backend->>Service: fetchWorkflowRunLogs(...)
    alt hasLiveObservability = true
      Service->>API: GET /namespaces/.../workflow-runs/{runName}/logs?step=...&sinceSeconds=...
      API-->>Service: ComponentWorkflowRunLogEntry[]
    else observer path
      Service->>API: resolve observer URL via main API
      Service->>API: Observability client fetch logs
      API-->>Service: ComponentWorkflowRunLogEntry[]
    end
    Service-->>Backend: LogEntry[]
    Backend-->>Client: log entries
    Client-->>UI: append logs to step
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐇 I hopped through specs and added new trails,
Steps that poll softly and logs that set sails.
Accordion petals reveal each step's song,
Status and streams chime steady and long.
Hooray — small rabbit cheers for workflows made strong!

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
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.
Description check ❓ Inconclusive The PR description is partially complete with a clear Purpose section, but most template sections are left unfilled with placeholder text. Complete missing sections: Goals, Release note, Documentation, Security checks, and other key sections should be filled with actual information rather than template placeholders.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Build logs revamp' clearly and concisely summarizes the main change: a significant redesign of the build logs UI and functionality.

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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

Copy link
Copy Markdown

@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

🤖 Fix all issues with AI agents
In `@plugins/openchoreo-ci-backend/src/router.ts`:
- Around line 131-134: The handler currently sets sinceSeconds using
Number.parseInt which can produce NaN or negative values; update the logic
around sinceSeconds (the parse and assignment shown) to validate the parsed
value is a finite non-negative integer and, if invalid, return a 400 response
with an explanatory message; specifically, after parsing
Number.parseInt(sinceSeconds, 10) ensure Number.isInteger(value) && value >= 0
before assigning, otherwise send a 400 error from the router handler.
- Around line 118-137: The endpoint router.get('/workflow-run-logs') currently
accepts an arbitrary absolute URL from the logUrl query and passes it into
workflowService.fetchWorkflowRunLogs, enabling SSRF; before calling
getUserTokenFromRequest / fetchWorkflowRunLogs validate logUrl: require it to be
either a relative path or an absolute URL whose hostname matches the trusted
OpenChoreo API domain or an allowlist of trusted hosts, and if it fails
validation throw an InputError (or return 400). Update the validation logic
around the logUrl extraction (the code using logUrl, step, sinceSeconds and
workflowService.fetchWorkflowRunLogs) to parse the URL, check hostname against
the configured TRUSTED_LOG_HOSTS or specific domain, and only pass the validated
URL to workflowService.fetchWorkflowRunLogs.

In `@plugins/openchoreo-ci/src/components/BuildLogs/BuildLogs.tsx`:
- Around line 166-171: Appending fetched entries when useSinceSeconds is true
can produce duplicate log lines; create a deduplication helper (e.g.,
deduplicateLogs) that filters by a stable key such as `${timestamp}-${log}` or a
unique id, then call it when updating state: inside the setLogsByStep update for
the activeStepName combine existing (prev[activeStepName] ?? []) and entries,
pass the combined array through deduplicateLogs, and set that result; reference
setLogsByStep, activeStepName, useSinceSeconds, and entries when making the
change.
- Around line 51-132: The polling closure in the useEffect (fetchStatus)
captures activeStepName once, causing stale reads and re-auto-selection after a
user changes steps; fix by introducing a mutable ref (e.g., activeStepNameRef)
that is kept in sync with activeStepName and read inside fetchStatus before
doing auto-selection (use setActiveStepName normally but also update
activeStepNameRef.current whenever activeStepName changes or right after calling
setActiveStepName), so the effect can remain with [client, build] deps and the
interval logic in useEffect/fetchStatus continues to use the up-to-date selected
step.
🧹 Nitpick comments (6)
packages/openchoreo-client-node/openapi/openchoreo-api.yaml (1)

822-848: Consider avoiding empty-string sentinels for logURL.

If logs aren’t available, consider making logURL nullable or omitting it so clients don’t need to special-case empty strings.

plugins/openchoreo-ci/src/api/OpenChoreoCiClient.ts (1)

174-204: Consider reusing the apiFetch helper for consistency.

This method duplicates fetch and error handling logic instead of leveraging the existing apiFetch helper. While functionally correct, using apiFetch would ensure consistent error messaging and reduce code duplication.

♻️ Proposed refactor using apiFetch
   async fetchWorkflowRunLogs(
     logUrl: string,
     options?: { step?: string; sinceSeconds?: number },
   ): Promise<LogEntry[]> {
     if (!logUrl) {
       throw new Error('logUrl is required to fetch workflow run logs');
     }

-    const baseUrl = await this.discovery.getBaseUrl('openchoreo-ci-backend');
-    const url = new URL(`${baseUrl}/workflow-run-logs`);
-
-    url.searchParams.set('logUrl', logUrl);
-    if (options?.step) {
-      url.searchParams.set('step', options.step);
-    }
-    if (typeof options?.sinceSeconds === 'number' && options.sinceSeconds > 0) {
-      url.searchParams.set('sinceSeconds', String(options.sinceSeconds));
-    }
-
-    const response = await this.fetchApi.fetch(url.toString());
-
-    if (!response.ok) {
-      const errorText = await response.text();
-      throw new Error(
-        `Failed to fetch workflow run logs (${response.status}): ${errorText}`,
-      );
+    const params: Record<string, string> = { logUrl };
+    if (options?.step) {
+      params.step = options.step;
+    }
+    if (typeof options?.sinceSeconds === 'number' && options.sinceSeconds > 0) {
+      params.sinceSeconds = String(options.sinceSeconds);
     }

-    const entries = (await response.json()) as LogEntry[];
-    return entries;
+    return this.apiFetch<LogEntry[]>('/workflow-run-logs', { params });
   }
plugins/openchoreo-ci-backend/src/services/WorkflowService.ts (2)

409-414: Consider adding a request timeout and simplifying the token check.

The native fetch call has no timeout, which could cause requests to hang indefinitely if the server doesn't respond. Additionally, token && token.length > 0 is redundant since an empty string is already falsy.

♻️ Proposed fix with timeout and simplified check
-    const response = await fetch(url.toString(), {
-      headers:
-        token && token.length > 0
-          ? { Authorization: `Bearer ${token}` }
-          : undefined,
-    });
+    const controller = new AbortController();
+    const timeoutId = setTimeout(() => controller.abort(), 30_000);
+
+    try {
+      const response = await fetch(url.toString(), {
+        headers: token ? { Authorization: `Bearer ${token}` } : undefined,
+        signal: controller.signal,
+      });
+      clearTimeout(timeoutId);

Then wrap the rest of the response handling inside the try block and add a catch for AbortError:

    } catch (err) {
      clearTimeout(timeoutId);
      if (err instanceof Error && err.name === 'AbortError') {
        throw new Error('Request timed out while fetching workflow run logs');
      }
      throw err;
    }

427-443: Type assertion could be replaced with runtime validation.

The inline type definition with as assertion trusts the external API response shape. Consider validating the array structure before mapping, which you partially do on line 433-438 but only for the array type.

plugins/openchoreo-ci/src/components/BuildLogs/BuildLogs.tsx (2)

42-48: Unnecessary useMemo for a constant array.

The terminalStatuses array never changes between renders, so wrapping it in useMemo with an empty dependency array adds overhead without benefit. Consider defining it as a module-level constant or inside the component without memoization.

♻️ Proposed fix
+const TERMINAL_STATUSES = ['completed', 'failed', 'succeeded', 'error'];
+
+const isTerminalStatus = (status?: string) =>
+  status ? TERMINAL_STATUSES.includes(status.toLowerCase()) : false;
+
 export const LogsContent = ({ build }: LogsContentProps) => {
   const classes = useStyles();
   const client = useApi(openChoreoCiClientApiRef);
   // ... state declarations ...

-  const terminalStatuses = useMemo(
-    () => ['completed', 'failed', 'succeeded', 'error'],
-    [],
-  );
-
-  const isTerminalStatus = (status?: string) =>
-    status ? terminalStatuses.includes(status.toLowerCase()) : false;

322-327: Inline style creates a new object on every render.

The inline style={{ marginBottom: '4px' }} creates a new object reference on each render, which can impact performance in large log lists. Consider moving this to the stylesheet.

♻️ Move style to CSS class

In styles.ts, add:

logEntry: {
  marginBottom: '4px',
},

Then update the component:

-                    <Box key={index} style={{ marginBottom: '4px' }}>
+                    <Box key={index} className={classes.logEntry}>

Comment thread plugins/openchoreo-ci-backend/src/router.ts Outdated
Comment on lines +131 to +134
sinceSeconds:
typeof sinceSeconds === 'string'
? Number.parseInt(sinceSeconds, 10)
: undefined,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Reject non-numeric or negative sinceSeconds values.

parseInt can yield NaN; please validate and return a 400 for invalid input.

💡 Suggested validation
-    const entries = await workflowService.fetchWorkflowRunLogs(
-      logUrl,
-      {
-        step: typeof step === 'string' ? step : undefined,
-        sinceSeconds:
-          typeof sinceSeconds === 'string'
-            ? Number.parseInt(sinceSeconds, 10)
-            : undefined,
-      },
-      userToken,
-    );
+    const parsedSinceSeconds =
+      typeof sinceSeconds === 'string'
+        ? Number.parseInt(sinceSeconds, 10)
+        : undefined;
+    if (
+      parsedSinceSeconds !== undefined &&
+      (Number.isNaN(parsedSinceSeconds) || parsedSinceSeconds < 0)
+    ) {
+      throw new InputError('sinceSeconds must be a non-negative integer');
+    }
+
+    const entries = await workflowService.fetchWorkflowRunLogs(
+      logUrl,
+      {
+        step: typeof step === 'string' ? step : undefined,
+        sinceSeconds: parsedSinceSeconds,
+      },
+      userToken,
+    );
🤖 Prompt for AI Agents
In `@plugins/openchoreo-ci-backend/src/router.ts` around lines 131 - 134, The
handler currently sets sinceSeconds using Number.parseInt which can produce NaN
or negative values; update the logic around sinceSeconds (the parse and
assignment shown) to validate the parsed value is a finite non-negative integer
and, if invalid, return a 400 response with an explanatory message;
specifically, after parsing Number.parseInt(sinceSeconds, 10) ensure
Number.isInteger(value) && value >= 0 before assigning, otherwise send a 400
error from the router handler.

Comment thread plugins/openchoreo-ci/src/components/BuildLogs/BuildLogs.tsx
Comment thread plugins/openchoreo-ci/src/components/BuildLogs/BuildLogs.tsx
Copy link
Copy Markdown

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

🤖 Fix all issues with AI agents
In `@packages/openchoreo-client-node/openapi/openchoreo-observability-api.yaml`:
- Around line 111-118: The OpenAPI parameter example for namespaceName is
incorrect (it shows a workflow run ID); update the example value for the
parameter named "namespaceName" in openchoreo-observability-api.yaml to a
realistic namespace string (e.g., a service or team name) rather than a run ID
so consumers see a proper namespace example—locate the parameter block for
"namespaceName" and replace the example value accordingly.
- Around line 1116-1129: The ComponentWorkflowRunLogEntry schema declares logs
include a timestamp but does not require the timestamp property; update the
schema so it matches the endpoint contract by adding "timestamp" to the required
array of ComponentWorkflowRunLogEntry (ensuring the timestamp property remains
type: string, format: date-time) OR change the description text on
ComponentWorkflowRunLogEntry.timestamp to indicate timestamps may be absent;
modify the required array or the timestamp description accordingly to keep the
OpenAPI definition consistent with the endpoint documentation.

In `@plugins/openchoreo-ci-backend/src/services/WorkflowService.ts`:
- Around line 371-378: The fetchWorkflowRunLogs function can throw if callers
pass undefined for the options parameter; change the function to default options
to an empty object (e.g., options = {}) in the signature or add an early guard
that assigns options = {} at the top of fetchWorkflowRunLogs so accesses like
options.step or options.sinceSeconds are safe; update any related uses in the
function (references to options.step and options.sinceSeconds) accordingly.

In `@plugins/openchoreo-ci/src/api/OpenChoreoCiClientApi.ts`:
- Around line 64-79: The docstring for fetchWorkflowRunLogs is inaccurate: it
mentions a "provided log URL" but the method signature accepts run identifiers
(namespaceName, projectName, componentName, runName, hasLiveObservability,
options) rather than a URL; update the docstring to describe that the client
fetches logs for a workflow run by resolving or invoking the appropriate log
endpoint using those identifiers (or alternatively change the API to accept a
logUrl parameter if you intend callers to pass a URL), and ensure the updated
comment on fetchWorkflowRunLogs clearly lists the parameters used to locate the
logs and the behavior when hasLiveObservability is true.

In `@plugins/openchoreo-ci/src/components/BuildLogs/BuildLogs.tsx`:
- Around line 228-233: The useEffect that fetches logs (referenced by the
useEffect block using client, statusState?.hasLiveObservability, activeStepName,
statusState?.status) reads build.namespaceName, build.projectName,
build.componentName, and build.name but does not include build in the dependency
array; update the dependency array to include the build object (or the specific
build properties: build.namespaceName, build.projectName, build.componentName,
build.name) so the effect re-runs whenever the build prop changes and prevents
showing stale logs.
🧹 Nitpick comments (6)
plugins/openchoreo-ci/src/api/OpenChoreoCiClientApi.ts (1)

50-53: Add a proper @deprecated JSDoc instead of a TODO.
Line 51: consumers won’t see deprecation warnings in TS tooling with a TODO.

Proposed tweak
-  // TODO: Deprecate this method and use the new method fetchWorkflowRunLogs instead
+  /** `@deprecated` Use fetchWorkflowRunLogs instead. */
   fetchBuildLogsForBuild(build: ModelsBuild): Promise<RuntimeLogsResponse>;
plugins/openchoreo-ci/src/api/OpenChoreoCiClient.ts (1)

174-216: Consider reusing apiFetch for consistent error handling and headers.
Lines 174–216: this duplicates base URL building and error handling, which can drift.

Suggested refactor
-    const baseUrl = await this.discovery.getBaseUrl('openchoreo-ci-backend');
-    const url = new URL(`${baseUrl}/workflow-run-logs`);
-
-    url.searchParams.set('namespaceName', namespaceName);
-    url.searchParams.set('projectName', projectName);
-    url.searchParams.set('componentName', componentName);
-    url.searchParams.set('runName', runName);
-    url.searchParams.set(
-      'hasLiveObservability',
-      hasLiveObservability.toString(),
-    );
-    if (options?.step) {
-      url.searchParams.set('step', options.step);
-    }
-    if (typeof options?.sinceSeconds === 'number' && options.sinceSeconds > 0) {
-      url.searchParams.set('sinceSeconds', String(options.sinceSeconds));
-    }
-
-    const response = await this.fetchApi.fetch(url.toString());
-
-    if (!response.ok) {
-      const errorText = await response.text();
-      throw new Error(
-        `Failed to fetch workflow run logs (${response.status}): ${errorText}`,
-      );
-    }
-
-    const entries = (await response.json()) as LogEntry[];
-    return entries;
+    const params: Record<string, string> = {
+      namespaceName,
+      projectName,
+      componentName,
+      runName,
+      hasLiveObservability: String(hasLiveObservability),
+    };
+    if (options?.step) params.step = options.step;
+    if (typeof options?.sinceSeconds === 'number' && options.sinceSeconds > 0) {
+      params.sinceSeconds = String(options.sinceSeconds);
+    }
+
+    return this.apiFetch<LogEntry[]>('/workflow-run-logs', { params });
plugins/openchoreo-ci/src/components/BuildLogs/BuildLogs.tsx (3)

42-48: Consider moving constant array outside the component.

terminalStatuses is a static array that never changes. Using useMemo with an empty dependency array works but adds unnecessary overhead. Defining it as a module-level constant is cleaner and avoids the hook call entirely.

♻️ Suggested refactor
+const TERMINAL_STATUSES = ['completed', 'failed', 'succeeded', 'error'];
+
+const isTerminalStatus = (status?: string) =>
+  status ? TERMINAL_STATUSES.includes(status.toLowerCase()) : false;
+
 export const LogsContent = ({ build }: LogsContentProps) => {
   const classes = useStyles();
   const client = useApi(openChoreoCiClientApiRef);
   // ... state declarations ...
-
-  const terminalStatuses = useMemo(
-    () => ['completed', 'failed', 'succeeded', 'error'],
-    [],
-  );
-
-  const isTerminalStatus = (status?: string) =>
-    status ? terminalStatuses.includes(status.toLowerCase()) : false;

323-329: Consider a stable key for log entries if logs could be deduplicated.

Using array index as key (key={index}) works here since logs only append. However, if the duplicate log entries issue is addressed with deduplication (which may filter entries), index-based keys could cause unnecessary re-renders. Using a composite key from log data would be more robust.

♻️ Suggested improvement
-                  {activeLogs.map((logEntry, index) => (
-                    <Box key={index} style={{ marginBottom: '4px' }}>
+                  {activeLogs.map((logEntry, index) => (
+                    <Box key={`${logEntry.timestamp ?? index}-${index}`} style={{ marginBottom: '4px' }}>

331-335: Dead code: else branch is never visible.

When activeStepName !== step.name, the Accordion is collapsed (expanded={false} on line 287), so the AccordionDetails content is hidden. The "Expand this step to view logs" message will never be seen by users.

♻️ Suggested simplification
             <AccordionDetails>
-              {activeStepName === step.name ? (
                 <Box className={classes.logsContainer}>
                   {logsLoading && activeLogs.length === 0 && (
                     <Box className={classes.inlineLoadingContainer}>
                       <CircularProgress size={18} />
                       <Typography variant="body2" color="textSecondary">
                         Loading logs...
                       </Typography>
                     </Box>
                   )}

                   {!logsLoading && activeLogs.length === 0 && (
                     <Typography variant="body2" className={classes.noLogsText}>
                       No logs available for this step
                     </Typography>
                   )}

                   {activeLogs.map((logEntry, index) => (
                     <Box key={index} style={{ marginBottom: '4px' }}>
                       <Typography variant="body2" className={classes.logText}>
                         {logEntry.log}
                       </Typography>
                     </Box>
                   ))}
                 </Box>
-              ) : (
-                <Typography variant="body2" className={classes.noLogsText}>
-                  Expand this step to view logs.
-                </Typography>
-              )}
             </AccordionDetails>
packages/openchoreo-client-node/openapi/openchoreo-observability-api.yaml (1)

104-155: Clarify streaming semantics and cap response size.

The summary says “stream” but the response is a JSON array. If this isn’t SSE/NDJSON, drop “stream”; if it is, define the proper content type. Also consider adding limit and maxItems to avoid unbounded payloads (CKV_OPENAPI_21).

♻️ Suggested spec tweaks
-      summary: Get workflow run logs (stream)
+      summary: Get workflow run logs
...
-        Logs are returned as a JSON array of log entries with timestamps, optionally filtered by step.
+        Logs are returned as a JSON array of log entries with timestamps, optionally filtered by step.
+        Use `limit` to cap the number of entries returned.
...
         - name: step
           in: query
           required: false
           description: Filter logs by a specific workflow step name
           schema:
             type: string
             example: build-step
+        - name: limit
+          in: query
+          required: false
+          description: Maximum number of log entries to return
+          schema:
+            type: integer
+            minimum: 1
+            maximum: 10000
+            default: 100
+            example: 100
...
               schema:
                 type: array
                 description: Array of log entries with timestamps
+                maxItems: 10000
                 items:
                   $ref: '#/components/schemas/ComponentWorkflowRunLogEntry'

Comment on lines +111 to +118
- name: namespaceName
in: path
required: true
description: The namespace name
schema:
type: string
example: 'reading-list-service-workflow-290506e5'
- name: projectName
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Namespace example value looks copied from runName.

This example should be a namespace, not the workflow run ID.

📝 Example fix
-            example: 'reading-list-service-workflow-290506e5'
+            example: 'default'
🤖 Prompt for AI Agents
In `@packages/openchoreo-client-node/openapi/openchoreo-observability-api.yaml`
around lines 111 - 118, The OpenAPI parameter example for namespaceName is
incorrect (it shows a workflow run ID); update the example value for the
parameter named "namespaceName" in openchoreo-observability-api.yaml to a
realistic namespace string (e.g., a service or team name) rather than a run ID
so consumers see a proper namespace example—locate the parameter block for
"namespaceName" and replace the example value accordingly.

Comment on lines +1116 to +1129
ComponentWorkflowRunLogEntry:
type: object
description: A single log entry from a component workflow run
required:
- log
properties:
timestamp:
type: string
format: date-time
description: Timestamp when the log entry was generated (RFC3339 format)
example: '2025-01-06T10:00:00.123Z'
log:
type: string
description: The log message content
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Timestamp should be required (or description updated).

Endpoint text says logs are returned “with timestamps,” but the schema doesn’t require timestamp. Align the schema or revise the description.

📝 Suggested schema alignment
     ComponentWorkflowRunLogEntry:
       type: object
       description: A single log entry from a component workflow run
       required:
-        - log
+        - log
+        - timestamp
📝 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
ComponentWorkflowRunLogEntry:
type: object
description: A single log entry from a component workflow run
required:
- log
properties:
timestamp:
type: string
format: date-time
description: Timestamp when the log entry was generated (RFC3339 format)
example: '2025-01-06T10:00:00.123Z'
log:
type: string
description: The log message content
ComponentWorkflowRunLogEntry:
type: object
description: A single log entry from a component workflow run
required:
- log
- timestamp
properties:
timestamp:
type: string
format: date-time
description: Timestamp when the log entry was generated (RFC3339 format)
example: '2025-01-06T10:00:00.123Z'
log:
type: string
description: The log message content
🤖 Prompt for AI Agents
In `@packages/openchoreo-client-node/openapi/openchoreo-observability-api.yaml`
around lines 1116 - 1129, The ComponentWorkflowRunLogEntry schema declares logs
include a timestamp but does not require the timestamp property; update the
schema so it matches the endpoint contract by adding "timestamp" to the required
array of ComponentWorkflowRunLogEntry (ensuring the timestamp property remains
type: string, format: date-time) OR change the description text on
ComponentWorkflowRunLogEntry.timestamp to indicate timestamps may be absent;
modify the required array or the timestamp description accordingly to keep the
OpenAPI definition consistent with the endpoint documentation.

Comment thread plugins/openchoreo-ci-backend/src/services/WorkflowService.ts
Comment thread plugins/openchoreo-ci/src/api/OpenChoreoCiClientApi.ts
Comment thread plugins/openchoreo-ci/src/components/BuildLogs/BuildLogs.tsx
- Introduced new API endpoints to retrieve the status and logs of component workflow runs.
- Added schemas for `ComponentWorkflowRunStatusResponse` and `WorkflowStepStatus` to define the structure of the responses.
- Updated OpenAPI specification to include detailed descriptions and examples for the new endpoints.

Signed-off-by: Akila-I <akila.99g@gmail.com>
- Added new methods `fetchWorkflowRunStatus` and `fetchWorkflowRunLogs` to the OpenChoreoCiClient for retrieving workflow run status and logs.
- Updated the OpenChoreoCiClientApi interface to include the new methods.
- Enhanced the BuildLogs component to utilize the new API methods for improved log fetching and status display.
- Introduced necessary styles for better UI presentation of workflow steps and logs.

Signed-off-by: Akila-I <akila.99g@gmail.com>
- Enhanced the BuildStatusChip component to include a new case for 'succeeded' status.
- Added a Chip element with a success icon and label for better visual representation of successful builds.

Signed-off-by: Akila-I <akila.99g@gmail.com>
… run status and logs endpoints

Signed-off-by: Akila-I <akila.99g@gmail.com>
Copy link
Copy Markdown

@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 `@plugins/openchoreo-ci-backend/src/services/WorkflowService.ts`:
- Around line 494-509: The error handling in the WorkflowService fetch for
workflow run logs calls await response.text(), which can be empty or throw
because the openapi-fetch client may have already consumed the body; replace
that call and use the existing error object instead (the same pattern used in
fetchBuildLogs). In the error branch of the method (the block checking if (error
|| !response.ok)), remove await response.text() and derive details from the
error variable (e.g., error?.message or JSON.stringify(error)) when logging and
when constructing the thrown Error, while keeping the 404 branch and
ObservableNotConfiguredError behavior unchanged.
🧹 Nitpick comments (3)
plugins/openchoreo-ci/src/components/BuildStatusChip/BuildStatusChip.tsx (1)

12-29: Consider consolidating duplicate switch cases.

The 'completed' and 'succeeded' cases render identical JSX. You can use fall-through to reduce duplication.

♻️ Suggested consolidation
   switch (statusType) {
     case 'completed':
+    case 'succeeded':
       return (
         <Chip
           icon={<CheckCircleIcon style={{ color: '#2e7d32' }} />}
           label={status}
           size="small"
           className={`${classes.statusChip} ${classes.successChip}`}
         />
       );
-    case 'succeeded':
-      return (
-        <Chip
-          icon={<CheckCircleIcon style={{ color: '#2e7d32' }} />}
-          label={status}
-          size="small"
-          className={`${classes.statusChip} ${classes.successChip}`}
-        />
-      );
     case 'failed':
plugins/openchoreo-ci/src/api/OpenChoreoCiClient.ts (1)

174-217: Consider using apiFetch for consistency.

This method manually constructs the URL and handles the fetch, while other methods use the apiFetch helper. This inconsistency makes the code harder to maintain.

♻️ Suggested refactor using apiFetch
   async fetchWorkflowRunLogs(
     namespaceName: string,
     projectName: string,
     componentName: string,
     runName: string,
     hasLiveObservability: boolean,
     options?: { step?: string; sinceSeconds?: number },
   ): Promise<LogEntry[]> {
     if (!namespaceName || !projectName || !componentName || !runName) {
       throw new Error(
         'namespaceName, projectName, componentName and runName are required fields for fetching workflow run logs',
       );
     }

-    const baseUrl = await this.discovery.getBaseUrl('openchoreo-ci-backend');
-    const url = new URL(`${baseUrl}/workflow-run-logs`);
-
-    url.searchParams.set('namespaceName', namespaceName);
-    url.searchParams.set('projectName', projectName);
-    url.searchParams.set('componentName', componentName);
-    url.searchParams.set('runName', runName);
-    url.searchParams.set(
-      'hasLiveObservability',
-      hasLiveObservability.toString(),
-    );
-    if (options?.step) {
-      url.searchParams.set('step', options.step);
-    }
-    if (typeof options?.sinceSeconds === 'number' && options.sinceSeconds > 0) {
-      url.searchParams.set('sinceSeconds', String(options.sinceSeconds));
-    }
-
-    const response = await this.fetchApi.fetch(url.toString());
-
-    if (!response.ok) {
-      const errorText = await response.text();
-      throw new Error(
-        `Failed to fetch workflow run logs (${response.status}): ${errorText}`,
-      );
-    }
-
-    const entries = (await response.json()) as LogEntry[];
-    return entries;
+    const params: Record<string, string> = {
+      namespaceName,
+      projectName,
+      componentName,
+      runName,
+      hasLiveObservability: hasLiveObservability.toString(),
+    };
+    if (options?.step) {
+      params.step = options.step;
+    }
+    if (typeof options?.sinceSeconds === 'number' && options.sinceSeconds > 0) {
+      params.sinceSeconds = String(options.sinceSeconds);
+    }
+
+    return this.apiFetch<LogEntry[]>('/workflow-run-logs', { params });
   }
packages/openchoreo-client-node/openapi/openchoreo-observability-api.yaml (1)

146-164: Consider adding maxItems to the response array schema.

The response array schema lacks a maxItems constraint. While not always necessary, adding a reasonable limit can help document expected behavior and may be flagged by security-focused OpenAPI linters.

Suggested fix
           content:
             application/json:
               schema:
                 type: array
                 description: Array of log entries with timestamps
+                maxItems: 10000
                 items:
                   $ref: '#/components/schemas/ComponentWorkflowRunLogEntry'

Comment on lines +494 to +509
if (error || !response.ok) {
if (response.status === 404) {
this.logger.info(
`Workflow run logs endpoint not available (404). The observability service may not support workflow run logs yet.`,
);
throw new ObservabilityNotConfiguredError(componentName);
}
const errorText = await response.text();
this.logger.error(
`Failed to fetch workflow run logs for component ${componentName}: ${response.status} ${response.statusText}`,
{ error: errorText },
);
throw new Error(
`Failed to fetch workflow run logs: ${response.status} ${response.statusText}`,
);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Potential issue: response.text() may fail or return empty after error parsing.

When the openapi-fetch client encounters an error response, it typically consumes the response body to populate the error object. Calling response.text() afterwards may return an empty string or throw, resulting in an unhelpful error message.

Consider using the error object directly for error details, similar to how fetchBuildLogs handles this at line 334-338:

Suggested approach
       if (error || !response.ok) {
         if (response.status === 404) {
           this.logger.info(
             `Workflow run logs endpoint not available (404). The observability service may not support workflow run logs yet.`,
           );
           throw new ObservabilityNotConfiguredError(componentName);
         }
-        const errorText = await response.text();
         this.logger.error(
           `Failed to fetch workflow run logs for component ${componentName}: ${response.status} ${response.statusText}`,
-          { error: errorText },
+          { error },
         );
         throw new Error(
           `Failed to fetch workflow run logs: ${response.status} ${response.statusText}`,
         );
       }
📝 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
if (error || !response.ok) {
if (response.status === 404) {
this.logger.info(
`Workflow run logs endpoint not available (404). The observability service may not support workflow run logs yet.`,
);
throw new ObservabilityNotConfiguredError(componentName);
}
const errorText = await response.text();
this.logger.error(
`Failed to fetch workflow run logs for component ${componentName}: ${response.status} ${response.statusText}`,
{ error: errorText },
);
throw new Error(
`Failed to fetch workflow run logs: ${response.status} ${response.statusText}`,
);
}
if (error || !response.ok) {
if (response.status === 404) {
this.logger.info(
`Workflow run logs endpoint not available (404). The observability service may not support workflow run logs yet.`,
);
throw new ObservabilityNotConfiguredError(componentName);
}
this.logger.error(
`Failed to fetch workflow run logs for component ${componentName}: ${response.status} ${response.statusText}`,
{ error },
);
throw new Error(
`Failed to fetch workflow run logs: ${response.status} ${response.statusText}`,
);
}
🤖 Prompt for AI Agents
In `@plugins/openchoreo-ci-backend/src/services/WorkflowService.ts` around lines
494 - 509, The error handling in the WorkflowService fetch for workflow run logs
calls await response.text(), which can be empty or throw because the
openapi-fetch client may have already consumed the body; replace that call and
use the existing error object instead (the same pattern used in fetchBuildLogs).
In the error branch of the method (the block checking if (error ||
!response.ok)), remove await response.text() and derive details from the error
variable (e.g., error?.message or JSON.stringify(error)) when logging and when
constructing the thrown Error, while keeping the 404 branch and
ObservableNotConfiguredError behavior unchanged.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants