Skip to content

Commit 0a95eef

Browse files
committed
✨ [+feature] Added "pending" column icons
- Ensure that all bullet items are expressed in terms of dashes - Use dash in columns to indicate that no information is available - Only look for the last 30 days of CI info - Fixed scrolling issues in additional_data when switching columns - Fixed refresh issue when additional_data changes for the selected column
1 parent a4a8b96 commit 0a95eef

4 files changed

Lines changed: 528 additions & 53 deletions

File tree

src/AllGitStatus/MainApp.py

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,8 @@ async def _ResetAllRepositories(self) -> None:
254254
self._additional_info_data.clear()
255255
self._state_data.clear()
256256
self._data_table.clear()
257-
await self._OnSelectionChanged(repopulate_changes=False)
257+
258+
await self._OnSelectionChanged()
258259

259260
# Get the repositories
260261

@@ -287,6 +288,7 @@ async def _ResetRepository(self, repository: Repository, repository_index: int)
287288
)
288289

289290
if repository_index == self._data_table.cursor_coordinate.row:
291+
await self._OnSelectionChanged()
290292
self._RefreshBindings()
291293

292294
# Create the repo name
@@ -314,22 +316,43 @@ async def _ResetRepository(self, repository: Repository, repository_index: int)
314316
async def LoadCells() -> None:
315317
assert self._github_session is not None
316318

317-
for source in [
319+
sources = [
318320
LocalGitSource(),
319321
GitHubSource(self._github_session),
320-
]:
322+
]
323+
324+
# Set all of the column values to pending
325+
for source in sources:
326+
if not source.Applies(repository):
327+
continue
328+
329+
for column_key, column in COLUMN_MAP.items():
330+
if not column_key[0] and not column_key[1]:
331+
continue
332+
333+
if column_key[0] != source.__class__.__name__:
334+
continue
335+
336+
self._data_table.update_cell_at(
337+
Coordinate(repository_index, column.value),
338+
Text("⏳", justify=column.justify), # ty: ignore[invalid-argument-type]
339+
update_width=True,
340+
)
341+
342+
# Get the actual values
343+
for source in sources:
321344
if not source.Applies(repository):
322345
continue
323346

324347
async for info in source.Query(repository):
325-
self._PopulateCell(repository_index, info)
348+
await self._PopulateCell(repository_index, info)
326349

327350
# ----------------------------------------------------------------------
328351

329352
self.run_worker(LoadCells())
330353

331354
# ----------------------------------------------------------------------
332-
def _PopulateCell(self, repository_index: int, info: ResultInfo | ErrorInfo) -> None:
355+
async def _PopulateCell(self, repository_index: int, info: ResultInfo | ErrorInfo) -> None:
333356
column = COLUMN_MAP[info.key]
334357

335358
if isinstance(info, ErrorInfo):
@@ -361,21 +384,22 @@ def _PopulateCell(self, repository_index: int, info: ResultInfo | ErrorInfo) ->
361384

362385
self._additional_info_data.setdefault(repository_index, {})[column.value] = additional_info
363386

387+
if self._data_table.cursor_row == repository_index and self._data_table.cursor_column == column.value:
388+
await self._OnSelectionChanged()
389+
364390
# ----------------------------------------------------------------------
365-
async def _OnSelectionChanged(self, *, repopulate_changes: bool = True) -> None:
391+
async def _OnSelectionChanged(self) -> None:
366392
self._additional_info.clear()
367393
self._RefreshBindings()
368394

369-
if not repopulate_changes:
370-
return
371-
372395
row_index = self._data_table.cursor_coordinate.row
373396
col_index = self._data_table.cursor_coordinate.column
374397

375398
additional_info = self._additional_info_data.get(row_index, {}).get(col_index)
376399

377400
if additional_info:
378401
self._additional_info.write(additional_info)
402+
self._additional_info.scroll_home()
379403

380404
# ----------------------------------------------------------------------
381405
def _RefreshBindings(self) -> None:

src/AllGitStatus/Sources/GitHubSource.py

