All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 00/10] delete builds, projects, recipes, layers
@ 2016-09-26 10:59 Ed Bartosh
  2016-09-26 10:59 ` [PATCH 01/10] toaster: Clean up and convert to rest api project edit and get calls Ed Bartosh
                   ` (9 more replies)
  0 siblings, 10 replies; 12+ messages in thread
From: Ed Bartosh @ 2016-09-26 10:59 UTC (permalink / raw)
  To: bitbake-devel

Work to implement deleting of builds projects recipes and layers.
Includes a number of clean ups in affected areas.

The following changes since commit 725e66e1d08ae000d8f68455ddca0e192080dc1f:

  meta-environment: ensure corret TOOLCHAIN_CONFIGSITE_NOCACHE value (2016-09-26 10:06:02 +0100)

are available in the git repository at:

  git://git.yoctoproject.org/poky-contrib ed/submit/michaelw/toaster/delete_buttons
  http://git.yoctoproject.org/cgit.cgi/poky-contrib/log/?h=ed/submit/michaelw/toaster/delete_buttons

Michael Wood (10):
  toaster: Clean up and convert to rest api project edit and get calls
  toaster: move MostRecentBuildsView to its own widget
  toaster: libtoaster Add a global notification set/show mechanism
  toaster: project page Implement front end feature to delete project
  toaster: alerts and modals Avoid modals and alerts overlaying each
    other
  toaster: Add backend API for deleting a build
  toaster: Add front end controls for deleting a build
  toaster: importlayer Convert success import to new notification system
  toaster: customrecipe Add frontend feature to delete custom image
    recipe
  toaster: layerdetails Update implementation of delete imported layer

 lib/toaster/toastergui/api.py                      | 312 +++++++++++++--------
 lib/toaster/toastergui/static/js/customrecipe.js   |  32 +++
 lib/toaster/toastergui/static/js/importlayer.js    |  59 +++-
 lib/toaster/toastergui/static/js/layerDepsModal.js |  12 +-
 lib/toaster/toastergui/static/js/layerdetails.js   |   7 +-
 lib/toaster/toastergui/static/js/libtoaster.js     |  42 ++-
 lib/toaster/toastergui/static/js/projectpage.js    | 237 +++-------------
 lib/toaster/toastergui/static/js/projecttopbar.js  |   6 +-
 lib/toaster/toastergui/static/js/tests/test.js     |   7 +-
 lib/toaster/toastergui/templates/base.html         |   1 +
 .../toastergui/templates/basebuildpage.html        | 138 ++++++---
 .../toastergui/templates/baseprojectpage.html      |   6 +
 .../toastergui/templates/builddashboard.html       |   2 +-
 lib/toaster/toastergui/templates/customrecipe.html |  35 ++-
 lib/toaster/toastergui/templates/layerdetails.html |   9 +-
 lib/toaster/toastergui/templates/project.html      |  35 ++-
 .../toastergui/templates/projecttopbar.html        |   2 +-
 lib/toaster/toastergui/urls.py                     |  11 +-
 lib/toaster/toastergui/views.py                    | 131 +--------
 lib/toaster/toastergui/widgets.py                  | 141 ++++++++--
 20 files changed, 679 insertions(+), 546 deletions(-)

-- 
2.6.6



^ permalink raw reply	[flat|nested] 12+ messages in thread

* [PATCH 01/10] toaster: Clean up and convert to rest api project edit and get calls
  2016-09-26 10:59 [PATCH 00/10] delete builds, projects, recipes, layers Ed Bartosh
