Skip to content

Commit

Permalink
Merge pull request #2 from Orbitale/chart
Browse files Browse the repository at this point in the history
Start working on charts system
  • Loading branch information
Pierstoval authored Feb 1, 2020
2 parents aabf9dd + f74f931 commit 4151d9f
Show file tree
Hide file tree
Showing 12 changed files with 303 additions and 19 deletions.
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ ADMIN_PASSWORD='$argon2id$v=19$m=65536,t=4,p=1$N0R4Zi5hUWQ3QXB0bjVGdg$VsVcHzGRfG
Feel free to contribute 😉.

* Make many analytics dashboards (that's what this app is for in the first place, probably with Highcharts).
* Support JS closures in Chart objects (by using a placeholder to remove quotes maybe?).
* Add a lot of fixtures to play with.
* Add translations for tags (maybe using an extension like gedmo or knp?).
* Implement more source file types like xls, ods, etc., that could be transformed to CSV before importing them. [PHPSpreadsheet](https://phpspreadsheet.readthedocs.io/) is already installed, though not used yet.
Expand All @@ -97,9 +98,10 @@ Feel free to contribute 😉.

* Operation tags (insurance, internet provider, car loan, etc.). Multiple tags per operation.
* Demo app at https://piers.ovh/compotes/ with credentials `admin`/`admin` and database reset every day.
* Added default tags (in French only for now)
* Docker setup with Compose
* Added tons of other commands to the Makefile
* Added default tags (in French only for now).
* Docker setup with Compose.
* Added tons of other commands to the Makefile.
* Made a first PoC for the analytics dashboard.

# License

Expand Down
4 changes: 2 additions & 2 deletions assets/js/app.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import '../css/app.css';
import * as Highcharts from "highcharts";

// import $ from 'jquery';
require('../css/app.css');

console.info('App startup');
global.Highcharts = Highcharts;
7 changes: 4 additions & 3 deletions config/packages/easy_admin.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@ easy_admin:
- { icon: sync, label: "Sync operations", route: apply_rules }

- label: ''
- { icon: ruler-vertical, entity: TagRule }
- { icon: tags, entity: Tag }
- { icon: money-check, entity: Operation }
- { icon: chart-bar, label: "Analytics", route: analytics }

- label: ''
- { icon: money-check, entity: Operation }
- { icon: ruler-vertical, entity: TagRule }
- { icon: tags, entity: Tag }

entities:
TagRule:
Expand Down
52 changes: 52 additions & 0 deletions src/Controller/AnalyticsController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<?php

declare(strict_types=1);

/*
* This file is part of the Compotes package.
*
* (c) Alex "Pierstoval" Rock <[email protected]>.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace App\Controller;

use App\Highcharts\Chart\TagUsageChart;
use App\Repository\OperationRepository;
use App\Repository\TagRepository;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Twig\Environment;

class AnalyticsController
{
private Environment $twig;
private TagRepository $tagRepository;
private OperationRepository $operationRepository;

public function __construct(
Environment $twig,
TagRepository $tagRepository,
OperationRepository $operationRepository
) {
$this->twig = $twig;
$this->tagRepository = $tagRepository;
$this->operationRepository = $operationRepository;
}

/**
* @Route("/admin/analytics", name="analytics")
*/
public function analytics(): Response
{
$operations = $this->operationRepository->findWithTags();

return new Response($this->twig->render('analytics.html.twig', [
'charts' => [
TagUsageChart::create($operations),
],
]));
}
}
14 changes: 7 additions & 7 deletions src/DataFixtures/TagFixtures.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ protected function getObjects(): array
['name' => 'Cheque-recu', 'parent' => $getParentClosure($earningsParent)],
['name' => 'Dividendes', 'parent' => $getParentClosure($earningsParent)],
['name' => 'Deblocage-emprunt', 'parent' => $getParentClosure($earningsParent)],
['name' => 'Depôt-argent', 'parent' => $getParentClosure($earningsParent)],
['name' => 'Depot-argent', 'parent' => $getParentClosure($earningsParent)],
['name' => 'Interets', 'parent' => $getParentClosure($earningsParent)],
['name' => 'Loyers', 'parent' => $getParentClosure($earningsParent)],
['name' => 'Pensions', 'parent' => $getParentClosure($earningsParent)],
Expand Down Expand Up @@ -96,12 +96,12 @@ protected function getObjects(): array
['name' => 'Pension-alimentaire', 'parent' => $getParentClosure($parent)],
['name' => 'Scolarite-etudes', 'parent' => $getParentClosure($parent)],

['name' => $parent = 'Impôts-et-taxes', 'parent' => $getParentClosure($expensesParent)],
['name' => $parent = 'Impots-et-taxes', 'parent' => $getParentClosure($expensesParent)],
['name' => 'Amendes', 'parent' => $getParentClosure($parent)],
['name' => 'Contributions-sociales-(csg-crds)', 'parent' => $getParentClosure($parent)],
['name' => 'Impôt-sur-la-fortune', 'parent' => $getParentClosure($parent)],
['name' => 'Impôt-sur-le-revenu', 'parent' => $getParentClosure($parent)],
['name' => 'Impôts-et-taxes-autres', 'parent' => $getParentClosure($parent)],
['name' => 'Contributions-sociales-csg-crds', 'parent' => $getParentClosure($parent)],
['name' => 'Impot-sur-la-fortune', 'parent' => $getParentClosure($parent)],
['name' => 'Impot-sur-le-revenu', 'parent' => $getParentClosure($parent)],
['name' => 'Impots-et-taxes-autres', 'parent' => $getParentClosure($parent)],
['name' => 'Taxe-habitation', 'parent' => $getParentClosure($parent)],
['name' => 'Taxe-fonciere', 'parent' => $getParentClosure($parent)],

Expand Down Expand Up @@ -135,7 +135,7 @@ protected function getObjects(): array

['name' => $parent = 'Transports-et-vehicules', 'parent' => $getParentClosure($expensesParent)],
['name' => 'Assurance-vehicule', 'parent' => $getParentClosure($parent)],
['name' => 'Billet-avion, billet de train', 'parent' => $getParentClosure($parent)],
['name' => 'Billet-avion-ou-train', 'parent' => $getParentClosure($parent)],
['name' => 'Carburant', 'parent' => $getParentClosure($parent)],
['name' => 'Credit-auto', 'parent' => $getParentClosure($parent)],
['name' => 'Entretien-vehicule', 'parent' => $getParentClosure($parent)],
Expand Down
26 changes: 26 additions & 0 deletions src/Highcharts/Chart/AbstractChart.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

declare(strict_types=1);

/*
* This file is part of the Compotes package.
*
* (c) Alex "Pierstoval" Rock <[email protected]>.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace App\Highcharts\Chart;

abstract class AbstractChart implements ChartInterface
{
public function getConfig(): array
{
return $this->getOptions() + ['series' => $this->getSeries()];
}

abstract protected function getSeries(): array;

abstract protected function getOptions(): array;
}
29 changes: 29 additions & 0 deletions src/Highcharts/Chart/ChartInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

declare(strict_types=1);

/*
* This file is part of the Compotes package.
*
* (c) Alex "Pierstoval" Rock <[email protected]>.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace App\Highcharts\Chart;

interface ChartInterface
{
public function getName(): string;

/**
* This corresponds to the options sent to the Highcharts js object.
*
* Config has to be convertible to JSON or JS.
* If your config contains closures, it will be rendered as a string (for now).
*
* @see https://api.highcharts.com/highcharts/
*/
public function getConfig(): array;
}
98 changes: 98 additions & 0 deletions src/Highcharts/Chart/TagUsageChart.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
<?php

