Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,8 +155,20 @@ builds = client.get_builds(
commit="0123456789abcdef0123456789abcdef01234567",
arch="x86_64",
)

issues = client.get_issue_list(
origin="maestro",
start_date="2026-08-01T00:00:00Z",
end_date="2026-08-08T00:00:00Z",
)
```

`get_issue_list()` defaults to the last 7 days. Pass `days=N` for a relative
window, or both `start_date` and `end_date` for exact boundaries, without
`days`. Date-only values use midnight UTC; timestamps must include a timezone.
The CLI equivalent is `kci-dev results issues --start-date ... --end-date ...`,
with optional `--origin` and `--json` (see [issue commands](docs/issues.md)).

### Run kci-dev subcommands from Python

For existing integrations that still need full CLI behavior, the public API can
Expand Down
33 changes: 30 additions & 3 deletions docs/issues.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ Fetch KernelCI Database (KCIDB) issues associated with **builds** and **boots**,

```bash
kci-dev results issues [--builds | --boots | --new]
[--days <N>] [--arch <ARCH>] [--origin <ORIGIN>] [--latest]
[--days <N> | --start-date <DATE> --end-date <DATE>]
[--arch <ARCH>] [--origin <ORIGIN>] [--latest]
[--giturl <URL> --branch <BRANCH> --commit <COMMIT>]
[--git-folder <PATH>]
```
Expand All @@ -33,6 +34,28 @@ kci-dev results issues [--builds | --boots | --new]

## Common Workflows

### List issues within an exact date range

```bash
kci-dev results issues --origin maestro \
--start-date 2026-08-01T00:00:00Z \
--end-date 2026-08-08T00:00:00Z

# Date-only boundaries mean midnight UTC, including the end boundary.
kci-dev results issues --origin maestro \
--start-date 2026-08-01 --end-date 2026-08-08 --json
```

Both boundaries are required. Timestamps must include `Z` or a UTC offset, such
as `2026-08-01T09:00:00+09:00`. Boundaries are sent as Unix seconds without
rounding to whole days; fractional seconds are retained. The start must not be
later than the end. A date-only end is not expanded to the end of that day.

Explicit ranges apply to the issue listing, with optional `--origin` and
`--json`. They cannot be combined with `--days`, `--new`, `--missing`, checkout
selection, or the build/boot, architecture and tree options. Unsupported
combinations produce a usage error rather than silently ignoring the range.

### Detect build issues

```bash
Expand Down Expand Up @@ -90,8 +113,12 @@ kci-dev results issues --builds --arch arm64 --days 3 --latest

### Dashboard Scope

* `--days <N>` *(int, default: 7)*
Time window (in days) to consider when scanning dashboard results.
* `--days <N>` *(int, default: 5)*
Time window (in days) to consider when scanning dashboard results. For the
issue listing, this is measured backwards from the current time in UTC.
* `--start-date <DATE>` and `--end-date <DATE>`
Exact issue-listing boundaries. Accept `YYYY-MM-DD` (midnight UTC) or
timezone-aware ISO 8601 timestamps. Use both together, without `--days`.
* `--origin <ORIGIN>` *(default: `maestro`)*
KCIDB origin to query.
* `--arch <ARCH>`
Expand Down
9 changes: 8 additions & 1 deletion kcidev/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -822,13 +822,20 @@ def get_boot_issues(self, test_id, error_verbose=True):
error_verbose,
)

def get_issue_list(self, origin=None, days=7):
def get_issue_list(self, origin=None, days=None, *, start_date=None, end_date=None):
"""List issues for the last 7 days or an explicit pair of date boundaries.

Dates use UTC midnight; timestamps must include a timezone. Explicit
boundaries cannot be combined with days. Invalid ranges raise KciDevError.
"""
return self._dashboard_request(
"Dashboard issue list request failed",
dashboard_fetch_issue_list,
origin,
days,
True,
start_date,
end_date,
)

def get_issue(self, issue_id):
Expand Down
44 changes: 41 additions & 3 deletions kcidev/libs/dashboard.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import json
import logging
import re
import time
import urllib
from contextlib import contextmanager
from contextvars import ContextVar
from datetime import datetime, timedelta
from datetime import datetime, timedelta, timezone
from functools import wraps
from urllib.parse import urlparse

