Skip to content

Commit

Permalink
Wow, I did my first chart and it works!
Browse files Browse the repository at this point in the history
  • Loading branch information
Pierstoval committed Feb 1, 2020
1 parent de95454 commit f74f931
Show file tree
Hide file tree
Showing 13 changed files with 296 additions and 112 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
40 changes: 2 additions & 38 deletions assets/js/app.js
Original file line number Diff line number Diff line change
@@ -1,42 +1,6 @@
import '../css/app.css';
import * as Highcharts from "highcharts";

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

console.info('App startup');

(async function () {

const chartsContainer = document.getElementById('charts');

if (chartsContainer) {
try {
const filtersUrl = chartsContainer.getAttribute('data-filters-url');

const url = new URL(filtersUrl);
url.searchParams.append('filters', '...');

const result = await fetch(url);

if (result.status !== 200) {
throw new Error('Filters request returned an error.');
}

const json = await result.json();

console.info('Filters fetch result: ', json);

if (!json.highcharts) {
throw new Error('Highcharts field is not defined in response.');
}

const myChart = Highcharts.chart('charts', json.highcharts);

console.info('Chart: ', myChart);
} catch (e) {
console.error('Cannot fetch filters: '+e.message);
}

}

})();
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),
],
]));
}
}
58 changes: 0 additions & 58 deletions src/Controller/OperationChartController.php

This file was deleted.

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()
;
}
}
Loading

0 comments on commit f74f931

Please sign in to comment.