Skip to content

Commit 3f911f7

Browse files
authored
SalesDataExporter functionality (#141)
* SFAPP-558: [MCEE] Obtain Magento orders data
1 parent 41809dd commit 3f911f7

File tree

62 files changed

+4365
-18
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

62 files changed

+4365
-18
lines changed

CatalogDataExporter/Test/Integration/AbstractProductTestHelper.php

+9
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,15 @@ protected function validateMediaGallery(ProductInterface $product, array $extrac
394394
'videoDescription' => $extensionAttributes->getVideoContent()->getVideoDescription(),
395395
'videoMetadata' => $extensionAttributes->getVideoContent()->getVideoMetadata(),
396396
];
397+
} else {
398+
$expectedResult['video_attributes'] = [
399+
'mediaType' => null,
400+
'videoUrl' => null,
401+
'videoProvider' => null,
402+
'videoTitle' => null,
403+
'videoDescription' => null,
404+
'videoMetadata' => null,
405+
];
397406
}
398407

399408
$this->assertEquals($expectedResult, \array_shift($extractedProduct['feedData']['media_gallery']));

DataExporter/Export/Extractor.php

+21-4
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
namespace Magento\DataExporter\Export;
99

10+
use Magento\DataExporter\Exception\UnableRetrieveData;
1011
use Magento\DataExporter\Export\Request\Info;
1112
use Magento\DataExporter\Export\Request\Node;
1213
use Magento\DataExporter\Model\Logging\CommerceDataExportLoggerInterface;
@@ -107,20 +108,36 @@ private function extractDataForNode(Info $info, Node $node, array $value)
107108
);
108109
$this->profilerStop($isRoot, $providerClass, $value);
109110
foreach ($node->getChildren() as $child) {
110-
$output = array_replace_recursive(
111-
$output,
112-
$this->extractDataForNode($info, $child, $data)
113-
);
111+
try {
112+
$output = array_replace_recursive(
113+
$output,
114+
$this->extractDataForNode($info, $child, $data)
115+
);
116+
} catch (\Throwable $e) {
117+
throw new UnableRetrieveData(
118+
"child provider: " . $child->getField()['provider'],
119+
0,
120+
$e
121+
);
122+
}
114123
}
115124
}
116125

117126
$output[$key] = $data;
118127
} else {
119128
foreach ($node->getChildren() as $child) {
129+
try {
120130
$output = array_replace_recursive(
121131
$output,
122132
$this->extractDataForNode($info, $child, $value)
123133
);
134+
} catch (\Throwable $e) {
135+
throw new UnableRetrieveData(
136+
"child provider: " . $child->getField()['provider'],
137+
0,
138+
$e
139+
);
140+
}
124141
}
125142
}
126143
return $output;

DataExporter/Export/LookupBuilder.php

+7-3
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@
88
namespace Magento\DataExporter\Export;
99

1010
/**
11-
* Build lookup value based in "using" field
11+
* Build lookup value based on "using" field
12+
* Fields specified in "using" field in et_schema used to build relation between data returned in provider and
13+
* parent entity to be able to assign current entity to parent entity.
14+
* If usinf field not set field with type "ID" will be used by default
1215
*/
1316
class LookupBuilder
1417
{
@@ -26,9 +29,10 @@ public static function build(array $field, array $item): string
2629
if (!isset($key['field'], $item[$key['field']])) {
2730
$fieldName = $key['field'] ?? '';
2831
throw new \InvalidArgumentException(\sprintf(
29-
'DataExporter error: No value in Data Provider for "%s" specified in "using" expression: "%s"',
32+
'Exporter error: no value in Data Provider for "%s" in "using" field: "%s", item: %s',
3033
$fieldName,
31-
\var_export($field, true)
34+
\var_export($field, true),
35+
\var_export($item, true)
3236
));
3337
}
3438
// cast to string: we don't care about type here

DataExporter/Export/Transformer.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ private function convertComplexRow(array $row, array $type, array $snapshot) : a
177177
//todo: add Filter cond
178178
$result[$field['name']] = $this->convertComplexData($field, $snapshot, $lookupReference);
179179
}
180-
} elseif (isset($row[$field['name']])) {
180+
} elseif (array_key_exists($field['name'], $row)) {
181181
$result[$field['name']] = $this->castToFieldType($field, $row[$field['name']]);
182182
}
183183
}

