phase 3
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Event;
|
||||
use App\Models\Issue;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class DashboardController extends Controller
|
||||
{
|
||||
public function __invoke(Request $request): View
|
||||
{
|
||||
$user = $request->user();
|
||||
$projectIds = $user->projects()->pluck('projects.id');
|
||||
|
||||
$unresolvedCount = Issue::whereIn('project_id', $projectIds)
|
||||
->where('status', 'unresolved')
|
||||
->count();
|
||||
|
||||
$events24h = Event::whereIn('project_id', $projectIds)
|
||||
->where('created_at', '>=', Carbon::now()->subDay())
|
||||
->count();
|
||||
|
||||
$mostActiveProjects = $user->projects()
|
||||
->withCount(['issues' => fn ($q) => $q->where('status', 'unresolved')])
|
||||
->withCount(['events' => fn ($q) => $q->where('events.created_at', '>=', Carbon::now()->subDay())])
|
||||
->orderByDesc('events_count')
|
||||
->limit(5)
|
||||
->get();
|
||||
|
||||
$days = (int) $request->query('days', 14);
|
||||
$eventsOverTime = $this->getEventsOverTime($projectIds, $days);
|
||||
|
||||
return view('dashboard', [
|
||||
'unresolvedCount' => $unresolvedCount,
|
||||
'events24h' => $events24h,
|
||||
'mostActiveProjects' => $mostActiveProjects,
|
||||
'eventsOverTime' => $eventsOverTime,
|
||||
'days' => $days,
|
||||
]);
|
||||
}
|
||||
|
||||
private function getEventsOverTime($projectIds, int $days): array
|
||||
{
|
||||
$since = Carbon::now()->subDays($days)->startOfDay();
|
||||
|
||||
$rows = Event::whereIn('project_id', $projectIds)
|
||||
->where('created_at', '>=', $since)
|
||||
->select(DB::raw('DATE(created_at) as date'), DB::raw('COUNT(*) as count'))
|
||||
->groupBy('date')
|
||||
->orderBy('date')
|
||||
->pluck('count', 'date')
|
||||
->toArray();
|
||||
|
||||
$result = [];
|
||||
for ($i = $days - 1; $i >= 0; $i--) {
|
||||
$date = Carbon::now()->subDays($i)->format('Y-m-d');
|
||||
$result[] = [
|
||||
'date' => $date,
|
||||
'count' => $rows[$date] ?? 0,
|
||||
];
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Event;
|
||||
use App\Models\Issue;
|
||||
use App\Models\Project;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class EventController extends Controller
|
||||
{
|
||||
public function show(Request $request, Project $project, Issue $issue, Event $event): View
|
||||
{
|
||||
$this->authorizeProject($request, $project);
|
||||
abort_unless($issue->project_id === $project->id, 404);
|
||||
abort_unless($event->issue_id === $issue->id, 404);
|
||||
|
||||
$previousEvent = Event::where('issue_id', $issue->id)
|
||||
->where('timestamp', '<', $event->timestamp)
|
||||
->orderByDesc('timestamp')
|
||||
->first();
|
||||
|
||||
$nextEvent = Event::where('issue_id', $issue->id)
|
||||
->where('timestamp', '>', $event->timestamp)
|
||||
->orderBy('timestamp')
|
||||
->first();
|
||||
|
||||
return view('events.show', [
|
||||
'project' => $project,
|
||||
'issue' => $issue,
|
||||
'event' => $event,
|
||||
'previousEvent' => $previousEvent,
|
||||
'nextEvent' => $nextEvent,
|
||||
]);
|
||||
}
|
||||
|
||||
private function authorizeProject(Request $request, Project $project): void
|
||||
{
|
||||
abort_unless(
|
||||
$request->user()->projects()->where('projects.id', $project->id)->exists(),
|
||||
403,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Issue;
|
||||
use App\Models\Project;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class IssueController extends Controller
|
||||
{
|
||||
public function index(Request $request, Project $project): View
|
||||
{
|
||||
$this->authorizeProject($request, $project);
|
||||
|
||||
$query = $project->issues();
|
||||
|
||||
if ($status = $request->query('status')) {
|
||||
$query->where('status', $status);
|
||||
} else {
|
||||
$query->where('status', 'unresolved');
|
||||
}
|
||||
|
||||
if ($level = $request->query('level')) {
|
||||
$query->where('level', $level);
|
||||
}
|
||||
|
||||
if ($search = $request->query('search')) {
|
||||
$query->where(function ($q) use ($search) {
|
||||
$q->where('title', 'like', "%{$search}%")
|
||||
->orWhere('culprit', 'like', "%{$search}%");
|
||||
});
|
||||
}
|
||||
|
||||
$sortField = $request->query('sort', 'last_seen_at');
|
||||
$sortDir = $request->query('dir', 'desc');
|
||||
$allowedSorts = ['last_seen_at', 'first_seen_at', 'times_seen', 'level'];
|
||||
if (in_array($sortField, $allowedSorts)) {
|
||||
$query->orderBy($sortField, $sortDir === 'asc' ? 'asc' : 'desc');
|
||||
}
|
||||
|
||||
$issues = $query->cursorPaginate(25)->withQueryString();
|
||||
|
||||
return view('issues.index', [
|
||||
'project' => $project,
|
||||
'issues' => $issues,
|
||||
'currentStatus' => $status ?: 'unresolved',
|
||||
'currentLevel' => $level,
|
||||
'search' => $search,
|
||||
]);
|
||||
}
|
||||
|
||||
public function show(Request $request, Project $project, Issue $issue): View
|
||||
{
|
||||
$this->authorizeProject($request, $project);
|
||||
abort_unless($issue->project_id === $project->id, 404);
|
||||
|
||||
$latestEvent = $issue->events()->latest('timestamp')->first();
|
||||
|
||||
$events = $issue->events()
|
||||
->latest('timestamp')
|
||||
->cursorPaginate(10)
|
||||
->withQueryString();
|
||||
|
||||
return view('issues.show', [
|
||||
'project' => $project,
|
||||
'issue' => $issue,
|
||||
'latestEvent' => $latestEvent,
|
||||
'events' => $events,
|
||||
]);
|
||||
}
|
||||
|
||||
public function updateStatus(Request $request, Project $project, Issue $issue): RedirectResponse
|
||||
{
|
||||
$this->authorizeProject($request, $project);
|
||||
abort_unless($issue->project_id === $project->id, 404);
|
||||
|
||||
$action = $request->input('action');
|
||||
|
||||
match ($action) {
|
||||
'resolve' => $issue->resolve(),
|
||||
'unresolve' => $issue->unresolve(),
|
||||
'ignore' => $issue->ignore(),
|
||||
'mute' => $issue->mute(),
|
||||
default => null,
|
||||
};
|
||||
|
||||
return back()->with('status', 'Issue ' . $action . 'd.');
|
||||
}
|
||||
|
||||
public function bulkAction(Request $request, Project $project): RedirectResponse
|
||||
{
|
||||
$this->authorizeProject($request, $project);
|
||||
|
||||
$validated = $request->validate([
|
||||
'action' => 'required|in:resolve,ignore,mute,delete',
|
||||
'issues' => 'required|array',
|
||||
'issues.*' => 'string',
|
||||
]);
|
||||
|
||||
$query = Issue::where('project_id', $project->id)
|
||||
->whereIn('id', $validated['issues']);
|
||||
|
||||
match ($validated['action']) {
|
||||
'resolve' => $query->update(['status' => 'resolved', 'is_regression' => false]),
|
||||
'ignore' => $query->update(['status' => 'ignored']),
|
||||
'mute' => $query->update(['status' => 'muted']),
|
||||
'delete' => $query->delete(),
|
||||
};
|
||||
|
||||
$count = count($validated['issues']);
|
||||
|
||||
return back()->with('status', "{$count} issue(s) {$validated['action']}d.");
|
||||
}
|
||||
|
||||
private function authorizeProject(Request $request, Project $project): void
|
||||
{
|
||||
abort_unless(
|
||||
$request->user()->projects()->where('projects.id', $project->id)->exists(),
|
||||
403,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Project;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class ProjectController extends Controller
|
||||
{
|
||||
public function index(Request $request): View
|
||||
{
|
||||
$projects = $request->user()
|
||||
->projects()
|
||||
->withCount(['issues' => fn ($q) => $q->where('status', 'unresolved')])
|
||||
->orderBy('name')
|
||||
->get();
|
||||
|
||||
return view('projects.index', compact('projects'));
|
||||
}
|
||||
|
||||
public function create(): View
|
||||
{
|
||||
return view('projects.create');
|
||||
}
|
||||
|
||||
public function store(Request $request): RedirectResponse
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'name' => 'required|string|max:255',
|
||||
'platform' => 'nullable|string|max:50',
|
||||
]);
|
||||
|
||||
$project = Project::create($validated);
|
||||
|
||||
$request->user()->projects()->attach($project, ['role' => 'owner']);
|
||||
|
||||
return redirect()
|
||||
->route('projects.settings', $project)
|
||||
->with('status', 'Project created. Here is your DSN.');
|
||||
}
|
||||
|
||||
public function settings(Request $request, Project $project): View
|
||||
{
|
||||
$this->authorizeProject($request, $project);
|
||||
|
||||
return view('projects.settings', compact('project'));
|
||||
}
|
||||
|
||||
public function update(Request $request, Project $project): RedirectResponse
|
||||
{
|
||||
$this->authorizeProject($request, $project);
|
||||
|
||||
$validated = $request->validate([
|
||||
'name' => 'required|string|max:255',
|
||||
'platform' => 'nullable|string|max:50',
|
||||
]);
|
||||
|
||||
$project->update($validated);
|
||||
|
||||
return back()->with('status', 'Project updated.');
|
||||
}
|
||||
|
||||
public function regenerateKeys(Request $request, Project $project): RedirectResponse
|
||||
{
|
||||
$this->authorizeProject($request, $project);
|
||||
|
||||
$project->regenerateKeys();
|
||||
|
||||
return back()->with('status', 'DSN keys regenerated.');
|
||||
}
|
||||
|
||||
public function destroy(Request $request, Project $project): RedirectResponse
|
||||
{
|
||||
$this->authorizeProject($request, $project);
|
||||
|
||||
$project->delete();
|
||||
|
||||
return redirect()->route('projects.index')->with('status', 'Project deleted.');
|
||||
}
|
||||
|
||||
private function authorizeProject(Request $request, Project $project): void
|
||||
{
|
||||
abort_unless(
|
||||
$request->user()->projects()->where('projects.id', $project->id)->exists(),
|
||||
403,
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user