8
8
import asyncio
9
9
import aiohttp
10
10
from rich import print
11
- from datetime import datetime , timezone
11
+ from datetime import datetime , timezone , timedelta
12
12
import humanize
13
13
from itertools import count
14
14
import diskcache
15
15
import pathlib
16
16
from typing import Optional , List , Dict
17
17
import argparse
18
18
19
- orgs = [
19
+ default_orgs = [
20
20
"binder-examples" ,
21
21
"binderhub-ci-repos" ,
22
22
"ipython" ,
23
23
"jupyter" ,
24
+ "jupyter-attic" ,
24
25
"jupyter-book" ,
25
26
"jupyter-governance" ,
26
27
"jupyter-incubator" ,
28
+ "jupyter-resources" ,
27
29
"jupyter-server" ,
30
+ "jupyter-standard" ,
28
31
"jupyter-standards" ,
29
32
"jupyter-widgets" ,
30
- "jupyterhub" ,
31
- "jupyterlab" ,
32
33
"jupyter-xeus" ,
33
34
"jupytercon" ,
35
+ "jupyterhub" ,
36
+ "jupyterlab" ,
34
37
"voila-dashboards" ,
35
38
"voila-gallery" ,
39
+ "pickleshare" ,
36
40
]
37
41
38
42
token = os .getenv ("GH_TOKEN" )
@@ -221,7 +225,7 @@ def clear_cache() -> None:
221
225
print (f"[red]Error clearing cache: { str (e )} [/red]" )
222
226
223
227
224
- async def main (debug : bool ):
228
+ async def main (orgs , debug : bool , timelimit_days : int ):
225
229
"""Main execution function."""
226
230
# Show cache status
227
231
print (f"[blue]Cache directory: { CACHE_DIR } (size: { get_cache_size ()} )[/blue]" )
@@ -278,23 +282,33 @@ async def main(debug: bool):
278
282
all_members [username ],
279
283
)
280
284
)
281
-
282
- for username , last_activity , user_orgs in sorted (
283
- user_activities ,
284
- key = lambda x : (x [1 ], x [0 ])
285
- if x [1 ] is not None
286
- else (datetime .fromtimestamp (0 ).replace (tzinfo = timezone .utc ), x [0 ]),
287
- reverse = True ,
288
- ):
289
- last_activity_ago = (
290
- humanize .naturaltime (datetime .now (last_activity .tzinfo ) - last_activity )
291
- if last_activity
292
- else "[red]never[/red]"
293
- )
294
- orgs_str = ", " .join (user_orgs )
295
- print (
296
- f"{ username :<20} : Last activity { last_activity_ago } in orgs: { orgs_str } "
297
- )
285
+ for org in orgs :
286
+ print (f"[bold]{ org } [/bold]" )
287
+ n_active = 0
288
+ n_inactive = 0
289
+ for username , last_activity , user_orgs in sorted (
290
+ 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 ]),
294
+ reverse = True ,
295
+ ):
296
+ if org not in user_orgs :
297
+ continue
298
+ if last_activity is not None and last_activity > (datetime .now ().replace (tzinfo = timezone .utc ) - timedelta (days = timelimit_days )):
299
+ n_active += 1
300
+ continue
301
+ n_inactive += 1
302
+ last_activity_ago = (
303
+ humanize .naturaltime (datetime .now (last_activity .tzinfo ) - last_activity )
304
+ if last_activity
305
+ else "[red]never[/red]"
306
+ )
307
+ 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." )
298
312
299
313
300
314
if __name__ == "__main__" :
@@ -303,9 +317,24 @@ async def main(debug: bool):
303
317
"--clear-cache" , action = "store_true" , help = "Clear the cache before running"
304
318
)
305
319
parser .add_argument ("--debug" , action = "store_true" , help = "Show debug information" )
320
+
321
+
322
+ parser .add_argument (
323
+ "--timelimit-days" ,
324
+ type = int ,
325
+ default = 0 ,
326
+ help = "Time limit in days for the last activity (default: 30)" ,
327
+ )
328
+ parser .add_argument (
329
+ "--orgs" ,
330
+ nargs = "+" ,
331
+ default = default_orgs ,
332
+ help = "GitHub organizations to track (default: all)" ,
333
+ )
306
334
args = parser .parse_args ()
307
335
336
+
308
337
if args .clear_cache :
309
338
clear_cache ()
310
339
311
- asyncio .run (main (args .debug ))
340
+ asyncio .run (main (args .orgs , args . debug , args . timelimit_days ))
0 commit comments