Repository navigation
Attribute Export/Import - #358
Conversation
There was a problem hiding this comment.
Pull request overview
Adds end-to-end import/export support in the DataTransfer module for attribute-related entities (Attributes, Attribute Groups/Families, Attribute Options) plus Category Fields, including job validators, localization strings, and Playwright/Pest coverage.
Changes:
- Introduces new importers/exporters and storage helpers for Category Fields and attribute entities.
- Adds job-instance validators and config registration for the new import/export types.
- Adds E2E Playwright specs + CSV assets and a unit test for the Attribute Family importer.
Reviewed changes
Copilot reviewed 87 out of 87 changed files in this pull request and generated 13 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/e2e-pw/tests/04-datatransfer/Test-Import/category-fields-import/import.spec.js | Playwright coverage for creating/deleting a Category Fields import job |
| tests/e2e-pw/tests/04-datatransfer/Test-Import/attribute-options-import/import.spec.js | Playwright coverage for creating/deleting an Attribute Options import job |
| tests/e2e-pw/tests/04-datatransfer/Test-Import/attribute-import/import.spec.js | Playwright coverage for creating/deleting an Attributes import job |
| tests/e2e-pw/tests/04-datatransfer/Test-Import/attribute-group-import/import.spec.js | Playwright coverage for creating/deleting an Attribute Groups import job |
| tests/e2e-pw/tests/04-datatransfer/Test-Import/attribute-family-import/import.spec.js | Playwright coverage for creating/deleting an Attribute Families import job |
| tests/e2e-pw/tests/04-datatransfer/Test Export/category-field-export/export.spec.js | Playwright coverage for Category Fields export workflow (CSV→XLS→delete) |
| tests/e2e-pw/tests/04-datatransfer/Test Export/attribute-options-export/export.spec.js | Playwright coverage for Attribute Options export workflow |
| tests/e2e-pw/tests/04-datatransfer/Test Export/attribute-group-export/export.spec.js | Playwright coverage for Attribute Groups export workflow |
| tests/e2e-pw/tests/04-datatransfer/Test Export/attribute-family-export/export.spec.js | Playwright coverage for Attribute Families export workflow |
| tests/e2e-pw/tests/04-datatransfer/Test Export/attribute-export/export.spec.js | Playwright coverage for Attributes export workflow |
| tests/e2e-pw/assets/category-fields.csv | Fixture CSV for Category Fields import |
| tests/e2e-pw/assets/attributes.csv | Fixture CSV for Attributes import |
| tests/e2e-pw/assets/attribute-options.csv | Fixture CSV for Attribute Options import |
| tests/e2e-pw/assets/attribute-groups.csv | Fixture CSV for Attribute Groups import |
| tests/e2e-pw/assets/attribute-families.csv | Fixture CSV for Attribute Families import |
| packages/Webkul/DataTransfer/tests/Unit/Helpers/Importers/AttributeFamily/ImporterTest.php | Unit coverage for Attribute Family importer behavior |
| packages/Webkul/DataTransfer/src/Validators/JobInstances/Import/CategoryFieldJobValidator.php | Adds import-job validation rules for Category Fields |
| packages/Webkul/DataTransfer/src/Validators/JobInstances/Import/AttributeOptionJobValidator.php | Adds import-job validation rules for Attribute Options |
| packages/Webkul/DataTransfer/src/Validators/JobInstances/Import/AttributeJobValidator.php | Adds import-job validation rules for Attributes |
| packages/Webkul/DataTransfer/src/Validators/JobInstances/Import/AttributeGroupJobValidator.php | Adds import-job validation rules for Attribute Groups |
| packages/Webkul/DataTransfer/src/Validators/JobInstances/Import/AttributeFamilyJobValidator.php | Adds import-job validation rules for Attribute Families |
| packages/Webkul/DataTransfer/src/Validators/JobInstances/Export/CategoryFieldJobValidator.php | Adds export-job validation rules for Category Fields |
| packages/Webkul/DataTransfer/src/Validators/JobInstances/Export/AttributeOptionJobValidator.php | Adds export-job validation rules for Attribute Options |
| packages/Webkul/DataTransfer/src/Validators/JobInstances/Export/AttributeJobValidator.php | Adds export-job validation rules for Attributes |
| packages/Webkul/DataTransfer/src/Validators/JobInstances/Export/AttributeGroupJobValidator.php | Adds export-job validation rules for Attribute Groups |
| packages/Webkul/DataTransfer/src/Validators/JobInstances/Export/AttributeFamilyJobValidator.php | Adds export-job validation rules for Attribute Families |
| packages/Webkul/DataTransfer/src/Resources/lang/zh_TW/app.php | Adds new importer/exporter labels + validation strings (zh_TW) |
| packages/Webkul/DataTransfer/src/Resources/lang/zh_CN/app.php | Adds new importer/exporter labels + validation strings (zh_CN) |
| packages/Webkul/DataTransfer/src/Resources/lang/vi_VN/app.php | Adds new importer/exporter labels + validation strings (vi_VN) |
| packages/Webkul/DataTransfer/src/Resources/lang/uk_UA/app.php | Adds new importer/exporter labels + validation strings (uk_UA) |
| packages/Webkul/DataTransfer/src/Resources/lang/tr_TR/app.php | Adds new importer/exporter labels + validation strings (tr_TR) |
| packages/Webkul/DataTransfer/src/Resources/lang/tl_PH/app.php | Adds new importer/exporter labels + validation strings (tl_PH) |
| packages/Webkul/DataTransfer/src/Resources/lang/sv_SE/app.php | Adds new importer/exporter labels + validation strings (sv_SE) |
| packages/Webkul/DataTransfer/src/Resources/lang/ru_RU/app.php | Adds new importer/exporter labels + validation strings (ru_RU) |
| packages/Webkul/DataTransfer/src/Resources/lang/ro_RO/app.php | Adds new importer/exporter labels + validation strings (ro_RO) |
| packages/Webkul/DataTransfer/src/Resources/lang/pt_PT/app.php | Adds new importer/exporter labels + validation strings (pt_PT) |
| packages/Webkul/DataTransfer/src/Resources/lang/pt_BR/app.php | Adds new importer/exporter labels + validation strings (pt_BR) |
| packages/Webkul/DataTransfer/src/Resources/lang/pl_PL/app.php | Adds new importer/exporter labels + validation strings (pl_PL) |
| packages/Webkul/DataTransfer/src/Resources/lang/no_NO/app.php | Adds new importer/exporter labels + validation strings (no_NO) |
| packages/Webkul/DataTransfer/src/Resources/lang/nl_NL/app.php | Adds new importer/exporter labels + validation strings (nl_NL) |
| packages/Webkul/DataTransfer/src/Resources/lang/mn_MN/app.php | Adds new importer/exporter labels + validation strings (mn_MN) |
| packages/Webkul/DataTransfer/src/Resources/lang/ko_KR/app.php | Adds new importer/exporter labels + validation strings (ko_KR) |
| packages/Webkul/DataTransfer/src/Resources/lang/ja_JP/app.php | Adds new importer/exporter labels + validation strings (ja_JP) |
| packages/Webkul/DataTransfer/src/Resources/lang/it_IT/app.php | Adds new importer/exporter labels + validation strings (it_IT) |
| packages/Webkul/DataTransfer/src/Resources/lang/id_ID/app.php | Adds new importer/exporter labels + validation strings (id_ID) |
| packages/Webkul/DataTransfer/src/Resources/lang/hr_HR/app.php | Adds new importer/exporter labels + validation strings (hr_HR) |
| packages/Webkul/DataTransfer/src/Resources/lang/hi_IN/app.php | Adds new importer/exporter labels + validation strings (hi_IN) |
| packages/Webkul/DataTransfer/src/Resources/lang/fr_FR/app.php | Adds new importer/exporter labels + validation strings (fr_FR) |
| packages/Webkul/DataTransfer/src/Resources/lang/fi_FI/app.php | Adds new importer/exporter labels + validation strings (fi_FI) |
| packages/Webkul/DataTransfer/src/Resources/lang/es_VE/app.php | Adds new importer/exporter labels + validation strings (es_VE) |
| packages/Webkul/DataTransfer/src/Resources/lang/es_ES/app.php | Adds new importer/exporter labels + validation strings (es_ES) |
| packages/Webkul/DataTransfer/src/Resources/lang/en_US/app.php | Adds new importer/exporter labels + validation strings (en_US) |
| packages/Webkul/DataTransfer/src/Resources/lang/en_NZ/app.php | Adds new importer/exporter labels + validation strings (en_NZ) |
| packages/Webkul/DataTransfer/src/Resources/lang/en_GB/app.php | Adds new importer/exporter labels + validation strings (en_GB) |
| packages/Webkul/DataTransfer/src/Resources/lang/en_AU/app.php | Adds new importer/exporter labels + validation strings (en_AU) |
| packages/Webkul/DataTransfer/src/Resources/lang/de_DE/app.php | Adds new importer/exporter labels + validation strings (de_DE) |
| packages/Webkul/DataTransfer/src/Resources/lang/da_DK/app.php | Adds new importer/exporter labels + validation strings (da_DK) |
| packages/Webkul/DataTransfer/src/Resources/lang/ca_ES/app.php | Adds new importer/exporter labels + validation strings (ca_ES) |
| packages/Webkul/DataTransfer/src/Resources/lang/ar_AE/app.php | Adds new importer/exporter labels + validation strings (ar_AE) |
| packages/Webkul/DataTransfer/src/Helpers/Sources/Excel.php | Adjusts Excel cell value normalization for import parsing |
| packages/Webkul/DataTransfer/src/Helpers/Importers/CategoryField/Storage.php | Adds storage cache for Category Field code→id mapping |
| packages/Webkul/DataTransfer/src/Helpers/Importers/CategoryField/Importer.php | Implements Category Fields importer (append/delete) |
| packages/Webkul/DataTransfer/src/Helpers/Importers/AttributeOption/Storage.php | Adds storage cache for Attribute Option code→id mapping |
| packages/Webkul/DataTransfer/src/Helpers/Importers/AttributeOption/Importer.php | Implements Attribute Options importer (append/delete) |
| packages/Webkul/DataTransfer/src/Helpers/Importers/AttributeGroup/Storage.php | Adds storage cache for Attribute Group code→id mapping |
| packages/Webkul/DataTransfer/src/Helpers/Importers/AttributeGroup/Importer.php | Implements Attribute Groups importer (append/delete) |
| packages/Webkul/DataTransfer/src/Helpers/Importers/AttributeFamily/Storage.php | Adds storage cache for Attribute Family code→id mapping |
| packages/Webkul/DataTransfer/src/Helpers/Importers/Attribute/Storage.php | Adds storage cache for Attribute code→id mapping |
| packages/Webkul/DataTransfer/src/Helpers/Importers/Attribute/Importer.php | Implements Attributes importer (append/delete) |
| packages/Webkul/DataTransfer/src/Helpers/Exporters/CategoryField/Exporter.php | Implements Category Fields exporter |
| packages/Webkul/DataTransfer/src/Helpers/Exporters/AttributeOption/Exporter.php | Implements Attribute Options exporter |
| packages/Webkul/DataTransfer/src/Helpers/Exporters/AttributeGroup/Exporter.php | Implements Attribute Groups exporter |
| packages/Webkul/DataTransfer/src/Helpers/Exporters/AttributeFamily/Exporter.php | Implements Attribute Families exporter |
| packages/Webkul/DataTransfer/src/Helpers/Exporters/Attribute/Exporter.php | Implements Attributes exporter |
| packages/Webkul/DataTransfer/src/Helpers/Export.php | Fixes missing with_media filter default handling |
| packages/Webkul/DataTransfer/src/Config/importers.php | Registers new importer types in module config |
| packages/Webkul/DataTransfer/src/Config/exporters.php | Registers new exporter types + file_format filters |
| foreach ($batch->data as $rowData) { | ||
| $productCounts = $this->productCountsByAttribute($rowData['code']); | ||
| $translations = collect($rowData['translations'] ?? [])->keyBy('locale'); | ||
|
|
||
| foreach ($locales as $locale) { | ||
| $data = [ | ||
| 'code' => $rowData['code'] ?? null, | ||
| 'type' => $rowData['type'] ?? null, | ||
| 'locale' => $locale, | ||
| 'name' => $translations[$locale]['name'] ?? null, | ||
| 'position' => $rowData['position'] ?? null, | ||
| 'enable_wysiwyg' => $rowData['enable_wysiwyg'] ?? null, | ||
| 'swatch_type' => $rowData['swatch_type'] ?? null, | ||
| 'is_required' => $rowData['is_required'] ?? null, | ||
| 'is_unique' => $rowData['is_unique'] ?? null, | ||
| 'validation' => $rowData['validation'] ?? null, | ||
| 'regex_pattern' => $rowData['regex_pattern'] ?? null, | ||
| 'value_per_locale' => $rowData['value_per_locale'] ?? null, | ||
| 'value_per_channel' => $rowData['value_per_channel'] ?? null, | ||
| 'is_filterable' => $rowData['is_filterable'] ?? null, | ||
| 'ai_translate' => $rowData['ai_translate'] ?? null, | ||
| 'productCounts' => $productCounts, | ||
| ]; | ||
|
|
There was a problem hiding this comment.
productCountsByAttribute() runs a separate COUNT query per attribute being exported. For installs with many attributes/products this becomes an N+1 query pattern and can make exports very slow. Consider computing these counts in bulk (e.g., one query per batch using JSON path predicates / generated columns, or making productCounts optional behind a filter).
| 'wrong-quotes' => '使用弯引号代替直引号。', | ||
| 'file-empty' => '文件为空或不包含标题行。请上传包含数据的有效文件。', | ||
| 'file-empty' => 'The file is empty or does not contain a header row. Please upload a valid file with data.', | ||
| ], |
There was a problem hiding this comment.
This locale file’s file-empty validation message was changed to an English string, which breaks localization consistency for zh_CN. It should remain translated (or be updated to a proper zh_CN translation) rather than defaulting to English.
| protected array $rules = [ | ||
| 'action' => 'required:in:append,delete', | ||
| 'validation_strategy' => 'required:in:stop-on-errors,skip-errors', | ||
| 'allowed_errors' => 'required|integer|min:0', | ||
| ]; |
There was a problem hiding this comment.
In Laravel validation syntax, required:in:... does not apply the in constraint (it is parsed as parameters to required, which are ignored). This means invalid action / validation_strategy values will pass validation. Use required|in:append,delete and required|in:stop-on-errors,skip-errors instead.
| protected array $rules = [ | ||
| 'action' => 'required:in:append,delete', | ||
| 'validation_strategy' => 'required:in:stop-on-errors,skip-errors', | ||
| 'allowed_errors' => 'required|integer|min:0', | ||
| ]; |
There was a problem hiding this comment.
In Laravel validation syntax, required:in:... does not apply the in constraint (it is parsed as parameters to required, which are ignored). This means invalid action / validation_strategy values will pass validation. Use required|in:append,delete and required|in:stop-on-errors,skip-errors instead.
| /** | ||
| * Is storage is empty | ||
| */ | ||
| public function isEmpty(): int |
There was a problem hiding this comment.
isEmpty() declares a return type of int but returns the boolean result of empty($this->items). In PHP this will throw a TypeError when the method is called. Change the return type to bool (or cast the return value to (int) if an integer is truly intended).
| public function isEmpty(): int | |
| public function isEmpty(): bool |
| protected array $rules = [ | ||
| 'action' => 'required:in:append,delete', | ||
| 'validation_strategy' => 'required:in:stop-on-errors,skip-errors', | ||
| 'allowed_errors' => 'required|integer|min:0', | ||
| ]; |
There was a problem hiding this comment.
In Laravel validation syntax, required:in:... does not apply the in constraint (it is parsed as parameters to required, which are ignored). This means invalid action / validation_strategy values will pass validation. Use required|in:append,delete and required|in:stop-on-errors,skip-errors instead.
| protected array $rules = [ | ||
| 'action' => 'required:in:append,delete', | ||
| 'validation_strategy' => 'required:in:stop-on-errors,skip-errors', | ||
| 'allowed_errors' => 'required|integer|min:0', | ||
| ]; |
There was a problem hiding this comment.
In Laravel validation syntax, required:in:... does not apply the in constraint (it is parsed as parameters to required, which are ignored). This means invalid action / validation_strategy values will pass validation. Use required|in:append,delete and required|in:stop-on-errors,skip-errors instead.
| /** | ||
| * Is storage empty | ||
| */ | ||
| public function isEmpty(): int |
There was a problem hiding this comment.
isEmpty() declares a return type of int but returns the boolean result of empty($this->items). In PHP this will throw a TypeError when the method is called. Change the return type to bool (or cast the return value to (int) if an integer is truly intended).
| public function isEmpty(): int | |
| public function isEmpty(): bool |
|
|
||
| // Protect the built-in 'name' field from deletion | ||
| if ($rowData['code'] === CategoryField::NON_DELETABLE_FIELD_CODE) { | ||
| $this->skipRow($rowNumber, self::ERROR_DUPLICATE_CODE, $rowData['code']); |
There was a problem hiding this comment.
When attempting to delete the non-deletable built-in field, this marks the row with ERROR_DUPLICATE_CODE ("code already in use"), which is a misleading error for this case. Introduce a dedicated error code/message for “system/non-deletable field cannot be deleted” and use that here (similar to how attributes handle sku).
| $this->skipRow($rowNumber, self::ERROR_DUPLICATE_CODE, $rowData['code']); | |
| $this->skipRow( | |
| $rowNumber, | |
| 'ERROR_NON_DELETABLE_FIELD_CANNOT_BE_DELETED', | |
| $rowData['code'], | |
| 'System/non-deletable field cannot be deleted.' | |
| ); |
37bd973 to
8739ec1
Compare
* Data-Transfer Added : Attribute, Attribute Groups, Attribute Family * translation changes pint fix * Add attribute import/export functionality with tests * All Export-import completed * import playwright test * pint fix * translation fix * Translated all the attribute related keywords * Pint fix * fixed matchBatch Issue * Fix MakeImporter Issue * Fixing pest test cases issues * Fixed all attribute import/export issues * Add category field import/export with translations and configuration * fixed folder naming convention * Fixed the issue #902 * fix: restore missing localized translations from upstream/master * fix: auto-translate remaining english strings using existing unopim dictionary * fixed comments and pint --------- Co-authored-by: Aditya Anand <[email protected]>
Description
This PR adds import and export functionality for Attributes, Attribute Groups, Attribute Families, and Attribute Options in the DataTransfer module.
It includes:
Exporter and Importer implementations for all entities
Job validation for import/export processes
Unit and Playwright test coverage
This provides a complete setup for handling attribute-related data via import/export.