diff --git a/resources/js/angular/controllers/index.js b/resources/js/angular/controllers/index.js index 4daeeaeee4..23d8149455 100644 --- a/resources/js/angular/controllers/index.js +++ b/resources/js/angular/controllers/index.js @@ -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; @@ -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; @@ -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"; diff --git a/resources/js/angular/legacy.js b/resources/js/angular/legacy.js index 4c29ff2fdf..561a318eb0 100644 --- a/resources/js/angular/legacy.js +++ b/resources/js/angular/legacy.js @@ -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"; diff --git a/resources/js/angular/views/partials/build.html b/resources/js/angular/views/partials/build.html index 57db9a8329..841afe98fc 100644 --- a/resources/js/angular/views/partials/build.html +++ b/resources/js/angular/views/partials/build.html @@ -1,3 +1,11 @@ + + + + + @@ -7,7 +15,7 @@ - info + @@ -16,14 +24,14 @@ name="notesLink" ng-if="::build.notes > 0" ng-href="builds/{{::build.id}}/notes"> - Notes + {{::build.site}} - + @@ -52,7 +60,7 @@ name="notesLink" ng-if="::build.notes > 0" ng-href="builds/{{::build.id}}/notes"> - Notes + - Files + @@ -68,7 +76,7 @@ href="" style="float: left;" ng-if="::build.compilation.error > 0 || build.test.fail > 0" ng-click="toggleBuildProblems(build)"> - info + @@ -76,22 +84,22 @@ href="" style="float: left;" ng-if="::build.expectedandmissing == 1" ng-click="toggleExpectedInfo(build)"> - info + - note +
- + - + - +
@@ -168,31 +176,37 @@
-
+
- - - - -
+ {{::group.name}}: - - [mark as expected] - - - [mark as non expected] - + + + - expected + + - [move to group] + +
- [remove this build] + +
@@ -200,13 +214,13 @@
- - [mark as done] - - - [mark as not done] - + style="margin-top: 4px;"> + +
diff --git a/resources/js/angular/views/partials/buildgroup.html b/resources/js/angular/views/partials/buildgroup.html index d2d8fb1b44..8ac06cccb2 100644 --- a/resources/js/angular/views/partials/buildgroup.html +++ b/resources/js/angular/views/partials/buildgroup.html @@ -1,15 +1,75 @@ -

- - {{::buildgroup.name}} - - {{::buildgroup.numbuildslabel}} - [view timeline] +

+
+ + {{::buildgroup.name}} + + {{::buildgroup.numbuildslabel}} +
+ + + + + View Timeline + + + + +

+ + +
+
+
+ + + {{buildgroup.selectedBuilds.length}} build(s) selected + +
+ +
+ + + +
+ +
+ + +
+ +
+ +
+
+
+ + @@ -20,6 +80,13 @@

+ +
Update Configure
+ +