Skip to content

Conversation

@chandrasekharan-zipstack
Copy link
Contributor

@chandrasekharan-zipstack chandrasekharan-zipstack commented Jan 29, 2026

What

Introduces a reusable CardGridView component for displaying Pipelines and API Deployments in a modern card-based layout with execution status tracking, pagination, search, and scroll restoration.

Why

The existing table-based view does not effectively display rich metadata like execution statuses, connector icons, and scheduling info. Cards provide better UX for scanning pipeline/deployment details at a glance.

How

Commits

# Commit Description
1 61132896e Backend: Add execution status tracking and list enhancements
2 ec69cc3cb Frontend: Add reusable CardGridView component
3 f938be528 Frontend: Refactor API Deployments to use CardGridView
4 558dbb81d Frontend: Refactor Pipelines to use CardGridView
5 ab0190ca1 Frontend: Improve navigation with scroll restoration
6 4fddcda01 Fix: Address CodeRabbit review comments

Backend Changes

  • Add WorkflowExecution.get_last_run_statuses() method with dynamic PARTIAL_SUCCESS computation
  • Extend serializers with last_5_run_statuses, run_count, last_run_time, next_run_time
  • Add source_instance_name and destination_instance_name to pipeline representation
  • Enable CustomPagination and search filters for both endpoints

Frontend Changes

  • New CardGridView widget with card/list modes, expand/collapse, status badges
  • PipelineCardConfig with StatusPills showing clickable execution statuses
  • ApiDeploymentCardConfig with similar card layout
  • Scroll restoration: clicking back from workflow/logs returns to the original card
  • Search and pagination integrated into list views

Can this PR break any existing features? If yes, please list possible items. If no, please explain why.

No. The changes replace the table view with card view but maintain the same underlying data and functionality. All existing actions (edit, delete, share, toggle, etc.) are preserved.

Database Migrations

Not applicable.

Env Config

Not applicable.

Relevant Docs

Related Issues or PRs

Dependencies Versions

Notes on Testing

  • Navigate to Pipelines list - verify cards display with all metadata
  • Navigate to API Deployments list - verify cards display correctly
  • Click status pill on a card -> navigate to logs -> click back -> verify card scrolls into view
  • Click workflow link on a card -> navigate to workflow -> click back -> verify card scrolls into view
  • Test search functionality filters cards correctly
  • Test pagination navigates through pages

Screenshots

Checklist

I have read and understood the Contribution Guidelines.

🤖 Generated with Claude Code

- Add WorkflowExecution.get_last_run_statuses() method to fetch last N
  executions with PARTIAL_SUCCESS computed dynamically
- Add last_5_run_statuses, run_count, last_run_time to API deployment serializer
- Add last_5_run_statuses, next_run_time to pipeline serializer
- Add source/destination instance names to pipeline representation
- Enable pagination (CustomPagination) for both pipelines and deployments
- Add search filter by name for both endpoints

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Create CardGridView widget for displaying items in card/list layouts
- Add CardItem component with expand/collapse, status badges, actions
- Add LoadingSkeleton for loading states
- Support pagination, search, scroll restoration, and card configuration
- Add dateFormatter helper for consistent date formatting

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add ApiDeploymentCardConfig for card layout configuration
- Refactor ApiDeployment to use CardGridView with pagination
- Update api-deployments-service with pagination and search support
- Refactor Body component to use CardGridView instead of Table
- Add search functionality to Header component
- Update Layout to pass card configuration and pagination props

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add PipelineCardConfig with StatusPills component for execution status
- Display source/destination connector icons and instance names
- Show last 5 run statuses with clickable navigation to execution logs
- Add schedule display, next run time, and run count
- Support pagination and search functionality

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Update MenuLayout to forward scrollToCardId state on back navigation
- Pass scrollToCardId from workflow links in card configs
- Add back route with state to ExecutionLogs for scroll restoration
- Remove redundant back button from DetailedLogs header
- Add previousRoute and previousRouteState props to ToolNavBar

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 29, 2026

Walkthrough

Adds server-driven pagination and search to deployment and pipeline endpoints, augments serializers with run-count and recent-run metadata, implements WorkflowExecution.get_last_run_statuses, and replaces table lists with a reusable CardGridView UI plus routing/state-preservation for navigation, search, and pagination.

Changes

Cohort / File(s) Summary
Backend: Viewsets
backend/api_v2/api_deployment_views.py, backend/pipeline_v2/views.py
Enable CustomPagination on viewsets; add ?search= filtering via __icontains; PipelineViewSet adds OrderingFilter and ordering_fields with default ordering including last_run_time and created_at.
Backend: Serializers
backend/api_v2/serializers.py, backend/pipeline_v2/serializers/crud.py
Add SerializerMethodFields for recent-run metadata: run_count, last_run_time, last_5_run_statuses to deployments; add last_5_run_statuses and next_run_time to PipelineSerializer and include connector instance name context.
Backend: Workflow model
backend/workflow_manager/workflow_v2/models/execution.py
Add WorkflowExecution.get_last_run_statuses(pipeline_id, limit=5) to return recent executions with per-run success/failure counts and map to PARTIAL_SUCCESS when mixed outcomes occur.
Frontend: Card grid core
frontend/src/components/widgets/card-grid-view/...
CardGridView.jsx, CardItem.jsx, LoadingSkeleton.jsx, CardFieldComponents.jsx, CardGridView.css, index.js
Introduce a reusable CardGridView system: CardGridView, CardItem, LoadingSkeleton, CardFieldComponents, responsive CSS for grid/list modes, and barrel exports.
Frontend: Deployments UI & service
frontend/src/components/deployments/api-deployment/ApiDeployment.jsx, .../ApiDeploymentCardConfig.jsx, .../api-deployments-service.js
Replace table with card-based UI and createApiDeploymentCardConfig; add server pagination/search state and handlers; update service call to accept page, page_size, and optional search.
Frontend: Pipelines UI & config
frontend/src/components/pipelines-or-deployments/pipelines/Pipelines.jsx, .../PipelineCardConfig.jsx
Switch pipelines to card-based layout with createPipelineCardConfig and StatusPills; add server pagination/search, optimistic enable toggle, and numerous per-card action handlers.
Frontend: Layout & wiring
frontend/src/components/deployments/body/Body.jsx, .../header/Header.jsx, .../layout/Layout.jsx
Body now consumes cardConfig and renders CardGridView; Header/Layout accept and propagate search and pagination props to support card lists and listMode.
Frontend: Navigation & logs state
frontend/src/components/logging/execution-logs/ExecutionLogs.jsx, .../detailed-logs/DetailedLogs.jsx, .../tool-nav-bar/ToolNavBar.jsx, frontend/src/layouts/menu-layout/MenuLayout.jsx
Preserve and pass previous route state for back-navigation and scroll restoration (previousRouteState); remove back button UI in DetailedLogs; make menu back navigation conditional on location.state.from.
Frontend: Helpers & formatters
frontend/src/helpers/GetStaticData.js, frontend/src/helpers/dateFormatter.js
Add shortenApiEndpoint and improved number formatting; add date utilities: formatCompactDate, formatSmartDate, formatFullDate, checkIsToday.

Sequence Diagram(s)

mermaid
sequenceDiagram
participant Client
participant Frontend
participant API
participant DB
Client->>Frontend: Request list (page, pageSize, search)
Frontend->>API: GET /api/.../?page=...&page_size=...&search=...
API->>DB: Query deployments/pipelines with optional icontains filter, apply limit/offset
DB-->>API: Return rows + total count
API->>Frontend: Return paginated response (results, count)
Frontend->>Frontend: Render CardGridView, show pagination and status pills

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 62.22% 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
Description check ✅ Passed The description follows the template with all major sections completed: What, Why, How (with commits and detailed changes), breaking changes assessment, testing notes, and checklist. It comprehensively documents the feature scope and implementation approach.
Title check ✅ Passed The title accurately summarizes the main feature: introducing a card-based layout for Pipelines and API Deployments. It is specific, concise, and clearly conveys the primary change.

