Skip to content

Commit

Permalink
dev: Refactor edit ticket actors with Symfony Form
Browse files Browse the repository at this point in the history
  • Loading branch information
marien-probesys committed Aug 13, 2024
1 parent 5470325 commit 8b35064
Show file tree
Hide file tree
Showing 12 changed files with 400 additions and 222 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,18 @@ export default class extends Controller {
return;
}

const teamAgentsUids = JSON.parse(selectedTeamOption.dataset.agentsUids);
const agentsIds = selectedTeamOption.dataset.agentsIds;
const agentsUids = selectedTeamOption.dataset.agentsUids;

const teamAgentsIds = JSON.parse(agentsIds ? agentsIds : agentsUids);

for (const option of this.assigneesTarget.options) {
if (option.value === '') {
// The "Unassigned" option must always be visible.
continue;
}

if (teamAgentsUids.includes(option.value)) {
if (teamAgentsIds.includes(option.value)) {
option.hidden = false;
} else {
option.hidden = true;
Expand Down
11 changes: 9 additions & 2 deletions src/Controller/BaseController.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,15 @@ protected function isPathRedirectable(string $path): bool
}
}

protected function createNamedForm(string $name, string $type, mixed $data = null, array $options = []): FormInterface
{
/**
* @param array<string, mixed> $options
*/
protected function createNamedForm(
string $name,
string $type,
mixed $data = null,
array $options = [],
): FormInterface {
return $this->container->get('form.factory')->createNamed($name, $type, $data, $options);
}
}
95 changes: 13 additions & 82 deletions src/Controller/Tickets/ActorsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

use App\Controller\BaseController;
use App\Entity\Ticket;
use App\Form;
use App\Repository\TeamRepository;
use App\Repository\TicketRepository;
use App\Repository\UserRepository;
Expand Down Expand Up @@ -35,30 +36,18 @@ public function edit(
$organization = $ticket->getOrganization();
$this->denyAccessUnlessGranted('orga:update:tickets:actors', $organization);

/** @var \App\Entity\User $user */
/** @var \App\Entity\User */
$user = $this->getUser();

if (!$ticket->hasActor($user)) {
$this->denyAccessUnlessGranted('orga:see:tickets:all', $organization);
}

$allUsers = $actorsLister->findByOrganization($organization);
$agents = $actorsLister->findByOrganization($organization, roleType: 'agent');
$teams = $teamRepository->findByOrganization($organization);
$teamSorter->sort($teams);

$requester = $ticket->getRequester();
$team = $ticket->getTeam();
$assignee = $ticket->getAssignee();
$form = $this->createNamedForm('ticket_actors', Form\Ticket\ActorsForm::class, $ticket);

return $this->render('tickets/actors/edit.html.twig', [
'ticket' => $ticket,
'requesterUid' => $requester ? $requester->getUid() : '',
'teamUid' => $team ? $team->getUid() : '',
'assigneeUid' => $assignee ? $assignee->getUid() : '',
'allUsers' => $allUsers,
'teams' => $teams,
'agents' => $agents,
'form' => $form,
]);
}

Expand All @@ -78,89 +67,31 @@ public function update(
$organization = $ticket->getOrganization();
$this->denyAccessUnlessGranted('orga:update:tickets:actors', $organization);

/** @var \App\Entity\User $user */
/** @var \App\Entity\User */
$user = $this->getUser();

if (!$ticket->hasActor($user)) {
$this->denyAccessUnlessGranted('orga:see:tickets:all', $organization);
}

$initialRequester = $ticket->getRequester();
$initialTeam = $ticket->getTeam();
$initialAssignee = $ticket->getAssignee();

/** @var string $requesterUid */
$requesterUid = $request->request->get('requesterUid', $initialRequester ? $initialRequester->getUid() : '');

/** @var string $teamUid */
$teamUid = $request->request->get('teamUid', $initialTeam ? $initialTeam->getUid() : '');

/** @var string $assigneeUid */
$assigneeUid = $request->request->get('assigneeUid', $initialAssignee ? $initialAssignee->getUid() : '');

/** @var string $csrfToken */
$csrfToken = $request->request->get('_csrf_token', '');

$allUsers = $actorsLister->findByOrganization($organization);
$agents = $actorsLister->findByOrganization($organization, roleType: 'agent');
$teams = $teamRepository->findByOrganization($organization);
$teamSorter->sort($teams);

if (!$this->isCsrfTokenValid('update ticket actors', $csrfToken)) {
return $this->renderBadRequest('tickets/actors/edit.html.twig', [
'ticket' => $ticket,
'requesterUid' => $requesterUid,
'teamUid' => $teamUid,
'assigneeUid' => $assigneeUid,
'allUsers' => $allUsers,
'teams' => $teams,
'agents' => $agents,
'error' => $translator->trans('csrf.invalid', [], 'errors'),
]);
}

$requester = ArrayHelper::find($allUsers, function ($user) use ($requesterUid): bool {
return $user->getUid() === $requesterUid;
});

$team = null;
if ($teamUid) {
$team = ArrayHelper::find($teams, function ($team) use ($teamUid): bool {
return $team->getUid() === $teamUid;
});
}

$assignee = null;
if ($assigneeUid) {
$availableAgents = $team ? $team->getAgents()->toArray() : $agents;
$assignee = ArrayHelper::find($availableAgents, function ($agent) use ($assigneeUid): bool {
return $agent->getUid() === $assigneeUid;
});
}

$previousAssignee = $ticket->getAssignee();

$ticket->setRequester($requester);
$ticket->setTeam($team);
$ticket->setAssignee($assignee);
$form = $this->createNamedForm('ticket_actors', Form\Ticket\ActorsForm::class, $ticket);
$form->handleRequest($request);

$errors = $validator->validate($ticket);
if (count($errors) > 0) {
if (!$form->isSubmitted() || !$form->isValid()) {
return $this->renderBadRequest('tickets/actors/edit.html.twig', [
'ticket' => $ticket,
'requesterUid' => $requesterUid,
'teamUid' => $teamUid,
'assigneeUid' => $assigneeUid,
'allUsers' => $allUsers,
'teams' => $teams,
'agents' => $agents,
'errors' => ConstraintErrorsFormatter::format($errors),
'form' => $form,
]);
}

$ticket = $form->getData();
$ticketRepository->save($ticket, true);

if ($previousAssignee != $assignee) {
$newAssignee = $ticket->getAssignee();

if ($previousAssignee != $newAssignee) {
$ticketEvent = new TicketEvent($ticket);
$eventDispatcher->dispatch($ticketEvent, TicketEvent::ASSIGNED);
}
Expand Down
11 changes: 11 additions & 0 deletions src/Entity/Team.php
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,17 @@ public function getAgents(): Collection
}

/**
* @return int[]
*/
public function getAgentsIds(): array
{
return array_map(function ($agent): ?int {
return $agent->getId();
}, $this->agents->toArray());
}

/**
* @deprecated
* @return string[]
*/
public function getAgentsUids(): array
Expand Down
69 changes: 69 additions & 0 deletions src/Form/Ticket/ActorsForm.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<?php

// This file is part of Bileto.
// Copyright 2022-2024 Probesys
// SPDX-License-Identifier: AGPL-3.0-or-later

namespace App\Form\Ticket;

use App\Entity;
use App\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormError;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\OptionsResolver\OptionsResolver;

class ActorsForm extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event): void {
$form = $event->getForm();
$ticket = $event->getData();

$organization = $ticket->getOrganization();

$form->add('requester', Type\ActorType::class, [
'organization' => $organization,
'required' => true,
]);

$form->add('team', Type\TeamType::class, [
'organization' => $organization,
]);

$form->add('assignee', Type\ActorType::class, [
'organization' => $organization,
'roleType' => 'agent',
]);
});

$builder->addEventListener(FormEvents::POST_SUBMIT, function (FormEvent $event): void {
$form = $event->getForm();
$ticket = $event->getData();

$team = $ticket->getTeam();
$assignee = $ticket->getAssignee();

if ($team === null || $assignee === null) {
return;
}

if (!$team->hasAgent($assignee)) {
$error = new FormError('The selected choice is invalid.');
$form->get('assignee')->addError($error);
}
});
}

