From 876370419a50264a28feb4278f8c465b0019e709 Mon Sep 17 00:00:00 2001 From: Alexandru DAMIAN Date: Thu, 8 Jan 2015 13:15:14 +0000 Subject: bitbake: toaster: implementation of project page This patch brings the project page in line with the design, including build error handling and suggestions. Includes some refactoring for already existing code. [YOCTO #6587] (Bitbake rev: 1ea658dcdfde5465d3ecdb97550e0a66cb8b122e) Signed-off-by: Alexandru DAMIAN Signed-off-by: Richard Purdie --- .../lib/toaster/toastergui/static/js/projectapp.js | 234 +++++++++++++++------ .../lib/toaster/toastergui/templates/project.html | 50 +++-- bitbake/lib/toaster/toastergui/views.py | 97 ++++----- 3 files changed, 246 insertions(+), 135 deletions(-) (limited to 'bitbake') diff --git a/bitbake/lib/toaster/toastergui/static/js/projectapp.js b/bitbake/lib/toaster/toastergui/static/js/projectapp.js index 767ea13a7e..0bdc55a733 100644 --- a/bitbake/lib/toaster/toastergui/static/js/projectapp.js +++ b/bitbake/lib/toaster/toastergui/static/js/projectapp.js @@ -100,6 +100,16 @@ function _diffArrays(existingArray, newArray, compareElements, onAdded, onDelete } +// add Array findIndex if not there + +if (Array.prototype.findIndex === undefined) { + Array.prototype.findIndex = function (callback) { + var i = 0; + for ( i = 0; i < this.length; i++ ) + if (callback(this[i], i, this)) return i; + return -1; + } +} var projectApp = angular.module('project', ['ngCookies', 'ngAnimate', 'ui.bootstrap', 'ngRoute', 'ngSanitize'], angular_formpost); @@ -126,11 +136,17 @@ projectApp.filter('timediff', function() { } }); +/** + * main controller for the project page + */ -// main controller for the project page projectApp.controller('prjCtrl', function($scope, $modal, $http, $interval, $location, $cookies, $cookieStore, $q, $sce, $anchorScroll, $animate, $sanitize) { - $scope.getSuggestions = function(type, currentValue) { + /** + * Retrieves text suggestions for text-edit drop down autocomplete boxes + */ + + $scope.getAutocompleteSuggestions = function(type, currentValue) { var deffered = $q.defer(); $http({method:"GET", url: $scope.urls.xhr_datatypeahead, params : { type: type, value: currentValue}}) @@ -147,17 +163,19 @@ projectApp.controller('prjCtrl', function($scope, $modal, $http, $interval, $loc var inXHRcall = false; - // default handling of XHR calls that handles errors and updates commonly-used pages + /** + * XHR call wrapper that automatically handles errors and auto-updates the page content to reflect project state on server side. + */ $scope._makeXHRCall = function(callparams) { if (inXHRcall) { if (callparams.data === undefined) { // we simply skip the data refresh calls - console.warn("race on XHR, aborted"); + console.warn("TRC1: race on XHR, aborted"); return; } else { // we return a promise that we'll solve by reissuing the command later var delayed = $q.defer(); - console.warn("race on XHR, delayed"); + console.warn("TRC2: race on XHR, delayed"); $interval(function () {$scope._makeXHRCall(callparams).then(function (d) { delayed.resolve(d); });}, 100, 1); return delayed.promise; @@ -178,33 +196,6 @@ projectApp.controller('prjCtrl', function($scope, $modal, $http, $interval, $loc deffered.reject(_data.error); } else { - - if (_data.builds !== undefined) { - var toDelete = []; - // step 1 - delete entries not found - $scope.builds.forEach(function (elem) { - if (-1 == _data.builds.findIndex(function (elemX) { return elemX.id == elem.id && elemX.status == elem.status; })) { - toDelete.push(elem); - } - }); - toDelete.forEach(function (elem) { - $scope.builds.splice($scope.builds.indexOf(elem),1); - }); - // step 2 - merge new entries - _data.builds.forEach(function (elem) { - var found = false; - var i = 0; - for (i = 0 ; i < $scope.builds.length; i ++) { - if ($scope.builds[i].id > elem.id) continue; - if ($scope.builds[i].id == elem.id) { found=true; break;} - if ($scope.builds[i].id < elem.id) break; - } - if (!found) { - $scope.builds.splice(i, 0, elem); - } - }); - - } if (_data.layers !== undefined) { var addedLayers = []; @@ -239,12 +230,59 @@ projectApp.controller('prjCtrl', function($scope, $modal, $http, $interval, $loc // step 3 - display alerts. if (addedLayers.length > 0) { $scope.displayAlert($scope.zone2alerts, "You have added "+addedLayers.length+" layer" + ((addedLayers.length>1)?"s: ":": ") + addedLayers.map(function (e) { return ""+e.name+"" }).join(", "), "alert-info"); + // invalidate error layer data based on current layers + $scope.layersForTargets = {}; } if (deletedLayers.length > 0) { $scope.displayAlert($scope.zone2alerts, "You have deleted "+deletedLayers.length+" layer" + ((deletedLayers.length>1)?"s: ":": ") + deletedLayers.map(function (e) { return ""+e.name+"" }).join(", "), "alert-info"); + // invalidate error layer data based on current layers + $scope.layersForTargets = {}; } } + + + if (_data.builds !== undefined) { + var toDelete = []; + // step 1 - delete entries not found + $scope.builds.forEach(function (elem) { + if (-1 == _data.builds.findIndex(function (elemX) { return elemX.id == elem.id && elemX.status == elem.status; })) { + toDelete.push(elem); + } + }); + toDelete.forEach(function (elem) { + $scope.builds.splice($scope.builds.indexOf(elem),1); + }); + // step 2 - merge new entries + _data.builds.forEach(function (elem) { + var found = false; + var i = 0; + for (i = 0 ; i < $scope.builds.length; i ++) { + if ($scope.builds[i].id > elem.id) continue; + if ($scope.builds[i].id == elem.id) { found=true; break;} + if ($scope.builds[i].id < elem.id) break; + } + if (!found) { + $scope.builds.splice(i, 0, elem); + } + }); + // step 3 - merge "Canceled" builds + $scope.canceledBuilds.forEach(function (elem) { + // mock the build object + var found = false; + var i = 0; + for (i = 0; i < $scope.builds.length; i ++) { + if ($scope.builds[i].id > elem.id) continue; + if ($scope.builds[i].id == elem.id) { found=true; break;} + if ($scope.builds[i].id < elem.id) break; + } + if (!found) { + $scope.builds.splice(i, 0, elem); + } + }); + + $scope.fetchLayersForTargets(); + } if (_data.targets !== undefined) { $scope.targets = _data.targets; } @@ -267,17 +305,26 @@ projectApp.controller('prjCtrl', function($scope, $modal, $http, $interval, $loc deffered.resolve(_data); } }).error(function(_data, _status, _headers, _config) { - console.warn("Failed HTTP XHR request (" + _status + ")" + _data); + if (_status == 0) { + // the server has gone away + alert("The server is not responding. The application will terminate now") + $interval.cancel($scope.pollHandle); + } + else { console.error("Failed HTTP XHR request: ", _data, _status, _headers, _config); inXHRcall = false; deffered.reject(_data.error); + } }); return deffered.promise; } $scope.layeralert = undefined; - // shows user alerts on invalid project data + /** + * Verifies and shows user alerts on invalid project data + */ + $scope.validateData = function () { if ($scope.layers.length == 0) { $scope.layeralert = $scope.displayAlert($scope.zone1alerts, "You need to add some layers to this project. View all layers available in Toaster or import a layer"); @@ -289,14 +336,14 @@ projectApp.controller('prjCtrl', function($scope, $modal, $http, $interval, $loc } } - $scope.targetExistingBuild = function(targets) { + $scope.buildExistingTarget = function(targets) { var oldTargetName = $scope.targetName; $scope.targetName = targets.map(function(v,i,a){return v.target}).join(' '); $scope.targetNamedBuild(); $scope.targetName = oldTargetName; } - $scope.targetNamedBuild = function(target) { + $scope.buildNamedTarget = function(target) { if ($scope.targetName === undefined && $scope.targetName1 === undefined){ console.warn("No target defined, please type in a target name"); return; @@ -310,7 +357,7 @@ projectApp.controller('prjCtrl', function($scope, $modal, $http, $interval, $loc targets: $scope.safeTargetName, } }).then(function (data) { - console.warn("received ", data); + console.warn("TRC3: received ", data); $scope.targetName = undefined; $scope.targetName1 = undefined; $location.hash('buildslist'); @@ -329,22 +376,20 @@ projectApp.controller('prjCtrl', function($scope, $modal, $http, $interval, $loc $scope.safeTargetName = $scope.safeTargetName.replace(/\[.*\]/, '').trim(); } - $scope.buildCancel = function(id) { + $scope.buildCancel = function(build) { $scope._makeXHRCall({ method: "POST", url: $scope.urls.xhr_build, data: { - buildCancel: id, + buildCancel: build.id, } + }).then( function () { + build['status'] = "deleted"; + $scope.canceledBuilds.push(build); }); } - $scope.buildDelete = function(id) { - $scope._makeXHRCall({ - method: "POST", url: $scope.urls.xhr_build, - data: { - buildDelete: id, - } - }); + $scope.buildDelete = function(build) { + $scope.canceledBuilds.splice($scope.canceledBuilds.indexOf(build), 1); } @@ -352,6 +397,12 @@ projectApp.controller('prjCtrl', function($scope, $modal, $http, $interval, $loc $scope.layerAddId = item.id; } + + $scope.layerAddById = function (id) { + $scope.layerAddId = id; + $scope.layerAdd(); + } + $scope.layerAdd = function() { $http({method:"GET", url: $scope.urls.xhr_datatypeahead, params : { type: "layerdeps", value: $scope.layerAddId }}) @@ -369,7 +420,7 @@ projectApp.controller('prjCtrl', function($scope, $modal, $http, $interval, $loc $scope.selectedItems = (function () { s = {}; for (var i = 0; i < items.length; i++) { s[items[i].id] = true; };return s; })(); $scope.ok = function() { - console.warn("scope selected is ", $scope.selectedItems); + console.warn("TRC4: scope selected is ", $scope.selectedItems); $modalInstance.close(Object.keys($scope.selectedItems).filter(function (e) { return $scope.selectedItems[e];})); }; @@ -378,7 +429,7 @@ projectApp.controller('prjCtrl', function($scope, $modal, $http, $interval, $loc }; $scope.update = function() { - console.warn("updated ", $scope.selectedItems); + console.warn("TRC5: updated ", $scope.selectedItems); }; }, resolve: { @@ -393,7 +444,7 @@ projectApp.controller('prjCtrl', function($scope, $modal, $http, $interval, $loc modalInstance.result.then(function (selectedArray) { selectedArray.push($scope.layerAddId); - console.warn("selected", selectedArray); + console.warn("TRC6: selected", selectedArray); $scope._makeXHRCall({ method: "POST", url: $scope.urls.xhr_edit, @@ -429,7 +480,16 @@ projectApp.controller('prjCtrl', function($scope, $modal, $http, $interval, $loc } - $scope.test = function(elementid) { + /** + * Verifies if a project settings change would trigger layer updates. If user confirmation is needed, + * a modal dialog will prompt the user to ack the changes. If not, the editProjectSettings() function is called directly. + * + * Only "versionlayers" change for is supported (and hardcoded) for now. + */ + + $scope.testProjectSettingsChange = function(elementid) { + if (elementid != '#change-project-version') throw "Not implemented"; + $http({method:"GET", url: $scope.urls.xhr_datatypeahead, params : { type: "versionlayers", value: $scope.projectVersion }}). success(function (_data) { if (_data.error != "ok") { @@ -463,17 +523,21 @@ projectApp.controller('prjCtrl', function($scope, $modal, $http, $interval, $loc } }); - modalInstance.result.then(function () { $scope.edit(elementid)}); + modalInstance.result.then(function () { $scope.editProjectSettings(elementid)}); } else { - $scope.edit(elementid); + $scope.editProjectSettings(elementid); } } }); } - $scope.edit = function(elementid) { + /** + * Performs changes to project settings, and updates the user interface accordingly. + */ + + $scope.editProjectSettings = function(elementid) { var data = {}; - console.warn("edit with ", elementid); + console.warn("TRC7: editProjectSettings with ", elementid); var alertText = undefined; var alertZone = undefined; var oldLayers = []; @@ -508,10 +572,14 @@ projectApp.controller('prjCtrl', function($scope, $modal, $http, $interval, $loc alertText += "" + $scope.project.release.desc + ". "; } if (elementid == '#change-project-version') { + $scope.layersForTargets = {}; // invalidate error layers for the targets, since layers changed + // requirement https://bugzilla.yoctoproject.org/attachment.cgi?id=2229, notification for changed version to include layers $scope.zone2alerts.forEach(function (e) { e.close() }); alertText += "This has caused the following changes in your project layers:
    " + + // warnings - this is executed AFTER the generic XHRCall handling is done; at this point, if (_data.layers !== undefined) { // show added/deleted layer notifications; scope.layers is already updated by this point. var addedLayers = []; @@ -547,6 +615,10 @@ projectApp.controller('prjCtrl', function($scope, $modal, $http, $interval, $loc } + /** + * Extracts a command passed through the local path in location, and executes/updates UI based on the command + */ + $scope.updateDisplayWithCommands = function() { cmd = $location.path(); @@ -630,6 +702,10 @@ projectApp.controller('prjCtrl', function($scope, $modal, $http, $interval, $loc }); } + /** + * Utility function to display an alert to the user + */ + $scope.displayAlert = function(zone, text, type) { if (zone.maxid === undefined) { zone.maxid = 0; } var crtid = zone.maxid ++; @@ -644,6 +720,10 @@ projectApp.controller('prjCtrl', function($scope, $modal, $http, $interval, $loc return o; } + /** + * Toggles display items between label and input box (the edit pencil icon) on selected settings in project page + */ + $scope.toggle = function(id) { $scope.projectName = $scope.project.name; $scope.projectVersion = $scope.project.release.id; @@ -657,34 +737,52 @@ projectApp.controller('prjCtrl', function($scope, $modal, $http, $interval, $loc keys = Object.keys($scope.mostBuiltTargets); keys = keys.filter(function (e) { if ($scope.mostBuiltTargets[e]) return e }); return keys.length == 0; + } + + /** + * Helper function to deal with error string recognition and manipulation + */ + $scope.getTargetNameFromErrorMsg = function (msg) { + targets = msg.split(" ").splice(2).map(function (v) { return v.replace(/'/g, '')}) + return targets; } - // init code - // - $scope.init = function() { - $scope.pollHandle = $interval(function () { $scope._makeXHRCall({method: "GET", url: $scope.urls.xhr_edit, data: undefined});}, 2000, 0); + $scope.fetchLayersForTargets = function () { + $scope.builds.forEach(function (buildrequest) { + buildrequest.errors.forEach(function (error) { + if (error.msg.indexOf("Nothin") == 0) { + $scope.getTargetNameFromErrorMsg(error.msg).forEach(function (target) { + if ($scope.layersForTargets[target] === undefined) + $scope.getAutocompleteSuggestions("layers4target", target).then( function (list) { + $scope.layersForTargets[target] = list; + }) + }) + } + }) + }) } - $scope.init(); -}); + /** + * Page init code - just init variables and set the automated refresh + */ -/** - TESTING CODE -*/ + $scope.init = function() { + $scope.canceledBuilds = []; + $scope.layersForTargets = {}; + $scope.fetchLayersForTargets(); + $scope.pollHandle = $interval(function () { $scope._makeXHRCall({method: "GET", url: $scope.urls.xhr_edit, data: undefined});}, 2000, 0); + } -function test_diff_arrays() { - _diffArrays([1,2,3], [2,3,4], function(e,f) { return e==f; }, function(e) {console.warn("added", e)}, function(e) {console.warn("deleted", e);}) -} +}); -// test_diff_arrays(); var s = undefined; function test_set_alert(text) { s = angular.element("div#main").scope(); s.displayAlert(s.zone3alerts, text); - console.warn(s.zone3alerts); + console.warn("TRC8: zone3alerts", s.zone3alerts); s.$digest(); } diff --git a/bitbake/lib/toaster/toastergui/templates/project.html b/bitbake/lib/toaster/toastergui/templates/project.html index 2979db74ed..67b267256b 100644 --- a/bitbake/lib/toaster/toastergui/templates/project.html +++ b/bitbake/lib/toaster/toastergui/templates/project.html @@ -89,9 +89,9 @@ vim: expandtab tabstop=2
    -
    +
    - + @@ -108,6 +108,9 @@ vim: expandtab tabstop=2
    + + +

    Latest builds

    @@ -116,9 +119,27 @@ vim: expandtab tabstop=2
    +
    + +
    - Error type {[e.type]}:
    {[e.msg]}
    +
    {[e.msg]}
    + +
    +

    The target {[t]} is not provided by any of your project layers.

    +

    Your build has failed because the target {[t]} is not provided by any of your project layers.

    + +

    The following layers provide this target. You could add one of them to your project.

    + +
    +
    +
    + +

    + Please contact your system administrator to help troubleshoot this error. +

    +
    @@ -128,7 +149,7 @@ vim: expandtab tabstop=2
    Build queued
    - + @@ -136,7 +157,7 @@ vim: expandtab tabstop=2
    Creating build
    - +
    @@ -144,7 +165,7 @@ vim: expandtab tabstop=2
    Build deleted
    - +
    @@ -198,7 +219,7 @@ vim: expandtab tabstop=2
    Build time: {[b.build[0].build_time|timediff]} + ng-click="buildExistingTarget(b.targets)">Run again
    @@ -244,7 +265,7 @@ vim: expandtab tabstop=2
    - +
    {% csrf_token %} @@ -265,9 +286,9 @@ vim: expandtab tabstop=2 Targets - +
    - +
    @@ -304,8 +325,8 @@ vim: expandtab tabstop=2 Machine changes have a big impact on build outcome. You cannot really compare the builds for the new machine with the previous ones. - - + + {% csrf_token %} @@ -337,7 +358,7 @@ vim: expandtab tabstop=2