Initial commit
Rewrite of old releasestatus code to take advantage of Jinja2 templating, introducing increased maintainability and configurability. The previous code lived at: https://code.launchpad.net/~ttx/+junk/releasestatus
|
@ -0,0 +1,176 @@
|
|||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
ReleaseStatus page generator
|
||||
============================
|
||||
|
||||
The releasestatus.py script is used to extract data from Launchpad
|
||||
and Gerrit and produce static HTML that shows where we are in the
|
||||
current release cycle.
|
||||
|
||||
Prerequisites
|
||||
-------------
|
||||
|
||||
You'll need the following Python modules installed:
|
||||
- launchpadlib
|
||||
- jinja2
|
||||
- yaml
|
||||
|
||||
You'll also need a valid SSH key for accessing Gerrit via SSH
|
||||
installed in ~/.gerritssh/id_rsa.
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
python releasestatus.py grizzly.yaml > static/index.html
|
||||
|
||||
It may take a few minutes to run, depending on the number of
|
||||
projects and how many blueprints they contain.
|
||||
|
||||
Configuration
|
||||
-------------
|
||||
|
||||
The YAML configuration file describes the series and projects
|
||||
you want to generate data for. See comments in the example file
|
||||
releasestatus.yaml.sample for details.
|
|
@ -0,0 +1,236 @@
|
|||
# Copyright 2011 Thierry Carrez <thierry@openstack.org>
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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.
|
||||
|
||||
from launchpadlib.launchpad import Launchpad
|
||||
from datetime import date, datetime
|
||||
from jinja2 import Environment, FileSystemLoader
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import yaml
|
||||
|
||||
|
||||
class GerritReviews():
|
||||
def __init__(self, products):
|
||||
self.products = products
|
||||
self.under_review = self._get_from_gerrit('AND', 'NOT', 'is:submitted')
|
||||
self.submitted = self._get_from_gerrit('is:submitted')
|
||||
self.merged = self._get_from_gerrit('is:merged')
|
||||
|
||||
def _get_from_gerrit(self, *query):
|
||||
chg = {}
|
||||
age = '2mon'
|
||||
host = "review.openstack.org"
|
||||
port = "29418"
|
||||
username = "releasestatus"
|
||||
keyfile = os.getenv('HOME') + "/.gerritssh/id_rsa"
|
||||
|
||||
base_cmd = ['/usr/bin/ssh', '-p', port, '-i', keyfile, '-l', username,
|
||||
host, 'gerrit', 'query', '--format=JSON',
|
||||
'branch:master', 'AND', 'NOT', 'age:%s' % age]
|
||||
|
||||
cmd = base_cmd + list(query)
|
||||
|
||||
proc = subprocess.Popen(cmd, bufsize=1,
|
||||
stdin=None, stdout=subprocess.PIPE, stderr=None)
|
||||
|
||||
for line in proc.stdout:
|
||||
data = json.loads(line)
|
||||
if "project" in data:
|
||||
project = data['project'].split("/")[-1]
|
||||
if project in self.products:
|
||||
if project not in chg:
|
||||
chg[project] = []
|
||||
chg[project].append(data)
|
||||
return chg
|
||||
|
||||
|
||||
class BlueprintReview():
|
||||
def __init__(self, link, image):
|
||||
self.url = link['url']
|
||||
self.subject = link['subject']
|
||||
self.image = image
|
||||
|
||||
|
||||
class ExtendedBlueprint():
|
||||
priorities = ('Essential', 'High', 'Medium', 'Low', 'Undefined')
|
||||
|
||||
implementations = ('Implemented', 'Deployment', 'Needs Code Review',
|
||||
'Beta Available', 'Good progress', 'Slow progress',
|
||||
'Blocked', 'Needs Infrastructure', 'Started',
|
||||
'Not started', 'Unknown', 'Deferred', 'Informational')
|
||||
|
||||
def __init__(self, lbp):
|
||||
self.name = lbp.name
|
||||
self.pname = lbp.target.name
|
||||
self.whiteboard = lbp.whiteboard
|
||||
self.priority = lbp.priority
|
||||
self.implementation = lbp.implementation_status
|
||||
if lbp.milestone:
|
||||
self.milestonename = lbp.milestone.name
|
||||
self.milestonedate = lbp.milestone.date_targeted or '2099-12-30'
|
||||
self.milestonelink = lbp.milestone.web_link
|
||||
else:
|
||||
self.milestonename = ''
|
||||
self.milestonedate = '2099-12-31'
|
||||
self.milestonelink = ''
|
||||
self.implementationindex = self.implementations.index(
|
||||
self.implementation)
|
||||
self.priorityindex = self.priorities.index(self.priority)
|
||||
self.reviews = []
|
||||
self.assignee = lbp.assignee
|
||||
if (self.assignee is None):
|
||||
self.drafter = lbp.drafter
|
||||
if self.drafter is None:
|
||||
self.assigneename = ''
|
||||
self.assigneedisplay = ''
|
||||
else:
|
||||
self.assigneename = self.drafter.name
|
||||
self.assigneedisplay = '<i>%s</i>' % self.drafter.display_name
|
||||
else:
|
||||
self.assigneename = self.assignee.name
|
||||
try:
|
||||
self.assigneedisplay = str(self.assignee.display_name)
|
||||
except UnicodeEncodeError:
|
||||
self.assigneedisplay = self.assignee.name
|
||||
|
||||
def grab_xtra_info(self, gerritreviews):
|
||||
if self.whiteboard:
|
||||
matches = re.findall(
|
||||
r'Addressed by: https://review.openstack.org/(\d+)',
|
||||
bp.whiteboard)
|
||||
self.reviews.extend(self.grab_links(matches,
|
||||
gerritreviews.merged, "MERGED"))
|
||||
self.reviews.extend(self.grab_links(matches,
|
||||
gerritreviews.submitted,
|
||||
"WORKINPROGRESS"))
|
||||
self.reviews.extend(self.grab_links(matches,
|
||||
gerritreviews.under_review,
|
||||
"NEEDSREVIEW"))
|
||||
|
||||
self.impl_warn = ''
|
||||
self.impl_error = ''
|
||||
self.assignee_warn = ''
|
||||
self.assignee_error = ''
|
||||
|
||||
# Design is not approved
|
||||
#if (bp.definition_status != "Approved"):
|
||||
# impl_warn += '- Design not approved '
|
||||
|
||||
# Spec is "Needs review" but has no branch up for review
|
||||
if (self.implementation == 'Needs Code Review'
|
||||
and len(self.reviews) == 0):
|
||||
self.impl_warn += '- Topic missing on reviews ? '
|
||||
|
||||
# Spec isn't started but has branch merge proposal
|
||||
if (len(self.reviews) > 0 and (self.implementationindex > 8)):
|
||||
self.impl_warn += '- Spec has branch, should be started '
|
||||
|
||||
# Spec has "Unknown" delivery status
|
||||
if (self.implementation == 'Unknown'):
|
||||
self.impl_error += '- Status needs to be set '
|
||||
|
||||
# Assignee should not be a group
|
||||
if (self.assignee is None):
|
||||
if self.drafter is None:
|
||||
self.assignee_error += '- No assignee or drafter '
|
||||
else:
|
||||
self.assignee_warn += '- No assignee yet '
|
||||
else:
|
||||
if (self.assignee.is_team):
|
||||
self.assignee_warn += '- Should be assigned to an individual '
|
||||
|
||||
def grab_links(self, matches, changes, image):
|
||||
links = {}
|
||||
if self.pname in changes:
|
||||
for change in changes[self.pname]:
|
||||
if (change['number'] in matches or
|
||||
('topic' in changes and
|
||||
change['topic'].split("/")[-1] == bp.name)):
|
||||
links[int(change['number'])] = change
|
||||
reviews = []
|
||||
for num in sorted(links.keys()):
|
||||
reviews.append(BlueprintReview(links[num], image))
|
||||
return reviews
|
||||
|
||||
|
||||
class ExtendedBlueprintSet():
|
||||
def __init__(self, includelinks=False, reviews=None):
|
||||
self.bps = []
|
||||
self.includelinks = includelinks
|
||||
self.reviews = reviews
|
||||
|
||||
def add(self, bp):
|
||||
newebp = ExtendedBlueprint(bp)
|
||||
if self.includelinks:
|
||||
newebp.grab_xtra_info(self.reviews)
|
||||
self.bps.append(newebp)
|
||||
|
||||
|
||||
class CycleGaugeData(object):
|
||||
def __init__(self, config):
|
||||
self.ticks = ''
|
||||
self.end = -7
|
||||
for milestone in config['milestones']:
|
||||
self.ticks += ("''," * milestone[0])
|
||||
self.ticks += ("'%s'," % milestone[1])
|
||||
self.end += ((milestone[0] + 1) * 7)
|
||||
self.red = self.end - (config['milestones'][-1][0] + 1) * 7
|
||||
self.yellow = self.red - (config['milestones'][-2][0] + 1) * 7
|
||||
self.green = self.yellow - (config['milestones'][-3][0] + 1) * 7
|
||||
delta = config['releasedate'] - date.today()
|
||||
self.progress = self.end - delta.days
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
template_dir = os.path.dirname(sys.argv[0])
|
||||
if len(sys.argv) < 1:
|
||||
print >> sys.stderr, "Usage: %s config.yaml" % sys.argv[0]
|
||||
sys.exit(1)
|
||||
|
||||
with open(sys.argv[1]) as f:
|
||||
config = yaml.load(f)
|
||||
|
||||
gaugedata = CycleGaugeData(config)
|
||||
|
||||
env = Environment(loader=FileSystemLoader(template_dir))
|
||||
template = env.get_template('template.html')
|
||||
|
||||
# Get changes from Gerrit
|
||||
reviews = GerritReviews(config['products'])
|
||||
|
||||
# Log into LP
|
||||
lp = Launchpad.login_anonymously('releasestatus', 'production',
|
||||
'~/.launchpadlib-cache', version='devel')
|
||||
|
||||
# Get the blueprints
|
||||
activebps = ExtendedBlueprintSet(includelinks=True, reviews=reviews)
|
||||
pastbps = ExtendedBlueprintSet(reviews=reviews)
|
||||
for p in config['products']:
|
||||
for bp in lp.projects[p].getSeries(
|
||||
name=config['series']).valid_specifications:
|
||||
if bp.milestone and not bp.milestone.is_active:
|
||||
pastbps.add(bp)
|
||||
else:
|
||||
activebps.add(bp)
|
||||
|
||||
print template.render(series=config['series'],
|
||||
gaugedata=gaugedata,
|
||||
date=str(datetime.utcnow()),
|
||||
activebps=activebps.bps,
|
||||
pastbps=pastbps.bps)
|
|
@ -0,0 +1,24 @@
|
|||
# Name of the series
|
||||
series: grizzly
|
||||
|
||||
# Final release date
|
||||
releasedate: 2013-04-04
|
||||
|
||||
# Short milestone codes, with number of weeks leading to them
|
||||
# The last one should be the release date
|
||||
milestones:
|
||||
- [5, g1]
|
||||
- [6, g2]
|
||||
- [5, g3]
|
||||
- [5, Apr 4]
|
||||
|
||||
# Set of projects to consider in the report
|
||||
products:
|
||||
- nova
|
||||
- glance
|
||||
- swift
|
||||
- keystone
|
||||
- horizon
|
||||
- quantum
|
||||
- cinder
|
||||
- oslo
|
After Width: | Height: | Size: 398 B |
After Width: | Height: | Size: 91 B |
After Width: | Height: | Size: 292 B |
After Width: | Height: | Size: 297 B |
After Width: | Height: | Size: 435 B |
After Width: | Height: | Size: 397 B |
After Width: | Height: | Size: 415 B |
After Width: | Height: | Size: 405 B |
After Width: | Height: | Size: 415 B |
After Width: | Height: | Size: 431 B |
|
@ -0,0 +1,45 @@
|
|||
// sorttable/sorttable-min.js
|
||||
|
||||
var SORT_COLUMN_INDEX;var arrowUp="arrowUp";var arrowDown="arrowDown";var arrowBlank="arrowBlank";function trim(str){return str.replace(/^\s*|\s*$/g,"");}
|
||||
function sortables_init(){if(!document.getElementsByTagName)return;tbls=document.getElementsByTagName("table");for(ti=0;ti<tbls.length;ti++){thisTbl=tbls[ti];if(((' '+thisTbl.className+' ').indexOf(" sortable ")!=-1)&&(thisTbl.id)){ts_makeSortable(thisTbl);}}}
|
||||
function ts_makeSortable(table){if(table.tHead&&table.tHead.rows&&table.tHead.rows.length>0){var firstRow=table.tHead.rows[0];}else if(table.rows&&table.rows.length>0){var firstRow=table.rows[0];}
|
||||
if(!firstRow)return;for(var i=0;i<firstRow.cells.length;i++){var cell=firstRow.cells[i];var txt=ts_getInnerText(cell);cell.innerHTML='<a href="#" class="sortheader" onclick="ts_resortTable(this); return false;">'
|
||||
+txt+'<img class="sortarrow" src="'+arrowBlank+'" height="6" width="9"></a>';}
|
||||
for(var i=0;i<firstRow.cells.length;i++){var cell=firstRow.cells[i];var lnk=ts_firstChildByName(cell,'A');var img=ts_firstChildByName(lnk,'IMG')
|
||||
if((' '+cell.className+' ').indexOf(" default-sort ")!=-1){ts_arrowDown(img);}
|
||||
if((' '+cell.className+' ').indexOf(" default-revsort ")!=-1){ts_arrowUp(img);}
|
||||
if((' '+cell.className+' ').indexOf(" initial-sort ")!=-1){ts_resortTable(lnk);}}}
|
||||
function ts_getInnerText(el){if(typeof el=="string")return el;if(typeof el=="undefined"){return el};/*if(el.innerText)return el.innerText*/;var str="";var cs=el.childNodes;var l=cs.length;for(var i=0;i<l;i++){node=cs[i];switch(node.nodeType){case 1:if(node.className=="sortkey"){return ts_getInnerText(node);}else if(node.className=="revsortkey"){return"-"+ts_getInnerText(node);}else{str+=ts_getInnerText(node);break;}
|
||||
case 3:str+=node.nodeValue;break;}}
|
||||
return str;}
|
||||
function ts_firstChildByName(el,name){for(var ci=0;ci<el.childNodes.length;ci++){if(el.childNodes[ci].tagName&&el.childNodes[ci].tagName.toLowerCase()==name.toLowerCase())
|
||||
return el.childNodes[ci];}}
|
||||
function ts_arrowUp(img){img.setAttribute('sortdir','up');img.src=arrowUp;}
|
||||
function ts_arrowDown(img){img.setAttribute('sortdir','down');img.src=arrowDown;}
|
||||
function ts_resortTable(lnk){var img=ts_firstChildByName(lnk,'IMG')
|
||||
var td=lnk.parentNode;var column=td.cellIndex;var table=getParent(td,'TABLE');if(table.rows.length<=1)return;SORT_COLUMN_INDEX=column;while(td.previousSibling!=null){td=td.previousSibling;if(td.nodeType!=1){continue}
|
||||
colspan=td.getAttribute("colspan");if(colspan){SORT_COLUMN_INDEX+=parseInt(colspan)-1;}}
|
||||
var itm=ts_getInnerText(table.rows[1].cells[SORT_COLUMN_INDEX]);itm=trim(itm);sortfn=ts_sort_caseinsensitive;if(itm.match(/^\d\d[\/-]\d\d[\/-]\d\d\d\d$/))sortfn=ts_sort_date;if(itm.match(/^\d\d[\/-]\d\d[\/-]\d\d$/))sortfn=ts_sort_date;if(itm.match(/^[£$]/))sortfn=ts_sort_currency;if(itm.match(/^-?[\d\.]+$/))sortfn=ts_sort_numeric;var firstRow=new Array();var newRows=new Array();for(i=0;i<table.rows[0].length;i++){firstRow[i]=table.rows[0][i];}
|
||||
for(j=1;j<table.rows.length;j++){newRows[j-1]=table.rows[j];newRows[j-1].oldPosition=j-1;}
|
||||
newRows.sort(ts_stableSort(sortfn));if(img.getAttribute("sortdir")=='down'){newRows.reverse();ts_arrowUp(img);}else{ts_arrowDown(img);}
|
||||
for(i=0;i<newRows.length;i++){if(!newRows[i].className||(newRows[i].className&&(newRows[i].className.indexOf('sortbottom')==-1)))
|
||||
table.tBodies[0].appendChild(newRows[i]);}
|
||||
for(i=0;i<newRows.length;i++){if(newRows[i].className&&(newRows[i].className.indexOf('sortbottom')!=-1))
|
||||
table.tBodies[0].appendChild(newRows[i]);}
|
||||
var allimgs=document.getElementsByTagName("img");for(var ci=0;ci<allimgs.length;ci++){var one_img=allimgs[ci];if(one_img!=img&&one_img.className=='sortarrow'&&getParent(one_img,"table")==getParent(lnk,"table")){one_img.src=arrowBlank;one_img.setAttribute('sortdir','');}}}
|
||||
function getParent(el,pTagName){if(el==null)
|
||||
return null;else if(el.nodeType==1&&el.tagName.toLowerCase()==pTagName.toLowerCase())
|
||||
return el;else
|
||||
return getParent(el.parentNode,pTagName);}
|
||||
function ts_stableSort(sortfn){function stableSort(a,b){var cmp=sortfn(a,b);if(cmp!=0){return cmp;}else{return a.oldPosition-b.oldPosition;}}
|
||||
return stableSort;}
|
||||
function ts_sort_date(a,b){aa=trim(ts_getInnerText(a.cells[SORT_COLUMN_INDEX]));bb=trim(ts_getInnerText(b.cells[SORT_COLUMN_INDEX]));if(aa.length==10){dt1=aa.substr(6,4)+aa.substr(3,2)+aa.substr(0,2);}else{yr=aa.substr(6,2);if(parseInt(yr)<50){yr='20'+yr;}else{yr='19'+yr;}
|
||||
dt1=yr+aa.substr(3,2)+aa.substr(0,2);}
|
||||
if(bb.length==10){dt2=bb.substr(6,4)+bb.substr(3,2)+bb.substr(0,2);}else{yr=bb.substr(6,2);if(parseInt(yr)<50){yr='20'+yr;}else{yr='19'+yr;}
|
||||
dt2=yr+bb.substr(3,2)+bb.substr(0,2);}
|
||||
if(dt1==dt2)return 0;if(dt1<dt2)return-1;return 1;}
|
||||
function ts_sort_currency(a,b){aa=ts_getInnerText(a.cells[SORT_COLUMN_INDEX]).replace(/[^0-9.]/g,'');bb=ts_getInnerText(b.cells[SORT_COLUMN_INDEX]).replace(/[^0-9.]/g,'');return parseFloat(aa)-parseFloat(bb);}
|
||||
function ts_sort_numeric(a,b){aa=parseFloat(ts_getInnerText(a.cells[SORT_COLUMN_INDEX]));if(isNaN(aa))aa=0;bb=parseFloat(ts_getInnerText(b.cells[SORT_COLUMN_INDEX]));if(isNaN(bb))bb=0;return aa-bb;}
|
||||
function ts_sort_caseinsensitive(a,b){aa=ts_getInnerText(a.cells[SORT_COLUMN_INDEX]).toLowerCase();bb=ts_getInnerText(b.cells[SORT_COLUMN_INDEX]).toLowerCase();if(aa==bb)return 0;if(aa<bb)return-1;return 1;}
|
||||
function ts_sort_default(a,b){aa=ts_getInnerText(a.cells[SORT_COLUMN_INDEX]);bb=ts_getInnerText(b.cells[SORT_COLUMN_INDEX]);if(aa==bb)return 0;if(aa<bb)return-1;return 1;}
|
||||
function addEvent(elm,evType,fn,useCapture){if(elm.addEventListener){elm.addEventListener(evType,fn,useCapture);return true;}else if(elm.attachEvent){var r=elm.attachEvent("on"+evType,fn);return r;}else{alert("Handler could not be removed");}}
|
|
@ -0,0 +1,168 @@
|
|||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
|
||||
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en" dir="ltr">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<base href=".">
|
||||
<title>{{ series|capitalize }} release status</title>
|
||||
<link rel="shortcut icon"
|
||||
href="https://blueprints.launchpad.net/@@/launchpad.png">
|
||||
<link type="text/css" rel="stylesheet" media="screen,print" href="combo.css">
|
||||
<script type="text/javascript" src="sorting.js"></script>
|
||||
<script type='text/javascript' src='https://www.google.com/jsapi'></script>
|
||||
<script type='text/javascript'>
|
||||
google.load('visualization', '1', {packages:['gauge']});
|
||||
google.setOnLoadCallback(drawChart);
|
||||
function drawChart() {
|
||||
var data = new google.visualization.DataTable();
|
||||
data.addColumn('string', 'Label');
|
||||
data.addColumn('number', 'Value');
|
||||
data.addRows(1);
|
||||
data.setValue(0, 0, '');
|
||||
data.setValue(0, 1, {{ gaugedata.progress }});
|
||||
var chart = new google.visualization.Gauge(
|
||||
document.getElementById('chart_div'));
|
||||
var options = {width: 140, height: 140,
|
||||
greenFrom: {{ gaugedata.green }}, greenTo: {{ gaugedata.yellow }},
|
||||
yellowFrom: {{ gaugedata.yellow }}, yellowTo: {{ gaugedata.red }},
|
||||
redFrom: {{ gaugedata.red }}, redTo: {{ gaugedata.end }},
|
||||
majorTicks: [ {{ gaugedata.ticks }} ],
|
||||
minorTicks: 0, max: {{ gaugedata.end}} };
|
||||
chart.draw(data, options);
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body id="document" class="tab-specifications">
|
||||
<div class="yui-d0">
|
||||
<div class="flowed-block">
|
||||
<div id='chart_div'></div>
|
||||
</div>
|
||||
<div class="flowed-block wide">
|
||||
<h1>OpenStack blueprints for {{ series }}</h1><p> </p>
|
||||
<a href="http://wiki.openstack.org/{{ series|capitalize }}ReleaseSchedule">Release
|
||||
Schedule</a><br>
|
||||
<a href="http://wiki.openstack.org/ReleaseCycle">Release
|
||||
Cycle</a><br> <br>
|
||||
<ol class="breadcrumbs">
|
||||
<li>Page refreshed at {{ date }} UTC </li>
|
||||
<li>{{ activebps|length + pastbps|length }} total blueprints </li>
|
||||
<li>{{ activebps|length }} active blueprints</li>
|
||||
</ol>
|
||||
<ol class="breadcrumbs">
|
||||
<li><img src=bmpNEEDSREVIEW.png> Proposed change (needs review) </li>
|
||||
<li><img src=bmpWORKINPROGRESS.png> Submitted change </li>
|
||||
<li><img src=bmpMERGED.png> Merged change</li>
|
||||
</ol>
|
||||
</div>
|
||||
<table class="listing sortable" id="speclisting">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><a href="#" class="sortheader" id="sortprio"
|
||||
onclick="ts_resortTable(this); return false;">Priority<img
|
||||
class="sortarrow" src="arrowBlank" height="6" width="9"></a></th>
|
||||
<th><a href="#" class="sortheader"
|
||||
onclick="ts_resortTable(this); return false;">Project<img
|
||||
class="sortarrow" src="arrowDown" height="6" width="9"></a></th>
|
||||
<th><a href="#" class="sortheader" id="sortmilestone"
|
||||
onclick="ts_resortTable(this); return false;">Milestone<img
|
||||
class="sortarrow" src="arrowBlank" height="6" width="9"></a></th>
|
||||
<th><a href="#" class="sortheader"
|
||||
onclick="ts_resortTable(this); return false;">Blueprint<img
|
||||
class="sortarrow" src="arrowBlank" height="6" width="9"></a></th>
|
||||
<th><a href="#" class="sortheader" id="sortdelivery"
|
||||
onclick="ts_resortTable(this); return false;">Delivery<img
|
||||
class="sortarrow" src="arrowBlank" height="6" width="9"></a></th>
|
||||
<th><a href="#" class="sortheader"
|
||||
onclick="ts_resortTable(this); return false;">Assignee<img
|
||||
class="sortarrow" src="arrowBlank" height="6" width="9"></a></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% macro render_bp(bp) -%}
|
||||
<tr>
|
||||
<td>
|
||||
<span class="sortkey">{{ bp.priorityindex }}</span>
|
||||
<span class="specpriority{{ bp.priority|upper }}">{{ bp.priority }}</span>
|
||||
</td>
|
||||
<td>
|
||||
<a href="https://blueprints.launchpad.net/{{bp.pname}}/{{series}}">{{bp.pname}}</a>
|
||||
</td>
|
||||
<td>
|
||||
<span class="sortkey">{{bp.milestonedate}}</span>
|
||||
<a href="{{bp.milestonelink}}">{{bp.milestonename}}</a>
|
||||
</td>
|
||||
<td>
|
||||
<a href="https://blueprints.launchpad.net/{{bp.pname}}/+spec/{{bp.name}}">{{bp.name}}</a>
|
||||
</td>
|
||||
<td>
|
||||
<span class="sortkey">{{bp.implementationindex}}</span>
|
||||
<span class="specdelivery{{bp.implementation|replace(' ','')|upper}}">{{bp.implementation}}</span>
|
||||
{% for review in bp.reviews %}
|
||||
<a href="{{review.url}}" title="{{review.subject}}"><img src="bmp{{review.image}}.png"></a>
|
||||
{% endfor %}
|
||||
{% if bp.impl_error %}<img src="error.png" title="%s-{{bp.impl_error}}">
|
||||
{% endif %}
|
||||
{% if bp.impl_warn %}<img src="alert.png" title="%s-{{bp.impl_warn}}">
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<span><a href="https://launchpad.net/~{{bp.assigneename}}">{{bp.assigneedisplay}}</a>
|
||||
{% if bp.assignee_error %}<img src="error.png" title="{{bp.assignee_error}}-">
|
||||
{% endif %}
|
||||
{% if bp.assignee_warn %}<img src="alert.png" title="{{bp.assignee_warn}}-">
|
||||
{% endif %}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
{%- endmacro %}
|
||||
{% for bp in activebps %}{{ render_bp(bp) }}{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<script type="text/javascript">
|
||||
// Sort by default by priority, then delivery
|
||||
ts_resortTable(document.getElementById("sortdelivery"))
|
||||
ts_resortTable(document.getElementById("sortprio"))
|
||||
ts_resortTable(document.getElementById("sortmilestone"))
|
||||
</script>
|
||||
<div class="flowed-block wide">
|
||||
<p> </p>
|
||||
<h2>Past milestones</h2>
|
||||
<ol class="breadcrumbs">
|
||||
<li>{{ pastbps|length }} completed blueprints</li>
|
||||
</ol>
|
||||
</div>
|
||||
<table class="listing sortable" id="speclisting">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><a href="#" class="sortheader" id="sortBprio"
|
||||
onclick="ts_resortTable(this); return false;">Priority<img
|
||||
class="sortarrow" src="arrowBlank" height="6" width="9"></a></th>
|
||||
<th><a href="#" class="sortheader"
|
||||
onclick="ts_resortTable(this); return false;">Project<img
|
||||
class="sortarrow" src="arrowDown" height="6" width="9"></a></th>
|
||||
<th><a href="#" class="sortheader" id="sortBmilestone"
|
||||
onclick="ts_resortTable(this); return false;">Milestone<img
|
||||
class="sortarrow" src="arrowBlank" height="6" width="9"></a></th>
|
||||
<th><a href="#" class="sortheader"
|
||||
onclick="ts_resortTable(this); return false;">Blueprint<img
|
||||
class="sortarrow" src="arrowBlank" height="6" width="9"></a></th>
|
||||
<th><a href="#" class="sortheader" id="sortBdelivery"
|
||||
onclick="ts_resortTable(this); return false;">Delivery<img
|
||||
class="sortarrow" src="arrowBlank" height="6" width="9"></a></th>
|
||||
<th><a href="#" class="sortheader"
|
||||
onclick="ts_resortTable(this); return false;">Assignee<img
|
||||
class="sortarrow" src="arrowBlank" height="6" width="9"></a></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for bp in pastbps %}{{ render_bp(bp) }}{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<script type="text/javascript">
|
||||
// Sort by default by priority, then delivery
|
||||
ts_resortTable(document.getElementById("sortBdelivery"))
|
||||
ts_resortTable(document.getElementById("sortBprio"))
|
||||
ts_resortTable(document.getElementById("sortBmilestone"))
|
||||
</script>
|
||||
</body></html>
|