Skip to content

Attribute Export/Import - #358

Merged
navneetkumar-pim-webkul merged 21 commits into
unopim:masterfrom
adiAkeneo3763:test-export-import
Jul 2, 2026
Merged

navneetkumar-pim-webkul merged 21 commits into
unopim:masterfrom
adiAkeneo3763:test-export-import

Conversation

@adiAkeneo3763

@adiAkeneo3763 adiAkeneo3763 commented Apr 27, 2026 •

Copy link
Copy Markdown
Contributor

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.

@adiAkeneo3763 adiAkeneo3763 changed the title Test export import Attribute Export/Import Apr 27, 2026

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment on lines +77 to +100
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,
];

Copilot AI Apr 29, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Copilot uses AI. Check for mistakes.
Comment on lines 128 to 130
'wrong-quotes' => '使用弯引号代替直引号。',
'file-empty' => '文件为空或不包含标题行。请上传包含数据的有效文件。',
'file-empty' => 'The file is empty or does not contain a header row. Please upload a valid file with data.',
],

Copilot AI Apr 29, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +29 to +33
protected array $rules = [
'action' => 'required:in:append,delete',
'validation_strategy' => 'required:in:stop-on-errors,skip-errors',
'allowed_errors' => 'required|integer|min:0',
];

Copilot AI Apr 29, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +29 to +33
protected array $rules = [
'action' => 'required:in:append,delete',
'validation_strategy' => 'required:in:stop-on-errors,skip-errors',
'allowed_errors' => 'required|integer|min:0',
];

Copilot AI Apr 29, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
/**
* Is storage is empty
*/
public function isEmpty(): int

Copilot AI Apr 29, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Suggested change
public function isEmpty(): int
public function isEmpty(): bool

Copilot uses AI. Check for mistakes.
Comment on lines +29 to +33
protected array $rules = [
'action' => 'required:in:append,delete',
'validation_strategy' => 'required:in:stop-on-errors,skip-errors',
'allowed_errors' => 'required|integer|min:0',
];

Copilot AI Apr 29, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +29 to +33
protected array $rules = [
'action' => 'required:in:append,delete',
'validation_strategy' => 'required:in:stop-on-errors,skip-errors',
'allowed_errors' => 'required|integer|min:0',
];

Copilot AI Apr 29, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment thread packages/Webkul/DataTransfer/src/Helpers/Importers/AttributeGroup/Storage.php Outdated
/**
* Is storage empty
*/
public function isEmpty(): int

Copilot AI Apr 29, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Suggested change
public function isEmpty(): int
public function isEmpty(): bool

Copilot uses AI. Check for mistakes.

// 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']);

Copilot AI Apr 29, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Suggested change
$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.'
);

Copilot uses AI. Check for mistakes.
Comment thread packages/Webkul/DataTransfer/src/Helpers/Exporters/Attribute/Exporter.php Outdated
@navneetkumar-pim-webkul
navneetkumar-pim-webkul merged commit 68fd192 into unopim:master Jul 2, 2026
14 checks passed
kunal-kumar-dev pushed a commit to kunal-kumar-dev/unopim that referenced this pull request Jul 6, 2026
* 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]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants