From 7b65fa9bbbee2843e10cf8b8ba8128912100a345 Mon Sep 17 00:00:00 2001 From: Alexandru DAMIAN Date: Fri, 7 Nov 2014 13:26:45 +0000 Subject: bitbake: toastergui: changes for the Project page, round 3 of reviews This patch implements the round 3 of reviews for the Project page, including fixing the time display, fixing the build list display, with fade-in and fade-out animations, and various small layout fixes. [YOCTO #6587] [YOCTO #6731] (Bitbake rev: 09e3ba8f800a03de731b022543cae33a46be17ef) Signed-off-by: Alexandru DAMIAN Signed-off-by: Richard Purdie --- .../lib/toaster/toastergui/static/css/default.css | 27 +++ .../lib/toaster/toastergui/static/js/projectapp.js | 182 ++++++++++++++++----- .../lib/toaster/toastergui/templates/project.html | 62 +++++-- bitbake/lib/toaster/toastergui/views.py | 41 +++-- 4 files changed, 244 insertions(+), 68 deletions(-) (limited to 'bitbake/lib') diff --git a/bitbake/lib/toaster/toastergui/static/css/default.css b/bitbake/lib/toaster/toastergui/static/css/default.css index fb20fc9241..da9697c408 100644 --- a/bitbake/lib/toaster/toastergui/static/css/default.css +++ b/bitbake/lib/toaster/toastergui/static/css/default.css @@ -113,6 +113,9 @@ select { width: auto; } .top-air { margin-top: 40px;} .progress { margin-bottom: 0px; } .lead .badge { font-size: 18px; font-weight: normal; border-radius: 15px; padding: 9px; } +.lead ol > li, .lead ul > li { + line-height: 35px; +} .well > .lead, .alert .lead { margin-bottom: 0px; } .well-transparent { background-color: transparent; } .no-results { margin: 10px 0; } @@ -191,3 +194,27 @@ dd > span { line-height: 20px; } .new-build form { margin: 5px 0 0; } .new-build .input-append { margin-bottom: 0; } #build-selected { margin-top: 15px; } + + +.animate-repeat { + list-style:none; + box-sizing:border-box; +} + +.animate-repeat.ng-move, +.animate-repeat.ng-enter, +.animate-repeat.ng-leave { + -webkit-transition:all linear 0.5s; + transition:all linear 0.5s; +} + +.animate-repeat.ng-leave.ng-leave-active, +.animate-repeat.ng-move, +.animate-repeat.ng-enter { + opacity:0; +} + +.animate-repeat.ng-leave, +.animate-repeat.ng-enter.ng-enter-active { + opacity:1; +} diff --git a/bitbake/lib/toaster/toastergui/static/js/projectapp.js b/bitbake/lib/toaster/toastergui/static/js/projectapp.js index b347451e88..356b92fc5a 100644 --- a/bitbake/lib/toaster/toastergui/static/js/projectapp.js +++ b/bitbake/lib/toaster/toastergui/static/js/projectapp.js @@ -74,17 +74,34 @@ angular_formpost = function($httpProvider) { * * no return */ -function _diffArrays(oldArray, newArray, compareElements, onAdded, onDeleted ) { - if (onDeleted !== undefined) { - oldArray.filter(function (e) { var found = 0; newArray.map(function (f) { if (compareElements(e, f)) {found = 1};}); return !found;}).map(onDeleted); +function _diffArrays(existingArray, newArray, compareElements, onAdded, onDeleted ) { + var added = []; + var removed = []; + newArray.forEach( function( newElement, newIndex, _newArray) { + var existingIndex = existingArray.findIndex(function ( existingElement, _existingIndex, _existingArray ) { + return compareElements(newElement, existingElement); + }); + if (existingIndex < 0 && onAdded) { added.push(newElement); } + }); + existingArray.forEach( function( existingElement, existingIndex, _existingArray) { + var newIndex = newArray.findIndex(function ( newElement, _newIndex, _newArray ) { + return compareElements(newElement, existingElement); + }); + if (newIndex < 0 && onDeleted) { removed.push(existingElement); } + }); + + if (onAdded) { + added.map(onAdded); } - if (onAdded !== undefined) { - newArray.filter(function (e) { var found = 0; oldArray.map(function (f) { if (compareElements(e, f)) {found = 1};}); return !found;}).map(onAdded); + + if (onDeleted) { + removed.map(onDeleted); } + } -var projectApp = angular.module('project', ['ui.bootstrap', 'ngCookies'], angular_formpost); +var projectApp = angular.module('project', ['ngCookies', 'ngAnimate', 'ui.bootstrap' ], angular_formpost); // modify the template tag markers to prevent conflicts with Django projectApp.config(function($interpolateProvider) { @@ -111,7 +128,7 @@ projectApp.filter('timediff', function() { // main controller for the project page -projectApp.controller('prjCtrl', function($scope, $modal, $http, $interval, $location, $cookies, $q, $sce) { +projectApp.controller('prjCtrl', function($scope, $modal, $http, $interval, $location, $cookies, $q, $sce, $anchorScroll, $animate) { $scope.getSuggestions = function(type, currentValue) { var deffered = $q.defer(); @@ -159,40 +176,65 @@ projectApp.controller('prjCtrl', function($scope, $modal, $http, $interval, $loc deffered.reject(_data.error); } else { - // TODO: update screen data if we have fields here if (_data.builds !== undefined) { - - var oldbuilds = $scope.builds; - $scope.builds = _data.builds; - - // identify canceled builds here, so we can display them. - _diffArrays(oldbuilds, $scope.builds, - function (e,f) { return e.id == f.id }, // compare - undefined, // added - function (e) { // deleted - if (e.status == "deleted") return; - e.status = "deleted"; - for (var i = 0; i < $scope.builds.length; i++) { - if ($scope.builds[i].status == "queued" && $scope.builds[i].id > e.id) - continue; - $scope.builds.splice(i, 0, e); - break; - } - }); + 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 oldlayers = $scope.layers; - $scope.layers = _data.layers; - // show added/deleted layer notifications var addedLayers = []; var deletedLayers = []; - _diffArrays( oldlayers, $scope.layers, function (e, f) { return e.id == f.id }, - function (e) { console.log("new layer", e);addedLayers.push(e); }, - function (e) { console.log("del layer", e);deletedLayers.push(e); }); + // step 1 - delete entries not found + $scope.layers.forEach(function (elem) { + if (-1 == _data.layers.findIndex(function (elemX) { return elemX.id == elem.id && elemX.name == elem.name; })) { + deletedLayers.push(elem); + } + }); + deletedLayers.forEach(function (elem) { + $scope.layers.splice($scope.layers.indexOf(elem),1); + }); + // step 2 - merge new entries + _data.layers.forEach(function (elem) { + var found = false; + var i; + for (i = 0 ; i < $scope.layers.length; i ++) { + if ($scope.layers[i].orderid < elem.orderid) continue; + if ($scope.layers[i].orderid == elem.orderid) { + found = true; break; + } + if ($scope.layers[i].orderid > elem.orderid) break; + } + if (!found) { + $scope.layers.splice(i, 0, elem); + addedLayers.push(elem); + } + }); + + // 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"); } @@ -253,7 +295,7 @@ projectApp.controller('prjCtrl', function($scope, $modal, $http, $interval, $loc } $scope.targetNamedBuild = function(target) { - if ($scope.targetName === undefined){ + if ($scope.targetName === undefined && $scope.targetName1 === undefined){ alert("No target defined, please type in a target name"); return; } @@ -263,17 +305,26 @@ projectApp.controller('prjCtrl', function($scope, $modal, $http, $interval, $loc $scope._makeXHRCall({ method: "POST", url: $scope.urls.xhr_build, data : { - targets: $scope.targetName + targets: $scope.safeTargetName, } }).then(function (data) { console.log("received ", data); $scope.targetName = undefined; + $scope.targetName1 = undefined; + $location.hash('buildslist'); + // call $anchorScroll() + $anchorScroll(); }); } $scope.sanitizeTargetName = function() { - if (undefined === $scope.targetName) return; - $scope.targetName = $scope.targetName.replace(/\[.*\]/, '').trim(); + $scope.safeTargetName = undefined; + if (undefined === $scope.targetName) $scope.safeTargetName = $scope.targetName1; + if (undefined === $scope.targetName1) $scope.safeTargetName = $scope.targetName; + + if (undefined === $scope.safeTargetName) return; + + $scope.safeTargetName = $scope.safeTargetName.replace(/\[.*\]/, '').trim(); } $scope.buildCancel = function(id) { @@ -285,6 +336,16 @@ projectApp.controller('prjCtrl', function($scope, $modal, $http, $interval, $loc }); } + $scope.buildDelete = function(id) { + $scope._makeXHRCall({ + method: "POST", url: $scope.urls.xhr_build, + data: { + buildDelete: id, + } + }); + } + + $scope.onLayerSelect = function (item, model, label) { $scope.layerAddId = item.id; } @@ -413,6 +474,7 @@ projectApp.controller('prjCtrl', function($scope, $modal, $http, $interval, $loc console.log("edit with ", elementid); var alertText = undefined; var alertZone = undefined; + var oldLayers = []; switch(elementid) { case '#select-machine': alertText = "You have changed the machine to: " + $scope.machineName + ""; @@ -428,25 +490,61 @@ projectApp.controller('prjCtrl', function($scope, $modal, $http, $interval, $loc data['projectVersion'] = $scope.projectVersion; alertText = "You have changed the release to: "; alertZone = $scope.zone3alerts; + // save old layers + oldLayers = $scope.layers.slice(0); break; default: throw "FIXME: implement conversion for element " + elementid; } - console.log("calling edit with ", data); $scope._makeXHRCall({ method: "POST", url: $scope.urls.xhr_edit, data: data, - }).then( function () { + }).then( function (_data) { $scope.toggle(elementid); if (data['projectVersion'] != undefined) { - alertText += "" + $scope.project.release.name + ""; + alertText += "" + $scope.project.release.desc + ". "; + } + if (elementid == '#change-project-version') { + // 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:
    " + + if (_data.layers !== undefined) { + // show added/deleted layer notifications; scope.layers is already updated by this point. + var addedLayers = []; + var deletedLayers = []; + _diffArrays( oldLayers, $scope.layers, function (e, f) { return e.id == f.id }, + function (e) {addedLayers.push(e); }, + function (e) {deletedLayers.push(e); }); + + // some of the deleted layers are actually replaced (changed) layers + var changedLayers = []; + deletedLayers.forEach(function (e) { + if ( -1 < addedLayers.findIndex(function (f) { return f.name == e.name })) { + changedLayers.push(e); + } + }); + + changedLayers.forEach(function (e) { + deletedLayers.splice(deletedLayers.indexOf(e), 1); + }); + + if (addedLayers.length > 0) { + alertText += "
  • "+addedLayers.length+" layer" + ((addedLayers.length>1)?"s changed: ":" changed: ") + addedLayers.map(function (e) { return ""+e.name+"" }).join(", ") + "
  • "; + } + if (deletedLayers.length > 0) { + alertText += "
  • "+deletedLayers.length+" layer" + ((deletedLayers.length>1)?"s deleted: ":"deleted: ") + deletedLayers.map(function (e) { return ""+e.name+"" }).join(", ") + "
  • "; + } + + } + alertText += "
"; } $scope.displayAlert(alertZone, alertText, "alert-info"); }); } - $scope.executeCommands = function() { + $scope.updateDisplayWithCommands = function() { cmd = $location.path(); function _cmdExecuteWithParam(param, f) { @@ -524,7 +622,7 @@ projectApp.controller('prjCtrl', function($scope, $modal, $http, $interval, $loc // init code // $scope.init = function() { - $scope.pollHandle = $interval(function () { $scope._makeXHRCall({method: "GET", url: $scope.urls.xhr_edit, data: undefined});}, 4000, 0); + $scope.pollHandle = $interval(function () { $scope._makeXHRCall({method: "GET", url: $scope.urls.xhr_edit, data: undefined});}, 2000, 0); } $scope.init(); @@ -539,6 +637,8 @@ function test_diff_arrays() { _diffArrays([1,2,3], [2,3,4], function(e,f) { return e==f; }, function(e) {console.log("added", e)}, function(e) {console.log("deleted", e);}) } +// test_diff_arrays(); + var s = undefined; function test_set_alert(text) { diff --git a/bitbake/lib/toaster/toastergui/templates/project.html b/bitbake/lib/toaster/toastergui/templates/project.html index 6a812834d3..a8f2d6ad4f 100644 --- a/bitbake/lib/toaster/toastergui/templates/project.html +++ b/bitbake/lib/toaster/toastergui/templates/project.html @@ -9,6 +9,7 @@ vim: expandtab tabstop=2 {% block projectinfomain %} + @@ -105,19 +106,21 @@ vim: expandtab tabstop=2 +

Latest builds

- -
+
+ -
+
- {[e.type]}:
{[e.msg]}
+ Error type {[e.type]}:
{[e.msg]}
+
Build queued @@ -125,6 +128,7 @@ vim: expandtab tabstop=2
+
@@ -132,35 +136,56 @@ vim: expandtab tabstop=2
+
Build deleted
- +
+ + -
-
-
-
+ + +
+
+ Checking out layers
-
-
ETA: at {[b.build[0].eta|date:"shortDate"]}
+ + +
+
+
+
+
+
+
ETA: {[b.build[0].eta|date:"HH:mm:ss"]}
+
+ +
- {[b.build[0].completed_on|date:'dd/MM/yy HH:mm']} + + {[b.build[0].completed_on|date:'HH:mm']} + + + {[b.build[0].completed_on|date:'dd/MM/yy HH:mm']} +
-
Build time: {[b.build[0].build_time|timediff]} +
Build time: {[b.build[0].build_time|timediff]}
+ +
FIXME!
@@ -206,7 +231,7 @@ vim: expandtab tabstop=2

View all layers | Import layer

    -
  • +
  • {[l.name]}
  • @@ -221,8 +246,8 @@ vim: expandtab tabstop=2
    - - {% csrf_token %}
    @@ -343,13 +368,16 @@ angular.element(document).ready(function() { scope.machine = {{machine|safe}}; scope.releases = {{releases|safe}}; + var now = (new Date()).getTime(); + scope.todaydate = now - (now % 86400000); + scope.zone1alerts = []; scope.zone2alerts = []; scope.zone3alerts = []; scope.mostBuiltTargets = {}; - scope.executeCommands(); + scope.updateDisplayWithCommands(); scope.validateData(); scope.$digest(); diff --git a/bitbake/lib/toaster/toastergui/views.py b/bitbake/lib/toaster/toastergui/views.py index fb8075067a..3b29dbcfb2 100755 --- a/bitbake/lib/toaster/toastergui/views.py +++ b/bitbake/lib/toaster/toastergui/views.py @@ -1943,11 +1943,13 @@ if toastermain.settings.MANAGED: "completed_on" : y.completed_on.strftime('%s')+"000", "build_time" : (y.completed_on - y.started_on).total_seconds(), "build_page_url" : reverse('builddashboard', args=(y.pk,)), + 'build_time_page_url': reverse('buildtime', args=(y.pk,)), "errors": y.errors_no, "warnings": y.warnings_no, "completeper": y.completeper(), - "eta": y.eta().ctime()}, Build.objects.filter(buildrequest = x)), - }, prj.buildrequest_set.order_by("-pk")[:5]) + "eta": y.eta().strftime('%s')+"000"}, Build.objects.filter(buildrequest = x)), + }, list(prj.buildrequest_set.filter(Q(state__lt=BuildRequest.REQ_COMPLETED) or Q(state=BuildRequest.REQ_DELETED)).order_by("-pk")) + + list(prj.buildrequest_set.filter(state__in=[BuildRequest.REQ_COMPLETED, BuildRequest.REQ_FAILED]).order_by("-pk")[:3])) # Shows the edit project page @@ -1979,10 +1981,17 @@ if toastermain.settings.MANAGED: context = { "project" : prj, "completedbuilds": Build.objects.filter(project = prj).exclude(outcome = Build.IN_PROGRESS), - "prj" : json.dumps({"name": prj.name, "release": { "id": prj.release.pk, "name": prj.release.name}}), + "prj" : json.dumps({"name": prj.name, "release": { "id": prj.release.pk, "name": prj.release.name, "desc": prj.release.description}}), #"buildrequests" : prj.buildrequest_set.filter(state=BuildRequest.REQ_QUEUED), "builds" : json.dumps(_project_recent_build_list(prj)), - "layers" : json.dumps(map(lambda x: {"id": x.layercommit.pk, "name" : x.layercommit.layer.name, "url": x.layercommit.layer.layer_index_url, "layerdetailurl": reverse("layerdetails", args=(x.layercommit.layer.pk,)), "branch" : { "name" : x.layercommit.up_branch.name, "layersource" : x.layercommit.up_branch.layer_source.name}}, prj.projectlayer_set.all())), + "layers" : json.dumps(map(lambda x: { + "id": x.layercommit.pk, + "orderid": x.pk, + "name" : x.layercommit.layer.name, + "url": x.layercommit.layer.layer_index_url, + "layerdetailurl": reverse("layerdetails", args=(x.layercommit.layer.pk,)), + "branch" : { "name" : x.layercommit.up_branch.name, "layersource" : x.layercommit.up_branch.layer_source.name}}, + prj.projectlayer_set.all().order_by("id"))), "targets" : json.dumps(map(lambda x: {"target" : x.target, "task" : x.task, "pk": x.pk}, prj.projecttarget_set.all())), "freqtargets": json.dumps(freqtargets), "releases": json.dumps(map(lambda x: {"id": x.pk, "name": x.name}, Release.objects.all())), @@ -2012,12 +2021,17 @@ if toastermain.settings.MANAGED: for i in request.POST['buildCancel'].strip().split(" "): try: br = BuildRequest.objects.select_for_update().get(project = prj, pk = i, state__lte = BuildRequest.REQ_QUEUED) - print "selected for delete", br.pk - br.delete() - print "selected for delete", br.pk + br.state = BuildRequest.REQ_DELETED + br.save() except BuildRequest.DoesNotExist: pass + if 'buildDelete' in request.POST: + for i in request.POST['buildDelete'].strip().split(" "): + try: + br = BuildRequest.objects.select_for_update().get(project = prj, pk = i, state__lte = BuildRequest.REQ_DELETED).delete() + except BuildRequest.DoesNotExist: + pass if 'targets' in request.POST: ProjectTarget.objects.filter(project = prj).delete() @@ -2054,9 +2068,10 @@ if toastermain.settings.MANAGED: prj.name = request.POST['projectName'] prj.save(); - if 'projectVersion' in request.POST: prj.release = Release.objects.get(pk = request.POST['projectVersion']) + # we need to change the bitbake version + prj.bitbake_version = prj.release.bitbake_version prj.save() # we need to change the layers for i in prj.projectlayer_set.all(): @@ -2067,13 +2082,19 @@ if toastermain.settings.MANAGED: # get rid of the old entry i.delete() + if 'machineName' in request.POST: + machinevar = prj.projectvariable_set.get(name="MACHINE") + machinevar.value=request.POST['machineName'] + machinevar.save() + # return all project settings return HttpResponse(json.dumps( { "error": "ok", - "layers" : map(lambda x: {"id": x.layercommit.pk, "name" : x.layercommit.layer.name, "url": x.layercommit.layer.layer_index_url, "layerdetailurl": reverse("layerdetails", args=(x.layercommit.layer.pk,)), "branch" : { "name" : x.layercommit.up_branch.name, "layersource" : x.layercommit.up_branch.layer_source.name}}, prj.projectlayer_set.all()), + "layers" : map(lambda x: {"id": x.layercommit.pk, "orderid" : x.pk, "name" : x.layercommit.layer.name, "url": x.layercommit.layer.layer_index_url, "layerdetailurl": reverse("layerdetails", args=(x.layercommit.layer.pk,)), "branch" : { "name" : x.layercommit.up_branch.name, "layersource" : x.layercommit.up_branch.layer_source.name}}, prj.projectlayer_set.all().order_by("id")), "builds" : _project_recent_build_list(prj), "variables": map(lambda x: (x.name, x.value), prj.projectvariable_set.all()), - "prj": {"name": prj.name, "release": { "id": prj.release.pk, "name": prj.release.name}}, + "machine": {"name": prj.projectvariable_set.get(name="MACHINE").value}, + "prj": {"name": prj.name, "release": { "id": prj.release.pk, "name": prj.release.name, "desc": prj.release.description}}, }), content_type = "application/json") except Exception as e: -- cgit v1.2.3-54-g00ecf