-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Description
Summary
The CSV product importer (Importer.importProducts()) currently creates a separate option group per product, even when multiple products have identical option groups with identical options. With the new shared/channel-aware option group model (#4469), the importer should consolidate identical option groups into a single shared group.
Current behaviour
For each product row in the CSV, the importer:
- Creates a new
ProductOptionGroupwith code{product-name}-{group-name}(e.g.ultraboost-running-shoe-size) - Creates new
ProductOptionentities for each value - Associates the group with the product
This means 6 shoes with identical "size" options (Size 40, 42, 44, 46) produce 6 separate option groups, each with 4 duplicate options — 24 option entities instead of 4.
Proposed behaviour
Auto-sharing heuristic (default)
Before the main product import loop, do a first pass over all parsed products:
- Build a signature map keyed by
groupName + sorted option values (across all translations) - Groups that appear in 2+ products with identical signatures are created once and shared
- Groups unique to one product are created as before
Option group code generation
| Scenario | Code |
|---|---|
| Shared group (identical options across products) | {group-name} e.g. size |
| Unique group (only one product) | {product-name}-{group-name} e.g. curvy-monitor-monitor-size (unchanged) |
| Code collision on shared group name | Append numeric suffix: size, size-2 |
Display names and option names are unaffected — they come directly from the CSV values.
Explicit code via CSV format extension
Extend the optionGroups column to support an optional :code suffix:
optionGroups
"size:shoe-size|color:shoe-color"
Where size is the display name and shoe-size is the explicit code. When two products specify the same explicit code, they share the group deterministically. If no :code part is given, fall back to the auto-sharing heuristic.
This gives importers explicit control over sharing and disambiguation (e.g. shoe "size" vs RAM "size" could use size:shoe-size and size:memory-capacity).
Opt-out flag
Add a shareOptionGroups?: boolean option (default true) to Importer.importProducts():
async importProducts(
ctx: RequestContext,
rows: ParsedProductWithVariants[],
onProgress: OnProgressFn,
options?: { shareOptionGroups?: boolean },
): Promise<string[]>This preserves backward compatibility for anyone relying on the current per-product behaviour. The parseAndImport() method and GraphQL mutation would use the default (true).
Backward compatibility
Importer,FastImporterService, andImportParserare all publicly exported from@vendure/coreimportProducts()is called directly by load-testing benchmarks and potentially by user plugins/scripts- The
importProductsFromCsv()CLI helper is used by@vendure/testingand@vendure/create - Option group codes will change for shared groups (e.g.
ultraboost-running-shoe-size→size), affecting anyone querying by code after re-populate - The
ParsedOptionGroupinterface would get a new optionalcode?: stringfield (non-breaking)
Since this targets the minor branch, the behaviour change with opt-out is acceptable.
Demo data consolidation
The packages/create/assets/products.csv should be updated to demonstrate sharing. Current duplicates:
| Group name | Options | Products (currently 6 separate groups) |
|---|---|---|
| size | Size 40, Size 42, Size 44, Size 46 | Ultraboost, Freerun, Hi-Top Basketball, Pureboost, RunX, Allstar |
The RAM product also uses "size" but with different values (4GB, 8GB, 16GB) — the heuristic correctly keeps this separate.
Implementation plan
- Add optional
code?: stringtoParsedOptionGroupinterface - Update
ImportParser.parseProductFromRecord()to parsename:codesyntax - Add first-pass signature analysis in
importProducts()before the product loop - Pre-create shared option groups and build a shared
optionsMap - In the product loop, look up shared groups before creating new ones
- Update
products.csvdemo data - Update E2E tests in
packages/core/e2e/import.e2e-spec.ts