diff --git a/.woodpecker/prod_economics.yml b/.woodpecker/prod_economics.yml index 57b16b09b..d39990149 100644 --- a/.woodpecker/prod_economics.yml +++ b/.woodpecker/prod_economics.yml @@ -26,3 +26,12 @@ steps: - itkdev-docker-compose-server run --rm phpfpm bin/console doctrine:migrations:migrate --no-interaction - itkdev-docker-compose-server run --rm phpfpm bin/console messenger:setup-transports - itkdev-docker-compose-server run --rm phpfpm bin/console cache:clear + cron: + # @TODO This should run with Symfony scheduler, see App\Command\QueueSyncCommand + leantime-sync: + minute: '22' + hour: '*' + day: '*' + month: '*' + weekday: '*' + job: 'itkdev-docker-compose-server exec phpfpm bin/console app:sync' \ No newline at end of file diff --git a/.woodpecker/prod_itk_economics.yml b/.woodpecker/prod_itk_economics.yml index b58881de9..84dc97474 100644 --- a/.woodpecker/prod_itk_economics.yml +++ b/.woodpecker/prod_itk_economics.yml @@ -26,3 +26,12 @@ steps: - itkdev-docker-compose-server run --rm phpfpm bin/console doctrine:migrations:migrate --no-interaction - itkdev-docker-compose-server run --rm phpfpm bin/console messenger:setup-transports - itkdev-docker-compose-server run --rm phpfpm bin/console cache:clear + cron: + # @TODO This should run with Symfony scheduler, see App\Command\QueueSyncCommand + leantime-sync: + minute: '22' + hour: '*' + day: '*' + month: '*' + weekday: '*' + job: 'itkdev-docker-compose-server exec phpfpm bin/console app:sync' diff --git a/.woodpecker/stg.yml b/.woodpecker/stg.yml index 8288018c9..d2cb7b2ed 100644 --- a/.woodpecker/stg.yml +++ b/.woodpecker/stg.yml @@ -35,3 +35,12 @@ steps: - docker compose run --rm node npm clean-install - docker compose run --rm node npm run build - docker compose run --rm node rm -rf node_modules + cron: + # @TODO This should run with Symfony scheduler, see App\Command\QueueSyncCommand + leantime-sync: + minute: '22' + hour: '*' + day: '*' + month: '*' + weekday: '*' + job: 'itkdev-docker-compose-server exec phpfpm bin/console app:sync' diff --git a/CHANGELOG.md b/CHANGELOG.md index 90772654b..4d5b017c8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [2.9.6] - 2025-09-25 + +* [PR-240](https://github.com/itk-dev/economics/pull/240) + Fixes and optimizations for `app:sync` command + +## [2.9.5] - 2025-07-10 + +* [PR-235](https://github.com/itk-dev/economics/pull/235) + Various minor changes and request timeout increase + ## [2.9.4] - 2025-07-10 * [PR-232](https://github.com/itk-dev/economics/pull/232) diff --git a/config/services.yaml b/config/services.yaml index bacdfe02a..fc1602bcd 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -74,3 +74,7 @@ services: App\MessageHandler\SynchronizeHandler: arguments: $monitoringUrl: '%env(string:SYNC_MONITORING_URL)%' + + App\Command\SyncCommand: + arguments: + $monitoringUrl: '%env(string:SYNC_MONITORING_URL)%' diff --git a/src/Command/SyncCommand.php b/src/Command/SyncCommand.php index 594296ead..46ec1b5f2 100644 --- a/src/Command/SyncCommand.php +++ b/src/Command/SyncCommand.php @@ -2,16 +2,25 @@ namespace App\Command; +use App\Entity\Project; +use App\Entity\SynchronizationJob; +use App\Enum\SynchronizationStatusEnum; +use App\Enum\SynchronizationStepEnum; use App\Exception\EconomicsException; use App\Exception\UnsupportedDataProviderException; use App\Repository\DataProviderRepository; use App\Repository\ProjectRepository; +use App\Repository\SynchronizationJobRepository; use App\Service\DataSynchronizationService; +use Doctrine\ORM\EntityManagerInterface; +use Doctrine\Persistence\ManagerRegistry; +use Psr\Log\LoggerInterface; use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Contracts\HttpClient\HttpClientInterface; #[AsCommand( name: 'app:sync', @@ -23,6 +32,12 @@ public function __construct( private readonly ProjectRepository $projectRepository, private readonly DataProviderRepository $dataProviderRepository, private readonly DataSynchronizationService $dataSynchronizationService, + private readonly SynchronizationJobRepository $synchronizationJobRepository, + private readonly EntityManagerInterface $entityManager, + private readonly ManagerRegistry $managerRegistry, + private readonly HttpClientInterface $client, + private readonly LoggerInterface $logger, + private readonly string $monitoringUrl, ) { parent::__construct($this->getName()); } @@ -39,80 +54,144 @@ protected function execute(InputInterface $input, OutputInterface $output): int { $io = new SymfonyStyle($input, $output); - $io->info('Processing projects'); - $dataProviders = $this->dataProviderRepository->findBy(['enabled' => true]); - foreach ($dataProviders as $dataProvider) { - $this->dataSynchronizationService->syncProjects(function ($i, $length) use ($io) { - if (0 == $i) { - $io->progressStart($length); - } elseif ($i >= $length - 1) { - $io->progressFinish(); - } else { - $io->progressAdvance(); - } - }, $dataProvider); - } + $job = new SynchronizationJob(); + $job->setStarted(new \DateTime()); + $job->addMessage('Synchronization started'); + $job->setProgress(0); + $job->setStatus(SynchronizationStatusEnum::RUNNING); + $this->synchronizationJobRepository->save($job, true); + + try { + $io->info('Processing projects'); + $job->setStep(SynchronizationStepEnum::PROJECTS); + + $this->entityManager->flush(); - $io->info('Processing accounts'); + foreach ($dataProviders as $dataProvider) { + $job->addMessage('Processing projects from '.$dataProvider->getName()); + $this->dataSynchronizationService->syncProjects(function ($i, $length) use ($io, $job) { + $this->setProgress($i, $length, $io, $job); + }, $dataProvider); + } + + $io->info('Processing accounts'); + $this->setStep(SynchronizationStepEnum::ACCOUNTS, $job); + + foreach ($dataProviders as $dataProvider) { + $this->dataSynchronizationService->syncAccounts(function ($i, $length) use ($io, $job) { + $this->setProgress($i, $length, $io, $job); + }, $dataProvider); + } - foreach ($dataProviders as $dataProvider) { - $this->dataSynchronizationService->syncAccounts(function ($i, $length) use ($io) { - if (0 == $i) { - $io->progressStart($length); - } elseif ($i >= $length - 1) { - $io->progressFinish(); - } else { - $io->progressAdvance(); + $io->info('Processing issues'); + $this->setStep(SynchronizationStepEnum::ISSUES, $job); + + foreach ($dataProviders as $dataProvider) { + $projects = $this->projectRepository->findBy(['include' => true, 'dataProvider' => $dataProvider]); + + foreach ($projects as $project) { + $io->writeln("Processing issues for {$project->getName()}"); + + $this->dataSynchronizationService->syncIssuesForProject($project->getId(), $dataProvider, function ($i, $length) use ($io, $job) { + $this->setProgress($i, $length, $io, $job); + }); + + $io->writeln(''); } - }, $dataProvider); - } + } - $io->info('Processing issues'); + $io->info('Processing worklogs'); + $this->setStep(SynchronizationStepEnum::WORKLOGS, $job); - foreach ($dataProviders as $dataProvider) { - $projects = $this->projectRepository->findBy(['include' => true, 'dataProvider' => $dataProvider]); + foreach ($dataProviders as $dataProvider) { + $projects = $this->projectRepository->findBy(['include' => true, 'dataProvider' => $dataProvider]); - foreach ($projects as $project) { - $io->writeln("Processing issues for {$project->getName()}"); + /** @var Project $project */ + foreach ($projects as $project) { + $io->writeln("Processing worklogs for {$project->getName()}"); - $this->dataSynchronizationService->syncIssuesForProject($project->getId(), $dataProvider, function ($i, $length) use ($io) { - if (0 == $i) { - $io->progressStart($length); - } elseif ($i >= $length - 1) { - $io->progressFinish(); - } else { - $io->progressAdvance(); + $projectId = $project->getId(); + + if (null === $projectId) { + throw new \RuntimeException('Project id is null'); } - }); - $io->writeln(''); + $this->dataSynchronizationService->syncWorklogsForProject($projectId, $dataProvider, function ($i, $length) use ($io, $job) { + $this->setProgress($i, $length, $io, $job); + }); + + $io->writeln(''); + } } + + $job = $this->getJob($job); + $job->setStatus(SynchronizationStatusEnum::DONE); + $job->setEnded(new \DateTime()); + $this->entityManager->flush(); + + // Call monitoring url if defined. + if ('' !== $this->monitoringUrl) { + try { + $this->client->request('GET', $this->monitoringUrl); + } catch (\Throwable $e) { + $this->logger->error('Error calling monitoringUrl: '.$e->getMessage()); + } + } + + return Command::SUCCESS; + } catch (\Exception $exception) { + $this->managerRegistry->resetManager(); + + $job = $this->getJob($job); + $job->setStatus(SynchronizationStatusEnum::ERROR); + $job->setEnded(new \DateTime()); + $job->addMessage($exception->getMessage()); + + $this->entityManager->flush(); + + $io->error($exception->getMessage()); + + return Command::FAILURE; } + } - $io->info('Processing worklogs'); + private function setProgress(int $i, int $length, SymfonyStyle $io, SynchronizationJob $job): void + { + $job = $this->getJob($job); + + if (0 == $i) { + $io->progressStart($length); + $job->setProgress($i); + } elseif ($i >= $length - 1) { + $io->progressFinish(); + $job->setProgress(100); + } else { + $length = 0 < $length ? $length : 1; + $io->progressAdvance(); + $job->setProgress(intdiv($i * 100, $length)); + } + } - foreach ($dataProviders as $dataProvider) { - $projects = $this->projectRepository->findBy(['include' => true, 'dataProvider' => $dataProvider]); + private function setStep(SynchronizationStepEnum $step, SynchronizationJob $job): void + { + $job = $this->getJob($job); - foreach ($projects as $project) { - $io->writeln("Processing worklogs for {$project->getName()}"); + $job->setStep($step); + $this->entityManager->flush(); + } - $this->dataSynchronizationService->syncWorklogsForProject($project->getId(), $dataProvider, function ($i, $length) use ($io) { - if (0 == $i) { - $io->progressStart($length); - } elseif ($i >= $length - 1) { - $io->progressFinish(); - } else { - $io->progressAdvance(); - } - }); + private function getJob(SynchronizationJob $job): SynchronizationJob + { + if (!$this->entityManager->contains($job)) { + $job = $this->synchronizationJobRepository->find($job->getId()); - $io->writeln(''); + if (null === $job) { + throw new \RuntimeException('Job not found'); } } - return Command::SUCCESS; + return $job; } } diff --git a/src/Controller/SynchronizationJobController.php b/src/Controller/SynchronizationJobController.php index fd3eac1ba..6dd89aa17 100644 --- a/src/Controller/SynchronizationJobController.php +++ b/src/Controller/SynchronizationJobController.php @@ -44,26 +44,29 @@ public function status(SynchronizationJobRepository $synchronizationJobRepositor #[IsGranted('ROLE_ADMIN')] public function sync(SynchronizationJobRepository $synchronizationJobRepository, MessageBusInterface $bus): Response { - $latestJob = $synchronizationJobRepository->getLatestJob(); - - if (null !== $latestJob && in_array($latestJob->getStatus(), [SynchronizationStatusEnum::NOT_STARTED, SynchronizationStatusEnum::RUNNING])) { - return new JsonResponse(['message' => 'existing job'], Response::HTTP_CONFLICT); - } - - $job = new SynchronizationJob(); - $job->setStatus(SynchronizationStatusEnum::NOT_STARTED); - $synchronizationJobRepository->save($job, true); - - $jobId = $job->getId(); - - if (null === $jobId) { - throw new \Exception('Job id not found'); - } - - $message = new SynchronizeMessage($jobId); - - $bus->dispatch($message); + return new JsonResponse([], Response::HTTP_SERVICE_UNAVAILABLE); - return new JsonResponse([], 200); + // @TODO re-enable when sync is fully re-factored + // $latestJob = $synchronizationJobRepository->getLatestJob(); + // + // if (null !== $latestJob && in_array($latestJob->getStatus(), [SynchronizationStatusEnum::NOT_STARTED, SynchronizationStatusEnum::RUNNING])) { + // return new JsonResponse(['message' => 'existing job'], Response::HTTP_CONFLICT); + // } + // + // $job = new SynchronizationJob(); + // $job->setStatus(SynchronizationStatusEnum::NOT_STARTED); + // $synchronizationJobRepository->save($job, true); + // + // $jobId = $job->getId(); + // + // if (null === $jobId) { + // throw new \Exception('Job id not found'); + // } + // + // $message = new SynchronizeMessage($jobId); + // + // $bus->dispatch($message); + // + // return new JsonResponse([], 200); } } diff --git a/src/Entity/SynchronizationJob.php b/src/Entity/SynchronizationJob.php index 39db4acd5..b7b626b63 100644 --- a/src/Entity/SynchronizationJob.php +++ b/src/Entity/SynchronizationJob.php @@ -100,4 +100,11 @@ public function setMessages(?string $messages): static return $this; } + + public function addMessage(string $message): static + { + $this->messages .= $message."\n"; + + return $this; + } } diff --git a/src/Service/DataSynchronizationService.php b/src/Service/DataSynchronizationService.php index baaf1058a..4252d0d66 100644 --- a/src/Service/DataSynchronizationService.php +++ b/src/Service/DataSynchronizationService.php @@ -32,8 +32,13 @@ class DataSynchronizationService { - private const BATCH_SIZE = 200; - private const MAX_RESULTS = 50; + private const int BATCH_SIZE = 200; + private const int MAX_RESULTS = 50; + + private array $issues = []; + private array $workers = []; + private array $versions = []; + private array $epics = []; public function __construct( private readonly ProjectRepository $projectRepository, @@ -60,8 +65,6 @@ public function __construct( */ public function syncProjects(callable $progressCallback, DataProvider $dataProvider): void { - $dataProviderId = $dataProvider->getId(); - $service = $this->dataProviderService->getService($dataProvider); // Get all projects from ApiService. @@ -71,7 +74,7 @@ public function syncProjects(callable $progressCallback, DataProvider $dataProvi 'projectTrackerId' => $projectDatum->projectTrackerId, 'dataProvider' => $dataProvider, ]); - $dataProvider = $this->dataProviderRepository->find($dataProviderId); + $dataProvider = $this->getDataProvider($dataProvider); if (!$project) { $project = new Project(); @@ -87,10 +90,7 @@ public function syncProjects(callable $progressCallback, DataProvider $dataProvi foreach ($projectDatum->versions as $versions) { /** @var VersionModel $versionDatum */ foreach ($versions as $versionDatum) { - $version = $this->versionRepository->findOneBy([ - 'projectTrackerId' => $versionDatum->id, - 'dataProvider' => $dataProvider, - ]); + $version = $this->getVersion($versionDatum->id, $dataProvider); if (!$version) { $version = new Version(); @@ -105,7 +105,7 @@ public function syncProjects(callable $progressCallback, DataProvider $dataProvi } // Only synchronize clients if this is enabled. - if (null != $dataProvider && $dataProvider->isEnableClientSync()) { + if ($dataProvider->isEnableClientSync()) { $projectClientData = $service->getClientDataForProject($projectDatum->projectTrackerId); foreach ($projectClientData as $clientData) { @@ -138,14 +138,14 @@ public function syncProjects(callable $progressCallback, DataProvider $dataProvi // Flush and clear for each batch. if (0 === intval($index) % self::BATCH_SIZE) { $this->entityManager->flush(); - $this->entityManager->clear(); + $this->clear(); } $progressCallback($index, count($allProjectData->projectData)); } $this->entityManager->flush(); - $this->entityManager->clear(); + $this->clear(); } /** @@ -184,14 +184,14 @@ public function syncAccounts(callable $progressCallback, DataProvider $dataProvi // Flush and clear for each batch. if (0 === intval($index) % self::BATCH_SIZE) { $this->entityManager->flush(); - $this->entityManager->clear(); + $this->clear(); } $progressCallback($index, count($allAccountData)); } $this->entityManager->flush(); - $this->entityManager->clear(); + $this->clear(); } } @@ -203,8 +203,6 @@ public function syncAccounts(callable $progressCallback, DataProvider $dataProvi */ public function syncIssuesForProject(int $projectId, DataProvider $dataProvider, ?callable $progressCallback = null): void { - $dataProviderId = $dataProvider->getId(); - $service = $this->dataProviderService->getService($dataProvider); $project = $this->projectRepository->find($projectId); @@ -223,20 +221,15 @@ public function syncIssuesForProject(int $projectId, DataProvider $dataProvider, $startAt = 0; do { - $dataProvider = $this->dataProviderRepository->find($dataProviderId); - $project = $this->projectRepository->find($projectId); - if (!$project) { - throw new EconomicsException($this->translator->trans('exception.project_not_found')); - } + $dataProvider = $this->getDataProvider($dataProvider); + $project = $this->getProject($project); $pagedIssueData = $service->getIssuesDataForProjectPaged($projectTrackerId, $startAt, self::MAX_RESULTS); $total = $pagedIssueData->total; foreach ($pagedIssueData->items as $issueDatum) { - $issue = $this->issueRepository->findOneBy([ - 'projectTrackerId' => $issueDatum->projectTrackerId, - 'dataProvider' => $dataProvider, - ]); + $dataProvider = $this->getDataProvider($dataProvider); + $issue = $this->getIssue($issueDatum->projectTrackerId, $dataProvider); if (!$issue) { $issue = new Issue(); @@ -265,15 +258,12 @@ public function syncIssuesForProject(int $projectId, DataProvider $dataProvider, $issue->setLinkToIssue($issueDatum->linkToIssue); // Leantime (as of now) supports only a single version (milestone) per issue. - if (LeantimeApiService::class === $dataProvider?->getClass()) { + if (LeantimeApiService::class === $dataProvider->getClass()) { $issue->getVersions()->clear(); } foreach ($issueDatum->versions as $versionData) { - $version = $this->versionRepository->findOneBy([ - 'projectTrackerId' => $versionData->projectTrackerId, - 'dataProvider' => $dataProvider, - ]); + $version = $this->getVersion($versionData->projectTrackerId, $dataProvider); if (null !== $version) { $issue->addVersion($version); @@ -284,13 +274,12 @@ public function syncIssuesForProject(int $projectId, DataProvider $dataProvider, if (empty($epicTitle)) { continue; } - $epic = $this->epicRepository->findOneBy(['title' => $epicTitle]); + $epic = $this->getEpic($epicTitle); if (null === $epic) { $epic = new Epic(); $epic->setTitle($epicTitle); $this->entityManager->persist($epic); - $this->entityManager->flush(); } $issue->addEpic($epic); @@ -305,11 +294,11 @@ public function syncIssuesForProject(int $projectId, DataProvider $dataProvider, $startAt += $pagedIssueData->maxResults; $this->entityManager->flush(); - $this->entityManager->clear(); + $this->clear(); } while ($startAt < $total); $this->entityManager->flush(); - $this->entityManager->clear(); + $this->clear(); } /** @@ -320,8 +309,6 @@ public function syncIssuesForProject(int $projectId, DataProvider $dataProvider, */ public function syncWorklogsForProject(int $projectId, DataProvider $dataProvider, ?callable $progressCallback = null): void { - $dataProviderId = $dataProvider->getId(); - $service = $this->dataProviderService->getService($dataProvider); $project = $this->projectRepository->find($projectId); @@ -359,17 +346,11 @@ public function syncWorklogsForProject(int $projectId, DataProvider $dataProvide $worklogData = $service->getWorklogDataCollection($projectTrackerId); $worklogsAdded = 0; + $worklogsCount = count($worklogData->worklogData); foreach ($worklogData->worklogData as $worklogDatum) { - $project = $this->projectRepository->find($projectId); + $project = $this->getProject($project); - if (!$project) { - throw new EconomicsException($this->translator->trans('exception.project_not_found')); - } - - $issue = !empty($worklogDatum->projectTrackerIssueId) ? $this->issueRepository->findOneBy([ - 'projectTrackerId' => $worklogDatum->projectTrackerIssueId, - 'dataProvider' => $dataProvider, - ]) : null; + $issue = $this->getIssue($worklogDatum->projectTrackerIssueId, $dataProvider); if (null === $issue) { // A worklog should always have an issue, so ignore the worklog. @@ -384,8 +365,6 @@ public function syncWorklogsForProject(int $projectId, DataProvider $dataProvide if (!$worklog) { $worklog = new Worklog(); - $dataProvider = $this->dataProviderRepository->find($dataProviderId); - $worklog->setDataProvider($dataProvider); $this->entityManager->persist($worklog); @@ -400,12 +379,8 @@ public function syncWorklogsForProject(int $projectId, DataProvider $dataProvide ->setTimeSpentSeconds($worklogDatum->timeSpentSeconds) ->setTimeSpentSeconds($worklogDatum->timeSpentSeconds) ->setIssue($issue) - ->setKind(BillableKindsEnum::tryFrom($worklogDatum->kind)); - - if (null != $worklog->getProjectTrackerIssueId()) { - $issue = $this->issueRepository->findOneBy(['projectTrackerId' => $worklog->getProjectTrackerIssueId()]); - $worklog->setIssue($issue); - } + ->setKind(BillableKindsEnum::tryFrom($worklogDatum->kind)) + ->setIssue($issue); if (null === $worklog->isBilled()) { $worklog->setIsBilled(false); @@ -420,7 +395,7 @@ public function syncWorklogsForProject(int $projectId, DataProvider $dataProvide } if (null !== $progressCallback) { - $progressCallback($worklogsAdded, count($worklogData->worklogData)); + $progressCallback($worklogsAdded, $worklogsCount); ++$worklogsAdded; } @@ -428,21 +403,13 @@ public function syncWorklogsForProject(int $projectId, DataProvider $dataProvide $workerEmail = $worklog->getWorker(); if ($workerEmail && filter_var($workerEmail, FILTER_VALIDATE_EMAIL)) { - $worker = $this->workerRepository->findOneBy(['email' => $workerEmail]); - - if (!$worker) { - $worker = new Worker(); - $worker->setEmail($workerEmail); - - $this->entityManager->persist($worker); - $this->entityManager->flush(); - } + $this->getWorker($workerEmail); } // Flush and clear for each batch. if (0 === $worklogsAdded % self::BATCH_SIZE) { $this->entityManager->flush(); - $this->entityManager->clear(); + $this->clear(); } } @@ -454,7 +421,7 @@ public function syncWorklogsForProject(int $projectId, DataProvider $dataProvide } $this->entityManager->flush(); - $this->entityManager->clear(); + $this->clear(); } /** @@ -555,4 +522,106 @@ public function migrateEpics(?callable $progressCallback = null): void // Save changes to the database $this->entityManager->flush(); } + + private function getWorker(string $email): Worker + { + if (isset($this->workers[$email])) { + return $this->workers[$email]; + } + + $worker = $this->workerRepository->findOneBy([ + 'email' => $email, + ]); + + if (null === $worker) { + $worker = new Worker(); + $worker->setEmail($email); + $this->entityManager->persist($worker); + } + + $this->workers[$email] = $worker; + + return $worker; + } + + private function getIssue(string $projectTrackerIssueId, DataProvider $dataProvider): ?Issue + { + if (isset($this->issues[$projectTrackerIssueId])) { + return $this->issues[$projectTrackerIssueId]; + } + + $issue = $this->issueRepository->findOneBy([ + 'projectTrackerId' => $projectTrackerIssueId, + 'dataProvider' => $dataProvider, + ]); + + $this->issues[$projectTrackerIssueId] = $issue; + + return $issue; + } + + private function getVersion(string $projectTrackerId, DataProvider $dataProvider): ?Version + { + if (isset($this->versions[$projectTrackerId])) { + return $this->versions[$projectTrackerId]; + } + + $version = $this->versionRepository->findOneBy([ + 'projectTrackerId' => $projectTrackerId, + 'dataProvider' => $dataProvider, + ]); + + $this->versions[$projectTrackerId] = $version; + + return $version; + } + + private function getEpic(string $title): ?Epic + { + if (isset($this->epics[$title])) { + return $this->epics[$title]; + } + + $epic = $this->epicRepository->findOneBy(['title' => $title]); + + $this->epics[$title] = $epic; + + return $epic; + } + + private function getProject(Project $project): Project + { + if (!$this->entityManager->contains($project)) { + $project = $this->projectRepository->find($project->getId()); + } + + if (null === $project) { + throw new \RuntimeException('Project not found'); + } + + return $project; + } + + private function getDataProvider(DataProvider $dataProvider): DataProvider + { + if (!$this->entityManager->contains($dataProvider)) { + $dataProvider = $this->dataProviderRepository->find($dataProvider->getId()); + } + + if (null === $dataProvider) { + throw new \RuntimeException('DataProvider not found'); + } + + return $dataProvider; + } + + private function clear(): void + { + $this->workers = []; + $this->issues = []; + $this->versions = []; + $this->epics = []; + + $this->entityManager->clear(); + } } diff --git a/templates/components/sync-status.html.twig b/templates/components/sync-status.html.twig index fc9366d7e..8cda2521d 100644 --- a/templates/components/sync-status.html.twig +++ b/templates/components/sync-status.html.twig @@ -1,7 +1,8 @@