allow to select reports as initial view (#2403)
This commit is contained in:
@@ -9,10 +9,14 @@
|
|||||||
|
|
||||||
namespace App\Form\Type;
|
namespace App\Form\Type;
|
||||||
|
|
||||||
|
use App\Entity\User;
|
||||||
|
use App\Reporting\ReportingService;
|
||||||
use Symfony\Component\Form\AbstractType;
|
use Symfony\Component\Form\AbstractType;
|
||||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||||
|
use Symfony\Component\OptionsResolver\Options;
|
||||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||||
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
|
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
|
||||||
|
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Custom form field type to select the initial view, where the user should be redirected to after login.
|
* Custom form field type to select the initial view, where the user should be redirected to after login.
|
||||||
@@ -21,7 +25,7 @@ class InitialViewType extends AbstractType
|
|||||||
{
|
{
|
||||||
public const DEFAULT_VIEW = 'timesheet';
|
public const DEFAULT_VIEW = 'timesheet';
|
||||||
|
|
||||||
public const ALLOWED_VIEWS = [
|
private const ALLOWED_VIEWS = [
|
||||||
'dashboard' => 'menu.homepage',
|
'dashboard' => 'menu.homepage',
|
||||||
'timesheet' => 'menu.timesheet',
|
'timesheet' => 'menu.timesheet',
|
||||||
'calendar' => 'calendar.title',
|
'calendar' => 'calendar.title',
|
||||||
@@ -34,7 +38,7 @@ class InitialViewType extends AbstractType
|
|||||||
'admin_activity' => 'menu.admin_activity',
|
'admin_activity' => 'menu.admin_activity',
|
||||||
];
|
];
|
||||||
|
|
||||||
protected const ROUTE_PERMISSION = [
|
private const ROUTE_PERMISSION = [
|
||||||
'dashboard' => '',
|
'dashboard' => '',
|
||||||
'timesheet' => 'view_own_timesheet',
|
'timesheet' => 'view_own_timesheet',
|
||||||
'calendar' => 'view_own_timesheet',
|
'calendar' => 'view_own_timesheet',
|
||||||
@@ -47,17 +51,15 @@ class InitialViewType extends AbstractType
|
|||||||
'admin_activity' => 'view_activity',
|
'admin_activity' => 'view_activity',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
private $voter;
|
||||||
* @var AuthorizationCheckerInterface
|
private $reportingService;
|
||||||
*/
|
private $translator;
|
||||||
protected $voter;
|
|
||||||
|
|
||||||
/**
|
public function __construct(AuthorizationCheckerInterface $voter, ReportingService $reportingService, TranslatorInterface $translator)
|
||||||
* @param AuthorizationCheckerInterface $voter
|
|
||||||
*/
|
|
||||||
public function __construct(AuthorizationCheckerInterface $voter)
|
|
||||||
{
|
{
|
||||||
$this->voter = $voter;
|
$this->voter = $voter;
|
||||||
|
$this->reportingService = $reportingService;
|
||||||
|
$this->translator = $translator;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -65,18 +67,26 @@ class InitialViewType extends AbstractType
|
|||||||
*/
|
*/
|
||||||
public function configureOptions(OptionsResolver $resolver)
|
public function configureOptions(OptionsResolver $resolver)
|
||||||
{
|
{
|
||||||
$choices = [];
|
$resolver->setDefault('required', true);
|
||||||
foreach (self::ROUTE_PERMISSION as $route => $permission) {
|
$resolver->setDefault('choices', function (Options $options) {
|
||||||
if (empty($permission) || $this->voter->isGranted($permission)) {
|
$choices = [];
|
||||||
$name = self::ALLOWED_VIEWS[$route];
|
foreach (self::ROUTE_PERMISSION as $route => $permission) {
|
||||||
$choices[$name] = $route;
|
if (empty($permission) || $this->voter->isGranted($permission)) {
|
||||||
|
$name = self::ALLOWED_VIEWS[$route];
|
||||||
|
$choices[$name] = $route;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
$resolver->setDefaults([
|
/** @var User $user */
|
||||||
'required' => true,
|
$user = $options['user'];
|
||||||
'choices' => $choices,
|
|
||||||
]);
|
foreach ($this->reportingService->getAvailableReports($user) as $report) {
|
||||||
|
$label = $this->translator->trans('menu.reporting') . ': ' . $this->translator->trans($report->getLabel(), [], 'reporting');
|
||||||
|
$choices[$label] = $report->getRoute();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $choices;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -7,18 +7,14 @@
|
|||||||
* file that was distributed with this source code.
|
* file that was distributed with this source code.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace App\Twig;
|
namespace App\Reporting;
|
||||||
|
|
||||||
use App\Entity\User;
|
use App\Entity\User;
|
||||||
use App\Event\ReportingEvent;
|
use App\Event\ReportingEvent;
|
||||||
use App\Reporting\Report;
|
|
||||||
use App\Reporting\ReportInterface;
|
|
||||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||||
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
|
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
|
||||||
use Twig\Extension\AbstractExtension;
|
|
||||||
use Twig\TwigFunction;
|
|
||||||
|
|
||||||
final class ReportingExtension extends AbstractExtension
|
final class ReportingService
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @var EventDispatcherInterface
|
* @var EventDispatcherInterface
|
||||||
@@ -35,16 +31,6 @@ final class ReportingExtension extends AbstractExtension
|
|||||||
$this->security = $security;
|
$this->security = $security;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function getFunctions()
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
new TwigFunction('available_reports', [$this, 'getAvailableReports'], []),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param User $user
|
* @param User $user
|
||||||
* @return ReportInterface[]
|
* @return ReportInterface[]
|
||||||
34
src/Twig/Runtime/ReportingExtension.php
Normal file
34
src/Twig/Runtime/ReportingExtension.php
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
<?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\Twig\Runtime;
|
||||||
|
|
||||||
|
use App\Entity\User;
|
||||||
|
use App\Reporting\ReportingService;
|
||||||
|
use App\Reporting\ReportInterface;
|
||||||
|
use Twig\Extension\RuntimeExtensionInterface;
|
||||||
|
|
||||||
|
final class ReportingExtension implements RuntimeExtensionInterface
|
||||||
|
{
|
||||||
|
private $service;
|
||||||
|
|
||||||
|
public function __construct(ReportingService $reportingService)
|
||||||
|
{
|
||||||
|
$this->service = $reportingService;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param User $user
|
||||||
|
* @return ReportInterface[]
|
||||||
|
*/
|
||||||
|
public function getAvailableReports(User $user): array
|
||||||
|
{
|
||||||
|
return $this->service->getAvailableReports($user);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,6 +12,7 @@ namespace App\Twig;
|
|||||||
use App\Twig\Runtime\EncoreExtension;
|
use App\Twig\Runtime\EncoreExtension;
|
||||||
use App\Twig\Runtime\ExporterExtension;
|
use App\Twig\Runtime\ExporterExtension;
|
||||||
use App\Twig\Runtime\MarkdownExtension;
|
use App\Twig\Runtime\MarkdownExtension;
|
||||||
|
use App\Twig\Runtime\ReportingExtension;
|
||||||
use App\Twig\Runtime\ThemeExtension;
|
use App\Twig\Runtime\ThemeExtension;
|
||||||
use App\Twig\Runtime\TimesheetExtension;
|
use App\Twig\Runtime\TimesheetExtension;
|
||||||
use App\Twig\Runtime\WidgetExtension;
|
use App\Twig\Runtime\WidgetExtension;
|
||||||
@@ -34,6 +35,7 @@ class RuntimeExtensions extends AbstractExtension
|
|||||||
new TwigFunction('active_timesheets', [TimesheetExtension::class, 'activeEntries']),
|
new TwigFunction('active_timesheets', [TimesheetExtension::class, 'activeEntries']),
|
||||||
new TwigFunction('encore_entry_css_source', [EncoreExtension::class, 'getEncoreEntryCssSource']),
|
new TwigFunction('encore_entry_css_source', [EncoreExtension::class, 'getEncoreEntryCssSource']),
|
||||||
new TwigFunction('render_widget', [WidgetExtension::class, 'renderWidget'], ['is_safe' => ['html']]),
|
new TwigFunction('render_widget', [WidgetExtension::class, 'renderWidget'], ['is_safe' => ['html']]),
|
||||||
|
new TwigFunction('available_reports', [ReportingExtension::class, 'getAvailableReports'], []),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,41 +7,31 @@
|
|||||||
* file that was distributed with this source code.
|
* file that was distributed with this source code.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace App\Tests\Twig;
|
namespace App\Tests\Reporting;
|
||||||
|
|
||||||
use App\Entity\User;
|
use App\Entity\User;
|
||||||
use App\Twig\ReportingExtension;
|
use App\Event\ReportingEvent;
|
||||||
|
use App\Reporting\ReportingService;
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||||
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
|
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
|
||||||
use Twig\TwigFunction;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @covers \App\Twig\ReportingExtension
|
* @covers \App\Reporting\ReportingService
|
||||||
*/
|
*/
|
||||||
class ReportingExtensionTest extends TestCase
|
class ReportingServiceTest extends TestCase
|
||||||
{
|
{
|
||||||
protected function getSut(bool $isGranted = false): ReportingExtension
|
protected function getSut(bool $isGranted = false): ReportingService
|
||||||
{
|
{
|
||||||
$dispatcher = $this->createMock(EventDispatcherInterface::class);
|
$dispatcher = $this->createMock(EventDispatcherInterface::class);
|
||||||
|
$dispatcher->expects($this->once())->method('dispatch')->willReturnCallback(function ($event) {
|
||||||
|
$this->assertInstanceOf(ReportingEvent::class, $event);
|
||||||
|
});
|
||||||
|
|
||||||
$security = $this->createMock(AuthorizationCheckerInterface::class);
|
$security = $this->createMock(AuthorizationCheckerInterface::class);
|
||||||
$security->method('isGranted')->willReturn($isGranted);
|
$security->expects($this->any())->method('isGranted')->willReturn($isGranted);
|
||||||
|
|
||||||
return new ReportingExtension($dispatcher, $security);
|
return new ReportingService($dispatcher, $security);
|
||||||
}
|
|
||||||
|
|
||||||
public function testGetFunctions()
|
|
||||||
{
|
|
||||||
$sut = $this->getSut();
|
|
||||||
$functions = ['available_reports'];
|
|
||||||
$twigFunctions = $sut->getFunctions();
|
|
||||||
$this->assertCount(\count($functions), $twigFunctions);
|
|
||||||
$i = 0;
|
|
||||||
/** @var TwigFunction $function */
|
|
||||||
foreach ($twigFunctions as $function) {
|
|
||||||
$this->assertInstanceOf(TwigFunction::class, $function);
|
|
||||||
$this->assertEquals($functions[$i++], $function->getName());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGetAvailableReports()
|
public function testGetAvailableReports()
|
||||||
43
tests/Twig/Runtime/ReportingExtensionTest.php
Normal file
43
tests/Twig/Runtime/ReportingExtensionTest.php
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
<?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\Tests\Twig\Runtime;
|
||||||
|
|
||||||
|
use App\Entity\User;
|
||||||
|
use App\Reporting\ReportingService;
|
||||||
|
use App\Twig\Runtime\ReportingExtension;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Symfony\Component\EventDispatcher\EventDispatcher;
|
||||||
|
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \App\Twig\Runtime\ReportingExtension
|
||||||
|
*/
|
||||||
|
class ReportingExtensionTest extends TestCase
|
||||||
|
{
|
||||||
|
protected function getSut(bool $isGranted): ReportingExtension
|
||||||
|
{
|
||||||
|
$eventDispatcher = new EventDispatcher();
|
||||||
|
|
||||||
|
$authorization = $this->createMock(AuthorizationCheckerInterface::class);
|
||||||
|
$authorization->expects($this->any())->method('isGranted')->willReturn($isGranted);
|
||||||
|
|
||||||
|
$service = new ReportingService($eventDispatcher, $authorization);
|
||||||
|
|
||||||
|
return new ReportingExtension($service);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testRenderWidgetForInvalidValue()
|
||||||
|
{
|
||||||
|
$sut = $this->getSut(true);
|
||||||
|
$reports = $sut->getAvailableReports(new User());
|
||||||
|
|
||||||
|
$this->assertCount(3, $reports);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -37,7 +37,17 @@ class RuntimeExtensionsTest extends TestCase
|
|||||||
|
|
||||||
public function testGetFunctions()
|
public function testGetFunctions()
|
||||||
{
|
{
|
||||||
$expected = ['trigger', 'actions', 'javascript_translations', 'timesheet_exporter', 'active_timesheets', 'encore_entry_css_source', 'render_widget'];
|
$expected = [
|
||||||
|
'trigger',
|
||||||
|
'actions',
|
||||||
|
'javascript_translations',
|
||||||
|
'timesheet_exporter',
|
||||||
|
'active_timesheets',
|
||||||
|
'encore_entry_css_source',
|
||||||
|
'render_widget',
|
||||||
|
'available_reports'
|
||||||
|
];
|
||||||
|
|
||||||
$i = 0;
|
$i = 0;
|
||||||
|
|
||||||
$sut = new RuntimeExtensions();
|
$sut = new RuntimeExtensions();
|
||||||
|
|||||||
Reference in New Issue
Block a user