Commit 6f50c4f4 authored by Piotr Borek's avatar Piotr Borek Committed by Damian Kryger
Browse files

#783 F2F attribute mapping - selects

parent b19bb0fd
......@@ -11,6 +11,7 @@ namespace PcmtRulesBundle\Connector\Job\ConstraintCollectionProvider;
use Akeneo\Tool\Component\Batch\Job\JobParameters\ConstraintCollectionProviderInterface;
use PcmtRulesBundle\Constraints\AttributeExistsInBothFamiliesConstraint;
use PcmtRulesBundle\Constraints\CorrectAttributeMappingConstraint;
use PcmtRulesBundle\Constraints\DifferentFamilyConstraint;
use PcmtSharedBundle\Connector\Job\JobParameters\SupportedJobsTrait;
use Symfony\Component\Validator\Constraints\Collection;
......@@ -51,6 +52,7 @@ class FamilyToFamilyConstraintCollectionProvider implements ConstraintCollection
],
'attributeMapping' => [
new Type('array'),
new CorrectAttributeMappingConstraint(),
],
],
]
......
<?php
declare(strict_types=1);
/**
* Copyright (c) 2020, VillageReach
* Licensed under the Non-Profit Open Software License version 3.0.
* SPDX-License-Identifier: NPOSL-3.0
*/
namespace PcmtRulesBundle\Constraints;
use Symfony\Component\Validator\Constraint;
class CorrectAttributeMappingConstraint extends Constraint
{
/** {@inheritdoc} */
public function getTargets()
{
return self::PROPERTY_CONSTRAINT;
}
/** {@inheritdoc} */
public function validatedBy()
{
return 'pcmt_correct_attribute_mapping_constraint_validator';
}
/** @var string */
public $message = 'The attribute mapping is incorrect';
}
......@@ -46,7 +46,7 @@ extensions:
tabCode: pim-job-instance-properties
pcmt-rules-family-to-family-job-edit-mapping-tab:
module: pim/job/common/edit/properties
module: pcmt/rules/job/rules/edit/mapping-tab
parent: pcmt-rules-family-to-family-job-edit-tabs
aclResourceId: pcmt_permission_rules_edit
targetZone: container
......
......@@ -11,6 +11,7 @@ config:
pcmt/rules/job/field/key-attribute-select: pcmtrules/js/job/field/key-attribute-select
pcmt/rules/job/field/destination-attribute-select: pcmtrules/js/job/field/destination-attribute-select
pcmt/rules/job/rules/edit/attribute-mapping: pcmtrules/js/job/rules/edit/attribute-mapping
pcmt/rules/job/rules/edit/mapping-tab: pcmtrules/js/job/rules/edit/mapping-tab
pcmt/rules/job/rules/edit/save: pcmtrules/js/job/rules/edit/save
......@@ -18,6 +19,7 @@ config:
pcmt/rules/template/group: pcmtrules/templates/group.html
pcmt/rules/template/job/field/select: pcmtrules/templates/job/field/select.html
pcmt/rules/template/job/rules/edit/mapping-tab: pcmtrules/templates/job/rules/edit/mapping-tab.html
pcmt/rules/template/job/rules/edit/attribute-mapping: pcmtrules/templates/job/rules/edit/attribute-mapping.html
pcmt/rules/template/job/rules/edit/attribute-mapping-row: pcmtrules/templates/job/rules/edit/attribute-mapping-row.html
......
......@@ -119,6 +119,13 @@ services:
tags:
- { name: validator.constraint_validator, alias: pcmt_attribute_different_family_constraint_validator }
pcmt.constraints.correct_attribute_mapping_constraint_validator:
class: PcmtRulesBundle\Validators\CorrectAttributeMappingConstraintValidator
arguments:
- '@pim_catalog.repository.family'
tags:
- { name: validator.constraint_validator, alias: pcmt_correct_attribute_mapping_constraint_validator }
pcmt.constraints.family_has_variants_constraint_validator:
class: PcmtRulesBundle\Validators\FamilyHasVariantsConstraintValidator
arguments:
......
......@@ -15,24 +15,34 @@ define(
'underscore',
'pim/job/common/edit/field/field',
'pcmt/rules/template/job/rules/edit/attribute-mapping',
'pcmt/rules/template/job/rules/edit/attribute-mapping-row'
'pcmt/rules/template/job/rules/edit/attribute-mapping-row',
'pim/fetcher-registry',
'pim/i18n',
'pim/user-context',
],
function (
$,
_,
BaseForm,
template,
rowTemplate
rowTemplate,
FetcherRegistry,
i18n,
UserContext
) {
return BaseForm.extend({
events: {
"click .add-row": "addRow",
"click .remove-row": "removeRow",
"change input": "updateModelAfterChange",
"change select": "updateModelAfterChange",
},
template: _.template(template),
rowTemplate: _.template(rowTemplate),
sourceFamily: '',
destinationFamily: '',
sourceAttributeList: [],
destinationAttributeList: [],
mappingValues: [],
initialize: function(config) {
BaseForm.prototype.initialize.apply(this, arguments);
......@@ -43,6 +53,7 @@ define(
configure: function() {
BaseForm.prototype.configure.apply(this, arguments);
this.listenTo(this.getRoot(), 'pim_enrich:form:entity:post_fetch', this.postFetch);
this.listenTo(this.getRoot(), this.postUpdateEventName, this.postUpdate);
},
postFetch: function(data) {
let value = this.getValue();
......@@ -53,6 +64,39 @@ define(
this.addRow();
}
},
postUpdate: function(data) {
if (data.configuration.sourceFamily !== this.sourceFamily) {
this.sourceFamily = data.configuration.sourceFamily;
this.fetchSourceAttributes();
}
if (data.configuration.destinationFamily !== this.destinationFamily) {
this.destinationFamily = data.configuration.destinationFamily;
this.fetchDestinationAttributes();
}
},
fetchSourceAttributes: function() {
let options = {
family: this.sourceFamily
};
FetcherRegistry.getFetcher('attributes-for-rules-job').fetchForOptions(options).then(
function (options) {
this.sourceAttributeList = options;
this.render();
}.bind(this)
);
},
fetchDestinationAttributes: function() {
let options = {
family: this.destinationFamily
};
FetcherRegistry.getFetcher('attributes-for-rules-job').fetchForOptions(options).then(
function (options) {
this.destinationAttributeList = options;
this.render();
}.bind(this)
);
},
addRow: function() {
let index = this.getMaxIndex() + 1;
this.mappingValues.push({
......@@ -75,8 +119,14 @@ define(
this.$el.html(this.template({
rowCount : this.rowCount,
rowTemplate: this.rowTemplate,
data: this.mappingValues,
mappingValues: this.mappingValues,
sourceAttributeList: this.sourceAttributeList,
destinationAttributeList: this.destinationAttributeList,
i18n: i18n,
locale: UserContext.get('catalogLocale'),
error: this.getParent().getValidationErrorsForField(this.getFieldCode()),
}));
this.$('.select2').select2();
this.delegateEvents();
return this;
},
......
/*
* Copyright (c) 2021, VillageReach
* Licensed under the Non-Profit Open Software License version 3.0.
* SPDX-License-Identifier: NPOSL-3.0
*/
'use strict';
define(
[
'pim/job/common/edit/properties',
'pcmt/rules/template/job/rules/edit/mapping-tab',
],
function (
BaseForm,
template
) {
return BaseForm.extend({
template: _.template(template),
});
}
);
\ No newline at end of file
......@@ -7,8 +7,19 @@
<tr class="AknGrid-bodyRow">
<td class="AknGrid-bodyCell">
<input class="AknTextField" title="Source attribute" value="<%- element.sourceValue %>"
type="text" name="sourceValue" data-index="<%- element.index %>">
<select class="select2" title="Source attribute" name="sourceValue" data-index="<%- element.index %>">
<option value="" <% if (_.isEmpty(element.sourceValue)) { %> selected<% } %> ></option>
<% _.each(sourceAttributeList, function (option) { %>
<option value="<%- option.code %>"
<% if (option.code === element.sourceValue) { %> selected<% } %>
>
<%- i18n.getLabel(option.labels, locale, option.code) %>
</option>
<% }); %>
</select>
</td>
<td class="AknGrid-bodyCell">
......@@ -16,8 +27,18 @@
</td>
<td class="AknGrid-bodyCell">
<input class="AknTextField" title="Destination attribute" value="<%- element.destinationValue %>"
type="text" name="destinationValue" data-index="<%- element.index %>">
<select class="select2" title="Destination attribute" name="destinationValue" data-index="<%- element.index %>">
<option value="" <% if (_.isEmpty(element.destinationValue)) { %> selected<% } %> ></option>
<% _.each(destinationAttributeList, function (option) { %>
<option value="<%- option.code %>"
<% if (option.code === element.destinationValue) { %> selected<% } %>
>
<%- i18n.getLabel(option.labels, locale, option.code) %>
</option>
<% }); %>
</select>
</td>
......
......@@ -7,14 +7,29 @@
<table class="AknGrid">
<tbody class="AknGrid-body">
<% _.each(data, function(element) { %>
<%= rowTemplate({element: element}) %>
<% _.each(mappingValues, function(element) { %>
<%= rowTemplate({
element: element,
sourceAttributeList: sourceAttributeList,
destinationAttributeList: destinationAttributeList,
i18n: i18n,
locale: locale
}) %>
<% }); %>
</tbody>
</table>
<% if (error) { %>
<div class="AknFieldContainer-footer AknFieldContainer-validationErrors validation-errors">
<span class="AknFieldContainer-validationError">
<span class="error-message"><%- error %></span>
</span>
</div>
<% } %>
<button class="add-row AknButton AknButton--apply">Add row</button>
<div class="AknFormContainer--withPadding">
<button class="add-row AknButton AknButton--apply">Add row</button>
</div>
<!--
~ Copyright (c) 2021, VillageReach
~ Licensed under the Non-Profit Open Software License version 3.0.
~ SPDX-License-Identifier: NPOSL-3.0
-->
<div class="tabsections">
<div class="tabsection">
<div class="tabsection-content AknFormContainer--withPadding">
<div class="" data-drop-zone="properties"></div>
</div>
</div>
</div>
<?php
declare(strict_types=1);
/*
* Copyright (c) 2020, VillageReach
* Licensed under the Non-Profit Open Software License version 3.0.
* SPDX-License-Identifier: NPOSL-3.0
*/
namespace PcmtRulesBundle\Tests\TestDataBuilder;
use PcmtRulesBundle\Constraints\CorrectAttributeMappingConstraint;
class CorrectAttributeMappingConstraintBuilder
{
/** @var CorrectAttributeMappingConstraint */
private $constraint;
public function __construct()
{
$this->constraint = new CorrectAttributeMappingConstraint();
}
public function build(): CorrectAttributeMappingConstraint
{
return $this->constraint;
}
}
<?php
declare(strict_types=1);
/*
* Copyright (c) 2020, VillageReach
* Licensed under the Non-Profit Open Software License version 3.0.
* SPDX-License-Identifier: NPOSL-3.0
*/
namespace PcmtRulesBundle\Tests\Validators;
use Akeneo\Pim\Structure\Component\Model\FamilyInterface;
use Akeneo\Pim\Structure\Component\Repository\FamilyRepositoryInterface;
use PcmtRulesBundle\Tests\TestDataBuilder\AttributeBuilder;
use PcmtRulesBundle\Tests\TestDataBuilder\CorrectAttributeMappingConstraintBuilder;
use PcmtRulesBundle\Tests\TestDataBuilder\FamilyBuilder;
use PcmtRulesBundle\Validators\CorrectAttributeMappingConstraintValidator;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Form\Exception\UnexpectedTypeException;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
use Symfony\Component\Validator\Violation\ConstraintViolationBuilderInterface;
class CorrectAttributeMappingConstraintValidatorTest extends TestCase
{
/** @var ExecutionContextInterface|MockObject */
private $contextMock;
/** @var ConstraintViolationBuilderInterface|MockObject */
private $violationBuilderMock;
/** @var FamilyRepositoryInterface|MockObject */
private $familyRepositoryMock;
protected function setUp(): void
{
$this->contextMock = $this->createMock(ExecutionContextInterface::class);
$this->violationBuilderMock = $this->createMock(ConstraintViolationBuilderInterface::class);
$this->violationBuilderMock->method('atPath')->willReturn($this->violationBuilderMock);
$this->familyRepositoryMock = $this->createMock(FamilyRepositoryInterface::class);
}
public function dataValidate(): array
{
$sourceFamily = (new FamilyBuilder())->withCode('SOURCE')->build();
$sourceFamily->addAttribute(
(new AttributeBuilder())->withCode('attr_source')->withType('type1')->build()
);
$destinationFamily = (new FamilyBuilder())->withCode('DESTINATION')->build();
$destinationFamily->addAttribute(
(new AttributeBuilder())->withCode('attr_dest')->withType('type1')->build()
);
$destinationFamily->addAttribute(
(new AttributeBuilder())->withCode('attr_dest2')->withType('type2')->build()
);
return [
[
[],
$sourceFamily,
$destinationFamily,
true,
],
[
[
[
'sourceValue' => '',
'destinationValue' => '',
],
],
$sourceFamily,
$destinationFamily,
true,
],
[
[
[
'sourceValue' => 'attr_source',
'destinationValue' => 'attr_dest',
],
],
$sourceFamily,
$destinationFamily,
true,
],
[
[
[
'sourceValue' => 'attr_source',
'destinationValue' => 'attr_dest2',
],
],
$sourceFamily,
$destinationFamily,
false,
],
[
[
[
'sourceValue' => 'attr_source_not_existing',
'destinationValue' => 'attr_dest1',
],
],
$sourceFamily,
$destinationFamily,
false,
],
[
[
[
'sourceValue' => 'attr_source',
'destinationValue' => 'attr_dest_not_existing',
],
],
$sourceFamily,
$destinationFamily,
false,
],
];
}
/** @dataProvider dataValidate */
public function testValidate(array $attributeMapping, FamilyInterface $sourceFamily, FamilyInterface $destinationFamily, bool $result): void
{
$constraint = (new CorrectAttributeMappingConstraintBuilder())->build();
$this->familyRepositoryMock
->method('findOneBy')
->withConsecutive(
[['code' => $sourceFamily->getCode()]],
[['code' => $destinationFamily->getCode()]]
)
->willReturnOnConsecutiveCalls(
$sourceFamily,
$destinationFamily
);
$root = [
'sourceFamily' => $sourceFamily->getCode(),
'destinationFamily' => $destinationFamily->getCode(),
'attributeMapping' => $attributeMapping,
];
$this->contextMock
->method('getRoot')
->willReturn($root);
if ($result) {
$this->contextMock->expects($this->never())->method('buildViolation');
} else {
$this->contextMock->expects($this->once())->method('buildViolation')->willReturn(
$this->violationBuilderMock
);
}
$validator = $this->getValidatorInstance();
$validator->validate('x', $constraint);
}
/** @dataProvider dataValidate */
public function testValidateNoFamily(array $attributeMapping, FamilyInterface $sourceFamily, FamilyInterface $destinationFamily, bool $result): void
{
$constraint = (new CorrectAttributeMappingConstraintBuilder())->build();
$this->familyRepositoryMock
->method('findOneBy')
->withConsecutive(
[['code' => $sourceFamily->getCode()]],
[['code' => $destinationFamily->getCode()]]
)
->willReturnOnConsecutiveCalls(
$sourceFamily,
null
);
$root = [
'sourceFamily' => $sourceFamily->getCode(),
'destinationFamily' => $destinationFamily->getCode(),
'attributeMapping' => $attributeMapping,
];
$this->contextMock
->method('getRoot')
->willReturn($root);
$this->contextMock->expects($this->never())->method('buildViolation');
$validator = $this->getValidatorInstance();
$validator->validate('x', $constraint);
}
public function testValidateThrowsException(): void
{
$this->expectException(UnexpectedTypeException::class);
$constraint = $this->createMock(Constraint::class);
$value = [
'sourceFamily' => 'SOURCE',
'destinationFamily' => 'DESTINATION',
'keyAttribute' => '',
'user_to_notify' => '',
];
$validator = $this->getValidatorInstance();
$validator->validate($value, $constraint);
}
private function getValidatorInstance(): CorrectAttributeMappingConstraintValidator
{
$validator = new CorrectAttributeMappingConstraintValidator(
$this->familyRepositoryMock
);
$validator->initialize($this->contextMock);
return $validator;
}
}
<?php
/*
* Copyright (c) 2020, VillageReach
* Licensed under the Non-Profit Open Software License version 3.0.
* SPDX-License-Identifier: NPOSL-3.0
*/