@ 2016-09-26 10:59 ` Ed Bartosh
  2016-09-26 10:59 ` [PATCH 02/10] toaster: move MostRecentBuildsView to its own widget Ed Bartosh
                   ` (8 subsequent siblings)
  9 siblings, 0 replies; 12+ messages in thread
From: Ed Bartosh @ 2016-09-26 10:59 UTC (permalink / raw)
  To: bitbake-devel

From: Michael Wood <michael.g.wood@intel.com>

Convert the project xhr calls into proper rest api and port the client
side calls to use the new API. Fix all the pyflakes identified issues
and clean up unused fields.

Also remove the api and client side code for changing release on the fly
as this is no longer supported.

[YOCTO #9519]

Signed-off-by: Michael Wood <michael.g.wood@intel.com>
Signed-off-by: Ed Bartosh <ed.bartosh@linux.intel.com>
---
 lib/toaster/toastergui/api.py                   | 161 ++++++++++++++++++++++++
 lib/toaster/toastergui/static/js/libtoaster.js  |   3 +-
 lib/toaster/toastergui/static/js/projectpage.js | 147 +---------------------
 lib/toaster/toastergui/static/js/tests/test.js  |   7 +-
 lib/toaster/toastergui/templates/base.html      |   1 +
 lib/toaster/toastergui/urls.py                  |   4 +
 lib/toaster/toastergui/views.py                 | 131 +------------------
 7 files changed, 179 insertions(+), 275 deletions(-)

diff --git a/lib/toaster/toastergui/api.py b/lib/toaster/toastergui/api.py
index be18090..8876409 100644
--- a/lib/toaster/toastergui/api.py
+++ b/lib/toaster/toastergui/api.py
@@ -20,11 +20,13 @@
 
 import re
 import logging
+from collections import Counter
 
 from orm.models import Project, ProjectTarget, Build, Layer_Version
 from orm.models import LayerVersionDependency, LayerSource, ProjectLayer
 from orm.models import Recipe, CustomImageRecipe, CustomImagePackage
 from orm.models import Layer, Target, Package, Package_Dependency
+from orm.models import ProjectVariable
 from bldcontrol.models import BuildRequest
 from bldcontrol import bbcontroller
 
@@ -772,3 +774,162 @@ class XhrCustomRecipePackages(View):
         except CustomImageRecipe.DoesNotExist:
             return error_response("Tried to remove package that wasn't"
                                   " present")
+
+
+class XhrProject(View):
+    """ Create, delete or edit a project
+
+    Entry point: /xhr_project/<project_id>
+    """
+    def post(self, request, *args, **kwargs):
+        """
+          Edit project control
+
+          Args:
+              layerAdd = layer_version_id layer_version_id ...
+              layerDel = layer_version_id layer_version_id ...
+              projectName = new_project_name
+              machineName = new_machine_name
+
+          Returns:
+              {"error": "ok"}
+            or
+              {"error": <error message>}
+        """
+        try:
+            prj = Project.objects.get(pk=kwargs['project_id'])
+        except Project.DoesNotExist:
+            return error_response("No such project")
+
+        # Add layers
+        if 'layerAdd' in request.POST and len(request.POST['layerAdd']) > 0:
+            for layer_version_id in request.POST['layerAdd'].split(','):
+                try:
+                    lv = Layer_Version.objects.get(pk=int(layer_version_id))
+                    ProjectLayer.objects.get_or_create(project=prj,
+                                                       layercommit=lv)
+                except Layer_Version.DoesNotExist:
+                    return error_response("Layer version %s asked to add "
+                                          "doesn't exist" % layer_version_id)
+
+        # Remove layers
+        if 'layerDel' in request.POST and len(request.POST['layerDel']) > 0:
+            layer_version_ids = request.POST['layerDel'].split(',')
+            ProjectLayer.objects.filter(
+                project=prj,
+                layercommit_id__in=layer_version_ids).delete()
+
+        # Project name change
+        if 'projectName' in request.POST:
+            prj.name = request.POST['projectName']
+            prj.save()
+
+        # Machine name change
+        if 'machineName' in request.POST:
+            machinevar = prj.projectvariable_set.get(name="MACHINE")
+            machinevar.value = request.POST['machineName']
+            machinevar.save()
+
+        return JsonResponse({"error": "ok"})
+
+    def get(self, request, *args, **kwargs):
+        """
+        Returns:
+            json object representing the current project
+        or:
+            {"error": <error message>}
+        """
+
+        try:
+            project = Project.objects.get(pk=kwargs['project_id'])
+        except Project.DoesNotExist:
+            return error_response("Project %s does not exist" %
+                                  kwargs['project_id'])
+
+        # Create the frequently built targets list
+
+        freqtargets = Counter(Target.objects.filter(
+            Q(build__project=project),
+            ~Q(build__outcome=Build.IN_PROGRESS)
+        ).order_by("target").values_list("target", flat=True))
+
+        freqtargets = freqtargets.most_common(5)
+
+        # We now have the targets in order of frequency but if there are two
+        # with the same frequency then we need to make sure those are in
+        # alphabetical order without losing the frequency ordering
+
+        tmp = []
+        switch = None
+        for i, freqtartget in enumerate(freqtargets):
+            target, count = freqtartget
+            try:
+                target_next, count_next = freqtargets[i+1]
+                if count == count_next and target > target_next:
+                    switch = target
+                    continue
+            except IndexError:
+                pass
+
+            tmp.append(target)
+
+            if switch:
+                tmp.append(switch)
+                switch = None
+
+        freqtargets = tmp
+
+        layers = []
+        for layer in project.projectlayer_set.all():
+            layers.append({
+                "id": layer.layercommit.pk,
+                "name": layer.layercommit.layer.name,
+                "vcs_url": layer.layercommit.layer.vcs_url,
+                "local_source_dir": layer.layercommit.layer.local_source_dir,
+                "vcs_reference": layer.layercommit.get_vcs_reference(),
+                "url": layer.layercommit.layer.layer_index_url,
+                "layerdetailurl": layer.layercommit.get_detailspage_url(
+                    project.pk),
+                "layersource": layer.layercommit.layer_source
+            })
+
+        data = {
+            "name": project.name,
+            "layers": layers,
+            "freqtargets": freqtargets,
+        }
+
+        if project.release is not None:
+            data['release'] = {
+                "id": project.release.pk,
+                "name": project.release.name,
+                "description": project.release.description
+            }
+
+        try:
+            data["machine"] = {"name":
+                               project.projectvariable_set.get(
+                                   name="MACHINE").value}
+        except ProjectVariable.DoesNotExist:
+            data["machine"] = None
+        try:
+            data["distro"] = project.projectvariable_set.get(
+                name="DISTRO").value
+        except ProjectVariable.DoesNotExist:
+            data["distro"] = "-- not set yet"
+
+        data['error'] = "ok"
+
+        return JsonResponse(data)
+
+    def put(self, request, *args, **kwargs):
+        # TODO create new project api
+        return HttpResponse()
+
+    def delete(self, request, *args, **kwargs):
+        try:
+            Project.objects.get(kwargs['project_id']).delete()
+        except Project.DoesNotExist:
+            return error_response("Project %s does not exist" %
+                                  kwargs['project_id'])
+        return JsonResponse({"error": "ok"})
diff --git a/lib/toaster/toastergui/static/js/libtoaster.js b/lib/toaster/toastergui/static/js/libtoaster.js
index f56affd..b2099a6 100644
--- a/lib/toaster/toastergui/static/js/libtoaster.js
+++ b/lib/toaster/toastergui/static/js/libtoaster.js
@@ -167,7 +167,6 @@ var libtoaster = (function () {
   function _getProjectInfo(url, onsuccess, onfail){
     $.ajax({
         type: "GET",
-        data : { format: "json" },
         url: url,
         headers: { 'X-CSRFToken' : $.cookie('csrftoken')},
         success: function (_data) {
@@ -194,7 +193,7 @@ var libtoaster = (function () {
   function _editCurrentProject(data, onSuccess, onFail){
     $.ajax({
         type: "POST",
-        url: libtoaster.ctx.projectPageUrl + "?format=json",
+        url: libtoaster.ctx.xhrProjectUrl,
         data: data,
         headers: { 'X-CSRFToken' : $.cookie('csrftoken')},
         success: function (data) {
diff --git a/lib/toaster/toastergui/static/js/projectpage.js b/lib/toaster/toastergui/static/js/projectpage.js
index b75b3e1..3bf3cba 100644
--- a/lib/toaster/toastergui/static/js/projectpage.js
+++ b/lib/toaster/toastergui/static/js/projectpage.js
@@ -27,11 +27,10 @@ function projectPageInit(ctx) {
 
   var urlParams = libtoaster.parseUrlParams();
 
-  libtoaster.getProjectInfo(libtoaster.ctx.projectPageUrl, function(prjInfo){
+  libtoaster.getProjectInfo(libtoaster.ctx.xhrProjectUrl, function(prjInfo){
     updateProjectLayers(prjInfo.layers);
     updateFreqBuildRecipes(prjInfo.freqtargets);
     updateProjectRelease(prjInfo.release);
-    updateProjectReleases(prjInfo.releases, prjInfo.release);
 
     /* If we're receiving a machine set from the url and it's different from
      * our current machine then activate set machine sequence.
@@ -287,7 +286,9 @@ function projectPageInit(ctx) {
     machineNameTitle.text(machineName);
   }
 
-  libtoaster.makeTypeahead(machineChangeInput, libtoaster.ctx.machinesTypeAheadUrl, { }, function(item){
+  libtoaster.makeTypeahead(machineChangeInput,
+                           libtoaster.ctx.machinesTypeAheadUrl,
+                           { }, function(item){
     currentMachineAddSelection = item.name;
     machineChangeBtn.removeAttr("disabled");
   });
@@ -324,146 +325,10 @@ function projectPageInit(ctx) {
     releaseTitle.text(release.description);
   }
 
-  function updateProjectReleases(releases, current){
-    for (var i in releases){
-      var releaseOption = $("<option></option>");
 
-      releaseOption.val(releases[i].id);
-      releaseOption.text(releases[i].description);
-      releaseOption.data('release', releases[i]);
-
-      if (releases[i].id == current.id)
-        releaseOption.attr("selected", "selected");
-
-      releaseForm.children("select").append(releaseOption);
-    }
-  }
-
-  releaseChangeFormToggle.click(function(){
-    releaseForm.slideDown();
-    releaseTitle.hide();
-    $(this).hide();
-  });
-
-  cancelReleaseChange.click(function(e){
+  $("#delete-project-confirmed").click(function(e){
     e.preventDefault();
-    releaseForm.slideUp(function(){
-      releaseTitle.show();
-      releaseChangeFormToggle.show();
-    });
-  });
-
-  function changeProjectRelease(release, layersToRm){
-    libtoaster.editCurrentProject({ projectVersion : release.id },
-      function(){
-        /* Success */
-        /* Update layers list with new layers */
-        layersInPrjList.addClass('muted');
-        libtoaster.getProjectInfo(libtoaster.ctx.projectPageUrl,
-            function(prjInfo){
-              layersInPrjList.children().remove();
-              updateProjectLayers(prjInfo.layers);
-              layersInPrjList.removeClass('muted');
-              releaseChangedNotification(release, prjInfo.layers, layersToRm);
-        });
-        updateProjectRelease(release);
-        cancelReleaseChange.click();
-    });
-  }
-
-  /* Create a notification to show the changes to the layer configuration
-   * caused by changing a release.
-   */
-
-  function releaseChangedNotification(release, layers, layersToRm){
-
-    var message;
-
-    if (layers.length === 0 && layersToRm.length === 0){
-      message = $('<span><span class="lead">You have changed the project release to: <strong><span id="notify-release-name"></span></strong>.');
-      message.find("#notify-release-name").text(release.description);
-      libtoaster.showChangeNotification(message);
-      return;
-    }
-
-    /* Create the whitespace separated list of layers removed */
-    var layersDelList = "";
-
-    layersToRm.map(function(layer, i){
-      layersDelList += layer.name;
-      if (layersToRm[i+1] !== undefined)
-        layersDelList += ', ';
-    });
-
-    message = $('<span><span class="lead">You have changed the project release to: <strong><span id="notify-release-name"></span></strong>. This has caused the following changes in your project layers:</span><ul id="notify-layers-changed-list"></ul></span>');
-
-    var changedList = message.find("#notify-layers-changed-list");
-
-    message.find("#notify-release-name").text(release.description);
-
-    /* Manually construct the list item for changed layers */
-    var li = '<li><strong>'+layers.length+'</strong> layers changed to the <strong>'+release.name+'</strong> release: ';
-    for (var i in layers){
-      li += '<a href='+layers[i].layerdetailurl+'>'+layers[i].name+'</a>';
-      if (i !== 0)
-        li += ', ';
-    }
-
-    changedList.append($(li));
-
-    /* Layers removed */
-    if (layersToRm && layersToRm.length > 0){
-      if (layersToRm.length == 1)
-        li = '<li><strong>1</strong> layer removed: '+layersToRm[0].name+'</li>';
-      else
-        li = '<li><strong>'+layersToRm.length+'</strong> layers deleted: '+layersDelList+'</li>';
-
-      changedList.append($(li));
-    }
-
-    libtoaster.showChangeNotification(message);
-  }
-
-  /* Show the modal dialog which gives the option to remove layers which
-   * aren't compatible with the proposed release
-   */
-  function showReleaseLayerChangeModal(release, layers){
-    var layersToRmList = releaseModal.find("#layers-to-remove-list");
-    layersToRmList.text("");
-
-    releaseModal.find(".proposed-release-change-name").text(release.description);
-    releaseModal.data("layers", layers);
-    releaseModal.data("release", release);
-
-    for (var i in layers){
-      layersToRmList.append($("<li></li>").text(layers[i].name));
-    }
-    releaseModal.modal('show');
-  }
-
-  $("#change-release-btn").click(function(e){
-    e.preventDefault();
-
-    var newRelease = releaseForm.find("option:selected").data('release');
-
-    $.getJSON(ctx.testReleaseChangeUrl,
-      { new_release_id: newRelease.id },
-      function(layers) {
-        if (layers.rows.length === 0){
-          /* No layers to change for this release */
-          changeProjectRelease(newRelease, []);
-        } else {
-          showReleaseLayerChangeModal(newRelease, layers.rows);
-        }
-    });
-  });
-
-  /* Release change modal accept */
-  $("#change-release-and-rm-layers").click(function(){
-    var layers = releaseModal.data("layers");
-    var release =  releaseModal.data("release");
-
-    changeProjectRelease(release, layers);
+  
   });
 
 }
diff --git a/lib/toaster/toastergui/static/js/tests/test.js b/lib/toaster/toastergui/static/js/tests/test.js
index f8d566b..d7953de 100644
--- a/lib/toaster/toastergui/static/js/tests/test.js
+++ b/lib/toaster/toastergui/static/js/tests/test.js
@@ -42,9 +42,8 @@ QUnit.test("Layer alert notification", function(assert) {
 
 QUnit.test("Project info", function(assert){
   var done = assert.async();
-  libtoaster.getProjectInfo(libtoaster.ctx.projectPageUrl, function(prjInfo){
+  libtoaster.getProjectInfo(libtoaster.ctx.xhrProjectUrl, function(prjInfo){
     assert.ok(prjInfo.machine.name);
-    assert.ok(prjInfo.releases.length > 0);
     assert.ok(prjInfo.layers.length > 0);
     assert.ok(prjInfo.freqtargets);
     assert.ok(prjInfo.release);
@@ -82,11 +81,11 @@ QUnit.test("Add layer", function(assert){
   }, 200);
 
   /* Compare the number of layers before and after the add in the project */
-  libtoaster.getProjectInfo(libtoaster.ctx.projectPageUrl, function(prjInfo){
+  libtoaster.getProjectInfo(libtoaster.ctx.xhrProjectUrl, function(prjInfo){
     var origNumLayers = prjInfo.layers.length;
 
     libtoaster.addRmLayer(layer, true, function(deps){
-      libtoaster.getProjectInfo(libtoaster.ctx.projectPageUrl,
+      libtoaster.getProjectInfo(libtoaster.ctx.xhrProjectUrl,
         function(prjInfo){
         assert.ok(prjInfo.layers.length > origNumLayers,
           "Layer not added to project");
diff --git a/lib/toaster/toastergui/templates/base.html b/lib/toaster/toastergui/templates/base.html
index 58491eb..c1b1417 100644
--- a/lib/toaster/toastergui/templates/base.html
+++ b/lib/toaster/toastergui/templates/base.html
@@ -44,6 +44,7 @@
         {% if project.id %}
         projectId : {{project.id}},
         projectPageUrl : {% url 'project' project.id as purl %}{{purl|json}},
+        xhrProjectUrl : {% url 'xhr_project' project.id as pxurl %}{{pxurl|json}},
         projectName : {{project.name|json}},
         recipesTypeAheadUrl: {% url 'xhr_recipestypeahead' project.id as paturl%}{{paturl|json}},
         layersTypeAheadUrl: {% url 'xhr_layerstypeahead' project.id as paturl%}{{paturl|json}},
diff --git a/lib/toaster/toastergui/urls.py b/lib/toaster/toastergui/urls.py
index 9509cd5..1232611 100644
--- a/lib/toaster/toastergui/urls.py
+++ b/lib/toaster/toastergui/urls.py
@@ -220,6 +220,10 @@ urlpatterns = patterns('toastergui.views',
             api.XhrBuildRequest.as_view(),
             name='xhr_buildrequest'),
 
+        url(r'xhr_project/(?P<project_id>\d+)$',
+            api.XhrProject.as_view(),
+            name='xhr_project'),
+
         url(r'^mostrecentbuilds$', api.MostRecentBuildsView.as_view(),
             name='most_recent_builds'),
 
diff --git a/lib/toaster/toastergui/views.py b/lib/toaster/toastergui/views.py
index 365a1e8..2efb0fd 100755
--- a/lib/toaster/toastergui/views.py
+++ b/lib/toaster/toastergui/views.py
@@ -1361,136 +1361,11 @@ if True:
 
         raise Exception("Invalid HTTP method for this page")
 
-
-
     # Shows the edit project page
-    @_template_renderer('project.html')
     def project(request, pid):
-        prj = Project.objects.get(id = pid)
-
-        try:
-            puser = User.objects.get(id = prj.user_id)
-        except User.DoesNotExist:
-            puser = None
-
-        # execute POST requests
-        if request.method == "POST":
-            # add layers
-            if 'layerAdd' in request.POST and len(request.POST['layerAdd']) > 0:
-                for lc in Layer_Version.objects.filter(pk__in=[i for i in request.POST['layerAdd'].split(",") if len(i) > 0]):
-                    ProjectLayer.objects.get_or_create(project = prj, layercommit = lc)
-
-            # remove layers
-            if 'layerDel' in request.POST and len(request.POST['layerDel']) > 0:
-                for t in request.POST['layerDel'].strip().split(" "):
-                    pt = ProjectLayer.objects.filter(project = prj, layercommit_id = int(t)).delete()
-
-            if 'projectName' in request.POST:
-                prj.name = request.POST['projectName']
-                prj.save();
-
-            if 'projectVersion' in request.POST:
-                # If the release is the current project then return now
-                if prj.release.pk == int(request.POST.get('projectVersion',-1)):
-                    return {}
-
-                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 project in prj.projectlayer_set.all():
-                    # find and add a similarly-named layer on the new branch
-                    try:
-                        layer_versions = prj.get_all_compatible_layer_versions()
-                        layer_versions = layer_versions.filter(layer__name = project.layercommit.layer.name)
-                        ProjectLayer.objects.get_or_create(project = prj, layercommit = layer_versions.first())
-                    except IndexError:
-                        pass
-                    finally:
-                        # get rid of the old entry
-                        project.delete()
-
-            if 'machineName' in request.POST:
-                machinevar = prj.projectvariable_set.get(name="MACHINE")
-                machinevar.value=request.POST['machineName']
-                machinevar.save()
-
-
-        # we use implicit knowledge of the current user's project to filter layer information, e.g.
-        pid = prj.id
-
-        from collections import Counter
-
-        freqtargets = Counter(Target.objects.filter(
-            Q(build__project=prj),
-            ~Q(build__outcome=Build.IN_PROGRESS)
-        ).order_by("target").values_list("target", flat=True))
-
-        freqtargets = freqtargets.most_common(5)
-
-        # We now have the targets in order of frequency but if there are two
-        # with the same frequency then we need to make sure those are in
-        # alphabetical order without losing the frequency ordering
-
-        tmp = []
-        switch = None
-        for i, freqtartget in enumerate(freqtargets):
-            target, count = freqtartget
-            try:
-                target_next, count_next = freqtargets[i+1]
-                if count == count_next and target > target_next:
-                    switch = target
-                    continue
-            except IndexError:
-                pass
-
-            tmp.append(target)
-
-            if switch:
-                tmp.append(switch)
-                switch = None
-
-        freqtargets = tmp
-
-        layers = [{"id": x.layercommit.pk, "orderid": x.pk, "name" : x.layercommit.layer.name,
-                   "vcs_url": x.layercommit.layer.vcs_url, "local_source_dir": x.layercommit.layer.local_source_dir, "vcs_reference" : x.layercommit.get_vcs_reference(),
-                   "url": x.layercommit.layer.layer_index_url, "layerdetailurl": x.layercommit.get_detailspage_url(prj.pk),
-                   "branch" : {"name" : x.layercommit.get_vcs_reference(),
-                               "layersource" : x.layercommit.layer_source }
-                   } for x in prj.projectlayer_set.all().order_by("id")]
-
-        context = {
-            "project" : prj,
-            "lvs_nos" : Layer_Version.objects.all().count(),
-            "completedbuilds": Build.objects.exclude(outcome = Build.IN_PROGRESS).filter(project_id = pid),
-            "prj" : {"name": prj.name, },
-            "buildrequests" : prj.build_set.filter(outcome=Build.IN_PROGRESS),
-            "builds" : Build.get_recent(prj),
-            "layers" : layers,
-            "targets" : [{"target" : x.target, "task" : x.task, "pk": x.pk} for x in prj.projecttarget_set.all()],
-            "variables": [(x.name, x.value) for x in prj.projectvariable_set.all()],
-            "freqtargets": freqtargets,
-            "releases": [{"id": x.pk, "name": x.name, "description":x.description} for x in Release.objects.all()],
-            "project_html": 1,
-            "recipesTypeAheadUrl": reverse('xhr_recipestypeahead', args=(prj.pk,)),
-            "projectBuildsUrl": reverse('projectbuilds', args=(prj.pk,)),
-        }
-
-        if prj.release is not None:
-            context['release'] = { "id": prj.release.pk, "name": prj.release.name, "description": prj.release.description}
-
-
-        try:
-            context["machine"] = {"name": prj.projectvariable_set.get(name="MACHINE").value}
-        except ProjectVariable.DoesNotExist:
-            context["machine"] = None
-        try:
-            context["distro"] = prj.projectvariable_set.get(name="DISTRO").value
-        except ProjectVariable.DoesNotExist:
-            context["distro"] = "-- not set yet"
-
-        return context
+        project = Project.objects.get(pk=pid)
+        context = {"project": project}
+        return render(request, "project.html", context)
 
     def jsunittests(request):
         """ Provides a page for the js unit tests """
-- 
2.6.6



^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [PATCH 02/10] toaster: move MostRecentBuildsView to its own widget
  2016-09-26 10:59 [PATCH 00/10] delete builds, projects, recipes, layers Ed Bartosh
  2016-09-26 10:59 ` [PATCH 01/10] toaster: Clean up and convert to rest api project edit and get calls Ed Bartosh
@ 2016-09-26 10:59 ` Ed Bartosh
  2016-09-26 10:59 ` [PATCH 03/10] toaster: libtoaster Add a global notification set/show mechanism Ed Bartosh
                   ` (7 subsequent siblings)
  9 siblings, 0 replies; 12+ messages in thread
From: Ed Bartosh @ 2016-09-26 10:59 UTC (permalink / raw)
  To: bitbake-devel

From: Michael Wood <michael.g.wood@intel.com>

This view is specific to the builds dashboard rather than gernic api so
like ToasterTable and ToasterTypeAhead we class it as a widget as it has
a single purpose. Also clean up some flake8 identified issues.

Original author of the code moved in this commit is Elliot Smith.

Signed-off-by: Michael Wood <michael.g.wood@intel.com>
Signed-off-by: Ed Bartosh <ed.bartosh@linux.intel.com>
---
 lib/toaster/toastergui/api.py     | 108 -----------------------------
 lib/toaster/toastergui/urls.py    |   3 +-
 lib/toaster/toastergui/widgets.py | 141 +++++++++++++++++++++++++++++++++-----
 3 files changed, 127 insertions(+), 125 deletions(-)

diff --git a/lib/toaster/toastergui/api.py b/lib/toaster/toastergui/api.py
index 8876409..5589118 100644
--- a/lib/toaster/toastergui/api.py
+++ b/lib/toaster/toastergui/api.py
@@ -33,10 +33,8 @@ from bldcontrol import bbcontroller
 from django.http import HttpResponse, JsonResponse
 from django.views.generic import View
 from django.core.urlresolvers import reverse
-from django.utils import timezone
 from django.db.models import Q, F
 from django.db import Error
-from toastergui.templatetags.projecttags import json, sectohms, get_tasks
 from toastergui.templatetags.projecttags import filtered_filesizeformat
 
 logger = logging.getLogger("toaster")
@@ -227,112 +225,6 @@ class XhrLayer(View):
         })
 
 
-class MostRecentBuildsView(View):
-    def _was_yesterday_or_earlier(self, completed_on):
-        now = timezone.now()
-        delta = now - completed_on
-
-        if delta.days >= 1:
-            return True
-
-        return False
-
-    def get(self, request, *args, **kwargs):
-        """
-        Returns a list of builds in JSON format.
-        """
-        project = None
-
-        project_id = request.GET.get('project_id', None)
-        if project_id:
-            try:
-                project = Project.objects.get(pk=project_id)
-            except:
-                # if project lookup fails, assume no project
-                pass
-
-        recent_build_objs = Build.get_recent(project)
-        recent_builds = []
-
-        for build_obj in recent_build_objs:
-            dashboard_url = reverse('builddashboard', args=(build_obj.pk,))
-            buildtime_url = reverse('buildtime', args=(build_obj.pk,))
-            rebuild_url = \
-                reverse('xhr_buildrequest', args=(build_obj.project.pk,))
-            cancel_url = \
-                reverse('xhr_buildrequest', args=(build_obj.project.pk,))
-
-            build = {}
-            build['id'] = build_obj.pk
-            build['dashboard_url'] = dashboard_url
-
-            buildrequest_id = None
-            if hasattr(build_obj, 'buildrequest'):
-                buildrequest_id = build_obj.buildrequest.pk
-            build['buildrequest_id'] = buildrequest_id
-
-            build['recipes_parsed_percentage'] = \
-                int((build_obj.recipes_parsed /
-                     build_obj.recipes_to_parse) * 100)
-
-            tasks_complete_percentage = 0
-            if build_obj.outcome in (Build.SUCCEEDED, Build.FAILED):
-                tasks_complete_percentage = 100
-            elif build_obj.outcome == Build.IN_PROGRESS:
-                tasks_complete_percentage = build_obj.completeper()
-            build['tasks_complete_percentage'] = tasks_complete_percentage
-
-            build['state'] = build_obj.get_state()
-
-            build['errors'] = build_obj.errors.count()
-            build['dashboard_errors_url'] = dashboard_url + '#errors'
-
-            build['warnings'] = build_obj.warnings.count()
-            build['dashboard_warnings_url'] = dashboard_url + '#warnings'
-
-            build['buildtime'] = sectohms(build_obj.timespent_seconds)
-            build['buildtime_url'] = buildtime_url
-
-            build['rebuild_url'] = rebuild_url
-            build['cancel_url'] = cancel_url
-
-            build['is_default_project_build'] = build_obj.project.is_default
-
-            build['build_targets_json'] = \
-                json(get_tasks(build_obj.target_set.all()))
-
-            # convert completed_on time to user's timezone
-            completed_on = timezone.localtime(build_obj.completed_on)
-
-            completed_on_template = '%H:%M'
-            if self._was_yesterday_or_earlier(completed_on):
-                completed_on_template = '%d/%m/%Y ' + completed_on_template
-            build['completed_on'] = completed_on.strftime(
-                completed_on_template)
-
-            targets = []
-            target_objs = build_obj.get_sorted_target_list()
-            for target_obj in target_objs:
-                if target_obj.task:
-                    targets.append(target_obj.target + ':' + target_obj.task)
-                else:
-                    targets.append(target_obj.target)
-            build['targets'] = ' '.join(targets)
-
-            # abbreviated form of the full target list
-            abbreviated_targets = ''
-            num_targets = len(targets)
-            if num_targets > 0:
-                abbreviated_targets = targets[0]
-            if num_targets > 1:
-                abbreviated_targets += (' +%s' % (num_targets - 1))
-            build['targets_abbreviated'] = abbreviated_targets
-
-            recent_builds.append(build)
-
-        return JsonResponse(recent_builds, safe=False)
-
-
 class XhrCustomRecipe(View):
     """ Create a custom image recipe """
 
diff --git a/lib/toaster/toastergui/urls.py b/lib/toaster/toastergui/urls.py
index 1232611..0002a5a 100644
--- a/lib/toaster/toastergui/urls.py
+++ b/lib/toaster/toastergui/urls.py
@@ -24,6 +24,7 @@ from toastergui import tables
 from toastergui import buildtables
 from toastergui import typeaheads
 from toastergui import api
+from toastergui import widgets
 
 urlpatterns = patterns('toastergui.views',
         # landing page
@@ -224,7 +225,7 @@ urlpatterns = patterns('toastergui.views',
             api.XhrProject.as_view(),
             name='xhr_project'),
 
-        url(r'^mostrecentbuilds$', api.MostRecentBuildsView.as_view(),
+        url(r'^mostrecentbuilds$', widgets.MostRecentBuildsView.as_view(),
             name='most_recent_builds'),
 
           # default redirection
diff --git a/lib/toaster/toastergui/widgets.py b/lib/toaster/toastergui/widgets.py
index 005a562..026903d 100644
--- a/lib/toaster/toastergui/widgets.py
+++ b/lib/toaster/toastergui/widgets.py
@@ -22,23 +22,24 @@
 from django.views.generic import View, TemplateView
 from django.views.decorators.cache import cache_control
 from django.shortcuts import HttpResponse
-from django.http import HttpResponseBadRequest
-from django.core import serializers
 from django.core.cache import cache
 from django.core.paginator import Paginator, EmptyPage
 from django.db.models import Q
-from orm.models import Project, ProjectLayer, Layer_Version
+from orm.models import Project, Build
 from django.template import Context, Template
 from django.template import VariableDoesNotExist
 from django.template import TemplateSyntaxError
 from django.core.serializers.json import DjangoJSONEncoder
 from django.core.exceptions import FieldError
-from django.conf.urls import url, patterns
+from django.utils import timezone
+from toastergui.templatetags.projecttags import sectohms, get_tasks
+from toastergui.templatetags.projecttags import json as template_json
+from django.http import JsonResponse
+from django.core.urlresolvers import reverse
 
 import types
 import json
 import collections
-import operator
 import re
 
 try:
@@ -55,6 +56,7 @@ from toastergui.tablefilter import TableFilterMap
 class NoFieldOrDataName(Exception):
     pass
 
+
 class ToasterTable(TemplateView):
     def __init__(self, *args, **kwargs):
         super(ToasterTable, self).__init__()
@@ -81,7 +83,7 @@ class ToasterTable(TemplateView):
     def get_context_data(self, **kwargs):
         context = super(ToasterTable, self).get_context_data(**kwargs)
         context['title'] = self.title
-        context['table_name'] =  type(self).__name__.lower()
+        context['table_name'] = type(self).__name__.lower()
         context['empty_state'] = self.empty_state
 
         return context
@@ -406,7 +408,6 @@ class ToasterTable(TemplateView):
         return data
 
 
-
 class ToasterTypeAhead(View):
     """ A typeahead mechanism to support the front end typeahead widgets """
     MAX_RESULTS = 6
@@ -427,34 +428,142 @@ class ToasterTypeAhead(View):
         error = "ok"
 
         search_term = request.GET.get("search", None)
-        if search_term == None:
+        if search_term is None:
             # We got no search value so return empty reponse
-            return response({'error' : error , 'results': []})
+            return response({'error': error, 'results': []})
 
         try:
             prj = Project.objects.get(pk=kwargs['pid'])
         except KeyError:
             prj = None
 
-        results = self.apply_search(search_term, prj, request)[:ToasterTypeAhead.MAX_RESULTS]
+        results = self.apply_search(search_term,
+                                    prj,
+                                    request)[:ToasterTypeAhead.MAX_RESULTS]
 
         if len(results) > 0:
             try:
                 self.validate_fields(results[0])
-            except MissingFieldsException as e:
+            except self.MissingFieldsException as e:
                 error = e
 
-        data = { 'results' : results,
-                'error' : error,
-               }
+        data = {'results': results,
+                'error': error}
 
         return response(data)
 
     def validate_fields(self, result):
-        if 'name' in result == False or 'detail' in result == False:
-            raise MissingFieldsException("name and detail are required fields")
+        if 'name' in result is False or 'detail' in result is False:
+            raise self.MissingFieldsException(
+                "name and detail are required fields")
 
     def apply_search(self, search_term, prj):
         """ Override this function to implement search. Return an array of
         dictionaries with a minium of a name and detail field"""
         pass
+
+
+class MostRecentBuildsView(View):
+    def _was_yesterday_or_earlier(self, completed_on):
+        now = timezone.now()
+        delta = now - completed_on
+
+        if delta.days >= 1:
+            return True
+
+        return False
+
+    def get(self, request, *args, **kwargs):
+        """
+        Returns a list of builds in JSON format.
+        """
+        project = None
+
+        project_id = request.GET.get('project_id', None)
+        if project_id:
+            try:
+                project = Project.objects.get(pk=project_id)
+            except:
+                # if project lookup fails, assume no project
+                pass
+
+        recent_build_objs = Build.get_recent(project)
+        recent_builds = []
+
+        for build_obj in recent_build_objs:
+            dashboard_url = reverse('builddashboard', args=(build_obj.pk,))
+            buildtime_url = reverse('buildtime', args=(build_obj.pk,))
+            rebuild_url = \
+                reverse('xhr_buildrequest', args=(build_obj.project.pk,))
+            cancel_url = \
+                reverse('xhr_buildrequest', args=(build_obj.project.pk,))
+
+            build = {}
+            build['id'] = build_obj.pk
+            build['dashboard_url'] = dashboard_url
+
+            buildrequest_id = None
+            if hasattr(build_obj, 'buildrequest'):
+                buildrequest_id = build_obj.buildrequest.pk
+            build['buildrequest_id'] = buildrequest_id
+
+            build['recipes_parsed_percentage'] = \
+                int((build_obj.recipes_parsed /
+                     build_obj.recipes_to_parse) * 100)
+
+            tasks_complete_percentage = 0
+            if build_obj.outcome in (Build.SUCCEEDED, Build.FAILED):
+                tasks_complete_percentage = 100
+            elif build_obj.outcome == Build.IN_PROGRESS:
+                tasks_complete_percentage = build_obj.completeper()
+            build['tasks_complete_percentage'] = tasks_complete_percentage
+
+            build['state'] = build_obj.get_state()
+
+            build['errors'] = build_obj.errors.count()
+            build['dashboard_errors_url'] = dashboard_url + '#errors'
+
+            build['warnings'] = build_obj.warnings.count()
+            build['dashboard_warnings_url'] = dashboard_url + '#warnings'
+
+            build['buildtime'] = sectohms(build_obj.timespent_seconds)
+            build['buildtime_url'] = buildtime_url
+
+            build['rebuild_url'] = rebuild_url
+            build['cancel_url'] = cancel_url
+
+            build['is_default_project_build'] = build_obj.project.is_default
+
+            build['build_targets_json'] = \
+                template_json(get_tasks(build_obj.target_set.all()))
+
+            # convert completed_on time to user's timezone
+            completed_on = timezone.localtime(build_obj.completed_on)
+
+            completed_on_template = '%H:%M'
+            if self._was_yesterday_or_earlier(completed_on):
+                completed_on_template = '%d/%m/%Y ' + completed_on_template
+            build['completed_on'] = completed_on.strftime(
+                completed_on_template)
+
+            targets = []
+            target_objs = build_obj.get_sorted_target_list()
+            for target_obj in target_objs:
+                if target_obj.task:
+                    targets.append(target_obj.target + ':' + target_obj.task)
+                else:
+                    targets.append(target_obj.target)
+            build['targets'] = ' '.join(targets)
+
+            # abbreviated form of the full target list
+            abbreviated_targets = ''
+            num_targets = len(targets)
+            if num_targets > 0:
+                abbreviated_targets = targets[0]
+            if num_targets > 1:
+                abbreviated_targets += (' +%s' % (num_targets - 1))
+            build['targets_abbreviated'] = abbreviated_targets
+
+            recent_builds.append(build)
+
+        return JsonResponse(recent_builds, safe=False)
-- 
2.6.6



^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [PATCH 03/10] toaster: libtoaster Add a global notification set/show mechanism
  2016-09-26 10:59 [PATCH 00/10] delete builds, projects, recipes, layers Ed Bartosh
  2016-09-26 10:59 ` [PATCH 01/10] toaster: Clean up and convert to rest api project edit and get calls Ed Bartosh
  2016-09-26 10:59 ` [PATCH 02/10] toaster: move MostRecentBuildsView to its own widget Ed Bartosh
@ 2016-09-26 10:59 ` Ed Bartosh
  2016-09-26 10:59 ` [PATCH 04/10] toaster: project page Implement front end feature to delete project Ed Bartosh
                   ` (6 subsequent siblings)
  9 siblings, 0 replies; 12+ messages in thread
From: Ed Bartosh @ 2016-09-26 10:59 UTC (permalink / raw)
  To: bitbake-devel

From: Michael Wood <michael.g.wood@intel.com>

We now have a number of places where we show change notifications based
on an event in a previous page (imported a layer, deleted a build,
deleted a project etc) and we show these notifications on various pages
so we add a simple notification utility to libtoaster.

Signed-off-by: Michael Wood <michael.g.wood@intel.com>
Signed-off-by: Ed Bartosh <ed.bartosh@linux.intel.com>
---
 lib/toaster/toastergui/static/js/libtoaster.js | 26 ++++++++++++++++++++++++++
 1 file changed, 26 insertions(+)

diff --git a/lib/toaster/toastergui/static/js/libtoaster.js b/lib/toaster/toastergui/static/js/libtoaster.js
index b2099a6..8e2221d 100644
--- a/lib/toaster/toastergui/static/js/libtoaster.js
+++ b/lib/toaster/toastergui/static/js/libtoaster.js
@@ -449,6 +449,16 @@ var libtoaster = (function () {
     ajaxLoadingTimerEnabled = false;
   }
 
+  /* Utility function to set a notification for the next page load */
+  function _setNotification(name, message){
+    var data = {
+      name: name,
+      message: message
+    };
+
+    $.cookie('toaster-notification', JSON.stringify(data), { path: '/'});
+  }
+
   return {
     enableAjaxLoadingTimer: _enableAjaxLoadingTimer,
     disableAjaxLoadingTimer: _disableAjaxLoadingTimer,
@@ -468,6 +478,7 @@ var libtoaster = (function () {
     showChangeNotification : _showChangeNotification,
     createCustomRecipe: _createCustomRecipe,
     makeProjectNameValidation: _makeProjectNameValidation,
+    setNotification: _setNotification,
   };
 })();
 
@@ -502,6 +513,21 @@ function reload_params(params) {
 /* Things that happen for all pages */
 $(document).ready(function() {
 
+  (function showNotificationRequest(){
+    var cookie = $.cookie('toaster-notification');
+
+    if (!cookie)
+      return;
+
+    var notificationData = JSON.parse(cookie);
+
+    libtoaster.showChangeNotification(notificationData.message);
+
+    $.removeCookie('toaster-notification', { path: "/"});
+  })();
+
+
+
   var ajaxLoadingTimer;
 
   /* If we don't have a console object which might be the case in some
-- 
2.6.6



^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [PATCH 04/10] toaster: project page Implement front end feature to delete project
  2016-09-26 10:59 [PATCH 00/10] delete builds, projects, recipes, layers Ed Bartosh
                   ` (2 preceding siblings ...)
  2016-09-26 10:59 ` [PATCH 03/10] toaster: libtoaster Add a global notification set/show mechanism Ed Bartosh
@ 2016-09-26 10:59 ` Ed Bartosh
  2016-09-26 10:59 ` [PATCH 05/10] toaster: alerts and modals Avoid modals and alerts overlaying each other Ed Bartosh
                   ` (5 subsequent siblings)
  9 siblings, 0 replies; 12+ messages in thread
From: Ed Bartosh @ 2016-09-26 10:59 UTC (permalink / raw)
  To: bitbake-devel

From: Michael Wood <michael.g.wood@intel.com>

Add confirm modal and api calls to delete a project from the project
dashboard.

[YOCTO #6238]

Signed-off-by: Michael Wood <michael.g.wood@intel.com>
Signed-off-by: Ed Bartosh <ed.bartosh@linux.intel.com>
---
 lib/toaster/toastergui/api.py                      | 15 +++++++---
 lib/toaster/toastergui/static/js/projectpage.js    | 30 ++++++++++++++++++-
 lib/toaster/toastergui/static/js/projecttopbar.js  |  6 ++--
 .../toastergui/templates/baseprojectpage.html      |  6 ++++
 lib/toaster/toastergui/templates/project.html      | 35 +++++++++++++---------
 .../toastergui/templates/projecttopbar.html        |  2 +-
 6 files changed, 71 insertions(+), 23 deletions(-)

diff --git a/lib/toaster/toastergui/api.py b/lib/toaster/toastergui/api.py
index 5589118..856918b 100644
--- a/lib/toaster/toastergui/api.py
+++ b/lib/toaster/toastergui/api.py
@@ -393,7 +393,7 @@ class XhrCustomRecipeId(View):
         """ Get Custom Image recipe or return an error response"""
         try:
             custom_recipe = \
-                    CustomImageRecipe.objects.get(pk=recipe_id)
+                CustomImageRecipe.objects.get(pk=recipe_id)
             return custom_recipe, None
 
         except CustomImageRecipe.DoesNotExist:
@@ -418,8 +418,12 @@ class XhrCustomRecipeId(View):
         if error:
             return error
 
+        project = custom_recipe.project
+
         custom_recipe.delete()
-        return JsonResponse({"error": "ok"})
+        return JsonResponse({"error": "ok",
+                             "gotoUrl": reverse("projectcustomimages",
+                                                args=(project.pk,))})
 
 
 class XhrCustomRecipePackages(View):
@@ -820,8 +824,11 @@ class XhrProject(View):
 
     def delete(self, request, *args, **kwargs):
         try:
-            Project.objects.get(kwargs['project_id']).delete()
+            Project.objects.get(pk=kwargs['project_id']).delete()
         except Project.DoesNotExist:
             return error_response("Project %s does not exist" %
                                   kwargs['project_id'])
-        return JsonResponse({"error": "ok"})
+        return JsonResponse({
+            "error": "ok",
+            "gotoUrl": reverse("all-projects", args=[])
+        })
diff --git a/lib/toaster/toastergui/static/js/projectpage.js b/lib/toaster/toastergui/static/js/projectpage.js
index 3bf3cba..7f19c0d 100644
--- a/lib/toaster/toastergui/static/js/projectpage.js
+++ b/lib/toaster/toastergui/static/js/projectpage.js
@@ -45,6 +45,9 @@ function projectPageInit(ctx) {
 
    /* Now we're really ready show the page */
     $("#project-page").show();
+
+    /* Set the project name in the delete modal */
+    $("#delete-project-modal .project-name").text(prjInfo.name);
   });
 
   (function notificationRequest(){
@@ -328,7 +331,32 @@ function projectPageInit(ctx) {
 
   $("#delete-project-confirmed").click(function(e){
     e.preventDefault();
-  
+    libtoaster.disableAjaxLoadingTimer();
+    $(this).find('[data-role="submit-state"]').hide();
+    $(this).find('[data-role="loading-state"]').show();
+    $(this).attr("disabled", "disabled");
+    $('#delete-project-modal [data-dismiss="modal"]').hide();
+
+    $.ajax({
+        type: 'DELETE',
+        url: libtoaster.ctx.xhrProjectUrl,
+        headers: { 'X-CSRFToken' : $.cookie('csrftoken')},
+        success: function (data) {
+          if (data.error !== "ok") {
+            console.warn(data.error);
+          } else {
+            var msg =  $('<span>You have deleted <strong>1</strong> project: <strong id="project-deleted"></strong></span>');
+
+            msg.find("#project-deleted").text(libtoaster.ctx.projectName);
+            libtoaster.setNotification("project-deleted", msg.html());
+
+            window.location.replace(data.gotoUrl);
+          }
+        },
+        error: function (data) {
+          console.warn(data);
+        }
+    });
   });
 
 }
diff --git a/lib/toaster/toastergui/static/js/projecttopbar.js b/lib/toaster/toastergui/static/js/projecttopbar.js
index f0cd18b..92ab2d6 100644
--- a/lib/toaster/toastergui/static/js/projecttopbar.js
+++ b/lib/toaster/toastergui/static/js/projecttopbar.js
@@ -4,7 +4,7 @@ function projectTopBarInit(ctx) {
 
   var projectNameForm = $("#project-name-change-form");
   var projectNameContainer = $("#project-name-container");
-  var projectName = $("#project-name");
+  var projectName = $(".project-name");
   var projectNameFormToggle = $("#project-change-form-toggle");
   var projectNameChangeCancel = $("#project-name-change-cancel");
 
@@ -25,14 +25,14 @@ function projectTopBarInit(ctx) {
     e.preventDefault();
     projectNameForm.hide();
     projectNameContainer.fadeIn();
-    $("#project-name-change-input").val(projectName.text());
+    $("#project-name-change-input").val(projectName.first().text());
   });
 
   $("#project-name-change-btn").click(function(){
     var newProjectName = $("#project-name-change-input").val();
 
     libtoaster.editCurrentProject({ projectName: newProjectName }, function (){
-      projectName.html(newProjectName);
+      projectName.text(newProjectName);
       libtoaster.ctx.projectName = newProjectName;
       projectNameChangeCancel.click();
     });
diff --git a/lib/toaster/toastergui/templates/baseprojectpage.html b/lib/toaster/toastergui/templates/baseprojectpage.html
index b3b6f1c..8427d25 100644
--- a/lib/toaster/toastergui/templates/baseprojectpage.html
+++ b/lib/toaster/toastergui/templates/baseprojectpage.html
@@ -34,6 +34,12 @@ $(document).ready(function(){
       <li><a href="{% url 'projectlayers' project.id %}">Layers</a></li>
       <li class="nav-header">Extra configuration</li>
       <li><a href="{% url 'projectconf' project.id %}">BitBake variables</a></li>
+
+      <li class="nav-header">Actions</li>
+      <li>
+        <a href="#delete-project-modal" role="button" class="text-danger" data-toggle="modal" data-target="#delete-project-modal">
+          <i class="icon-trash text-danger"></i> Delete project</a>
+      </li>
     </ul>
   </div>
   <div class="col-md-10">
diff --git a/lib/toaster/toastergui/templates/project.html b/lib/toaster/toastergui/templates/project.html
index 30ee93a..7644dad 100644
--- a/lib/toaster/toastergui/templates/project.html
+++ b/lib/toaster/toastergui/templates/project.html
@@ -24,30 +24,37 @@
   });
 </script>
 
-{% comment %}
-<!-- Comment out the ability to change the project release, until we decide what to do this functionality -->
-<div id="change-release-modal" class="modal hide fade in" tabindex="-1" role="dialog" aria-labelledby="change-release-modal" aria-hidden="false">
+<div id="delete-project-modal" class="modal fade" tabindex="-1" role="dialog" data-backdrop="static" data-keyboard="false">
   <div class="modal-dialog">
     <div class="modal-content">
-
       <div class="modal-header">
-	<button type="button" class="close" data-dismiss="modal" aria-hidden="true">x</button>
-	<h3>Changing Yocto Project release to <span class="proposed-release-change-name"></span></h3>
+        <h4>Are you sure you want to delete this project?</h4>
       </div>
       <div class="modal-body">
-	<p>The following added layers do not exist for <span class="proposed-release-change-name"></span>: </p>
-	<ul id="layers-to-remove-list">
-	</ul>
-	<p>If you change the Yocto Project release to <span class="proposed-release-change-name"></span>, the above layers will be deleted from your added layers.</p>
+        <p>Deleting the <strong class="project-name"></strong> project will remove forever:</p>
+        <ul>
+          <li>Its configuration information</li>
+          <li>Its imported layers</li>
+          <li>Its custom images</li>
+          <li>All its build information</li>
+        </ul>
       </div>
       <div class="modal-footer">
-	<button id="change-release-and-rm-layers" data-dismiss="modal" type="submit" class="btn btn-primary">Change release and delete layers</button>
-	<button class="btn" data-dismiss="modal" aria-hidden="true">Cancel</button>
+        <button type="button" class="btn btn-primary" id="delete-project-confirmed">
+          <span data-role="submit-state">Delete project</span>
+          <span data-role="loading-state" style="display:none">
+            <span class="fa-pulse">
+            <i class="fa-pulse icon-spinner"></i>
+          </span>
+            &nbsp;Deleting project...
+          </span>
+        </button>
+        <button type="button" class="btn btn-link" data-dismiss="modal">Cancel</button>
       </div>
     </div><!-- /.modal-content -->
   </div><!-- /.modal-dialog -->
-</div><!-- /.modal -->
-{% endcomment %}
+</div>
+
 
 <div class="row" id="project-page" style="display:none">
   <div class="col-md-6">
diff --git a/lib/toaster/toastergui/templates/projecttopbar.html b/lib/toaster/toastergui/templates/projecttopbar.html
index 2734af0..768ca94 100644
--- a/lib/toaster/toastergui/templates/projecttopbar.html
+++ b/lib/toaster/toastergui/templates/projecttopbar.html
@@ -24,7 +24,7 @@
   <!-- project name -->
   <div class="page-header">
     <h1 id="project-name-container">
-      <span id="project-name">{{project.name}}</span>
+      <span class="project-name">{{project.name}}</span>
 
       <span class="glyphicon glyphicon-edit" id="project-change-form-toggle"></i>
 
-- 
2.6.6



^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [PATCH 05/10] toaster: alerts and modals Avoid modals and alerts overlaying each other
  2016-09-26 10:59 [PATCH 00/10] delete builds, projects, recipes, layers Ed Bartosh
                   ` (3 preceding siblings ...)
  2016-09-26 10:59 ` [PATCH 04/10] toaster: project page Implement front end feature to delete project Ed Bartosh
@ 2016-09-26 10:59 ` Ed Bartosh
  2016-09-26 10:59 ` [PATCH 06/10] toaster: Add backend API for deleting a build Ed Bartosh
                   ` (4 subsequent siblings)
  9 siblings, 0 replies; 12+ messages in thread
From: Ed Bartosh @ 2016-09-26 10:59 UTC (permalink / raw)
  To: bitbake-devel

From: Michael Wood <michael.g.wood@intel.com>

Make sure that when we spawn a modal we clear any notifications and also
make sure that old notifications are cleared before showing a new one.

Signed-off-by: Michael Wood <michael.g.wood@intel.com>
Signed-off-by: Ed Bartosh <ed.bartosh@linux.intel.com>
---
 lib/toaster/toastergui/static/js/layerDepsModal.js | 12 ++++++++++--
 lib/toaster/toastergui/static/js/libtoaster.js     | 13 ++++++++++---
 2 files changed, 20 insertions(+), 5 deletions(-)

diff --git a/lib/toaster/toastergui/static/js/layerDepsModal.js b/lib/toaster/toastergui/static/js/layerDepsModal.js
index b79049e..e962224 100644
--- a/lib/toaster/toastergui/static/js/layerDepsModal.js
+++ b/lib/toaster/toastergui/static/js/layerDepsModal.js
@@ -6,7 +6,12 @@
  * addToProject: Whether to add layers to project on accept
  * successAdd: function to run on success
  */
-function showLayerDepsModal(layer, dependencies, title, body, addToProject, successAdd) {
+function showLayerDepsModal(layer,
+                            dependencies,
+                            title,
+                            body,
+                            addToProject,
+                            successAdd) {
 
   if ($("#dependencies-modal").length === 0) {
     $.get(libtoaster.ctx.htmlUrl + "/layer_deps_modal.html", function(html){
@@ -43,7 +48,10 @@ function showLayerDepsModal(layer, dependencies, title, body, addToProject, succ
 
     $("#dependencies-modal").data("deps", dependencies);
 
-    $('#dependencies-modal').modal('show');
+    /* Clear any alert notifications before showing the modal */
+    $(".alert").fadeOut(function(){
+      $('#dependencies-modal').modal('show');
+    });
 
     /* Discard the old submission function */
     $("#dependencies-modal-form").unbind('submit');
diff --git a/lib/toaster/toastergui/static/js/libtoaster.js b/lib/toaster/toastergui/static/js/libtoaster.js
index 8e2221d..0832ba4 100644
--- a/lib/toaster/toastergui/static/js/libtoaster.js
+++ b/lib/toaster/toastergui/static/js/libtoaster.js
@@ -342,10 +342,12 @@ var libtoaster = (function () {
   }
 
   function _showChangeNotification(message){
-    var alertMsg = $("#change-notification-msg");
+    $(".alert").fadeOut().promise().done(function(){
+      var alertMsg = $("#change-notification-msg");
 
-    alertMsg.html(message);
-    $("#change-notification, #change-notification *").fadeIn();
+      alertMsg.html(message);
+      $("#change-notification, #change-notification *").fadeIn();
+    });
   }
 
   function _createCustomRecipe(name, baseRecipeId, doneCb){
@@ -716,6 +718,11 @@ $(document).ready(function() {
       });
     }
 
+    /* Make sure we don't have a notification overlay a modal */
+    $(".modal").on('show.bs.modal', function(){
+      $(".alert-dismissible").fadeOut();
+    });
+
     if (libtoaster.debug) {
       check_for_duplicate_ids();
     } else {
-- 
2.6.6



^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [PATCH 06/10] toaster: Add backend API for deleting a build
  2016-09-26 10:59 [PATCH 00/10] delete builds, projects, recipes, layers Ed Bartosh
                   ` (4 preceding siblings ...)
  2016-09-26 10:59 ` [PATCH 05/10] toaster: alerts and modals Avoid modals and alerts overlaying each other Ed Bartosh
@ 2016-09-26 10:59 ` Ed Bartosh
  2016-09-26 10:59 ` [PATCH 07/10] toaster: Add front end controls " Ed Bartosh
                   ` (3 subsequent siblings)
  9 siblings, 0 replies; 12+ messages in thread
From: Ed Bartosh @ 2016-09-26 10:59 UTC (permalink / raw)
  To: bitbake-devel

From: Michael Wood <michael.g.wood@intel.com>

Signed-off-by: Michael Wood <michael.g.wood@intel.com>
Signed-off-by: Ed Bartosh <ed.bartosh@linux.intel.com>
---
 lib/toaster/toastergui/api.py  | 30 ++++++++++++++++++++++++++++++
 lib/toaster/toastergui/urls.py |  4 ++++
 2 files changed, 34 insertions(+)

diff --git a/lib/toaster/toastergui/api.py b/lib/toaster/toastergui/api.py
index 856918b..149abf7 100644
--- a/lib/toaster/toastergui/api.py
+++ b/lib/toaster/toastergui/api.py
@@ -832,3 +832,33 @@ class XhrProject(View):
             "error": "ok",
             "gotoUrl": reverse("all-projects", args=[])
         })
+
+
+class XhrBuild(View):
+    """ Delete a build object
+
+    Entry point: /xhr_build/<build_id>
+    """
+    def delete(self, request, *args, **kwargs):
+        """
+          Delete build data
+
+          Args:
+              build_id = build_id
+
+          Returns:
+              {"error": "ok"}
+            or
+              {"error": <error message>}
+        """
+        try:
+            build = Build.objects.get(pk=kwargs['build_id'])
+            project = build.project
+            build.delete()
+        except Build.DoesNotExist:
+            return error_response("Build %s does not exist" %
+                                  kwargs['build_id'])
+        return JsonResponse({
+            "error": "ok",
+            "gotoUrl": reverse("projectbuilds", args=(project.pk,))
+        })
diff --git a/lib/toaster/toastergui/urls.py b/lib/toaster/toastergui/urls.py
index 0002a5a..ece9ac1 100644
--- a/lib/toaster/toastergui/urls.py
+++ b/lib/toaster/toastergui/urls.py
@@ -225,6 +225,10 @@ urlpatterns = patterns('toastergui.views',
             api.XhrProject.as_view(),
             name='xhr_project'),
 
+        url(r'xhr_build/(?P<build_id>\d+)$',
+            api.XhrBuild.as_view(),
+            name='xhr_build'),
+
         url(r'^mostrecentbuilds$', widgets.MostRecentBuildsView.as_view(),
             name='most_recent_builds'),
 
-- 
2.6.6



^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [PATCH 07/10] toaster: Add front end controls for deleting a build
  2016-09-26 10:59 [PATCH 00/10] delete builds, projects, recipes, layers Ed Bartosh
                   ` (5 preceding siblings ...)
  2016-09-26 10:59 ` [PATCH 06/10] toaster: Add backend API for deleting a build Ed Bartosh
@ 2016-09-26 10:59 ` Ed Bartosh
  2016-09-26 10:59 ` [PATCH 08/10] toaster: importlayer Convert success import to new notification system Ed Bartosh
                   ` (2 subsequent siblings)
  9 siblings, 0 replies; 12+ messages in thread
From: Ed Bartosh @ 2016-09-26 10:59 UTC (permalink / raw)
  To: bitbake-devel

From: Michael Wood <michael.g.wood@intel.com>

Add front end modal and controls for deleting a build from the build
dashboard.

Also convert the Actions list to links instead of buttons as per the
design.

[YOCTO #6238]

Signed-off-by: Michael Wood <michael.g.wood@intel.com>
Signed-off-by: Ed Bartosh <ed.bartosh@linux.intel.com>
---
 .../toastergui/templates/basebuildpage.html        | 138 ++++++++++++++++-----
 .../toastergui/templates/builddashboard.html       |   2 +-
 2 files changed, 105 insertions(+), 35 deletions(-)

diff --git a/lib/toaster/toastergui/templates/basebuildpage.html b/lib/toaster/toastergui/templates/basebuildpage.html
index 01d3117..0b6ef56 100644
--- a/lib/toaster/toastergui/templates/basebuildpage.html
+++ b/lib/toaster/toastergui/templates/basebuildpage.html
@@ -3,8 +3,88 @@
 {% load project_url_tag %}
 {% load objects_to_dictionaries_filter %}
 {% load humanize %}
+{% load field_values_filter %}
 {% block pagecontent %}
 
+ <script>
+  var configVarUrl = "{% url 'configvars' build.id %}";
+
+  $(document).ready(function(){
+
+    $("#delete-build-confirm").click(function(){
+      libtoaster.disableAjaxLoadingTimer();
+      $(this).find('[data-role="submit-state"]').hide();
+      $(this).find('[data-role="loading-state"]').show();
+      $(this).attr("disabled", "disabled");
+
+      /* Make the modal non cancelable while delete is in progress */
+      $('#delete-build-modal button[data-dismiss="modal"]').hide();
+
+      $.ajax({
+          type: 'DELETE',
+          url: "{% url 'xhr_build' build.id %}",
+          headers: { 'X-CSRFToken' : $.cookie('csrftoken')},
+          success: function (data) {
+            if (data.error !== "ok") {
+              console.warn(data.error);
+            } else {
+             window.location.replace(data.gotoUrl);
+            }
+          },
+          error: function (data) {
+            console.warn(data);
+        }
+      });
+    });
+
+
+    $('#breadcrumb > li').append('<span class="divider">&rarr;</span>');
+    $('#breadcrumb > li:last').addClass("active");
+    $('#breadcrumb > li:last > span').remove();
+
+    $("#build-menu li a").each(function(){
+      /* Set the page active state in the Build menu */
+      var currentUrl = window.location.href.split('?')[0];
+      if (currentUrl === $(this).prop("href")){
+        $(this).parent().addClass("active");
+      } else {
+      /* Special case the configvar as this is part of configuration
+       * page but is a separate url
+       */
+      if (window.location.pathname === configVarUrl){
+          $("#menu-configuration").addClass("active");
+        } else {
+          $(this).parent().removeClass("active");
+        }
+      }
+    });
+  });
+ </script>
+
+
+
+<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">
+    <div class="modal-content">
+      <div class="modal-body">
+        <p>Are you sure you want to delete the 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>?</p>
+      </div>
+      <div class="modal-footer">
+        <button id="delete-build-confirm" class="btn btn-primary btn-large">
+          <span data-role="submit-state">Delete build</span>
+          <span data-role="loading-state" style="display:none">
+            <span class="fa-pulse">
+              <i class="icon-spinner"></i>
+            </span>
+              &nbsp;Deleting build...
+          </span>
+        </button>
+        <button type="button" class="btn btn-link" data-dismiss="modal">Cancel</button>
+      </div>
+    </div><!-- /.modal-content -->
+  </div><!-- /.modal-dialog -->
+</div> <!-- / modal -->
+
 <div class="row">
   <!-- breadcrumbs -->
   <div class="col-md-12">
@@ -22,36 +102,6 @@
       </li>
       {% block localbreadcrumb %}{% endblock %}
     </ul>
-    <script>
-
-      var configVarUrl = "{% url 'configvars' build.id %}";
-
-      $(document).ready(function(){
-        $('#breadcrumb > li').append('<span class="divider">&rarr;</span>');
-        $('#breadcrumb > li:last').addClass("active");
-        $('#breadcrumb > li:last > span').remove();
-
-        $("#build-menu li a").each(function(){
-          /* Set the page active state in the Build menu */
-          var currentUrl = window.location.href.split('?')[0];
-          if (currentUrl === $(this).prop("href")){
-            $(this).parent().addClass("active");
-          } else {
-            /* Special case the configvar as this is part of configuration
-             * page but is a separate url, and the direct links to errors
-             * and warnings, which are part of the build dashboard
-             */
-            if (window.location.pathname === configVarUrl){
-              $("#menu-configuration").addClass("active");
-            } else if (currentUrl.indexOf('error') > 1 || currentUrl.indexOf('warning') > 1){
-              $("#menu-dashboard").addClass("active");
-            } else {
-              $(this).parent().removeClass("active");
-            }
-          }
-        });
-      });
-    </script>
   </div>
 </div>
 
@@ -86,13 +136,22 @@
             <li><a href="{% url 'cputime' build.pk %}">CPU usage</a></li>
             <li><a href="{% url 'diskio' build.pk %}">Disk I/O</a></li>
 
-	  <li class="nav-header">Actions</li>
-         	<a class="btn btn-default btn-block navbar-btn" href="{% url 'build_artifact' build.id 'cookerlog' build.id %}">Download build log</a>
+          <li class="nav-header">Actions</li>
+          <li>
+            <a href="{% url 'build_artifact' build.id 'cookerlog' build.id %}">
+              <span class="glyphicon glyphicon-download-alt"></span>
+            Download build log
+            </a>
+          </li>
 
           {% with build.get_custom_image_recipes as custom_image_recipes %}
             {% if custom_image_recipes.count > 0 %}
               <!-- edit custom image built during this build -->
-                <button class="btn btn-default btn-block navbar-btn" data-role="edit-custom-image-trigger">Edit custom image</button>
+              <li>
+                <a href="#" data-role="edit-custom-image-trigger">
+                  <span class="glyphicon glyphicon-edit"></span>
+                  Edit custom image
+                </a>
                 {% include 'editcustomimage_modal.html' %}
                 <script>
                   var editableCustomImageRecipes = {{ custom_image_recipes | objects_to_dictionaries:"id,name" | json }};
@@ -117,12 +176,17 @@
                     });
                   });
                 </script>
+              </li>
             {% endif %}
           {% endwith %}
 
             <!-- new custom image from image recipe in this build -->
             {% if build.has_image_recipes %}
-              <button class="btn btn-default btn-block navbar-btn" data-role="new-custom-image-trigger">New custom image</button>
+            <li>
+              <a href="#"  data-role="new-custom-image-trigger">
+                <span class="glyphicon glyphicon-plus"></span>
+                New custom image
+              </a>
               {% include 'newcustomimage_modal.html' %}
               <script>
                 // imageRecipes includes both custom image recipes and built-in
@@ -147,6 +211,12 @@
                 });
               </script>
             {% endif %}
+
+            <li>
+            <a href="#delete-build-modal" id="delete-build" data-toggle="modal" data-target="#delete-build-modal" class="text-danger">
+              <span class="glyphicon glyphicon-trash"></span>
+              Delete build
+            </a>
         </ul>
       </div>
       <!-- end left sidebar container -->
diff --git a/lib/toaster/toastergui/templates/builddashboard.html b/lib/toaster/toastergui/templates/builddashboard.html
index 1c390cd..02a2981 100644
--- a/lib/toaster/toastergui/templates/builddashboard.html
+++ b/lib/toaster/toastergui/templates/builddashboard.html
@@ -34,7 +34,7 @@
 				<a href="#warnings" class="show-warnings"> {{build.warnings.count}} warning{{build.warnings.count|pluralize}}</a>
 			{% endif %}
 			{% if build.cooker_log_path %}
-			<a class="alert-link pull-right log" href="{% url 'build_artifact' build.id "cookerlog" build.id %}">Download build log</a>
+			<a class="pull-right log" href="{% url 'build_artifact' build.id "cookerlog" build.id %}">Download build log</a>
 			{% endif %}
 			<span class="pull-right">
 				Build time:
-- 
2.6.6



^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [PATCH 08/10] toaster: importlayer Convert success import to new notification system
  2016-09-26 10:59 [PATCH 00/10] delete builds, projects, recipes, layers Ed Bartosh
                   ` (6 preceding siblings ...)
  2016-09-26 10:59 ` [PATCH 07/10] toaster: Add front end controls " Ed Bartosh
@ 2016-09-26 10:59 ` Ed Bartosh
  2016-09-26 10:59 ` [PATCH 09/10] toaster: customrecipe Add frontend feature to delete custom image recipe Ed Bartosh
  2016-09-26 10:59 ` [PATCH 10/10] toaster: layerdetails Update implementation of delete imported layer Ed Bartosh
  9 siblings, 0 replies; 12+ messages in thread
From: Ed Bartosh @ 2016-09-26 10:59 UTC (permalink / raw)
  To: bitbake-devel

From: Michael Wood <michael.g.wood@intel.com>

Use the simpler libtoaster method of showing a notification about
successful import of a layer.
Also a number of whitespace clean ups.

Signed-off-by: Michael Wood <michael.g.wood@intel.com>
Signed-off-by: Ed Bartosh <ed.bartosh@linux.intel.com>
---
 lib/toaster/toastergui/static/js/importlayer.js | 59 ++++++++++++++++------
 lib/toaster/toastergui/static/js/projectpage.js | 66 +------------------------
 2 files changed, 47 insertions(+), 78 deletions(-)

diff --git a/lib/toaster/toastergui/static/js/importlayer.js b/lib/toaster/toastergui/static/js/importlayer.js
index 191b30f..570ca33 100644
--- a/lib/toaster/toastergui/static/js/importlayer.js
+++ b/lib/toaster/toastergui/static/js/importlayer.js
@@ -17,7 +17,9 @@ function importLayerPageInit (ctx) {
   var currentLayerDepSelection;
   var validLayerName = /^(\w|-)+$/;
 
-  libtoaster.makeTypeahead(layerDepInput, libtoaster.ctx.layersTypeAheadUrl, { include_added: "true" }, function(item){
+  libtoaster.makeTypeahead(layerDepInput,
+                           libtoaster.ctx.layersTypeAheadUrl,
+                           { include_added: "true" }, function(item){
     currentLayerDepSelection = item;
   });
 
@@ -95,7 +97,8 @@ function importLayerPageInit (ctx) {
 
     $("#layer-deps-list").append(newLayerDep);
 
-    libtoaster.getLayerDepsForProject(currentLayerDepSelection.layerdetailurl, function (data){
+    libtoaster.getLayerDepsForProject(currentLayerDepSelection.layerdetailurl,
+                                      function (data){
         /* These are the dependencies of the layer added as a dependency */
         if (data.list.length > 0) {
           currentLayerDepSelection.url = currentLayerDepSelection.layerdetailurl;
@@ -150,7 +153,9 @@ function importLayerPageInit (ctx) {
       var body = "<strong>"+layer.name+"</strong>'s dependencies ("+
         depNames.join(", ")+"</span>) require some layers that are not added to your project. Select the ones you want to add:</p>";
 
-      showLayerDepsModal(layer, depDepsArray, title, body, false, function(layerObsList){
+      showLayerDepsModal(layer,
+                         depDepsArray,
+                         title, body, false, function(layerObsList){
         /* Add the accepted layer dependencies' ids to the allDeps array */
         for (var key in layerObsList){
           allDeps.push(layerObsList[key].id);
@@ -191,9 +196,8 @@ function importLayerPageInit (ctx) {
             if (data.error != "ok") {
               console.log(data.error);
             } else {
-              /* Success layer import now go to the project page */
-              $.cookie('layer-imported-alert', JSON.stringify(data), { path: '/'});
-              window.location.replace(libtoaster.ctx.projectPageUrl+'?notify=layer-imported');
+              createImportedNotification(data);
+              window.location.replace(libtoaster.ctx.projectPageUrl);
             }
           },
           error: function (data) {
@@ -204,6 +208,30 @@ function importLayerPageInit (ctx) {
     }
   });
 
+  /* Layer imported notification */
+  function createImportedNotification(imported){
+    var message = "Layer imported";
+
+    if (imported.deps_added.length === 0) {
+      message = "You have imported <strong><a class=\"alert-link\" href=\""+imported.imported_layer.layerdetailurl+"\">"+imported.imported_layer.name+"</a></strong> and added it to your project.";
+    } else {
+
+      var links = "<a href=\""+imported.imported_layer.layerdetailurl+"\">"+imported.imported_layer.name+"</a>, ";
+
+      imported.deps_added.map (function(item, index){
+        links +='<a href="'+item.layerdetailurl+'">'+item.name+'</a>';
+        /*If we're at the last element we don't want the trailing comma */
+        if (imported.deps_added[index+1] !== undefined)
+          links += ', ';
+      });
+
+      /* Length + 1 here to do deps + the imported layer */
+      message = 'You have imported <strong><a href="'+imported.imported_layer.layerdetailurl+'">'+imported.imported_layer.name+'</a></strong> and added <strong>'+(imported.deps_added.length+1)+'</strong> layers to your project: <strong>'+links+'</strong>';
+    }
+
+    libtoaster.setNotification("layer-imported", message);
+  }
+
   function enable_import_btn(enabled) {
     var importAndAddHint = $("#import-and-add-hint");
 
@@ -230,10 +258,13 @@ function importLayerPageInit (ctx) {
     }
 
     if (valid) {
-      if ($("#local-dir-radio").prop("checked") && localDirPath.val().length > 0) {
+      if ($("#local-dir-radio").prop("checked") &&
+          localDirPath.val().length > 0) {
         enable_import_btn(true);
       }
-      if ($("#git-repo-radio").prop("checked") && vcsURLInput.val().length > 0 && gitRefInput.val().length > 0) {
+
+      if ($("#git-repo-radio").prop("checked") &&
+          vcsURLInput.val().length > 0 && gitRefInput.val().length > 0) {
         enable_import_btn(true);
       }
     }
@@ -266,13 +297,13 @@ function importLayerPageInit (ctx) {
   }
 
   layerNameInput.on('blur', function() {
-      if (!$(this).val()){
-        return;
-      }
-      var name = $(this).val();
+    if (!$(this).val()){
+      return;
+    }
+    var name = $(this).val();
 
-      /* Check if the layer name exists */
-      $.getJSON(libtoaster.ctx.layersTypeAheadUrl,
+    /* Check if the layer name exists */
+    $.getJSON(libtoaster.ctx.layersTypeAheadUrl,
         { include_added: "true" , search: name, format: "json" },
         function(layer) {
           if (layer.results.length > 0) {
diff --git a/lib/toaster/toastergui/static/js/projectpage.js b/lib/toaster/toastergui/static/js/projectpage.js
index 7f19c0d..4536703 100644
--- a/lib/toaster/toastergui/static/js/projectpage.js
+++ b/lib/toaster/toastergui/static/js/projectpage.js
@@ -50,70 +50,8 @@ function projectPageInit(ctx) {
     $("#delete-project-modal .project-name").text(prjInfo.name);
   });
 
-  (function notificationRequest(){
-
-    if (urlParams.hasOwnProperty('notify')){
-      switch (urlParams.notify){
-        case 'new-project':
-          $("#project-created-notification").show();
-          break;
-        case 'layer-imported':
-          layerImportedNotification();
-          break;
-        case 'layer-deleted':
-          layerDeletedNotification();
-        default:
-          break;
-      }
-    }
-  })();
-
-  /* Layer deleted notification */
-  function layerDeletedNotification(){
-    var layer = $.cookie("layer-deleted");
-
-    if (!layer)
-      return;
-
-    var message = "You have deleted <strong>1</strong> layer from your ";
-    message += "project: <strong>" + layer + "</strong>";
-
-    libtoaster.showChangeNotification(message);
-
-    $.removeCookie("layer-deleted", { path: "/"});
-  }
-
-
-  /* Layer imported notification */
-  function layerImportedNotification(){
-    var imported = $.cookie("layer-imported-alert");
-    var message = "Layer imported";
-
-    if (!imported)
-      return;
-    else
-      imported = JSON.parse(imported);
-
-    if (imported.deps_added.length === 0) {
-      message = "You have imported <strong><a class=\"alert-link\" href=\""+imported.imported_layer.layerdetailurl+"\">"+imported.imported_layer.name+"</a></strong> and added it to your project.";
-    } else {
-
-      var links = "<a href=\""+imported.imported_layer.layerdetailurl+"\">"+imported.imported_layer.name+"</a>, ";
-
-      imported.deps_added.map (function(item, index){
-        links +='<a href="'+item.layerdetailurl+'">'+item.name+'</a>';
-        /*If we're at the last element we don't want the trailing comma */
-        if (imported.deps_added[index+1] !== undefined)
-          links += ', ';
-      });
-
-      /* Length + 1 here to do deps + the imported layer */
-      message = 'You have imported <strong><a href="'+imported.imported_layer.layerdetailurl+'">'+imported.imported_layer.name+'</a></strong> and added <strong>'+(imported.deps_added.length+1)+'</strong> layers to your project: <strong>'+links+'</strong>';
-    }
-
-    libtoaster.showChangeNotification(message);
-
-    $.removeCookie("layer-imported-alert", { path: "/"});
+  if (urlParams.hasOwnProperty('notify') && urlParams.notify === 'new-project'){
+    $("#project-created-notification").show();
   }
 
   /* Add/Rm layer functionality */
-- 
2.6.6



^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [PATCH 09/10] toaster: customrecipe Add frontend feature to delete custom image recipe
  2016-09-26 10:59 [PATCH 00/10] delete builds, projects, recipes, layers Ed Bartosh
                   ` (7 preceding siblings ...)
  2016-09-26 10:59 ` [PATCH 08/10] toaster: importlayer Convert success import to new notification system Ed Bartosh
@ 2016-09-26 10:59 ` Ed Bartosh
  2016-09-26 10:59 ` [PATCH 10/10] toaster: layerdetails Update implementation of delete imported layer Ed Bartosh
  9 siblings, 0 replies; 12+ messages in thread
From: Ed Bartosh @ 2016-09-26 10:59 UTC (permalink / raw)
  To: bitbake-devel

From: Michael Wood <michael.g.wood@intel.com>

[YOCTO #8132]

Signed-off-by: Michael Wood <michael.g.wood@intel.com>
Signed-off-by: Ed Bartosh <ed.bartosh@linux.intel.com>
---
 lib/toaster/toastergui/static/js/customrecipe.js   | 32 ++++++++++++++++++++
 lib/toaster/toastergui/templates/customrecipe.html | 35 +++++++++++++++++++---
 2 files changed, 63 insertions(+), 4 deletions(-)

diff --git a/lib/toaster/toastergui/static/js/customrecipe.js b/lib/toaster/toastergui/static/js/customrecipe.js
index 505a81c..9ea9602 100644
--- a/lib/toaster/toastergui/static/js/customrecipe.js
+++ b/lib/toaster/toastergui/static/js/customrecipe.js
@@ -281,4 +281,36 @@ function customRecipePageInit(ctx) {
         window.location.replace(libtoaster.ctx.projectBuildsUrl);
     });
   });
+
+  $("#delete-custom-recipe-confirmed").click(function(e){
+    e.preventDefault();
+    libtoaster.disableAjaxLoadingTimer();
+    $(this).find('[data-role="submit-state"]').hide();
+    $(this).find('[data-role="loading-state"]').show();
+    $(this).attr("disabled", "disabled");
+
+    $.ajax({
+        type: 'DELETE',
+        url: ctx.recipe.xhrCustomRecipeUrl,
+        headers: { 'X-CSRFToken' : $.cookie('csrftoken')},
+        success: function (data) {
+          if (data.error !== "ok") {
+            console.warn(data.error);
+          } else {
+            var msg = $('<span>You have deleted <strong>1</strong> custom image: <strong id="deleted-custom-image-name"></strong></span>');
+            msg.find("#deleted-custom-image-name").text(ctx.recipe.name);
+
+            libtoaster.setNotification("custom-image-recipe-deleted",
+                                       msg.html());
+
+            window.location.replace(data.gotoUrl);
+          }
+        },
+        error: function (data) {
+          console.warn(data);
+        }
+    });
+  });
+
+
 }
diff --git a/lib/toaster/toastergui/templates/customrecipe.html b/lib/toaster/toastergui/templates/customrecipe.html
index 8b61fb7..945fc97 100644
--- a/lib/toaster/toastergui/templates/customrecipe.html
+++ b/lib/toaster/toastergui/templates/customrecipe.html
@@ -28,6 +28,7 @@
         includedPackagesCount: {{recipe.includes_set.count}},
         baseRecipeId: {{recipe.base_recipe.pk}},
         xhrPackageListUrl: "{% url 'xhr_customrecipe_packages' recipe.pk %}",
+        xhrCustomRecipeUrl: "{% url 'xhr_customrecipe_id' recipe.pk %}",
       }
     };
 
@@ -39,6 +40,32 @@
     }
   });
 </script>
+
+<!-- Delete recipe modal -->
+<div id="delete-recipe-modal" class="modal fade" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="false">
+  <div class="modal-dialog">
+    <div class="modal-content">
+      <div class="modal-body">
+        <p>Are you sure you want to delete the <strong>{{recipe.name}}</strong>
+         custom image?</p>
+      </div>
+      <div class="modal-footer">
+        <button type="button" class="btn btn-primary" id="delete-custom-recipe-confirmed">
+          <span data-role="submit-state">Delete custom image</span>
+          <span data-role="loading-state" style="display:none">
+            <span class="fa-pulse">
+            <i class="fa-pulse icon-spinner"></i>
+          </span>
+            &nbsp;Deleting custom image...
+          </span>
+        </button>
+        <button type="button" class="btn btn-link" data-dismiss="modal">Cancel</button>
+      </div>
+    </div><!-- /.modal-content -->
+  </div><!-- /.modal-dialog -->
+</div><!-- /.modal -->
+<!-- end delete recipe modal -->
+
 <!-- package dependencies modal -->
 <div id="package-deps-modal" class="modal fade" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="false">
   <div class="modal-dialog">
@@ -229,10 +256,10 @@
         <span class="glyphicon glyphicon-question-sign get-help" title="All custom images have their license set to MIT. This is because the license applies only to the recipe (.bb) file, and not to the image itself. To see which licenses apply to the image you must check the license manifest generated with each build"></i>
         </dd>
       </dl>
-      <!--
-        <i class="icon-trash no-tooltip"></i>
-        <a href="#" class="error" id="delete">Delete custom image</a>
-      -->
+      <i class="icon-trash text-danger"></i>
+      <a href="#delete-recipe-modal" data-target="#delete-recipe-modal" data-toggle="modal" class="text-danger" id="delete-recipe">
+        Delete custom image
+      </a>
     </div>
   </div>
 </div>
-- 
2.6.6



^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [PATCH 10/10] toaster: layerdetails Update implementation of delete imported layer
  2016-09-26 10:59 [PATCH 00/10] delete builds, projects, recipes, layers Ed Bartosh
                   ` (8 preceding siblings ...)
  2016-09-26 10:59 ` [PATCH 09/10] toaster: customrecipe Add frontend feature to delete custom image recipe Ed Bartosh
@ 2016-09-26 10:59 ` Ed Bartosh
  9 siblings, 0 replies; 12+ messages in thread
From: Ed Bartosh @ 2016-09-26 10:59 UTC (permalink / raw)
  To: bitbake-devel

From: Michael Wood <michael.g.wood@intel.com>

Update the implementation of delete an imported layer so that it is
consistent with the other delete messages and wording. Also use the new
libtoaster way of setting a notification that the delete was successful.

Signed-off-by: Michael Wood <michael.g.wood@intel.com>
Signed-off-by: Ed Bartosh <ed.bartosh@linux.intel.com>
---
 lib/toaster/toastergui/api.py                      | 2 +-
 lib/toaster/toastergui/static/js/layerdetails.js   | 7 +++++--
 lib/toaster/toastergui/templates/layerdetails.html | 9 ++++++---
 3 files changed, 12 insertions(+), 6 deletions(-)

diff --git a/lib/toaster/toastergui/api.py b/lib/toaster/toastergui/api.py
index 149abf7..3a05d66 100644
--- a/lib/toaster/toastergui/api.py
+++ b/lib/toaster/toastergui/api.py
@@ -221,7 +221,7 @@ class XhrLayer(View):
 
         return JsonResponse({
             "error": "ok",
-            "redirect": reverse('project', args=(kwargs['pid'],))
+            "gotoUrl": reverse('project', args=(kwargs['pid'],))
         })
 
 
diff --git a/lib/toaster/toastergui/static/js/layerdetails.js b/lib/toaster/toastergui/static/js/layerdetails.js
index 8165bad..4c0d042 100644
--- a/lib/toaster/toastergui/static/js/layerdetails.js
+++ b/lib/toaster/toastergui/static/js/layerdetails.js
@@ -391,7 +391,9 @@ function layerDetailsPageInit (ctx) {
   });
 
   $("#layer-delete-confirmed").click(function(){
-    $.cookie("layer-deleted", ctx.layerVersion.name, { path: '/'});
+
+    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);
 
     $.ajax({
         type: "DELETE",
@@ -401,7 +403,8 @@ function layerDetailsPageInit (ctx) {
           if (data.error != "ok") {
             console.warn(data.error);
           } else {
-            window.location = data.redirect + "?notify=layer-deleted";
+            libtoaster.setNotification("layer-deleted", message.html());
+            window.location.replace(data.gotoUrl);
           }
         },
         error: function(data) {
diff --git a/lib/toaster/toastergui/templates/layerdetails.html b/lib/toaster/toastergui/templates/layerdetails.html
index 0594b55..f1569bd 100644
--- a/lib/toaster/toastergui/templates/layerdetails.html
+++ b/lib/toaster/toastergui/templates/layerdetails.html
@@ -6,14 +6,16 @@
 {% block title %} {{layerversion.layer.name}} - {{project.name}} - Toaster {% endblock %}
 {% block pagecontent %}
 
-<div id="delete-layer-modal" class="modal fade" tabindex="-1" role="dialog">
+<div id="delete-layer-modal" class="modal fade" tabindex="-1" role="dialog"
+    data-keyboard="false" data-backdrop="static">
   <div class="modal-dialog" role="document">
     <div class="modal-content">
       <div class="modal-body">
 		  Are you sure you want to delete the <strong>{{layerversion.layer.name}}</strong> layer?
       </div>
       <div class="modal-footer">
-        <button type="button" id="layer-delete-confirmed" class="btn btn-primary">Delete</button>
+        <button type="button" id="layer-delete-confirmed" class="btn
+            btn-primary">Delete layer</button>
         <button type="button" class="btn btn-default btn-link" data-dismiss="modal">Cancel</button>
       </div>
     </div>
@@ -358,7 +360,8 @@
           {# Only show delete link for imported layers #}
           {% if layerversion.layer_source == layer_source.TYPE_IMPORTED %}
           <i class="icon-trash text-danger"></i>
-          <a href="#delete-layer-modal"  role="button" class="text-danger" data-toggle="modal" data-target="#delete-layer-modal">Delete {{layerversion.layer.name}}</a>
+          <a href="#delete-layer-modal"  role="button" class="text-danger"
+              data-toggle="modal" data-target="#delete-layer-modal">Delete layer</a>
           {% endif %}
         </div>
       </div>
-- 
2.6.6



^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [PATCH 10/10] toaster: layerdetails Update implementation of delete imported layer
  2016-09-22 16:55 [PATCH 00/10] delete builds,projects,recipes,layers Michael Wood
@ 2016-09-22 16:55 ` Michael Wood
  0 siblings, 0 replies; 12+ messages in thread
From: Michael Wood @ 2016-09-22 16:55 UTC (permalink / raw)
  To: toaster

Update the implementation of delete an imported layer so that it is
consistent with the other delete messages and wording. Also use the new
libtoaster way of setting a notification that the delete was successful.

Signed-off-by: Michael Wood <michael.g.wood@intel.com>
---
 bitbake/lib/toaster/toastergui/api.py                      | 2 +-
 bitbake/lib/toaster/toastergui/static/js/layerdetails.js   | 7 +++++--
 bitbake/lib/toaster/toastergui/templates/layerdetails.html | 9 ++++++---
 3 files changed, 12 insertions(+), 6 deletions(-)

diff --git a/bitbake/lib/toaster/toastergui/api.py b/bitbake/lib/toaster/toastergui/api.py
index 149abf7..3a05d66 100644
--- a/bitbake/lib/toaster/toastergui/api.py
+++ b/bitbake/lib/toaster/toastergui/api.py
@@ -221,7 +221,7 @@ class XhrLayer(View):
 
         return JsonResponse({
             "error": "ok",
-            "redirect": reverse('project', args=(kwargs['pid'],))
+            "gotoUrl": reverse('project', args=(kwargs['pid'],))
         })
 
 
diff --git a/bitbake/lib/toaster/toastergui/static/js/layerdetails.js b/bitbake/lib/toaster/toastergui/static/js/layerdetails.js
index 8165bad..4c0d042 100644
--- a/bitbake/lib/toaster/toastergui/static/js/layerdetails.js
+++ b/bitbake/lib/toaster/toastergui/static/js/layerdetails.js
@@ -391,7 +391,9 @@ function layerDetailsPageInit (ctx) {
   });
 
   $("#layer-delete-confirmed").click(function(){
-    $.cookie("layer-deleted", ctx.layerVersion.name, { path: '/'});
+
+    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);
 
     $.ajax({
         type: "DELETE",
@@ -401,7 +403,8 @@ function layerDetailsPageInit (ctx) {
           if (data.error != "ok") {
             console.warn(data.error);
           } else {
-            window.location = data.redirect + "?notify=layer-deleted";
+            libtoaster.setNotification("layer-deleted", message.html());
+            window.location.replace(data.gotoUrl);
           }
         },
         error: function(data) {
diff --git a/bitbake/lib/toaster/toastergui/templates/layerdetails.html b/bitbake/lib/toaster/toastergui/templates/layerdetails.html
index 0594b55..f1569bd 100644
--- a/bitbake/lib/toaster/toastergui/templates/layerdetails.html
+++ b/bitbake/lib/toaster/toastergui/templates/layerdetails.html
@@ -6,14 +6,16 @@
 {% block title %} {{layerversion.layer.name}} - {{project.name}} - Toaster {% endblock %}
 {% block pagecontent %}
 
-<div id="delete-layer-modal" class="modal fade" tabindex="-1" role="dialog">
+<div id="delete-layer-modal" class="modal fade" tabindex="-1" role="dialog"
+    data-keyboard="false" data-backdrop="static">
   <div class="modal-dialog" role="document">
     <div class="modal-content">
       <div class="modal-body">
 		  Are you sure you want to delete the <strong>{{layerversion.layer.name}}</strong> layer?
       </div>
       <div class="modal-footer">
-        <button type="button" id="layer-delete-confirmed" class="btn btn-primary">Delete</button>
+        <button type="button" id="layer-delete-confirmed" class="btn
+            btn-primary">Delete layer</button>
         <button type="button" class="btn btn-default btn-link" data-dismiss="modal">Cancel</button>
       </div>
     </div>
@@ -358,7 +360,8 @@
           {# Only show delete link for imported layers #}
           {% if layerversion.layer_source == layer_source.TYPE_IMPORTED %}
           <i class="icon-trash text-danger"></i>
-          <a href="#delete-layer-modal"  role="button" class="text-danger" data-toggle="modal" data-target="#delete-layer-modal">Delete {{layerversion.layer.name}}</a>
+          <a href="#delete-layer-modal"  role="button" class="text-danger"
+              data-toggle="modal" data-target="#delete-layer-modal">Delete layer</a>
           {% endif %}
         </div>
       </div>
-- 
2.7.4



^ permalink raw reply related	[flat|nested] 12+ messages in thread

end of thread, other threads:[~2016-09-26 11:00 UTC | newest]

Thread overview: 12+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2016-09-26 10:59 [PATCH 00/10] delete builds, projects, recipes, layers Ed Bartosh
2016-09-26 10:59 ` [PATCH 01/10] toaster: Clean up and convert to rest api project edit and get calls Ed Bartosh
2016-09-26 10:59 ` [PATCH 02/10] toaster: move MostRecentBuildsView to its own widget Ed Bartosh
2016-09-26 10:59 ` [PATCH 03/10] toaster: libtoaster Add a global notification set/show mechanism Ed Bartosh
2016-09-26 10:59 ` [PATCH 04/10] toaster: project page Implement front end feature to delete project Ed Bartosh
2016-09-26 10:59 ` [PATCH 05/10] toaster: alerts and modals Avoid modals and alerts overlaying each other Ed Bartosh
2016-09-26 10:59 ` [PATCH 06/10] toaster: Add backend API for deleting a build Ed Bartosh
2016-09-26 10:59 ` [PATCH 07/10] toaster: Add front end controls " Ed Bartosh
2016-09-26 10:59 ` [PATCH 08/10] toaster: importlayer Convert success import to new notification system Ed Bartosh
2016-09-26 10:59 ` [PATCH 09/10] toaster: customrecipe Add frontend feature to delete custom image recipe Ed Bartosh
2016-09-26 10:59 ` [PATCH 10/10] toaster: layerdetails Update implementation of delete imported layer Ed Bartosh
  -- strict thread matches above, loose matches on Subject: below --
2016-09-22 16:55 [PATCH 00/10] delete builds,projects,recipes,layers Michael Wood
2016-09-22 16:55 ` [PATCH 10/10] toaster: layerdetails Update implementation of delete imported layer Michael Wood

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.