* [PATCH 01/18] add build artifacts table and other improvements
2014-12-12 11:45 [PATCH 00/18 v2] Toaster patchset with new features and bugfixes Alex DAMIAN
@ 2014-12-12 11:45 ` Alex DAMIAN
2014-12-12 11:45 ` [PATCH 02/18] toasterui: add extra debug and development infos Alex DAMIAN
` (16 subsequent siblings)
17 siblings, 0 replies; 19+ messages in thread
From: Alex DAMIAN @ 2014-12-12 11:45 UTC (permalink / raw)
To: bitbake-devel; +Cc: Alexandru DAMIAN
From: Alexandru DAMIAN <alexandru.damian@intel.com>
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]
Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
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
diff --git a/lib/bb/ui/buildinfohelper.py b/lib/bb/ui/buildinfohelper.py
index 9aa8fe3..6f4f568 100644
--- a/lib/bb/ui/buildinfohelper.py
+++ b/lib/bb/ui/buildinfohelper.py
@@ -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):
diff --git a/lib/bb/ui/toasterui.py b/lib/bb/ui/toasterui.py
index 3a6104b..9aff489 100644
--- a/lib/bb/ui/toasterui.py
+++ b/lib/bb/ui/toasterui.py
@@ -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):
diff --git a/lib/toaster/bldcontrol/models.py b/lib/toaster/bldcontrol/models.py
index e643d08..cab4463 100644
--- a/lib/toaster/bldcontrol/models.py
+++ b/lib/toaster/bldcontrol/models.py
@@ -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
diff --git a/lib/toaster/orm/migrations/0019_auto__add_buildartifact.py b/lib/toaster/orm/migrations/0019_auto__add_buildartifact.py
new file mode 100644
index 0000000..0dce9ea
--- /dev/null
+++ b/lib/toaster/orm/migrations/0019_auto__add_buildartifact.py
@@ -0,0 +1,342 @@
+# -*- coding: utf-8 -*-
+from south.utils import datetime_utils as datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+
+class Migration(SchemaMigration):
+
+ def forwards(self, orm):
+ # Adding model 'BuildArtifact'
+ db.create_table(u'orm_buildartifact', (
+ (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('build', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['orm.Build'])),
+ ('file_name', self.gf('django.db.models.fields.FilePathField')(max_length=100)),
+ ('file_size', self.gf('django.db.models.fields.IntegerField')()),
+ ))
+ db.send_create_signal(u'orm', ['BuildArtifact'])
+
+
+ def backwards(self, orm):
+ # Deleting model 'BuildArtifact'
+ db.delete_table(u'orm_buildartifact')
+
+
+ models = {
+ u'orm.bitbakeversion': {
+ 'Meta': {'object_name': 'BitbakeVersion'},
+ 'branch': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
+ 'dirpath': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'giturl': ('django.db.models.fields.URLField', [], {'max_length': '200'}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '32'})
+ },
+ u'orm.branch': {
+ 'Meta': {'unique_together': "(('layer_source', 'name'), ('layer_source', 'up_id'))", 'object_name': 'Branch'},
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'layer_source': ('django.db.models.fields.related.ForeignKey', [], {'default': 'True', 'to': u"orm['orm.LayerSource']", 'null': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
+ 'short_description': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}),
+ 'up_date': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}),
+ 'up_id': ('django.db.models.fields.IntegerField', [], {'default': 'None', 'null': 'True'})
+ },
+ u'orm.build': {
+ 'Meta': {'object_name': 'Build'},
+ 'bitbake_version': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
+ 'build_name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'completed_on': ('django.db.models.fields.DateTimeField', [], {}),
+ 'cooker_log_path': ('django.db.models.fields.CharField', [], {'max_length': '500'}),
+ 'distro': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'distro_version': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'errors_no': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'machine': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'outcome': ('django.db.models.fields.IntegerField', [], {'default': '2'}),
+ 'project': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Project']", 'null': 'True'}),
+ 'started_on': ('django.db.models.fields.DateTimeField', [], {}),
+ 'timespent': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'warnings_no': ('django.db.models.fields.IntegerField', [], {'default': '0'})
+ },
+ u'orm.buildartifact': {
+ 'Meta': {'object_name': 'BuildArtifact'},
+ 'build': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Build']"}),
+ 'file_name': ('django.db.models.fields.FilePathField', [], {'max_length': '100'}),
+ 'file_size': ('django.db.models.fields.IntegerField', [], {}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
+ },
+ u'orm.helptext': {
+ 'Meta': {'object_name': 'HelpText'},
+ 'area': ('django.db.models.fields.IntegerField', [], {}),
+ 'build': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'helptext_build'", 'to': u"orm['orm.Build']"}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'key': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'text': ('django.db.models.fields.TextField', [], {})
+ },
+ u'orm.layer': {
+ 'Meta': {'unique_together': "(('layer_source', 'up_id'), ('layer_source', 'name'))", 'object_name': 'Layer'},
+ 'description': ('django.db.models.fields.TextField', [], {'default': 'None', 'null': 'True'}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'layer_index_url': ('django.db.models.fields.URLField', [], {'max_length': '200'}),
+ 'layer_source': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['orm.LayerSource']", 'null': 'True'}),
+ 'local_path': ('django.db.models.fields.FilePathField', [], {'default': 'None', 'max_length': '255', 'null': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'summary': ('django.db.models.fields.TextField', [], {'default': 'None', 'null': 'True'}),
+ 'up_date': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}),
+ 'up_id': ('django.db.models.fields.IntegerField', [], {'default': 'None', 'null': 'True'}),
+ 'vcs_url': ('django.db.models.fields.URLField', [], {'default': 'None', 'max_length': '200', 'null': 'True'}),
+ 'vcs_web_file_base_url': ('django.db.models.fields.URLField', [], {'default': 'None', 'max_length': '200', 'null': 'True'}),
+ 'vcs_web_tree_base_url': ('django.db.models.fields.URLField', [], {'default': 'None', 'max_length': '200', 'null': 'True'}),
+ 'vcs_web_url': ('django.db.models.fields.URLField', [], {'default': 'None', 'max_length': '200', 'null': 'True'})
+ },
+ u'orm.layer_version': {
+ 'Meta': {'unique_together': "(('layer_source', 'up_id'),)", 'object_name': 'Layer_Version'},
+ 'branch': ('django.db.models.fields.CharField', [], {'max_length': '80'}),
+ 'build': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'layer_version_build'", 'null': 'True', 'to': u"orm['orm.Build']"}),
+ 'commit': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'dirpath': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '255', 'null': 'True'}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'layer': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'layer_version_layer'", 'to': u"orm['orm.Layer']"}),
+ 'layer_source': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['orm.LayerSource']", 'null': 'True'}),
+ 'priority': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'project': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['orm.Project']", 'null': 'True'}),
+ 'up_branch': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['orm.Branch']", 'null': 'True'}),
+ 'up_date': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}),
+ 'up_id': ('django.db.models.fields.IntegerField', [], {'default': 'None', 'null': 'True'})
+ },
+ u'orm.layersource': {
+ 'Meta': {'unique_together': "(('sourcetype', 'apiurl'),)", 'object_name': 'LayerSource'},
+ 'apiurl': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '255', 'null': 'True'}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '63'}),
+ 'sourcetype': ('django.db.models.fields.IntegerField', [], {})
+ },
+ u'orm.layerversiondependency': {
+ 'Meta': {'unique_together': "(('layer_source', 'up_id'),)", 'object_name': 'LayerVersionDependency'},
+ 'depends_on': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'dependees'", 'to': u"orm['orm.Layer_Version']"}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'layer_source': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['orm.LayerSource']", 'null': 'True'}),
+ 'layer_version': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'dependencies'", 'to': u"orm['orm.Layer_Version']"}),
+ 'up_id': ('django.db.models.fields.IntegerField', [], {'default': 'None', 'null': 'True'})
+ },
+ u'orm.logmessage': {
+ 'Meta': {'object_name': 'LogMessage'},
+ 'build': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Build']"}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'level': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'lineno': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
+ 'message': ('django.db.models.fields.CharField', [], {'max_length': '240'}),
+ 'pathname': ('django.db.models.fields.FilePathField', [], {'max_length': '255', 'blank': 'True'}),
+ 'task': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Task']", 'null': 'True', 'blank': 'True'})
+ },
+ u'orm.machine': {
+ 'Meta': {'unique_together': "(('layer_source', 'up_id'),)", 'object_name': 'Machine'},
+ 'description': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'layer_source': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['orm.LayerSource']", 'null': 'True'}),
+ 'layer_version': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Layer_Version']"}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'up_date': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}),
+ 'up_id': ('django.db.models.fields.IntegerField', [], {'default': 'None', 'null': 'True'})
+ },
+ u'orm.package': {
+ 'Meta': {'object_name': 'Package'},
+ 'build': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Build']"}),
+ 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'installed_name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '100'}),
+ 'installed_size': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'license': ('django.db.models.fields.CharField', [], {'max_length': '80', 'blank': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'recipe': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Recipe']", 'null': 'True'}),
+ 'revision': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'}),
+ 'section': ('django.db.models.fields.CharField', [], {'max_length': '80', 'blank': 'True'}),
+ 'size': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'summary': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'version': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'})
+ },
+ u'orm.package_dependency': {
+ 'Meta': {'object_name': 'Package_Dependency'},
+ 'dep_type': ('django.db.models.fields.IntegerField', [], {}),
+ 'depends_on': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'package_dependencies_target'", 'to': u"orm['orm.Package']"}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'package': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'package_dependencies_source'", 'to': u"orm['orm.Package']"}),
+ 'target': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Target']", 'null': 'True'})
+ },
+ u'orm.package_file': {
+ 'Meta': {'object_name': 'Package_File'},
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'package': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'buildfilelist_package'", 'to': u"orm['orm.Package']"}),
+ 'path': ('django.db.models.fields.FilePathField', [], {'max_length': '255', 'blank': 'True'}),
+ 'size': ('django.db.models.fields.IntegerField', [], {})
+ },
+ u'orm.project': {
+ 'Meta': {'object_name': 'Project'},
+ 'bitbake_version': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.BitbakeVersion']"}),
+ 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'release': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Release']"}),
+ 'short_description': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}),
+ 'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
+ 'user_id': ('django.db.models.fields.IntegerField', [], {'null': 'True'})
+ },
+ u'orm.projectlayer': {
+ 'Meta': {'unique_together': "(('project', 'layercommit'),)", 'object_name': 'ProjectLayer'},
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'layercommit': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Layer_Version']", 'null': 'True'}),
+ 'optional': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'project': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Project']"})
+ },
+ u'orm.projecttarget': {
+ 'Meta': {'object_name': 'ProjectTarget'},
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'project': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Project']"}),
+ 'target': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'task': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True'})
+ },
+ u'orm.projectvariable': {
+ 'Meta': {'object_name': 'ProjectVariable'},
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'project': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Project']"}),
+ 'value': ('django.db.models.fields.TextField', [], {'blank': 'True'})
+ },
+ u'orm.recipe': {
+ 'Meta': {'unique_together': "(('layer_version', 'file_path'),)", 'object_name': 'Recipe'},
+ 'bugtracker': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}),
+ 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'file_path': ('django.db.models.fields.FilePathField', [], {'max_length': '255'}),
+ 'homepage': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'layer_source': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['orm.LayerSource']", 'null': 'True'}),
+ 'layer_version': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'recipe_layer_version'", 'to': u"orm['orm.Layer_Version']"}),
+ 'license': ('django.db.models.fields.CharField', [], {'max_length': '200', 'blank': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
+ 'section': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
+ 'summary': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'up_date': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}),
+ 'up_id': ('django.db.models.fields.IntegerField', [], {'default': 'None', 'null': 'True'}),
+ 'version': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'})
+ },
+ u'orm.recipe_dependency': {
+ 'Meta': {'object_name': 'Recipe_Dependency'},
+ 'dep_type': ('django.db.models.fields.IntegerField', [], {}),
+ 'depends_on': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'r_dependencies_depends'", 'to': u"orm['orm.Recipe']"}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'recipe': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'r_dependencies_recipe'", 'to': u"orm['orm.Recipe']"})
+ },
+ u'orm.release': {
+ 'Meta': {'object_name': 'Release'},
+ 'bitbake_version': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.BitbakeVersion']"}),
+ 'branch_name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '50'}),
+ 'description': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'helptext': ('django.db.models.fields.TextField', [], {'null': 'True'}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '32'})
+ },
+ u'orm.releasedefaultlayer': {
+ 'Meta': {'object_name': 'ReleaseDefaultLayer'},
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'layer_name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '100'}),
+ 'release': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Release']"})
+ },
+ u'orm.releaselayersourcepriority': {
+ 'Meta': {'unique_together': "(('release', 'layer_source'),)", 'object_name': 'ReleaseLayerSourcePriority'},
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'layer_source': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.LayerSource']"}),
+ 'priority': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'release': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Release']"})
+ },
+ u'orm.target': {
+ 'Meta': {'object_name': 'Target'},
+ 'build': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Build']"}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'image_size': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'is_image': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'license_manifest_path': ('django.db.models.fields.CharField', [], {'max_length': '500', 'null': 'True'}),
+ 'target': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+ },
+ u'orm.target_file': {
+ 'Meta': {'object_name': 'Target_File'},
+ 'directory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'directory_set'", 'null': 'True', 'to': u"orm['orm.Target_File']"}),
+ 'group': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'inodetype': ('django.db.models.fields.IntegerField', [], {}),
+ 'owner': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'path': ('django.db.models.fields.FilePathField', [], {'max_length': '100'}),
+ 'permission': ('django.db.models.fields.CharField', [], {'max_length': '16'}),
+ 'size': ('django.db.models.fields.IntegerField', [], {}),
+ 'sym_target': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'symlink_set'", 'null': 'True', 'to': u"orm['orm.Target_File']"}),
+ 'target': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Target']"})
+ },
+ u'orm.target_image_file': {
+ 'Meta': {'object_name': 'Target_Image_File'},
+ 'file_name': ('django.db.models.fields.FilePathField', [], {'max_length': '254'}),
+ 'file_size': ('django.db.models.fields.IntegerField', [], {}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'target': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Target']"})
+ },
+ u'orm.target_installed_package': {
+ 'Meta': {'object_name': 'Target_Installed_Package'},
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'package': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'buildtargetlist_package'", 'to': u"orm['orm.Package']"}),
+ 'target': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Target']"})
+ },
+ u'orm.task': {
+ 'Meta': {'ordering': "('order', 'recipe')", 'unique_together': "(('build', 'recipe', 'task_name'),)", 'object_name': 'Task'},
+ 'build': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'task_build'", 'to': u"orm['orm.Build']"}),
+ 'cpu_usage': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '6', 'decimal_places': '2'}),
+ 'disk_io': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
+ 'elapsed_time': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '6', 'decimal_places': '2'}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'line_number': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'logfile': ('django.db.models.fields.FilePathField', [], {'max_length': '255', 'blank': 'True'}),
+ 'message': ('django.db.models.fields.CharField', [], {'max_length': '240'}),
+ 'order': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
+ 'outcome': ('django.db.models.fields.IntegerField', [], {'default': '-1'}),
+ 'path_to_sstate_obj': ('django.db.models.fields.FilePathField', [], {'max_length': '500', 'blank': 'True'}),
+ 'recipe': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'build_recipe'", 'to': u"orm['orm.Recipe']"}),
+ 'script_type': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'source_url': ('django.db.models.fields.FilePathField', [], {'max_length': '255', 'blank': 'True'}),
+ 'sstate_checksum': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
+ 'sstate_result': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'task_executed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'task_name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'work_directory': ('django.db.models.fields.FilePathField', [], {'max_length': '255', 'blank': 'True'})
+ },
+ u'orm.task_dependency': {
+ 'Meta': {'object_name': 'Task_Dependency'},
+ 'depends_on': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'task_dependencies_depends'", 'to': u"orm['orm.Task']"}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'task': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'task_dependencies_task'", 'to': u"orm['orm.Task']"})
+ },
+ u'orm.toastersetting': {
+ 'Meta': {'object_name': 'ToasterSetting'},
+ 'helptext': ('django.db.models.fields.TextField', [], {}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '63'}),
+ 'value': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+ },
+ u'orm.variable': {
+ 'Meta': {'object_name': 'Variable'},
+ 'build': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'variable_build'", 'to': u"orm['orm.Build']"}),
+ 'changed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'human_readable_name': ('django.db.models.fields.CharField', [], {'max_length': '200'}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'variable_name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'variable_value': ('django.db.models.fields.TextField', [], {'blank': 'True'})
+ },
+ u'orm.variablehistory': {
+ 'Meta': {'object_name': 'VariableHistory'},
+ 'file_name': ('django.db.models.fields.FilePathField', [], {'max_length': '255'}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'line_number': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
+ 'operation': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
+ 'value': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'variable': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'vhistory'", 'to': u"orm['orm.Variable']"})
+ }
+ }
+
+ complete_apps = ['orm']
\ No newline at end of file
diff --git a/lib/toaster/orm/models.py b/lib/toaster/orm/models.py
index 34d3754..f5c600b 100644
--- a/lib/toaster/orm/models.py
+++ b/lib/toaster/orm/models.py
@@ -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")
--
1.9.1
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [PATCH 02/18] toasterui: add extra debug and development infos
2014-12-12 11:45 [PATCH 00/18 v2] Toaster patchset with new features and bugfixes Alex DAMIAN
2014-12-12 11:45 ` [PATCH 01/18] add build artifacts table and other improvements Alex DAMIAN
@ 2014-12-12 11:45 ` Alex DAMIAN
2014-12-12 11:45 ` [PATCH 03/18] add option to write offline event log file Alex DAMIAN
` (15 subsequent siblings)
17 siblings, 0 replies; 19+ messages in thread
From: Alex DAMIAN @ 2014-12-12 11:45 UTC (permalink / raw)
To: bitbake-devel; +Cc: Alexandru DAMIAN
From: Alexandru DAMIAN <alexandru.damian@intel.com>
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.
Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
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(-)
diff --git a/lib/bb/ui/buildinfohelper.py b/lib/bb/ui/buildinfohelper.py
index 6f4f568..533f4ce 100644
--- a/lib/bb/ui/buildinfohelper.py
+++ b/lib/bb/ui/buildinfohelper.py
@@ -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 = {}
diff --git a/lib/bb/ui/toasterui.py b/lib/bb/ui/toasterui.py
index 9aff489..7a316be 100644
--- a/lib/bb/ui/toasterui.py
+++ b/lib/bb/ui/toasterui.py
@@ -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
diff --git a/lib/toaster/bldcontrol/localhostbecontroller.py b/lib/toaster/bldcontrol/localhostbecontroller.py
index ebf2b4f..22d31e3 100644
--- a/lib/toaster/bldcontrol/localhostbecontroller.py
+++ b/lib/toaster/bldcontrol/localhostbecontroller.py
@@ -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
diff --git a/lib/toaster/bldcontrol/management/commands/checksettings.py b/lib/toaster/bldcontrol/management/commands/checksettings.py
index 96d2d51..55f118c 100644
--- a/lib/toaster/bldcontrol/management/commands/checksettings.py
+++ b/lib/toaster/bldcontrol/management/commands/checksettings.py
@@ -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
diff --git a/lib/toaster/bldcontrol/management/commands/runbuilds.py b/lib/toaster/bldcontrol/management/commands/runbuilds.py
index 5e253e0..56c989c 100644
--- a/lib/toaster/bldcontrol/management/commands/runbuilds.py
+++ b/lib/toaster/bldcontrol/management/commands/runbuilds.py
@@ -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
diff --git a/lib/toaster/toastermain/settings.py b/lib/toaster/toastermain/settings.py
index 0974b90..acc20cc 100644
--- a/lib/toaster/toastermain/settings.py
+++ b/lib/toaster/toastermain/settings.py
@@ -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',
diff --git a/lib/toaster/toastermain/urls.py b/lib/toaster/toastermain/urls.py
index 549fda1..a2916e2 100644
--- a/lib/toaster/toastermain/urls.py
+++ b/lib/toaster/toastermain/urls.py
@@ -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')))
--
1.9.1
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [PATCH 03/18] add option to write offline event log file
2014-12-12 11:45 [PATCH 00/18 v2] Toaster patchset with new features and bugfixes Alex DAMIAN
2014-12-12 11:45 ` [PATCH 01/18] add build artifacts table and other improvements Alex DAMIAN
2014-12-12 11:45 ` [PATCH 02/18] toasterui: add extra debug and development infos Alex DAMIAN
@ 2014-12-12 11:45 ` Alex DAMIAN
2014-12-12 11:45 ` [PATCH 04/18] toastergui: implement UI changes to allow file download Alex DAMIAN
` (14 subsequent siblings)
17 siblings, 0 replies; 19+ messages in thread
From: Alex DAMIAN @ 2014-12-12 11:45 UTC (permalink / raw)
To: bitbake-devel; +Cc: Alexandru DAMIAN
From: Alexandru DAMIAN <alexandru.damian@intel.com>
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.
Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
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
diff --git a/bin/bitbake b/bin/bitbake
index 7f8449c..d46c3dd 100755
--- a/bin/bitbake
+++ b/bin/bitbake
@@ -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()
diff --git a/bin/toaster-eventreplay b/bin/toaster-eventreplay
new file mode 100755
index 0000000..624829a
--- /dev/null
+++ b/bin/toaster-eventreplay
@@ -0,0 +1,179 @@
+#!/usr/bin/env python
+# ex:ts=4:sw=4:sts=4:et
+# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
+#
+# Copyright (C) 2014 Alex Damian
+#
+# This file re-uses code spread throughout other Bitbake source files.
+# As such, all other copyrights belong to their own right holders.
+#
+#
+# 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.
+
+
+# This command takes a filename as a single parameter. The filename is read
+# as a build eventlog, and the ToasterUI is used to process events in the file
+# and log data in the database
+
+import os
+import sys, logging
+
+# mangle syspath to allow easy import of modules
+sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
+ 'lib'))
+
+
+import bb.cooker
+from bb.ui import toasterui
+import sys
+import logging
+
+logger = logging.getLogger(__name__)
+console = logging.StreamHandler(sys.stdout)
+format_str = "%(levelname)s: %(message)s"
+logging.basicConfig(format=format_str)
+
+
+import json, pickle
+
+
+class FileReadEventsServerConnection():
+ """ Emulates a connection to a bitbake server that feeds
+ events coming actually read from a saved log file.
+ """
+
+ class MockConnection():
+ """ fill-in for the proxy to the server. we just return generic data
+ """
+ def __init__(self, sc):
+ self._sc = sc
+
+ def runCommand(self, commandArray):
+ """ emulates running a command on the server; only read-only commands are accepted """
+ command_name = commandArray[0]
+
+ if command_name == "getVariable":
+ if commandArray[1] in self._sc._variables:
+ return (self._sc._variables[commandArray[1]]['v'], None)
+ return (None, "Missing variable")
+
+ elif command_name == "getAllKeysWithFlags":
+ dump = {}
+ flaglist = commandArray[1]
+ for k in self._sc._variables.keys():
+ try:
+ if not k.startswith("__"):
+ v = self._sc._variables[k]['v']
+ dump[k] = {
+ 'v' : v ,
+ 'history' : self._sc._variables[k]['history'],
+ }
+ for d in flaglist:
+ dump[k][d] = self._sc._variables[k][d]
+ except Exception as e:
+ print(e)
+ return (dump, None)
+ else:
+ raise Exception("Command %s not implemented" % commandArray[0])
+
+ def terminateServer(self):
+ """ do not do anything """
+ pass
+
+
+
+ class EventReader():
+ def __init__(self, sc):
+ self._sc = sc
+ self.firstraise = 0
+
+ def _create_event(self, line):
+ def _import_class(name):
+ assert len(name) > 0
+ assert "." in name, name
+
+ components = name.strip().split(".")
+ modulename = ".".join(components[:-1])
+ moduleklass = components[-1]
+
+ module = __import__(modulename, fromlist=[str(moduleklass)])
+ return getattr(module, moduleklass)
+
+ # we build a toaster event out of current event log line
+ try:
+ event_data = json.loads(line.strip())
+ event_class = _import_class(event_data['class'])
+ event_object = pickle.loads(json.loads(event_data['vars']))
+ except ValueError as e:
+ print("Failed loading ", line)
+ raise e
+
+ if not isinstance(event_object, event_class):
+ raise Exception("Error loading objects %s class %s ", event_object, event_class)
+
+ return event_object
+
+ def waitEvent(self, timeout):
+
+ nextline = self._sc._eventfile.readline()
+ if len(nextline) == 0:
+ # the build data ended, while toasterui still waits for events.
+ # this happens when the server was abruptly stopped, so we simulate this
+ self.firstraise += 1
+ if self.firstraise == 1:
+ raise KeyboardInterrupt()
+ else:
+ return None
+ else:
+ self._sc.lineno += 1
+ return self._create_event(nextline)
+
+
+ def _readVariables(self, variableline):
+ self._variables = json.loads(variableline.strip())['allvariables']
+
+
+ def __init__(self, file_name):
+ self.connection = FileReadEventsServerConnection.MockConnection(self)
+ self._eventfile = open(file_name, "r")
+
+ # we expect to have the variable dump at the start of the file
+ self.lineno = 1
+ self._readVariables(self._eventfile.readline())
+
+ self.events = FileReadEventsServerConnection.EventReader(self)
+
+
+
+
+
+class MockConfigParameters():
+ """ stand-in for cookerdata.ConfigParameters; as we don't really config a cooker, this
+ serves just to supply needed interfaces for the toaster ui to work """
+ def __init__(self):
+ self.observe_only = True # we can only read files
+
+
+# run toaster ui on our mock bitbake class
+if __name__ == "__main__":
+ if len(sys.argv) < 2:
+ logger.error("Usage: %s event.log " % sys.argv[0])
+ sys.exit(1)
+
+ file_name = sys.argv[-1]
+ mock_connection = FileReadEventsServerConnection(file_name)
+ configParams = MockConfigParameters()
+
+ # run the main program
+ toasterui.main(mock_connection.connection, mock_connection.events, configParams)
diff --git a/lib/bb/cooker.py b/lib/bb/cooker.py
index df9a0ca..16fd4ad 100644
--- a/lib/bb/cooker.py
+++ b/lib/bb/cooker.py
@@ -205,6 +205,75 @@ class BBCooker:
self.data = self.databuilder.data
self.data_hash = self.databuilder.data_hash
+
+ # we log all events to a file if so directed
+ if self.configuration.writeeventlog:
+ import json, pickle
+ DEFAULT_EVENTFILE = self.configuration.writeeventlog
+ class EventLogWriteHandler():
+
+ class EventWriter():
+ def __init__(self, cooker):
+ self.file_inited = None
+ self.cooker = cooker
+ self.event_queue = []
+
+ def init_file(self):
+ try:
+ # delete the old log
+ os.remove(DEFAULT_EVENTFILE)
+ except:
+ pass
+
+ # write current configuration data
+ with open(DEFAULT_EVENTFILE, "w") as f:
+ f.write("%s\n" % json.dumps({ "allvariables" : self.cooker.getAllKeysWithFlags(["doc", "func"])}))
+
+ def write_event(self, event):
+ with open(DEFAULT_EVENTFILE, "a") as f:
+ try:
+ f.write("%s\n" % json.dumps({"class":event.__module__ + "." + event.__class__.__name__, "vars":json.dumps(pickle.dumps(event)) }))
+ except Exception as e:
+ import traceback
+ print(e, traceback.format_exc(e))
+
+
+ def send(self, event):
+ event_class = event.__module__ + "." + event.__class__.__name__
+
+ # init on bb.event.BuildStarted
+ if self.file_inited is None:
+ if event_class == "bb.event.BuildStarted":
+ self.init_file()
+ self.file_inited = True
+
+ # write pending events
+ for e in self.event_queue:
+ self.write_event(e)
+
+ # also write the current event
+ self.write_event(event)
+
+ else:
+ # queue all events until the file is inited
+ self.event_queue.append(event)
+
+ else:
+ # we have the file, just write the event
+ self.write_event(event)
+
+ # set our handler's event processor
+ event = EventWriter(self) # self is the cooker here
+
+
+ # set up cooker features for this mock UI handler
+
+ # we need to write the dependency tree in the log
+ self.featureset.setFeature(CookerFeatures.SEND_DEPENDS_TREE)
+ # register the log file writer as UI Handler
+ bb.event.register_UIHhandler(EventLogWriteHandler())
+
+
#
# Special updated configuration we use for firing events
#
@@ -505,7 +574,7 @@ class BBCooker:
taskdata, runlist, pkgs_to_build = self.buildTaskData(pkgs_to_build, task, False)
return runlist, taskdata
-
+
######## WARNING : this function requires cache_extra to be enabled ########
def generateTaskDepTreeData(self, pkgs_to_build, task):
@@ -1550,10 +1619,10 @@ class CookerCollectFiles(object):
for p in pkgfns:
realfn, cls = bb.cache.Cache.virtualfn2realfn(p)
priorities[p] = self.calc_bbfile_priority(realfn, matched)
-
+
# Don't show the warning if the BBFILE_PATTERN did match .bbappend files
unmatched = set()
- for _, _, regex, pri in self.bbfile_config_priorities:
+ for _, _, regex, pri in self.bbfile_config_priorities:
if not regex in matched:
unmatched.add(regex)
diff --git a/lib/bb/cookerdata.py b/lib/bb/cookerdata.py
index 470d538..2ceed2d 100644
--- a/lib/bb/cookerdata.py
+++ b/lib/bb/cookerdata.py
@@ -139,6 +139,7 @@ class CookerConfiguration(object):
self.dry_run = False
self.tracking = False
self.interface = []
+ self.writeeventlog = False
self.env = {}
diff --git a/lib/bb/ui/buildinfohelper.py b/lib/bb/ui/buildinfohelper.py
index 533f4ce..f825b57 100644
--- a/lib/bb/ui/buildinfohelper.py
+++ b/lib/bb/ui/buildinfohelper.py
@@ -556,7 +556,6 @@ class ORMWrapper(object):
assert isinstance(build_obj, Build)
helptext_objects = []
-
for k in vardump:
desc = vardump[k]['doc']
if desc is None:
@@ -667,9 +666,11 @@ class BuildInfoHelper(object):
if (path.startswith(bl.layer.local_path)):
return bl
- #TODO: if we get here, we didn't read layers correctly
- assert False
- return None
+ #if we get here, we didn't read layers correctly; mockup the new layer
+ unknown_layer, created = Layer.objects.get_or_create(name="unknown", local_path="/", layer_index_url="")
+ unknown_layer_version_obj, created = Layer_Version.objects.get_or_create(layer = unknown_layer, build = self.internal_state['build'])
+
+ return unknown_layer_version_obj
def _get_recipe_information_from_taskfile(self, taskfile):
localfilepath = taskfile.split(":")[-1]
@@ -732,7 +733,6 @@ class BuildInfoHelper(object):
def store_started_build(self, event):
assert '_pkgs' in vars(event)
- assert 'lvs' in self.internal_state, "Layer version information not found; Check if the bitbake server was configured to inherit toaster.bbclass."
build_information = self._get_build_information()
build_obj = self.orm_wrapper.create_build_object(build_information, self.brbe)
@@ -740,10 +740,13 @@ class BuildInfoHelper(object):
self.internal_state['build'] = build_obj
# save layer version information for this build
- for layer_obj in self.internal_state['lvs']:
- self.orm_wrapper.get_update_layer_version_object(build_obj, layer_obj, self.internal_state['lvs'][layer_obj])
+ if not 'lvs' in self.internal_state:
+ logger.error("Layer version information not found; Check if the bitbake server was configured to inherit toaster.bbclass.")
+ else:
+ for layer_obj in self.internal_state['lvs']:
+ self.orm_wrapper.get_update_layer_version_object(build_obj, layer_obj, self.internal_state['lvs'][layer_obj])
- del self.internal_state['lvs']
+ del self.internal_state['lvs']
# create target information
target_information = {}
@@ -753,7 +756,8 @@ class BuildInfoHelper(object):
self.internal_state['targets'] = self.orm_wrapper.create_target_objects(target_information)
# Save build configuration
- self.orm_wrapper.save_build_variables(build_obj, self.server.runCommand(["getAllKeysWithFlags", ["doc", "func"]])[0])
+ data = self.server.runCommand(["getAllKeysWithFlags", ["doc", "func"]])[0]
+ self.orm_wrapper.save_build_variables(build_obj, [])
return self.brbe
@@ -980,14 +984,29 @@ class BuildInfoHelper(object):
recipe_info = {}
recipe_info['name'] = pn
- recipe_info['version'] = event._depgraph['pn'][pn]['version'].lstrip(":")
recipe_info['layer_version'] = layer_version_obj
- recipe_info['summary'] = event._depgraph['pn'][pn]['summary']
- recipe_info['license'] = event._depgraph['pn'][pn]['license']
- recipe_info['description'] = event._depgraph['pn'][pn]['description']
- recipe_info['section'] = event._depgraph['pn'][pn]['section']
- recipe_info['homepage'] = event._depgraph['pn'][pn]['homepage']
- recipe_info['bugtracker'] = event._depgraph['pn'][pn]['bugtracker']
+
+ if 'version' in event._depgraph['pn'][pn]:
+ recipe_info['version'] = event._depgraph['pn'][pn]['version'].lstrip(":")
+
+ if 'summary' in event._depgraph['pn'][pn]:
+ recipe_info['summary'] = event._depgraph['pn'][pn]['summary']
+
+ if 'license' in event._depgraph['pn'][pn]:
+ recipe_info['license'] = event._depgraph['pn'][pn]['license']
+
+ if 'description' in event._depgraph['pn'][pn]:
+ recipe_info['description'] = event._depgraph['pn'][pn]['description']
+
+ if 'section' in event._depgraph['pn'][pn]:
+ recipe_info['section'] = event._depgraph['pn'][pn]['section']
+
+ if 'homepage' in event._depgraph['pn'][pn]:
+ recipe_info['homepage'] = event._depgraph['pn'][pn]['homepage']
+
+ if 'bugtracker' in event._depgraph['pn'][pn]:
+ recipe_info['bugtracker'] = event._depgraph['pn'][pn]['bugtracker']
+
recipe_info['file_path'] = file_name
recipe = self.orm_wrapper.get_update_recipe_object(recipe_info)
recipe.is_image = False
@@ -1146,4 +1165,4 @@ class BuildInfoHelper(object):
if 'backlog' in self.internal_state:
for event in self.internal_state['backlog']:
- print "NOTE: Unsaved log: ", event.msg
+ logger.error("Unsaved log: %s", event.msg)
diff --git a/lib/bb/ui/toasterui.py b/lib/bb/ui/toasterui.py
index 7a316be..a85ad5a 100644
--- a/lib/bb/ui/toasterui.py
+++ b/lib/bb/ui/toasterui.py
@@ -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
--
1.9.1
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [PATCH 04/18] toastergui: implement UI changes to allow file download
2014-12-12 11:45 [PATCH 00/18 v2] Toaster patchset with new features and bugfixes Alex DAMIAN
` (2 preceding siblings ...)
2014-12-12 11:45 ` [PATCH 03/18] add option to write offline event log file Alex DAMIAN
@ 2014-12-12 11:45 ` Alex DAMIAN
2014-12-12 11:45 ` [PATCH 05/18] add POST endpoint for uploading eventlog files Alex DAMIAN
` (13 subsequent siblings)
17 siblings, 0 replies; 19+ messages in thread
From: Alex DAMIAN @ 2014-12-12 11:45 UTC (permalink / raw)
To: bitbake-devel; +Cc: Alexandru DAMIAN
From: Alexandru DAMIAN <alexandru.damian@intel.com>
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]
Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
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(-)
diff --git a/lib/toaster/toastergui/templates/base.html b/lib/toaster/toastergui/templates/base.html
index 594c495..bc7a0ee 100644
--- a/lib/toaster/toastergui/templates/base.html
+++ b/lib/toaster/toastergui/templates/base.html
@@ -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>
diff --git a/lib/toaster/toastergui/templates/build.html b/lib/toaster/toastergui/templates/build.html
index f20f041..e71e38f 100644
--- a/lib/toaster/toastergui/templates/build.html
+++ b/lib/toaster/toastergui/templates/build.html
@@ -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>
diff --git a/lib/toaster/toastergui/templates/builddashboard.html b/lib/toaster/toastergui/templates/builddashboard.html
index e682094..2458cdb 100644
--- a/lib/toaster/toastergui/templates/builddashboard.html
+++ b/lib/toaster/toastergui/templates/builddashboard.html
@@ -42,9 +42,9 @@
{% if build.toaster_exceptions.count > 0 %}
<div class="row">
<small class="pull-right">
- <i class="icon-question-sign get-help get-help-blue" title="" data-original-title="Toaster exceptions do not affect your build: only the operation of Toaster"></i>
- <a class="show-exceptions" href="#exceptions">Toaster threw {{build.toaster_exceptions.count}} exception{{build.toaster_exceptions.count|pluralize}}</a>
- </small>
+ <i class="icon-question-sign get-help get-help-blue" title="" data-original-title="Toaster exceptions do not affect your build: only the operation of Toaster"></i>
+ <a class="show-exceptions" href="#exceptions">Toaster threw {{build.toaster_exceptions.count}} exception{{build.toaster_exceptions.count|pluralize}}</a>
+ </small>
</div>
{% endif %}
</div>
@@ -54,6 +54,9 @@
<div class="accordion span10 pull-right" id="errors">
<div class="accordion-group">
<div class="accordion-heading">
+ {% if MANAGED and build.project %}
+ <a class="btn btn-large pull-right" href="{% url 'build_artifact' build.id "cookerlog" build.id %}" style="margin:15px;">Download build log</a>
+ {% endif %}
<a class="accordion-toggle error toggle-errors">
<h2 id="error-toggle">
<i class="icon-minus-sign"></i>
@@ -66,13 +69,10 @@
<div class="span10">
{% for error in logmessages %}{% if error.level == 2 %}
<div class="alert alert-error">
- {% if MANAGED and error.pathname %}
- <pre><a href="{% url 'build_artifact' build.pk 'logmessagefile' error.pk %}" target="_blanc">{{error.message}}</pre>
- {% else %}
<pre>{{error.message}}</pre>
- {% endif %}
</div>
- {% endif %}{% endfor %}
+ {% endif %}
+ {% endfor %}
</div>
</div>
</div>
@@ -84,21 +84,21 @@
<!-- built images -->
{% if hasImages %}
<div class="row-fluid span10 pull-right">
- <h2>Images</h2>
- {% for target in targets %}
+ <h2>Images</h2>
+ {% for target in targets %}
{% if target.target.is_image %}
- <div class="well dashboard-section">
- <h3><a href="{% url 'target' build.pk target.target.pk %}">{{target.target}}</a>
+ <div class="well dashboard-section">
+ <h3><a href="{% url 'target' build.pk target.target.pk %}">{{target.target}}</a>
</h3>
- <dl class="dl-horizontal">
- <dt>Packages included</dt>
- <dd><a href="{% url 'target' build.pk target.target.pk %}">{{target.npkg}}</a></dd>
- <dt>Total package size</dt>
- <dd>{{target.pkgsz|filtered_filesizeformat}}</dd>
+ <dl class="dl-horizontal">
+ <dt>Packages included</dt>
+ <dd><a href="{% url 'target' build.pk target.target.pk %}">{{target.npkg}}</a></dd>
+ <dt>Total package size</dt>
+ <dd>{{target.pkgsz|filtered_filesizeformat}}</dd>
{% if target.targetHasNoImages %}
- </dl>
- <div class="row-fluid">
- <div class="alert alert-info span7">
+ </dl>
+ <div class="row-fluid">
+ <div class="alert alert-info span7">
<p>
<b>This build did not create any image files</b>
</p>
@@ -111,30 +111,45 @@
license manifest information</a> in Toaster.
</p>
</div>
- </div>
+ </div>
{% else %}
- <dt>
- <i class="icon-question-sign get-help" title="The location in disk of the license manifest, a document listing all packages installed in your image and their licenses"></i>
- <a href="{% url 'targetpkg' build.pk target.target.pk %}">License manifest</a>
- </dt>
- <dd><code>{{target.target.license_manifest_path}}</code></dd>
- <dt>
- <i class="icon-question-sign get-help" title="Image files are stored in <code>/build/tmp/deploy/images/</code>"></i>
- Image files
- </dt>
- <dd>
- <ul>
+ <dt>
+ <i class="icon-question-sign get-help" title="The location in disk of the license manifest, a document listing all packages installed in your image and their licenses"></i>
+
+ {% if MANAGED and build.project %}
+ License manifest
+ {% else %}
+ <a href="{% url 'targetpkg' build.pk target.target.pk %}">License manifest</a>
+ {% endif %}
+ </dt>
+ {% if MANAGED and build.project %}
+ <dd>
+ <a href="{% url 'targetpkg' build.pk target.target.pk %}">View in Toaster</a> |
+ <a href="{% url 'build_artifact' build.pk 'licensemanifest' target.target.pk %}">Download</a></dd>
+ {% else %}
+ <dd><code>{{target.target.license_manifest_path}}</code></dd>
+ {% endif %}
+ <dt>
+ <i class="icon-question-sign get-help" title="Image files are stored in <code>/build/tmp/deploy/images/</code>"></i>
+ Image files
+ </dt>
+ <dd>
+ <ul>
{% for i in target.imageFiles %}
- <li>{{i.path}}
- ({{i.size|filtered_filesizeformat}})</li>
+ {% if build.project %}
+ <li><a href="{% url 'build_artifact' build.pk 'imagefile' i.id %}">{{i.path}}</a>
+ {% else %}
+ <li>{{i.path}}
+ {% endif %}
+ ({{i.size|filtered_filesizeformat}})</li>
{% endfor %}
- </ul>
- </dd>
- </dl>
+ </ul>
+ </dd>
+ </dl>
{% endif %}
- </div>
+ </div>
{% endif %}
- {% endfor %}
+ {% endfor %}
</div>
{% endif %}
@@ -142,6 +157,35 @@
<!-- error dump -->
{%endif%}
+<!-- other artifacts -->
+{% if build.buildartifact_set.all.count > 0 %}
+<div class="row-fluid span10 pull-right">
+<h2>Other artifacts</h2>
+
+ <div class="well dashboard-section">
+ <dl class="dl-horizontal">
+ <dt>
+ <i class="icon-question-sign get-help" title="Build artifacts discovered in <i>tmp/deploy/images</i>. Usually kernel images and kernel modules."></i>
+ Other artifacts</dt>
+ <dd><div>
+ {% for ba in build.buildartifact_set.all|dictsort:"file_name" %}
+ {% if MANAGED and build.project %}
+ <a href="{%url 'build_artifact' build.id 'buildartifact' ba.id %}">
+ {% endif %}
+ {{ba.get_local_file_name}}
+ {% if MANAGED and build.project %}
+ </a>
+ {% endif %}
+
+ ({{ba.file_size|filtered_filesizeformat}}) <br/>
+ {% endfor %}
+ </div>
+ </dd>
+
+ </div>
+
+</div>
+{% endif %}
<!-- build summary -->
<div class="row-fluid span10 pull-right">
<h2>Build summary</h2>
@@ -158,12 +202,19 @@
<dl>
{% query build.task_build outcome=4 order__gt=0 as exectask%}
{% if exectask.count > 0 %}
- <dt>Failed tasks</td>
+ <dt>Failed tasks</dt>
<dd>
{% if exectask.count == 1 %}
<a class="error" href="{% url "task" build.id exectask.0.id %}">
{{exectask.0.recipe.name}}
<span class="task-name">{{exectask.0.task_name}}</span>
+
+ {% 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 %}
+
</a>
{% elif exectask.count > 1%}
<a class="error" href="{% url "tasks" build.id %}?filter=outcome%3A4">{{exectask.count}}</a>
diff --git a/lib/toaster/toastergui/templates/configuration.html b/lib/toaster/toastergui/templates/configuration.html
index 49a6a89..d3b34a2 100644
--- a/lib/toaster/toastergui/templates/configuration.html
+++ b/lib/toaster/toastergui/templates/configuration.html
@@ -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>
diff --git a/lib/toaster/toastergui/templates/package_detail_base.html b/lib/toaster/toastergui/templates/package_detail_base.html
index cd015d3..dfeba55 100644
--- a/lib/toaster/toastergui/templates/package_detail_base.html
+++ b/lib/toaster/toastergui/templates/package_detail_base.html
@@ -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 %}
diff --git a/lib/toaster/toastergui/templates/recipe.html b/lib/toaster/toastergui/templates/recipe.html
index a830ba9..b20c65e 100644
--- a/lib/toaster/toastergui/templates/recipe.html
+++ b/lib/toaster/toastergui/templates/recipe.html
@@ -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>
diff --git a/lib/toaster/toastergui/templates/recipes.html b/lib/toaster/toastergui/templates/recipes.html
index 791a487..889e676 100644
--- a/lib/toaster/toastergui/templates/recipes.html
+++ b/lib/toaster/toastergui/templates/recipes.html
@@ -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 %}
diff --git a/lib/toaster/toastergui/templates/target.html b/lib/toaster/toastergui/templates/target.html
index c879c39d..1309b52 100644
--- a/lib/toaster/toastergui/templates/target.html
+++ b/lib/toaster/toastergui/templates/target.html
@@ -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 %}
diff --git a/lib/toaster/toastergui/templates/task.html b/lib/toaster/toastergui/templates/task.html
index 1b27042..09fd25b 100644
--- a/lib/toaster/toastergui/templates/task.html
+++ b/lib/toaster/toastergui/templates/task.html
@@ -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
diff --git a/lib/toaster/toastergui/templates/tasks.html b/lib/toaster/toastergui/templates/tasks.html
index d0c6f4e..4cbcc5e 100644
--- a/lib/toaster/toastergui/templates/tasks.html
+++ b/lib/toaster/toastergui/templates/tasks.html
@@ -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 %}
diff --git a/lib/toaster/toastergui/views.py b/lib/toaster/toastergui/views.py
index 8301f6c..736de78 100755
--- a/lib/toaster/toastergui/views.py
+++ b/lib/toaster/toastergui/views.py
@@ -27,7 +27,7 @@ from django.db import IntegrityError
from django.shortcuts import render, redirect
from orm.models import Build, Target, Task, Layer, Layer_Version, Recipe, LogMessage, Variable
from orm.models import Task_Dependency, Recipe_Dependency, Package, Package_File, Package_Dependency
-from orm.models import Target_Installed_Package, Target_File, Target_Image_File
+from orm.models import Target_Installed_Package, Target_File, Target_Image_File, BuildArtifact
from django.views.decorators.cache import cache_control
from django.core.urlresolvers import reverse
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
@@ -421,30 +421,36 @@ def builds(request):
'ordericon':_get_toggle_order_icon(request, "timespent"),
'orderkey' : 'timespent',
},
- {'name': 'Log',
+ {'name': 'Image files', 'clclass': 'output',
+ 'qhelp': "The root file system types produced by the build. You can find them in your <code>/build/tmp/deploy/images/</code> directory",
+ # TODO: compute image fstypes from Target_Image_File
+ },
+ ]
+ }
+
+ if not toastermain.settings.MANAGED:
+ context['tablecols'].insert(-2,
+ {'name': 'Log1',
'dclass': "span4",
'qhelp': "Path to the build main log file",
'clclass': 'log', 'hidden': 1,
'orderfield': _get_toggle_order(request, "cooker_log_path"),
'ordericon':_get_toggle_order_icon(request, "cooker_log_path"),
'orderkey' : 'cooker_log_path',
- },
- {'name': 'Output', 'clclass': 'output',
- 'qhelp': "The root file system types produced by the build. You can find them in your <code>/build/tmp/deploy/images/</code> directory",
- # TODO: compute image fstypes from Target_Image_File
- },
- ]
- }
+ }
+ )
+
if toastermain.settings.MANAGED:
context['tablecols'].append(
- {'name': 'Project', 'clclass': 'project',
- 'filter': {'class': 'project',
+ {'name': 'Project', 'clclass': 'project',
+ 'filter': {'class': 'project',
'label': 'Project:',
'options': map(lambda x: (x.name,'',x.build_set.filter(outcome__lt=Build.IN_PROGRESS).count()), Project.objects.all()),
}
- })
+ }
+ )
response = render(request, template, context)
@@ -481,12 +487,8 @@ def builddashboard( request, build_id ):
hasImages = True
npkg = 0
pkgsz = 0
- pid= 0
- tp = Target_Installed_Package.objects.filter( target_id = t.id )
package = None
- for p in tp:
- pid = p.package_id
- package = Package.objects.get( pk = p.package_id )
+ for package in Package.objects.filter(id__in = [x.package_id for x in t.target_installed_package_set.all()]):
pkgsz = pkgsz + package.size
if ( package.installed_name ):
npkg = npkg + 1
@@ -499,7 +501,7 @@ def builddashboard( request, build_id ):
if ( ndx < 0 ):
ndx = 0;
f = i.file_name[ ndx + 1: ]
- imageFiles.append({ 'path': f, 'size' : i.file_size })
+ imageFiles.append({ 'id': i.id, 'path': f, 'size' : i.file_size })
if ( t.is_image and
(( len( imageFiles ) <= 0 ) or ( len( t.license_manifest_path ) <= 0 ))):
targetHasNoImages = True
@@ -517,6 +519,8 @@ def builddashboard( request, build_id ):
if ( p.installed_name ):
packageCount = packageCount + 1
+ logmessages = list(LogMessage.objects.filter( build = build_id ))
+
context = {
'build' : build,
'hasImages' : hasImages,
@@ -524,7 +528,7 @@ def builddashboard( request, build_id ):
'targets' : targets,
'recipecount' : recipeCount,
'packagecount' : packageCount,
- 'logmessages' : LogMessage.objects.filter( build = build_id ),
+ 'logmessages' : logmessages,
}
return render( request, template, context )
@@ -637,6 +641,9 @@ def target_common( request, build_id, target_id, variant ):
Package, queryset, filter_string, search_term, ordering_string, 'name' )
packages = _build_page_range( Paginator(queryset, pagesize), request.GET.get( 'page', 1 ))
+
+ build = Build.objects.get( pk = build_id )
+
# bring in package dependencies
for p in packages.object_list:
p.runtime_dependencies = p.package_dependencies_source.filter(
@@ -697,8 +704,7 @@ eans multiple licenses exist that cover different parts of the source',
tc_dependencies[ "hidden" ] = 1
tc_rdependencies = {
'name' : 'Reverse dependencies',
- 'qhelp' : 'Package run-time reverse dependencies (i.e. which other packages depend on t\
-his package',
+ 'qhelp' : 'Package run-time reverse dependencies (i.e. which other packages depend on this package',
'clclass' : 'brought_in_by',
}
if ( variant == 'target' ):
@@ -741,18 +747,10 @@ his package',
'clclass' : 'layer_commit',
'hidden' : 1,
}
- tc_layerDir = {
- 'name':'Layer directory',
- 'qhelp':'Location in disk of the layer providing the recipe that builds the package',
- 'orderfield' : _get_toggle_order( request, "recipe__layer_version__layer__local_path" ),
- 'ordericon' : _get_toggle_order_icon( request, "recipe__layer_version__layer__local_path" )\
-,
- 'clclass' : 'layer_directory',
- 'hidden' : 1,
- }
+
context = {
'objectname': variant,
- 'build' : Build.objects.filter( pk = build_id )[ 0 ],
+ 'build' : build,
'target' : Target.objects.filter( pk = target_id )[ 0 ],
'objects' : packages,
'packages_sum' : packages_sum[ 'installed_size__sum' ],
@@ -771,10 +769,21 @@ his package',
tc_layer,
tc_layerBranch,
tc_layerCommit,
- tc_layerDir,
]
}
+ if not toastermain.settings.MANAGED or build.project is None:
+
+ tc_layerDir = {
+ 'name':'Layer directory',
+ 'qhelp':'Location in disk of the layer providing the recipe that builds the package',
+ 'orderfield' : _get_toggle_order( request, "recipe__layer_version__layer__local_path" ),
+ 'ordericon' : _get_toggle_order_icon( request, "recipe__layer_version__layer__local_path" ),
+ 'clclass' : 'layer_directory',
+ 'hidden' : 1,
+ }
+ context['tablecols'].append(tc_layerDir)
+
response = render(request, template, context)
_save_parameters_cookies(response, pagesize, orderby, request)
return response
@@ -1136,12 +1145,13 @@ def tasks_common(request, build_id, variant, task_anchor):
}
if 'diskio' == variant: tc_diskio['hidden']='0'; del tc_diskio['clclass']; tc_cache['hidden']='1';
+ build = Build.objects.get(pk=build_id)
context = { 'objectname': variant,
'object_search_display': object_search_display,
'filter_search_display': filter_search_display,
'title': title_variant,
- 'build': Build.objects.get(pk=build_id),
+ 'build': build,
'objects': tasks,
'default_orderby' : orderby,
'search_term': search_term,
@@ -1157,9 +1167,12 @@ def tasks_common(request, build_id, variant, task_anchor):
tc_time,
tc_cpu,
tc_diskio,
- tc_log,
]}
+
+ if not toastermain.settings.MANAGED or build.project is None:
+ context['tablecols'].append(tc_log)
+
response = render(request, template, context)
_save_parameters_cookies(response, pagesize, orderby, request)
return response
@@ -1206,9 +1219,11 @@ def recipes(request, build_id):
revlist.append(recipe_dep)
revs[recipe.id] = revlist
+ build = Build.objects.get(pk=build_id)
+
context = {
'objectname': 'recipes',
- 'build': Build.objects.get(pk=build_id),
+ 'build': build,
'objects': recipes,
'default_orderby' : 'name:+',
'recipe_deps' : deps,
@@ -1279,6 +1294,11 @@ def recipes(request, build_id):
'qhelp':'The Git commit of the layer providing the recipe',
'clclass': 'layer_version__layer__commit', 'hidden': 1,
},
+ ]
+ }
+
+ if not toastermain.settings.MANAGED or build.project is None:
+ context['tablecols'].append(
{
'name':'Layer directory',
'qhelp':'Path to the layer prodiving the recipe',
@@ -1286,9 +1306,8 @@ def recipes(request, build_id):
'ordericon':_get_toggle_order_icon(request, "layer_version__layer__local_path"),
'orderkey' : 'layer_version__layer__local_path',
'clclass': 'layer_version__layer__local_path', 'hidden': 1,
- },
- ]
- }
+ })
+
response = render(request, template, context)
_save_parameters_cookies(response, pagesize, orderby, request)
@@ -2685,41 +2704,53 @@ if toastermain.settings.MANAGED:
return render(request, template, context)
+ def _file_name_for_artifact(b, artifact_type, artifact_id):
+ file_name = None
+ # Target_Image_File file_name
+ if artifact_type == "imagefile":
+ file_name = Target_Image_File.objects.get(target__build = b, pk = artifact_id).file_name
- def build_artifact(request, build_id, artifact_type, artifact_id):
- try:
- b = Build.objects.get(pk=build_id)
- if b.buildrequest is None or b.buildrequest.environment is None:
- raise Exception("Cannot download file")
+ elif artifact_type == "cookerlog":
+ file_name = b.cooker_log_path
+
+ elif artifact_type == "buildartifact":
+ file_name = BuildArtifact.objects.get(build = b, pk = artifact_id).file_name
- file_name = None
- fsock = None
- content_type='application/force-download'
- # Target_Image_File file_name
- # Task logfile
- if artifact_type == "tasklogfile":
- file_name = Task.objects.get(build = b, pk = artifact_id).logfile
+ elif artifact_type == "licensemanifest":
+ file_name = Target.objects.get(build = b, pk = artifact_id).license_manifest_path
- # Task path_to_sstate_obj
- # Package_File path
- # Recipe file_path
- # VariableHistory file_name
- # LogMessage pathname
- if artifact_type == "logmessagefile":
- file_name = LogMessage.objects.get(build = b, pk = artifact_id).pathname
+ elif artifact_type == "tasklogfile":
+ file_name = Task.objects.get(build = b, pk = artifact_id).logfile
- if file_name is not None:
- content_type = b.buildrequest.environment.get_artifact_type(file_name)
- fsock = b.buildrequest.environment.get_artifact(file_name)
- file_name = os.path.basename(file_name)
+ elif artifact_type == "logmessagefile":
+ file_name = LogMessage.objects.get(build = b, pk = artifact_id).pathname
+ else:
+ raise Exception("FIXME: artifact type %s not implemented" % (artifact_type))
- response = HttpResponse(fsock, content_type = content_type)
+ return file_name
- # returns a file from the environment
- response['Content-Disposition'] = 'attachment; filename=' + file_name
- return response
- except:
- raise
+
+ def build_artifact(request, build_id, artifact_type, artifact_id):
+ b = Build.objects.get(pk=build_id)
+ if b.buildrequest is None or b.buildrequest.environment is None:
+ raise Exception("Artifact not available for download (missing build request or build environment)")
+
+ file_name = _file_name_for_artifact(b, artifact_type, artifact_id)
+ fsock = None
+ content_type='application/force-download'
+
+ if file_name is None:
+ raise Exception("Could not handle artifact %s id %s" % (artifact_type, artifact_id))
+ else:
+ content_type = b.buildrequest.environment.get_artifact_type(file_name)
+ fsock = b.buildrequest.environment.get_artifact(file_name)
+ file_name = os.path.basename(file_name) # we assume that the build environment system has the same path conventions as host
+
+ response = HttpResponse(fsock, content_type = content_type)
+
+ # returns a file from the environment
+ response['Content-Disposition'] = 'attachment; filename=' + file_name
+ return response
@@ -2856,3 +2887,6 @@ else:
def projects(request):
raise Exception("page not available in interactive mode")
+
+ def xhr_importlayer(request):
+ raise Exception("page not available in interactive mode")
--
1.9.1
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [PATCH 05/18] add POST endpoint for uploading eventlog files
2014-12-12 11:45 [PATCH 00/18 v2] Toaster patchset with new features and bugfixes Alex DAMIAN
` (3 preceding siblings ...)
2014-12-12 11:45 ` [PATCH 04/18] toastergui: implement UI changes to allow file download Alex DAMIAN
@ 2014-12-12 11:45 ` Alex DAMIAN
2014-12-12 11:45 ` [PATCH 06/18] toaster: base Only show change project icon when > one project Alex DAMIAN
` (12 subsequent siblings)
17 siblings, 0 replies; 19+ messages in thread
From: Alex DAMIAN @ 2014-12-12 11:45 UTC (permalink / raw)
To: bitbake-devel; +Cc: Alexandru DAMIAN
From: Alexandru DAMIAN <alexandru.damian@intel.com>
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.
Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
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
diff --git a/lib/toaster/orm/urls.py b/lib/toaster/orm/urls.py
new file mode 100644
index 0000000..961bc19
--- /dev/null
+++ b/lib/toaster/orm/urls.py
@@ -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'),
+ )
+
diff --git a/lib/toaster/orm/views.py b/lib/toaster/orm/views.py
new file mode 100644
index 0000000..97d792b
--- /dev/null
+++ b/lib/toaster/orm/views.py
@@ -0,0 +1,60 @@
+#
+# 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.views.decorators.cache import cache_control
+from django.core.urlresolvers import reverse
+from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
+from django.http import HttpResponseBadRequest, HttpResponse
+from django.utils import timezone
+from django.utils.html import escape
+from datetime import timedelta
+from django.utils import formats
+from toastergui.templatetags.projecttags import json as jsonfilter
+import json
+import os
+import tempfile
+import subprocess
+import toastermain
+from django.views.decorators.csrf import csrf_exempt
+
+
+@csrf_exempt
+def eventfile(request):
+ """ Receives a file by POST, and runs toaster-eventreply on this file """
+ if request.method != "POST":
+ return HttpResponseBadRequest("This API only accepts POST requests. Post a file with:\n\ncurl -F eventlog=@bitbake_eventlog.json http[s]://[server-address]/orm/eventfile\n", content_type="text/plain;utf8")
+
+ # write temporary file
+ (handle, abstemppath) = tempfile.mkstemp(dir="/tmp/")
+ with os.fdopen(handle, "w") as tmpfile:
+ for chunk in request.FILES['eventlog'].chunks():
+ tmpfile.write(chunk)
+ tmpfile.close()
+
+ # compute the path to "bitbake/bin/toaster-eventreplay"
+ from os.path import dirname as DN
+ import_script = os.path.join(DN(DN(DN(DN(os.path.abspath(__file__))))), "bin/toaster-eventreplay")
+ if not os.path.exists(import_script):
+ raise Exception("script missing %s" % import_script)
+ scriptenv = os.environ.copy()
+ scriptenv["DATABASE_URL"] = toastermain.settings.getDATABASE_URL()
+
+ # run the data loading process and return the results
+ (out, err) = subprocess.Popen([import_script, abstemppath], stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=scriptenv).communicate()
+ os.remove(abstemppath)
+ return HttpResponse("%s\n%s" % (out, err), content_type="text/plain;utf8")
--
1.9.1
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [PATCH 06/18] toaster: base Only show change project icon when > one project
2014-12-12 11:45 [PATCH 00/18 v2] Toaster patchset with new features and bugfixes Alex DAMIAN
` (4 preceding siblings ...)
2014-12-12 11:45 ` [PATCH 05/18] add POST endpoint for uploading eventlog files Alex DAMIAN
@ 2014-12-12 11:45 ` Alex DAMIAN
2014-12-12 11:45 ` [PATCH 07/18] toaster: Initialise the 'change' icon tooltips Alex DAMIAN
` (11 subsequent siblings)
17 siblings, 0 replies; 19+ messages in thread
From: Alex DAMIAN @ 2014-12-12 11:45 UTC (permalink / raw)
To: bitbake-devel
From: Belen Barros Pena <belen.barros.pena@intel.com>
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.
Signed-off-by: Belen Barros Pena <belen.barros.pena@intel.com>
---
lib/toaster/toastergui/static/js/base.js | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/lib/toaster/toastergui/static/js/base.js b/lib/toaster/toastergui/static/js/base.js
index fac59e6..619ad28 100644
--- a/lib/toaster/toastergui/static/js/base.js
+++ b/lib/toaster/toastergui/static/js/base.js
@@ -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");
--
1.9.1
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [PATCH 07/18] toaster: Initialise the 'change' icon tooltips
2014-12-12 11:45 [PATCH 00/18 v2] Toaster patchset with new features and bugfixes Alex DAMIAN
` (5 preceding siblings ...)
2014-12-12 11:45 ` [PATCH 06/18] toaster: base Only show change project icon when > one project Alex DAMIAN
@ 2014-12-12 11:45 ` Alex DAMIAN
2014-12-12 11:45 ` [PATCH 08/18] toaster: libtoaster Add a error handler to GET in makeTypehead Alex DAMIAN
` (10 subsequent siblings)
17 siblings, 0 replies; 19+ messages in thread
From: Alex DAMIAN @ 2014-12-12 11:45 UTC (permalink / raw)
To: bitbake-devel
From: Belen Barros Pena <belen.barros.pena@intel.com>
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.
Signed-off-by: Belen Barros Pena <belen.barros.pena@intel.com>
---
lib/toaster/toastergui/static/js/libtoaster.js | 3 +++
1 file changed, 3 insertions(+)
diff --git a/lib/toaster/toastergui/static/js/libtoaster.js b/lib/toaster/toastergui/static/js/libtoaster.js
index 8e76ecb..37fc80e 100644
--- a/lib/toaster/toastergui/static/js/libtoaster.js
+++ b/lib/toaster/toastergui/static/js/libtoaster.js
@@ -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;
--
1.9.1
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [PATCH 08/18] toaster: libtoaster Add a error handler to GET in makeTypehead
2014-12-12 11:45 [PATCH 00/18 v2] Toaster patchset with new features and bugfixes Alex DAMIAN
` (6 preceding siblings ...)
2014-12-12 11:45 ` [PATCH 07/18] toaster: Initialise the 'change' icon tooltips Alex DAMIAN
@ 2014-12-12 11:45 ` Alex DAMIAN
2014-12-12 11:45 ` [PATCH 09/18] toaster: libtoaster Add editProject and getLayerDepsForProject Alex DAMIAN
` (9 subsequent siblings)
17 siblings, 0 replies; 19+ messages in thread
From: Alex DAMIAN @ 2014-12-12 11:45 UTC (permalink / raw)
To: bitbake-devel
From: Michael Wood <michael.g.wood@intel.com>
If the JSON data comes back from the request with an error set, have a
default handler which logs the error to the console.
Signed-off-by: Michael Wood <michael.g.wood@intel.com>
---
lib/toaster/toastergui/static/js/libtoaster.js | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/lib/toaster/toastergui/static/js/libtoaster.js b/lib/toaster/toastergui/static/js/libtoaster.js
index 37fc80e..b691a3b 100644
--- a/lib/toaster/toastergui/static/js/libtoaster.js
+++ b/lib/toaster/toastergui/static/js/libtoaster.js
@@ -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);
});
},
--
1.9.1
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [PATCH 09/18] toaster: libtoaster Add editProject and getLayerDepsForProject
2014-12-12 11:45 [PATCH 00/18 v2] Toaster patchset with new features and bugfixes Alex DAMIAN
` (7 preceding siblings ...)
2014-12-12 11:45 ` [PATCH 08/18] toaster: libtoaster Add a error handler to GET in makeTypehead Alex DAMIAN
@ 2014-12-12 11:45 ` Alex DAMIAN
2014-12-12 11:45 ` [PATCH 10/18] toasterui: refactor project layer finding logic Alex DAMIAN
` (8 subsequent siblings)
17 siblings, 0 replies; 19+ messages in thread
From: Alex DAMIAN @ 2014-12-12 11:45 UTC (permalink / raw)
To: bitbake-devel
From: Michael Wood <michael.g.wood@intel.com>
Add two utility functions for editing project settings and returning the
layer "dependencies" for a specified layer.
Signed-off-by: Michael Wood <michael.g.wood@intel.com>
---
lib/toaster/toastergui/static/js/libtoaster.js | 49 ++++++++++++++++++++++++++
1 file changed, 49 insertions(+)
diff --git a/lib/toaster/toastergui/static/js/libtoaster.js b/lib/toaster/toastergui/static/js/libtoaster.js
index b691a3b..15815b3 100644
--- a/lib/toaster/toastergui/static/js/libtoaster.js
+++ b/lib/toaster/toastergui/static/js/libtoaster.js
@@ -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,
}
})();
--
1.9.1
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [PATCH 10/18] toasterui: refactor project layer finding logic
2014-12-12 11:45 [PATCH 00/18 v2] Toaster patchset with new features and bugfixes Alex DAMIAN
` (8 preceding siblings ...)
2014-12-12 11:45 ` [PATCH 09/18] toaster: libtoaster Add editProject and getLayerDepsForProject Alex DAMIAN
@ 2014-12-12 11:45 ` Alex DAMIAN
2014-12-12 11:45 ` [PATCH 11/18] toaster: Add import layer feature Alex DAMIAN
` (7 subsequent siblings)
17 siblings, 0 replies; 19+ messages in thread
From: Alex DAMIAN @ 2014-12-12 11:45 UTC (permalink / raw)
To: bitbake-devel; +Cc: Alexandru DAMIAN
From: Alexandru DAMIAN <alexandru.damian@intel.com>
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.
Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
lib/toaster/orm/models.py | 21 +++++++++++++++++-
lib/toaster/toastergui/views.py | 49 +++++++++++++++--------------------------
2 files changed, 38 insertions(+), 32 deletions(-)
diff --git a/lib/toaster/orm/models.py b/lib/toaster/orm/models.py
index f5c600b..46b704c 100644
--- a/lib/toaster/orm/models.py
+++ b/lib/toaster/orm/models.py
@@ -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)
diff --git a/lib/toaster/toastergui/views.py b/lib/toaster/toastergui/views.py
index 736de78..434e118 100755
--- a/lib/toaster/toastergui/views.py
+++ b/lib/toaster/toastergui/views.py
@@ -1957,20 +1957,6 @@ if toastermain.settings.MANAGED:
raise Exception("Invalid HTTP method for this page")
- # returns a queryset of compatible layers for a project
- def _compatible_layerversions_for_project(prj, release = None, layer_name = None):
- if release == None:
- release = prj.release
- # layers on the same branch or layers specifically set for this project
- return Layer_Version.objects.filter((Q(up_branch__name = release.branch_name) & Q(project = None)) | Q(project = prj))
-
-
- # returns the equivalence group for all the layers currently set in the project
- def _project_equivalent_layerversions(prj):
- return reduce(lambda x, y: list(x) + list(y),
- # take all equivalent layers for each entry
- map(lambda x: x.layercommit.get_equivalents_wpriority(prj), prj.projectlayer_set.all()) , [])
-
# returns a list for most recent builds; for use in the Project page, xhr_ updates, and other places, as needed
def _project_recent_build_list(prj):
return map(lambda x: {
@@ -2137,7 +2123,7 @@ if toastermain.settings.MANAGED:
for i in prj.projectlayer_set.all():
# find and add a similarly-named layer on the new branch
try:
- lv = _compatible_layerversions_for_project(prj).filter(layer__name = i.layer.name).get_equivalents_wpriority(prj)[0]
+ lv = prj.compatible_layerversions(layer_name = i.layercommit.layer.name)[0]
ProjectLayer.objects.get_or_create(project = prj, layercommit = lv)
except IndexError:
pass
@@ -2185,24 +2171,25 @@ if toastermain.settings.MANAGED:
# returns layers for current project release that are not in the project set, matching the name
if request.GET['type'] == "layers":
- queryset_all = _compatible_layerversions_for_project(prj).filter(layer__name__icontains=request.GET.get('value',''))
+ # all layers for the current project
+ queryset_all = prj.compatible_layerversions().filter(layer__name__icontains=request.GET.get('value',''))
- queryset_all = queryset_all.exclude(pk__in = [x.id for x in _project_equivalent_layerversions(prj)])
+ # but not layers with equivalent layers already in project
+ queryset_all = queryset_all.exclude(pk__in = [x.id for x in prj.projectlayer_equivalent_set()])[:8]
- queryset_all = set([x.get_equivalents_wpriority(prj)[0] for x in queryset_all[:8]])
+ # and show only the selected layers for this project
+ final_list = set([x.get_equivalents_wpriority(prj)[0] for x in queryset_all])
- return HttpResponse(jsonfilter( { "error":"ok", "list" : map( _lv_to_dict, queryset_all) }), content_type = "application/json")
+ return HttpResponse(jsonfilter( { "error":"ok", "list" : map( _lv_to_dict, final_list) }), content_type = "application/json")
# returns layer dependencies for a layer, excluding current project layers
if request.GET['type'] == "layerdeps":
- queryset_all = LayerVersionDependency.objects.filter(layer_version_id = request.GET['value'])
- queryset_all = queryset_all.exclude(depends_on__in = _project_equivalent_layerversions(prj))
- queryset_all.order_by("-up_id");
+ queryset = prj.compatible_layerversions().exclude(pk__in = [x.id for x in prj.projectlayer_equivalent_set()]).filter(
+ layer__name__in = [ x.depends_on.layer.name for x in LayerVersionDependency.objects.filter(layer_version_id = request.GET['value'])])
+
+ return HttpResponse(jsonfilter( { "error":"ok", "list" : map( _lv_to_dict, queryset) }), content_type = "application/json")
- return HttpResponse(jsonfilter( { "error":"ok",
- "list" : map( _lv_to_dict, map(lambda x: x.depends_on.get_equivalents_wpriority(prj)[0], queryset_all))
- }), content_type = "application/json")
# returns layer versions that would be deleted on the new release__pk
@@ -2212,7 +2199,7 @@ if toastermain.settings.MANAGED:
retval = []
for i in prj.projectlayer_set.all():
- lv = _compatible_layerversions_for_project(prj, release = Release.objects.get(pk=request.GET['value']))
+ lv = prj.compatible_layerversions(release = Release.objects.get(pk=request.GET['value'])).filter(layer__name = i.layercommit.layer.name)
# there is no layer_version with the new release id, and the same name
if lv.count() < 1:
retval.append(i)
@@ -2225,7 +2212,7 @@ if toastermain.settings.MANAGED:
# returns targets provided by current project layers
if request.GET['type'] == "targets":
queryset_all = Recipe.objects.all()
- queryset_all = queryset_all.filter(layer_version__in = reduce(lambda x, y: list(x) + list(y), map(lambda x: x.layercommit.get_equivalents_wpriority(prj), prj.projectlayer_set.all()), []))
+ queryset_all = queryset_all.filter(layer_version__in = prj.projectlayer_equivalent_set())
return HttpResponse(jsonfilter({ "error":"ok",
"list" : map ( lambda x: {"id": x.pk, "name": x.name, "detail":"[" + x.layer_version.layer.name+ (" | " + x.layer_version.up_branch.name + "]" if x.layer_version.up_branch is not None else "]")},
queryset_all.filter(name__icontains=request.GET.get('value',''))[:8]),
@@ -2235,7 +2222,8 @@ if toastermain.settings.MANAGED:
if request.GET['type'] == "machines":
queryset_all = Machine.objects.all()
if 'project_id' in request.session:
- queryset_all = queryset_all.filter(layer_version__layer__in = map(lambda x: x.layercommit.layer, ProjectLayer.objects.filter(project_id=request.session['project_id'])))
+ queryset_all = queryset_all.filter(layer_version__in = prj.projectlayer_equivalent_set())
+
return HttpResponse(jsonfilter({ "error":"ok",
"list" : map ( lambda x: {"id": x.pk, "name": x.name, "detail":"[" + x.layer_version.layer.name+ (" | " + x.layer_version.up_branch.name + "]" if x.layer_version.up_branch is not None else "]")},
queryset_all.filter(name__icontains=request.GET.get('value',''))[:8]),
@@ -2283,14 +2271,13 @@ if toastermain.settings.MANAGED:
prj = Project.objects.get(pk = request.session['project_id'])
- queryset_all = _compatible_layerversions_for_project(prj)
+ queryset_all = prj.compatible_layerversions()
queryset_all = _get_queryset(Layer_Version, queryset_all, filter_string, search_term, ordering_string, '-layer__name')
- objects_all= list(set([x.get_equivalents_wpriority(prj)[0] for x in queryset_all[:pagesize]]))
# retrieve the objects that will be displayed in the table; layers a paginator and gets a page range to display
- layer_info = _build_page_range(Paginator(objects_all, request.GET.get('count', 10)),request.GET.get('page', 1))
+ layer_info = _build_page_range(Paginator(queryset_all, request.GET.get('count', 10)),request.GET.get('page', 1))
context = {
--
1.9.1
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [PATCH 11/18] toaster: Add import layer feature.
2014-12-12 11:45 [PATCH 00/18 v2] Toaster patchset with new features and bugfixes Alex DAMIAN
` (9 preceding siblings ...)
2014-12-12 11:45 ` [PATCH 10/18] toasterui: refactor project layer finding logic Alex DAMIAN
@ 2014-12-12 11:45 ` Alex DAMIAN
2014-12-12 11:45 ` [PATCH 12/18] toaster: tweaking the import layers form Alex DAMIAN
` (6 subsequent siblings)
17 siblings, 0 replies; 19+ messages in thread
From: Alex DAMIAN @ 2014-12-12 11:45 UTC (permalink / raw)
To: bitbake-devel; +Cc: Alexandru DAMIAN
From: Michael Wood <michael.g.wood@intel.com>
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]
Signed-off-by: Michael Wood <michael.g.wood@intel.com>
Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
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
diff --git a/lib/toaster/toastergui/static/js/importlayer.js b/lib/toaster/toastergui/static/js/importlayer.js
new file mode 100644
index 0000000..e2bc1ab
--- /dev/null
+++ b/lib/toaster/toastergui/static/js/importlayer.js
@@ -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);
+ }
+ });
+
+}
diff --git a/lib/toaster/toastergui/static/js/projectapp.js b/lib/toaster/toastergui/static/js/projectapp.js
index e9b07c7..8e3499a 100644
--- a/lib/toaster/toastergui/static/js/projectapp.js
+++ b/lib/toaster/toastergui/static/js/projectapp.js
@@ -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(" ");
diff --git a/lib/toaster/toastergui/templates/importlayer.html b/lib/toaster/toastergui/templates/importlayer.html
index 7e48eac..913f951 100644
--- a/lib/toaster/toastergui/templates/importlayer.html
+++ b/lib/toaster/toastergui/templates/importlayer.html
@@ -1,68 +1,115 @@
{% extends "baseprojectpage.html" %}
{% load projecttags %}
{% load humanize %}
+{% load static %}
{% block localbreadcrumb %}
-<li>Layers</li>
+<li>Import layer</li>
{% endblock %}
{% block projectinfomain %}
+
+ <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}};
+
+ try {
+ importLayerPageInit(ctx);
+ } catch(e) {
+ document.write(e.stack);
+ console.log(e);
+ }
+ });
+ </script>
+
<div class="page-header">
<h1>Import layer</h1>
</div>
+
+ {% 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>
{% endif %}
- <fieldset class="air">
- <legend>Layer repository information</legend>
+ <fieldset class="air">
+ <legend>Layer repository information</legend>
+ <div class="alert alert-error" id="import-error" style="display:none">
+ <button type="button" class="close" data-dismiss="alert">×</button>
+ <h3></h3>
+ <span></span>
+ <ul></ul>
+ </div>
+
+ <div class="control-group" id="layer-name-ctrl">
+ <label class="control-label" for="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>
+ <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>
+
+ </div>
+
<label>
Git repository URL
- <i class="icon-question-sign get-help" title="Fetch/clone URL of the repository. Currently, Toaster only supports Git repositories."></i>
+ <span class="icon-question-sign get-help" title="Fetch/clone URL of the repository. Currently, Toaster only supports Git repositories." />
</label>
- <input id="repo" type="text" class="input-xxlarge" required>
- <label class="project-form">
+
+ <input type="text" id="layer-git-repo-url" class="input-xxlarge" required>
+ <label class="project-form" for="layer-subdir">
Repository subdirectory
<span class="muted">(optional)</span>
- <i class="icon-question-sign get-help" title="Subdirectory within the repository where the layer is located, if not in the root (usually only used if the repository contains more than one layer)"></i>
- </label>
- <input type="text" id="subdir">
- <label class="project-form">Branch, tag or commit</label>
- <input type="text" class="span4" id="layer-version" required>
- <label class="project-form">
- Layer name
- <i 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"></i>
+ <span class="icon-question-sign get-help" title="Subdirectory within the repository where the layer is located, if not in the root (usually only used if the repository contains more than one layer)" />
</label>
- <input id="layer-name" type="text" required>
+ <input type="text" id="layer-subdir">
+
+ <div class="control-group" id="layer-revision-ctrl">
+ <label class="control-label" for="layer-git-ref">Revision
+ <span class="icon-question-sign get-help" title="You can provide a Git branch, a tag or a commit SHA as the revision"></span>
+ </label>
+ <div class="controls">
+ <input type="text" class="span4" id="layer-git-ref" required>
+ <span class="help-inline" style="diaply:none;" id="invalid-layer-revision-hint"></span>
+ </div>
+ </div>
+
+ <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" />
+ </label>
+ <input id="layer-description" type="text" class="input-xxlarge" />
+
</fieldset>
<fieldset class="air">
<legend>
Layer dependencies
<span class="muted">(optional)</span>
- <i class="icon-question-sign get-help heading-help" title="Other layers this layer depends upon"></i>
+ <span class="icon-question-sign get-help heading-help" title="Other layers this layer depends upon" />
</legend>
- <ul class="unstyled configuration-list">
- <li>
- <a href="" class="layer-info" title="OpenEmbedded | daisy">openembedded-core (meta)</a>
- <i class="icon-trash"></i>
- </li>
+ <ul class="unstyled configuration-list" id="layer-deps-list">
</ul>
<div class="input-append">
- <input type="text" autocomplete="off" data-minLength="1" data-autocomplete="off"
- data-provide="typeahead" data-source='
- []
- ' placeholder="Type a layer name" id="layer-dependency" class="input-xlarge">
- <a class="btn" type="button" id="add-layer-dependency" disabled>
+ <input type="text" autocomplete="off" data-minLength="1" data-autocomplete="off" data-provide="typeahead" placeholder="Type a layer name" id="layer-dependency" class="input-xlarge">
+ <a class="btn" type="button" id="add-layer-dependency-btn" disabled>
Add layer
</a>
</div>
<span class="help-inline">You can only add layers Toaster knows about</span>
</fieldset>
- <div class="form-actions">
- <a href="#dependencies-message" class="btn btn-primary btn-large" data-toggle="modal" data-target="#dependencies-message" disabled>Import and add to project</a>
- <a href="layer-details-just-imported.html" class="btn btn-large" disabled>Just import for the moment</a>
- <span class="help-inline" style="vertical-align: middle;">To import a layer, you need to enter a repository URL, a branch, tag or commit and a layer name</span>
+ <div class="form-actions" id="form-actions">
+ <button class="btn btn-primary btn-large" data-toggle="modal" id="import-and-add-btn" data-target="#dependencies-message" disabled>Import and add to project</button>
+ <span class="help-inline" id="import-and-add-hint" style="vertical-align: middle;">To import a layer, you need to enter a repository URL, a branch, tag or commit and a layer name</span>
</div>
- </form>
+ </form>
{% endblock %}
diff --git a/lib/toaster/toastergui/templates/layers_dep_modal.html b/lib/toaster/toastergui/templates/layers_dep_modal.html
new file mode 100644
index 0000000..821bbda
--- /dev/null
+++ b/lib/toaster/toastergui/templates/layers_dep_modal.html
@@ -0,0 +1,68 @@
+<!-- 'Layer dependencies modal' -->
+ <div id="dependencies_modal" class="modal hide fade" tabindex="-1" role="dialog" aria-hidden="true">
+ <form id="dependencies_modal_form">
+ <div class="modal-header">
+ <button type="button" class="close" data-dismiss="modal" aria-hidden="true">x</button>
+ <h3><span class="layer-name"></span> dependencies</h3>
+ </div>
+ <div class="modal-body">
+ <p><strong class="layer-name"></strong> depends on some layers that are not added to your project. Select the ones you want to add:</p>
+ <ul class="unstyled" id="dependencies_list">
+ </ul>
+ </div>
+ <div class="modal-footer">
+ <button class="btn btn-primary" type="submit">Add layers</button>
+ <button class="btn" type="reset" data-dismiss="modal">Cancel</button>
+ </div>
+ </form>
+ </div>
+
+<script>
+function show_layer_deps_modal(projectId, layer, dependencies, successAdd) {
+ // update layer name
+ $('.layer-name').text(layer.name);
+ var deplistHtml = "";
+ for (var i = 0; i < dependencies.length; i++) {
+ deplistHtml += "<li><label class=\"checkbox\"><input name=\"dependencies\" value=\"";
+ deplistHtml += dependencies[i].id;
+ deplistHtml +="\" type=\"checkbox\" checked=\"checked\"/>";
+ deplistHtml += dependencies[i].name;
+ deplistHtml += "</label></li>";
+ }
+ $('#dependencies_list').html(deplistHtml);
+
+ var selected = [layer.id];
+ var layer_link_list = "<a href='"+layer.url+"'>"+layer.name+"</a>";
+
+ $("#dependencies_modal_form").submit(function (e) {
+ e.preventDefault();
+ $("input[name='dependencies']:checked").map(function () { selected.push(parseInt($(this).val()))});
+ if (selected.length > 1) {
+ tooltipUpdateText = "" + selected.length + " layers added";
+ } else {
+ tooltipUpdateText = "1 layer added";
+ }
+
+ for (var i = 0; i < selected.length; i++) {
+ for (var j = 0; j < dependencies.length; j++) {
+ if (dependencies[j].id == selected[i]) {
+ layer_link_list+= ", <a href='"+dependencies[j].layerdetailurl+"'>"+dependencies[j].name+"</a>"
+ break;
+ }
+ }
+ }
+
+ $('#dependencies_modal').modal('hide');
+
+ var editProjectUrl = "{% url 'xhr_projectedit' project.id %}";
+ libtoaster.editProject(editProjectUrl, projectId, { 'layerAdd': selected.join(",") }, function () {
+ if (successAdd) {
+ successAdd(selected);
+ }
+ }, function () {
+ console.log ("Adding layers to project failed");
+ });
+ });
+ $('#dependencies_modal').modal('show');
+}
+</script>
diff --git a/lib/toaster/toastergui/urls.py b/lib/toaster/toastergui/urls.py
index b60f761..6e1b0ab 100644
--- a/lib/toaster/toastergui/urls.py
+++ b/lib/toaster/toastergui/urls.py
@@ -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
diff --git a/lib/toaster/toastergui/views.py b/lib/toaster/toastergui/views.py
index 434e118..ec055d3 100755
--- a/lib/toaster/toastergui/views.py
+++ b/lib/toaster/toastergui/views.py
@@ -30,6 +30,7 @@ from orm.models import Task_Dependency, Recipe_Dependency, Package, Package_File
from orm.models import Target_Installed_Package, Target_File, Target_Image_File, BuildArtifact
from django.views.decorators.cache import cache_control
from django.core.urlresolvers import reverse
+from django.core.exceptions import MultipleObjectsReturned
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from django.http import HttpResponseBadRequest, HttpResponseNotFound
from django.utils import timezone
@@ -2016,8 +2017,9 @@ if toastermain.settings.MANAGED:
"name" : x.layercommit.layer.name,
"giturl": x.layercommit.layer.vcs_url,
"url": x.layercommit.layer.layer_index_url,
- "layerdetailurl": reverse("layerdetails", args=(x.layercommit.layer.pk,)),
- "branch" : { "name" : x.layercommit.up_branch.name, "layersource" : x.layercommit.up_branch.layer_source.name}},
+ "layerdetailurl": reverse("layerdetails", args=(x.layercommit.pk,)),
+ # This branch name is actually the release
+ "branch" : { "name" : x.layercommit.commit, "layersource" : x.layercommit.up_branch.layer_source.name}},
prj.projectlayer_set.all().order_by("id")),
"targets" : map(lambda x: {"target" : x.target, "task" : x.task, "pk": x.pk}, prj.projecttarget_set.all()),
"freqtargets": freqtargets,
@@ -2164,7 +2166,7 @@ if toastermain.settings.MANAGED:
def _lv_to_dict(x):
- return {"id": x.pk, "name": x.layer.name,
+ return {"id": x.pk, "name": x.layer.name, "tooltip": x.layer.vcs_url+" | "+x.commit,
"detail": "(" + x.layer.vcs_url + (")" if x.up_branch == None else " | "+x.up_branch.name+")"),
"giturl": x.layer.vcs_url, "layerdetailurl" : reverse('layerdetails', args=(x.pk,))}
@@ -2174,8 +2176,9 @@ if toastermain.settings.MANAGED:
# all layers for the current project
queryset_all = prj.compatible_layerversions().filter(layer__name__icontains=request.GET.get('value',''))
- # but not layers with equivalent layers already in project
- queryset_all = queryset_all.exclude(pk__in = [x.id for x in prj.projectlayer_equivalent_set()])[:8]
+ # but not layers with equivalent layers already in project
+ if not request.GET.has_key('include_added'):
+ queryset_all = queryset_all.exclude(pk__in = [x.id for x in prj.projectlayer_equivalent_set()])[:8]
# and show only the selected layers for this project
final_list = set([x.get_equivalents_wpriority(prj)[0] for x in queryset_all])
@@ -2243,6 +2246,100 @@ if toastermain.settings.MANAGED:
return HttpResponse(jsonfilter({"error":str(e) + "\n" + traceback.format_exc()}), content_type = "application/json")
+ def xhr_importlayer(request):
+ if (not request.POST.has_key('vcs_url') or
+ not request.POST.has_key('name') or
+ not request.POST.has_key('git_ref') or
+ 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")
+
+ # Rudimentary check for any possible html tags
+ if "<" in request.POST:
+ return HttpResponse(jsonfilter({"error": "Invalid character <"}), content_type = "application/json")
+
+ prj = Project.objects.get(pk=request.POST['project_id'])
+
+ # Strip trailing/leading whitespace from all values
+ # put into a new dict because POST one is immutable
+ post_data = dict()
+ for key,val in request.POST.iteritems():
+ post_data[key] = val.strip()
+
+
+ # We need to know what release the current project is so that we
+ # can set the imported layer's up_branch_id
+ prj_branch_name = Release.objects.get(pk=prj.release_id).branch_name
+ up_branch, branch_created = Branch.objects.get_or_create(name=prj_branch_name, layer_source_id=LayerSource.TYPE_IMPORTED)
+
+ layer_source = LayerSource.objects.get(sourcetype=LayerSource.TYPE_IMPORTED)
+ try:
+ layer, layer_created = Layer.objects.get_or_create(name=post_data['name'])
+ except MultipleObjectsReturned:
+ return HttpResponse(jsonfilter({"error": "hint-layer-exists"}), content_type = "application/json")
+
+ if layer:
+ if layer_created:
+ layer.layer_source = layer_source
+ layer.vcs_url = post_data['vcs_url']
+ if post_data.has_key('summary'):
+ layer.summary = layer.description = post_data['summary']
+
+ layer.up_date = timezone.now()
+ layer.save()
+ else:
+ # We have an existing layer by this name, let's see if the git
+ # url is the same, if it is then we can just create a new layer
+ # version for this layer. Otherwise we need to bail out.
+ if layer.vcs_url != post_data['vcs_url']:
+ return HttpResponse(jsonfilter({"error": "hint-layer-exists-with-different-url" , "current_url" : layer.vcs_url, "current_id": layer.id }), content_type = "application/json")
+
+
+ layer_version, version_created = Layer_Version.objects.get_or_create(layer_source=layer_source, layer=layer, project=prj, up_branch_id=up_branch.id,branch=post_data['git_ref'], commit=post_data['git_ref'], dirpath=post_data['dir_path'])
+
+ if layer_version:
+ if not version_created:
+ return HttpResponse(jsonfilter({"error": "hint-layer-version-exists", "existing_layer_version": layer_version.id }), content_type = "application/json")
+
+ layer_version.up_date = timezone.now()
+ layer_version.save()
+
+ # Add the dependencies specified for this new layer
+ if (post_data.has_key("layer_deps") and
+ version_created and
+ len(post_data["layer_deps"]) > 0):
+ for layer_dep_id in post_data["layer_deps"].split(","):
+
+ layer_dep_obj = Layer_Version.objects.get(pk=layer_dep_id)
+ LayerVersionDependency.objects.get_or_create(layer_version=layer_version, depends_on=layer_dep_obj)
+ # Now add them to the project, we could get an execption
+ # 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)
+ except:
+ pass
+
+
+ # If an old layer version exists in our project then remove it
+ for prj_layers in ProjectLayer.objects.filter(project=prj):
+ dup_layer_v = Layer_Version.objects.filter(id=prj_layers.layercommit_id, layer_id=layer.id)
+ if len(dup_layer_v) >0 :
+ prj_layers.delete()
+
+ # finally add the imported layer (version id) to the project
+ ProjectLayer.objects.create(layercommit=layer_version, project=prj,optional=1)
+
+ else:
+ # We didn't create a layer version so back out now and clean up.
+ if layer_created:
+ layer.delete()
+
+ return HttpResponse(jsonfilter({"error": "Uncaught error: Could not create layer version"}), content_type = "application/json")
+
+
+ return HttpResponse(jsonfilter({"error": "ok"}), content_type = "application/json")
+
+
def importlayer(request):
template = "importlayer.html"
--
1.9.1
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [PATCH 12/18] toaster: tweaking the import layers form
2014-12-12 11:45 [PATCH 00/18 v2] Toaster patchset with new features and bugfixes Alex DAMIAN
` (10 preceding siblings ...)
2014-12-12 11:45 ` [PATCH 11/18] toaster: Add import layer feature Alex DAMIAN
@ 2014-12-12 11:45 ` Alex DAMIAN
2014-12-12 11:45 ` [PATCH 13/18] toaster: Importlayer add notify exactly which layers changed Alex DAMIAN
` (5 subsequent siblings)
17 siblings, 0 replies; 19+ messages in thread
From: Alex DAMIAN @ 2014-12-12 11:45 UTC (permalink / raw)
To: bitbake-devel; +Cc: Alexandru DAMIAN, Belen Barros Pena
From: Belen Barros Pena <belen.barros.pena@linux.intel.com>
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
Signed-off-by: Belen Barros Pena <belen.barros.pena@linux.intel.com>
Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
lib/toaster/toastergui/templates/importlayer.html | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/lib/toaster/toastergui/templates/importlayer.html b/lib/toaster/toastergui/templates/importlayer.html
index 913f951..00fd694 100644
--- a/lib/toaster/toastergui/templates/importlayer.html
+++ b/lib/toaster/toastergui/templates/importlayer.html
@@ -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">
--
1.9.1
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [PATCH 13/18] toaster: Importlayer add notify exactly which layers changed
2014-12-12 11:45 [PATCH 00/18 v2] Toaster patchset with new features and bugfixes Alex DAMIAN
` (11 preceding siblings ...)
2014-12-12 11:45 ` [PATCH 12/18] toaster: tweaking the import layers form Alex DAMIAN
@ 2014-12-12 11:45 ` Alex DAMIAN
2014-12-12 11:45 ` [PATCH 14/18] toaster: importlayer Avoid namespace clash with layer name Alex DAMIAN
` (4 subsequent siblings)
17 siblings, 0 replies; 19+ messages in thread
From: Alex DAMIAN @ 2014-12-12 11:45 UTC (permalink / raw)
To: bitbake-devel
From: Michael Wood <michael.g.wood@intel.com>
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.
Signed-off-by: Michael Wood <michael.g.wood@intel.com>
---
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(-)
diff --git a/lib/toaster/toastergui/static/js/importlayer.js b/lib/toaster/toastergui/static/js/importlayer.js
index e2bc1ab..15830dd 100644
--- a/lib/toaster/toastergui/static/js/importlayer.js
+++ b/lib/toaster/toastergui/static/js/importlayer.js
@@ -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) {
diff --git a/lib/toaster/toastergui/static/js/projectapp.js b/lib/toaster/toastergui/static/js/projectapp.js
index 8e3499a..94c24f4 100644
--- a/lib/toaster/toastergui/static/js/projectapp.js
+++ b/lib/toaster/toastergui/static/js/projectapp.js
@@ -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) {
diff --git a/lib/toaster/toastergui/templates/layers_dep_modal.html b/lib/toaster/toastergui/templates/layers_dep_modal.html
index 821bbda..b03fd0b 100644
--- a/lib/toaster/toastergui/templates/layers_dep_modal.html
+++ b/lib/toaster/toastergui/templates/layers_dep_modal.html
@@ -3,10 +3,10 @@
<form id="dependencies_modal_form">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">x</button>
- <h3><span class="layer-name"></span> dependencies</h3>
+ <h3><span id="title"></span> dependencies</h3>
</div>
<div class="modal-body">
- <p><strong class="layer-name"></strong> depends on some layers that are not added to your project. Select the ones you want to add:</p>
+ <p id="body-text"> <strong id="layer-name"></strong> depends on some layers that are not added to your project. Select the ones you want to add:</p>
<ul class="unstyled" id="dependencies_list">
</ul>
</div>
@@ -18,9 +18,20 @@
</div>
<script>
-function show_layer_deps_modal(projectId, layer, dependencies, successAdd) {
+function show_layer_deps_modal(projectId, layer, dependencies, title, body, addToProject, successAdd) {
// update layer name
- $('.layer-name').text(layer.name);
+ if (title) {
+ $('#dependencies_modal #title').text(title);
+ } else {
+ $('#dependencies_modal #title').text(layer.name);
+ }
+
+ if (body) {
+ $("#dependencies_modal #body-text").html(body);
+ } else {
+ $("#dependencies_modal #layer-name").text(layer.name);
+ }
+
var deplistHtml = "";
for (var i = 0; i < dependencies.length; i++) {
deplistHtml += "<li><label class=\"checkbox\"><input name=\"dependencies\" value=\"";
@@ -31,7 +42,13 @@ function show_layer_deps_modal(projectId, layer, dependencies, successAdd) {
}
$('#dependencies_list').html(deplistHtml);
- var selected = [layer.id];
+ var selected = [];
+ /* -1 is a special dummy Id which we use when the layer isn't yet in the
+ * system, normally we would add the current layer to the selection.
+ */
+ if (layer.id != -1)
+ selected.push(layer.id);
+
var layer_link_list = "<a href='"+layer.url+"'>"+layer.name+"</a>";
$("#dependencies_modal_form").submit(function (e) {
@@ -54,15 +71,20 @@ function show_layer_deps_modal(projectId, layer, dependencies, successAdd) {
$('#dependencies_modal').modal('hide');
- var editProjectUrl = "{% url 'xhr_projectedit' project.id %}";
- libtoaster.editProject(editProjectUrl, projectId, { 'layerAdd': selected.join(",") }, function () {
- if (successAdd) {
- successAdd(selected);
- }
- }, function () {
- console.log ("Adding layers to project failed");
- });
+ if (addToProject) {
+ var editProjectUrl = "{% url 'xhr_projectedit' project.id %}";
+ libtoaster.editProject(editProjectUrl, projectId, { 'layerAdd': selected.join(",") }, function () {
+ if (successAdd) {
+ successAdd(selected);
+ }
+ }, function () {
+ console.log ("Adding layers to project failed");
+ });
+ } else {
+ successAdd(selected);
+ }
});
+
$('#dependencies_modal').modal('show');
}
</script>
diff --git a/lib/toaster/toastergui/views.py b/lib/toaster/toastergui/views.py
index ec055d3..dd43080 100755
--- a/lib/toaster/toastergui/views.py
+++ b/lib/toaster/toastergui/views.py
@@ -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")
--
1.9.1
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [PATCH 00/18 v2] Toaster patchset with new features and bugfixes
@ 2014-12-12 11:45 Alex DAMIAN
2014-12-12 11:45 ` [PATCH 01/18] add build artifacts table and other improvements Alex DAMIAN
` (17 more replies)
0 siblings, 18 replies; 19+ messages in thread
From: Alex DAMIAN @ 2014-12-12 11:45 UTC (permalink / raw)
To: bitbake-devel; +Cc: Alexandru DAMIAN, Belen Barros Pena
From: Alexandru DAMIAN <alexandru.damian@intel.com>
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
--
1.9.1
^ permalink raw reply [flat|nested] 19+ messages in thread
* [PATCH 14/18] toaster: importlayer Avoid namespace clash with layer name
2014-12-12 11:45 [PATCH 00/18 v2] Toaster patchset with new features and bugfixes Alex DAMIAN
` (12 preceding siblings ...)
2014-12-12 11:45 ` [PATCH 13/18] toaster: Importlayer add notify exactly which layers changed Alex DAMIAN
@ 2014-12-12 11:45 ` Alex DAMIAN
2014-12-12 11:45 ` [PATCH 15/18] toaster: projectapp Add links to the imported layers notify Alex DAMIAN
` (3 subsequent siblings)
17 siblings, 0 replies; 19+ messages in thread
From: Alex DAMIAN @ 2014-12-12 11:45 UTC (permalink / raw)
To: bitbake-devel
From: Michael Wood <michael.g.wood@intel.com>
This was causing validation issues as the layer name was being picked up
from a different location in the dom.
Signed-off-by: Michael Wood <michael.g.wood@intel.com>
---
lib/toaster/toastergui/static/js/importlayer.js | 2 +-
lib/toaster/toastergui/templates/importlayer.html | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/lib/toaster/toastergui/static/js/importlayer.js b/lib/toaster/toastergui/static/js/importlayer.js
index 15830dd..34dbcd9 100644
--- a/lib/toaster/toastergui/static/js/importlayer.js
+++ b/lib/toaster/toastergui/static/js/importlayer.js
@@ -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");
diff --git a/lib/toaster/toastergui/templates/importlayer.html b/lib/toaster/toastergui/templates/importlayer.html
index 00fd694..c59247f 100644
--- a/lib/toaster/toastergui/templates/importlayer.html
+++ b/lib/toaster/toastergui/templates/importlayer.html
@@ -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>
--
1.9.1
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [PATCH 15/18] toaster: projectapp Add links to the imported layers notify
2014-12-12 11:45 [PATCH 00/18 v2] Toaster patchset with new features and bugfixes Alex DAMIAN
` (13 preceding siblings ...)
2014-12-12 11:45 ` [PATCH 14/18] toaster: importlayer Avoid namespace clash with layer name Alex DAMIAN
@ 2014-12-12 11:45 ` Alex DAMIAN
2014-12-12 11:45 ` [PATCH 16/18] toaster: Formatting changes to error messages Alex DAMIAN
` (2 subsequent siblings)
17 siblings, 0 replies; 19+ messages in thread
From: Alex DAMIAN @ 2014-12-12 11:45 UTC (permalink / raw)
To: bitbake-devel
From: Michael Wood <michael.g.wood@intel.com>
Adds links to layer details for each of the layers that has been added
to the project.
Signed-off-by: Michael Wood <michael.g.wood@intel.com>
---
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(-)
diff --git a/lib/toaster/toastergui/static/js/importlayer.js b/lib/toaster/toastergui/static/js/importlayer.js
index 34dbcd9..5748efd 100644
--- a/lib/toaster/toastergui/static/js/importlayer.js
+++ b/lib/toaster/toastergui/static/js/importlayer.js
@@ -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');
}
},
diff --git a/lib/toaster/toastergui/static/js/projectapp.js b/lib/toaster/toastergui/static/js/projectapp.js
index 94c24f4..741038d 100644
--- a/lib/toaster/toastergui/static/js/projectapp.js
+++ b/lib/toaster/toastergui/static/js/projectapp.js
@@ -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
diff --git a/lib/toaster/toastergui/templates/project.html b/lib/toaster/toastergui/templates/project.html
index 113e382..2979db7 100644
--- a/lib/toaster/toastergui/templates/project.html
+++ b/lib/toaster/toastergui/templates/project.html
@@ -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}};
diff --git a/lib/toaster/toastergui/views.py b/lib/toaster/toastergui/views.py
index dd43080..679c1e9 100755
--- a/lib/toaster/toastergui/views.py
+++ b/lib/toaster/toastergui/views.py
@@ -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")
--
1.9.1
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [PATCH 16/18] toaster: Formatting changes to error messages
2014-12-12 11:45 [PATCH 00/18 v2] Toaster patchset with new features and bugfixes Alex DAMIAN
` (14 preceding siblings ...)
2014-12-12 11:45 ` [PATCH 15/18] toaster: projectapp Add links to the imported layers notify Alex DAMIAN
@ 2014-12-12 11:45 ` Alex DAMIAN
2014-12-12 11:45 ` [PATCH 17/18] toaster: Change the full stop to a colon Alex DAMIAN
2014-12-12 11:45 ` [PATCH 18/18] toaster: importlayer Tidy up the page initialisation Alex DAMIAN
17 siblings, 0 replies; 19+ messages in thread
From: Alex DAMIAN @ 2014-12-12 11:45 UTC (permalink / raw)
To: bitbake-devel
From: Belen Barros Pena <belen.barros.pena@intel.com>
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.
Signed-off-by: Belen Barros Pena <belen.barros.pena@intel.com>
---
lib/toaster/toastergui/static/js/importlayer.js | 6 +++---
lib/toaster/toastergui/templates/importlayer.html | 2 +-
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/lib/toaster/toastergui/static/js/importlayer.js b/lib/toaster/toastergui/static/js/importlayer.js
index 5748efd..d6e140f 100644
--- a/lib/toaster/toastergui/static/js/importlayer.js
+++ b/lib/toaster/toastergui/static/js/importlayer.js
@@ -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:
diff --git a/lib/toaster/toastergui/templates/importlayer.html b/lib/toaster/toastergui/templates/importlayer.html
index c59247f..c92e552 100644
--- a/lib/toaster/toastergui/templates/importlayer.html
+++ b/lib/toaster/toastergui/templates/importlayer.html
@@ -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>
--
1.9.1
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [PATCH 17/18] toaster: Change the full stop to a colon
2014-12-12 11:45 [PATCH 00/18 v2] Toaster patchset with new features and bugfixes Alex DAMIAN
` (15 preceding siblings ...)
2014-12-12 11:45 ` [PATCH 16/18] toaster: Formatting changes to error messages Alex DAMIAN
@ 2014-12-12 11:45 ` Alex DAMIAN
2014-12-12 11:45 ` [PATCH 18/18] toaster: importlayer Tidy up the page initialisation Alex DAMIAN
17 siblings, 0 replies; 19+ messages in thread
From: Alex DAMIAN @ 2014-12-12 11:45 UTC (permalink / raw)
To: bitbake-devel
From: Belen Barros Pena <belen.barros.pena@intel.com>
In the import layers notification for multiple layers
there was a full stop where there should be a colon.
Signed-off-by: Belen Barros Pena <belen.barros.pena@intel.com>
---
lib/toaster/toastergui/static/js/projectapp.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lib/toaster/toastergui/static/js/projectapp.js b/lib/toaster/toastergui/static/js/projectapp.js
index 741038d..bb97f32 100644
--- a/lib/toaster/toastergui/static/js/projectapp.js
+++ b/lib/toaster/toastergui/static/js/projectapp.js
@@ -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");
--
1.9.1
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [PATCH 18/18] toaster: importlayer Tidy up the page initialisation
2014-12-12 11:45 [PATCH 00/18 v2] Toaster patchset with new features and bugfixes Alex DAMIAN
` (16 preceding siblings ...)
2014-12-12 11:45 ` [PATCH 17/18] toaster: Change the full stop to a colon Alex DAMIAN
@ 2014-12-12 11:45 ` Alex DAMIAN
17 siblings, 0 replies; 19+ messages in thread
From: Alex DAMIAN @ 2014-12-12 11:45 UTC (permalink / raw)
To: bitbake-devel
From: Michael Wood <michael.g.wood@intel.com>
Tidy up the page initialisation and change the error logging so that
it's consistent with other pages.
Signed-off-by: Michael Wood <michael.g.wood@intel.com>
---
lib/toaster/toastergui/templates/importlayer.html | 21 +++++++++++----------
1 file changed, 11 insertions(+), 10 deletions(-)
diff --git a/lib/toaster/toastergui/templates/importlayer.html b/lib/toaster/toastergui/templates/importlayer.html
index c92e552..a4d8ee1 100644
--- a/lib/toaster/toastergui/templates/importlayer.html
+++ b/lib/toaster/toastergui/templates/importlayer.html
@@ -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>
--
1.9.1
^ permalink raw reply related [flat|nested] 19+ messages in thread
end of thread, other threads:[~2014-12-12 11:45 UTC | newest]
Thread overview: 19+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2014-12-12 11:45 [PATCH 00/18 v2] Toaster patchset with new features and bugfixes Alex DAMIAN
2014-12-12 11:45 ` [PATCH 01/18] add build artifacts table and other improvements Alex DAMIAN
2014-12-12 11:45 ` [PATCH 02/18] toasterui: add extra debug and development infos Alex DAMIAN
2014-12-12 11:45 ` [PATCH 03/18] add option to write offline event log file Alex DAMIAN
2014-12-12 11:45 ` [PATCH 04/18] toastergui: implement UI changes to allow file download Alex DAMIAN
2014-12-12 11:45 ` [PATCH 05/18] add POST endpoint for uploading eventlog files Alex DAMIAN
2014-12-12 11:45 ` [PATCH 06/18] toaster: base Only show change project icon when > one project Alex DAMIAN
2014-12-12 11:45 ` [PATCH 07/18] toaster: Initialise the 'change' icon tooltips Alex DAMIAN
2014-12-12 11:45 ` [PATCH 08/18] toaster: libtoaster Add a error handler to GET in makeTypehead Alex DAMIAN
2014-12-12 11:45 ` [PATCH 09/18] toaster: libtoaster Add editProject and getLayerDepsForProject Alex DAMIAN
2014-12-12 11:45 ` [PATCH 10/18] toasterui: refactor project layer finding logic Alex DAMIAN
2014-12-12 11:45 ` [PATCH 11/18] toaster: Add import layer feature Alex DAMIAN
2014-12-12 11:45 ` [PATCH 12/18] toaster: tweaking the import layers form Alex DAMIAN
2014-12-12 11:45 ` [PATCH 13/18] toaster: Importlayer add notify exactly which layers changed Alex DAMIAN
2014-12-12 11:45 ` [PATCH 14/18] toaster: importlayer Avoid namespace clash with layer name Alex DAMIAN
2014-12-12 11:45 ` [PATCH 15/18] toaster: projectapp Add links to the imported layers notify Alex DAMIAN
2014-12-12 11:45 ` [PATCH 16/18] toaster: Formatting changes to error messages Alex DAMIAN
2014-12-12 11:45 ` [PATCH 17/18] toaster: Change the full stop to a colon Alex DAMIAN
2014-12-12 11:45 ` [PATCH 18/18] toaster: importlayer Tidy up the page initialisation Alex DAMIAN
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.