Skip to content

feat(assets): CSV import of the asset inventory #861

Description

@alex-dembele

Problem

A team moving to OpenRisk already has its asset inventory in a spreadsheet. Today the only way in is the form, one asset at a time. #755 lets the risk import link risks to assets by name, which makes the gap more visible: you have to type every asset by hand before your risk file can reference it. The original stub (internal/service/migration_service.go, TODO: Implement actual asset import logic) was never reachable, and the #755 audit split asset import out.

Acceptance criteria

  1. POST /assets/import (multipart file, CSV) is mounted behind asset:create and capAssets, and is scoped to the session's tenant_id. Tenant and author never come from the request.
  2. Columns: name (required, ≤ 255), type, criticality (LOW/MEDIUM/HIGH/CRITICAL, case-insensitive, plus the French FAIBLE/MOYENNE/ÉLEVÉE/CRITIQUE; default MEDIUM), owner. Unknown columns are refused and named. Typed category/attributes are out of scope.
  3. All-or-nothing. Every row is validated first. One invalid row writes nothing, and the response lists every error with line, column, code, params and message, in the same contract as fix(risks): CSV import of risks and assets fails silently #755. A valid file is written in one transaction through CreateAssetUseCase.
  4. A name repeated in the file, or matching an existing asset of the tenant (case-insensitive), is refused with its line. Re-importing the same file never duplicates the inventory.
  5. The plan limit is checked against the whole file (402 limit_reached). Nothing is written.
  6. Same file handling as fix(risks): CSV import of risks and assets fails silently #755: UTF-8 BOM, ; delimiter, 1000 rows / 2 MB max.
  7. UI: an import page reachable from the inventory, with a downloadable template, FR/EN rendering of every error code, the three UI states, and no success toast unless created > 0.
  8. Tests: Success, Validation (bad row → zero rows), Unauthorized (no asset:create → 403), NotFound (no file), cross-tenant (rows land only in the caller's tenant; another tenant's names do not count as duplicates).

Definition of Done

Criteria 1–8 verified by tests and by a live pass on Postgres. PR open with Closes #<n>, stacked on #859.

Activity

  1. added this to the trust-v1 milestone on Oct 1, 2026
  2. alex-dembele commented on Oct 1, 2026

    @alex-dembele
    MemberAuthor

    backend-go + frontend-react — 2026-10-01

    Done

    • Backend: backend/internal/application/asset/import_assets.go (+ _test.go), CreateAssetInput.Source (create_asset.go), repository.RunAssetTx / ListAssetNames (gorm_asset_repository.go), backend/internal/handler/asset_import_handler.go (+ asset_import_test.go), route POST /assets/import in cmd/server/main.go (assets:create + capAssets + whole-file capacity).
    • Frontend: frontend/src/features/assets/ImportAssetsPage.tsx on the shared shared/csvImport/CsvImportPage from fix(risks): working, all-or-nothing CSV import of risks (#755) #859, template in importAssetsTemplate.ts, route assets/import in App.tsx, Import button in InventoryPage.tsx, strings under csvImport.assets / csvImport.errors in locales/{fr,en}.json.

    Verified

    • go test ./... -count=1 → 79 ok, 0 FAIL; re-run of asset/risk/handler/repository/cmd after the final merge → ok.
    • tsc -b ok; eslint ok; vitest run → 103 files, 953 tests passed (i18n ratchet, parity and orphan audit included).
    • Live on Postgres + Vite (French UI): bad file → 422 with line/column/code, nothing written; French Excel export → 3 assets source=IMPORT; re-import → 422 asset_exists × 3; a risk file naming them → scores 9.0 / 1.5 / 3.0.

    Criteria — 1 ✅ · 2 ✅ · 3 ✅ · 4 ✅ · 5 ✅ · 6 ✅ · 7 ✅ · 8 ✅

    Next — owner: merge in order #855 → #859 → #862.
    Blocked on — merge of #855 and #859

  3. added a commit that references this issue on Oct 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions