Skip to content

Commit 797cdfa

Browse files
authored
Merge pull request #191 from JenySadadia/detect-build-issues
Implement `detect issues` command
2 parents 11f5f84 + cd8e1e7 commit 797cdfa

File tree

6 files changed

+275
-3
lines changed

6 files changed

+275
-3
lines changed

kcidev/libs/dashboard.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ def wrapper(endpoint, params, use_json, body=None, max_retries=3):
5858
kci_msg(data)
5959
else:
6060
kci_msg("json error: " + str(data["error"]))
61-
raise click.Abort()
61+
raise click.ClickException(data.get("error"))
6262

6363
logging.info(f"Successfully completed {func.__name__} request")
6464
return data
@@ -280,3 +280,15 @@ def dashboard_fetch_hardware_tests(name, origin, use_json):
280280
return dashboard_api_post(
281281
f"hardware/{urllib.parse.quote_plus(name)}/tests", {}, use_json, body
282282
)
283+
284+
285+
def dashboard_fetch_build_issues(build_id, use_json):
286+
endpoint = f"build/{build_id}/issues"
287+
logging.info(f"Fetching build issues for build ID: {build_id}")
288+
return dashboard_api_fetch(endpoint, {}, use_json)
289+
290+
291+
def dashboard_fetch_boot_issues(test_id, use_json):
292+
endpoint = f"test/{test_id}/issues"
293+
logging.info(f"Fetching test issues for test ID: {test_id}")
294+
return dashboard_api_fetch(endpoint, {}, use_json)

