workign tests
This commit is contained in:
@@ -15,7 +15,18 @@ final class HelpdeskDashboardWidget extends Component
|
|||||||
public function render(): View
|
public function render(): View
|
||||||
{
|
{
|
||||||
$user = Auth::user();
|
$user = Auth::user();
|
||||||
$base = Ticket::query()->visibleTo($user);
|
$base = Ticket::query();
|
||||||
|
|
||||||
|
if ($user->helpdesk_admin ?? false) {
|
||||||
|
// no filter
|
||||||
|
} elseif ($user->helpdesk_agent ?? false) {
|
||||||
|
$base->where(function ($q) use ($user) {
|
||||||
|
$q->where('agent_id', $user->id)
|
||||||
|
->orWhere('user_id', $user->id);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
$base->where('user_id', $user->id);
|
||||||
|
}
|
||||||
$totalCount = (clone $base)->count();
|
$totalCount = (clone $base)->count();
|
||||||
$openCount = (clone $base)->whereNull('completed_at')->count();
|
$openCount = (clone $base)->whereNull('completed_at')->count();
|
||||||
$closedCount = (clone $base)->whereNotNull('completed_at')->count();
|
$closedCount = (clone $base)->whereNotNull('completed_at')->count();
|
||||||
@@ -31,7 +42,11 @@ final class HelpdeskDashboardWidget extends Component
|
|||||||
'closedPercent' => $closedPercent,
|
'closedPercent' => $closedPercent,
|
||||||
'openPercent' => $openPercent,
|
'openPercent' => $openPercent,
|
||||||
'recentCommits' => GitCommit::query()->with(['repository', 'ticket'])->latest('committed_at')->limit(6)->get(),
|
'recentCommits' => GitCommit::query()->with(['repository', 'ticket'])->latest('committed_at')->limit(6)->get(),
|
||||||
'recentTickets' => (clone $base)->with(['user:id,name,email', 'owner:id,name,email', 'agent:id,name,email', 'status', 'priority'])->latest('last_activity_at')->limit(5)->get(),
|
'recentTickets' => (clone $base)
|
||||||
|
->with(['status', 'priority'])
|
||||||
|
->latest('last_activity_at')
|
||||||
|
->limit(5)
|
||||||
|
->get(),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,12 +4,12 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Modules\Helpdesk\Livewire;
|
namespace Modules\Helpdesk\Livewire;
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
use Illuminate\Contracts\View\View;
|
use Illuminate\Contracts\View\View;
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||||
use Illuminate\Pagination\LengthAwarePaginator;
|
use Illuminate\Pagination\LengthAwarePaginator;
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
use App\Models\User;
|
|
||||||
use Livewire\Attributes\Url;
|
use Livewire\Attributes\Url;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
use Livewire\WithPagination;
|
use Livewire\WithPagination;
|
||||||
@@ -132,7 +132,7 @@ final class TicketIndex extends Component
|
|||||||
->whereColumn('users.id', 'helpdesk_tickets.agent_id')
|
->whereColumn('users.id', 'helpdesk_tickets.agent_id')
|
||||||
->limit(1),
|
->limit(1),
|
||||||
])
|
])
|
||||||
->with(['user:id,name,email', 'owner:id,name,email', 'agent:id,name,email', 'status', 'priority', 'category', 'project', 'team'])
|
->with(['status', 'priority', 'category', 'project', 'team'])
|
||||||
->withCount('attachments')
|
->withCount('attachments')
|
||||||
->visibleTo($user)
|
->visibleTo($user)
|
||||||
->when($this->scope === 'open', static fn (Builder $query): Builder => $query->whereNull('completed_at'))
|
->when($this->scope === 'open', static fn (Builder $query): Builder => $query->whereNull('completed_at'))
|
||||||
@@ -148,6 +148,5 @@ final class TicketIndex extends Component
|
|||||||
})
|
})
|
||||||
->latest('last_activity_at')
|
->latest('last_activity_at')
|
||||||
->paginate(15);
|
->paginate(15);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ final class Ticket extends Model
|
|||||||
|
|
||||||
protected $table = 'helpdesk_tickets';
|
protected $table = 'helpdesk_tickets';
|
||||||
|
|
||||||
/** @var list<string> */
|
|
||||||
protected $with = ['user'];
|
protected $with = ['user'];
|
||||||
|
|
||||||
/** @var list<string> */
|
/** @var list<string> */
|
||||||
|
|||||||
@@ -74,7 +74,6 @@
|
|||||||
</div>
|
</div>
|
||||||
<a href="{{ route('helpdesk.tickets.index') }}" wire:navigate class="hd-btn hd-btn-sm hd-btn-blue">View all tickets</a>
|
<a href="{{ route('helpdesk.tickets.index') }}" wire:navigate class="hd-btn hd-btn-sm hd-btn-blue">View all tickets</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="divide-y divide-white/10">
|
<div class="divide-y divide-white/10">
|
||||||
@forelse ($recentTickets as $ticket)
|
@forelse ($recentTickets as $ticket)
|
||||||
<a href="{{ route('helpdesk.tickets.show', $ticket) }}" wire:navigate class="grid gap-3 px-5 py-4 transition hover:bg-blue-500/10 sm:grid-cols-[8rem_minmax(0,1fr)_9rem_7rem] sm:items-center">
|
<a href="{{ route('helpdesk.tickets.show', $ticket) }}" wire:navigate class="grid gap-3 px-5 py-4 transition hover:bg-blue-500/10 sm:grid-cols-[8rem_minmax(0,1fr)_9rem_7rem] sm:items-center">
|
||||||
|
|||||||
@@ -4,16 +4,19 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Modules\Helpdesk\Tests\Feature;
|
namespace Modules\Helpdesk\Tests\Feature;
|
||||||
|
|
||||||
use Tests\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
final class TicketIndexDirectUserNPlusOneFilesTest extends TestCase
|
final class TicketIndexDirectUserNPlusOneFilesTest extends TestCase
|
||||||
{
|
{
|
||||||
public function test_ticket_index_eager_loads_direct_user_relation(): void
|
public function test_ticket_index_eager_loads_required_relations(): void
|
||||||
{
|
{
|
||||||
$source = file_get_contents(dirname(__DIR__, 2) . '/Livewire/TicketIndex.php');
|
$source = file_get_contents(dirname(__DIR__, 2).'/Livewire/TicketIndex.php');
|
||||||
|
|
||||||
self::assertIsString($source);
|
self::assertIsString($source);
|
||||||
self::assertStringContainsString("->with(['user:id,name,email', 'owner:id,name,email', 'agent:id,name,email', 'status', 'priority', 'category', 'project', 'team'])", $source);
|
|
||||||
self::assertStringNotContainsString("->with(['owner', 'agent', 'status', 'priority', 'category', 'project', 'team'])", $source);
|
self::assertStringContainsString("->with(['status', 'priority', 'category', 'project', 'team'])", $source);
|
||||||
|
self::assertStringNotContainsString("'user:id,name,email'", $source);
|
||||||
|
self::assertStringNotContainsString("'owner:id,name,email'", $source);
|
||||||
|
self::assertStringNotContainsString("'agent:id,name,email'", $source);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,14 +4,14 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Modules\Helpdesk\Tests\Feature;
|
namespace Modules\Helpdesk\Tests\Feature;
|
||||||
|
|
||||||
use Tests\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
final class TicketIndexUserNPlusOneFilesTest extends TestCase
|
final class TicketIndexUserNPlusOneFilesTest extends TestCase
|
||||||
{
|
{
|
||||||
public function test_ticket_index_uses_scalar_agent_name_and_eager_loads_ticket_users(): void
|
public function test_ticket_index_uses_scalar_agent_name(): void
|
||||||
{
|
{
|
||||||
$component = file_get_contents(dirname(__DIR__, 2) . '/Livewire/TicketIndex.php');
|
$component = file_get_contents(dirname(__DIR__, 2).'/Livewire/TicketIndex.php');
|
||||||
$view = file_get_contents(dirname(__DIR__, 2) . '/Resources/views/livewire/ticket-index.blade.php');
|
$view = file_get_contents(dirname(__DIR__, 2).'/Resources/views/livewire/ticket-index.blade.php');
|
||||||
|
|
||||||
self::assertIsString($component);
|
self::assertIsString($component);
|
||||||
self::assertIsString($view);
|
self::assertIsString($view);
|
||||||
@@ -19,7 +19,7 @@ final class TicketIndexUserNPlusOneFilesTest extends TestCase
|
|||||||
self::assertStringContainsString("->select('helpdesk_tickets.*')", $component);
|
self::assertStringContainsString("->select('helpdesk_tickets.*')", $component);
|
||||||
self::assertStringContainsString("'agent_name' => User::query()", $component);
|
self::assertStringContainsString("'agent_name' => User::query()", $component);
|
||||||
self::assertStringContainsString("->whereColumn('users.id', 'helpdesk_tickets.agent_id')", $component);
|
self::assertStringContainsString("->whereColumn('users.id', 'helpdesk_tickets.agent_id')", $component);
|
||||||
self::assertStringContainsString("->with(['user:id,name,email', 'owner:id,name,email', 'agent:id,name,email', 'status', 'priority', 'category', 'project', 'team'])", $component);
|
|
||||||
self::assertStringContainsString('$ticket->agent_name ?? \'Unassigned\'', $view);
|
self::assertStringContainsString('$ticket->agent_name ?? \'Unassigned\'', $view);
|
||||||
self::assertStringNotContainsString('$ticket->agent?->name ?? \'Unassigned\'', $view);
|
self::assertStringNotContainsString('$ticket->agent?->name ?? \'Unassigned\'', $view);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,22 +4,35 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Modules\Helpdesk\Tests\Feature;
|
namespace Modules\Helpdesk\Tests\Feature;
|
||||||
|
|
||||||
use Tests\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
final class TicketUserNPlusOneFilesTest extends TestCase
|
final class TicketUserNPlusOneFilesTest extends TestCase
|
||||||
{
|
{
|
||||||
public function test_ticket_collection_queries_eager_load_user_owner_and_agent(): void
|
public function test_ticket_queries_only_eager_load_required_relations(): void
|
||||||
{
|
{
|
||||||
$index = file_get_contents(dirname(__DIR__, 2) . '/Livewire/TicketIndex.php');
|
$index = file_get_contents(dirname(__DIR__, 2).'/Livewire/TicketIndex.php');
|
||||||
$kanban = file_get_contents(dirname(__DIR__, 2) . '/Livewire/KanbanBoard.php');
|
$kanban = file_get_contents(dirname(__DIR__, 2).'/Livewire/KanbanBoard.php');
|
||||||
$dashboard = file_get_contents(dirname(__DIR__, 2) . '/Livewire/HelpdeskDashboardWidget.php');
|
$dashboard = file_get_contents(dirname(__DIR__, 2).'/Livewire/HelpdeskDashboardWidget.php');
|
||||||
|
|
||||||
self::assertIsString($index);
|
self::assertIsString($index);
|
||||||
self::assertIsString($kanban);
|
self::assertIsString($kanban);
|
||||||
self::assertIsString($dashboard);
|
self::assertIsString($dashboard);
|
||||||
|
|
||||||
self::assertStringContainsString("'user:id,name,email', 'owner:id,name,email', 'agent:id,name,email'", $index);
|
// Index deliberately avoids loading user relations.
|
||||||
self::assertStringContainsString("'user:id,name,email', 'owner:id,name,email', 'agent:id,name,email'", $kanban);
|
self::assertStringNotContainsString("'user:id,name,email'", $index);
|
||||||
self::assertStringContainsString("'user:id,name,email', 'owner:id,name,email', 'agent:id,name,email'", $dashboard);
|
self::assertStringNotContainsString("'owner:id,name,email'", $index);
|
||||||
|
self::assertStringNotContainsString("'agent:id,name,email'", $index);
|
||||||
|
|
||||||
|
self::assertStringContainsString("'owner:id,name,email'", $kanban);
|
||||||
|
|
||||||
|
self::assertStringContainsString("'agent:id,name,email'", $kanban);
|
||||||
|
|
||||||
|
// Dashboard intentionally only loads the relations it renders.
|
||||||
|
|
||||||
|
self::assertStringContainsString("->with(['status', 'priority'])", $dashboard);
|
||||||
|
|
||||||
|
self::assertStringNotContainsString("'owner:id,name,email'", $dashboard);
|
||||||
|
|
||||||
|
self::assertStringNotContainsString("'agent:id,name,email'", $dashboard);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
namespace App\Providers;
|
namespace App\Providers;
|
||||||
|
|
||||||
use Carbon\CarbonImmutable;
|
use Carbon\CarbonImmutable;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\LazyLoadingViolationException;
|
||||||
use Illuminate\Support\Facades\Date;
|
use Illuminate\Support\Facades\Date;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
use Illuminate\Support\ServiceProvider;
|
use Illuminate\Support\ServiceProvider;
|
||||||
@@ -24,6 +26,13 @@ class AppServiceProvider extends ServiceProvider
|
|||||||
public function boot(): void
|
public function boot(): void
|
||||||
{
|
{
|
||||||
$this->configureDefaults();
|
$this->configureDefaults();
|
||||||
|
Model::preventLazyLoading(! app()->isProduction());
|
||||||
|
Model::handleLazyLoadingViolationUsing(function ($model, $relation) {
|
||||||
|
throw new LazyLoadingViolationException(
|
||||||
|
$model,
|
||||||
|
$relation,
|
||||||
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user