Attempt to fix N+1 issues
This commit is contained in:
@@ -24,6 +24,8 @@ final class TicketControlsController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function updateAgent(Request $request, Ticket $ticket, WorkflowNotificationService $workflow): RedirectResponse
|
public function updateAgent(Request $request, Ticket $ticket, WorkflowNotificationService $workflow): RedirectResponse
|
||||||
{
|
{
|
||||||
|
$ticket->loadMissing($this->ticketActionRelations());
|
||||||
|
|
||||||
Gate::authorize('update', $ticket);
|
Gate::authorize('update', $ticket);
|
||||||
|
|
||||||
$validated = $request->validate([
|
$validated = $request->validate([
|
||||||
@@ -48,7 +50,7 @@ final class TicketControlsController extends Controller
|
|||||||
'last_activity_at' => now(),
|
'last_activity_at' => now(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$fresh = $ticket->fresh(['agent.company', 'owner.company', 'status', 'priority', 'category', 'project', 'team', 'product']) ?? $ticket;
|
$fresh = $ticket->fresh(['agent', 'owner', 'status', 'priority', 'category', 'project', 'team', 'product']) ?? $ticket;
|
||||||
$workflow->dispatch($fresh, 'ticket.updated', Auth::user());
|
$workflow->dispatch($fresh, 'ticket.updated', Auth::user());
|
||||||
|
|
||||||
return redirect()
|
return redirect()
|
||||||
@@ -63,6 +65,8 @@ final class TicketControlsController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function updateStatus(Request $request, Ticket $ticket, WorkflowNotificationService $workflow, SlaService $sla): RedirectResponse
|
public function updateStatus(Request $request, Ticket $ticket, WorkflowNotificationService $workflow, SlaService $sla): RedirectResponse
|
||||||
{
|
{
|
||||||
|
$ticket->loadMissing($this->ticketActionRelations());
|
||||||
|
|
||||||
Gate::authorize('update', $ticket);
|
Gate::authorize('update', $ticket);
|
||||||
|
|
||||||
$validated = $request->validate([
|
$validated = $request->validate([
|
||||||
@@ -79,7 +83,7 @@ final class TicketControlsController extends Controller
|
|||||||
|
|
||||||
$this->ensureBoardContainsStatus($ticket, (int) $status->id);
|
$this->ensureBoardContainsStatus($ticket, (int) $status->id);
|
||||||
|
|
||||||
$fresh = $ticket->fresh(['agent.company', 'owner.company', 'status', 'priority', 'category', 'project', 'team', 'product']) ?? $ticket;
|
$fresh = $ticket->fresh(['agent', 'owner', 'status', 'priority', 'category', 'project', 'team', 'product']) ?? $ticket;
|
||||||
|
|
||||||
if ($status->is_closed) {
|
if ($status->is_closed) {
|
||||||
$sla->recordClosure($fresh);
|
$sla->recordClosure($fresh);
|
||||||
@@ -97,6 +101,8 @@ final class TicketControlsController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function updatePriority(Request $request, Ticket $ticket, WorkflowNotificationService $workflow): RedirectResponse
|
public function updatePriority(Request $request, Ticket $ticket, WorkflowNotificationService $workflow): RedirectResponse
|
||||||
{
|
{
|
||||||
|
$ticket->loadMissing($this->ticketActionRelations());
|
||||||
|
|
||||||
Gate::authorize('update', $ticket);
|
Gate::authorize('update', $ticket);
|
||||||
|
|
||||||
$validated = $request->validate([
|
$validated = $request->validate([
|
||||||
@@ -108,7 +114,7 @@ final class TicketControlsController extends Controller
|
|||||||
'last_activity_at' => now(),
|
'last_activity_at' => now(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$fresh = $ticket->fresh(['agent.company', 'owner.company', 'status', 'priority', 'category', 'project', 'team', 'product']) ?? $ticket;
|
$fresh = $ticket->fresh(['agent', 'owner', 'status', 'priority', 'category', 'project', 'team', 'product']) ?? $ticket;
|
||||||
$workflow->dispatch($fresh, 'ticket.updated', Auth::user());
|
$workflow->dispatch($fresh, 'ticket.updated', Auth::user());
|
||||||
|
|
||||||
return redirect()
|
return redirect()
|
||||||
@@ -116,6 +122,25 @@ final class TicketControlsController extends Controller
|
|||||||
->with('ticket-action-updated', 'Priority updated to '.($fresh->priority?->name ?? 'selected priority').'.');
|
->with('ticket-action-updated', 'Priority updated to '.($fresh->priority?->name ?? 'selected priority').'.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Relations needed by policies, redirect messages, and workflow templates.
|
||||||
|
*
|
||||||
|
* @return list<string>
|
||||||
|
*/
|
||||||
|
private function ticketActionRelations(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'owner.company',
|
||||||
|
'agent.company',
|
||||||
|
'status',
|
||||||
|
'priority',
|
||||||
|
'category',
|
||||||
|
'project',
|
||||||
|
'team',
|
||||||
|
'product',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Apply a direct guarded ticket update without relying on Livewire hydration.
|
* Apply a direct guarded ticket update without relying on Livewire hydration.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -224,7 +224,7 @@ final class TicketEdit extends Component
|
|||||||
$columns[] = 'company_id';
|
$columns[] = 'company_id';
|
||||||
}
|
}
|
||||||
|
|
||||||
$query = User::query()->select($columns)->orderBy('name');
|
$query = User::query()->select($columns)->with('company')->orderBy('name');
|
||||||
|
|
||||||
if ($this->company_id !== null && Schema::hasColumn('users', 'company_id')) {
|
if ($this->company_id !== null && Schema::hasColumn('users', 'company_id')) {
|
||||||
$tenantUsers = (clone $query)
|
$tenantUsers = (clone $query)
|
||||||
@@ -248,8 +248,8 @@ final class TicketEdit extends Component
|
|||||||
private function freshTicket(Ticket $ticket): Ticket
|
private function freshTicket(Ticket $ticket): Ticket
|
||||||
{
|
{
|
||||||
return $ticket->fresh([
|
return $ticket->fresh([
|
||||||
'owner.company',
|
'owner',
|
||||||
'agent.company',
|
'agent',
|
||||||
'status',
|
'status',
|
||||||
'priority',
|
'priority',
|
||||||
'category',
|
'category',
|
||||||
|
|||||||
@@ -55,8 +55,7 @@ final class TicketShow extends Component
|
|||||||
|
|
||||||
public function mount(Ticket $ticket): void
|
public function mount(Ticket $ticket): void
|
||||||
{
|
{
|
||||||
Auth::user()?->loadMissing('company');
|
$ticket->loadMissing($this->ticketDetailRelations());
|
||||||
$ticket->loadMissing(['owner.company', 'agent.company']);
|
|
||||||
|
|
||||||
$this->authorize('view', $ticket);
|
$this->authorize('view', $ticket);
|
||||||
$this->ticket = $this->freshTicket($ticket);
|
$this->ticket = $this->freshTicket($ticket);
|
||||||
@@ -262,6 +261,8 @@ final class TicketShow extends Component
|
|||||||
|
|
||||||
public function render(): View
|
public function render(): View
|
||||||
{
|
{
|
||||||
|
$this->ticket = $this->freshTicket($this->ticket);
|
||||||
|
|
||||||
return view('helpdesk::livewire.ticket-show', [
|
return view('helpdesk::livewire.ticket-show', [
|
||||||
'statuses' => Status::query()->orderBy('sort_order')->get(),
|
'statuses' => Status::query()->orderBy('sort_order')->get(),
|
||||||
'priorities' => Priority::query()->orderBy('sort_order')->get(),
|
'priorities' => Priority::query()->orderBy('sort_order')->get(),
|
||||||
@@ -282,9 +283,14 @@ final class TicketShow extends Component
|
|||||||
return (int) $value;
|
return (int) $value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function freshTicket(Ticket $ticket): Ticket
|
/**
|
||||||
|
* Relations required by the ticket detail view and workflow renderers.
|
||||||
|
*
|
||||||
|
* @return list<string>
|
||||||
|
*/
|
||||||
|
private function ticketDetailRelations(): array
|
||||||
{
|
{
|
||||||
$ticket = $ticket->fresh([
|
return [
|
||||||
'owner.company',
|
'owner.company',
|
||||||
'agent.company',
|
'agent.company',
|
||||||
'status',
|
'status',
|
||||||
@@ -300,7 +306,12 @@ final class TicketShow extends Component
|
|||||||
'checklistItems',
|
'checklistItems',
|
||||||
'attachments.user.company',
|
'attachments.user.company',
|
||||||
'gitCommits.repository',
|
'gitCommits.repository',
|
||||||
]) ?? $ticket;
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function freshTicket(Ticket $ticket): Ticket
|
||||||
|
{
|
||||||
|
$ticket = $ticket->fresh($this->ticketDetailRelations()) ?? $ticket;
|
||||||
|
|
||||||
$comments = \Modules\Helpdesk\Models\Comment::query()
|
$comments = \Modules\Helpdesk\Models\Comment::query()
|
||||||
->where('ticket_id', $ticket->id)
|
->where('ticket_id', $ticket->id)
|
||||||
|
|||||||
@@ -24,6 +24,21 @@ final class Ticket extends Model
|
|||||||
|
|
||||||
protected $table = 'helpdesk_tickets';
|
protected $table = 'helpdesk_tickets';
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Relations that are repeatedly displayed in ticket lists/detail views.
|
||||||
|
*
|
||||||
|
* Eager loading these user/company relations prevents the debug-bar N+1
|
||||||
|
* detector from firing when Blade, policies, or workflow rendering read the
|
||||||
|
* requester or assigned agent details.
|
||||||
|
*
|
||||||
|
* @var list<string>
|
||||||
|
*/
|
||||||
|
protected $with = [
|
||||||
|
'owner.company',
|
||||||
|
'agent.company',
|
||||||
|
];
|
||||||
|
|
||||||
/** @var list<string> */
|
/** @var list<string> */
|
||||||
protected $fillable = [
|
protected $fillable = [
|
||||||
'reference', 'company_id', 'user_id', 'agent_id', 'status_id', 'board_id', 'priority_id', 'category_id', 'project_id', 'team_id',
|
'reference', 'company_id', 'user_id', 'agent_id', 'status_id', 'board_id', 'priority_id', 'category_id', 'project_id', 'team_id',
|
||||||
|
|||||||
@@ -193,14 +193,8 @@ versions:
|
|||||||
|
|
||||||
"1.3.10":
|
"1.3.10":
|
||||||
changed:
|
changed:
|
||||||
- Fixed the ticket detail and ticket edit agent options queries by replacing unsupported orWhereKey usage with supported id comparisons.
|
- Fixed ticket detail N+1 warnings by eager-loading requester, agent and company relations on ticket hydration, ticket refresh and ticket control POST actions.
|
||||||
- Eager-loaded requester and agent company relations on the ticket detail/edit views to prevent Debugbar N+1 alerts.
|
- Added default eager loading for Helpdesk ticket requester and assigned-agent company details to prevent repeated lazy user/company queries in detail and list views.
|
||||||
- Kept agent, status and priority persistence on CSRF-protected POST routes with compact action buttons.
|
- Replaced remaining unsupported orWhereKey usage with supported id predicates in ticket assignment option queries.
|
||||||
|
- Kept agent, status and priority controls as compact CSRF-protected POST forms that persist directly to helpdesk_tickets.
|
||||||
- Kept module.json version in sync with changelog.yaml.
|
- Kept module.json version in sync with changelog.yaml.
|
||||||
|
|
||||||
"1.3.11":
|
|
||||||
changed:
|
|
||||||
- Eager-loaded the current user company, ticket requester company, ticket agent company, comment user company and attachment user company on the ticket detail page to remove the remaining Debugbar N+1 alert.
|
|
||||||
- Eager-loaded company data for assignable agent dropdown users without changing agent, status or priority persistence behaviour.
|
|
||||||
- Kept module.json version in sync with changelog.yaml.
|
|
||||||
|
|
||||||
|
|||||||
@@ -22,5 +22,5 @@
|
|||||||
"Modules\\Helpdesk\\Providers\\HelpdeskServiceProvider"
|
"Modules\\Helpdesk\\Providers\\HelpdeskServiceProvider"
|
||||||
],
|
],
|
||||||
"files": [],
|
"files": [],
|
||||||
"version": "1.3.11"
|
"version": "1.3.10"
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user