Merge lp:~cprov/uci-engine/webui-subtickets into lp:uci-engine
- webui-subtickets
- Merge into trunk
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 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Canonical CI Engineering | Pending | ||
Review via email: mp+240676@code.launchpad.net |
Commit message
Description of the change
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' |
218 | Binary 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' |
220 | Binary 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: ' ', |
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; |