Krinkle has uploaded a new change for review. https://gerrit.wikimedia.org/r/57899
Change subject: Zuul status page: Rewrite (bug fixes, bootstrap markup, redesign) ...................................................................... Zuul status page: Rewrite (bug fixes, bootstrap markup, redesign) Bug fixes: * Fix race condition where JS will start another update while the previous one has not ended yet (e.g. timeout or slow server) by exposing the Promise object from $.ajax and scheduling the next update asynchronously from the complete callback instead of directly after calling update(); * Added an error handler for the ajax call. Previously an error would be silently ignored and old data removed. Now the old data stays on the page and an error message is added on top. Enhancements: * Add sample JSON files for local testing (triggered by adding ?demo=basic or ?demo=openstack). * Only re-parse the DOM if there was a change. Cleanup: * Prefix class names with 'zuul-'. * Remove unused css code (presumably copied from openstack). * Simplified logic result class determination. Redesign: * The page is dimmed until the first update is ready. This makes for a smoother experience by not having the page redraw a couple times but instead present it as a whole. * The "message" contaitainer now slides in/out when it activates or deactivates. * Use Bootstrap markup and classes for the overal design. Added a few extra classes to better fit the content (not all scenarios are covered best with the default bootstrap css). * Using a nav-list construction for the job lists. This means the links occupy the list as a block-level element, making the entire row clickable instead of just the text at the beginning of the line. * Use lowercase labels instead of uppercase for the job result. * Enable the footer of the integration-wide template class. * Move WMF logo to the left and center it vertically with the page heading. Also swapped it for the version without the "Foundation" subtitle. * Change (ellipsis-ified) queue title from <a> to <abbr>. Having an anchor tag with a title looks weird as it has no href, <abbr> seems like the better fit here. Change-Id: Ifa93f856f29e1a2fe7b27e0b5a9d7e43935d8acc --- M org/wikimedia/integration/zuul/index.php A org/wikimedia/integration/zuul/sample-status-basic.json A org/wikimedia/integration/zuul/sample-status-openstack.json M org/wikimedia/integration/zuul/status.js M shared/IntegrationPage.php 5 files changed, 700 insertions(+), 154 deletions(-) git pull ssh://gerrit.wikimedia.org:29418/integration/docroot refs/changes/99/57899/1 diff --git a/org/wikimedia/integration/zuul/index.php b/org/wikimedia/integration/zuul/index.php index 0987bae..953bab1 100644 --- a/org/wikimedia/integration/zuul/index.php +++ b/org/wikimedia/integration/zuul/index.php @@ -1,7 +1,7 @@ <?php require_once( __DIR__ . '/../../../../shared/IntegrationPage.php' ); -$p = IntegrationPage::newFromPageName( 'Zuul status page' ); +$p = IntegrationPage::newFromPageName( 'Zuul Status' ); $p->setDir( __DIR__ ); $p->setRootDir( dirname( __DIR__ ) ); @@ -9,93 +9,103 @@ /** * Zuul status */ -.change { - border: 1px solid #95c7db; - margin-top: 10px; - padding: 2px; +.zuul-container { + transition-property: opacity, background-color; + transition-duration: 1s; + transition-timing-function: ease-in-out; + clear: both; + opacity: 0; + cursor: progress; + min-height: 200px; + background-color: #f8ffaa; } -.change > .header { - background: #E2ECEF; - color: black; - margin: -2px -2px 2px -2px; - padding: 4px; +.zuul-container-ready { + opacity: 1; + cursor: auto; + background-color: #fff; } -.change > .header > .changeid { - float: right; +.zuul-spinner { + opacity: 0; + transition: opacity 6s ease-in-out; + cursor: default; +} +.zuul-spinner-on { + opacity: 1; + transition-duration: 0.5s; + cursor: progress; } -.job { - display: block; -} - -.pipeline { - float: left; - width: 25em; - padding: 4px; -} - -.pipeline > .header { - background: #0000cc; - color: white; -} - -.arrow { +.zuul-change-arrow { text-align: center; font-size: 16pt; line-height: 1.0; } -.result_success { - color: #007f00; +.zuul-change-id { + text-transform: none; + float: right; } -.result_failure { - color: #cf2f19; +.zuul-change-job a { + overflow: auto; } -.result_unstable { - color: #e39f00; +.zuul-result { + text-shadow: none; + font-weight: normal; + float: right; + background-color: #E9E9E9; + color: #555; } -a:link { - color: #204A87; +.zuul-result.label-success { + background-color: #CDF0CD; + color: #468847; } -#message p { +.zuul-result.label-important { + background-color: #F1DBDA; + color: #B94A48; +} + +.zuul-result.label-warning { + background-color: #F3E6D4; + color: #F89406; +} + +.zuul-msg-wrap { + max-height: 150px; + overflow: hidden; +} + +.zuul-container-ready .zuul-msg-wrap { + transition: max-height 1s ease-in; +} + +.zuul-msg-wrap-off { + max-height: 0; +} + +.zuul-msg p { margin: 0; -} - -.alertbox { - border: 1px solid #e5574d; - background: #ffaba5; - color: black; - padding: 1em; - font-size: 12pt; - margin: 0pt; } '); $p->addHtmlContent(' -<p>This is the status page for the Zuul daemon on Wikimedia infrastructure.</p> +<p>Real-time status monitor of Zuul, the pipeline manager between Gerrit and Jenkins. <a href="https://www.mediawiki.org/wiki/Continuous_integration/Zuul">more info »</a></p> -<p>Queue lengths: <span id="trigger_event_queue_length"></span> events, -<span id="result_event_queue_length"></span> results.</p> - -<div class="container"> - <div id="message"></div> -</div> - -<div id="pipeline-container"></div> - -<!-- TODO: add graphite -<div class="container" id="graph-container"> +<div class="zuul-container" id="zuul-container"> + <p>Queue lengths: <span id="zuul-eventqueue-length">..</span> events, <span id="zuul-resulteventqueue-length">..</span> results.</p> + <div id="zuul-pipelines" class="row"></div> + <!-- TODO: add graphite <h2>Job statistics</h2> + --> </div> ---> '); +$p->enableFooter(); $p->addScript( 'status.js' ); $p->flush(); diff --git a/org/wikimedia/integration/zuul/sample-status-basic.json b/org/wikimedia/integration/zuul/sample-status-basic.json new file mode 100644 index 0000000..0994eac --- /dev/null +++ b/org/wikimedia/integration/zuul/sample-status-basic.json @@ -0,0 +1,164 @@ +{ + "message": "Example error message", + "pipelines": [ + { + "name": "test", + "description": "Lint and unit tests", + "change_queues": [ + { + "name": "some-j...@worker301.ci-example.org", + "heads": [ + [ + { + "id": "10101,1", + "url": "#!/review.example.org/r/10101", + "project": "openstack/infra/zuul", + "jobs": [ + { + "name": "zuul-merge", + "url": "#!/jenkins.example.org/job/zuul-merge/201", + "result": "SUCCESS", + "voting": true + }, + { + "name": "zuul-lint", + "url": "#!/jenkins.example.org/job/zuul-lint/201", + "result": "SUCCESS", + "voting": true + }, + { + "name": "zuul-test", + "url": "#!/jenkins.example.org/job/zuul-test/201", + "result": "SUCCESS", + "voting": true + } + ] + } + ], + [ + { + "id": "10103,1", + "url": "#!/review.example.org/r/10103", + "project": "google/gerrit", + "jobs": [ + { + "name": "gerrit-merge", + "url": "#!/jenkins.example.org/job/gerrit-merge/203", + "result": "SUCCESS", + "voting": false + } + ] + } + ] + ] + }, + { + "name": "other-j...@worker301.ci-example.org", + "heads": [ + [ + { + "id": "10102,1", + "url": "#!/review.example.org/r/10102", + "project": "google/gerrit", + "jobs": [ + { + "name": "gerrit-merge", + "url": "#!/jenkins.example.org/job/gerrit-merge/202", + "result": "UNSTABLE", + "voting": false + } + ] + } + ], + [ + { + "id": "10104,1", + "url": "#!/review.example.org/r/10104", + "project": "openstack/infra/zuul", + "jobs": [ + { + "name": "zuul-merge", + "url": "#!/jenkins.example.org/job/zuul-merge/204", + "result": "SUCCESS", + "voting": true + }, + { + "name": "zuul-lint", + "url": "#!/jenkins.example.org/job/zuul-lint/204", + "result": "FAILURE", + "voting": true + }, + { + "name": "zuul-test", + "url": "#!/jenkins.example.org/job/zuul-test/204", + "result": null, + "voting": true + } + ] + } + ] + ] + } + ] + }, + { + "name": "gate-and-submit", + "change_queues": [] + }, + { + "name": "postmerge", + "change_queues": [ + { + "name": "some-j...@worker301.ci-example.org", + "heads": [ + [ + { + "id": "7f1d65cb0f663c907698f915da01c008c7ef4748", + "url": "#!/review.example.org/r/10100", + "project": "openstack/infra/zuul", + "jobs": [ + { + "name": "zuul-lint", + "url": "#!/jenkins.example.org/job/zuul-lint/200", + "result": "SUCCESS", + "voting": true + }, + { + "name": "zuul-test", + "url": "#!/jenkins.example.org/job/zuul-test/200", + "result": "SUCCESS", + "voting": true + }, + { + "name": "zuul-regression", + "url": "#!/jenkins.example.org/job/zuul-regression/200", + "result": "FAILURE", + "voting": true + }, + { + "name": "zuul-performance", + "url": "#!/jenkins.example.org/job/zuul-performance/200", + "result": null, + "voting": false + }, + { + "name": "zuul-docs-publish", + "url": null, + "result": null, + "voting": true + } + ] + } + ] + ] + } + ] + } + ], + "trigger_event_queue": { + "length": 0 + }, + "result_event_queue": { + "length": 0 + } +} diff --git a/org/wikimedia/integration/zuul/sample-status-openstack.json b/org/wikimedia/integration/zuul/sample-status-openstack.json new file mode 100644 index 0000000..5d16bdd --- /dev/null +++ b/org/wikimedia/integration/zuul/sample-status-openstack.json @@ -0,0 +1,310 @@ +{ + "pipelines": [ + { + "name": "check", + "description": "Newly uploaded patchsets enter this pipeline to receive an initial +/-1 Verified vote from Jenkins.", + "change_queues": [ + { + "heads": [], + "name": "stackforge/tripleo-image-elements" + } + ] + }, + { + "description": "Changes that have been approved by core developers are enqueued in order in this pipeline, and .", + "change_queues": [ + { + "heads": [], + "name": "openstack-detackforge/reddwarf-integration" + }, + { + "heads": [], + "name": "stackforge/moniker" + }, + { + "heads": [ + [ + { + "url": "https://review.openstack.org/26292", + "project": "openstack/quantum", + "jobs": [ + { + "url": "https://jenkins.openstack.org/job/gate-quantum-docs/5501/consoleFull", + "voting": true, + "result": "SUCCESS", + "name": "gate-quantum-docs" + }, + { + "url": "https://jenkins.openstack.org/job/gate-quantum-pep8/6254/consoleFull", + "voting": true, + "result": "SUCCESS", + "name": "gate-quantum-pep8" + }, + { + "url": "https://jenkins.openstack.org/job/gate-quantum-python26/5876/", + "voting": true, + "result": null, + "name": "gate-quantum-python26" + }, + { + "url": "https://jenkins.openstack.org/job/gate-quantum-python27/5887/", + "voting": true, + "result": null, + "name": "gate-quantum-python27" + }, + { + "url": "https://jenkins.openstack.org/job/gate-tempest-devstack-vm-quantum/17548/", + "voting": true, + "result": null, + "name": "gate-tempest-devstack-vm-quantum" + } + ], + "id": "26292,1" + } + ] + ], + "name": "openstack-dev/devstack, openstack-infra/devstack-gate, openstack/cinder, openstack/glance, openstack/horizon, openstack/keystone, openstack/nova, openstack/python-cinderclient, openstack/python-glanceclient, openstack/python-keystoneclient, openstack/python-novaclient, openstack/python-quantumclient, openstack/quantum, openstack/swift, openstack/tempest, z/tempest" + }, + { + "heads": [], + "name": "openstack/ceilometer" + }, + { + "heads": [], + "name": "stackforge/puppet-openstack" + }, + { + "heads": [], + "name": "stackforge/puppet-cinder" + }, + { + "heads": [], + "name": "stackforge/marconi" + }, + { + "heads": [], + "name": "openstack-infra/config" + }, + { + "heads": [], + "name": "stackforge/tripleo-image-elements" + }, + { + "heads": [], + "name": "stackforge/kwapi" + }, + { + "heads": [], + "name": "stackforge/python-reddwarfclient" + }, + { + "heads": [], + "name": "stackforge/python-savannaclient" + }, + { + "heads": [], + "name": "stackforge/python-monikerclient" + }, + { + "heads": [], + "name": "stackforge/packstack" + }, + { + "heads": [], + "name": "openstack/oslo.config" + }, + { + "heads": [], + "name": "openstack-infra/jenkins-job-builder" + }, + { + "heads": [], + "name": "stackforge/puppet-horizon" + }, + { + "heads": [], + "name": "openstack/heat-cfntools" + }, + { + "heads": [], + "name": "openstack/oslo-incubator" + }, + { + "heads": [], + "name": "stackforge/os-config-applier" + }, + { + "heads": [], + "name": "openstack/requirements" + }, + { + "heads": [], + "name": "stackforge/puppet-glance" + }, + { + "heads": [], + "name": "openstack-infra/gearman-plugin" + }, + { + "heads": [], + "name": "stackforge/puppet-keystone" + }, + { + "heads": [], + "name": "stackforge/puppet-nova" + }, + { + "heads": [], + "name": "stackforge/climate" + }, + { + "heads": [], + "name": "openstack/python-swiftclient" + }, + { + "heads": [], + "name": "openstack/python-ceilometerclient" + }, + { + "heads": [], + "name": "openstack-infra/git-review" + }, + { + "heads": [], + "name": "stackforge/bufunfa" + }, + { + "heads": [], + "name": "stackforge/puppet-swift" + }, + { + "heads": [], + "name": "openstack-infra/statusbot" + }, + { + "heads": [], + "name": "openstack/openstack-planet" + }, + { + "heads": [], + "name": "openstack/python-openstackclient" + }, + { + "heads": [], + "name": "stackforge/diskimage-builder" + }, + { + "heads": [], + "name": "openstack-infra/gerritlib" + }, + { + "heads": [], + "name": "openstack-infra/zuul" + }, + { + "heads": [], + "name": "stackforge/reddwarf" + }, + { + "heads": [], + "name": "openstack-dev/hacking" + }, + { + "heads": [], + "name": "openstack/python-heatclient" + }, + { + "heads": [], + "name": "stackforge/python-libraclient" + }, + { + "heads": [], + "name": "openstack-infra/reviewday" + }, + { + "heads": [], + "name": "openstack-infra/jeepyb" + }, + { + "heads": [], + "name": "openstack/heat" + }, + { + "heads": [], + "name": "stackforge/libra" + }, + { + "heads": [], + "name": "openstack-infra/gerrit" + }, + { + "heads": [], + "name": "stackforge/healthnmon" + }, + { + "heads": [], + "name": "openstack-infra/gerritbot" + }, + { + "heads": [], + "name": "openstack-dev/pbr" + }, + { + "heads": [], + "name": "stackforge/savanna" + }, + { + "heads": [], + "name": "openstack/openstack-manuals" + } + ], + "name": "gate" + }, + { + "description": "This pipeline runs jobs that operate after each change is merged.", + "change_queues": [ + { + "heads": [], + "name": "openstack-dev/hacking, openstack-dev/openstack-qa, openstack-dev/pbr, openstack-infra/config, openstack-infra/gearman-plugin, openstack-infra/gerrit, openstack-infra/gerritbot, openstack-infra/git-review, openstack-infra/jenkins-job-builder, openstack-infra/nose-html-output, openstack-infra/reviewday, openstack-infra/statusbot, openstack-infra/zuul, openstack/api-site, openstack/ceilometer, openstack/cinder, openstack/compute-api, openstack/glance, openstack/heat, openstack/heat-cfntools, openstack/horizon, openstack/identity-api, openstack/image-api, openstack/keystone, openstack/netconn-api, openstack/nova, openstack/object-api, openstack/openstack-manuals, openstack/oslo-incubator, openstack/oslo.config, openstack/python-ceilometerclient, openstack/python-cinderclient, openstack/python-glanceclient, openstack/python-heatclient, openstack/python-keystoneclient, openstack/python-novaclient, openstack/python-openstackclient, openstack/python-quantumclient, openstack/python-swiftclient, openstack/quantum, openstack/requirements, openstack/swift, openstack/volume-api, stackforge/bufunfa, stackforge/diskimage-builder, stackforge/moniker, stackforge/os-config-applier, stackforge/python-monikerclient, stackforge/python-savannaclient, stackforge/reddwarf, stackforge/savanna, stackforge/tripleo-image-elements" + } + ], + "name": "post" + }, + { + "description": "This pipeline runs jobs on projects in response to pre-release tags.", + "change_queues": [ + { + "heads": [], + "name": "openstack-dev/hacking, openstack-dev/pbr, openstack-infra/gerritbot, openstack-infra/gerritlib, openstack-infra/git-review, openstack-infra/jeepyb, openstack-infra/jenkins-job-builder, openstack-infra/nose-html-output, openstack-infra/reviewday, openstack-infra/statusbot, openstack-infra/zuul, openstack/ceilometer, openstack/cinder, openstack/glance, openstack/heat, openstack/heat-cfntools, openstack/horizon, openstack/keystone, openstack/nova, openstack/oslo.config, openstack/python-ceilometerclient, openstack/python-cinderclient, openstack/python-glanceclient, openstack/python-heatclient, openstack/python-keystoneclient, openstack/python-novaclient, openstack/python-openstackclient, openstack/python-quantumclient, openstack/python-swiftclient, openstack/quantum, openstack/swift, stackforge/moniker, stackforge/python-monikerclient, stackforge/python-reddwarfclient, stackforge/python-savannaclient, stackforge/savanna" + } + ], + "name": "pre-release" + }, + { + "description": "When a commit is tagged as a release, this pipeline runs jobs that publish archives and documentation.", + "change_queues": [ + { + "heads": [], + "name": "openstack-dev/hacking, openstack-dev/openstack-qa, openstack-dev/pbr, openstack-infra/gerritbot, openstack-infra/gerritlib, openstack-infra/git-review, openstack-infra/jeepyb, openstack-infra/jenkins-job-builder, openstack-infra/nose-html-output, openstack-infra/reviewday, openstack-infra/statusbot, openstack-infra/zuul, openstack/ceilometer, openstack/cinder, openstack/glance, openstack/heat, openstack/heat-cfntools, openstack/horizon, openstack/keystone, openstack/nova, openstack/oslo-incubator, openstack/oslo.config, openstack/python-ceilometerclient, openstack/python-cinderclient, openstack/python-glanceclient, openstack/python-heatclient, openstack/python-keystoneclient, openstack/python-novaclient, openstack/python-openstackclient, openstack/python-quantumclient, openstack/python-swiftclient, openstack/quantum, openstack/swift, stackforge/moniker, stackforge/python-monikerclient, stackforge/python-reddwarfclient, stackforge/python-savannaclient, stackforge/savanna" + } + ], + "name": "release" + }, + { + "description": "This pipeline is used for silently testing new jobs.", + "change_queues": [ + { + "heads": [], + "name": "" + } + ], + "name": "silent" + } + ], + "trigger_event_queue": { + "length": 0 + }, + "result_event_queue": { + "length": 0 + } +} diff --git a/org/wikimedia/integration/zuul/status.js b/org/wikimedia/integration/zuul/status.js index 79c22a2..5df46af 100644 --- a/org/wikimedia/integration/zuul/status.js +++ b/org/wikimedia/integration/zuul/status.js @@ -16,33 +16,42 @@ // License for the specific language governing permissions and limitations // under the License. -/*jshint eqnull:true */ +/*jshint eqnull:true, camelcase:false */ (function ($) { - var updateCount = 0, - enableStatusUpdates = true; + var $container, $msg, $msgWrap, $indicator, prevHtml, + updateCount = 0, + enableStatusUpdates = true, + demo = location.search.match(/[?&]demo=([^?&]*)/), + source = demo ? './sample-status-' + (demo[1] || 'basic') + '.json' : '/zuul/status.json'; - function formatPipeline(data) { - var html = '<div class="pipeline"><h3 class="subhead">' + - data.name + '</h3>'; - if (data.description != null) { - html += '<p>' + data.description + '</p>'; + /** + * @param {Object} pipeline + * @return {string} html + */ + function formatPipeline(pipeline) { + var html = '<div class="zuul-pipeline span4"><h3>' + + pipeline.name + '</h3>'; + if (pipeline.description != null) { + html += '<p><small>' + pipeline.description + '</small></p>'; } - $.each(data.change_queues, function (queueNum, changeQueue) { - $.each(changeQueue.heads, function (headNum, head) { - if (data.change_queues.length > 1 && headNum === 0) { - html += '<div> Change queue: '; + $.each(pipeline.change_queues, function (queueNum, changeQueue) { + $.each(changeQueue.heads, function (headNum, changes) { + if (pipeline.change_queues.length > 1 && headNum === 0) { + html += '<p>Queue: '; var name = changeQueue.name; - html += '<a title="' + name + '">'; + html += '<abbr title="' + name + '">'; if (name.length > 32) { name = name.substr(0, 32) + '...'; } - html += name + '</a></div>'; + html += name + '</abbr></p>'; } - $.each(head, function (changeNum, change) { + $.each(changes, function (changeNum, change) { + // If there are multiple changes in the same head it means + // they're connected (dependant?) if (changeNum > 0) { - html += '<div class="arrow">↑</div>'; + html += '<div class="zuul-change-arrow">↑</div>'; } html += formatChange(change); }); @@ -53,16 +62,20 @@ return html; } + /** + * @param {Object} change + * @return {string} html + */ function formatChange(change) { - var html = '<div class="change"><div class="header">', + var html = '<div class="well well-small zuul-change"><ul class="nav nav-list">', id = change.id, url = change.url; - html += '<span class="project">' + change.project + '</span>'; + html += '<li class="nav-header">' + change.project; if (id.length === 40) { id = id.substr(0, 7); } - html += '<span class="changeid">'; + html += '<span class="zuul-change-id">'; if (url != null) { html += '<a href="' + url + '">'; } @@ -70,98 +83,134 @@ if (url != null) { html += '</a>'; } - html += '</span></div><div class="jobs">'; + html += '</span></li>'; $.each(change.jobs, function (i, job) { - var result = job.result, - resultClass = 'result'; - if (result == null) { - if (job.url != null) { - result = 'in progress'; - } else { - result = 'queued'; - } - } else if (result === 'SUCCESS') { - resultClass += ' result_success'; - } else if (result === 'FAILURE') { - resultClass += ' result_failure'; - } else if (result === 'LOST') { - resultClass += ' result_unstable'; - } else if (result === 'UNSTABLE') { - resultClass += ' result_unstable'; + var result = job.result ? job.result.toLowerCase() : null, + resultClass = 'zuul-result label'; + if (result === null) { + result = job.url ? 'in progress' : 'queued'; } - html += '<span class="job">'; + switch (result) { + case 'success': + resultClass += ' label-success'; + break; + case 'failure': + resultClass += ' label-important'; + break; + case 'lost': + case 'unstable': + resultClass += ' label-warning'; + break; + } + html += '<li class="zuul-change-job">'; if (job.url != null) { html += '<a href="' + job.url + '">'; } html += job.name; + html += ' <span class="' + resultClass + '">' + result + '</span>'; + if (job.voting === false) { + html += ' <span class="muted">(non-voting)</span>'; + } if (job.url != null) { html += '</a>'; } - html += ': <span class="' + resultClass + '">' + result + '</span>'; - if (job.voting === false) { - html += ' (non-voting)'; - } - html += '</span>'; + html += '</li>'; }); - html += '</div></div>'; + html += '</ul></div>'; return html; } - function updateTimeout() { + function scheduleUpdate() { if (!enableStatusUpdates) { - setTimeout(updateTimeout, 5000); + setTimeout(scheduleUpdate, 5000); return; } - updateCount += 1; + update().complete(function () { - update(); - /* Only update graphs every minute */ - if (updateCount > 11) { - updateCount = 0; - updateGraphs(); - } + updateCount += 1; - setTimeout(updateTimeout, 5000); - } - - function update() { - - $.getJSON('/zuul/status.json', function (data) { - data = data || {}; - var html = ''; - if ('message' in data) { - $('#message') - .attr('class', 'alertbox') - .html(data.message); - } else { - $('#message') - .removeClass('alertbox') - .empty(); + // Only update graphs every minute + if (updateCount > 12) { + updateCount = 1; + purgeGraphs(); } - html += '<br style="clear:both"/>'; - - $.each(data.pipelines, function (i, pipeline) { - html = html + formatPipeline(pipeline); - }); - - html += '<br style="clear:both"/>'; - $('#pipeline-container').html(html); - - $('#trigger_event_queue_length').text( - (data.trigger_event_queue || []).length - ); - $('#result_event_queue_length').text( - (data.result_event_queue || []).length - ); - + setTimeout(scheduleUpdate, 5000); }); } - function updateGraphs() { + function updateStart() { + $container.addClass('zuul-container-loading'); + $indicator.addClass('zuul-spinner-on'); + } + + function updateEnd() { + $container.removeClass('zuul-container-loading'); + setTimeout(function () { + $indicator.removeClass('zuul-spinner-on'); + }, 550); + if (updateCount === 0) { + // Remove this asynchronous so that if the first + // update adds a message, the message does not animate + // but appears instantly with the rest of the content. + setTimeout(function () { + $container.addClass('zuul-container-ready'); + }); + } + } + + /** + * @return {jQuery.Promise} + */ + function update() { + updateStart(); + return $.ajax({ + url: source, + dataType: 'json', + cache: false + }) + .done(function (data) { + var html = ''; + data = data || {}; + + if ('message' in data) { + $msg.html(data.message); + $msgWrap.removeClass('zuul-msg-wrap-off'); + } else { + $msg.empty(); + $msgWrap.addClass('zuul-msg-wrap-off'); + } + + $.each(data.pipelines, function (i, pipeline) { + html += formatPipeline(pipeline); + }); + + // Only re-parse the DOM if we have to + if (html !== prevHtml) { + prevHtml = html; + $('#zuul-pipelines').html(html); + } + + $('#zuul-eventqueue-length').text( + data.trigger_event_queue ? data.trigger_event_queue.length : '?' + ); + $('#zuul-resulteventqueue-length').text( + data.result_event_queue ? data.result_event_queue.length : '?' + ); + updateEnd(); + + }) + .fail(function (err, jqXHR, errMsg) { + $msg.text(source + ': ' + errMsg).show(); + $msgWrap.removeClass('zuul-msg-wrap-off'); + updateEnd(); + }); + } + + function purgeGraphs() { $('.graph').each(function (i, img) { var newimg = new Image(), parts = img.src.split('#'); @@ -172,14 +221,23 @@ }); } - $(document).ready(function () { - updateTimeout(); + $(function ($) { + $container = $('#zuul-container'); + $indicator = $('<span class="btn pull-right zuul-spinner">updating <i class="icon-refresh"></i></span>') + .prependTo($container); + $msg = $('<div class="zuul-msg alert alert-error"></div>'); + $msgWrap = $msg + .wrap('<div class="zuul-msg-wrap zuul-msg-wrap-off"></div>') + .parent() + .prependTo($container); + + scheduleUpdate(); $(document).on({ 'show.visibility': function () { enableStatusUpdates = true; update(); - updateGraphs(); + purgeGraphs(); }, 'hide.visibility': function () { enableStatusUpdates = false; diff --git a/shared/IntegrationPage.php b/shared/IntegrationPage.php index b6ec7f2..936832d 100644 --- a/shared/IntegrationPage.php +++ b/shared/IntegrationPage.php @@ -96,8 +96,12 @@ * Logo */ .logo { - float: right; - background: #fff; /* Fix header line through logo */ + vertical-align: middle; + margin-right: 1em; +} + +.page-header { + overflow: hidden; } '); @@ -166,11 +170,11 @@ <div class="page-wrap"> <div class="container"> <div class="page-header"> - <a href="//www.wikimedia.org/"> - <img src="//upload.wikimedia.org/wikipedia/foundation/9/9a/Wikimediafoundation-logo.png" - width="135" height="135" title="Visit Wikimedia.org" alt="Wikimedia Foundation logo" class="logo"> - </a> - <h2><?php echo htmlentities( $this->pageName ); ?></h2> + <h2> + <a href="//www.wikimedia.org/"><img src="//upload.wikimedia.org/wikipedia/commons/thumb/1/12/Wikimedia_logo_text_RGB.svg/120px-Wikimedia_logo_text_RGB.svg.png" + width="120" height="120" title="Visit Wikimedia.org" alt="Wikimedia Foundation logo" class="logo"></a> + <?php echo htmlentities( $this->pageName ); ?> + </h2> </div> <?php echo "\t\t" . implode( "\n\t\t", explode( "\n", $this->content ) ) . "\n"; ?> </div><!-- /.container --> -- To view, visit https://gerrit.wikimedia.org/r/57899 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: newchange Gerrit-Change-Id: Ifa93f856f29e1a2fe7b27e0b5a9d7e43935d8acc Gerrit-PatchSet: 1 Gerrit-Project: integration/docroot Gerrit-Branch: master Gerrit-Owner: Krinkle <ttij...@wikimedia.org> _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits