diff --git a/config/packages/kimai.yaml b/config/packages/kimai.yaml index ce56039b..6d002b6f 100644 --- a/config/packages/kimai.yaml +++ b/config/packages/kimai.yaml @@ -201,6 +201,11 @@ kimai: order: 50 permission: view_all_data widgets: [amountToday, amountWeek, amountMonth, amountYear] + totals: + title: ~ + order: 100 + permission: ROLE_USER + widgets: [TotalsUser, TotalsCustomer, TotalsProject, TotalsActivity] # -------------------------------------------------------------------------------- diff --git a/src/EventSubscriber/DashboardSubscriber.php b/src/EventSubscriber/DashboardSubscriber.php deleted file mode 100644 index d689f254..00000000 --- a/src/EventSubscriber/DashboardSubscriber.php +++ /dev/null @@ -1,161 +0,0 @@ -security = $security; - $this->user = $user; - $this->activity = $activity; - $this->project = $project; - $this->customer = $customer; - } - - /** - * @return array - */ - public static function getSubscribedEvents(): array - { - return [ - DashboardEvent::class => ['onDashboardEvent', 100], - ]; - } - - /** - * @param DashboardEvent $event - */ - public function onDashboardEvent(DashboardEvent $event) - { - $user = $event->getUser(); - $section = new CompoundRow(); - $section->setTitle(''); - $section->setOrder(100); - - if ($this->security->isGranted('view_user')) { - $query = new UserQuery(); - $query->setCurrentUser($user); - $section->addWidget( - (new More()) - ->setId('userTotal') - ->setTitle('stats.userTotal') - ->setData($this->user->countUsersForQuery($query)) - ->setOptions([ - 'route' => 'admin_user', - 'icon' => 'user', - 'color' => 'primary', - ]) - ); - } - - if ($this->security->isGranted('view_customer')) { - $query = new CustomerQuery(); - $query->setCurrentUser($user); - $section->addWidget( - (new More()) - ->setId('customerTotal') - ->setTitle('stats.customerTotal') - ->setData($this->customer->countCustomersForQuery($query)) - ->setOptions([ - 'route' => 'admin_customer', - 'icon' => 'customer', - 'color' => 'primary', - ]) - ); - } - - if ($this->security->isGranted('view_project')) { - $query = new ProjectQuery(); - $query->setCurrentUser($user); - $section->addWidget( - (new More()) - ->setId('projectTotal') - ->setTitle('stats.projectTotal') - ->setData($this->project->countProjectsForQuery($query)) - ->setOptions([ - 'route' => 'admin_project', - 'icon' => 'project', - 'color' => 'primary', - ]) - ); - } - - if ($this->security->isGranted('view_activity')) { - $query = new ActivityQuery(); - $query->setCurrentUser($user); - $section->addWidget( - (new More()) - ->setId('activityTotal') - ->setTitle('stats.activityTotal') - ->setData($this->activity->countActivitiesForQuery($query)) - ->setOptions([ - 'route' => 'admin_activity', - 'icon' => 'activity', - 'color' => 'primary', - ]) - ); - } - - if (\count($section->getWidgets()) > 0) { - $event->addSection($section); - } - } -} diff --git a/src/Widget/Type/PaginatedWorkingTimeChart.php b/src/Widget/Type/PaginatedWorkingTimeChart.php index 9461efd8..3782fd09 100644 --- a/src/Widget/Type/PaginatedWorkingTimeChart.php +++ b/src/Widget/Type/PaginatedWorkingTimeChart.php @@ -24,7 +24,6 @@ final class PaginatedWorkingTimeChart extends SimpleWidget implements UserWidget { $this->repository = $repository; $this->systemConfiguration = $systemConfiguration; - $this->setId('PaginatedWorkingTimeChart'); $this->setTitle('stats.yourWorkingHours'); } diff --git a/src/Widget/Type/TotalsActivity.php b/src/Widget/Type/TotalsActivity.php new file mode 100644 index 00000000..779fde84 --- /dev/null +++ b/src/Widget/Type/TotalsActivity.php @@ -0,0 +1,65 @@ +activity = $activity; + $this->setTitle('stats.activityTotal'); + } + + public function getOptions(array $options = []): array + { + return array_merge([ + 'route' => 'admin_activity', + 'icon' => 'activity', + 'color' => 'primary', + 'dataType' => 'int', + ], parent::getOptions($options)); + } + + public function getData(array $options = []) + { + $options = $this->getOptions($options); + + $user = $options['user']; + if (null === $user || !($user instanceof User)) { + throw new \InvalidArgumentException('Widget option "user" must be an instance of ' . User::class); + } + + $query = new ActivityQuery(); + $query->setCurrentUser($user); + + return $this->activity->countActivitiesForQuery($query); + } + + /** + * @return string[] + */ + public function getPermissions(): array + { + return ['view_activity', 'view_teamlead_activity', 'view_team_activity']; + } + + public function getTemplateName(): string + { + return 'widget/widget-more.html.twig'; + } +} diff --git a/src/Widget/Type/TotalsCustomer.php b/src/Widget/Type/TotalsCustomer.php new file mode 100644 index 00000000..2b722d6b --- /dev/null +++ b/src/Widget/Type/TotalsCustomer.php @@ -0,0 +1,65 @@ +customer = $customer; + $this->setTitle('stats.customerTotal'); + } + + public function getOptions(array $options = []): array + { + return array_merge([ + 'route' => 'admin_customer', + 'icon' => 'customer', + 'color' => 'primary', + 'dataType' => 'int', + ], parent::getOptions($options)); + } + + public function getData(array $options = []) + { + $options = $this->getOptions($options); + + $user = $options['user']; + if (null === $user || !($user instanceof User)) { + throw new \InvalidArgumentException('Widget option "user" must be an instance of ' . User::class); + } + + $query = new CustomerQuery(); + $query->setCurrentUser($user); + + return $this->customer->countCustomersForQuery($query); + } + + /** + * @return string[] + */ + public function getPermissions(): array + { + return ['view_customer', 'view_teamlead_customer', 'view_team_customer']; + } + + public function getTemplateName(): string + { + return 'widget/widget-more.html.twig'; + } +} diff --git a/src/Widget/Type/TotalsProject.php b/src/Widget/Type/TotalsProject.php new file mode 100644 index 00000000..e5c20147 --- /dev/null +++ b/src/Widget/Type/TotalsProject.php @@ -0,0 +1,65 @@ +project = $project; + $this->setTitle('stats.projectTotal'); + } + + public function getOptions(array $options = []): array + { + return array_merge([ + 'route' => 'admin_project', + 'icon' => 'project', + 'color' => 'primary', + 'dataType' => 'int', + ], parent::getOptions($options)); + } + + public function getData(array $options = []) + { + $options = $this->getOptions($options); + + $user = $options['user']; + if (null === $user || !($user instanceof User)) { + throw new \InvalidArgumentException('Widget option "user" must be an instance of ' . User::class); + } + + $query = new ProjectQuery(); + $query->setCurrentUser($user); + + return $this->project->countProjectsForQuery($query); + } + + /** + * @return string[] + */ + public function getPermissions(): array + { + return ['view_project', 'view_teamlead_project', 'view_team_project']; + } + + public function getTemplateName(): string + { + return 'widget/widget-more.html.twig'; + } +} diff --git a/src/Widget/Type/TotalsUser.php b/src/Widget/Type/TotalsUser.php new file mode 100644 index 00000000..d83f1a3a --- /dev/null +++ b/src/Widget/Type/TotalsUser.php @@ -0,0 +1,65 @@ +user = $user; + $this->setTitle('stats.userTotal'); + } + + public function getOptions(array $options = []): array + { + return array_merge([ + 'route' => 'admin_user', + 'icon' => 'user', + 'color' => 'primary', + 'dataType' => 'int', + ], parent::getOptions($options)); + } + + public function getData(array $options = []) + { + $options = $this->getOptions($options); + + $user = $options['user']; + if (null === $user || !($user instanceof User)) { + throw new \InvalidArgumentException('Widget option "user" must be an instance of ' . User::class); + } + + $query = new UserQuery(); + $query->setCurrentUser($user); + + return $this->user->countUsersForQuery($query); + } + + /** + * @return string[] + */ + public function getPermissions(): array + { + return ['view_user']; + } + + public function getTemplateName(): string + { + return 'widget/widget-more.html.twig'; + } +} diff --git a/src/Widget/Type/UserWidgetTrait.php b/src/Widget/Type/UserWidgetTrait.php new file mode 100644 index 00000000..e89db83d --- /dev/null +++ b/src/Widget/Type/UserWidgetTrait.php @@ -0,0 +1,24 @@ +setOption('user', $user); + } +} diff --git a/tests/EventSubscriber/DashboardSubscriberTest.php b/tests/EventSubscriber/DashboardSubscriberTest.php deleted file mode 100644 index 5de4b4a3..00000000 --- a/tests/EventSubscriber/DashboardSubscriberTest.php +++ /dev/null @@ -1,91 +0,0 @@ -assertArrayHasKey(DashboardEvent::class, $events); - $methodName = $events[DashboardEvent::class][0]; - $this->assertTrue(method_exists(DashboardSubscriber::class, $methodName)); - } - - public function testWithNonAdminUser() - { - $sut = $this->getSubscriber(false, 13, 28, 37, 5); - $event = new DashboardEvent(new User()); - - $this->assertEquals(0, \count($event->getSections())); - $sut->onDashboardEvent($event); - $this->assertEquals(0, \count($event->getSections())); - } - - public function testWithAdminUser() - { - $sut = $this->getSubscriber(true, 13, 28, 37, 5); - $event = new DashboardEvent(new User()); - - $this->assertEquals(0, \count($event->getSections())); - $sut->onDashboardEvent($event); - - $sections = $event->getSections(); - $widgets = $sections[0]->getWidgets(); - - $this->assertEquals(1, \count($sections)); - $this->assertEquals(4, \count($widgets)); - - $this->assertEquals('stats.userTotal', $widgets[0]->getTitle()); - $this->assertEquals(13, $widgets[0]->getData()); - - $this->assertEquals('stats.customerTotal', $widgets[1]->getTitle()); - $this->assertEquals(5, $widgets[1]->getData()); - - $this->assertEquals('stats.projectTotal', $widgets[2]->getTitle()); - $this->assertEquals(37, $widgets[2]->getData()); - - $this->assertEquals('stats.activityTotal', $widgets[3]->getTitle()); - $this->assertEquals(28, $widgets[3]->getData()); - } - - protected function getSubscriber(bool $isAdmin, int $userCount, int $activityCount, int $projectCount, int $customerCount) - { - $authMock = $this->getMockBuilder(AuthorizationCheckerInterface::class)->getMock(); - $authMock->method('isGranted')->willReturn($isAdmin); - - $userMock = $this->getMockBuilder(UserRepository::class)->disableOriginalConstructor()->getMock(); - $userMock->method('countUsersForQuery')->willReturn($userCount); - - $projectMock = $this->getMockBuilder(ProjectRepository::class)->disableOriginalConstructor()->getMock(); - $projectMock->method('countProjectsForQuery')->willReturn($projectCount); - - $activityMock = $this->getMockBuilder(ActivityRepository::class)->disableOriginalConstructor()->getMock(); - $activityMock->method('countActivitiesForQuery')->willReturn($activityCount); - - $customerMock = $this->getMockBuilder(CustomerRepository::class)->disableOriginalConstructor()->getMock(); - $customerMock->method('countCustomersForQuery')->willReturn($customerCount); - - return new DashboardSubscriber($authMock, $userMock, $activityMock, $projectMock, $customerMock); - } -} diff --git a/tests/EventSubscriber/EmailSubscriberTest.php b/tests/EventSubscriber/EmailSubscriberTest.php index 5cf4621b..ffccf117 100644 --- a/tests/EventSubscriber/EmailSubscriberTest.php +++ b/tests/EventSubscriber/EmailSubscriberTest.php @@ -10,20 +10,12 @@ namespace App\Tests\EventSubscriber; use App\Configuration\MailConfiguration; -use App\Entity\User; -use App\Event\DashboardEvent; use App\Event\EmailEvent; -use App\EventSubscriber\DashboardSubscriber; use App\EventSubscriber\EmailSubscriber; use App\Mail\KimaiMailer; -use App\Repository\ActivityRepository; -use App\Repository\CustomerRepository; -use App\Repository\ProjectRepository; -use App\Repository\UserRepository; use PHPUnit\Framework\TestCase; use Symfony\Component\Mailer\MailerInterface; use Symfony\Component\Mime\Email; -use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; /** * @covers \App\EventSubscriber\EmailSubscriber @@ -54,51 +46,4 @@ class EmailSubscriberTest extends TestCase $sut->onMailEvent($event); } - - public function testWithAdminUser() - { - $sut = $this->getSubscriber(true, 13, 28, 37, 5); - $event = new DashboardEvent(new User()); - - $this->assertEquals(0, \count($event->getSections())); - $sut->onDashboardEvent($event); - - $sections = $event->getSections(); - $widgets = $sections[0]->getWidgets(); - - $this->assertEquals(1, \count($sections)); - $this->assertEquals(4, \count($widgets)); - - $this->assertEquals('stats.userTotal', $widgets[0]->getTitle()); - $this->assertEquals(13, $widgets[0]->getData()); - - $this->assertEquals('stats.customerTotal', $widgets[1]->getTitle()); - $this->assertEquals(5, $widgets[1]->getData()); - - $this->assertEquals('stats.projectTotal', $widgets[2]->getTitle()); - $this->assertEquals(37, $widgets[2]->getData()); - - $this->assertEquals('stats.activityTotal', $widgets[3]->getTitle()); - $this->assertEquals(28, $widgets[3]->getData()); - } - - protected function getSubscriber(bool $isAdmin, int $userCount, int $activityCount, int $projectCount, int $customerCount) - { - $authMock = $this->getMockBuilder(AuthorizationCheckerInterface::class)->getMock(); - $authMock->method('isGranted')->willReturn($isAdmin); - - $userMock = $this->getMockBuilder(UserRepository::class)->disableOriginalConstructor()->getMock(); - $userMock->method('countUsersForQuery')->willReturn($userCount); - - $projectMock = $this->getMockBuilder(ProjectRepository::class)->disableOriginalConstructor()->getMock(); - $projectMock->method('countProjectsForQuery')->willReturn($projectCount); - - $activityMock = $this->getMockBuilder(ActivityRepository::class)->disableOriginalConstructor()->getMock(); - $activityMock->method('countActivitiesForQuery')->willReturn($activityCount); - - $customerMock = $this->getMockBuilder(CustomerRepository::class)->disableOriginalConstructor()->getMock(); - $customerMock->method('countCustomersForQuery')->willReturn($customerCount); - - return new DashboardSubscriber($authMock, $userMock, $activityMock, $projectMock, $customerMock); - } } diff --git a/tests/Widget/Type/AbstractWidgetTypeTest.php b/tests/Widget/Type/AbstractWidgetTypeTest.php index 290df29b..74afe980 100644 --- a/tests/Widget/Type/AbstractWidgetTypeTest.php +++ b/tests/Widget/Type/AbstractWidgetTypeTest.php @@ -21,12 +21,17 @@ abstract class AbstractWidgetTypeTest extends TestCase abstract public function getDefaultOptions(): array; - public function testDefaultValues() + protected function assertDefaultData(AbstractWidgetType $sut) + { + self::assertNull($sut->getData()); + } + + public function testDefaultData() { $sut = $this->createSut(); self::assertInstanceOf(AbstractWidgetType::class, $sut); self::assertEquals($this->getDefaultOptions(), $sut->getOptions()); - self::assertNull($sut->getData()); + $this->assertDefaultData($sut); self::assertEquals('bar', $sut->getOption('foo', 'bar')); } diff --git a/tests/Widget/Type/TotalsActivityTest.php b/tests/Widget/Type/TotalsActivityTest.php new file mode 100644 index 00000000..cf85c58b --- /dev/null +++ b/tests/Widget/Type/TotalsActivityTest.php @@ -0,0 +1,79 @@ +setAlias('foo'); + + $this->user = $user; + } + + public function createSut(): AbstractWidgetType + { + return $this->createWidget(); + } + + private function createWidget(int $results = 1): TotalsActivity + { + $repository = $this->createMock(ActivityRepository::class); + $repository->expects($this->any())->method('countActivitiesForQuery')->willReturn($results); + + $widget = new TotalsActivity($repository); + $widget->setUser($this->user); + + return $widget; + } + + public function getDefaultOptions(): array + { + return [ + 'route' => 'admin_activity', + 'icon' => 'activity', + 'color' => 'primary', + 'dataType' => 'int', + 'user' => $this->user, + ]; + } + + protected function assertDefaultData(AbstractWidgetType $sut) + { + self::assertEquals(1, $sut->getData()); + } + + public function testData() + { + $user = new User(); + $user->setAlias('foo'); + + $sut = $this->createWidget(99); + self::assertEquals('widget/widget-more.html.twig', $sut->getTemplateName()); + $sut->setUser($user); + + self::assertEquals(['view_activity', 'view_teamlead_activity', 'view_team_activity'], $sut->getPermissions()); + self::assertEquals(99, $sut->getData([])); + } +} diff --git a/tests/Widget/Type/TotalsCustomerTest.php b/tests/Widget/Type/TotalsCustomerTest.php new file mode 100644 index 00000000..8c3dd8a5 --- /dev/null +++ b/tests/Widget/Type/TotalsCustomerTest.php @@ -0,0 +1,79 @@ +setAlias('foo'); + + $this->user = $user; + } + + public function createSut(): AbstractWidgetType + { + return $this->createWidget(); + } + + private function createWidget(int $results = 1): TotalsCustomer + { + $repository = $this->createMock(CustomerRepository::class); + $repository->expects($this->any())->method('countCustomersForQuery')->willReturn($results); + + $widget = new TotalsCustomer($repository); + $widget->setUser($this->user); + + return $widget; + } + + public function getDefaultOptions(): array + { + return [ + 'route' => 'admin_customer', + 'icon' => 'customer', + 'color' => 'primary', + 'dataType' => 'int', + 'user' => $this->user, + ]; + } + + protected function assertDefaultData(AbstractWidgetType $sut) + { + self::assertEquals(1, $sut->getData()); + } + + public function testData() + { + $user = new User(); + $user->setAlias('foo'); + + $sut = $this->createWidget(99); + self::assertEquals('widget/widget-more.html.twig', $sut->getTemplateName()); + $sut->setUser($user); + + self::assertEquals(['view_customer', 'view_teamlead_customer', 'view_team_customer'], $sut->getPermissions()); + self::assertEquals(99, $sut->getData([])); + } +} diff --git a/tests/Widget/Type/TotalsProjectTest.php b/tests/Widget/Type/TotalsProjectTest.php new file mode 100644 index 00000000..dbd6b351 --- /dev/null +++ b/tests/Widget/Type/TotalsProjectTest.php @@ -0,0 +1,79 @@ +setAlias('foo'); + + $this->user = $user; + } + + public function createSut(): AbstractWidgetType + { + return $this->createWidget(); + } + + private function createWidget(int $results = 1): TotalsProject + { + $repository = $this->createMock(ProjectRepository::class); + $repository->expects($this->any())->method('countProjectsForQuery')->willReturn($results); + + $widget = new TotalsProject($repository); + $widget->setUser($this->user); + + return $widget; + } + + public function getDefaultOptions(): array + { + return [ + 'route' => 'admin_project', + 'icon' => 'project', + 'color' => 'primary', + 'dataType' => 'int', + 'user' => $this->user, + ]; + } + + protected function assertDefaultData(AbstractWidgetType $sut) + { + self::assertEquals(1, $sut->getData()); + } + + public function testData() + { + $user = new User(); + $user->setAlias('foo'); + + $sut = $this->createWidget(99); + self::assertEquals('widget/widget-more.html.twig', $sut->getTemplateName()); + $sut->setUser($user); + + self::assertEquals(['view_project', 'view_teamlead_project', 'view_team_project'], $sut->getPermissions()); + self::assertEquals(99, $sut->getData([])); + } +} diff --git a/tests/Widget/Type/TotalsUserTest.php b/tests/Widget/Type/TotalsUserTest.php new file mode 100644 index 00000000..c19c26c7 --- /dev/null +++ b/tests/Widget/Type/TotalsUserTest.php @@ -0,0 +1,79 @@ +setAlias('foo'); + + $this->user = $user; + } + + public function createSut(): AbstractWidgetType + { + return $this->createWidget(); + } + + private function createWidget(int $results = 1): TotalsUser + { + $repository = $this->createMock(UserRepository::class); + $repository->expects($this->any())->method('countUsersForQuery')->willReturn($results); + + $widget = new TotalsUser($repository); + $widget->setUser($this->user); + + return $widget; + } + + public function getDefaultOptions(): array + { + return [ + 'route' => 'admin_user', + 'icon' => 'user', + 'color' => 'primary', + 'dataType' => 'int', + 'user' => $this->user, + ]; + } + + protected function assertDefaultData(AbstractWidgetType $sut) + { + self::assertEquals(1, $sut->getData()); + } + + public function testData() + { + $user = new User(); + $user->setAlias('foo'); + + $sut = $this->createWidget(99); + self::assertEquals('widget/widget-more.html.twig', $sut->getTemplateName()); + $sut->setUser($user); + + self::assertEquals(['view_user'], $sut->getPermissions()); + self::assertEquals(99, $sut->getData([])); + } +}