All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 0/4] add link to delete imported layer
@ 2016-07-06 17:22 Elliot Smith
  2016-07-06 17:22 ` [PATCH 1/4] toaster: layerdetails api Fix saving of git revision of a layer Elliot Smith
                   ` (3 more replies)
  0 siblings, 4 replies; 6+ messages in thread
From: Elliot Smith @ 2016-07-06 17:22 UTC (permalink / raw)
  To: bitbake-devel

On the layer details page, add a link which enables an imported layer to be
deleted.

The following changes since commit 7fad9160ca998496af82747fc7fe69d0f5886e94:

  buildinfohelper: ensure task datetimes are timezone-aware (2016-07-06 10:51:46 +0100)

are available in the git repository at:

  git://git.yoctoproject.org/poky-contrib elliot/submit/michaelw/toaster/layerdetails-save-rev-del
  http://git.yoctoproject.org/cgit.cgi/poky-contrib/log/?h=elliot/submit/michaelw/toaster/layerdetails-save-rev-del

Michael Wood (4):
  toaster: layerdetails api Fix saving of git revision of a layer
  toaster: api Add util function for returning the error response
  toaster: add Layer delete front end feature to layerdetails
  toaster: tests Add selenium test for layerdetails page

 .../tests/browser/test_layerdetails_page.py        | 190 +++++++++++++++++++++
 lib/toaster/toastergui/api.py                      | 121 +++++++++++--
 lib/toaster/toastergui/static/js/layerdetails.js   |  20 +++
 lib/toaster/toastergui/static/js/projectpage.js    |  18 ++
 lib/toaster/toastergui/templates/layerdetails.html |  20 ++-
 lib/toaster/toastergui/urls.py                     |   5 +-
 lib/toaster/toastergui/views.py                    |  43 -----
 7 files changed, 362 insertions(+), 55 deletions(-)
 create mode 100644 bitbake/lib/toaster/tests/browser/test_layerdetails_page.py

--
2.7.4



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

* [PATCH 1/4] toaster: layerdetails api Fix saving of git revision of a layer
  2016-07-06 17:22 [PATCH 0/4] add link to delete imported layer Elliot Smith
