Skip to content

Commit 32935c9

Browse files
Move join/leave project functionality to members page (#2898)
This PR finishes our recent effort to modernize the project membership experience started in #2778. The join/leave functionality has been moved to the project members page, leaving `subscribeProject.php` as only a place to configure notifications.
1 parent f261012 commit 32935c9

15 files changed

Lines changed: 543 additions & 288 deletions

File tree

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace App\GraphQL\Mutations;
6+
7+
use App\Models\Project;
8+
use App\Models\User;
9+
use Exception;
10+
11+
final class JoinProject extends AbstractMutation
12+
{
13+
/**
14+
* @param array{
15+
* projectId: int,
16+
* } $args
17+
*
18+
* @throws Exception
19+
*/
20+
protected function mutate(array $args): void
21+
{
22+
/** @var ?User $user */
23+
$user = auth()->user();
24+
$project = isset($args['projectId']) ? Project::find((int) $args['projectId']) : null;
25+
26+
if (
27+
$user === null
28+
|| $project === null
29+
|| $user->cannot('join', $project)
30+
) {
31+
abort(401, 'This action is unauthorized.');
32+
}
33+
34+
$project
35+
->users()
36+
->attach($user->id, [
37+
'emailtype' => 0,
38+
'emailcategory' => 62,
39+
'emailsuccess' => false,
40+
'emailmissingsites' => false,
41+
'role' => Project::PROJECT_USER,
42+
]);
43+
}
44+
}

app/GraphQL/Mutations/RemoveProjectUser.php

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,14 @@ protected function mutate(array $args): void
2626
$user === null
2727
|| $project === null
2828
|| $userToRemove === null
29-
|| $userToRemove->id === $user->id
30-
|| $user->cannot('removeUser', $project)
29+
|| (
30+
$userToRemove->id === $user->id
31+
&& $user->cannot('leave', $project)
32+
)
33+
|| (
34+
$userToRemove->id !== $user->id
35+
&& $user->cannot('removeUser', $project)
36+
)
3137
|| !$project->users()->where('id', $userToRemove->id)->exists()
3238
) {
3339
abort(401, 'This action is unauthorized.');

app/Http/Controllers/ProjectMembersController.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ public function members(int $project_id): View
2424
'user-id' => $user?->id,
2525
'can-invite-users' => $user?->can('inviteUser', $eloquentProject) ?? false,
2626
'can-remove-users' => $user?->can('removeUser', $eloquentProject) ?? false,
27+
'can-join-project' => $user?->can('join', $eloquentProject) ?? false,
28+
'can-leave-project' => $user?->can('leave', $eloquentProject) ?? false,
2729
]);
2830
}
2931
}

app/Http/Controllers/SubscribeProjectController.php

Lines changed: 1 addition & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -68,10 +68,7 @@ public function subscribeProject(): View|RedirectResponse
6868
}
6969

