@@ -47,17 +47,44 @@ def handle_onboard_command(ack, command, client: WebClient, respond):
4747
4848 user_id = command ["user_id" ]
4949 text = command .get ("text" , "" ).strip ()
50+ logger .info (f"Onboard command from { user_id } , text: '{ text } '" )
5051
5152 # Check if user is admin
5253 if user_id != config .slack .admin_user_id :
5354 respond ("Only the lab admin can initiate onboarding." )
5455 return
5556
56- # Parse mentioned user if provided
57+ # Parse mentioned user from text
58+ # Slack may send mentions as <@U12345|username>, <@U12345>,
59+ # or just @username (common in slash commands where Slack doesn't
60+ # auto-convert mentions to the <@...> format)
61+ import re
5762 target_user_id = None
58- if text .startswith ("<@" ) and ">" in text :
59- # Extract user ID from mention like <@U12345|username>
60- target_user_id = text .split ("<@" )[1 ].split ("|" )[0 ].split (">" )[0 ]
63+ mention_match = re .search (r'<@([A-Z0-9]+)(?:\|[^>]*)?>' , text )
64+ if mention_match :
65+ target_user_id = mention_match .group (1 )
66+ elif text .startswith ("@" ) or text :
67+ # Try to look up user by name/display name
68+ search_name = text .lstrip ("@" ).strip ()
69+ if search_name :
70+ try :
71+ users_resp = client .users_list ()
72+ for member in users_resp ["members" ]:
73+ if member .get ("deleted" ) or member .get ("is_bot" ):
74+ continue
75+ name = member .get ("name" , "" )
76+ display_name = member .get ("profile" , {}).get ("display_name" , "" )
77+ real_name = member .get ("real_name" , "" )
78+ if search_name .lower () in (
79+ name .lower (),
80+ display_name .lower (),
81+ real_name .lower (),
82+ ):
83+ target_user_id = member ["id" ]
84+ logger .info (f"Resolved '{ search_name } ' to user { target_user_id } ({ real_name } )" )
85+ break
86+ except SlackApiError as e :
87+ logger .error (f"Error looking up user: { e } " )
6188
6289 if not target_user_id :
6390 respond (
@@ -114,6 +141,43 @@ def handle_onboard_command(ack, command, client: WebClient, respond):
114141
115142 respond (f"Started onboarding for <@{ target_user_id } >. They've been sent the welcome message." )
116143
144+ # Dynamic form updates — show/hide grad fields based on role selection
145+ @app .action ("role_select" )
146+ def handle_role_select (ack , body , client : WebClient ):
147+ """Update the form when role is selected to show/hide grad fields."""
148+ ack ()
149+ selected_role = body ["actions" ][0 ]["selected_option" ]["value" ]
150+ logger .info (f"Role selected: { selected_role } " )
151+ client .views_update (
152+ view_id = body ["view" ]["id" ],
153+ view = {
154+ "type" : "modal" ,
155+ "callback_id" : "onboarding_form" ,
156+ "title" : {"type" : "plain_text" , "text" : "CDL Onboarding" },
157+ "submit" : {"type" : "plain_text" , "text" : "Submit" },
158+ "close" : {"type" : "plain_text" , "text" : "Cancel" },
159+ "blocks" : _build_form_blocks (role = selected_role ),
160+ },
161+ )
162+
163+ @app .action ("grad_type_select" )
164+ def handle_grad_type_select (ack , body , client : WebClient ):
165+ """Update the form when grad type is selected to show/hide masters field."""
166+ ack ()
167+ selected_grad_type = body ["actions" ][0 ]["selected_option" ]["value" ]
168+ logger .info (f"Grad type selected: { selected_grad_type } " )
169+ client .views_update (
170+ view_id = body ["view" ]["id" ],
171+ view = {
172+ "type" : "modal" ,
173+ "callback_id" : "onboarding_form" ,
174+ "title" : {"type" : "plain_text" , "text" : "CDL Onboarding" },
175+ "submit" : {"type" : "plain_text" , "text" : "Submit" },
176+ "close" : {"type" : "plain_text" , "text" : "Cancel" },
177+ "blocks" : _build_form_blocks (role = "Graduate Student" , grad_type = selected_grad_type ),
178+ },
179+ )
180+
117181 # Handle the onboarding form submission
118182 @app .view ("onboarding_form" )
119183 def handle_onboarding_form (ack , body , client : WebClient , view ):
@@ -436,6 +500,130 @@ def _build_welcome_message(user_name: str) -> list:
436500 ]
437501
438502
503+ def _build_form_blocks (role = None , grad_type = None ):
504+ """Build the onboarding form blocks, conditionally showing grad fields."""
505+ role_options = [
506+ {"text" : {"type" : "plain_text" , "text" : "Graduate Student" }, "value" : "Graduate Student" },
507+ {"text" : {"type" : "plain_text" , "text" : "Undergraduate" }, "value" : "Undergraduate" },
508+ {"text" : {"type" : "plain_text" , "text" : "Postdoctoral Researcher" }, "value" : "Postdoctoral Researcher" },
509+ {"text" : {"type" : "plain_text" , "text" : "Lab Manager" }, "value" : "Lab Manager" },
510+ {"text" : {"type" : "plain_text" , "text" : "Research Scientist" }, "value" : "Research Scientist" },
511+ ]
512+
513+ role_element = {
514+ "type" : "static_select" ,
515+ "action_id" : "role_select" ,
516+ "placeholder" : {"type" : "plain_text" , "text" : "Select your role" },
517+ "options" : role_options ,
518+ }
519+ if role :
520+ role_element ["initial_option" ] = next (
521+ (o for o in role_options if o ["value" ] == role ), None
522+ )
523+
524+ blocks = [
525+ {
526+ "type" : "section" ,
527+ "text" : {
528+ "type" : "mrkdwn" ,
529+ "text" : "Please provide the following information for your CDL profile." ,
530+ },
531+ },
532+ {
533+ "type" : "input" ,
534+ "block_id" : "role_block" ,
535+ "dispatch_action" : True ,
536+ "element" : role_element ,
537+ "label" : {"type" : "plain_text" , "text" : "Your Role in the Lab" },
538+ },
539+ ]
540+
541+ # Show grad type only for Graduate Students
542+ if role == "Graduate Student" :
543+ grad_type_options = [
544+ {"text" : {"type" : "plain_text" , "text" : "Doctoral" }, "value" : "Doctoral" },
545+ {"text" : {"type" : "plain_text" , "text" : "Masters" }, "value" : "Masters" },
546+ ]
547+ grad_type_element = {
548+ "type" : "static_select" ,
549+ "action_id" : "grad_type_select" ,
550+ "placeholder" : {"type" : "plain_text" , "text" : "Select program type" },
551+ "options" : grad_type_options ,
552+ }
553+ if grad_type :
554+ grad_type_element ["initial_option" ] = next (
555+ (o for o in grad_type_options if o ["value" ] == grad_type ), None
556+ )
557+ blocks .append ({
558+ "type" : "input" ,
559+ "block_id" : "grad_type_block" ,
560+ "dispatch_action" : True ,
561+ "element" : grad_type_element ,
562+ "label" : {"type" : "plain_text" , "text" : "Graduate Program Type" },
563+ })
564+
565+ # Show masters field only for Masters students
566+ if grad_type == "Masters" :
567+ blocks .append ({
568+ "type" : "input" ,
569+ "block_id" : "grad_field_block" ,
570+ "element" : {
571+ "type" : "plain_text_input" ,
572+ "action_id" : "grad_field_input" ,
573+ "placeholder" : {"type" : "plain_text" , "text" : "e.g., Quantitative Biomedical Sciences" },
574+ },
575+ "label" : {"type" : "plain_text" , "text" : "Masters Program/Field" },
576+ })
577+
578+ # Common fields (always shown)
579+ blocks .extend ([
580+ {
581+ "type" : "input" ,
582+ "block_id" : "github_block" ,
583+ "element" : {
584+ "type" : "plain_text_input" ,
585+ "action_id" : "github_input" ,
586+ "placeholder" : {"type" : "plain_text" , "text" : "e.g., octocat" },
587+ },
588+ "label" : {"type" : "plain_text" , "text" : "GitHub Username" },
589+ "hint" : {"type" : "plain_text" , "text" : "Your GitHub username (not email). We'll invite you to the ContextLab organization." },
590+ },
591+ {
592+ "type" : "input" ,
593+ "block_id" : "bio_block" ,
594+ "element" : {
595+ "type" : "plain_text_input" ,
596+ "action_id" : "bio_input" ,
597+ "multiline" : True ,
598+ "placeholder" : {"type" : "plain_text" , "text" : "Tell us about yourself and your research interests..." },
599+ },
600+ "label" : {"type" : "plain_text" , "text" : "Short Bio" },
601+ "hint" : {"type" : "plain_text" , "text" : "3-4 sentences about you. We'll edit it for style consistency." },
602+ },
603+ {
604+ "type" : "input" ,
605+ "block_id" : "website_block" ,
606+ "optional" : True ,
607+ "element" : {
608+ "type" : "plain_text_input" ,
609+ "action_id" : "website_input" ,
610+ "placeholder" : {"type" : "plain_text" , "text" : "https://your-website.com" },
611+ },
612+ "label" : {"type" : "plain_text" , "text" : "Personal Website (optional)" },
613+ "hint" : {"type" : "plain_text" , "text" : "If you have a personal website, we'll link to it from your profile." },
614+ },
615+ {
616+ "type" : "section" ,
617+ "text" : {
618+ "type" : "mrkdwn" ,
619+ "text" : ":camera: *Photo:* After submitting this form, please upload a profile photo by sending it as a message in this conversation." ,
620+ },
621+ },
622+ ])
623+
624+ return blocks
625+
626+
439627def _open_onboarding_form (client : WebClient , trigger_id : str , request : OnboardingRequest ):
440628 """Open the onboarding information form modal."""
441629 try :
@@ -447,129 +635,7 @@ def _open_onboarding_form(client: WebClient, trigger_id: str, request: Onboardin
447635 "title" : {"type" : "plain_text" , "text" : "CDL Onboarding" },
448636 "submit" : {"type" : "plain_text" , "text" : "Submit" },
449637 "close" : {"type" : "plain_text" , "text" : "Cancel" },
450- "blocks" : [
451- {
452- "type" : "section" ,
453- "text" : {
454- "type" : "mrkdwn" ,
455- "text" : "Please provide the following information for your CDL profile." ,
456- },
457- },
458- {
459- "type" : "input" ,
460- "block_id" : "role_block" ,
461- "element" : {
462- "type" : "static_select" ,
463- "action_id" : "role_select" ,
464- "placeholder" : {"type" : "plain_text" , "text" : "Select your role" },
465- "options" : [
466- {"text" : {"type" : "plain_text" , "text" : "Graduate Student" }, "value" : "Graduate Student" },
467- {"text" : {"type" : "plain_text" , "text" : "Undergraduate" }, "value" : "Undergraduate" },
468- {"text" : {"type" : "plain_text" , "text" : "Postdoctoral Researcher" }, "value" : "Postdoctoral Researcher" },
469- {"text" : {"type" : "plain_text" , "text" : "Lab Manager" }, "value" : "Lab Manager" },
470- {"text" : {"type" : "plain_text" , "text" : "Research Scientist" }, "value" : "Research Scientist" },
471- ],
472- },
473- "label" : {"type" : "plain_text" , "text" : "Your Role in the Lab" },
474- },
475- {
476- "type" : "input" ,
477- "block_id" : "grad_type_block" ,
478- "optional" : True ,
479- "element" : {
480- "type" : "static_select" ,
481- "action_id" : "grad_type_select" ,
482- "placeholder" : {"type" : "plain_text" , "text" : "Select program type" },
483- "options" : [
484- {"text" : {"type" : "plain_text" , "text" : "Doctoral" }, "value" : "Doctoral" },
485- {"text" : {"type" : "plain_text" , "text" : "Masters" }, "value" : "Masters" },
486- ],
487- },
488- "label" : {"type" : "plain_text" , "text" : "Graduate Program Type" },
489- "hint" : {"type" : "plain_text" , "text" : "Required for Graduate Students only" },
490- },
491- {
492- "type" : "input" ,
493- "block_id" : "grad_field_block" ,
494- "optional" : True ,
495- "element" : {
496- "type" : "plain_text_input" ,
497- "action_id" : "grad_field_input" ,
498- "placeholder" : {"type" : "plain_text" , "text" : "e.g., Quantitative Biomedical Sciences" },
499- },
500- "label" : {"type" : "plain_text" , "text" : "Masters Program/Field" },
501- "hint" : {"type" : "plain_text" , "text" : "Required for Masters students only" },
502- },
503- {
504- "type" : "input" ,
505- "block_id" : "github_block" ,
506- "element" : {
507- "type" : "plain_text_input" ,
508- "action_id" : "github_input" ,
509- "placeholder" : {
510- "type" : "plain_text" ,
511- "text" : "e.g., octocat" ,
512- },
513- },
514- "label" : {
515- "type" : "plain_text" ,
516- "text" : "GitHub Username" ,
517- },
518- "hint" : {
519- "type" : "plain_text" ,
520- "text" : "Your GitHub username (not email). We'll invite you to the ContextLab organization." ,
521- },
522- },
523- {
524- "type" : "input" ,
525- "block_id" : "bio_block" ,
526- "element" : {
527- "type" : "plain_text_input" ,
528- "action_id" : "bio_input" ,
529- "multiline" : True ,
530- "placeholder" : {
531- "type" : "plain_text" ,
532- "text" : "Tell us about yourself and your research interests..." ,
533- },
534- },
535- "label" : {
536- "type" : "plain_text" ,
537- "text" : "Short Bio" ,
538- },
539- "hint" : {
540- "type" : "plain_text" ,
541- "text" : "3-4 sentences about you. We'll edit it for style consistency." ,
542- },
543- },
544- {
545- "type" : "input" ,
546- "block_id" : "website_block" ,
547- "optional" : True ,
548- "element" : {
549- "type" : "plain_text_input" ,
550- "action_id" : "website_input" ,
551- "placeholder" : {
552- "type" : "plain_text" ,
553- "text" : "https://your-website.com" ,
554- },
555- },
556- "label" : {
557- "type" : "plain_text" ,
558- "text" : "Personal Website (optional)" ,
559- },
560- "hint" : {
561- "type" : "plain_text" ,
562- "text" : "If you have a personal website, we'll link to it from your profile." ,
563- },
564- },
565- {
566- "type" : "section" ,
567- "text" : {
568- "type" : "mrkdwn" ,
569- "text" : ":camera: *Photo:* After submitting this form, please upload a profile photo by sending it as a message in this conversation." ,
570- },
571- },
572- ],
638+ "blocks" : _build_form_blocks (),
573639 },
574640 )
575641 except SlackApiError as e :
0 commit comments