@ 2016-07-06 17:22 ` Elliot Smith
  2016-07-06 17:22 ` [PATCH 2/4] toaster: api Add util function for returning the error response Elliot Smith
                   ` (2 subsequent siblings)
  3 siblings, 0 replies; 6+ messages in thread
From: Elliot Smith @ 2016-07-06 17:22 UTC (permalink / raw)
  To: bitbake-devel

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

Update, clean up and move the api for updating a layerversion from the
views to api. Also update the layerdetails page to include the
layerversion id in the url getter.

[YOCTO #8952]

Signed-off-by: Michael Wood <michael.g.wood@intel.com>
Signed-off-by: Elliot Smith <elliot.smith@intel.com>
---
 lib/toaster/toastergui/api.py                      | 99 +++++++++++++++++++++-
 lib/toaster/toastergui/templates/layerdetails.html |  2 +-
 lib/toaster/toastergui/urls.py                     |  5 +-
 lib/toaster/toastergui/views.py                    | 43 ----------
 4 files changed, 103 insertions(+), 46 deletions(-)

diff --git a/lib/toaster/toastergui/api.py b/lib/toaster/toastergui/api.py
index 961b594..a024748 100644
--- a/lib/toaster/toastergui/api.py
+++ b/lib/toaster/toastergui/api.py
@@ -20,11 +20,14 @@
 # Temporary home for the UI's misc API
 import re
 
-from orm.models import Project, ProjectTarget, Build
+from orm.models import Project, ProjectTarget, Build, Layer_Version
+from orm.models import LayerVersionDependency, LayerSource, ProjectLayer
 from bldcontrol.models import BuildRequest
 from bldcontrol import bbcontroller
 from django.http import HttpResponse, JsonResponse
 from django.views.generic import View
+from django.core.urlresolvers import reverse
+
 
 
 class XhrBuildRequest(View):
@@ -109,3 +112,97 @@ class XhrBuildRequest(View):
         response = HttpResponse()
         response.status_code = 500
         return response
+
+
+class XhrLayer(View):
+    """ Get and Update Layer information """
+
+    def post(self, request, *args, **kwargs):
+        """
+          Update a layer
+
+          Entry point: /xhr_layer/<layerversion_id>
+          Method: POST
+
+          Args:
+              vcs_url, dirpath, commit, up_branch, summary, description
+
+              add_dep = append a layerversion_id as a dependency
+              rm_dep = remove a layerversion_id as a depedency
+          Returns:
+              {"error": "ok"}
+            or
+              {"error": <error message>}
+        """
+
+        def error_response(error):
+            return JsonResponse({"error": error})
+
+        try:
+            # We currently only allow Imported layers to be edited
+            layer_version = Layer_Version.objects.get(
+                id=kwargs['layerversion_id'],
+                project=kwargs['pid'],
+                layer_source__sourcetype=LayerSource.TYPE_IMPORTED)
+
+        except Layer_Version.DoesNotExist:
+            return error_response("Cannot find imported layer to update")
+
+        if "vcs_url" in request.POST:
+            layer_version.layer.vcs_url = request.POST["vcs_url"]
+        if "dirpath" in request.POST:
+            layer_version.dirpath = request.POST["dirpath"]
+        if "commit" in request.POST:
+            layer_version.commit = request.POST["commit"]
+            layer_version.branch = request.POST["commit"]
+        if "up_branch" in request.POST:
+            layer_version.up_branch_id = int(request.POST["up_branch"])
+        if "summary" in request.POST:
+            layer_version.layer.summary = request.POST["summary"]
+        if "description" in request.POST:
+            layer_version.layer.description = request.POST["description"]
+
+        if "add_dep" in request.POST:
+            lvd = LayerVersionDependency(
+                layer_version=layer_version,
+                depends_on_id=request.POST["add_dep"])
+            lvd.save()
+
+        if "rm_dep" in request.POST:
+            rm_dep = LayerVersionDependency.objects.get(
+                layer_version=layer_version,
+                depends_on_id=request.POST["rm_dep"])
+            rm_dep.delete()
+
+        try:
+            layer_version.layer.save()
+            layer_version.save()
+        except Exception as e:
+            return error_response("Could not update layer version entry: %s"
+                                  % e)
+
+        return JsonResponse({"error": "ok"})
+
+    def delete(self, request, *args, **kwargs):
+        try:
+            # We currently only allow Imported layers to be deleted
+            layer_version = Layer_Version.objects.get(
+                id=kwargs['layerversion_id'],
+                project=kwargs['pid'],
+                layer_source__sourcetype=LayerSource.TYPE_IMPORTED)
+        except Layer_Version.DoesNotExist:
+            return error_response("Cannot find imported layer to delete")
+
+        try:
+            ProjectLayer.objects.get(project=kwargs['pid'],
+                                     layercommit=layer_version).delete()
+        except ProjectLayer.DoesNotExist:
+            pass
+
+        layer_version.layer.delete()
+        layer_version.delete()
+
+        return JsonResponse({
+            "error": "ok",
+            "redirect": reverse('project', args=(kwargs['pid'],))
+        })
diff --git a/lib/toaster/toastergui/templates/layerdetails.html b/lib/toaster/toastergui/templates/layerdetails.html
index 0f0b2b4..143ec25 100644
--- a/lib/toaster/toastergui/templates/layerdetails.html
+++ b/lib/toaster/toastergui/templates/layerdetails.html
@@ -44,7 +44,7 @@
 
     $(document).ready(function(){
       var ctx = {
-        xhrUpdateLayerUrl : "{% url 'xhr_updatelayer' %}",
+        xhrUpdateLayerUrl : "{% url 'xhr_layer' layerversion.pk %}",
         layerVersion : {
           name : "{{layerversion.layer.name}}",
           id : {{layerversion.id}},
diff --git a/lib/toaster/toastergui/urls.py b/lib/toaster/toastergui/urls.py
index 9510a38..15b1063 100644
--- a/lib/toaster/toastergui/urls.py
+++ b/lib/toaster/toastergui/urls.py
@@ -190,7 +190,10 @@ urlpatterns = patterns('toastergui.views',
             name='xhr_configvaredit'),
 
         url(r'^xhr_importlayer/$', 'xhr_importlayer', name='xhr_importlayer'),
-        url(r'^xhr_updatelayer/$', 'xhr_updatelayer', name='xhr_updatelayer'),
+
+        url(r'^xhr_layer/(?P<layerversion_id>\d+)$',
+            api.XhrLayer.as_view(),
+            name='xhr_layer'),
 
         # JS Unit tests
         url(r'^js-unit-tests/$', 'jsunittests', name='js-unit-tests'),
diff --git a/lib/toaster/toastergui/views.py b/lib/toaster/toastergui/views.py
index 2db68bd..ad85faf 100755
--- a/lib/toaster/toastergui/views.py
+++ b/lib/toaster/toastergui/views.py
@@ -1739,49 +1739,6 @@ if True:
 
         return HttpResponse(jsonfilter(json_response), content_type = "application/json")
 
-    def xhr_updatelayer(request):
-
-        def error_response(error):
-            return HttpResponse(jsonfilter({"error": error}), content_type = "application/json")
-
-        if "layer_version_id" not in request.POST:
-            return error_response("Please specify a layer version id")
-        try:
-            layer_version_id = request.POST["layer_version_id"]
-            layer_version = Layer_Version.objects.get(id=layer_version_id)
-        except Layer_Version.DoesNotExist:
-            return error_response("Cannot find layer to update")
-
-
-        if "vcs_url" in request.POST:
-            layer_version.layer.vcs_url = request.POST["vcs_url"]
-        if "dirpath" in request.POST:
-            layer_version.dirpath = request.POST["dirpath"]
-        if "commit" in request.POST:
-            layer_version.commit = request.POST["commit"]
-        if "up_branch" in request.POST:
-            layer_version.up_branch_id = int(request.POST["up_branch"])
-
-        if "add_dep" in request.POST:
-            lvd = LayerVersionDependency(layer_version=layer_version, depends_on_id=request.POST["add_dep"])
-            lvd.save()
-
-        if "rm_dep" in request.POST:
-            rm_dep = LayerVersionDependency.objects.get(layer_version=layer_version, depends_on_id=request.POST["rm_dep"])
-            rm_dep.delete()
-
-        if "summary" in request.POST:
-            layer_version.layer.summary = request.POST["summary"]
-        if "description" in request.POST:
-            layer_version.layer.description = request.POST["description"]
-
-        try:
-            layer_version.layer.save()
-            layer_version.save()
-        except Exception as e:
-            return error_response("Could not update layer version entry: %s" % e)
-
-        return HttpResponse(jsonfilter({"error": "ok",}), content_type = "application/json")
 
     @xhr_response
     def xhr_customrecipe(request):
-- 
2.7.4



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

* [PATCH 2/4] toaster: api Add util function for returning the error response
  2016-07-06 17:22 [PATCH 0/4] add link to delete imported layer Elliot Smith
  2016-07-06 17:22 ` [PATCH 1/4] toaster: layerdetails api Fix saving of git revision of a layer Elliot Smith
