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
128 changes: 126 additions & 2 deletions resources/js/angular/controllers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export function showEmptyBuildsLast() {
};
}

export function IndexController($scope, $rootScope, $location, $http, $filter, $timeout, anchors, apiLoader, filters, multisort, modalSvc) {
export function IndexController($scope, $rootScope, $location, $http, $filter, $timeout, $q, anchors, apiLoader, filters, multisort, modalSvc) {
// Show spinner while page is loading.
$scope.loading = true;

Expand Down Expand Up @@ -201,6 +201,17 @@ export function IndexController($scope, $rootScope, $location, $http, $filter, $
$scope.cdash.buildgroups[i].builds = $filter('orderBy')($scope.cdash.buildgroups[i].builds, $scope.cdash.buildgroups[i].orderByFields);
$scope.cdash.buildgroups[i].builds = $filter('showEmptyBuildsLast')($scope.cdash.buildgroups[i].builds, $scope.cdash.buildgroups[i].orderByFields);

// Initialize expectedInGroup property for each build to avoid checkbox binding conflicts
for (var j = 0; j < $scope.cdash.buildgroups[i].builds.length; j++) {
$scope.cdash.buildgroups[i].builds[j].expectedInGroup = {};
}

// Initialize bulk selection properties
$scope.cdash.buildgroups[i].selectedBuilds = [];
$scope.cdash.buildgroups[i].selectAll = false;
$scope.cdash.buildgroups[i].bulkTargetGroup = '';
$scope.cdash.buildgroups[i].selectionMode = false;

// Mark this group has having "normal" builds if it only contains missing & expected builds.
if (!$scope.cdash.buildgroups[i].hasnormalbuilds && !$scope.cdash.buildgroups[i].hasparentbuilds && $scope.cdash.buildgroups[i].builds.length > 0) {
$scope.cdash.buildgroups[i].hasnormalbuilds = true;
Expand Down Expand Up @@ -498,20 +509,133 @@ export function IndexController($scope, $rootScope, $location, $http, $filter, $
$http.post('api/v1/expectedbuild.php', parameters)
.then(function success() {
window.location.reload();
}).catch(function(error) {
console.error('Error moving expected build:', error);
alert('An error occurred while moving the build. Please try again.');
});
} else {
// Use the checkbox value for this specific group, default to current expected value
var expectedInNewGroup = build.expectedInGroup && build.expectedInGroup[groupid] !== undefined
? (build.expectedInGroup[groupid] ? 1 : 0)
: build.expected;

// Use the build API with the correct parameters
var parameters = {
buildid: build.id,
newgroupid: groupid,
expected: build.expected
expected: expectedInNewGroup
};
$http.post('api/v1/build.php', parameters)
.then(function success() {
window.location.reload();
}).catch(function(error) {
console.error('Error moving build:', error);
alert('Error moving build: ' + (error.data && error.data.error ? error.data.error : 'Unknown error'));
});
}
};

// Bulk selection functions
$scope.toggleBuildSelection = function(build, buildgroup) {
if (build.selected) {
buildgroup.selectedBuilds.push(build);
} else {
var index = buildgroup.selectedBuilds.indexOf(build);
if (index > -1) {
buildgroup.selectedBuilds.splice(index, 1);
}
buildgroup.selectAll = false;
}
};

$scope.toggleSelectAll = function(buildgroup) {
buildgroup.selectedBuilds = [];
for (var i = 0; i < buildgroup.pagination.filteredBuilds.length; i++) {
var build = buildgroup.pagination.filteredBuilds[i];
if (build.id) { // Only select builds that have IDs (not expected missing builds)
build.selected = buildgroup.selectAll;
if (buildgroup.selectAll) {
buildgroup.selectedBuilds.push(build);
}
}
}
};

$scope.clearBuildSelection = function(buildgroup) {
buildgroup.selectAll = false;
buildgroup.selectedBuilds = [];
for (var i = 0; i < buildgroup.builds.length; i++) {
buildgroup.builds[i].selected = false;
}
};

$scope.toggleSelectionMode = function(buildgroup) {
buildgroup.selectionMode = !buildgroup.selectionMode;
// Clear selection when exiting selection mode
if (!buildgroup.selectionMode) {
$scope.clearBuildSelection(buildgroup);
}
};

$scope.bulkMoveToGroup = function(buildgroup) {
if (!buildgroup.bulkTargetGroup || buildgroup.selectedBuilds.length === 0) {
return;
}

var targetGroupId = parseInt(buildgroup.bulkTargetGroup, 10);
var movePromises = [];

// Move each selected build using the build API
for (var i = 0; i < buildgroup.selectedBuilds.length; i++) {
var build = buildgroup.selectedBuilds[i];
var expectedInNewGroup = build.expectedInGroup && build.expectedInGroup[targetGroupId] !== undefined
? (build.expectedInGroup[targetGroupId] ? 1 : 0)
: (build.expected || 0);

var parameters = {
buildid: build.id,
newgroupid: targetGroupId,
expected: expectedInNewGroup
};
movePromises.push($http.post('api/v1/build.php', parameters));
}

// Wait for all moves to complete, then reload
$q.all(movePromises).then(function() {
window.location.reload();
}).catch(function(error) {
console.error('Error moving builds:', error);
alert('An error occurred while moving builds. Please try again.');
});
};

$scope.bulkMarkAsExpected = function(buildgroup, expectedValue) {
if (buildgroup.selectedBuilds.length === 0) {
return;
}

var updatePromises = [];

// Update expected status for each selected build using the build API
for (var i = 0; i < buildgroup.selectedBuilds.length; i++) {
var build = buildgroup.selectedBuilds[i];
var parameters = {
buildid: build.id,
groupid: parseInt(buildgroup.id, 10),
expected: expectedValue
};
updatePromises.push($http.post('api/v1/build.php', parameters));
}

// Wait for all updates to complete, then reload
$q.all(updatePromises).then(function() {
window.location.reload();
}).catch(function(error) {
console.error('Error updating builds:', error);
alert('An error occurred while updating builds. Please try again.');
});
};

$scope.colorblind_toggle = function() {
if ($scope.cdash.filterdata.colorblind) {
$rootScope.cssfile = "colorblind";
Expand Down
2 changes: 1 addition & 1 deletion resources/js/angular/legacy.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ import { HeadController } from "./controllers/head";
CDash.controller('HeadController', ["$rootScope", "$document", HeadController]);

import { IndexController, showEmptyBuildsLast } from "./controllers/index";
CDash.controller('IndexController', ["$scope", "$rootScope", "$location", "$http", "$filter", "$timeout", "anchors", "apiLoader", "filters", "multisort", "modalSvc", IndexController]);
CDash.controller('IndexController', ["$scope", "$rootScope", "$location", "$http", "$filter", "$timeout", "$q", "anchors", "apiLoader", "filters", "multisort", "modalSvc", IndexController]);
CDash.filter('showEmptyBuildsLast', showEmptyBuildsLast);

import { SubProjectController } from "./controllers/subproject";
Expand Down
80 changes: 47 additions & 33 deletions resources/js/angular/views/partials/build.html
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
<!-- Selection checkbox for bulk actions (admin only) -->
<td ng-if="cdash.user.admin == 1 && buildgroup.selectionMode" align="center" class="paddt">
<input type="checkbox"
ng-if="::build.id"
ng-model="build.selected"
ng-change="toggleBuildSelection(build, buildgroup)">
</td>

<!-- For child view, display the label(s) as a link to the build summary -->
<td ng-if="::cdash.childview == 1" align="left" class="paddt" colspan="2">
<a class="cdash-link" ng-href="builds/{{::build.id}}">
Expand All @@ -7,7 +15,7 @@
<!-- Icon for build errors / test failing -->
<a class="cdash-link" ng-if="::build.compilation.error > 0 || build.test.fail > 0" href=""
ng-click="toggleBuildProblems(build)">
<img src="img/Info.png" alt="info" class="icon"></img>
<span class="glyphicon glyphicon-info-sign" style="color: #5bc0de;" title="Build information"></span>
</a>

<!-- Link to notes specific to this subproject build -->
Expand All @@ -16,14 +24,14 @@
name="notesLink"
ng-if="::build.notes > 0"
ng-href="builds/{{::build.id}}/notes">
<img src="img/document.png" alt="Notes" class="icon"/>
<span class="glyphicon glyphicon-file" style="color: #f0ad4e;"></span>
</a>
</td>

<!-- Otherwise, show build name & site on the row. -->
<td ng-if="::cdash.childview != 1" align="left" class="paddt">
<a class="cdash-link" ng-href="sites/{{::build.siteid}}?project={{::cdash.projectid}}&currenttime={{::cdash.unixtimestamp}}">{{::build.site}}</a>
<img ng-if="::build.siteoutoforder == 1" border="0" src="img/flag.png" title="flag"></img>
<span ng-if="::build.siteoutoforder == 1" class="glyphicon glyphicon-flag" style="color: #d9534f;" title="Site out of order"></span>
</td>

<td ng-if="::cdash.childview != 1" align="left">
Expand Down Expand Up @@ -52,46 +60,46 @@
name="notesLink"
ng-if="::build.notes > 0"
ng-href="builds/{{::build.id}}/notes">
<img src="img/document.png" alt="Notes" class="icon"/>
<span class="glyphicon glyphicon-file" style="color: #f0ad4e; margin-right: 4px;"></span>
</a>

<a class="cdash-link"
href="" style="float: left;"
ng-if="::build.uploadfilecount > 0"
ng-href="builds/{{::build.id}}/files"
title="{{::build.uploadfilecount}} files uploaded with this build">
<img src="img/package.png" alt="Files" class="icon"/>
<span class="glyphicon glyphicon-compressed" style="color: #5cb85c; margin-right: 4px;"></span>
</a>

<!-- If the build has errors or test failing -->
<a class="cdash-link"
href="" style="float: left;"
ng-if="::build.compilation.error > 0 || build.test.fail > 0"
ng-click="toggleBuildProblems(build)">
<img src="img/Info.png" alt="info" class="icon"></img>
<span class="glyphicon glyphicon-info-sign" style="color: #5bc0de; margin-right: 4px;" title="Build information"></span>
</a>

<!-- If the build is expected and missing -->
<a class="cdash-link"
href="" style="float: left;"
ng-if="::build.expectedandmissing == 1"
ng-click="toggleExpectedInfo(build)">
<img src="img/Info.png" alt="info" class="icon"></img>
<span class="glyphicon glyphicon-info-sign" style="color: #5bc0de; margin-right: 4px;" title="Expected build information"></span>
</a>

<!-- Display the note icon -->
<a class="cdash-link" name="Build Notes" id="buildnote_{{::build.id}}"
ng-if="::build.buildnotes > 0"
ng-href="ajax/buildnote.php?buildid={{::build.id}}">
<img src="img/note.png" alt="note" class="icon"></img>
<span class="glyphicon glyphicon-comment" style="color: #337ab7; margin-right: 4px;"></span>
</a>

<div style="float: left;" ng-if="::cdash.user.admin == 1">
<!-- Display folder icon to edit this build for administrative users -->
<!-- Display cog icon to edit this build for administrative users -->
<a class="cdash-link" href="" ng-click="toggleAdminOptions(build)">
<img name="adminoptions" src="img/folder.png" class="icon"/>
<span name="adminoptions" class="glyphicon glyphicon-cog" style="color: #777; margin-right: 4px;"></span>
</a>
<img src="img/loading.gif" ng-if="build.loading == 1"/>
<span class="glyphicon glyphicon-refresh glyphicon-spin" ng-if="build.loading == 1" style="color: #5bc0de;"></span>
</div>
</div>

Expand Down Expand Up @@ -168,45 +176,51 @@

<!-- admin options table -->
<div ng-if="::cdash.user.admin == 1">
<div ng-if="build.showAdminOptions == 1">
<div ng-if="build.showAdminOptions == 1" style="margin-top: 8px;">
<table width="100%" border="0" class="animate-show">
<!-- If user is admin of the project propose to group this build -->
<tr ng-repeat="group in ::cdash.all_buildgroups">
<td width="35%">
<td width="35%" style="padding: 4px 0;">
<b>{{::group.name}}</b>:
</td>
<td ng-if="::group.name == buildgroup.name" colspan="2">
<a class="cdash-link" ng-if="build.expected == 0" href="" ng-click="toggleExpected(build, group.id)">
[mark as expected]
</a>
<a class="cdash-link" ng-if="build.expected == 1 || build.expectedandmissing == 1" href="" ng-click="toggleExpected(build, group.id)">
[mark as non expected]
</a>
<td ng-if="::group.name == buildgroup.name" colspan="2" style="padding: 4px 0;">
<button class="btn btn-xs btn-success" ng-if="build.expected == 0" ng-click="toggleExpected(build, group.id)">
<span class="glyphicon glyphicon-ok"></span> Mark as Expected
</button>
<button class="btn btn-xs btn-warning" ng-if="build.expected == 1 || build.expectedandmissing == 1" ng-click="toggleExpected(build, group.id)">
<span class="glyphicon glyphicon-remove"></span> Mark as Non Expected
</button>
</td>
<td ng-if="::group.name != buildgroup.name" colspan="2">
<input type="checkbox" ng-model="build.expected" ng-true-value="'1'" ng-false-value="'0'"> expected</input>
<td ng-if="::group.name != buildgroup.name" colspan="2" style="padding: 4px 0;">
<label style="font-weight: normal; margin: 0;">
<input type="checkbox" ng-model="build.expectedInGroup[group.id]" ng-true-value="true" ng-false-value="false"> expected
</label>
</td>
<td ng-if="::group.name != buildgroup.name" class="nob">
<a class="cdash-link" href="" ng-click="moveToGroup(build, group.id)"> [move to group] </a>
<td ng-if="::group.name != buildgroup.name" class="nob" style="padding: 4px 0;">
<button class="btn btn-xs btn-primary" ng-click="moveToGroup(build, group.id)">
<span class="glyphicon glyphicon-arrow-right"></span> Move to Group
</button>
</td>
</tr>
<tr>
<td colspan="3" class="nob">
<a class="cdash-link" href="" ng-click="showModal(build)"> [remove this build] </a>
<td colspan="3" class="nob" style="padding: 8px 0;">
<button class="btn btn-xs btn-danger" ng-click="showModal(build)">
<span class="glyphicon glyphicon-trash"></span> Remove This Build
</button>
</td>
</tr>
</table>

<div tooltip-popup-delay="1500"
tooltip-append-to-body="true"
uib-tooltip="Done builds will be overwritten if a new one is submitted with the same site, build name, and timestamp."
>
<a class="cdash-link" ng-if="build.done == 0" href="" ng-click="toggleDone(build)">
[mark as done]
</a>
<a class="cdash-link" ng-if="build.done == 1" href="" ng-click="toggleDone(build)">
[mark as not done]
</a>
style="margin-top: 4px;">
<button class="btn btn-xs btn-info" ng-if="build.done == 0" ng-click="toggleDone(build)">
<span class="glyphicon glyphicon-ok-circle"></span> Mark as Done
</button>
<button class="btn btn-xs btn-default" ng-if="build.done == 1" ng-click="toggleDone(build)">
<span class="glyphicon glyphicon-ban-circle"></span> Mark as Not Done
</button>
</div>
</div>
</div>
Expand Down
Loading
Loading