From e92769b43b00764082a7cb2207e314b40510ef62 Mon Sep 17 00:00:00 2001 From: Michael Wood Date: Tue, 11 Nov 2014 16:30:22 +0000 Subject: toaster: Add New Build Button feature This adds a quick access dropdown menu feature for running builds on a selected project. [YOCTO #6677] Signed-off-by: Michael Wood Signed-off-by: Alexandru DAMIAN --- lib/toaster/toastergui/static/css/default.css | 4 + lib/toaster/toastergui/static/js/base.js | 125 ++++++++++++++++++++++++++ lib/toaster/toastergui/templates/base.html | 69 ++++++++++++-- lib/toaster/toastergui/urls.py | 3 + lib/toaster/toastergui/views.py | 32 ++++++- 5 files changed, 227 insertions(+), 6 deletions(-) create mode 100644 lib/toaster/toastergui/static/js/base.js diff --git a/lib/toaster/toastergui/static/css/default.css b/lib/toaster/toastergui/static/css/default.css index 8e60fd8b5..6194c97a0 100644 --- a/lib/toaster/toastergui/static/css/default.css +++ b/lib/toaster/toastergui/static/css/default.css @@ -131,6 +131,10 @@ select { width: auto; } /* make tables Chrome-happy (me, not so much) */ #otable { table-layout: fixed; word-wrap: break-word; } +/* styles for the new build button */ +.new-build .btn-primary { padding: 4px 30px; } +#view-all-projects { display: block; } + /* Configuration styles */ .icon-trash { color: #B94A48; font-size: 16px; padding-left: 2px; } .icon-trash:hover { color: #943A38; text-decoration: none; cursor: pointer; } diff --git a/lib/toaster/toastergui/static/js/base.js b/lib/toaster/toastergui/static/js/base.js new file mode 100644 index 000000000..864130def --- /dev/null +++ b/lib/toaster/toastergui/static/js/base.js @@ -0,0 +1,125 @@ + + +function basePageInit (ctx) { + + var newBuildButton = $("#new-build-button"); + /* Hide the button if we're on the project,newproject or importlyaer page */ + if (ctx.currentUrl.search('newproject|project/\\d/$|importlayer/$') > 0){ + newBuildButton.hide(); + return; + } + + + newBuildButton.show().removeAttr("disabled"); + + _checkProjectBuildable() + _setupNewBuildButton(); + + + function _checkProjectBuildable(){ + libtoaster.getProjectInfo(ctx.projectInfoUrl, ctx.projectId, + function(data){ + if (data.machine.name == undefined || data.layers.length == 0) { + /* we can't build anything with out a machine and some layers */ + $("#new-build-button #targets-form").hide(); + $("#new-build-button .alert").show(); + } else { + $("#new-build-button #targets-form").show(); + $("#new-build-button .alert").hide(); + } + }, null); + } + + function _setupNewBuildButton() { + /* Setup New build button */ + var newBuildProjectInput = $("#new-build-button #project-name-input"); + var newBuildTargetBuildBtn = $("#new-build-button #build-button"); + var newBuildTargetInput = $("#new-build-button #build-target-input"); + var newBuildProjectSaveBtn = $("#new-build-button #save-project-button"); + var selectedTarget; + var selectedProject; + + /* If we don't have a current project then present the set project + * form. + */ + if (ctx.projectId == undefined) { + $('#change-project-form').show(); + $('#project .icon-pencil').hide(); + } + + libtoaster.makeTypeahead(newBuildTargetInput, ctx.xhrDataTypeaheadUrl, { type : "targets", project_id: ctx.projectId }, function(item){ + /* successfully selected a target */ + selectedTarget = item; + }); + + + libtoaster.makeTypeahead(newBuildProjectInput, ctx.xhrDataTypeaheadUrl, { type : "projects" }, function(item){ + /* successfully selected a project */ + newBuildProjectSaveBtn.removeAttr("disabled"); + selectedProject = item; + }); + + /* Any typing in the input apart from enter key is going to invalidate + * the value that has been set by selecting a suggestion from the typeahead + */ + newBuildProjectInput.keyup(function(event) { + if (event.keyCode == 13) + return; + newBuildProjectSaveBtn.attr("disabled", "disabled"); + }); + + newBuildTargetInput.keyup(function() { + if ($(this).val().length == 0) + newBuildTargetBuildBtn.attr("disabled", "disabled"); + else + newBuildTargetBuildBtn.removeAttr("disabled"); + }); + + newBuildTargetBuildBtn.click(function() { + if (!newBuildTargetInput.val()) + return; + + /* fire and forget */ + libtoaster.startABuild(ctx.projectBuildUrl, ctx.projectId, selectedTarget.name, null, null); + window.location.replace(ctx.projectPageUrl+ctx.projectId); + }); + + newBuildProjectSaveBtn.click(function() { + ctx.projectId = selectedProject.id + /* Update the typeahead project_id paramater */ + _checkProjectBuildable(); + newBuildTargetInput.data('typeahead').options.xhrParams.project_id = ctx.projectId; + newBuildTargetInput.val(""); + + $("#new-build-button #project a").text(selectedProject.name).attr('href', ctx.projectPageUrl+ctx.projectId); + $("#new-build-button .alert a").attr('href', ctx.projectPageUrl+ctx.projectId); + + + $("#change-project-form").slideUp({ 'complete' : function() { + $("#new-build-button #project").show(); + }}); + }); + + $('#new-build-button #project .icon-pencil').click(function() { + newBuildProjectSaveBtn.attr("disabled", "disabled"); + newBuildProjectInput.val($("#new-build-button #project a").text()); + $(this).parent().hide(); + $("#change-project-form").slideDown(); + }); + + $("#new-build-button #cancel-change-project").click(function() { + $("#change-project-form").hide(function(){ + $('#new-build-button #project').show(); + }); + + newBuildProjectInput.val(""); + newBuildProjectSaveBtn.attr("disabled", "disabled"); + }); + + /* Keep the dropdown open even unless we click outside the dropdown area */ + $(".new-build").click (function(event) { + event.stopPropagation(); + }); + }; + +} diff --git a/lib/toaster/toastergui/templates/base.html b/lib/toaster/toastergui/templates/base.html index 1b9edfd7b..87746bfc8 100644 --- a/lib/toaster/toastergui/templates/base.html +++ b/lib/toaster/toastergui/templates/base.html @@ -8,6 +8,7 @@ + + +{%if MANAGED %} + +{% endif %} @@ -34,15 +53,55 @@ diff --git a/lib/toaster/toastergui/urls.py b/lib/toaster/toastergui/urls.py index bae710309..b60f7614a 100644 --- a/lib/toaster/toastergui/urls.py +++ b/lib/toaster/toastergui/urls.py @@ -80,10 +80,13 @@ urlpatterns = patterns('toastergui.views', url(r'^machines/$', 'machines', name='machines'), url(r'^projects/$', 'projects', name='all-projects'), + + url(r'^project/$', 'project', name='project'), url(r'^project/(?P\d+)/$', 'project', name='project'), url(r'^project/(?P\d+)/configuration$', 'projectconf', name='projectconf'), url(r'^project/(?P\d+)/builds$', 'projectbuilds', name='projectbuilds'), + url(r'^xhr_build/$', 'xhr_build', name='xhr_build'), url(r'^xhr_projectbuild/(?P\d+)/$', 'xhr_projectbuild', name='xhr_projectbuild'), url(r'^xhr_projectinfo/$', 'xhr_projectinfo', name='xhr_projectinfo'), url(r'^xhr_projectedit/(?P\d+)/$', 'xhr_projectedit', name='xhr_projectedit'), diff --git a/lib/toaster/toastergui/views.py b/lib/toaster/toastergui/views.py index 9f214bb67..a0dcf8797 100755 --- a/lib/toaster/toastergui/views.py +++ b/lib/toaster/toastergui/views.py @@ -2015,10 +2015,20 @@ if toastermain.settings.MANAGED: response['Pragma'] = "no-cache" return response + # This is a wrapper for xhr_projectbuild which allows for a project id + # which only becomes known client side. + def xhr_build(request): + if request.POST.has_key("project_id"): + pid = request.POST['project_id'] + return xhr_projectbuild(request, pid) + else: + raise BadParameterException("invalid project id") + def xhr_projectbuild(request, pid): try: if request.method != "POST": raise BadParameterException("invalid method") + request.session['project_id'] = pid prj = Project.objects.get(id = pid) @@ -2057,6 +2067,8 @@ if toastermain.settings.MANAGED: except Exception as e: return HttpResponse(jsonfilter({"error":str(e) + "\n" + traceback.format_exc()}), content_type = "application/json") + # This is a wraper for xhr_projectedit which allows for a project id + # which only becomes known client side def xhr_projectinfo(request): if request.POST.has_key("project_id") == False: raise BadParameterException("invalid project id") @@ -2121,8 +2133,12 @@ if toastermain.settings.MANAGED: def xhr_datatypeahead(request): try: prj = None - if 'project_id' in request.session: + if request.GET.has_key('project_id'): + prj = Project.objects.get(pk = request.GET['project_id']) + elif 'project_id' in request.session: prj = Project.objects.get(pk = request.session['project_id']) + else: + raise Exception("No valid project selected") # returns layers for current project release that are not in the project set if request.GET['type'] == "layers": @@ -2188,6 +2204,14 @@ if toastermain.settings.MANAGED: }), content_type = "application/json") + if request.GET['type'] == "projects": + queryset_all = Project.objects.all() + ret = { "error": "ok", + "list": map (lambda x: {"id":x.pk, "name": x.name}, + queryset_all.filter(name__icontains=request.GET.get('value',''))[:8])} + + return HttpResponse(jsonfilter(ret), content_type = "application/json") + raise Exception("Unknown request! " + request.GET.get('type', "No parameter supplied")) except Exception as e: return HttpResponse(jsonfilter({"error":str(e) + "\n" + traceback.format_exc()}), content_type = "application/json") @@ -2773,6 +2797,12 @@ else: def xhr_projectbuild(request, pid): raise Exception("page not available in interactive mode") + def xhr_build(request, pid): + raise Exception("page not available in interactive mode") + + def xhr_projectinfo(request, pid): + raise Exception("page not available in interactive mode") + def xhr_projectedit(request, pid): raise Exception("page not available in interactive mode") -- cgit 1.2.3-korg