Lines changed: 38 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import textwrap
44

55
from collections.abc import AsyncGenerator
6+
from datetime import datetime, timedelta, UTC
67

78
import aiohttp
89

@@ -131,6 +132,8 @@ async def _GenerateIssueInfo(
131132
repo: Repository,
132133
github_url: str,
133134
) -> AsyncGenerator[ResultInfo | ErrorInfo]:
135+
key = (self.__class__.__name__, "issues")
136+
134137
try:
135138
label_counts: dict[str, int] = {}
136139
total_count = 0
@@ -160,7 +163,7 @@ async def _GenerateIssueInfo(
160163

161164
label_str = f" [{', '.join(issue_labels)}]" if issue_labels else ""
162165

163-
issue_data.append(f" #{issue_number}{label_str} {issue_title} (by {issue_author})")
166+
issue_data.append(f"- #{issue_number}{label_str} {issue_title} (by {issue_author})")
164167

165168
# Build additional info with issue details
166169
additional_info_lines = [
@@ -182,24 +185,22 @@ async def _GenerateIssueInfo(
182185

183186
yield ResultInfo(
184187
repo,
185-
(self.__class__.__name__, "issues"),
188+
key,
186189
f"{total_count:5} 🐛",
187190
"\n".join(additional_info_lines),
188191
)
189192

190193
except Exception as ex:
191-
yield ErrorInfo(
192-
repo,
193-
(self.__class__.__name__, "issues"),
194-
ex,
195-
)
194+
yield ErrorInfo(repo, key, ex)
196195

197196
# ----------------------------------------------------------------------
198197
async def _GeneratePullRequestInfo(
199198
self,
200199
repo: Repository,
201200
github_url: str,
202201
) -> AsyncGenerator[ResultInfo | ErrorInfo]:
202+
key = (self.__class__.__name__, "pull_requests")
203+
203204
try:
204205
total_count = 0
205206
pr_data: list[str] = []
@@ -216,7 +217,7 @@ async def _GeneratePullRequestInfo(
216217

217218
draft_indicator = "[DRAFT] " if pr_draft else ""
218219

219-
pr_data.append(f" #{pr_number} {draft_indicator}{pr_title} (by {pr_author})")
220+
pr_data.append(f"- #{pr_number} {draft_indicator}{pr_title} (by {pr_author})")
220221

221222
additional_info_lines = [
222223
f"Pull Requests: {github_url}/pulls",
@@ -229,24 +230,22 @@ async def _GeneratePullRequestInfo(
229230

230231
yield ResultInfo(
231232
repo,
232-
(self.__class__.__name__, "pull_requests"),
233+
key,
233234
f"{total_count:5} 🔀",
234235
"\n".join(additional_info_lines),
235236
)
236237

237238
except Exception as ex:
238-
yield ErrorInfo(
239-
repo,
240-
(self.__class__.__name__, "pull_requests"),
241-
ex,
242-
)
239+
yield ErrorInfo(repo, key, ex)
243240

244241
# ----------------------------------------------------------------------
245242
async def _GenerateSecurityAlertInfo(
246243
self,
247244
repo: Repository,
248245
github_url: str,
249246
) -> AsyncGenerator[ResultInfo | ErrorInfo]:
247+
key = (self.__class__.__name__, "security_alerts")
248+
250249
try:
251250
severity_counts: dict[str, int] = {
252251
"critical": 0,
@@ -271,7 +270,7 @@ async def _GenerateSecurityAlertInfo(
271270
package = alert.get("security_vulnerability", {}).get("package", {})
272271

273272
alert_data.append(
274-
f" [{advisory.get('severity', 'unknown').upper()}] {package.get('name', 'unknown')}: {advisory.get('summary', 'No summary')}"
273+
f"- [{advisory.get('severity', 'unknown').upper()}] {package.get('name', 'unknown')}: {advisory.get('summary', 'No summary')}"
275274
)
276275

277276
# Build display value with icon based on severity
@@ -303,17 +302,13 @@ async def _GenerateSecurityAlertInfo(
303302

304303
yield ResultInfo(
305304
repo,
306-
(self.__class__.__name__, "security_alerts"),
305+
key,
307306
display_value,
308307
"\n".join(additional_info_lines),
309308
)
310309

311310
except Exception as ex:
312-
yield ErrorInfo(
313-
repo,
314-
(self.__class__.__name__, "security_alerts"),
315-
ex,
316-
)
311+
yield ErrorInfo(repo, key, ex)
317312

318313
# ----------------------------------------------------------------------
319314
async def _GenerateCICDInfo(
@@ -322,12 +317,20 @@ async def _GenerateCICDInfo(
322317
github_url: str,
323318
default_branch: str,
324319
) -> AsyncGenerator[ResultInfo | ErrorInfo]:
325-
info_key = (self.__class__.__name__, "cicd_status")
320+
key = (self.__class__.__name__, "cicd_status")
326321

327322
try:
328-
url = f"https://api.github.com/repos/{repo.github_owner}/{repo.github_repo}/actions/runs?branch={default_branch}&per_page=100"
323+
prev_month = datetime.now(tz=UTC) - timedelta(days=30)
324+
325+
url = f"https://api.github.com/repos/{repo.github_owner}/{repo.github_repo}/actions/runs"
329326

330-
async with self._session.get(url) as response:
327+
params = {
328+
"branch": default_branch,
329+
"per_page": 100,
330+
"created": f">={prev_month.date()}",
331+
}
332+
333+
async with self._session.get(url, params=params) as response:
331334
response.raise_for_status()
332335
result = await response.json()
333336

@@ -336,17 +339,18 @@ async def _GenerateCICDInfo(
336339
if not workflow_runs:
337340
yield ResultInfo(
338341
repo,
339-
info_key,
340-
"🔘",
342+
key,
343+
"-",
341344
textwrap.dedent(
342345
"""\
343346
CI/CD Status: {github_url}/actions
344347
345-
No workflow runs found for branch '{default_branch}'
348+
No workflow runs found for branch '{default_branch}' since '{prev_month}'.
346349
""",
347350
).format(
348351
github_url=github_url,
349352
default_branch=default_branch,
353+
prev_month=prev_month.date(),
350354
),
351355
)
352356
return
@@ -386,7 +390,7 @@ async def _GenerateCICDInfo(
386390
else:
387391
status_label = conclusion or status or "UNKNOWN"
388392

389-
run_details.append(f" [{status_label}] {run['created_at']} {run['path']}: {run['name']}")
393+
run_details.append(f"- [{status_label}] {run['created_at']} {run['path']}: {run['name']}")
390394

391395
# Determine display icon based on priority: failure > in_progress > success
392396
if status_counts["failure"] > 0:
@@ -415,33 +419,29 @@ async def _GenerateCICDInfo(
415419

416420
yield ResultInfo(
417421
repo,
418-
info_key,
422+
key,
419423
display_icon,
420424
"\n".join(additional_info_lines),
421425
)
422426

423427
except Exception as ex:
424-
yield ErrorInfo(
425-
repo,
426-
info_key,
427-
ex,
428-
)
428+
yield ErrorInfo(repo, key, ex)
429429

430430
# ----------------------------------------------------------------------
431-
async def _GeneratePaginatedResults(self, raw_url: str) -> AsyncGenerator[dict]:
432-
url: str | None = f"{raw_url}?state=open&per_page=100"
431+
async def _GeneratePaginatedResults(self, url: str) -> AsyncGenerator[dict]:
432+
params = {"state": "open", "per_page": 100}
433433
next_page_regex = re.compile(r"<([^>]+)>")
434434

435435
while url:
436-
async with self._session.get(url) as response:
436+
async with self._session.get(url, params=params) as response:
437437
response.raise_for_status()
438438

439439
results = await response.json()
440440

441441
for result in results:
442442
yield result
443443

444-
url = None
444+
url: str | None = None
445445
link_header = response.headers.get("Link", "")
446446

447447
for link in link_header.split(","):

0 commit comments

Comments
 (0)