@ 2016-07-06 17:22 ` Elliot Smith
  2016-07-06 17:22 ` [PATCH 3/4] toaster: add Layer delete front end feature to layerdetails Elliot Smith
  2016-07-06 17:22 ` [PATCH 4/4] toaster: tests Add selenium test for layerdetails page Elliot Smith
  3 siblings, 0 replies; 6+ messages in thread
From: Elliot Smith @ 2016-07-06 17:22 UTC (permalink / raw)
  To: bitbake-devel

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

Also clean up flake8 warnings in XhrBuildRequest

Signed-off-by: Michael Wood <michael.g.wood@intel.com>
Signed-off-by: Elliot Smith <elliot.smith@intel.com>
---
 lib/toaster/toastergui/api.py | 28 ++++++++++++++++------------
 1 file changed, 16 insertions(+), 12 deletions(-)

diff --git a/lib/toaster/toastergui/api.py b/lib/toaster/toastergui/api.py
index a024748..112ce58 100644
--- a/lib/toaster/toastergui/api.py
+++ b/lib/toaster/toastergui/api.py
@@ -29,6 +29,9 @@ from django.views.generic import View
 from django.core.urlresolvers import reverse
 
 
+def error_response(error):
+    return JsonResponse({"error": error})
+
 
 class XhrBuildRequest(View):
 
@@ -81,20 +84,24 @@ class XhrBuildRequest(View):
                     br.save()
 
                 except BuildRequest.DoesNotExist:
-                    return JsonResponse({'error':'No such build id %s' % i})
+                    return error_response('No such build id %s' % i)
 
-            return JsonResponse({'error': 'ok'})
+            return error_response('ok')
 
         if 'buildDelete' in request.POST:
             for i in request.POST['buildDelete'].strip().split(" "):
                 try:
-                    BuildRequest.objects.select_for_update().get(project = project, pk = i, state__lte = BuildRequest.REQ_DELETED).delete()
+                    BuildRequest.objects.select_for_update().get(
+                        project=project,
+                        pk=i,
+                        state__lte=BuildRequest.REQ_DELETED).delete()
+
                 except BuildRequest.DoesNotExist:
                     pass
-            return JsonResponse({'error': 'ok' })
+            return error_response("ok")
 
         if 'targets' in request.POST:
-            ProjectTarget.objects.filter(project = project).delete()
+            ProjectTarget.objects.filter(project=project).delete()
             s = str(request.POST['targets'])
             for t in re.sub(r'[;%|"]', '', s).split(" "):
                 if ":" in t:
@@ -102,12 +109,12 @@ class XhrBuildRequest(View):
                 else:
                     target = t
                     task = ""
-                ProjectTarget.objects.create(project = project,
-                                             target = target,
-                                             task = task)
+                ProjectTarget.objects.create(project=project,
+                                             target=target,
+                                             task=task)
             project.schedule_build()
 
-            return JsonResponse({'error': 'ok' })
+            return error_response('ok')
 
         response = HttpResponse()
         response.status_code = 500
@@ -135,9 +142,6 @@ class XhrLayer(View):
               {"error": <error message>}
         """
 
-        def error_response(error):
-            return JsonResponse({"error": error})
-
         try:
             # We currently only allow Imported layers to be edited
             layer_version = Layer_Version.objects.get(
-- 
2.7.4



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

* [PATCH 3/4] toaster: add Layer delete front end feature to layerdetails
  2016-07-06 17:22 [PATCH 0/4] add link to delete imported layer Elliot Smith
  2016-07-06 17:22 ` [PATCH 1/4] toaster: layerdetails api Fix saving of git revision of a layer Elliot Smith
  2016-07-06 17:22 ` [PATCH 2/4] toaster: api Add util function for returning the error response Elliot Smith
@ 2016-07-06 17:22 ` Elliot Smith
  2016-07-06 17:22 ` [PATCH 4/4] toaster: tests Add selenium test for layerdetails page Elliot Smith
  3 siblings, 0 replies; 6+ messages in thread
From: Elliot Smith @ 2016-07-06 17:22 UTC (permalink / raw)
  To: bitbake-devel

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

Add the front end feature to delete a layer from the layer details page.

[YOCO #9184]

Signed-off-by: Michael Wood <michael.g.wood@intel.com>
Signed-off-by: Elliot Smith <elliot.smith@intel.com>
---
 lib/toaster/toastergui/static/js/layerdetails.js   | 20 ++++++++++++++++++++
 lib/toaster/toastergui/static/js/projectpage.js    | 18 ++++++++++++++++++
 lib/toaster/toastergui/templates/layerdetails.html | 20 +++++++++++++++++++-
 lib/toaster/toastergui/urls.py                     |  2 +-
 4 files changed, 58 insertions(+), 2 deletions(-)

diff --git a/lib/toaster/toastergui/static/js/layerdetails.js b/lib/toaster/toastergui/static/js/layerdetails.js
index a56087b..683486e 100644
--- a/lib/toaster/toastergui/static/js/layerdetails.js
+++ b/lib/toaster/toastergui/static/js/layerdetails.js
@@ -397,6 +397,26 @@ function layerDetailsPageInit (ctx) {
     $(this).parents("form").submit();
   });
 
+  $("#layer-delete-confirmed").click(function(){
+    $.cookie("layer-deleted", ctx.layerVersion.name, { path: '/'});
+
+    $.ajax({
+        type: "DELETE",
+        url: ctx.xhrUpdateLayerUrl,
+        headers: { 'X-CSRFToken' : $.cookie('csrftoken')},
+        success: function(data) {
+          if (data.error != "ok") {
+            console.warn(data.error);
+          } else {
+            window.location = data.redirect + "?notify=layer-deleted";
+          }
+        },
+        error: function(data) {
+          console.warn("Call failed");
+          console.warn(data);
+        }
+    });
+  });
 
   layerDepsList.find(".glyphicon-trash").click(layerDepRemoveClick);
   layerDepsList.find("a").tooltip();
diff --git a/lib/toaster/toastergui/static/js/projectpage.js b/lib/toaster/toastergui/static/js/projectpage.js
index 6d92490..df79849 100644
--- a/lib/toaster/toastergui/static/js/projectpage.js
+++ b/lib/toaster/toastergui/static/js/projectpage.js
@@ -58,12 +58,30 @@ function projectPageInit(ctx) {
         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");
diff --git a/lib/toaster/toastergui/templates/layerdetails.html b/lib/toaster/toastergui/templates/layerdetails.html
index 143ec25..4b51d1a 100644
--- a/lib/toaster/toastergui/templates/layerdetails.html
+++ b/lib/toaster/toastergui/templates/layerdetails.html
@@ -6,6 +6,20 @@
 {% block title %} {{layerversion.layer.name}} - {{project.name}} - Toaster {% endblock %}
 {% block pagecontent %}
 
+<div id="delete-layer-modal" class="modal fade" tabindex="-1" role="dialog">
+  <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" class="btn btn-default btn-link" data-dismiss="modal">Cancel</button>
+      </div>
+    </div>
+  </div>
+</div>
+
 <div class="row">
   <div class="col-md-12">
     <ul class="breadcrumb">
@@ -44,7 +58,7 @@
 
     $(document).ready(function(){
       var ctx = {
-        xhrUpdateLayerUrl : "{% url 'xhr_layer' layerversion.pk %}",
+        xhrUpdateLayerUrl : "{% url 'xhr_layer' project.id layerversion.pk %}",
         layerVersion : {
           name : "{{layerversion.layer.name}}",
           id : {{layerversion.id}},
@@ -271,6 +285,10 @@
             </dd>
             {% endif %}
           </dl>
+          {% if layerversion.layer_source_id and layerversion.layer_source.sourcetype == layerversion.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>
+          {% endif %}
         </div>
       </div>
     </div>
diff --git a/lib/toaster/toastergui/urls.py b/lib/toaster/toastergui/urls.py
index 15b1063..1c0ccbb 100644
--- a/lib/toaster/toastergui/urls.py
+++ b/lib/toaster/toastergui/urls.py
@@ -191,7 +191,7 @@ urlpatterns = patterns('toastergui.views',
 
         url(r'^xhr_importlayer/$', 'xhr_importlayer', name='xhr_importlayer'),
 
-        url(r'^xhr_layer/(?P<layerversion_id>\d+)$',
+        url(r'^xhr_layer/(?P<pid>\d+)/(?P<layerversion_id>\d+)$',
             api.XhrLayer.as_view(),
             name='xhr_layer'),
 
-- 
2.7.4



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

* [PATCH 4/4] toaster: tests Add selenium test for layerdetails page
  2016-07-06 17:22 [PATCH 0/4] add link to delete imported layer Elliot Smith
                   ` (2 preceding siblings ...)
  2016-07-06 17:22 ` [PATCH 3/4] toaster: add Layer delete front end feature to layerdetails Elliot Smith
@ 2016-07-06 17:22 ` Elliot Smith
  3 siblings, 0 replies; 6+ messages in thread
