mirror of
https://github.com/YunoHost/tartiflette.git
synced 2024-09-03 20:06:08 +02:00
Initial commit
This commit is contained in:
commit
56aa387b5d
5 changed files with 339 additions and 0 deletions
32
README.md
Normal file
32
README.md
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
Aiguillette
|
||||||
|
===========
|
||||||
|
|
||||||
|
A quick and dirty PR dashboard using Eden UI
|
||||||
|
|
||||||
|
Install
|
||||||
|
-------
|
||||||
|
|
||||||
|
- Clone this repo
|
||||||
|
- Download and unzip Eden UI into www : http://scripteden.com/download/eden-ui-bootstrap-3-skin/
|
||||||
|
- Install dependencies :
|
||||||
|
```
|
||||||
|
apt install -y python3-pip
|
||||||
|
pip3 install ansi2html
|
||||||
|
```
|
||||||
|
- Make your web browser serve www/index.html
|
||||||
|
|
||||||
|
Usage
|
||||||
|
-----
|
||||||
|
|
||||||
|
```
|
||||||
|
./fetch.py
|
||||||
|
./analyze.py
|
||||||
|
./publish.py
|
||||||
|
```
|
||||||
|
|
||||||
|
- Edit fetch.py and analyze.py to custom list of repo to fetch (should be moved
|
||||||
|
to a central conf file)
|
||||||
|
- Don't know the number of API calls someone is allowed to do, so limit the call
|
||||||
|
to fetch.py :/
|
||||||
|
- Everything is pretty dirty so far and should be cleaned
|
||||||
|
|
91
analyze.py
Executable file
91
analyze.py
Executable file
|
@ -0,0 +1,91 @@
|
||||||
|
#!/usr/bin/python3
|
||||||
|
|
||||||
|
import json
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
repos = ["yunohost", "yunohost-admin", "SSOwat", "moulinette", "doc", "ynh-dev",
|
||||||
|
"apps", "CI_package_check", "example_ynh", "package_linter", "Simone",
|
||||||
|
"project-organization", "build.yunohost.org", "dynette", "YunoPorts",
|
||||||
|
"rebuildd", "cd_build", "install_script"]
|
||||||
|
|
||||||
|
|
||||||
|
prs = {}
|
||||||
|
|
||||||
|
|
||||||
|
def githubDateToDaysAgo(date):
|
||||||
|
now = datetime.datetime.now()
|
||||||
|
date = datetime.datetime.strptime(date, "%Y-%m-%dT%H:%M:%SZ")
|
||||||
|
return (now - date).days
|
||||||
|
|
||||||
|
|
||||||
|
def isPRDying(pr):
|
||||||
|
return (pr["createdDaysAgo"] > 60 and pr["updatedDaysAgo"] > 30)
|
||||||
|
|
||||||
|
|
||||||
|
def priority(pr):
|
||||||
|
if "important" in pr["labels"]:
|
||||||
|
base_priority = 100
|
||||||
|
elif "opinion needed" in pr["labels"]:
|
||||||
|
base_priority = 50
|
||||||
|
elif "work needed" in pr["labels"]:
|
||||||
|
base_priority = -50
|
||||||
|
elif "postponed" in pr["labels"]:
|
||||||
|
base_priority = -100
|
||||||
|
elif "inactive" in pr["labels"]:
|
||||||
|
base_priority = -100
|
||||||
|
else:
|
||||||
|
base_priority = 0
|
||||||
|
|
||||||
|
if "dying" in pr["labels"] and base_priority > -100:
|
||||||
|
base_priority += 5
|
||||||
|
|
||||||
|
return base_priority
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
|
||||||
|
for repo in repos:
|
||||||
|
|
||||||
|
print("Analyzing %s ..." % repo)
|
||||||
|
|
||||||
|
with open("./%s.json" % repo, "r") as f:
|
||||||
|
j = json.loads(f.read())
|
||||||
|
|
||||||
|
for issue in j:
|
||||||
|
|
||||||
|
# Ignore non-pullrequest issues
|
||||||
|
if "pull_request" not in issue.keys():
|
||||||
|
continue
|
||||||
|
|
||||||
|
pr = {
|
||||||
|
"repo": repo,
|
||||||
|
"title": issue["title"],
|
||||||
|
"labels": [label["name"] for label in issue["labels"]],
|
||||||
|
"id": "%s-%s" % (repo, issue["number"]),
|
||||||
|
"createdDaysAgo": githubDateToDaysAgo(issue["created_at"]),
|
||||||
|
"updatedDaysAgo": githubDateToDaysAgo(issue["updated_at"]),
|
||||||
|
"url": issue["pull_request"]["html_url"]
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(pr["title"]) > 53:
|
||||||
|
pr["title"] = pr["title"][0:50] + "..."
|
||||||
|
|
||||||
|
if isPRDying(pr):
|
||||||
|
pr["labels"].append("dying")
|
||||||
|
pr["priority"] = priority(pr)
|
||||||
|
|
||||||
|
prs[pr["id"]] = pr
|
||||||
|
|
||||||
|
prs_sorted = sorted(prs.keys(), key=lambda x: (prs[x]["priority"],
|
||||||
|
prs[x]["createdDaysAgo"]), reverse=True )
|
||||||
|
|
||||||
|
summary = []
|
||||||
|
|
||||||
|
for name in prs_sorted:
|
||||||
|
summary.append(prs[name])
|
||||||
|
|
||||||
|
with open("summary.json", "w") as f:
|
||||||
|
f.write(json.dumps(summary))
|
||||||
|
|
||||||
|
|
||||||
|
main()
|
18
fetch.py
Executable file
18
fetch.py
Executable file
|
@ -0,0 +1,18 @@
|
||||||
|
#!/usr/bin/python3
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
|
repos = ["yunohost", "yunohost-admin", "SSOwat", "moulinette", "doc", "ynh-dev",
|
||||||
|
"apps", "CI_package_check", "example_ynh", "package_linter", "Simone",
|
||||||
|
"project-organization", "build.yunohost.org", "dynette", "YunoPorts",
|
||||||
|
"rebuildd", "cd_build", "install_script"]
|
||||||
|
|
||||||
|
|
||||||
|
for repo in repos:
|
||||||
|
|
||||||
|
print("Fetching pull requests for %s" % repo)
|
||||||
|
|
||||||
|
issues = requests.get("https://api.github.com/repos/yunohost/%s/issues?per_page=100" % repo)
|
||||||
|
|
||||||
|
with open("./%s.json" % repo, "w") as f:
|
||||||
|
f.write(issues.text)
|
44
publish.py
Executable file
44
publish.py
Executable file
|
@ -0,0 +1,44 @@
|
||||||
|
#!/usr/bin/python3
|
||||||
|
|
||||||
|
import os
|
||||||
|
import json
|
||||||
|
|
||||||
|
from jinja2 import Template
|
||||||
|
from ansi2html import Ansi2HTMLConverter
|
||||||
|
from ansi2html.style import get_styles
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
output_dir = "./www/"
|
||||||
|
|
||||||
|
template_path = os.path.join(output_dir,"template.html")
|
||||||
|
output_path = os.path.join(output_dir,"index.html")
|
||||||
|
|
||||||
|
summary_path = os.path.join("./", "summary.json")
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
conv = Ansi2HTMLConverter()
|
||||||
|
shell_css = "\n".join(map(str, get_styles(conv.dark_bg, conv.scheme)))
|
||||||
|
|
||||||
|
def shell_to_html(shell):
|
||||||
|
return conv.convert(shell, False)
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
|
||||||
|
# Fetch the list of all reports, sorted in reverse-chronological order
|
||||||
|
|
||||||
|
pr_list = json.load(open(summary_path))
|
||||||
|
|
||||||
|
# Generate the output using the template
|
||||||
|
|
||||||
|
template = open(template_path, "r").read()
|
||||||
|
t = Template(template)
|
||||||
|
|
||||||
|
result = t.render(data=pr_list, convert=shell_to_html, shell_css=shell_css)
|
||||||
|
|
||||||
|
open(output_path, "w").write(result)
|
||||||
|
|
||||||
|
print("Done.")
|
154
www/template.html
Normal file
154
www/template.html
Normal file
|
@ -0,0 +1,154 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Bootstrap Skin: Eden</title>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<link rel="stylesheet" href="css/bootstrap.css" media="screen">
|
||||||
|
<link rel="stylesheet" href="skins/eden.css" media="screen">
|
||||||
|
<link href="//maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css" rel="stylesheet">
|
||||||
|
<link href="css/animate.css" rel="stylesheet">
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.navbar-holder-dark{
|
||||||
|
padding: 20px 20px 200px 20px;
|
||||||
|
background: #333333;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
width:1500px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
|
||||||
|
<div class="page-header" id="banner">
|
||||||
|
<div class="row">
|
||||||
|
<h1>Yunohost Pull Requests Dashboard</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>WTF is this shit? How is priority defined?</h3>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
First with labels :
|
||||||
|
<p>"important" implies top-priority ------
|
||||||
|
"opinion-needed" implies medium priority ------
|
||||||
|
"work needed" implies low priority ------
|
||||||
|
"inactive" / "postponed" implies very low priority.</p>
|
||||||
|
Then with creation time :
|
||||||
|
<p>older PRs have higher priority than newer PR</p>
|
||||||
|
'Dying' status
|
||||||
|
<p>A PR is considered "dying" if it has been created more than 60
|
||||||
|
days ago and not updated since 30 days. This is meant to be an incentive to
|
||||||
|
either revive it or flag it as inactive/postponed... 'Dying' PRs
|
||||||
|
have a small boost in priority to be listed first</a>
|
||||||
|
</br>
|
||||||
|
<p>Repos considered for now (completely arbitrary) : [yunohost, yunohost-admin, SSOwat, moulinette, doc, ynh-dev,
|
||||||
|
apps, CI_package_check, example_ynh, package_linter, Simone,
|
||||||
|
project-organization, build.yunohost.org, dynette, YunoPorts,
|
||||||
|
rebuildd, cd_build, install_script]</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>What should you review ?</h3>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
|
||||||
|
<div class="col-md-offset-3 col-md-6">
|
||||||
|
<div class="bs-component">
|
||||||
|
<ul class="nav nav-pills">
|
||||||
|
<li class="active"><a href="#">All <span class="badge">1664</span></a></li>
|
||||||
|
<li><a href="#">Core <span class="badge">42</span></a></li>
|
||||||
|
<li><a href="#">Apps <span class="badge">42</span></a/li>
|
||||||
|
<li><a href="#">Infra / dist<span class="badge">42</span></a></li>
|
||||||
|
<li><a href="#">Doc / i18n<span class="badge">42</span></a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-12">
|
||||||
|
<div>
|
||||||
|
|
||||||
|
|
||||||
|
<table class="table table-striped table-responsive">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th></th>
|
||||||
|
<th>Title</th>
|
||||||
|
<th>Created</th>
|
||||||
|
<th>Labels</th>
|
||||||
|
<th>Reviews</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for pr in data %}
|
||||||
|
<tr>
|
||||||
|
<td><center><a class="btn
|
||||||
|
{% if pr.priority >= 100 %}btn-warning{% else %}
|
||||||
|
{% if pr.priority >= 50 %}btn-primary{% else %}
|
||||||
|
{% if pr.priority >= 0 %}btn-info{% else %}
|
||||||
|
{% if pr.priority >= -50 %}btn-default{% else %}
|
||||||
|
btn-link{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}"
|
||||||
|
href="{{ pr.url }}">{{ pr.id }}</a></center></td>
|
||||||
|
<td><strong>{{ pr.title }}</strong></td>
|
||||||
|
<td>{{ pr.createdDaysAgo }} days ago</td>
|
||||||
|
<td>
|
||||||
|
{% for label in pr.labels %}
|
||||||
|
<span class="label
|
||||||
|
{% if label == "important" %}label-danger{%endif%}
|
||||||
|
{% if label == "opinion needed" %}label-warning{%endif%}
|
||||||
|
{% if label == "small decision" %}label-info{%endif%}
|
||||||
|
{% if label == "medium decision" %}label-info{%endif%}
|
||||||
|
{% if label == "big decision" %}label-info{%endif%}
|
||||||
|
{% if label == "work needed" %}label-primary{%endif%}
|
||||||
|
{% if label == "inactive" %}label-default{%endif%}
|
||||||
|
{% if label == "postponed" %}label-default{%endif%}
|
||||||
|
{% if label == "dying" %}label-danger{%endif%}
|
||||||
|
">{{ label }}</span>
|
||||||
|
|
||||||
|
{% endfor %}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span style="border:1px solid black; border-radius:4px; padding-left:2px; padding-right:2px;">foo <span class="text-success">✔</span></span>
|
||||||
|
<span style="border:1px solid black; border-radius:4px; padding-left:2px; padding-right:2px;">bar <span class="text-danger" >✘</span></span>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-12">
|
||||||
|
<hr/>
|
||||||
|
<p>CSS Skin/boilerplate/whatever you call it : <a href="http://scripteden.com/" rel="nofollow">Script Eden</a>.</p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<script src="https://code.jquery.com/jquery-2.1.3.min.js"></script>
|
||||||
|
<script src="js/bootstrap.min.js"></script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
Loading…
Reference in a new issue