7070
// If we ask to subscribe
71-
@$Subscribe = $_POST['subscribe'];
7271
@$UpdateSubscription = $_POST['updatesubscription'];
73-
@$Unsubscribe = $_POST['unsubscribe'];
74-
@$Role = $_POST['role'];
7572
@$EmailType = $_POST['emailtype'];
7673
if (!isset($_POST['emailmissingsites'])) {
7774
$EmailMissingSites = 0;
@@ -90,26 +87,7 @@ public function subscribeProject(): View|RedirectResponse
9087
$LabelEmail->ProjectId = $this->project->Id;
9188
$LabelEmail->UserId = $user->id;
9289

93-
if ($Unsubscribe) {
94-
DB::delete('DELETE FROM user2project WHERE userid=? AND projectid=?', [$user->id, $this->project->Id]);
95-
96-
// Remove the claim sites for this project if they are only part of this project
97-
DB::delete('
98-
DELETE FROM site2user
99-
WHERE
100-
userid=?
101-
AND siteid NOT IN (
102-
SELECT build.siteid
103-
FROM build, user2project AS up
104-
WHERE
105-
up.projectid = build.projectid
106-
AND up.userid=?
107-
AND up.role>0
108-
GROUP BY build.siteid
109-
)
110-
', [$user->id, $user->id]);
111-
return redirect('/user?note=unsubscribedtoproject');
112-
} elseif ($UpdateSubscription) {
90+
if ($UpdateSubscription) {
11391
$emailcategory_update = intval($_POST['emailcategory_update'] ?? 0);
11492
$emailcategory_configure = intval($_POST['emailcategory_configure'] ?? 0);
11593
$emailcategory_warning = intval($_POST['emailcategory_warning'] ?? 0);
@@ -122,7 +100,6 @@ public function subscribeProject(): View|RedirectResponse
122100
$db->executePrepared('
123101
UPDATE user2project
124102
SET
125-
role=?,
126103
emailtype=?,
127104
emailcategory=?,
128105
emailmissingsites=?,
@@ -131,32 +108,13 @@ public function subscribeProject(): View|RedirectResponse
131108
userid=?
132109
AND projectid=?
133110
', [
134-
$Role,
135111
$EmailType,
136112
$EmailCategory,
137113
$EmailMissingSites,
138114
$EmailSuccess,
139115
$user->id,
140116
$this->project->Id,
141117
]);
142-
143-
if ($Role == 0) {
144-
// Remove the claim sites for this project if they are only part of this project
145-
DB::delete('
146-
DELETE FROM site2user
147-
WHERE
148-
userid=?
149-
AND siteid NOT IN (
150-
SELECT build.siteid
151-
FROM build, user2project AS up
152-
WHERE
153-
up.projectid=build.projectid
154-
AND up.userid=?
155-
AND up.role>0
156-
GROUP BY build.siteid
157-
)
158-
', [$user->id, $user->id]);
159-
}
160118
}
161119

162120
if (isset($_POST['emaillabels'])) {
@@ -166,77 +124,6 @@ public function subscribeProject(): View|RedirectResponse
166124
}
167125
// Redirect
168126
return redirect('/user');
169-
} elseif ($Subscribe) {
170-
$emailcategory_update = intval($_POST['emailcategory_update'] ?? 0);
171-
$emailcategory_configure = intval($_POST['emailcategory_configure'] ?? 0);
172-
$emailcategory_warning = intval($_POST['emailcategory_warning'] ?? 0);
173-
$emailcategory_error = intval($_POST['emailcategory_error'] ?? 0);
174-
$emailcategory_test = intval($_POST['emailcategory_test'] ?? 0);
175-
$emailcategory_dynamicanalysis = intval($_POST['emailcategory_dynamicanalysis'] ?? 0);
176-
177-
$EmailCategory = $emailcategory_update + $emailcategory_configure + $emailcategory_warning + $emailcategory_error + $emailcategory_test + $emailcategory_dynamicanalysis;
178-
if (!empty($user2project)) {
179-
$db->executePrepared('
180-
UPDATE user2project
181-
SET
182-
role=?,
183-
emailtype=?,
184-
emailcategory=?.
185-
emailmissingsites=?,
186-
emailsuccess=?
187-
WHERE
188-
userid=?
189-
AND projectid=?
190-
', [
191-
$Role,
192-
$EmailType,
193-
$EmailCategory,
194-
$EmailMissingSites,
195-
$EmailSuccess,
196-
$user->id,
197-
$this->project->Id,
198-
]);
199-
200-
if ($Role == 0) {
201-
// Remove the claim sites for this project if they are only part of this project
202-
DB::delete('
203-
DELETE FROM site2user
204-
WHERE
205-
userid=?
206-
AND siteid NOT IN (
207-
SELECT build.siteid
208-
FROM build, user2project AS up
209-
WHERE
210-
up.projectid0=build.projectid
211-
AND up.userid=?
212-
AND up.role>0
213-
GROUP BY build.siteid
214-
)
215-
', [$user->id, $user->id]);
216-
}
217-
} else {
218-
$db->executePrepared('
219-
INSERT INTO user2project (
220-
role,
221-
userid,
222-
projectid,
223-
emailtype,
224-
emailcategory,
225-
emailsuccess,
226-
emailmissingsites
227-
)
228-
VALUES (?, ?, ?, ?, ?, ?, ?)
229-
', [
230-
$Role,
231-
$user->id,
232-
$this->project->Id,
233-
$EmailType,
234-
$EmailCategory,
235-
$EmailSuccess,
236-
$EmailMissingSites,
237-
]);
238-
}
239-
return redirect('/user?note=subscribedtoproject');
240127
}
241128

242129
$xml .= '<project>';

app/Policies/ProjectPolicy.php

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -98,8 +98,7 @@ public function inviteUser(User $currentUser, Project $project): bool
9898
return false;
9999
}
100100

