Merge lp:~stephenjohnson505/lava-dashboard/graphingenhancement into lp:lava-dashboard

Proposed by Stephen Johnson
Status: Needs review
Proposed branch: lp:~stephenjohnson505/lava-dashboard/graphingenhancement
Merge into: lp:lava-dashboard
Diff against target: 3228 lines (+2914/-88) (has conflicts)
10 files modified
dashboard_app/admin.py (+11/-0)
dashboard_app/migrations/0030_auto__add_goal__add_testresults.py (+344/-0)
dashboard_app/models.py (+112/-0)
dashboard_app/static/dashboard_app/js/jquery.flot.axislabels.js (+404/-75)
dashboard_app/static/dashboard_app/js/jquery.flot.dashes.js (+231/-0)
dashboard_app/static/dashboard_app/js/jquery.flot.resize.js (+60/-0)
dashboard_app/static/dashboard_app/js/jquery.flot.threshold.js (+142/-0)
dashboard_app/templates/dashboard_app/image-report.html (+1280/-0)
dashboard_app/templates/dashboard_app/image-reports.html (+5/-3)
dashboard_app/views/images.py (+325/-10)
Text conflict in dashboard_app/templates/dashboard_app/image-report.html
Text conflict in dashboard_app/views/images.py
To merge this branch: bzr merge lp:~stephenjohnson505/lava-dashboard/graphingenhancement
Reviewer Review Type Date Requested Status
Seth Beinhart Pending
Linaro Validation Team Pending
Review via email: mp+166139@code.launchpad.net

Description of the change

Added graphing functionality to the image-reports tab of the dashboard.
Graphs can be viewed based on pass/fail, or based on measurements over time. Measurements can be further changed to include a target value, a floor value, and a weight.
Furthermore, project goals can be added to an image-set, which give the table of dates, as well as a dashed target-line for the weighted graph.

An example can be seen:
https://docs.google.com/file/d/0B9bUqR8Lmid1TlctZzZ0M3V0OHc/edit?usp=sharing

To post a comment you must log in.
407. By Stephen Johnson

Added pretty colored graphs.

Unmerged revisions

407. By Stephen Johnson

Added pretty colored graphs.

406. By Stephen Johnson

Added migration file

405. By Stephen Johnson

Fixed row graph, ready for merge

404. By Stephen Johnson

Fixed single date bug

403. By Stephen Johnson

Fixed typo, added the right axislabels

402. By Stephen Johnson