✏️ 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
  • Commit unit tests in branch UN-3154-card-grid-view-pipelines-deployments

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.

Copy link
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: 6

Caution

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

⚠️ Outside diff range comments (1)
frontend/src/components/pipelines-or-deployments/pipelines/Pipelines.jsx (1)

53-53: Avoid using require() inside component body.

Using require() inside a React component is non-idiomatic and can cause issues with tree-shaking and bundling. This should be a static import at the top of the file.

♻️ Proposed fix: Use static import
  import { LogsModal } from "../log-modal/LogsModal.jsx";
+ import { fetchExecutionLogs } from "../log-modal/fetchExecutionLogs.js";
  import { EtlTaskDeploy } from "../etl-task-deploy/EtlTaskDeploy.jsx";

  // ... inside component, remove:
- const { fetchExecutionLogs } = require("../log-modal/fetchExecutionLogs.js");
🤖 Fix all issues with AI agents
In `@frontend/src/components/logging/execution-logs/ExecutionLogs.jsx`:
- Around line 29-30: In ExecutionLogs.jsx remove the unused "type" variable from
the destructuring of useParams() so only the needed "id" is extracted (i.e.
change the const { id, type } = useParams() usage to only extract id); this
keeps the component clean since the child component DetailedLogs already calls
useParams() to get type.

In
`@frontend/src/components/pipelines-or-deployments/pipelines/PipelineCardConfig.jsx`:
- Around line 199-201: The code uses cronstrue.toString(pipeline.cron_string)
directly which can throw on invalid cron expressions; wrap the conversion in a
try-catch inside the component (the logic that sets scheduleDisplay) so that if
cronstrue.toString throws, you fall back to "Not scheduled" or another safe
message and optionally log the error; update the assignment for scheduleDisplay
(the pipeline.cron_string -> cronstrue.toString usage) to handle exceptions
gracefully.

In `@frontend/src/components/pipelines-or-deployments/pipelines/Pipelines.jsx`:
- Around line 262-270: After enabling/disabling a pipeline the code calls
getPipelineList() with no args which resets pagination to page 1; update the
success handler of axiosPrivate in the enable/disable flow to call
getPipelineList with the current page state (the same pagination value used by
the delete handler) so the list refresh preserves the user's current page—locate
the axiosPrivate call and replace getPipelineList() with
getPipelineList(currentPage) (or the existing pagination variable name) while
keeping the existing failure rollback (handleLoaderInTableData) and error
handling (setAlertDetails(handleException(err))).

In `@frontend/src/components/widgets/card-grid-view/CardGridView.jsx`:
- Around line 125-126: The comparisons using strict equality between
forceExpandedId/scrollToId and item.id can fail if one side is a string and the
other a number; update the checks in CardGridView (where
forceExpanded={forceExpandedId === item.id} and scrollIntoView={scrollToId ===
item.id}) to perform a type-safe comparison (e.g., coerce both sides to string
before comparing: String(forceExpandedId) === String(item.id) and
String(scrollToId) === String(item.id)), and update the component PropTypes for
forceExpandedId and scrollToId (in the PropTypes block) to accept
oneOfType([PropTypes.string, PropTypes.number]) so callers and type-checking
align.

In `@frontend/src/helpers/GetStaticData.js`:
- Around line 548-554: shortenApiEndpoint currently uses split("/") which can
include query/hash or return the host for host-only URLs; update
shortenApiEndpoint to parse the input (using the URL constructor where possible,
falling back to treating it as a relative path), strip search and hash, trim
trailing slashes, then take only the last pathname segment as the suffix (with a
safe fallback to the entire sanitized path or the original input when parsing
fails) so you don't leak query tokens or produce host-only labels.

In `@frontend/src/layouts/menu-layout/MenuLayout.jsx`:
- Around line 38-44: The onClick handler in MenuLayout.jsx currently calls
navigate(location.state.from, { state: location.state?.scrollToCardId ? {
scrollToCardId: ... } : undefined }) which overwrites any existing state on
location.state.from; change the call so if location.state.from is a location
object with its own state you merge its state with { scrollToCardId } (e.g.,
build a mergedState = { ...(location.state.from.state || {}),
...(location.state?.scrollToCardId ? { scrollToCardId: ... } : {}) } and pass
that mergedState to navigate) and ensure you still pass the original location
object (location.state.from) to navigate so pathname/key/search are preserved.
🧹 Nitpick comments (19)
backend/workflow_manager/workflow_v2/models/execution.py (2)

401-406: Consider using ExecutionStatus enum values instead of hardcoded strings.

The ExecutionStatus enum is already imported in this file (line 18). Using the enum values would be more maintainable and type-safe.

♻️ Suggested refactor
             successful = WorkflowFileExecution.objects.filter(
-                workflow_execution_id=e.id, status="COMPLETED"
+                workflow_execution_id=e.id, status=ExecutionStatus.COMPLETED.value
             ).count()
             failed = WorkflowFileExecution.objects.filter(
-                workflow_execution_id=e.id, status="ERROR"
+                workflow_execution_id=e.id, status=ExecutionStatus.ERROR.value
             ).count()

Also update the condition on line 410:

-            if e.status == "COMPLETED" and failed > 0 and successful > 0:
+            if e.status == ExecutionStatus.COMPLETED.value and failed > 0 and successful > 0:

391-406: Acknowledged: N+1 query pattern documented for future optimization.

The TODO comment appropriately documents the performance concern. Given the small default limit (5), this is acceptable for now. For future optimization, consider using Django's annotate() with conditional aggregation:

from django.db.models import Count, Q

executions = cls.objects.filter(pipeline_id=pipeline_id).annotate(
    successful=Count('file_executions', filter=Q(file_executions__status='COMPLETED')),
    failed=Count('file_executions', filter=Q(file_executions__status='ERROR'))
).order_by("-created_at")[:limit]

This would reduce 11 queries to 1.

backend/pipeline_v2/serializers/crud.py (1)

213-222: Consider logging the exception for debugging purposes.

The bare except Exception catch is flagged by static analysis (BLE001). While catching a broad exception is acceptable here since croniter can raise various exception types for edge cases, logging the error would aid debugging when next_run_time unexpectedly returns None.

♻️ Suggested improvement
     def get_next_run_time(self, instance: Pipeline) -> str | None:
         """Calculate next scheduled run time from cron expression."""
         if not instance.cron_string or not instance.active:
             return None
         try:
             cron = croniter(instance.cron_string, timezone.now())
             next_run: datetime = cron.get_next(datetime)
             return next_run.isoformat()
-        except Exception:
+        except Exception as e:
+            logger.warning(
+                f"Failed to calculate next run time for pipeline {instance.id}: {e}"
+            )
             return None
frontend/src/components/navigations/tool-nav-bar/ToolNavBar.jsx (1)

81-81: Consider using PropTypes.shape for stricter validation.

The previousRouteState prop is typed as a generic PropTypes.object. Based on the usage in ExecutionLogs.jsx, you could define a more specific shape for better documentation and validation.

🔧 Optional: Add shape validation
-  previousRouteState: PropTypes.object,
+  previousRouteState: PropTypes.shape({
+    scrollToCardId: PropTypes.string,
+    cardExpanded: PropTypes.bool,
+  }),
frontend/src/components/deployments/api-deployment/api-deployments-service.js (1)

