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
8.7 KiB
Implementation Plan: Granite Loader Sheet Export
Overview
Add a Granite Team_Device Loader xlsx generator accessible from the Ivanti Queue (for CARD/GRANITE items) and as a standalone tool. The system enriches device data from the CARD API, allows bulk defaults with per-row overrides in an editable preview table, and generates a properly formatted xlsx for upload to SNIP XperLoad. Implementation proceeds from column configuration utility → backend enrichment endpoint → frontend modal → queue integration → standalone access.
Tasks
-
1. Create column configuration utility module
-
1.1 Create
frontend/src/utils/graniteLoaderConfig.js- Define
LOADER_COLUMNSarray with all 41 columns: id, label (exact Granite header name), group, requiredFor array - Define
COLUMN_GROUPSordered array for UI grouping - Define
OPERATION_TYPESarray: Change, Add, Delete, Move - Export
getRequiredColumns(operationType)helper that returns column IDs required for the given operation - Export
getColumnsByGroup(group)helper that returns columns in a group - Requirements: 2.1–2.6, 3.1–3.5
- Define
-
1.2 Create
frontend/src/utils/graniteLoaderExport.js- Export
generateLoaderXlsx(config)function that accepts{ operationType, columnIds, rows }and returns a Blob - Use the
xlsxlibrary (already in frontend dependencies) to create a workbook with a single "Load_Sheet" worksheet - First row contains exact canonical column headers from LOADER_COLUMNS (matched by columnIds, in canonical order)
- Subsequent rows contain device data; empty values become empty cells (not "null")
- DELETE column auto-filled with "X" when operationType is "Delete"
- EQUIPMENT CLASS defaults to "S" unless overridden
- Export
generateFilename(operationType, teamName)helper returningLoader_{op}_{team}_{YYYY-MM-DD}.xlsx - Requirements: 6.1–6.8
- Export
-
-
2. Backend CARD enrichment endpoint
-
2.1 Add
POST /api/card/enrich-batchendpoint inbackend/routes/cardApi.js- Accept
{ ips: string[] }in request body - Validate: ips is a non-empty array, max 200 items, each item is a non-empty string
- Require Admin or Standard_User group
- Require CARD API to be configured (return 503 if not)
- For each IP, attempt owner lookup with known suffixes (CTEC, NATL, CHTR, etc.)
- Extract from asset record: equip_inst_id (from ncim_discovery, netops_granite_allips, or ise_granite_equipment), hostname, site_name, mgmt_ip_asn, responsible_team, equipment_class, equip_template, equip_status
- Return
{ results: [...], enriched_count, not_found_count, total } - Handle per-IP errors gracefully (mark as not-found, continue with others)
- Handle CARD API auth failures (return 502 with error message)
- Requirements: 5.1–5.8
- Accept
-
2.2 Add
GET /api/card/configuredendpoint (or extend existing/api/card/status)- Return
{ configured: boolean }so the frontend knows whether to show the "Enrich from CARD" option - This may already exist as
GET /api/card/status— verify and reuse if so - Requirements: 5.8
- Return
-
-
3. Frontend LoaderModal component
-
3.1 Create
frontend/src/components/LoaderModal.js- Accept props:
isOpen,onClose,initialDevices(array of{ ip_address, hostname }or null) - Render modal overlay with header "Generate Granite Loader Sheet"
- Include Operation Type selector (dropdown, defaults to "Change")
- Include Column Selection panel with collapsible groups and checkboxes
- Required columns for selected operation are pre-checked and disabled
- Include "Enrich from CARD" button (hidden if CARD not configured, checked via
/api/card/statuson mount) - Include Bulk Defaults section: one input per selected column, setting value applies to all non-overridden rows
- Include editable Preview Table: rows = devices, columns = selected columns
- Cells are inline-editable on click; overridden cells show amber dot indicator
- Right-click or clear button on overridden cell reverts to bulk default
- Sticky column headers and bulk default row when scrolling
- Validation: highlight missing required fields in red, show warning count
- Download button: merges bulk defaults + overrides into final row data, calls
generateLoaderXlsx, triggers browser download - Cancel button closes modal
- Requirements: 1.2, 2.1–2.6, 3.1–3.5, 4.1–4.7, 6.1–6.8, 8.1–8.5
- Accept props:
-
3.2 Implement CARD enrichment flow in LoaderModal
- On "Enrich from CARD" click, collect all device IPs, POST to
/api/card/enrich-batch - Show progress indicator during request
- On response, populate enriched fields into device rows (equip_inst_id, hostname, site_name, mgmt_ip_asn, etc.)
- Do NOT overwrite values the user has already manually entered
- Show warning indicators on rows where IP was not found
- Show error toast if CARD API auth fails
- Requirements: 5.1–5.7
- On "Enrich from CARD" click, collect all device IPs, POST to
-
3.3 Implement standalone mode (paste IPs)
- When
initialDevicesis null, show a textarea for pasting IPs (one per line or comma-separated) - Parse input into device rows on "Load" button click
- Allow manually adding/removing rows via + and trash icons
- Requirements: 7.1–7.4
- When
-
-
4. Integrate with Ivanti Queue page
-
4.1 Add "Generate Loader Sheet" button to IvantiTodoQueuePage floating action bar
- Show button when one or more selected items have workflow_type CARD or GRANITE
- Button label: "Generate Loader Sheet" with a FileSpreadsheet icon
- On click, open LoaderModal with
initialDevicespopulated from selected items' ip_address and hostname - Requirements: 1.1–1.5
-
4.2 Add standalone access point
- Add "Granite Loader" link in the navigation drawer under Tools section (or similar)
- Clicking opens LoaderModal in standalone mode (initialDevices = null)
- Alternatively, add a "Generate Loader Sheet" button on the CARD status section if one exists
- Requirements: 7.1
-
-
5. Checkpoint — Verify build and basic functionality
- Build frontend:
cd frontend && npm run build - Verify no lint errors or build failures
- Ensure all existing tests still pass
- Ask the user if questions arise
- Build frontend:
-
* 6. Property-based tests for enrichment endpoint
-
* 6.1 Write property test: Enrichment result count
- Property 1: Result count equals input count — For any array of N IPs (1 ≤ N ≤ 200), the response
resultsarray has exactly N elements - File:
backend/__tests__/granite-loader-enrichment.property.test.js - Validates: Requirements 5.1, 5.2
- Property 1: Result count equals input count — For any array of N IPs (1 ≤ N ≤ 200), the response
-
* 6.2 Write property test: Found results have equip_inst_id
- Property 2: Found results have required fields — For any result where
found === true,equip_inst_idis a non-empty string - Validates: Requirements 5.2
- Property 2: Found results have required fields — For any result where
-
* 6.3 Write property test: Not-found results have null fields
- Property 3: Not-found results have null equip_inst_id — For any result where
found === false,equip_inst_idis null - Validates: Requirements 5.4
- Property 3: Not-found results have null equip_inst_id — For any result where
-
-
* 7. Unit tests for xlsx generation
- * 7.1 Write unit tests for
generateLoaderXlsx- Test correct column headers in canonical order
- Test DELETE column auto-fill for Delete operation
- Test EQUIPMENT CLASS defaults to "S"
- Test empty values produce empty cells (not "null" string)
- Test bulk default + override merge produces correct row values
- File:
backend/__tests__/granite-loader-xlsx-generation.test.js - Validates: Requirements 6.1–6.8
- * 7.1 Write unit tests for
-
8. Final checkpoint
- Build frontend and verify no regressions
- Ensure all tests pass
- Ask the user if questions arise
Notes
- Tasks marked with
*are optional property-based and unit tests that can be skipped for faster MVP - The
xlsxlibrary is already a frontend dependency — no new packages needed for xlsx generation - The CARD API enrichment reuses the existing
cardApi.jshelper (token management, TLS skip, etc.) - No database schema changes are required — this feature reads from queue items and CARD API only
- The LoaderModal follows the same pattern as ConsolidationModal (modal overlay, form state, action buttons)
- The preview table follows the same inline-edit pattern as the Reporting page (click to edit, amber dot for overrides)
- Maximum 200 devices per batch aligns with CARD API pagination limits and practical XperLoad batch sizes
Task Dependency Graph
{
"waves": [
{ "id": 0, "tasks": ["1.1", "1.2"] },
{ "id": 1, "tasks": ["2.1", "2.2"] },
{ "id": 2, "tasks": ["3.1"] },
{ "id": 3, "tasks": ["3.2", "3.3"] },
{ "id": 4, "tasks": ["4.1", "4.2"] },
{ "id": 5, "tasks": ["5"] },
{ "id": 6, "tasks": ["6.1", "6.2", "6.3", "7.1"] }
]
}