mirror of
https://github.com/code-rhapsodie/ibexa-dataflow-bundle.git
synced 2026-03-24 06:32:07 +01:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6ece645267 | ||
|
|
5180108598 | ||
|
|
08cd3eccd6 | ||
|
|
9dc90bb001 | ||
|
|
4b8554c64a |
12
CHANGELOG.md
12
CHANGELOG.md
@@ -1,3 +1,15 @@
|
||||
# Version 5.2.1
|
||||
* Fixed datepicker in oneshot modal
|
||||
|
||||
# Version 5.2.0
|
||||
* Added Dashboard tab
|
||||
|
||||
# Version 5.1.1
|
||||
* Add branding label
|
||||
|
||||
# Version 5.1.0
|
||||
* Added possibility to create one shot job from scheduled job
|
||||
|
||||
# Version 5.0.0
|
||||
|
||||
* Renamed bundle to IbexaDataflowBundle
|
||||
|
||||
@@ -13,6 +13,9 @@ use Symfony\Component\HttpKernel\Bundle\Bundle;
|
||||
|
||||
class CodeRhapsodieIbexaDataflowBundle extends Bundle
|
||||
{
|
||||
public const VERSION = '5.2.0';
|
||||
public const PRODUCT_NAME = 'ibexadataflow';
|
||||
|
||||
protected $name = 'CodeRhapsodieIbexaDataflowBundle';
|
||||
|
||||
public function getContainerExtension()
|
||||
|
||||
@@ -6,6 +6,7 @@ namespace CodeRhapsodie\IbexaDataflowBundle\Controller;
|
||||
|
||||
use CodeRhapsodie\DataflowBundle\Entity\Job;
|
||||
use CodeRhapsodie\DataflowBundle\Entity\ScheduledDataflow;
|
||||
use CodeRhapsodie\IbexaDataflowBundle\CodeRhapsodieIbexaDataflowBundle;
|
||||
use CodeRhapsodie\IbexaDataflowBundle\Form\CreateOneshotType;
|
||||
use CodeRhapsodie\IbexaDataflowBundle\Form\CreateScheduledType;
|
||||
use CodeRhapsodie\IbexaDataflowBundle\Form\UpdateScheduledType;
|
||||
@@ -14,6 +15,7 @@ use CodeRhapsodie\IbexaDataflowBundle\Gateway\JobGateway;
|
||||
use CodeRhapsodie\IbexaDataflowBundle\Gateway\ScheduledDataflowGateway;
|
||||
use Doctrine\DBAL\Query\QueryBuilder;
|
||||
use Ibexa\Contracts\AdminUi\Controller\Controller;
|
||||
use Ibexa\Contracts\Core\Ibexa;
|
||||
use Ibexa\Core\MVC\Symfony\Security\Authorization\Attribute;
|
||||
use Pagerfanta\Doctrine\DBAL\QueryAdapter;
|
||||
use Pagerfanta\Pagerfanta;
|
||||
@@ -33,7 +35,18 @@ class DashboardController extends Controller
|
||||
{
|
||||
$this->denyAccessUnlessGranted(new Attribute('ibexa_dataflow', 'view'));
|
||||
|
||||
return $this->render('@ibexadesign/ibexa_dataflow/Dashboard/main.html.twig');
|
||||
$data = [
|
||||
'product' => CodeRhapsodieIbexaDataflowBundle::PRODUCT_NAME,
|
||||
'version' => CodeRhapsodieIbexaDataflowBundle::VERSION,
|
||||
'php' => PHP_VERSION,
|
||||
'ibexa' => Ibexa::VERSION,
|
||||
];
|
||||
|
||||
return $this->render('@ibexadesign/ibexa_dataflow/Dashboard/main.html.twig', [
|
||||
'link' => 'https://www.code-rhapsodie.fr/product/redirect/'.str_replace('=', '',
|
||||
base64_encode(json_encode($data))
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
public function repeating(Request $request): Response
|
||||
@@ -117,7 +130,7 @@ class DashboardController extends Controller
|
||||
{
|
||||
$pager = new Pagerfanta(
|
||||
new ExceptionJSONDecoderAdapter(
|
||||
new QueryAdapter($query, fn($queryBuilder) => $queryBuilder->select('COUNT(DISTINCT id) AS total_results')
|
||||
new QueryAdapter($query, fn ($queryBuilder) => $queryBuilder->select('COUNT(DISTINCT id) AS total_results')
|
||||
->resetQueryPart('orderBy')
|
||||
->setMaxResults(1))
|
||||
)
|
||||
@@ -127,4 +140,13 @@ class DashboardController extends Controller
|
||||
|
||||
return $pager;
|
||||
}
|
||||
|
||||
public function dashboard(): Response
|
||||
{
|
||||
$this->denyAccessUnlessGranted(new Attribute('ibexa_dataflow', 'view'));
|
||||
|
||||
return $this->render('@ibexadesign/ibexa_dataflow/Dashboard/dashboard.html.twig', [
|
||||
'jobs' => $this->jobGateway->getListPendindOrRunning(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,12 +7,14 @@ namespace CodeRhapsodie\IbexaDataflowBundle\Controller;
|
||||
use CodeRhapsodie\DataflowBundle\Entity\Job;
|
||||
use CodeRhapsodie\IbexaDataflowBundle\Form\CreateOneshotType;
|
||||
use CodeRhapsodie\IbexaDataflowBundle\Gateway\JobGateway;
|
||||
use CodeRhapsodie\IbexaDataflowBundle\Gateway\ScheduledDataflowGateway;
|
||||
use Ibexa\Contracts\AdminUi\Controller\Controller;
|
||||
use Ibexa\Contracts\AdminUi\Notification\NotificationHandlerInterface;
|
||||
use Ibexa\Core\MVC\Symfony\Security\Authorization\Attribute;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
@@ -20,7 +22,7 @@ use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
#[Route(path: '/ibexa_dataflow/job')]
|
||||
class JobController extends Controller
|
||||
{
|
||||
public function __construct(private readonly JobGateway $jobGateway, private readonly NotificationHandlerInterface $notificationHandler, private readonly TranslatorInterface $translator)
|
||||
public function __construct(private readonly JobGateway $jobGateway, private readonly NotificationHandlerInterface $notificationHandler, private readonly TranslatorInterface $translator, private readonly ScheduledDataflowGateway $scheduledDataflowGateway)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -81,4 +83,35 @@ class JobController extends Controller
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
#[Route(path: '/run-oneshot/{id}', name: 'coderhapsodie.ibexa_dataflow.job.run-oneshot', methods: ['GET'])]
|
||||
public function runOneShot(int $id): Response
|
||||
{
|
||||
$this->denyAccessUnlessGranted(new Attribute('ibexa_dataflow', 'view'));
|
||||
|
||||
$scheduledDataflow = $this->scheduledDataflowGateway->find($id);
|
||||
|
||||
if ($scheduledDataflow === null) {
|
||||
throw new NotFoundHttpException();
|
||||
}
|
||||
|
||||
$newOneshotJob = new Job();
|
||||
$newOneshotJob->setOptions($scheduledDataflow->getOptions());
|
||||
$newOneshotJob->setLabel("Manual " . $scheduledDataflow->getLabel());
|
||||
$newOneshotJob->setScheduledDataflowId($scheduledDataflow->getId());
|
||||
$newOneshotJob->setRequestedDate((new \DateTime())->add(new \DateInterval('PT1H')));
|
||||
$newOneshotJob->setDataflowType($scheduledDataflow->getDataflowType());
|
||||
|
||||
$form = $this->createForm(CreateOneshotType::class, $newOneshotJob, [
|
||||
'action' => $this->generateUrl('coderhapsodie.ibexa_dataflow.job.create'),
|
||||
]);
|
||||
|
||||
return new JsonResponse([
|
||||
'form' => $this->renderView('@ibexadesign/ibexa_dataflow/parts/form_modal.html.twig', [
|
||||
'form' => $form->createView(),
|
||||
'id' => 'modal-new-oneshot',
|
||||
'mode' => 'oneshot',
|
||||
]),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,5 +54,15 @@ final class JobGateway
|
||||
{
|
||||
$this->jobRepository->save($job);
|
||||
}
|
||||
|
||||
public function getListPendindOrRunning(): array
|
||||
{
|
||||
$qb = $this->jobRepository->createQueryBuilder('w');
|
||||
return $qb->andWhere($qb->expr()->in('w.status', ':status'))
|
||||
->setParameter('status', implode(',',[Job::STATUS_RUNNING, Job::STATUS_PENDING, Job::STATUS_QUEUED]))
|
||||
->orderBy('w.requested_date', 'ASC')
|
||||
->execute()
|
||||
->fetchAllAssociative();
|
||||
}
|
||||
}
|
||||
class_alias(JobGateway::class, 'CodeRhapsodie\EzDataflowBundle\Gateway\JobGateway');
|
||||
|
||||
@@ -38,6 +38,7 @@ services:
|
||||
$jobGateway: '@CodeRhapsodie\IbexaDataflowBundle\Gateway\JobGateway'
|
||||
$notificationHandler: '@Ibexa\Contracts\AdminUi\Notification\NotificationHandlerInterface'
|
||||
$translator: '@translator'
|
||||
$scheduledDataflowGateway: '@CodeRhapsodie\IbexaDataflowBundle\Gateway\ScheduledDataflowGateway'
|
||||
calls:
|
||||
- [ 'setContainer', [ '@service_container' ] ]
|
||||
- [ 'performAccessCheck', [ ] ]
|
||||
@@ -128,6 +129,14 @@ services:
|
||||
arguments:
|
||||
$jobRepository: '@CodeRhapsodie\DataflowBundle\Repository\JobRepository'
|
||||
|
||||
CodeRhapsodie\IbexaDataflowBundle\Tab\DashboardTab:
|
||||
parent: Ibexa\Contracts\AdminUi\Tab\AbstractTab
|
||||
public: false
|
||||
arguments:
|
||||
$httpKernelRuntime: '@twig.runtime.httpkernel'
|
||||
tags:
|
||||
- { name: ibexa.admin_ui.tab, group: coderhapsodie-ibexa_dataflow }
|
||||
|
||||
CodeRhapsodie\IbexaDataflowBundle\Tab\RepeatingTab:
|
||||
parent: Ibexa\Contracts\AdminUi\Tab\AbstractTab
|
||||
public: false
|
||||
|
||||
@@ -16,6 +16,7 @@ coderhapsodie.ibexa_dataflow.workflow.list.history: History
|
||||
coderhapsodie.ibexa_dataflow.workflow.list.edit: Edit
|
||||
coderhapsodie.ibexa_dataflow.workflow.list.disable: Disable
|
||||
coderhapsodie.ibexa_dataflow.workflow.list.enable: Enable
|
||||
coderhapsodie.ibexa_dataflow.workflow.list.runonce: Run now
|
||||
coderhapsodie.ibexa_dataflow.history.title: History
|
||||
coderhapsodie.ibexa_dataflow.history.list.title: 'Executions list'
|
||||
coderhapsodie.ibexa_dataflow.history.list.name: Name
|
||||
@@ -82,3 +83,7 @@ coderhapsodie.dataflow.update.next: 'Next execution'
|
||||
coderhapsodie.ibexa_dataflow.workflow.edit.success: 'Dataflow schedule successfully updated.'
|
||||
coderhapsodie.ibexa_dataflow.workflow.edit.error: 'An error occurred during the dataflow schedule update: "%message%".'
|
||||
coderhapsodie.ibexa_dataflow.notfound: 'Requested data is not found'
|
||||
coderhapsodie.ibexa_dataflow.powered_by: 'Powered by'
|
||||
coderhapsodie.ibexa_dataflow.made_by: 'Made by'
|
||||
coderhapsodie.ibexa_dataflow.dashboard: Dashboard
|
||||
coderhapsodie.ibexa_dataflow.dashboard.title: Running or waiting tasks
|
||||
@@ -16,6 +16,7 @@ coderhapsodie.ibexa_dataflow.workflow.list.history: Historique
|
||||
coderhapsodie.ibexa_dataflow.workflow.list.edit: Éditer
|
||||
coderhapsodie.ibexa_dataflow.workflow.list.disable: Désactiver
|
||||
coderhapsodie.ibexa_dataflow.workflow.list.enable: Activer
|
||||
coderhapsodie.ibexa_dataflow.workflow.list.runonce: Lancer maintenant
|
||||
coderhapsodie.ibexa_dataflow.history.title: Historique
|
||||
coderhapsodie.ibexa_dataflow.history.list.title: 'Liste des exécutions'
|
||||
coderhapsodie.ibexa_dataflow.history.list.name: Nom
|
||||
@@ -80,3 +81,7 @@ coderhapsodie.dataflow.update.next: 'Prochaine exécution'
|
||||
coderhapsodie.ibexa_dataflow.workflow.edit.success: 'La programmation du dataflow a été mise à jour avec succès.'
|
||||
coderhapsodie.ibexa_dataflow.workflow.edit.error: 'Une erreur est survenue lors de la modification de la programmation du dataflow : "%message%".'
|
||||
coderhapsodie.ibexa_dataflow.notfound: 'Les données demandées sont introuvables'
|
||||
coderhapsodie.ibexa_dataflow.powered_by: 'Propulsé par'
|
||||
coderhapsodie.ibexa_dataflow.made_by: 'Fabriqué par'
|
||||
coderhapsodie.ibexa_dataflow.dashboard: Dashboard
|
||||
coderhapsodie.ibexa_dataflow.dashboard.title: Tâches en cours ou en attente
|
||||
@@ -0,0 +1,5 @@
|
||||
{%- block content -%}
|
||||
{% include '@ibexadesign/ibexa_dataflow/parts/tab/dashboard_list.html.twig' with {
|
||||
identifier: 'ibexa_dataflow_schedule_dashboard_results',
|
||||
} %}
|
||||
{%- endblock -%}
|
||||
File diff suppressed because one or more lines are too long
@@ -1,10 +1,11 @@
|
||||
{%- block content -%}
|
||||
{% set actions %}
|
||||
<button
|
||||
type="button"
|
||||
class="btn ibexa-btn ibexa-btn--tertiary ibexa-btn--small"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#modal-new-oneshot"
|
||||
id="create-oneshot-button"
|
||||
type="button"
|
||||
class="btn ibexa-btn ibexa-btn--tertiary ibexa-btn--small"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#modal-new-oneshot"
|
||||
>
|
||||
<svg class="ibexa-icon ibexa-icon--small ibexa-icon--create">
|
||||
<use xlink:href="{{ ibexa_icon_path('create') }}"></use>
|
||||
@@ -53,6 +54,16 @@
|
||||
})
|
||||
;
|
||||
});
|
||||
const createButton = document.getElementById('create-oneshot-button')
|
||||
|
||||
if (createButton) {
|
||||
createButton.addEventListener('click', () => {
|
||||
const oneShotModal = document.getElementById('modal-new-oneshot');
|
||||
oneShotModal.querySelector('#create_oneshot_label').value = '';
|
||||
oneShotModal.querySelector('#create_oneshot_options').value = '';
|
||||
oneShotModal.querySelector('.flatpickr.flatpickr-input').parentNode.parentNode.parentNode.ibexaInstance.flatpickrInstance.setDate(new Date(), true);
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{%- endblock -%}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
{% set id = identifier|default('ibexa_dataflow_history_results') %}
|
||||
|
||||
{% import '@ibexadesign/ibexa_dataflow/macros.twig' as macros %}
|
||||
|
||||
<div id="loading_{{ id }}" class="text-center" hidden>
|
||||
<svg class="ez-icon ez-icon--extra-large">
|
||||
<use xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xlink:href="{{ ibexa_icon_path('spinner') }}"></use>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div id="{{ id }}" data-loader="loading_{{ id }}" class="history-details-aware">
|
||||
|
||||
{% set body_rows = [] %}
|
||||
|
||||
{% for job in jobs %}
|
||||
{% set body_row_cols = [] %}
|
||||
{% set body_row_cols = body_row_cols|merge([
|
||||
{content: job.label},
|
||||
{content: date(job.requested_date)|ibexa_short_datetime},
|
||||
{content: job.start_time ? date(job.start_time)|ibexa_short_datetime : '—'},
|
||||
{content: macros.translateStatus(job.status)},
|
||||
]) %}
|
||||
|
||||
|
||||
{% set body_rows = body_rows|merge([{ cols: body_row_cols }]) %}
|
||||
{% endfor %}
|
||||
|
||||
{% embed '@ibexadesign/ui/component/table/table.html.twig' with {
|
||||
headline: 'coderhapsodie.ibexa_dataflow.dashboard.title'|trans,
|
||||
head_cols: [
|
||||
{ content: 'coderhapsodie.ibexa_dataflow.history.list.name'|trans },
|
||||
{ content: 'coderhapsodie.ibexa_dataflow.history.list.request'|trans },
|
||||
{ content: 'coderhapsodie.ibexa_dataflow.history.list.start'|trans },
|
||||
{ content: 'coderhapsodie.ibexa_dataflow.history.list.status'|trans },
|
||||
{ },
|
||||
],
|
||||
body_rows,
|
||||
empty_table_info_text: 'coderhapsodie.ibexa_dataflow.history.list.empty'|trans,
|
||||
} %}
|
||||
{% endembed %}
|
||||
</div>
|
||||
@@ -40,6 +40,15 @@
|
||||
<use xlink:href="{{ ibexa_icon_path('edit') }}"></use>
|
||||
</svg>
|
||||
</a>
|
||||
<button
|
||||
class="btn ibexa-btn ibexa-btn--ghost run-oneshot ibexa-btn--no-text"
|
||||
data-url="{{ path('coderhapsodie.ibexa_dataflow.job.run-oneshot', {id: item.id}) }}"
|
||||
title="{{ 'coderhapsodie.ibexa_dataflow.workflow.list.runonce'|trans }}"
|
||||
>
|
||||
<svg class="ibexa-icon ibexa-icon--small ibexa-icon--create">
|
||||
<use xlink:href="{{ ibexa_icon_path('create') }}"></use>
|
||||
</svg>
|
||||
</button>
|
||||
{% if item.enabled %}
|
||||
<a href="{{ path('coderhapsodie.ibexa_dataflow.workflow.disable', {id: item.id}) }}"
|
||||
class="btn ibexa-btn ibexa-btn--ghost ibexa-btn--no-text"
|
||||
@@ -96,10 +105,10 @@
|
||||
{% embed '@ibexadesign/ui/component/table/table_header.html.twig' %}
|
||||
{% block actions %}
|
||||
<button
|
||||
type="button"
|
||||
class="btn ibexa-btn ibexa-btn--tertiary ibexa-btn--small"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#modal-new-scheduled"
|
||||
type="button"
|
||||
class="btn ibexa-btn ibexa-btn--tertiary ibexa-btn--small"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#modal-new-scheduled"
|
||||
>
|
||||
<svg class="ibexa-icon ibexa-icon--small ibexa-icon--create">
|
||||
<use xlink:href="{{ ibexa_icon_path('create') }}"></use>
|
||||
@@ -125,3 +134,27 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.querySelectorAll(".run-oneshot").forEach((el) => {
|
||||
el.addEventListener('click', () => {
|
||||
const oneShotModal = document.getElementById('modal-new-oneshot');
|
||||
|
||||
fetch(el.getAttribute('data-url'))
|
||||
.then(response => response.json())
|
||||
.then(result => {
|
||||
const node = document.createElement('div');
|
||||
node.innerHTML = result.form;
|
||||
oneShotModal.querySelector('#create_oneshot_label').value = node.querySelector('#create_oneshot_label').value;
|
||||
oneShotModal.querySelector('#create_oneshot_options').value = node.querySelector('#create_oneshot_options').value;
|
||||
oneShotModal.querySelector('.ibexa-dropdown').ibexaInstance.selectOption(node.querySelector('#create_oneshot_dataflowType').value)
|
||||
oneShotModal.querySelector('.flatpickr.flatpickr-input').parentNode.parentNode.parentNode.ibexaInstance.flatpickrInstance.setDate(new Date(), true);
|
||||
})
|
||||
.then(() => {
|
||||
bootstrap.Tab.getOrCreateInstance(document.querySelector('#ibexa-tab-label-coderhapsodie-ibexa_dataflow-code-rhapsodie-ibexa_dataflow-oneshot')).show()
|
||||
bootstrap.Modal.getOrCreateInstance(oneShotModal).show()
|
||||
})
|
||||
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
45
src/Tab/DashboardTab.php
Normal file
45
src/Tab/DashboardTab.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace CodeRhapsodie\IbexaDataflowBundle\Tab;
|
||||
|
||||
use CodeRhapsodie\IbexaDataflowBundle\Controller\DashboardController;
|
||||
use Ibexa\Contracts\AdminUi\Tab\AbstractControllerBasedTab;
|
||||
use Ibexa\Contracts\AdminUi\Tab\OrderedTabInterface;
|
||||
use Symfony\Component\HttpKernel\Controller\ControllerReference;
|
||||
|
||||
class DashboardTab extends AbstractControllerBasedTab implements OrderedTabInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getControllerReference(array $parameters): ControllerReference
|
||||
{
|
||||
return new ControllerReference(DashboardController::class.'::dashboard');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getOrder(): int
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getIdentifier(): string
|
||||
{
|
||||
return 'code-rhapsodie-ibexa_dataflow-dashboard';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->translator->trans('coderhapsodie.ibexa_dataflow.dashboard');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user