4-4: Pre-existing: Shared mutable options variable could cause race conditions.

The module-level let options = {} is reassigned by every method, which could cause issues if multiple concurrent API calls are made (e.g., rapid pagination clicks). This is a pre-existing pattern, so not blocking, but worth noting for future improvement.

♻️ Suggested pattern: Use local `const` in each method
-let options = {};
-
 function apiDeploymentsService() {
   // ...
   return {
     getApiDeploymentsList: (page = 1, pageSize = 10, search = "") => {
       const params = { page, page_size: pageSize };
       if (search) params.search = search;
-      options = {
+      const options = {
         url: `${path}/api/deployment/`,
         method: "GET",
         params,
       };
       return axiosPrivate(options);
     },
     // Apply same pattern to other methods...
   };
 }
frontend/src/components/widgets/card-grid-view/CardGridView.css (1)

638-641: Consider removing the unused .connector-flow-arrow rule.

The comment states this element is "no longer used in vertical layout" and the rule just hides it. If the arrow element has been removed from the JSX, this CSS rule can be deleted entirely. If it might be reintroduced, consider adding a TODO comment.

frontend/src/helpers/dateFormatter.js (2)

64-76: Future dates may produce unexpected relative formatting.

When date is in the future, differenceInHours(now, date) and differenceInMinutes(now, date) return negative values. This means a date 5 minutes in the future would pass the minutesAgo < 1 check (since -5 < 1) and display "Just now", which is misleading.

If future dates are valid inputs (e.g., next_run_time), consider adding explicit handling:

💡 Proposed fix to handle future dates
   // Handle relative formatting for recent dates
   if (relative) {
     const hoursAgo = differenceInHours(now, date);
     const minutesAgo = differenceInMinutes(now, date);
 
+    // Skip relative formatting for future dates
+    if (minutesAgo < 0) {
+      return includeTime
+        ? format(date, "dd MMM yyyy HH:mm")
+        : format(date, "dd MMM yyyy");
+    }
+
     // "Just now" for < 1 minute
     if (minutesAgo < 1) {
       return "Just now";
     }

51-59: Consider extracting shared date parsing logic.

The try/catch parsing pattern is duplicated in formatCompactDate, formatFullDate, and checkIsToday. A small helper would reduce repetition.

♻️ Optional: Extract parsing helper
/**
 * Safely parse a date value
 * `@param` {string|Date} value - Date string or Date object
 * `@return` {Date|null} Parsed Date or null if invalid
 */
function safeParse(value) {
  if (!value) return null;
  try {
    const date = typeof value === "string" ? parseISO(value) : new Date(value);
    return isValid(date) ? date : null;
  } catch {
    return null;
  }
}

Then usage becomes:

const date = safeParse(isoDate);
if (!date) return isoDate; // or "-" or false depending on function
frontend/src/components/deployments/api-deployment/ApiDeploymentCardConfig.jsx (2)

320-333: PropTypes on a factory function have no runtime effect.

createApiDeploymentCardConfig.propTypes won't be validated by React since this is a plain function returning a config object, not a React component. This serves as documentation but provides no runtime type checking.

Consider either:

  1. Using JSDoc @param types (already partially done) for documentation
  2. Using TypeScript for actual type safety
  3. Moving PropTypes validation to where the config is consumed

257-261: Prop naming mismatch: pipelineId used for deployment ID.

StatusPills receives pipelineId={deployment.id} but this is an API deployment, not a pipeline. Consider renaming the prop to a more generic entityId in StatusPills to avoid confusion when reused across different entity types.

frontend/src/components/widgets/card-grid-view/CardGridView.jsx (1)

78-83: Index-based key fallback may cause reconciliation issues.

Using index as the fallback key (line 82) can cause incorrect component reuse if items are reordered, inserted, or deleted. While this is a reasonable fallback, consider logging a dev warning when the fallback is used to help identify data without proper keys.

frontend/src/components/deployments/api-deployment/ApiDeployment.jsx (3)

291-308: Missing dependencies in useMemo may cause stale closures.

The useMemo dependency array only includes [sessionDetails, location], but the config references multiple handler functions (handleEditDeployment, handleShareDeployment, handleDeleteDeployment, etc.) that are recreated on every render. Since these handlers reference state like pagination, they could become stale.

Consider either:

  1. Adding all handlers to the dependency array
  2. Wrapping handlers in useCallback with proper dependencies
  3. Using refs to access current values within handlers
♻️ Option: Wrap handlers in useCallback
+ const handleEditDeployment = useCallback(() => {
+   openAddModal(true);
+ }, []);

+ const handleDeleteDeployment = useCallback(() => {
+   setOpenDeleteModal(true);
+ }, []);

// ... similar for other handlers

  const apiDeploymentCardConfig = useMemo(
    () =>
      createApiDeploymentCardConfig({
        // ...
      }),
-   [sessionDetails, location]
+   [sessionDetails, location, handleEditDeployment, handleShareDeployment, handleDeleteDeployment, /* ... other handlers */]
  );

92-99: Consider combining effects or removing redundant state.

filteredData is set directly from tableData without any transformation. Since search is now server-side (line 96-99), this intermediate state appears unnecessary. You could pass tableData directly to the Layout component.

♻️ Simplify by removing filteredData
- const [filteredData, setFilteredData] = useState([]);

- useEffect(() => {
-   setFilteredData(tableData);
- }, [tableData]);

  // In the Layout component:
- tableData={filteredData}
+ tableData={tableData}

115-138: Pagination total calculation may be incorrect when data is not paginated.

On line 129, when the response is not paginated (data.count is undefined), the fallback uses the current page's result length rather than the actual total. This would break pagination controls if the backend returns unpaginated data.

total: data.count || (data.results ? data.results.length : data.length),

If the API can return both paginated and unpaginated responses, the non-paginated case should set total to the full array length, which is correct. However, if the backend always returns paginated responses now, consider simplifying this logic.

frontend/src/components/deployments/body/Body.jsx (1)

21-23: Redundant loading state handling.

The isTableLoading check at line 21-23 shows a SpinnerLoader and returns early, but loading={isTableLoading} is also passed to CardGridView at line 42. Since the early return prevents CardGridView from ever receiving loading={true}, the loading prop is effectively always false.

Either remove the early return and let CardGridView handle the loading state internally (via LoadingSkeleton), or remove the loading prop from CardGridView.

♻️ Option 1: Let CardGridView handle loading
- if (isTableLoading) {
-   return <SpinnerLoader />;
- }
  if (!tableData?.length) {
+   if (isTableLoading) {
+     return <SpinnerLoader />;
+   }
    return (
      <IslandLayout>
        <EmptyState

Also applies to: 39-49

frontend/src/components/pipelines-or-deployments/pipelines/PipelineCardConfig.jsx (1)

302-320: Popconfirm delete flow may have stale pipeline reference.

In the onConfirm callback (lines 305-308), setSelectedPorD(pipeline) is called before onDelete?.(pipeline). If onDelete triggers a re-render or async operation that depends on selectedPorD, there could be a race condition since setSelectedPorD is asynchronous.

However, since pipeline is passed directly to onDelete, this should work correctly. The setSelectedPorD call might be redundant here if onDelete doesn't rely on it.

frontend/src/components/pipelines-or-deployments/pipelines/Pipelines.jsx (2)

465-490: Same useMemo dependency issue as ApiDeployment.jsx.

The dependency array [sessionDetails, isClearingFileHistory, location, type] is missing the handler functions that are referenced in the config. This could lead to stale closures when handlers depend on state that changes.


104-114: Scroll restoration timer works but doesn't clean up navigation state.

The scroll restoration correctly clears scrollRestoreId after 500ms, but the location.state.scrollToCardId remains in the browser history. If the user navigates away and back, it may trigger again unexpectedly. Consider using navigate with replace: true to clear the state after handling.

frontend/src/components/widgets/card-grid-view/CardItem.jsx (1)

82-98: Helper functions recreated on every render.

resolveValue, isFieldVisible, renderField, renderSection, and renderActions are all defined inside the component and recreated on every render. For a card component rendered multiple times in a grid, this could have minor performance implications.

However, since these functions depend on item and config from props, extracting them would require passing these as arguments. The current approach is acceptable for most use cases.

- Remove unused variables: type, filteredData, copyUrl, handleStatusRefresh,
  getPipelineData, sessionDetails
- Wrap cronstrue.toString() in try-catch for invalid cron expressions
- Use current pagination when refreshing after pipeline enable/disable
- Use String() coercion for type-safe ID comparison in CardGridView
- Update PropTypes to accept string or number for IDs
- Improve shortenApiEndpoint to strip query/hash and handle edge cases
- Merge state properly in MenuLayout back navigation

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copy link
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: 1

Caution

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

⚠️ Outside diff range comments (1)
frontend/src/components/deployments/api-deployment/ApiDeployment.jsx (1)

163-175: Direct object mutation in updateStatus may cause stale UI or React warnings.

Line 165 directly mutates the record object (record.is_active = !record?.is_active). This is an anti-pattern in React since:

  1. It mutates state directly instead of using setState
  2. The UI update relies on the component re-rendering from setIsTableLoading, not from actual data changes
  3. If the API call fails, there's no rollback mechanism

Consider using the optimistic update pattern like in Pipelines.jsx:

🔧 Proposed fix with optimistic update and rollback
   const updateStatus = (record) => {
-    setIsTableLoading(true);
-    record.is_active = !record?.is_active;
+    const newStatus = !record?.is_active;
+    // Optimistic update
+    setTableData((prevData) =>
+      prevData.map((item) =>
+        item.id === record.id ? { ...item, is_active: newStatus } : item
+      )
+    );
+
     apiDeploymentsApiService
-      .updateApiDeployment(record)
-      .then(() => {})
+      .updateApiDeployment({ ...record, is_active: newStatus })
+      .then(() => {
+        // Optionally refresh to sync with server
+        getApiDeploymentList(pagination.current, pagination.pageSize);
+      })
       .catch((err) => {
+        // Rollback on failure
+        setTableData((prevData) =>
+          prevData.map((item) =>
+            item.id === record.id ? { ...item, is_active: !newStatus } : item
+          )
+        );
         setAlertDetails(handleException(err));
-      })
-      .finally(() => {
-        setIsTableLoading(false);
       });
   };
🤖 Fix all issues with AI agents
In `@frontend/src/components/pipelines-or-deployments/pipelines/Pipelines.jsx`:
- Around line 98-100: The component calls setFilteredData but never declares the
corresponding state; add a useState declaration for filteredData and
setFilteredData (initialized from tableData, e.g., using useState(tableData ||
[])) near the top of the Pipelines component so the existing useEffect (which
calls setFilteredData(tableData)) and other calls to setFilteredData work
without runtime errors; also update any places that should read the derived
array to use filteredData instead of tableData (e.g., the render/logic that
currently references tableData on line ~464).
🧹 Nitpick comments (2)
frontend/src/components/pipelines-or-deployments/pipelines/PipelineCardConfig.jsx (1)

151-161: PropTypes for pipelineId should accept both string and number.

For consistency with similar changes in CardGridView.jsx, pipelineId should accept both types since pipeline.id could be either.

🔧 Proposed fix
 StatusPills.propTypes = {
   statuses: PropTypes.arrayOf(
     PropTypes.shape({
       status: PropTypes.string,
       timestamp: PropTypes.string,
       execution_id: PropTypes.string,
     })
   ),
   executionType: PropTypes.string,
-  pipelineId: PropTypes.string,
+  pipelineId: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
 };
frontend/src/components/pipelines-or-deployments/pipelines/Pipelines.jsx (1)

52-52: Using require() inside a function component is non-standard and may cause bundling issues.

Dynamic require() calls inside React components are typically avoided as they:

  1. Bypass static analysis and tree-shaking
  2. May behave differently in ESM vs CommonJS environments
  3. Are evaluated at runtime rather than build time

Consider moving this to a standard import at the top of the file.

🔧 Proposed fix

At the top of the file, add:

 import { SharePermission } from "../../widgets/share-permission/SharePermission";
 import { createPipelineCardConfig } from "./PipelineCardConfig.jsx";
+import { fetchExecutionLogs } from "../log-modal/fetchExecutionLogs";

Then remove line 52:

-  const { fetchExecutionLogs } = require("../log-modal/fetchExecutionLogs");

- Fix HIGH severity: Remove orphaned setFilteredData references in Pipelines.jsx
- Fix LOW severity: Add keyboard listeners to clickable spans for accessibility
  - PipelineCardConfig.jsx: Add role, tabIndex, onKeyDown to status badge
  - CardItem.jsx: Add keyboard handlers to action spans and expand chevron
- Reduce code duplication between ApiDeploymentCardConfig and PipelineCardConfig
  - Extract shared components to CardFieldComponents.jsx:
    CardActionBox, OwnerFieldRow, LastRunFieldRow, Last5RunsFieldRow,
    WorkflowFieldRow, ApiEndpointSection, CardHeaderRow
  - Net reduction: -289 lines in existing files

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Replace regex /\/+$/ with iterative string manipulation to strip
trailing slashes, avoiding potential ReDoS concerns flagged by
SonarCloud security scanner.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copy link
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: 4

Caution

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

⚠️ Outside diff range comments (1)
frontend/src/components/pipelines-or-deployments/pipelines/Pipelines.jsx (1)

265-272: Fix stale selectedPorD when clearing file history.
clearFileMarkers ignores the passed workflow id and relies on selectedPorD, which may still be the previous pipeline due to async state updates. Use the workflow id argument directly.

🐛 Proposed fix
-  const clearFileMarkers = async () => {
-    const workflowId = selectedPorD?.workflow_id;
-    const success = await clearFileHistory(workflowId);
+  const clearFileMarkers = async (workflowId) => {
+    if (!workflowId) return;
+    const success = await clearFileHistory(workflowId);
     if (success && openDeleteModal) {
       setOpenDeleteModal(false);
     }
   };
@@
   const handleClearFileHistoryPipeline = (pipeline) => {
     setSelectedPorD(pipeline);
     clearFileMarkers(pipeline.workflow_id);
   };

Also applies to: 390-393

🤖 Fix all issues with AI agents
In `@frontend/src/components/pipelines-or-deployments/pipelines/Pipelines.jsx`:
- Around line 110-113: The server-side search term isn't persisted across
pagination/refresh; modify the component to store the query in state (e.g., add
a searchTerm state and setSearchTerm) and update handleSearch to set that state
instead of only calling getPipelineList; then ensure every call site that
invokes getPipelineList (including the pagination handlers, refresh/delete/share
callbacks referenced around lines where getPipelineList is called) passes the
current searchTerm state (trimmed or "") as the search parameter so paging and
subsequent refreshes keep the filter applied.
- Around line 418-444: The memoized pipelineCardConfig is missing pagination in
its dependency array which causes handlers that close over pagination
(handleEnablePipeline, handleSharePipeline, handleDeletePipeline) to use stale
pagination values; update the useMemo dependency array for
createPipelineCardConfig(...) to include pagination.current and
pagination.pageSize (or the pagination object) so pipelineCardConfig is
recomputed when pagination changes, ensuring those handlers refresh to the
correct page.

In `@frontend/src/components/widgets/card-grid-view/CardFieldComponents.jsx`:
- Around line 198-209: The Link target can be malformed if sessionDetails or
sessionDetails.orgName is undefined; update the Link rendering in
CardFieldComponents.jsx (the Link using sessionDetails?.orgName and workflowId)
to guard and provide a safe fallback (e.g., defaultOrgName or avoid rendering
the Link) by computing a safeOrg = sessionDetails?.orgName || '<fallback>'
before use, or conditionally render a non-navigating element when safeOrg is
missing, and ensure state (from, scrollToCardId) still uses location and itemId
correctly; adjust the Link's to prop to use safeOrg instead of
sessionDetails?.orgName so the route is never `/undefined/...`.
- Around line 38-86: These interactive icons (EditOutlined, ShareAltOutlined,
DeleteOutlined, MoreOutlined) need keyboard accessibility: add role="button" and
tabIndex={0} to each and implement onKeyDown handlers that listen for Enter and
Space to perform the same action as the onClick handlers (for EditOutlined call
setSelectedItem(item) then onEdit?.(item); for ShareAltOutlined call
setSelectedItem(item) then onShare?.(item); for DeleteOutlined call
setSelectedItem(item) then trigger the Popconfirm flow / onDelete?.(item); for
MoreOutlined ensure the kebabMenu dropdown is opened/activated), and ensure each
handler calls e.stopPropagation() like the click handlers.
🧹 Nitpick comments (2)
frontend/src/components/widgets/card-grid-view/CardFieldComponents.jsx (1)

165-169: Consider a more generic prop name for the StatusPillsComponent.

The prop pipelineId={itemId} uses pipeline-specific naming, but this component is reused for API Deployments as well. If StatusPillsComponent can accept a generic prop name like itemId or entityId, that would improve clarity.

frontend/src/components/pipelines-or-deployments/pipelines/Pipelines.jsx (1)

52-52: Prefer a static ES module import for fetchExecutionLogs.
Keeping require inside the component re-resolves the module each render and can be awkward with ESM tooling. Consider moving it to a top-level import (please verify your bundler supports this import path).

♻️ Proposed refactor
-import { useLocation } from "react-router-dom";
+import { useLocation } from "react-router-dom";
+import { fetchExecutionLogs } from "../log-modal/fetchExecutionLogs";
@@
-  const { fetchExecutionLogs } = require("../log-modal/fetchExecutionLogs");

Comment on lines 38 to 86
<Tooltip title="Edit">
<EditOutlined
className="action-icon-btn edit-icon"
onClick={(e) => {
e.stopPropagation();
setSelectedItem(item);
onEdit?.(item);
}}
/>
</Tooltip>
<Tooltip title="Share">
<ShareAltOutlined
className="action-icon-btn share-icon"
onClick={(e) => {
e.stopPropagation();
setSelectedItem(item);
onShare?.(item);
}}
/>
</Tooltip>
<Popconfirm
title={deleteTitle}
description="This action cannot be undone."
onConfirm={() => {
setSelectedItem(item);
onDelete?.(item);
}}
onCancel={(e) => e?.stopPropagation()}
okText="Delete"
cancelText="Cancel"
okButtonProps={{ danger: true }}
>
<Tooltip title="Delete">
<DeleteOutlined
className="action-icon-btn delete-icon"
onClick={(e) => e.stopPropagation()}
/>
</Tooltip>
</Popconfirm>
<Dropdown
menu={kebabMenuItems}
trigger={["click"]}
placement="bottomRight"
>
<MoreOutlined
className="card-kebab-menu"
onClick={(e) => e.stopPropagation()}
/>
</Dropdown>
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Clickable icons missing keyboard accessibility attributes.

The EditOutlined, ShareAltOutlined, DeleteOutlined, and MoreOutlined icons are interactive but lack role="button", tabIndex={0}, and onKeyDown handlers. This prevents keyboard-only users from focusing and activating these actions.

The PR commit message mentions adding accessibility handlers, but these icons appear to be missing them. Consider wrapping each in an accessible element or adding the required attributes.

♿ Proposed fix for EditOutlined (apply similar pattern to others)
       <Tooltip title="Edit">
         <EditOutlined
           className="action-icon-btn edit-icon"
+          role="button"
+          tabIndex={0}
           onClick={(e) => {
             e.stopPropagation();
             setSelectedItem(item);
             onEdit?.(item);
           }}
+          onKeyDown={(e) => {
+            if (e.key === "Enter" || e.key === " ") {
+              e.preventDefault();
+              e.stopPropagation();
+              setSelectedItem(item);
+              onEdit?.(item);
+            }
+          }}
         />
       </Tooltip>
📝 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
<Tooltip title="Edit">
<EditOutlined
className="action-icon-btn edit-icon"
onClick={(e) => {
e.stopPropagation();
setSelectedItem(item);
onEdit?.(item);
}}
/>
</Tooltip>
<Tooltip title="Share">
<ShareAltOutlined
className="action-icon-btn share-icon"
onClick={(e) => {
e.stopPropagation();
setSelectedItem(item);
onShare?.(item);
}}
/>
</Tooltip>
<Popconfirm
title={deleteTitle}
description="This action cannot be undone."
onConfirm={() => {
setSelectedItem(item);
onDelete?.(item);
}}
onCancel={(e) => e?.stopPropagation()}
okText="Delete"
cancelText="Cancel"
okButtonProps={{ danger: true }}
>
<Tooltip title="Delete">
<DeleteOutlined
className="action-icon-btn delete-icon"
onClick={(e) => e.stopPropagation()}
/>
</Tooltip>
</Popconfirm>
<Dropdown
menu={kebabMenuItems}
trigger={["click"]}
placement="bottomRight"
>
<MoreOutlined
className="card-kebab-menu"
onClick={(e) => e.stopPropagation()}
/>
</Dropdown>
<Tooltip title="Edit">
<EditOutlined
className="action-icon-btn edit-icon"
role="button"
tabIndex={0}
onClick={(e) => {
e.stopPropagation();
setSelectedItem(item);
onEdit?.(item);
}}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
e.stopPropagation();
setSelectedItem(item);
onEdit?.(item);
}
}}
/>
</Tooltip>
<Tooltip title="Share">
<ShareAltOutlined
className="action-icon-btn share-icon"
role="button"
tabIndex={0}
onClick={(e) => {
e.stopPropagation();
setSelectedItem(item);
onShare?.(item);
}}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
e.stopPropagation();
setSelectedItem(item);
onShare?.(item);
}
}}
/>
</Tooltip>
<Popconfirm
title={deleteTitle}
description="This action cannot be undone."
onConfirm={() => {
setSelectedItem(item);
onDelete?.(item);
}}
onCancel={(e) => e?.stopPropagation()}
okText="Delete"
cancelText="Cancel"
okButtonProps={{ danger: true }}
>
<Tooltip title="Delete">
<DeleteOutlined
className="action-icon-btn delete-icon"
role="button"
tabIndex={0}
onClick={(e) => e.stopPropagation()}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
e.stopPropagation();
}
}}
/>
</Tooltip>
</Popconfirm>
<Dropdown
menu={kebabMenuItems}
trigger={["click"]}
placement="bottomRight"
>
<MoreOutlined
className="card-kebab-menu"
role="button"
tabIndex={0}
onClick={(e) => e.stopPropagation()}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
e.stopPropagation();
}
}}
/>
</Dropdown>
🤖 Prompt for AI Agents
In `@frontend/src/components/widgets/card-grid-view/CardFieldComponents.jsx`
around lines 38 - 86, These interactive icons (EditOutlined, ShareAltOutlined,
DeleteOutlined, MoreOutlined) need keyboard accessibility: add role="button" and
tabIndex={0} to each and implement onKeyDown handlers that listen for Enter and
Space to perform the same action as the onClick handlers (for EditOutlined call
setSelectedItem(item) then onEdit?.(item); for ShareAltOutlined call
setSelectedItem(item) then onShare?.(item); for DeleteOutlined call
setSelectedItem(item) then trigger the Popconfirm flow / onDelete?.(item); for
MoreOutlined ensure the kebabMenu dropdown is opened/activated), and ensure each
handler calls e.stopPropagation() like the click handlers.

