Merge lp:~cprov/uci-engine/webui-subtickets into lp:uci-engine

Proposed by Celso Providelo
Status: Superseded
Proposed branch: lp:~cprov/uci-engine/webui-subtickets
Merge into: lp:uci-engine
Diff against target: 703 lines (+277/-192)
8 files modified
ci-utils/ci_utils/__init__.py (+1/-0)
juju-deployer/configs/unit_config.yaml.tmpl (+1/-0)
ticket_system/ticket/admin.py (+7/-4)
ticket_system/ticket/api.py (+5/-2)
ticket_system/ticket/models.py (+6/-5)
ticket_system/ticket/tests/test_write_api.py (+54/-7)
webui/common/static/common/webui.css (+37/-12)
webui/tickets/static/tickets/webui.js (+166/-162)
To merge this branch: bzr merge lp:~cprov/uci-engine/webui-subtickets
Reviewer Review Type Date Requested Status
Canonical CI Engineering Pending
Review via email: mp+240676@code.launchpad.net
To post a comment you must log in.
884. By Celso Providelo

Minor CSS adjusting.

885. By Celso Providelo

merge trunk

886. By Celso Providelo

Invert subtickets rows to avoid text wrapping on its attributes values.

887. By Celso Providelo

CSS tweak.

Unmerged revisions

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'ci-utils/ci_utils/__init__.py'
2--- ci-utils/ci_utils/__init__.py 2014-10-30 17:30:49 +0000
3+++ ci-utils/ci_utils/__init__.py 2014-11-05 05:36:04 +0000
4@@ -37,6 +37,7 @@
5 'precise',
6 'trusty',
7 'utopic',
8+ 'vivid',
9 ]
10
11
12
13=== modified file 'juju-deployer/configs/unit_config.yaml.tmpl'
14--- juju-deployer/configs/unit_config.yaml.tmpl 2014-10-16 09:27:12 +0000
15+++ juju-deployer/configs/unit_config.yaml.tmpl 2014-11-05 05:36:04 +0000
16@@ -37,6 +37,7 @@
17 private_ppas_only: $CI_PRIVATE_PPAS_ONLY
18
19 image_map:
20+ vivid: http://cloud-images.ubuntu.com/vivid/current/vivid-server-cloudimg-amd64-disk1.img
21 utopic: http://cloud-images.ubuntu.com/utopic/current/utopic-server-cloudimg-amd64-disk1.img
22 trusty: http://cloud-images.ubuntu.com/trusty/current/trusty-server-cloudimg-amd64-disk1.img
23 precise: http://cloud-images.ubuntu.com/precise/current/precise-server-cloudimg-amd64-disk1.img
24
25=== modified file 'ticket_system/ticket/admin.py'
26--- ticket_system/ticket/admin.py 2014-10-23 01:21:04 +0000
27+++ ticket_system/ticket/admin.py 2014-11-05 05:36:04 +0000
28@@ -15,6 +15,7 @@
29
30 from django.contrib import admin
31 from ticket.models import (
32+ MergeProposal,
33 Review,
34 SourcePackageUpload,
35 SubTicket,
36@@ -60,10 +61,12 @@
37 list_filter = ['name']
38 search_fields = ['name']
39
40+
41+admin.site.register(MergeProposal)
42+admin.site.register(Review)
43+admin.site.register(SourcePackageUpload, SourcePackageUploadAdmin)
44+admin.site.register(SubTicket, SubTicketAdmin)
45+admin.site.register(SubTicketArtifact, SubTicketArtifactAdmin)
46 admin.site.register(Ticket, TicketAdmin)
47-admin.site.register(SubTicket, SubTicketAdmin)
48-admin.site.register(SourcePackageUpload, SourcePackageUploadAdmin)
49 admin.site.register(TicketArtifact, TicketArtifactAdmin)
50-admin.site.register(Review)
51-admin.site.register(SubTicketArtifact, SubTicketArtifactAdmin)
52 admin.site.register(Workflow, WorkflowAdmin)
53
54=== modified file 'ticket_system/ticket/api.py'
55--- ticket_system/ticket/api.py 2014-10-30 04:57:56 +0000
56+++ ticket_system/ticket/api.py 2014-11-05 05:36:04 +0000
57@@ -136,7 +136,9 @@
58 return bundle
59
60 for subticket in subtickets:
61- if not isinstance(subticket, dict):
62+ if isinstance(subticket, basestring):
63+ continue
64+ elif not isinstance(subticket, dict):
65 sp = subticket.data['sourcepackage']
66 else:
67 sp = subticket['sourcepackage']
68@@ -403,7 +405,7 @@
69 help_text="Related `SourcePackageUpload`s URIs.")
70
71 merge_proposals = fields.ToManyField(
72- 'ticket.api.MergeProposalsResource', 'merge_proposals',
73+ 'ticket.api.MergeProposalResource', 'merge_proposals',
74 related_name='subticket', null=True, full=True,
75 help_text="Related `MergeProposal`s URIs.")
76
77@@ -602,6 +604,7 @@
78 class Meta:
79 queryset = Review.objects.all()
80 authorization = Authorization()
81+ allowed_methods = ['get', 'post', 'patch', 'delete']
82 filtering = {
83 'ticket': ALL_WITH_RELATIONS,
84 'review_type': ALL,
85
86=== modified file 'ticket_system/ticket/models.py'
87--- ticket_system/ticket/models.py 2014-11-04 09:05:06 +0000
88+++ ticket_system/ticket/models.py 2014-11-05 05:36:04 +0000
89@@ -99,7 +99,7 @@
90 }, {
91 'name': 'citrain',
92 'steps': json.dumps([
93- {'name': 'silo_creation'},
94+ {'name': 'silo_creating'},
95 {'name': 'silo_building'},
96 {'name': 'silo_testing'},
97 {'name': 'silo_publishing'}
98@@ -124,7 +124,7 @@
99 owner = models.EmailField(max_length=254)
100 creator = models.EmailField(max_length=254, null=True, blank=True)
101 title = models.CharField(max_length=4096)
102- description = models.TextField()
103+ description = models.TextField(blank=True)
104 bug_id = models.IntegerField(null=True, blank=True)
105 current_workflow_step = models.IntegerField(
106 choices=_choices(TicketWorkflowStep), max_length=4096,
107@@ -288,7 +288,7 @@
108 help_text='Branch revision number approved for this cycle.')
109
110 source_package_upload = models.ForeignKey(
111- SourcePackageUpload, null=True,
112+ SourcePackageUpload, null=True, blank=True,
113 help_text='Current SPU generated by this merge proposal.')
114
115 class Meta:
116@@ -336,7 +336,8 @@
117 ~models.Q(pk=self.ticket.pk),
118 ~models.Q(status=TicketWorkflowStepStatus.FAILED),
119 current_workflow_step__range=(
120- TicketWorkflowStep.QUEUED, TicketWorkflowStep.PKG_PUBLISHING),
121+ TicketWorkflowStep.SILO_BUILDING,
122+ TicketWorkflowStep.PKG_PUBLISHING),
123 )
124
125 return conflicting_tickets
126@@ -405,4 +406,4 @@
127
128 def __unicode__(self):
129 return 'ticket-{} - {}: completed({})'.format(
130- self.ticket.id, self.review_type, self.completed)
131+ self.ticket.uuid, self.review_type, self.completed)
132
133=== modified file 'ticket_system/ticket/tests/test_write_api.py'
134--- ticket_system/ticket/tests/test_write_api.py 2014-11-04 17:01:32 +0000
135+++ ticket_system/ticket/tests/test_write_api.py 2014-11-05 05:36:04 +0000
136@@ -135,25 +135,72 @@
137 [unicode(spu) for spu in sub_bar.source_package_uploads.all()])
138
139 def test_post_ticket_atomically_for_reviews(self):
140+ # 'Reviews' can be created atomically with the related `Ticket`.
141+
142+ # We are injecting a review here to check if tastypie it behaving
143+ # correctly and not updating existing reviews to accomplish the
144+ # request.
145+ other_ticket = create_ticket()
146+ existing_review = other_ticket.review_set.create(
147+ review_type='QA',
148+ workflow_step=TicketWorkflowStep.IMAGE_BUILDING)
149+
150+ # Let's POST to create a new ticket with pre-defined reviews.
151 params = self.post_ticket_data.copy()
152 params.update({
153 'title': 'Atomic Ticket with Review',
154 'reviews': [{
155 'review_type': 'QA',
156 'workflow_step': 'Package validation',
157+ }, {
158+ 'review_type': 'QA',
159+ 'workflow_step': 'Image building',
160 }],
161 })
162 self.post(resource=self.resource, params=params)
163
164 # 'Atomic Ticket with Review' was created.
165- [existing, ticket] = list(Ticket.objects.all())
166+ [existing, other, ticket] = list(Ticket.objects.all())
167+
168 self.assertEqual('Atomic Ticket with Review', unicode(ticket))
169- # and already contain the specified `Review`
170- [review] = list(ticket.review_set.all())
171- self.assertEqual('QA', review.review_type)
172- self.assertEqual(
173- TicketWorkflowStep.PKG_VALIDATION.value, review.workflow_step)
174- self.assertFalse(review.completed)
175+ # and already contain the specified `Review`s
176+ [review_pkg, review_img] = list(ticket.review_set.all())
177+ self.assertEqual('QA', review_pkg.review_type)
178+ self.assertEqual(
179+ TicketWorkflowStep.PKG_VALIDATION.value, review_pkg.workflow_step)
180+ self.assertFalse(review_pkg.completed)
181+ self.assertEqual('QA', review_img.review_type)
182+ self.assertEqual(
183+ TicketWorkflowStep.IMAGE_BUILDING.value, review_img.workflow_step)
184+ self.assertFalse(review_img.completed)
185+
186+ # Note that the reviews created via POST are new, they are not
187+ # mysteriously reused/updated by tastypie.
188+ self.assertNotIn(existing_review.pk, [review_pkg.pk, review_img.pk])
189+
190+ def test_patch_ticket_reviews(self):
191+ # `Ticket` PATCH can update `Review`s.
192+ review = self.ticket.review_set.create(
193+ review_type='QA',
194+ workflow_step=TicketWorkflowStep.IMAGE_BUILDING)
195+
196+ params = self.post_ticket_data.copy()
197+ params.update({
198+ 'title': 'Atomic Patched Ticket',
199+ 'reviews': [{
200+ 'id': review.id,
201+ 'completed': True,
202+ 'workflow_step': 'Image building',
203+ }],
204+ })
205+ self.patch(resource=self.detail_url, params=params)
206+
207+ # The existing `Subticket` was updated and a new one was created.
208+ [ticket] = list(Ticket.objects.all())
209+ self.assertEqual('Atomic Patched Ticket', unicode(ticket))
210+ [new_review] = list(ticket.review_set.all())
211+ self.assertEqual(review.id, new_review.id)
212+ self.assertTrue(new_review.completed)
213
214 def test_post_ticket_atomically_reuses_workflows(self):
215 # `Ticket` atomic creation re-uses existing `Workflows`s.
216
217=== added file 'webui/common/static/common/merge-proposal-icon.png'
218Binary files webui/common/static/common/merge-proposal-icon.png 1970-01-01 00:00:00 +0000 and webui/common/static/common/merge-proposal-icon.png 2014-11-05 05:36:04 +0000 differ
219=== added file 'webui/common/static/common/warning.png'
220Binary files webui/common/static/common/warning.png 1970-01-01 00:00:00 +0000 and webui/common/static/common/warning.png 2014-11-05 05:36:04 +0000 differ
221=== modified file 'webui/common/static/common/webui.css'
222--- webui/common/static/common/webui.css 2014-11-04 13:32:04 +0000
223+++ webui/common/static/common/webui.css 2014-11-05 05:36:04 +0000
224@@ -175,18 +175,6 @@
225 padding-left: 2em;
226 }
227
228-td.conflicts a {
229- color: #FF0000;
230- font-style: bold;
231- padding-left: 1em;
232- vertical-align: middle;
233-}
234-
235-td.conflicts img {
236- height: 1em;
237- vertical-align: middle;
238-}
239-
240 div.form-error {
241 display: block;
242 -moz-border-radius: 4px;
243@@ -286,6 +274,25 @@
244 font-weight: bold;
245 }
246
247+/* Subtickets datatable */
248+div.subticket_wrapper table {
249+ width: 95%;
250+ line-height: 2;
251+}
252+
253+div.subticket_wrapper th {
254+ background: none;
255+ text-align: left;
256+ font-weight: bold;
257+ border-bottom: 1px solid black;
258+}
259+
260+div.subticket_wrapper td {
261+ background: white;
262+ text-align: left;
263+ border-bottom: 1px dotted black;
264+}
265+
266 /* Ticket inline review. */
267 .yui3-inlinereview-content {
268 padding: 3px 3px;
269@@ -358,3 +365,21 @@
270 margin-left: 5px;
271 font-size: 10px;
272 }
273+
274+a.mp-link {
275+ background-image: url('/static/common/merge-proposal-icon.png');
276+ background-repeat: no-repeat;
277+ background-size: contain;
278+ padding-left: 1.8em;
279+ margin-left: 5px;
280+ font-size: 10px;
281+}
282+
283+a.conflict-link {
284+ background-image: url('/static/common/warning.png');
285+ background-repeat: no-repeat;
286+ background-size: contain;
287+ padding-left: 1.2em;
288+ margin-left: 5px;
289+ font-size: 10px;
290+}
291
292=== modified file 'webui/tickets/static/tickets/webui.js'
293--- webui/tickets/static/tickets/webui.js 2014-11-04 13:32:04 +0000
294+++ webui/tickets/static/tickets/webui.js 2014-11-05 05:36:04 +0000
295@@ -54,9 +54,11 @@
296 this.get('srcNode').all('button').on('click', function(e) {
297 var review = self.get('review');
298 var data = {
299- reviews: [
300- {'id': review.id, 'completed': true}
301- ]
302+ reviews: [{
303+ 'id': review.id,
304+ 'workflow_step': review.workflow_step,
305+ 'completed': true
306+ }]
307 };
308 // Issue a PATCH TS-request to update the review and
309 // update the widget accordingly.
310@@ -91,14 +93,34 @@
311 "The ticket system returned data that could not be understood. " +
312 "Please try refreshing, or contact support if this problem " +
313 "persists."),
314+
315 refresh_msg = "Refreshing data...",
316+
317 default_refresh = 60,
318+
319 static_prefix = "/static/common/";
320+
321+ var ticket_status_templates = {
322+ completed: ('<img src="{static_prefix}check.svg" ' +
323+ 'title="{title}" /></br>'),
324+ failed: ('<img src="{static_prefix}cross.svg" ' +
325+ 'title="{title}"/></br>'),
326+ inprogress: ('<img src="{static_prefix}spinner3.gif" ' +
327+ 'title="{title}"/></br>'),
328+ paused: ('<img src="{static_prefix}paused.svg" ' +
329+ 'title="{title}"/></br>'),
330+ 'continue': ('<img src="{static_prefix}spinner3.gif" ' +
331+ 'title="{title}"/></br>')
332+ };
333+
334 Y.webui = {
335+
336 url_prefix: "/api/v1/",
337+
338 refresh_interval: default_refresh * 1000,
339+
340 refresh_timer: null,
341- done_loading: false,
342+
343 tempurls: {},
344
345 /**
346@@ -201,111 +223,6 @@
347 }
348 });
349 },
350- add_subticket: function (data) {
351- var main_tr = Y.Node.create("<tr>"),
352- key,
353- value,
354- tr;
355-
356- main_tr.appendChild(Y.Node.create('<th>subticket</th>'));
357- for (key in data) {
358- if (data.hasOwnProperty(key)) {
359- value = data[key];
360- tr = main_tr.appendChild(Y.Node.create('<tr>'));
361- tr.addClass('subticket');
362- tr.appendChild(Y.Node.create('<th>' + key + '</th>'));
363- tr.appendChild(Y.Node.create('<td>' + value + '</td>'));
364- }
365- }
366-
367- main_tr.addClass('subticket');
368- return main_tr;
369- },
370- make_subticket: function (data) {
371- var div = Y.Node.create("<div class='subticket'></div>"),
372- table = div.appendChild(Y.Node.create("<table>")),
373- hidden_fields = Y.Array([
374- 'id',
375- 'resource_uri',
376- 'current_workflow_step',
377- 'status',
378- 'sourcepackage',
379- 'merge_proposals',
380- 'source_package_uploads'
381- ]),
382- key,
383- value,
384- tmp_th,
385- tr,
386- count,
387- suffix,
388- span;
389-
390- tr = table.appendChild(Y.Node.create('<tr>'));
391- tr.appendChild(Y.Node.create('<th>Current version:</th>'));
392- var td = tr.appendChild(Y.Node.create('<td>-</td>'));
393- var spus = data.source_package_uploads;
394- if (spus.length > 0) {
395- var last_spu = spus[spus.length - 1];
396- td.set('text', last_spu.version);
397- }
398-
399- for (key in data) {
400- if (data.hasOwnProperty(key)) {
401- value = data[key];
402-
403- // Skip hidden fields
404- if (Y.Array.indexOf(hidden_fields, key) === -1) {
405-
406- suffix = "";
407- if (key === 'artifact') {
408- span = Y.webui.add_artifacts(data.artifact);
409- value = span.getHTML();
410- count = value.match(/<a/g);
411- if (count && count.length > 1) {
412- suffix = "s";
413- }
414- }
415-
416- key = key.replace("_", " ");
417- if (value === null) {
418- value = "";
419- }
420-
421- // subticket
422- tr = table.appendChild(Y.Node.create('<tr>'));
423- tmp_th = ('<th>' + key.charAt(0).toUpperCase() +
424- key.slice(1) + suffix + ': </th>');
425- tr.appendChild(Y.Node.create(tmp_th));
426- if (key === 'conflicts') {
427- // Display 'conflicts' as 'ticket' links.
428- td = Y.Node.create('<td>')
429- .addClass('conflicts');
430- var i;
431- for (i in value) {
432- var icon = Y.Node.create('<img>')
433- .set('src', '/static/common/cross.svg');
434- td.appendChild(icon);
435- var href = value[i].replace('/api/v1', '');
436- var text = href.replace(
437- '/ticket/', '').replace('/', '');
438- var link = Y.Node.create('<a>')
439- .set('href', href)
440- .set('text', text);
441- td.appendChild(link);
442- td.appendChild('</br>');
443- }
444- tr.appendChild(td);
445- } else {
446- // Display other fields as plain-text.
447- tr.appendChild(
448- Y.Node.create('<td>' + value + '</td>'));
449- }
450- }
451- }
452- }
453- return div;
454- },
455 basename: function (path) {
456 var result = path;
457
458@@ -349,6 +266,27 @@
459
460 return link;
461 },
462+
463+ /**
464+ * Build a link to a Merge Proposal model ({lp_url: 'lp:<MP ref>'}).
465+ *
466+ * Returns a new <a>.
467+ *
468+ * @method mp_link
469+ */
470+
471+ mp_link: function (mp) {
472+ var link = Y.Node.create('<a/>')
473+ .set('text', mp.lp_url)
474+ .addClass('mp-link');
475+
476+ var lp_href = mp.lp_url.replace(
477+ 'lp:', 'https://code.launchpad.net/');
478+ link.set('href', lp_href);
479+
480+ return link;
481+ },
482+
483 /**
484 * Build a link to ppa reference (ppa:<user>/[<distro>/]<name>)
485 *
486@@ -383,36 +321,14 @@
487 },
488 ticket_detail_element: function (data) {
489 var MAX_DESCRIPTION = 200,
490- count,
491- current_step,
492 description = "",
493 description_p,
494 div,
495- fail_str,
496- found_step,
497 header_description,
498 header_wrapper,
499- i,
500- key,
501- not_yet_step,
502- short_status,
503- skipped_step,
504- span,
505- status_image,
506- step_status,
507- subt,
508- subticket,
509- subticket_count,
510 subticket_wrapper,
511- suffix,
512- table,
513- td,
514- td1,
515 ticket_div,
516 ticket_wrapper,
517- tmp_th,
518- tr,
519- value,
520 workflow_inner_div,
521 workflow_wrapper;
522
523@@ -480,27 +396,127 @@
524 dt_details.addRow({'value': watch_log});
525 }
526
527- if (data.subticket) {
528- subticket_count = data.subticket.length;
529-
530- subticket_wrapper.appendChild("<h3>Uploads</h3>");
531- if (subticket_count > 0) {
532- for (i = 0; i < subticket_count; i += 1) {
533- subticket = data.subticket[i];
534-
535- subticket_wrapper.appendChild("<h4>" + data.subticket[i].sourcepackage.name + "</h4><div id='status'><strong>Landing Progress:</strong> " + data.subticket[i].current_workflow_step + " <strong>Status:</strong> " + data.subticket[i].status + "</div>");
536-
537- subt = Y.webui.make_subticket(subticket);
538-
539- subticket_wrapper.appendChild(subt);
540- }
541- }
542- }
543+ // Build and render the subtickets datatable.
544+ var dt_subtickets = Y.webui.get_subtickets_datatable(data);
545+ dt_subtickets.render(subticket_wrapper);
546
547 return div;
548 },
549
550 /**
551+ * Build a datatable for presenting the given Ticket subtickets.
552+ *
553+ * Returns a Y.Datatable loaded with subtickets.
554+ *
555+ * @method get_subtickets_datatable
556+ */
557+ get_subtickets_datatable: function(ticket) {
558+ var format_status = function (o) {
559+ var template_vars = {
560+ static_prefix: static_prefix, title: o.data.status};
561+ var key = o.data.status.replace(' ', '').toLowerCase();
562+ if (ticket_status_templates.hasOwnProperty(key)) {
563+ return Y.Lang.sub(
564+ ticket_status_templates[key], template_vars);
565+ }
566+ // NEW & 'NOT STARTED'.
567+ return '-';
568+ };
569+
570+ var format_source = function (o) {
571+ return o.data.sourcepackage.name;
572+ };
573+
574+ var format_step = function (o) {
575+ return o.data.current_workflow_step;
576+ };
577+
578+ var format_version = function (o) {
579+ var last_version = 'Not available'
580+ var spus = o.data.source_package_uploads;
581+ if (spus.length > 0) {
582+ var last_spu = spus[spus.length - 1];
583+ last_version = last_spu.version;
584+ }
585+ return last_version
586+ };
587+
588+ var format_assignee = function (o) {
589+ return o.data.assignee;
590+ };
591+
592+ var format_mps = function (o) {
593+ var container = Y.Node.create('<div>')
594+ for (var c in o.data.merge_proposals) {
595+ var mp = o.data.merge_proposals[c]
596+ container.appendChild(Y.webui.mp_link(mp));
597+ container.appendChild('<br>');
598+ }
599+ return container.getHTML();
600+ };
601+
602+ var format_artifacts = function (o) {
603+ var container = Y.Node.create('<div>')
604+ for (var c in o.data.artifact) {
605+ var artifact = o.data.artifact[c]
606+ container.appendChild(Y.webui.artifact_link(artifact));
607+ container.appendChild('<br>');
608+ }
609+ return container.getHTML();
610+ };
611+
612+ var format_conflicts = function (o) {
613+ var container = Y.Node.create('<div>')
614+ for (var c in o.data.conflicts) {
615+ var href = o.data.conflicts[c].replace('/api/v1', '');
616+ var text = href.replace(
617+ '/ticket/', '').replace('/', '');
618+ var link = Y.Node.create('<a>')
619+ .set('href', href)
620+ .set('text', text)
621+ .addClass('conflict-link');
622+ container.appendChild(link);
623+ container.appendChild('<br>');
624+ }
625+ return container.getHTML();
626+ };
627+
628+ var dt = new Y.DataTable({
629+ columns: [
630+ {key: 'status', label: '&nbsp;',
631+ allowHTML: true, formatter: format_status,
632+ className: 'dt-subticket-status'},
633+ {key: 'source', label: 'Source',
634+ allowHTML: true, formatter: format_source,
635+ className: 'dt-subticket-source'},
636+ {key: 'step', label: 'Step',
637+ allowHTML: true, formatter: format_step,
638+ className: 'dt-subticket-step'},
639+ {key: 'assignee', label: 'Assignee',
640+ allowHTML: true, formatter: format_assignee,
641+ className: 'dt-subticket-assignee'},
642+ {key: 'version', label: 'Current version',
643+ allowHTML: true, formatter: format_version,
644+ className: 'dt-subticket-version'},
645+ {key: 'merge_proposals', label: 'MPs',
646+ allowHTML: true, formatter: format_mps,
647+ className: 'dt-subticket-merge-proposals'},
648+ {key: 'artifacts', label: 'Artifacts',
649+ allowHTML: true, formatter: format_artifacts,
650+ className: 'dt-subticket-artifacts'},
651+ {key: 'conflicts', label: 'Conflicts',
652+ allowHTML: true, formatter: format_conflicts,
653+ className: 'dt-subticket-conflicts'},
654+ ],
655+ caption: '<h3>Uploads</h3>',
656+ data: ticket.subticket
657+ });
658+
659+
660+ return dt;
661+ },
662+
663+ /**
664 * Build a datatable for presenting the given Ticket status.
665 *
666 * Returns a Y.Datatable loaded with ticket status.
667@@ -532,18 +548,6 @@
668 'Silo publishing': {
669 step: 'silo_publishing', log_prefix: 'silo_publishing'}
670 };
671- var icon_templates = {
672- completed: ('<img src="{static_prefix}check.svg" ' +
673- 'title="{title}" /></br>'),
674- failed: ('<img src="{static_prefix}cross.svg" ' +
675- 'title="{title}"/></br>'),
676- inprogress: ('<img src="{static_prefix}spinner3.gif" ' +
677- 'title="{title}"/></br>'),
678- paused: ('<img src="{static_prefix}paused.svg" ' +
679- 'title="{title}"/></br>'),
680- 'continue': ('<img src="{static_prefix}spinner3.gif" ' +
681- 'title="{title}"/></br>')
682- };
683 var dataset = [];
684 var step_values = {};
685 var hidden_steps = Y.Array([
686@@ -617,14 +621,14 @@
687 if (o.data.value < current_step_value) {
688 // Past steps always 'Passed'.
689 value = Y.Lang.sub(
690- icon_templates.completed, template_vars);
691+ ticket_status_templates.completed, template_vars);
692 } else if (o.data.value === current_step_value) {
693 // Current step according to the ticket 'status'.
694 o.rowClass = 'dt-status-current';
695 var key = ticket.status.replace(' ', '').toLowerCase();
696- if (icon_templates.hasOwnProperty(key)) {
697+ if (ticket_status_templates.hasOwnProperty(key)) {
698 value = Y.Lang.sub(
699- icon_templates[key], template_vars);
700+ ticket_status_templates[key], template_vars);
701 } else {
702 // Just render the text of unexpected statuses.
703 value = ticket.status;

Subscribers

People subscribed via source and target branches