Files
cve-dashboard/.kiro/specs/queue-collapsible-sections/design.md
Jordan Ramos a61d254ff9 Sync .kiro/ from master — v2.2.0 release batch
New specs: archer-template-library, ccp-metrics-view-restructure,
compliance-list-stale-after-sidebar-edit, compliance-metric-estimated-resolution-date,
compliance-remediation-display-fix, flexible-jira-ticket-creation,
forecast-burndown-chart, granite-loader-export, ivanti-queue-clear-completed-fix,
multi-item-jira-ticket, queue-collapsible-sections, vendor-issue-type-dropdown

New steering: archer-template-gen.md

Updated: migration-registration-check hook, remediation-plan-history spec,
gitlab-workflow, tech, versioning steering files
2026-06-04 11:27:31 -06:00

11 KiB
Raw Blame History

Queue Collapsible Sections — Design

Overview

This feature adds collapsible, grouped sections to the IvantiTodoQueuePage. Items are organized into a hybrid layout: an "Inventory" section (CARD, GRANITE, DECOM workflow types) at the top, followed by vendor-grouped sections for FP and Archer items. Each section has a clickable header that toggles visibility of its items. The implementation is entirely frontend — no backend or database changes are needed.

Architecture

The feature modifies a single file: frontend/src/components/pages/IvantiTodoQueuePage.js. It introduces:

  1. A grouping computation (via useMemo) that transforms the flat visibleItems array into a structured array of sections.
  2. A collapse state (via useState) that tracks which sections are expanded or collapsed.
  3. Section header components rendered inline that display group labels, item counts, and chevron icons.
  4. Extended styles added to the existing STYLES constant.

No new files, components, or dependencies are introduced. The existing selection logic, floating action bar, consolidation modal, and ticket link badges continue to operate unchanged.

Components and Data Flow

queueItems (from API)
  │
  ▼
visibleItems = useMemo(filter pending)     ← existing
  │
  ▼
groupedSections = useMemo(groupItems)      ← NEW: hybrid grouping logic
  │
  ▼
collapsedSections (useState)               ← NEW: collapse state
  │
  ▼
Render: map over groupedSections
  ├── Section Header (clickable, toggles collapse)
  └── Section Body (conditionally rendered based on collapse state)
        └── Queue Item Rows (existing rendering logic)

Data Model

Grouped Section Structure

The useMemo grouping hook produces an array of section objects:

// Output of the grouping computation
const groupedSections = [
  {
    key: 'inventory',           // Unique section identifier
    label: 'Inventory',         // Display label
    type: 'inventory',          // Section type for styling
    items: [/* queue items */], // Items belonging to this section
  },
  {
    key: 'vendor:Microsoft',    // Unique section identifier
    label: 'Microsoft',         // Display label (vendor name)
    type: 'vendor',             // Section type for styling
    items: [/* queue items */], // Items belonging to this section
  },
  // ... more vendor sections, alphabetically sorted
];

Collapse State

// Key: section.key string, Value: boolean (true = collapsed)
const [collapsedSections, setCollapsedSections] = useState({});
// Initial state: empty object → all sections default to expanded

Interfaces

Grouping Function

/**
 * Groups visible queue items into the hybrid section layout.
 * 
 * @param {Array} visibleItems - Queue items with status 'pending'
 * @returns {Array<{key: string, label: string, type: string, items: Array}>}
 * 
 * Rules:
 * - Items with workflow_type CARD, GRANITE, or DECOM → Inventory section
 * - Items with workflow_type FP or Archer → grouped by vendor field
 * - Items with null/undefined/empty vendor → placed in "Unknown" vendor section
 * - Inventory section appears first (if non-empty)
 * - Vendor sections sorted alphabetically by label
 * - Sections with zero items are omitted from output
 */
const groupedSections = useMemo(() => {
  const INVENTORY_TYPES = new Set(['CARD', 'GRANITE', 'DECOM']);
  
  const inventoryItems = [];
  const vendorMap = new Map(); // vendor name → items array
  
  for (const item of visibleItems) {
    if (INVENTORY_TYPES.has(item.workflow_type)) {
      inventoryItems.push(item);
    } else {
      const vendor = item.vendor?.trim() || 'Unknown';
      if (!vendorMap.has(vendor)) vendorMap.set(vendor, []);
      vendorMap.get(vendor).push(item);
    }
  }
  
  const sections = [];
  
  // Inventory section first (only if non-empty)
  if (inventoryItems.length > 0) {
    sections.push({
      key: 'inventory',
      label: 'Inventory',
      type: 'inventory',
      items: inventoryItems,
    });
  }
  
  // Vendor sections sorted alphabetically
  const sortedVendors = [...vendorMap.entries()]
    .sort((a, b) => a[0].localeCompare(b[0]));
  
  for (const [vendor, items] of sortedVendors) {
    sections.push({
      key: `vendor:${vendor}`,
      label: vendor,
      type: 'vendor',
      items,
    });
  }
  
  return sections;
}, [visibleItems]);

Toggle Collapse Handler

const toggleSection = useCallback((sectionKey) => {
  setCollapsedSections((prev) => ({
    ...prev,
    [sectionKey]: !prev[sectionKey],
  }));
}, []);

Section Header Rendering