added graphing

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'dashboard_app/admin.py'
--- dashboard_app/admin.py 2013-04-01 08:50:05 +0000
+++ dashboard_app/admin.py 2013-07-17 17:43:24 +0000
@@ -40,6 +40,8 @@
40 SoftwarePackage,40 SoftwarePackage,
41 SoftwareSource,41 SoftwareSource,
42 Tag,42 Tag,
43 Goal,
44 GoalDate,
43 Test,45 Test,
44 TestCase,46 TestCase,
45 TestResult,47 TestResult,
@@ -154,6 +156,14 @@
154 list_filter = ('test',)156 list_filter = ('test',)
155157
156158
159class GoalsAdmin(admin.ModelAdmin):
160 class GoalDates(admin.TabularInline):
161 model = GoalDate
162 extra = 0
163 list_display = ('name',)
164 inlines = [GoalDates]
165
166
157class TestAdmin(admin.ModelAdmin):167class TestAdmin(admin.ModelAdmin):
158 pass168 pass
159169
@@ -219,6 +229,7 @@
219admin.site.register(SoftwarePackage, SoftwarePackageAdmin)229admin.site.register(SoftwarePackage, SoftwarePackageAdmin)
220admin.site.register(SoftwareSource, SoftwareSourceAdmin)230admin.site.register(SoftwareSource, SoftwareSourceAdmin)
221admin.site.register(Test, TestAdmin)231admin.site.register(Test, TestAdmin)
232admin.site.register(Goal, GoalsAdmin)
222admin.site.register(TestCase, TestCaseAdmin)233admin.site.register(TestCase, TestCaseAdmin)
223admin.site.register(TestResult, TestResultAdmin)234admin.site.register(TestResult, TestResultAdmin)
224admin.site.register(TestRun, TestRunAdmin)235admin.site.register(TestRun, TestRunAdmin)
225236
=== added file 'dashboard_app/migrations/0030_auto__add_goal__add_testresults.py'
--- dashboard_app/migrations/0030_auto__add_goal__add_testresults.py 1970-01-01 00:00:00 +0000
+++ dashboard_app/migrations/0030_auto__add_goal__add_testresults.py 2013-07-17 17:43:24 +0000
@@ -0,0 +1,344 @@
1# -*- coding: utf-8 -*-
2import datetime
3from south.db import db
4from south.v2 import SchemaMigration
5from django.db import models
6
7
8class Migration(SchemaMigration):
9
10 def forwards(self, orm):
11 # Adding model 'GoalDate'
12 db.create_table('dashboard_app_goaldate', (
13 ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
14 ('goal', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['dashboard_app.Goal'])),
15 ('tag', self.gf('django.db.models.fields.CharField')(max_length=4)),
16 ('startdate', self.gf('django.db.models.fields.DateField')()),
17 ('enddate', self.gf('django.db.models.fields.DateField')()),
18 ('value', self.gf('django.db.models.fields.FloatField')(default=0, max_length=64)),
19 ))
20 db.send_create_signal('dashboard_app', ['GoalDate'])
21
22 # Adding model 'Goal'
23 db.create_table('dashboard_app_goal', (
24 ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
25 ('name', self.gf('django.db.models.fields.TextField')()),
26 ))
27 db.send_create_signal('dashboard_app', ['Goal'])
28
29 # Adding field 'ImageSet.relatedDates'
30 db.add_column('dashboard_app_imageset', 'relatedDates',
31 self.gf('django.db.models.fields.related.ForeignKey')(to=orm['dashboard_app.Goal'], null=True, blank=True),
32 keep_default=False)
33
34 # Adding field 'TestCase.target'
35 db.add_column('dashboard_app_testcase', 'target',
36 self.gf('django.db.models.fields.FloatField')(default=0, max_length=64, null=True),
37 keep_default=False)
38
39 # Adding field 'TestCase.floor'
40 db.add_column('dashboard_app_testcase', 'floor',
41 self.gf('django.db.models.fields.FloatField')(default=0, max_length=64, null=True),
42 keep_default=False)
43
44 # Adding field 'TestCase.weight'
45 db.add_column('dashboard_app_testcase', 'weight',
46 self.gf('django.db.models.fields.FloatField')(default=1, max_length=64, null=True),
47 keep_default=False)
48
49 # Adding field 'TestCase.goal'
50 db.add_column('dashboard_app_testcase', 'goal',
51 self.gf('django.db.models.fields.FloatField')(default=0, max_length=64, null=True),
52 keep_default=False)
53
54
55 def backwards(self, orm):
56 # Deleting model 'GoalDate'
57 db.delete_table('dashboard_app_goaldate')
58
59 # Deleting model 'Goal'
60 db.delete_table('dashboard_app_goal')
61
62 # Deleting field 'ImageSet.relatedDates'
63 db.delete_column('dashboard_app_imageset', 'relatedDates_id')
64
65 # Deleting field 'TestCase.target'
66 db.delete_column('dashboard_app_testcase', 'target')
67
68 # Deleting field 'TestCase.floor'
69 db.delete_column('dashboard_app_testcase', 'floor')
70
71 # Deleting field 'TestCase.weight'
72 db.delete_column('dashboard_app_testcase', 'weight')
73
74 # Deleting field 'TestCase.goal'
75 db.delete_column('dashboard_app_testcase', 'goal')
76
77
78 models = {
79 'auth.group': {
80 'Meta': {'object_name': 'Group'},
81 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
82 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
83 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
84 },
85 'auth.permission': {
86 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
87 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
88 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
89 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
90 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
91 },
92 'auth.user': {
93 'Meta': {'object_name': 'User'},
94 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
95 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
96 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
97 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
98 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
99 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
100 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
101 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
102 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
103 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
104 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
105 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
106 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
107 },
108 'contenttypes.contenttype': {
109 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
110 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
111 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
112 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
113 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
114 },
115 'dashboard_app.attachment': {
116 'Meta': {'object_name': 'Attachment'},
117 'content': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True'}),
118 'content_filename': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
119 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
120 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
121 'mime_type': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
122 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}),
123 'public_url': ('django.db.models.fields.URLField', [], {'max_length': '512', 'blank': 'True'})
124 },
125 'dashboard_app.bundle': {
126 'Meta': {'ordering': "['-uploaded_on']", 'object_name': 'Bundle'},
127 '_gz_content': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True', 'db_column': "'gz_content'"}),
128 '_raw_content': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True', 'db_column': "'content'"}),
129 'bundle_stream': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'bundles'", 'to': "orm['dashboard_app.BundleStream']"}),
130 'content_filename': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
131 'content_sha1': ('django.db.models.fields.CharField', [], {'max_length': '40', 'unique': 'True', 'null': 'True'}),
132 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
133 'is_deserialized': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
134 'uploaded_by': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'uploaded_bundles'", 'null': 'True', 'to': "orm['auth.User']"}),
135 'uploaded_on': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.utcnow'})
136 },
137 'dashboard_app.bundledeserializationerror': {
138 'Meta': {'object_name': 'BundleDeserializationError'},
139 'bundle': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'deserialization_error'", 'unique': 'True', 'primary_key': 'True', 'to': "orm['dashboard_app.Bundle']"}),
140 'error_message': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
141 'traceback': ('django.db.models.fields.TextField', [], {'max_length': '32768'})
142 },
143 'dashboard_app.bundlestream': {
144 'Meta': {'object_name': 'BundleStream'},
145 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.Group']", 'null': 'True', 'blank': 'True'}),
146 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
147 'is_anonymous': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
148 'is_public': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
149 'name': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}),
150 'pathname': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'}),
151 'slug': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}),
152 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'})
153 },
154 'dashboard_app.goal': {
155 'Meta': {'object_name': 'Goal'},
156 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
157 'name': ('django.db.models.fields.TextField', [], {})
158 },
159 'dashboard_app.goaldate': {
160 'Meta': {'object_name': 'GoalDate'},
161 'enddate': ('django.db.models.fields.DateField', [], {}),
162 'goal': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['dashboard_app.Goal']"}),
163 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
164 'startdate': ('django.db.models.fields.DateField', [], {}),
165 'tag': ('django.db.models.fields.CharField', [], {'max_length': '4'}),
166 'value': ('django.db.models.fields.FloatField', [], {'default': '0', 'max_length': '64'})
167 },
168 'dashboard_app.hardwaredevice': {
169 'Meta': {'object_name': 'HardwareDevice'},
170 'description': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
171 'device_type': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
172 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
173 },
174 'dashboard_app.image': {
175 'Meta': {'object_name': 'Image'},
176 'filter': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'null': 'True', 'to': "orm['dashboard_app.TestRunFilter']"}),
177 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
178 'name': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '1024'})
179 },
180 'dashboard_app.imageset': {
181 'Meta': {'object_name': 'ImageSet'},
182 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
183 'images': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['dashboard_app.Image']", 'symmetrical': 'False'}),
184 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '1024'}),
185 'relatedDates': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['dashboard_app.Goal']", 'null': 'True', 'blank': 'True'})
186 },
187 'dashboard_app.launchpadbug': {
188 'Meta': {'object_name': 'LaunchpadBug'},
189 'bug_id': ('django.db.models.fields.PositiveIntegerField', [], {'unique': 'True'}),
190 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
191 'test_runs': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'launchpad_bugs'", 'symmetrical': 'False', 'to': "orm['dashboard_app.TestRun']"})
192 },
193 'dashboard_app.namedattribute': {
194 'Meta': {'unique_together': "(('object_id', 'name'),)", 'object_name': 'NamedAttribute'},
195 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
196 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
197 'name': ('django.db.models.fields.TextField', [], {}),
198 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}),
199 'value': ('django.db.models.fields.TextField', [], {})
200 },
201 'dashboard_app.pmqabundlestream': {
202 'Meta': {'object_name': 'PMQABundleStream'},
203 'bundle_stream': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'to': "orm['dashboard_app.BundleStream']"}),
204 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
205 },
206 'dashboard_app.softwarepackage': {
207 'Meta': {'unique_together': "(('name', 'version'),)", 'object_name': 'SoftwarePackage'},
208 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
209 'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
210 'version': ('django.db.models.fields.CharField', [], {'max_length': '128'})
211 },
212 'dashboard_app.softwarepackagescratch': {
213 'Meta': {'object_name': 'SoftwarePackageScratch'},
214 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
215 'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
216 'version': ('django.db.models.fields.CharField', [], {'max_length': '128'})
217 },
218 'dashboard_app.softwaresource': {
219 'Meta': {'object_name': 'SoftwareSource'},
220 'branch_revision': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
221 'branch_url': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
222 'branch_vcs': ('django.db.models.fields.CharField', [], {'max_length': '10'}),
223 'commit_timestamp': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
224 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
225 'project_name': ('django.db.models.fields.CharField', [], {'max_length': '32'})
226 },
227 'dashboard_app.tag': {
228 'Meta': {'object_name': 'Tag'},
229 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
230 'name': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '256'})
231 },
232 'dashboard_app.test': {
233 'Meta': {'object_name': 'Test'},
234 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
235 'name': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'blank': 'True'}),
236 'test_id': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '1024'})
237 },
238 'dashboard_app.testcase': {
239 'Meta': {'unique_together': "(('test', 'test_case_id'),)", 'object_name': 'TestCase'},
240 'floor': ('django.db.models.fields.FloatField', [], {'default': '0', 'max_length': '64', 'null': 'True'}),
241 'goal': ('django.db.models.fields.FloatField', [], {'default': '0', 'max_length': '64', 'null': 'True'}),
242 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
243 'name': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
244 'target': ('django.db.models.fields.FloatField', [], {'default': '0', 'max_length': '64', 'null': 'True'}),
245 'test': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'test_cases'", 'to': "orm['dashboard_app.Test']"}),
246 'test_case_id': ('django.db.models.fields.TextField', [], {}),
247 'units': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
248 'weight': ('django.db.models.fields.FloatField', [], {'default': '1', 'max_length': '64', 'null': 'True'})
249 },
250 'dashboard_app.testdefinition': {
251 'Meta': {'object_name': 'TestDefinition'},
252 'content': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
253 'description': ('django.db.models.fields.TextField', [], {}),
254 'environment': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
255 'format': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
256 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
257 'location': ('django.db.models.fields.CharField', [], {'default': "'LOCAL'", 'max_length': '64'}),
258 'mime_type': ('django.db.models.fields.CharField', [], {'default': "'text/plain'", 'max_length': '64'}),
259 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '512'}),
260 'target_dev_types': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
261 'target_os': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
262 'url': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
263 'version': ('django.db.models.fields.CharField', [], {'max_length': '256'})
264 },
265 'dashboard_app.testresult': {
266 'Meta': {'ordering': "('_order',)", 'object_name': 'TestResult'},
267 '_order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
268 'filename': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}),
269 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
270 'lineno': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}),
271 'measurement': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '20', 'decimal_places': '10', 'blank': 'True'}),
272 'message': ('django.db.models.fields.TextField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}),
273 'microseconds': ('django.db.models.fields.BigIntegerField', [], {'null': 'True', 'blank': 'True'}),
274 'relative_index': ('django.db.models.fields.PositiveIntegerField', [], {}),
275 'result': ('django.db.models.fields.PositiveSmallIntegerField', [], {}),
276 'test_case': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'test_results'", 'null': 'True', 'to': "orm['dashboard_app.TestCase']"}),
277 'test_run': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'test_results'", 'to': "orm['dashboard_app.TestRun']"}),
278 'timestamp': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'})
279 },
280 'dashboard_app.testrun': {
281 'Meta': {'ordering': "['-import_assigned_date']", 'object_name': 'TestRun'},
282 'analyzer_assigned_date': ('django.db.models.fields.DateTimeField', [], {}),
283 'analyzer_assigned_uuid': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '36'}),
284 'bundle': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'test_runs'", 'to': "orm['dashboard_app.Bundle']"}),
285 'devices': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'test_runs'", 'blank': 'True', 'to': "orm['dashboard_app.HardwareDevice']"}),
286 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
287 'import_assigned_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
288 'microseconds': ('django.db.models.fields.BigIntegerField', [], {'null': 'True', 'blank': 'True'}),
289 'packages': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'test_runs'", 'blank': 'True', 'to': "orm['dashboard_app.SoftwarePackage']"}),
290 'sources': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'test_runs'", 'blank': 'True', 'to': "orm['dashboard_app.SoftwareSource']"}),
291 'sw_image_desc': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
292 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'test_runs'", 'blank': 'True', 'to': "orm['dashboard_app.Tag']"}),
293 'test': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'test_runs'", 'to': "orm['dashboard_app.Test']"}),
294 'time_check_performed': ('django.db.models.fields.BooleanField', [], {'default': 'False'})
295 },
296 'dashboard_app.testrundenormalization': {
297 'Meta': {'object_name': 'TestRunDenormalization'},
298 'count_fail': ('django.db.models.fields.PositiveIntegerField', [], {}),
299 'count_pass': ('django.db.models.fields.PositiveIntegerField', [], {}),
300 'count_skip': ('django.db.models.fields.PositiveIntegerField', [], {}),
301 'count_unknown': ('django.db.models.fields.PositiveIntegerField', [], {}),
302 'test_run': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'denormalization'", 'unique': 'True', 'primary_key': 'True', 'to': "orm['dashboard_app.TestRun']"})
303 },
304 'dashboard_app.testrunfilter': {
305 'Meta': {'unique_together': "(('owner', 'name'),)", 'object_name': 'TestRunFilter'},
306 'build_number_attribute': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}),
307 'bundle_streams': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['dashboard_app.BundleStream']", 'symmetrical': 'False'}),
308 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
309 'name': ('django.db.models.fields.SlugField', [], {'max_length': '1024'}),
310 'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}),
311 'public': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
312 'uploaded_by': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'+'", 'null': 'True', 'to': "orm['auth.User']"})
313 },
314 'dashboard_app.testrunfilterattribute': {
315 'Meta': {'object_name': 'TestRunFilterAttribute'},
316 'filter': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'attributes'", 'to': "orm['dashboard_app.TestRunFilter']"}),
317 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
318 'name': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
319 'value': ('django.db.models.fields.CharField', [], {'max_length': '1024'})
320 },
321 'dashboard_app.testrunfiltersubscription': {
322 'Meta': {'unique_together': "(('user', 'filter'),)", 'object_name': 'TestRunFilterSubscription'},
323 'filter': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['dashboard_app.TestRunFilter']"}),
324 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
325 'level': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
326 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
327 },
328 'dashboard_app.testrunfiltertest': {
329 'Meta': {'object_name': 'TestRunFilterTest'},
330 'filter': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'tests'", 'to': "orm['dashboard_app.TestRunFilter']"}),
331 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
332 'index': ('django.db.models.fields.PositiveIntegerField', [], {}),
333 'test': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'to': "orm['dashboard_app.Test']"})
334 },
335 'dashboard_app.testrunfiltertestcase': {
336 'Meta': {'object_name': 'TestRunFilterTestCase'},
337 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
338 'index': ('django.db.models.fields.PositiveIntegerField', [], {}),
339 'test': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'cases'", 'to': "orm['dashboard_app.TestRunFilterTest']"}),
340 'test_case': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'to': "orm['dashboard_app.TestCase']"})
341 }
342 }
343
344 complete_apps = ['dashboard_app']
0\ No newline at end of file345\ No newline at end of file
1346
=== modified file 'dashboard_app/models.py'
--- dashboard_app/models.py 2013-04-01 08:50:05 +0000
+++ dashboard_app/models.py 2013-07-17 17:43:24 +0000
@@ -659,6 +659,45 @@
659 result=TestResult.RESULT_FAIL).count()659 result=TestResult.RESULT_FAIL).count()
660660
661661
662class Goal(models.Model):
663 name = models.TextField(
664 verbose_name = _("Goal Name"),
665 help_text = (_("""The name for this series of goals""")))
666
667 def __unicode__(self):
668 return self.name
669
670 def return_dates(self):
671 goaldate = GoalDate.objects.all()
672 date = {}
673 for name in goaldate:
674 if (name.goal == self):
675 date[name] = {'startdate':name.startdate.isoformat(),'enddate':name.enddate.isoformat(), 'value':name.value}
676 return date
677
678
679class GoalDate(models.Model):
680 goal = models.ForeignKey(Goal)
681
682 tag = models.CharField(
683 max_length = 4,
684 help_text = (_("""1-4 character tag which distinguishes this goal on the graph""")))
685
686 startdate = models.DateField(
687 verbose_name= _("Start Date"))
688
689 enddate = models.DateField(
690 verbose_name= _("End Date"))
691
692 value = models.FloatField(
693 verbose_name= _("Value"),
694 default=0,
695 max_length = 64,
696 help_text = (_("""The goal % for this milestone.""")))
697
698 def __unicode__(self):
699 return self.tag
700
662class TestCase(models.Model):701class TestCase(models.Model):
663 """702 """
664 Model for representing test cases.703 Model for representing test cases.
@@ -687,6 +726,34 @@
687 + _help_max_length(100)),726 + _help_max_length(100)),
688 verbose_name = _("Units"))727 verbose_name = _("Units"))
689728
729 target = models.FloatField(
730 default=0,
731 null = True,
732 max_length = 64,
733 help_text = (_("""The target value of the testcase, representing 100%.""")),
734 verbose_name= _("Target"))
735
736 floor = models.FloatField(
737 default=0,
738 max_length = 64,
739 null = True,
740 help_text = (_("""The lowest value of the testcase, representing 0%.""")),
741 verbose_name= _("Floor"))
742
743 weight = models.FloatField(
744 default=1,
745 null = True,
746 max_length = 64,
747 help_text = (_("""The weight of the testcase when graphed against others.""")),
748 verbose_name= _("Weight"))
749
750 goal = models.FloatField(
751 default=0,
752 null = True,
753 max_length = 64,
754 help_text = (_("""The goal % for a testcase.""")),
755 verbose_name= _("Goal"))
756
690 class Meta:757 class Meta:
691 unique_together = (('test', 'test_case_id'))758 unique_together = (('test', 'test_case_id'))
692759
@@ -1177,6 +1244,22 @@
1177 def test(self):1244 def test(self):
1178 return self.test_run.test1245 return self.test_run.test
11791246
1247 @property
1248 def target(self):
1249 return self.test_case.target
1250
1251 @property
1252 def weight(self):
1253 return self.test_case.weight
1254
1255 @property
1256 def floor(self):
1257 return self.test_case.floor
1258
1259 @property
1260 def goal(self):
1261 return self.test_case.goal
1262
1180 # Core attributes1263 # Core attributes
11811264
1182 result = models.PositiveSmallIntegerField(1265 result = models.PositiveSmallIntegerField(
@@ -1517,6 +1600,8 @@
15171600
1518 name = models.CharField(max_length=1024, unique=True)1601 name = models.CharField(max_length=1024, unique=True)
15191602
1603 relatedDates = models.ForeignKey(Goal, null=True, blank=True)
1604
1520 images = models.ManyToManyField(Image)1605 images = models.ManyToManyField(Image)
15211606
1522 def __unicode__(self):1607 def __unicode__(self):
@@ -1715,6 +1800,33 @@
1715 "dashboard_app.views.filters.views.filter_detail",1800 "dashboard_app.views.filters.views.filter_detail",
1716 [self.owner.username, self.name])1801 [self.owner.username, self.name])
17171802
1803 def get_testruns_impl(self, user, bundle_streams, attributes):
1804 accessible_bundle_streams = BundleStream.objects.accessible_by_principal(
1805 user)
1806 testruns = TestRun.objects.filter(
1807 models.Q(bundle__bundle_stream__in=accessible_bundle_streams),
1808 models.Q(bundle__bundle_stream__in=bundle_streams),
1809 )
1810
1811 for (name, value) in attributes:
1812 testruns = TestRun.objects.filter(
1813 id__in=testruns.values_list('id'),
1814 attributes__name=name, attributes__value=value)
1815
1816 # if the filter doesn't specify a test, we still only return one
1817 # test run per bundle. the display code knows to do different
1818 # things in this case.
1819 testruns = TestRun.objects.filter(
1820 id__in=testruns.values_list('id'),
1821 test=Test.objects.get(test_id='lava'))
1822
1823 return testruns
1824
1825 def get_test_runs(self, user):
1826 return self.get_testruns_impl(
1827 user,
1828 self.bundle_streams.all(),
1829 self.attributes.values_list('name', 'value'))
17181830
1719class TestRunFilterSubscription(models.Model):1831class TestRunFilterSubscription(models.Model):
17201832
17211833
=== modified file 'dashboard_app/static/dashboard_app/js/jquery.flot.axislabels.js' (properties changed: -x to +x)
--- dashboard_app/static/dashboard_app/js/jquery.flot.axislabels.js 2011-05-05 01:22:07 +0000
+++ dashboard_app/static/dashboard_app/js/jquery.flot.axislabels.js 2013-07-17 17:43:24 +0000
@@ -1,87 +1,416 @@
1/*1/*
2Flot plugin for labeling axis2Axis Labels Plugin for flot.
33http://github.com/markrcote/flot-axislabels
4 (xy)axis: {4
5 label: "label string",5Original code is Copyright (c) 2010 Xuan Luo.
6 labelPos: "high" or "low"6Original code was released under the GPLv3 license by Xuan Luo, September 2010.
7 }7Original code was rereleased under the MIT license by Xuan Luo, April 2012.
88
9This plugin allows you to label an axis without much fuss, by9Improvements by Mark Cote.
10replacing one of the extreme ticks with the chosen label string. Set10
11labelPos to "high" or "low" to replace respectively the maximum or the11Permission is hereby granted, free of charge, to any person obtaining
12minimum value of the ticks. User set axis.tickFormatter are respected12a copy of this software and associated documentation files (the
13and multiple axes supported.13"Software"), to deal in the Software without restriction, including
1414without limitation the rights to use, copy, modify, merge, publish,
15Rui Pereira15distribute, sublicense, and/or sell copies of the Software, and to
16rui (dot) pereira (at) gmail (dot) com16permit persons to whom the Software is furnished to do so, subject to
17the following conditions:
18
19The above copyright notice and this permission notice shall be
20included in all copies or substantial portions of the Software.
21
22THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
26LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
27OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
29
17*/30*/
18(function ($) {31(function ($) {
1932 var options = { };
20 function labelAxis(val, axis){33
21 var ticks, opts = axis.options;34 function canvasSupported() {
2235 return !!document.createElement('canvas').getContext;
23 // generator36 }
24 var tmpopts = axis.n == 1? opts: (typeof opts.alignedTo != 'undefined')? opts.alignedTo.options: null;37
25 // first axis or some axis aligned wrt it38 function canvasTextSupported() {
26 if (tmpopts && (tmpopts.autoscaleMargin == null ||39 if (!canvasSupported()) {
27 (tmpopts.labelPos == 'high' && tmpopts.max != null) ||40 return false;
28 (tmpopts.labelPos == 'low' && tmpopts.min != null)))41 }
29 // cut ticks not seen42 var dummy_canvas = document.createElement('canvas');
30 ticks = $.grep(axis.tickGenerator(axis), function(v){43 var context = dummy_canvas.getContext('2d');
31 return (v > axis.min && v < axis.max);44 return typeof context.fillText == 'function';
32 });45 }
33 // standard tick generator46
34 else ticks = axis.tickGenerator(axis);47 function css3TransitionSupported() {
3548 var div = document.createElement('div');
36 // formatter49 return typeof div.style.MozTransition != 'undefined' // Gecko
37 if ((opts.labelPos == 'high' && val == ticks[ticks.length-1]) ||50 || typeof div.style.OTransition != 'undefined' // Opera
38 (opts.labelPos == 'low' && val == ticks[0]))51 || typeof div.style.webkitTransition != 'undefined' // WebKit
39 return opts.label;52 || typeof div.style.transition != 'undefined';
40 else {53 }
41 // user set tickFormatter54
42 if ($.isFunction(opts.userFormatter)){55
43 var tmp = opts.userFormatter;56 function AxisLabel(axisName, position, padding, plot, opts) {
44 // avoid infinite loops57 this.axisName = axisName;
45 opts.userFormatter = null;58 this.position = position;
46 return tmp(val, axis);59 this.padding = padding;
60 this.plot = plot;
61 this.opts = opts;
62 this.width = 0;
63 this.height = 0;
64 }
65
66
67 CanvasAxisLabel.prototype = new AxisLabel();
68 CanvasAxisLabel.prototype.constructor = CanvasAxisLabel;
69 function CanvasAxisLabel(axisName, position, padding, plot, opts) {
70 AxisLabel.prototype.constructor.call(this, axisName, position, padding,
71 plot, opts);
72 }
73
74 CanvasAxisLabel.prototype.calculateSize = function() {
75 if (!this.opts.axisLabelFontSizePixels)
76 this.opts.axisLabelFontSizePixels = 14;
77 if (!this.opts.axisLabelFontFamily)
78 this.opts.axisLabelFontFamily = 'sans-serif';
79
80 var textWidth = this.opts.axisLabelFontSizePixels + this.padding;
81 var textHeight = this.opts.axisLabelFontSizePixels + this.padding;
82 if (this.position == 'left' || this.position == 'right') {
83 this.width = this.opts.axisLabelFontSizePixels + this.padding;
84 this.height = 0;
85 } else {
86 this.width = 0;
87 this.height = this.opts.axisLabelFontSizePixels + this.padding;
88 }
89 };
90
91 CanvasAxisLabel.prototype.draw = function(box) {
92 var ctx = this.plot.getCanvas().getContext('2d');
93 ctx.save();
94 ctx.font = this.opts.axisLabelFontSizePixels + 'px ' +
95 this.opts.axisLabelFontFamily;
96 var width = ctx.measureText(this.opts.axisLabel).width;
97 var height = this.opts.axisLabelFontSizePixels;
98 var x, y, angle = 0;
99 if (this.position == 'top') {
100 x = box.left + box.width/2 - width/2;
101 y = box.top + height*0.72;
102 } else if (this.position == 'bottom') {
103 x = box.left + box.width/2 - width/2;
104 y = box.top + box.height - height*0.72;
105 } else if (this.position == 'left') {
106 x = box.left + height*0.72;
107 y = box.height/2 + box.top + width/2;
108 angle = -Math.PI/2;
109 } else if (this.position == 'right') {
110 x = box.left + box.width - height*0.72;
111 y = box.height/2 + box.top - width/2;
112 angle = Math.PI/2;
113 }
114 ctx.translate(x, y);
115 ctx.rotate(angle);
116 ctx.fillText(this.opts.axisLabel, 0, 0);
117 ctx.restore();
118 };
119
120
121 HtmlAxisLabel.prototype = new AxisLabel();
122 HtmlAxisLabel.prototype.constructor = HtmlAxisLabel;
123 function HtmlAxisLabel(axisName, position, padding, plot, opts) {
124 AxisLabel.prototype.constructor.call(this, axisName, position,
125 padding, plot, opts);
126 }
127
128 HtmlAxisLabel.prototype.calculateSize = function() {
129 var elem = $('<div class="axisLabels" style="position:absolute;">' +
130 this.opts.axisLabel + '</div>');
131 this.plot.getPlaceholder().append(elem);
132 // store height and width of label itself, for use in draw()
133 this.labelWidth = elem.outerWidth(true);
134 this.labelHeight = elem.outerHeight(true);
135 elem.remove();
136
137 this.width = this.height = 0;
138 if (this.position == 'left' || this.position == 'right') {
139 this.width = this.labelWidth + this.padding;
140 } else {
141 this.height = this.labelHeight + this.padding;
142 }
143 };
144
145 HtmlAxisLabel.prototype.draw = function(box) {
146 this.plot.getPlaceholder().find('#' + this.axisName + 'Label').remove();
147 var elem = $('<div id="' + this.axisName +
148 'Label" " class="axisLabels" style="position:absolute;">'
149 + this.opts.axisLabel + '</div>');
150 this.plot.getPlaceholder().append(elem);
151 if (this.position == 'top') {
152 elem.css('left', box.left + box.width/2 - this.labelWidth/2 + 'px');
153 elem.css('top', box.top + 'px');
154 } else if (this.position == 'bottom') {
155 elem.css('left', box.left + box.width/2 - this.labelWidth/2 + 'px');
156 elem.css('top', box.top + box.height - this.labelHeight + 'px');
157 } else if (this.position == 'left') {
158 elem.css('top', box.top + box.height/2 - this.labelHeight/2 + 'px');
159 elem.css('left', box.left + 'px');
160 } else if (this.position == 'right') {
161 elem.css('top', box.top + box.height/2 - this.labelHeight/2 + 'px');
162 elem.css('left', box.left + box.width - this.labelWidth + 'px');
163 }
164 };
165
166
167 CssTransformAxisLabel.prototype = new HtmlAxisLabel();
168 CssTransformAxisLabel.prototype.constructor = CssTransformAxisLabel;
169 function CssTransformAxisLabel(axisName, position, padding, plot, opts) {
170 HtmlAxisLabel.prototype.constructor.call(this, axisName, position,
171 padding, plot, opts);
172 }
173
174 CssTransformAxisLabel.prototype.calculateSize = function() {
175 HtmlAxisLabel.prototype.calculateSize.call(this);
176 this.width = this.height = 0;
177 if (this.position == 'left' || this.position == 'right') {
178 this.width = this.labelHeight + this.padding;
179 } else {
180 this.height = this.labelHeight + this.padding;
181 }
182 };
183
184 CssTransformAxisLabel.prototype.transforms = function(degrees, x, y) {
185 var stransforms = {
186 '-moz-transform': '',
187 '-webkit-transform': '',
188 '-o-transform': '',
189 '-ms-transform': ''
190 };
191 if (x != 0 || y != 0) {
192 var stdTranslate = ' translate(' + x + 'px, ' + y + 'px)';
193 stransforms['-moz-transform'] += stdTranslate;
194 stransforms['-webkit-transform'] += stdTranslate;
195 stransforms['-o-transform'] += stdTranslate;
196 stransforms['-ms-transform'] += stdTranslate;
197 }
198 if (degrees != 0) {
199 var rotation = degrees / 90;
200 var stdRotate = ' rotate(' + degrees + 'deg)';
201 stransforms['-moz-transform'] += stdRotate;
202 stransforms['-webkit-transform'] += stdRotate;
203 stransforms['-o-transform'] += stdRotate;
204 stransforms['-ms-transform'] += stdRotate;
205 }
206 var s = 'top: 0; left: 0; ';
207 for (var prop in stransforms) {
208 if (stransforms[prop]) {
209 s += prop + ':' + stransforms[prop] + ';';
210 }
211 }
212 s += ';';
213 return s;
214 };
215
216 CssTransformAxisLabel.prototype.calculateOffsets = function(box) {
217 var offsets = { x: 0, y: 0, degrees: 0 };
218 if (this.position == 'bottom') {
219 offsets.x = box.left + box.width/2 - this.labelWidth/2;
220 offsets.y = box.top + box.height - this.labelHeight;
221 } else if (this.position == 'top') {
222 offsets.x = box.left + box.width/2 - this.labelWidth/2;
223 offsets.y = box.top;
224 } else if (this.position == 'left') {
225 offsets.degrees = -90;
226 offsets.x = box.left - this.labelWidth/2 + this.labelHeight/2;
227 offsets.y = box.height/2 + box.top;
228 } else if (this.position == 'right') {
229 offsets.degrees = 90;
230 offsets.x = box.left + box.width - this.labelWidth/2
231 - this.labelHeight/2;
232 offsets.y = box.height/2 + box.top;
233 }
234 return offsets;
235 };
236
237 CssTransformAxisLabel.prototype.draw = function(box) {
238 this.plot.getPlaceholder().find("." + this.axisName + "Label").remove();
239 var offsets = this.calculateOffsets(box);
240 var elem = $('<div class="axisLabels ' + this.axisName +
241 'Label" style="position:absolute; ' +
242 'color: ' + this.opts.color + '; ' +
243 this.transforms(offsets.degrees, offsets.x, offsets.y) +
244 '">' + this.opts.axisLabel + '</div>');
245 this.plot.getPlaceholder().append(elem);
246 };
247
248
249 IeTransformAxisLabel.prototype = new CssTransformAxisLabel();
250 IeTransformAxisLabel.prototype.constructor = IeTransformAxisLabel;
251 function IeTransformAxisLabel(axisName, position, padding, plot, opts) {
252 CssTransformAxisLabel.prototype.constructor.call(this, axisName,
253 position, padding,
254 plot, opts);
255 this.requiresResize = false;
256 }
257
258 IeTransformAxisLabel.prototype.transforms = function(degrees, x, y) {
259 // I didn't feel like learning the crazy Matrix stuff, so this uses
260 // a combination of the rotation transform and CSS positioning.
261 var s = '';
262 if (degrees != 0) {
263 var rotation = degrees/90;
264 while (rotation < 0) {
265 rotation += 4;
266 }
267 s += ' filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=' + rotation + '); ';
268 // see below
269 this.requiresResize = (this.position == 'right');
270 }
271 if (x != 0) {
272 s += 'left: ' + x + 'px; ';
273 }
274 if (y != 0) {
275 s += 'top: ' + y + 'px; ';
276 }
277 return s;
278 };
279
280 IeTransformAxisLabel.prototype.calculateOffsets = function(box) {
281 var offsets = CssTransformAxisLabel.prototype.calculateOffsets.call(
282 this, box);
283 // adjust some values to take into account differences between
284 // CSS and IE rotations.
285 if (this.position == 'top') {
286 // FIXME: not sure why, but placing this exactly at the top causes
287 // the top axis label to flip to the bottom...
288 offsets.y = box.top + 1;
289 } else if (this.position == 'left') {
290 offsets.x = box.left;
291 offsets.y = box.height/2 + box.top - this.labelWidth/2;
292 } else if (this.position == 'right') {
293 offsets.x = box.left + box.width - this.labelHeight;
294 offsets.y = box.height/2 + box.top - this.labelWidth/2;
295 }
296 return offsets;
297 };
298
299 IeTransformAxisLabel.prototype.draw = function(box) {
300 CssTransformAxisLabel.prototype.draw.call(this, box);
301 if (this.requiresResize) {
302 var elem = this.plot.getPlaceholder().find("." + this.axisName + "Label");
303 // Since we used CSS positioning instead of transforms for
304 // translating the element, and since the positioning is done
305 // before any rotations, we have to reset the width and height
306 // in case the browser wrapped the text (specifically for the
307 // y2axis).
308 elem.css('width', this.labelWidth);
309 elem.css('height', this.labelHeight);
310 }
311 };
312
313
314 function init(plot) {
315 // This is kind of a hack. There are no hooks in Flot between
316 // the creation and measuring of the ticks (setTicks, measureTickLabels
317 // in setupGrid() ) and the drawing of the ticks and plot box
318 // (insertAxisLabels in setupGrid() ).
319 //
320 // Therefore, we use a trick where we run the draw routine twice:
321 // the first time to get the tick measurements, so that we can change
322 // them, and then have it draw it again.
323 var secondPass = false;
324
325 var axisLabels = {};
326 var axisOffsetCounts = { left: 0, right: 0, top: 0, bottom: 0 };
327
328 var defaultPadding = 2; // padding between axis and tick labels
329 plot.hooks.draw.push(function (plot, ctx) {
330 var hasAxisLabels = false;
331 if (!secondPass) {
332 // MEASURE AND SET OPTIONS
333 $.each(plot.getAxes(), function(axisName, axis) {
334 var opts = axis.options // Flot 0.7
335 || plot.getOptions()[axisName]; // Flot 0.6
336 if (!opts || !opts.axisLabel || !axis.show)
337 return;
338
339 hasAxisLabels = true;
340 var renderer = null;
341
342 if (!opts.axisLabelUseHtml &&
343 navigator.appName == 'Microsoft Internet Explorer') {
344 var ua = navigator.userAgent;
345 var re = new RegExp("MSIE ([0-9]{1,}[\.0-9]{0,})");
346 if (re.exec(ua) != null) {
347 rv = parseFloat(RegExp.$1);
348 }
349 if (rv >= 9 && !opts.axisLabelUseCanvas && !opts.axisLabelUseHtml) {
350 renderer = CssTransformAxisLabel;
351 } else if (!opts.axisLabelUseCanvas && !opts.axisLabelUseHtml) {
352 renderer = IeTransformAxisLabel;
353 } else if (opts.axisLabelUseCanvas) {
354 renderer = CanvasAxisLabel;
355 } else {
356 renderer = HtmlAxisLabel;
357 }
358 } else {
359 if (opts.axisLabelUseHtml || (!css3TransitionSupported() && !canvasTextSupported()) && !opts.axisLabelUseCanvas) {
360 renderer = HtmlAxisLabel;
361 } else if (opts.axisLabelUseCanvas || !css3TransitionSupported()) {
362 renderer = CanvasAxisLabel;
363 } else {
364 renderer = CssTransformAxisLabel;
365 }
366 }
367
368 var padding = opts.axisLabelPadding === undefined ?
369 defaultPadding : opts.axisLabelPadding;
370
371 axisLabels[axisName] = new renderer(axisName,
372 axis.position, padding,
373 plot, opts);
374
375 // flot interprets axis.labelHeight and .labelWidth as
376 // the height and width of the tick labels. We increase
377 // these values to make room for the axis label and
378 // padding.
379
380 axisLabels[axisName].calculateSize();
381
382 // AxisLabel.height and .width are the size of the
383 // axis label and padding.
384 axis.labelHeight += axisLabels[axisName].height;
385 axis.labelWidth += axisLabels[axisName].width;
386 opts.labelHeight = axis.labelHeight;
387 opts.labelWidth = axis.labelWidth;
388 });
389 // if there are axis labels re-draw with new label widths and heights
390 if (hasAxisLabels) {
391 secondPass = true;
392 plot.setupGrid();
393 plot.draw();
394 }
47 } else {395 } else {
48 // scientific notation for small values396 // DRAW
49 if ((axis.datamax != 0 && Math.abs(axis.datamax) < 1e-5) ||397 $.each(plot.getAxes(), function(axisName, axis) {
50 (axis.datamin != 0 && Math.abs(axis.datamin) < 1e-5))398 var opts = axis.options // Flot 0.7
51 return val.toPrecision(2);399 || plot.getOptions()[axisName]; // Flot 0.6
52 else return val.toFixed(axis.tickDecimals);400 if (!opts || !opts.axisLabel || !axis.show)
53 }401 return;
54 }
55 }
56402
57 function init(plot){403 axisLabels[axisName].draw(axis.box);
58 plot.hooks.processOptions.push(function(plot, options){
59 // separate X and Y
60 $.each({x: options.xaxes, y: options.yaxes}, function(direction, axes){
61 // get only axes with labels
62 $.each($.grep(axes, function(v){
63 return (typeof v.label != 'undefined' && v.label);
64 }), function(i, axis){
65 if ($.isFunction(axis.tickFormatter))
66 axis.userFormatter = axis.tickFormatter;
67 if (typeof axis.alignTicksWithAxis != 'undefined')
68 $.each(plot.getAxes(), function(k,v){
69 if (v.n == axis.alignTicksWithAxis && v.direction == direction)
70 axis.alignedTo = v;
71 });
72 axis.tickFormatter = labelAxis;
73 });404 });
74 });405 }
75 });406 });
76 }407 }
77408
78 var options = { xaxis: {label: null, labelPos: 'high'},
79 yaxis: {label: null, labelPos: 'high'} };
80409
81 $.plot.plugins.push({410 $.plot.plugins.push({
82 init: init,411 init: init,
83 options: options,412 options: options,
84 name: "axislabels",413 name: 'axisLabels',
85 version: "0.1"414 version: '2.0b0'
86 });415 });
87})(jQuery);416})(jQuery);
88417
=== added file 'dashboard_app/static/dashboard_app/js/jquery.flot.dashes.js'
--- dashboard_app/static/dashboard_app/js/jquery.flot.dashes.js 1970-01-01 00:00:00 +0000
+++ dashboard_app/static/dashboard_app/js/jquery.flot.dashes.js 2013-07-17 17:43:24 +0000
@@ -0,0 +1,231 @@
1/*
2 * jQuery.flot.dashes
3 *
4 * options = {
5 * series: {
6 * dashes: {
7 *
8 * // show
9 * // default: false
10 * // Whether to show dashes for the series.
11 * show: <boolean>,
12 *
13 * // lineWidth
14 * // default: 2
15 * // The width of the dashed line in pixels.
16 * lineWidth: <number>,
17 *
18 * // dashLength
19 * // default: 10
20 * // Controls the length of the individual dashes and the amount of
21 * // space between them.
22 * // If this is a number, the dashes and spaces will have that length.
23 * // If this is an array, it is read as [ dashLength, spaceLength ]
24 * dashLength: <number> or <array[2]>
25 * }
26 * }
27 * }
28 */
29(function($){
30
31 function init(plot) {
32
33 plot.hooks.drawSeries.push(function(plot, ctx, series) {
34
35 if (!series.dashes.show) {
36 return;
37 }
38
39 var plotOffset = plot.getPlotOffset();
40
41 function plotDashes(datapoints, xoffset, yoffset, axisx, axisy) {
42
43 var points = datapoints.points,
44 ps = datapoints.pointsize,
45 prevx = null,
46 prevy = null,
47 dashRemainder = 0,
48 dashOn = true,
49 dashOnLength,
50 dashOffLength;
51
52 if (series.dashes.dashLength[0]) {
53 dashOnLength = series.dashes.dashLength[0];
54 if (series.dashes.dashLength[1]) {
55 dashOffLength = series.dashes.dashLength[1];
56 } else {
57 dashOffLength = dashOnLength;
58 }
59 } else {
60 dashOffLength = dashOnLength = series.dashes.dashLength;
61 }
62
63 ctx.beginPath();
64
65 for (var i = ps; i < points.length; i += ps) {
66
67 var x1 = points[i - ps],
68 y1 = points[i - ps + 1],
69 x2 = points[i],
70 y2 = points[i + 1];
71
72 if (x1 == null || x2 == null) continue;
73
74 // clip with ymin
75 if (y1 <= y2 && y1 < axisy.min) {
76 if (y2 < axisy.min) continue; // line segment is outside
77 // compute new intersection point
78 x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
79 y1 = axisy.min;
80 } else if (y2 <= y1 && y2 < axisy.min) {
81 if (y1 < axisy.min) continue;
82 x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
83 y2 = axisy.min;
84 }
85
86 // clip with ymax
87 if (y1 >= y2 && y1 > axisy.max) {
88 if (y2 > axisy.max) continue;
89 x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
90 y1 = axisy.max;
91 } else if (y2 >= y1 && y2 > axisy.max) {
92 if (y1 > axisy.max) continue;
93 x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
94 y2 = axisy.max;
95 }
96
97 // clip with xmin
98 if (x1 <= x2 && x1 < axisx.min) {
99 if (x2 < axisx.min) continue;
100 y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
101 x1 = axisx.min;
102 } else if (x2 <= x1 && x2 < axisx.min) {
103 if (x1 < axisx.min) continue;
104 y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
105 x2 = axisx.min;
106 }
107
108 // clip with xmax
109 if (x1 >= x2 && x1 > axisx.max) {
110 if (x2 > axisx.max) continue;
111 y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
112 x1 = axisx.max;
113 } else if (x2 >= x1 && x2 > axisx.max) {
114 if (x1 > axisx.max) continue;
115 y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
116 x2 = axisx.max;
117 }
118
119 if (x1 != prevx || y1 != prevy) {
120 ctx.moveTo(axisx.p2c(x1) + xoffset, axisy.p2c(y1) + yoffset);
121 }
122
123 var ax1 = axisx.p2c(x1) + xoffset,
124 ay1 = axisy.p2c(y1) + yoffset,
125 ax2 = axisx.p2c(x2) + xoffset,
126 ay2 = axisy.p2c(y2) + yoffset,
127 dashOffset;
128
129 function lineSegmentOffset(segmentLength) {
130
131 var c = Math.sqrt(Math.pow(ax2 - ax1, 2) + Math.pow(ay2 - ay1, 2));
132
133 if (c <= segmentLength) {
134 return {
135 deltaX: ax2 - ax1,
136 deltaY: ay2 - ay1,
137 distance: c,
138 remainder: segmentLength - c
139 }
140 } else {
141 var xsign = ax2 > ax1 ? 1 : -1,
142 ysign = ay2 > ay1 ? 1 : -1;
143 return {
144 deltaX: xsign * Math.sqrt(Math.pow(segmentLength, 2) / (1 + Math.pow((ay2 - ay1)/(ax2 - ax1), 2))),
145 deltaY: ysign * Math.sqrt(Math.pow(segmentLength, 2) - Math.pow(segmentLength, 2) / (1 + Math.pow((ay2 - ay1)/(ax2 - ax1), 2))),
146 distance: segmentLength,
147 remainder: 0
148 };
149 }
150 }
151 //-end lineSegmentOffset
152
153 do {
154
155 dashOffset = lineSegmentOffset(
156 dashRemainder > 0 ? dashRemainder :
157 dashOn ? dashOnLength : dashOffLength);
158
159 if (dashOffset.deltaX != 0 || dashOffset.deltaY != 0) {
160 if (dashOn) {
161 ctx.lineTo(ax1 + dashOffset.deltaX, ay1 + dashOffset.deltaY);
162 } else {
163 ctx.moveTo(ax1 + dashOffset.deltaX, ay1 + dashOffset.deltaY);
164 }
165 }
166
167 dashOn = !dashOn;
168 dashRemainder = dashOffset.remainder;
169 ax1 += dashOffset.deltaX;
170 ay1 += dashOffset.deltaY;
171
172 } while (dashOffset.distance > 0);
173
174 prevx = x2;
175 prevy = y2;
176 }
177
178 ctx.stroke();
179 }
180 //-end plotDashes
181
182 ctx.save();
183 ctx.translate(plotOffset.left, plotOffset.top);
184 ctx.lineJoin = 'round';
185
186 var lw = series.dashes.lineWidth,
187 sw = series.shadowSize;
188
189 // FIXME: consider another form of shadow when filling is turned on
190 if (lw > 0 && sw > 0) {
191 // draw shadow as a thick and thin line with transparency
192 ctx.lineWidth = sw;
193 ctx.strokeStyle = "rgba(0,0,0,0.1)";
194 // position shadow at angle from the mid of line
195 var angle = Math.PI/18;
196 plotDashes(series.datapoints, Math.sin(angle) * (lw/2 + sw/2), Math.cos(angle) * (lw/2 + sw/2), series.xaxis, series.yaxis);
197 ctx.lineWidth = sw/2;
198 plotDashes(series.datapoints, Math.sin(angle) * (lw/2 + sw/4), Math.cos(angle) * (lw/2 + sw/4), series.xaxis, series.yaxis);
199 }
200
201 ctx.lineWidth = lw;
202 ctx.strokeStyle = series.color;
203
204 if (lw > 0) {
205 plotDashes(series.datapoints, 0, 0, series.xaxis, series.yaxis);
206 }
207
208 ctx.restore();
209
210 });
211 //-end drawSeries hook
212
213 }
214 //-end init
215
216 $.plot.plugins.push({
217 init: init,
218 options: {
219 series: {
220 dashes: {
221 show: false,
222 lineWidth: 2,
223 dashLength: 10
224 }
225 }
226 },
227 name: 'dashes',
228 version: '0.1b'
229 });
230
231})(jQuery)
0\ No newline at end of file232\ No newline at end of file
1233
=== added file 'dashboard_app/static/dashboard_app/js/jquery.flot.resize.js'
--- dashboard_app/static/dashboard_app/js/jquery.flot.resize.js 1970-01-01 00:00:00 +0000
+++ dashboard_app/static/dashboard_app/js/jquery.flot.resize.js 2013-07-17 17:43:24 +0000
@@ -0,0 +1,60 @@
1/*
2Flot plugin for automatically redrawing plots when the placeholder
3size changes, e.g. on window resizes.
4
5It works by listening for changes on the placeholder div (through the
6jQuery resize event plugin) - if the size changes, it will redraw the
7plot.
8
9There are no options. If you need to disable the plugin for some
10plots, you can just fix the size of their placeholders.
11*/
12
13
14/* Inline dependency:
15 * jQuery resize event - v1.1 - 3/14/2010
16 * http://benalman.com/projects/jquery-resize-plugin/
17 *
18 * Copyright (c) 2010 "Cowboy" Ben Alman
19 * Dual licensed under the MIT and GPL licenses.
20 * http://benalman.com/about/license/
21 */
22(function($,h,c){var a=$([]),e=$.resize=$.extend($.resize,{}),i,k="setTimeout",j="resize",d=j+"-special-event",b="delay",f="throttleWindow";e[b]=250;e[f]=true;$.event.special[j]={setup:function(){if(!e[f]&&this[k]){return false}var l=$(this);a=a.add(l);$.data(this,d,{w:l.width(),h:l.height()});if(a.length===1){g()}},teardown:function(){if(!e[f]&&this[k]){return false}var l=$(this);a=a.not(l);l.removeData(d);if(!a.length){clearTimeout(i)}},add:function(l){if(!e[f]&&this[k]){return false}var n;function m(s,o,p){var q=$(this),r=$.data(this,d);r.w=o!==c?o:q.width();r.h=p!==c?p:q.height();n.apply(this,arguments)}if($.isFunction(l)){n=l;return m}else{n=l.handler;l.handler=m}}};function g(){i=h[k](function(){a.each(function(){var n=$(this),m=n.width(),l=n.height(),o=$.data(this,d);if(m!==o.w||l!==o.h){n.trigger(j,[o.w=m,o.h=l])}});g()},e[b])}})(jQuery,this);
23
24
25(function ($) {
26 var options = { }; // no options
27
28 function init(plot) {
29 function onResize() {
30 var placeholder = plot.getPlaceholder();
31
32 // somebody might have hidden us and we can't plot
33 // when we don't have the dimensions
34 if (placeholder.width() == 0 || placeholder.height() == 0)
35 return;
36
37 plot.resize();
38 plot.setupGrid();
39 plot.draw();
40 }
41
42 function bindEvents(plot, eventHolder) {
43 plot.getPlaceholder().resize(onResize);
44 }
45
46 function shutdown(plot, eventHolder) {
47 plot.getPlaceholder().unbind("resize", onResize);
48 }
49
50 plot.hooks.bindEvents.push(bindEvents);
51 plot.hooks.shutdown.push(shutdown);
52 }
53
54 $.plot.plugins.push({
55 init: init,
56 options: options,
57 name: 'resize',
58 version: '1.0'
59 });
60})(jQuery);
061
=== added file 'dashboard_app/static/dashboard_app/js/jquery.flot.threshold.js'
--- dashboard_app/static/dashboard_app/js/jquery.flot.threshold.js 1970-01-01 00:00:00 +0000
+++ dashboard_app/static/dashboard_app/js/jquery.flot.threshold.js 2013-07-17 17:43:24 +0000
@@ -0,0 +1,142 @@
1/* Flot plugin for thresholding data.
2
3Copyright (c) 2007-2013 IOLA and Ole Laursen.
4Licensed under the MIT license.
5
6The plugin supports these options:
7
8series: {
9threshold: {
10below: number
11color: colorspec
12}
13}
14
15It can also be applied to a single series, like this:
16
17$.plot( $("#placeholder"), [{
18data: [ ... ],
19threshold: { ... }
20}])
21
22An array can be passed for multiple thresholding, like this:
23
24threshold: [{
25below: number1
26color: color1
27},{
28below: number2
29color: color2
30}]
31
32These multiple threshold objects can be passed in any order since they are
33sorted by the processing function.
34
35The data points below "below" are drawn with the specified color. This makes
36it easy to mark points below 0, e.g. for budget data.
37
38Internally, the plugin works by splitting the data into two series, above and
39below the threshold. The extra series below the threshold will have its label
40cleared and the special "originSeries" attribute set to the original series.
41You may need to check for this in hover events.
42
43*/
44
45(function ($) {
46 var options = {
47 series: { threshold: null } // or { below: number, color: color spec}
48 };
49
50 function init(plot) {
51 function thresholdData(plot, s, datapoints, below, color) {
52 var ps = datapoints.pointsize, i, x, y, p, prevp,
53 thresholded = $.extend({}, s); // note: shallow copy
54
55 thresholded.datapoints = { points: [], pointsize: ps, format: datapoints.format };
56 thresholded.label = null;
57 thresholded.color = color;
58 thresholded.threshold = null;
59 thresholded.originSeries = s;
60 thresholded.data = [];
61
62 var origpoints = datapoints.points,
63 addCrossingPoints = s.lines.show;
64
65 var threspoints = [];
66 var newpoints = [];
67 var m;
68
69 for (i = 0; i < origpoints.length; i += ps) {
70 x = origpoints[i];
71 y = origpoints[i + 1];
72
73 prevp = p;
74 if (y < below)
75 p = threspoints;
76 else
77 p = newpoints;
78
79 if (addCrossingPoints && prevp != p && x != null
80 && i > 0 && origpoints[i - ps] != null) {
81 var interx = x + (below - y) * (x - origpoints[i - ps]) / (y - origpoints[i - ps + 1]);
82 prevp.push(interx);
83 prevp.push(below);
84 for (m = 2; m < ps; ++m)
85 prevp.push(origpoints[i + m]);
86
87 p.push(null); // start new segment
88 p.push(null);
89 for (m = 2; m < ps; ++m)
90 p.push(origpoints[i + m]);
91 p.push(interx);
92 p.push(below);
93 for (m = 2; m < ps; ++m)
94 p.push(origpoints[i + m]);
95 }
96
97 p.push(x);
98 p.push(y);
99 for (m = 2; m < ps; ++m)
100 p.push(origpoints[i + m]);
101 }
102
103 datapoints.points = newpoints;
104 thresholded.datapoints.points = threspoints;
105
106 if (thresholded.datapoints.points.length > 0) {
107 var origIndex = $.inArray(s, plot.getData());
108 // Insert newly-generated series right after original one (to prevent it from becoming top-most)
109 plot.getData().splice(origIndex + 1, 0, thresholded);
110 }
111
112 // FIXME: there are probably some edge cases left in bars
113 }
114
115 function processThresholds(plot, s, datapoints) {
116 if (!s.threshold)
117 return;
118
119 if (s.threshold instanceof Array) {
120 s.threshold.sort(function(a, b) {
121 return a.below - b.below;
122 });
123
124 $(s.threshold).each(function(i, th) {
125 thresholdData(plot, s, datapoints, th.below, th.color);
126 });
127 }
128 else {
129 thresholdData(plot, s, datapoints, s.threshold.below, s.threshold.color);
130 }
131 }
132
133 plot.hooks.processDatapoints.push(processThresholds);
134 }
135
136 $.plot.plugins.push({
137 init: init,
138 options: options,
139 name: 'threshold',
140 version: '1.2'
141 });
142})(jQuery);
0143
=== modified file 'dashboard_app/templates/dashboard_app/image-report.html'
--- dashboard_app/templates/dashboard_app/image-report.html 2013-07-01 15:49:15 +0000
+++ dashboard_app/templates/dashboard_app/image-report.html 2013-07-17 17:43:24 +0000
@@ -4,6 +4,7 @@
4{{ block.super }}4{{ block.super }}
5<link rel="stylesheet" type="text/css" href="{{ STATIC_URL }}dashboard_app/css/image-report.css"/>5<link rel="stylesheet" type="text/css" href="{{ STATIC_URL }}dashboard_app/css/image-report.css"/>
6<script type="text/javascript" src="{{ STATIC_URL }}dashboard_app/js/image-report.js"></script>6<script type="text/javascript" src="{{ STATIC_URL }}dashboard_app/js/image-report.js"></script>
7<<<<<<< TREE
7<script src="{{ STATIC_URL }}dashboard_app/js/excanvas.min.js"></script>8<script src="{{ STATIC_URL }}dashboard_app/js/excanvas.min.js"></script>
8<script src="{{ STATIC_URL }}dashboard_app/js/jquery.flot.min.js"></script>9<script src="{{ STATIC_URL }}dashboard_app/js/jquery.flot.min.js"></script>
9<script src="{{ STATIC_URL }}dashboard_app/js/jquery.flot.dashes.min.js"></script>10<script src="{{ STATIC_URL }}dashboard_app/js/jquery.flot.dashes.min.js"></script>
@@ -15,10 +16,23 @@
15 test_names = $.parseJSON($('<div/>').html("{{test_names}}").text());16 test_names = $.parseJSON($('<div/>').html("{{test_names}}").text());
16 columns = $.parseJSON($('<div/>').html("{{columns}}").text());17 columns = $.parseJSON($('<div/>').html("{{columns}}").text());
17</script>18</script>
19=======
20<script type="text/javascript" src="{{ STATIC_URL }}lava-server/js/jquery.dataTables.min.js"></script>
21<script type="text/javascript" src="{{ STATIC_URL }}dashboard_app/js/jquery.flot.min.js"></script>
22<script type="text/javascript" src="{{ STATIC_URL }}dashboard_app/js/jquery.flot.stack.min.js"></script>
23<script type="text/javascript" src="{{ STATIC_URL }}dashboard_app/js/jquery.flot.resize.js"></script>
24<script type="text/javascript" src="{{ STATIC_URL }}dashboard_app/js/jquery.flot.dashes.js"></script>
25<script type="text/javascript" src="{{ STATIC_URL }}dashboard_app/js/jquery.flot.axislabels.js"></script>
26<script type="text/javascript" src="{{ STATIC_URL }}dashboard_app/js/jquery.flot.threshold.js"></script>
27<!-- cp jquery.flot.resize.js /var/www/lava-server/static/dashboard_app/js/ -->
28<!-- cp jquery.flot.dashes.js /var/www/lava-server/static/dashboard_app/js/ -->
29<!-- cp jquery.flot.axislabels.js /var/www/lava-server/static/dashboard_app/js/ -->
30>>>>>>> MERGE-SOURCE
18{% endblock %}31{% endblock %}
1932
20{% block content %}33{% block content %}
21<h1>Image Report: {{ image.name }}</h1>34<h1>Image Report: {{ image.name }}</h1>
35<<<<<<< TREE
2236
2337
24<div id="outer-container">38<div id="outer-container">
@@ -83,6 +97,1272 @@
83</div>97</div>
8498
8599
100=======
101<div style="display:inline-block;padding-left:20px;width:95%;">
102<div id="outer" style="width: 95%;">
103 <div id="inner" style="width: 75%; margin: 0 auto; ">
104 <input type="radio" name=typeselect id="passfailcheck" onclick="exposeList()" unchecked></input>Pass/Fail Graph
105 <input type="radio" name=typeselect id="kpicheck" onclick="exposeList()" checked></input>Measurement Graph
106 <div id="kpiinfo" style="display:inline">(
107 <input type="checkbox" id="weightedcheck" onclick="exposeList()"></input>Weighted
108 <div id="weightedinfo" style="display:none">
109 <input type="checkbox" id="weightedonly" onclick="draw()"></input>Only Goal
110 </div>
111)
112 <div style=float:right> <input type="checkbox" id="adjustedcheck" onclick="draw()"></input>Graph Adjusted Values</div>
113 </div>
114 </div>
115</div>
116
117<div id="error" style=color:#FF0000;>
118</div>
119<br>
120<div id="placeholder" style="width:90%;height:250px;float:left;">
121</div>
122<div id="legendtitle" style="width:9%;float:right;"><b>Legend</b></div>
123<div id="labeler" style="height:180px;width:9%;float:right;overflow:auto;border:1px solid gray;">
124</div>
125<div id="legendhelp" style="width:9%;float:right;border:1px solid gray;""><p style="font-size:11px">&nbsp;-Solid lines are values<br>&nbsp;-Dashed lines are goals</p>
126</div>
127<br>
128<select id="startDate" onchange="draw()" style="display:inline;width:15%">
129 <option>Start Date</option>
130</select>
131<select id="endDate" onchange="draw()" style="display:inline;width:15%">
132 <option>End Date</option>
133</select>
134<select id="startImage" onchange="draw()" style="display:none;width:15%">
135 <option>Start Image</option>
136</select>
137<select id="endImage" onchange="draw()" style="display:none;width:15%">
138 <option>End Image</option>
139</select>
140<br>
141<input type="checkbox" id="imagecheck" style="float:left" onclick="changeType()"></input>Graph by Image
142<br>
143<input type="checkbox" id="cbChoices" style="float:left;" onclick="exposeList()"></input>Choose Tests:
144<div id="goal_dates_label" style="float:right;width:15%"><div id="goal_dates_label" style="float:left"><b>Project Goals</b></div></div>
145<br>
146<div id="rows" style="height:120px;width:30%;overflow:auto;border:1px solid blue;display:none">
147</div>
148<div id="testcases" style="height:120px;width:30%;overflow:auto;border:1px solid blue;float:left;display:none">
149</div>
150<div id="goal_dates" style="height:160px;width:15%;overflow:auto;border:1px solid #ff4400;display:inline;float:right">
151</div>
152<div id="kpi" style="height:120px;width:20%;overflow:auto;border:1px solid green;display:none">
153 <b><label for="kpi" id="kpilabel" >KPI Data</label></b><br>
154 Target:&emsp;&nbsp;&emsp;&nbsp;<input type="text" id="kpitarget" style="width:50px" readonly></input>
155 <br>
156 Floor:&emsp;&emsp;&emsp;<input type="text" id="kpifloor" style="width:50px" readonly></input>
157 <br>
158 Weight:&emsp;&emsp;&nbsp;<input type="text" id="kpiweight" style="width:50px" readonly></input>
159 <br>
160 Goal(XX%)&emsp;<input type="text" id="kpigoal" style="width:50px" readonly></input>
161</div>
162<br>
163Link: <input id="permalink" type="text" style="width:75%"></input>
164<br>
165<input type="checkbox" id="cbHelp" style="float:left;" onclick="exposeHelp()"></input><b>Display Help</b><br>
166<div id="help" style="height:80px;width:80%;overflow:auto;border:1px solid black;display:none">
167 <b>Pass/Fail Graph:</b> Displays a Pass Fail graph for the current Image Report.<br>
168 <b>&emsp;Choose Tests:</b> If this box is unchecked, the graph displays a cumulative graph. Otherwise you can select individual tests to graph.<br>
169 <b>KPI Graph:</b> Displays a graph based on the measurements of the selected test cases. These are multiple ways to display the information.<br>&nbsp;&nbsp;<u>Note</u>: If no options are checked, displays the measurement values of the testcases over time.<br>
170 <b>&emsp;Weighted:</b> Displays a bar graph which aggregates the selected testcases into a percent. Calculated by averaging the adjusted value multiplied by the testcases weight.<br>
171 <b>&emsp;Only Goal:</b> Removes the individual test lines to display only the weighted bar graph.<br>
172 <b>&emsp;Graph Adjusted Values:</b> Displays the measurements as a percent, calculated using the range between the target and floor.<br>
173 <b>Graph By Image:</b> Allows you to graph using image names, instead of dates.<br>
174 <b>Link:</b> A URL link to this page with current settings.
175</div>
176<script type="text/javascript">
177
178if (document.URL.indexOf("?") != -1) {
179 var baseurl=document.URL.slice(0,document.URL.lastIndexOf("?"));
180 var url_options = document.URL.slice(document.URL.lastIndexOf("?")+1,document.URL.length+1);
181}
182else {
183 var baseurl = document.URL;
184 var url_options = '';
185}
186
187function zip() {
188 var args = [].slice.call(arguments);
189 var shortest = args.length==0 ? [] : args.reduce(function(a,b){
190 return a.length<b.length ? a : b
191 });
192
193 return shortest.map(function(_,i){
194 return args.map(function(array){return array[i]})
195 });
196};
197
198function trim (str) {
199 return str.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
200}
201
202function unzip(a) {
203 var b = [];
204 var c = [];
205 for (var i=0;i<a.length;i++) {
206 b.push(a[i][0]);
207 c.push(a[i][1]);
208 }
209 return [b,c];
210};
211
212function doublesort(a, b, func) {
213 var c = zip(a,b);
214 c = c.sort(function(a,b) { if(a[0][2] > b[0][2]){return 1}else{return -1} } );
215 return unzip(c);
216}
217
218function pare(a, m) {
219 var b = new Array(m);
220 var n2 = a.length - 2;
221 var m2 = m - 2;
222 b[0] = a[0];
223 rem = Math.floor(n2/m2)
224 for (var i=0;i<(n2/rem)+1;i++) {
225 b[i]=a[i*rem];
226 }
227 b[b.length] = a[n2+1];
228 diff = b[1][0] - b[0][0];
229 for (var i=6;i<b.length;i++){
230 try {
231 if (b[i][0] - b[i-1][0] != diff) {
232 b.splice(i,1);
233 i--;
234 }
235 }
236 catch(err){
237 b.splice(i,1);
238 i--;
239 }
240 }
241 return b;
242}
243
244function rainbow(numOfSteps, step) {
245 var r, g, b;
246 var h = step / numOfSteps;
247 var i = ~~(h * 5);
248 var f = h * 8 - i;
249 var q = 1 - f;
250 switch(i % 6){
251 case 0: r = q, g = 0, b = 0; break;
252 case 1: r = 0, g = q, b = 0; break;
253 case 2: r = 0, g = q, b = 1; break;
254 case 3: r = 1, g = q, b = 0; break;
255 case 4: r = q, g = 1, b = 0; break;
256 case 5: r = 1, g = q, b = 0; break;
257 }
258 var c = "#" + ("00" + (~ ~(r * 255)).toString(16)).slice(-2) + ("00" + (~ ~(g * 255)).toString(16)).slice(-2) + ("00" + (~ ~(b * 255)).toString(16)).slice(-2);
259 return (c);
260}
261
262function contains(a, obj, gen) {
263 for (var i = 0; i < a.length; i++) {
264 if (gen) {
265} if (a[i].toString().substr(0,a[i].toString().lastIndexOf(',')) === obj.toString()) {
266 return true;
267 }
268 else {
269 if (a[i].toString() === obj.toString()) {
270 return true;
271 }
272 }
273 }
274 return false;
275};
276
277function allimages(start, end) {
278 var images= [];
279 var counter=[]
280 {% for iimage in pfreport.images %}
281 images.push([{{forloop.counter0}},'{{iimage}}']);
282 {% endfor %}
283 tstart=start;
284 tend=end;
285 if (tstart == 0) {
286 tstart = 1;
287 }
288 if (tend == 0) {
289 tend = images.length;
290 }
291 if (tstart > tend) {
292 tend = images.length;
293 tstart = 1;
294 var div = document.getElementById("error");
295 div.textContent = "Invalid image Range: Start > End";
296 }
297 else {
298 var div = document.getElementById("error");
299 div.textContent = "";
300 }
301 images = images.slice(tstart-1,tend);
302 return [images,tstart-1,tend];
303};
304
305function alldates(start, end) {
306 var ddates = [];
307 var counter=[];
308 count = 0;
309 {% for ddate in pfreport.ddates %}
310 ddates.push([count,'{{ddate}}']);
311 count++;
312 {% endfor %}
313// This pushes the goalddates to the dates, worth it?
314/*
315 {% for tag, value in goaldates.items %}
316 ddates.push([count, '{{value.startdate}}'])
317 ddates.push([count, '{{value.enddate}}'])
318 count++
319 {% endfor %}
320*/
321 tstart=start;
322 tend=end;
323 if (tstart == 0) {
324 tstart = 1;
325 }
326 if (tend == 0) {
327 tend = ddates.length;
328 }
329 if (tstart > tend) {
330 tend = ddates.length;
331 tstart = 1;
332 var div = document.getElementById("error");
333 div.textContent = "Invalid date Range: Start > End";
334 }
335 else {
336 var div = document.getElementById("error");
337 div.textContent = "";
338 }
339 ddates = ddates.slice(tstart-1,tend);
340 return [ddates,tstart-1,tend];
341};
342
343function rownames() {
344 var rrow=[];
345 {% for row in test_run_names %}
346 rrow.push(['{{row}}']);
347 {% endfor %}
348 return rrow;
349};
350
351function casenames() {
352 var rrow=[];
353 {% for row, value in kpireport.items %}
354 rrow.push(['{{row}}', '{{value.units}}']);
355 {% endfor %}
356 return rrow;
357};
358
359function goalaxis(tstart, tend, datelocation) {
360 if (document.getElementById('imagecheck').checked == true) {
361 tstart = -1;
362 tend = -1;
363 for (var i=0; i<datelocation.length; i++){
364 if (datelocation[i].length > 0) {
365 if (tstart == -1) {
366 tstart = datelocation[i][1];
367 }
368 tend = datelocation[i][1];
369 }
370 }
371 }
372 var alldate = alldates(tstart, tend)[0];
373 var ret_array = [];
374 var goal_dates = [];
375 {% for tag, value in goaldates.items %}
376 goal_dates.push(['{{tag}}', '{{value.startdate}}', '{{value.enddate}}']);
377 ret_array.push(['{{tag}}',[], '{{value.value}}']);
378 {% endfor %}
379 ret = doublesort(goal_dates, ret_array, function(a,b) { return a[2] > b[2] } )
380 goal_dates = ret[0];
381 ret_array = ret[1];
382 for (var all=0;all<alldate.length;all++) {
383 for (var gl=0;gl<goal_dates.length;gl++) {
384 // if the date falls within the tag range...
385 // push the location and the tag
386 if (alldate[all][1] >= goal_dates[gl][1] && alldate[all][1] <= goal_dates[gl][2]) {
387 if (document.getElementById('imagecheck').checked == true) {
388 ret_array[gl][1].push([all, ret_array[gl][2]]);
389 }
390 else {
391 ret_array[gl][1].push([alldate[all][0], ret_array[gl][2]]);
392 }
393 }
394 }
395 }
396
397 // Make the dates overlap a little
398 var premx =-100;
399 var mxtag=1;
400 var around = false
401 for (var tag=0;tag<ret_array.length;tag++) {
402 var mn=9999999;
403 var mx =-100;
404 if (ret_array[tag][1].length > 0) {
405 for (var date=0;date<ret_array[tag][1].length;date++) {
406 if (ret_array[tag][1][date][0] < mn) {
407 mn =ret_array[tag][1][date][0];
408 }
409 if (ret_array[tag][1][date][0] > mx) {
410 mx =ret_array[tag][1][date][0];
411 }
412 }
413 if (mn != premx && around == true) {
414 ret_array[tag][1].push([mn-1, ret_array[tag][2]]);
415 }
416
417 premx = mx;
418 mxtag = tag;
419 around = true
420 }
421 }
422 ret_array[mxtag][1].push([premx+1, ret_array[mxtag][2]]);
423 return ret_array;
424}
425
426function allgraph(ddates) {
427 image = ddates[0];
428 images = [];
429 for (var i=0;i<image.length;i++){
430 images.push(image[i][1]);
431 }
432 tstart = ddates[1];
433 tend = ddates[2];
434 axis = [];
435 if (document.getElementById('imagecheck').checked == true) {
436 var treportpass = {{pfreport.dpass}};
437 var reportpass = [];
438 imagelist = {{pfreport.imagelist|safe}};
439 count = 0;
440 for (var i=0;i<treportpass.length;i++) {
441 if (contains(images, imagelist[i], false) && (imagelist[i] != '')) {
442 reportpass[count] = treportpass[i][1];
443 axis.push([count, imagelist[i]]);
444 count++;
445 }
446 }
447 var zipper = [];
448 for (var c=0; c<reportpass.length;c++) {
449 zipper.push(c);
450 }
451 reportpass = zip(zipper, reportpass);
452 var treportfail = {{pfreport.dfail}};
453 var reportfail = [];
454 count = 0;
455 for (var i=0;i<treportfail.length;i++) {
456 if (contains(images, imagelist[i], false) && (imagelist[i] != '')) {
457 reportfail[count] = treportfail[i][1];
458 count++;
459 }
460 }
461 reportfail = zip(zipper, reportfail);
462 }
463 else {
464 reportpass = {{pfreport.dpass}};
465 reportpass = reportpass.slice(tstart, tend);
466 reportfail = {{pfreport.dfail}};
467 reportfail = reportfail.slice(tstart, tend);
468 axis = ddates[0];
469 }
470 var reporttotalfails = 0;
471 for (var i=0; i < reportfail.length; i++) {
472 reporttotalfails = reporttotalfails + reportfail[i][1];
473 }
474 var reporttotalpasses = 0;
475 for (var i=0; i < reportpass.length; i++) {
476 reporttotalpasses = reporttotalpasses + reportpass[i][1];
477 }
478 total = reporttotalpasses+reporttotalfails;
479 var reportpercentpasses = parseInt((reporttotalpasses / total) *100);
480 var reportpercentfails = parseInt((reporttotalfails / total) *100);
481 var show = (reportfail.length == 1);
482 var ddata = [
483 {
484 'label': '<br><big><b>Fail: ' + reporttotalfails + '</b><br>' + reportpercentfails + '% failed.</big><br>',
485 'data': reportfail,
486 'color': '#FF0000',
487 'lines': { show: true },
488 'points': { show: show },
489 },
490 {
491 'label': '<br><big><b>Pass: ' + reporttotalpasses + '</b><br>' + reportpercentpasses + '% passed.</big><br>',
492 'data': reportpass,
493 'color': '#00FF00',
494 'lines': { show: true },
495 'points': { show: show },
496 },
497 ];
498 return [ddata, axis];
499};
500
501function adjustvalues(measures, i, target, flr, wgt) {
502 var rvrs = flr > target; // little numbers are better
503 var range = Math.abs(target-flr)
504 var measures_copy = []
505 for (var i=0;i<measures.length;i++)
506 measures_copy[i] = measures[i];
507 for (var j=0;j<measures.length;j++) {
508 if (measures[j] == null) {
509 measures_copy[j]=null;
510 }
511 else if (rvrs == true && measures[j] > flr) {
512 measures_copy[j] = 0;
513 }
514 else if (rvrs == true && measures[j] < target) {
515 measures_copy[j] = 100;
516 }
517 else if (rvrs == false && measures[j] > target) {
518 measures_copy[j] = 100;
519 }
520 else if (rvrs == false && measures[j] < flr) {
521 measures_copy[j] = 0;
522 }
523 else {
524 adjustednumber = Math.abs(measures[j]-flr);
525 measures_copy[j]=Math.ceil((adjustednumber/range)*100);
526 }
527 }
528 return measures_copy;
529}
530
531function averagevals(measures){
532 function average_val(arr, start, end, startval, endval) {
533 if ((start != end) && (start != end-1)) {
534 if (startval == null) {
535 for (var i=start+1; i < end; i++){
536 arr[i] = null;
537 }
538 }
539 else {
540 var points = end - start;
541 var range = endval - startval;
542 var change = range / points;
543 var count = 0;
544 for (var i=start+1; i < end; i++){
545 count++;
546 if (i == 0) {
547 arr[i] = parseFloat(startval)+change;
548 }
549 else {
550 arr[i] = parseFloat(arr[i-1])+change;
551 }
552 }
553 }
554 }
555 return arr;
556 };
557
558 var startval = 0; // value at start
559 var endval = 0; // value at end
560 var start = -1; // location
561 var end = -1; // location
562 for (var num = 0; num < measures.length; num++) {
563 if (measures[num] == -1) {
564 if (num == measures.length-1){
565 end = num+1;
566 startval = null;
567 measures = average_val(measures, start, end, startval, endval);
568 }
569 }
570 else { //value != -1
571 // first num you find, make all -1 before = val
572 if (startval == 0 && endval == 0) {
573 startval = null;
574 }
575 end = num;
576 endval = parseFloat(measures[num]);
577 measures = average_val(measures, start, end, startval, endval);
578 start = num;
579 startval = parseFloat(measures[num]);
580 }
581 }
582 return measures;
583}
584
585function kpigraph(pts, tests) {
586 var tstart = -1;
587 var tend = -1;
588 var ddata = [];
589 var weighteddata = [];
590 var sumweights = [];
591 var image = pts[0];
592 var images = [];
593 var label = [];
594 var dates = [];
595 for (var i=0;i<image.length;i++){
596 images.push(image[i][1]);
597 }
598 adjusted = document.getElementById("adjustedcheck").checked;
599 weighted = document.getElementById("weightedcheck").checked;
600 var i=-1;
601 var axis = [];
602 var needaxis = true;
603 var init = false;
604 {% for test,value in kpireport.items %}
605 i++;
606 var tgt;
607 var gl;
608 var wgt;
609 var flr;
610 if ('{{value.target}}'=='None') tgt = 0;
611 else tgt= {{value.target}};
612 if ('{{value.goal}}'=='None') gl = 0;
613 else gl= {{value.goal}};
614 if ('{{value.weight}}'=='None') wgt = 0;
615 else wgt= {{value.weight}};
616 if ('{{value.flr}}'=='None') flr = 0;
617 else flr= {{value.flr}};
618
619 glo_casedata[i][0] = tgt;
620 glo_casedata[i][1] = flr;
621 glo_casedata[i][2] = wgt;
622 glo_casedata[i][3] = gl;
623 var measures =[];
624 if (axis.length > 0) {
625 needaxis=false;
626 }
627 // its one of the selected test cases!
628 if (contains(tests,"{{test}}", true)) {
629 if (document.getElementById('imagecheck').checked == true) {
630 imagelist = {{pfreport.imagelist|safe}};
631 var tmeasures= new Array{{value.measurement|safe}};
632 count = 0;
633 for (var k=0;k<tmeasures.length;k++) {
634 if (contains(images, imagelist[k], false) && (imagelist[k] != '')) {
635 measures[count] = tmeasures[k];
636 if (needaxis == true) {
637 tstart = 0;
638 tend = count+1;
639 axis.push([count, imagelist[k]]);
640 }
641 count++;
642 dates.push([count, k]);
643 }
644 else {
645 dates.push([]);
646 }
647 }
648 }
649 else {
650 // Grab the measurements::Add the (0,#,1,#) with a zip
651 measures = new Array{{value.measurement|safe}};
652 for (var q=0;q<measures.length;q++){
653 dates.push(true);
654 }
655 axis = pts[0];
656 tstart = pts[1];
657 tend = pts[2];
658 }
659 measures = averagevals(measures);
660 if (weighteddata.length == 0) {
661 for (var k=0; k<measures.length;k++) {
662 weighteddata.push(0);
663 sumweights.push(0);
664 }
665 }
666 // If they want to % adjusted values
667 goalmeasures = adjustvalues(measures, i, tgt, flr, wgt);
668 if (adjusted == true) {
669 measures = adjustvalues(measures, i, tgt, flr, wgt);
670 }
671 for (var k=0; k<measures.length;k++) {
672 if (wgt != 0) {
673 if (goalmeasures[k] == null) {
674 // ignore it, don't update sumweights
675 }
676 else {
677 weighteddata[k] += goalmeasures[k]*wgt;
678 sumweights[k] += wgt;
679 }
680 }
681 }
682 //Lets save the data to graph the weighted graph
683
684 var zipper = [];
685 for (var c=0; c<measures.length;c++) {
686 zipper.push(c);
687 }
688 measures = zip(zipper, measures);
689 measures = measures.slice(tstart, tend);
690 goalmeasures = zip(zipper, goalmeasures);
691 goalmeasures = goalmeasures.slice(tstart, tend);
692 var color = rainbow(5, i);
693 len = 0
694 for (var m=0;m<measures.length;m++){
695 if (measures[m][1] != null) {
696 len++;
697 }
698 }
699 var show = (len <= 1);
700 ddata.push(
701 {
702 'label': "{{test}}",
703 'data': measures,
704 'color': color,
705 'lines': { show: true },
706 'points': { show: show },
707 }
708 );
709 label.push(false);
710 // Add the dashed goal line
711 if (adjusted == true && weighted == false) {
712 var kpitarget = [];
713 var count = -1;
714 for (var j=measures[0][0];j<=measures[measures.length-1][0];j++) {
715 count++;
716 if (gl != 0) {
717 if (measures[count][1] == null) {
718 kpitarget.push([j, null]);
719 }
720 else {
721 kpitarget.push([j, gl]);
722 }
723 }
724 }
725
726 ddata.push(
727 {
728 'data': kpitarget,
729 'color': color,
730 'dashes': {show: true, },
731 'opacity': .9,
732 'points': {show: false },
733 }
734 );
735 label.push(false);
736 }
737 }
738 {% endfor %}
739
740 //check if they want the single weighted graph
741 if (weighted == true) {
742 if (document.getElementById('weightedonly').checked == true) {
743 ddata = [];
744 label = [];
745 }
746 for (var wt = 0; wt < weighteddata.length; wt++) {
747 weighteddata[wt]= ((weighteddata[wt]/100)/sumweights[wt])*100;
748 }
749 if (weighteddata.length > 0 && '{{goalname}}' != 'None') {
750 goaltarget = goalaxis(tstart, tend, dates);
751 for (var gl=0;gl<goaltarget.length;gl++) {
752 if (goaltarget[gl][1].length > 0) {
753 currentdates = goaltarget[gl][1]
754 ret = unzip(goaltarget[gl][1]);
755 ret[0] = ret[0].sort(function(a,b){ if(a > b){return 1}else{return -1} } );
756 goaltarget[gl][1] = zip(ret[0], ret[1]);
757 ddata.push({
758 'label': goaltarget[gl][0],
759 'data': goaltarget[gl][1],
760 //CAN YOU MAKE THIS OPACQUE?
761 'color': "#000000",
762 'dashes': {show: true, lineWidth: 2.5, dashLength: 5},
763 'points': {show: false },
764 'yaxis': 2,
765 });
766 label.push(true);
767 targetvalue = parseInt(currentdates[0][1])
768 mn = 9999
769 mx = -1
770 for (var val = 0; val<currentdates.length;val++) {
771 if (currentdates[val][0] < mn) mn = currentdates[val][0]
772 if (currentdates[val][0] > mx) mx = currentdates[val][0]
773 }
774 var goalvalues = weighteddata.slice(mn, mx)
775 var zipper = [];
776 for (var c=mn; c<mx;c++) {
777 zipper.push(c);
778 }
779 goalvalues = zip(zipper, goalvalues);
780 ddata.push(
781 {
782 'data': goalvalues,
783 'color': "#555555",
784 'bars': { show: true, barWidth: 0.8, align: "center", fillColor: { colors: [ { opacity: 0.5 }, { opacity: 0.2 } ] }},
785 'points': { show: false },
786 'yaxis': 2,
787 'threshold': [{below: targetvalue-5, color:'#990000'},{below: 101, color:'#009900'}, {below:targetvalue, color:'#999900'}],
788 }
789 );
790 }
791 }
792 }
793 else { // NO DATES GIVEN, JUST MAKE IT BLACK
794 var zipper = [];
795 for (var c=0; c<weighteddata.length;c++) {
796 zipper.push(c);
797 }
798 weighteddata = zip(zipper, weighteddata);
799 weighteddata = weighteddata.slice(tstart, tend);
800 ddata.push(
801 {
802 'data': weighteddata,
803 'color': "#555555",
804 'bars': { show: true, barWidth: 0.8, align: "center", fillColor: { colors: [ { opacity: 0.4 }, { opacity: 0.1 } ] }},
805 'points': { show: false },
806 'yaxis': 2,
807 }
808 );
809 label.push(false);
810 }
811 }
812 return [ddata,axis,label];
813};
814
815function rowgraph(ddates, selectedrows){
816 var dates = ddates[0];
817 images = [];
818 for (var i=0;i<dates.length;i++){
819 images.push(dates[i][1]);
820 }
821 var tstart = ddates[1];
822 var tend = ddates[2];
823 //Slice dictionary as needed..?
824 var ddata = [];
825 var axis = [];
826 var needaxis = true;
827 totalrows = rownames().length;
828 var selectnames = [];
829 for (var i=0; i<selectedrows.length;i++) {
830 selectnames.push(selectedrows[i][0]);
831 }
832 var m=-1;
833 var n=-1;
834 {% for value, row in rowreport.items %}
835 var passes =[];
836 var fails =[];
837 if (axis.length > 0) {
838 needaxis=false;
839 }
840 if (contains(selectnames, '{{value}}', true)) {
841 m++;
842 n++;
843 // graph this row
844 if (document.getElementById('imagecheck').checked == true) {
845 imagelist = {{pfreport.imagelist|safe}};
846
847 var tpasses=new Array({{row.pass}});
848 var tfails=new Array({{row.fail}});
849 tpasses = tpasses[0];
850 tfails = tfails[0];
851
852 count = 0;
853 for (var i=0;i<tpasses.length;i++) {
854 if (contains(images, imagelist[i], false) && (imagelist[i] != '')) {
855 passes[count] = tpasses[i][1];
856 if (needaxis == true) {
857 axis.push([count, imagelist[i]]);
858 }
859 count++;
860 }
861 }
862 var zipper = [];
863 for (var c=0; c<passes.length;c++) {
864 zipper.push(c);
865 }
866 passes = zip(zipper, passes);
867
868 count = 0;
869 for (var i=0;i<tfails.length;i++) {
870 if (contains(images, imagelist[i], false) && (imagelist[i] != '')) {
871 fails[count] = tfails[i][1];
872 count++;
873 }
874 }
875 fails = zip(zipper, fails);
876 }
877 else {
878 passes=new Array({{row.pass}});
879 fails=new Array({{row.fail}});
880 passes = passes[0];
881 fails = fails[0];
882 axis = ddates[0];
883 fails = fails.slice(tstart, tend);
884 passes = passes.slice(tstart, tend);
885 }
886 //ddata it up
887 var failcolor = '';
888 var passcolor = '';
889 m = n%8;
890 if (m<4){
891 failcolor = '#'+(15-m).toString(16)+(15-m).toString(16)
892 +(m*4).toString(16)+(m*4).toString(16)
893 +'00';
894 passcolor = '#00'
895 +(15-m).toString(16)+(15-m).toString(16)
896 +(m*4).toString(16)+(m*4).toString(16);
897 }
898 else {
899 m = n%4;
900 failcolor = '#'+(15-m).toString(16)+(15-m).toString(16)
901 +'00'
902 +(m*4).toString(16)+(m*4).toString(16);
903 passcolor = '#'+(m*4).toString(16)+(m*4).toString(16)
904 +(15-m).toString(16)+(15-m).toString(16)
905 +'00';
906 }
907 var show = (fails.length == 1);
908 ddata.push(
909 {
910 'label': '{{value}}'+' Fails',
911 'data': fails,
912 'color': failcolor,
913 'lines': { show: true },
914 'points': { show: show },
915 }
916 );
917 ddata.push(
918 {
919 'label': '{{value}}'+' Passes',
920 'data': passes,
921 'color': passcolor,
922 'lines': { show: true },
923 'points': { show: show },
924 }
925 );
926 }
927 {% endfor %}
928 return [ddata,axis];
929};
930
931function exposeList() {
932 var status = document.getElementById('cbChoices').checked;
933 var passstatus = document.getElementById('passfailcheck').checked;
934 if (passstatus == true) {
935 document.getElementById('testcases').style.display = 'none';
936 document.getElementById('kpi').style.display = 'none';
937 document.getElementById('cbChoices').style.display = "block";
938 document.getElementById('kpiinfo').style.display = "none";
939 if (status == true) {
940 document.getElementById('rows').style.display = "block";
941 }
942 else {
943 document.getElementById('rows').style.display = 'none';
944 }
945 }
946 else {
947 document.getElementById('rows').style.display = 'none';
948 document.getElementById('testcases').style.display = "block";
949 document.getElementById('cbChoices').style.display = "none";
950 document.getElementById('kpi').style.display = 'block';
951 document.getElementById('kpiinfo').style.display = "inline";
952 if (document.getElementById('weightedcheck').checked == true) {
953 document.getElementById('weightedinfo').style.display = 'inline';
954 }
955 else {
956 document.getElementById('weightedinfo').style.display = 'none';
957 }
958 }
959 draw();
960}
961
962function exposeHelp() {
963 var status = document.getElementById('cbHelp').checked;
964 if (status == true) {
965 document.getElementById('help').style.display = 'block';
966 }
967 else {
968 document.getElementById('help').style.display = 'none';
969 }
970}
971
972function changeType() {
973 var status = document.getElementById('imagecheck').checked;
974 if (status == true) {
975 document.getElementById('startDate').style.display = 'none';
976 document.getElementById('endDate').style.display = 'none';
977 document.getElementById('startImage').style.display = 'inline';
978 document.getElementById('endImage').style.display = 'inline';
979 }
980 else {
981 document.getElementById('startDate').style.display = 'inline';
982 document.getElementById('endDate').style.display = 'inline';
983 document.getElementById('startImage').style.display = 'none';
984 document.getElementById('endImage').style.display = 'none';
985 }
986 draw();
987}
988
989function loadtext() {
990 glo_selectedkpi=this.id.match(/\d+/);
991 document.getElementById('kpilabel').innerHTML = this.value;
992 document.getElementById('kpitarget').value = glo_casedata[glo_selectedkpi][0];
993 document.getElementById('kpifloor').value = glo_casedata[glo_selectedkpi][1];
994 document.getElementById('kpiweight').value = glo_casedata[glo_selectedkpi][2];
995 document.getElementById('kpigoal').value = glo_casedata[glo_selectedkpi][3];
996 draw();
997}
998
999function updateURL(casename, rowname) {
1000 var url = baseurl;
1001 url += '?sets={{setname}}&var=';
1002 for (var i=0;i<=6;i++){
1003 if ($("input").get(i).checked == true){
1004 url += i+",";
1005 }
1006 }
1007 url += '&kpis=';
1008 for (var i=0; i < casename.length; i++) {
1009 if (document.getElementById("case"+i) != null) {
1010 if (document.getElementById("case"+i).checked == true) {
1011 url += casename[i][0]+",";
1012 }
1013 }
1014 }
1015 url += '&rows=';
1016 for (var i=0; i < rowname.length; i++) {
1017 if (document.getElementById("row"+i) != null) {
1018 if (document.getElementById("row"+i).checked == true) {
1019 url += rowname[i][0]+",";
1020 }
1021 }
1022 }
1023 url += '&select=';
1024 for (var i=0;i<=3;i++){
1025 url += $("select").get(i).selectedIndex+","
1026 }
1027 $("#permalink")[0].value = url;
1028
1029}
1030
1031function draw() {
1032 var ddata = [];
1033 var val = [];
1034 var labels = [];
1035 if (document.getElementById('imagecheck').checked == true) {
1036 var startd=document.getElementById("startImage");
1037 var endd=document.getElementById("endImage");
1038 var start = startd.options[startd.selectedIndex].index;
1039 var end = endd.options[endd.selectedIndex].index;
1040 var axis = allimages(start,end);
1041 }
1042 else {
1043 var startd=document.getElementById("startDate");
1044 var endd=document.getElementById("endDate");
1045 var start = startd.options[startd.selectedIndex].index;
1046 var end = endd.options[endd.selectedIndex].index;
1047 var axis = alldates(start,end);
1048 }
1049 var rowstatus = document.getElementById('cbChoices').checked;
1050 var passfailstatus = document.getElementById('passfailcheck').checked;
1051 var selectedrows = [];
1052 var kpi = false;
1053 var casename = casenames();
1054 var rowname = rownames();
1055 if (passfailstatus == false) {
1056 for (var i=0; i < casename.length; i++) {
1057 if (document.getElementById("case"+i) != null) {
1058 if (document.getElementById("case"+i).checked == true) {
1059 selectedrows.push([casename[i][0], i]);
1060 }
1061 }
1062 }
1063 val = kpigraph(axis, selectedrows);
1064 ddata = val[0];
1065 axis = val[1];
1066 labels = val[2];
1067 }
1068 else {
1069 if (rowstatus == true) {
1070 selectedrows = [];
1071 for (var i=0; i < rowname.length; i++) {
1072 if (document.getElementById("row"+i).checked == true) {
1073 selectedrows.push([rowname[i], i]);
1074 }
1075 }
1076 if (selectedrows.length != 0) {
1077 val = rowgraph(axis, selectedrows);
1078 ddata = val[0];
1079 axis = val[1];
1080 }
1081 else {
1082 val = allgraph(axis);
1083 ddata = val[0];
1084 axis = val[1];
1085 }
1086 }
1087 else {
1088 val = allgraph(axis);
1089 ddata = val[0];
1090 axis = val[1];
1091 }
1092 }
1093 updateURL(casename,rowname);
1094 // Make the dates not look terrible..
1095 var goal = 10;
1096 axispoints = axis;
1097 if (axispoints.length > goal) {
1098 axispoints = pare(axispoints, goal);
1099 }
1100
1101 if (document.getElementById('adjustedcheck').checked == true && document.getElementById('kpicheck').checked == true) {
1102 p = $.plot($("#placeholder"), ddata, {
1103 xaxis: {
1104 ticks: axispoints,
1105 },
1106 yaxis: {
1107 min: 0,
1108 max: 100,
1109 autoscaleMargin: false,
1110 axisLabel: 'Percent',
1111 },
1112 y2axis: {
1113 min: 0,
1114 max: 100,
1115 autoscaleMargin: false,
1116 axisLabel: 'Weighted Percent',
1117 },
1118 legend:{
1119 container: $("#labeler")
1120 },
1121 });
1122 }
1123 else if (document.getElementById('kpicheck').checked == true){
1124 p = $.plot($("#placeholder"), ddata, {
1125 xaxis: {
1126 ticks: axispoints,
1127 },
1128 yaxis: {
1129 axisLabel: 'Units',
1130 },
1131 y2axis: {
1132 min: 0,
1133 max: 100,
1134 autoscaleMargin: false,
1135 axisLabel: 'Weighted Percent',
1136 },
1137 legend:{
1138 container: $("#labeler")
1139 },
1140 });
1141 }
1142 else {
1143 p = $.plot($("#placeholder"), ddata, {
1144 xaxis: {
1145 ticks: axispoints,
1146 },
1147 yaxis: {
1148 axisLabel: 'Units',
1149 },
1150 y2axis: {
1151 min: 0,
1152 max: 100,
1153 autoscaleMargin: false,
1154 axisLabel: 'Weighted Percent',
1155 },
1156 legend:{
1157 container: $("#labeler")
1158 },
1159 });
1160 }
1161 // Add the labels to the weighted graph
1162 var count = -1;
1163 if (p.getData().length > 0) {
1164 $.each(p.getData(), function(i, row){
1165 if (row.label) {
1166 count++;
1167 if (labels[count] == true) {
1168 tlabel = row.label;
1169 row.label = '';
1170 if (row.data.length > 1) {
1171 var o = p.pointOffset({x: row.data[0][0], y: row.data[0][1], yaxis: 2});
1172 $('<div class="data-point-label">' +'<b><font size=3>'+ tlabel+ '</font></b>' + '</div>').css( {
1173 position: 'absolute',
1174 left: o.left + 2,
1175 top: o.top - 23,
1176 display: 'none',
1177 escapeHTML: 'false',
1178 border: '1.5px solid #aaaaaa',
1179 background: '#aaccee',
1180 opacity: '0.9',
1181 }).appendTo(p.getPlaceholder()).fadeIn(0);
1182 }
1183 }
1184 }
1185 });
1186 p.setupGrid();
1187 }
1188};
1189
1190var glo_selectedkpi = 0;
1191var glo_casedata = [];
1192
1193$(window).resize(function() {
1194 draw();
1195});
1196
1197$(document).ready(function(){
1198 var select = document.getElementById("startDate");
1199 var select2 = document.getElementById("endDate");
1200 var options = alldates(0,0)[0];
1201// url_options
1202 for(var i = 0; i < options.length; i++) {
1203 var opt = options[i][1];
1204 var el = document.createElement("option");
1205 var el2 = document.createElement("option");
1206 el.textContent = opt;
1207 el.value = opt;
1208 el2.textContent = opt;
1209 el2.value = opt;
1210 select.appendChild(el);
1211 select2.appendChild(el2);
1212 }
1213
1214 select = document.getElementById("startImage");
1215 select2 = document.getElementById("endImage");
1216 options = allimages(0,0)[0];
1217 for(var i = 0; i < options.length; i++) {
1218 var opt = options[i][1];
1219 var el = document.createElement("option");
1220 var el2 = document.createElement("option");
1221 el.textContent = opt;
1222 el.value = opt;
1223 el2.textContent = opt;
1224 el2.value = opt;
1225 select.appendChild(el);
1226 select2.appendChild(el2);
1227 }
1228 select = document.getElementById("rows");
1229 options = rownames();
1230 rows=url_options.split("&rows=")[1];
1231 if (rows != null) {
1232 if (rows.indexOf("&") != -1) {rows=rows.substr(0,rows.indexOf("&")).split(",");}
1233 else {rows = rows.split(",");}
1234 }
1235 else {
1236 rows = [];
1237 }
1238 for (var r=0;r<rows.length;r++) {rows[r] = trim(rows[r]);}
1239 for(var i = 0; i < options.length; i++) {
1240 var opt = options[i];
1241 var el = document.createElement("input");
1242 el.type = "checkbox";
1243 el.onclick=draw;
1244 el.id = "row"+i;
1245 el.name = "row"+i;
1246 var label = document.createElement("label");
1247 label.htmlFor = opt;
1248 if (contains(rows, opt, false)) {el.checked = true;}
1249 else {el.checked = false;}
1250 label.appendChild(document.createTextNode(opt));
1251 var br = document.createElement("br");
1252 label.appendChild(br);
1253 select.appendChild(el);
1254 select.appendChild(label);
1255 }
1256
1257 select = document.getElementById("testcases");
1258 options = casenames();
1259 rows=url_options.split("&kpis=")[1];
1260 if (rows != null) {
1261 if (rows.indexOf("&") != -1) {rows=rows.substr(0,rows.indexOf("&")).split(",");}
1262 else {rows = rows.split(",");}
1263 }
1264 else {
1265 rows = [];
1266 }
1267 for (var r=0;r<rows.length;r++) {rows[r] = trim(rows[r]);}
1268 for(var i = 0; i < options.length; i++) {
1269 glo_casedata.push([0, 0, 1, 100]);
1270 }
1271 for(var i = 0; i < options.length; i++) {
1272 glo_selectedkpi=i;
1273 var opt = options[i][0];
1274 var units = options[i][1];
1275 var el = document.createElement("input");
1276 el.type = "checkbox";
1277 el.onclick=loadtext;
1278 el.id = "case"+i;
1279 el.name = "case"+i;
1280 el.value = opt
1281 if (contains(rows, opt, false)) {el.checked = true;}
1282 else {el.checked = false;}
1283 var label = document.createElement("input");
1284 label.type = "button";
1285 label.value = opt;
1286 label.id = "button"+i;
1287 label.name = "button"+i;
1288 label.onclick= loadtext;
1289 var unit = document.createElement("input");
1290 unit.type = "button";
1291 unit.onclick = draw;
1292 unit.value = units;
1293 unit.id = "unit"+i;
1294 label.name = "unit"+i;
1295 var br = document.createElement("br");
1296 select.appendChild(el);
1297 select.appendChild(label);
1298 select.appendChild(unit);
1299 select.appendChild(br);
1300 label.click();
1301 }
1302
1303 rows=url_options.split("&var=")[1];
1304 if (rows != null) {
1305 if (rows.indexOf("&") != -1) {rows=rows.substr(0,rows.indexOf("&")).split(",");}
1306 else {rows = rows.split(",");}
1307 }
1308 else {
1309 rows = [1,2];
1310 }
1311 for (var i=0;i<rows.length;i++){
1312 if (rows[i] != ''){
1313 $("input").get(parseInt(rows[i])).checked = true
1314 }
1315 }
1316
1317 rows=url_options.split("&select=")[1];
1318 var sdata = 0;
1319 var simage = 0;
1320 if (rows != null) {
1321 if (rows.indexOf("&") != -1) {rows=rows.substr(0,rows.indexOf("&")).split(",");}
1322 else {rows = rows.split(",");}
1323 }
1324 else {
1325 if ($("select").get(0).length>7){ sdata = 7;}
1326 else{ sdata=$("select").get(0).length-1;}
1327 if ($("select").get(2).length>7){ simage = 7;}
1328 else{ simage=$("select").get(2).length-1;}
1329 rows = [$("select").get(0).length-sdata,$("select").get(1).length-1,$("select").get(2).length-simage,$("select").get(3).length-1];
1330 }
1331 for (var i=0;i<Math.min(rows.length, 4);i++){
1332 $("select").get(i).selectedIndex = parseInt(rows[i]);
1333 }
1334 changeType();
1335 exposeList();
1336 goal_dates = [];
1337 {% for tag, value in goaldates.items %}
1338 goal_dates.push(['{{tag}}', '{{value.startdate}}', '{{value.enddate}}', '{{value.value}}']);
1339 {% endfor %}
1340 goal_dates = goal_dates.sort(function(a,b) { if(a[1] > b[1]){return 1}else{return -1} } );
1341 goalElement = document.getElementById("goal_dates");
1342 if (goal_dates.length > 0) {
1343 goalElement.style.display = 'inline';
1344 document.getElementById('goal_dates_label').style.display = 'inline';
1345 inputString = '<table border="0"><tr><td><b>Tag&emsp;</b></td><td><b>Target Date&emsp;</b></td><td><b>Goal</b></td></tr>';
1346 for (var tag=0;tag<goal_dates.length;tag++){
1347 inputString += '<tr><td>'+goal_dates[tag][0]+'</td>';
1348 inputString += '<td>'+goal_dates[tag][1]+'</td>';
1349 inputString += '<td>'+goal_dates[tag][3]+'%</td></tr>';
1350 }
1351 inputString += '</table>';
1352 goalElement.innerHTML = inputString;
1353 }
1354 else {
1355 goalElement.style.display = 'none';
1356 document.getElementById('goal_dates_label').style.display = 'none';
1357 }
1358 glo_selectedkpi=0;
1359 draw();
1360});
1361
1362</script>
1363
1364<h2>Detailed results:</h2>
1365>>>>>>> MERGE-SOURCE
86<table id="outer-table">1366<table id="outer-table">
87 <tr>1367 <tr>
88 <td>1368 <td>
891369
=== modified file 'dashboard_app/templates/dashboard_app/image-reports.html'
--- dashboard_app/templates/dashboard_app/image-reports.html 2012-07-18 05:20:11 +0000
+++ dashboard_app/templates/dashboard_app/image-reports.html 2013-07-17 17:43:24 +0000
@@ -9,10 +9,12 @@
9 {% for image in imageset.images %}9 {% for image in imageset.images %}
10 <li>10 <li>
11 {% if image.bundle_count %}11 {% if image.bundle_count %}
12 <a href="{{ image.link }}">{{ image.name }}</a>12 <a href="{{ image.link }}">{{ image.imagename }}</a>
13 ({{ image.bundle_count }} results)13 ({{ image.bundle_count }} results)
14 (<font color="Blue">{{image.grandtotal}} Totalcases;</font> <font color="green"> {{ image.passes }} Passes; </font><font color="red">{{image.fails}} Fails</font>)
14 {% else %}15 {% else %}
15 {{ image.name }} ({{ image.bundle_count }} results)16 {{ imagename }} ({{ image.bundle_count }} results)
17 (<font color="Blue">{{image.grandtotal}} Totalcases;</font> <font color="green"> {{ image.passes }} Passes; </font><font color="red">{{image.fails}} Fails</font>)
16 {% endif %}18 {% endif %}
17 </li>19 </li>
18 {% endfor %}20 {% endfor %}
1921
=== modified file 'dashboard_app/views/images.py'
--- dashboard_app/views/images.py 2013-07-03 11:42:45 +0000
+++ dashboard_app/views/images.py 2013-07-17 17:43:24 +0000
@@ -16,11 +16,14 @@
16# You should have received a copy of the GNU Affero General Public License16# You should have received a copy of the GNU Affero General Public License
17# along with Launch Control. If not, see <http://www.gnu.org/licenses/>.17# along with Launch Control. If not, see <http://www.gnu.org/licenses/>.
1818
1919import json
20import re
21from django.db import models
20from django.http import HttpResponseRedirect22from django.http import HttpResponseRedirect
21from django.shortcuts import get_object_or_404, render_to_response23from django.shortcuts import get_object_or_404, render_to_response
22from django.template import RequestContext24from django.template import RequestContext
23from django.views.decorators.http import require_POST25from django.views.decorators.http import require_POST
26from datetime import date
2427
25from lava_server.bread_crumbs import (28from lava_server.bread_crumbs import (
26 BreadCrumb,29 BreadCrumb,
@@ -29,32 +32,302 @@
2932
30from dashboard_app.filters import evaluate_filter33from dashboard_app.filters import evaluate_filter
31from dashboard_app.models import (34from dashboard_app.models import (
35 Bundle,
32 LaunchpadBug,36 LaunchpadBug,
33 Image,37 Image,
34 ImageSet,38 ImageSet,
35 TestRun,39 TestRun,
36)40)
37from dashboard_app.views import index41from dashboard_app.views import index
42<<<<<<< TREE
38import json43import json
44=======
45
46def pfdict_insert(dic, match, tag):
47 if tag not in dic:
48 dic[tag] = {'pass': 0, 'fail': 0, 'test_names': [], 'image': '', 'allpass':0, 'allfail':0}
49 dic[tag]['pass'] += match['count_pass']
50 dic[tag]['fail'] += match['count_fail']
51 dic[tag]['test_names']=match['test_names']
52 dic[tag]['allpass']=match['allpass']
53 dic[tag]['allfail']=match['allfail']
54 if (str(match['image']) != ''):
55 dic[tag]['image'] = match['image']
56 return dic
57
58def kpidict_insert(dic, aMatch, tag):
59 for match in aMatch:
60 testname = match
61 if testname not in dic:
62 dic[testname] = {'date': [],'measurement': [], 'units': '',
63 'target': '', 'floor': '', 'goal': '', 'weight': ''}
64 dic[testname]['date'].append(tag)
65 if (aMatch[match]['measurement']==None):
66 dic[testname]['measurement'].append(-1)
67 else:
68 dic[testname]['measurement'].append(str(aMatch[match]['measurement']))
69 if (dic[testname]['units'] == ''):
70 dic[testname]['units']=aMatch[match]['units']
71 dic[testname]['target']=aMatch[match]['target']
72 dic[testname]['floor']=aMatch[match]['floor']
73 dic[testname]['goal']=aMatch[match]['goal']
74 dic[testname]['weight']=aMatch[match]['weight']
75 return dic
76
77def rowdict_insert(dic, rowMatch, tag):
78 for match in rowMatch:
79 if tag not in dic:
80 dic[tag] = {}
81 if match not in dic[tag]:
82 dic[tag][match] = {'pass':0,'fail':0}
83 for testname in dic[tag]:
84 try:
85 dic[tag][testname]['pass'] = rowMatch[testname]['pass']
86 except KeyError:
87 dic[tag][testname]['pass'] = 0
88 try:
89 dic[tag][testname]['fail'] = rowMatch[testname]['fail']
90 except KeyError:
91 dic[tag][testname]['fail'] = 0
92 return dic
93
94
95def generate_dicts(allmatches, getcases):
96 # Lets only go through the database once..
97 kpidict = {}
98 detaildict = {}
99 rowdict = {}
100 dates = []
101 images = []
102 tagdic = {}
103 for matches in allmatches:
104 tag = ""
105 for match in matches.test_runs:
106 #Only keep latest test for same image
107 if (tag == ""):
108 tag = match.analyzer_assigned_date
109 tag = date(tag.year,tag.month,tag.day)
110 #tag = matches.tag
111 if tag not in dates:
112 dates.append(tag)
113 if tag not in tagdic:
114 tagdic[tag]=[]
115 tagdic[tag].append(match)
116 for tagname in tagdic:
117 aMatch = {'count_pass': 0,'count_fail': 0, 'test_names': [], 'image': '', 'allpass': 0, 'allfail': 0}
118 rowMatch = {}
119 for ddate in tagdic[tagname]:
120 aMatch['count_pass'] += ddate.denormalization.count_pass
121 aMatch['count_fail'] += ddate.denormalization.count_fail
122 aMatch['test_names'].append(ddate)
123 aMatch['image'] = ddate.sw_image_desc
124 aMatch['allpass'] = aMatch['count_pass']
125 aMatch['allfail'] = aMatch['count_fail']
126 aMatch['count_pass'] = float(aMatch['count_pass']) / float(len(tagdic[tagname]))
127 aMatch['count_fail'] = float(aMatch['count_fail']) / float(len(tagdic[tagname]))
128 detaildict = pfdict_insert(detaildict, aMatch, tagname)
129
130 if (match.sw_image_desc not in images) and not (match.sw_image_desc == ''):
131 images.append(match.sw_image_desc)
132 if (getcases == 1):
133 aMatch = {}
134 rowMatch = {}
135 for ddate in tagdic[tagname]:
136 if (ddate.test.test_id not in rowMatch):
137 rowMatch[ddate.test.test_id] = {'pass':0,'fail':0,'count':0}
138 denorm = ddate.denormalization
139 rowMatch[ddate.test.test_id]['fail'] += denorm.count_fail
140 rowMatch[ddate.test.test_id]['pass'] += denorm.count_pass
141 if (denorm.count_fail == 0 and denorm.count_pass == 0):
142 pass
143 else:
144 rowMatch[ddate.test.test_id]['count'] += 1
145 testname = ddate.test # <-- TESTNAME
146 if (str(testname) != 'lava'):
147 # Only take measuresments with a . in them, current lava implementation
148 # measurements are stored as floats
149 for test_case in ddate.test_results.filter(measurement__icontains='.'):
150 test_case_name = test_case.test_case
151 if (test_case.test_case not in aMatch):
152 aMatch[test_case_name] = {'date': '','measurement': None, 'units': '','target': '',
153 'floor': '', 'goal': '', 'weight': '', 'count': 0}
154 if (aMatch[test_case_name]['measurement'] == None and test_case.measurement != None):
155 aMatch[test_case_name]['measurement'] = 0
156 if (test_case.measurement == None):
157 test_case.measurement = 0
158 if (aMatch[test_case_name] != None):
159 aMatch[test_case_name]['measurement'] += test_case.measurement
160 aMatch[test_case_name]['count'] += 1
161 aMatch[test_case_name]['date'] = tagname
162 aMatch[test_case_name]['units'] = test_case.units
163 aMatch[test_case_name]['target']=test_case.target
164 aMatch[test_case_name]['floor']=test_case.floor
165 aMatch[test_case_name]['goal']=test_case.goal
166 aMatch[test_case_name]['weight']=test_case.weight
167 for test in aMatch:
168 if (aMatch[test]['count'] == 0):
169 aMatch[test]['measurement'] = 0
170 else:
171 aMatch[test]['measurement'] = aMatch[test]['measurement'] / aMatch[test]['count']
172 kpidict = kpidict_insert(kpidict, aMatch, tagname)
173 for test in rowMatch:
174 if (rowMatch[test]['count'] == 0):
175 rowMatch[test]['pass'] = 0
176 rowMatch[test]['fail'] = 0
177 else:
178 rowMatch[test]['pass'] = float(rowMatch[test]['pass']) / float(rowMatch[test]['count'])
179 rowMatch[test]['fail'] = float(rowMatch[test]['fail']) / float(rowMatch[test]['count'])
180 rowdict = rowdict_insert(rowdict, rowMatch, tagname)
181 return kpidict, detaildict, rowdict, dates
182
183def report_for_kpigraph(counters, dates, images):
184 temp = counters.copy()
185 for key in counters:
186 found = 0;
187 for i in range(0, len(counters[key]['measurement'])):
188 if (counters[key]['measurement'][i] != -1):
189 found = 1
190 break
191 if (found == 0):
192 temp.pop(key)
193 else:
194 for curdate in dates:
195 if curdate not in temp[key]['date']:
196 temp[key]['date'].append(curdate)
197 temp[key]['measurement'].append(-1)
198 # So now we have a dictionary of the following format..
199 # caseid: {date:[] measurement:[], units:''}}
200
201 cases = []
202 dates = []
203 measures = []
204 returndict = {}
205 for key in temp:
206 returndict[key] = {'date': [],'measurement': [], 'units': '',
207 'target': '', 'flr': '', 'goal': '', 'weight': ''}
208 dates = temp[key]['date']
209 measures = temp[key]['measurement']
210 if (len(dates)>1):
211 dates, measures = zip(*sorted(zip(dates, measures)))
212 else:
213 measures = str(measures).replace('[','(').replace(']',')')
214 dates = str(dates).replace('[','(').replace(']',')')
215 returndict[key]['date'] = dates
216 returndict[key]['measurement'] = measures
217 returndict[key]['units'] = temp[key]['units']
218 returndict[key]['target'] = temp[key]['target']
219 returndict[key]['flr'] = temp[key]['floor']
220 returndict[key]['goal'] = temp[key]['goal']
221 returndict[key]['weight'] = temp[key]['weight']
222 return returndict
223
224def report_for_detailgraph(detaildict):
225 report = {'ddates': [], 'dfail': [], 'dpass': [], 'imagelist': [], 'totalfails': 0,
226 'totalpasses': 0, 'percentfails': 0, 'percentpasses': 0,
227 'grandtotal': 0, 'images': [], 'unfail': [], 'unpass': []}
228 taglist = []
229 sortable = 0
230 for tag in detaildict:
231 taglist.append(tag)
232 taglist = sorted(taglist)
233 count = 0
234 for tag in taglist:
235 fail_count = detaildict[tag]['fail']
236 pass_count = detaildict[tag]['pass']
237 image = detaildict[tag]['image']
238 if (image not in report['images']) and not (image == ''):
239 report['images'].append(image)
240 report['imagelist'].append(str(detaildict[tag]['image']))
241 report['ddates'].append(unicode(tag))
242 report['dfail'].append([count, fail_count])
243 report['dpass'].append([count, pass_count])
244 report['unfail'].append([count, detaildict[tag]['allfail']])
245 report['unpass'].append([count, detaildict[tag]['allpass']])
246 report['totalfails'] += detaildict[tag]['allfail']
247 report['totalpasses'] += detaildict[tag]['allpass']
248 report['grandtotal'] += detaildict[tag]['allfail']+detaildict[tag]['allpass']
249 count += 1
250 if report['grandtotal'] == 0:
251 report['percentfails'] = 0
252 report['percentpasses'] = 0
253 else:
254 report['percentfails'] = report['totalfails'] * 100 / report['grandtotal']
255 report['percentpasses'] = report['totalpasses'] * 100 / report['grandtotal']
256 report['dfail'] = json.dumps(report['dfail'])
257 report['dpass'] = json.dumps(report['dpass'])
258 return report
259
260def report_for_row(rowdict):
261 tags = {}
262 counters = {}
263 count = 0
264 sortedtests = []
265 sorteddates = []
266 lava = False
267 for dates in rowdict:
268 sorteddates.append(dates)
269 for rows in rowdict[dates]:
270 if rows not in sortedtests:
271 if rows!='lava':
272 sortedtests.append(rows)
273 else:
274 lava = True
275 sorteddates = sorted(sorteddates)
276 sortedtests = sorted(sortedtests)
277 if (lava):
278 sortedtests.insert(0, 'lava')
279 count = 0
280 for dates in sorteddates:
281 for testname in rowdict[dates]:
282 if (testname not in counters):
283 counters[testname] = {'pass':[],'fail':[]}
284 counters[testname]['pass'].append([count, rowdict[dates][testname]['pass']])
285 counters[testname]['fail'].append([count, rowdict[dates][testname]['fail']])
286 count += 1
287 return counters
288
289def generate_reports(allmatches, getcases):
290 kpidict, detaildict, rowdict, dates = generate_dicts(allmatches, getcases)
291 detailreport = report_for_detailgraph(detaildict)
292 rowreport = report_for_row(rowdict)
293 if (getcases == 1):
294 kpireport = report_for_kpigraph(kpidict, dates, detailreport['images'])
295 else:
296 kpireport = {}
297 return kpireport, detailreport, rowreport
298>>>>>>> MERGE-SOURCE
39299
40@BreadCrumb("Image Reports", parent=index)300@BreadCrumb("Image Reports", parent=index)
41def image_report_list(request):301def image_report_list(request):
302 kpireport = {}
303 pfreport = {}
304 testcases = []
42 imagesets = ImageSet.objects.filter()305 imagesets = ImageSet.objects.filter()
43 imagesets_data = []306 imagesets_data = []
44 for imageset in imagesets:307 for imageset in imagesets:
45 images_data = []308 images_data = []
309 bundle_ids = set()
310 count = 0
46 for image in imageset.images.all():311 for image in imageset.images.all():
47 # Migration hack: Image.filter cannot be auto populated, so ignore312 # Migration hack: Image.filter cannot be auto populated, so ignore
48 # images that have not been migrated to filters for now.313 # images that have not been migrated to filters for now.
49 if image.filter:314 if image.filter:
50 filter_data = image.filter.as_data()315 filter_data = image.filter.as_data()
316 matches = evaluate_filter(request.user, filter_data, prefetch_related=['launchpad_bugs'])
317 kpireport, pfreport, rowreport = generate_reports(matches, 0)
318 filter_data = image.filter.as_data()
51 image_data = {319 image_data = {
52 'name': image.name,320 'name': image.name+"?"+"sets="+imageset.name,
53 'bundle_count': evaluate_filter(request.user, filter_data).count(),321 'imagename': image.name,
54 'link': image.name,322 'imageset': imageset.name,
323 'bundle_count': matches.count(),
324 'link': image.name+"?"+"sets="+imageset.name,
325 'passes': pfreport['totalpasses'],
326 'grandtotal': pfreport['grandtotal'],
327 'fails': pfreport['grandtotal']-pfreport['totalpasses'],
55 }328 }
56 images_data.append(image_data)329 images_data.append(image_data)
57 images_data.sort(key=lambda d:d['name'])330 #Quick hack
58 imageset_data = {331 imageset_data = {
59 'name': imageset.name,332 'name': imageset.name,
60 'images': images_data,333 'images': images_data,
@@ -70,13 +343,36 @@
70343
71@BreadCrumb("{name}", parent=image_report_list, needs=['name'])344@BreadCrumb("{name}", parent=image_report_list, needs=['name'])
72def image_report_detail(request, name):345def image_report_detail(request, name):
73346 setname = request.GET.get("sets", None)
347 currentset = None
348 if (setname != None):
349 imagesets = ImageSet.objects.filter()
350 for imageset in imagesets:
351 if (setname == imageset.name):
352 currentset = imageset
353 break
354 else:
355 goaldates = {}
356 currentset = None
357 if (currentset != None):
358 if (currentset.relatedDates != None):
359 goaldates = currentset.relatedDates.return_dates()
360 currentset = currentset.relatedDates.name
361 else:
362 goaldates = {}
363 currentset = None
364 else:
365 goaldates = {}
74 image = Image.objects.get(name=name)366 image = Image.objects.get(name=name)
75 filter_data = image.filter.as_data()367 filter_data = image.filter.as_data()
368<<<<<<< TREE
76 matches = evaluate_filter(request.user, filter_data, prefetch_related=['launchpad_bugs', 'test_results'])[:50]369 matches = evaluate_filter(request.user, filter_data, prefetch_related=['launchpad_bugs', 'test_results'])[:50]
77370
371=======
372 matches = evaluate_filter(request.user, filter_data, prefetch_related=['launchpad_bugs'])
373 kpireport, pfreport, rowreport = generate_reports(matches, 1)
374>>>>>>> MERGE-SOURCE
78 build_number_to_cols = {}375 build_number_to_cols = {}
79
80 test_run_names = set()376 test_run_names = set()
81377
82 for match in matches:378 for match in matches:
@@ -86,7 +382,7 @@
86 if denorm.count_fail == 0:382 if denorm.count_fail == 0:
87 cls = 'present pass'383 cls = 'present pass'
88 else:384 else:
89 cls = 'present fail'385 cls = 'present fail'
90 bug_ids = sorted([b.bug_id for b in test_run.launchpad_bugs.all()])386 bug_ids = sorted([b.bug_id for b in test_run.launchpad_bugs.all()])
91387
92 measurements = [{'measurement': str(item.measurement)}388 measurements = [{'measurement': str(item.measurement)}
@@ -121,6 +417,7 @@
121417
122 table_data = {}418 table_data = {}
123419
420 table_data_dict = {}
124 for test_run_name in test_run_names:421 for test_run_name in test_run_names:
125 row_data = []422 row_data = []
126 for col in cols:423 for col in cols:
@@ -131,16 +428,34 @@
131 cls='missing',428 cls='missing',
132 )429 )
133 row_data.append(test_run_data)430 row_data.append(test_run_data)
431<<<<<<< TREE
134 table_data[test_run_name] = row_data432 table_data[test_run_name] = row_data
433=======
434 table_data.append(row_data)
435 table_data_dict[test_run_name] = row_data
436
437 #Lets see what we can do with table_data_dict
438>>>>>>> MERGE-SOURCE
135439
136 return render_to_response(440 return render_to_response(
137 "dashboard_app/image-report.html", {441 "dashboard_app/image-report.html", {
138 'bread_crumb_trail': BreadCrumbTrail.leading_to(442 'bread_crumb_trail': BreadCrumbTrail.leading_to(image_report_detail, name=image.name),
139 image_report_detail, name=image.name),
140 'image': image,443 'image': image,
444<<<<<<< TREE
141 'chart_data': json.dumps(table_data),445 'chart_data': json.dumps(table_data),
142 'test_names': json.dumps(test_run_names),446 'test_names': json.dumps(test_run_names),
143 'columns': json.dumps(cols),447 'columns': json.dumps(cols),
448=======
449 'cols': cols,
450 'table_data': table_data,
451 'test_run_names': test_run_names,
452 'pfreport': pfreport,
453 'rowreport': rowreport,
454 'kpireport': kpireport,
455 'goaldates': goaldates,
456 'goalname': currentset,
457 'setname': setname,
458>>>>>>> MERGE-SOURCE
144 }, RequestContext(request))459 }, RequestContext(request))
145460
146461

Subscribers

People subscribed via source and target branches