101-
// If an LDAP filter has been specified and LDAP is enabled, CDash controls the entire members list.
102-
if ((bool) config('cdash.ldap_enabled') && $project->ldapfilter !== null && $project->ldapfilter !== '') {
101+
if ($this->isLdapControlledMembership($project)) {
103102
return false;
104103
}
105104

@@ -111,13 +110,40 @@ public function revokeInvitation(User $currentUser, Project $project): bool
111110
return $this->update($currentUser, $project);
112111
}
113112

113+
/**
114+
* Whether the current user can remove other users or not. Use leave() to determine whether
115+
* the current user can remove themselves from the project.
116+
*/
114117
public function removeUser(User $currentUser, Project $project): bool
115118
{
116-
// If an LDAP filter has been specified and LDAP is enabled, CDash controls the entire members list.
117-
if ((bool) config('cdash.ldap_enabled') && $project->ldapfilter !== null && $project->ldapfilter !== '') {
119+
if ($this->isLdapControlledMembership($project)) {
118120
return false;
119121
}
120122

121123
return $this->update($currentUser, $project);
122124
}
125+
126+
public function join(User $currentUser, Project $project): bool
127+
{
128+
if (!$this->view($currentUser, $project)) {
129+
return false;
130+
}
131+
132+
if ($this->isLdapControlledMembership($project)) {
133+
return false;
134+
}
135+
136+
return !$project->users()->where('id', $currentUser->id)->exists();
137+
}
138+
139+
public function leave(User $currentUser, Project $project): bool
140+
{
141+
return !$this->isLdapControlledMembership($project) && $project->users()->where('id', $currentUser->id)->exists();
142+
}
143+
144+
private function isLdapControlledMembership(Project $project): bool
145+
{
146+
// If a LDAP filter has been specified and LDAP is enabled, CDash controls the entire members list.
147+
return (bool) config('cdash.ldap_enabled') && $project->ldapfilter !== null && $project->ldapfilter !== '';
148+
}
123149
}

app/cdash/public/subscribeProject.xsl

