Zuul status page: Redesign and fix bugs

status.html:
- Added eventqueue-length status (was already present in production
  but not committed here yet).
- HTML5 markup.
- Remove unused jquery-visibility.min.js and jquery-graphite.js.

status.js:
- Clean up (various js best practices and consistent coding style)
- Use empty() instead of html('').
- Use text() for text instead of html().
  html() will trigger the parser where text will simply create
  a text node with the string literal, much faster, safer and
  semantically correct.
- Fix implied global variable leak 'result'.
- Fix reference error that crashes/freezes the page
  Property data.trigger_event_queue and data.trigger_event_queue
  can be undefined, in which case data.trigger_event_queue.length
  causes an uncaught TypeError to be thrown.
- Use a closure instead of polluting global scope.
- Rewrite object oriented.
- Added 'demo' feature for easy local testing.

Downstream commits at
https://gerrit.wikimedia.org/r/#/q/project:integration/docroot+topic:zuul-js+branch:master+owner:Krinkle+is:merged,n,z

Change-Id: Iddd4e2787f2e2eb27bf428f733fbb8b4a9d162d5
Reviewed-on: https://review.openstack.org/26416
Reviewed-by: James E. Blair <corvus@inaugust.com>
Reviewed-by: Clark Boylan <clark.boylan@gmail.com>
Reviewed-by: Jeremy Stanley <fungi@yuggoth.org>
Approved: James E. Blair <corvus@inaugust.com>
Tested-by: Jenkins
diff --git a/etc/status/.gitignore b/etc/status/.gitignore
new file mode 100644
index 0000000..a4b9570
--- /dev/null
+++ b/etc/status/.gitignore
@@ -0,0 +1,3 @@
+public_html/jquery.min.js
+public_html/jquery-visibility.min.js
+public_html/bootstrap
diff --git a/etc/status/README.rst b/etc/status/README.rst
new file mode 100644
index 0000000..5b4a6bb
--- /dev/null
+++ b/etc/status/README.rst
@@ -0,0 +1,27 @@
+Zuul Status
+====
+
+Zuul Status is a web portal for a Zuul server.
+
+Set up
+------------
+
+The markup generated by the javascript is fairly generic so it should be easy
+to drop into an existing portal. All it needs is
+``<div id="id="zuul-container"></div>``.
+
+Having said that, the markup is optimised for Twitter Bootstrap, though it in
+no way depends on Boostrap and any element using a bootstrap class has a
+``zuul-`` prefixed class alongside it.
+
+The script depends on jQuery (tested with version 1.8 and 1.9).
+
+The script optimises updates by stopping when the page is not visible.
+This is done by listerning to ``show`` and ``hide`` events emitted by the
+Page Visibility plugin for jQuery. If you don't want to load this plugin you
+can undo undo this optimisation by removing the 9 lines using this on the
+bottom of ``app.js``
+
+To automatically fetch the latest versions of jQuery, the Page Visibility
+plugin and Twitter Boostrap, run the ``fetch-dependencies.sh`` script.
+The default ``index.html`` references these.
diff --git a/etc/status/fetch-dependencies.sh b/etc/status/fetch-dependencies.sh
new file mode 100755
index 0000000..f224557
--- /dev/null
+++ b/etc/status/fetch-dependencies.sh
@@ -0,0 +1,9 @@
+#!/bin/bash
+BASE_DIR=$(cd $(dirname $0); pwd)
+echo "Destination: $BASE_DIR/public_html"
+echo "Fetching jquery.min.js..."
+curl --silent http://code.jquery.com/jquery.min.js > $BASE_DIR/public_html/jquery.min.js
+echo "Fetching jquery-visibility.min.js..."
+curl --silent https://raw.github.com/mathiasbynens/jquery-visibility/master/jquery-visibility.min.js > $BASE_DIR/public_html/jquery-visibility.min.js
+echo "Fetching bootstrap..."
+curl --silent http://twitter.github.io/bootstrap/assets/bootstrap.zip > bootstrap.zip && unzip -q -o bootstrap.zip -d $BASE_DIR/public_html/ && rm bootstrap.zip
diff --git a/etc/status/public_html/app.js b/etc/status/public_html/app.js
new file mode 100644
index 0000000..f7de2cc
--- /dev/null
+++ b/etc/status/public_html/app.js
@@ -0,0 +1,241 @@
+// Client script for Zuul status page
+//
+// Copyright 2012 OpenStack Foundation
+// Copyright 2013 Timo Tijhof
+// Copyright 2013 Wikimedia Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+(function ($) {
+    var $container, $msg, $msgWrap, $indicator, $queueInfo, $queueEventsNum, $queueResultsNum, $pipelines,
+        prevHtml, xhr, zuul, $jq,
+        demo = location.search.match(/[?&]demo=([^?&]*)/),
+        source = demo ?
+            './status-' + (demo[1] || 'basic') + '.json-sample' :
+            '/zuul/status.json';
+
+    zuul = {
+        enabled: true,
+
+        schedule: function () {
+            if (!zuul.enabled) {
+                setTimeout(zuul.schedule, 5000);
+                return;
+            }
+            zuul.update().complete(function () {
+                setTimeout(zuul.schedule, 5000);
+            });
+        },
+
+        /** @return {jQuery.Promise} */
+        update: function () {
+            // Cancel the previous update if it hasn't completed yet.
+            if (xhr) {
+                xhr.abort();
+            }
+
+            zuul.emit('update-start');
+
+            xhr = $.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 += zuul.format.pipeline(pipeline);
+                });
+
+                // Only re-parse the DOM if we have to
+                if (html !== prevHtml) {
+                    prevHtml = html;
+                    $pipelines.html(html);
+                }
+
+                $queueEventsNum.text(
+                    data.trigger_event_queue ? data.trigger_event_queue.length : '0'
+                );
+                $queueResultsNum.text(
+                    data.result_event_queue ? data.result_event_queue.length : '0'
+                );
+            })
+            .fail(function (err, jqXHR, errMsg) {
+                $msg.text(source + ': ' + errMsg).show();
+                $msgWrap.removeClass('zuul-msg-wrap-off');
+            })
+            .complete(function () {
+                xhr = undefined;
+                zuul.emit('update-end');
+            });
+
+            return xhr;
+        },
+
+        format: {
+            change: function (change) {
+                var html = '<div class="well well-small zuul-change"><ul class="nav nav-list">',
+                    id = change.id,
+                    url = change.url;
+
+                html += '<li class="nav-header">' + change.project;
+                if (id.length === 40) {
+                    id = id.substr(0, 7);
+                }
+                html += ' <span class="zuul-change-id">';
+                if (url !== null) {
+                    html += '<a href="' + url + '">';
+                }
+                html += id;
+                if (url !== null) {
+                    html += '</a>';
+                }
+                html += '</span></li>';
+
+                $.each(change.jobs, function (i, job) {
+                    var result = job.result ? job.result.toLowerCase() : null,
+                        resultClass = 'zuul-result label';
+                    if (result === null) {
+                        result = job.url ? 'in progress' : 'queued';
+                    }
+                    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">';
+                    html += job.url !== null ?
+                        '<a href="' + job.url + '" class="zuul-change-job-link">' :
+                        '<span class="zuul-change-job-link">';
+                    html += job.name;
+                    html += ' <span class="' + resultClass + '">' + result + '</span>';
+                    if (job.voting === false) {
+                        html += ' <span class="muted">(non-voting)</span>';
+                    }
+                    html += job.url !== null ? '</a>' : '</span>';
+                    html += '</li>';
+                });
+
+                html += '</ul></div>';
+                return html;
+            },
+
+            pipeline: function (pipeline) {
+                var html = '<div class="zuul-pipeline span4"><h3>' +
+                    pipeline.name + '</h3>';
+                if (typeof pipeline.description === 'string') {
+                    html += '<p><small>' + pipeline.description + '</small></p>';
+                }
+
+                $.each(pipeline.change_queues, function (queueNum, changeQueue) {
+                    $.each(changeQueue.heads, function (headNum, changes) {
+                        if (pipeline.change_queues.length > 1 && headNum === 0) {
+                            var name = changeQueue.name;
+                            html += '<p>Queue: <abbr title="' + name + '">';
+                            if (name.length > 32) {
+                                name = name.substr(0, 32) + '...';
+                            }
+                            html += name + '</abbr></p>';
+                        }
+                        $.each(changes, function (changeNum, change) {
+                            // If there are multiple changes in the same head it means they're connected
+                            if (changeNum > 0) {
+                                html += '<div class="zuul-change-arrow">&uarr;</div>';
+                            }
+                            html += zuul.format.change(change);
+                        });
+                    });
+                });
+
+                html += '</div>';
+                return html;
+            }
+        },
+
+        emit: function () {
+            $jq.trigger.apply($jq, arguments);
+            return this;
+        },
+        on: function () {
+            $jq.on.apply($jq, arguments);
+            return this;
+        },
+        one: function () {
+            $jq.one.apply($jq, arguments);
+            return this;
+        }
+    };
+
+    $jq = $(zuul);
+
+    $jq.on('update-start', function () {
+        $container.addClass('zuul-container-loading');
+        $indicator.addClass('zuul-spinner-on');
+    });
+
+    $jq.on('update-end', function () {
+        $container.removeClass('zuul-container-loading');
+        setTimeout(function () {
+            $indicator.removeClass('zuul-spinner-on');
+        }, 550);
+    });
+
+    $jq.one('update-end', function () {
+        // Do this asynchronous so that if the first update adds a message, it will not animate
+        // while we fade in the content. Instead it simply appears with the rest of the content.
+        setTimeout(function () {
+            $container.addClass('zuul-container-ready'); // Fades in the content
+        });
+    });
+
+    $(function ($) {
+        $msg = $('<div class="zuul-msg alert alert-error"></div>');
+        $msgWrap = $msg.wrap('<div class="zuul-msg-wrap zuul-msg-wrap-off"></div>').parent();
+        $indicator = $('<span class="btn pull-right zuul-spinner">updating <i class="icon-refresh"></i></span>');
+        $queueInfo = $('<p>Queue lengths: <span>0</span> events, <span>0</span> results.</p>');
+        $queueEventsNum =  $queueInfo.find('span').eq(0);
+        $queueResultsNum =  $queueEventsNum.next();
+        $pipelines = $('<div class="row"></div>');
+
+        $container = $('#zuul-container').append($msgWrap, $indicator, $queueInfo, $pipelines);
+
+        zuul.schedule();
+
+        $(document).on({
+            'show.visibility': function () {
+                zuul.enabled = true;
+                zuul.update();
+            },
+            'hide.visibility': function () {
+                zuul.enabled = false;
+            }
+        });
+    });
+}(jQuery));
diff --git a/etc/status/public_html/index.html b/etc/status/public_html/index.html
new file mode 100644
index 0000000..f5efce0
--- /dev/null
+++ b/etc/status/public_html/index.html
@@ -0,0 +1,141 @@
+<!--
+Copyright 2013 OpenStack Foundation
+Copyright 2013 Timo Tijhof
+Copyright 2013 Wikimedia Foundation
+
+Licensed under the Apache License, Version 2.0 (the "License"); you may
+not use this file except in compliance with the License. You may obtain
+a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+License for the specific language governing permissions and limitations
+under the License.
+-->
+<!DOCTYPE html>
+<html dir="ltr" lang="en">
+<head>
+    <title>Zuul Status</title>
+    <link rel="stylesheet" href="bootstrap/css/bootstrap.min.css">
+    <link rel="stylesheet" href="bootstrap/css/bootstrap-responsive.min.css">
+    <style>
+.zuul-container {
+    transition-property: opacity, background-color;
+    transition-duration: 1s;
+    transition-timing-function: ease-in-out;
+    clear: both;
+    opacity: 0;
+    cursor: progress;
+    min-height: 400px;
+    background-color: #f8ffaa;
+}
+
+.zuul-container-ready {
+    opacity: 1;
+    cursor: auto;
+    background-color: #fff;
+}
+
+.zuul-spinner,
+.zuul-spinner:hover {
+    opacity: 0;
+    transition: opacity 3s ease-out;
+    cursor: default;
+    pointer-events: none;
+}
+
+.zuul-spinner-on,
+.zuul-spinner-on:hover {
+    opacity: 1;
+    transition-duration: 0.4s;
+    cursor: progress;
+}
+
+.zuul-change-arrow {
+    text-align: center;
+    font-size: 16pt;
+    line-height: 1.0;
+}
+
+.zuul-change-id {
+    text-transform: none;
+}
+
+.zuul-change-job a {
+    overflow: auto;
+}
+
+.zuul-result {
+    text-shadow: none;
+    font-weight: normal;
+    background-color: #E9E9E9;
+    color: #555;
+}
+
+.zuul-result.label-success {
+    background-color: #CDF0CD;
+    color: #468847;
+}
+
+.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;
+}
+
+/**
+ * Styles for Bootstrap only
+ * (remove when not using Bootstrap)
+ */
+
+.zuul-change-id {
+    float: right;
+}
+
+.zuul-change-job-link {
+    overflow: auto;
+    display: block;
+}
+
+.zuul-result {
+    float: right;
+}
+
+    </style>
+</head>
+<body>
+    <div class="container">
+        <h1>Zuul Status</h1>
+        <p>Real-time status monitor of Zuul, the pipeline manager between Gerrit and Jenkins.</p>
+
+        <div class="zuul-container" id="zuul-container"></div>
+    </div>
+    <script src="jquery.min.js"></script>
+    <script src="jquery-visibility.min.js"></script>
+    <script src="app.js"></script>
+</body>
+</html>
diff --git a/etc/status/public_html/status-basic.json-sample b/etc/status/public_html/status-basic.json-sample
new file mode 100644
index 0000000..4e12cd6
--- /dev/null
+++ b/etc/status/public_html/status-basic.json-sample
@@ -0,0 +1,176 @@
+{
+    "message": "Example error message",
+    "pipelines": [
+        {
+            "name": "test",
+            "description": "Lint and unit tests",
+            "change_queues": [
+                {
+                    "name": "some-jobs@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-jobs@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-jobs@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": "FAILURE",
+                                        "voting": true
+                                    },
+                                    {
+                                        "name": "zuul-regression-python2",
+                                        "url": "#!/jenkins.example.org/job/zuul-regression-python2/200",
+                                        "result": "SUCCESS",
+                                        "voting": true
+                                    },
+                                    {
+                                        "name": "zuul-regression-python3",
+                                        "url": "#!/jenkins.example.org/job/zuul-regression-python3/200",
+                                        "result": "FAILURE",
+                                        "voting": true
+                                    },
+                                    {
+                                        "name": "zuul-performance-python2",
+                                        "url": null,
+                                        "result": null,
+                                        "voting": true
+                                    },
+                                    {
+                                        "name": "zuul-performance-python3",
+                                        "url": null,
+                                        "result": null,
+                                        "voting": true
+                                    },
+                                    {
+                                        "name": "zuul-docs-publish",
+                                        "url": null,
+                                        "result": null,
+                                        "voting": true
+                                    }
+                                ]
+                            }
+                        ]
+                    ]
+                }
+            ]
+        }
+    ],
+    "trigger_event_queue": {
+        "length": 0
+    },
+    "result_event_queue": {
+        "length": 0
+    }
+}
diff --git a/etc/status/public_html/status-openstack.json-sample b/etc/status/public_html/status-openstack.json-sample
new file mode 100644
index 0000000..5d16bdd
--- /dev/null
+++ b/etc/status/public_html/status-openstack.json-sample
@@ -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
+    }
+}