From: Elliot Smith @ 2016-07-06 17:22 UTC (permalink / raw)
  To: bitbake-devel

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

This tests:
 - Adding remove layer from project
 - Deleting layer
 - Editing layer fields

Signed-off-by: Michael Wood <michael.g.wood@intel.com>
Signed-off-by: Elliot Smith <elliot.smith@intel.com>
---
 .../tests/browser/test_layerdetails_page.py        | 190 +++++++++++++++++++++
 1 file changed, 190 insertions(+)
 create mode 100644 bitbake/lib/toaster/tests/browser/test_layerdetails_page.py

diff --git a/lib/toaster/tests/browser/test_layerdetails_page.py b/lib/toaster/tests/browser/test_layerdetails_page.py
new file mode 100644
index 0000000..fb1007f
--- /dev/null
+++ b/lib/toaster/tests/browser/test_layerdetails_page.py
@@ -0,0 +1,190 @@
+#! /usr/bin/env python
+# ex:ts=4:sw=4:sts=4:et
+# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
+#
+# BitBake Toaster Implementation
+#
+# Copyright (C) 2013-2016 Intel Corporation
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+from django.core.urlresolvers import reverse
+from tests.browser.selenium_helpers import SeleniumTestCase
+
+from orm.models import Layer, Layer_Version, Project, LayerSource, Release
+from orm.models import BitbakeVersion
+
+from selenium.webdriver.support import expected_conditions as EC
+from selenium.webdriver.support.ui import WebDriverWait
+from selenium.webdriver.common.by import By
+
+
+class TestLayerDetailsPage(SeleniumTestCase):
+    """ Test layerdetails page works correctly """
+
+    def __init__(self, *args, **kwargs):
+        super(TestLayerDetailsPage, self).__init__(*args, **kwargs)
+
+        self.initial_values = None
+        self.url = None
+        self.imported_layer_version = None
+
+    def setUp(self):
+        release = Release.objects.create(
+            name='baz',
+            bitbake_version=BitbakeVersion.objects.create(name='v1')
+        )
+
+        # project to add new custom images to
+        self.project = Project.objects.create(name='foo', release=release)
+
+        layer_source = LayerSource.objects.create(
+            sourcetype=LayerSource.TYPE_IMPORTED)
+
+        name = "meta-imported"
+        vcs_url = "git://example.com/meta-imported"
+        subdir = "/layer"
+        gitrev = "d33d"
+        summary = "A imported layer"
+        description = "This was imported"
+
+        imported_layer = Layer.objects.create(name=name,
+                                              vcs_url=vcs_url,
+                                              summary=summary,
+                                              description=description)
+
+        self.imported_layer_version = Layer_Version.objects.create(
+            layer=imported_layer,
+            layer_source=layer_source,
+            branch=gitrev,
+            commit=gitrev,
+            dirpath=subdir,
+            project=self.project)
+
+        self.initial_values = [name, vcs_url, subdir, gitrev, summary,
+                               description]
+        self.url = reverse('layerdetails',
+                           args=(self.project.pk,
+                                 self.imported_layer_version.pk))
+
+    def test_edit_layerdetails(self):
+        """ Edit all the editable fields for the layer refresh the page and
+        check that the new values exist"""
+
+        self.get(self.url)
+
+        self.click("#add-remove-layer-btn")
+
+        # Open every edit box
+        for btn in self.find_all("dd .glyphicon-edit"):
+            btn.click()
+
+        self.wait_until_visible("dd input")
+
+        # Edit each value
+        for inputs in self.find_all("dd input[type=text]") + \
+                self.find_all("dd textarea"):
+            # ignore the tt inputs (twitter typeahead input)
+            if "tt-" in inputs.get_attribute("class"):
+                continue
+
+            value = inputs.get_attribute("value")
+
+            self.assertTrue(value in self.initial_values,
+                            "Expecting any of \"%s\"but got \"%s\"" %
+                            (self.initial_values, value))
+
+            inputs.send_keys("-edited")
+
+        for save_btn in self.find_all(".change-btn"):
+            save_btn.click()
+
+        # Refresh the page to see if the new values are returned
+        self.get(self.url)
+
+        new_values = ["%s-edited" % old_val
+                      for old_val in self.initial_values]
+
+        for inputs in self.find_all("dd input[type=text]") + \
+                self.find_all("dd textarea"):
+            # ignore the tt inputs (twitter typeahead input)
+            if "tt-" in inputs.get_attribute("class"):
+                continue
+
+            value = inputs.get_attribute("value")
+
+            self.assertTrue(value in new_values,
+                            "Expecting any of \"%s\"but got \"%s\"" %
+                            (self.initial_values, value))
+
+    def test_delete_layer(self):
+        """ Delete the layer """
+
+        self.get(self.url)
+
+        # Wait for the tables to load to avoid a race condition where the
+        # toaster tables have made an async request. If the layer is deleted
+        # before the request finishes it will cause an exception and fail this
+        # test.
+        wait = WebDriverWait(self.driver, 30)
+
+        wait.until(EC.text_to_be_present_in_element(
+            (By.CLASS_NAME,
+             "table-count-recipestable"), "0"))
+
+        wait.until(EC.text_to_be_present_in_element(
+            (By.CLASS_NAME,
+             "table-count-machinestable"), "0"))
+
+        self.click('a[data-target="#delete-layer-modal"]')
+        self.wait_until_visible("#delete-layer-modal")
+        self.click("#layer-delete-confirmed")
+
+        notification = self.wait_until_visible("#change-notification-msg")
+        expected_text = "You have deleted 1 layer from your project: %s" % \
+            self.imported_layer_version.layer.name
+
+        self.assertTrue(expected_text in notification.text,
+                        "Expected notification text \"%s\" not found instead"
+                        "it was \"%s\"" %
+                        (expected_text, notification.text))
+
+    def test_addrm_to_project(self):
+        self.get(self.url)
+
+        # Add the layer
+        self.click("#add-remove-layer-btn")
+
+        notification = self.wait_until_visible("#change-notification-msg")
+
+        expected_text = "You have added 1 layer to your project: %s" % \
+            self.imported_layer_version.layer.name
+
+        self.assertTrue(expected_text in notification.text,
+                        "Expected notification text %s not found was "
+                        " \"%s\" instead" %
+                        (expected_text, notification.text))
+
+        # Remove the layer
+        self.click("#add-remove-layer-btn")
+
+        notification = self.wait_until_visible("#change-notification-msg")
+
+        expected_text = "You have removed 1 layer from your project: %s" % \
+            self.imported_layer_version.layer.name
+
+        self.assertTrue(expected_text in notification.text,
+                        "Expected notification text %s not found was "
+                        " \"%s\" instead" %
+                        (expected_text, notification.text))
-- 
2.7.4



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

