added avatars, show user teams in list, new team dashboard widgets (#1150)

This commit is contained in:
Kevin Papst
2019-10-03 13:44:16 +02:00
committed by GitHub
parent 718ad4b398
commit e8c25d8ac5
120 changed files with 2585 additions and 642 deletions

View File

@@ -13,6 +13,9 @@ use App\Entity\Activity;
use App\Entity\Project;
use Doctrine\ORM\EntityManagerInterface;
/**
* @internal
*/
final class ActivityIdLoader implements LoaderInterface
{
/**

View File

@@ -12,6 +12,9 @@ namespace App\Repository\Loader;
use App\Entity\Customer;
use Doctrine\ORM\EntityManagerInterface;
/**
* @internal
*/
final class CustomerIdLoader implements LoaderInterface
{
/**

View File

@@ -12,6 +12,9 @@ namespace App\Repository\Loader;
use App\Entity\Project;
use Doctrine\ORM\EntityManagerInterface;
/**
* @internal
*/
final class ProjectIdLoader implements LoaderInterface
{
/**

View File

@@ -12,6 +12,9 @@ namespace App\Repository\Loader;
use App\Entity\Team;
use Doctrine\ORM\EntityManagerInterface;
/**
* @internal
*/
final class TeamIdLoader implements LoaderInterface
{
/**

View File

@@ -15,6 +15,9 @@ use App\Entity\Project;
use App\Entity\Timesheet;
use Doctrine\ORM\EntityManagerInterface;
/**
* @internal
*/
final class TimesheetIdLoader implements LoaderInterface
{
/**

View File

@@ -0,0 +1,76 @@
<?php
/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace App\Repository\Loader;
use App\Entity\Team;
use App\Entity\User;
use Doctrine\ORM\EntityManagerInterface;
/**
* @internal
*/
final class UserIdLoader implements LoaderInterface
{
/**
* @var EntityManagerInterface
*/
private $entityManager;
public function __construct(EntityManagerInterface $entityManager)
{
$this->entityManager = $entityManager;
}
/**
* @param int[] $ids
*/
public function loadResults(array $ids): void
{
if (empty($ids)) {
return;
}
$em = $this->entityManager;
$qb = $em->createQueryBuilder();
/** @var User[] $users */
$users = $qb->select('PARTIAL u.{id}', 'teams')
->from(User::class, 'u')
->leftJoin('u.teams', 'teams')
->andWhere($qb->expr()->in('u.id', $ids))
->getQuery()
->execute();
$teamIds = [];
foreach ($users as $user) {
foreach ($user->getTeams() as $team) {
$teamIds[] = $team->getId();
}
}
if (count($teamIds) > 0) {
$qb = $em->createQueryBuilder();
$qb->select('PARTIAL t.{id}', 'teamlead')
->from(Team::class, 't')
->leftJoin('t.teamlead', 'teamlead')
->andWhere($qb->expr()->in('t.id', $teamIds))
->getQuery()
->execute();
$qb = $em->createQueryBuilder();
$qb->select('PARTIAL t.{id}', 'users')
->from(Team::class, 't')
->leftJoin('t.users', 'users')
->andWhere($qb->expr()->in('t.id', $teamIds))
->getQuery()
->execute();
}
}
}

View File

@@ -0,0 +1,38 @@
<?php
/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace App\Repository\Loader;
use App\Entity\User;
use Doctrine\ORM\EntityManagerInterface;
final class UserLoader implements LoaderInterface
{
/**
* @var UserIdLoader
*/
private $loader;
public function __construct(EntityManagerInterface $entityManager)
{
$this->loader = new UserIdLoader($entityManager);
}
/**
* @param User[] $users
*/
public function loadResults(array $users): void
{
$ids = array_map(function (User $user) {
return $user->getId();
}, $users);
$this->loader->loadResults($ids);
}
}

View File

@@ -74,7 +74,7 @@ class ProjectRepository extends EntityRepository
public function getProjectStatistics(Project $project): ProjectStatistic
{
$stats = new ProjectStatistic();
$stats = new ProjectStatistic($project);
$qb = $this->getEntityManager()->createQueryBuilder();

View File

@@ -25,8 +25,17 @@ class BaseQuery
public const DEFAULT_PAGESIZE = 50;
public const DEFAULT_PAGE = 1;
/**
* @deprecated since 1.4, will be removed with 1.6
*/
public const RESULT_TYPE_OBJECTS = 'Objects';
/**
* @deprecated since 1.4, will be removed with 1.6
*/
public const RESULT_TYPE_PAGER = 'PagerFanta';
/**
* @deprecated since 1.4, will be removed with 1.6
*/
public const RESULT_TYPE_QUERYBUILDER = 'QueryBuilder';
private $defaults = [
@@ -54,6 +63,7 @@ class BaseQuery
private $order = self::ORDER_ASC;
/**
* @var string
* @deprecated since 1.4, will be removed with 1.6
*/
private $resultType = self::RESULT_TYPE_PAGER;
/**
@@ -179,28 +189,11 @@ class BaseQuery
*/
public function getResultType()
{
@trigger_error('BaseQuery::getResultType() is deprecated and will be removed with 1.6', E_USER_DEPRECATED);
return $this->resultType;
}
/**
* @deprecated since 1.0
* @param string $resultType
* @return $this
* @throws \InvalidArgumentException
*/
public function setResultType(string $resultType)
{
$allowed = [self::RESULT_TYPE_PAGER, self::RESULT_TYPE_QUERYBUILDER, self::RESULT_TYPE_OBJECTS];
if (!in_array($resultType, $allowed)) {
throw new \InvalidArgumentException('Unsupported query result type');
}
$this->resultType = $resultType;
return $this;
}
public function hasSearchTerm(): bool
{
return null !== $this->searchTerm;

View File

@@ -1,54 +0,0 @@
<?php
/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace App\Repository;
use App\Repository\Query\BaseQuery;
use Doctrine\ORM\Query;
use Doctrine\ORM\QueryBuilder;
use Pagerfanta\Adapter\DoctrineORMAdapter;
use Pagerfanta\Pagerfanta;
/**
* @deprecated since 1.0
*/
trait RepositoryTrait
{
/**
* @deprecated since 1.0
* @param QueryBuilder $qb
* @param BaseQuery $query
* @return QueryBuilder|Pagerfanta|array
*/
protected function getBaseQueryResult(QueryBuilder $qb, BaseQuery $query)
{
if (BaseQuery::RESULT_TYPE_PAGER === $query->getResultType()) {
return $this->getPager($qb->getQuery(), $query->getPage(), $query->getPageSize());
} elseif (BaseQuery::RESULT_TYPE_OBJECTS === $query->getResultType()) {
return $qb->getQuery()->execute();
}
return $qb;
}
/**
* @param Query $query
* @param int $page
* @param int $maxPerPage
* @return Pagerfanta
*/
private function getPager(Query $query, $page = 1, $maxPerPage = 25)
{
$paginator = new Pagerfanta(new DoctrineORMAdapter($query, false));
$paginator->setMaxPerPage($maxPerPage);
$paginator->setCurrentPage($page);
return $paginator;
}
}

View File

@@ -341,7 +341,10 @@ class TimesheetRepository extends EntityRepository
}
$duration = $newDateBegin->getTimestamp() - $beginTmp->getTimestamp();
$durationPercent = $duration / $result->getDuration();
$durationPercent = 0;
if ($result->getDuration() !== null && $result->getDuration() > 0) {
$durationPercent = $duration / $result->getDuration();
}
$rate = $result->getRate() * $durationPercent;
$results[$dateKey]['rate'] += $rate;
@@ -605,7 +608,9 @@ class TimesheetRepository extends EntityRepository
if (!empty($query->getTeams())) {
foreach ($query->getTeams() as $team) {
$user = array_merge($user, $team->getUsers()->toArray());
foreach ($team->getUsers() as $teamUser) {
$user[] = $teamUser;
}
}
}

View File

@@ -10,16 +10,20 @@
namespace App\Repository;
use App\Entity\User;
use App\Repository\Loader\UserLoader;
use App\Repository\Paginator\LoaderPaginator;
use App\Repository\Paginator\PaginatorInterface;
use App\Repository\Query\BaseQuery;
use App\Repository\Query\UserFormTypeQuery;
use App\Repository\Query\UserQuery;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\QueryBuilder;
use Pagerfanta\Adapter\DoctrineORMAdapter;
use Pagerfanta\Pagerfanta;
use Symfony\Bridge\Doctrine\Security\User\UserLoaderInterface;
class UserRepository extends EntityRepository implements UserLoaderInterface
{
use RepositoryTrait;
public function getById($id): ?User
{
@trigger_error('UserRepository::getById is deprecated and will be removed with 2.0', E_USER_DEPRECATED);
@@ -81,8 +85,84 @@ class UserRepository extends EntityRepository implements UserLoaderInterface
/**
* @param UserQuery $query
* @return array|\Doctrine\ORM\QueryBuilder|\Pagerfanta\Pagerfanta
* @deprecated since 1.4, use getUsersForQuery() instead
*/
public function findByQuery(UserQuery $query)
{
@trigger_error('UserRepository::findByQuery() is deprecated and will be removed with 1.6', E_USER_DEPRECATED);
$qb = $this->getQueryBuilderForQuery($query);
if (BaseQuery::RESULT_TYPE_PAGER === $query->getResultType()) {
$paginator = new Pagerfanta(new DoctrineORMAdapter($qb->getQuery(), false));
$paginator->setMaxPerPage($query->getPageSize());
$paginator->setCurrentPage($query->getPage());
return $paginator;
}
if (BaseQuery::RESULT_TYPE_OBJECTS === $query->getResultType()) {
return $qb->getQuery()->execute();
}
return $qb;
}
/**
* @param string $username
* @return mixed|null|\Symfony\Component\Security\Core\User\UserInterface
* @throws \Doctrine\ORM\NoResultException
* @throws \Doctrine\ORM\NonUniqueResultException
*/
public function loadUserByUsername($username)
{
return $this->createQueryBuilder('u')
->select('u', 'p', 't', 'tu', 'tl')
->leftJoin('u.preferences', 'p')
->leftJoin('u.teams', 't')
->leftJoin('t.users', 'tu')
->leftJoin('t.teamlead', 'tl')
->where('u.username = :username')
->orWhere('u.email = :username')
->setParameter('username', $username)
->getQuery()
->getSingleResult();
}
public function getQueryBuilderForFormType(UserFormTypeQuery $query): QueryBuilder
{
$qb = $this->createQueryBuilder('u');
$qb->andWhere($qb->expr()->eq('u.enabled', ':enabled'));
$qb->setParameter('enabled', true, \PDO::PARAM_BOOL);
$qb->orderBy('u.username', 'ASC');
$this->addPermissionCriteria($qb, $query->getUser(), $query->getTeams());
return $qb;
}
private function addPermissionCriteria(QueryBuilder $qb, ?User $user = null, array $teams = [])
{
// make sure that all queries without a user see all user
if (null === $user && empty($teams)) {
return;
}
// make sure that admins see all user
if (null !== $user && ($user->isSuperAdmin() || $user->isAdmin())) {
return;
}
if (null !== $user) {
$qb->leftJoin('u.teams', 'teams')
->leftJoin('teams.users', 'users')
->andWhere('teams.teamlead = :id')
->setParameter('id', $user);
}
}
private function getQueryBuilderForQuery(UserQuery $query): QueryBuilder
{
$qb = $this->getEntityManager()->createQueryBuilder();
@@ -144,61 +224,55 @@ class UserRepository extends EntityRepository implements UserLoaderInterface
}
}
return $this->getBaseQueryResult($qb, $query);
}
/**
* @param string $username
* @return mixed|null|\Symfony\Component\Security\Core\User\UserInterface
* @throws \Doctrine\ORM\NoResultException
* @throws \Doctrine\ORM\NonUniqueResultException
*/
public function loadUserByUsername($username)
{
return $this->createQueryBuilder('u')
->select('u', 'p', 't', 'tu', 'tl')
->leftJoin('u.preferences', 'p')
->leftJoin('u.teams', 't')
->leftJoin('t.users', 'tu')
->leftJoin('t.teamlead', 'tl')
->where('u.username = :username')
->orWhere('u.email = :username')
->setParameter('username', $username)
->getQuery()
->getSingleResult();
}
public function getQueryBuilderForFormType(UserFormTypeQuery $query): QueryBuilder
{
$qb = $this->createQueryBuilder('u');
$qb->andWhere($qb->expr()->eq('u.enabled', ':enabled'));
$qb->setParameter('enabled', true, \PDO::PARAM_BOOL);
$qb->orderBy('u.username', 'ASC');
$this->addPermissionCriteria($qb, $query->getUser(), $query->getTeams());
return $qb;
}
private function addPermissionCriteria(QueryBuilder $qb, ?User $user = null, array $teams = [])
public function getPagerfantaForQuery(UserQuery $query): Pagerfanta
{
// make sure that all queries without a user see all user
if (null === $user && empty($teams)) {
return;
}
$paginator = new Pagerfanta($this->getPaginatorForQuery($query));
$paginator->setMaxPerPage($query->getPageSize());
$paginator->setCurrentPage($query->getPage());
// make sure that admins see all user
if (null !== $user && ($user->isSuperAdmin() || $user->isAdmin())) {
return;
}
return $paginator;
}
if (null !== $user) {
$qb->leftJoin('u.teams', 'teams')
->leftJoin('teams.users', 'users')
->andWhere('teams.teamlead = :id')
->setParameter('id', $user);
}
protected function getPaginatorForQuery(UserQuery $query): PaginatorInterface
{
$qb = $this->getQueryBuilderForQuery($query);
$qb
->resetDQLPart('select')
->resetDQLPart('orderBy')
->select($qb->expr()->countDistinct('u.id'))
;
$counter = (int) $qb->getQuery()->getSingleScalarResult();
$qb = $this->getQueryBuilderForQuery($query);
return new LoaderPaginator(new UserLoader($qb->getEntityManager()), $qb, $counter);
}
/**
* @param UserQuery $query
* @return User[]
*/
public function getUsersForQuery(UserQuery $query): iterable
{
$qb = $this->getQueryBuilderForQuery($query);
return $this->getHydratedResultsByQuery($qb);
}
/**
* @param QueryBuilder $qb
* @return User[]
*/
protected function getHydratedResultsByQuery(QueryBuilder $qb): iterable
{
$results = $qb->getQuery()->getResult();
$loader = new UserLoader($qb->getEntityManager());
$loader->loadResults($results);
return $results;
}
}

View File

@@ -12,6 +12,8 @@ namespace App\Repository;
use App\Entity\User;
use App\Security\CurrentUser;
use App\Widget\Type\AbstractWidgetType;
use App\Widget\Type\Counter;
use App\Widget\Type\YearChart;
use App\Widget\WidgetException;
use App\Widget\WidgetInterface;
@@ -79,6 +81,12 @@ class WidgetRepository
return $this->widgets[$id];
}
/**
* @param string $name
* @param array $widget
* @return WidgetInterface
* @throws WidgetException
*/
protected function create(string $name, array $widget): WidgetInterface
{
$user = $this->user;
@@ -88,23 +96,33 @@ class WidgetRepository
if (!isset($widget['type'])) {
@trigger_error('Using a widget definition without a "type" is deprecated', E_USER_DEPRECATED);
$widget['type'] = 'counter';
$widget['type'] = Counter::class;
}
$widgetClassName = '\\App\\Widget\\Type\\' . ucfirst($widget['type']);
$widgetClassName = ucfirst($widget['type']);
if (!class_exists($widgetClassName)) {
throw new WidgetException(sprintf('Unknown widget type "%s"', $widgetClassName));
}
$model = new \ReflectionClass($widgetClassName);
if (!$model->isSubclassOf(AbstractWidgetType::class)) {
throw new WidgetException(sprintf('Invalid widget type "%s" does not extend AbstractWidgetType', $widgetClassName));
}
$data = $this->repository->getStatistic($widget['query'], $begin, $end, $theUser);
/** @var AbstractWidgetType $model */
$model = new $widgetClassName();
if (!($model instanceof AbstractWidgetType)) {
throw new WidgetException(
sprintf(
'Widget type "%s" is not an instance of "%s"',
$widgetClassName,
AbstractWidgetType::class
)
);
}
try {
$data = $this->repository->getStatistic($widget['query'], $begin, $end, $theUser);
} catch (\Exception $ex) {
throw new WidgetException(
'Failed loading widget data: ' . $ex->getMessage()
);
}
$model
->setId($name)
->setTitle($widget['title'])
@@ -140,7 +158,7 @@ class WidgetRepository
'end' => '23:59:59',
'icon' => 'duration',
'color' => 'green',
'type' => 'counter'
'type' => Counter::class,
],
'userDurationWeek' => [
'title' => 'stats.durationWeek',
@@ -150,7 +168,7 @@ class WidgetRepository
'end' => 'sunday this week 23:59:59',
'icon' => 'duration',
'color' => 'blue',
'type' => 'counter'
'type' => Counter::class,
],
'userDurationMonth' => [
'title' => 'stats.durationMonth',
@@ -160,7 +178,7 @@ class WidgetRepository
'end' => 'last day of this month 23:59:59',
'icon' => 'duration',
'color' => 'purple',
'type' => 'counter'
'type' => Counter::class,
],
'userDurationYear' => [
'title' => 'stats.durationYear',
@@ -170,7 +188,7 @@ class WidgetRepository
'end' => '31 december this year 23:59:59',
'icon' => 'duration',
'color' => 'yellow',
'type' => 'counter'
'type' => Counter::class,
],
'userDurationTotal' => [
'title' => 'stats.durationTotal',
@@ -178,7 +196,7 @@ class WidgetRepository
'user' => true,
'icon' => 'duration',
'color' => 'red',
'type' => 'counter'
'type' => Counter::class,
],
'userAmountToday' => [
'title' => 'stats.amountToday',
@@ -188,7 +206,7 @@ class WidgetRepository
'end' => '23:59:59',
'icon' => 'money',
'color' => 'green',
'type' => 'counter'
'type' => Counter::class,
],
'userAmountWeek' => [
'title' => 'stats.amountWeek',
@@ -198,7 +216,7 @@ class WidgetRepository
'end' => 'sunday this week 23:59:59',
'icon' => 'money',
'color' => 'blue',
'type' => 'counter'
'type' => Counter::class,
],
'userAmountMonth' => [
'title' => 'stats.amountMonth',
@@ -208,7 +226,7 @@ class WidgetRepository
'end' => 'last day of this month 23:59:59',
'icon' => 'money',
'color' => 'purple',
'type' => 'counter'
'type' => Counter::class,
],
'userAmountYear' => [
'title' => 'stats.amountYear',
@@ -218,7 +236,7 @@ class WidgetRepository
'end' => '31 december this year 23:59:59',
'icon' => 'money',
'color' => 'yellow',
'type' => 'counter'
'type' => Counter::class,
],
'userAmountTotal' => [
'title' => 'stats.amountTotal',
@@ -226,7 +244,7 @@ class WidgetRepository
'user' => true,
'icon' => 'money',
'color' => 'red',
'type' => 'counter'
'type' => Counter::class,
],
'durationToday' => [
'title' => 'stats.durationToday',
@@ -236,7 +254,7 @@ class WidgetRepository
'icon' => 'duration',
'color' => 'green',
'user' => false,
'type' => 'counter'
'type' => Counter::class,
],
'durationWeek' => [
'title' => 'stats.durationWeek',
@@ -246,7 +264,7 @@ class WidgetRepository
'icon' => 'duration',
'color' => 'blue',
'user' => false,
'type' => 'counter'
'type' => Counter::class,
],
'durationMonth' => [
'title' => 'stats.durationMonth',
@@ -256,7 +274,7 @@ class WidgetRepository
'icon' => 'duration',
'color' => 'purple',
'user' => false,
'type' => 'counter'
'type' => Counter::class,
],
'durationYear' => [
'title' => 'stats.durationYear',
@@ -266,7 +284,7 @@ class WidgetRepository
'icon' => 'duration',
'color' => 'yellow',
'user' => false,
'type' => 'counter'
'type' => Counter::class,
],
'durationTotal' => [
'title' => 'stats.durationTotal',
@@ -274,7 +292,7 @@ class WidgetRepository
'icon' => 'duration',
'color' => 'red',
'user' => false,
'type' => 'counter'
'type' => Counter::class,
],
'amountToday' => [
'title' => 'stats.amountToday',
@@ -284,7 +302,7 @@ class WidgetRepository
'icon' => 'money',
'color' => 'green',
'user' => false,
'type' => 'counter'
'type' => Counter::class,
],
'amountWeek' => [
'title' => 'stats.amountWeek',
@@ -294,7 +312,7 @@ class WidgetRepository
'icon' => 'money',
'color' => 'blue',
'user' => false,
'type' => 'counter'
'type' => Counter::class,
],
'amountMonth' => [
'title' => 'stats.amountMonth',
@@ -304,7 +322,7 @@ class WidgetRepository
'icon' => 'money',
'color' => 'purple',
'user' => false,
'type' => 'counter'
'type' => Counter::class,
],
'amountYear' => [
'title' => 'stats.amountYear',
@@ -314,7 +332,7 @@ class WidgetRepository
'icon' => 'money',
'color' => 'yellow',
'user' => false,
'type' => 'counter'
'type' => Counter::class,
],
'amountTotal' => [
'title' => 'stats.amountTotal',
@@ -322,7 +340,7 @@ class WidgetRepository
'icon' => 'money',
'color' => 'red',
'user' => false,
'type' => 'counter'
'type' => Counter::class,
],
'activeUsersToday' => [
'title' => 'stats.userActiveToday',
@@ -332,7 +350,7 @@ class WidgetRepository
'icon' => 'user',
'color' => 'green',
'user' => false,
'type' => 'counter'
'type' => Counter::class,
],
'activeUsersWeek' => [
'title' => 'stats.userActiveWeek',
@@ -342,7 +360,7 @@ class WidgetRepository
'icon' => 'user',
'color' => 'blue',
'user' => false,
'type' => 'counter'
'type' => Counter::class,
],
'activeUsersMonth' => [
'title' => 'stats.userActiveMonth',
@@ -352,7 +370,7 @@ class WidgetRepository
'icon' => 'user',
'color' => 'purple',
'user' => false,
'type' => 'counter'
'type' => Counter::class,
],
'activeUsersYear' => [
'title' => 'stats.userActiveYear',
@@ -362,7 +380,7 @@ class WidgetRepository
'icon' => 'user',
'color' => 'yellow',
'user' => false,
'type' => 'counter'
'type' => Counter::class,
],
'activeUsersTotal' => [
'title' => 'stats.userActiveTotal',
@@ -370,7 +388,7 @@ class WidgetRepository
'icon' => 'user',
'color' => 'red',
'user' => false,
'type' => 'counter'
'type' => Counter::class,
],
'activeRecordings' => [
'title' => 'stats.activeRecordings',
@@ -378,7 +396,7 @@ class WidgetRepository
'icon' => 'duration',
'color' => 'red',
'user' => false,
'type' => 'counter'
'type' => Counter::class,
],
'userRecapThisYear' => [
'title' => 'stats.yourWorkingHours',
@@ -388,7 +406,7 @@ class WidgetRepository
'end' => '31 december this year 23:59:59',
'color' => '',
'icon' => '',
'type' => 'yearChart'
'type' => YearChart::class,
],
'userRecapLastYear' => [
'title' => 'stats.yourWorkingHours',
@@ -398,7 +416,7 @@ class WidgetRepository
'end' => '31 december last year 23:59:59',
'color' => 'rgba(0,115,183,0.7)|#3b8bba',
'icon' => '',
'type' => 'yearChart'
'type' => YearChart::class,
],
'userRecapTwoYears' => [
'title' => 'stats.yourWorkingHours',
@@ -408,7 +426,7 @@ class WidgetRepository
'end' => '31 december this year 23:59:59',
'color' => 'rgba(0,115,183,0.6)|#3b8bba;rgba(233,233,233,0.8)|#ccc',
'icon' => '',
'type' => 'yearChart'
'type' => YearChart::class,
],
'userRecapThreeYears' => [
'title' => 'stats.yourWorkingHours',
@@ -418,7 +436,7 @@ class WidgetRepository
'end' => 'this year last day of december 23:59:59',
'color' => 'rgba(0,115,183,0.4)|#3b8bba;rgba(233,233,233,0.7)|#ccc;rgba(210,214,222,0.9)|#c1c7d1',
'icon' => '',
'type' => 'yearChart'
'type' => YearChart::class,
],
];
}