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
1=== modified file 'dashboard_app/admin.py'
2--- dashboard_app/admin.py 2013-04-01 08:50:05 +0000
3+++ dashboard_app/admin.py 2013-07-17 17:43:24 +0000
4@@ -40,6 +40,8 @@
5 SoftwarePackage,
6 SoftwareSource,
7 Tag,
8+ Goal,
9+ GoalDate,
10 Test,
11 TestCase,
12 TestResult,
13@@ -154,6 +156,14 @@
14 list_filter = ('test',)
15
16
17+class GoalsAdmin(admin.ModelAdmin):
18+ class GoalDates(admin.TabularInline):
19+ model = GoalDate
20+ extra = 0
21+ list_display = ('name',)
22+ inlines = [GoalDates]
23+
24+
25 class TestAdmin(admin.ModelAdmin):
26 pass
27
28@@ -219,6 +229,7 @@
29 admin.site.register(SoftwarePackage, SoftwarePackageAdmin)
30 admin.site.register(SoftwareSource, SoftwareSourceAdmin)
31 admin.site.register(Test, TestAdmin)
32+admin.site.register(Goal, GoalsAdmin)
33 admin.site.register(TestCase, TestCaseAdmin)
34 admin.site.register(TestResult, TestResultAdmin)
35 admin.site.register(TestRun, TestRunAdmin)
36
37=== added file 'dashboard_app/migrations/0030_auto__add_goal__add_testresults.py'
38--- dashboard_app/migrations/0030_auto__add_goal__add_testresults.py 1970-01-01 00:00:00 +0000
39+++ dashboard_app/migrations/0030_auto__add_goal__add_testresults.py 2013-07-17 17:43:24 +0000
40@@ -0,0 +1,344 @@
41+# -*- coding: utf-8 -*-
42+import datetime
43+from south.db import db
44+from south.v2 import SchemaMigration
45+from django.db import models
46+
47+
48+class Migration(SchemaMigration):
49+
50+ def forwards(self, orm):
51+ # Adding model 'GoalDate'
52+ db.create_table('dashboard_app_goaldate', (
53+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
54+ ('goal', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['dashboard_app.Goal'])),
55+ ('tag', self.gf('django.db.models.fields.CharField')(max_length=4)),
56+ ('startdate', self.gf('django.db.models.fields.DateField')()),
57+ ('enddate', self.gf('django.db.models.fields.DateField')()),
58+ ('value', self.gf('django.db.models.fields.FloatField')(default=0, max_length=64)),
59+ ))
60+ db.send_create_signal('dashboard_app', ['GoalDate'])
61+
62+ # Adding model 'Goal'
63+ db.create_table('dashboard_app_goal', (
64+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
65+ ('name', self.gf('django.db.models.fields.TextField')()),
66+ ))
67+ db.send_create_signal('dashboard_app', ['Goal'])
68+
69+ # Adding field 'ImageSet.relatedDates'
70+ db.add_column('dashboard_app_imageset', 'relatedDates',
71+ self.gf('django.db.models.fields.related.ForeignKey')(to=orm['dashboard_app.Goal'], null=True, blank=True),
72+ keep_default=False)
73+
74+ # Adding field 'TestCase.target'
75+ db.add_column('dashboard_app_testcase', 'target',
76+ self.gf('django.db.models.fields.FloatField')(default=0, max_length=64, null=True),
77+ keep_default=False)
78+
79+ # Adding field 'TestCase.floor'
80+ db.add_column('dashboard_app_testcase', 'floor',
81+ self.gf('django.db.models.fields.FloatField')(default=0, max_length=64, null=True),
82+ keep_default=False)
83+
84+ # Adding field 'TestCase.weight'
85+ db.add_column('dashboard_app_testcase', 'weight',
86+ self.gf('django.db.models.fields.FloatField')(default=1, max_length=64, null=True),
87+ keep_default=False)
88+
89+ # Adding field 'TestCase.goal'
90+ db.add_column('dashboard_app_testcase', 'goal',
91+ self.gf('django.db.models.fields.FloatField')(default=0, max_length=64, null=True),
92+ keep_default=False)
93+
94+
95+ def backwards(self, orm):
96+ # Deleting model 'GoalDate'
97+ db.delete_table('dashboard_app_goaldate')
98+
99+ # Deleting model 'Goal'
100+ db.delete_table('dashboard_app_goal')
101+
102+ # Deleting field 'ImageSet.relatedDates'
103+ db.delete_column('dashboard_app_imageset', 'relatedDates_id')
104+
105+ # Deleting field 'TestCase.target'
106+ db.delete_column('dashboard_app_testcase', 'target')
107+
108+ # Deleting field 'TestCase.floor'
109+ db.delete_column('dashboard_app_testcase', 'floor')
110+
111+ # Deleting field 'TestCase.weight'
112+ db.delete_column('dashboard_app_testcase', 'weight')
113+
114+ # Deleting field 'TestCase.goal'
115+ db.delete_column('dashboard_app_testcase', 'goal')
116+
117+
118+ models = {
119+ 'auth.group': {
120+ 'Meta': {'object_name': 'Group'},
121+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
122+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
123+ 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
124+ },
125+ 'auth.permission': {
126+ 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
127+ 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
128+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
129+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
130+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
131+ },
132+ 'auth.user': {
133+ 'Meta': {'object_name': 'User'},
134+ 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
135+ 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
136+ 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
137+ 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
138+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
139+ 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
140+ 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
141+ 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
142+ 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
143+ 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
144+ 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
145+ 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
146+ 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
147+ },
148+ 'contenttypes.contenttype': {
149+ 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
150+ 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
151+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
152+ 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
153+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
154+ },
155+ 'dashboard_app.attachment': {
156+ 'Meta': {'object_name': 'Attachment'},
157+ 'content': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True'}),
158+ 'content_filename': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
159+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
160+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
161+ 'mime_type': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
162+ 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}),
163+ 'public_url': ('django.db.models.fields.URLField', [], {'max_length': '512', 'blank': 'True'})
164+ },
165+ 'dashboard_app.bundle': {
166+ 'Meta': {'ordering': "['-uploaded_on']", 'object_name': 'Bundle'},
167+ '_gz_content': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True', 'db_column': "'gz_content'"}),
168+ '_raw_content': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True', 'db_column': "'content'"}),
169+ 'bundle_stream': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'bundles'", 'to': "orm['dashboard_app.BundleStream']"}),
170+ 'content_filename': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
171+ 'content_sha1': ('django.db.models.fields.CharField', [], {'max_length': '40', 'unique': 'True', 'null': 'True'}),
172+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
173+ 'is_deserialized': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
174+ 'uploaded_by': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'uploaded_bundles'", 'null': 'True', 'to': "orm['auth.User']"}),
175+ 'uploaded_on': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.utcnow'})
176+ },
177+ 'dashboard_app.bundledeserializationerror': {
178+ 'Meta': {'object_name': 'BundleDeserializationError'},
179+ 'bundle': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'deserialization_error'", 'unique': 'True', 'primary_key': 'True', 'to': "orm['dashboard_app.Bundle']"}),
180+ 'error_message': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
181+ 'traceback': ('django.db.models.fields.TextField', [], {'max_length': '32768'})
182+ },
183+ 'dashboard_app.bundlestream': {
184+ 'Meta': {'object_name': 'BundleStream'},
185+ 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.Group']", 'null': 'True', 'blank': 'True'}),
186+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
187+ 'is_anonymous': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
188+ 'is_public': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
189+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}),
190+ 'pathname': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'}),
191+ 'slug': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}),
192+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'})
193+ },
194+ 'dashboard_app.goal': {
195+ 'Meta': {'object_name': 'Goal'},
196+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
197+ 'name': ('django.db.models.fields.TextField', [], {})
198+ },
199+ 'dashboard_app.goaldate': {
200+ 'Meta': {'object_name': 'GoalDate'},
201+ 'enddate': ('django.db.models.fields.DateField', [], {}),
202+ 'goal': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['dashboard_app.Goal']"}),
203+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
204+ 'startdate': ('django.db.models.fields.DateField', [], {}),
205+ 'tag': ('django.db.models.fields.CharField', [], {'max_length': '4'}),
206+ 'value': ('django.db.models.fields.FloatField', [], {'default': '0', 'max_length': '64'})
207+ },
208+ 'dashboard_app.hardwaredevice': {
209+ 'Meta': {'object_name': 'HardwareDevice'},
210+ 'description': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
211+ 'device_type': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
212+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
213+ },
214+ 'dashboard_app.image': {
215+ 'Meta': {'object_name': 'Image'},
216+ 'filter': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'null': 'True', 'to': "orm['dashboard_app.TestRunFilter']"}),
217+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
218+ 'name': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '1024'})
219+ },
220+ 'dashboard_app.imageset': {
221+ 'Meta': {'object_name': 'ImageSet'},
222+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
223+ 'images': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['dashboard_app.Image']", 'symmetrical': 'False'}),
224+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '1024'}),
225+ 'relatedDates': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['dashboard_app.Goal']", 'null': 'True', 'blank': 'True'})
226+ },
227+ 'dashboard_app.launchpadbug': {
228+ 'Meta': {'object_name': 'LaunchpadBug'},
229+ 'bug_id': ('django.db.models.fields.PositiveIntegerField', [], {'unique': 'True'}),
230+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
231+ 'test_runs': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'launchpad_bugs'", 'symmetrical': 'False', 'to': "orm['dashboard_app.TestRun']"})
232+ },
233+ 'dashboard_app.namedattribute': {
234+ 'Meta': {'unique_together': "(('object_id', 'name'),)", 'object_name': 'NamedAttribute'},
235+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
236+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
237+ 'name': ('django.db.models.fields.TextField', [], {}),
238+ 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}),
239+ 'value': ('django.db.models.fields.TextField', [], {})
240+ },
241+ 'dashboard_app.pmqabundlestream': {
242+ 'Meta': {'object_name': 'PMQABundleStream'},
243+ 'bundle_stream': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'to': "orm['dashboard_app.BundleStream']"}),
244+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
245+ },
246+ 'dashboard_app.softwarepackage': {
247+ 'Meta': {'unique_together': "(('name', 'version'),)", 'object_name': 'SoftwarePackage'},
248+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
249+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
250+ 'version': ('django.db.models.fields.CharField', [], {'max_length': '128'})
251+ },
252+ 'dashboard_app.softwarepackagescratch': {
253+ 'Meta': {'object_name': 'SoftwarePackageScratch'},
254+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
255+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
256+ 'version': ('django.db.models.fields.CharField', [], {'max_length': '128'})
257+ },
258+ 'dashboard_app.softwaresource': {
259+ 'Meta': {'object_name': 'SoftwareSource'},
260+ 'branch_revision': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
261+ 'branch_url': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
262+ 'branch_vcs': ('django.db.models.fields.CharField', [], {'max_length': '10'}),
263+ 'commit_timestamp': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
264+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
265+ 'project_name': ('django.db.models.fields.CharField', [], {'max_length': '32'})
266+ },
267+ 'dashboard_app.tag': {
268+ 'Meta': {'object_name': 'Tag'},
269+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
270+ 'name': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '256'})
271+ },
272+ 'dashboard_app.test': {
273+ 'Meta': {'object_name': 'Test'},
274+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
275+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'blank': 'True'}),
276+ 'test_id': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '1024'})
277+ },
278+ 'dashboard_app.testcase': {
279+ 'Meta': {'unique_together': "(('test', 'test_case_id'),)", 'object_name': 'TestCase'},
280+ 'floor': ('django.db.models.fields.FloatField', [], {'default': '0', 'max_length': '64', 'null': 'True'}),
281+ 'goal': ('django.db.models.fields.FloatField', [], {'default': '0', 'max_length': '64', 'null': 'True'}),
282+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
283+ 'name': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
284+ 'target': ('django.db.models.fields.FloatField', [], {'default': '0', 'max_length': '64', 'null': 'True'}),
285+ 'test': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'test_cases'", 'to': "orm['dashboard_app.Test']"}),
286+ 'test_case_id': ('django.db.models.fields.TextField', [], {}),
287+ 'units': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
288+ 'weight': ('django.db.models.fields.FloatField', [], {'default': '1', 'max_length': '64', 'null': 'True'})
289+ },
290+ 'dashboard_app.testdefinition': {
291+ 'Meta': {'object_name': 'TestDefinition'},
292+ 'content': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
293+ 'description': ('django.db.models.fields.TextField', [], {}),
294+ 'environment': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
295+ 'format': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
296+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
297+ 'location': ('django.db.models.fields.CharField', [], {'default': "'LOCAL'", 'max_length': '64'}),
298+ 'mime_type': ('django.db.models.fields.CharField', [], {'default': "'text/plain'", 'max_length': '64'}),
299+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '512'}),
300+ 'target_dev_types': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
301+ 'target_os': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
302+ 'url': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
303+ 'version': ('django.db.models.fields.CharField', [], {'max_length': '256'})
304+ },
305+ 'dashboard_app.testresult': {
306+ 'Meta': {'ordering': "('_order',)", 'object_name': 'TestResult'},
307+ '_order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
308+ 'filename': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}),
309+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
310+ 'lineno': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}),
311+ 'measurement': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '20', 'decimal_places': '10', 'blank': 'True'}),
312+ 'message': ('django.db.models.fields.TextField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}),
313+ 'microseconds': ('django.db.models.fields.BigIntegerField', [], {'null': 'True', 'blank': 'True'}),
314+ 'relative_index': ('django.db.models.fields.PositiveIntegerField', [], {}),
315+ 'result': ('django.db.models.fields.PositiveSmallIntegerField', [], {}),
316+ 'test_case': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'test_results'", 'null': 'True', 'to': "orm['dashboard_app.TestCase']"}),
317+ 'test_run': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'test_results'", 'to': "orm['dashboard_app.TestRun']"}),
318+ 'timestamp': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'})
319+ },
320+ 'dashboard_app.testrun': {
321+ 'Meta': {'ordering': "['-import_assigned_date']", 'object_name': 'TestRun'},
322+ 'analyzer_assigned_date': ('django.db.models.fields.DateTimeField', [], {}),
323+ 'analyzer_assigned_uuid': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '36'}),
324+ 'bundle': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'test_runs'", 'to': "orm['dashboard_app.Bundle']"}),
325+ 'devices': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'test_runs'", 'blank': 'True', 'to': "orm['dashboard_app.HardwareDevice']"}),
326+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
327+ 'import_assigned_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
328+ 'microseconds': ('django.db.models.fields.BigIntegerField', [], {'null': 'True', 'blank': 'True'}),
329+ 'packages': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'test_runs'", 'blank': 'True', 'to': "orm['dashboard_app.SoftwarePackage']"}),
330+ 'sources': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'test_runs'", 'blank': 'True', 'to': "orm['dashboard_app.SoftwareSource']"}),
331+ 'sw_image_desc': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
332+ 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'test_runs'", 'blank': 'True', 'to': "orm['dashboard_app.Tag']"}),
333+ 'test': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'test_runs'", 'to': "orm['dashboard_app.Test']"}),
334+ 'time_check_performed': ('django.db.models.fields.BooleanField', [], {'default': 'False'})
335+ },
336+ 'dashboard_app.testrundenormalization': {
337+ 'Meta': {'object_name': 'TestRunDenormalization'},
338+ 'count_fail': ('django.db.models.fields.PositiveIntegerField', [], {}),
339+ 'count_pass': ('django.db.models.fields.PositiveIntegerField', [], {}),
340+ 'count_skip': ('django.db.models.fields.PositiveIntegerField', [], {}),
341+ 'count_unknown': ('django.db.models.fields.PositiveIntegerField', [], {}),
342+ 'test_run': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'denormalization'", 'unique': 'True', 'primary_key': 'True', 'to': "orm['dashboard_app.TestRun']"})
343+ },
344+ 'dashboard_app.testrunfilter': {
345+ 'Meta': {'unique_together': "(('owner', 'name'),)", 'object_name': 'TestRunFilter'},
346+ 'build_number_attribute': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}),
347+ 'bundle_streams': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['dashboard_app.BundleStream']", 'symmetrical': 'False'}),
348+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
349+ 'name': ('django.db.models.fields.SlugField', [], {'max_length': '1024'}),
350+ 'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}),
351+ 'public': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
352+ 'uploaded_by': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'+'", 'null': 'True', 'to': "orm['auth.User']"})
353+ },
354+ 'dashboard_app.testrunfilterattribute': {
355+ 'Meta': {'object_name': 'TestRunFilterAttribute'},
356+ 'filter': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'attributes'", 'to': "orm['dashboard_app.TestRunFilter']"}),
357+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
358+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
359+ 'value': ('django.db.models.fields.CharField', [], {'max_length': '1024'})
360+ },
361+ 'dashboard_app.testrunfiltersubscription': {
362+ 'Meta': {'unique_together': "(('user', 'filter'),)", 'object_name': 'TestRunFilterSubscription'},
363+ 'filter': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['dashboard_app.TestRunFilter']"}),
364+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
365+ 'level': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
366+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
367+ },
368+ 'dashboard_app.testrunfiltertest': {
369+ 'Meta': {'object_name': 'TestRunFilterTest'},
370+ 'filter': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'tests'", 'to': "orm['dashboard_app.TestRunFilter']"}),
371+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
372+ 'index': ('django.db.models.fields.PositiveIntegerField', [], {}),
373+ 'test': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'to': "orm['dashboard_app.Test']"})
374+ },
375+ 'dashboard_app.testrunfiltertestcase': {
376+ 'Meta': {'object_name': 'TestRunFilterTestCase'},
377+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
378+ 'index': ('django.db.models.fields.PositiveIntegerField', [], {}),
379+ 'test': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'cases'", 'to': "orm['dashboard_app.TestRunFilterTest']"}),
380+ 'test_case': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'to': "orm['dashboard_app.TestCase']"})
381+ }
382+ }
383+
384+ complete_apps = ['dashboard_app']
385\ No newline at end of file
386
387=== modified file 'dashboard_app/models.py'
388--- dashboard_app/models.py 2013-04-01 08:50:05 +0000
389+++ dashboard_app/models.py 2013-07-17 17:43:24 +0000
390@@ -659,6 +659,45 @@
391 result=TestResult.RESULT_FAIL).count()
392
393
394+class Goal(models.Model):
395+ name = models.TextField(
396+ verbose_name = _("Goal Name"),
397+ help_text = (_("""The name for this series of goals""")))
398+
399+ def __unicode__(self):
400+ return self.name
401+
402+ def return_dates(self):
403+ goaldate = GoalDate.objects.all()
404+ date = {}
405+ for name in goaldate:
406+ if (name.goal == self):
407+ date[name] = {'startdate':name.startdate.isoformat(),'enddate':name.enddate.isoformat(), 'value':name.value}
408+ return date
409+
410+
411+class GoalDate(models.Model):
412+ goal = models.ForeignKey(Goal)
413+
414+ tag = models.CharField(
415+ max_length = 4,
416+ help_text = (_("""1-4 character tag which distinguishes this goal on the graph""")))
417+
418+ startdate = models.DateField(
419+ verbose_name= _("Start Date"))
420+
421+ enddate = models.DateField(
422+ verbose_name= _("End Date"))
423+
424+ value = models.FloatField(
425+ verbose_name= _("Value"),
426+ default=0,
427+ max_length = 64,
428+ help_text = (_("""The goal % for this milestone.""")))
429+
430+ def __unicode__(self):
431+ return self.tag
432+
433 class TestCase(models.Model):
434 """
435 Model for representing test cases.
436@@ -687,6 +726,34 @@
437 + _help_max_length(100)),
438 verbose_name = _("Units"))
439
440+ target = models.FloatField(
441+ default=0,
442+ null = True,
443+ max_length = 64,
444+ help_text = (_("""The target value of the testcase, representing 100%.""")),
445+ verbose_name= _("Target"))
446+
447+ floor = models.FloatField(
448+ default=0,
449+ max_length = 64,
450+ null = True,
451+ help_text = (_("""The lowest value of the testcase, representing 0%.""")),
452+ verbose_name= _("Floor"))
453+
454+ weight = models.FloatField(
455+ default=1,
456+ null = True,
457+ max_length = 64,
458+ help_text = (_("""The weight of the testcase when graphed against others.""")),
459+ verbose_name= _("Weight"))
460+
461+ goal = models.FloatField(
462+ default=0,
463+ null = True,
464+ max_length = 64,
465+ help_text = (_("""The goal % for a testcase.""")),
466+ verbose_name= _("Goal"))
467+
468 class Meta:
469 unique_together = (('test', 'test_case_id'))
470
471@@ -1177,6 +1244,22 @@
472 def test(self):
473 return self.test_run.test
474
475+ @property
476+ def target(self):
477+ return self.test_case.target
478+
479+ @property
480+ def weight(self):
481+ return self.test_case.weight
482+
483+ @property
484+ def floor(self):
485+ return self.test_case.floor
486+
487+ @property
488+ def goal(self):
489+ return self.test_case.goal
490+
491 # Core attributes
492
493 result = models.PositiveSmallIntegerField(
494@@ -1517,6 +1600,8 @@
495
496 name = models.CharField(max_length=1024, unique=True)
497
498+ relatedDates = models.ForeignKey(Goal, null=True, blank=True)
499+
500 images = models.ManyToManyField(Image)
501
502 def __unicode__(self):
503@@ -1715,6 +1800,33 @@
504 "dashboard_app.views.filters.views.filter_detail",
505 [self.owner.username, self.name])
506
507+ def get_testruns_impl(self, user, bundle_streams, attributes):
508+ accessible_bundle_streams = BundleStream.objects.accessible_by_principal(
509+ user)
510+ testruns = TestRun.objects.filter(
511+ models.Q(bundle__bundle_stream__in=accessible_bundle_streams),
512+ models.Q(bundle__bundle_stream__in=bundle_streams),
513+ )
514+
515+ for (name, value) in attributes:
516+ testruns = TestRun.objects.filter(
517+ id__in=testruns.values_list('id'),
518+ attributes__name=name, attributes__value=value)
519+
520+ # if the filter doesn't specify a test, we still only return one
521+ # test run per bundle. the display code knows to do different
522+ # things in this case.
523+ testruns = TestRun.objects.filter(
524+ id__in=testruns.values_list('id'),
525+ test=Test.objects.get(test_id='lava'))
526+
527+ return testruns
528+
529+ def get_test_runs(self, user):
530+ return self.get_testruns_impl(
531+ user,
532+ self.bundle_streams.all(),
533+ self.attributes.values_list('name', 'value'))
534
535 class TestRunFilterSubscription(models.Model):
536
537
538=== modified file 'dashboard_app/static/dashboard_app/js/jquery.flot.axislabels.js' (properties changed: -x to +x)
539--- dashboard_app/static/dashboard_app/js/jquery.flot.axislabels.js 2011-05-05 01:22:07 +0000
540+++ dashboard_app/static/dashboard_app/js/jquery.flot.axislabels.js 2013-07-17 17:43:24 +0000
541@@ -1,87 +1,416 @@
542 /*
543-Flot plugin for labeling axis
544-
545- (xy)axis: {
546- label: "label string",
547- labelPos: "high" or "low"
548- }
549-
550-This plugin allows you to label an axis without much fuss, by
551-replacing one of the extreme ticks with the chosen label string. Set
552-labelPos to "high" or "low" to replace respectively the maximum or the
553-minimum value of the ticks. User set axis.tickFormatter are respected
554-and multiple axes supported.
555-
556-Rui Pereira
557-rui (dot) pereira (at) gmail (dot) com
558+Axis Labels Plugin for flot.
559+http://github.com/markrcote/flot-axislabels
560+
561+Original code is Copyright (c) 2010 Xuan Luo.
562+Original code was released under the GPLv3 license by Xuan Luo, September 2010.
563+Original code was rereleased under the MIT license by Xuan Luo, April 2012.
564+
565+Improvements by Mark Cote.
566+
567+Permission is hereby granted, free of charge, to any person obtaining
568+a copy of this software and associated documentation files (the
569+"Software"), to deal in the Software without restriction, including
570+without limitation the rights to use, copy, modify, merge, publish,
571+distribute, sublicense, and/or sell copies of the Software, and to
572+permit persons to whom the Software is furnished to do so, subject to
573+the following conditions:
574+
575+The above copyright notice and this permission notice shall be
576+included in all copies or substantial portions of the Software.
577+
578+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
579+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
580+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
581+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
582+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
583+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
584+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
585+
586 */
587 (function ($) {
588-
589- function labelAxis(val, axis){
590- var ticks, opts = axis.options;
591-
592- // generator
593- var tmpopts = axis.n == 1? opts: (typeof opts.alignedTo != 'undefined')? opts.alignedTo.options: null;
594- // first axis or some axis aligned wrt it
595- if (tmpopts && (tmpopts.autoscaleMargin == null ||
596- (tmpopts.labelPos == 'high' && tmpopts.max != null) ||
597- (tmpopts.labelPos == 'low' && tmpopts.min != null)))
598- // cut ticks not seen
599- ticks = $.grep(axis.tickGenerator(axis), function(v){
600- return (v > axis.min && v < axis.max);
601- });
602- // standard tick generator
603- else ticks = axis.tickGenerator(axis);
604-
605- // formatter
606- if ((opts.labelPos == 'high' && val == ticks[ticks.length-1]) ||
607- (opts.labelPos == 'low' && val == ticks[0]))
608- return opts.label;
609- else {
610- // user set tickFormatter
611- if ($.isFunction(opts.userFormatter)){
612- var tmp = opts.userFormatter;
613- // avoid infinite loops
614- opts.userFormatter = null;
615- return tmp(val, axis);
616+ var options = { };
617+
618+ function canvasSupported() {
619+ return !!document.createElement('canvas').getContext;
620+ }
621+
622+ function canvasTextSupported() {
623+ if (!canvasSupported()) {
624+ return false;
625+ }
626+ var dummy_canvas = document.createElement('canvas');
627+ var context = dummy_canvas.getContext('2d');
628+ return typeof context.fillText == 'function';
629+ }
630+
631+ function css3TransitionSupported() {
632+ var div = document.createElement('div');
633+ return typeof div.style.MozTransition != 'undefined' // Gecko
634+ || typeof div.style.OTransition != 'undefined' // Opera
635+ || typeof div.style.webkitTransition != 'undefined' // WebKit
636+ || typeof div.style.transition != 'undefined';
637+ }
638+
639+
640+ function AxisLabel(axisName, position, padding, plot, opts) {
641+ this.axisName = axisName;
642+ this.position = position;
643+ this.padding = padding;
644+ this.plot = plot;
645+ this.opts = opts;
646+ this.width = 0;
647+ this.height = 0;
648+ }
649+
650+
651+ CanvasAxisLabel.prototype = new AxisLabel();
652+ CanvasAxisLabel.prototype.constructor = CanvasAxisLabel;
653+ function CanvasAxisLabel(axisName, position, padding, plot, opts) {
654+ AxisLabel.prototype.constructor.call(this, axisName, position, padding,
655+ plot, opts);
656+ }
657+
658+ CanvasAxisLabel.prototype.calculateSize = function() {
659+ if (!this.opts.axisLabelFontSizePixels)
660+ this.opts.axisLabelFontSizePixels = 14;
661+ if (!this.opts.axisLabelFontFamily)
662+ this.opts.axisLabelFontFamily = 'sans-serif';
663+
664+ var textWidth = this.opts.axisLabelFontSizePixels + this.padding;
665+ var textHeight = this.opts.axisLabelFontSizePixels + this.padding;
666+ if (this.position == 'left' || this.position == 'right') {
667+ this.width = this.opts.axisLabelFontSizePixels + this.padding;
668+ this.height = 0;
669+ } else {
670+ this.width = 0;
671+ this.height = this.opts.axisLabelFontSizePixels + this.padding;
672+ }
673+ };
674+
675+ CanvasAxisLabel.prototype.draw = function(box) {
676+ var ctx = this.plot.getCanvas().getContext('2d');
677+ ctx.save();
678+ ctx.font = this.opts.axisLabelFontSizePixels + 'px ' +
679+ this.opts.axisLabelFontFamily;
680+ var width = ctx.measureText(this.opts.axisLabel).width;
681+ var height = this.opts.axisLabelFontSizePixels;
682+ var x, y, angle = 0;
683+ if (this.position == 'top') {
684+ x = box.left + box.width/2 - width/2;
685+ y = box.top + height*0.72;
686+ } else if (this.position == 'bottom') {
687+ x = box.left + box.width/2 - width/2;
688+ y = box.top + box.height - height*0.72;
689+ } else if (this.position == 'left') {
690+ x = box.left + height*0.72;
691+ y = box.height/2 + box.top + width/2;
692+ angle = -Math.PI/2;
693+ } else if (this.position == 'right') {
694+ x = box.left + box.width - height*0.72;
695+ y = box.height/2 + box.top - width/2;
696+ angle = Math.PI/2;
697+ }
698+ ctx.translate(x, y);
699+ ctx.rotate(angle);
700+ ctx.fillText(this.opts.axisLabel, 0, 0);
701+ ctx.restore();
702+ };
703+
704+
705+ HtmlAxisLabel.prototype = new AxisLabel();
706+ HtmlAxisLabel.prototype.constructor = HtmlAxisLabel;
707+ function HtmlAxisLabel(axisName, position, padding, plot, opts) {
708+ AxisLabel.prototype.constructor.call(this, axisName, position,
709+ padding, plot, opts);
710+ }
711+
712+ HtmlAxisLabel.prototype.calculateSize = function() {
713+ var elem = $('<div class="axisLabels" style="position:absolute;">' +
714+ this.opts.axisLabel + '</div>');
715+ this.plot.getPlaceholder().append(elem);
716+ // store height and width of label itself, for use in draw()
717+ this.labelWidth = elem.outerWidth(true);
718+ this.labelHeight = elem.outerHeight(true);
719+ elem.remove();
720+
721+ this.width = this.height = 0;
722+ if (this.position == 'left' || this.position == 'right') {
723+ this.width = this.labelWidth + this.padding;
724+ } else {
725+ this.height = this.labelHeight + this.padding;
726+ }
727+ };
728+
729+ HtmlAxisLabel.prototype.draw = function(box) {
730+ this.plot.getPlaceholder().find('#' + this.axisName + 'Label').remove();
731+ var elem = $('<div id="' + this.axisName +
732+ 'Label" " class="axisLabels" style="position:absolute;">'
733+ + this.opts.axisLabel + '</div>');
734+ this.plot.getPlaceholder().append(elem);
735+ if (this.position == 'top') {
736+ elem.css('left', box.left + box.width/2 - this.labelWidth/2 + 'px');
737+ elem.css('top', box.top + 'px');
738+ } else if (this.position == 'bottom') {
739+ elem.css('left', box.left + box.width/2 - this.labelWidth/2 + 'px');
740+ elem.css('top', box.top + box.height - this.labelHeight + 'px');
741+ } else if (this.position == 'left') {
742+ elem.css('top', box.top + box.height/2 - this.labelHeight/2 + 'px');
743+ elem.css('left', box.left + 'px');
744+ } else if (this.position == 'right') {
745+ elem.css('top', box.top + box.height/2 - this.labelHeight/2 + 'px');
746+ elem.css('left', box.left + box.width - this.labelWidth + 'px');
747+ }
748+ };
749+
750+
751+ CssTransformAxisLabel.prototype = new HtmlAxisLabel();
752+ CssTransformAxisLabel.prototype.constructor = CssTransformAxisLabel;
753+ function CssTransformAxisLabel(axisName, position, padding, plot, opts) {
754+ HtmlAxisLabel.prototype.constructor.call(this, axisName, position,
755+ padding, plot, opts);
756+ }
757+
758+ CssTransformAxisLabel.prototype.calculateSize = function() {
759+ HtmlAxisLabel.prototype.calculateSize.call(this);
760+ this.width = this.height = 0;
761+ if (this.position == 'left' || this.position == 'right') {
762+ this.width = this.labelHeight + this.padding;
763+ } else {
764+ this.height = this.labelHeight + this.padding;
765+ }
766+ };
767+
768+ CssTransformAxisLabel.prototype.transforms = function(degrees, x, y) {
769+ var stransforms = {
770+ '-moz-transform': '',
771+ '-webkit-transform': '',
772+ '-o-transform': '',
773+ '-ms-transform': ''
774+ };
775+ if (x != 0 || y != 0) {
776+ var stdTranslate = ' translate(' + x + 'px, ' + y + 'px)';
777+ stransforms['-moz-transform'] += stdTranslate;
778+ stransforms['-webkit-transform'] += stdTranslate;
779+ stransforms['-o-transform'] += stdTranslate;
780+ stransforms['-ms-transform'] += stdTranslate;
781+ }
782+ if (degrees != 0) {
783+ var rotation = degrees / 90;
784+ var stdRotate = ' rotate(' + degrees + 'deg)';
785+ stransforms['-moz-transform'] += stdRotate;
786+ stransforms['-webkit-transform'] += stdRotate;
787+ stransforms['-o-transform'] += stdRotate;
788+ stransforms['-ms-transform'] += stdRotate;
789+ }
790+ var s = 'top: 0; left: 0; ';
791+ for (var prop in stransforms) {
792+ if (stransforms[prop]) {
793+ s += prop + ':' + stransforms[prop] + ';';
794+ }
795+ }
796+ s += ';';
797+ return s;
798+ };
799+
800+ CssTransformAxisLabel.prototype.calculateOffsets = function(box) {
801+ var offsets = { x: 0, y: 0, degrees: 0 };
802+ if (this.position == 'bottom') {
803+ offsets.x = box.left + box.width/2 - this.labelWidth/2;
804+ offsets.y = box.top + box.height - this.labelHeight;
805+ } else if (this.position == 'top') {
806+ offsets.x = box.left + box.width/2 - this.labelWidth/2;
807+ offsets.y = box.top;
808+ } else if (this.position == 'left') {
809+ offsets.degrees = -90;
810+ offsets.x = box.left - this.labelWidth/2 + this.labelHeight/2;
811+ offsets.y = box.height/2 + box.top;
812+ } else if (this.position == 'right') {
813+ offsets.degrees = 90;
814+ offsets.x = box.left + box.width - this.labelWidth/2
815+ - this.labelHeight/2;
816+ offsets.y = box.height/2 + box.top;
817+ }
818+ return offsets;
819+ };
820+
821+ CssTransformAxisLabel.prototype.draw = function(box) {
822+ this.plot.getPlaceholder().find("." + this.axisName + "Label").remove();
823+ var offsets = this.calculateOffsets(box);
824+ var elem = $('<div class="axisLabels ' + this.axisName +
825+ 'Label" style="position:absolute; ' +
826+ 'color: ' + this.opts.color + '; ' +
827+ this.transforms(offsets.degrees, offsets.x, offsets.y) +
828+ '">' + this.opts.axisLabel + '</div>');
829+ this.plot.getPlaceholder().append(elem);
830+ };
831+
832+
833+ IeTransformAxisLabel.prototype = new CssTransformAxisLabel();
834+ IeTransformAxisLabel.prototype.constructor = IeTransformAxisLabel;
835+ function IeTransformAxisLabel(axisName, position, padding, plot, opts) {
836+ CssTransformAxisLabel.prototype.constructor.call(this, axisName,
837+ position, padding,
838+ plot, opts);
839+ this.requiresResize = false;
840+ }
841+
842+ IeTransformAxisLabel.prototype.transforms = function(degrees, x, y) {
843+ // I didn't feel like learning the crazy Matrix stuff, so this uses
844+ // a combination of the rotation transform and CSS positioning.
845+ var s = '';
846+ if (degrees != 0) {
847+ var rotation = degrees/90;
848+ while (rotation < 0) {
849+ rotation += 4;
850+ }
851+ s += ' filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=' + rotation + '); ';
852+ // see below
853+ this.requiresResize = (this.position == 'right');
854+ }
855+ if (x != 0) {
856+ s += 'left: ' + x + 'px; ';
857+ }
858+ if (y != 0) {
859+ s += 'top: ' + y + 'px; ';
860+ }
861+ return s;
862+ };
863+
864+ IeTransformAxisLabel.prototype.calculateOffsets = function(box) {
865+ var offsets = CssTransformAxisLabel.prototype.calculateOffsets.call(
866+ this, box);
867+ // adjust some values to take into account differences between
868+ // CSS and IE rotations.
869+ if (this.position == 'top') {
870+ // FIXME: not sure why, but placing this exactly at the top causes
871+ // the top axis label to flip to the bottom...
872+ offsets.y = box.top + 1;
873+ } else if (this.position == 'left') {
874+ offsets.x = box.left;
875+ offsets.y = box.height/2 + box.top - this.labelWidth/2;
876+ } else if (this.position == 'right') {
877+ offsets.x = box.left + box.width - this.labelHeight;
878+ offsets.y = box.height/2 + box.top - this.labelWidth/2;
879+ }
880+ return offsets;
881+ };
882+
883+ IeTransformAxisLabel.prototype.draw = function(box) {
884+ CssTransformAxisLabel.prototype.draw.call(this, box);
885+ if (this.requiresResize) {
886+ var elem = this.plot.getPlaceholder().find("." + this.axisName + "Label");
887+ // Since we used CSS positioning instead of transforms for
888+ // translating the element, and since the positioning is done
889+ // before any rotations, we have to reset the width and height
890+ // in case the browser wrapped the text (specifically for the
891+ // y2axis).
892+ elem.css('width', this.labelWidth);
893+ elem.css('height', this.labelHeight);
894+ }
895+ };
896+
897+
898+ function init(plot) {
899+ // This is kind of a hack. There are no hooks in Flot between
900+ // the creation and measuring of the ticks (setTicks, measureTickLabels
901+ // in setupGrid() ) and the drawing of the ticks and plot box
902+ // (insertAxisLabels in setupGrid() ).
903+ //
904+ // Therefore, we use a trick where we run the draw routine twice:
905+ // the first time to get the tick measurements, so that we can change
906+ // them, and then have it draw it again.
907+ var secondPass = false;
908+
909+ var axisLabels = {};
910+ var axisOffsetCounts = { left: 0, right: 0, top: 0, bottom: 0 };
911+
912+ var defaultPadding = 2; // padding between axis and tick labels
913+ plot.hooks.draw.push(function (plot, ctx) {
914+ var hasAxisLabels = false;
915+ if (!secondPass) {
916+ // MEASURE AND SET OPTIONS
917+ $.each(plot.getAxes(), function(axisName, axis) {
918+ var opts = axis.options // Flot 0.7
919+ || plot.getOptions()[axisName]; // Flot 0.6
920+ if (!opts || !opts.axisLabel || !axis.show)
921+ return;
922+
923+ hasAxisLabels = true;
924+ var renderer = null;
925+
926+ if (!opts.axisLabelUseHtml &&
927+ navigator.appName == 'Microsoft Internet Explorer') {
928+ var ua = navigator.userAgent;
929+ var re = new RegExp("MSIE ([0-9]{1,}[\.0-9]{0,})");
930+ if (re.exec(ua) != null) {
931+ rv = parseFloat(RegExp.$1);
932+ }
933+ if (rv >= 9 && !opts.axisLabelUseCanvas && !opts.axisLabelUseHtml) {
934+ renderer = CssTransformAxisLabel;
935+ } else if (!opts.axisLabelUseCanvas && !opts.axisLabelUseHtml) {
936+ renderer = IeTransformAxisLabel;
937+ } else if (opts.axisLabelUseCanvas) {
938+ renderer = CanvasAxisLabel;
939+ } else {
940+ renderer = HtmlAxisLabel;
941+ }
942+ } else {
943+ if (opts.axisLabelUseHtml || (!css3TransitionSupported() && !canvasTextSupported()) && !opts.axisLabelUseCanvas) {
944+ renderer = HtmlAxisLabel;
945+ } else if (opts.axisLabelUseCanvas || !css3TransitionSupported()) {
946+ renderer = CanvasAxisLabel;
947+ } else {
948+ renderer = CssTransformAxisLabel;
949+ }
950+ }
951+
952+ var padding = opts.axisLabelPadding === undefined ?
953+ defaultPadding : opts.axisLabelPadding;
954+
955+ axisLabels[axisName] = new renderer(axisName,
956+ axis.position, padding,
957+ plot, opts);
958+
959+ // flot interprets axis.labelHeight and .labelWidth as
960+ // the height and width of the tick labels. We increase
961+ // these values to make room for the axis label and
962+ // padding.
963+
964+ axisLabels[axisName].calculateSize();
965+
966+ // AxisLabel.height and .width are the size of the
967+ // axis label and padding.
968+ axis.labelHeight += axisLabels[axisName].height;
969+ axis.labelWidth += axisLabels[axisName].width;
970+ opts.labelHeight = axis.labelHeight;
971+ opts.labelWidth = axis.labelWidth;
972+ });
973+ // if there are axis labels re-draw with new label widths and heights
974+ if (hasAxisLabels) {
975+ secondPass = true;
976+ plot.setupGrid();
977+ plot.draw();
978+ }
979 } else {
980- // scientific notation for small values
981- if ((axis.datamax != 0 && Math.abs(axis.datamax) < 1e-5) ||
982- (axis.datamin != 0 && Math.abs(axis.datamin) < 1e-5))
983- return val.toPrecision(2);
984- else return val.toFixed(axis.tickDecimals);
985- }
986- }
987- }
988+ // DRAW
989+ $.each(plot.getAxes(), function(axisName, axis) {
990+ var opts = axis.options // Flot 0.7
991+ || plot.getOptions()[axisName]; // Flot 0.6
992+ if (!opts || !opts.axisLabel || !axis.show)
993+ return;
994
995- function init(plot){
996- plot.hooks.processOptions.push(function(plot, options){
997- // separate X and Y
998- $.each({x: options.xaxes, y: options.yaxes}, function(direction, axes){
999- // get only axes with labels
1000- $.each($.grep(axes, function(v){
1001- return (typeof v.label != 'undefined' && v.label);
1002- }), function(i, axis){
1003- if ($.isFunction(axis.tickFormatter))
1004- axis.userFormatter = axis.tickFormatter;
1005- if (typeof axis.alignTicksWithAxis != 'undefined')
1006- $.each(plot.getAxes(), function(k,v){
1007- if (v.n == axis.alignTicksWithAxis && v.direction == direction)
1008- axis.alignedTo = v;
1009- });
1010- axis.tickFormatter = labelAxis;
1011+ axisLabels[axisName].draw(axis.box);
1012 });
1013- });
1014+ }
1015 });
1016 }
1017
1018- var options = { xaxis: {label: null, labelPos: 'high'},
1019- yaxis: {label: null, labelPos: 'high'} };
1020
1021 $.plot.plugins.push({
1022- init: init,
1023- options: options,
1024- name: "axislabels",
1025- version: "0.1"
1026- });
1027+ init: init,
1028+ options: options,
1029+ name: 'axisLabels',
1030+ version: '2.0b0'
1031+ });
1032 })(jQuery);
1033
1034=== added file 'dashboard_app/static/dashboard_app/js/jquery.flot.dashes.js'
1035--- dashboard_app/static/dashboard_app/js/jquery.flot.dashes.js 1970-01-01 00:00:00 +0000
1036+++ dashboard_app/static/dashboard_app/js/jquery.flot.dashes.js 2013-07-17 17:43:24 +0000
1037@@ -0,0 +1,231 @@
1038+/*
1039+ * jQuery.flot.dashes
1040+ *
1041+ * options = {
1042+ * series: {
1043+ * dashes: {
1044+ *
1045+ * // show
1046+ * // default: false
1047+ * // Whether to show dashes for the series.
1048+ * show: <boolean>,
1049+ *
1050+ * // lineWidth
1051+ * // default: 2
1052+ * // The width of the dashed line in pixels.
1053+ * lineWidth: <number>,
1054+ *
1055+ * // dashLength
1056+ * // default: 10
1057+ * // Controls the length of the individual dashes and the amount of
1058+ * // space between them.
1059+ * // If this is a number, the dashes and spaces will have that length.
1060+ * // If this is an array, it is read as [ dashLength, spaceLength ]
1061+ * dashLength: <number> or <array[2]>
1062+ * }
1063+ * }
1064+ * }
1065+ */
1066+(function($){
1067+
1068+ function init(plot) {
1069+
1070+ plot.hooks.drawSeries.push(function(plot, ctx, series) {
1071+
1072+ if (!series.dashes.show) {
1073+ return;
1074+ }
1075+
1076+ var plotOffset = plot.getPlotOffset();
1077+
1078+ function plotDashes(datapoints, xoffset, yoffset, axisx, axisy) {
1079+
1080+ var points = datapoints.points,
1081+ ps = datapoints.pointsize,
1082+ prevx = null,
1083+ prevy = null,
1084+ dashRemainder = 0,
1085+ dashOn = true,
1086+ dashOnLength,
1087+ dashOffLength;
1088+
1089+ if (series.dashes.dashLength[0]) {
1090+ dashOnLength = series.dashes.dashLength[0];
1091+ if (series.dashes.dashLength[1]) {
1092+ dashOffLength = series.dashes.dashLength[1];
1093+ } else {
1094+ dashOffLength = dashOnLength;
1095+ }
1096+ } else {
1097+ dashOffLength = dashOnLength = series.dashes.dashLength;
1098+ }
1099+
1100+ ctx.beginPath();
1101+
1102+ for (var i = ps; i < points.length; i += ps) {
1103+
1104+ var x1 = points[i - ps],
1105+ y1 = points[i - ps + 1],
1106+ x2 = points[i],
1107+ y2 = points[i + 1];
1108+
1109+ if (x1 == null || x2 == null) continue;
1110+
1111+ // clip with ymin
1112+ if (y1 <= y2 && y1 < axisy.min) {
1113+ if (y2 < axisy.min) continue; // line segment is outside
1114+ // compute new intersection point
1115+ x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
1116+ y1 = axisy.min;
1117+ } else if (y2 <= y1 && y2 < axisy.min) {
1118+ if (y1 < axisy.min) continue;
1119+ x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
1120+ y2 = axisy.min;
1121+ }
1122+
1123+ // clip with ymax
1124+ if (y1 >= y2 && y1 > axisy.max) {
1125+ if (y2 > axisy.max) continue;
1126+ x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
1127+ y1 = axisy.max;
1128+ } else if (y2 >= y1 && y2 > axisy.max) {
1129+ if (y1 > axisy.max) continue;
1130+ x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
1131+ y2 = axisy.max;
1132+ }
1133+
1134+ // clip with xmin
1135+ if (x1 <= x2 && x1 < axisx.min) {
1136+ if (x2 < axisx.min) continue;
1137+ y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
1138+ x1 = axisx.min;
1139+ } else if (x2 <= x1 && x2 < axisx.min) {
1140+ if (x1 < axisx.min) continue;
1141+ y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
1142+ x2 = axisx.min;
1143+ }
1144+
1145+ // clip with xmax
1146+ if (x1 >= x2 && x1 > axisx.max) {
1147+ if (x2 > axisx.max) continue;
1148+ y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
1149+ x1 = axisx.max;
1150+ } else if (x2 >= x1 && x2 > axisx.max) {
1151+ if (x1 > axisx.max) continue;
1152+ y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
1153+ x2 = axisx.max;
1154+ }
1155+
1156+ if (x1 != prevx || y1 != prevy) {
1157+ ctx.moveTo(axisx.p2c(x1) + xoffset, axisy.p2c(y1) + yoffset);
1158+ }
1159+
1160+ var ax1 = axisx.p2c(x1) + xoffset,
1161+ ay1 = axisy.p2c(y1) + yoffset,
1162+ ax2 = axisx.p2c(x2) + xoffset,
1163+ ay2 = axisy.p2c(y2) + yoffset,
1164+ dashOffset;
1165+
1166+ function lineSegmentOffset(segmentLength) {
1167+
1168+ var c = Math.sqrt(Math.pow(ax2 - ax1, 2) + Math.pow(ay2 - ay1, 2));
1169+
1170+ if (c <= segmentLength) {
1171+ return {
1172+ deltaX: ax2 - ax1,
1173+ deltaY: ay2 - ay1,
1174+ distance: c,
1175+ remainder: segmentLength - c
1176+ }
1177+ } else {
1178+ var xsign = ax2 > ax1 ? 1 : -1,
1179+ ysign = ay2 > ay1 ? 1 : -1;
1180+ return {
1181+ deltaX: xsign * Math.sqrt(Math.pow(segmentLength, 2) / (1 + Math.pow((ay2 - ay1)/(ax2 - ax1), 2))),
1182+ deltaY: ysign * Math.sqrt(Math.pow(segmentLength, 2) - Math.pow(segmentLength, 2) / (1 + Math.pow((ay2 - ay1)/(ax2 - ax1), 2))),
1183+ distance: segmentLength,
1184+ remainder: 0
1185+ };
1186+ }
1187+ }
1188+ //-end lineSegmentOffset
1189+
1190+ do {
1191+
1192+ dashOffset = lineSegmentOffset(
1193+ dashRemainder > 0 ? dashRemainder :
1194+ dashOn ? dashOnLength : dashOffLength);
1195+
1196+ if (dashOffset.deltaX != 0 || dashOffset.deltaY != 0) {
1197+ if (dashOn) {
1198+ ctx.lineTo(ax1 + dashOffset.deltaX, ay1 + dashOffset.deltaY);
1199+ } else {
1200+ ctx.moveTo(ax1 + dashOffset.deltaX, ay1 + dashOffset.deltaY);
1201+ }
1202+ }
1203+
1204+ dashOn = !dashOn;
1205+ dashRemainder = dashOffset.remainder;
1206+ ax1 += dashOffset.deltaX;
1207+ ay1 += dashOffset.deltaY;
1208+
1209+ } while (dashOffset.distance > 0);
1210+
1211+ prevx = x2;
1212+ prevy = y2;
1213+ }
1214+
1215+ ctx.stroke();
1216+ }
1217+ //-end plotDashes
1218+
1219+ ctx.save();
1220+ ctx.translate(plotOffset.left, plotOffset.top);
1221+ ctx.lineJoin = 'round';
1222+
1223+ var lw = series.dashes.lineWidth,
1224+ sw = series.shadowSize;
1225+
1226+ // FIXME: consider another form of shadow when filling is turned on
1227+ if (lw > 0 && sw > 0) {
1228+ // draw shadow as a thick and thin line with transparency
1229+ ctx.lineWidth = sw;
1230+ ctx.strokeStyle = "rgba(0,0,0,0.1)";
1231+ // position shadow at angle from the mid of line
1232+ var angle = Math.PI/18;
1233+ plotDashes(series.datapoints, Math.sin(angle) * (lw/2 + sw/2), Math.cos(angle) * (lw/2 + sw/2), series.xaxis, series.yaxis);
1234+ ctx.lineWidth = sw/2;
1235+ plotDashes(series.datapoints, Math.sin(angle) * (lw/2 + sw/4), Math.cos(angle) * (lw/2 + sw/4), series.xaxis, series.yaxis);
1236+ }
1237+
1238+ ctx.lineWidth = lw;
1239+ ctx.strokeStyle = series.color;
1240+
1241+ if (lw > 0) {
1242+ plotDashes(series.datapoints, 0, 0, series.xaxis, series.yaxis);
1243+ }
1244+
1245+ ctx.restore();
1246+
1247+ });
1248+ //-end drawSeries hook
1249+
1250+ }
1251+ //-end init
1252+
1253+ $.plot.plugins.push({
1254+ init: init,
1255+ options: {
1256+ series: {
1257+ dashes: {
1258+ show: false,
1259+ lineWidth: 2,
1260+ dashLength: 10
1261+ }
1262+ }
1263+ },
1264+ name: 'dashes',
1265+ version: '0.1b'
1266+ });
1267+
1268+})(jQuery)
1269\ No newline at end of file
1270
1271=== added file 'dashboard_app/static/dashboard_app/js/jquery.flot.resize.js'
1272--- dashboard_app/static/dashboard_app/js/jquery.flot.resize.js 1970-01-01 00:00:00 +0000
1273+++ dashboard_app/static/dashboard_app/js/jquery.flot.resize.js 2013-07-17 17:43:24 +0000
1274@@ -0,0 +1,60 @@
1275+/*
1276+Flot plugin for automatically redrawing plots when the placeholder
1277+size changes, e.g. on window resizes.
1278+
1279+It works by listening for changes on the placeholder div (through the
1280+jQuery resize event plugin) - if the size changes, it will redraw the
1281+plot.
1282+
1283+There are no options. If you need to disable the plugin for some
1284+plots, you can just fix the size of their placeholders.
1285+*/
1286+
1287+
1288+/* Inline dependency:
1289+ * jQuery resize event - v1.1 - 3/14/2010
1290+ * http://benalman.com/projects/jquery-resize-plugin/
1291+ *
1292+ * Copyright (c) 2010 "Cowboy" Ben Alman
1293+ * Dual licensed under the MIT and GPL licenses.
1294+ * http://benalman.com/about/license/
1295+ */
1296+(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);
1297+
1298+
1299+(function ($) {
1300+ var options = { }; // no options
1301+
1302+ function init(plot) {
1303+ function onResize() {
1304+ var placeholder = plot.getPlaceholder();
1305+
1306+ // somebody might have hidden us and we can't plot
1307+ // when we don't have the dimensions
1308+ if (placeholder.width() == 0 || placeholder.height() == 0)
1309+ return;
1310+
1311+ plot.resize();
1312+ plot.setupGrid();
1313+ plot.draw();
1314+ }
1315+
1316+ function bindEvents(plot, eventHolder) {
1317+ plot.getPlaceholder().resize(onResize);
1318+ }
1319+
1320+ function shutdown(plot, eventHolder) {
1321+ plot.getPlaceholder().unbind("resize", onResize);
1322+ }
1323+
1324+ plot.hooks.bindEvents.push(bindEvents);
1325+ plot.hooks.shutdown.push(shutdown);
1326+ }
1327+
1328+ $.plot.plugins.push({
1329+ init: init,
1330+ options: options,
1331+ name: 'resize',
1332+ version: '1.0'
1333+ });
1334+})(jQuery);
1335
1336=== added file 'dashboard_app/static/dashboard_app/js/jquery.flot.threshold.js'
1337--- dashboard_app/static/dashboard_app/js/jquery.flot.threshold.js 1970-01-01 00:00:00 +0000
1338+++ dashboard_app/static/dashboard_app/js/jquery.flot.threshold.js 2013-07-17 17:43:24 +0000
1339@@ -0,0 +1,142 @@
1340+/* Flot plugin for thresholding data.
1341+
1342+Copyright (c) 2007-2013 IOLA and Ole Laursen.
1343+Licensed under the MIT license.
1344+
1345+The plugin supports these options:
1346+
1347+series: {
1348+threshold: {
1349+below: number
1350+color: colorspec
1351+}
1352+}
1353+
1354+It can also be applied to a single series, like this:
1355+
1356+$.plot( $("#placeholder"), [{
1357+data: [ ... ],
1358+threshold: { ... }
1359+}])
1360+
1361+An array can be passed for multiple thresholding, like this:
1362+
1363+threshold: [{
1364+below: number1
1365+color: color1
1366+},{
1367+below: number2
1368+color: color2
1369+}]
1370+
1371+These multiple threshold objects can be passed in any order since they are
1372+sorted by the processing function.
1373+
1374+The data points below "below" are drawn with the specified color. This makes
1375+it easy to mark points below 0, e.g. for budget data.
1376+
1377+Internally, the plugin works by splitting the data into two series, above and
1378+below the threshold. The extra series below the threshold will have its label
1379+cleared and the special "originSeries" attribute set to the original series.
1380+You may need to check for this in hover events.
1381+
1382+*/
1383+
1384+(function ($) {
1385+ var options = {
1386+ series: { threshold: null } // or { below: number, color: color spec}
1387+ };
1388+
1389+ function init(plot) {
1390+ function thresholdData(plot, s, datapoints, below, color) {
1391+ var ps = datapoints.pointsize, i, x, y, p, prevp,
1392+ thresholded = $.extend({}, s); // note: shallow copy
1393+
1394+ thresholded.datapoints = { points: [], pointsize: ps, format: datapoints.format };
1395+ thresholded.label = null;
1396+ thresholded.color = color;
1397+ thresholded.threshold = null;
1398+ thresholded.originSeries = s;
1399+ thresholded.data = [];
1400+
1401+ var origpoints = datapoints.points,
1402+ addCrossingPoints = s.lines.show;
1403+
1404+ var threspoints = [];
1405+ var newpoints = [];
1406+ var m;
1407+
1408+ for (i = 0; i < origpoints.length; i += ps) {
1409+ x = origpoints[i];
1410+ y = origpoints[i + 1];
1411+
1412+ prevp = p;
1413+ if (y < below)
1414+ p = threspoints;
1415+ else
1416+ p = newpoints;
1417+
1418+ if (addCrossingPoints && prevp != p && x != null
1419+ && i > 0 && origpoints[i - ps] != null) {
1420+ var interx = x + (below - y) * (x - origpoints[i - ps]) / (y - origpoints[i - ps + 1]);
1421+ prevp.push(interx);
1422+ prevp.push(below);
1423+ for (m = 2; m < ps; ++m)
1424+ prevp.push(origpoints[i + m]);
1425+
1426+ p.push(null); // start new segment
1427+ p.push(null);
1428+ for (m = 2; m < ps; ++m)
1429+ p.push(origpoints[i + m]);
1430+ p.push(interx);
1431+ p.push(below);
1432+ for (m = 2; m < ps; ++m)
1433+ p.push(origpoints[i + m]);
1434+ }
1435+
1436+ p.push(x);
1437+ p.push(y);
1438+ for (m = 2; m < ps; ++m)
1439+ p.push(origpoints[i + m]);
1440+ }
1441+
1442+ datapoints.points = newpoints;
1443+ thresholded.datapoints.points = threspoints;
1444+
1445+ if (thresholded.datapoints.points.length > 0) {
1446+ var origIndex = $.inArray(s, plot.getData());
1447+ // Insert newly-generated series right after original one (to prevent it from becoming top-most)
1448+ plot.getData().splice(origIndex + 1, 0, thresholded);
1449+ }
1450+
1451+ // FIXME: there are probably some edge cases left in bars
1452+ }
1453+
1454+ function processThresholds(plot, s, datapoints) {
1455+ if (!s.threshold)
1456+ return;
1457+
1458+ if (s.threshold instanceof Array) {
1459+ s.threshold.sort(function(a, b) {
1460+ return a.below - b.below;
1461+ });
1462+
1463+ $(s.threshold).each(function(i, th) {
1464+ thresholdData(plot, s, datapoints, th.below, th.color);
1465+ });
1466+ }
1467+ else {
1468+ thresholdData(plot, s, datapoints, s.threshold.below, s.threshold.color);
1469+ }
1470+ }
1471+
1472+ plot.hooks.processDatapoints.push(processThresholds);
1473+ }
1474+
1475+ $.plot.plugins.push({
1476+ init: init,
1477+ options: options,
1478+ name: 'threshold',
1479+ version: '1.2'
1480+ });
1481+})(jQuery);
1482
1483=== modified file 'dashboard_app/templates/dashboard_app/image-report.html'
1484--- dashboard_app/templates/dashboard_app/image-report.html 2013-07-01 15:49:15 +0000
1485+++ dashboard_app/templates/dashboard_app/image-report.html 2013-07-17 17:43:24 +0000
1486@@ -4,6 +4,7 @@
1487 {{ block.super }}
1488 <link rel="stylesheet" type="text/css" href="{{ STATIC_URL }}dashboard_app/css/image-report.css"/>
1489 <script type="text/javascript" src="{{ STATIC_URL }}dashboard_app/js/image-report.js"></script>
1490+<<<<<<< TREE
1491 <script src="{{ STATIC_URL }}dashboard_app/js/excanvas.min.js"></script>
1492 <script src="{{ STATIC_URL }}dashboard_app/js/jquery.flot.min.js"></script>
1493 <script src="{{ STATIC_URL }}dashboard_app/js/jquery.flot.dashes.min.js"></script>
1494@@ -15,10 +16,23 @@
1495 test_names = $.parseJSON($('<div/>').html("{{test_names}}").text());
1496 columns = $.parseJSON($('<div/>').html("{{columns}}").text());
1497 </script>
1498+=======
1499+<script type="text/javascript" src="{{ STATIC_URL }}lava-server/js/jquery.dataTables.min.js"></script>
1500+<script type="text/javascript" src="{{ STATIC_URL }}dashboard_app/js/jquery.flot.min.js"></script>
1501+<script type="text/javascript" src="{{ STATIC_URL }}dashboard_app/js/jquery.flot.stack.min.js"></script>
1502+<script type="text/javascript" src="{{ STATIC_URL }}dashboard_app/js/jquery.flot.resize.js"></script>
1503+<script type="text/javascript" src="{{ STATIC_URL }}dashboard_app/js/jquery.flot.dashes.js"></script>
1504+<script type="text/javascript" src="{{ STATIC_URL }}dashboard_app/js/jquery.flot.axislabels.js"></script>
1505+<script type="text/javascript" src="{{ STATIC_URL }}dashboard_app/js/jquery.flot.threshold.js"></script>
1506+<!-- cp jquery.flot.resize.js /var/www/lava-server/static/dashboard_app/js/ -->
1507+<!-- cp jquery.flot.dashes.js /var/www/lava-server/static/dashboard_app/js/ -->
1508+<!-- cp jquery.flot.axislabels.js /var/www/lava-server/static/dashboard_app/js/ -->
1509+>>>>>>> MERGE-SOURCE
1510 {% endblock %}
1511
1512 {% block content %}
1513 <h1>Image Report: {{ image.name }}</h1>
1514+<<<<<<< TREE
1515
1516
1517 <div id="outer-container">
1518@@ -83,6 +97,1272 @@
1519 </div>
1520
1521
1522+=======
1523+<div style="display:inline-block;padding-left:20px;width:95%;">
1524+<div id="outer" style="width: 95%;">
1525+ <div id="inner" style="width: 75%; margin: 0 auto; ">
1526+ <input type="radio" name=typeselect id="passfailcheck" onclick="exposeList()" unchecked></input>Pass/Fail Graph
1527+ <input type="radio" name=typeselect id="kpicheck" onclick="exposeList()" checked></input>Measurement Graph
1528+ <div id="kpiinfo" style="display:inline">(
1529+ <input type="checkbox" id="weightedcheck" onclick="exposeList()"></input>Weighted
1530+ <div id="weightedinfo" style="display:none">
1531+ <input type="checkbox" id="weightedonly" onclick="draw()"></input>Only Goal
1532+ </div>
1533+)
1534+ <div style=float:right> <input type="checkbox" id="adjustedcheck" onclick="draw()"></input>Graph Adjusted Values</div>
1535+ </div>
1536+ </div>
1537+</div>
1538+
1539+<div id="error" style=color:#FF0000;>
1540+</div>
1541+<br>
1542+<div id="placeholder" style="width:90%;height:250px;float:left;">
1543+</div>
1544+<div id="legendtitle" style="width:9%;float:right;"><b>Legend</b></div>
1545+<div id="labeler" style="height:180px;width:9%;float:right;overflow:auto;border:1px solid gray;">
1546+</div>
1547+<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>
1548+</div>
1549+<br>
1550+<select id="startDate" onchange="draw()" style="display:inline;width:15%">
1551+ <option>Start Date</option>
1552+</select>
1553+<select id="endDate" onchange="draw()" style="display:inline;width:15%">
1554+ <option>End Date</option>
1555+</select>
1556+<select id="startImage" onchange="draw()" style="display:none;width:15%">
1557+ <option>Start Image</option>
1558+</select>
1559+<select id="endImage" onchange="draw()" style="display:none;width:15%">
1560+ <option>End Image</option>
1561+</select>
1562+<br>
1563+<input type="checkbox" id="imagecheck" style="float:left" onclick="changeType()"></input>Graph by Image
1564+<br>
1565+<input type="checkbox" id="cbChoices" style="float:left;" onclick="exposeList()"></input>Choose Tests:
1566+<div id="goal_dates_label" style="float:right;width:15%"><div id="goal_dates_label" style="float:left"><b>Project Goals</b></div></div>
1567+<br>
1568+<div id="rows" style="height:120px;width:30%;overflow:auto;border:1px solid blue;display:none">
1569+</div>
1570+<div id="testcases" style="height:120px;width:30%;overflow:auto;border:1px solid blue;float:left;display:none">
1571+</div>
1572+<div id="goal_dates" style="height:160px;width:15%;overflow:auto;border:1px solid #ff4400;display:inline;float:right">
1573+</div>
1574+<div id="kpi" style="height:120px;width:20%;overflow:auto;border:1px solid green;display:none">
1575+ <b><label for="kpi" id="kpilabel" >KPI Data</label></b><br>
1576+ Target:&emsp;&nbsp;&emsp;&nbsp;<input type="text" id="kpitarget" style="width:50px" readonly></input>
1577+ <br>
1578+ Floor:&emsp;&emsp;&emsp;<input type="text" id="kpifloor" style="width:50px" readonly></input>
1579+ <br>
1580+ Weight:&emsp;&emsp;&nbsp;<input type="text" id="kpiweight" style="width:50px" readonly></input>
1581+ <br>
1582+ Goal(XX%)&emsp;<input type="text" id="kpigoal" style="width:50px" readonly></input>
1583+</div>
1584+<br>
1585+Link: <input id="permalink" type="text" style="width:75%"></input>
1586+<br>
1587+<input type="checkbox" id="cbHelp" style="float:left;" onclick="exposeHelp()"></input><b>Display Help</b><br>
1588+<div id="help" style="height:80px;width:80%;overflow:auto;border:1px solid black;display:none">
1589+ <b>Pass/Fail Graph:</b> Displays a Pass Fail graph for the current Image Report.<br>
1590+ <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>
1591+ <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>
1592+ <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>
1593+ <b>&emsp;Only Goal:</b> Removes the individual test lines to display only the weighted bar graph.<br>
1594+ <b>&emsp;Graph Adjusted Values:</b> Displays the measurements as a percent, calculated using the range between the target and floor.<br>
1595+ <b>Graph By Image:</b> Allows you to graph using image names, instead of dates.<br>
1596+ <b>Link:</b> A URL link to this page with current settings.
1597+</div>
1598+<script type="text/javascript">
1599+
1600+if (document.URL.indexOf("?") != -1) {
1601+ var baseurl=document.URL.slice(0,document.URL.lastIndexOf("?"));
1602+ var url_options = document.URL.slice(document.URL.lastIndexOf("?")+1,document.URL.length+1);
1603+}
1604+else {
1605+ var baseurl = document.URL;
1606+ var url_options = '';
1607+}
1608+
1609+function zip() {
1610+ var args = [].slice.call(arguments);
1611+ var shortest = args.length==0 ? [] : args.reduce(function(a,b){
1612+ return a.length<b.length ? a : b
1613+ });
1614+
1615+ return shortest.map(function(_,i){
1616+ return args.map(function(array){return array[i]})
1617+ });
1618+};
1619+
1620+function trim (str) {
1621+ return str.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
1622+}
1623+
1624+function unzip(a) {
1625+ var b = [];
1626+ var c = [];
1627+ for (var i=0;i<a.length;i++) {
1628+ b.push(a[i][0]);
1629+ c.push(a[i][1]);
1630+ }
1631+ return [b,c];
1632+};
1633+
1634+function doublesort(a, b, func) {
1635+ var c = zip(a,b);
1636+ c = c.sort(function(a,b) { if(a[0][2] > b[0][2]){return 1}else{return -1} } );
1637+ return unzip(c);
1638+}
1639+
1640+function pare(a, m) {
1641+ var b = new Array(m);
1642+ var n2 = a.length - 2;
1643+ var m2 = m - 2;
1644+ b[0] = a[0];
1645+ rem = Math.floor(n2/m2)
1646+ for (var i=0;i<(n2/rem)+1;i++) {
1647+ b[i]=a[i*rem];
1648+ }
1649+ b[b.length] = a[n2+1];
1650+ diff = b[1][0] - b[0][0];
1651+ for (var i=6;i<b.length;i++){
1652+ try {
1653+ if (b[i][0] - b[i-1][0] != diff) {
1654+ b.splice(i,1);
1655+ i--;
1656+ }
1657+ }
1658+ catch(err){
1659+ b.splice(i,1);
1660+ i--;
1661+ }
1662+ }
1663+ return b;
1664+}
1665+
1666+function rainbow(numOfSteps, step) {
1667+ var r, g, b;
1668+ var h = step / numOfSteps;
1669+ var i = ~~(h * 5);
1670+ var f = h * 8 - i;
1671+ var q = 1 - f;
1672+ switch(i % 6){
1673+ case 0: r = q, g = 0, b = 0; break;
1674+ case 1: r = 0, g = q, b = 0; break;
1675+ case 2: r = 0, g = q, b = 1; break;
1676+ case 3: r = 1, g = q, b = 0; break;
1677+ case 4: r = q, g = 1, b = 0; break;
1678+ case 5: r = 1, g = q, b = 0; break;
1679+ }
1680+ var c = "#" + ("00" + (~ ~(r * 255)).toString(16)).slice(-2) + ("00" + (~ ~(g * 255)).toString(16)).slice(-2) + ("00" + (~ ~(b * 255)).toString(16)).slice(-2);
1681+ return (c);
1682+}
1683+
1684+function contains(a, obj, gen) {
1685+ for (var i = 0; i < a.length; i++) {
1686+ if (gen) {
1687+} if (a[i].toString().substr(0,a[i].toString().lastIndexOf(',')) === obj.toString()) {
1688+ return true;
1689+ }
1690+ else {
1691+ if (a[i].toString() === obj.toString()) {
1692+ return true;
1693+ }
1694+ }
1695+ }
1696+ return false;
1697+};
1698+
1699+function allimages(start, end) {
1700+ var images= [];
1701+ var counter=[]
1702+ {% for iimage in pfreport.images %}
1703+ images.push([{{forloop.counter0}},'{{iimage}}']);
1704+ {% endfor %}
1705+ tstart=start;
1706+ tend=end;
1707+ if (tstart == 0) {
1708+ tstart = 1;
1709+ }
1710+ if (tend == 0) {
1711+ tend = images.length;
1712+ }
1713+ if (tstart > tend) {
1714+ tend = images.length;
1715+ tstart = 1;
1716+ var div = document.getElementById("error");
1717+ div.textContent = "Invalid image Range: Start > End";
1718+ }
1719+ else {
1720+ var div = document.getElementById("error");
1721+ div.textContent = "";
1722+ }
1723+ images = images.slice(tstart-1,tend);
1724+ return [images,tstart-1,tend];
1725+};
1726+
1727+function alldates(start, end) {
1728+ var ddates = [];
1729+ var counter=[];
1730+ count = 0;
1731+ {% for ddate in pfreport.ddates %}
1732+ ddates.push([count,'{{ddate}}']);
1733+ count++;
1734+ {% endfor %}
1735+// This pushes the goalddates to the dates, worth it?
1736+/*
1737+ {% for tag, value in goaldates.items %}
1738+ ddates.push([count, '{{value.startdate}}'])
1739+ ddates.push([count, '{{value.enddate}}'])
1740+ count++
1741+ {% endfor %}
1742+*/
1743+ tstart=start;
1744+ tend=end;
1745+ if (tstart == 0) {
1746+ tstart = 1;
1747+ }
1748+ if (tend == 0) {
1749+ tend = ddates.length;
1750+ }
1751+ if (tstart > tend) {
1752+ tend = ddates.length;
1753+ tstart = 1;
1754+ var div = document.getElementById("error");
1755+ div.textContent = "Invalid date Range: Start > End";
1756+ }
1757+ else {
1758+ var div = document.getElementById("error");
1759+ div.textContent = "";
1760+ }
1761+ ddates = ddates.slice(tstart-1,tend);
1762+ return [ddates,tstart-1,tend];
1763+};
1764+
1765+function rownames() {
1766+ var rrow=[];
1767+ {% for row in test_run_names %}
1768+ rrow.push(['{{row}}']);
1769+ {% endfor %}
1770+ return rrow;
1771+};
1772+
1773+function casenames() {
1774+ var rrow=[];
1775+ {% for row, value in kpireport.items %}
1776+ rrow.push(['{{row}}', '{{value.units}}']);
1777+ {% endfor %}
1778+ return rrow;
1779+};
1780+
1781+function goalaxis(tstart, tend, datelocation) {
1782+ if (document.getElementById('imagecheck').checked == true) {
1783+ tstart = -1;
1784+ tend = -1;
1785+ for (var i=0; i<datelocation.length; i++){
1786+ if (datelocation[i].length > 0) {
1787+ if (tstart == -1) {
1788+ tstart = datelocation[i][1];
1789+ }
1790+ tend = datelocation[i][1];
1791+ }
1792+ }
1793+ }
1794+ var alldate = alldates(tstart, tend)[0];
1795+ var ret_array = [];
1796+ var goal_dates = [];
1797+ {% for tag, value in goaldates.items %}
1798+ goal_dates.push(['{{tag}}', '{{value.startdate}}', '{{value.enddate}}']);
1799+ ret_array.push(['{{tag}}',[], '{{value.value}}']);
1800+ {% endfor %}
1801+ ret = doublesort(goal_dates, ret_array, function(a,b) { return a[2] > b[2] } )
1802+ goal_dates = ret[0];
1803+ ret_array = ret[1];
1804+ for (var all=0;all<alldate.length;all++) {
1805+ for (var gl=0;gl<goal_dates.length;gl++) {
1806+ // if the date falls within the tag range...
1807+ // push the location and the tag
1808+ if (alldate[all][1] >= goal_dates[gl][1] && alldate[all][1] <= goal_dates[gl][2]) {
1809+ if (document.getElementById('imagecheck').checked == true) {
1810+ ret_array[gl][1].push([all, ret_array[gl][2]]);
1811+ }
1812+ else {
1813+ ret_array[gl][1].push([alldate[all][0], ret_array[gl][2]]);
1814+ }
1815+ }
1816+ }
1817+ }
1818+
1819+ // Make the dates overlap a little
1820+ var premx =-100;
1821+ var mxtag=1;
1822+ var around = false
1823+ for (var tag=0;tag<ret_array.length;tag++) {
1824+ var mn=9999999;
1825+ var mx =-100;
1826+ if (ret_array[tag][1].length > 0) {
1827+ for (var date=0;date<ret_array[tag][1].length;date++) {
1828+ if (ret_array[tag][1][date][0] < mn) {
1829+ mn =ret_array[tag][1][date][0];
1830+ }
1831+ if (ret_array[tag][1][date][0] > mx) {
1832+ mx =ret_array[tag][1][date][0];
1833+ }
1834+ }
1835+ if (mn != premx && around == true) {
1836+ ret_array[tag][1].push([mn-1, ret_array[tag][2]]);
1837+ }
1838+
1839+ premx = mx;
1840+ mxtag = tag;
1841+ around = true
1842+ }
1843+ }
1844+ ret_array[mxtag][1].push([premx+1, ret_array[mxtag][2]]);
1845+ return ret_array;
1846+}
1847+
1848+function allgraph(ddates) {
1849+ image = ddates[0];
1850+ images = [];
1851+ for (var i=0;i<image.length;i++){
1852+ images.push(image[i][1]);
1853+ }
1854+ tstart = ddates[1];
1855+ tend = ddates[2];
1856+ axis = [];
1857+ if (document.getElementById('imagecheck').checked == true) {
1858+ var treportpass = {{pfreport.dpass}};
1859+ var reportpass = [];
1860+ imagelist = {{pfreport.imagelist|safe}};
1861+ count = 0;
1862+ for (var i=0;i<treportpass.length;i++) {
1863+ if (contains(images, imagelist[i], false) && (imagelist[i] != '')) {
1864+ reportpass[count] = treportpass[i][1];
1865+ axis.push([count, imagelist[i]]);
1866+ count++;
1867+ }
1868+ }
1869+ var zipper = [];
1870+ for (var c=0; c<reportpass.length;c++) {
1871+ zipper.push(c);
1872+ }
1873+ reportpass = zip(zipper, reportpass);
1874+ var treportfail = {{pfreport.dfail}};
1875+ var reportfail = [];
1876+ count = 0;
1877+ for (var i=0;i<treportfail.length;i++) {
1878+ if (contains(images, imagelist[i], false) && (imagelist[i] != '')) {
1879+ reportfail[count] = treportfail[i][1];
1880+ count++;
1881+ }
1882+ }
1883+ reportfail = zip(zipper, reportfail);
1884+ }
1885+ else {
1886+ reportpass = {{pfreport.dpass}};
1887+ reportpass = reportpass.slice(tstart, tend);
1888+ reportfail = {{pfreport.dfail}};
1889+ reportfail = reportfail.slice(tstart, tend);
1890+ axis = ddates[0];
1891+ }
1892+ var reporttotalfails = 0;
1893+ for (var i=0; i < reportfail.length; i++) {
1894+ reporttotalfails = reporttotalfails + reportfail[i][1];
1895+ }
1896+ var reporttotalpasses = 0;
1897+ for (var i=0; i < reportpass.length; i++) {
1898+ reporttotalpasses = reporttotalpasses + reportpass[i][1];
1899+ }
1900+ total = reporttotalpasses+reporttotalfails;
1901+ var reportpercentpasses = parseInt((reporttotalpasses / total) *100);
1902+ var reportpercentfails = parseInt((reporttotalfails / total) *100);
1903+ var show = (reportfail.length == 1);
1904+ var ddata = [
1905+ {
1906+ 'label': '<br><big><b>Fail: ' + reporttotalfails + '</b><br>' + reportpercentfails + '% failed.</big><br>',
1907+ 'data': reportfail,
1908+ 'color': '#FF0000',
1909+ 'lines': { show: true },
1910+ 'points': { show: show },
1911+ },
1912+ {
1913+ 'label': '<br><big><b>Pass: ' + reporttotalpasses + '</b><br>' + reportpercentpasses + '% passed.</big><br>',
1914+ 'data': reportpass,
1915+ 'color': '#00FF00',
1916+ 'lines': { show: true },
1917+ 'points': { show: show },
1918+ },
1919+ ];
1920+ return [ddata, axis];
1921+};
1922+
1923+function adjustvalues(measures, i, target, flr, wgt) {
1924+ var rvrs = flr > target; // little numbers are better
1925+ var range = Math.abs(target-flr)
1926+ var measures_copy = []
1927+ for (var i=0;i<measures.length;i++)
1928+ measures_copy[i] = measures[i];
1929+ for (var j=0;j<measures.length;j++) {
1930+ if (measures[j] == null) {
1931+ measures_copy[j]=null;
1932+ }
1933+ else if (rvrs == true && measures[j] > flr) {
1934+ measures_copy[j] = 0;
1935+ }
1936+ else if (rvrs == true && measures[j] < target) {
1937+ measures_copy[j] = 100;
1938+ }
1939+ else if (rvrs == false && measures[j] > target) {
1940+ measures_copy[j] = 100;
1941+ }
1942+ else if (rvrs == false && measures[j] < flr) {
1943+ measures_copy[j] = 0;
1944+ }
1945+ else {
1946+ adjustednumber = Math.abs(measures[j]-flr);
1947+ measures_copy[j]=Math.ceil((adjustednumber/range)*100);
1948+ }
1949+ }
1950+ return measures_copy;
1951+}
1952+
1953+function averagevals(measures){
1954+ function average_val(arr, start, end, startval, endval) {
1955+ if ((start != end) && (start != end-1)) {
1956+ if (startval == null) {
1957+ for (var i=start+1; i < end; i++){
1958+ arr[i] = null;
1959+ }
1960+ }
1961+ else {
1962+ var points = end - start;
1963+ var range = endval - startval;
1964+ var change = range / points;
1965+ var count = 0;
1966+ for (var i=start+1; i < end; i++){
1967+ count++;
1968+ if (i == 0) {
1969+ arr[i] = parseFloat(startval)+change;
1970+ }
1971+ else {
1972+ arr[i] = parseFloat(arr[i-1])+change;
1973+ }
1974+ }
1975+ }
1976+ }
1977+ return arr;
1978+ };
1979+
1980+ var startval = 0; // value at start
1981+ var endval = 0; // value at end
1982+ var start = -1; // location
1983+ var end = -1; // location
1984+ for (var num = 0; num < measures.length; num++) {
1985+ if (measures[num] == -1) {
1986+ if (num == measures.length-1){
1987+ end = num+1;
1988+ startval = null;
1989+ measures = average_val(measures, start, end, startval, endval);
1990+ }
1991+ }
1992+ else { //value != -1
1993+ // first num you find, make all -1 before = val
1994+ if (startval == 0 && endval == 0) {
1995+ startval = null;
1996+ }
1997+ end = num;
1998+ endval = parseFloat(measures[num]);
1999+ measures = average_val(measures, start, end, startval, endval);
2000+ start = num;
2001+ startval = parseFloat(measures[num]);
2002+ }
2003+ }
2004+ return measures;
2005+}
2006+
2007+function kpigraph(pts, tests) {
2008+ var tstart = -1;
2009+ var tend = -1;
2010+ var ddata = [];
2011+ var weighteddata = [];
2012+ var sumweights = [];
2013+ var image = pts[0];
2014+ var images = [];
2015+ var label = [];
2016+ var dates = [];
2017+ for (var i=0;i<image.length;i++){
2018+ images.push(image[i][1]);
2019+ }
2020+ adjusted = document.getElementById("adjustedcheck").checked;
2021+ weighted = document.getElementById("weightedcheck").checked;
2022+ var i=-1;
2023+ var axis = [];
2024+ var needaxis = true;
2025+ var init = false;
2026+ {% for test,value in kpireport.items %}
2027+ i++;
2028+ var tgt;
2029+ var gl;
2030+ var wgt;
2031+ var flr;
2032+ if ('{{value.target}}'=='None') tgt = 0;
2033+ else tgt= {{value.target}};
2034+ if ('{{value.goal}}'=='None') gl = 0;
2035+ else gl= {{value.goal}};
2036+ if ('{{value.weight}}'=='None') wgt = 0;
2037+ else wgt= {{value.weight}};
2038+ if ('{{value.flr}}'=='None') flr = 0;
2039+ else flr= {{value.flr}};
2040+
2041+ glo_casedata[i][0] = tgt;
2042+ glo_casedata[i][1] = flr;
2043+ glo_casedata[i][2] = wgt;
2044+ glo_casedata[i][3] = gl;
2045+ var measures =[];
2046+ if (axis.length > 0) {
2047+ needaxis=false;
2048+ }
2049+ // its one of the selected test cases!
2050+ if (contains(tests,"{{test}}", true)) {
2051+ if (document.getElementById('imagecheck').checked == true) {
2052+ imagelist = {{pfreport.imagelist|safe}};
2053+ var tmeasures= new Array{{value.measurement|safe}};
2054+ count = 0;
2055+ for (var k=0;k<tmeasures.length;k++) {
2056+ if (contains(images, imagelist[k], false) && (imagelist[k] != '')) {
2057+ measures[count] = tmeasures[k];
2058+ if (needaxis == true) {
2059+ tstart = 0;
2060+ tend = count+1;
2061+ axis.push([count, imagelist[k]]);
2062+ }
2063+ count++;
2064+ dates.push([count, k]);
2065+ }
2066+ else {
2067+ dates.push([]);
2068+ }
2069+ }
2070+ }
2071+ else {
2072+ // Grab the measurements::Add the (0,#,1,#) with a zip
2073+ measures = new Array{{value.measurement|safe}};
2074+ for (var q=0;q<measures.length;q++){
2075+ dates.push(true);
2076+ }
2077+ axis = pts[0];
2078+ tstart = pts[1];
2079+ tend = pts[2];
2080+ }
2081+ measures = averagevals(measures);
2082+ if (weighteddata.length == 0) {
2083+ for (var k=0; k<measures.length;k++) {
2084+ weighteddata.push(0);
2085+ sumweights.push(0);
2086+ }
2087+ }
2088+ // If they want to % adjusted values
2089+ goalmeasures = adjustvalues(measures, i, tgt, flr, wgt);
2090+ if (adjusted == true) {
2091+ measures = adjustvalues(measures, i, tgt, flr, wgt);
2092+ }
2093+ for (var k=0; k<measures.length;k++) {
2094+ if (wgt != 0) {
2095+ if (goalmeasures[k] == null) {
2096+ // ignore it, don't update sumweights
2097+ }
2098+ else {
2099+ weighteddata[k] += goalmeasures[k]*wgt;
2100+ sumweights[k] += wgt;
2101+ }
2102+ }
2103+ }
2104+ //Lets save the data to graph the weighted graph
2105+
2106+ var zipper = [];
2107+ for (var c=0; c<measures.length;c++) {
2108+ zipper.push(c);
2109+ }
2110+ measures = zip(zipper, measures);
2111+ measures = measures.slice(tstart, tend);
2112+ goalmeasures = zip(zipper, goalmeasures);
2113+ goalmeasures = goalmeasures.slice(tstart, tend);
2114+ var color = rainbow(5, i);
2115+ len = 0
2116+ for (var m=0;m<measures.length;m++){
2117+ if (measures[m][1] != null) {
2118+ len++;
2119+ }
2120+ }
2121+ var show = (len <= 1);
2122+ ddata.push(
2123+ {
2124+ 'label': "{{test}}",
2125+ 'data': measures,
2126+ 'color': color,
2127+ 'lines': { show: true },
2128+ 'points': { show: show },
2129+ }
2130+ );
2131+ label.push(false);
2132+ // Add the dashed goal line
2133+ if (adjusted == true && weighted == false) {
2134+ var kpitarget = [];
2135+ var count = -1;
2136+ for (var j=measures[0][0];j<=measures[measures.length-1][0];j++) {
2137+ count++;
2138+ if (gl != 0) {
2139+ if (measures[count][1] == null) {
2140+ kpitarget.push([j, null]);
2141+ }
2142+ else {
2143+ kpitarget.push([j, gl]);
2144+ }
2145+ }
2146+ }
2147+
2148+ ddata.push(
2149+ {
2150+ 'data': kpitarget,
2151+ 'color': color,
2152+ 'dashes': {show: true, },
2153+ 'opacity': .9,
2154+ 'points': {show: false },
2155+ }
2156+ );
2157+ label.push(false);
2158+ }
2159+ }
2160+ {% endfor %}
2161+
2162+ //check if they want the single weighted graph
2163+ if (weighted == true) {
2164+ if (document.getElementById('weightedonly').checked == true) {
2165+ ddata = [];
2166+ label = [];
2167+ }
2168+ for (var wt = 0; wt < weighteddata.length; wt++) {
2169+ weighteddata[wt]= ((weighteddata[wt]/100)/sumweights[wt])*100;
2170+ }
2171+ if (weighteddata.length > 0 && '{{goalname}}' != 'None') {
2172+ goaltarget = goalaxis(tstart, tend, dates);
2173+ for (var gl=0;gl<goaltarget.length;gl++) {
2174+ if (goaltarget[gl][1].length > 0) {
2175+ currentdates = goaltarget[gl][1]
2176+ ret = unzip(goaltarget[gl][1]);
2177+ ret[0] = ret[0].sort(function(a,b){ if(a > b){return 1}else{return -1} } );
2178+ goaltarget[gl][1] = zip(ret[0], ret[1]);
2179+ ddata.push({
2180+ 'label': goaltarget[gl][0],
2181+ 'data': goaltarget[gl][1],
2182+ //CAN YOU MAKE THIS OPACQUE?
2183+ 'color': "#000000",
2184+ 'dashes': {show: true, lineWidth: 2.5, dashLength: 5},
2185+ 'points': {show: false },
2186+ 'yaxis': 2,
2187+ });
2188+ label.push(true);
2189+ targetvalue = parseInt(currentdates[0][1])
2190+ mn = 9999
2191+ mx = -1
2192+ for (var val = 0; val<currentdates.length;val++) {
2193+ if (currentdates[val][0] < mn) mn = currentdates[val][0]
2194+ if (currentdates[val][0] > mx) mx = currentdates[val][0]
2195+ }
2196+ var goalvalues = weighteddata.slice(mn, mx)
2197+ var zipper = [];
2198+ for (var c=mn; c<mx;c++) {
2199+ zipper.push(c);
2200+ }
2201+ goalvalues = zip(zipper, goalvalues);
2202+ ddata.push(
2203+ {
2204+ 'data': goalvalues,
2205+ 'color': "#555555",
2206+ 'bars': { show: true, barWidth: 0.8, align: "center", fillColor: { colors: [ { opacity: 0.5 }, { opacity: 0.2 } ] }},
2207+ 'points': { show: false },
2208+ 'yaxis': 2,
2209+ 'threshold': [{below: targetvalue-5, color:'#990000'},{below: 101, color:'#009900'}, {below:targetvalue, color:'#999900'}],
2210+ }
2211+ );
2212+ }
2213+ }
2214+ }
2215+ else { // NO DATES GIVEN, JUST MAKE IT BLACK
2216+ var zipper = [];
2217+ for (var c=0; c<weighteddata.length;c++) {
2218+ zipper.push(c);
2219+ }
2220+ weighteddata = zip(zipper, weighteddata);
2221+ weighteddata = weighteddata.slice(tstart, tend);
2222+ ddata.push(
2223+ {
2224+ 'data': weighteddata,
2225+ 'color': "#555555",
2226+ 'bars': { show: true, barWidth: 0.8, align: "center", fillColor: { colors: [ { opacity: 0.4 }, { opacity: 0.1 } ] }},
2227+ 'points': { show: false },
2228+ 'yaxis': 2,
2229+ }
2230+ );
2231+ label.push(false);
2232+ }
2233+ }
2234+ return [ddata,axis,label];
2235+};
2236+
2237+function rowgraph(ddates, selectedrows){
2238+ var dates = ddates[0];
2239+ images = [];
2240+ for (var i=0;i<dates.length;i++){
2241+ images.push(dates[i][1]);
2242+ }
2243+ var tstart = ddates[1];
2244+ var tend = ddates[2];
2245+ //Slice dictionary as needed..?
2246+ var ddata = [];
2247+ var axis = [];
2248+ var needaxis = true;
2249+ totalrows = rownames().length;
2250+ var selectnames = [];
2251+ for (var i=0; i<selectedrows.length;i++) {
2252+ selectnames.push(selectedrows[i][0]);
2253+ }
2254+ var m=-1;
2255+ var n=-1;
2256+ {% for value, row in rowreport.items %}
2257+ var passes =[];
2258+ var fails =[];
2259+ if (axis.length > 0) {
2260+ needaxis=false;
2261+ }
2262+ if (contains(selectnames, '{{value}}', true)) {
2263+ m++;
2264+ n++;
2265+ // graph this row
2266+ if (document.getElementById('imagecheck').checked == true) {
2267+ imagelist = {{pfreport.imagelist|safe}};
2268+
2269+ var tpasses=new Array({{row.pass}});
2270+ var tfails=new Array({{row.fail}});
2271+ tpasses = tpasses[0];
2272+ tfails = tfails[0];
2273+
2274+ count = 0;
2275+ for (var i=0;i<tpasses.length;i++) {
2276+ if (contains(images, imagelist[i], false) && (imagelist[i] != '')) {
2277+ passes[count] = tpasses[i][1];
2278+ if (needaxis == true) {
2279+ axis.push([count, imagelist[i]]);
2280+ }
2281+ count++;
2282+ }
2283+ }
2284+ var zipper = [];
2285+ for (var c=0; c<passes.length;c++) {
2286+ zipper.push(c);
2287+ }
2288+ passes = zip(zipper, passes);
2289+
2290+ count = 0;
2291+ for (var i=0;i<tfails.length;i++) {
2292+ if (contains(images, imagelist[i], false) && (imagelist[i] != '')) {
2293+ fails[count] = tfails[i][1];
2294+ count++;
2295+ }
2296+ }
2297+ fails = zip(zipper, fails);
2298+ }
2299+ else {
2300+ passes=new Array({{row.pass}});
2301+ fails=new Array({{row.fail}});
2302+ passes = passes[0];
2303+ fails = fails[0];
2304+ axis = ddates[0];
2305+ fails = fails.slice(tstart, tend);
2306+ passes = passes.slice(tstart, tend);
2307+ }
2308+ //ddata it up
2309+ var failcolor = '';
2310+ var passcolor = '';
2311+ m = n%8;
2312+ if (m<4){
2313+ failcolor = '#'+(15-m).toString(16)+(15-m).toString(16)
2314+ +(m*4).toString(16)+(m*4).toString(16)
2315+ +'00';
2316+ passcolor = '#00'
2317+ +(15-m).toString(16)+(15-m).toString(16)
2318+ +(m*4).toString(16)+(m*4).toString(16);
2319+ }
2320+ else {
2321+ m = n%4;
2322+ failcolor = '#'+(15-m).toString(16)+(15-m).toString(16)
2323+ +'00'
2324+ +(m*4).toString(16)+(m*4).toString(16);
2325+ passcolor = '#'+(m*4).toString(16)+(m*4).toString(16)
2326+ +(15-m).toString(16)+(15-m).toString(16)
2327+ +'00';
2328+ }
2329+ var show = (fails.length == 1);
2330+ ddata.push(
2331+ {
2332+ 'label': '{{value}}'+' Fails',
2333+ 'data': fails,
2334+ 'color': failcolor,
2335+ 'lines': { show: true },
2336+ 'points': { show: show },
2337+ }
2338+ );
2339+ ddata.push(
2340+ {
2341+ 'label': '{{value}}'+' Passes',
2342+ 'data': passes,
2343+ 'color': passcolor,
2344+ 'lines': { show: true },
2345+ 'points': { show: show },
2346+ }
2347+ );
2348+ }
2349+ {% endfor %}
2350+ return [ddata,axis];
2351+};
2352+
2353+function exposeList() {
2354+ var status = document.getElementById('cbChoices').checked;
2355+ var passstatus = document.getElementById('passfailcheck').checked;
2356+ if (passstatus == true) {
2357+ document.getElementById('testcases').style.display = 'none';
2358+ document.getElementById('kpi').style.display = 'none';
2359+ document.getElementById('cbChoices').style.display = "block";
2360+ document.getElementById('kpiinfo').style.display = "none";
2361+ if (status == true) {
2362+ document.getElementById('rows').style.display = "block";
2363+ }
2364+ else {
2365+ document.getElementById('rows').style.display = 'none';
2366+ }
2367+ }
2368+ else {
2369+ document.getElementById('rows').style.display = 'none';
2370+ document.getElementById('testcases').style.display = "block";
2371+ document.getElementById('cbChoices').style.display = "none";
2372+ document.getElementById('kpi').style.display = 'block';
2373+ document.getElementById('kpiinfo').style.display = "inline";
2374+ if (document.getElementById('weightedcheck').checked == true) {
2375+ document.getElementById('weightedinfo').style.display = 'inline';
2376+ }
2377+ else {
2378+ document.getElementById('weightedinfo').style.display = 'none';
2379+ }
2380+ }
2381+ draw();
2382+}
2383+
2384+function exposeHelp() {
2385+ var status = document.getElementById('cbHelp').checked;
2386+ if (status == true) {
2387+ document.getElementById('help').style.display = 'block';
2388+ }
2389+ else {
2390+ document.getElementById('help').style.display = 'none';
2391+ }
2392+}
2393+
2394+function changeType() {
2395+ var status = document.getElementById('imagecheck').checked;
2396+ if (status == true) {
2397+ document.getElementById('startDate').style.display = 'none';
2398+ document.getElementById('endDate').style.display = 'none';
2399+ document.getElementById('startImage').style.display = 'inline';
2400+ document.getElementById('endImage').style.display = 'inline';
2401+ }
2402+ else {
2403+ document.getElementById('startDate').style.display = 'inline';
2404+ document.getElementById('endDate').style.display = 'inline';
2405+ document.getElementById('startImage').style.display = 'none';
2406+ document.getElementById('endImage').style.display = 'none';
2407+ }
2408+ draw();
2409+}
2410+
2411+function loadtext() {
2412+ glo_selectedkpi=this.id.match(/\d+/);
2413+ document.getElementById('kpilabel').innerHTML = this.value;
2414+ document.getElementById('kpitarget').value = glo_casedata[glo_selectedkpi][0];
2415+ document.getElementById('kpifloor').value = glo_casedata[glo_selectedkpi][1];
2416+ document.getElementById('kpiweight').value = glo_casedata[glo_selectedkpi][2];
2417+ document.getElementById('kpigoal').value = glo_casedata[glo_selectedkpi][3];
2418+ draw();
2419+}
2420+
2421+function updateURL(casename, rowname) {
2422+ var url = baseurl;
2423+ url += '?sets={{setname}}&var=';
2424+ for (var i=0;i<=6;i++){
2425+ if ($("input").get(i).checked == true){
2426+ url += i+",";
2427+ }
2428+ }
2429+ url += '&kpis=';
2430+ for (var i=0; i < casename.length; i++) {
2431+ if (document.getElementById("case"+i) != null) {
2432+ if (document.getElementById("case"+i).checked == true) {
2433+ url += casename[i][0]+",";
2434+ }
2435+ }
2436+ }
2437+ url += '&rows=';
2438+ for (var i=0; i < rowname.length; i++) {
2439+ if (document.getElementById("row"+i) != null) {
2440+ if (document.getElementById("row"+i).checked == true) {
2441+ url += rowname[i][0]+",";
2442+ }
2443+ }
2444+ }
2445+ url += '&select=';
2446+ for (var i=0;i<=3;i++){
2447+ url += $("select").get(i).selectedIndex+","
2448+ }
2449+ $("#permalink")[0].value = url;
2450+
2451+}
2452+
2453+function draw() {
2454+ var ddata = [];
2455+ var val = [];
2456+ var labels = [];
2457+ if (document.getElementById('imagecheck').checked == true) {
2458+ var startd=document.getElementById("startImage");
2459+ var endd=document.getElementById("endImage");
2460+ var start = startd.options[startd.selectedIndex].index;
2461+ var end = endd.options[endd.selectedIndex].index;
2462+ var axis = allimages(start,end);
2463+ }
2464+ else {
2465+ var startd=document.getElementById("startDate");
2466+ var endd=document.getElementById("endDate");
2467+ var start = startd.options[startd.selectedIndex].index;
2468+ var end = endd.options[endd.selectedIndex].index;
2469+ var axis = alldates(start,end);
2470+ }
2471+ var rowstatus = document.getElementById('cbChoices').checked;
2472+ var passfailstatus = document.getElementById('passfailcheck').checked;
2473+ var selectedrows = [];
2474+ var kpi = false;
2475+ var casename = casenames();
2476+ var rowname = rownames();
2477+ if (passfailstatus == false) {
2478+ for (var i=0; i < casename.length; i++) {
2479+ if (document.getElementById("case"+i) != null) {
2480+ if (document.getElementById("case"+i).checked == true) {
2481+ selectedrows.push([casename[i][0], i]);
2482+ }
2483+ }
2484+ }
2485+ val = kpigraph(axis, selectedrows);
2486+ ddata = val[0];
2487+ axis = val[1];
2488+ labels = val[2];
2489+ }
2490+ else {
2491+ if (rowstatus == true) {
2492+ selectedrows = [];
2493+ for (var i=0; i < rowname.length; i++) {
2494+ if (document.getElementById("row"+i).checked == true) {
2495+ selectedrows.push([rowname[i], i]);
2496+ }
2497+ }
2498+ if (selectedrows.length != 0) {
2499+ val = rowgraph(axis, selectedrows);
2500+ ddata = val[0];
2501+ axis = val[1];
2502+ }
2503+ else {
2504+ val = allgraph(axis);
2505+ ddata = val[0];
2506+ axis = val[1];
2507+ }
2508+ }
2509+ else {
2510+ val = allgraph(axis);
2511+ ddata = val[0];
2512+ axis = val[1];
2513+ }
2514+ }
2515+ updateURL(casename,rowname);
2516+ // Make the dates not look terrible..
2517+ var goal = 10;
2518+ axispoints = axis;
2519+ if (axispoints.length > goal) {
2520+ axispoints = pare(axispoints, goal);
2521+ }
2522+
2523+ if (document.getElementById('adjustedcheck').checked == true && document.getElementById('kpicheck').checked == true) {
2524+ p = $.plot($("#placeholder"), ddata, {
2525+ xaxis: {
2526+ ticks: axispoints,
2527+ },
2528+ yaxis: {
2529+ min: 0,
2530+ max: 100,
2531+ autoscaleMargin: false,
2532+ axisLabel: 'Percent',
2533+ },
2534+ y2axis: {
2535+ min: 0,
2536+ max: 100,
2537+ autoscaleMargin: false,
2538+ axisLabel: 'Weighted Percent',
2539+ },
2540+ legend:{
2541+ container: $("#labeler")
2542+ },
2543+ });
2544+ }
2545+ else if (document.getElementById('kpicheck').checked == true){
2546+ p = $.plot($("#placeholder"), ddata, {
2547+ xaxis: {
2548+ ticks: axispoints,
2549+ },
2550+ yaxis: {
2551+ axisLabel: 'Units',
2552+ },
2553+ y2axis: {
2554+ min: 0,
2555+ max: 100,
2556+ autoscaleMargin: false,
2557+ axisLabel: 'Weighted Percent',
2558+ },
2559+ legend:{
2560+ container: $("#labeler")
2561+ },
2562+ });
2563+ }
2564+ else {
2565+ p = $.plot($("#placeholder"), ddata, {
2566+ xaxis: {
2567+ ticks: axispoints,
2568+ },
2569+ yaxis: {
2570+ axisLabel: 'Units',
2571+ },
2572+ y2axis: {
2573+ min: 0,
2574+ max: 100,
2575+ autoscaleMargin: false,
2576+ axisLabel: 'Weighted Percent',
2577+ },
2578+ legend:{
2579+ container: $("#labeler")
2580+ },
2581+ });
2582+ }
2583+ // Add the labels to the weighted graph
2584+ var count = -1;
2585+ if (p.getData().length > 0) {
2586+ $.each(p.getData(), function(i, row){
2587+ if (row.label) {
2588+ count++;
2589+ if (labels[count] == true) {
2590+ tlabel = row.label;
2591+ row.label = '';
2592+ if (row.data.length > 1) {
2593+ var o = p.pointOffset({x: row.data[0][0], y: row.data[0][1], yaxis: 2});
2594+ $('<div class="data-point-label">' +'<b><font size=3>'+ tlabel+ '</font></b>' + '</div>').css( {
2595+ position: 'absolute',
2596+ left: o.left + 2,
2597+ top: o.top - 23,
2598+ display: 'none',
2599+ escapeHTML: 'false',
2600+ border: '1.5px solid #aaaaaa',
2601+ background: '#aaccee',
2602+ opacity: '0.9',
2603+ }).appendTo(p.getPlaceholder()).fadeIn(0);
2604+ }
2605+ }
2606+ }
2607+ });
2608+ p.setupGrid();
2609+ }
2610+};
2611+
2612+var glo_selectedkpi = 0;
2613+var glo_casedata = [];
2614+
2615+$(window).resize(function() {
2616+ draw();
2617+});
2618+
2619+$(document).ready(function(){
2620+ var select = document.getElementById("startDate");
2621+ var select2 = document.getElementById("endDate");
2622+ var options = alldates(0,0)[0];
2623+// url_options
2624+ for(var i = 0; i < options.length; i++) {
2625+ var opt = options[i][1];
2626+ var el = document.createElement("option");
2627+ var el2 = document.createElement("option");
2628+ el.textContent = opt;
2629+ el.value = opt;
2630+ el2.textContent = opt;
2631+ el2.value = opt;
2632+ select.appendChild(el);
2633+ select2.appendChild(el2);
2634+ }
2635+
2636+ select = document.getElementById("startImage");
2637+ select2 = document.getElementById("endImage");
2638+ options = allimages(0,0)[0];
2639+ for(var i = 0; i < options.length; i++) {
2640+ var opt = options[i][1];
2641+ var el = document.createElement("option");
2642+ var el2 = document.createElement("option");
2643+ el.textContent = opt;
2644+ el.value = opt;
2645+ el2.textContent = opt;
2646+ el2.value = opt;
2647+ select.appendChild(el);
2648+ select2.appendChild(el2);
2649+ }
2650+ select = document.getElementById("rows");
2651+ options = rownames();
2652+ rows=url_options.split("&rows=")[1];
2653+ if (rows != null) {
2654+ if (rows.indexOf("&") != -1) {rows=rows.substr(0,rows.indexOf("&")).split(",");}
2655+ else {rows = rows.split(",");}
2656+ }
2657+ else {
2658+ rows = [];
2659+ }
2660+ for (var r=0;r<rows.length;r++) {rows[r] = trim(rows[r]);}
2661+ for(var i = 0; i < options.length; i++) {
2662+ var opt = options[i];
2663+ var el = document.createElement("input");
2664+ el.type = "checkbox";
2665+ el.onclick=draw;
2666+ el.id = "row"+i;
2667+ el.name = "row"+i;
2668+ var label = document.createElement("label");
2669+ label.htmlFor = opt;
2670+ if (contains(rows, opt, false)) {el.checked = true;}
2671+ else {el.checked = false;}
2672+ label.appendChild(document.createTextNode(opt));
2673+ var br = document.createElement("br");
2674+ label.appendChild(br);
2675+ select.appendChild(el);
2676+ select.appendChild(label);
2677+ }
2678+
2679+ select = document.getElementById("testcases");
2680+ options = casenames();
2681+ rows=url_options.split("&kpis=")[1];
2682+ if (rows != null) {
2683+ if (rows.indexOf("&") != -1) {rows=rows.substr(0,rows.indexOf("&")).split(",");}
2684+ else {rows = rows.split(",");}
2685+ }
2686+ else {
2687+ rows = [];
2688+ }
2689+ for (var r=0;r<rows.length;r++) {rows[r] = trim(rows[r]);}
2690+ for(var i = 0; i < options.length; i++) {
2691+ glo_casedata.push([0, 0, 1, 100]);
2692+ }
2693+ for(var i = 0; i < options.length; i++) {
2694+ glo_selectedkpi=i;
2695+ var opt = options[i][0];
2696+ var units = options[i][1];
2697+ var el = document.createElement("input");
2698+ el.type = "checkbox";
2699+ el.onclick=loadtext;
2700+ el.id = "case"+i;
2701+ el.name = "case"+i;
2702+ el.value = opt
2703+ if (contains(rows, opt, false)) {el.checked = true;}
2704+ else {el.checked = false;}
2705+ var label = document.createElement("input");
2706+ label.type = "button";
2707+ label.value = opt;
2708+ label.id = "button"+i;
2709+ label.name = "button"+i;
2710+ label.onclick= loadtext;
2711+ var unit = document.createElement("input");
2712+ unit.type = "button";
2713+ unit.onclick = draw;
2714+ unit.value = units;
2715+ unit.id = "unit"+i;
2716+ label.name = "unit"+i;
2717+ var br = document.createElement("br");
2718+ select.appendChild(el);
2719+ select.appendChild(label);
2720+ select.appendChild(unit);
2721+ select.appendChild(br);
2722+ label.click();
2723+ }
2724+
2725+ rows=url_options.split("&var=")[1];
2726+ if (rows != null) {
2727+ if (rows.indexOf("&") != -1) {rows=rows.substr(0,rows.indexOf("&")).split(",");}
2728+ else {rows = rows.split(",");}
2729+ }
2730+ else {
2731+ rows = [1,2];
2732+ }
2733+ for (var i=0;i<rows.length;i++){
2734+ if (rows[i] != ''){
2735+ $("input").get(parseInt(rows[i])).checked = true
2736+ }
2737+ }
2738+
2739+ rows=url_options.split("&select=")[1];
2740+ var sdata = 0;
2741+ var simage = 0;
2742+ if (rows != null) {
2743+ if (rows.indexOf("&") != -1) {rows=rows.substr(0,rows.indexOf("&")).split(",");}
2744+ else {rows = rows.split(",");}
2745+ }
2746+ else {
2747+ if ($("select").get(0).length>7){ sdata = 7;}
2748+ else{ sdata=$("select").get(0).length-1;}
2749+ if ($("select").get(2).length>7){ simage = 7;}
2750+ else{ simage=$("select").get(2).length-1;}
2751+ rows = [$("select").get(0).length-sdata,$("select").get(1).length-1,$("select").get(2).length-simage,$("select").get(3).length-1];
2752+ }
2753+ for (var i=0;i<Math.min(rows.length, 4);i++){
2754+ $("select").get(i).selectedIndex = parseInt(rows[i]);
2755+ }
2756+ changeType();
2757+ exposeList();
2758+ goal_dates = [];
2759+ {% for tag, value in goaldates.items %}
2760+ goal_dates.push(['{{tag}}', '{{value.startdate}}', '{{value.enddate}}', '{{value.value}}']);
2761+ {% endfor %}
2762+ goal_dates = goal_dates.sort(function(a,b) { if(a[1] > b[1]){return 1}else{return -1} } );
2763+ goalElement = document.getElementById("goal_dates");
2764+ if (goal_dates.length > 0) {
2765+ goalElement.style.display = 'inline';
2766+ document.getElementById('goal_dates_label').style.display = 'inline';
2767+ inputString = '<table border="0"><tr><td><b>Tag&emsp;</b></td><td><b>Target Date&emsp;</b></td><td><b>Goal</b></td></tr>';
2768+ for (var tag=0;tag<goal_dates.length;tag++){
2769+ inputString += '<tr><td>'+goal_dates[tag][0]+'</td>';
2770+ inputString += '<td>'+goal_dates[tag][1]+'</td>';
2771+ inputString += '<td>'+goal_dates[tag][3]+'%</td></tr>';
2772+ }
2773+ inputString += '</table>';
2774+ goalElement.innerHTML = inputString;
2775+ }
2776+ else {
2777+ goalElement.style.display = 'none';
2778+ document.getElementById('goal_dates_label').style.display = 'none';
2779+ }
2780+ glo_selectedkpi=0;
2781+ draw();
2782+});
2783+
2784+</script>
2785+
2786+<h2>Detailed results:</h2>
2787+>>>>>>> MERGE-SOURCE
2788 <table id="outer-table">
2789 <tr>
2790 <td>
2791
2792=== modified file 'dashboard_app/templates/dashboard_app/image-reports.html'
2793--- dashboard_app/templates/dashboard_app/image-reports.html 2012-07-18 05:20:11 +0000
2794+++ dashboard_app/templates/dashboard_app/image-reports.html 2013-07-17 17:43:24 +0000
2795@@ -9,10 +9,12 @@
2796 {% for image in imageset.images %}
2797 <li>
2798 {% if image.bundle_count %}
2799- <a href="{{ image.link }}">{{ image.name }}</a>
2800- ({{ image.bundle_count }} results)
2801+ <a href="{{ image.link }}">{{ image.imagename }}</a>
2802+ ({{ image.bundle_count }} results)
2803+ (<font color="Blue">{{image.grandtotal}} Totalcases;</font> <font color="green"> {{ image.passes }} Passes; </font><font color="red">{{image.fails}} Fails</font>)
2804 {% else %}
2805- {{ image.name }} ({{ image.bundle_count }} results)
2806+ {{ imagename }} ({{ image.bundle_count }} results)
2807+ (<font color="Blue">{{image.grandtotal}} Totalcases;</font> <font color="green"> {{ image.passes }} Passes; </font><font color="red">{{image.fails}} Fails</font>)
2808 {% endif %}
2809 </li>
2810 {% endfor %}
2811
2812=== modified file 'dashboard_app/views/images.py'
2813--- dashboard_app/views/images.py 2013-07-03 11:42:45 +0000
2814+++ dashboard_app/views/images.py 2013-07-17 17:43:24 +0000
2815@@ -16,11 +16,14 @@
2816 # You should have received a copy of the GNU Affero General Public License
2817 # along with Launch Control. If not, see <http://www.gnu.org/licenses/>.
2818
2819-
2820+import json
2821+import re
2822+from django.db import models
2823 from django.http import HttpResponseRedirect
2824 from django.shortcuts import get_object_or_404, render_to_response
2825 from django.template import RequestContext
2826 from django.views.decorators.http import require_POST
2827+from datetime import date
2828
2829 from lava_server.bread_crumbs import (
2830 BreadCrumb,
2831@@ -29,32 +32,302 @@
2832
2833 from dashboard_app.filters import evaluate_filter
2834 from dashboard_app.models import (
2835+ Bundle,
2836 LaunchpadBug,
2837 Image,
2838 ImageSet,
2839 TestRun,
2840 )
2841 from dashboard_app.views import index
2842+<<<<<<< TREE
2843 import json
2844+=======
2845+
2846+def pfdict_insert(dic, match, tag):
2847+ if tag not in dic:
2848+ dic[tag] = {'pass': 0, 'fail': 0, 'test_names': [], 'image': '', 'allpass':0, 'allfail':0}
2849+ dic[tag]['pass'] += match['count_pass']
2850+ dic[tag]['fail'] += match['count_fail']
2851+ dic[tag]['test_names']=match['test_names']
2852+ dic[tag]['allpass']=match['allpass']
2853+ dic[tag]['allfail']=match['allfail']
2854+ if (str(match['image']) != ''):
2855+ dic[tag]['image'] = match['image']
2856+ return dic
2857+
2858+def kpidict_insert(dic, aMatch, tag):
2859+ for match in aMatch:
2860+ testname = match
2861+ if testname not in dic:
2862+ dic[testname] = {'date': [],'measurement': [], 'units': '',
2863+ 'target': '', 'floor': '', 'goal': '', 'weight': ''}
2864+ dic[testname]['date'].append(tag)
2865+ if (aMatch[match]['measurement']==None):
2866+ dic[testname]['measurement'].append(-1)
2867+ else:
2868+ dic[testname]['measurement'].append(str(aMatch[match]['measurement']))
2869+ if (dic[testname]['units'] == ''):
2870+ dic[testname]['units']=aMatch[match]['units']
2871+ dic[testname]['target']=aMatch[match]['target']
2872+ dic[testname]['floor']=aMatch[match]['floor']
2873+ dic[testname]['goal']=aMatch[match]['goal']
2874+ dic[testname]['weight']=aMatch[match]['weight']
2875+ return dic
2876+
2877+def rowdict_insert(dic, rowMatch, tag):
2878+ for match in rowMatch:
2879+ if tag not in dic:
2880+ dic[tag] = {}
2881+ if match not in dic[tag]:
2882+ dic[tag][match] = {'pass':0,'fail':0}
2883+ for testname in dic[tag]:
2884+ try:
2885+ dic[tag][testname]['pass'] = rowMatch[testname]['pass']
2886+ except KeyError:
2887+ dic[tag][testname]['pass'] = 0
2888+ try:
2889+ dic[tag][testname]['fail'] = rowMatch[testname]['fail']
2890+ except KeyError:
2891+ dic[tag][testname]['fail'] = 0
2892+ return dic
2893+
2894+
2895+def generate_dicts(allmatches, getcases):
2896+ # Lets only go through the database once..
2897+ kpidict = {}
2898+ detaildict = {}
2899+ rowdict = {}
2900+ dates = []
2901+ images = []
2902+ tagdic = {}
2903+ for matches in allmatches:
2904+ tag = ""
2905+ for match in matches.test_runs:
2906+ #Only keep latest test for same image
2907+ if (tag == ""):
2908+ tag = match.analyzer_assigned_date
2909+ tag = date(tag.year,tag.month,tag.day)
2910+ #tag = matches.tag
2911+ if tag not in dates:
2912+ dates.append(tag)
2913+ if tag not in tagdic:
2914+ tagdic[tag]=[]
2915+ tagdic[tag].append(match)
2916+ for tagname in tagdic:
2917+ aMatch = {'count_pass': 0,'count_fail': 0, 'test_names': [], 'image': '', 'allpass': 0, 'allfail': 0}
2918+ rowMatch = {}
2919+ for ddate in tagdic[tagname]:
2920+ aMatch['count_pass'] += ddate.denormalization.count_pass
2921+ aMatch['count_fail'] += ddate.denormalization.count_fail
2922+ aMatch['test_names'].append(ddate)
2923+ aMatch['image'] = ddate.sw_image_desc
2924+ aMatch['allpass'] = aMatch['count_pass']
2925+ aMatch['allfail'] = aMatch['count_fail']
2926+ aMatch['count_pass'] = float(aMatch['count_pass']) / float(len(tagdic[tagname]))
2927+ aMatch['count_fail'] = float(aMatch['count_fail']) / float(len(tagdic[tagname]))
2928+ detaildict = pfdict_insert(detaildict, aMatch, tagname)
2929+
2930+ if (match.sw_image_desc not in images) and not (match.sw_image_desc == ''):
2931+ images.append(match.sw_image_desc)
2932+ if (getcases == 1):
2933+ aMatch = {}
2934+ rowMatch = {}
2935+ for ddate in tagdic[tagname]:
2936+ if (ddate.test.test_id not in rowMatch):
2937+ rowMatch[ddate.test.test_id] = {'pass':0,'fail':0,'count':0}
2938+ denorm = ddate.denormalization
2939+ rowMatch[ddate.test.test_id]['fail'] += denorm.count_fail
2940+ rowMatch[ddate.test.test_id]['pass'] += denorm.count_pass
2941+ if (denorm.count_fail == 0 and denorm.count_pass == 0):
2942+ pass
2943+ else:
2944+ rowMatch[ddate.test.test_id]['count'] += 1
2945+ testname = ddate.test # <-- TESTNAME
2946+ if (str(testname) != 'lava'):
2947+ # Only take measuresments with a . in them, current lava implementation
2948+ # measurements are stored as floats
2949+ for test_case in ddate.test_results.filter(measurement__icontains='.'):
2950+ test_case_name = test_case.test_case
2951+ if (test_case.test_case not in aMatch):
2952+ aMatch[test_case_name] = {'date': '','measurement': None, 'units': '','target': '',
2953+ 'floor': '', 'goal': '', 'weight': '', 'count': 0}
2954+ if (aMatch[test_case_name]['measurement'] == None and test_case.measurement != None):
2955+ aMatch[test_case_name]['measurement'] = 0
2956+ if (test_case.measurement == None):
2957+ test_case.measurement = 0
2958+ if (aMatch[test_case_name] != None):
2959+ aMatch[test_case_name]['measurement'] += test_case.measurement
2960+ aMatch[test_case_name]['count'] += 1
2961+ aMatch[test_case_name]['date'] = tagname
2962+ aMatch[test_case_name]['units'] = test_case.units
2963+ aMatch[test_case_name]['target']=test_case.target
2964+ aMatch[test_case_name]['floor']=test_case.floor
2965+ aMatch[test_case_name]['goal']=test_case.goal
2966+ aMatch[test_case_name]['weight']=test_case.weight
2967+ for test in aMatch:
2968+ if (aMatch[test]['count'] == 0):
2969+ aMatch[test]['measurement'] = 0
2970+ else:
2971+ aMatch[test]['measurement'] = aMatch[test]['measurement'] / aMatch[test]['count']
2972+ kpidict = kpidict_insert(kpidict, aMatch, tagname)
2973+ for test in rowMatch:
2974+ if (rowMatch[test]['count'] == 0):
2975+ rowMatch[test]['pass'] = 0
2976+ rowMatch[test]['fail'] = 0
2977+ else:
2978+ rowMatch[test]['pass'] = float(rowMatch[test]['pass']) / float(rowMatch[test]['count'])
2979+ rowMatch[test]['fail'] = float(rowMatch[test]['fail']) / float(rowMatch[test]['count'])
2980+ rowdict = rowdict_insert(rowdict, rowMatch, tagname)
2981+ return kpidict, detaildict, rowdict, dates
2982+
2983+def report_for_kpigraph(counters, dates, images):
2984+ temp = counters.copy()
2985+ for key in counters:
2986+ found = 0;
2987+ for i in range(0, len(counters[key]['measurement'])):
2988+ if (counters[key]['measurement'][i] != -1):
2989+ found = 1
2990+ break
2991+ if (found == 0):
2992+ temp.pop(key)
2993+ else:
2994+ for curdate in dates:
2995+ if curdate not in temp[key]['date']:
2996+ temp[key]['date'].append(curdate)
2997+ temp[key]['measurement'].append(-1)
2998+ # So now we have a dictionary of the following format..
2999+ # caseid: {date:[] measurement:[], units:''}}
3000+
3001+ cases = []
3002+ dates = []
3003+ measures = []
3004+ returndict = {}
3005+ for key in temp:
3006+ returndict[key] = {'date': [],'measurement': [], 'units': '',
3007+ 'target': '', 'flr': '', 'goal': '', 'weight': ''}
3008+ dates = temp[key]['date']
3009+ measures = temp[key]['measurement']
3010+ if (len(dates)>1):
3011+ dates, measures = zip(*sorted(zip(dates, measures)))
3012+ else:
3013+ measures = str(measures).replace('[','(').replace(']',')')
3014+ dates = str(dates).replace('[','(').replace(']',')')
3015+ returndict[key]['date'] = dates
3016+ returndict[key]['measurement'] = measures
3017+ returndict[key]['units'] = temp[key]['units']
3018+ returndict[key]['target'] = temp[key]['target']
3019+ returndict[key]['flr'] = temp[key]['floor']
3020+ returndict[key]['goal'] = temp[key]['goal']
3021+ returndict[key]['weight'] = temp[key]['weight']
3022+ return returndict
3023+
3024+def report_for_detailgraph(detaildict):
3025+ report = {'ddates': [], 'dfail': [], 'dpass': [], 'imagelist': [], 'totalfails': 0,
3026+ 'totalpasses': 0, 'percentfails': 0, 'percentpasses': 0,
3027+ 'grandtotal': 0, 'images': [], 'unfail': [], 'unpass': []}
3028+ taglist = []
3029+ sortable = 0
3030+ for tag in detaildict:
3031+ taglist.append(tag)
3032+ taglist = sorted(taglist)
3033+ count = 0
3034+ for tag in taglist:
3035+ fail_count = detaildict[tag]['fail']
3036+ pass_count = detaildict[tag]['pass']
3037+ image = detaildict[tag]['image']
3038+ if (image not in report['images']) and not (image == ''):
3039+ report['images'].append(image)
3040+ report['imagelist'].append(str(detaildict[tag]['image']))
3041+ report['ddates'].append(unicode(tag))
3042+ report['dfail'].append([count, fail_count])
3043+ report['dpass'].append([count, pass_count])
3044+ report['unfail'].append([count, detaildict[tag]['allfail']])
3045+ report['unpass'].append([count, detaildict[tag]['allpass']])
3046+ report['totalfails'] += detaildict[tag]['allfail']
3047+ report['totalpasses'] += detaildict[tag]['allpass']
3048+ report['grandtotal'] += detaildict[tag]['allfail']+detaildict[tag]['allpass']
3049+ count += 1
3050+ if report['grandtotal'] == 0:
3051+ report['percentfails'] = 0
3052+ report['percentpasses'] = 0
3053+ else:
3054+ report['percentfails'] = report['totalfails'] * 100 / report['grandtotal']
3055+ report['percentpasses'] = report['totalpasses'] * 100 / report['grandtotal']
3056+ report['dfail'] = json.dumps(report['dfail'])
3057+ report['dpass'] = json.dumps(report['dpass'])
3058+ return report
3059+
3060+def report_for_row(rowdict):
3061+ tags = {}
3062+ counters = {}
3063+ count = 0
3064+ sortedtests = []
3065+ sorteddates = []
3066+ lava = False
3067+ for dates in rowdict:
3068+ sorteddates.append(dates)
3069+ for rows in rowdict[dates]:
3070+ if rows not in sortedtests:
3071+ if rows!='lava':
3072+ sortedtests.append(rows)
3073+ else:
3074+ lava = True
3075+ sorteddates = sorted(sorteddates)
3076+ sortedtests = sorted(sortedtests)
3077+ if (lava):
3078+ sortedtests.insert(0, 'lava')
3079+ count = 0
3080+ for dates in sorteddates:
3081+ for testname in rowdict[dates]:
3082+ if (testname not in counters):
3083+ counters[testname] = {'pass':[],'fail':[]}
3084+ counters[testname]['pass'].append([count, rowdict[dates][testname]['pass']])
3085+ counters[testname]['fail'].append([count, rowdict[dates][testname]['fail']])
3086+ count += 1
3087+ return counters
3088+
3089+def generate_reports(allmatches, getcases):
3090+ kpidict, detaildict, rowdict, dates = generate_dicts(allmatches, getcases)
3091+ detailreport = report_for_detailgraph(detaildict)
3092+ rowreport = report_for_row(rowdict)
3093+ if (getcases == 1):
3094+ kpireport = report_for_kpigraph(kpidict, dates, detailreport['images'])
3095+ else:
3096+ kpireport = {}
3097+ return kpireport, detailreport, rowreport
3098+>>>>>>> MERGE-SOURCE
3099
3100 @BreadCrumb("Image Reports", parent=index)
3101 def image_report_list(request):
3102+ kpireport = {}
3103+ pfreport = {}
3104+ testcases = []
3105 imagesets = ImageSet.objects.filter()
3106 imagesets_data = []
3107 for imageset in imagesets:
3108 images_data = []
3109+ bundle_ids = set()
3110+ count = 0
3111 for image in imageset.images.all():
3112 # Migration hack: Image.filter cannot be auto populated, so ignore
3113 # images that have not been migrated to filters for now.
3114 if image.filter:
3115 filter_data = image.filter.as_data()
3116+ matches = evaluate_filter(request.user, filter_data, prefetch_related=['launchpad_bugs'])
3117+ kpireport, pfreport, rowreport = generate_reports(matches, 0)
3118+ filter_data = image.filter.as_data()
3119 image_data = {
3120- 'name': image.name,
3121- 'bundle_count': evaluate_filter(request.user, filter_data).count(),
3122- 'link': image.name,
3123+ 'name': image.name+"?"+"sets="+imageset.name,
3124+ 'imagename': image.name,
3125+ 'imageset': imageset.name,
3126+ 'bundle_count': matches.count(),
3127+ 'link': image.name+"?"+"sets="+imageset.name,
3128+ 'passes': pfreport['totalpasses'],
3129+ 'grandtotal': pfreport['grandtotal'],
3130+ 'fails': pfreport['grandtotal']-pfreport['totalpasses'],
3131 }
3132 images_data.append(image_data)
3133- images_data.sort(key=lambda d:d['name'])
3134+ #Quick hack
3135 imageset_data = {
3136 'name': imageset.name,
3137 'images': images_data,
3138@@ -70,13 +343,36 @@
3139
3140 @BreadCrumb("{name}", parent=image_report_list, needs=['name'])
3141 def image_report_detail(request, name):
3142-
3143+ setname = request.GET.get("sets", None)
3144+ currentset = None
3145+ if (setname != None):
3146+ imagesets = ImageSet.objects.filter()
3147+ for imageset in imagesets:
3148+ if (setname == imageset.name):
3149+ currentset = imageset
3150+ break
3151+ else:
3152+ goaldates = {}
3153+ currentset = None
3154+ if (currentset != None):
3155+ if (currentset.relatedDates != None):
3156+ goaldates = currentset.relatedDates.return_dates()
3157+ currentset = currentset.relatedDates.name
3158+ else:
3159+ goaldates = {}
3160+ currentset = None
3161+ else:
3162+ goaldates = {}
3163 image = Image.objects.get(name=name)
3164 filter_data = image.filter.as_data()
3165+<<<<<<< TREE
3166 matches = evaluate_filter(request.user, filter_data, prefetch_related=['launchpad_bugs', 'test_results'])[:50]
3167
3168+=======
3169+ matches = evaluate_filter(request.user, filter_data, prefetch_related=['launchpad_bugs'])
3170+ kpireport, pfreport, rowreport = generate_reports(matches, 1)
3171+>>>>>>> MERGE-SOURCE
3172 build_number_to_cols = {}
3173-
3174 test_run_names = set()
3175
3176 for match in matches:
3177@@ -86,7 +382,7 @@
3178 if denorm.count_fail == 0:
3179 cls = 'present pass'
3180 else:
3181- cls = 'present fail'
3182+ cls = 'present fail'
3183 bug_ids = sorted([b.bug_id for b in test_run.launchpad_bugs.all()])
3184
3185 measurements = [{'measurement': str(item.measurement)}
3186@@ -121,6 +417,7 @@
3187
3188 table_data = {}
3189
3190+ table_data_dict = {}
3191 for test_run_name in test_run_names:
3192 row_data = []
3193 for col in cols:
3194@@ -131,16 +428,34 @@
3195 cls='missing',
3196 )
3197 row_data.append(test_run_data)
3198+<<<<<<< TREE
3199 table_data[test_run_name] = row_data
3200+=======
3201+ table_data.append(row_data)
3202+ table_data_dict[test_run_name] = row_data
3203+
3204+ #Lets see what we can do with table_data_dict
3205+>>>>>>> MERGE-SOURCE
3206
3207 return render_to_response(
3208 "dashboard_app/image-report.html", {
3209- 'bread_crumb_trail': BreadCrumbTrail.leading_to(
3210- image_report_detail, name=image.name),
3211+ 'bread_crumb_trail': BreadCrumbTrail.leading_to(image_report_detail, name=image.name),
3212 'image': image,
3213+<<<<<<< TREE
3214 'chart_data': json.dumps(table_data),
3215 'test_names': json.dumps(test_run_names),
3216 'columns': json.dumps(cols),
3217+=======
3218+ 'cols': cols,
3219+ 'table_data': table_data,
3220+ 'test_run_names': test_run_names,
3221+ 'pfreport': pfreport,
3222+ 'rowreport': rowreport,
3223+ 'kpireport': kpireport,
3224+ 'goaldates': goaldates,
3225+ 'goalname': currentset,
3226+ 'setname': setname,
3227+>>>>>>> MERGE-SOURCE
3228 }, RequestContext(request))
3229
3230

Subscribers

People subscribed via source and target branches