Files
bugdaystats/bugdaystats.py
Masayuki Igawa f10ee2d6bd Introduce daily bug stats chart
This commit introduces daily bug stats graphs. To do this, this commit
also introduces nvd3 js library for enabling to use lineWithFocusChart.
It is not same style chart of the main pages ones. Because
stackedAreaWithFocusChart is a little bit buggy. So it sometimes shows
wrong charts. And I also think lineWithFocusChart is better than the
stackedArea chart because it is simple enough to understand the trend.

Change-Id: Iacf22cddbd676d1447cfd9ae8ba1e7ce387720f6
2021-07-26 14:06:14 -05:00

199 lines
7.2 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 json
import os
import sys
import time
from jinja2 import Environment
from jinja2 import FileSystemLoader
from launchpadlib.launchpad import Launchpad
def create_files(templatepath, outputpath, projects, config):
# Create index file
env = Environment(loader=FileSystemLoader(templatepath))
indexfile = os.path.join(outputpath, "index.html")
template = env.get_template('index.html')
daily = config.get('daily')
template.stream(projects=projects,
openstack_status=openstack_status,
daily=daily).dump(indexfile)
# Create each project file
for project in projects:
if 'height' in project:
project['height'] = project['height'] - 30
projectfile = os.path.join(outputpath,
"%s.html" % project['project'])
if not os.path.exists(projectfile):
template = env.get_template('project.html')
template.stream(project=project).dump(projectfile)
if daily:
projectfile = os.path.join(outputpath,
"%s-daily.html" % project['project'])
if not os.path.exists(projectfile):
template = env.get_template('project-daily.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("No directory supplied", file=sys.stderr)
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('%s is not a directory' % outputpath, file=sys.stderr)
sys.exit(1)
if not os.path.isfile(configpath):
print('%s does not contain config.js' % basepath, file=sys.stderr)
sys.exit(1)
cachedir = os.path.expanduser("~/.launchpadlib/cache/")
if not os.path.exists(cachedir):
os.makedirs(cachedir, 0o0700)
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, config)
# 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)