toaster: Delete project fixes and clean ups
cover toaster: Delete project fixes and clean ups
This addresses several toaster bugs:
9936 - import layer dependencies button toggle 9097 - startup message cleanup
10289 - stop in progress build before deleting project
Also has some buildinfohelper cleanup as well as some test fixes for the ui changes.
-bavery
The following changes since commit 4189a1977e65f71ddb8fc0498bcef91754c673d8:
bitbake: toaster: add Font Awesome license (2016-10-05 10:28:53 +0100)
are available in the git repository at:
git://git.yoctoproject.org/poky-contrib bavery/submit/michaelw/toaster/pending_for_upstream_5_10_16
http://git.yoctoproject.org/cgit.cgi/poky-contrib/log/?h=bavery/submit/michaelw/toaster/pending_for_upstream_5_10_16
Michael Wood (6):
toaster: api / project Cancel any in progress builds before project
delete
toaster: buildinfohelper: Use correct way to get message from
LogMessage
toaster: checksettings Remove confusing startup messages
toaster: importlayer Fix layer dependencies button state toggle
toaster: Delete notification update front end implementation to design
toaster: Update tests to reflect front end changes
lib/bb/ui/buildinfohelper.py | 9 ++-- .../management/commands/checksettings.py | 2 - .../tests/browser/test_layerdetails_page.py | 4 ++ lib/toaster/tests/browser/test_project_page.py | 2 +- lib/toaster/toastergui/api.py | 57 +++++++++++++--------- lib/toaster/toastergui/static/js/importlayer.js | 30 ++---------- lib/toaster/toastergui/static/js/layerdetails.js | 2 + lib/toaster/toastergui/static/js/libtoaster.js | 2 +- lib/toaster/toastergui/templates/base.html | 2 +- .../toastergui/templates/basebuildpage.html | 6 ++- lib/toaster/toastergui/templates/project.html | 12 +++-- lib/toaster/toastergui/tests.py | 8 ++- 12 files changed, 71 insertions(+), 65 deletions(-)
--
1.9.1
PATCH 1/6 toaster: api / project Cancel any in progress builds before project delete
From: Michael Wood <[email protected]>
Before we finally delete any project make sure we send the cancel command to any in-progress builds. This ensures that an inaccessible build doesn't block up the system and that we don't get errors after deletion.
[YOCTO #10289]
--- lib/toaster/toastergui/api.py | 55 +++++++++++++++++---------- lib/toaster/toastergui/templates/project.html | 12 +++--- 2 files changed, 41 insertions(+), 26 deletions(-)
lib/toaster/toastergui/api.py +34 -21
@@ -49,6 +49,28 @@ class XhrBuildRequest(View): def get(self, request, *args, **kwargs): return HttpResponse() + @staticmethod + def cancel_build(br): + """Cancel a build request""" + try: + bbctrl = bbcontroller.BitbakeController(br.environment) + bbctrl.forceShutDown() + except: + # We catch a bunch of exceptions here because + # this is where the server has not had time to start up + # and the build request or build is in transit between + # processes. + # We can safely just set the build as cancelled + # already as it never got started + build = br.build + build.outcome = Build.CANCELLED + build.save() + + # We now hand over to the buildinfohelper to update the + # build state once we've finished cancelling + br.state = BuildRequest.REQ_CANCELLING + br.save() + def post(self, request, *args, **kwargs): """ Build control
@@ -74,26 +96,7 @@ class XhrBuildRequest(View): for i in request.POST['buildCancel'].strip().split(" "): try: br = BuildRequest.objects.get(project=project, pk=i) - - try: - bbctrl = bbcontroller.BitbakeController(br.environment) - bbctrl.forceShutDown() - except: - # We catch a bunch of exceptions here because - # this is where the server has not had time to start up - # and the build request or build is in transit between - # processes. - # We can safely just set the build as cancelled - # already as it never got started - build = br.build - build.outcome = Build.CANCELLED - build.save() - - # We now hand over to the buildinfohelper to update the - # build state once we've finished cancelling - br.state = BuildRequest.REQ_CANCELLING - br.save() - + self.cancel_build(br) except BuildRequest.DoesNotExist: return error_response('No such build request id %s' % i)
@@ -823,11 +826,21 @@ class XhrProject(View): return HttpResponse() def delete(self, request, *args, **kwargs): + """Delete a project. Cancels any builds in progress""" try: - Project.objects.get(pk=kwargs['project_id']).delete() + project = Project.objects.get(pk=kwargs['project_id']) + # Cancel any builds in progress + for br in BuildRequest.objects.filter( + project=project, + state=BuildRequest.REQ_INPROGRESS): + XhrBuildRequest.cancel_build(br) + + project.delete() + except Project.DoesNotExist: return error_response("Project %s does not exist" % kwargs['project_id']) + return JsonResponse({ "error": "ok", "gotoUrl": reverse("all-projects", args=[])
lib/toaster/toastergui/templates/project.html +7 -5
@@ -31,12 +31,14 @@ <h4>Are you sure you want to delete this project?</h4> </div> <div class="modal-body"> - <p>Deleting the <strong class="project-name"></strong> project will remove forever:</p> + <p>Deleting the <strong class="project-name"></strong> project + will:</p> <ul> - <li>Its configuration information</li> - <li>Its imported layers</li> - <li>Its custom images</li> - <li>All its build information</li> + <li>Cancel its builds currently in progress</li> + <li>Remove its configuration information</li> + <li>Remove its imported layers</li> + <li>Remove its custom images</li> + <li>Remove all its build information</li> </ul> </div> <div class="modal-footer">
Signature · 1 line
1.9.1
PATCH 2/6 toaster: buildinfohelper: Use correct way to get message from LogMessage
From: Michael Wood <[email protected]>
Use the correct method to get a message value from the LogMessage object rather than constructing it ourselves which is not recommended. This causes an exception when the msg contains a '%' such as when there are wildcards in file names (something2.%.bbappends)
--- lib/bb/ui/buildinfohelper.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-)
lib/bb/ui/buildinfohelper.py +3 -6
@@ -1565,16 +1565,12 @@ class BuildInfoHelper(object): mockevent.lineno = -1 self.store_log_event(mockevent) - def store_log_event(self, event): self._ensure_build() if event.levelno < formatter.WARNING: return - if 'args' in vars(event): - event.msg = event.msg % event.args - # early return for CLI builds if self.brbe is None: if not 'backlog' in self.internal_state:
@@ -1586,7 +1582,8 @@ class BuildInfoHelper(object): # if we have a backlog of events, do our best to save them here if len(self.internal_state['backlog']): tempevent = self.internal_state['backlog'].pop() - logger.debug(1, "buildinfohelper: Saving stored event %s " % tempevent) + logger.debug(1, "buildinfohelper: Saving stored event %s " + % tempevent) self.store_log_event(tempevent) else: logger.info("buildinfohelper: All events saved")
@@ -1605,7 +1602,7 @@ class BuildInfoHelper(object): else: log_information['level'] = LogMessage.INFO - log_information['message'] = event.msg + log_information['message'] = event.getMessage() log_information['pathname'] = event.pathname log_information['lineno'] = event.lineno logger.info("Logging error 2: %s", log_information)
Signature · 1 line
1.9.1
PATCH 3/6 toaster: checksettings Remove confusing startup messages
In reply to baveryFrom: Michael Wood <[email protected]>
These "validation" messages are shown regardless as to whether the settings are being correctly set or not.
For the time being remove them.
[YOCTO #9097]
--- lib/toaster/bldcontrol/management/commands/checksettings.py | 2 -- 1 file changed, 2 deletions(-)
lib/toaster/bldcontrol/management/commands/checksettings.py +0 -2
@@ -42,7 +42,6 @@ class Command(NoArgsCommand): return True if len(be.sourcedir) == 0: - print("\n -- Validation: The layers checkout directory must be set.") is_changed = _update_sourcedir() if not be.sourcedir.startswith("/"):
@@ -59,7 +58,6 @@ class Command(NoArgsCommand): return True if len(be.builddir) == 0: - print("\n -- Validation: The build directory must be set.") is_changed = _update_builddir() if not be.builddir.startswith("/"):
Signature · 1 line
1.9.1
PATCH 4/6 toaster: importlayer Fix layer dependencies button state toggle
In reply to baveryFrom: Michael Wood <[email protected]>
Fix regression introduced by switching typeahead library. Make sure we enable and disable the add button based on whether the selection event has fired or not.
[YOCTO #9936]
--- lib/toaster/toastergui/static/js/importlayer.js | 30 +++++-------------------- 1 file changed, 5 insertions(+), 25 deletions(-)
lib/toaster/toastergui/static/js/importlayer.js +5 -25
@@ -21,21 +21,11 @@ function importLayerPageInit (ctx) { libtoaster.ctx.layersTypeAheadUrl, { include_added: "true" }, function(item){ currentLayerDepSelection = item; + layerDepBtn.removeAttr("disabled"); }); - // choices available in the typeahead - var layerDepsChoices = {}; - - // when the typeahead choices change, store an array of the available layer - // choices locally, to use for enabling/disabling the "Add layer" button - layerDepInput.on("typeahead-choices-change", function (event, data) { - layerDepsChoices = {}; - - if (data.choices) { - data.choices.forEach(function (item) { - layerDepsChoices[item.name] = item; - }); - } + layerDepInput.on("typeahead:select", function(event, data){ + currentLayerDepSelection = data; }); // Disable local dir repo when page is loaded.
@@ -43,18 +33,8 @@ function importLayerPageInit (ctx) { // disable the "Add layer" button when the layer input typeahead is empty // or not in the typeahead choices - layerDepInput.on("input change", function () { - // get the choices from the typeahead - var choice = layerDepsChoices[$(this).val()]; - - if (choice) { - layerDepBtn.removeAttr("disabled"); - currentLayerDepSelection = choice; - } - else { - layerDepBtn.attr("disabled","disabled"); - currentLayerDepSelection = undefined; - } + layerDepInput.on("input change", function(){ + layerDepBtn.attr("disabled","disabled"); }); /* We automatically add "openembedded-core" layer for convenience as a
Signature · 1 line
1.9.1
PATCH 5/6 toaster: Delete notification update front end implementation to design
In reply to baveryFrom: Michael Wood <[email protected]>
Update the delete notifications to reflect feedback from design review comments.
--- lib/toaster/toastergui/api.py | 2 +- lib/toaster/toastergui/static/js/layerdetails.js | 2 ++ lib/toaster/toastergui/static/js/libtoaster.js | 2 +- lib/toaster/toastergui/templates/base.html | 2 +- lib/toaster/toastergui/templates/basebuildpage.html | 6 +++++- 5 files changed, 10 insertions(+), 4 deletions(-)
lib/toaster/toastergui/api.py +1 -1
@@ -224,7 +224,7 @@ class XhrLayer(View): return JsonResponse({ "error": "ok", - "gotoUrl": reverse('project', args=(kwargs['pid'],)) + "gotoUrl": reverse('projectlayers', args=(kwargs['pid'],)) })
lib/toaster/toastergui/static/js/layerdetails.js +2 -0
@@ -392,6 +392,8 @@ function layerDetailsPageInit (ctx) { $("#layer-delete-confirmed").click(function(){ + $("#delete-layer-modal button[data-dismiss='modal']").hide(); + var message = $('<span>You have deleted <strong>1</strong> layer from your project: <strong id="deleted-layer-name"></strong>'); message.find("#deleted-layer-name").text(ctx.layerVersion.name);
lib/toaster/toastergui/static/js/libtoaster.js +1 -1
@@ -342,7 +342,7 @@ var libtoaster = (function () { } function _showChangeNotification(message){ - $(".alert").fadeOut().promise().done(function(){ + $(".alert-dismissible").fadeOut().promise().done(function(){ var alertMsg = $("#change-notification-msg"); alertMsg.html(message);
lib/toaster/toastergui/templates/base.html +1 -1
@@ -74,7 +74,7 @@ </div> <div id="change-notification" class="alert alert-info alert-dismissible change-notification" style="display:none"> - <button type="button" class="close" id="hide-alert" data-dismiss="alert">×</button> + <button type="button" class="close" id="hide-alert" data-toggle="alert">×</button> <span id="change-notification-msg"></span> </div>
lib/toaster/toastergui/templates/basebuildpage.html +5 -1
@@ -28,6 +28,8 @@ if (data.error !== "ok") { console.warn(data.error); } else { + libtoaster.setNotification("build-deleted", + $("#deleted-build-message").html()); window.location.replace(data.gotoUrl); } },
@@ -61,7 +63,9 @@ }); </script> - +<span style="display:none" id="deleted-build-message"> + You have deleted 1 build: <strong>{{build.get_sorted_target_list|field_values:"target"|join:", "}} {{build.machine}}</strong> completed on <strong>{{build.completed_on|date:"d/m/y H:i"}}</strong> +</span> <div class="modal fade" tabindex="-1" role="dialog" id="delete-build-modal" style="display: none;" data-backdrop="static" data-keyboard="false"> <div class="modal-dialog">
Signature · 1 line
1.9.1
PATCH 6/6 toaster: Update tests to reflect front end changes
In reply to baveryFrom: Michael Wood <[email protected]>
- Browser test we changed the project heading access to use the class name
- Update toastergui unit test for additional gotoUrl property
- On faster browsers we had a race for layer details inputs being visible
--- lib/toaster/tests/browser/test_layerdetails_page.py | 4 ++++ lib/toaster/tests/browser/test_project_page.py | 2 +- lib/toaster/toastergui/tests.py | 8 +++++++- 3 files changed, 12 insertions(+), 2 deletions(-)
lib/toaster/tests/browser/test_layerdetails_page.py +4 -0
@@ -91,6 +91,10 @@ class TestLayerDetailsPage(SeleniumTestCase): for btn in self.find_all("dd .glyphicon-edit"): btn.click() + # Wait for the inputs to become visible + self.wait_until_visible("#layer-git input[type=text]") + self.wait_until_visible("dd textarea") + # Edit each value for inputs in self.find_all("#layer-git input[type=text]") + \ self.find_all("dd textarea"):
lib/toaster/tests/browser/test_project_page.py +1 -1
@@ -55,5 +55,5 @@ class TestProjectPage(SeleniumTestCase): self.get(url) # check that we get a project page with the correct heading - project_name = self.find('#project-name').text.strip() + project_name = self.find('.project-name').text.strip() self.assertEqual(project_name, self.CLI_BUILDS_PROJECT_NAME)
lib/toaster/toastergui/tests.py +7 -1
@@ -248,7 +248,13 @@ class ViewTests(TestCase): url = reverse('xhr_customrecipe_id', args=(recipe.id,)) response = self.client.delete(url) self.assertEqual(response.status_code, 200) - self.assertEqual(json.loads(response.content.decode('utf-8')), {"error": "ok"}) + + gotoUrl = reverse('projectcustomimages', args=(self.project.pk,)) + + self.assertEqual(json.loads(response.content.decode('utf-8')), + {"error": "ok", + "gotoUrl": gotoUrl}) + # try to delete not-existent recipe url = reverse('xhr_customrecipe_id', args=(recipe.id,)) response = self.client.delete(url)
Signature · 1 line
1.9.1