Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion app/Http/Controllers/BuildController.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace App\Http\Controllers;

use App\Enums\BuildGroupType;
use App\Models\Build as EloquentBuild;
use App\Models\BuildGroup;
use App\Models\Project;
Expand Down Expand Up @@ -484,6 +485,11 @@ private function restApiPost(): JsonResponse

// Should we change whether or not this build is expected?
if (request()->has('expected') && request()->has('groupid')) {
// The rule would also route future submissions of this build into the group.
if (BuildGroup::findOrFail(request()->integer('groupid'))->type !== BuildGroupType::DAILY) {
abort(400, 'Builds cannot be assigned to dynamic build groups.');
}

$buildgrouprule->Expected = request()->input('expected');
$buildgrouprule->GroupId = request()->input('groupid');
$buildgrouprule->SetExpected();
Expand All @@ -496,9 +502,13 @@ private function restApiPost(): JsonResponse

$eloquent_build = EloquentBuild::findOrFail((int) $this->build->Id);

if (BuildGroup::findOrFail((int) $newgroupid)->project()->isNot($eloquent_build->project)) {
$newgroup = BuildGroup::findOrFail((int) $newgroupid);
if ($newgroup->project()->isNot($eloquent_build->project)) {
abort(403, 'Requested build group is not associated with this project.');
}
if ($newgroup->type !== BuildGroupType::DAILY) {
abort(400, 'Builds cannot be assigned to dynamic build groups.');
}

// Remove the build from its previous group.
$eloquent_build->buildGroups()->detach();
Expand Down
7 changes: 6 additions & 1 deletion app/Http/Controllers/ExpectedBuildController.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

namespace App\Http\Controllers;

use App\Enums\BuildGroupType;
use App\Models\BuildGroup as EloquentBuildGroup;
use CDash\Model\BuildGroup;
use CDash\Model\BuildGroupRule;
use Illuminate\Http\JsonResponse;
Expand Down Expand Up @@ -103,7 +105,10 @@ private static function rest_post(Request $request, int $siteid, int $buildgroup
abort(400, 'newgroupid not specified.');
}

$newgroupid = htmlspecialchars($request->input('newgroupid'));
$newgroupid = $request->integer('newgroupid');
if (EloquentBuildGroup::findOrFail($newgroupid)->type !== BuildGroupType::DAILY) {
abort(400, 'Builds cannot be assigned to dynamic build groups.');
}

$rule = new BuildGroupRule();
$rule->SiteId = $siteid;
Expand Down
1 change: 1 addition & 0 deletions app/cdash/app/Controller/Api/Index.php
Original file line number Diff line number Diff line change
Expand Up @@ -515,6 +515,7 @@ public function beginResponseForBuildGroup(BuildGroup $buildgroup): void

$buildgroup_response['id'] = $buildgroup->GetId();
$buildgroup_response['name'] = $groupname;
$buildgroup_response['type'] = $buildgroup->GetType();
$buildgroup_response['linkname'] = urlencode($groupname);
$buildgroup_response['position'] = $buildgroup->GetPosition();

Expand Down
10 changes: 10 additions & 0 deletions app/cdash/app/Model/BuildGroup.php
Original file line number Diff line number Diff line change
Expand Up @@ -371,11 +371,13 @@ public function Delete(): bool
/** @var ?EloquentBuildGroup $newGroup */
$newGroup = $this->eloquent_model->project?->buildgroups()->where([
'name' => $oldbuild->type,
'type' => BuildGroupType::DAILY,
])->first();

if ($newGroup === null) {
$newGroup = $this->eloquent_model->project?->buildgroups()->where([
'name' => 'Experimental',
'type' => BuildGroupType::DAILY,
])->first();
}

Expand Down Expand Up @@ -410,6 +412,10 @@ public function Delete(): bool
return true;
}

/**
* Builds are only ever assigned to daily groups. Dynamic groups compute their builds from
* their own rules at display time.
*/
public function GetGroupIdFromRule(Build $build): int
{
$starttime = $build->StartTime;
Expand All @@ -419,6 +425,7 @@ public function GetGroupIdFromRule(Build $build): int
$rule_row = DB::table('build2grouprule')
->join('buildgroup', 'buildgroup.id', '=', 'build2grouprule.groupid')
->where('buildgroup.projectid', '=', $build->ProjectId)
->where('buildgroup.type', '=', BuildGroupType::DAILY)
->where('build2grouprule.buildtype', '=', $build->Type)
->where('build2grouprule.siteid', '=', $build->SiteId)
->where('build2grouprule.buildname', '=', $build->Name)
Expand All @@ -435,6 +442,7 @@ public function GetGroupIdFromRule(Build $build): int
$name_rule_row = DB::table('build2grouprule')
->join('buildgroup', 'buildgroup.id', '=', 'build2grouprule.groupid')
->where('buildgroup.projectid', '=', $build->ProjectId)
->where('buildgroup.type', '=', BuildGroupType::DAILY)
->where('build2grouprule.buildtype', '=', $build->Type)
->where('build2grouprule.siteid', '=', -1)
->whereRaw('? LIKE build2grouprule.buildname', [$build->Name])
Expand All @@ -454,6 +462,7 @@ public function GetGroupIdFromRule(Build $build): int
$default_model = EloquentBuildGroup::where([
'name' => $build->Type,
'projectid' => $build->ProjectId,
'type' => BuildGroupType::DAILY,
])->first();
if ($default_model !== null) {
return $default_model->id;
Expand All @@ -462,6 +471,7 @@ public function GetGroupIdFromRule(Build $build): int
return EloquentBuildGroup::where([
'name' => 'Experimental',
'projectid' => (int) $build->ProjectId,
'type' => BuildGroupType::DAILY,
])->first()->id ?? 0;
}

Expand Down
13 changes: 10 additions & 3 deletions app/cdash/public/api/v1/buildgroup.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@

namespace CDash\Api\v1\BuildGroup;

use App\Enums\BuildGroupType;
use App\Models\BuildGroup as EloquentBuildGroup;
use CDash\Database;
use CDash\Model\Build;
use CDash\Model\BuildGroup;
Expand Down Expand Up @@ -197,10 +199,14 @@ function rest_post($pdo, $projectid)
if (isset($_POST['builds'])) {
// Move builds to a new group.
$group = $_POST['group'];
if ($group['id'] < 1) {
$groupid = $group['id'];
if ($groupid < 1) {
$error_msg = 'Please select a group for these builds';
abort(400, $error_msg);
}
if (EloquentBuildGroup::whereKey($groupid)->firstOrFail()->type !== BuildGroupType::DAILY) {
abort(400, 'Builds cannot be assigned to dynamic build groups.');
}

$builds = $_POST['builds'];
if (array_key_exists('expected', $_POST)) {
Expand All @@ -210,8 +216,6 @@ function rest_post($pdo, $projectid)
}

foreach ($builds as $buildinfo) {
$groupid = $group['id'];

$Build = new Build();
$Build->Id = (int) $buildinfo['id'];
$Build->FillFromId($Build->Id);
Expand Down Expand Up @@ -247,6 +251,9 @@ function rest_post($pdo, $projectid)
$error_msg = 'Please select a BuildGroup to define.';
abort(400, $error_msg);
}
if (EloquentBuildGroup::whereKey($groupid)->firstOrFail()->type !== BuildGroupType::DAILY) {
abort(400, 'Builds cannot be assigned to dynamic build groups.');
}

$nameMatch = convert_wildcards($_POST['nameMatch']);
$type = $_POST['type'];
Expand Down
5 changes: 5 additions & 0 deletions app/cdash/public/api/v1/index.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

namespace CDash\Api\v1\Index;

use App\Enums\BuildGroupType;
use App\Services\ProjectService;
use CDash\Controller\Api\Index as IndexController;
use CDash\Database;
Expand Down Expand Up @@ -516,8 +517,12 @@

// Create a separate "all buildgroups" section of our response.
// This is used to allow project admins to move builds between groups.
// Builds can't be moved into dynamic groups, so only daily groups are listed.
$response['all_buildgroups'] = [];
foreach ($controller->buildgroupsResponse as $group) {
if ($group['type'] !== BuildGroupType::DAILY->value) {
continue;
}
$response['all_buildgroups'][] = [
'id' => $group['id'],
'name' => $group['name'],
Expand Down
2 changes: 2 additions & 0 deletions app/cdash/tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,8 @@ add_feature_test_in_transaction(/Feature/ManageOverviewTest)

add_feature_test_in_transaction(/Feature/TestOverviewTest)

add_feature_test_in_transaction(/Feature/DynamicBuildGroupTest)

add_feature_test_in_transaction(/Feature/Services/AuthTokenServiceTest)

add_feature_test_in_transaction(/Feature/Services/ProjectServiceTest)
Expand Down
2 changes: 1 addition & 1 deletion phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -738,7 +738,7 @@ parameters:
-
rawMessage: 'Parameter #1 $string of function htmlspecialchars expects string, mixed given.'
identifier: argument.type
count: 3
count: 2
path: app/Http/Controllers/ExpectedBuildController.php

-
Expand Down
3 changes: 3 additions & 0 deletions resources/js/angular/views/partials/build.html
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,10 @@
data-cy="build-admin-options-panel"
style="padding: 8px 0; display: flex; flex-direction: column; gap: 8px;">
<div style="display: flex; flex-wrap: wrap; align-items: center; gap: 8px;">
<!-- Expected builds belong to daily groups. Expected-and-missing rows in dynamic groups
can only come from old data, so they can still be cleared. -->
<button class="btn btn-xs"
ng-if="buildgroup.type != 'Latest' || build.expectedandmissing == 1"
ng-class="(build.expected == 1 || build.expectedandmissing == 1) ? 'btn-warning' : 'btn-success'"
ng-click="toggleExpected(build, buildgroup.id)"
ng-disabled="build.expectedLoading"
Expand Down
5 changes: 3 additions & 2 deletions resources/js/angular/views/partials/buildgroup.html
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,11 @@ <h3 class="buildgroupname" style="display: flex; align-items: center; justify-co
</div>

<div style="display: flex; align-items: center; gap: 8px;">
<button class="btn btn-sm btn-success" ng-click="bulkMarkAsExpected(buildgroup, 1)" data-cy="bulk-mark-expected-btn">
<!-- Expected builds belong to daily groups. -->
<button class="btn btn-sm btn-success" ng-if="buildgroup.type != 'Latest'" ng-click="bulkMarkAsExpected(buildgroup, 1)" data-cy="bulk-mark-expected-btn">
<span class="glyphicon glyphicon-ok"></span> Mark as Expected
</button>
<button class="btn btn-sm btn-warning" ng-click="bulkMarkAsExpected(buildgroup, 0)" data-cy="bulk-mark-not-expected-btn">
<button class="btn btn-sm btn-warning" ng-if="buildgroup.type != 'Latest'" ng-click="bulkMarkAsExpected(buildgroup, 0)" data-cy="bulk-mark-not-expected-btn">
<span class="glyphicon glyphicon-remove"></span> Mark as Not Expected
</button>
<button class="btn btn-sm btn-danger" ng-click="bulkDeleteBuilds(buildgroup)" data-cy="bulk-delete-btn">
Expand Down
25 changes: 23 additions & 2 deletions tests/Feature/BuildGroupRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -75,13 +75,13 @@ private function getDashboardBuildNames(string $group_name, string $date, bool $
return [];
}

private function getGroupIdForNewBuild(string $name, string $starttime): int
private function getGroupIdForNewBuild(string $name, string $starttime, string $type = 'Nightly'): int
{
$build = new LegacyBuild();
$build->ProjectId = $this->project->id;
$build->SiteId = $this->site->id;
$build->Name = $name;
$build->Type = 'Nightly';
$build->Type = $type;
$build->StartTime = $starttime;
return (new LegacyBuildGroup())->GetGroupIdFromRule($build);
}
Expand Down Expand Up @@ -143,6 +143,27 @@ public function testWildcardRulesAssignNewBuildsWhileActive(): void
self::assertSame($this->nightly->id, $this->getGroupIdForNewBuild('gcc-ended-debug', '2025-01-15 12:00:00'));
}

public function testRulesOnDynamicGroupsDoNotAssignNewBuilds(): void
{
$latest = BuildGroup::factory()->for($this->project)->create(['type' => BuildGroupType::LATEST]);
$this->createRule($latest, ['buildname' => 'explicit']);
$this->createRule($latest, ['buildname' => '%wildcard%', 'siteid' => -1]);

self::assertSame($this->nightly->id, $this->getGroupIdForNewBuild('explicit', '2025-01-15 12:00:00'));
self::assertSame($this->nightly->id, $this->getGroupIdForNewBuild('gcc-wildcard-debug', '2025-01-15 12:00:00'));
}

public function testDynamicGroupNamedAfterBuildTypeIsNotTheDefault(): void
{
BuildGroup::factory()->for($this->project)->create([
'name' => 'Custom',
'type' => BuildGroupType::LATEST,
]);
$experimental = $this->project->buildgroups()->where('name', 'Experimental')->firstOrFail();

self::assertSame($experimental->id, $this->getGroupIdForNewBuild('custom', '2025-01-15 12:00:00', 'Custom'));
}

public function testChangingGroupMovesOnlyTheActiveRule(): void
{
$active = $this->createRule($this->nightly, ['buildname' => 'moved', 'expected' => 1]);
Expand Down
Loading
Loading