Lines changed: 0 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -47,68 +47,13 @@
4747
<form name="form1" enctype="multipart/form-data" method="post" action="">
4848
<div id="wizard">
4949
<ul>
50-
<li>
51-
<a class="cdash-link" href="#fragment-1"><span>Select your role in this project</span></a></li>
5250
<li>
5351
<a class="cdash-link" href="#fragment-3"><span>Email Notifications</span></a></li>
5452
<li>
5553
<a class="cdash-link" href="#fragment-4"><span>Email Category</span></a></li>
5654
<li>
5755
<a class="cdash-link" href="#fragment-5"><span>Email Labels</span></a></li>
5856
</ul>
59-
<div id="fragment-1" class="tab_content" >
60-
<div class="tab_help"></div>
61-
<table width="800" >
62-
<tr>
63-
<td></td>
64-
<td><input type="radio" onchange="saveChanges();" name="role" value="0" checked="checked">
65-
<xsl:if test="/cdash/role=0">
66-
<xsl:attribute name="checked"></xsl:attribute>
67-
</xsl:if>
68-
</input>
69-
Normal user <i>(you are working on or using this project)</i></td>
70-
</tr>
71-
<tr>
72-
<td></td>
73-
<td><input type="radio" onchange="saveChanges();" name="role" value="1">
74-
<xsl:if test="/cdash/role=1">
75-
<xsl:attribute name="checked"></xsl:attribute>
76-
</xsl:if>
77-
</input>
78-
Site maintainer <i>(you are responsible for machines that are submitting builds for this project)</i></td>
79-
</tr>
80-
<xsl:if test="/cdash/role>1">
81-
<tr>
82-
<td></td>
83-
<td ><b>Warning: if you change to a normal or maintainer role you won't be able to go back.</b> </td>
84-
</tr>
85-
<tr>
86-
<td></td>
87-
<td ><input type="radio" onchange="saveChanges();" name="role" value="2" checked="checked">
88-
<xsl:if test="/cdash/role=2">
89-
<xsl:attribute name="checked"></xsl:attribute>
90-
</xsl:if>
91-
</input>
92-
Project Administrator <i>(You are administering the project)</i></td>
93-
</tr>
94-
</xsl:if>
95-
<xsl:if test="/cdash/role>2">
96-
<tr>
97-
<td></td>
98-
<td ><input type="radio" onchange="saveChanges();" name="role" value="3">
99-
<xsl:if test="/cdash/role=3">
100-
<xsl:attribute name="checked"></xsl:attribute>
101-
</xsl:if>
102-
</input>
103-
Project Super Administrator<i>(You have full control of this project)</i></td>
104-
</tr>
105-
</xsl:if>
106-
<tr>
107-
<td></td>
108-
<td bgcolor="#FFFFFF"></td>
109-
</tr>
110-
</table>
111-
</div>
11257
<div id="fragment-3" class="tab_content" >
11358
<div class="tab_help"></div>
11459
<table width="800" >
@@ -312,18 +257,12 @@
312257
<div style="width:900px;margin-left:auto;margin-right:auto;text-align:right;">
313258
<table width="100%" border="0">
314259
<tr>
315-
<td style="text-align:left;" ><input type="submit" onclick="return confirm('Are you sure you want to unsubscribe?')" name="unsubscribe" value="Unsubscribe"/></td>
316260
<td><span id="changesmade" style="color:red;display:none;">*Changes need to be updated </span>
317261
<input type="submit" onclick="SubmitForm()" name="updatesubscription" value="Update Subscription"/></td>
318262
</tr>
319263
</table>
320264
</div>
321265
</xsl:if>
322-
<xsl:if test="/cdash/edit=0">
323-
<div style="width:900px;margin-left:auto;margin-right:auto;text-align:right;"><br/>
324-
<input type="submit" name="subscribe" value="Subscribe"/>
325-
</div>
326-
</xsl:if>
327266

328267
</form>
329268
</td>

app/cdash/tests/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,8 @@ add_feature_test(/Feature/Traits/UpdatesSiteInformationTest)
302302

303303
add_feature_test(/Feature/GraphQL/Mutations/ChangeGlobalRoleTest)
304304

305+
add_feature_test(/Feature/GraphQL/Mutations/JoinProjectTest)
306+
305307
# Needs RUN_SERIAL to verify that no unexpected invitations are created
306308
add_feature_test(/Feature/GraphQL/Mutations/CreateGlobalInvitationTest)
307309
set_property(TEST /Feature/GraphQL/Mutations/CreateGlobalInvitationTest APPEND PROPERTY RUN_SERIAL TRUE)

app/cdash/tests/test_email.php

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,14 @@ public function testRegisterUser()
4343
return;
4444
}
4545

46-
$this->actingAs(['email' => 'user1@kw', 'password' => 'user1'])
47-
->connect($this->url . "/subscribeProject.php?projectid={$this->project}");
48-
$this->setField('credentials[0]', 'user1kw');
49-
$this->setField('emailsuccess', '1');
50-
$this->clickSubmitByName('subscribe');
46+
$user->projects()->attach($this->project, [
47+
'role' => 0,
48+
'emailtype' => 1,
49+
'emailcategory' => 126,
50+
'emailsuccess' => 1,
51+
'emailmissingsites' => 0,
52+
]);
53+
5154
if (!$this->checkLog($this->logfilename)) {
5255
$this->fail('Errors logged');
5356
}

0 commit comments

Comments
 (0)