declare(strict_types=1);

/*
* This file is part of the Compotes package.
*
* (c) Alex "Pierstoval" Rock <[email protected]>.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace App\Highcharts\Chart;

use App\Entity\Operation;

class TagUsageChart extends AbstractChart
{
/** * @var Operation[] */
private array $operations = [];

private function __construct()
{
}

public function getName(): string
{
return 'Tags';
}

public static function create($operations): self
{
$self = new self();

foreach ($operations as $operation) {
$self->addOperation($operation);
}

return $self;
}

protected function getOptions(): array
{
return [
'chart' => [
'type' => $type = 'bar',
'height' => 500,
],
'legend' => [
'align' => 'right',
'layout' => 'vertical',
],
'title' => ['text' => 'Tags usage'],
'xAxis' => [
'categories' => ['Tags'],
],
'yAxis' => [
'title' => ['text' => 'Number of operations with these tags'],
],
'plotOptions' => [
$type => [
'pointWidth' => 10,
'borderWidth' => 0,
'groupPadding' => 0.01,
],
],
];
}

protected function getSeries(): array
{
$series = [];

foreach ($this->operations as $operation) {
foreach ($operation->getTags() as $tag) {
$tagName = $tag->getName();
if (!isset($series[$tagName])) {
$series[$tagName] = [
'name' => $tagName,
'data' => [0],
];
}

$series[$tagName]['data'][0]++;
}
}

\ksort($series);

return \array_values($series);
}

