Toaster patchset with new features and bugfixes
v2 cover Toaster patchset with new features and bugfixes
From: Alexandru DAMIAN <[email protected]>
Hello,
This is the v2 patchset with new features and bug fixes. Patches have been individually reviewed on the toaster mailing list.
Among the features submitted, we have the import layers pages, the download artifacts patchset, a new command line option for bitbake to write event log files, and corresponding command to import log files into toaster, as well as new debug options.
We also have fixes around the layout and refactoring to make code cleaner.
Can you please pull at your convenience ?
Thanks,
Alex
The following changes since commit 9e27bb2869e8ec6781d2f68d0585ebbf9ca6f3d8:
bitbake-user-manual-metadata.xml: Updated do_package_write example (2014-12-09 22:25:11 +0000)
are available in the git repository at:
git://git.yoctoproject.org/poky-contrib adamian/20141211-submission-1
http://git.yoctoproject.org/cgit.cgi/poky-contrib/log/?h=adamian/20141211-submission-1
Alexandru DAMIAN (6):
add build artifacts table and other improvements
toasterui: add extra debug and development infos add option to write offline event log file
toastergui: implement UI changes to allow file download add POST endpoint for uploading eventlog files
toasterui: refactor project layer finding logic
Belen Barros Pena (5):
toaster: base Only show change project icon when > one project
toaster: Initialise the 'change' icon tooltips
toaster: tweaking the import layers form
toaster: Formatting changes to error messages
toaster: Change the full stop to a colon
Michael Wood (7):
toaster: libtoaster Add a error handler to GET in makeTypehead
toaster: libtoaster Add editProject and getLayerDepsForProject
toaster: Add import layer feature.
toaster: Importlayer add notify exactly which layers changed
toaster: importlayer Avoid namespace clash with layer name
toaster: projectapp Add links to the imported layers notify
toaster: importlayer Tidy up the page initialisation
bin/bitbake | 12 +- bin/toaster-eventreplay | 179 +++++++++++ lib/bb/cooker.py | 75 ++++- lib/bb/cookerdata.py | 1 + lib/bb/ui/buildinfohelper.py | 91 ++++-- lib/bb/ui/toasterui.py | 25 +- lib/toaster/bldcontrol/localhostbecontroller.py | 27 +- .../management/commands/checksettings.py | 2 +- .../bldcontrol/management/commands/runbuilds.py | 13 +- lib/toaster/bldcontrol/models.py | 10 +- .../orm/migrations/0019_auto__add_buildartifact.py | 342 +++++++++++++++++++++ lib/toaster/orm/models.py | 50 ++- lib/toaster/orm/urls.py | 27 ++ lib/toaster/orm/views.py | 60 ++++ lib/toaster/toastergui/static/js/base.js | 4 + lib/toaster/toastergui/static/js/importlayer.js | 289 +++++++++++++++++ lib/toaster/toastergui/static/js/libtoaster.js | 57 ++++ lib/toaster/toastergui/static/js/projectapp.js | 40 ++- lib/toaster/toastergui/templates/base.html | 18 +- lib/toaster/toastergui/templates/build.html | 29 +- .../toastergui/templates/builddashboard.html | 131 +++++--- .../toastergui/templates/configuration.html | 6 +- lib/toaster/toastergui/templates/importlayer.html | 114 +++++-- .../toastergui/templates/layers_dep_modal.html | 90 ++++++ .../toastergui/templates/package_detail_base.html | 3 + lib/toaster/toastergui/templates/project.html | 3 +- lib/toaster/toastergui/templates/recipe.html | 11 +- lib/toaster/toastergui/templates/recipes.html | 7 +- lib/toaster/toastergui/templates/target.html | 8 +- lib/toaster/toastergui/templates/task.html | 18 +- lib/toaster/toastergui/templates/tasks.html | 12 +- lib/toaster/toastergui/urls.py | 2 + lib/toaster/toastergui/views.py | 325 ++++++++++++++------ lib/toaster/toastermain/settings.py | 27 +- lib/toaster/toastermain/urls.py | 10 +- 35 files changed, 1853 insertions(+), 265 deletions(-) create mode 100755 bin/toaster-eventreplay create mode 100644 lib/toaster/orm/migrations/0019_auto__add_buildartifact.py create mode 100644 lib/toaster/orm/urls.py create mode 100644 lib/toaster/orm/views.py create mode 100644 lib/toaster/toastergui/static/js/importlayer.js create mode 100644 lib/toaster/toastergui/templates/layers_dep_modal.html
Signature · 1 line
1.9.1
PATCH v2 1/18 add build artifacts table and other improvements
From: Alexandru DAMIAN <[email protected]>
We add a BuildArtifacts class to store data about files discovered during the build process and not stored anywhere else.
Small cosmetic changes in the toasterui.
Add model methods to return file path display data relative to the build environment instead of absolute file paths.
[YOCTO #6834]
--- lib/bb/ui/buildinfohelper.py | 26 +- lib/bb/ui/toasterui.py | 4 + lib/toaster/bldcontrol/models.py | 10 +- .../orm/migrations/0019_auto__add_buildartifact.py | 342 +++++++++++++++++++++ lib/toaster/orm/models.py | 29 ++ 5 files changed, 404 insertions(+), 7 deletions(-) create mode 100644 lib/toaster/orm/migrations/0019_auto__add_buildartifact.py
lib/bb/ui/buildinfohelper.py +20 -6
@@ -26,7 +26,7 @@ os.environ["DJANGO_SETTINGS_MODULE"] = "toaster.toastermain.settings" import toaster.toastermain.settings as toaster_django_settings from toaster.orm.models import Build, Task, Recipe, Layer_Version, Layer, Target, LogMessage, HelpText -from toaster.orm.models import Target_Image_File +from toaster.orm.models import Target_Image_File, BuildArtifact from toaster.orm.models import Variable, VariableHistory from toaster.orm.models import Package, Package_File, Target_Installed_Package, Target_File from toaster.orm.models import Task_Dependency, Package_Dependency
@@ -156,8 +156,7 @@ class ORMWrapper(object): build.outcome = outcome build.save() - def update_target_object(self, target, license_manifest_path): - + def update_target_set_license_manifest(self, target, license_manifest_path): target.license_manifest_path = license_manifest_path target.save()
@@ -447,7 +446,17 @@ class ORMWrapper(object): target_image_file = Target_Image_File.objects.create( target = target_obj, file_name = file_name, file_size = file_size) - target_image_file.save() + + def save_artifact_information(self, build_obj, file_name, file_size): + # we skip the image files from other builds + if Target_Image_File.objects.filter(file_name = file_name).count() > 0: + return + + # do not update artifacts found in other builds + if BuildArtifact.objects.filter(file_name = file_name).count() > 0: + return + + BuildArtifact.objects.create(build = build_obj, file_name = file_name, file_size = file_size) def create_logmessage(self, log_information): assert 'build' in log_information
@@ -752,6 +761,11 @@ class BuildInfoHelper(object): if t.target in output and output.split('.rootfs.')[1] in image_fstypes: self.orm_wrapper.save_target_image_file_information(t, output, evdata[output]) + def update_artifact_image_file(self, event): + evdata = BuildInfoHelper._get_data_from_event(event) + for artifact_path in evdata.keys(): + self.orm_wrapper.save_artifact_information(self.internal_state['build'], artifact_path, evdata[artifact_path]) + def update_build_information(self, event, errors, warnings, taskfailures): if 'build' in self.internal_state: self.orm_wrapper.update_build_object(self.internal_state['build'], errors, warnings, taskfailures)
@@ -760,10 +774,10 @@ class BuildInfoHelper(object): def store_license_manifest_path(self, event): deploy_dir = BuildInfoHelper._get_data_from_event(event)['deploy_dir'] image_name = BuildInfoHelper._get_data_from_event(event)['image_name'] - path = deploy_dir + "/licenses/" + image_name + "/" + path = deploy_dir + "/licenses/" + image_name + "/license.manifest" for target in self.internal_state['targets']: if target.target in image_name: - self.orm_wrapper.update_target_object(target, path) + self.orm_wrapper.update_target_set_license_manifest(target, path) def store_started_task(self, event):
lib/bb/ui/toasterui.py +4 -0
@@ -261,8 +261,12 @@ def main(server, eventHandler, params ): buildinfohelper.store_missed_state_tasks(event) elif event.type == "ImageFileSize": buildinfohelper.update_target_image_file(event) + elif event.type == "ArtifactFileSize": + buildinfohelper.update_artifact_image_file(event) elif event.type == "LicenseManifestPath": buildinfohelper.store_license_manifest_path(event) + else: + logger.error("Unprocessed MetadataEvent %s " % str(event)) continue if isinstance(event, bb.cooker.CookerExit):
lib/toaster/bldcontrol/models.py +9 -1
@@ -70,11 +70,19 @@ class BuildEnvironment(models.Model): return "binary/octet-stream" except ImportError: return "binary/octet-stream" + raise Exception("FIXME: artifact type not implemented for build environment type %s" % be.get_betype_display()) + def get_artifact(self, path): if self.betype == BuildEnvironment.TYPE_LOCAL: return open(path, "r") - raise Exception("FIXME: not implemented") + raise Exception("FIXME: artifact download not implemented for build environment type %s" % be.get_betype_display()) + + def has_artifact(self, path): + import os + if self.betype == BuildRequest.TYPE_LOCAL: + return os.path.exists(path) + raise Exception("FIXME: has artifact not implemented for build environment type %s" % be.get_betype_display()) # a BuildRequest is a request that the scheduler will build using a BuildEnvironment # the build request queue is the table itself, ordered by state
lib/toaster/orm/migrations/0019_auto__add_buildartifact.py new file +342 -0
343 lines not on this page · raw message
lib/toaster/orm/models.py +29 -0
@@ -25,6 +25,7 @@ from django.utils import timezone from django.core import validators +from django.conf import settings class GitURLValidator(validators.URLValidator): import re
@@ -183,6 +184,28 @@ class Build(models.Model): return self.logmessage_set.filter(level=LogMessage.EXCEPTION) +# an Artifact is anything that results from a Build, and may be of interest to the user, and is not stored elsewhere +class BuildArtifact(models.Model): + build = models.ForeignKey(Build) + file_name = models.FilePathField() + file_size = models.IntegerField() + + + def get_local_file_name(self): + try: + deploydir = Variable.objects.get(build = self.build, variable_name="DEPLOY_DIR").variable_value + return self.file_name[len(deploydir)+1:] + except: + raise + + return self.file_name + + + def is_available(self): + if settings.MANAGED and build.project is not None: + return build.buildrequest.environment.has_artifact(file_path) + return False + class ProjectTarget(models.Model): project = models.ForeignKey(Project) target = models.CharField(max_length=100)
@@ -457,6 +480,12 @@ class Recipe(models.Model): def __unicode__(self): return "Recipe " + self.name + ":" + self.version + def get_local_path(self): + if settings.MANAGED and self.layer_version.build.project is not None: + return self.file_path[len(self.layer_version.layer.local_path)+1:] + + return self.file_path + class Meta: unique_together = ("layer_version", "file_path")
Signature · 1 line
1.9.1
PATCH v2 2/18 toasterui: add extra debug and development infos
From: Alexandru DAMIAN <[email protected]>
We update and add logs throughout the code in order to help with development. The extra logging is turned off by default, but it can be enabled by using environment variables.
All logging happens through the Python logging facilities.
The toaster UI will save a log of all incoming events if the TOASTER_EVENTLOG variable is set.
If TOASTER_SQLDEBUG is set all DB queries will be logged.
If TOASTER_DEVEL is set and the django-fresh module is available, the module is enabled to allow auto-reload of pages when the source is changed.
--- lib/bb/ui/buildinfohelper.py | 12 ++++++++-- lib/bb/ui/toasterui.py | 19 +++++++-------- lib/toaster/bldcontrol/localhostbecontroller.py | 27 ++++++++++++++-------- .../management/commands/checksettings.py | 2 +- .../bldcontrol/management/commands/runbuilds.py | 13 +++++++---- lib/toaster/toastermain/settings.py | 27 ++++++++++++++++++++-- lib/toaster/toastermain/urls.py | 10 +++++--- 7 files changed, 79 insertions(+), 31 deletions(-)
lib/bb/ui/buildinfohelper.py +10 -2
@@ -33,6 +33,11 @@ from toaster.orm.models import Task_Dependency, Package_Dependency from toaster.orm.models import Recipe_Dependency from bb.msg import BBLogFormatter as format from django.db import models +import logging + + +logger = logging.getLogger("BitBake") + class NotExisting(Exception): pass
@@ -115,7 +120,9 @@ class ORMWrapper(object): build_name=build_info['build_name'], bitbake_version=build_info['bitbake_version']) + logger.debug(1, "buildinfohelper: build is created %s" % build) if brbe is not None: + logger.debug(1, "buildinfohelper: brbe is %s" % brbe) from bldcontrol.models import BuildEnvironment, BuildRequest br, be = brbe.split(":")
@@ -605,6 +612,7 @@ class BuildInfoHelper(object): self.has_build_history = has_build_history self.tmp_dir = self.server.runCommand(["getVariable", "TMPDIR"])[0] self.brbe = self.server.runCommand(["getVariable", "TOASTER_BRBE"])[0] + logger.debug(1, "buildinfohelper: Build info helper inited %s" % vars(self)) def _configure_django(self):
@@ -1110,10 +1118,10 @@ class BuildInfoHelper(object): if 'build' in self.internal_state and 'backlog' in self.internal_state: if len(self.internal_state['backlog']): tempevent = self.internal_state['backlog'].pop() - print "DEBUG: Saving stored event ", tempevent + logger.debug(1, "buildinfohelper: Saving stored event %s " % tempevent) self.store_log_event(tempevent) else: - print "ERROR: Events not saved: \n", self.internal_state['backlog'] + logger.error("buildinfohelper: Events not saved: %s" % self.internal_state['backlog']) del self.internal_state['backlog'] log_information = {}
lib/bb/ui/toasterui.py +10 -9
@@ -62,15 +62,6 @@ def _log_settings_from_server(server): def main(server, eventHandler, params ): - includelogs, loglines = _log_settings_from_server(server) - - # verify and warn - build_history_enabled = True - inheritlist, error = server.runCommand(["getVariable", "INHERIT"]) - if not "buildhistory" in inheritlist.split(" "): - logger.warn("buildhistory is not enabled. Please enable INHERIT += \"buildhistory\" to see image details.") - build_history_enabled = False - helper = uihelper.BBUIHelper() console = logging.StreamHandler(sys.stdout)
@@ -80,6 +71,16 @@ def main(server, eventHandler, params ): console.setFormatter(format) logger.addHandler(console) + includelogs, loglines = _log_settings_from_server(server) + + # verify and warn + build_history_enabled = True + inheritlist, error = server.runCommand(["getVariable", "INHERIT"]) + + if not "buildhistory" in inheritlist.split(" "): + logger.warn("buildhistory is not enabled. Please enable INHERIT += \"buildhistory\" to see image details.") + build_history_enabled = False + if not params.observe_only: logger.error("ToasterUI can only work in observer mode") return
lib/toaster/bldcontrol/localhostbecontroller.py +17 -10
@@ -32,6 +32,10 @@ from toastermain import settings from bbcontroller import BuildEnvironmentController, ShellCmdException, BuildSetupException, _getgitcheckoutdirectoryname +import logging +logger = logging.getLogger("toaster") + + class LocalhostBEController(BuildEnvironmentController): """ Implementation of the BuildEnvironmentController for the localhost; this controller manages the default build directory,
@@ -56,8 +60,10 @@ class LocalhostBEController(BuildEnvironmentController): err = "command: %s \n%s" % (command, out) else: err = "command: %s \n%s" % (command, err) + logger.debug("localhostbecontroller: shellcmd error %s" % err) raise ShellCmdException(err) else: + logger.debug("localhostbecontroller: shellcmd success") return out def _createdirpath(self, path):
@@ -90,7 +96,7 @@ class LocalhostBEController(BuildEnvironmentController): for i in self._shellcmd(cmd).split("\n"): if i.startswith("Bitbake server address"): port = i.split(" ")[-1] - print "Found bitbake server port ", port + logger.debug("localhostbecontroller: Found bitbake server port %s" % port) def _toaster_ui_started(filepath): if not os.path.exists(filepath):
@@ -103,10 +109,10 @@ class LocalhostBEController(BuildEnvironmentController): while not _toaster_ui_started(os.path.join(self.be.builddir, "toaster_ui.log")): import time - print "DEBUG: Waiting server to start" + logger.debug("localhostbecontroller: Waiting bitbake server to start") time.sleep(0.5) - print("DEBUG: Started server") + logger.debug("localhostbecontroller: Started bitbake server") assert self.be.sourcedir and os.path.exists(self.be.builddir) self.be.bbaddress = "localhost" self.be.bbport = port
@@ -116,11 +122,11 @@ class LocalhostBEController(BuildEnvironmentController): def stopBBServer(self): assert self.pokydirname and os.path.exists(self.pokydirname) assert self.islayerset - print self._shellcmd("bash -c \"source %s/oe-init-build-env %s && %s source toaster stop\"" % + self._shellcmd("bash -c \"source %s/oe-init-build-env %s && %s source toaster stop\"" % (self.pokydirname, self.be.builddir, (lambda: "" if self.be.bbtoken is None else "BBTOKEN=%s" % self.be.bbtoken)())) self.be.bbstate = BuildEnvironment.SERVER_STOPPED self.be.save() - print "Stopped server" + logger.debug("localhostbecontroller: Stopped bitbake server") def setLayers(self, bitbakes, layers): """ a word of attention: by convention, the first layer for any build will be poky! """
@@ -149,12 +155,13 @@ class LocalhostBEController(BuildEnvironmentController): raise BuildSetupException("More than one commit per git url, unsupported configuration: \n%s" % pprint.pformat(gitrepos)) + logger.debug("localhostbecontroller, our git repos are %s" % gitrepos) layerlist = [] # 2. checkout the repositories for giturl in gitrepos.keys(): localdirname = os.path.join(self.be.sourcedir, _getgitcheckoutdirectoryname(giturl)) - print "DEBUG: giturl ", giturl ,"checking out in current directory", localdirname + logger.debug("localhostbecontroller: giturl %s checking out in current directory %s" % (giturl, localdirname)) # make sure our directory is a git repository if os.path.exists(localdirname):
@@ -167,17 +174,17 @@ class LocalhostBEController(BuildEnvironmentController): # branch magic name "HEAD" will inhibit checkout if commit != "HEAD": - print "DEBUG: checking out commit ", commit, "to", localdirname + logger.debug("localhostbecontroller: checking out commit %s to %s " % (commit, localdirname)) self._shellcmd("git fetch --all && git checkout \"%s\"" % commit , localdirname) # take the localdirname as poky dir if we can find the oe-init-build-env if self.pokydirname is None and os.path.exists(os.path.join(localdirname, "oe-init-build-env")): - print "DEBUG: selected poky dir name", localdirname + logger.debug("localhostbecontroller: selected poky dir name %s" % localdirname) self.pokydirname = localdirname # make sure we have a working bitbake if not os.path.exists(os.path.join(self.pokydirname, 'bitbake')): - print "DEBUG: checking bitbake into the poky dirname %s " % self.pokydirname + logger.debug("localhostbecontroller: checking bitbake into the poky dirname %s " % self.pokydirname) self._shellcmd("git clone -b \"%s\" \"%s\" \"%s\" " % (bitbakes[0].commit, bitbakes[0].giturl, os.path.join(self.pokydirname, 'bitbake'))) # verify our repositories
@@ -189,7 +196,7 @@ class LocalhostBEController(BuildEnvironmentController): if name != "bitbake": layerlist.append(localdirpath.rstrip("/")) - print "DEBUG: current layer list ", layerlist + logger.debug("localhostbecontroller: current layer list %s " % layerlist) # 3. configure the build environment, so we have a conf/bblayers.conf assert self.pokydirname is not None
lib/toaster/bldcontrol/management/commands/checksettings.py +1 -1
@@ -1,7 +1,7 @@ from django.core.management.base import NoArgsCommand, CommandError from django.db import transaction from bldcontrol.bbcontroller import getBuildEnvironmentController, ShellCmdException -from bldcontrol.models import BuildRequest, BuildEnvironment +from bldcontrol.models import BuildRequest, BuildEnvironment, BRError from orm.models import ToasterSetting import os
lib/toaster/bldcontrol/management/commands/runbuilds.py +9 -4
@@ -4,6 +4,9 @@ from orm.models import Build from bldcontrol.bbcontroller import getBuildEnvironmentController, ShellCmdException, BuildSetupException from bldcontrol.models import BuildRequest, BuildEnvironment, BRError import os +import logging + +logger = logging.getLogger("toaster") class Command(NoArgsCommand): args = ""
@@ -32,6 +35,7 @@ class Command(NoArgsCommand): # select the build environment and the request to build br = self._selectBuildRequest() except IndexError as e: + # logger.debug("runbuilds: No build request") return try: bec = self._selectBuildEnvironment()
@@ -39,10 +43,10 @@ class Command(NoArgsCommand): # we could not find a BEC; postpone the BR br.state = BuildRequest.REQ_QUEUED br.save() - print "No build env" + logger.debug("runbuilds: No build env") return - print "Build %s, Environment %s" % (br, bec.be) + logger.debug("runbuilds: starting build %s, environment %s" % (br, bec.be)) # let the build request know where it is being executed br.environment = bec.be br.save()
@@ -63,7 +67,7 @@ class Command(NoArgsCommand): task = None bbctrl.build(list(map(lambda x:x.target, br.brtarget_set.all())), task) - print "Build launched, exiting" + logger.debug("runbuilds: Build launched, exiting") # disconnect from the server bbctrl.disconnect()
@@ -71,7 +75,7 @@ class Command(NoArgsCommand): except Exception as e: - print " EE Error executing shell command\n", e + logger.error("runbuilds: Error executing shell command %s" % e) traceback.print_exc(e) BRError.objects.create(req = br, errtype = str(type(e)),
@@ -83,6 +87,7 @@ class Command(NoArgsCommand): bec.be.save() + def cleanup(self): from django.utils import timezone from datetime import timedelta
lib/toaster/toastermain/settings.py +25 -2
@@ -21,11 +21,15 @@ # Django settings for Toaster project. +import os, re + DEBUG = True TEMPLATE_DEBUG = DEBUG # Set to True to see the SQL queries in console SQL_DEBUG = False +if os.environ.get("TOASTER_SQLDEBUG", None) is not None: + SQL_DEBUG = True ADMINS = (
@@ -46,7 +50,6 @@ DATABASES = { } # Reinterpret database settings if we have DATABASE_URL environment variable defined -import os, re if 'DATABASE_URL' in os.environ: dburl = os.environ['DATABASE_URL']
@@ -212,6 +215,9 @@ MIDDLEWARE_CLASSES = ( # 'django.middleware.clickjacking.XFrameOptionsMiddleware', ) +from os.path import dirname as DN +SITE_ROOT=DN(DN(os.path.abspath(__file__))) + ROOT_URLCONF = 'toastermain.urls' # Python dotted path to the WSGI application used by Django's runserver.
@@ -245,6 +251,19 @@ INSTALLED_APPS = ( 'south', ) + +# Load django-fresh is TOASTER_DEVEL is set, and the module is available +FRESH_ENABLED = False +if os.environ.get('TOASTER_DEVEL', None) is not None: + try: + import fresh + MIDDLEWARE_CLASSES = MIDDLEWARE_CLASSES + ("fresh.middleware.FreshMiddleware",) + INSTALLED_APPS = INSTALLED_APPS + ('fresh',) + FRESH_ENABLED = True + except: + pass + + SOUTH_TESTS_MIGRATE = False # if we run in managed mode, we need user support
@@ -286,7 +305,7 @@ LOGGING = { }, 'formatters': { 'datetime': { - 'format': 'DB %(asctime)s %(message)s' + 'format': '%(levelname)s %(asctime)s %(message)s' } }, 'handlers': {
@@ -302,6 +321,10 @@ LOGGING = { } }, 'loggers': { + 'toaster' : { + 'handlers': ['console'], + 'level': 'DEBUG', + }, 'django.request': { 'handlers': ['mail_admins'], 'level': 'ERROR',
lib/toaster/toastermain/urls.py +7 -3
@@ -44,11 +44,15 @@ urlpatterns = patterns('', ) import toastermain.settings + +if toastermain.settings.FRESH_ENABLED: + urlpatterns.insert(1, url(r'', include('fresh.urls'))) + if toastermain.settings.MANAGED: - urlpatterns = urlpatterns + [ + urlpatterns = [ # Uncomment the next line to enable the admin: url(r'^admin/', include(admin.site.urls)), - ] + ] + urlpatterns # Automatically discover urls.py in various apps, beside our own # and map module directories to the patterns
@@ -61,4 +65,4 @@ for t in os.walk(os.path.dirname(currentdir)): if "urls.py" in t[2] and t[0] != currentdir: modulename = os.path.basename(t[0]) - urlpatterns.append( url(r'^' + modulename + '/', include ( modulename + '.urls'))) + urlpatterns.insert(0, url(r'^' + modulename + '/', include ( modulename + '.urls')))
Signature · 1 line
1.9.1
PATCH v2 3/18 add option to write offline event log file
In reply to Alex DAMIANFrom: Alexandru DAMIAN <[email protected]>
This patch adds a "-w/--write-log" option to bitbake that writes an event log file for the current build.
The name of the file is passed as a parameter to the "-w" argument. If the parameter is the empty string '', the file name is generated in the form bitbake_eventlog_DATE.json, where DATE is the current date and time, with second precision.
The "-w" option can also be supplied as the BBEVENTLOG environment variable.
We add a script, toater-eventreplay, that reads an event log file and loads the data into a Toaster database, creating a build entry.
We modify the toasterui to fix minor issues with reading events from an event log file.
Performance impact is undetectable under no-task executed builds.
--- bin/bitbake | 12 ++- bin/toaster-eventreplay | 179 +++++++++++++++++++++++++++++++++++++++++++ lib/bb/cooker.py | 75 +++++++++++++++++- lib/bb/cookerdata.py | 1 + lib/bb/ui/buildinfohelper.py | 53 +++++++++---- lib/bb/ui/toasterui.py | 2 +- 6 files changed, 300 insertions(+), 22 deletions(-) create mode 100755 bin/toaster-eventreplay
bin/bitbake +11 -1
@@ -196,6 +196,9 @@ class BitBakeConfigParameters(cookerdata.ConfigParameters): parser.add_option("", "--status-only", help = "Check the status of the remote bitbake server.", action = "store_true", dest = "status_only", default = False) + parser.add_option("-w", "--write-log", help = "Writes the event log of the build to a bitbake event json file. Use '' (empty string) to assign the name automatically.", + action = "store", dest = "writeeventlog") + options, targets = parser.parse_args(sys.argv) # some environmental variables set also configuration options
@@ -206,6 +209,14 @@ class BitBakeConfigParameters(cookerdata.ConfigParameters): if "BBTOKEN" in os.environ: options.xmlrpctoken = os.environ["BBTOKEN"] + if "BBEVENTLOG" is os.environ: + options.writeeventlog = os.environ["BBEVENTLOG"] + + # fill in proper log name if not supplied + if options.writeeventlog is not None and len(options.writeeventlog) == 0: + import datetime + options.writeeventlog = "bitbake_eventlog_%s.json" % datetime.datetime.now().strftime("%Y%m%d%H%M%S") + # if BBSERVER says to autodetect, let's do that if options.remote_server: [host, port] = options.remote_server.split(":", 2)
@@ -266,7 +277,6 @@ def start_server(servermodule, configParams, configuration, features): return server - def main(): configParams = BitBakeConfigParameters()
bin/toaster-eventreplay new file +179 -0
179 lines not on this page · raw message
lib/bb/cooker.py +72 -3
95 lines not on this page · raw message
lib/bb/cookerdata.py +1 -0
@@ -139,6 +139,7 @@ class CookerConfiguration(object): self.dry_run = False self.tracking = False self.interface = [] + self.writeeventlog = False self.env = {}
lib/bb/ui/buildinfohelper.py +36 -17
94 lines not on this page · raw message
lib/bb/ui/toasterui.py +1 -1
@@ -309,7 +309,7 @@ def main(server, eventHandler, params ): try: buildinfohelper.store_log_exception("%s\n%s" % (str(e), exception_data)) except Exception as ce: - print("CRITICAL: failed to to save toaster exception to the database: %s" % str(ce)) + logger.error("CRITICAL - Failed to to save toaster exception to the database: %s" % str(ce)) pass
Signature · 1 line
1.9.1
PATCH v2 4/18 toastergui: implement UI changes to allow file download
In reply to Alex DAMIANFrom: Alexandru DAMIAN <[email protected]>
This patchset adds download links in the build analisys pages if toaster runs in managed mode. This allows the user to access data directly from the web interface.
[YOCTO #6837]
--- lib/toaster/toastergui/templates/base.html | 18 +-- lib/toaster/toastergui/templates/build.html | 29 +++- .../toastergui/templates/builddashboard.html | 131 +++++++++++----- .../toastergui/templates/configuration.html | 6 +- .../toastergui/templates/package_detail_base.html | 3 + lib/toaster/toastergui/templates/recipe.html | 11 +- lib/toaster/toastergui/templates/recipes.html | 7 +- lib/toaster/toastergui/templates/target.html | 8 +- lib/toaster/toastergui/templates/task.html | 18 ++- lib/toaster/toastergui/templates/tasks.html | 12 +- lib/toaster/toastergui/views.py | 168 +++++++++++++-------- 11 files changed, 276 insertions(+), 135 deletions(-)
lib/toaster/toastergui/templates/base.html +9 -9
@@ -3,14 +3,14 @@ <html lang="en"> <head> <title>{% if objectname %} {{objectname|title}} - {% endif %}Toaster</title> -<link rel="stylesheet" href="{% static 'css/bootstrap.min.css' %}" type="text/css"> -<link rel="stylesheet" href="{% static 'css/bootstrap-responsive.min.css' %}" type='text/css'> -<link rel="stylesheet" href="{% static 'css/font-awesome.min.css' %}" type='text/css'> -<link rel="stylesheet" href="{% static 'css/prettify.css' %}" type='text/css'> -<link rel="stylesheet" href="{% static 'css/default.css' %}" type='text/css'> +<link rel="stylesheet" href="{% static 'css/bootstrap.min.css' %}" type="text/css"/> +<link rel="stylesheet" href="{% static 'css/bootstrap-responsive.min.css' %}" type='text/css'/> +<link rel="stylesheet" href="{% static 'css/font-awesome.min.css' %}" type='text/css'/> +<link rel="stylesheet" href="{% static 'css/prettify.css' %}" type='text/css'/> +<link rel="stylesheet" href="{% static 'css/default.css' %}" type='text/css'/> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> -<meta http-equiv="Content-Type" content="text/html;charset=UTF-8"> +<meta http-equiv="Content-Type" content="text/html;charset=UTF-8" /> <script src="{% static 'js/jquery-2.0.3.min.js' %}"> </script> <script src="{% static 'js/jquery.cookie.js' %}">
@@ -64,7 +64,7 @@ </div> <!-- New build popover --> <div class="btn-group pull-right" id="new-build-button"> - <button class="btn dropdown-toggle" data-toggle="dropdown" href="#"> + <button class="btn dropdown-toggle" data-toggle="dropdown"> New build <i class="icon-caret-down"></i> </button>
@@ -78,7 +78,7 @@ </span> <form id="change-project-form" style="display:none;"> <div class="input-append"> - <input type="text" class="input-medium" id="project-name-input" placeholder="Type a project name" autocomplete="off" data-minLength="1" data-autocomplete="off" data-provide="typeahead"> + <input type="text" class="input-medium" id="project-name-input" placeholder="Type a project name" autocomplete="off" data-minLength="1" data-autocomplete="off" data-provide="typeahead"/> <button id="save-project-button" class="btn" type="button">Save</button> <a href="#" id="cancel-change-project" class="btn btn-link">Cancel</a> </div>
@@ -92,7 +92,7 @@ <li id="targets-form"> <h6>Target(s):</h6> <form> - <input type="text" class="input-xlarge" id="build-target-input" placeholder="Type a target name" autocomplete="off" data-minLength="1" data-autocomplete="off" data-provide="typeahead" > + <input type="text" class="input-xlarge" id="build-target-input" placeholder="Type a target name" autocomplete="off" data-minLength="1" data-autocomplete="off" data-provide="typeahead" /> <div> <a class="btn btn-primary" id="build-button" disabled="disabled" data-project-id="{{project.id}}">Build</a> </div>
lib/toaster/toastergui/templates/build.html +26 -3
@@ -45,11 +45,34 @@ <td class="machine"><a href="{% url "builddashboard" build.id %}">{{build.machine}}</a></td> <td class="started_on"><a href="{% url "builddashboard" build.id %}">{{build.started_on|date:"d/m/y H:i"}}</a></td> <td class="completed_on"><a href="{% url "builddashboard" build.id %}">{{build.completed_on|date:"d/m/y H:i"}}</a></td> - <td class="failed_tasks error">{% query build.task_build outcome=4 order__gt=0 as exectask%}{% if exectask.count == 1 %}<a href="{% url "task" build.id exectask.0.id %}">{{exectask.0.recipe.name}}.{{exectask.0.task_name}}</a>{% elif exectask.count > 1%}<a href="{% url "tasks" build.id %}?filter=outcome%3A4">{{exectask.count}}</a>{%endif%}</td> - <td class="errors_no">{% if build.errors_no %}<a class="errors_no error" href="{% url "builddashboard" build.id %}#errors">{{build.errors_no}} error{{build.errors_no|pluralize}}</a>{%endif%}</td> + <td class="failed_tasks error"> + {% query build.task_build outcome=4 order__gt=0 as exectask%} + {% if exectask.count == 1 %} + <a href="{% url "task" build.id exectask.0.id %}">{{exectask.0.recipe.name}}.{{exectask.0.task_name}}</a> + {% if MANAGED and build.project %} + <a href="{% url 'build_artifact' build.id "tasklogfile" exectask.0.id %}"> + <i class="icon-download-alt" title="" data-original-title="Download task log file"></i> + </a> + {% endif %} + {% elif exectask.count > 1%} + <a href="{% url "tasks" build.id %}?filter=outcome%3A4">{{exectask.count}} task{{exectask.count|pluralize}}</a> + {%endif%} + </td> + <td class="errors_no"> + {% if build.errors_no %} + <a class="errors_no error" href="{% url "builddashboard" build.id %}#errors">{{build.errors_no}} error{{build.errors_no|pluralize}}</a> + {% if MANAGED and build.project %} + <a href="{% url 'build_artifact' build.id "cookerlog" build.id %}"> + <i class="icon-download-alt" title="" data-original-title="Download build log"></i> + </a> + {% endif %} + {%endif%} + </td> <td class="warnings_no">{% if build.warnings_no %}<a class="warnings_no warning" href="{% url "builddashboard" build.id %}#warnings">{{build.warnings_no}} warning{{build.warnings_no|pluralize}}</a>{%endif%}</td> <td class="time"><a href="{% url "buildtime" build.id %}">{{build.timespent|sectohms}}</a></td> - <td class="log">{{build.cooker_log_path}}</td> + {% if not MANAGED or not build.project %} + <td class="log">{{build.cooker_log_path}}</td> + {% endif %} <td class="output"> {% if build.outcome == build.SUCCEEDED %} <a href="{%url "builddashboard" build.id%}#images">{{fstypes|get_dict_value:build.id}}</a>
lib/toaster/toastergui/templates/builddashboard.html +91 -40
188 lines not on this page · raw message
lib/toaster/toastergui/templates/configuration.html +5 -1
@@ -50,7 +50,9 @@ <th>Layer</th> <th>Layer branch</th> <th>Layer commit</th> - <th>Layer directory</th> + {% if not MANAGED or not build.project %} + <th>Layer directory</th> + {% endif %} </tr> </thead> <tbody>{% for lv in build.layer_version_build.all|dictsort:"layer.name" %}
@@ -61,7 +63,9 @@ <li>{{lv.commit}}</li> </ul>"> {{lv.commit|truncatechars:13}} </a></td> + {% if not MANAGED or not build.project %} <td>{{lv.layer.local_path}}</td> + {% endif %} </tr>{% endfor %} </tbody> </table>
lib/toaster/toastergui/templates/package_detail_base.html +3 -0
@@ -135,11 +135,14 @@ </dt> <dd class="iscommit">{{package.recipe.layer_version.commit}}</dd> + + {% if not MANAGED or not build.project %} <dt> Layer directory <i class="icon-question-sign get-help" title="Path to the layer providing the recipe that builds this package"></i> </dt> <dd><code>{{package.recipe.layer_version.layer.local_path}}</code></dd> + {% endif %} </dl> </div> <!-- row4 well --> {% endblock twocolumns %}
lib/toaster/toastergui/templates/recipe.html +10 -1
@@ -52,16 +52,19 @@ Layer </dt> <dd>{{layer.name}}</dd> + + {% if not MANAGED or not build.project %} <dt> <i class="icon-question-sign get-help" title="Path to the layer providing the recipe"></i> Layer directory </dt> <dd><code>{{layer.local_path}}</code></dd> + {% endif %} <dt> <i class="icon-question-sign get-help" title="Path to the recipe .bb file"></i> Recipe file </dt> - <dd><code>{{object.file_path}}</code></dd> + <dd><code>{{object.get_local_path}}</code></dd> {% if layer_version.branch %} <dt> <i class="icon-question-sign get-help" title="The Git branch of the layer providing the recipe"></i>
@@ -126,6 +129,12 @@ <td> {% ifnotequal task.sstate_result task.SSTATE_NA %} <a {{ task|task_color }} href="{% url "task" build.pk task.pk %}">{{task.get_sstate_result_display}}</a> + {% if MANAGED and build.project and task.outcome = task.OUTCOME_FAILED %} + <a href="{% url 'build_artifact' build.pk "tasklogfile" task.pk %}"> + <i class="icon-download-alt" title="" data-original-title="Download task log file"></i> + </a> + {% endif %} + {% endifnotequal %} </td>
lib/toaster/toastergui/templates/recipes.html +5 -2
@@ -98,8 +98,11 @@ {{recipe.layer_version.commit|truncatechars:13}} </a> </td> - <!-- Layer directory --> - <td class="layer_version__layer__local_path">{{recipe.layer_version.layer.local_path}}</td> + + {% if not MANAGED or not build.project %} + <!-- Layer directory --> + <td class="layer_version__layer__local_path">{{recipe.layer_version.layer.local_path}}</td> + {% endif %} </tr> {% endfor %}
lib/toaster/toastergui/templates/target.html +5 -3
@@ -152,9 +152,11 @@ {{package.recipe.layer_version.commit|truncatechars:13}} </a> </td> - <td class="layer_directory"> - {{ package.recipe.layer_version.layer.local_path }} - </td> + {% if not MANAGED or not build.project %} + <td class="layer_directory"> + {{ package.recipe.layer_version.layer.local_path }} + </td> + {% endif %} </tr> {% endfor %}
lib/toaster/toastergui/templates/task.html +11 -7
@@ -24,17 +24,17 @@ {# executed tasks outcome #} <dl class="dl-horizontal"> {% if task.logfile %} + {% if MANAGED and build.project %} + <a class="btn btn-large" href="{% url 'build_artifact' build.id "tasklogfile" task.pk %}" style="margin:15px;">Download task log</a> + {% else %} <dt> <i class="icon-question-sign get-help" title="Path the task log file"></i> Log file </dt> <dd> - {% if MANAGED %} - <code><a href="{% url 'build_artifact' build.pk 'tasklogfile' task.pk %}" target="_blanc">{{task.logfile}}</a></code> - {% else %} - <code>{{task.logfile}}</code> - {% endif %} + <code>{{task.logfile}}</code> </dd> {% endif %} + {% endif %} {# show stack trace for failed task #} {% if task.outcome == task.OUTCOME_FAILED and log_head %} <h3>Python stack trace</h3>
@@ -191,6 +191,9 @@ <strong>Failed</strong> to restore output from sstate cache. The file was found but could not be unpacked. </div> <dl class="dl-horizontal"> + {% if MANAGED and build.project %} + <a href="{% url 'build_artifact' build.id "tasklogfile" task.pk %}" style="margin:15px;">Download log</a> + {% else %} <dt> <i class="icon-question-sign get-help" title="Path to the cache attempt log file"></i> Log file
@@ -201,6 +204,7 @@ Time (secs) </dt> <dd>{{task.elapsed_time|format_none_and_zero}}</dd> + {% endif %} </dl> <div class="alert alert-info"> Running the real task instead.
@@ -268,8 +272,8 @@ Time (secs) </dt> <dd>{{task.elapsed_time|format_none_and_zero|floatformat:2}}</dd> - {% endif %} - {% if task.cpu_usage > 0 %} + {% endif %} + {% if task.cpu_usage > 0 %} <dt> <i class="icon-question-sign get-help" title="The percentage of task CPU utilization"></i> CPU usage
lib/toaster/toastergui/templates/tasks.html +10 -2
@@ -94,6 +94,11 @@ <td class="outcome"> <a href="{%url "task" build.pk task.pk%} ">{{task.get_outcome_display}} </a> <i class="icon-question-sign get-help hover-help" title="{{task.get_outcome_help}}"></i> + {% if MANAGED and build.project and task.outcome = task.OUTCOME_FAILED %} + <a href="{% url 'build_artifact' build.pk "tasklogfile" task.pk %}"> + <i class="icon-download-alt" title="" data-original-title="Download task log file"></i> + </a> + {% endif %} </td> <td class="cache_attempt"> <a href="{%url "task" build.pk task.pk%} ">{{task.get_sstate_result_display|format_none_and_zero}}</a>
@@ -107,9 +112,12 @@ <td class="disk_io"> {{task.disk_io|format_none_and_zero}} </td> + + {% if not MANAGED or not build.project %} <td class="task_log"> {{task.logfile}} </td> + {% endif %} </tr> {% endfor %}
@@ -124,10 +132,10 @@ // enable blue hightlight animation for the order link if (location.href.search('#') > -1) { var task_order = location.href.split('#')[1]; - $("#" + task_order).addClass("highlight"); + $("#" + task_order).addClass("highlight"); } }); - + </script> {% endblock %}
lib/toaster/toastergui/views.py +101 -67
301 lines not on this page · raw message
Signature · 1 line
1.9.1
PATCH v2 5/18 add POST endpoint for uploading eventlog files
In reply to Alex DAMIANFrom: Alexandru DAMIAN <[email protected]>
This patch adds a simple UI-less POST endpoint, where bitbake_eventlog.json files generated by a bitbake run can be uploaded to the running toaster instance for insertion into the database.
--- lib/toaster/orm/urls.py | 27 ++++++++++++++++++++++ lib/toaster/orm/views.py | 60 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+) create mode 100644 lib/toaster/orm/urls.py create mode 100644 lib/toaster/orm/views.py
lib/toaster/orm/urls.py new file +27 -0
@@ -0,0 +1,27 @@ +# +# BitBake Toaster Implementation +# +# Copyright (C) 2014 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.conf.urls import patterns, include, url +from django.views.generic import RedirectView + +urlpatterns = patterns('orm.views', + # landing point for pushing a bitbake_eventlog.json file to this toaster instace + url(r'^eventfile$', 'eventfile', name='eventfile'), + ) +
lib/toaster/orm/views.py new file +60 -0
60 lines not on this page · raw message
Signature · 1 line
1.9.1
PATCH v2 6/18 toaster: base Only show change project icon when > one project
In reply to Alex DAMIANFrom: Belen Barros Pena <[email protected]>
In the new build button, it only makes sense to change the selected project when there is more than one project in the Toaster instance. If the number of projects is 1, we hide the change project icon.
--- lib/toaster/toastergui/static/js/base.js | 4 ++++ 1 file changed, 4 insertions(+)
lib/toaster/toastergui/static/js/base.js +4 -0
@@ -11,6 +11,10 @@ function basePageInit (ctx) { return; } + /* Hide the change project icon when there is only one project */ + if (ctx.numProjects == 1){ + $('#project .icon-pencil').hide(); + } newBuildButton.show().removeAttr("disabled");
Signature · 1 line
1.9.1
PATCH v2 7/18 toaster: Initialise the 'change' icon tooltips
In reply to Alex DAMIANFrom: Belen Barros Pena <[email protected]>
All 'change' icons should have a tooltip that appears when you hover over them. The tootlip says (fittingly):
"Change". Initialise those icons in the libtoaster.js file so that they work outside the project page.
--- lib/toaster/toastergui/static/js/libtoaster.js | 3 +++ 1 file changed, 3 insertions(+)
lib/toaster/toastergui/static/js/libtoaster.js +3 -0
@@ -213,6 +213,9 @@ $(document).ready(function() { // show task type and outcome in task details pages $(".task-info").tooltip({ container: 'body', html: true, delay: {show: 200}, placement: 'right' }); + // initialise the tooltips for the icon-pencil icons + $(".icon-pencil").tooltip({ container: 'body', html: true, delay: {show: 400}, title: "Change" }); + // linking directly to tabs $(function(){ var hash = window.location.hash;
Signature · 1 line
1.9.1
PATCH v2 8/18 toaster: libtoaster Add a error handler to GET in makeTypehead
In reply to Alex DAMIANFrom: Michael Wood <[email protected]>
If the JSON data comes back from the request with an error set, have a default handler which logs the error to the console.
--- lib/toaster/toastergui/static/js/libtoaster.js | 5 +++++ 1 file changed, 5 insertions(+)
lib/toaster/toastergui/static/js/libtoaster.js +5 -0
@@ -20,6 +20,11 @@ var libtoaster = (function (){ source: function(query, process){ xhrParams.value = query; $.getJSON(xhrUrl, this.options.xhrParams, function(data){ + if (data.error != "ok") { + console.log("Error getting data from server "+data.error); + return; + } + return process (data.list); }); },
Signature · 1 line
1.9.1
PATCH v2 9/18 toaster: libtoaster Add editProject and getLayerDepsForProject
In reply to Alex DAMIANFrom: Michael Wood <[email protected]>
Add two utility functions for editing project settings and returning the layer "dependencies" for a specified layer.
--- lib/toaster/toastergui/static/js/libtoaster.js | 49 ++++++++++++++++++++++++++ 1 file changed, 49 insertions(+)
lib/toaster/toastergui/static/js/libtoaster.js +49 -0
@@ -114,11 +114,60 @@ var libtoaster = (function (){ }); }; + /* Properties for data can be: + * layerDel (csv) + * layerAdd (csv) + * projectName + * projectVersion + * machineName + */ + function _editProject(url, projectId, data, onSuccess, onFail){ + $.ajax({ + type: "POST", + url: url, + data: data, + headers: { 'X-CSRFToken' : $.cookie('csrftoken')}, + success: function (data) { + if (data.error != "ok") { + console.log(data.error); + if (onFail != undefined) + onFail(data); + } else { + if (onSuccess != undefined) + onSuccess(data); + } + }, + error: function (data) { + console.log("Call failed"); + console.log(data); + } + }); + }; + + function _getLayerDepsForProject(xhrDataTypeaheadUrl, projectId, layerId, onSuccess, onFail){ + /* Check for dependencies not in the current project */ + $.getJSON(xhrDataTypeaheadUrl, + { type: 'layerdeps', 'value': layerId , project_id: projectId }, + function(data) { + if (data.error != "ok") { + console.log(data.error); + if (onFail != undefined) + onFail(data); + } else { + onSuccess(data); + } + }, function() { + console.log("E: Failed to make request"); + }); + }; + return { reload_params : reload_params, startABuild : _startABuild, makeTypeahead : _makeTypeahead, getProjectInfo: _getProjectInfo, + getLayerDepsForProject : _getLayerDepsForProject, + editProject : _editProject, } })();
Signature · 1 line
1.9.1
PATCH v2 10/18 toasterui: refactor project layer finding logic
In reply to Alex DAMIANFrom: Alexandru DAMIAN <[email protected]>
This is a basic refactoring of the code computing the layer equivalence classes for a project, in order to bring common bits of logic in a single place.
--- lib/toaster/orm/models.py | 21 +++++++++++++++++- lib/toaster/toastergui/views.py | 49 +++++++++++++++-------------------------- 2 files changed, 38 insertions(+), 32 deletions(-)
lib/toaster/orm/models.py +20 -1
@@ -20,7 +20,7 @@ # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. from django.db import models -from django.db.models import F +from django.db.models import F, Q from django.utils import timezone
@@ -98,6 +98,25 @@ class Project(models.Model): def __unicode__(self): return "%s (%s, %s)" % (self.name, self.release, self.bitbake_version) + # returns a queryset of compatible layers for a project + def compatible_layerversions(self, release = None, layer_name = None): + if release == None: + release = self.release + # layers on the same branch or layers specifically set for this project + queryset = Layer_Version.objects.filter((Q(up_branch__name = release.branch_name) & Q(project = None)) | Q(project = self)) + if layer_name is not None: + # we select only a layer name + queryset = queryset.filter(layer__name = layer_name) + + # order by layer version priority + queryset = queryset.filter(layer_source__releaselayersourcepriority__release = release).order_by("-layer_source__releaselayersourcepriority__priority") + + return queryset + + # returns a set of layer-equivalent set of layers already in project + def projectlayer_equivalent_set(self): + return [j for i in [x.layercommit.get_equivalents_wpriority(self) for x in self.projectlayer_set.all()] for j in i] + def schedule_build(self): from bldcontrol.models import BuildRequest, BRTarget, BRLayer, BRVariable, BRBitbake br = BuildRequest.objects.create(project = self)
lib/toaster/toastergui/views.py +18 -31
104 lines not on this page · raw message
Signature · 1 line
1.9.1
PATCH v2 11/18 toaster: Add import layer feature.
In reply to Alex DAMIANFrom: Michael Wood <[email protected]>
This feature allows users to import layers from git into their current project and associate it with the release of the current project and the dependencies for the newly imported layer with existing layers. It will also resolve the child dependencies of the dependencies added.
[YOCTO #6595]
--- lib/toaster/toastergui/static/js/importlayer.js | 277 +++++++++++++++++++++ lib/toaster/toastergui/static/js/projectapp.js | 6 + lib/toaster/toastergui/templates/importlayer.html | 109 +++++--- .../toastergui/templates/layers_dep_modal.html | 68 +++++ lib/toaster/toastergui/urls.py | 2 + lib/toaster/toastergui/views.py | 107 +++++++- 6 files changed, 533 insertions(+), 36 deletions(-) create mode 100644 lib/toaster/toastergui/static/js/importlayer.js create mode 100644 lib/toaster/toastergui/templates/layers_dep_modal.html
lib/toaster/toastergui/static/js/importlayer.js new file +277 -0
@@ -0,0 +1,277 @@ +"use strict" + +function importLayerPageInit (ctx) { + + var layerDepBtn = $("#add-layer-dependency-btn"); + var importAndAddBtn = $("#import-and-add-btn"); + var layerNameInput = $("#layer-name"); + var vcsURLInput = $("#layer-git-repo-url"); + var gitRefInput = $("#layer-git-ref"); + var layerDepInput = $("#layer-dependency"); + var layerNameCtrl = $("#layer-name-ctrl"); + var duplicatedLayerName = $("#duplicated-layer-name-hint"); + + var layerDeps = {}; + var layerDepsDeps = {}; + var currentLayerDepSelection; + var validLayerName = /^(\w|-)+$/; + + $("#new-project-button").hide(); + + libtoaster.makeTypeahead(layerDepInput, ctx.xhrDataTypeaheadUrl, { type : "layers", project_id: ctx.projectId, include_added: "true" }, function(item){ + currentLayerDepSelection = item; + + layerDepBtn.removeAttr("disabled"); + }); + + + /* We automatically add "openembedded-core" layer for convenience as a + * dependency as pretty much all layers depend on this one + */ + $.getJSON(ctx.xhrDataTypeaheadUrl, { type : "layers", project_id: ctx.projectId, include_added: "true" , value: "openembedded-core" }, function(layer) { + if (layer.list.length == 1) { + currentLayerDepSelection = layer.list[0]; + layerDepBtn.click(); + } + }); + + layerDepBtn.click(function(){ + if (currentLayerDepSelection == undefined) + return; + + layerDeps[currentLayerDepSelection.id] = currentLayerDepSelection; + + /* Make a list item for the new layer dependency */ + var newLayerDep = $("<li><a></a><span class=\"icon-trash\" data-toggle=\"tooltip\" title=\"Delete\"></span></li>"); + + newLayerDep.data('layer-id', currentLayerDepSelection.id); + newLayerDep.children("span").tooltip(); + + var link = newLayerDep.children("a"); + link.attr("href", ctx.layerDetailsUrl+String(currentLayerDepSelection.id)); + link.text(currentLayerDepSelection.name); + link.tooltip({title: currentLayerDepSelection.tooltip, placement: "right"}); + + var trashItem = newLayerDep.children("span"); + trashItem.click(function () { + var toRemove = $(this).parent().data('layer-id'); + delete layerDeps[toRemove]; + $(this).parent().fadeOut(function (){ + $(this).remove(); + }); + }); + + $("#layer-deps-list").append(newLayerDep); + + libtoaster.getLayerDepsForProject(ctx.xhrDataTypeaheadUrl, ctx.projectId, currentLayerDepSelection.id, function (data){ + /* These are the dependencies of the layer added as a dependency */ + if (data.list.length > 0) { + currentLayerDepSelection.url = ctx.layerDetailsUrl+currentLayerDepSelection.id; + layerDeps[currentLayerDepSelection.id].deps = data.list + } + + /* Clear the current selection */ + layerDepInput.val(""); + currentLayerDepSelection = undefined; + layerDepBtn.attr("disabled","disabled"); + }, null); + }); + + importAndAddBtn.click(function(){ + /* arrray of all layer dep ids includes parent and child deps */ + var allDeps = []; + /* temporary object to use to do a reduce on the dependencies for each + * layer dependency added + */ + var depDeps = {}; + + /* the layers that have dependencies have an extra property "deps" + * look in this for each layer and reduce this to a unquie object + * of deps. + */ + for (var key in layerDeps){ + if (layerDeps[key].hasOwnProperty('deps')){ + for (var dep in layerDeps[key].deps){ + var layer = layerDeps[key].deps[dep]; + depDeps[layer.id] = layer; + } + } + allDeps.push(layerDeps[key].id); + } + + /* we actually want it as an array so convert it now */ + var depDepsArray = []; + for (var key in depDeps) + depDepsArray.push (depDeps[key]); + + if (depDepsArray.length > 0) { + var layer = { name: layerNameInput.val(), url: "#", id: -1 }; + show_layer_deps_modal(ctx.projectId, layer, depDepsArray, function(selected){ + /* Add the accepted dependencies to the allDeps array */ + if (selected.length > 0){ + allDeps.concat (selected); + } + import_and_add (); + }); + } else { + import_and_add (); + } + + function import_and_add () { + /* convert to a csv of all the deps to be added */ + var layerDepsCsv = allDeps.join(","); + + var layerData = { + name: layerNameInput.val(), + vcs_url: vcsURLInput.val(), + git_ref: gitRefInput.val(), + summary: $("#layer-summary").val(), + dir_path: $("#layer-subdir").val(), + project_id: ctx.projectId, + layer_deps: layerDepsCsv, + }; + + $.ajax({ + type: "POST", + url: ctx.xhrImportLayerUrl, + data: layerData, + headers: { 'X-CSRFToken' : $.cookie('csrftoken')}, + success: function (data) { + if (data.error != "ok") { + show_error_message(data, layerData); + console.log(data.error); + } else { + /* Success layer import now go to the project page */ + window.location.replace(ctx.projectPageUrl+'#/layerimported='+layerData.name); + } + }, + error: function (data) { + console.log("Call failed"); + console.log(data); + } + }); + } + }); + + function show_error_message(error, layerData) { + + var errorMsg = $("#import-error").fadeIn(); + var errorType = error.error; + var body = errorMsg.children("span"); + var title = errorMsg.children("h3"); + var optionsList = errorMsg.children("ul"); + var invalidLayerRevision = $("#invalid-layer-revision-hint"); + var layerRevisionCtrl = $("#layer-revision-ctrl"); + + /* remove any existing items */ + optionsList.children().each(function(){ $(this).remove(); }); + body.text(""); + title.text(""); + invalidLayerRevision.hide(); + layerNameCtrl.removeClass("error"); + layerRevisionCtrl.removeClass("error"); + + switch (errorType){ + case 'hint-layer-version-exists': + title.text("This layer already exists"); + body.html("A layer <strong>"+layerData.name+"</strong> already exists with this Git repository URL and this revision. You can:"); + optionsList.append("<li>Import <strong>"+layerData.name+"</strong> with a different revision </li>"); + optionsList.append("<li>or <a href=\""+ctx.layerDetailsUrl+error.existing_layer_version+"/\" >change the revision of the existing layer</a></li>"); + + layerRevisionCtrl.addClass("error"); + + invalidLayerRevision.html("A layer <strong>"+layerData.name+"</strong> already exists with this revision.<br />You can import <strong>"+layerData.name+"</strong> with a different revision"); + invalidLayerRevision.show(); + break; + + case 'hint-layer-exists-with-different-url': + title.text("This layer already exists"); + body.html("A layer <strong>"+layerData.name+"</strong> already exists with a different Git repository URL:<br /><br />"+error.current_url+"<br /><br />You Can:"); + optionsList.append("<li>Import the layer under a different name</li>"); + optionsList.append("<li>or <a href=\""+ctx.layerDetailsUrl+error.current_id+"/\" >change the Git repository URL of the existing layer</a></li>"); + duplicatedLayerName.html("A layer <strong>"+layerData.name+"</strong> already exists with a different Git repository URL.<br />To import this layer give it a different name."); + duplicatedLayerName.show(); + layerNameCtrl.addClass("error"); + break; + + case 'hint-layer-exists': + title.text("This layer already exists"); + body.html("A layer <strong>"+layerData.name+"</strong> already exists: You Can:"); + optionsList.append("<li>Import the layer under a different name</li>"); + break; + default: + title.text("Error") + body.text(data.error); + } + } + + function enable_import_btn (enabled) { + var importAndAddHint = $("#import-and-add-hint"); + + if (enabled) { + importAndAddBtn.removeAttr("disabled"); + importAndAddHint.hide(); + return; + } + + importAndAddBtn.attr("disabled", "disabled"); + importAndAddHint.show(); + } + + function check_form() { + var valid = false; + var inputs = $("input:required"); + + for (var i=0; i<inputs.length; i++){ + if (!(valid = inputs[i].value)){ + enable_import_btn(false); + break; + } + } + + if (valid) + enable_import_btn(true); + } + + vcsURLInput.keyup(function() { + check_form(); + }); + + gitRefInput.keyup(function() { + check_form(); + }); + + layerNameInput.keyup(function() { + if ($(this).val() && !validLayerName.test($(this).val())){ + layerNameCtrl.addClass("error") + $("#invalid-layer-name-hint").show(); + enable_import_btn(false); + return; + } + + /* Don't remove the error class if we're displaying the error for another + * reason. + */ + if (!duplicatedLayerName.is(":visible")) + layerNameCtrl.removeClass("error") + + $("#invalid-layer-name-hint").hide(); + check_form(); + }); + + /* Have a guess at the layer name */ + vcsURLInput.focusout(function (){ + /* If we a layer name specified don't overwrite it or if there isn't a + * url typed in yet return + */ + if (layerNameInput.val() || !$(this).val()) + return; + + if ($(this).val().search("/")){ + var urlPts = $(this).val().split("/"); + var suggestion = urlPts[urlPts.length-1].replace(".git",""); + layerNameInput.val(suggestion); + } + }); + +}
lib/toaster/toastergui/static/js/projectapp.js +6 -0
@@ -571,6 +571,12 @@ projectApp.controller('prjCtrl', function($scope, $modal, $http, $interval, $loc "\">select targets</a> you want to build.", "alert-success"); }); + _cmdExecuteWithParam("/layerimported", function (layer) { + $scope.displayAlert($scope.zone2alerts, + "You have imported <strong>" + layer + + "</strong> and added it to your project.", "alert-success"); + }); + _cmdExecuteWithParam("/targetbuild=", function (targets) { var oldTargetName = $scope.targetName; $scope.targetName = targets.split(",").join(" ");
lib/toaster/toastergui/templates/importlayer.html +78 -31
146 lines not on this page · raw message
lib/toaster/toastergui/templates/layers_dep_modal.html new file +68 -0
68 lines not on this page · raw message
lib/toaster/toastergui/urls.py +2 -0
@@ -76,6 +76,7 @@ urlpatterns = patterns('toastergui.views', url(r'^layers/$', 'layers', name='layers'), url(r'^layer/(?P<layerid>\d+)/$', 'layerdetails', name='layerdetails'), + url(r'^layer/$', 'layerdetails', name='layerdetails'), url(r'^targets/$', 'targets', name='targets'), url(r'^machines/$', 'machines', name='machines'),
@@ -92,6 +93,7 @@ urlpatterns = patterns('toastergui.views', url(r'^xhr_projectedit/(?P<pid>\d+)/$', 'xhr_projectedit', name='xhr_projectedit'), url(r'^xhr_datatypeahead/$', 'xhr_datatypeahead', name='xhr_datatypeahead'), + url(r'^xhr_importlayer/$', 'xhr_importlayer', name='xhr_importlayer'), # default redirection
lib/toaster/toastergui/views.py +102 -5
137 lines not on this page · raw message
Signature · 1 line
1.9.1
PATCH v2 12/18 toaster: tweaking the import layers form
In reply to Alex DAMIANFrom: Belen Barros Pena <[email protected]>
This commit makes the following changes to the import layers form:
* In the explanatory text at the top of the page, show only the release description (to match the information shown in all other pages)
* Make sure the spacing between form fields and labels is consistent across the form
* Change the 'Branch, tag or commit' label to 'Revision' and add some help text to the label
* Change the help text and the input type in the layer description
* Change the help text next to the form actions to match the new field ordering in the form
--- lib/toaster/toastergui/templates/importlayer.html | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-)
lib/toaster/toastergui/templates/importlayer.html +4 -4
@@ -36,7 +36,7 @@ {% include "layers_dep_modal.html" %} <form> {% if project %} - <span class="help-block" style="padding-left:19px;">The layer you are importing must be compatible with {{project.release.name}} ({{project.release.description}}), which is the release you are using in this project.</span> + <span class="help-block" style="padding-left:19px;">The layer you are importing must be compatible with <strong>{{project.release.description}}</strong>, which is the release you are using in this project.</span> {% endif %} <fieldset class="air"> <legend>Layer repository information</legend>
@@ -60,7 +60,7 @@ </div> - <label> + <label for="layer-git-repo-url" class="project-form"> Git repository URL <span class="icon-question-sign get-help" title="Fetch/clone URL of the repository. Currently, Toaster only supports Git repositories." /> </label>
@@ -85,9 +85,9 @@ <label class="project-form" for="layer-description">Layer description <span class="muted">(optional)</span> - <span class="icon-question-sign get-help" title="Short description for for the layer" /> + <span class="icon-question-sign get-help" title="A short layer explanation" /> </label> - <input id="layer-description" type="text" class="input-xxlarge" /> + <textarea id="layer-description" class="input-xxlarge"></textarea> </fieldset> <fieldset class="air">
Signature · 1 line
1.9.1
PATCH v2 13/18 toaster: Importlayer add notify exactly which layers changed
In reply to Alex DAMIANFrom: Michael Wood <[email protected]>
This changes when the dependencies are added to the project so that we can know which ones were successfully added by waiting for the server to respond with a list. This is more reliable because we may have specified dependencies which are already in the project.
To pass this information to the project page a temporary cookie is used with the values for the notification.
--- lib/toaster/toastergui/static/js/importlayer.js | 19 +++++++-- lib/toaster/toastergui/static/js/projectapp.js | 27 ++++++++++-- .../toastergui/templates/layers_dep_modal.html | 48 ++++++++++++++++------ lib/toaster/toastergui/views.py | 11 +++-- 4 files changed, 82 insertions(+), 23 deletions(-)
lib/toaster/toastergui/static/js/importlayer.js +16 -3
@@ -78,8 +78,14 @@ function importLayerPageInit (ctx) { }); importAndAddBtn.click(function(){ + /* This is a list of the names from layerDeps for the layer deps + * modal dialog body + */ + var depNames = []; + /* arrray of all layer dep ids includes parent and child deps */ var allDeps = []; + /* temporary object to use to do a reduce on the dependencies for each * layer dependency added */
@@ -96,6 +102,7 @@ function importLayerPageInit (ctx) { depDeps[layer.id] = layer; } } + depNames.push(layerDeps[key].name); allDeps.push(layerDeps[key].id); }
@@ -106,10 +113,14 @@ function importLayerPageInit (ctx) { if (depDepsArray.length > 0) { var layer = { name: layerNameInput.val(), url: "#", id: -1 }; - show_layer_deps_modal(ctx.projectId, layer, depDepsArray, function(selected){ + var title = "Layer"; + 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>"; + + show_layer_deps_modal(ctx.projectId, layer, depDepsArray, title, body, false, function(selected){ /* Add the accepted dependencies to the allDeps array */ if (selected.length > 0){ - allDeps.concat (selected); + allDeps = allDeps.concat (selected); } import_and_add (); });
@@ -141,8 +152,10 @@ function importLayerPageInit (ctx) { show_error_message(data, layerData); console.log(data.error); } else { + layerData.layersAdded = data.layers_added; /* Success layer import now go to the project page */ - window.location.replace(ctx.projectPageUrl+'#/layerimported='+layerData.name); + $.cookie('layer-imported-alert', JSON.stringify(layerData), { path: '/'}); + window.location.replace(ctx.projectPageUrl+'#/layerimported'); } }, error: function (data) {
lib/toaster/toastergui/static/js/projectapp.js +23 -4
@@ -128,7 +128,7 @@ projectApp.filter('timediff', function() { // main controller for the project page -projectApp.controller('prjCtrl', function($scope, $modal, $http, $interval, $location, $cookies, $q, $sce, $anchorScroll, $animate, $sanitize) { +projectApp.controller('prjCtrl', function($scope, $modal, $http, $interval, $location, $cookies, $cookieStore, $q, $sce, $anchorScroll, $animate, $sanitize) { $scope.getSuggestions = function(type, currentValue) { var deffered = $q.defer();
@@ -572,9 +572,28 @@ projectApp.controller('prjCtrl', function($scope, $modal, $http, $interval, $loc }); _cmdExecuteWithParam("/layerimported", function (layer) { - $scope.displayAlert($scope.zone2alerts, - "You have imported <strong>" + layer + - "</strong> and added it to your project.", "alert-success"); + var imported = $cookieStore.get("layer-imported-alert"); + var text; + + if (!imported) + return; + + if (imported.layersAdded.length == 0) { + text = "You have imported <strong>"+imported.name+ + "</strong> and added it to your project."; + } else { + text = "You have imported <strong>"+imported.name+ + "</strong> and added <strong>"+imported.layersAdded.length+ + "</strong> layers to your project. <strong>"+ + imported.layersAdded.join(", ")+"</strong>"; + } + + $scope.displayAlert($scope.zone2alerts, text, "alert-info"); + + // This doesn't work + $cookieStore.remove("layer-imported-alert"); + //use jquery plugin instead + $.removeCookie("layer-imported-alert", { path: "/"}); }); _cmdExecuteWithParam("/targetbuild=", function (targets) {
lib/toaster/toastergui/templates/layers_dep_modal.html +35 -13
76 lines not on this page · raw message
lib/toaster/toastergui/views.py +8 -3
@@ -2253,6 +2253,8 @@ if toastermain.settings.MANAGED: not request.POST.has_key('project_id')): return HttpResponse(jsonfilter({"error": "Missing parameters; requires vcs_url, name, git_ref and project_id"}), content_type = "application/json") + layers_added = []; + # Rudimentary check for any possible html tags if "<" in request.POST: return HttpResponse(jsonfilter({"error": "Invalid character <"}), content_type = "application/json")
@@ -2315,9 +2317,12 @@ if toastermain.settings.MANAGED: # if the project now contains the exact # dependency already (like modified on another page) try: - ProjectLayer.objects.get_or_create(layercommit=layer_dep_obj, project=prj) + prj_layer, prj_layer_created = ProjectLayer.objects.get_or_create(layercommit=layer_dep_obj, project=prj) except: - pass + continue + + if prj_layer_created: + layers_added.append(Layer.objects.get(id=layer_dep_obj.layer_id).name) # If an old layer version exists in our project then remove it
@@ -2337,7 +2342,7 @@ if toastermain.settings.MANAGED: return HttpResponse(jsonfilter({"error": "Uncaught error: Could not create layer version"}), content_type = "application/json") - return HttpResponse(jsonfilter({"error": "ok"}), content_type = "application/json") + return HttpResponse(jsonfilter({"error": "ok", "layers_added": layers_added}), content_type = "application/json")
Signature · 1 line
1.9.1
PATCH v2 14/18 toaster: importlayer Avoid namespace clash with layer name
In reply to Alex DAMIANFrom: Michael Wood <[email protected]>
This was causing validation issues as the layer name was being picked up from a different location in the dom.
--- lib/toaster/toastergui/static/js/importlayer.js | 2 +- lib/toaster/toastergui/templates/importlayer.html | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-)
lib/toaster/toastergui/static/js/importlayer.js +1 -1
@@ -4,7 +4,7 @@ function importLayerPageInit (ctx) { var layerDepBtn = $("#add-layer-dependency-btn"); var importAndAddBtn = $("#import-and-add-btn"); - var layerNameInput = $("#layer-name"); + var layerNameInput = $("#import-layer-name"); var vcsURLInput = $("#layer-git-repo-url"); var gitRefInput = $("#layer-git-ref"); var layerDepInput = $("#layer-dependency");
lib/toaster/toastergui/templates/importlayer.html +2 -2
@@ -48,12 +48,12 @@ </div> <div class="control-group" id="layer-name-ctrl"> - <label class="control-label" for="layer-name"> + <label class="control-label" for="import-layer-name"> Layer name <span class="icon-question-sign get-help" title="Something like 'meta-mylayer'. Your layer name must be unique and can only include letters, numbers and dashes" /> </label> <div class="controls"> - <input id="layer-name" type="text" required autofocus> + <input id="import-layer-name" type="text" required autofocus> <span class="help-inline" style="display: none;" id="invalid-layer-name-hint">A valid layer name can only include letters, numbers and dashes</span> <span class="help-inline" style="display: none;" id="duplicated-layer-name-hint"></span> </div>
Signature · 1 line
1.9.1
PATCH v2 15/18 toaster: projectapp Add links to the imported layers notify
In reply to Alex DAMIANFrom: Michael Wood <[email protected]>
Adds links to layer details for each of the layers that has been added to the project.
--- lib/toaster/toastergui/static/js/importlayer.js | 3 +-- lib/toaster/toastergui/static/js/projectapp.js | 29 ++++++++++++++++++------- lib/toaster/toastergui/templates/project.html | 3 ++- lib/toaster/toastergui/views.py | 4 ++-- 4 files changed, 26 insertions(+), 13 deletions(-)
lib/toaster/toastergui/static/js/importlayer.js +1 -2
@@ -152,9 +152,8 @@ function importLayerPageInit (ctx) { show_error_message(data, layerData); console.log(data.error); } else { - layerData.layersAdded = data.layers_added; /* Success layer import now go to the project page */ - $.cookie('layer-imported-alert', JSON.stringify(layerData), { path: '/'}); + $.cookie('layer-imported-alert', JSON.stringify(data), { path: '/'}); window.location.replace(ctx.projectPageUrl+'#/layerimported'); } },
lib/toaster/toastergui/static/js/projectapp.js +21 -8
@@ -578,18 +578,31 @@ projectApp.controller('prjCtrl', function($scope, $modal, $http, $interval, $loc if (!imported) return; - if (imported.layersAdded.length == 0) { - text = "You have imported <strong>"+imported.name+ - "</strong> and added it to your project."; + if (imported.deps_added.length == 0) { + text = "You have imported <strong><a href=\""+$scope.urls.layer+ + imported.imported_layer.id+"\">"+imported.imported_layer.name+ + "</a></strong> and added it to your project."; } else { - text = "You have imported <strong>"+imported.name+ - "</strong> and added <strong>"+imported.layersAdded.length+ - "</strong> layers to your project. <strong>"+ - imported.layersAdded.join(", ")+"</strong>"; + var links = "<a href=\""+$scope.urls.layer+ + imported.imported_layer.id+"\">"+imported.imported_layer.name+ + "</a>, "; + + imported.deps_added.map (function(item, index){ + links +="<a href=\""+$scope.urls.layer+item.id+"\" >"+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 */ + text = "You have imported <strong><a href=\""+$scope.urls.layer+ + imported.imported_layer.id+"\">"+imported.imported_layer.name+ + "</a></strong> and added <strong>"+(imported.deps_added.length+1)+ + "</strong> layers to your project. <strong>"+links+"</strong>"; } $scope.displayAlert($scope.zone2alerts, text, "alert-info"); - // This doesn't work $cookieStore.remove("layer-imported-alert"); //use jquery plugin instead
lib/toaster/toastergui/templates/project.html +2 -1
@@ -382,7 +382,8 @@ angular.element(document).ready(function() { scope.urls.xhr_datatypeahead = "{% url 'xhr_datatypeahead' %}"; scope.urls.layers = "{% url 'layers' %}"; scope.urls.targets = "{% url 'targets' %}"; - scope.urls.importlayer = "{% url 'importlayer'%}" + scope.urls.importlayer = "{% url 'importlayer'%}"; + scope.urls.layer = "{% url 'layerdetails' %}"; scope.project = {{prj|json}}; scope.builds = {{builds|json}}; scope.layers = {{layers|json}};
lib/toaster/toastergui/views.py +2 -2
@@ -2322,7 +2322,7 @@ if toastermain.settings.MANAGED: continue if prj_layer_created: - layers_added.append(Layer.objects.get(id=layer_dep_obj.layer_id).name) + layers_added.append({'id': layer_dep_obj.id, 'name': Layer.objects.get(id=layer_dep_obj.layer_id).name}) # If an old layer version exists in our project then remove it
@@ -2342,7 +2342,7 @@ if toastermain.settings.MANAGED: return HttpResponse(jsonfilter({"error": "Uncaught error: Could not create layer version"}), content_type = "application/json") - return HttpResponse(jsonfilter({"error": "ok", "layers_added": layers_added}), content_type = "application/json") + return HttpResponse(jsonfilter({"error": "ok", "imported_layer" : { "name" : layer.name, "id": layer_version.id }, "deps_added": layers_added }), content_type = "application/json")
Signature · 1 line
1.9.1
PATCH v2 16/18 toaster: Formatting changes to error messages
In reply to Alex DAMIANFrom: Belen Barros Pena <[email protected]>
Just giving a bit of space to the content of the error messages we show when you try to import a layer that already exists.
--- lib/toaster/toastergui/static/js/importlayer.js | 6 +++--- lib/toaster/toastergui/templates/importlayer.html | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-)
lib/toaster/toastergui/static/js/importlayer.js +3 -3
@@ -169,7 +169,7 @@ function importLayerPageInit (ctx) { var errorMsg = $("#import-error").fadeIn(); var errorType = error.error; - var body = errorMsg.children("span"); + var body = errorMsg.children("p"); var title = errorMsg.children("h3"); var optionsList = errorMsg.children("ul"); var invalidLayerRevision = $("#invalid-layer-revision-hint");
@@ -198,7 +198,7 @@ function importLayerPageInit (ctx) { case 'hint-layer-exists-with-different-url': title.text("This layer already exists"); - body.html("A layer <strong>"+layerData.name+"</strong> already exists with a different Git repository URL:<br /><br />"+error.current_url+"<br /><br />You Can:"); + body.html("A layer <strong>"+layerData.name+"</strong> already exists with a different Git repository URL:<p style='margin-top:10px;'><strong>"+error.current_url+"</strong></p><p>You can:</p>"); optionsList.append("<li>Import the layer under a different name</li>"); optionsList.append("<li>or <a href=\""+ctx.layerDetailsUrl+error.current_id+"/\" >change the Git repository URL of the existing layer</a></li>"); duplicatedLayerName.html("A layer <strong>"+layerData.name+"</strong> already exists with a different Git repository URL.<br />To import this layer give it a different name.");
@@ -208,7 +208,7 @@ function importLayerPageInit (ctx) { case 'hint-layer-exists': title.text("This layer already exists"); - body.html("A layer <strong>"+layerData.name+"</strong> already exists: You Can:"); + body.html("A layer <strong>"+layerData.name+"</strong> already exists. You can:"); optionsList.append("<li>Import the layer under a different name</li>"); break; default:
lib/toaster/toastergui/templates/importlayer.html +1 -1
@@ -43,7 +43,7 @@ <div class="alert alert-error" id="import-error" style="display:none"> <button type="button" class="close" data-dismiss="alert">×</button> <h3></h3> - <span></span> + <p></p> <ul></ul> </div>
Signature · 1 line
1.9.1
PATCH v2 17/18 toaster: Change the full stop to a colon
In reply to Alex DAMIANFrom: Belen Barros Pena <[email protected]>
In the import layers notification for multiple layers there was a full stop where there should be a colon.
--- lib/toaster/toastergui/static/js/projectapp.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
lib/toaster/toastergui/static/js/projectapp.js +1 -1
@@ -599,7 +599,7 @@ projectApp.controller('prjCtrl', function($scope, $modal, $http, $interval, $loc text = "You have imported <strong><a href=\""+$scope.urls.layer+ imported.imported_layer.id+"\">"+imported.imported_layer.name+ "</a></strong> and added <strong>"+(imported.deps_added.length+1)+ - "</strong> layers to your project. <strong>"+links+"</strong>"; + "</strong> layers to your project: <strong>"+links+"</strong>"; } $scope.displayAlert($scope.zone2alerts, text, "alert-info");
Signature · 1 line
1.9.1
PATCH v2 18/18 toaster: importlayer Tidy up the page initialisation
In reply to Alex DAMIANFrom: Michael Wood <[email protected]>
Tidy up the page initialisation and change the error logging so that it's consistent with other pages.
--- lib/toaster/toastergui/templates/importlayer.html | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-)
lib/toaster/toastergui/templates/importlayer.html +11 -10
@@ -12,19 +12,20 @@ <script src="{% static 'js/importlayer.js' %}"></script> <script> $(document).ready(function (){ - var ctx = {}; - ctx.xhrDataTypeaheadUrl = "{% url 'xhr_datatypeahead' %}"; - ctx.layerDetailsUrl = "{% url 'layerdetails' %}"; - ctx.xhrImportLayerUrl = "{% url 'xhr_importlayer' %}"; - ctx.xhrEditProjectUrl = "{% url 'xhr_projectedit' project.id %}"; - ctx.projectPageUrl = "{% url 'project' project.id %}"; - ctx.projectId = {{project.id}}; + var ctx = { + xhrDataTypeaheadUrl : "{% url 'xhr_datatypeahead' %}", + layerDetailsUrl : "{% url 'layerdetails' %}", + xhrImportLayerUrl : "{% url 'xhr_importlayer' %}", + xhrEditProjectUrl : "{% url 'xhr_projectedit' project.id %}", + projectPageUrl : "{% url 'project' project.id %}", + projectId : {{project.id}} + }; try { importLayerPageInit(ctx); - } catch(e) { - document.write(e.stack); - console.log(e); + } catch (e) { + document.write("Sorry, An error has occurred loading this page"); + console.warn(e); } }); </script>
Signature · 1 line
1.9.1