From 9cefd3e532d4421b4ee1bca21409da9063fd4e3a Mon Sep 17 00:00:00 2001 From: AmonDeShir Date: Thu, 8 Aug 2024 09:16:21 +0200 Subject: [PATCH 1/7] add organization page --- app/Http/Controllers/GithubController.php | 33 ------- .../Controllers/OrganizationController.php | 93 +++++++++++++++++++ app/Http/Resources/OrganizationResource.php | 32 +++++++ app/Models/Organization.php | 47 +++++++++- ...26_add_fetch_at_to_organizations_table.php | 23 +++++ resources/js/Components/Navbar.vue | 1 + resources/js/Components/ProgressButton.vue | 21 +++++ resources/js/Pages/Organization.vue | 89 ++++++++++++++++++ resources/js/Types/BatchProgress.d.ts | 5 + resources/js/Types/Organization.d.ts | 14 +++ routes/web.php | 7 +- 11 files changed, 329 insertions(+), 36 deletions(-) create mode 100644 app/Http/Controllers/OrganizationController.php create mode 100644 app/Http/Resources/OrganizationResource.php create mode 100644 database/migrations/2024_08_08_063726_add_fetch_at_to_organizations_table.php create mode 100644 resources/js/Components/ProgressButton.vue create mode 100644 resources/js/Pages/Organization.vue create mode 100644 resources/js/Types/BatchProgress.d.ts create mode 100644 resources/js/Types/Organization.d.ts diff --git a/app/Http/Controllers/GithubController.php b/app/Http/Controllers/GithubController.php index 5c4bb42e..7cc0e20a 100644 --- a/app/Http/Controllers/GithubController.php +++ b/app/Http/Controllers/GithubController.php @@ -4,12 +4,9 @@ namespace App\Http\Controllers; -use App\Jobs\FetchRepositoriesJob; use App\Models\User; use App\Services\AssignUserToOrganizationsService; -use Illuminate\Http\JsonResponse; use Illuminate\Support\Facades\Auth; -use Illuminate\Support\Facades\Bus; use Inertia\Inertia; use Inertia\Response; use Laravel\Socialite\Facades\Socialite; @@ -46,36 +43,6 @@ public function callback(): RedirectResponse return redirect("/"); } - public function fetchData(int $organizationId): JsonResponse - { - $jobs = [ - new FetchRepositoriesJob($organizationId, Auth::user()->id), - ]; - - $batch = Bus::batch($jobs)->dispatch(); - - return response()->json(["batch" => $batch->id]); - } - - public function status(string $batchId): JsonResponse - { - $batch = Bus::findBatch($batchId); - - if ($batch === null) { - return response()->json(["message" => "Batch not found"], 404); - } - - if ($batch->cancelled()) { - return response()->json(["message" => "There was an error, please try again latter."], 500); - } - - return response()->json([ - "all" => $batch->totalJobs, - "done" => $batch->processedJobs(), - "finished" => $batch->finished(), - ]); - } - public function login(): Response { return Inertia::render("Login"); diff --git a/app/Http/Controllers/OrganizationController.php b/app/Http/Controllers/OrganizationController.php new file mode 100644 index 00000000..ea5652f9 --- /dev/null +++ b/app/Http/Controllers/OrganizationController.php @@ -0,0 +1,93 @@ +user(); + $organizations = $user->organizations()->with(["repositories.workflowRuns.workflowJobs", "repositories.workflowRuns.workflowActor", "users"])->get(); + $data = OrganizationResource::collection($organizations); + $status = []; + + foreach ($data as $organization) { + $status[$organization->id] = $this->getProgress($organization->id); + } + + return Inertia::render("Organization", ["data" => $data, "progress" => $status]); + } + + public function fetchData(int $organizationId): JsonResponse + { + $organization = Organization::query()->findOrFail($organizationId); + $batch = $this->findBatch($organizationId); + + if ($this->isFetching($batch)) { + return response()->json(["message" => "please wait"], Response::HTTP_CONFLICT); + } + + $jobs = [ + new FetchRepositoriesJob($organizationId, Auth::user()->id), + ]; + + $organization->fetch_at = Carbon::now(); + $organization->save(); + + $batch = Bus::batch($jobs)->finally(fn(): bool => Cache::delete("fetch/" . $organizationId))->dispatch(); + Cache::set("fetch/" . $organizationId, $batch->id); + + return response()->json(["message" => "fetching started"], Response::HTTP_OK); + } + + protected function isFetching(?Batch $batch): bool + { + return $batch !== null && !$batch->finished(); + } + + protected function getProgress(int $organizationId): ?array + { + $batch = $this->findBatch($organizationId); + + if ($batch !== null) { + return [ + "all" => $batch->totalJobs, + "done" => $batch->processedJobs(), + "finished" => $batch->finished(), + ]; + } + + return null; + } + + protected function findBatch(int $organizationId): ?Batch + { + $batch_id = Cache::get("fetch/" . $organizationId); + + if ($batch_id === null) { + return null; + } + + return Bus::findBatch($batch_id); + } +} diff --git a/app/Http/Resources/OrganizationResource.php b/app/Http/Resources/OrganizationResource.php new file mode 100644 index 00000000..3f095eba --- /dev/null +++ b/app/Http/Resources/OrganizationResource.php @@ -0,0 +1,32 @@ + + */ + public function toArray(Request $request): array + { + return [ + "id" => $this->id, + "github_id" => $this->github_id, + "name" => $this->name, + "avatar_url" => $this->avatar_url, + "fetched_at" => $this->fetch_at, + "users" => $this->users->count(), + "repos" => $this->repositories->count(), + "runs" => $this->workflowRuns->count(), + "jobs" => $this->jobCount, + "actors" => $this->actorCount, + "minutes" => $this->totalMinutes, + "price" => $this->totalPrice, + ]; + } +} diff --git a/app/Models/Organization.php b/app/Models/Organization.php index 56b191f2..53f91333 100644 --- a/app/Models/Organization.php +++ b/app/Models/Organization.php @@ -5,11 +5,13 @@ namespace App\Models; use Carbon\Carbon; +use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Database\Eloquent\Relations\HasMany; +use Illuminate\Database\Eloquent\Relations\HasManyThrough; /** * @property int $id @@ -18,10 +20,15 @@ * @property string $avatar_url * @property Carbon $created_at * @property Carbon $updated_at + * @property Carbon $fetch_at + * + * @property float $totalMinutes + * @property float $totalPrice * * @property Collection $users * @property Collection $repositories - */ + * @property Collection $workflowRuns + * */ class Organization extends Model { use HasFactory; @@ -41,4 +48,42 @@ public function repositories(): HasMany { return $this->HasMany(Repository::class); } + + public function workflowRuns(): HasManyThrough + { + return $this->HasManyThrough(WorkflowRun::class, Repository::class); + } + + protected function casts(): array + { + return [ + "fetch_at" => "datetime", + ]; + } + + protected function totalMinutes(): Attribute + { + return Attribute::get(fn(): float => $this->repositories->sum("totalMinutes")); + } + + protected function jobCount(): Attribute + { + return Attribute::get(fn(): int => $this->workflowRuns() + ->withCount("workflowJobs") + ->get() + ->sum("workflow_jobs_count")); + } + + protected function actorCount(): Attribute + { + return Attribute::get(fn(): int => $this->workflowRuns() + ->pluck("workflow_actor_id") + ->unique() + ->count()); + } + + protected function totalPrice(): Attribute + { + return Attribute::get(fn(): float => $this->repositories->sum("totalPrice")); + } } diff --git a/database/migrations/2024_08_08_063726_add_fetch_at_to_organizations_table.php b/database/migrations/2024_08_08_063726_add_fetch_at_to_organizations_table.php new file mode 100644 index 00000000..cedfd72a --- /dev/null +++ b/database/migrations/2024_08_08_063726_add_fetch_at_to_organizations_table.php @@ -0,0 +1,23 @@ +timestamp("fetch_at")->nullable(); + }); + } + + public function down(): void + { + Schema::table("organizations", function (Blueprint $table): void { + $table->dropColumn("fetch_at"); + }); + } +}; diff --git a/resources/js/Components/Navbar.vue b/resources/js/Components/Navbar.vue index 57ed33ee..de41bdc5 100644 --- a/resources/js/Components/Navbar.vue +++ b/resources/js/Components/Navbar.vue @@ -19,6 +19,7 @@ function getNavigationClass (tab: string) { Logo
+ Organizations Table Repositories Authors diff --git a/resources/js/Components/ProgressButton.vue b/resources/js/Components/ProgressButton.vue new file mode 100644 index 00000000..2862ee7e --- /dev/null +++ b/resources/js/Components/ProgressButton.vue @@ -0,0 +1,21 @@ + + + diff --git a/resources/js/Pages/Organization.vue b/resources/js/Pages/Organization.vue new file mode 100644 index 00000000..a252c37e --- /dev/null +++ b/resources/js/Pages/Organization.vue @@ -0,0 +1,89 @@ + + + diff --git a/resources/js/Types/BatchProgress.d.ts b/resources/js/Types/BatchProgress.d.ts new file mode 100644 index 00000000..ef205956 --- /dev/null +++ b/resources/js/Types/BatchProgress.d.ts @@ -0,0 +1,5 @@ +export interface BatchProgress { + all: number + done: number + finished: boolean +} diff --git a/resources/js/Types/Organization.d.ts b/resources/js/Types/Organization.d.ts new file mode 100644 index 00000000..a716d184 --- /dev/null +++ b/resources/js/Types/Organization.d.ts @@ -0,0 +1,14 @@ +export interface Organization { + id: number + github_id: string + name: string + avatar_url: string + fetched_at: number + repos: number + jobs: number + runs: number + minutes: number + price: number + actors: number + users: number +} diff --git a/routes/web.php b/routes/web.php index 5382c5ea..c5b89da4 100755 --- a/routes/web.php +++ b/routes/web.php @@ -4,6 +4,7 @@ use App\Http\Controllers\AuthorsController; use App\Http\Controllers\GithubController; +use App\Http\Controllers\OrganizationController; use App\Http\Controllers\RepositoriesController; use App\Http\Controllers\TableController; use Illuminate\Support\Facades\Route; @@ -13,8 +14,10 @@ Route::get("/repositories", [RepositoriesController::class, "show"]); Route::get("/authors", [AuthorsController::class, "show"]); - Route::get("/{organizationId}/fetch", [GithubController::class, "fetchData"])->middleware("auth"); -}); + Route::get("/organization", [OrganizationController::class, "show"]); + Route::post("/organization/{organizationId}/fetch", [OrganizationController::class, "fetchData"]); + Route::get("/organization/{organizationId}/progress", [OrganizationController::class, "status"]); +})->middleware("auth"); Route::redirect("/", "table"); Route::get("/auth/login", [GithubController::class, "login"])->name("login"); From b0862a31bf5507aebf4d0ba9a74ca757cd64bebe Mon Sep 17 00:00:00 2001 From: AmonDeShir Date: Thu, 8 Aug 2024 09:25:14 +0200 Subject: [PATCH 2/7] redirect from / to organization --- routes/web.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/routes/web.php b/routes/web.php index b5f5016c..fc22a177 100755 --- a/routes/web.php +++ b/routes/web.php @@ -20,7 +20,7 @@ Route::get("/organization/{organizationId}/progress", [OrganizationController::class, "status"]); })->middleware("auth"); -Route::redirect("/", "table"); +Route::redirect("/", "organization"); Route::get("/auth/login", [GithubController::class, "login"])->name("login"); Route::get("/auth/redirect", [GithubController::class, "redirect"]); Route::get("/auth/callback", [GithubController::class, "callback"]); From fd506fd64e0c90592f95ea5ee3c953b0a7cc8839 Mon Sep 17 00:00:00 2001 From: AmonDeShir Date: Thu, 8 Aug 2024 09:36:01 +0200 Subject: [PATCH 3/7] add font-bold to button --- resources/js/Components/ProgressButton.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/js/Components/ProgressButton.vue b/resources/js/Components/ProgressButton.vue index 2862ee7e..421a3a48 100644 --- a/resources/js/Components/ProgressButton.vue +++ b/resources/js/Components/ProgressButton.vue @@ -11,7 +11,7 @@ const emit = defineEmits(['click'])