* [PATCH 4/4] toaster: tests Add selenium test for layerdetails page
  2016-07-04 20:56 [PATCH 0/4] Add delete layer and fix layer rev saving Michael Wood
@ 2016-07-04 20:56 ` Michael Wood
  0 siblings, 0 replies; 6+ messages in thread
From: Michael Wood @ 2016-07-04 20:56 UTC (permalink / raw)
  To: toaster

This tests:
 - Adding remove layer from project
 - Deleting layer
 - Editing layer fields

Signed-off-by: Michael Wood <michael.g.wood@intel.com>
---
 .../tests/browser/test_layerdetails_page.py        | 190 +++++++++++++++++++++
 1 file changed, 190 insertions(+)
 create mode 100644 bitbake/lib/toaster/tests/browser/test_layerdetails_page.py

diff --git a/bitbake/lib/toaster/tests/browser/test_layerdetails_page.py b/bitbake/lib/toaster/tests/browser/test_layerdetails_page.py
new file mode 100644
index 0000000..fb1007f
--- /dev/null
+++ b/bitbake/lib/toaster/tests/browser/test_layerdetails_page.py
@@ -0,0 +1,190 @@
+#! /usr/bin/env python
+# ex:ts=4:sw=4:sts=4:et
+# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
+#
+# BitBake Toaster Implementation
+#
+# Copyright (C) 2013-2016 Intel Corporation
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+from django.core.urlresolvers import reverse
+from tests.browser.selenium_helpers import SeleniumTestCase
+
+from orm.models import Layer, Layer_Version, Project, LayerSource, Release
+from orm.models import BitbakeVersion
+
+from selenium.webdriver.support import expected_conditions as EC
+from selenium.webdriver.support.ui import WebDriverWait
+from selenium.webdriver.common.by import By
+
+
+class TestLayerDetailsPage(SeleniumTestCase):
+    """ Test layerdetails page works correctly """
+
+    def __init__(self, *args, **kwargs):
+        super(TestLayerDetailsPage, self).__init__(*args, **kwargs)
+
+        self.initial_values = None
+        self.url = None
+        self.imported_layer_version = None
+
+    def setUp(self):
+        release = Release.objects.create(
+            name='baz',
+            bitbake_version=BitbakeVersion.objects.create(name='v1')
+        )
+
+        # project to add new custom images to
+        self.project = Project.objects.create(name='foo', release=release)
+
+        layer_source = LayerSource.objects.create(
+            sourcetype=LayerSource.TYPE_IMPORTED)
+
+        name = "meta-imported"
+        vcs_url = "git://example.com/meta-imported"
+        subdir = "/layer"
+        gitrev = "d33d"
+        summary = "A imported layer"
+        description = "This was imported"
+
+        imported_layer = Layer.objects.create(name=name,
+                                              vcs_url=vcs_url,
+                                              summary=summary,
+                                              description=description)
+
+        self.imported_layer_version = Layer_Version.objects.create(
+            layer=imported_layer,
+            layer_source=layer_source,
+            branch=gitrev,
+            commit=gitrev,
+            dirpath=subdir,
+            project=self.project)
+
+        self.initial_values = [name, vcs_url, subdir, gitrev, summary,
+                               description]
+        self.url = reverse('layerdetails',
+                           args=(self.project.pk,
+                                 self.imported_layer_version.pk))
+
+    def test_edit_layerdetails(self):
+        """ Edit all the editable fields for the layer refresh the page and
+        check that the new values exist"""
+
+        self.get(self.url)
+
+        self.click("#add-remove-layer-btn")
+
+        # Open every edit box
+        for btn in self.find_all("dd .glyphicon-edit"):
+            btn.click()
+
+        self.wait_until_visible("dd input")
+
+        # Edit each value
+        for inputs in self.find_all("dd input[type=text]") + \
+                self.find_all("dd textarea"):
+            # ignore the tt inputs (twitter typeahead input)
+            if "tt-" in inputs.get_attribute("class"):
+                continue
+
+            value = inputs.get_attribute("value")
+
+            self.assertTrue(value in self.initial_values,
+                            "Expecting any of \"%s\"but got \"%s\"" %
+                            (self.initial_values, value))
+
+            inputs.send_keys("-edited")
+
+        for save_btn in self.find_all(".change-btn"):
+            save_btn.click()
+
+        # Refresh the page to see if the new values are returned
+        self.get(self.url)
+
+        new_values = ["%s-edited" % old_val
+                      for old_val in self.initial_values]
+
+        for inputs in self.find_all("dd input[type=text]") + \
+                self.find_all("dd textarea"):
+            # ignore the tt inputs (twitter typeahead input)
+            if "tt-" in inputs.get_attribute("class"):
+                continue
+
+            value = inputs.get_attribute("value")
+
+            self.assertTrue(value in new_values,
+                            "Expecting any of \"%s\"but got \"%s\"" %
+                            (self.initial_values, value))
+
+    def test_delete_layer(self):
+        """ Delete the layer """
+
+        self.get(self.url)
+
+        # Wait for the tables to load to avoid a race condition where the
+        # toaster tables have made an async request. If the layer is deleted
+        # before the request finishes it will cause an exception and fail this
+        # test.
+        wait = WebDriverWait(self.driver, 30)
+
+        wait.until(EC.text_to_be_present_in_element(
+            (By.CLASS_NAME,
+             "table-count-recipestable"), "0"))
+
+        wait.until(EC.text_to_be_present_in_element(
+            (By.CLASS_NAME,
+             "table-count-machinestable"), "0"))
+
+        self.click('a[data-target="#delete-layer-modal"]')
+        self.wait_until_visible("#delete-layer-modal")
+        self.click("#layer-delete-confirmed")
+
+        notification = self.wait_until_visible("#change-notification-msg")
+        expected_text = "You have deleted 1 layer from your project: %s" % \
+            self.imported_layer_version.layer.name
+
+        self.assertTrue(expected_text in notification.text,
+                        "Expected notification text \"%s\" not found instead"
+                        "it was \"%s\"" %
+                        (expected_text, notification.text))
+
+    def test_addrm_to_project(self):
+        self.get(self.url)
+
+        # Add the layer
+        self.click("#add-remove-layer-btn")
+
+        notification = self.wait_until_visible("#change-notification-msg")
+
+        expected_text = "You have added 1 layer to your project: %s" % \
+            self.imported_layer_version.layer.name
+
+        self.assertTrue(expected_text in notification.text,
+                        "Expected notification text %s not found was "
+                        " \"%s\" instead" %
+                        (expected_text, notification.text))
+
+        # Remove the layer
+        self.click("#add-remove-layer-btn")
+
+        notification = self.wait_until_visible("#change-notification-msg")
+
+        expected_text = "You have removed 1 layer from your project: %s" % \
+            self.imported_layer_version.layer.name
+
+        self.assertTrue(expected_text in notification.text,
+                        "Expected notification text %s not found was "
+                        " \"%s\" instead" %
+                        (expected_text, notification.text))
-- 
2.7.4



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

end of thread, other threads:[~2016-07-06 17:31 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2016-07-06 17:22 [PATCH 0/4] add link to delete imported layer Elliot Smith
2016-07-06 17:22 ` [PATCH 1/4] toaster: layerdetails api Fix saving of git revision of a layer Elliot Smith
2016-07-06 17:22 ` [PATCH 2/4] toaster: api Add util function for returning the error response Elliot Smith
2016-07-06 17:22 ` [PATCH 3/4] toaster: add Layer delete front end feature to layerdetails Elliot Smith
2016-07-06 17:22 ` [PATCH 4/4] toaster: tests Add selenium test for layerdetails page Elliot Smith
  -- strict thread matches above, loose matches on Subject: below --
2016-07-04 20:56 [PATCH 0/4] Add delete layer and fix layer rev saving Michael Wood
2016-07-04 20:56 ` [PATCH 4/4] toaster: tests Add selenium test for layerdetails page 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.