mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-09-02 04:53:49 +00:00
[wip] documentation of exports
This commit is contained in:
123
source/_static/code/exports/BirthdateFilter.php
Normal file
123
source/_static/code/exports/BirthdateFilter.php
Normal file
@@ -0,0 +1,123 @@
|
||||
<?php
|
||||
|
||||
namespace Chill\PersonBundle\Export\Filter;
|
||||
|
||||
use Chill\MainBundle\Export\FilterInterface;
|
||||
use Symfony\Component\Form\Extension\Core\Type\DateType;
|
||||
use Symfony\Component\Validator\Context\ExecutionContextInterface;
|
||||
use Symfony\Component\Validator\Constraints;
|
||||
use Symfony\Component\Form\FormEvent;
|
||||
use Symfony\Component\Form\FormEvents;
|
||||
use Doctrine\ORM\Query\Expr;
|
||||
use Chill\MainBundle\Form\Type\Export\FilterType;
|
||||
use Symfony\Component\Form\FormError;
|
||||
use Chill\MainBundle\Export\ExportElementValidatedInterface;
|
||||
|
||||
class BirthdateFilter implements FilterInterface, ExportElementValidatedInterface
|
||||
{
|
||||
// add specific role for this filter
|
||||
public function addRole()
|
||||
{
|
||||
// we do not need any new role for this filter, so we return null
|
||||
return null;
|
||||
}
|
||||
|
||||
// we give information on which type of export this filter applies
|
||||
public function applyOn()
|
||||
{
|
||||
return 'person';
|
||||
}
|
||||
|
||||
public function getTitle()
|
||||
{
|
||||
return 'Filter by person\'s birthdate';
|
||||
}
|
||||
|
||||
// we build a form to collect some parameters from the users
|
||||
public function buildForm(\Symfony\Component\Form\FormBuilderInterface $builder)
|
||||
{
|
||||
$builder->add('date_from', DateType::class, array(
|
||||
'label' => "Born after this date",
|
||||
'data' => new \DateTime(),
|
||||
'attr' => array('class' => 'datepicker'),
|
||||
'widget'=> 'single_text',
|
||||
'format' => 'dd-MM-yyyy',
|
||||
));
|
||||
|
||||
$builder->add('date_to', DateType::class, array(
|
||||
'label' => "Born before this date",
|
||||
'data' => new \DateTime(),
|
||||
'attr' => array('class' => 'datepicker'),
|
||||
'widget'=> 'single_text',
|
||||
'format' => 'dd-MM-yyyy',
|
||||
));
|
||||
|
||||
}
|
||||
|
||||
// the form created above must be validated. The process of validation
|
||||
// is executed here. This function is added by the interface
|
||||
// `ExportElementValidatedInterface`, and can be ignore if there is
|
||||
// no need for a validation
|
||||
public function validateForm($data, ExecutionContextInterface $context)
|
||||
{
|
||||
$date_from = $data['date_from'];
|
||||
$date_to = $data['date_to'];
|
||||
|
||||
if ($date_from === null) {
|
||||
$context->buildViolation('The "date from" should not be empty')
|
||||
//->atPath('date_from')
|
||||
->addViolation();
|
||||
}
|
||||
|
||||
if ($date_to === null) {
|
||||
$context->buildViolation('The "date to" should not be empty')
|
||||
//->atPath('date_to')
|
||||
->addViolation();
|
||||
}
|
||||
|
||||
if (
|
||||
($date_from !== null && $date_to !== null)
|
||||
&&
|
||||
$date_from >= $date_to
|
||||
) {
|
||||
$context->buildViolation('The date "date to" should be after the '
|
||||
. 'date given in "date from" field')
|
||||
->addViolation();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// here, we alter the query created by Export
|
||||
public function alterQuery(\Doctrine\ORM\QueryBuilder $qb, $data)
|
||||
{
|
||||
$where = $qb->getDQLPart('where');
|
||||
// we create the clause here
|
||||
$clause = $qb->expr()->between('person.birthdate', ':date_from',
|
||||
':date_to');
|
||||
|
||||
// we have to take care **not to** remove previous clauses...
|
||||
if ($where instanceof Expr\Andx) {
|
||||
$where->add($clause);
|
||||
} else {
|
||||
$where = $qb->expr()->andX($clause);
|
||||
}
|
||||
|
||||
$qb->add('where', $where);
|
||||
// we add parameters from $data. $data contains the parameters from the form
|
||||
$qb->setParameter('date_from', $data['date_from']);
|
||||
$qb->setParameter('date_to', $data['date_to']);
|
||||
}
|
||||
|
||||
// here, we create a simple string which will describe the action of
|
||||
// the filter in the Response
|
||||
public function describeAction($data, $format = 'string')
|
||||
{
|
||||
return array('Filtered by person\'s birtdate: '
|
||||
. 'between %date_from% and %date_to%', array(
|
||||
'%date_from%' => $data['date_from']->format('d-m-Y'),
|
||||
'%date_to%' => $data['date_to']->format('d-m-Y')
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
}
|
118
source/_static/code/exports/CountPerson.php
Normal file
118
source/_static/code/exports/CountPerson.php
Normal file
@@ -0,0 +1,118 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace Chill\PersonBundle\Export\Export;
|
||||
|
||||
use Chill\MainBundle\Export\ExportInterface;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Doctrine\ORM\Query;
|
||||
use Chill\PersonBundle\Security\Authorization\PersonVoter;
|
||||
use Symfony\Component\Security\Core\Role\Role;
|
||||
use Chill\PersonBundle\Export\Declarations;
|
||||
use Chill\MainBundle\Export\FormatterInterface;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @author Julien Fastré <julien.fastre@champs-libres.coop>
|
||||
*/
|
||||
class CountPerson implements ExportInterface
|
||||
{
|
||||
/**
|
||||
*
|
||||
* @var EntityManagerInterface
|
||||
*/
|
||||
protected $entityManager;
|
||||
|
||||
public function __construct(
|
||||
EntityManagerInterface $em
|
||||
)
|
||||
{
|
||||
$this->entityManager = $em;
|
||||
}
|
||||
|
||||
public function getType()
|
||||
{
|
||||
return Declarations::PERSON_TYPE;
|
||||
}
|
||||
|
||||
public function getDescription()
|
||||
{
|
||||
return "Count peoples by various parameters.";
|
||||
}
|
||||
|
||||
public function getTitle()
|
||||
{
|
||||
return "Count peoples";
|
||||
}
|
||||
|
||||
public function requiredRole()
|
||||
{
|
||||
return new Role(PersonVoter::STATS);
|
||||
}
|
||||
|
||||
public function initiateQuery(array $requiredModifiers, array $acl, array $data = array())
|
||||
{
|
||||
// we gather all center the user choose.
|
||||
$centers = array_map(function($el) { return $el['center']; }, $acl);
|
||||
|
||||
$qb = $this->entityManager->createQueryBuilder();
|
||||
|
||||
$qb->select('COUNT(person.id) AS export_result')
|
||||
->from('ChillPersonBundle:Person', 'person')
|
||||
->join('person.center', 'center')
|
||||
->andWhere('center IN (:authorized_centers)')
|
||||
->setParameter('authorized_centers', $centers);
|
||||
;
|
||||
|
||||
|
||||
return $qb;
|
||||
}
|
||||
|
||||
public function getResult($qb, $data)
|
||||
{
|
||||
return $qb->getQuery()->getResult(Query::HYDRATE_SCALAR);
|
||||
}
|
||||
|
||||
public function getQueryKeys($data)
|
||||
{
|
||||
// this array match the result keys in the query. We have only
|
||||
// one column.
|
||||
return array('export_result');
|
||||
}
|
||||
|
||||
public function getLabels($key, array $values, $data)
|
||||
{
|
||||
|
||||
// the Closure which will be executed by the formatter.
|
||||
return function($value) {
|
||||
switch($value) {
|
||||
case '_header':
|
||||
// we have to process specifically the '_header' string,
|
||||
// which will be used by the formatter to show a column title
|
||||
return $this->getTitle();
|
||||
default:
|
||||
// for all value, we do not process them and return them
|
||||
// immediatly
|
||||
return $value;
|
||||
};
|
||||
}
|
||||
|
||||
public function getAllowedFormattersTypes()
|
||||
{
|
||||
return array(FormatterInterface::TYPE_TABULAR);
|
||||
}
|
||||
|
||||
public function buildForm(FormBuilderInterface $builder) {
|
||||
// this export does not add any form
|
||||
}
|
||||
|
||||
public function supportsModifiers()
|
||||
{
|
||||
// explain the export manager which formatters and filters are allowed
|
||||
return array(Declarations::PERSON_TYPE, Declarations::PERSON_IMPLIED_IN);
|
||||
}
|
||||
|
||||
}
|
BIN
source/_static/puml/exports/form_steps.png
Normal file
BIN
source/_static/puml/exports/form_steps.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 70 KiB |
82
source/_static/puml/exports/form_steps.puml
Normal file
82
source/_static/puml/exports/form_steps.puml
Normal file
@@ -0,0 +1,82 @@
|
||||
@startuml
|
||||
|
||||
actor User
|
||||
participant Controller
|
||||
participant ExportFormType as eft
|
||||
participant ExportManager as em
|
||||
participant SelectedExport as se
|
||||
participant AggregatorFormType as aft
|
||||
collections aggregators as a
|
||||
participant FilterFormType as fft
|
||||
collections filters as f
|
||||
|
||||
|
||||
User -> Controller: request "/exports/new/<id of the export>?step=export"
|
||||
|
||||
activate Controller
|
||||
Controller -> eft: build the form (AbstractType::buildForm)
|
||||
|
||||
activate eft
|
||||
eft -> em: get the export
|
||||
|
||||
activate em
|
||||
em -> eft: return the SelectedExport
|
||||
deactivate em
|
||||
|
||||
eft -> se: get the Export Type
|
||||
|
||||
activate se
|
||||
se -> eft: return the export Type (a string)
|
||||
deactivate se
|
||||
|
||||
eft -> aft: build the subform 'aggregators'
|
||||
activate aft
|
||||
|
||||
aft -> em: get aggregators for this export type
|
||||
activate em
|
||||
em -> aft: return a collection of aggregators
|
||||
deactivate em
|
||||
|
||||
loop for each aggregator
|
||||
aft -> a: build eventual subform for the aggregator
|
||||
activate a
|
||||
a -> a: append enventually his form
|
||||
a -> aft
|
||||
deactivate a
|
||||
end
|
||||
|
||||
aft -> eft
|
||||
deactivate aft
|
||||
|
||||
eft -> fft: build the subform 'filters'
|
||||
activate fft
|
||||
|
||||
fft -> em: get filters for this export type
|
||||
activate em
|
||||
em -> fft: return a collection for filters
|
||||
deactivate em
|
||||
|
||||
loop for each filter
|
||||
fft -> f: build eventual subform for the filter
|
||||
activate f
|
||||
f -> f: append eventually his form
|
||||
f -> fft
|
||||
deactivate f
|
||||
end
|
||||
|
||||
fft -> eft
|
||||
deactivate fft
|
||||
|
||||
eft -> se: build eventual subform for the export itsefl
|
||||
activate se
|
||||
se -> se: append eventually his form
|
||||
se -> eft
|
||||
deactivate se
|
||||
|
||||
se -> Controller: return a well-build form
|
||||
deactivate se
|
||||
|
||||
Controller -> User: render the page with the form
|
||||
deactivate Controller
|
||||
@enduml
|
||||
|
BIN
source/_static/puml/exports/processing_export.png
Normal file
BIN
source/_static/puml/exports/processing_export.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 178 KiB |
164
source/_static/puml/exports/processing_export.puml
Normal file
164
source/_static/puml/exports/processing_export.puml
Normal file
@@ -0,0 +1,164 @@
|
||||
@startuml
|
||||
|
||||
participant Controller
|
||||
participant ExportManager as em
|
||||
participant SelectedExport as se
|
||||
participant Formatter as f
|
||||
collections aggregators as aa
|
||||
collections filters as ff
|
||||
|
||||
activate Controller
|
||||
Controller -> em: ExportManager::generate(string 'export_alias', Centers[], $data, $formatterdata)
|
||||
activate em
|
||||
note left
|
||||
$data contains the data
|
||||
from the export form
|
||||
(export form + aggregators form +
|
||||
filters forms)
|
||||
end note
|
||||
|
||||
|
||||
em -> se: Export::initiateQuery($modifiers, $acl, $data)
|
||||
activate se
|
||||
note left
|
||||
$modifiers contains the selected modifiers
|
||||
and their data. Usually you can ignore this.
|
||||
$acl contains the list of selected centers. The
|
||||
export must check that the user has the right
|
||||
to compute the export for this center
|
||||
$data the data from the export form (if the export
|
||||
build a form with `buildForm` function
|
||||
end note
|
||||
|
||||
create Query as q
|
||||
se -> q: the export will create a query
|
||||
se -> em: return the query
|
||||
deactivate se
|
||||
|
||||
alt The query is a "native sql query"
|
||||
|
||||
note over em, se
|
||||
When the export create a native query, there aren't any filters
|
||||
or aggregators which can alter the query.
|
||||
|
||||
We immediatly get the results from the query.
|
||||
end note
|
||||
|
||||
else "The query is a query builder"
|
||||
|
||||
note over em, se
|
||||
As the query is a query builder, filters and formatters
|
||||
will be able to alter the query.
|
||||
end note
|
||||
|
||||
loop for each filter selected by user
|
||||
em -> ff: FilterInterface::alterQuery(query, $data)
|
||||
activate ff
|
||||
note over em, ff
|
||||
The filter will alter query, adding his own clause (i.e. WHERE clause). It is
|
||||
his responsability to avoid removing clauses added by other filters / aggregators.
|
||||
|
||||
The ExportManager will also transmit the $data filled by the user (if the
|
||||
filter has a form) to each filter / aggregator.
|
||||
end note
|
||||
ff -> q: append some clauses
|
||||
deactivate ff
|
||||
end
|
||||
|
||||
loop for each aggregator selected by user
|
||||
note over em, aa
|
||||
As of filter, aggregators will append their
|
||||
own clauses in the query (usually GROUP BY clause).
|
||||
end note
|
||||
em -> aa: AggregatorInterface::alterQuery(query, data)
|
||||
activate aa
|
||||
|
||||
aa -> q: append some clauses
|
||||
deactivate aa
|
||||
end
|
||||
end alt
|
||||
|
||||
note over se
|
||||
The query is now ready. We will fetch the results from databases.
|
||||
|
||||
The Export is responsible for getting the results
|
||||
end note
|
||||
|
||||
em -> se: Export::getResult(query, $data)
|
||||
activate se
|
||||
|
||||
se -> q: getResult()
|
||||
activate q
|
||||
q -> se: return result
|
||||
destroy q
|
||||
|
||||
se -> em: return result
|
||||
deactivate se
|
||||
|
||||
em -> f: FormatterInterface::getResponse()
|
||||
activate f
|
||||
|
||||
note over f, ff
|
||||
The formatter will ask the export, and each aggregators the keys they
|
||||
are responsible for.
|
||||
|
||||
Then, for each of those keys, he will ask to each participant
|
||||
(Export or aggregator) a Closure, which will render the value in
|
||||
results into a human readable string.
|
||||
end note
|
||||
|
||||
f -> se: getQueryKeys
|
||||
activate se
|
||||
se -> f: return string[]
|
||||
|
||||
loop for each keys the export is responsible for
|
||||
f -> se: ExportInterface::getLabel()
|
||||
create "closure for key export" as closuree
|
||||
se -> closuree: create a closure
|
||||
se -> f: return closure
|
||||
end
|
||||
|
||||
loop for each aggregator
|
||||
f -> aa: getQueryKeys()
|
||||
activate aa
|
||||
aa -> f: return string[]
|
||||
deactivate aa
|
||||
|
||||
loop for each keys the aggregator is responsible for
|
||||
f -> aa: getLabel()
|
||||
activate aa
|
||||
create "closure for key aggregators" as closureg
|
||||
aa -> closureg: create a closure
|
||||
aa -> f: return closure
|
||||
deactivate aa
|
||||
end
|
||||
end
|
||||
|
||||
loop over results
|
||||
note over f, closureg
|
||||
Each row in result will be transformed in a human readable format by the closure.
|
||||
|
||||
The human readable string will be appended by the Formatter in his response (a Spreadsheet, CSV file, ...)
|
||||
end note
|
||||
|
||||
f -> closuree: call
|
||||
activate closuree
|
||||
closuree -> f: return a human readable format for this value
|
||||
deactivate closuree
|
||||
|
||||
f -> closureg: call
|
||||
activate closureg
|
||||
closureg -> f: return a human readable format for the value
|
||||
deactivate g
|
||||
|
||||
f -> f: append the result in his response
|
||||
end
|
||||
|
||||
f -> em: return a Response
|
||||
deactivate f
|
||||
|
||||
em -> Controller: return a Response
|
||||
deactivate em
|
||||
deactivate Controller
|
||||
|
||||
@enduml
|
BIN
source/_static/screenshots/development/export_form-fullpage.png
Normal file
BIN
source/_static/screenshots/development/export_form-fullpage.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 306 KiB |
Reference in New Issue
Block a user