public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => Entity\Ticket::class,
'csrf_token_id' => 'ticket actors',
'csrf_message' => 'csrf.invalid',
]);
}
}
64 changes: 64 additions & 0 deletions src/Form/Type/ActorType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<?php

// This file is part of Bileto.
// Copyright 2022-2024 Probesys
// SPDX-License-Identifier: AGPL-3.0-or-later

namespace App\Form\Type;

use App\Entity;
use App\Service;
use Symfony\Bridge\Doctrine\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\ChoiceList\ChoiceList;
use Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface;
use Symfony\Component\OptionsResolver\Options;
use Symfony\Component\OptionsResolver\OptionsResolver;

class ActorType extends AbstractType
{
public function __construct(
private Service\ActorsLister $actorsLister,
) {
}

public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'class' => Entity\User::class,

'choice_loader' => function (Options $options): ChoiceLoaderInterface {
$organization = $options['organization'];
$roleType = $options['roleType'];

$vary = [$organization, $roleType];

return ChoiceList::lazy(
$this,
function () use ($organization, $roleType): array {
if ($organization) {
return $this->actorsLister->findByOrganization($organization, $roleType);
} else {
return $this->actorsLister->findAll($roleType);
}
},
$vary,
);
},

'choice_label' => 'displayName',
'choice_value' => 'id',

'organization' => null,
'roleType' => 'any',
]);

$resolver->setAllowedTypes('organization', [Entity\Organization::class, null]);
$resolver->setAllowedValues('roleType', Service\ActorsLister::VALID_ROLE_TYPES);
}

public function getParent(): string
{
return Type\EntityType::class;
}
}
1 change: 0 additions & 1 deletion src/Form/Type/LabelType.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ public function configureOptions(OptionsResolver $resolver): void
'choice_loader' => function (Options $options): ChoiceLoaderInterface {
return ChoiceList::lazy(
$this,

function () {
$labels = $this->labelRepository->findAll();
$this->labelSorter->sort($labels);
Expand Down
Loading

0 comments on commit 8b35064

Please sign in to comment.