Merge lp:~robru/bileto/kill-dashboard-dead into lp:bileto

Proposed by Robert Bruce Park
Status: Merged
Approved by: Robert Bruce Park
Approved revision: 240
Merged at revision: 240
Proposed branch: lp:~robru/bileto/kill-dashboard-dead
Merge into: lp:bileto
Diff against target: 1166 lines (+13/-1094)
8 files modified
tests/test_v1.py (+6/-0)
tickets/static/dashboard.css (+0/-176)
tickets/static/dashboard.html (+0/-146)
tickets/static/dashboard.js (+0/-328)
tickets/static/test/index.html (+0/-16)
tickets/static/test/jenkins_silo_mock.js (+0/-1)
tickets/static/test/test.js (+0/-426)
tickets/v1.py (+7/-1)
To merge this branch: bzr merge lp:~robru/bileto/kill-dashboard-dead
Reviewer Review Type Date Requested Status
PS Jenkins bot continuous-integration Approve
Robert Bruce Park (community) Approve
Review via email: mp+269544@code.launchpad.net

Commit message

THIS! IS! SPARTAN!

Description of the change

(don't merge or go live with this before monday the 31st)

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

PASSED: Continuous integration, rev:237
http://jenkins.qa.ubuntu.com/job/bileto-ci/47/
Executed test runs:

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/bileto-ci/47/rebuild

review: Approve (continuous-integration)
lp:~robru/bileto/kill-dashboard-dead updated
238. By Robert Bruce Park

Redirect dashboard to homepage.

239. By Robert Bruce Park

Import redirect.

Revision history for this message
Robert Bruce Park (robru) wrote :

Looks good in staging.

review: Approve
lp:~robru/bileto/kill-dashboard-dead updated
240. By Robert Bruce Park

Test.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

PASSED: Continuous integration, rev:240
http://jenkins.qa.ubuntu.com/job/bileto-ci/48/
Executed test runs:

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/bileto-ci/48/rebuild

review: Approve (continuous-integration)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'tests/test_v1.py'
2--- tests/test_v1.py 2015-08-28 04:38:56 +0000
3+++ tests/test_v1.py 2015-08-28 20:11:22 +0000
4@@ -32,6 +32,12 @@
5 sesh['teams'] = 'ubuntu-core-dev'
6 sesh['nickname'] = 'fritz'
7
8+ def test_dashboard_redirect(self):
9+ """Ensure dashboard redirects to front page."""
10+ ret = self.app.get('/static/dashboard.html', follow_redirects=False)
11+ self.assertIn(b'<a href="/">/</a>.', ret.data)
12+ self.assertEqual(ret.status_code, 302)
13+
14 def test_models(self):
15 """Ensure Request.update works as expected."""
16 req = Request()
17
18=== removed file 'tickets/static/dashboard.css'
19--- tickets/static/dashboard.css 2015-08-26 05:21:29 +0000
20+++ tickets/static/dashboard.css 1970-01-01 00:00:00 +0000
21@@ -1,176 +0,0 @@
22-html, body {
23- background-color: white;
24- color: black;
25- margin: 0;
26- padding: 0;
27- text-align: center;
28- font-size: small;
29- font-family: Ubuntu, sans-serif;
30-}
31-
32-a {
33- color: #dd4814;
34- font-weight: bold;
35- text-decoration: none;
36-}
37-
38-a:hover {
39- text-decoration: underline;
40-}
41-
42-.tiny {
43- font-size: x-small;
44-}
45-
46-.nowrap {
47- white-space: nowrap;
48-}
49-
50-#nav {
51- display: block;
52- width: 100%;
53- background-color: #dd4814;
54- color: white;
55- padding: 1em;
56- position: fixed;
57-}
58-
59-#nav a {
60- background-color: #dd4814;
61- color: white;
62- margin: 0 5px;
63-}
64-
65-#nav li {
66- display: inline-block;
67-}
68-
69-#bubble_wrap {
70- padding-top: 8em;
71-}
72-
73-.bubble {
74- text-align: left;
75- width: 400px;
76- display: inline-block;
77- margin: .5em;
78- vertical-align: top;
79-}
80-
81-.buttons {
82- text-align: center;
83- border-top: 1px solid black;
84- border-bottom: 1px solid black;
85-}
86-
87-.buttons a {
88- padding: 3px;
89- margin: 1px;
90- display: inline-block;
91- border-radius: 5px;
92-}
93-
94-.contents {
95- margin: 10px 0 1em 10px;
96-}
97-
98-p, ul, li {
99- list-style: none;
100- margin: 0;
101- padding: 0;
102-}
103-
104-.mp_list_show {
105- padding-left: 1em;
106-}
107-
108-.mp_list_show a {
109- font-weight: normal;
110-}
111-
112-.hover_mp .mp_list_hide a {
113- background-color: #dd4814;
114- color: white;
115-}
116-
117-.hover_mp .mp_list_hide {
118- margin-left: 2em;
119- display: none;
120- position: absolute;
121- background-color: #dd4814;
122- padding: 10px;
123- border-radius: 0 10px 10px 10px;
124- z-index: 200;
125-}
126-
127-.hover_mp:hover .mp_list_hide {
128- display: block;
129-}
130-
131-.details {
132- background-color: white;
133- border-radius: 5px;
134- padding: 0 0 3px 3px;
135- float: right;
136- margin: 10px 10px 1em 0;
137-}
138-
139-.details a {
140- display: block;
141-}
142-
143-.status {
144- font-weight: bold;
145- clear: left;
146- margin: 1em;
147- border-radius: 5px;
148- padding: 3px;
149-}
150-
151-.description {
152- font-size: xx-small;
153-}
154-
155-.comments {
156- font-size: xx-small;
157- color: red;
158-}
159-
160-.comment_author {
161- font-weight: bold;
162-}
163-
164-#svgbox {
165- display: none;
166- position: absolute;
167- background-color: white;
168- border: 1px solid black;
169- border-radius: 10px;
170-}
171-
172-.hover_svg:hover #svgbox {
173- display: block;
174-}
175-
176-#footer {
177- margin-top: 1em;
178-}
179-
180-#footer li {
181- display: inline-block;
182- margin: 1em;
183-}
184-
185-#deprecation {
186- font-weight: bold;
187- background-color: red;
188- color: white;
189- width: 100%;
190- padding: 1em;
191-}
192-
193-#deprecation a {
194- background-color: red;
195- color: white;
196- text-decoration: underline;
197-}
198
199=== removed file 'tickets/static/dashboard.html'
200--- tickets/static/dashboard.html 2015-08-26 21:53:17 +0000
201+++ tickets/static/dashboard.html 1970-01-01 00:00:00 +0000
202@@ -1,146 +0,0 @@
203-<!DOCTYPE html>
204-<html ng-app="CITrainWeb" ng-controller="appController">
205-<head>
206-<meta charset="utf-8" />
207-<title>CI Train Silo Dashboard</title>
208-<link rel="stylesheet" type="text/css" href="dashboard.css">
209-<link rel="icon" type="image/gif" href="train.gif">
210-<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.0-beta.13/angular.min.js"></script>
211-<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.0-beta.13/angular-sanitize.min.js"></script>
212-<script src="dashboard.js" charset="utf-8"></script>
213-</head>
214-<body>
215-
216-<ul id="nav">
217- <li ng-repeat="series in header.foundSeries"><a href="#?q={{series}}">{{series}}</a></li>
218- <li ng-repeat="(distro, v) in distros"><a href="#?q={{distro}}/">{{distro}}</a></li>
219- <li class="hover_svg">
220- <span class="tiny">
221- <a href="#?q=">
222- {{header.activeSilos}} silos in use as of
223- {{header.lastUpdated | date : 'yyyy-MM-dd HH:mm:ss'}}
224- </a>
225- </span>
226- <div id="svgbox">
227- <svg ng-attr-width="{{(header.uniqueStats + 2) * 36}}"
228- height="200"
229- preserveAspectRatio="xMidYMid meet">
230- <g ng-repeat="status in header.statusStats">
231- <circle ng-attr-cx="{{svgPlaceX($index)}}"
232- cy="30"
233- ng-attr-r="{{(status[1] + 2) * 6}}"
234- ng-attr-fill="{{getStatusColor(status[0])}}"
235- opacity=".2">
236- </circle>
237- <text ng-attr-x="{{svgPlaceX($index)}}"
238- y="35"
239- text-anchor="middle"
240- fill="black">
241- {{status[1]}}
242- </text>
243- </g>
244- <g ng-repeat="status in header.statusStats">
245- <a xlink:href="#?q=" ng-xlink-href="#?q={{status[0]}}">
246- <text ng-attr-x="{{svgPlaceX($index) - 10}}"
247- y="50"
248- fill="{{getStatusColor(status[0])}}"
249- transform="rotate(45 {{svgPlaceX($index)}},50)">
250- {{status[0]}}...
251- </text>
252- </a>
253- </g>
254- </svg>
255- </div>
256- </li>
257- <li><input type="search" ng-model="q" placeholder="Filter silos..." /></li>
258- <li ng-repeat="link in links">
259- <a target="_blank" href="{{link[1]}}">{{link[0]}}</a>
260- </li>
261-<div id="deprecation"> <p>This dashboard page is now deprecated. Please use <a
262-href="/">The Requests Page</a> exclusively, and please inform <a
263-href="mailto:robert.park@canonical.com">robru</a> if there are any features
264-missing from the request page that you need.</p> </div>
265-</ul>
266-
267-<div id="bubble_wrap">
268- <div ng-repeat="siloname in silonames | filter:siloSearchFilter"
269- id="{{siloname}}"
270- class="bubble">
271- <div class="buttons">
272- <a href="#?q={{siloname}}" class="button">{{siloname}}</a>
273- <a target="_blank"
274- href="{{j.ppa_url}}/{{siloname}}"
275- class="button">PPA</a>
276- <a target="_blank"
277- href="{{j.domain}}/{{j.root}}{{siloname.replace('/', '-')}}{{j.build}}{{j.suffix}}"
278- class="button">Build</a>
279- <a target="_blank"
280- href="{{j.domain}}/{{j.root}}{{siloname.replace('/', '-')}}{{j.publish}}{{j.suffix}}"
281- class="button">Publish</a>
282- <a target="_blank"
283- href="{{j.domain}}/{{j.root}}{{siloname.replace('/', '-')}}{{j.clean}}{{j.suffix}}"
284- class="button">Clean</a>
285- </div>
286- <div class="details">
287- <a ng-repeat="lander in silos[siloname].landers"
288- href="#?q={{lander}}">{{lander}}</a>
289- <div class="tiny">
290- {{silos[siloname].displaySeries}} {{silos[siloname].dual}}<br />
291- {{silos[siloname].displayDest}}<br />
292- <a target="_blank" href="/#/?req={{silos[siloname].requestid}}">{{silos[siloname].requestid}}</a><br />
293- </div>
294- </div>
295- <div class="contents">
296- <div ng-repeat="package in silos[siloname].sources">
297- <p>
298- <a target="_blank" href="{{lp_url}}{{siloname.split('/')[0]}}/+source/{{package}}">{{package}}</a>
299- <a href="#?q={{package}}">…</a>
300- </p>
301- </div>
302- <div class="hover_mp" ng-repeat="package in silos[siloname].pkgNames">
303- <p>
304- <a target="_blank" href="{{lp_url}}{{siloname.split('/')[0]}}/+source/{{package}}">{{package}}</a>
305- <a href="#?q={{package}}">…</a>
306- </p>
307- <ul class="mp_list_{{ q ? 'show' : 'hide' }}">
308- <li ng-repeat="mp in silos[siloname].mps[package]">
309- <a target="_blank" href="{{fixMergeURL(mp)}}">{{shortURL(mp)}}</a>
310- </li>
311- </ul>
312- </div>
313- </div>
314- <div class="status" style="color: {{getSiloColor(siloname)}};">
315- <div class="jenkins_status">
316- <p ng-repeat="status in silos[siloname].longStatus"
317- ng-bind-html="status"></p>
318- </div>
319- <div class="jenkins_url">
320- <a target="_blank" href="{{silos[siloname].global.status.url}}">
321- {{silos[siloname].displayLink}}
322- </a>
323- </div>
324- <div class="tiny">
325- <span ng-show="silos[siloname] !== undefined">
326- {{silos[siloname].bileto.qa_signoff}}
327- </span>
328- </div>
329- </div>
330- <div class="description" ng-bind-html="silos[siloname].bileto.description"
331- ng-show="silos[siloname] !== undefined"></div>
332- <div class="comments" ng-show="silos[siloname] !== undefined">
333- <div ng-repeat="comment in silos[siloname].bileto.comments">
334- <span class="comment_author">{{comment.author}}: </span>
335- <span ng-bind-html="linkify(comment.text)"></span>
336- </div>
337- </div>
338-</div>
339-</div>
340-
341-<ul id="footer">
342- <li ng-repeat="link in footerLinks">
343- <a target="_blank" href="{{link[1]}}">{{link[0]}}</a>
344- </li>
345-</ul>
346-
347-</body>
348-</html>
349
350=== removed file 'tickets/static/dashboard.js'
351--- tickets/static/dashboard.js 2015-08-06 02:13:54 +0000
352+++ tickets/static/dashboard.js 1970-01-01 00:00:00 +0000
353@@ -1,328 +0,0 @@
354-var ROOT = '/static/json/';
355-var BILETO = '/v1/ticket/';
356-if (window.location.origin == 'file://') {
357- var domain = 'https://requests.ci-train.ubuntu.com';
358- ROOT = domain + ROOT;
359- BILETO = domain + BILETO;
360-}
361-
362-// Figure out which browser's page visibility API we have.
363-var visibilitychange = null;
364-var hidden = null;
365-['h', 'mozH', 'webkitH', 'msH', 'oH'].some(function(prefix) {
366- hidden = prefix + 'idden';
367- if (hidden in document) {
368- visibilitychange = prefix.slice(0, -1) + 'visibilitychange';
369- return true; // Found it, break loop!
370- }
371-});
372-
373-STATUS_COLORS = {
374- 'caught signal': 'red',
375- 'uncaught exception': 'red',
376- 'build failed': 'red',
377- 'can t': 'red',
378- 'failed to': 'red',
379- 'free': 'purple',
380- 'gave up': 'green',
381- 'landed without': 'green',
382- 'landed cleaning': 'green',
383- 'merge failed': 'red',
384- 'merging': 'green',
385- 'migration all': 'blue',
386- 'packages migrating': 'green',
387- 'publication failed': 'red',
388- 'publication needs': 'blue',
389- 'reconfigure failed': 'red',
390- 'silo ready': 'purple',
391- 'silo dirty': 'olive',
392-};
393-
394-BILETO_COLORS = {
395- 'qa granted': 'blue',
396- 'publish without qa': 'blue',
397- 'qa failed': 'red',
398-};
399-
400-
401-function getStatusColor(status) {
402- return STATUS_COLORS[nWords(status, 2).toLowerCase()] || 'black';
403-};
404-
405-var app = angular.module('CITrainWeb', ['ngSanitize']);
406-
407-// This bit allows dynamic links inside SVGs. See
408-// http://stackoverflow.com/questions/15895483/angular-ng-href-and-svg-xlink
409-app.directive('ngXlinkHref', function () {
410- return {
411- priority: 99,
412- link: function (scope, element, attrs) {
413- attrs.$observe('ngXlinkHref', function(value) {
414- if (!value) return;
415- attrs.$set('xlink:href', value);
416- });
417- }
418- }
419-});
420-
421-function linkify(str) {
422- return str.replace(/(https?:\/\/[^\s)]+)/ig, '<a target="_blank" href="$1">$1</a>')
423- .replace(/>https?:\/\/([^<\/]+\/)?/g, ">");
424-}
425-
426-function excusify(str) {
427- return str.replace(
428- /^(Migration: )?(\S+)(.*is in the Proposed pocket)/,
429- '<a target="_blank" href="http://people.canonical.com/~ubuntu-archive' +
430- '/proposed-migration/update_excuses.html#$2">$2</a>$3');
431-}
432-
433-function fixMergeURL(url) {
434- return url.replace('https://api.launchpad.net/devel/',
435- 'https://code.launchpad.net/');
436-}
437-
438-function shortURL(url) {
439- // Just the branch name from the merge URL
440- return url.replace(/https?:\/\/[^\/]+\//, '')
441- .replace(/^.*~([^\/]+\/){2}([^\/]+).*$/, "$2")
442- .replace(/[_-]/g, ' ');
443-}
444-
445-// Append this to AJAX URLs to prevent browser caching.
446-function noCache() {
447- return 'nocache=' + new Date().getTime();
448-}
449-
450-// Return first N words of a string.
451-function nWords(phrase, n, regex) {
452- return phrase.split(regex || /\W+/).splice(0, n).join(' ');
453-}
454-
455-function searchFactory(getQuery, silos) {
456- // If this function returns true, it means 'display this search result'
457- // returning false hides that result from the list.
458- return function(siloname) {
459- var query = getQuery();
460- var regex = new RegExp(query, 'i');
461-
462- if (siloname.match(regex)) {
463- // Always display silos when referenced by name. This is necessary
464- // because empty silos don't have the search strings, and therefore
465- // empty silos don't show up at their own permalinks.
466- return true;
467- } else if (query) {
468- var silo = silos[siloname];
469- if (silo) {
470- // If a silo exists, show it only when it matches the search
471- return !!silo.searchString.match(regex);
472- } else if (query.match(/^free$/i)) {
473- // If the search is for 'free' silos, show only free ones.
474- return true;
475- } else {
476- // Hide empty silos when the search term isn't 'free'
477- return false;
478- }
479- } else {
480- // Show all silos when there is no search term.
481- return true;
482- }
483- }
484-}
485-
486-function setHeaderStatus(header, silos) {
487- var silonames = Object.keys(silos);
488- var stats = { };
489- header.activeSilos = silonames.length;
490- header.lastUpdated = new Date();
491- header.foundSeries = {};
492- silonames.forEach(function(siloname) {
493- var status = silos[siloname].shortStatus;
494- var count = stats[status];
495- stats[status] = (count || 0) + 1;
496- var series = silos[siloname].displaySeries;
497- header.foundSeries[series] = series;
498- });
499- header.uniqueStats = Object.keys(stats).length;
500- var sortable = [];
501- for (var stat in stats) {
502- sortable.push([stat, stats[stat]]);
503- }
504- sortable.sort(function(a, b) { return b[1] - a[1]; });
505- header.statusStats = sortable;
506-}
507-
508-function blankSiloStatus(silos) {
509- return function(name) {
510- return function() {
511- delete silos[name];
512- }
513- }
514-}
515-
516-function setSiloStatus($http, silos, j) {
517- return function(data) {
518- var data = data || {};
519- var siloname = (data.siloname || '');
520- var global = data.global || {};
521- var series = global.series || '';
522- var dest = global.dest || '';
523- var mps = data.mps || {};
524- var requestid = data.requestid || 0;
525- var status = global.status || {};
526- var message = status.message || '';
527- var url = status.url || '';
528- var ppa = global.ppa || '';
529-
530- // Poll Bileto as well.
531- data.bileto = (silos[siloname] || {}).bileto || {};
532- $http.get(BILETO + requestid).success(setBiletoStatus(data));
533-
534- // Pre-compute some useful values for display in the template.
535- data.displaySeries = series.replace(/^.*\//, '');
536- data.displayDest = dest.split('/').slice(-1)[0];
537- data.displayLink = shortURL(global.status.url);
538- data.pkgNames = Object.keys(mps).sort();
539- data.shortStatus = nWords(message, 3, /\s/);
540- data.longStatus = message.split('. ').map(linkify).map(excusify);
541- data.statusColor = getStatusColor(data.shortStatus);
542-
543- // Discover what jenkins instance we are monitoring.
544- j.ppa_url = ppa.split('/').slice(0, 6).join('/')
545- .replace('api.', '').replace('/devel', '');
546- if (url.substring(0, 4) == 'http') {
547- j.domain = url.split('/').slice(0, 3).join('/');
548- }
549-
550- // Build a string containing most relevant info to easily search against
551- data.searchString = [ siloname, data.displaySeries, message ].join(' ');
552- function searchAppender(string) { data.searchString += ' ' + string; }
553- data.landers.forEach(searchAppender);
554- data.pkgNames.forEach(searchAppender);
555- data.sources.forEach(searchAppender);
556-
557- // Save the silo state for displaying in the template.
558- silos[siloname] = data;
559- }
560-}
561-
562-function setBiletoStatus(silo) {
563- return function(data) {
564- data.requests.forEach(function(req) {
565- req.color = BILETO_COLORS[(req.qa_signoff || '').toLowerCase()] || '';
566- silo.bileto = req;
567- });
568- }
569-}
570-
571-function svgPlaceX(i) {
572- return (i + 1) * 30;
573-}
574-
575-function appController($scope, $location, $http) {
576- $scope.silos = {};
577- $scope.silonames = [];
578- $scope.distros = {};
579- $scope.statusStats = {};
580- $scope.header = { uniqueStats: 0 };
581- $scope.linkify = linkify;
582-
583- $scope.lp_url = 'https://launchpad.net/';
584-
585- // Jenkins URL components
586- $scope.j = {
587- root: '/job/',
588- recon: '-0-reconfigure',
589- build: '-1-build',
590- publish: '-2-publish',
591- clean: '-3-merge-clean',
592- suffix: '/build?delay=0sec'
593- };
594-
595- // Handy links
596- $scope.links = [
597- ['Team', 'https://wiki.ubuntu.com/citrain/LandingTeam'],
598- ['Excuses', 'http://people.canonical.com/~ubuntu-archive/proposed-migration/update_excuses.html'],
599- ['SRU', 'http://people.canonical.com/~ubuntu-archive/pending-sru.html'],
600- ['Uploads', 'http://people.canonical.com/~ubuntu-archive/pending-sru.html#upload-queues'],
601- ['RAW', '/static/json/index.txt'],
602- ['Bileto', '/'],
603- ['Jenkins', 'https://ci-train.ubuntu.com/'],
604- ];
605-
606- $scope.footerLinks = [
607- ['Get the Source', 'http://bazaar.launchpad.net/+branch/bileto/changes'],
608- ['Run Unit Tests', 'test/index.html'],
609- ];
610-
611- $scope.getSiloColor = function(siloname) {
612- var req = $scope.silos[siloname] || {};
613- if (!req.requestid || !req.shortStatus) {
614- return 'black';
615- }
616- var siloColor = req.statusColor || 'black';
617- var bileto = req.bileto || {};
618- var qaColor = bileto.color || 'black';
619- if (req.shortStatus.match(/packages built/i)) {
620- return qaColor;
621- } else {
622- return siloColor;
623- }
624- };
625-
626- function getQuery() {
627- return $scope.q || '';
628- }
629-
630- function URLParams() {
631- return $location.search();
632- }
633-
634- function URLWatcher(queryParams) {
635- $scope.q = queryParams.q;
636- }
637-
638- function URLSetter(newQ) {
639- $location.search('q', newQ);
640- }
641-
642- $scope.$watch(URLParams, URLWatcher);
643- $scope.$watch('q', URLSetter);
644-
645- $scope.$watchCollection('silos', function() {
646- setHeaderStatus($scope.header, $scope.silos);
647- });
648-
649- function fetchSilo(name) {
650- $scope.distros[name.split(/\//)[0]] = true;
651- $http.get(ROOT + name + '?' + noCache())
652- .success(setSiloStatus($http, $scope.silos, $scope.j))
653- .error(blankSiloStatus($scope.silos)(name));
654- }
655-
656- function fetchAllSilos() {
657- // Abort refresh if nobody is looking.
658- if (document[hidden]) return;
659- $http.get(ROOT + 'index.txt' + '?' + noCache())
660- .success(function(data) {
661- $scope.silonames = data.trim().split(/\n/);
662- $scope.silonames.forEach(fetchSilo);
663- })
664- .error(function(data, status, headers, config) {
665- alert('Failed to fetch assigned silo index: ' + status);
666- });
667- }
668-
669- fetchAllSilos();
670- setInterval(fetchAllSilos, 2 * 60 * 1000);
671- if (visibilitychange) {
672- document.addEventListener(visibilitychange, fetchAllSilos);
673- }
674-
675- // Expose some functions to the scope for use in the template.
676- $scope.fixMergeURL = fixMergeURL;
677- $scope.getStatusColor = getStatusColor;
678- $scope.shortURL = shortURL;
679- $scope.siloSearchFilter = searchFactory(getQuery, $scope.silos);
680- $scope.svgPlaceX = svgPlaceX;
681-}
682
683=== removed directory 'tickets/static/test'
684=== removed file 'tickets/static/test/index.html'
685--- tickets/static/test/index.html 2015-06-18 20:45:11 +0000
686+++ tickets/static/test/index.html 1970-01-01 00:00:00 +0000
687@@ -1,16 +0,0 @@
688-<!DOCTYPE html>
689-<html>
690-<head>
691-<meta charset="utf-8" />
692-<title>Unit Tests for CI Train Silo Dashboard</title>
693-<link rel="shortcut icon" type="image/png" href="https://cdnjs.cloudflare.com/ajax/libs/jasmine/2.0.0/jasmine_favicon.png">
694-<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/jasmine/2.0.0/jasmine.css">
695-
696-<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jasmine/2.0.0/jasmine.js"></script>
697-<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jasmine/2.0.0/jasmine-html.js"></script>
698-<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jasmine/2.0.0/boot.js"></script>
699-<script src="jenkins_silo_mock.js" charset="utf-8"></script>
700-<script src="../dashboard.js" charset="utf-8"></script>
701-<script src="test.js" charset="utf-8"></script>
702-</head>
703-</html>
704
705=== removed file 'tickets/static/test/jenkins_silo_mock.js'
706--- tickets/static/test/jenkins_silo_mock.js 2015-06-19 17:12:06 +0000
707+++ tickets/static/test/jenkins_silo_mock.js 1970-01-01 00:00:00 +0000
708@@ -1,1 +0,0 @@
709-var JENKINS_SILO_MOCK = {"siloname": "landing-008", "global": {"dest": "https://api.launchpad.net/devel/ubuntu/+archive/primary", "series": "https://api.launchpad.net/devel/ubuntu/utopic", "step": 1, "status": {"url": "https://ci-train.ubuntu.com/some/status/stuff", "message": "Packages built", "ping": true, "state": 3}, "ppa": "https://api.launchpad.net/devel/~ci-train-ppa-service/+archive/ubuntu/landing-008"}, "sources": [], "landers": ["tvoss", "slangasek"], "requestid": "1403854490728", "mps": {"indicator-datetime": ["https://api.launchpad.net/devel/~charlesk/indicator-datetime/make-gcc-version-explicit/+merge/224467"], "mediascanner2": ["https://api.launchpad.net/devel/~jpakkane/mediascanner2/gcc49/+merge/224648"], "unity-api": ["https://api.launchpad.net/devel/~unity-team/unity-api/require-g++-4.9/+merge/227299"], "process-cpp": ["https://api.launchpad.net/devel/~vorlon/process-cpp/explicit-gcc-version/+merge/226594"], "pay-service": ["https://api.launchpad.net/devel/~thomas-voss/pay-service/explicit-gcc-version/+merge/224763"], "dbus-cpp": ["https://api.launchpad.net/devel/~thomas-voss/dbus-cpp/explicit-gcc-version/+merge/224756"], "indicator-transfer": ["https://api.launchpad.net/devel/~charlesk/indicator-transfer/make-gcc-version-explicit/+merge/224473"], "unity-scopes-api": ["https://api.launchpad.net/devel/~unity-team/unity-scopes-api/require-g++-4.9-take-2/+merge/227391"], "trust-store": ["https://api.launchpad.net/devel/~thomas-voss/trust-store/explicit-gcc-version/+merge/224769"], "indicator-location": ["https://api.launchpad.net/devel/~charlesk/indicator-location/make-gcc-version-explicit/+merge/224474"], "platform-api": ["https://api.launchpad.net/devel/~vorlon/platform-api/explicit-gcc-version/+merge/226566"], "location-service": ["https://api.launchpad.net/devel/~thomas-voss/location-service/explicit-gcc-version/+merge/224776"], "unity-mir": ["https://api.launchpad.net/devel/~thomas-voss/unity-mir/explicit-gcc-version/+merge/224771"], "media-hub": ["https://api.launchpad.net/devel/~vorlon/media-hub/explicit-gcc-version/+merge/226567"], "net-cpp": ["https://api.launchpad.net/devel/~thomas-voss/net-cpp/explicit-gcc-version/+merge/224760"], "unity-scope-mediascanner": ["https://api.launchpad.net/devel/~jpakkane/unity-scope-mediascanner/gcc49/+merge/224650"]}};
710
711=== removed file 'tickets/static/test/test.js'
712--- tickets/static/test/test.js 2015-08-04 06:45:24 +0000
713+++ tickets/static/test/test.js 1970-01-01 00:00:00 +0000
714@@ -1,426 +0,0 @@
715-if (visibilitychange) {
716- describe('The page visibility API', function() {
717- it('should be correctly detected.', function() {
718- expect(hidden in document).toBe(true);
719- });
720- });
721-}
722-
723-describe('The getStatusColor functon', function() {
724- it('should know what statuses are red.', function() {
725- expect(getStatusColor('Build failed: incorrect planetary alignment.'))
726- .toBe('red');
727- expect(getStatusColor("Can't build: I don't like you."))
728- .toBe('red');
729- });
730- it('should know what statuses are green.', function() {
731- expect(getStatusColor('Gave up on life.'))
732- .toBe('green');
733- expect(getStatusColor('Landed: Cleaning out your bank account.'))
734- .toBe('green');
735- });
736- it('should know what statuses are blue.', function() {
737- expect(getStatusColor('Migration: All birds have flown home.'))
738- .toBe('blue');
739- expect(getStatusColor('Publication needs: a swift kick in the pants.'))
740- .toBe('blue');
741- });
742- it('should know what statuses are purple.', function() {
743- expect(getStatusColor('Silo ready to be filled with grain.'))
744- .toBe('purple');
745- expect(getStatusColor('Free'))
746- .toBe('purple');
747- });
748- it('should be case insensitive.', function() {
749- expect(getStatusColor('BUILD fAiled')).toBe('red');
750- expect(getStatusColor('miGRATion all')).toBe('blue');
751- });
752-});
753-
754-describe('The linkify function', function() {
755- it('should linkify URLs.', function() {
756- expect(linkify('http://example.com'))
757- .toBe('<a target="_blank" href="http://example.com">example.com</a>');
758- expect(linkify('http://example.com/foo'))
759- .toBe('<a target="_blank" href="http://example.com/foo">foo</a>');
760- });
761- it('should handle multiple URLs.', function() {
762- expect(linkify('Visit http://example.com today, but avoid http://example.com/badness at all costs.'))
763- .toBe('Visit <a target="_blank" href="http://example.com">example.com</a> today, but avoid ' +
764- '<a target="_blank" href="http://example.com/badness">badness</a> at all costs.');
765- });
766- it('should not touch ordinary strings.', function() {
767- expect(linkify('No URL here!')).toBe('No URL here!');
768- });
769- it('should not linkify parenthesis.', function() {
770- expect(linkify('(http://example.com)'))
771- .toBe('(<a target="_blank" href="http://example.com">example.com</a>)');
772- });
773-});
774-
775-describe('The excusify function', function() {
776- it('should link to excuses directly.', function() {
777- expect(excusify('')).toBe('');
778- expect(excusify('whargarble is in the Proposed pocket'))
779- .toBe('<a target="_blank" href="http://people.canonical.com/~ubuntu-archive/'
780- + 'proposed-migration/update_excuses.html#whargarble">whargarble</a>'
781- + ' is in the Proposed pocket');
782- });
783-});
784-
785-describe('The fixMergeURL function', function() {
786- it('should link to the right place.', function() {
787- expect(fixMergeURL('https://api.launchpad.net/devel/foobar'))
788- .toBe('https://code.launchpad.net/foobar');
789- });
790- it('should not harm other URLs.', function() {
791- expect(fixMergeURL('http://example.com'))
792- .toBe('http://example.com');
793- });
794-});
795-
796-describe('The shortURL function', function() {
797- it('should drop the protocol and domain.', function() {
798- expect(shortURL('http://example.com/foo')).toBe('foo');
799- expect(shortURL('http://example.com/bar/baz')).toBe('bar/baz');
800- });
801- it('should extract branch names from merges.', function() {
802- expect(shortURL('https://code.launchpad.net/~vorlon/platform-api/explicit-gcc-version/+merge/226566'))
803- .toBe('explicit gcc version');
804- expect(shortURL('https://code.launchpad.net/~compiz-team/compiz/compiz-0.9.11.2-version-bump/+merge/226511'))
805- .toBe('compiz 0.9.11.2 version bump');
806- });
807- it('should convert underscores to spaces.', function() {
808- expect(shortURL('well_hello_there-handsome')).toBe('well hello there handsome');
809- });
810-});
811-
812-describe('The noCache function', function() {
813- it('should return some numbers.', function() {
814- expect(noCache()).toMatch(/^nocache=\d+$/);
815- });
816-});
817-
818-describe('The nWords function', function() {
819- it('should ignore punctuation by default.', function() {
820- expect(nWords('Ready? Set? Go!', 1)).toBe('Ready');
821- });
822- it('should let you override its regex.', function() {
823- expect(nWords('Ready? Set? Go!', 1, /\s/)).toBe('Ready?');
824- });
825- it('should return the right number of words.', function() {
826- var example = 'One two three four';
827- expect(nWords(example, 0)).toBe('');
828- expect(nWords(example, 1)).toBe('One');
829- expect(nWords(example, 2)).toBe('One two');
830- expect(nWords(example, 3)).toBe('One two three');
831- expect(nWords(example, 4)).toBe('One two three four');
832- expect(nWords(example, 5)).toBe('One two three four');
833- });
834-});
835-
836-describe('The searchFactory function', function() {
837- var silos = {
838- 'landing-015': {
839- searchString: 'foo bar baz'
840- },
841- 'landing-010': {
842- searchString: 'apple banana orange'
843- },
844- };
845- function getQuery(q) {
846- return function() {
847- return q;
848- }
849- }
850- it('should match only silo 1 when we search for silo 1.', function() {
851- var search = searchFactory(getQuery('landing-001'), silos);
852- expect(search('landing-001')).toBe(true);
853- expect(search('landing-002')).toBe(false);
854- var search = searchFactory(getQuery('001'), silos);
855- expect(search('landing-001')).toBe(true);
856- expect(search('landing-020')).toBe(false);
857- });
858- it('should match the silos that have the stuff we are looking for.', function() {
859- var search = searchFactory(getQuery('foo'), silos);
860- expect(search('landing-003')).toBe(false); // false because it's empty
861- expect(search('landing-010')).toBe(false); // false because it doesn't match
862- expect(search('landing-015')).toBe(true);
863- var search = searchFactory(getQuery('grill'), silos);
864- expect(search('landing-003')).toBe(false); // false because it's empty
865- expect(search('landing-010')).toBe(false); // false because it doesn't match
866- expect(search('landing-015')).toBe(false);
867- });
868- it('should be able to find free silos.', function() {
869- var search = searchFactory(getQuery('Free'), silos);
870- expect(search('landing-004')).toBe(true);
871- expect(search('landing-015')).toBe(false);
872- var search = searchFactory(getQuery('free'), silos);
873- expect(search('landing-004')).toBe(true);
874- expect(search('landing-015')).toBe(false);
875- });
876- it('should show all silos when there is no search term.', function() {
877- var search = searchFactory(getQuery(''), silos);
878- expect(search('landing-001')).toBe(true); // true even though empty
879- expect(search('landing-010')).toBe(true); // not empty, still true
880- });
881- it('should allow regex search terms.', function() {
882- var search = searchFactory(getQuery('foo|apple'), silos);
883- expect(search('landing-010')).toBe(true);
884- expect(search('landing-015')).toBe(true);
885- expect(search('landing-011')).toBe(false);
886- });
887-});
888-
889-describe('The setHeaderStatus function', function() {
890- var silos = {
891- 'landing-001': { shortStatus: 'Hello' },
892- 'landing-003': { shortStatus: 'Goodbye' },
893- 'landing-005': { shortStatus: 'Error. Error.' },
894- 'landing-007': { shortStatus: 'Error. Error.' },
895- };
896- it('should calculate header stats accurately.', function() {
897- var header = {};
898- setHeaderStatus(header, silos);
899- expect(header.activeSilos).toBe(4);
900- expect(header.lastUpdated instanceof Date).toBe(true);
901- expect(header.statusStats).toEqual(
902- [['Error. Error.', 2], ['Hello', 1], ['Goodbye', 1]]);
903- });
904-});
905-
906-describe('The blankSiloStatus function', function() {
907- var silos = {
908- 'landing-001': {},
909- 'landing-007': {},
910- 'landing-020': {},
911- };
912- var spreadsheet = silos;
913- it('should delete stale statuses.', function() {
914- expect(Object.keys(silos).length).toBe(3);
915- blankSiloStatus(silos, spreadsheet)('landing-002')();
916- expect(Object.keys(silos).length).toBe(3);
917- blankSiloStatus(silos, spreadsheet)('landing-001')();
918- expect(Object.keys(silos).length).toBe(2);
919- blankSiloStatus(silos, spreadsheet)('landing-007')();
920- expect(Object.keys(silos).length).toBe(1);
921- blankSiloStatus(silos, spreadsheet)('landing-020')();
922- expect(Object.keys(silos).length).toBe(0);
923- blankSiloStatus(silos, spreadsheet)('landing-020')();
924- expect(Object.keys(silos).length).toBe(0);
925- });
926-});
927-
928-describe('The setSiloStatus function', function() {
929- var httpMock = {
930- 'get': function () {
931- return {
932- success: function() { return this },
933- error: function() { return this }
934- }
935- }
936- };
937- var silos = {};
938- var j = {};
939- it('should precompute a bunch of stuff.', function() {
940- spyOn(httpMock, 'get').and.callThrough();
941- setSiloStatus(httpMock, silos, j)(JENKINS_SILO_MOCK);
942- var expected = {
943- 'siloname': 'landing-008',
944- 'global': {
945- 'dest': 'https://api.launchpad.net/devel/ubuntu/+archive/primary',
946- 'series': 'https://api.launchpad.net/devel/ubuntu/utopic',
947- 'step': 1,
948- 'status': {
949- 'url': 'https://ci-train.ubuntu.com/some/status/stuff',
950- 'message': 'Packages built',
951- 'ping': true,
952- 'state': 3
953- },
954- 'ppa': 'https://api.launchpad.net/devel/~ci-train-ppa-service/+archive/ubuntu/landing-008'
955- },
956- 'sources': [],
957- 'landers': [
958- 'tvoss',
959- 'slangasek'
960- ],
961- 'requestid': '1403854490728',
962- 'mps': {
963- 'indicator-datetime': [
964- 'https://api.launchpad.net/devel/~charlesk/indicator-datetime/make-gcc-version-explicit/+merge/224467'
965- ],
966- 'mediascanner2': [
967- 'https://api.launchpad.net/devel/~jpakkane/mediascanner2/gcc49/+merge/224648'
968- ],
969- 'unity-api': [
970- 'https://api.launchpad.net/devel/~unity-team/unity-api/require-g++-4.9/+merge/227299'
971- ],
972- 'process-cpp': [
973- 'https://api.launchpad.net/devel/~vorlon/process-cpp/explicit-gcc-version/+merge/226594'
974- ],
975- 'pay-service': [
976- 'https://api.launchpad.net/devel/~thomas-voss/pay-service/explicit-gcc-version/+merge/224763'
977- ],
978- 'dbus-cpp': [
979- 'https://api.launchpad.net/devel/~thomas-voss/dbus-cpp/explicit-gcc-version/+merge/224756'
980- ],
981- 'indicator-transfer': [
982- 'https://api.launchpad.net/devel/~charlesk/indicator-transfer/make-gcc-version-explicit/+merge/224473'
983- ],
984- 'unity-scopes-api': [
985- 'https://api.launchpad.net/devel/~unity-team/unity-scopes-api/require-g++-4.9-take-2/+merge/227391'
986- ],
987- 'trust-store': [
988- 'https://api.launchpad.net/devel/~thomas-voss/trust-store/explicit-gcc-version/+merge/224769'
989- ],
990- 'indicator-location': [
991- 'https://api.launchpad.net/devel/~charlesk/indicator-location/make-gcc-version-explicit/+merge/224474'
992- ],
993- 'platform-api': [
994- 'https://api.launchpad.net/devel/~vorlon/platform-api/explicit-gcc-version/+merge/226566'
995- ],
996- 'location-service': [
997- 'https://api.launchpad.net/devel/~thomas-voss/location-service/explicit-gcc-version/+merge/224776'
998- ],
999- 'unity-mir': [
1000- 'https://api.launchpad.net/devel/~thomas-voss/unity-mir/explicit-gcc-version/+merge/224771'
1001- ],
1002- 'media-hub': [
1003- 'https://api.launchpad.net/devel/~vorlon/media-hub/explicit-gcc-version/+merge/226567'
1004- ],
1005- 'net-cpp': [
1006- 'https://api.launchpad.net/devel/~thomas-voss/net-cpp/explicit-gcc-version/+merge/224760'
1007- ],
1008- 'unity-scope-mediascanner': [
1009- 'https://api.launchpad.net/devel/~jpakkane/unity-scope-mediascanner/gcc49/+merge/224650'
1010- ]
1011- },
1012- 'displaySeries': 'utopic',
1013- 'displayLink': 'some/status/stuff',
1014- 'pkgNames': [
1015- 'dbus-cpp',
1016- 'indicator-datetime',
1017- 'indicator-location',
1018- 'indicator-transfer',
1019- 'location-service',
1020- 'media-hub',
1021- 'mediascanner2',
1022- 'net-cpp',
1023- 'pay-service',
1024- 'platform-api',
1025- 'process-cpp',
1026- 'trust-store',
1027- 'unity-api',
1028- 'unity-mir',
1029- 'unity-scope-mediascanner',
1030- 'unity-scopes-api'
1031- ],
1032- 'shortStatus': 'Packages built',
1033- 'longStatus': ['Packages built'],
1034- 'statusColor': 'black',
1035- 'searchString': 'landing-008 utopic Packages built tvoss slangasek dbus-cpp indicator-datetime indicator-location indicator-transfer location-service media-hub mediascanner2 net-cpp pay-service platform-api process-cpp trust-store unity-api unity-mir unity-scope-mediascanner unity-scopes-api'
1036- };
1037- expect(j.ppa_url).toEqual('https://launchpad.net/~ci-train-ppa-service/+archive');
1038- expect(j.domain).toEqual('https://ci-train.ubuntu.com');
1039- for (attr in expected) {
1040- expect(silos['landing-008'][attr]).toEqual(expected[attr]);
1041- }
1042- expect(httpMock.get).toHaveBeenCalled();
1043- var getCalls = httpMock.get.calls.all();
1044- expect(getCalls.length).toBe(1);
1045- expect(getCalls[0].args).toEqual(['/v1/ticket/1403854490728']);
1046- });
1047-});
1048-
1049-describe('The svgPlaceX function', function() {
1050- it('should do some math.', function() {
1051- expect(svgPlaceX(0)).toBe(30);
1052- expect(svgPlaceX(1)).toBe(60);
1053- expect(svgPlaceX(2)).toBe(90);
1054- expect(svgPlaceX(3)).toBe(120);
1055- expect(svgPlaceX(4)).toBe(150);
1056- expect(svgPlaceX(5)).toBe(180);
1057- expect(svgPlaceX(6)).toBe(210);
1058- expect(svgPlaceX(7)).toBe(240);
1059- expect(svgPlaceX(8)).toBe(270);
1060- expect(svgPlaceX(9)).toBe(300);
1061- });
1062-});
1063-
1064-describe('The appController function', function() {
1065- var scopeMock = {
1066- '$watch': function() {},
1067- '$watchCollection': function () {},
1068- };
1069- var locationMock = {};
1070- var httpMock = {
1071- 'get': function () {
1072- return {
1073- success: function() { return this },
1074- error: function() { return this }
1075- }
1076- }
1077- };
1078- httpMock.jsonp = httpMock.get;
1079- it('should initialize some data structures.', function() {
1080- appController(scopeMock, locationMock, httpMock);
1081- expect(scopeMock.silos).toBeDefined();
1082- expect(scopeMock.statusStats).toBeDefined();
1083- expect(scopeMock.header.uniqueStats).toBe(0);
1084- expect(scopeMock.silonames).toEqual([]);
1085- });
1086- it('should know about jenkins.', function() {
1087- appController(scopeMock, locationMock, httpMock);
1088- expect(scopeMock.j.root).toBe('/job/');
1089- expect(scopeMock.j.recon).toBe('-0-reconfigure');
1090- expect(scopeMock.j.build).toBe('-1-build');
1091- expect(scopeMock.j.suffix).toBe('/build?delay=0sec');
1092- });
1093- it('should have some handy links for the header and footer bars.', function() {
1094- appController(scopeMock, locationMock, httpMock);
1095- expect(scopeMock.links).toBeDefined();
1096- expect(scopeMock.links.length).toBeGreaterThan(5);
1097- expect(scopeMock.footerLinks).toBeDefined();
1098- expect(scopeMock.footerLinks.length).toBeGreaterThan(1);
1099- });
1100- it('should be able to determine silo coloring.', function() {
1101- appController(scopeMock, locationMock, httpMock);
1102- expect(scopeMock.getSiloColor).toBeDefined();
1103- expect(scopeMock.getSiloColor('landing-001')).toBe('black');
1104- expect(scopeMock.getSiloColor('landing-002')).toBe('black');
1105- });
1106- it('should expose some functions to the template scope.', function() {
1107- expect(scopeMock.fixMergeURL).toBe(fixMergeURL);
1108- expect(scopeMock.getStatusColor).toBe(getStatusColor);
1109- expect(scopeMock.shortURL).toBe(shortURL);
1110- expect(scopeMock.svgPlaceX).toBe(svgPlaceX);
1111- expect(scopeMock.siloSearchFilter).toBeDefined();
1112- });
1113- it('should call $watch twice.', function() {
1114- spyOn(scopeMock, '$watch');
1115- appController(scopeMock, locationMock, httpMock);
1116- expect(scopeMock.$watch).toHaveBeenCalled();
1117- var watchCalls = scopeMock.$watch.calls.all();
1118- expect(watchCalls[0].args[0].name).toBe('URLParams');
1119- expect(watchCalls[0].args[1].name).toBe('URLWatcher');
1120- expect(watchCalls[1].args[0]).toBe('q');
1121- expect(watchCalls[1].args[1].name).toBe('URLSetter');
1122- });
1123- it('should call $watchCollection.', function() {
1124- spyOn(scopeMock, '$watchCollection');
1125- appController(scopeMock, locationMock, httpMock);
1126- expect(scopeMock.$watchCollection).toHaveBeenCalled();
1127- var calls = scopeMock.$watchCollection.calls.all();
1128- expect(calls[0].args[0]).toBe('silos');
1129- expect(calls[0].args[1] instanceof Function).toBe(true);
1130- });
1131- it('should call $http methods.', function() {
1132- spyOn(httpMock, 'get').and.callThrough();
1133- spyOn(httpMock, 'jsonp').and.callThrough();
1134- appController(scopeMock, locationMock, httpMock);
1135- expect(httpMock.get).toHaveBeenCalled();
1136- var getCalls = httpMock.get.calls.all();
1137- expect(getCalls.length).toBe(1);
1138- expect(getCalls[0].args[0]).toMatch('^/static/json/index.txt\\?nocache=');
1139- });
1140-});
1141
1142=== modified file 'tickets/v1.py'
1143--- tickets/v1.py 2015-08-28 04:38:56 +0000
1144+++ tickets/v1.py 2015-08-28 20:11:22 +0000
1145@@ -13,7 +13,7 @@
1146 /v1/errors: Dump error log (authenticated users only).
1147 """
1148
1149-from flask import abort, jsonify, request, session
1150+from flask import abort, jsonify, redirect, request, session
1151 from urllib.parse import urlencode
1152 from sqlalchemy import func, desc
1153 from contextlib import suppress
1154@@ -77,6 +77,12 @@
1155 return app.send_static_file('index.html')
1156
1157
1158+@app.route('/static/dashboard.html', methods=['GET'])
1159+def dashboard_redirect():
1160+ """Redirect to the front page."""
1161+ return redirect('/')
1162+
1163+
1164 @app.route('/v1/tickets', methods=['POST'])
1165 def create_or_update():
1166 """Inject given JSON into db; either creating or updating a record."""

Subscribers

People subscribed via source and target branches