From a26f9e29f0d294dc4faf0e5c09c1cf3955f7f993 Mon Sep 17 00:00:00 2001 From: Gustavo Padovan Date: Mon, 20 Jan 2025 14:55:14 -0300 Subject: [PATCH 1/9] Remove leftover script for subcommands This python code was just an experiment that got merged by mistake through some git add/commit. Signed-off-by: Gustavo Padovan --- kcidev/subcommands/sub.py | 53 --------------------------------------- 1 file changed, 53 deletions(-) delete mode 100644 kcidev/subcommands/sub.py diff --git a/kcidev/subcommands/sub.py b/kcidev/subcommands/sub.py deleted file mode 100644 index 7a043d8..0000000 --- a/kcidev/subcommands/sub.py +++ /dev/null @@ -1,53 +0,0 @@ -import click - - -# Main CLI group -@click.group() -def cli(): - """kci-dev: A tool for KernelCI development.""" - pass - - -# Subgroup for 'maestro' -@cli.group() -def maestro(): - """Commands related to maestro.""" - pass - - -# Subcommands under 'maestro' -@maestro.command() -def test_checkout(): - """Test the checkout process.""" - click.echo("Running maestro test-checkout...") - - -@maestro.command() -def test_patch(): - """Test a patch.""" - click.echo("Running maestro test-patch...") - - -# Subgroup for 'results' -@cli.group() -def results(): - """Commands related to results.""" - pass - - -# Subcommands under 'results' -@results.command() -def summary(): - """Display a summary of results.""" - click.echo("Displaying results summary...") - - -@results.command() -def details(): - """Display detailed results.""" - click.echo("Displaying detailed results...") - - -# Entry point -if __name__ == "__main__": - cli() From 1d9089915333a8a9218f769998440b910817bbc2 Mon Sep 17 00:00:00 2001 From: Gustavo Padovan Date: Mon, 20 Jan 2025 15:28:18 -0300 Subject: [PATCH 2/9] results: use new Dashboard API endpoints to fetch results We asked the Dashboard team to make the endpoints easier to access, so they split them up for us. Signed-off-by: Gustavo Padovan --- kcidev/subcommands/results.py | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/kcidev/subcommands/results.py b/kcidev/subcommands/results.py index 4ebc46c..4c5bfa0 100644 --- a/kcidev/subcommands/results.py +++ b/kcidev/subcommands/results.py @@ -18,7 +18,7 @@ DASHBOARD_API = "https://dashboard.kernelci.org/api/" -def fetch_from_api(endpoint, params): +def dashboard_api_fetch(endpoint, params): base_url = urllib.parse.urljoin(DASHBOARD_API, endpoint) try: url = "{}?{}".format(base_url, urllib.parse.urlencode(params)) @@ -30,16 +30,26 @@ def fetch_from_api(endpoint, params): return r.json() -def fetch_full_results(origin, giturl, branch, commit): - endpoint = f"tree/{commit}/full" +def dashboard_fetch_summary(origin, giturl, branch, commit): + endpoint = f"tree/{commit}/summary" params = { "origin": origin, "git_url": giturl, "git_branch": branch, - "commit": commit, } - return fetch_from_api(endpoint, params) + return dashboard_api_fetch(endpoint, params) + + +def dashboard_fetch_builds(origin, giturl, branch, commit): + endpoint = f"tree/{commit}/builds" + params = { + "origin": origin, + "git_url": giturl, + "git_branch": branch, + } + + return dashboard_api_fetch(endpoint, params) def repository_url_cleaner(url): @@ -60,7 +70,7 @@ def fetch_tree_fast(origin): params = { "origin": origin, } - return fetch_from_api("tree-fast", params) + return dashboard_api_fetch("tree-fast", params) def is_inside_work_tree(git_folder): @@ -165,17 +175,17 @@ def sum_inconclusive_results(results): def cmd_summary(data): kci_msg("pass/fail/inconclusive") - - builds = data["buildsSummary"]["builds"] + summary = data["summary"] + builds = summary["builds"]["status"] print_summary("builds", builds["valid"], builds["invalid"], builds["null"]) - boots = data["bootStatusSummary"] + boots = summary["boots"]["status"] inconclusive_boots = sum_inconclusive_results(boots) pass_boots = boots["PASS"] if "PASS" in boots.keys() else 0 fail_boots = boots["FAIL"] if "FAIL" in boots.keys() else 0 print_summary("boots", pass_boots, fail_boots, inconclusive_boots) - tests = data["testStatusSummary"] + tests = summary["tests"]["status"] pass_tests = tests["PASS"] if "PASS" in tests.keys() else 0 fail_tests = tests["FAIL"] if "FAIL" in tests.keys() else 0 inconclusive_tests = sum_inconclusive_results(tests) @@ -325,7 +335,7 @@ def summary( giturl, branch, commit = set_giturl_branch_commit( origin, giturl, branch, commit, latest, git_folder ) - data = fetch_full_results(origin, giturl, branch, commit) + data = dashboard_fetch_summary(origin, giturl, branch, commit) cmd_summary(data) @@ -352,7 +362,7 @@ def builds( giturl, branch, commit = set_giturl_branch_commit( origin, giturl, branch, commit, latest, git_folder ) - data = fetch_full_results(origin, giturl, branch, commit) + data = dashboard_fetch_builds(origin, giturl, branch, commit) cmd_builds(data, commit, download_logs, status) From a5607987d4df12f1099bb9ba8d811fdd30c32d04 Mon Sep 17 00:00:00 2001 From: Gustavo Padovan Date: Mon, 20 Jan 2025 15:32:07 -0300 Subject: [PATCH 3/9] results: rename tree_list fetch function Make it consistent with the name of the other fetch functions. Signed-off-by: Gustavo Padovan --- kcidev/subcommands/results.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/kcidev/subcommands/results.py b/kcidev/subcommands/results.py index 4c5bfa0..55a0587 100644 --- a/kcidev/subcommands/results.py +++ b/kcidev/subcommands/results.py @@ -66,7 +66,7 @@ def repository_url_cleaner(url): return url_cleaned -def fetch_tree_fast(origin): +def dashboard_fetch_tree_list(origin): params = { "origin": origin, } @@ -141,7 +141,7 @@ def get_folder_repository(git_folder, branch): def get_latest_commit(origin, giturl, branch): - trees = fetch_tree_fast(origin) + trees = dashboard_fetch_tree_list(origin) for t in trees: if t["git_repository_url"] == giturl and t["git_repository_branch"] == branch: return t["git_commit_hash"] @@ -193,7 +193,7 @@ def cmd_summary(data): def cmd_list_trees(origin): - trees = fetch_tree_fast(origin) + trees = dashboard_fetch_tree_list(origin) for t in trees: kci_msg_green_nonl(f"- {t['tree_name']}/{t['git_repository_branch']}:\n") kci_msg(f" giturl: {t['git_repository_url']}") From bc2854b624faa98df0fa1997b020a73456f1c45e Mon Sep 17 00:00:00 2001 From: Gustavo Padovan Date: Mon, 20 Jan 2025 20:14:14 -0300 Subject: [PATCH 4/9] results: add boots subcommand Signed-off-by: Gustavo Padovan --- kcidev/subcommands/results.py | 102 +++++++++++++++++++++++++++++++++- 1 file changed, 100 insertions(+), 2 deletions(-) diff --git a/kcidev/subcommands/results.py b/kcidev/subcommands/results.py index 55a0587..35fc587 100644 --- a/kcidev/subcommands/results.py +++ b/kcidev/subcommands/results.py @@ -52,6 +52,17 @@ def dashboard_fetch_builds(origin, giturl, branch, commit): return dashboard_api_fetch(endpoint, params) +def dashboard_fetch_boots(origin, giturl, branch, commit): + endpoint = f"tree/{commit}/boots" + params = { + "origin": origin, + "git_url": giturl, + "git_branch": branch, + } + + return dashboard_api_fetch(endpoint, params) + + def repository_url_cleaner(url): # standardize protocol to https parsed = urllib.parse.urlsplit(url) @@ -251,6 +262,78 @@ def cmd_builds(data, commit, download_logs, status): kci_msg("") +def filter_out_by_status(status, filter): + if filter == "all": + return False + + if filter == status.lower(): + return False + + elif filter == "inconclusive" and status in [ + "ERROR", + "SKIP", + "MISS", + "DONE", + "NULL", + ]: + return False + + return True + + +def cmd_boots(data, commit, download_logs, status_filter): + for boot in data["boots"]: + if filter_out_by_status(boot["status"], status_filter): + continue + + log_path = boot["log_url"] + if download_logs: + try: + log_gz = requests.get(boots["log_url"]) + log = gzip.decompress(log_gz.content) + log_file = f"{boot['config']}-{boot['architecture']}-{boot['compiler']}-{commit}.log" + with open(log_file, mode="wb") as file: + file.write(log) + log_path = "file://" + os.path.join(os.getcwd(), log_file) + except: + kci_err(f"Failed to fetch log {log_file}).") + pass + + kci_msg_nonl("- test path: ") + kci_msg_cyan_nonl(boot["path"]) + kci_msg("") + + kci_msg_nonl(" platform: ") + kci_msg_cyan_nonl(boot["misc"]["platform"]) + kci_msg("") + + if boot["environment_compatible"]: + kci_msg_nonl(" compatibles: ") + kci_msg_cyan_nonl(" | ".join(boot["environment_compatible"])) + kci_msg("") + + kci_msg_nonl(" config: ") + kci_msg_cyan_nonl(boot["config"]) + kci_msg_nonl(" arch: ") + kci_msg_cyan_nonl(boot["architecture"]) + kci_msg_nonl(" compiler: ") + kci_msg_cyan_nonl(boot["compiler"]) + kci_msg("") + + kci_msg_nonl(" status:") + if boot["status"] == "PASS": + kci_msg_green_nonl("PASS") + elif boot["status"] == "FAIL": + kci_msg_red_nonl("FAIL") + else: + kci_msg_yellow_nonl(f"INCONCLUSIVE (status: {boot["status"]})") + kci_msg("") + + kci_msg(f" log: {log_path}") + kci_msg(f" id: {boot['id']}") + kci_msg("") + + def set_giturl_branch_commit(origin, giturl, branch, commit, latest, git_folder): if not giturl or not branch or not ((commit != None) ^ latest): giturl, branch, commit = get_folder_repository(git_folder, branch) @@ -293,7 +376,7 @@ def wrapper(*args, **kwargs): return wrapper -def build_options(func): +def build_and_test_options(func): @click.option( "--download-logs", is_flag=True, @@ -353,7 +436,7 @@ def trees(ctx, origin): @results.command() @common_options -@build_options +@build_and_test_options @click.pass_context def builds( ctx, origin, git_folder, giturl, branch, commit, latest, download_logs, status @@ -366,5 +449,20 @@ def builds( cmd_builds(data, commit, download_logs, status) +@results.command() +@common_options +@build_and_test_options +@click.pass_context +def boots( + ctx, origin, git_folder, giturl, branch, commit, latest, download_logs, status +): + """Display boot results.""" + giturl, branch, commit = set_giturl_branch_commit( + origin, giturl, branch, commit, latest, git_folder + ) + data = dashboard_fetch_boots(origin, giturl, branch, commit) + cmd_boots(data, commit, download_logs, status) + + if __name__ == "__main__": main_kcidev() From 217630241f8ab0e14406ea669e43593055e2e19e Mon Sep 17 00:00:00 2001 From: Gustavo Padovan Date: Mon, 20 Jan 2025 20:26:58 -0300 Subject: [PATCH 5/9] results: add test subcommand Enable users to see test results through kci-dev. The json structure is the same as builds, so this reuses the same function, renaming it to 'cmd_tests'. Signed-off-by: Gustavo Padovan --- kcidev/subcommands/results.py | 63 +++++++++++++++++++++++++---------- 1 file changed, 45 insertions(+), 18 deletions(-) diff --git a/kcidev/subcommands/results.py b/kcidev/subcommands/results.py index 35fc587..2c85cf7 100644 --- a/kcidev/subcommands/results.py +++ b/kcidev/subcommands/results.py @@ -63,6 +63,17 @@ def dashboard_fetch_boots(origin, giturl, branch, commit): return dashboard_api_fetch(endpoint, params) +def dashboard_fetch_tests(origin, giturl, branch, commit): + endpoint = f"tree/{commit}/tests" + params = { + "origin": origin, + "git_url": giturl, + "git_branch": branch, + } + + return dashboard_api_fetch(endpoint, params) + + def repository_url_cleaner(url): # standardize protocol to https parsed = urllib.parse.urlsplit(url) @@ -281,17 +292,17 @@ def filter_out_by_status(status, filter): return True -def cmd_boots(data, commit, download_logs, status_filter): - for boot in data["boots"]: - if filter_out_by_status(boot["status"], status_filter): +def cmd_tests(data, commit, download_logs, status_filter): + for test in data: + if filter_out_by_status(test["status"], status_filter): continue - log_path = boot["log_url"] + log_path = test["log_url"] if download_logs: try: log_gz = requests.get(boots["log_url"]) log = gzip.decompress(log_gz.content) - log_file = f"{boot['config']}-{boot['architecture']}-{boot['compiler']}-{commit}.log" + log_file = f"{test['config']}-{test['architecture']}-{test['compiler']}-{commit}.log" with open(log_file, mode="wb") as file: file.write(log) log_path = "file://" + os.path.join(os.getcwd(), log_file) @@ -300,37 +311,38 @@ def cmd_boots(data, commit, download_logs, status_filter): pass kci_msg_nonl("- test path: ") - kci_msg_cyan_nonl(boot["path"]) + kci_msg_cyan_nonl(test["path"]) kci_msg("") - kci_msg_nonl(" platform: ") - kci_msg_cyan_nonl(boot["misc"]["platform"]) + kci_msg_nonl(" hardware: ") + kci_msg_cyan_nonl(test["misc"]["platform"]) kci_msg("") - if boot["environment_compatible"]: + if test["environment_compatible"]: kci_msg_nonl(" compatibles: ") - kci_msg_cyan_nonl(" | ".join(boot["environment_compatible"])) + kci_msg_cyan_nonl(" | ".join(test["environment_compatible"])) kci_msg("") kci_msg_nonl(" config: ") - kci_msg_cyan_nonl(boot["config"]) + kci_msg_cyan_nonl(test["config"]) kci_msg_nonl(" arch: ") - kci_msg_cyan_nonl(boot["architecture"]) + kci_msg_cyan_nonl(test["architecture"]) kci_msg_nonl(" compiler: ") - kci_msg_cyan_nonl(boot["compiler"]) + kci_msg_cyan_nonl(test["compiler"]) kci_msg("") kci_msg_nonl(" status:") - if boot["status"] == "PASS": + if test["status"] == "PASS": kci_msg_green_nonl("PASS") - elif boot["status"] == "FAIL": + elif test["status"] == "FAIL": kci_msg_red_nonl("FAIL") else: - kci_msg_yellow_nonl(f"INCONCLUSIVE (status: {boot["status"]})") + kci_msg_yellow_nonl(f"INCONCLUSIVE (status: {test["status"]})") kci_msg("") kci_msg(f" log: {log_path}") - kci_msg(f" id: {boot['id']}") + kci_msg(f" start time: {test['start_time']}") + kci_msg(f" id: {test['id']}") kci_msg("") @@ -461,7 +473,22 @@ def boots( origin, giturl, branch, commit, latest, git_folder ) data = dashboard_fetch_boots(origin, giturl, branch, commit) - cmd_boots(data, commit, download_logs, status) + cmd_tests(data["boots"], commit, download_logs, status) + + +@results.command() +@common_options +@build_and_test_options +@click.pass_context +def tests( + ctx, origin, git_folder, giturl, branch, commit, latest, download_logs, status +): + """Display test results.""" + giturl, branch, commit = set_giturl_branch_commit( + origin, giturl, branch, commit, latest, git_folder + ) + data = dashboard_fetch_tests(origin, giturl, branch, commit) + cmd_tests(data["tests"], commit, download_logs, status) if __name__ == "__main__": From 55438c47a829268468fe66cd00832e2642f2aaa2 Mon Sep 17 00:00:00 2001 From: Gustavo Padovan Date: Tue, 21 Jan 2025 10:29:11 -0300 Subject: [PATCH 6/9] results: add hardware filter for boots and tests Create a filter file to specify the hardware you want to see in the results. It uses a yaml file: --- hardware: - radxa,rock2-square - fsl,imx6q - dell-latitude-3445-7520c-skyrim --- The filter file can be expanded to other characteristics as we evolve the tool. Signed-off-by: Gustavo Padovan --- kcidev/subcommands/results.py | 64 +++++++++++++++++++++++++++++++---- 1 file changed, 58 insertions(+), 6 deletions(-) diff --git a/kcidev/subcommands/results.py b/kcidev/subcommands/results.py index 2c85cf7..97ab655 100644 --- a/kcidev/subcommands/results.py +++ b/kcidev/subcommands/results.py @@ -12,6 +12,7 @@ import click import requests +import yaml from kcidev.libs.common import * @@ -292,11 +293,30 @@ def filter_out_by_status(status, filter): return True -def cmd_tests(data, commit, download_logs, status_filter): +def filter_out_by_hardware(test, filter_data): + # Check if the hardware name is in the list + hardware_list = filter_data["hardware"] + if test["misc"]["platform"] in hardware_list: + return False + + if test["environment_compatible"]: + for compatible in test["environment_compatible"]: + if compatible in hardware_list: + return False + + return True + + +def cmd_tests(data, commit, download_logs, status_filter, filter): + filter_data = yaml.safe_load(filter) if filter else None + for test in data: if filter_out_by_status(test["status"], status_filter): continue + if filter_data and filter_out_by_hardware(test, filter_data): + continue + log_path = test["log_url"] if download_logs: try: @@ -400,6 +420,11 @@ def build_and_test_options(func): help="Status of test result", default="all", ) + @click.option( + "--filter", + type=click.File("r"), + help="Pass filter file for builds, boot and tests results.", + ) @wraps(func) def wrapper(*args, **kwargs): return func(*args, **kwargs) @@ -451,7 +476,16 @@ def trees(ctx, origin): @build_and_test_options @click.pass_context def builds( - ctx, origin, git_folder, giturl, branch, commit, latest, download_logs, status + ctx, + origin, + git_folder, + giturl, + branch, + commit, + latest, + download_logs, + status, + filter, ): """Display build results.""" giturl, branch, commit = set_giturl_branch_commit( @@ -466,14 +500,23 @@ def builds( @build_and_test_options @click.pass_context def boots( - ctx, origin, git_folder, giturl, branch, commit, latest, download_logs, status + ctx, + origin, + git_folder, + giturl, + branch, + commit, + latest, + download_logs, + status, + filter, ): """Display boot results.""" giturl, branch, commit = set_giturl_branch_commit( origin, giturl, branch, commit, latest, git_folder ) data = dashboard_fetch_boots(origin, giturl, branch, commit) - cmd_tests(data["boots"], commit, download_logs, status) + cmd_tests(data["boots"], commit, download_logs, status, filter) @results.command() @@ -481,14 +524,23 @@ def boots( @build_and_test_options @click.pass_context def tests( - ctx, origin, git_folder, giturl, branch, commit, latest, download_logs, status + ctx, + origin, + git_folder, + giturl, + branch, + commit, + latest, + download_logs, + status, + filter, ): """Display test results.""" giturl, branch, commit = set_giturl_branch_commit( origin, giturl, branch, commit, latest, git_folder ) data = dashboard_fetch_tests(origin, giturl, branch, commit) - cmd_tests(data["tests"], commit, download_logs, status) + cmd_tests(data["tests"], commit, download_logs, status, filter) if __name__ == "__main__": From c62d11b3edcd7f398100775029612816ca96c6c8 Mon Sep 17 00:00:00 2001 From: Gustavo Padovan Date: Tue, 21 Jan 2025 10:43:19 -0300 Subject: [PATCH 7/9] results: fix download logs for boots and tests It broke during the recent refactor to enable tests results. Signed-off-by: Gustavo Padovan --- kcidev/subcommands/results.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kcidev/subcommands/results.py b/kcidev/subcommands/results.py index 97ab655..1817cc6 100644 --- a/kcidev/subcommands/results.py +++ b/kcidev/subcommands/results.py @@ -320,9 +320,9 @@ def cmd_tests(data, commit, download_logs, status_filter, filter): log_path = test["log_url"] if download_logs: try: - log_gz = requests.get(boots["log_url"]) + log_gz = requests.get(test["log_url"]) log = gzip.decompress(log_gz.content) - log_file = f"{test['config']}-{test['architecture']}-{test['compiler']}-{commit}.log" + log_file = f"{test["misc"]["platform"]}__{test["path"]}__{test['config']}-{test['architecture']}-{test['compiler']}-{commit}.log" with open(log_file, mode="wb") as file: file.write(log) log_path = "file://" + os.path.join(os.getcwd(), log_file) From 0a749dfb845d46c641f63703358200169b531954 Mon Sep 17 00:00:00 2001 From: Gustavo Padovan Date: Tue, 21 Jan 2025 10:44:59 -0300 Subject: [PATCH 8/9] docs: Improve descriptions for the results command Adding info about boots, tests and hardware filter. Signed-off-by: Gustavo Padovan --- docs/results.md | 45 +++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 41 insertions(+), 4 deletions(-) diff --git a/docs/results.md b/docs/results.md index 4906840..a02605a 100644 --- a/docs/results.md +++ b/docs/results.md @@ -30,7 +30,7 @@ kci-dev results summary --giturl 'https://git.kernel.org/pub/scm/linux/kernel/gi ### builds -List builds. +List builds results. Example: @@ -38,6 +38,26 @@ Example: kci-dev results builds --giturl 'https://git.kernel.org/pub/scm/linux/kernel/git/next/linux-next.git' --branch master --commit d1486dca38afd08ca279ae94eb3a397f10737824 ``` +### boots + +List boot results. + +Example: + +```sh +kci-dev results boots --giturl 'https://git.kernel.org/pub/scm/linux/kernel/git/next/linux-next.git' --branch master --latest +``` + +### tests + +List test results. + +Example: + +```sh +kci-dev results tests --giturl 'https://git.kernel.org/pub/scm/linux/kernel/git/next/linux-next.git' --branch master --commit d1486dca38afd08ca279ae94eb3a397f10737824 +``` + ## Common parameters ### --origin @@ -74,7 +94,7 @@ kci-dev results builds --giturl 'https://git.kernel.org/pub/scm/linux/kernel/git ### --status Filter results by the status: "all", "pass", "fail" or "inconclusive". -(available for subcommand `build`) +(available for subcommands `build`, `boots` and `tests`) Example: ```sh @@ -84,13 +104,30 @@ kci-dev results builds --giturl 'https://git.kernel.org/pub/scm/linux/kernel/git ## --download-logs Automatically download logs for results listed. -(available for subcommand `build`) +(available for subcommands `build`, `boots` and `tests`) Example: ```sh -kci-dev results build --giturl 'https://git.kernel.org/pub/scm/linux/kernel/git/next/linux-next.git' --branch master --commit d1486dca38afd08ca279ae94eb3a397f10737824 --download-logs +kci-dev results builds --giturl 'https://git.kernel.org/pub/scm/linux/kernel/git/next/linux-next.git' --branch master --commit d1486dca38afd08ca279ae94eb3a397f10737824 --download-logs +``` + +## --filter + +Pass a YAML filter file to customize results. Only supports hardware filtering at the moment. +See filter yaml example below: +(available for subcommands `boots` and `tests`) + +```yaml +hardware: + - radxa,rock2-square + - fsl,imx6q + - dell-latitude-3445-7520c-skyrim ``` +Example: +```sh +kci-dev results boots --giturl 'https://git.kernel.org/pub/scm/linux/kernel/git/next/linux-next.git' --branch master --latest --filter=filter.yaml +``` ### without arguments From ff21ab0ff213eacf2aeb55dcf02a7d3469cefa25 Mon Sep 17 00:00:00 2001 From: Gustavo Padovan Date: Tue, 21 Jan 2025 11:48:25 -0300 Subject: [PATCH 9/9] results: Add link to the dashboard for results details Signed-off-by: Gustavo Padovan --- kcidev/subcommands/results.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/kcidev/subcommands/results.py b/kcidev/subcommands/results.py index 1817cc6..2d1a1a4 100644 --- a/kcidev/subcommands/results.py +++ b/kcidev/subcommands/results.py @@ -271,6 +271,7 @@ def cmd_builds(data, commit, download_logs, status): kci_msg(f" config_url: {build['config_url']}") kci_msg(f" log: {log_path}") kci_msg(f" id: {build['id']}") + kci_msg(f" dashboard: https://dashboard.kernelci.org/build/{build['id']}") kci_msg("") @@ -363,6 +364,7 @@ def cmd_tests(data, commit, download_logs, status_filter, filter): kci_msg(f" log: {log_path}") kci_msg(f" start time: {test['start_time']}") kci_msg(f" id: {test['id']}") + kci_msg(f" dashboard: https://dashboard.kernelci.org/test/{test['id']}") kci_msg("")