kcidev/main.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
checkout,
1212
commit,
1313
config,
14+
detect,
1415
maestro,
1516
results,
1617
testretry,
@@ -60,6 +61,7 @@ def run():
6061
cli.add_command(checkout.checkout)
6162
cli.add_command(commit.commit)
6263
cli.add_command(config.config)
64+
cli.add_command(detect.detect)
6365
cli.add_command(maestro.maestro)
6466
cli.add_command(testretry.testretry)
6567
cli.add_command(results.results)
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import sys
2+
3+
import click
4+
5+
from kcidev.subcommands.detect.issues import issues
6+
7+
8+
@click.group(
9+
help="""Detect dashboard issues for builds and boots
10+
11+
\b
12+
Subcommands:
13+
issues - Fetch KCIDB issues for builds/boots
14+
15+
\b
16+
Examples:
17+
# Detect build issues
18+
kci-dev detect issues --builds --id <build-id>
19+
kci-dev detect issues --builds --all-checkouts --days <number-of-days> --origin <origin>
20+
# Detect boot issues
21+
kci-dev detect issues --boots --id <boot-id>
22+
kci-dev detect issues --boots --all-checkouts --days <number-of-days> --origin <origin>
23+
""",
24+
invoke_without_command=True,
25+
)
26+
@click.pass_context
27+
def detect(ctx):
28+
"""Commands related to results validation"""
29+
if ctx.invoked_subcommand is None:
30+
click.echo(ctx.get_help())
31+
sys.exit(0)
32+
33+
34+
# Add subcommands to the detect group
35+
detect.add_command(issues)
36+
37+
38+
if __name__ == "__main__":
39+
main_kcidev()
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
#!/usr/bin/env python3
2+
3+
import click
4+
from tabulate import tabulate
5+
6+
from kcidev.libs.common import kci_msg_red
7+
from kcidev.libs.dashboard import (
8+
dashboard_fetch_boot_issues,
9+
dashboard_fetch_build_issues,
10+
)
11+
from kcidev.subcommands.results import boots, builds
12+
13+
14+
def get_issues(ctx, origin, item_type, giturl, branch, commit, tree_name, arch):
15+
"""Get KCIDB issues for builds/boots"""
16+
try:
17+
if item_type == "builds":
18+
results_cmd = builds
19+
dashboard_func = dashboard_fetch_build_issues
20+
elif item_type == "boots":
21+
results_cmd = boots
22+
dashboard_func = dashboard_fetch_boot_issues
23+
else:
24+
kci_msg_red("Please specify 'builds' or 'boots' as items type")
25+
return []
26+
27+
dashboard_items = ctx.invoke(
28+
results_cmd,
29+
origin=origin,
30+
giturl=giturl,
31+
branch=branch,
32+
commit=commit,
33+
status="all",
34+
count=True,
35+
verbose=False,
36+
arch=arch,
37+
)
38+
final_stats = []
39+
for item in dashboard_items:
40+
# Exclude passed builds/boots
41+
if item["status"] == "PASS":
42+
continue
43+
item_id = item["id"]
44+
try:
45+
issues = dashboard_func(item_id, False)
46+
issue_ids = []
47+
for issue in issues:
48+
issue_ids.append(issue["id"])
49+
final_stats.append([f"{tree_name}/{branch}", item_id, issue_ids])
50+
except click.ClickException as e:
51+
if e.message in (
52+
"No issues were found for this build",
53+
"No issues were found for this test",
54+
):
55+
final_stats.append([f"{tree_name}/{branch}", item_id, []])
56+
return final_stats
57+
except click.Abort:
58+
kci_msg_red(
59+
f"{tree_name}/{branch}: Aborted while fetching dashboard builds/boots"
60+
)
61+
return []
62+
except click.ClickException as e:
63+
kci_msg_red(f"{tree_name}/{branch}: {e.message}")
64+
return []
65+
66+
67+
def get_issues_for_specific_item(item_type, item_id):
68+
"""Get KCIDB issues for a specific build/boot matching provided ID"""
69+
try:
70+
if item_type == "builds":
71+
dashboard_func = dashboard_fetch_build_issues
72+
elif item_type == "boots":
73+
dashboard_func = dashboard_fetch_boot_issues
74+
else:
75+
kci_msg_red("Please specify 'builds' or 'boots' as items type")
76+
return []
77+
issues = dashboard_func(item_id, False)
78+
issue_ids = []
79+
for issue in issues:
80+
issue_ids.append(issue["id"])
81+
stats = [[item_id, issue_ids]]
82+
return stats
83+
except click.ClickException as e:
84+
if e.message == "No issues were found for this build":
85+
stats = [[item_id, []]]
86+
return stats
87+
return None
88+
89+
90+
def print_stats(data, headers, max_col_width, table_fmt):
91+
"""Print build statistics in tabular format"""
92+
print("Creating a stats report...")
93+
print(
94+
tabulate(data, headers=headers, maxcolwidths=max_col_width, tablefmt=table_fmt)
95+
)
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
import click
2+
3+
from kcidev.subcommands.detect.helper import (
4+
get_issues,
5+
get_issues_for_specific_item,
6+
print_stats,
7+
)
8+
from kcidev.subcommands.results import trees
9+
10+
11+
@click.command(
12+
name="issues",
13+
help="""Detect KCIDB issues for builds and boots
14+
15+
\b
16+
The command is used to fetch KCIDB issues associated with builds and boots.
17+
Provide `--builds` and `--boots` option for builds issue detection and boots
18+
issue detection respectively.
19+
`--all-checkouts` option can be used to fetch KCIDB issues for all the
20+
failed and inconclusive builds/boots from all available trees on the dashboard.
21+
Issues for a single build/boot can be retrieved by providing `--id`
22+
option.
23+
24+
\b
25+
Examples:
26+
# Detect build issues
27+
kci-dev detect issues --builds --id <build-id>
28+
kci-dev detect issues --builds --all-checkouts --days <number-of-days> --origin <origin>
29+
# Detect boot issues
30+
kci-dev detect issues --boots --id <boot-id>
31+
kci-dev detect issues --boots --all-checkouts --days <number-of-days> --origin <origin>
32+
""",
33+
)
34+
@click.option(
35+
"--origin",
36+
help="Select KCIDB origin",
37+
default="maestro",
38+
)
39+
@click.option(
40+
"--builds",
41+
is_flag=True,
42+
help="Fetch KCIDB issues for builds",
43+
)
44+
@click.option(
45+
"--boots",
46+
is_flag=True,
47+
help="Fetch KCIDB issues for boots",
48+
)
49+
@click.option(
50+
"--id",
51+
"item_id",
52+
help="Build/boot id to get issues for",
53+
)
54+
@click.option(
55+
"--all-checkouts",
56+
is_flag=True,
57+
help="Fetch KCIDB issues for all failed/inconclusive builds/boots of all available checkouts",
58+
)
59+
@click.option("--arch", help="Filter by arch")
60+
@click.option(
61+
"--days",
62+
help="Provide a period of time in days to get results for",
63+
type=int,
64+
default="7",
65+
)
66+
@click.pass_context
67+
def issues(
68+
ctx,
69+
origin,
70+
builds,
71+
boots,
72+
item_id,
73+
all_checkouts,
74+
arch,
75+
days,
76+
):
77+
78+
if not (builds or boots):
79+
raise click.UsageError("Provide --builds or --boots to fetch issues for")
80+
81+
if builds and boots:
82+
raise click.UsageError("Specify only one option from --builds and --boots")
83+
84+
item_type = "builds" if builds else "boots"
85+
86+
if not (all_checkouts or item_id):
87+
raise click.UsageError("Provide --all-checkouts or --id")
88+
89+
print("Fetching KCIDB issues...")
90+
if all_checkouts:
91+
if item_id:
92+
raise click.UsageError("Cannot use --all-checkouts with --id")
93+
final_stats = []
94+
trees_list = ctx.invoke(trees, origin=origin, days=days, verbose=False)
95+
for tree in trees_list:
96+
giturl = tree["git_repository_url"]
97+
branch = tree["git_repository_branch"]
98+
commit = tree["git_commit_hash"]
99+
tree_name = tree["tree_name"]
100+
stats = get_issues(
101+
ctx, origin, item_type, giturl, branch, commit, tree_name, arch
102+
)
103+
final_stats.extend(stats)
104+
if final_stats:
105+
headers = [
106+
"tree/branch",
107+
"ID",
108+
"Issues",
109+
]
110+
max_col_width = [None, None, None]
111+
table_fmt = "simple_grid"
112+
print_stats(final_stats, headers, max_col_width, table_fmt)
113+
114+
elif item_id:
115+
stats = get_issues_for_specific_item(item_type, item_id)
116+
if stats:
117+
headers = [
118+
"ID",
119+
"Issues",
120+
]
121+
max_col_width = [None, None]
122+
table_fmt = "simple_grid"
123+
print_stats(stats, headers, max_col_width, table_fmt)

kcidev/subcommands/results/parser.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -202,14 +202,15 @@ def cmd_builds(
202202
logging.debug(f"Created filter set with {len(filter_set.filters)} filters")
203203

204204
filtered_builds = 0
205+
filtered_builds_list = []
205206
builds = []
206207
total_builds = len(data["builds"])
207208
logging.debug(f"Processing {total_builds} builds")
208209

209210
for build in data["builds"]:
210211
if not filter_set.matches(build):
211212
continue
212-
213+
filtered_builds_list.append(build)
213214
log_path = build["log_url"]
214215
if download_logs:
215216
try:
@@ -234,7 +235,7 @@ def cmd_builds(
234235
kci_msg(filtered_builds)
235236
elif use_json:
236237
kci_msg(json.dumps(builds))
237-
return data["builds"]
238+
return filtered_builds_list
238239

239240

240241
def print_build(build, log_path):

0 commit comments

Comments
 (0)