Expand Down Expand Up @@ -402,9 +403,46 @@ def dashboard_fetch_boot_issues(test_id, use_json, error_verbose):
return dashboard_api_fetch(endpoint, {}, use_json, error_verbose=error_verbose)


def dashboard_fetch_issue_list(origin, days, use_json):
def _parse_issue_date(value, option):
"""Parse a UTC calendar date or a timezone-aware ISO 8601 timestamp."""
try:
if re.fullmatch(r"[0-9]{4}-[0-9]{2}-[0-9]{2}", value):
return datetime.fromisoformat(value).replace(tzinfo=timezone.utc)
if not re.match(r"[0-9]{4}-[0-9]{2}-[0-9]{2}[Tt ]", value):
raise ValueError
parsed = datetime.fromisoformat(value.replace("Z", "+00:00"))
if parsed.utcoffset() is None:
raise ValueError
return parsed
except ValueError as exc:
raise click.BadParameter(
"Expected YYYY-MM-DD or an ISO 8601 timestamp with a timezone "
"(Z or an explicit UTC offset).",
param_hint=option,
) from exc


def dashboard_fetch_issue_list(origin, days, use_json, start_date=None, end_date=None):
if start_date is not None or end_date is not None:
if start_date is None or end_date is None:
raise click.UsageError("--start-date and --end-date must be used together")
if days is not None:
raise click.UsageError(
"An explicit date range cannot be combined with --days"
)
start = _parse_issue_date(start_date, "--start-date")
end = _parse_issue_date(end_date, "--end-date")
else:
end = datetime.now(timezone.utc)
start = end - timedelta(days=7 if days is None else days)
if start > end:
raise click.UsageError("The start date cannot be later than the end date")

# The issue endpoint uses Unix seconds, not the tree endpoint's date filters.
# Strings also retain fractional seconds in the dashboard's str | int schema.
params = {
"interval_in_days": days,
"startTimestampInSeconds": str(start.timestamp()),
"endTimestampInSeconds": str(end.timestamp()),
}
if origin:
params["filter_origin"] = origin
Expand Down
42 changes: 39 additions & 3 deletions kcidev/subcommands/results/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -932,6 +932,8 @@ def get_filtered_issues(origin, tree_name, giturl, branch, commit, arch):
Examples:
# Get issues
kci-dev results issues --origin <origin> --days <number-of-days>
# Get issues within exact UTC boundaries
kci-dev results issues --start-date 2026-08-01T00:00:00Z --end-date 2026-08-08T00:00:00Z
# Get new issues for all checkouts
kci-dev results issues --new --days <number-of-days> --origin <origin>
# Get new issues for a checkout
Expand All @@ -953,9 +955,17 @@ def get_filtered_issues(origin, tree_name, giturl, branch, commit, arch):
)
@click.option(
"--days",
help="Provide a period of time in days to get results for",
help="Get results for the last N days (default: 5); cannot be combined with a date range",
type=int,
default="5",
default=None,
)
@click.option(
"--start-date",
help="Issue listing start: YYYY-MM-DD (UTC midnight) or timezone-aware ISO 8601; requires --end-date",
)
@click.option(
"--end-date",
help="Issue listing end: YYYY-MM-DD (UTC midnight) or timezone-aware ISO 8601; requires --start-date",
)
@click.option(
"--new",
Expand Down Expand Up @@ -1007,6 +1017,8 @@ def issues(
ctx,
origin,
days,
start_date,
end_date,
new,
giturl,
branch,
Expand All @@ -1021,9 +1033,33 @@ def issues(
boots,
):
"""Issues command handler"""
has_date_range = start_date is not None or end_date is not None
if has_date_range and any(
[
new,
missing,
giturl,
branch,
commit,
git_folder,
latest,
arch,
tree,
builds,
boots,
]
):
raise click.UsageError(
"--start-date and --end-date are only supported for the unfiltered issue "
"listing (with optional --origin and --json)"
)
if not has_date_range and days is None:
days = 5
if not new and not missing:
if not any([giturl, branch, commit]):
data = dashboard_fetch_issue_list(origin, days, use_json)
data = dashboard_fetch_issue_list(
origin, days, use_json, start_date, end_date
)
print_issues(data, use_json)
return
if not all([giturl, branch, commit]):
Expand Down
Loading
Loading