Attempt to fix N+1 issues

This commit is contained in:
Mark Gregory
2026-06-16 20:09:21 +01:00
parent 85b96144c0
commit e81a509007
6 changed files with 67 additions and 22 deletions
@@ -24,6 +24,8 @@ final class TicketControlsController extends Controller
*/
public function updateAgent(Request $request, Ticket $ticket, WorkflowNotificationService $workflow): RedirectResponse
{
$ticket->loadMissing($this->ticketActionRelations());
Gate::authorize('update', $ticket);
$validated = $request->validate([
@@ -48,7 +50,7 @@ final class TicketControlsController extends Controller
'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());
return redirect()
@@ -63,6 +65,8 @@ final class TicketControlsController extends Controller
*/
public function updateStatus(Request $request, Ticket $ticket, WorkflowNotificationService $workflow, SlaService $sla): RedirectResponse
{
$ticket->loadMissing($this->ticketActionRelations());
Gate::authorize('update', $ticket);
$validated = $request->validate([
@@ -79,7 +83,7 @@ final class TicketControlsController extends Controller
$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) {
$sla->recordClosure($fresh);
@@ -97,6 +101,8 @@ final class TicketControlsController extends Controller
*/
public function updatePriority(Request $request, Ticket $ticket, WorkflowNotificationService $workflow): RedirectResponse
{
$ticket->loadMissing($this->ticketActionRelations());
Gate::authorize('update', $ticket);
$validated = $request->validate([
@@ -108,7 +114,7 @@ final class TicketControlsController extends Controller
'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());
return redirect()
@@ -116,6 +122,25 @@ final class TicketControlsController extends Controller
->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.
*
+3 -3
View File
@@ -224,7 +224,7 @@ final class TicketEdit extends Component
$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')) {
$tenantUsers = (clone $query)
@@ -248,8 +248,8 @@ final class TicketEdit extends Component
private function freshTicket(Ticket $ticket): Ticket
{
return $ticket->fresh([
'owner.company',
'agent.company',
'owner',
'agent',
'status',
'priority',
'category',
+16 -5
View File
@@ -55,8 +55,7 @@ final class TicketShow extends Component
public function mount(Ticket $ticket): void
{
Auth::user()?->loadMissing('company');
$ticket->loadMissing(['owner.company', 'agent.company']);
$ticket->loadMissing($this->ticketDetailRelations());
$this->authorize('view', $ticket);
$this->ticket = $this->freshTicket($ticket);
@@ -262,6 +261,8 @@ final class TicketShow extends Component
public function render(): View
{
$this->ticket = $this->freshTicket($this->ticket);
return view('helpdesk::livewire.ticket-show', [
'statuses' => Status::query()->orderBy('sort_order')->get(),
'priorities' => Priority::query()->orderBy('sort_order')->get(),
@@ -282,9 +283,14 @@ final class TicketShow extends Component
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',
'agent.company',
'status',
@@ -300,7 +306,12 @@ final class TicketShow extends Component
'checklistItems',
'attachments.user.company',
'gitCommits.repository',
]) ?? $ticket;
];
}
private function freshTicket(Ticket $ticket): Ticket
{
$ticket = $ticket->fresh($this->ticketDetailRelations()) ?? $ticket;
$comments = \Modules\Helpdesk\Models\Comment::query()
->where('ticket_id', $ticket->id)
+15
View File
@@ -24,6 +24,21 @@ final class Ticket extends Model
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> */
protected $fillable = [
'reference', 'company_id', 'user_id', 'agent_id', 'status_id', 'board_id', 'priority_id', 'category_id', 'project_id', 'team_id',
+4 -10
View File
@@ -193,14 +193,8 @@ versions:
"1.3.10":
changed:
- Fixed the ticket detail and ticket edit agent options queries by replacing unsupported orWhereKey usage with supported id comparisons.
- Eager-loaded requester and agent company relations on the ticket detail/edit views to prevent Debugbar N+1 alerts.
- Kept agent, status and priority persistence on CSRF-protected POST routes with compact action buttons.
- Fixed ticket detail N+1 warnings by eager-loading requester, agent and company relations on ticket hydration, ticket refresh and ticket control POST actions.
- 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.
- 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.
"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.
+1 -1
View File
@@ -22,5 +22,5 @@
"Modules\\Helpdesk\\Providers\\HelpdeskServiceProvider"
],
"files": [],
"version": "1.3.11"
"version": "1.3.10"
}