summaryrefslogtreecommitdiff
path: root/bin
diff options
context:
space:
mode:
authorHelen Koike <helen.koike@collabora.com>2023-12-21 20:59:24 -0300
committerMarge Bot <emma+marge@anholt.net>2024-01-20 00:02:56 +0000
commitbf461d856fc4af8dd7b808d377a48d26e3cd1e15 (patch)
tree78483e0738cd890ab2010a329e321a1122078048 /bin
parent1a9a71e2d99d508714f382ab1b88e8c3c1f3104d (diff)
ci/ci_post_gantt: add script that post gantt to Marge's messages
Generate Gantt chart and post an in thread reply to Marge's messages with it in html format. html format is used for being interactive. Signed-off-by: Helen Koike <helen.koike@collabora.com> Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/26796>
Diffstat (limited to 'bin')
-rwxr-xr-xbin/ci/ci_post_gantt.py178
-rwxr-xr-xbin/ci/ci_post_gantt.sh10
2 files changed, 188 insertions, 0 deletions
diff --git a/bin/ci/ci_post_gantt.py b/bin/ci/ci_post_gantt.py
new file mode 100755
index 00000000000..131f27e9373
--- /dev/null
+++ b/bin/ci/ci_post_gantt.py
@@ -0,0 +1,178 @@
+#!/usr/bin/env python3
+# Copyright © 2023 Collabora Ltd.
+# Authors:
+# Helen Koike <helen.koike@collabora.com>
+#
+# For the dependencies, see the requirements.txt
+# SPDX-License-Identifier: MIT
+
+
+import argparse
+import gitlab
+import re
+import os
+import pytz
+import traceback
+from datetime import datetime, timedelta
+from gitlab_common import (
+ read_token,
+ GITLAB_URL,
+ get_gitlab_pipeline_from_url,
+)
+from ci_gantt_chart import generate_gantt_chart
+
+MARGE_USER_ID = 9716 # Marge
+
+LAST_MARGE_EVENT_FILE = os.path.expanduser("~/.config/last_marge_event")
+
+
+def read_last_event_date_from_file():
+ try:
+ with open(LAST_MARGE_EVENT_FILE, "r") as f:
+ last_event_date = f.read().strip()
+ except FileNotFoundError:
+ # 3 days ago
+ last_event_date = (datetime.now() - timedelta(days=3)).isoformat()
+ return last_event_date
+
+
+def pretty_time(time_str):
+ """Pretty print time"""
+ local_timezone = datetime.now().astimezone().tzinfo
+
+ time_d = datetime.fromisoformat(time_str.replace("Z", "+00:00")).astimezone(
+ local_timezone
+ )
+ return f'{time_str} ({time_d.strftime("%d %b %Y %Hh%Mm%Ss")} {local_timezone})'
+
+
+def compose_message(file_name, attachment_url):
+ return f"""
+Here is the Gantt chart for the referred pipeline, I hope it helps 😄 (tip: click on the "Pan" button on the top right bar):
+
+[{file_name}]({attachment_url})
+
+<details>
+<summary>more info</summary>
+
+This message was generated by the ci_post_gantt.py script, which is running on a server at Collabora.
+</details>
+"""
+
+
+def gitlab_upload_file_get_url(gl, project_id, filepath):
+ project = gl.projects.get(project_id)
+ uploaded_file = project.upload(filepath, filepath=filepath)
+ return uploaded_file["url"]
+
+
+def gitlab_post_reply_to_note(gl, event, reply_message):
+ """
+ Post a reply to a note in thread based on a GitLab event.
+
+ :param gl: The GitLab connection instance.
+ :param event: The event object containing the note details.
+ :param reply_message: The reply message.
+ """
+ try:
+ note_id = event.target_id
+ merge_request_iid = event.note["noteable_iid"]
+
+ project = gl.projects.get(event.project_id)
+ merge_request = project.mergerequests.get(merge_request_iid)
+
+ # Find the discussion to which the note belongs
+ discussions = merge_request.discussions.list(as_list=False)
+ target_discussion = next(
+ (
+ d
+ for d in discussions
+ if any(n["id"] == note_id for n in d.attributes["notes"])
+ ),
+ None,
+ )
+
+ if target_discussion is None:
+ raise ValueError("Discussion for the note not found.")
+
+ # Add a reply to the discussion
+ reply = target_discussion.notes.create({"body": reply_message})
+ return reply
+
+ except gitlab.exceptions.GitlabError as e:
+ print(f"Failed to post a reply to '{event.note['body']}': {e}")
+ return None
+
+
+def parse_args() -> None:
+ parser = argparse.ArgumentParser(description="Monitor rejected pipelines by Marge.")
+ parser.add_argument(
+ "--token",
+ metavar="token",
+ help="force GitLab token, otherwise it's read from ~/.config/gitlab-token",
+ )
+ parser.add_argument(
+ "--since",
+ metavar="since",
+ help="consider only events after this date (ISO format), otherwise it's read from ~/.config/last_marge_event",
+ )
+ return parser.parse_args()
+
+
+if __name__ == "__main__":
+ args = parse_args()
+
+ token = read_token(args.token)
+
+ gl = gitlab.Gitlab(url=GITLAB_URL, private_token=token, retry_transient_errors=True)
+
+ user = gl.users.get(MARGE_USER_ID)
+ last_event_at = args.since if args.since else read_last_event_date_from_file()
+
+ print(f"Retrieving Marge messages since {pretty_time(last_event_at)}\n")
+
+ # the "after" only considers the "2023-10-24" part, it doesn't consider the time
+ events = user.events.list(
+ all=True,
+ target_type="note",
+ after=(datetime.now() - timedelta(days=3)).isoformat(),
+ sort="asc",
+ )
+
+ last_event_at_date = datetime.fromisoformat(
+ last_event_at.replace("Z", "+00:00")
+ ).replace(tzinfo=pytz.UTC)
+
+ for event in events:
+ created_at_date = datetime.fromisoformat(
+ event.created_at.replace("Z", "+00:00")
+ ).replace(tzinfo=pytz.UTC)
+ if created_at_date <= last_event_at_date:
+ continue
+ last_event_at = event.created_at
+
+ match = re.search(r"https://[^ ]+", event.note["body"])
+ if match:
+ try:
+ print("Found message:", event.note["body"])
+ pipeline_url = match.group(0)[:-1]
+ pipeline, _ = get_gitlab_pipeline_from_url(gl, pipeline_url)
+ print("Generating gantt chart...")
+ fig = generate_gantt_chart(pipeline)
+ file_name = "Gantt.html"
+ fig.write_html(file_name)
+ print("Uploading gantt file...")
+ file_url = gitlab_upload_file_get_url(gl, event.project_id, file_name)
+ print("Posting reply ...\n")
+ message = compose_message(file_name, file_url)
+ gitlab_post_reply_to_note(gl, event, message)
+ except Exception as e:
+ print(f"Failed to generate gantt chart, not posting reply.{e}")
+ traceback.print_exc()
+
+ if not args.since:
+ print(
+ f"Updating last event date to {pretty_time(last_event_at)} on {LAST_MARGE_EVENT_FILE}\n"
+ )
+ with open(LAST_MARGE_EVENT_FILE, "w") as f:
+ f.write(last_event_at)
diff --git a/bin/ci/ci_post_gantt.sh b/bin/ci/ci_post_gantt.sh
new file mode 100755
index 00000000000..b01b2fac02d
--- /dev/null
+++ b/bin/ci/ci_post_gantt.sh
@@ -0,0 +1,10 @@
+#!/usr/bin/env bash
+set -eu
+
+this_dir=$(dirname -- "$(readlink -f -- "${BASH_SOURCE[0]}")")
+readonly this_dir
+
+exec \
+ "$this_dir/../python-venv.sh" \
+ "$this_dir/requirements.txt" \
+ "$this_dir/ci_post_gantt.py" "$@"