DataExporter/Model/Feed.php

+29-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
namespace Magento\DataExporter\Model;
99

1010
use Magento\DataExporter\Model\Indexer\FeedIndexMetadata;
11+
use Magento\DataExporter\Model\Logging\CommerceDataExportLoggerInterface;
1112
use Magento\DataExporter\Model\Query\FeedQuery;
1213
use Magento\Framework\App\ResourceConnection;
1314
use Magento\Framework\Serialize\SerializerInterface;
@@ -44,22 +45,37 @@ class Feed implements FeedInterface
4445
*/
4546
private $feedQuery;
4647

48+
/**
49+
* @var string|null
50+
*/
51+
private $dateTimeFormat;
52+
53+
/**
54+
* @var CommerceDataExportLoggerInterface
55+
*/
56+
private $logger;
57+
4758
/**
4859
* @param ResourceConnection $resourceConnection
4960
* @param SerializerInterface $serializer
5061
* @param FeedIndexMetadata $feedIndexMetadata
5162
* @param FeedQuery $feedQuery
63+
* @param null $dateTimeFormat
5264
*/
5365
public function __construct(
5466
ResourceConnection $resourceConnection,
5567
SerializerInterface $serializer,
5668
FeedIndexMetadata $feedIndexMetadata,
57-
FeedQuery $feedQuery
69+
FeedQuery $feedQuery,
70+
CommerceDataExportLoggerInterface $logger,
71+
$dateTimeFormat = null
5872
) {
5973
$this->resourceConnection = $resourceConnection;
6074
$this->serializer = $serializer;
6175
$this->feedIndexMetadata = $feedIndexMetadata;
6276
$this->feedQuery = $feedQuery;
77+
$this->logger = $logger;
78+
$this->dateTimeFormat = $dateTimeFormat;
6379
}
6480