Update clearFileMarkers to accept optional workflowId parameter
to match the call site that passes pipeline.workflow_id.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Remove unused useSessionStore import in DetailedLogs.jsx
- Add search term persistence across pagination in Pipelines.jsx
- Fix stale closure in pipelineCardConfig useMemo dependencies
- Add keyboard accessibility (role, tabIndex, onKeyDown) to action icons
- Guard against undefined orgName in WorkflowFieldRow link rendering

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copy link
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: 1

🤖 Fix all issues with AI agents
In `@frontend/src/components/pipelines-or-deployments/pipelines/Pipelines.jsx`:
- Around line 143-149: The pagination total calculation in the setPagination
callback uses the || operator which treats data.count === 0 as falsy and falls
back incorrectly; update the logic in the setPagination call (where
setPagination is invoked to update current/pageSize/total) to use a nullish
check (or explicit undefined check) for data.count — e.g., set total to
data.count if it is not null/undefined, otherwise fall back to
data.results?.length or data.length — ensuring zero counts are preserved.
🧹 Nitpick comments (2)
frontend/src/components/widgets/card-grid-view/CardFieldComponents.jsx (1)

195-199: Consider renaming pipelineId prop for clarity.

This component is used for both pipelines and API deployments per the PR scope. Passing itemId as pipelineId works functionally but may be confusing for API deployment contexts. If StatusPillsComponent supports a more generic prop name (e.g., entityId or itemId), consider using that for consistency.

