Commit c5d1e03b authored by Laurent Peuch's avatar Laurent Peuch
Browse files

feat(mr): total rework of the dashboard to make it action oriented

parent 37b012bd3353
#!/usr/bin/env python3
import os
import json
import gitlab
import jinja2
import argparse
from typing import Dict
from datetime import datetime
from dateutil.parser import parse
gl = gitlab.Gitlab("https://forge.extranet.logilab.fr", oauth_token=os.environ.get("TOKEN"))
all_core_projects = [x for x in gl.projects.list(all=True) if "cw-core" in x.tag_list]
parser = argparse.ArgumentParser(description='generates public/mr.html')
parser.add_argument('--cache', required=False,
help='uses or generate a json cache file')
context = {"projects": [], "cubes": []}
args = parser.parse_args()
for project in all_core_projects:
# groups:
# - ready to be published with green pipeline and no rebase
# - ready to be published with red or unknown pipeline and no rebase
# - ready to be published but with manual rebase
# - old un-updated MR
# - un-tagged not in draft MR
# - to review
# - other
merge_requests = []
context: Dict[str, list] = {
"ready_to_be_merged": [],
"ready_to_be_merged_red": [],
"ready_to_be_merged_rebase": [],
"unactive_mr": [],
"old_mr": [],
"untagged_mr": [],
"to_review": [],
"need_revision": [],
"waiting_draft": [],
"other": [],
}
for mr_object in project.mergerequests.list(all=True):
if mr_object.state != "opened":
continue
if not args.cache or not os.path.exists(args.cache):
all_core_projects = [x for x in gl.projects.list(all=True) if "cw-core" in x.tag_list]
all_cubes = [x for x in gl.projects.list(all=True) if x.namespace["name"] == "cubes"]
if "Need Revision" in mr_object.labels:
continue
for category, projects in [("core-projects", all_core_projects), ("cubes", all_cubes)]:
if "Waiting" in mr_object.labels:
continue
for project in projects:
current = {
"ready_to_be_merged": [],
"ready_to_be_merged_red": [],
"ready_to_be_merged_rebase": [],
"unactive_mr": [],
"old_mr": [],
"untagged_mr": [],
"to_review": [],
"need_revision": [],
"waiting_draft": [],
"other": [],
}
if mr_object.title.startswith("WIP:"):
continue
for mr_object in project.mergerequests.list(all=True):
# skip closed mrs
if mr_object.state != "opened":
continue
merge_request = mr_object.attributes
pipelines = mr_object.pipelines()
merge_request["pipeline"] = pipelines[0] if pipelines else None
merge_requests.append(merge_request)
merge_request = mr_object.attributes
pipelines = mr_object.pipelines()
merge_request["pipeline"] = pipelines[0] if pipelines else None
merge_request["created_since_x_days"] = (datetime.now() - parse(mr_object.created_at).replace(tzinfo=None)).days
merge_request["updated_since_x_days"] = (datetime.now() - parse(mr_object.updated_at).replace(tzinfo=None)).days
merge_request["approved"] = mr_object.approvals.get().approved
context["projects"].append({
"project": project.attributes,
"merge_requests": merge_requests,
"labels": {x.name: x.attributes for x in project.labels.list(all=True)}
})
if "Waiting" in mr_object.labels:
current["waiting_draft"].append(merge_request)
continue
if mr_object.title.lower().startswith(("draft: ", "wip: ")):
current["waiting_draft"].append(merge_request)
continue
if "To Publish" in mr_object.labels or merge_request["approved"]:
if mr_object.attributes["merge_status"] != "can_be_merged":
current["ready_to_be_merged_rebase"].append(merge_request)
elif merge_request["pipeline"] and merge_request["pipeline"]["status"] == "success":
current["ready_to_be_merged"].append(merge_request)
else:
current["ready_to_be_merged_red"].append(merge_request)
continue
notes = sorted(mr_object.notes.list(all=True), key=lambda x: x.updated_at)
if not notes and merge_request["created_since_x_days"] >= 15:
current["unactive_mr"].append(merge_request)
continue
elif (notes and notes[-1].author["username"] == "assignbot"
and "It seems that there hasn't been any activity concerning this merge \
request for some time." in notes[-1].body):
current["unactive_mr"].append(merge_request)
continue
elif merge_request["updated_since_x_days"] >= 15:
current["unactive_mr"].append(merge_request)
continue
if merge_request["created_since_x_days"] >= 45:
current["old_mr"].append(merge_request)
continue
if not mr_object.labels:
current["untagged_mr"].append(merge_request)
continue
if "To Review" in mr_object.labels:
current["to_review"].append(merge_request)
continue
context["projects"] = sorted(context["projects"], key=lambda x: x["project"]["name"].lower())
if "need revision" in [x.lower() for x in mr_object.labels]:
current["need_revision"].append(merge_request)
continue
current["other"].append(merge_request)
for key in current:
if current[key]:
context[key].append({
"project": project.attributes,
"merge_requests": current[key],
"labels": {x.name: x.attributes for x in project.labels.list(all=True)}
})
# sort all project-mr groups by project.name
for key in context.keys():
context[key] = sorted(context[key], key=lambda x: x["project"]["name"].lower())
if args.cache:
json.dump(context, open(args.cache, "w"))
else:
context = json.load(open(args.cache))
os.system("mkdir -p public") # XXX lazy
result = jinja2.Template(open("template/mr.html", "r").read()).render(**context)
open("public/mr.html", "w").write(result)
all_cubes = [x for x in gl.projects.list(all=True) if x.namespace["name"] == "cubes"]
import sys
sys.exit(0)
for cube in all_cubes:
......
......@@ -6,3 +6,4 @@ rdflib
pygments
anybadge
hg+https://forge.extranet.logilab.fr/open-source/heptalab
python-dateutil
{% macro render_mrs(mrs, show_how_old="") %}
{% for project in mrs %}
{% if project["merge_requests"] %}
<h3>{{ project["project"]["name"] }}</h3>
<ul>
{% for mr in project["merge_requests"] %}
<li>
{% if mr["pipeline"] %}
<span class="job job-{{ mr["pipeline"]["status"] }}"> </span>
{% else %}
<span class="job job-empty">?</span>
{% endif %}
{% if not mr["assignees"] %}
<span title="no one is assigned">
⚠️
</span>
{% endif %}
<a href="{{ mr["web_url"] }}" target="_blank">{{ mr["title"] }}</a>
by <i>{{ mr["author"]["name"] }}</i>
{% if mr["assignees"] %}
(assigned: {% for assigned in mr["assignees"] %}{{ assigned["name"] }}{% endfor %})
{% endif %}
{% for label in mr["labels"] %}
<span class="label" style="color: {{ project["labels"][label]["text_color"] }}; background-color: {{ project["labels"][label]["color"] }}">{{ label }}</span>
{% endfor %}
{% if show_how_old == "updated" %}
(updated {{ mr["updated_since_x_days"] }} days ago)
{% elif show_how_old == "created" %}
(created {{ mr["created_since_x_days"] }} days ago)
{% endif %}
</li>
{% endfor %}
</ul>
{% endif %}
{% endfor %}
{% endmacro %}
<!doctype html>
<html>
<head>
......@@ -7,119 +50,80 @@
<body>
<h1>MR Center<small>all the Merge Requests that need attention in a single place</small></h1>
<h2>CubicWeb Core Projects</h2>
{% for project in projects %}
{% if project["merge_requests"] %}
<h3>{{ project["project"]["name"] }}</h3>
<ul>
{% for mr in project["merge_requests"] %}
<li>
{% if mr["pipeline"] %}
<span class="job job-{{ mr["pipeline"]["status"] }}"> </span>
{% else %}
<span class="job job-empty">?</span>
{% endif %}
{% if not mr["assignees"] %}
<span title="no one is assigned">
⚠️
</span>
{% endif %}
<a href="{{ mr["web_url"] }}" target="_blank">{{ mr["title"] }}</a>
by <i>{{ mr["author"]["name"] }}</i>
{% if mr["assignees"] %}
(assigned: {% for assigned in mr["assignees"] %}{{ assigned["name"] }}{% endfor %})
{% endif %}
{% for label in mr["labels"] %}
<span class="label" style="color: {{ project["labels"][label]["text_color"] }}; background-color: {{ project["labels"][label]["color"] }}">{{ label }}</span>
{% endfor %}
</li>
{% endfor %}
</ul>
{% if ready_to_be_merged %}
<h2>MRs ready to be merged</h2>
<i>MRs that are tagged as "To Publish" or approved</i>
{{ render_mrs(ready_to_be_merged) }}
<hr>
{% endif %}
{% endfor %}
{% if ready_to_be_merged_red %}
<h2>MRs ready to be merged but with a <u><i>failing pipeline</i></u> (or unknown pipeline status)</h2>
<i>MRs that are tagged as "To Publish" or approved but the pipeline for this MR is failing (or with an unknown status, probably because there is not pipeline)</i>
{{ render_mrs(ready_to_be_merged_red) }}
<hr>
{% endif %}
<h2>Project Cubes</h2>
{% for cube in cubes %}
{% if cube["merge_requests"] and "project-dependency" in cube["cube"]["tag_list"] %}
<h3>{{ cube["cube"]["name"] }}</h3>
<ul>
{% for mr in cube["merge_requests"] %}
<li>
{% if mr["pipeline"] %}
<span class="job job-{{ mr["pipeline"]["status"] }}"></span>
{% else %}
<span class="job job-empty">?</span>
{% endif %}
{% if not mr["assignees"] %}
<span title="no one is assigned">
⚠️
</span>
{% endif %}
<a href="{{ mr["web_url"] }}">{{ mr["title"] }}</a>
by <i>{{ mr["author"]["name"] }}</i>
{% if mr["assignees"] %}
(assigned: {% for assigned in mr["assignees"] %}{{ assigned["name"] }}{% endfor %})
{% endif %}
{% for label in mr["labels"] %}
<span class="label" style="color: {{ cube["labels"][label]["text_color"] }}; background-color: {{ cube["labels"][label]["color"] }}">{{ label }}</span>
{% endfor %}
</li>
{% endfor %}
</ul>
{% if ready_to_be_merged_rebase %}
<h2>MRs ready to be merged but needs rebasing</h2>
<i>MRs that are tagged as "To Publish" or approved but rebased is need to be done before</i>
{{ render_mrs(ready_to_be_merged_rebase) }}
<hr>
{% endif %}
{% endfor %}
{% if unactive_mr %}
<h2>Unactive MRs</h2>
<i>MRs that haven't been updated since more than 15 days or in which assignbot is the last one to have spoken</i>
{{ render_mrs(unactive_mr, show_how_old="updated") }}
<hr>
{% endif %}
<h2>Unused Cubes</h2>
{% for cube in cubes %}
{% if cube["merge_requests"] and "project-dependency" not in cube["cube"]["tag_list"] %}
<h3>{{ cube["cube"]["name"] }}</h3>
<ul>
{% for mr in cube["merge_requests"] %}
<li>
{% if mr["pipeline"] %}
<span class="job job-{{ mr["pipeline"]["status"] }}"></span>
{% else %}
<span class="job job-empty">?</span>
{% endif %}
{% if not mr["assignees"] %}
<span title="no one is assigned">
⚠️
</span>
{% endif %}
<a href="{{ mr["web_url"] }}">{{ mr["title"] }}</a>
by <i>{{ mr["author"]["name"] }}</i>
{% if mr["assignees"] %}
(assigned: {% for assigned in mr["assignees"] %}{{ assigned["name"] }}{% endfor %})
{% endif %}
{% for label in mr["labels"] %}
<span class="label" style="color: {{ cube["labels"][label]["text_color"] }}; background-color: {{ cube["labels"][label]["color"] }}">{{ label }}</span>
{% endfor %}
</li>
{% endfor %}
</ul>
{% if old_mr %}
<h2>Old MRs</h2>
<i>MRs that have been created 45 days ago and aren't draft</i>
{{ render_mrs(old_mr, show_how_old="created") }}
<hr>
{% endif %}
{% endfor %}
{% if untagged_mr %}
<h2>Unlabeled MRs</h2>
<i>MRs that don't have any tag. Hint: you probably want to add a 'To review' tag or mark the MR as draft</i>
{{ render_mrs(untagged_mr) }}
<hr>
{% endif %}
{% if to_review %}
<h2>To review MRs</h2>
{{ render_mrs(to_review) }}
<hr>
{% endif %}
{% if need_revision %}
<h2>MRs that are tagged as needing revision</h2>
{{ render_mrs(need_revision) }}
<hr>
{% endif %}
{% if waiting_draft %}
<h2>MRs tagged as waiting or in draft</h2>
{{ render_mrs(waiting_draft) }}
<hr>
{% endif %}
{% if other %}
<h2>Other MRs</h2>
<i>MRs that doesn't fall in any categories but aren't draft nor tagged as "Waiting"</i>
{{ render_mrs(other) }}
{% endif %}
</body>
</html>
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment