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 #!/usr/bin/env python3
import os import os
import json
import gitlab import gitlab
import jinja2 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")) 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 not args.cache or not os.path.exists(args.cache):
if mr_object.state != "opened": all_core_projects = [x for x in gl.projects.list(all=True) if "cw-core" in x.tag_list]
continue all_cubes = [x for x in gl.projects.list(all=True) if x.namespace["name"] == "cubes"]
if "Need Revision" in mr_object.labels: for category, projects in [("core-projects", all_core_projects), ("cubes", all_cubes)]:
continue
if "Waiting" in mr_object.labels: for project in projects:
continue 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:"): for mr_object in project.mergerequests.list(all=True):
continue # skip closed mrs
if mr_object.state != "opened":
continue
merge_request = mr_object.attributes merge_request = mr_object.attributes
pipelines = mr_object.pipelines() pipelines = mr_object.pipelines()
merge_request["pipeline"] = pipelines[0] if pipelines else None merge_request["pipeline"] = pipelines[0] if pipelines else None
merge_requests.append(merge_request) 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({ if "Waiting" in mr_object.labels:
"project": project.attributes, current["waiting_draft"].append(merge_request)
"merge_requests": merge_requests, continue
"labels": {x.name: x.attributes for x in project.labels.list(all=True)}
}) 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: for cube in all_cubes:
......
...@@ -6,3 +6,4 @@ rdflib ...@@ -6,3 +6,4 @@ rdflib
pygments pygments
anybadge anybadge
hg+https://forge.extranet.logilab.fr/open-source/heptalab 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> <!doctype html>
<html> <html>
<head> <head>
...@@ -7,119 +50,80 @@ ...@@ -7,119 +50,80 @@
<body> <body>
<h1>MR Center<small>all the Merge Requests that need attention in a single place</small></h1> <h1>MR Center<small>all the Merge Requests that need attention in a single place</small></h1>
<h2>CubicWeb Core Projects</h2> {% if ready_to_be_merged %}
{% for project in projects %} <h2>MRs ready to be merged</h2>
{% if project["merge_requests"] %} <i>MRs that are tagged as "To Publish" or approved</i>
<h3>{{ project["project"]["name"] }}</h3> {{ render_mrs(ready_to_be_merged) }}
<ul> <hr>
{% 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>
{% endif %} {% 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> <hr>
{% endif %}
<h2>Project Cubes</h2> {% if ready_to_be_merged_rebase %}
{% for cube in cubes %} <h2>MRs ready to be merged but needs rebasing</h2>
{% if cube["merge_requests"] and "project-dependency" in cube["cube"]["tag_list"] %} <i>MRs that are tagged as "To Publish" or approved but rebased is need to be done before</i>
<h3>{{ cube["cube"]["name"] }}</h3> {{ render_mrs(ready_to_be_merged_rebase) }}
<ul> <hr>
{% 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>
{% endif %} {% 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> <hr>
{% endif %}
<h2>Unused Cubes</h2> {% if old_mr %}
{% for cube in cubes %} <h2>Old MRs</h2>
{% if cube["merge_requests"] and "project-dependency" not in cube["cube"]["tag_list"] %} <i>MRs that have been created 45 days ago and aren't draft</i>
<h3>{{ cube["cube"]["name"] }}</h3> {{ render_mrs(old_mr, show_how_old="created") }}
<ul> <hr>
{% 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>
{% endif %} {% 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> </body>
</html> </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