bugdaystats/bugdaystats.py
Ken'ichi Ohmichi 28514885b5 Add "daily" option to bugdaystats.py
Recently, we are triaging bugs of Tempest on the launchpad and that
work was fine to clean up old bugs and fixed bugs with different
patches.
Current bugdaystats shows it in last several days statically, and it
would be nice to add more long terms like in 30 days or a release to see
bug triage trends.

This patch adds a "daily" option to bugdaystats.py for making daily data
json files. The file can be used to show long term data in other graphs.

Change-Id: Ieac396389ce13468b717d03264164c77f7c36104
2016-09-20 12:35:53 +02:00

187 lines
6.7 KiB
Python
Executable File

#!/usr/bin/env python
# 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.
import simplejson as json
import os
import sys
import time
from jinja2 import Environment, FileSystemLoader
from launchpadlib.launchpad import Launchpad
def create_files(templatepath, outputpath, projects):
# Create index file
env = Environment(loader=FileSystemLoader(templatepath))
indexfile = os.path.join(outputpath, "index.html")
template = env.get_template('index.html')
template.stream(projects=projects,
openstack_status=openstack_status).dump(indexfile)
# Create each project file
for project in projects:
projectfile = os.path.join(outputpath, "%s.html" % project['project'])
if not os.path.exists(projectfile):
if 'height' in project:
project['height'] = project['height'] - 30
template = env.get_template('project.html')
template.stream(project=project).dump(projectfile)
def update_stats(outputpath, project_name, rotation, daily=False):
now = int(time.time())
records = []
counts = {}
project = launchpad.projects[project_name]
if daily:
filename = "%s-bug-stats-daily.json" % project_name
else:
filename = "%s-bug-stats.json" % project_name
project_stats_filename = os.path.join(outputpath, filename)
try:
data_file = open(project_stats_filename, 'r')
json_data = json.load(data_file)
data_file.close()
for record in json_data['records']:
if daily:
if (now - record['date']) < (24 * 60 * 60):
# Skip to update stats if the recode contains the same
# day data.
return
if rotation:
if (now - record['date']) > (rotation * 24 * 60 * 60):
continue
records.append(record)
except IOError:
pass
open_statuses = ["New", "Incomplete", "Incomplete (with response)",
"Incomplete (without response)", "Confirmed", "Triaged",
"In Progress"]
closed_statuses = ["Fix Committed", "Fix Released", "Invalid", "Won't Fix"]
importances = ["Undecided", "Wishlist", "Low", "Medium", "High",
"Critical"]
open_tasks = project.searchTasks(status=open_statuses,
order_by='-datecreated',
omit_duplicates=True)
open_tasks_count = int(open_tasks._wadl_resource.representation
['total_size'])
counts['date'] = now
if open_tasks_count != 0:
for open_status in open_statuses:
if open_status == 'Incomplete':
status_count = int(
project.searchTasks(
status=['Incomplete (with response)',
'Incomplete (without response)'],
omit_duplicates=True
)._wadl_resource.representation['total_size'])
else:
status_count = int(
project.searchTasks(
status='%s' % open_status,
omit_duplicates=True
)._wadl_resource.representation['total_size'])
status_key = open_status.replace(" ", "").lower()
status_key = status_key.replace("(", "-")
status_key = status_key.replace(")", "")
counts[status_key] = status_count
elif open_tasks_count == 0:
for open_status in open_statuses:
status_key = open_status.replace(" ", "").lower()
counts[status_key] = 0
for closed_status in closed_statuses:
status_count = int(
project.searchTasks(
status='%s' % closed_status,
omit_duplicates=True
)._wadl_resource.representation['total_size'])
status_key = closed_status.replace(" ", "").replace("'", "").lower()
counts[status_key] = status_count
for importance in importances:
importance_count = int(
project.searchTasks(
importance='%s' % importance,
omit_duplicates=True
)._wadl_resource.representation['total_size'])
counts[importance.lower()] = importance_count
records.append(counts)
report = {'keys': ['date', 'new', 'incomplete', 'confirmed', 'triaged',
'inprogress', 'fixcommitted', 'fixreleased', 'invalid',
'wontfix', 'undecided', 'wishlist', 'low', 'medium',
'high', 'critical'],
'records': records}
project_stats_file = open(project_stats_filename, 'w')
project_stats_file.write(json.dumps(report, indent=4))
project_stats_file.close()
if __name__ == '__main__':
if len(sys.argv) < 2:
print >> sys.stderr, "No directory supplied"
basepath = os.path.dirname(sys.argv[0])
configpath = os.path.join(basepath, "config.js")
templatepath = os.path.join(basepath, "templates")
outputpath = sys.argv[1]
if not os.path.isdir(outputpath):
print >> sys.stderr, '%s is not a directory' % outputpath
sys.exit(1)
if not os.path.isfile(configpath):
print >> sys.stderr, '%s does not contain config.js' % basepath
sys.exit(1)
cachedir = os.path.expanduser("~/.launchpadlib/cache/")
if not os.path.exists(cachedir):
os.makedirs(cachedir, 0700)
with open(configpath, 'r') as configfile:
config = json.load(configfile)
projects = config['projects']
rotation = config.get('rotation')
daily = config.get('daily')
daily_rotation = config.get('daily_rotation')
openstack_status = config.get('openstack_status')
# Create files in output directory, if needed
create_files(templatepath, outputpath, projects)
# Refresh JSON stats files
launchpad = Launchpad.login_anonymously('bugdaystats', 'production',
cachedir)
for p in projects:
update_stats(outputpath, p['project'], rotation)
if (daily):
update_stats(outputpath, p['project'], daily_rotation, daily=True)