Skip to content

Commit adf435c

Browse files
committed
feat: approvedActivities API schema
1 parent a08212d commit adf435c

4 files changed

Lines changed: 113 additions & 8 deletions

File tree

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,11 @@
1212
.env
1313
.env.backup
1414
.phpunit.result.cache
15+
_lighthouse_ide_helper.php
1516
docker-compose.override.yml
1617
Homestead.json
1718
Homestead.yaml
1819
npm-debug.log
20+
programmatic-types.graphql
21+
schema-directives.graphql
1922
yarn-error.log
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
<?php
2+
3+
namespace App\GraphQL\Queries;
4+
5+
use App\Helper;
6+
use App\Models\Activity;
7+
use App\Models\Project;
8+
use Illuminate\Pagination\LengthAwarePaginator;
9+
use Nuwave\Lighthouse\Execution\ResolveInfo;
10+
use Nuwave\Lighthouse\Support\Contracts\GraphQLContext;
11+
12+
final class ApprovedActivities {
13+
public function __invoke($root, array $args, GraphQLContext $context, ResolveInfo $resolveInfo): LengthAwarePaginator {
14+
$perPage = $args['first'] ?? 25;
15+
$currentPage = $args['page'] ?? 1;
16+
$queryLimit = $perPage * $currentPage;
17+
18+
$projectsQuery = Project::where('closure_approved_status', 1)
19+
->whereBetween('closure_approved_at', [$args['from'], $args['until']]);
20+
$projects = $projectsQuery->with([
21+
'participants' => fn($query) => $query->where('approve_status', '>=', 1),
22+
'participants.user',
23+
'department',
24+
])
25+
->select([
26+
'id', 'year', 'number', 'name', 'department_id', 'period_start', 'period_end', 'duration', 'closure_approved_at',
27+
'closure_approved_status', 'advisor',
28+
])
29+
->orderBy('closure_approved_at', 'desc')
30+
->take($queryLimit)->get()
31+
->map(fn($project) => [
32+
'identifier' => $project->year.'-'.$project->number,
33+
'project_id' => $project->id,
34+
'name' => $project->name,
35+
'organization' => Helper::formatDepartmentName($project->department?->name ?? ''),
36+
'period_start' => $project->period_start?->addYears(543)->toDateTimeString(),
37+
'period_end' => $project->period_end?->addYears(543)->toDateTimeString(),
38+
'duration' => $project->duration,
39+
'approved_status' => $project->closure_approved_status,
40+
'approved_at' => $project->closure_approved_at,
41+
'advisor' => $project->advisor,
42+
'participants' => $project->participants,
43+
])->toBase();
44+
// Note that participant approve_status is not set in Activity
45+
$activitiesQuery = Activity::with(['participants', 'participants.user'])
46+
->whereBetween('updated_at', [$args['from'], $args['until']]);
47+
$activities = $activitiesQuery->orderBy('updated_at', 'desc')
48+
->take($queryLimit)->get()
49+
->map(fn($activity) => [
50+
'identifier' => 'A'.$activity->id,
51+
'activity_id' => $activity->id,
52+
'name' => $activity->name,
53+
'organization' => $activity->organization,
54+
'period_start' => $activity->period_start?->toDateTimeString(),
55+
'period_end' => $activity->period_end?->toDateTimeString(),
56+
'duration' => $activity->duration,
57+
'approved_at' => $activity->updated_at,
58+
'participants' => $activity->participants,
59+
]);
60+
$merged = $projects->merge($activities)->sortByDesc('approved_at');
61+
62+
return new LengthAwarePaginator(
63+
items: $merged->chunk($perPage)[$currentPage - 1] ?? [],
64+
total: ($projects->count() == $queryLimit or $activities->count() == $queryLimit)
65+
? ($projectsQuery->count() + $activitiesQuery->count()) // count directly from database
66+
: $merged->count(),
67+
perPage: $perPage,
68+
currentPage: $currentPage
69+
);
70+
}
71+
}

