1414import os
1515import subprocess
1616import sys
17- from datetime import UTC , datetime
17+ from datetime import datetime , timezone
1818from typing import Any
1919
2020import requests
@@ -216,15 +216,19 @@ def fetch_user_activity_insights(
216216 return {}
217217
218218
219- def get_team_mappings () -> dict [str , str ]:
220- """Get team mappings from Firestore.
219+ def get_participant_mappings () -> dict [str , dict [ str , Any ] ]:
220+ """Get participant data from Firestore including team and name info .
221221
222222 Returns
223223 -------
224- dict[str, str]
225- Mapping of github_handle (lowercase) -> team_name
224+ dict[str, dict[str, Any]]
225+ Mapping of github_handle (lowercase) -> {
226+ 'team_name': str,
227+ 'first_name': str | None,
228+ 'last_name': str | None
229+ }
226230 """
227- print ("Fetching team mappings from Firestore..." )
231+ print ("Fetching participant data from Firestore..." )
228232
229233 project_id = "coderd"
230234 database_id = "onboarding"
@@ -238,22 +242,25 @@ def get_team_mappings() -> dict[str, str]:
238242 data = doc .to_dict ()
239243 if data :
240244 github_handle = doc .id .lower ()
241- team_name = data .get ("team_name" , "Unassigned" )
242- mappings [github_handle ] = team_name
245+ mappings [github_handle ] = {
246+ "team_name" : data .get ("team_name" , "Unassigned" ),
247+ "first_name" : data .get ("first_name" ),
248+ "last_name" : data .get ("last_name" ),
249+ }
243250
244- print (f"✓ Loaded { len (mappings )} participant team mappings" )
251+ print (f"✓ Loaded { len (mappings )} participant mappings" )
245252 return mappings
246253
247254
248255def fetch_workspaces (
249- team_mappings : dict [str , str ], api_url : str , session_token : str
256+ participant_mappings : dict [str , dict [ str , Any ] ], api_url : str , session_token : str
250257) -> list [dict [str , Any ]]:
251258 """Fetch workspaces using Coder CLI and enrich with build data.
252259
253260 Parameters
254261 ----------
255- team_mappings : dict[str, str]
256- Mapping of github_handle -> team_name
262+ participant_mappings : dict[str, dict[ str, Any] ]
263+ Mapping of github_handle -> participant data ( team_name, first_name, last_name)
257264 api_url : str
258265 Coder API base URL
259266 session_token : str
@@ -262,7 +269,7 @@ def fetch_workspaces(
262269 Returns
263270 -------
264271 list[dict[str, Any]]
265- List of workspace objects with builds, usage hours, and active hours
272+ List of workspace objects with builds, usage hours, active hours, and team data
266273 """
267274 print ("Fetching workspaces from Coder..." )
268275 workspaces = run_command (["coder" , "list" , "-a" , "-o" , "json" ])
@@ -276,7 +283,8 @@ def fetch_workspaces(
276283 filtered_workspaces = []
277284 for ws in workspaces :
278285 owner_name = ws .get ("owner_name" , "" ).lower ()
279- team_name = team_mappings .get (owner_name , "Unassigned" )
286+ participant_data = participant_mappings .get (owner_name , {})
287+ team_name = participant_data .get ("team_name" , "Unassigned" )
280288
281289 if team_name not in excluded_teams :
282290 filtered_workspaces .append (ws )
@@ -299,14 +307,14 @@ def fetch_workspaces(
299307 for ws in filtered_workspaces
300308 if ws .get ("created_at" )
301309 ),
302- default = datetime .now (UTC ),
310+ default = datetime .now (timezone . utc ),
303311 )
304312 # Normalize to midnight (00:00:00) as required by the API
305313 start_time = earliest_created .replace (
306314 hour = 0 , minute = 0 , second = 0 , microsecond = 0
307315 ).strftime ("%Y-%m-%dT%H:%M:%SZ" )
308316 # Normalize end time to the start of the current hour (required by API)
309- now = datetime .now (UTC )
317+ now = datetime .now (timezone . utc )
310318 end_time = now .replace (minute = 0 , second = 0 , microsecond = 0 ).strftime (
311319 "%Y-%m-%dT%H:%M:%SZ"
312320 )
@@ -337,6 +345,13 @@ def fetch_workspaces(
337345 owner_name = workspace .get ("owner_name" , "" ).lower ()
338346 workspace ["active_hours" ] = activity_map .get (owner_name , 0.0 )
339347
348+ # Enrich with participant data (team and name)
349+ # This ensures all data needed for dashboard is in the snapshot
350+ participant_data = participant_mappings .get (owner_name , {})
351+ workspace ["team_name" ] = participant_data .get ("team_name" , "Unassigned" )
352+ workspace ["owner_first_name" ] = participant_data .get ("first_name" )
353+ workspace ["owner_last_name" ] = participant_data .get ("last_name" )
354+
340355 # Progress indicator
341356 if i % 10 == 0 :
342357 print (f" Processed { i } /{ len (filtered_workspaces )} workspaces..." )
@@ -371,7 +386,7 @@ def create_snapshot(
371386 workspaces : list [dict [str , Any ]], templates : list [dict [str , Any ]]
372387) -> dict [str , Any ]:
373388 """Create a snapshot object with timestamp."""
374- timestamp = datetime .now (UTC ).isoformat ().replace ("+00:00" , "Z" )
389+ timestamp = datetime .now (timezone . utc ).isoformat ().replace ("+00:00" , "Z" )
375390
376391 snapshot = {
377392 "timestamp" : timestamp ,
@@ -454,11 +469,11 @@ def main() -> None:
454469 api_url , session_token = get_coder_api_config ()
455470 print (f"✓ Using Coder API: { api_url } " )
456471
457- # Fetch team mappings first
458- team_mappings = get_team_mappings ()
472+ # Fetch participant mappings first
473+ participant_mappings = get_participant_mappings ()
459474
460475 # Fetch data (with filtering and build enrichment)
461- workspaces = fetch_workspaces (team_mappings , api_url , session_token )
476+ workspaces = fetch_workspaces (participant_mappings , api_url , session_token )
462477 templates = fetch_templates ()
463478
464479 # Create snapshot
0 commit comments