6581
/**
@@ -101,6 +117,18 @@ private function fetchData($select): array
101117
while ($row = $cursor->fetch()) {
102118
$dataRow = $this->serializer->unserialize($row['feed_data']);
103119
$dataRow['modifiedAt'] = $row['modifiedAt'];
120+
if (null !== $this->dateTimeFormat) {
121+
try {
122+
$dataRow['modifiedAt'] = (new \DateTime($dataRow['modifiedAt']))->format($this->dateTimeFormat);
123+
} catch (\Throwable $e) {
124+
$this->logger->warning(\sprintf(
125+
'Cannot convert modifiedAt "%s" to formant "%s", error: %s',
126+
$dataRow['modifiedAt'],
127+
$this->dateTimeFormat,
128+
$e->getMessage()
129+
));
130+
}
131+
}
104132
if (isset($row['deleted'])) {
105133
$dataRow['deleted'] = (bool)$row['deleted'];
106134
}

DataExporter/Model/Indexer/DataSerializer.php

+14-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@
1111

1212
/**
1313
* Class responsible for feed data serialization
14+
* "mapping" field determinate the unique field for feed table based on data from et_schema. Support both single and multi dimension values. Example format:
15+
* [
16+
* "feed_table_column_name" => "field name in et_schema", // 'id' => 'product_id'
17+
* "feed_table_column_name" => ["complex field type", "in et_schema"], // 'id' => ["product", "id"]
18+
* ]
1419
*/
1520
class DataSerializer implements DataSerializerInterface
1621
{
@@ -49,7 +54,15 @@ public function serialize(array $data): array
4954
$outputRow = [];
5055
$outputRow['feed_data'] = $this->serializer->serialize($row);
5156
foreach ($this->mapping as $field => $index) {
52-
if (isset($row[$index])) {
57+
if (\is_array($index)) {
58+
$indexValue = null;
59+
foreach ($index as $key) {
60+
$indexValue = $indexValue
61+
? $indexValue[$key] ?? null
62+
: $row[$key] ?? null;
63+
}
64+
$outputRow[$field] = $indexValue;
65+
} elseif (isset($row[$index])) {
5366
$outputRow[$field] = is_array($row[$index]) ?
5467
$this->serializer->serialize($row[$index]) :
5568
$row[$index];

DataExporter/Model/Provider/QueryDataProvider.php

+13-5
Original file line numberDiff line numberDiff line change
@@ -58,10 +58,13 @@ private function getParentNode(Node $node, Node $root): ?Node
5858
{
5959
$parent = null;
6060
foreach ($root->getChildren() as $child) {
61-
if (spl_object_hash($child) == spl_object_hash($node)) {
61+
if (spl_object_hash($child) === spl_object_hash($node)) {
6262
return $root;
6363
}
6464
$parent = $this->getParentNode($node, $child);
65+
if (null !== $parent) {
66+
return $parent;
67+
}
6568
}
6669
return $parent;
6770
}
@@ -87,12 +90,16 @@ private function isRepeated(Node $node): bool
8790
*/
8891
private function getQueryArguments(array $values, Node $node, Info $info): array
8992
{
90-
$argumentList = array_merge($node->getField()['using'], $this->queryArguments);
93+
$argumentList = $node->getField()['using'];
94+
9195
$arguments = [];
9296
$parent = $this->getParentNode($node, $info->getRootNode());
97+
$isSingleValue = $this->isRoot($node, $info)
98+
|| ($parent && $this->isRoot($parent, $info))
99+
|| ($parent && !$this->isRepeated($parent));
93100
foreach ($values as $value) {
94101
foreach ($argumentList as $argument) {
95-
if ($this->isRoot($node, $info) || $this->isRoot($parent, $info) || !$this->isRepeated($parent)) {
102+
if ($isSingleValue) {
96103
if (isset($value[$argument['field']])) {
97104
$arguments[$argument['field']][] = $value[$argument['field']];
98105
}
@@ -105,6 +112,7 @@ private function getQueryArguments(array $values, Node $node, Info $info): array
105112
}
106113
}
107114
}
115+
$arguments+= $this->queryArguments;
108116
return $arguments;
109117
}
110118

@@ -117,7 +125,7 @@ private function getQueryArguments(array $values, Node $node, Info $info): array
117125
*/
118126
private function isRoot(Node $node, Info $info): bool
119127
{
120-
return (spl_object_hash($info->getRootNode()) == spl_object_hash($node));
128+
return (spl_object_hash($info->getRootNode()) === spl_object_hash($node));
121129
}
122130

123131
/**
@@ -151,7 +159,7 @@ private function getNodeIndexFields(Node $node): array
151159
public function get(array $values, Node $node, Info $info): array
152160
{
153161
$field = $node->getField();
154-
$isRoot = (spl_object_hash($info->getRootNode()) == spl_object_hash($node));
162+
$isRoot = (spl_object_hash($info->getRootNode()) === spl_object_hash($node));
155163
$arguments = $this->getQueryArguments($values, $node, $info);
156164
$queryName = $this->queryName ?? $field['name'];
157165
if (!$isRoot) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\DataExporter\Test\Integration\Uuid;
9+
10+
use Magento\DataExporter\Uuid\UuidManager;
11+
use Magento\TestFramework\Helper\Bootstrap;
12+
use PHPUnit\Framework\TestCase;
13+
14+
class UuidManagerTest extends TestCase
15+
{
16+
/**
17+
* @var \Magento\Framework\ObjectManagerInterface|mixed
18+
*/
19+
private $objectManager;
20+
21+
/**
22+
* @inheritdoc
23+
*/
24+
protected function setUp(): void
25+
{
26+
parent::setUp();
27+
$this->objectManager = Bootstrap::getObjectManager();
28+
}
29+
30+
/**
31+
* @param array $entityIds
32+
* @param string $type
33+
* @return void
34+
* @dataProvider happyPathDataProvider
35+
*/
36+
public function testHappyPath(array $entityIds, string $type): void
37+
{
38+
/** @var UuidManager $uuidManager */
39+
$uuidManager = $this->objectManager->create(UuidManager::class);
40+
41+
if (count($entityIds) === 1) {
42+
$uuidManager->assign(current($entityIds), $type);
43+
} else {
44+
$uuidManager->assignBulk($entityIds, $type);
45+
}
46+
foreach ($entityIds as $entityId) {
47+
self::assertTrue(
48+
$uuidManager->isAssigned($entityId, $type),
49+
'uuid is not assigned for ' . $entityId . ' ' . $type
50+
);
51+
}
52+
}
53+
54+
/**
55+
* @return void
56+
*/
57+
public function testUuidAssignedOnlyOnce(): void
58+
{
59+
/** @var UuidManager $uuidManager */
60+
$uuidManager = $this->objectManager->create(UuidManager::class);
61+
62+
$assignedUuid = $uuidManager->assign(1, 'just-once');
63+
self::assertNotEmpty($assignedUuid);
64+
$assignedUuidRepeat = $uuidManager->assign(1, 'just-once');
65+
self::assertEquals($assignedUuid, $assignedUuidRepeat);
66+
67+
$assignedUuids = $uuidManager->assignBulk([3, 4], 'just-once');
68+
$assignedUuidsRepeat = $uuidManager->assignBulk([4, 3, 4, 3], 'just-once');
69+
70+
self::assertCount(2, $assignedUuids);
71+
self::assertEquals($assignedUuids, $assignedUuidsRepeat);
72+
}
73+
74+
/**
75+
* @return void
76+
*/
77+
public function testUuidDuplicatesGenerated(): void
78+
{
79+
$uuidGeneratorMock = $this->createMock(\Magento\Framework\DataObject\IdentityService::class);
80+
/** @var UuidManager $uuidManager */
81+
$uuidManager = $this->objectManager->create(UuidManager::class, ['identityService' => $uuidGeneratorMock]);
82+
$uuidGeneratorMock->method('generateId')->willReturn('uuid');
83+
84+
$this->expectException(\Magento\DataExporter\Uuid\UuidSaveException::class);
85+
$this->expectExceptionMessage('Failed to assign UUID for type: test-type, ids: 8,9. duplicates: uuid');
86+
87+
$uuidManager->assignBulk([8, 9], 'test-type');
88+
89+
}
90+
/**
91+
* @return array[]
92+
*/
93+
public function happyPathDataProvider(): array
94+
{
95+
return [
96+
[
97+
[1], //entityIds
98+
'test-type', // type
99+
],
100+
[
101+
[1, 2], //entityIds
102+
'test-type', // type
103+
],
104+
[
105+
[3, 4], //entityIds
106+
'test-type', // type
107+
],
108+
[
109+
[4, 4, 5, 4, 5, 5], //entityIds
110+
'test-type', // type
111+
]
112+
];
113+
}
114+
}

DataExporter/Test/Integration/_files/overrides.xml

+2
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,6 @@
88
<overrides>
99
<!-- skip unrelated test: cURL error 56: I/O operation timed out -->
1010
<test class="Magento\Framework\HTTP\AsyncClientInterfaceTest" skip="true"/>
11+
<!-- skip unrelated test: Product not found after import with staging (MDEE-166) -->
12+
<test class="Magento\CatalogImportExport\Model\ProductStagingTest" skip="true"/>
1113
</overrides>

0 commit comments

Comments
 (0)