// Within the render, for each section:
{groupedSections.map((section) => {
  const isCollapsed = !!collapsedSections[section.key];
  
  return (
    <div key={section.key}>
      {/* Section Header */}
      <div
        onClick={() => toggleSection(section.key)}
        style={section.type === 'inventory'
          ? STYLES.sectionHeaderInventory
          : STYLES.sectionHeaderVendor}
        role="button"
        tabIndex={0}
        onKeyDown={(e) => {
          if (e.key === 'Enter' || e.key === ' ') {
            e.preventDefault();
            toggleSection(section.key);
          }
        }}
        aria-expanded={!isCollapsed}
        aria-label={`${section.label} section, ${section.items.length} items`}
      >
        {isCollapsed
          ? <ChevronRight style={{ width: '14px', height: '14px' }} />
          : <ChevronDown style={{ width: '14px', height: '14px' }} />
        }
        <span>{section.label}</span>
        <span style={STYLES.sectionCount}>({section.items.length})</span>
      </div>
      
      {/* Section Body — only rendered when expanded */}
      {!isCollapsed && section.items.map((item) => (
        // ... existing queue item row rendering
      ))}
    </div>
  );
})}

Styling

New entries added to the existing STYLES constant:

sectionHeaderInventory: {
  display: 'flex',
  alignItems: 'center',
  gap: '0.5rem',
  padding: '0.5rem 0.75rem',
  marginTop: '0.5rem',
  borderBottom: '1px solid rgba(16, 185, 129, 0.2)',
  cursor: 'pointer',
  userSelect: 'none',
  fontFamily: 'monospace',
  fontSize: '0.7rem',
  fontWeight: 700,
  color: '#10B981',
  textTransform: 'uppercase',
  letterSpacing: '0.1em',
},
sectionHeaderVendor: {
  display: 'flex',
  alignItems: 'center',
  gap: '0.5rem',
  padding: '0.5rem 0.75rem',
  marginTop: '0.5rem',
  borderBottom: '1px solid rgba(148, 163, 184, 0.15)',
  cursor: 'pointer',
  userSelect: 'none',
  fontFamily: 'monospace',
  fontSize: '0.7rem',
  fontWeight: 700,
  color: '#94A3B8',
  textTransform: 'uppercase',
  letterSpacing: '0.1em',
},
sectionCount: {
  fontFamily: 'monospace',
  fontSize: '0.65rem',
  fontWeight: 600,
  color: '#64748B',
  marginLeft: '0.25rem',
},

Icon Imports

Add ChevronDown and ChevronRight to the existing lucide-react import:

import { ListTodo, RefreshCw, CheckSquare, Square, Loader, AlertCircle, X, Plus, CheckCircle, ChevronDown, ChevronRight } from 'lucide-react';

Select All Interaction with Collapsed Sections

The existing toggleSelectAll and allVisibleSelected logic operates on the visibleItems array, which contains ALL pending items regardless of collapse state. This means:

  • Select All selects/deselects all pending items across all sections, whether those sections are collapsed or expanded.
  • Selection count always reflects selectedIds.size, which includes items in collapsed sections.
  • Floating action bar operates on selectedQueueItems (derived from selectedIds), which is independent of collapse state.

No changes to the selection logic are needed. The collapse state is purely visual — it controls rendering, not data.

Error Handling

  • If visibleItems is empty, groupedSections will be an empty array, and the existing empty state renders instead.
  • If an item has an unexpected workflow_type (not CARD, GRANITE, DECOM, FP, or Archer), it falls into the vendor grouping path and is grouped by its vendor field. This is a safe fallback.
  • The collapsedSections state uses an object with string keys. Non-existent keys return undefined, which is falsy, so all sections default to expanded without explicit initialization.

Performance Considerations

  • The useMemo grouping computation runs only when visibleItems changes (on fetch or status update). For typical queue sizes (10100 items), this is negligible.
  • Collapse state changes trigger re-renders only of the affected section's body. React's reconciliation handles this efficiently since each section has a stable key.
  • No additional API calls or data fetching is introduced.

Correctness Properties

A property is a characteristic or behavior that should hold true across all valid executions of a system — essentially, a formal statement about what the system should do. Properties serve as the bridge between human-readable specifications and machine-verifiable correctness guarantees.

Property 1: Grouping Correctness

For any array of visible queue items, every item with workflow_type CARD, GRANITE, or DECOM SHALL appear in the Inventory section, every item with workflow_type FP or Archer SHALL appear in the vendor section matching its vendor field (or "Unknown" if vendor is null/empty), and no item SHALL appear in more than one section.

Validates: Requirements 1.1, 1.2, 1.5

Property 2: Section Ordering

For any non-empty grouping result, the Inventory section (if present) SHALL be the first element, and all subsequent vendor sections SHALL be sorted in case-insensitive alphabetical order by their label.

Validates: Requirements 1.3, 1.4

Property 3: Empty Section Omission

For any array of visible queue items, the grouped output SHALL contain no sections with zero items. Specifically, if no CARD/GRANITE/DECOM items exist, no Inventory section appears; if no items exist for a given vendor, no section for that vendor appears.

Validates: Requirements 1.6, 1.7

Property 4: Section Header Count Accuracy

For any section in the grouped output, the item count displayed in the section header SHALL equal the actual number of items in that section's items array.

Validates: Requirements 3.1, 3.2

Property 5: Selection Independence from Collapse State

For any combination of selected items and collapse state, the set of selected item IDs SHALL remain unchanged when sections are collapsed or expanded. The selection count SHALL always equal the total number of selected items across all sections, and Select All SHALL toggle selection for all visible items regardless of which sections are collapsed.

Validates: Requirements 4.2, 4.4, 4.5