Skip to content

Commit c18b121

Browse files
committed
add config file to manuall mark user as active
1 parent 8f22adc commit c18b121

File tree

2 files changed

+61
-5
lines changed

2 files changed

+61
-5
lines changed

last_user_activity.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# User : inviter : manual activity YYYY-MM : note
2+
JamiesHQ:fperez :2016-01:Hired admin at BIDS
3+
4+
jhamrick:carreau:2016-01:Wrote nbgrader and was a regular member of the team at bids.
5+
mpacer :carreau:2016-01:was a regular member of the team at bids on nbconvert
6+

tools/last_user_activity.py

Lines changed: 55 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,14 @@
88
import sys
99
import asyncio
1010
import aiohttp
11+
import json
1112
from rich import print
1213
from datetime import datetime, timezone, timedelta
1314
import humanize
1415
from itertools import count
1516
import diskcache
1617
import pathlib
18+
from pathlib import Path
1719
from typing import Optional, List, Dict
1820
import argparse
1921

@@ -247,12 +249,34 @@ async def check_user_admin(
247249
return (await response.json())["role"] == "admin"
248250

249251

250-
async def main(orgs, debug: bool, timelimit_days: int):
252+
async def main(orgs, debug: bool, timelimit_days: int, config_file: str):
251253
"""Main execution function."""
252254
# Show cache status
253255
print(f"[blue]Cache directory: {CACHE_DIR} (size: {get_cache_size()})[/blue]")
254256
print(f"[blue]Cache contains {len(cache)} items[/blue]")
255-
257+
now = datetime.now(timezone.utc)
258+
manual_users = {}
259+
for line in Path(config_file).read_text().splitlines():
260+
if line.startswith("#") or not line.strip():
261+
continue
262+
user, inviter, date_last_activity, reason = [x.strip() for x in line.split(":")]
263+
manual_users[user] = {
264+
"manual_last_activity": datetime.strptime(
265+
date_last_activity, "%Y-%m"
266+
).replace(tzinfo=timezone.utc),
267+
"last_activity_reason": reason,
268+
"inviter": inviter,
269+
}
270+
if not manual_users[user]["last_activity_reason"]:
271+
print(
272+
f"[yellow]Warning: No last_activity_reason for {user['username']}, skipping[/yellow]"
273+
)
274+
continue
275+
if now < manual_users[user]["manual_last_activity"]:
276+
print(
277+
f"[red]Warning: manual_last_activity for {user['username']} is in the future, skipping[/red]"
278+
)
279+
continue
256280
# check who the current user is
257281
async with aiohttp.ClientSession() as session:
258282
async with session.get(
@@ -296,7 +320,7 @@ async def main(orgs, debug: bool, timelimit_days: int):
296320
if member["login"] not in all_members:
297321
all_members[member["login"]] = []
298322
all_members[member["login"]].append(org)
299-
if member["is_owner"]:
323+
if member.get("is_owner", None):
300324
org_owners.setdefault(org, []).append(member["login"])
301325

302326
# Get activity for each user
@@ -312,6 +336,11 @@ async def main(orgs, debug: bool, timelimit_days: int):
312336
for (username, _), last_activity in zip(tasks, results):
313337
if last_activity is not None:
314338
assert isinstance(last_activity, datetime), last_activity
339+
340+
if last_activity is None:
341+
last_activity = manual_users.get(username, {}).get(
342+
"manual_last_activity", None
343+
)
315344
user_activities.append(
316345
(
317346
username,
@@ -320,6 +349,7 @@ async def main(orgs, debug: bool, timelimit_days: int):
320349
)
321350
)
322351
for org in orgs:
352+
# todo, check admin concurently
323353
print(f"[bold]{org}[/bold]")
324354
is_admin = await check_user_admin(session, org, current_user)
325355
if is_admin:
@@ -358,7 +388,21 @@ async def main(orgs, debug: bool, timelimit_days: int):
358388
)
359389
orgs_str = ", ".join(user_orgs)
360390
u_owner = " (owner)" if username in org_owners.get(org, []) else ""
361-
print(f" {username+u_owner:<20}: Last activity {last_activity_ago}")
391+
inviter = manual_users.get(username, {}).get("inviter", None)
392+
if inviter:
393+
inviter = f"[green]@{inviter}[/green]"
394+
else:
395+
inviter = "[red]unknown[/red]"
396+
reason = manual_users.get(username, {}).get(
397+
"last_activity_reason", None
398+
)
399+
if reason:
400+
reason = f"[green]{reason}[/green]"
401+
else:
402+
reason = "[red]unknown[/red]"
403+
print(
404+
f" {username + u_owner:<20}: Last activity {last_activity_ago}: reason: {reason}, inviter: {inviter}"
405+
)
362406
print(
363407
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."
364408
)
@@ -370,6 +414,12 @@ async def main(orgs, debug: bool, timelimit_days: int):
370414
"--clear-cache", action="store_true", help="Clear the cache before running"
371415
)
372416
parser.add_argument("--debug", action="store_true", help="Show debug information")
417+
parser.add_argument(
418+
"--config-file",
419+
type=str,
420+
default="last_user_activity.json",
421+
help="Path to the config file (default: last_user_activity.json)",
422+
)
373423

374424
parser.add_argument(
375425
"--timelimit-days",
@@ -388,4 +438,4 @@ async def main(orgs, debug: bool, timelimit_days: int):
388438
if args.clear_cache:
389439
clear_cache()
390440

391-
asyncio.run(main(args.orgs, args.debug, args.timelimit_days))
441+
asyncio.run(main(args.orgs, args.debug, args.timelimit_days, args.config_file))

0 commit comments

Comments
 (0)