feat(devices): implement bulk device import with column mapping, live preview, and inline banners (vertex app)#3544
Conversation
Replace ReusableToast notifications with context-aware banners for bulk device imports. The bulk import hook (useBulkImportDevices) no longer surfaces toasts or handle error/success UI; it delegates notification responsibility to callers and only invalidates relevant queries. ImportDeviceModal now uses useBanner (showBanner/hideBanner) to display inline success, warning, and error alerts, handles partial/failure cases, offers downloadable failed_devices.csv, and hides banners when the modal closes. Updated changelog to reflect the new banner-based feedback behavior.
|
Warning Review limit reached
More reviews will be available in 40 minutes and 36 seconds. Learn how PR review limits work. Your organization has run out of usage credits. Purchase more in the billing tab. ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (5)
📝 WalkthroughWalkthroughThis PR implements a complete device bulk import feature. Users can now upload CSV or JSON files, auto-map columns to device fields, preview the parsed data, and submit bulk imports with optional network overrides and batch-wide settings. The implementation includes new type contracts, a file upload component, API client methods, a React Query hook, modal state management, and standardized required-field styling. ChangesDevice Bulk Upload
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Warning Review ran into problems🔥 ProblemsErrors were encountered while retrieving linked issues. Errors (1)
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. Comment |
|
New azure vertex changes available for preview here |
There was a problem hiding this comment.
Actionable comments posted: 6
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/vertex/components/features/devices/import-device-modal.tsx`:
- Around line 230-252: The preview/import allows the same source column to be
mapped to multiple expected fields; update the validation before building
transformedDevices to detect duplicate mappings in fieldMapping (e.g., collect
Object.values(fieldMapping) excluding falsy, find duplicates) and if any
duplicates exist call setErrors with a clear message listing the duplicated
source headers (and optionally which expected fields collide), then return
early; place this check after computing missingRequired and before using
EXPECTED_FIELDS/parsedData to build transformedDevices so duplicates cannot
produce silent incorrect imports.
- Around line 142-160: The autoMapFields function currently only
lowercases/trims headers, so variants like "Device Name", "device-name", or
"serial number" are missed; update autoMapFields to normalize headers and keys
before matching by converting to lowercase, trimming, replacing non-alphanumeric
characters (spaces, hyphens, punctuation) with underscores, collapsing multiple
underscores, and trimming leading/trailing underscores, then build a
normalizedHeaders map from normalized -> original header; normalize each
EXPECTED_FIELDS key (and apply the special aliases for long_name and
serial_number using normalized forms) and use normalizedHeaders to find matches
and populate initialMapping[field.key] with the original header name; modify the
lowerHeaders variable and matching branches in autoMapFields (and the
long_name/serial_number alias logic) to use the normalized forms so common
export variants auto-map correctly.
- Around line 181-191: When parsing CSVs in import-device-modal.tsx (the
Papa.parse callback used when fileName.endsWith('.csv')), check that
results.data contains at least one non-empty row before enabling mappingMode:
call setFileHeaders(headers) and setParsedData(results.data) as you do, but only
call autoMapFields(headers) and setMappingMode(true) if results.data.length > 0
(or after filtering out empty rows). If there are zero device rows, set a
file-level validation error state (do not call autoMapFields or setMappingMode)
so the UI stays in file-selection/error state and the Preview Import path does
not fall through to validateForm().
In `@src/vertex/components/shared/fileupload/ReusableFileUpload.tsx`:
- Around line 53-62: The hidden file input in ReusableFileUpload.tsx (the <input
id={inputId} type="file" ... />) must be cleared after each selection so
choosing the same file again still triggers onChange; update the onChange
handler used on that input (the inline arrow handling e => { if (onChange)
onChange(e.target.files?.[0] || null); }) to also reset the native input value
(e.currentTarget.value = '' or e.target.value = '') after invoking onChange (or
nulling it before if you prefer) so the browser will fire change events for the
same file on subsequent attempts.
In `@src/vertex/components/shared/select/ReusableSelectInput.tsx`:
- Line 189: The required-field asterisk in ReusableSelectInput is using a
hardcoded "text-red-500"; update the span rendering inside the
ReusableSelectInput component to use the shared token/class used elsewhere
(e.g., "text-destructive") so the required indicator matches other select/input
components; replace the class on the {required && <span ...>*</span>} element
accordingly to the agreed shared token.
In `@src/vertex/core/apis/devices.ts`:
- Around line 463-470: The headers object passed to jwtApiClient.post for the
`/devices/soft/bulk` request is hardcoding 'Content-Type':
'multipart/form-data', which prevents Axios from adding the required boundary;
update the call in devices.ts (the jwtApiClient.post invocation) to remove the
'Content-Type' header while keeping 'X-Auth-Type': 'JWT' so Axios can infer and
set the correct multipart boundary from the FormData payload.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 9bb87309-82a8-4937-95d1-97a92381fb2c
⛔ Files ignored due to path filters (1)
src/vertex/package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (9)
src/vertex/app/changelog.mdsrc/vertex/app/types/devices.tssrc/vertex/components/features/devices/import-device-modal.tsxsrc/vertex/components/shared/fileupload/ReusableFileUpload.tsxsrc/vertex/components/shared/inputfield/ReusableInputField.tsxsrc/vertex/components/shared/select/ReusableSelectInput.tsxsrc/vertex/core/apis/devices.tssrc/vertex/core/hooks/useDevices.tssrc/vertex/package.json
Enhance autoMapFields in ImportDeviceModal to better match CSV headers by normalizing values (toLowerCase, trim, and remove non-alphanumeric chars) and using a normalizedHeaders array. Add alias handling for long_name and serial_number (e.g. locationname, devicename, serial, id) and simplify lookup logic for more robust automatic field mapping.
Add a guard in ImportDeviceModal to detect when the parsed CSV contains no data rows. Extract parsed rows into a variable, and if rows.length === 0 clear headers/parsedData/fieldMapping, disable mapping mode, set a general error ('The uploaded CSV does not contain any devices.'), and return early. Also use the rows variable when setting parsed data.
Add a check in ImportDeviceModal to detect and prevent duplicate mappings of file columns to device fields. The code collects mapped headers from fieldMapping, finds duplicates, and sets a general error "Each file column can only be mapped once." and aborts the import flow if duplicates are found. This prevents incorrect/ambiguous imports caused by mapping the same CSV column multiple times.
Refactor the file input change handler: compute nextFile using nullish coalescing and invoke the callback with optional chaining (onChange?.(nextFile)). Also clear e.currentTarget.value after handling so the same file can be selected again without issues. This is a small behavioral change to allow re-uploading the same file and a code cleanup.
Replace hardcoded Tailwind color class `text-red-500` with the semantic `text-destructive` utility for required field asterisks. Changes applied to ReusableInputField and ReusableSelectInput to ensure consistent styling and better theme/design token support.
Removed the explicit 'Content-Type: multipart/form-data' header from the JWT API client's POST request for soft bulk device import. This allows the HTTP client to set the multipart boundary automatically and prevents incorrect Content-Type issues; the 'X-Auth-Type: JWT' header is retained.
|
New azure vertex changes available for preview here |
Overview
This PR introduces a comprehensive bulk device import feature allowing administrators and operators to upload multiple devices at once via CSV or JSON files. It extends the
ImportDeviceModalto provide an interactive, multi-step flow that guides users through file parsing, intelligent column mapping, data preview, and batch processing with downloadable error logs for failed rows.To align with premium UI/UX guidelines, all notifications related to bulk import are routed through the context-aware
useBannersystem, displacing raw floating toasts in favor of scoped inline banner alerts directly inside the modal container.Key Features
ReusableFileUpload) accepting.csv(parsed client-side usingpapaparse) and.jsonfiles.long_name,serial_number,latitude,longitude,api_code,description, anddevice_number) using common aliases. Provides dropdowns for manual mapping overrides.useBannercontext to display success, warning, or error alerts inline inside the modal header. Provides a summary breakdown table and supports downloading a customfailed_devices.csvcontaining only failed rows and error reasons.useBulkImportDevicesmutation hook to interface with the/devices/soft/bulkendpoint, handle HTTP 207 responses, and invalidate cache keys for local and admin device grids.Changes Implemented
1. Types & Data Structures
BulkImportDeviceResultandBulkImportDeviceResponseto structure the backend batch response.2. API Services & Hooks
importBulkDevicesCSVandimportBulkDevicesJSONmethods underdevicesapi client to submit imports through the/devices/soft/bulkAPI endpoint.useBulkImportDeviceshook that handles mutation execution and invalidates React Query state keys. Removed allReusableToastcalls.3. UI & Components
*) to usetext-red-500for consistent design alignment.4. Dependencies
@types/papaparseversion to^5.5.2for type safety in CSV parsing.5. Documentation
Testing & Verification
failed_devices.csv.Screenshots (optional)
Summary by CodeRabbit