From 1ad4640b2ae9f0050b3bc8e052dadd99b803ae79 Mon Sep 17 00:00:00 2001 From: Caspar van Leeuwen Date: Mon, 18 Aug 2025 18:07:24 +0200 Subject: [PATCH 1/7] WIP: trying to get a status command that only prints the last status for each 'for' architecture --- eessi_bot_event_handler.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/eessi_bot_event_handler.py b/eessi_bot_event_handler.py index a8beff82..1c0ccbdc 100644 --- a/eessi_bot_event_handler.py +++ b/eessi_bot_event_handler.py @@ -586,6 +586,34 @@ def handle_bot_command_status(self, event_info, bot_command): pr_number = event_info['raw_request_body']['issue']['number'] status_table = request_bot_build_issue_comments(repo_name, pr_number) + + # TODO: make the block until 'status_table = status_table_last' conditional on the bot command + # If the bot command is something like 'bot:status=last', then we should execute this sorting block + # First, add a timestamp for the date, so that we can use it for sorting + dates = status_table['date'] + timestamps = [] + for date in dates: + date_object = datetime.datetime.strptime(date, "%b %d %X %Z %Y") + timestamps.append(int(date_object.timestamp())) + status_table['timestamp'] = timestamps + + # Figure out the sorting indices, so that things are sorted first by the 'for arch', and then by 'date' + sorted_indices = sorted(range(len(status_table['for arch'])), key=lambda x: (status_table['for arch'[x], status_table['timestamp'][x])) + # Reverse, so that the newest builds are first + sorted_indices.reverse() + # Apply the sorted indices to get a sorted table + sorted_table = {key: [status_table[key][i] for i in sorted_indices] for key in status_table} + + # Keep only the first entry for each 'for arch', as that is now the newest + status_table_last = {'on arch': [], 'for arch': [], 'for repo': [], 'date': [], 'status': [], 'url': [], 'result': []} + for x in range(0, len(status_table['date'])): + if status_table['for arch'][x] not in status_table_last['for arch']: + for key in status_table_last: + status_table_last[key].append(status_table[key][x]) + + # overwrite the original status_table + status_table = status_table_last + comment_status = '' comment_status += "\nThis is the status of all the `bot: build` commands:" comment_status += "\n|on|for|repo|result|date|status|url|" From 9a5bf4d6050d1b036c396f21366c9d9e652b5bed Mon Sep 17 00:00:00 2001 From: Caspar van Leeuwen Date: Tue, 19 Aug 2025 14:51:08 +0200 Subject: [PATCH 2/7] Add support for passing general arguments to a command, so that we can support bot:status last_build. Then, implement functionality in the status command that makes sure only the last build result for each architecture is printed --- eessi_bot_event_handler.py | 60 +++++++++++++++++++++----------------- tools/commands.py | 12 ++++++-- 2 files changed, 42 insertions(+), 30 deletions(-) diff --git a/eessi_bot_event_handler.py b/eessi_bot_event_handler.py index 1c0ccbdc..c7f02c0b 100644 --- a/eessi_bot_event_handler.py +++ b/eessi_bot_event_handler.py @@ -586,33 +586,39 @@ def handle_bot_command_status(self, event_info, bot_command): pr_number = event_info['raw_request_body']['issue']['number'] status_table = request_bot_build_issue_comments(repo_name, pr_number) - - # TODO: make the block until 'status_table = status_table_last' conditional on the bot command - # If the bot command is something like 'bot:status=last', then we should execute this sorting block - # First, add a timestamp for the date, so that we can use it for sorting - dates = status_table['date'] - timestamps = [] - for date in dates: - date_object = datetime.datetime.strptime(date, "%b %d %X %Z %Y") - timestamps.append(int(date_object.timestamp())) - status_table['timestamp'] = timestamps - - # Figure out the sorting indices, so that things are sorted first by the 'for arch', and then by 'date' - sorted_indices = sorted(range(len(status_table['for arch'])), key=lambda x: (status_table['for arch'[x], status_table['timestamp'][x])) - # Reverse, so that the newest builds are first - sorted_indices.reverse() - # Apply the sorted indices to get a sorted table - sorted_table = {key: [status_table[key][i] for i in sorted_indices] for key in status_table} - - # Keep only the first entry for each 'for arch', as that is now the newest - status_table_last = {'on arch': [], 'for arch': [], 'for repo': [], 'date': [], 'status': [], 'url': [], 'result': []} - for x in range(0, len(status_table['date'])): - if status_table['for arch'][x] not in status_table_last['for arch']: - for key in status_table_last: - status_table_last[key].append(status_table[key][x]) - - # overwrite the original status_table - status_table = status_table_last + if 'last_build' in bot_command.general_args: + # If the bot command is something like 'bot:status =last_build', then only retain the last build for each + # architecture in the status_table + # To do this, we first insert a timestamp to facilitate sorting by time + # Then, we obtain sorting indices that first sort by architecture, then by build time + # Then, we reverse the sorting, so that the last build (highest timestamp) for each archictecture occurs + # first. + # Finally, we copy the table, but each time we encounter an entry for an architecture that we've already + # copied, we ignore it, since - as a result of the sorting - the second entry is always older than the + # first + dates = status_table['date'] + timestamps = [] + for date in dates: + date_object = datetime.datetime.strptime(date, "%b %d %X %Z %Y") + timestamps.append(int(date_object.timestamp())) + status_table['timestamp'] = timestamps + + # Figure out the sorting indices, so that things are sorted first by the 'for arch', and then by 'date' + sorted_indices = sorted(range(len(status_table['for arch'])), key=lambda x: (status_table['for arch'[x], status_table['timestamp'][x])) + # Reverse, so that the newest builds are first + sorted_indices.reverse() + # Apply the sorted indices to get a sorted table + sorted_table = {key: [status_table[key][i] for i in sorted_indices] for key in status_table} + + # Keep only the first entry for each 'for arch', as that is now the newest + status_table_last = {'on arch': [], 'for arch': [], 'for repo': [], 'date': [], 'status': [], 'url': [], 'result': []} + for x in range(0, len(status_table['date'])): + if status_table['for arch'][x] not in status_table_last['for arch']: + for key in status_table_last: + status_table_last[key].append(status_table[key][x]) + + # overwrite the original status_table + status_table = status_table_last comment_status = '' comment_status += "\nThis is the status of all the `bot: build` commands:" diff --git a/tools/commands.py b/tools/commands.py index 3902ab70..122e363a 100644 --- a/tools/commands.py +++ b/tools/commands.py @@ -87,6 +87,7 @@ def __init__(self, cmd_str): # TODO add function name to log messages cmd_as_list = cmd_str.split() self.command = cmd_as_list[0] # E.g. 'build' or 'help' + self.general_args = [] # TODO always init self.action_filters with empty EESSIBotActionFilter? if len(cmd_as_list) > 1: @@ -109,9 +110,14 @@ def __init__(self, cmd_str): # according to the expected argument format for 'for:' self.build_params = EESSIBotBuildParams(build_params) else: - # Anything that is not 'on:' or 'for:' should just be passed on as normal - # No further parsing of the value is needed - other_filter_args.extend([arg]) + # Anything that is not 'on:' or 'for:' + # Check if it's a filter argument, if so, pass it on to other_filter_args witout further parsing + # If it's not a filter argument, it is a general argument - just store it so any other function + # can read it + if ':' in arg: + other_filter_args.extend([arg]) + else: + self.general_args.append(arg) # If no 'on:' is found in the argument list, everything that follows the 'for:' argument # (until the next space) is considered the argument list for the action filters From 583cacce5b09dbb6d2eff680f2506060459b5b23 Mon Sep 17 00:00:00 2001 From: Caspar van Leeuwen Date: Tue, 19 Aug 2025 14:53:31 +0200 Subject: [PATCH 3/7] Fix typo --- eessi_bot_event_handler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eessi_bot_event_handler.py b/eessi_bot_event_handler.py index c7f02c0b..dcf8d4ed 100644 --- a/eessi_bot_event_handler.py +++ b/eessi_bot_event_handler.py @@ -604,7 +604,7 @@ def handle_bot_command_status(self, event_info, bot_command): status_table['timestamp'] = timestamps # Figure out the sorting indices, so that things are sorted first by the 'for arch', and then by 'date' - sorted_indices = sorted(range(len(status_table['for arch'])), key=lambda x: (status_table['for arch'[x], status_table['timestamp'][x])) + sorted_indices = sorted(range(len(status_table['for arch'])), key=lambda x: (status_table['for arch'][x], status_table['timestamp'][x])) # Reverse, so that the newest builds are first sorted_indices.reverse() # Apply the sorted indices to get a sorted table From 25dd76d3667690f1a01512e2b955ebe60d2d3a23 Mon Sep 17 00:00:00 2001 From: Caspar van Leeuwen Date: Tue, 19 Aug 2025 17:50:08 +0200 Subject: [PATCH 4/7] Make sure that commands still function if no action filters were defined. Also, log the general args to the log --- tools/commands.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tools/commands.py b/tools/commands.py index 122e363a..64ce7fc7 100644 --- a/tools/commands.py +++ b/tools/commands.py @@ -88,6 +88,7 @@ def __init__(self, cmd_str): cmd_as_list = cmd_str.split() self.command = cmd_as_list[0] # E.g. 'build' or 'help' self.general_args = [] + self.action_filters = None # TODO always init self.action_filters with empty EESSIBotActionFilter? if len(cmd_as_list) > 1: @@ -145,6 +146,7 @@ def __init__(self, cmd_str): # so no special parsing needed there log(f"Extracted filter arguments related to hardware target: {normalized_filters}") log(f"Other extracted filter arguments: {other_filter_args}") + log(f"Other general arguments: {self.general_args}") normalized_filters += other_filter_args # Finally, change into a space-separated string, as expected by EESSIBotActionFilter @@ -175,5 +177,8 @@ def to_string(self): Returns: string: the string representation created by the method """ - action_filters_str = self.action_filters.to_string() - return f"{' '.join([self.command, action_filters_str]).rstrip()}" + if self.action_filters is None: + return "" + else: + action_filters_str = self.action_filters.to_string() + return f"{' '.join([self.command, action_filters_str]).rstrip()}" From 296942f8b7e5ab82900e733960bc46914bf3df1f Mon Sep 17 00:00:00 2001 From: Caspar van Leeuwen Date: Tue, 19 Aug 2025 17:51:24 +0200 Subject: [PATCH 5/7] Fix some bugs. Also, make sure to actually use the sorted table when composing the table with only the last builds. Finally, add resorting for an output that is sorted alphabetically by 'for' architecture --- eessi_bot_event_handler.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/eessi_bot_event_handler.py b/eessi_bot_event_handler.py index dcf8d4ed..a42c1e53 100644 --- a/eessi_bot_event_handler.py +++ b/eessi_bot_event_handler.py @@ -599,7 +599,7 @@ def handle_bot_command_status(self, event_info, bot_command): dates = status_table['date'] timestamps = [] for date in dates: - date_object = datetime.datetime.strptime(date, "%b %d %X %Z %Y") + date_object = datetime.strptime(date, "%b %d %X %Z %Y") timestamps.append(int(date_object.timestamp())) status_table['timestamp'] = timestamps @@ -609,16 +609,23 @@ def handle_bot_command_status(self, event_info, bot_command): sorted_indices.reverse() # Apply the sorted indices to get a sorted table sorted_table = {key: [status_table[key][i] for i in sorted_indices] for key in status_table} + self.log(f"Sorted status table: {sorted_table}") # Keep only the first entry for each 'for arch', as that is now the newest status_table_last = {'on arch': [], 'for arch': [], 'for repo': [], 'date': [], 'status': [], 'url': [], 'result': []} - for x in range(0, len(status_table['date'])): - if status_table['for arch'][x] not in status_table_last['for arch']: + for x in range(0, len(sorted_table['date'])): + if sorted_table['for arch'][x] not in status_table_last['for arch']: + self.log(f"arch: {sorted_table['for arch'][x]} not yet in status_table_last") for key in status_table_last: - status_table_last[key].append(status_table[key][x]) + self.log(f"Adding to '{key}' and the value {sorted_table[key][x]}") + status_table_last[key].append(sorted_table[key][x]) + + # Re-sort, now only on 'for arch', for nicer viewing + sorted_indices = sorted(range(len(status_table_last['for arch'])), key=lambda x: status_table_last['for arch'][x]) + sorted_table_last = {key: [status_table_last[key][i] for i in sorted_indices] for key in status_table_last} # overwrite the original status_table - status_table = status_table_last + status_table = sorted_table_last comment_status = '' comment_status += "\nThis is the status of all the `bot: build` commands:" From d335c3dec1e2c2b7aff8d65ae6c0ce0943826e65 Mon Sep 17 00:00:00 2001 From: Caspar van Leeuwen Date: Tue, 19 Aug 2025 17:59:56 +0200 Subject: [PATCH 6/7] Fix too long lines --- eessi_bot_event_handler.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/eessi_bot_event_handler.py b/eessi_bot_event_handler.py index a42c1e53..c0e26f86 100644 --- a/eessi_bot_event_handler.py +++ b/eessi_bot_event_handler.py @@ -604,7 +604,8 @@ def handle_bot_command_status(self, event_info, bot_command): status_table['timestamp'] = timestamps # Figure out the sorting indices, so that things are sorted first by the 'for arch', and then by 'date' - sorted_indices = sorted(range(len(status_table['for arch'])), key=lambda x: (status_table['for arch'][x], status_table['timestamp'][x])) + key_func = lambda x: (status_table['for arch'][x], status_table['timestamp'][x]) + sorted_indices = sorted(range(len(status_table['for arch'])), key=key_func) # Reverse, so that the newest builds are first sorted_indices.reverse() # Apply the sorted indices to get a sorted table @@ -612,7 +613,9 @@ def handle_bot_command_status(self, event_info, bot_command): self.log(f"Sorted status table: {sorted_table}") # Keep only the first entry for each 'for arch', as that is now the newest - status_table_last = {'on arch': [], 'for arch': [], 'for repo': [], 'date': [], 'status': [], 'url': [], 'result': []} + status_table_last = { + 'on arch': [], 'for arch': [], 'for repo': [], 'date': [], 'status': [], 'url': [], 'result': [] + } for x in range(0, len(sorted_table['date'])): if sorted_table['for arch'][x] not in status_table_last['for arch']: self.log(f"arch: {sorted_table['for arch'][x]} not yet in status_table_last") @@ -621,7 +624,8 @@ def handle_bot_command_status(self, event_info, bot_command): status_table_last[key].append(sorted_table[key][x]) # Re-sort, now only on 'for arch', for nicer viewing - sorted_indices = sorted(range(len(status_table_last['for arch'])), key=lambda x: status_table_last['for arch'][x]) + key_func = lambda x: status_table_last['for arch'][x] + sorted_indices = sorted(range(len(status_table_last['for arch'])), key=key_func) sorted_table_last = {key: [status_table_last[key][i] for i in sorted_indices] for key in status_table_last} # overwrite the original status_table From 3fc6a6ef536cf15e114ada7ac37e72362e75de4e Mon Sep 17 00:00:00 2001 From: Caspar van Leeuwen Date: Tue, 19 Aug 2025 18:04:04 +0200 Subject: [PATCH 7/7] Don't define the lambda's separately --- eessi_bot_event_handler.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/eessi_bot_event_handler.py b/eessi_bot_event_handler.py index c0e26f86..74886a0e 100644 --- a/eessi_bot_event_handler.py +++ b/eessi_bot_event_handler.py @@ -604,8 +604,10 @@ def handle_bot_command_status(self, event_info, bot_command): status_table['timestamp'] = timestamps # Figure out the sorting indices, so that things are sorted first by the 'for arch', and then by 'date' - key_func = lambda x: (status_table['for arch'][x], status_table['timestamp'][x]) - sorted_indices = sorted(range(len(status_table['for arch'])), key=key_func) + sorted_indices = sorted( + range(len(status_table['for arch'])), + key=lambda x: (status_table['for arch'][x], status_table['timestamp'][x]) + ) # Reverse, so that the newest builds are first sorted_indices.reverse() # Apply the sorted indices to get a sorted table @@ -624,8 +626,10 @@ def handle_bot_command_status(self, event_info, bot_command): status_table_last[key].append(sorted_table[key][x]) # Re-sort, now only on 'for arch', for nicer viewing - key_func = lambda x: status_table_last['for arch'][x] - sorted_indices = sorted(range(len(status_table_last['for arch'])), key=key_func) + sorted_indices = sorted( + range(len(status_table_last['for arch'])), + key=lambda x: status_table_last['for arch'][x] + ) sorted_table_last = {key: [status_table_last[key][i] for i in sorted_indices] for key in status_table_last} # overwrite the original status_table