Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
4rthem committed Nov 22, 2024
1 parent d7145df commit 5f995ae
Show file tree
Hide file tree
Showing 11 changed files with 184 additions and 57 deletions.
32 changes: 0 additions & 32 deletions databox/api/src/Api/Model/Output/ESDocumentOutput.php

This file was deleted.

29 changes: 29 additions & 0 deletions databox/api/src/Api/Model/Output/ESDocumentStateOutput.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

declare(strict_types=1);

namespace App\Api\Model\Output;

use Symfony\Component\Serializer\Annotation\Groups;

final readonly class ESDocumentStateOutput
{
public function __construct(
#[Groups(['_'])]
private array $data,
#[Groups(['_'])]
private bool $synced,
)
{
}

public function getData(): array
{
return $this->data;
}

public function getSynced(): bool
{
return $this->synced;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

declare(strict_types=1);

namespace App\Api\Processor;

use Alchemy\AuthBundle\Security\JwtUser;
use Alchemy\AuthBundle\Security\Traits\SecurityAwareTrait;
use Alchemy\ESBundle\Listener\DeferredIndexListener;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProcessorInterface;
use App\Entity\Core\Asset;
use Symfony\Component\HttpFoundation\Response;

class AssetElasticsearchDocumentSyncProcessor implements ProcessorInterface
{
use SecurityAwareTrait;

public function __construct(
private readonly DeferredIndexListener $deferredIndexListener,
) {
}

/**
* @param Asset $data
*/
public function process($data, Operation $operation, array $uriVariables = [], array $context = []): Response
{
$this->denyAccessUnlessGranted(JwtUser::ROLE_TECH);
$this->deferredIndexListener->scheduleForUpdate($data);

return new Response('', 201);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,11 @@
use Alchemy\AuthBundle\Security\Traits\SecurityAwareTrait;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProviderInterface;
use App\Api\Model\Output\ESDocumentOutput;
use App\Api\Traits\ItemProviderAwareTrait;
use App\Elasticsearch\ElasticSearchClient;
use App\Elasticsearch\ESDocumentStateManager;
use App\Entity\Core\Asset;
use App\Security\Voter\AbstractVoter;
use Doctrine\ORM\EntityManagerInterface;
use Elastica\Request;

final class AssetElasticsearchDocumentProvider implements ProviderInterface
{
Expand All @@ -23,7 +21,7 @@ final class AssetElasticsearchDocumentProvider implements ProviderInterface

public function __construct(
private readonly EntityManagerInterface $em,
private readonly ElasticSearchClient $elasticSearchClient,
private readonly ESDocumentStateManager $esDocumentStateManager,
) {
}

Expand All @@ -34,10 +32,7 @@ public function provide(Operation $operation, array $uriVariables = [], array $c
$this->denyAccessUnlessGranted(JwtUser::ROLE_TECH);

if ($asset instanceof Asset) {
$indexName = $this->elasticSearchClient->getIndexName('asset');
$response = $this->elasticSearchClient->request($indexName.'/_doc/'.$asset->getId(), [], Request::GET);

return new ESDocumentOutput($response->getData());
return $this->esDocumentStateManager->getObjectState($asset);
}

return null;
Expand Down
48 changes: 48 additions & 0 deletions databox/api/src/Elasticsearch/ESDocumentStateManager.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php

namespace App\Elasticsearch;

use Alchemy\CoreBundle\Entity\AbstractUuidEntity;
use App\Api\Model\Output\ESDocumentStateOutput;
use Elastica\Request;
use FOS\ElasticaBundle\Persister\ObjectPersister;
use FOS\ElasticaBundle\Persister\ObjectPersisterInterface;
use RuntimeException;
use Symfony\Component\DependencyInjection\Attribute\TaggedIterator;

final readonly class ESDocumentStateManager
{
/**
* @param ObjectPersisterInterface[] $objectPersisters
*/
public function __construct(
#[TaggedIterator(tag: 'fos_elastica.persister')]
private iterable $objectPersisters,
private ElasticSearchClient $elasticSearchClient,

) {
}

public function getObjectState(AbstractUuidEntity $object): ESDocumentStateOutput
{
$document = $this->getObjectPersister($object)->transformToElasticaDocument($object);
$indexName = $this->elasticSearchClient->getIndexName($document->getIndex());
$response = $this->elasticSearchClient->request($indexName.'/_doc/'.$object->getId(), [], Request::GET);

$data = $response->getData();
$synced = $document->getData() == $data['_source'];

return new ESDocumentStateOutput($data, $synced);
}

private function getObjectPersister(object $object): ObjectPersister
{
foreach ($this->objectPersisters as $objectPersister) {
if ($objectPersister instanceof ObjectPersister && $objectPersister->handlesObject($object)) {
return $objectPersister;
}
}

throw new RuntimeException(sprintf('No object persister found for object of class %s', get_class($object)));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,6 @@ private function compileAttributes(Asset $asset): array
$data[$locale][$fieldName][] = $translation;
}
}

} else {
foreach ($v as $locale => $translation) {
$data[$locale][$fieldName] = $translation;
Expand Down
10 changes: 8 additions & 2 deletions databox/api/src/Entity/Core/Asset.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,11 @@
use App\Api\Model\Input\MultipleAssetInput;
use App\Api\Model\Input\PrepareDeleteAssetsInput;
use App\Api\Model\Output\AssetOutput;
use App\Api\Model\Output\ESDocumentOutput;
use App\Api\Model\Output\ESDocumentStateOutput;
use App\Api\Model\Output\MultipleAssetOutput;
use App\Api\Model\Output\PrepareDeleteAssetsOutput;
use App\Api\Processor\AssetAttributeBatchUpdateProcessor;
use App\Api\Processor\AssetElasticsearchDocumentSyncProcessor;
use App\Api\Processor\CopyAssetProcessor;
use App\Api\Processor\MoveAssetProcessor;
use App\Api\Processor\MultipleAssetCreateProcessor;
Expand Down Expand Up @@ -161,10 +162,15 @@
),
new Get(
uriTemplate: '/assets/{id}/es-document',
output: ESDocumentOutput::class,
output: ESDocumentStateOutput::class,
name: 'es_document',
provider: AssetElasticsearchDocumentProvider::class,
),
new Post(
uriTemplate: '/assets/{id}/es-document-sync',
name: 'sync_es_document',
processor: AssetElasticsearchDocumentSyncProcessor::class,
),
],
normalizationContext: [
'groups' => [self::GROUP_LIST],
Expand Down
10 changes: 7 additions & 3 deletions databox/client/src/api/asset.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import apiClient from './api-client';
import {Asset, AssetFileVersion, Attribute, Collection, Share} from '../types';
import {Asset, AssetFileVersion, Attribute, Collection, ESDocumentState, Share} from '../types';
import {ApiCollectionResponse, getAssetsHydraCollection, getHydraCollection,} from './hydra';
import {AxiosRequestConfig} from 'axios';
import {TFacets} from '../components/Media/Asset/Facets';
Expand Down Expand Up @@ -95,8 +95,12 @@ export async function getAsset(id: string): Promise<Asset> {
return (await apiClient.get(`/assets/${id}`)).data;
}

export async function getAssetESDocument(id: string): Promise<Asset> {
return (await apiClient.get(`/assets/${id}/es-document`)).data.data;
export async function getAssetESDocument(id: string): Promise<ESDocumentState> {
return (await apiClient.get(`/assets/${id}/es-document`)).data;
}

export async function syncAssetESDocument(id: string): Promise<void> {
await apiClient.post(`/assets/${id}/es-document-sync`, {});
}

export async function getAssetShares(assetId: string): Promise<Share[]> {
Expand Down
56 changes: 46 additions & 10 deletions databox/client/src/components/Dialog/Asset/AssetESDocument.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import {Asset, StateSetter} from '../../../types';
import {Asset, ESDocumentState, StateSetter} from '../../../types';
import {DialogTabProps} from '../Tabbed/TabbedDialog';
import ContentTab from '../Tabbed/ContentTab';
import {getAssetESDocument} from '../../../api/asset';
import {getAssetESDocument, syncAssetESDocument} from '../../../api/asset';
import {useTranslation} from 'react-i18next';
import {useCallback, useEffect, useState} from "react";
import RefreshIcon from '@mui/icons-material/Refresh';
import {IconButton, LinearProgress, Typography} from "@mui/material";
import {Alert, Button} from "@mui/material";
import {LoadingButton} from "@mui/lab";

type Props = {
data: Asset;
Expand All @@ -16,8 +18,10 @@ export default function AssetESDocument({
onClose,
minHeight,
}: Props) {
const [document, setDocument] = useState<object>();
const {t} = useTranslation();
const [document, setDocument] = useState<ESDocumentState>();
const [loading, setLoading] = useState(false);
const [synced, setSynced] = useState(false);

const refresh = useCallback(async () => {
setLoading(true);
Expand All @@ -32,17 +36,49 @@ export default function AssetESDocument({
refresh();
}, [refresh]);

const sync = async () => {
setSynced(true);
try {
await syncAssetESDocument(data.id)
} catch (e) {
setSynced(false);
}
}

return (
<ContentTab onClose={onClose} minHeight={minHeight}>
{loading && <LinearProgress/>}
<ContentTab
loading={loading}
disablePadding
disableGutters
onClose={onClose}
minHeight={minHeight}
actions={<>
<LoadingButton
loading={loading}
disabled={loading}
onClick={refresh}
startIcon={<RefreshIcon/>}
>
{t('asset.es_document.refresh', 'Refresh')}
</LoadingButton>
</>}
>

{document ? <>
<IconButton onClick={refresh}>
<RefreshIcon />
</IconButton>
{!document.synced ? <Alert severity={'warning'}
action={<Button
onClick={sync}
disabled={synced}
>
{synced ? t('asset.es_document.sync_scheduled', 'Sync scheduled') : t('asset.es_document.sync_now', 'Sync Now')}
</Button>}
>
{t('asset.es_document.not_synced', 'This document is not synced.')}
</Alert> : null}
<pre style={{
fontSize: 12,
}}>
{JSON.stringify(document, null, 4)}
{JSON.stringify(document.data, null, 4)}
</pre>
</> : null}
</ContentTab>
Expand Down
5 changes: 4 additions & 1 deletion databox/client/src/components/Dialog/Tabbed/ContentTab.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {Button, Container, LinearProgress} from '@mui/material';
import {PropsWithChildren} from 'react';
import {PropsWithChildren, ReactNode} from 'react';
import DialogContent from '@mui/material/DialogContent';
import DialogActions from '@mui/material/DialogActions';
import {useTranslation} from 'react-i18next';
Expand All @@ -10,6 +10,7 @@ type Props = PropsWithChildren<{
minHeight?: number | undefined;
disableGutters?: boolean;
disablePadding?: boolean;
actions?: ReactNode;
}>;

export default function ContentTab({
Expand All @@ -19,6 +20,7 @@ export default function ContentTab({
minHeight,
disableGutters,
disablePadding,
actions,
}: Props) {
const {t} = useTranslation();
const progressHeight = 3;
Expand Down Expand Up @@ -47,6 +49,7 @@ export default function ContentTab({
/>
)}
<DialogActions>
{actions}
<Button onClick={onClose} disabled={loading}>
{t('dialog.close', 'Close')}
</Button>
Expand Down
5 changes: 5 additions & 0 deletions databox/client/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ export type Share = {
alternateUrls: ShareAlternateUrl[];
};

export type ESDocumentState = {
synced: boolean;
data: object;
}

export interface Asset
extends IPermissions<{
canEditAttributes: boolean;
Expand Down

0 comments on commit 5f995ae

Please sign in to comment.