frontend/src/components/pipelines-or-deployments/pipelines/Pipelines.jsx (1)

53-53: Move require() to a top-level ES import.

Using require() inside a component body is an anti-pattern in modern React applications. It evaluates on each render, isn't tree-shakeable, and is inconsistent with the ES module imports used throughout this file.

♻️ Proposed fix

Add at the top of the file with other imports:

+import { fetchExecutionLogs } from "../log-modal/fetchExecutionLogs";

Then remove line 53:

-  const { fetchExecutionLogs } = require("../log-modal/fetchExecutionLogs");

- Fix array index keys in PipelineCardConfig.jsx and LoadingSkeleton.jsx
- Replace role="button" with native <button> elements for accessibility
- Fix text contrast ratios to meet WCAG AA (4.5:1 minimum)
- Fix unexpected negated conditions in pagination handlers
- Use globalThis instead of window for better portability
- Add proper button reset styles in CSS

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copy link
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: 2

Caution

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

⚠️ Outside diff range comments (2)
frontend/src/components/deployments/api-deployment/ApiDeployment.jsx (1)

163-175: Direct mutation of state before API confirmation.

Line 165 mutates record.is_active directly. Since record likely references an object in tableData, this mutates React state outside of setState, which can cause inconsistent UI and debugging difficulties. If the API call fails, the local state remains incorrectly toggled with no rollback.