app/Models/User.php

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,11 @@
66
use Carbon\Carbon;
77
use Illuminate\Database\Eloquent\Builder;
88
use Illuminate\Database\Eloquent\Casts\Attribute;
9-
use Illuminate\Database\Eloquent\Collection;
109
use Illuminate\Database\Eloquent\Factories\HasFactory;
10+
use Illuminate\Database\Eloquent\Relations\HasMany;
1111
use Illuminate\Foundation\Auth\User as Authenticatable;
1212
use Illuminate\Notifications\Notifiable;
13+
use Illuminate\Support\Collection;
1314
use Laravel\Fortify\TwoFactorAuthenticatable;
1415
use Laravel\Jetstream\HasProfilePhoto;
1516
use Laravel\Sanctum\HasApiTokens;
@@ -86,7 +87,7 @@ class User extends Authenticatable {
8687
// 'profile_photo_url',
8788
];
8889

89-
public function participants(): \Illuminate\Database\Eloquent\Relations\HasMany {
90+
public function participants(): HasMany {
9091
return $this->hasMany(ProjectParticipant::class);
9192
}
9293

@@ -97,22 +98,21 @@ public function rolesArray(): Attribute {
9798
/**
9899
* @return Collection<ProjectParticipant>
99100
*/
100-
public function participantAndProjects(): Collection
101-
{
101+
public function participantAndProjects(): Collection {
102102
return $this->participants()->where('project_type', 'App\Models\Project')->with([
103103
'project', 'project.approvalDocument', 'project.summaryDocument',
104104
])->orderByDesc('id')->get()->filter(fn(ProjectParticipant $participant) => $participant->project?->approvalDocument)->values();
105105
}
106106

107-
public function projects(): \Illuminate\Database\Eloquent\Relations\HasMany {
107+
public function projects(): HasMany {
108108
return $this->hasMany(Project::class);
109109
}
110110

111111
/**
112112
* Returns the user's activity transcript, which is both
113113
* the projects and the activities (external projects) they have participated in.
114114
*/
115-
public function getActivityTranscript() {
115+
public function getActivityTranscript(): Collection {
116116
if (!$this->relationLoaded('participants')) {
117117
$this->load(['participants', 'participants.project']);
118118
$this->participants->where('project_type', 'App\Models\Project')->load('project.department');

graphql/schema.graphql

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,17 @@ type Query {
1818
users: [User!]! @paginate(defaultCount: 10)
1919

2020
"List multiple projects."
21-
projects: [Project!]! @paginate(defaultCount: 10)
21+
projects(
22+
orderBy: _ @orderBy(columns: ["closure_approved_at", "id", "created_at", "updated_at"])
23+
): [Project!]! @paginate(defaultCount: 10)
24+
25+
"List all approved projects and activities with approved participant list"
26+
approvedActivities(
27+
"Select activities with approved_at after this time"
28+
from: DateTime!
29+
"Select activities with approved_at before this time"
30+
until: DateTime!
31+
): [ApprovedActivity!]! @paginate(resolver: "App\\GraphQL\\Queries\\ApprovedActivities", defaultCount: 25)
2232
}
2333

2434
"Account of a person who uses this application."
@@ -64,7 +74,7 @@ type Project {
6474
period_start: DateTime
6575
period_end: DateTime
6676
department_id: Int
67-
duration: Int
77+
duration: Float
6878
estimated_attendees: String
6979
closure_submitted_at: DateTime
7080
closure_approved_status: Int
@@ -83,9 +93,12 @@ type ProjectParticipant {
8393
created_at: DateTime
8494
updated_at: DateTime
8595

96+
"Role; either organizer, staff, or attendee; optional for Activity"
8697
type: String
98+
"Role title, as set by project organizer"
8799
title: String
88100
verify_status: Int!
101+
"Approval status; 1 if approved"
89102
approve_status: Int!
90103

91104
user: User
@@ -106,3 +119,21 @@ type TranscriptRecord {
106119
approve_status: Int!
107120
title: String
108121
}
122+
123+
"A unified object representing an approved Project or Activity."
124+
type ApprovedActivity {
125+
identifier: String!
126+
"Project ID; present for Project type"
127+
project_id: ID
128+
"Activity ID; present for Activity type"
129+
activity_id: ID
130+
name: String!
131+
organization: String!
132+
period_start: DateTime!
133+
period_end: DateTime!
134+
duration: Float
135+
approved_at: DateTime!
136+
"Advisor name; absent in case of Activity"
137+
advisor: String
138+
participants: [ProjectParticipant!]!
139+
}

0 commit comments

Comments
 (0)