Skip to content

Commit 349a653

Browse files
Add indexed data diagnostics to admin edit pages (#4644)
2 parents 6ce6f1a + f5d7d73 commit 349a653

6 files changed

Lines changed: 327 additions & 0 deletions

File tree

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import ModalWindow from '@shopsys/administration/src/js/utils/modalWindow';
2+
import Ajax from '../../common/utils/Ajax';
3+
import Register from '../../common/utils/Register';
4+
5+
export default class ElasticsearchEntityData {
6+
static domainSwitchSelector = '[data-js-elasticsearch-entity-data-domain-switch]';
7+
8+
constructor($container) {
9+
$container.filterAllNodes('[data-js-elasticsearch-entity-data-open]').on('click', this.openWindow);
10+
}
11+
12+
openWindow(event) {
13+
event.preventDefault();
14+
event.stopPropagation();
15+
16+
const $button = $(event.currentTarget);
17+
18+
if ($button.data('loading')) {
19+
return false;
20+
}
21+
22+
$button.data('loading', true);
23+
24+
ElasticsearchEntityData.loadContent($button.data('url'), $button, content => {
25+
const modalInstance = new ModalWindow({
26+
content,
27+
size: 'xl',
28+
});
29+
30+
ElasticsearchEntityData.registerModalContent(modalInstance);
31+
32+
modalInstance.element.one('hidden.bs.modal', () => {
33+
$button.data('loading', false);
34+
});
35+
});
36+
37+
return false;
38+
}
39+
40+
static loadContent(url, $loaderElement, successCallback) {
41+
if (!url) {
42+
return;
43+
}
44+
45+
Ajax.ajax({
46+
loaderElement: $loaderElement,
47+
url,
48+
type: 'GET',
49+
success: successCallback,
50+
error: () => {
51+
$loaderElement.data('loading', false);
52+
},
53+
});
54+
}
55+
56+
static registerModalContent(modalInstance) {
57+
new Register().registerNewContent(modalInstance.element);
58+
59+
modalInstance.element.find(ElasticsearchEntityData.domainSwitchSelector).on('click', event => {
60+
event.preventDefault();
61+
event.stopPropagation();
62+
63+
const $switch = $(event.currentTarget);
64+
65+
ElasticsearchEntityData.loadContent($switch.attr('href'), $switch, content => {
66+
modalInstance.element.find('.modal-body').html(content);
67+
ElasticsearchEntityData.registerModalContent(modalInstance);
68+
});
69+
});
70+
}
71+
72+
static init($container) {
73+
// eslint-disable-next-line no-new
74+
new ElasticsearchEntityData($container);
75+
}
76+
}
77+
78+
new Register().registerCallback(ElasticsearchEntityData.init, 'ElasticsearchEntityData.init');

assets/js/admin/components/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import './CustomerRoleGroupForm';
1414
import './DynamicPlaceholder';
1515
import './EntityUrlsNew';
1616
import './EntityUrlsRow';
17+
import './ElasticsearchEntityData';
1718
import './ExecuteRefund';
1819
import './initBootstrapFileInput';
1920
import './FilePicker';
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Shopsys\FrameworkBundle\Controller\Admin;
6+
7+
use Shopsys\FrameworkBundle\Component\Domain\Domain;
8+
use Shopsys\FrameworkBundle\Component\Security\Attribute\SuperAdminOnly;
9+
use Shopsys\FrameworkBundle\Model\Elasticsearch\ElasticsearchEntityDataFacade;
10+
use Symfony\Component\HttpFoundation\Response;
11+
use Symfony\Component\Routing\Attribute\Route;
12+
13+
class ElasticsearchEntityDataController extends AdminBaseController
14+
{
15+
public function __construct(
16+
protected readonly ElasticsearchEntityDataFacade $elasticsearchEntityDataFacade,
17+
protected readonly Domain $domain,
18+
) {
19+
}
20+
21+
#[Route(
22+
path: '/elasticsearch/entity-data/{indexName}/{id}/{domainId}/{hideDomainSwitch}',
23+
name: 'admin_elasticsearch_entity_data',
24+
requirements: [
25+
'indexName' => '[\w\-]+',
26+
'id' => '\d+',
27+
'domainId' => '\d+',
28+
'hideDomainSwitch' => '0|1',
29+
],
30+
defaults: ['domainId' => null],
31+
)]
32+
#[SuperAdminOnly]
33+
public function detailAction(string $indexName, int $id, ?int $domainId, bool $hideDomainSwitch = false): Response
34+
{
35+
$selectedDomainId = $this->resolveSelectedDomainId($domainId);
36+
$elasticsearchEntityData = $this->elasticsearchEntityDataFacade->getElasticsearchEntityData(
37+
$indexName,
38+
$selectedDomainId,
39+
$id,
40+
);
41+
42+
return $this->render('@ShopsysAdministration/content/elasticsearch/entityDataModal.html.twig', [
43+
'domains' => $this->domain->getAdminEnabledDomains(),
44+
'selectedDomainId' => $selectedDomainId,
45+
'hideDomainSwitch' => $hideDomainSwitch,
46+
'entityId' => $id,
47+
'indexName' => $indexName,
48+
'elasticsearchEntityData' => $elasticsearchEntityData,
49+
]);
50+
}
51+
52+
protected function resolveSelectedDomainId(?int $requestedDomainId): int
53+
{
54+
$adminEnabledDomainIds = $this->domain->getAdminEnabledDomainIds();
55+
56+
if ($requestedDomainId !== null && in_array($requestedDomainId, $adminEnabledDomainIds, true)) {
57+
return $requestedDomainId;
58+
}
59+
60+
return array_first($adminEnabledDomainIds);
61+
}
62+
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Shopsys\FrameworkBundle\Model\Elasticsearch;
6+
7+
class ElasticsearchEntityData
8+
{
9+
public const string STATUS_FOUND = 'found';
10+
public const string STATUS_NOT_FOUND = 'not_found';
11+
public const string STATUS_ERROR = 'error';
12+
13+
/**
14+
* @param array<string, mixed>|null $rawDocument
15+
*/
16+
public function __construct(
17+
protected readonly string $status,
18+
protected readonly string $indexAlias,
19+
protected readonly int $domainId,
20+
protected readonly int $entityId,
21+
protected readonly ?array $rawDocument = null,
22+
protected readonly ?string $errorMessage = null,
23+
) {
24+
}
25+
26+
public function isFound(): bool
27+
{
28+
return $this->status === static::STATUS_FOUND;
29+
}
30+
31+
public function isNotFound(): bool
32+
{
33+
return $this->status === static::STATUS_NOT_FOUND;
34+
}
35+
36+
public function isError(): bool
37+
{
38+
return $this->status === static::STATUS_ERROR;
39+
}
40+
41+
public function getIndexAlias(): string
42+
{
43+
return $this->indexAlias;
44+
}
45+
46+
public function getDomainId(): int
47+
{
48+
return $this->domainId;
49+
}
50+
51+
public function getEntityId(): int
52+
{
53+
return $this->entityId;
54+
}
55+
56+
/**
57+
* @return array<string, mixed>|null
58+
*/
59+
public function getRawDocument(): ?array
60+
{
61+
return $this->rawDocument;
62+
}
63+
64+
public function getErrorMessage(): ?string
65+
{
66+
return $this->errorMessage;
67+
}
68+
69+
public function getPrettyJson(): string
70+
{
71+
if ($this->rawDocument === null) {
72+
return '';
73+
}
74+
75+
return json_encode(
76+
$this->rawDocument,
77+
JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR,
78+
);
79+
}
80+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Shopsys\FrameworkBundle\Model\Elasticsearch;
6+
7+
use Elasticsearch\Client;
8+
use Shopsys\FrameworkBundle\Component\Elasticsearch\IndexDefinitionLoader;
9+
use Shopsys\FrameworkBundle\Component\Elasticsearch\IndexRegistry;
10+
use Throwable;
11+
12+
class ElasticsearchEntityDataFacade
13+
{
14+
public function __construct(
15+
protected readonly Client $client,
16+
protected readonly IndexDefinitionLoader $indexDefinitionLoader,
17+
protected readonly IndexRegistry $indexRegistry,
18+
protected readonly ElasticsearchEntityDataFactory $elasticsearchEntityDataFactory,
19+
) {
20+
}
21+
22+
public function getElasticsearchEntityData(
23+
string $indexName,
24+
int $domainId,
25+
int $entityId,
26+
): ElasticsearchEntityData {
27+
$indexAlias = $this->indexDefinitionLoader->getIndexDefinition($indexName, $domainId)->getIndexAlias();
28+
29+
$parameters = [
30+
'index' => $indexAlias,
31+
'id' => (string)$entityId,
32+
];
33+
34+
try {
35+
if (!$this->indexRegistry->isIndexRegistered($indexName) || !$this->client->exists($parameters)) {
36+
return $this->elasticsearchEntityDataFactory->createNotFound($indexAlias, $domainId, $entityId);
37+
}
38+
39+
return $this->elasticsearchEntityDataFactory->createFound(
40+
$indexAlias,
41+
$domainId,
42+
$entityId,
43+
$this->client->get($parameters),
44+
);
45+
} catch (Throwable $exception) {
46+
return $this->elasticsearchEntityDataFactory->createError(
47+
$indexAlias,
48+
$domainId,
49+
$entityId,
50+
$exception->getMessage(),
51+
);
52+
}
53+
}
54+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Shopsys\FrameworkBundle\Model\Elasticsearch;
6+
7+
class ElasticsearchEntityDataFactory
8+
{
9+
/**
10+
* @param array<string, mixed> $rawDocument
11+
*/
12+
public function createFound(
13+
string $indexAlias,
14+
int $domainId,
15+
int $entityId,
16+
array $rawDocument,
17+
): ElasticsearchEntityData {
18+
return new ElasticsearchEntityData(
19+
ElasticsearchEntityData::STATUS_FOUND,
20+
$indexAlias,
21+
$domainId,
22+
$entityId,
23+
$rawDocument,
24+
);
25+
}
26+
27+
public function createNotFound(string $indexAlias, int $domainId, int $entityId): ElasticsearchEntityData
28+
{
29+
return new ElasticsearchEntityData(
30+
ElasticsearchEntityData::STATUS_NOT_FOUND,
31+
$indexAlias,
32+
$domainId,
33+
$entityId,
34+
);
35+
}
36+
37+
public function createError(
38+
string $indexAlias,
39+
int $domainId,
40+
int $entityId,
41+
string $errorMessage,
42+
): ElasticsearchEntityData {
43+
return new ElasticsearchEntityData(
44+
ElasticsearchEntityData::STATUS_ERROR,
45+
$indexAlias,
46+
$domainId,
47+
$entityId,
48+
null,
49+
$errorMessage,
50+
);
51+
}
52+
}

0 commit comments

Comments
 (0)