Commit 479db3f3 authored by Richard Haeser's avatar Richard Haeser Committed by Susanne Moog
Browse files

[FEATURE] Add bar- and doughnut-graph widgets to dashboard

You can now create widgets for the dashboard showing a bar- or
doughnut-graphs. As an example two new widgets are introduced
showing information about the number of errors in the sys_log
and about the number of normal backend users vs admin users.

Resolves: #90440
Releases: master
Change-Id: I5f4bb0201415434560c3c7297fefa7e897973be2
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/63328


Tested-by: Daniel Siepmann's avatarDaniel Siepmann <coding@daniel-siepmann.de>
Tested-by: Daniel Goerz's avatarDaniel Goerz <daniel.goerz@posteo.de>
Tested-by: default avatarTYPO3com <noreply@typo3.com>
Tested-by: Susanne Moog's avatarSusanne Moog <look@susi.dev>
Reviewed-by: Daniel Goerz's avatarDaniel Goerz <daniel.goerz@posteo.de>
Reviewed-by: Susanne Moog's avatarSusanne Moog <look@susi.dev>
parent 3db01f46
......@@ -481,12 +481,14 @@ module.exports = function (grunt) {
},
dashboard: {
options: {
destPrefix: "<%= paths.dashboard %>Public/JavaScript/Contrib"
destPrefix: "<%= paths.dashboard %>Public"
},
files: {
'muuri.js': 'muuri/dist/muuri.min.js',
'web-animations.min.js': 'web-animations-js/web-animations.min.js',
'web-animations.min.js.map': 'web-animations-js/web-animations.min.js.map'
'JavaScript/Contrib/muuri.js': 'muuri/dist/muuri.min.js',
'JavaScript/Contrib/chartjs.js': 'chart.js/dist/Chart.min.js',
'Css/Contrib/chart.css': 'chart.js/dist/Chart.min.css',
'JavaScript/Contrib/web-animations.min.js': 'web-animations-js/web-animations.min.js',
'JavaScript/Contrib/web-animations.min.js.map': 'web-animations-js/web-animations.min.js.map'
}
},
all: {
......
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
import * as $ from 'jquery';
let Chart: any = require('TYPO3/CMS/Dashboard/Contrib/chartjs');
class ChartInitializer {
private selector: string = '.dashboard-item--chart';
constructor() {
$((): void => {
this.initialize();
});
}
public initialize(): void {
const me = this;
$(document).on('widgetContentRendered', me.selector, (e: JQueryEventObject, config: any): void => {
e.preventDefault();
const $me = $(e.currentTarget);
if (typeof undefined === config.graphConfig) {
return;
}
let _canvas: any = $me.find('canvas:first');
let context;
if (_canvas.length > 0) {
context = _canvas[0].getContext('2d');
}
if (typeof undefined === context) {
return;
}
new Chart(context, config.graphConfig)
});
}
}
export = new ChartInitializer();
......@@ -32,6 +32,7 @@
"autosize": "^4.0.2",
"bootstrap-sass": "^3.4.1",
"bootstrap-slider": "^9.7.3",
"chart.js": "^2.9.3",
"chosen-js": "^1.8.7",
"ckeditor-wordcount-plugin": "^1.17.2",
"ckeditor4": "^4.13.0",
......
......@@ -1280,6 +1280,29 @@ chardet@^0.7.0:
resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e"
integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==
chart.js@^2.9.3:
version "2.9.3"
resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-2.9.3.tgz#ae3884114dafd381bc600f5b35a189138aac1ef7"
integrity sha512-+2jlOobSk52c1VU6fzkh3UwqHMdSlgH1xFv9FKMqHiNCpXsGPQa/+81AFa+i3jZ253Mq9aAycPwDjnn1XbRNNw==
dependencies:
chartjs-color "^2.1.0"
moment "^2.10.2"
chartjs-color-string@^0.6.0:
version "0.6.0"
resolved "https://registry.yarnpkg.com/chartjs-color-string/-/chartjs-color-string-0.6.0.tgz#1df096621c0e70720a64f4135ea171d051402f71"
integrity sha512-TIB5OKn1hPJvO7JcteW4WY/63v6KwEdt6udfnDE9iCAZgy+V4SrbSxoIbTw/xkUIapjEI4ExGtD0+6D3KyFd7A==
dependencies:
color-name "^1.0.0"
chartjs-color@^2.1.0:
version "2.4.1"
resolved "https://registry.yarnpkg.com/chartjs-color/-/chartjs-color-2.4.1.tgz#6118bba202fe1ea79dd7f7c0f9da93467296c3b0"
integrity sha512-haqOg1+Yebys/Ts/9bLo/BqUcONQOdr/hoEr2LLTRl6C5LXctUdHxsCYfvQVg5JIxITrfCNUDr4ntqmQk9+/0w==
dependencies:
chartjs-color-string "^0.6.0"
color-convert "^1.9.3"
chokidar@^3.0.0:
version "3.1.1"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.1.1.tgz#27e953f3950336efcc455fd03e240c7299062003"
......@@ -1501,7 +1524,7 @@ collection-visit@^1.0.0:
map-visit "^1.0.0"
object-visit "^1.0.0"
color-convert@^1.9.0:
color-convert@^1.9.0, color-convert@^1.9.3:
version "1.9.3"
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"
integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==
......@@ -1518,6 +1541,11 @@ color-name@1.1.3:
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=
color-name@^1.0.0:
version "1.1.4"
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
colorette@^1.0.5:
version "1.1.0"
resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.1.0.tgz#1f943e5a357fac10b4e0f5aaef3b14cdc1af6ec7"
......@@ -6162,7 +6190,7 @@ moment-timezone@^0.5.27:
resolved "https://registry.yarnpkg.com/moment/-/moment-2.22.2.tgz#3c257f9839fc0e93ff53149632239eb90783ff66"
integrity sha1-PCV/mDn8DpP/UxSWMiOeuQeD/2Y=
moment@^2.24.0:
moment@^2.10.2, moment@^2.24.0:
version "2.24.0"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b"
integrity sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==
......
......@@ -35,6 +35,8 @@ The following widgets are shipped by core extensions now:
* Getting started with TYPO3: This widget will provide a link to the Getting Started Tutorial (EXT:dashboard)
* TypoScript Template Reference: This widget will provide a link to the TypoScript Template Reference (EXT:dashboard)
* TSconfig Reference: This widget will provide a link to the TSconfig Reference (EXT:dashboard)
* Number of errors in system log: Shows the number of errors in the sys_log grouped by day for the last month (EXT:dashboard)
* Type of backend users: A widget to show the different types of backend users
Creating your own widget
^^^^^^^^^^^^^^^^^^^^^^^^
......@@ -45,6 +47,9 @@ do so, you can extend one of the WidgetAbstracts available in EXT:dashboard.
* AbstractRssWidget: with this abstract it is easy to create a widget showing a RSS feed
* AbstractListWidget: this abstract will give you an easy start to show a list of items
* AbstractCtaButtonWidget: when you want to show a Call-To-Action button, this is the right abstract
* AbstractChartWidget: the base of all chart widgets
* AbstractBarChartWidget: when you want to show a widget with a bar-chart you can extend this class
* AbstractDoughnutChartWidget: this abstract gives you the possibility to create a doughnut-chart widget
By extending one of those abstracts, and provide it with the right data, you are able to
have a new widget quite fast. The only thing that is left is to register the widget.
......
<?php
declare(strict_types = 1);
namespace TYPO3\CMS\Dashboard\Widgets;
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
/**
* The AbstractBarChartWidget class is the basic widget class for bar charts.
* It is possible to extend this class for custom widgets.
* In your class you have to store the data to display in $this->chartData
* More information can be found in the documentation.
*/
abstract class AbstractBarChartWidget extends AbstractChartWidget
{
protected $iconIdentifier = 'content-widget-chart-bar';
protected $chartType = 'bar';
protected $chartOptions = [
'maintainAspectRatio' => false,
'legend' => [
'display' => false
],
'scales' => [
'yAxes' => [
[
'ticks' => [
'beginAtZero' => true
]
]
],
'xAxes' => [
[
'ticks' => [
'maxTicksLimit' => 15
]
]
]
]
];
}
<?php
declare(strict_types = 1);
namespace TYPO3\CMS\Dashboard\Widgets;
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
use TYPO3\CMS\Dashboard\Widgets\Interfaces\AdditionalCssInterface;
use TYPO3\CMS\Dashboard\Widgets\Interfaces\EventDataInterface;
use TYPO3\CMS\Dashboard\Widgets\Interfaces\RequireJsModuleInterface;
/**
* The AbstractChartWidget class is the basic widget class for all chart widgets
* It is possible to extend this class for custom widgets. EXT:dashboard also provides
* more special chart types for widgets (bar chart and doughnut chart).
*/
abstract class AbstractChartWidget extends AbstractWidget implements AdditionalCssInterface, RequireJsModuleInterface, EventDataInterface
{
/**
* The type of chart you want to show. The types of charts that are available can be found in the
* chart.js documentation.
*
* @link https://www.chartjs.org/docs/latest/charts/
*
* Currently the following chart types are implemented:
* @see AbstractBarChartWidget
* @see AbstractDoughnutChartWidget
*
* @var string
*/
protected $chartType = '';
/**
* This array should contain the data for the graph. The data and options you have depend on the type
* of chart. More information can be found in the documentation of the specific type.
*
* @link https://www.chartjs.org/docs/latest/charts/bar.html#data-structure
* @link https://www.chartjs.org/docs/latest/charts/doughnut.html#data-structure
*
* @var array
*/
protected $chartData = [];
/**
* This property should contain the options to configure your graph. The options available are based on the type
* of chart. The implementations of the charts will contain the default options for that type of graph.
*
* @see AbstractBarChartWidget::$chartOptions
* @see AbstractDoughnutChartWidget::$chartOptions
*
* @var array
*/
protected $chartOptions = [];
/**
* This property can be used to pass data to the JavaScript that will handle the content rendering. For
* charts, the only property necessary for charts is the graphConfig element. This will be set in the
* getEventData method of this class. Setting this property manual is therefore not needed.
*
* @see getEventData
*
* @var array
*/
protected $eventData = [];
/**
* The default colors that will be used for the graphs.
*
* @var array
*/
protected $chartColors = ['#ff8700', '#a4276a', '#1a568f', '#4c7e3a', '#69bbb5'];
/**
* If you want to show a button below the graph, you need to set the title and the link to the button. This text
* can be a fixed string or can contain a translatable string.
*
* @var string
*/
protected $buttonText = '';
/**
* The link of the button. Besides the link, also the buttonText should be set before the buttons will be
* rendered.
*
* @var string
*/
protected $buttonLink = '';
/**
* By default the link of the button will be opened in the current frame. By setting the target, you can specify
* where the link should be opened.
*
* @var string
*/
protected $buttonTarget = '';
/**
* This CSS-class is used so the JavaScript can identify the widgets containing graphs. Overriding this
* property could lead to the widget not working.
*
* @internal
*
* @var string
*/
protected $additionalClasses = 'dashboard-item--chart';
/**
* @inheritDoc
*/
protected $iconIdentifier = 'content-widget-chart';
/**
* @inheritDoc
*/
protected $templateName = 'ChartWidget';
/**
* This method is used to define the data that will be shown in the graph. This method should be implemented and
* should set the data in the chartData property
*
* @see chartData
*/
abstract protected function prepareChartData(): void;
protected function initializeView(): void
{
parent::initializeView();
if ($this->buttonLink && $this->buttonText) {
$this->view->assign(
'button',
[
'text' => $this->getLanguageService()->sL($this->buttonText) ?: $this->buttonText,
'link' => $this->buttonLink,
'target' => $this->buttonTarget
]
);
}
}
public function getEventData(): array
{
$this->prepareChartData();
$this->eventData['graphConfig'] = [
'type' => $this->chartType,
'data' => $this->chartData,
'options' => $this->chartOptions
];
return $this->eventData;
}
public function getCssFiles(): array
{
return ['EXT:dashboard/Resources/Public/Css/Contrib/chart.css'];
}
public function getRequireJsModules(): array
{
return [
'TYPO3/CMS/Dashboard/Contrib/chartjs',
'TYPO3/CMS/Dashboard/ChartInitializer',
];
}
}
<?php
declare(strict_types = 1);
namespace TYPO3\CMS\Dashboard\Widgets;
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
/**
* The AbstractDoughnutChartWidget class is the basic widget class for doughnut charts.
* It is possible to extend this class for custom widgets.
* In your class you have to store the data to display in $this->chartData
* More information can be found in the documentation.
*/
abstract class AbstractDoughnutChartWidget extends AbstractChartWidget
{
protected $iconIdentifier = 'content-widget-chart-pie';
protected $chartType = 'doughnut';
protected $chartOptions = [
'maintainAspectRatio' => false,
'legend' => [
'display' => true,
'position' => 'bottom'
],
'cutoutPercentage' => 60
];
protected $width = 2;
protected $height = 4;
}
......@@ -3,6 +3,19 @@ declare(strict_types = 1);
namespace TYPO3\CMS\Dashboard\Widgets;
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
use RuntimeException;
use TYPO3\CMS\Core\Cache\CacheManager;
use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface;
......
<?php
declare(strict_types = 1);
namespace TYPO3\CMS\Dashboard\Widgets;
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
use TYPO3\CMS\Backend\Routing\UriBuilder;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
use TYPO3\CMS\Core\Utility\GeneralUtility;
class SysLogErrorsWidget extends AbstractBarChartWidget
{
protected $title = 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.sysLogErrors.title';
protected $description = 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.sysLogErrors.description';
protected $buttonText = 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.sysLogErrors.buttonText';
protected $width = 4;
protected $height = 4;
protected $data = [];
protected $labels = [];
protected function initializeView(): void
{
if (ExtensionManagementUtility::isLoaded('belog')) {
$uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
$this->buttonLink = (string)$uriBuilder->buildUriFromRoute(
'system_BelogLog',
['tx_belog_system_beloglog[constraint][action]' => -1]
);
}
parent::initializeView();
}
/**
* @inheritDoc
*/
protected function prepareChartData(): void
{
$this->calculateDataForLastDays(31);
$this->chartData = [
'labels' => $this->labels,
'datasets' => [
[
'label' => $this->getLanguageService()->sL('LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.sysLogErrors.chart.dataSet.0'),
'backgroundColor' => $this->chartColors[0],
'border' => 0,
'data' => $this->data
]
]
];
}
protected function getNumberOfErrorsInPeriod(int $start, int $end): int
{
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_log');
return (int)$queryBuilder
->count('*')
->from('sys_log')
->where(
$queryBuilder->expr()->eq('type', 5),
$queryBuilder->expr()->gte('tstamp', $start),
$queryBuilder->expr()->lte('tstamp', $end)
)
->execute()
->fetchColumn();
}
protected function calculateDataForLastDays(int $days): void
{
$format = $GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'] ?: 'Y-m-d';
for ($daysBefore=$days; $daysBefore>0; $daysBefore--) {
$this->labels[] = date($format, strtotime('-' . $daysBefore . ' day'));
$startPeriod = strtotime('-' . $daysBefore . ' day 0:00:00');
$endPeriod = strtotime('-' . $daysBefore . ' day 23:59:59');
$this->data[] = $this->getNumberOfErrorsInPeriod($startPeriod, $endPeriod);
}
}
}
<?php
declare(strict_types = 1);
namespace TYPO3\CMS\Dashboard\Widgets;
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Utility\GeneralUtility;
class TypeOfUsersWidget extends AbstractDoughnutChartWidget
{
protected $title = 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.typeOfUsers.title';
protected $description = 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.typeOfUsers.description';
/**
* @inheritDoc
*/
protected function prepareChartData(): void
{
$adminUsers = $this->getNumberOfUsers(true);
$normalUsers = $this->getNumberOfUsers(false);
$this->chartData = [
'labels' => [
$this->getLanguageService()->sL('LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.typeOfUsers.normalUsers'),
$this->getLanguageService()->sL('LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.typeOfUsers.adminUsers')
],
'datasets' => [
[
'backgroundColor' => [$this->chartColors[0], $this->chartColors[1]],