Proposed fix with optimistic update and rollback
   const updateStatus = (record) => {
     setIsTableLoading(true);
-    record.is_active = !record?.is_active;
+    const newStatus = !record?.is_active;
+    const updatedRecord = { ...record, is_active: newStatus };
+    
+    // Optimistically update the UI
+    setTableData((prev) =>
+      prev.map((item) => (item.id === record.id ? updatedRecord : item))
+    );
+    
     apiDeploymentsApiService
-      .updateApiDeployment(record)
-      .then(() => {})
+      .updateApiDeployment(updatedRecord)
+      .then(() => {
+        // Success - state already updated
+      })
       .catch((err) => {
+        // Rollback on failure
+        setTableData((prev) =>
+          prev.map((item) => (item.id === record.id ? record : item))
+        );
         setAlertDetails(handleException(err));
       })
       .finally(() => {
         setIsTableLoading(false);
       });
   };
frontend/src/components/pipelines-or-deployments/pipelines/Pipelines.jsx (1)

95-97: Search term not reset when type changes.

When the pipeline type changes, getPipelineList() is called with default parameters (empty search), but searchTerm state is not reset. This could cause the search input to show a stale value that doesn't match the displayed data.

🐛 Proposed fix
   useEffect(() => {
+    setSearchTerm("");
+    setPagination((prev) => ({ ...prev, current: 1 }));
     getPipelineList();
   }, [type]);
🤖 Fix all issues with AI agents
In `@frontend/src/components/deployments/api-deployment/ApiDeployment.jsx`:
- Around line 120-129: The pagination total logic incorrectly treats data.count
=== 0 as falsy and falls back to array length; update the code that sets
pagination (the setPagination call that computes total) to use a nullish check
(e.g., use data.count ?? (data.results ? data.results.length : data.length) or
explicitly check data.count !== undefined) so a legitimate zero total is
preserved; adjust the same expression that sets setTableData/setPagination to
rely on data.results when present and only fallback to lengths when count is
undefined.

In
`@frontend/src/components/pipelines-or-deployments/pipelines/PipelineCardConfig.jsx`:
- Around line 89-94: The current key computation inside the statuses.map mapping
(where key is set using run.execution_id || run.timestamp || config.label) can
produce duplicate React keys when multiple runs lack execution_id and timestamp;
update the mapping logic in the statuses.map callback (the function that calls
getStatusConfig) to append the array index as a final fallback so each key is
unique (e.g., use run.execution_id || run.timestamp ||
`${config.label}-${index}` or similar), ensuring you reference the index
parameter of the map callback when constructing the key.
🧹 Nitpick comments (5)
frontend/src/components/deployments/api-deployment/ApiDeployment.jsx (2)

290-307: useMemo has incomplete dependencies—handlers may become stale.

