Skip to content

Commit e605b05

Browse files
authored
Merge pull request #85 from Carreau/is-own
Print if current user is admin on check org, and whether users are owners if inaactive.
2 parents 1aee41c + ba2654c commit e605b05

File tree

1 file changed

+62
-11
lines changed

1 file changed

+62
-11
lines changed

tools/last_user_activity.py

Lines changed: 62 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"""
66

77
import os
8+
import sys
89
import asyncio
910
import aiohttp
1011
from rich import print
@@ -138,6 +139,12 @@ async def get_org_members(
138139

139140
members.extend(page_members)
140141

142+
tasks = [check_user_admin(session, org, member["login"]) for member in members]
143+
admin_statuses = await asyncio.gather(*tasks)
144+
145+
for member, is_owner in zip(members, admin_statuses):
146+
member["is_owner"] = is_owner
147+
141148
# Cache the results
142149
cache[cache_key] = members # Using __setitem__ instead of set()
143150
print(f"[green]Cached {len(members)} members for {org}[/green]")
@@ -225,12 +232,39 @@ def clear_cache() -> None:
225232
print(f"[red]Error clearing cache: {str(e)}[/red]")
226233

227234

235+
async def check_user_admin(
236+
session: aiohttp.ClientSession, org: str, username: str
237+
) -> bool:
238+
url = f"https://api.github.com/orgs/{org}/memberships/{username}"
239+
async with session.get(url, headers=headers) as response:
240+
if response.status == 404:
241+
return False
242+
elif response.status != 200:
243+
print(
244+
f"[red]Error fetching membership for {username} in {org}: {response.status}[/red]"
245+
)
246+
return False
247+
return (await response.json())["role"] == "admin"
248+
249+
228250
async def main(orgs, debug: bool, timelimit_days: int):
229251
"""Main execution function."""
230252
# Show cache status
231253
print(f"[blue]Cache directory: {CACHE_DIR} (size: {get_cache_size()})[/blue]")
232254
print(f"[blue]Cache contains {len(cache)} items[/blue]")
233255

256+
# check who the current user is
257+
async with aiohttp.ClientSession() as session:
258+
async with session.get(
259+
"https://api.github.com/user", headers=headers
260+
) as response:
261+
if response.status == 200:
262+
user_data = await response.json()
263+
current_user = user_data["login"]
264+
print(f"[blue]Current user: {current_user}[/blue]")
265+
else:
266+
sys.exit(f"[red]Error fetching user data: {response.status}[/red]")
267+
234268
async with aiohttp.ClientSession() as session:
235269
# Check rate limit
236270
async with session.get(
@@ -255,12 +289,15 @@ async def main(orgs, debug: bool, timelimit_days: int):
255289

256290
# Get all members from all orgs
257291
all_members = {}
292+
org_owners = {}
258293
for org in orgs:
259294
members = await get_org_members(session, org, debug)
260295
for member in members:
261296
if member["login"] not in all_members:
262297
all_members[member["login"]] = []
263298
all_members[member["login"]].append(org)
299+
if member["is_owner"]:
300+
org_owners.setdefault(org, []).append(member["login"])
264301

265302
# Get activity for each user
266303
tasks = []
@@ -284,31 +321,47 @@ async def main(orgs, debug: bool, timelimit_days: int):
284321
)
285322
for org in orgs:
286323
print(f"[bold]{org}[/bold]")
324+
is_admin = await check_user_admin(session, org, current_user)
325+
if is_admin:
326+
if debug:
327+
print(f" [green]{current_user} is an admin in {org}[/green]")
328+
else:
329+
print(
330+
f" [yellow]{current_user} is not an admin in {org}, list of users will be incomplete (limited to public membership)[/yellow]"
331+
)
287332
n_active = 0
288333
n_inactive = 0
289334
for username, last_activity, user_orgs in sorted(
290335
user_activities,
291-
key=lambda x: (x[1], x[0])
292-
if x[1] is not None
293-
else (datetime.fromtimestamp(0).replace(tzinfo=timezone.utc), x[0]),
336+
key=lambda x: (
337+
(x[1], x[0])
338+
if x[1] is not None
339+
else (datetime.fromtimestamp(0).replace(tzinfo=timezone.utc), x[0])
340+
),
294341
reverse=True,
295342
):
296343
if org not in user_orgs:
297344
continue
298-
if last_activity is not None and last_activity > (datetime.now().replace(tzinfo=timezone.utc) - timedelta(days=timelimit_days)):
345+
if last_activity is not None and last_activity > (
346+
datetime.now().replace(tzinfo=timezone.utc)
347+
- timedelta(days=timelimit_days)
348+
):
299349
n_active += 1
300350
continue
301351
n_inactive += 1
302352
last_activity_ago = (
303-
humanize.naturaltime(datetime.now(last_activity.tzinfo) - last_activity)
353+
humanize.naturaltime(
354+
datetime.now(last_activity.tzinfo) - last_activity
355+
)
304356
if last_activity
305357
else "[red]never[/red]"
306358
)
307359
orgs_str = ", ".join(user_orgs)
308-
print(
309-
f" {username:<20}: Last activity {last_activity_ago}"
310-
)
311-
print(f" Found [red]{n_inactive} inactive[/red] and [green]{n_active} active[/green] users in {org} with last activity more recent than {timelimit_days} days.")
360+
u_owner = " (owner)" if username in org_owners.get(org, []) else ""
361+
print(f" {username+u_owner:<20}: Last activity {last_activity_ago}")
362+
print(
363+
f" Found [red]{n_inactive} inactive[/red] and [green]{n_active} active[/green] users in {org} with last activity more recent than {timelimit_days} days."
364+
)
312365

313366

314367
if __name__ == "__main__":
@@ -318,7 +371,6 @@ async def main(orgs, debug: bool, timelimit_days: int):
318371
)
319372
parser.add_argument("--debug", action="store_true", help="Show debug information")
320373

321-
322374
parser.add_argument(
323375
"--timelimit-days",
324376
type=int,
@@ -333,7 +385,6 @@ async def main(orgs, debug: bool, timelimit_days: int):
333385
)
334386
args = parser.parse_args()
335387

336-
337388
if args.clear_cache:
338389
clear_cache()
339390

0 commit comments

Comments
 (0)