private function addOperation(Operation $operation): void
{
$this->operations[] = $operation;
}
}
16 changes: 16 additions & 0 deletions src/Repository/OperationRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,20 @@ public function monthIsPopulated(DateTimeImmutable $month): bool

return $count > 0;
}

/**
* @return Operation[]
*/
public function findWithTags(): array
{
return $this->_em->createQuery(
<<<DQL
SELECT operation, tags
FROM {$this->_entityName} as operation
LEFT JOIN operation.tags as tags
DQL
)
->getResult()
;
}
}
44 changes: 44 additions & 0 deletions src/Twig/SlugifyExtension.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php

declare(strict_types=1);

/*
* This file is part of the Compotes package.
*
* (c) Alex "Pierstoval" Rock <[email protected]>.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace App\Twig;

use Symfony\Component\String\Slugger\SluggerInterface;
use Twig\Extension\AbstractExtension;
use Twig\TwigFilter;

class SlugifyExtension extends AbstractExtension
{
private SluggerInterface $slugger;

public function __construct(SluggerInterface $slugger)
{
$this->slugger = $slugger;
}

public function getFilters()
{
return [
new TwigFilter('slug', [$this, 'slugify']),
];
}

public function slugify($string): string
{
if (\is_object($string)) {
$string = (string) $string;
}

return $this->slugger->slug($string)->toString();
}
}
20 changes: 20 additions & 0 deletions templates/analytics.html.twig
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{% extends '@EasyAdmin/default/layout.html.twig' %}

{% block content_title %}
Analytics
{% endblock %}

{% block main %}
{% for chart in charts %}
<div id="{{ chart.name|slug }}"></div>
{% endfor %}
{% endblock %}

{% block body_custom_javascript %}
{{ parent() }}
<script type="text/javascript" async>
{% for chart in charts %}
Highcharts.chart("{{ chart.name|slug }}", {{ chart.config|json_encode|raw }});
{% endfor %}
</script>
{% endblock %}
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,13 @@
{% if (_entity_config.name|default('')) is same as('TagRule') %}
<script type="text/javascript">
$(function() {
// Select2 widget is only enabled for the <select> elements which
// explicitly ask for it
function init() {
$('form select[data-widget="select2"]').select2({
theme: 'bootstrap',
language: '{{ app.request.locale }}',
tags: true // ONLY THIS LINE IS ADDED, THE REST IS COPY/PASTED FROM EASYADMIN
});
}
$(document).on('easyadmin.collection.item-added', init);
init();
});
Expand Down

0 comments on commit 4151d9f

Please sign in to comment.