The memo depends only on [sessionDetails, location], but it captures updateStatus and all handle* callbacks which are recreated on every render. If any of these handlers reference state that changes (e.g., pagination in updateStatus's flow, or selectedRow in handleShare), the memoized config will hold stale references.

Consider either:

  1. Wrapping the handlers with useCallback and including them in deps, or
  2. Removing useMemo if re-creation on every render is acceptable.
Option 1: Stabilize handlers with useCallback
const handleEditDeployment = useCallback(() => {
  openAddModal(true);
}, []);

const handleShareDeployment = useCallback(() => {
  handleShare();
}, [handleShare]); // handleShare would also need useCallback

// ... similarly for other handlers

const apiDeploymentCardConfig = useMemo(
  () =>
    createApiDeploymentCardConfig({
      setSelectedRow,
      updateStatus,
      sessionDetails,
      location,
      onEdit: handleEditDeployment,
      onShare: handleShareDeployment,
      // ...
    }),
  [
    sessionDetails,
    location,
    updateStatus,
    handleEditDeployment,
    handleShareDeployment,
    // ... all handlers
  ]
);

91-93: filteredData appears redundant with server-side search.

The useEffect syncs filteredData to match tableData on every update, and handleSearch fetches from the server rather than filtering locally. Passing setSearchList={setFilteredData} suggests client-side filtering capability, but it's not being used here.

If client-side filtering is not needed, consider removing filteredData state and passing tableData directly to simplify the data flow.

Also applies to: 323-323, 330-330

frontend/src/components/pipelines-or-deployments/pipelines/PipelineCardConfig.jsx (1)

142-152: PropTypes shape is incomplete.

The component uses run.successful_files and run.failed_files (lines 92, 100, 103), but these are not documented in the PropTypes shape.

📝 Proposed fix
 StatusPills.propTypes = {
   statuses: PropTypes.arrayOf(
     PropTypes.shape({
       status: PropTypes.string,
       timestamp: PropTypes.string,
       execution_id: PropTypes.string,
+      successful_files: PropTypes.number,
+      failed_files: PropTypes.number,
     })
   ),
   executionType: PropTypes.string,
   pipelineId: PropTypes.string,
 };
frontend/src/components/pipelines-or-deployments/pipelines/Pipelines.jsx (1)

53-53: Avoid require() inside functional components.

Using require() inside a React component is unconventional and may cause issues with tree-shaking and module bundling. It also runs on every render. Consider moving this to a top-level import.

♻️ Proposed fix

Move to top-level imports:

 import { SharePermission } from "../../widgets/share-permission/SharePermission";
 import { createPipelineCardConfig } from "./PipelineCardConfig.jsx";
+import { fetchExecutionLogs } from "../log-modal/fetchExecutionLogs";

 function Pipelines({ type }) {
   // ... state declarations ...
-  const { fetchExecutionLogs } = require("../log-modal/fetchExecutionLogs");
frontend/src/components/widgets/card-grid-view/CardGridView.css (1)

219-236: Consider adding prefers-reduced-motion support for accessibility.

Users who prefer reduced motion (due to vestibular disorders) should have animations disabled or minimized. This is a WCAG 2.1 AAA consideration but increasingly expected.

♻️ Suggested enhancement
 `@keyframes` cardExpandAnimation {
   from {
     opacity: 0;
     transform: translateY(-8px);
   }
   to {
     opacity: 1;
     transform: translateY(0);
   }
 }
+
+@media (prefers-reduced-motion: reduce) {
+  .card-expanded-content {
+    animation: none;
+  }
+  
+  .card-list-expanded {
+    transition: none;
+  }
+}

Comment on lines +120 to +129
const data = res?.data;
// Handle paginated response
setTableData(data.results || data);
setPagination((prev) => ({
...prev,
current: page,
pageSize,
total:
data.count || (data.results ? data.results.length : data.length),
}));
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Pagination total may be incorrect when data.count is 0.

When data.count is 0 (a valid count for empty results), the || operator treats it as falsy and falls back to the array length. This could misrepresent the total when server returns zero matching results.

Proposed fix
         setPagination((prev) => ({
           ...prev,
           current: page,
           pageSize,
-          total:
-            data.count || (data.results ? data.results.length : data.length),
+          total:
+            data.count !== undefined
+              ? data.count
+              : data.results
+                ? data.results.length
+                : data.length,
         }));
🤖 Prompt for AI Agents
In `@frontend/src/components/deployments/api-deployment/ApiDeployment.jsx` around
lines 120 - 129, The pagination total logic incorrectly treats data.count === 0
as falsy and falls back to array length; update the code that sets pagination
(the setPagination call that computes total) to use a nullish check (e.g., use
data.count ?? (data.results ? data.results.length : data.length) or explicitly
check data.count !== undefined) so a legitimate zero total is preserved; adjust
the same expression that sets setTableData/setPagination to rely on data.results
when present and only fallback to lengths when count is undefined.

- Revert status badge colors to vibrant originals (pre-SonarCloud fix)
- Fix pipeline list sorting with nulls_last for last_run_time
- Add index fallback to React key in StatusPills to prevent duplicates
- Simplify enableDisablePipeline by removing unnecessary .then()
- Remove unused pagination deps from useMemo

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@github-actions
Copy link
Contributor

Test Results

Summary
  • Runner Tests: 11 passed, 0 failed (11 total)
  • SDK1 Tests: 66 passed, 0 failed (66 total)

Runner Tests - Full Report
filepath function $$\textcolor{#23d18b}{\tt{passed}}$$ SUBTOTAL
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_logs}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_cleanup}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_cleanup\_skip}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_client\_init}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_get\_image\_exists}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_get\_image}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_get\_container\_run\_config}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_get\_container\_run\_config\_without\_mount}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_run\_container}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_get\_image\_for\_sidecar}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_sidecar\_container}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{TOTAL}}$$ $$\textcolor{#23d18b}{\tt{11}}$$ $$\textcolor{#23d18b}{\tt{11}}$$
SDK1 Tests - Full Report
filepath function $$\textcolor{#23d18b}{\tt{passed}}$$ SUBTOTAL
$$\textcolor{#23d18b}{\tt{tests/test\_platform.py}}$$ $$\textcolor{#23d18b}{\tt{TestPlatformHelperRetry.test\_success\_on\_first\_attempt}}$$ $$\textcolor{#23d18b}{\tt{2}}$$ $$\textcolor{#23d18b}{\tt{2}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_platform.py}}$$ $$\textcolor{#23d18b}{\tt{TestPlatformHelperRetry.test\_retry\_on\_connection\_error}}$$ $$\textcolor{#23d18b}{\tt{2}}$$ $$\textcolor{#23d18b}{\tt{2}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_platform.py}}$$ $$\textcolor{#23d18b}{\tt{TestPlatformHelperRetry.test\_non\_retryable\_http\_error}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_platform.py}}$$ $$\textcolor{#23d18b}{\tt{TestPlatformHelperRetry.test\_retryable\_http\_errors}}$$ $$\textcolor{#23d18b}{\tt{3}}$$ $$\textcolor{#23d18b}{\tt{3}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_platform.py}}$$ $$\textcolor{#23d18b}{\tt{TestPlatformHelperRetry.test\_post\_method\_retry}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_platform.py}}$$ $$\textcolor{#23d18b}{\tt{TestPlatformHelperRetry.test\_retry\_logging}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_prompt.py}}$$ $$\textcolor{#23d18b}{\tt{TestPromptToolRetry.test\_success\_on\_first\_attempt}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_prompt.py}}$$ $$\textcolor{#23d18b}{\tt{TestPromptToolRetry.test\_retry\_on\_errors}}$$ $$\textcolor{#23d18b}{\tt{2}}$$ $$\textcolor{#23d18b}{\tt{2}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_prompt.py}}$$ $$\textcolor{#23d18b}{\tt{TestPromptToolRetry.test\_wrapper\_methods\_retry}}$$ $$\textcolor{#23d18b}{\tt{4}}$$ $$\textcolor{#23d18b}{\tt{4}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestIsRetryableError.test\_connection\_error\_is\_retryable}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestIsRetryableError.test\_timeout\_is\_retryable}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestIsRetryableError.test\_http\_error\_retryable\_status\_codes}}$$ $$\textcolor{#23d18b}{\tt{3}}$$ $$\textcolor{#23d18b}{\tt{3}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestIsRetryableError.test\_http\_error\_non\_retryable\_status\_codes}}$$ $$\textcolor{#23d18b}{\tt{5}}$$ $$\textcolor{#23d18b}{\tt{5}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestIsRetryableError.test\_http\_error\_without\_response}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestIsRetryableError.test\_os\_error\_retryable\_errno}}$$ $$\textcolor{#23d18b}{\tt{5}}$$ $$\textcolor{#23d18b}{\tt{5}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestIsRetryableError.test\_os\_error\_non\_retryable\_errno}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestIsRetryableError.test\_other\_exception\_not\_retryable}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCalculateDelay.test\_exponential\_backoff\_without\_jitter}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCalculateDelay.test\_exponential\_backoff\_with\_jitter}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCalculateDelay.test\_max\_delay\_cap}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCalculateDelay.test\_max\_delay\_cap\_with\_jitter}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryWithExponentialBackoff.test\_successful\_call\_first\_attempt}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryWithExponentialBackoff.test\_retry\_after\_transient\_failure}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryWithExponentialBackoff.test\_max\_retries\_exceeded}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryWithExponentialBackoff.test\_max\_time\_exceeded}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryWithExponentialBackoff.test\_retry\_with\_custom\_predicate}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryWithExponentialBackoff.test\_no\_retry\_with\_predicate\_false}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryWithExponentialBackoff.test\_exception\_not\_in\_tuple\_not\_retried}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryWithExponentialBackoff.test\_delay\_would\_exceed\_max\_time}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_default\_configuration}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_environment\_variable\_configuration}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_invalid\_max\_retries}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_invalid\_max\_time}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_invalid\_base\_delay}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_invalid\_multiplier}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_jitter\_values}}$$ $$\textcolor{#23d18b}{\tt{2}}$$ $$\textcolor{#23d18b}{\tt{2}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_custom\_exceptions\_only}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_custom\_predicate\_only}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_both\_exceptions\_and\_predicate}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_exceptions\_match\_but\_predicate\_false}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestPreconfiguredDecorators.test\_retry\_platform\_service\_call\_exists}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestPreconfiguredDecorators.test\_retry\_prompt\_service\_call\_exists}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestPreconfiguredDecorators.test\_platform\_service\_decorator\_retries\_on\_connection\_error}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestPreconfiguredDecorators.test\_prompt\_service\_decorator\_retries\_on\_timeout}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryLogging.test\_warning\_logged\_on\_retry}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryLogging.test\_info\_logged\_on\_success\_after\_retry}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryLogging.test\_exception\_logged\_on\_giving\_up}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{TOTAL}}$$ $$\textcolor{#23d18b}{\tt{66}}$$ $$\textcolor{#23d18b}{\tt{66}}$$

@sonarqubecloud
Copy link

Copy link
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: 1

🤖 Fix all issues with AI agents
In `@frontend/src/components/pipelines-or-deployments/pipelines/Pipelines.jsx`:
- Around line 99-109: The effect only restores scrollToCardId; update the
useEffect handling location.state in Pipelines.jsx to also read page, pageSize,
and searchTerm from location.state and apply them before triggering the scroll:
call setPage(location.state.page), setPageSize(location.state.pageSize),
setSearchTerm(location.state.searchTerm) (or the component's equivalents) and
then re-fetch the list via the existing load/fetch function (e.g.,
fetchPipelines/loadPipelines) so the correct page/filter is loaded before
calling setScrollRestoreId; keep the same timeout/cleanup logic that clears
setScrollRestoreId after 500ms. Also ensure navigation code that opens a card
(the navigate(...) invoked from StatusPills or similar) includes page, pageSize
and searchTerm in state when pushing the route so the effect can restore full
list context.
🧹 Nitpick comments (3)
frontend/src/components/widgets/card-grid-view/CardGridView.css (2)

16-24: Consider specifying explicit transition properties.

Using transition: all can cause performance issues by transitioning unintended properties and may have unexpected side effects during future refactoring. Specifying exact properties is more predictable.

♻️ Suggested improvement
 .card-grid-item {
   height: 100%;
-  transition: all 0.2s ease;
+  transition: box-shadow 0.2s ease;
   border-radius: 8px;
 }
 .card-grid-item.card-list-mode {
   width: 100%;
-  transition: all 0.2s ease;
+  transition: box-shadow 0.2s ease;
 }

Also applies to: 286-289


645-648: Remove dead CSS class.

The comment indicates this class is no longer used. Consider removing it to keep the stylesheet clean.

🧹 Suggested cleanup
-/* Hide arrow - no longer used in vertical layout */
-.connector-flow-arrow {
-  display: none;
-}
frontend/src/components/pipelines-or-deployments/pipelines/Pipelines.jsx (1)

2-3: Avoid require inside an ES module component.
If this file is compiled as ESM (e.g., Vite), require is undefined at runtime. Prefer a static import (or a lazy import() if you intended to defer loading).

♻️ Suggested change
-import { useEffect, useMemo, useState } from "react";
+import { useEffect, useMemo, useState } from "react";
+import { fetchExecutionLogs } from "../log-modal/fetchExecutionLogs";
@@
-  const { fetchExecutionLogs } = require("../log-modal/fetchExecutionLogs");

Also applies to: 32-32, 53-53

Comment on lines +99 to +109
// Handle scroll restoration from navigation
useEffect(() => {
if (location.state?.scrollToCardId) {
setScrollRestoreId(location.state.scrollToCardId);
// Clear after a short delay to prevent re-triggering
const timer = setTimeout(() => {
setScrollRestoreId(null);
}, 500);
return () => clearTimeout(timer);
}
}, [location.state?.scrollToCardId]);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Scroll restoration should also restore pagination/search context.
Right now only scrollToCardId is restored; the list still reloads page 1 with an empty search term, so the originating card may not exist for paged/filtered lists. Pass {page, pageSize, searchTerm} in navigation state (from the StatusPills navigation) and re-fetch that page before scrolling.

🛠️ Suggested restoration logic
-  useEffect(() => {
-    if (location.state?.scrollToCardId) {
-      setScrollRestoreId(location.state.scrollToCardId);
-      // Clear after a short delay to prevent re-triggering
-      const timer = setTimeout(() => {
-        setScrollRestoreId(null);
-      }, 500);
-      return () => clearTimeout(timer);
-    }
-  }, [location.state?.scrollToCardId]);
+  useEffect(() => {
+    const state = location.state || {};
+    if (state.scrollToCardId) {
+      setScrollRestoreId(state.scrollToCardId);
+      const timer = setTimeout(() => setScrollRestoreId(null), 500);
+
+      const restoredSearch = state.searchTerm ?? "";
+      const restoredPageSize = state.pageSize ?? pagination.pageSize;
+      const restoredPage = state.page ?? 1;
+      setSearchTerm(restoredSearch);
+      getPipelineList(restoredPage, restoredPageSize, restoredSearch);
+
+      return () => clearTimeout(timer);
+    }
+  }, [location.state, pagination.pageSize]);
🧭 Also pass list context when navigating from a card
navigate(`/${sessionDetails.orgName}/logs/${executionType}/${run.execution_id}`, {
  state: {
    from: location.pathname,
    scrollToCardId: pipelineId,
    page: pagination.current,
    pageSize: pagination.pageSize,
    searchTerm,
  },
});
🤖 Prompt for AI Agents
In `@frontend/src/components/pipelines-or-deployments/pipelines/Pipelines.jsx`
around lines 99 - 109, The effect only restores scrollToCardId; update the
useEffect handling location.state in Pipelines.jsx to also read page, pageSize,
and searchTerm from location.state and apply them before triggering the scroll:
call setPage(location.state.page), setPageSize(location.state.pageSize),
setSearchTerm(location.state.searchTerm) (or the component's equivalents) and
then re-fetch the list via the existing load/fetch function (e.g.,
fetchPipelines/loadPipelines) so the correct page/filter is loaded before
calling setScrollRestoreId; keep the same timeout/cleanup logic that clears
setScrollRestoreId after 500ms. Also ensure navigation code that opens a card
(the navigate(...) invoked from StatusPills or similar) includes page, pageSize
and searchTerm in state when pushing the route so the effect can restore full
list context.

@chandrasekharan-zipstack chandrasekharan-zipstack changed the title UN-3154 [FEAT] Add CardGridView for Pipelines and API Deployments UN-3154 [FEAT] Card based layout to list Pipelines and API Deployments Jan 31, 2026
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