@@ -101,7 +101,13 @@ export async function getOrgInfo({
101101 name : x . name ,
102102 username : x . username ,
103103 title : x . title ,
104- } ) as { name : string ; username : string ; title : string | undefined } ,
104+ nonVotingMember : x . nonVotingMember || false ,
105+ } ) as {
106+ name : string ;
107+ username : string ;
108+ title : string | undefined ;
109+ nonVotingMember : boolean ;
110+ } ,
105111 ) ;
106112 response = { ...response , leads : unmarshalledLeads } ;
107113 }
@@ -525,42 +531,106 @@ export const removeLead = async ({
525531} ;
526532
527533/**
528- * Returns the Microsoft 365 Dynamic User query to return all members of all lead groups .
529- * Currently used to setup the Exec member list .
534+ * Returns all voting org leads across all organizations .
535+ * Uses consistent reads to avoid eventual consistency issues .
530536 * @param dynamoClient A DynamoDB client.
531- * @param includeGroupIds Used to ensure that a specific group ID is included (Scan could be eventually consistent.)
537+ * @param logger A logger instance.
532538 */
533- export async function getLeadsM365DynamicQuery ( {
539+ export async function getAllVotingLeads ( {
534540 dynamoClient,
535- includeGroupIds ,
541+ logger ,
536542} : {
537543 dynamoClient : DynamoDBClient ;
538- includeGroupIds ?: string [ ] ;
539- } ) : Promise < string | null > {
540- const command = new ScanCommand ( {
541- TableName : genericConfig . SigInfoTableName ,
542- IndexName : "LeadsGroupIdIndex" ,
544+ logger : ValidLoggers ;
545+ } ) : Promise <
546+ Array < { username : string ; org : string ; name : string ; title : string } >
547+ > {
548+ // Query all organizations in parallel for better performance
549+ const queryPromises = AllOrganizationNameList . map ( async ( orgName ) => {
550+ const leadsQuery = new QueryCommand ( {
551+ TableName : genericConfig . SigInfoTableName ,
552+ KeyConditionExpression : "primaryKey = :leadName" ,
553+ ExpressionAttributeValues : {
554+ ":leadName" : { S : `LEAD#${ orgName } ` } ,
555+ } ,
556+ ConsistentRead : true ,
557+ } ) ;
558+
559+ try {
560+ const responseMarshall = await dynamoClient . send ( leadsQuery ) ;
561+ if ( responseMarshall . Items ) {
562+ return responseMarshall . Items . map ( ( x ) => unmarshall ( x ) )
563+ . filter ( ( x ) => x . username && ! x . nonVotingMember )
564+ . map ( ( x ) => ( {
565+ username : x . username as string ,
566+ org : orgName ,
567+ name : x . name as string ,
568+ title : x . title as string ,
569+ } ) ) ;
570+ }
571+ return [ ] ;
572+ } catch ( e ) {
573+ if ( e instanceof BaseError ) {
574+ throw e ;
575+ }
576+ logger . error ( e ) ;
577+ throw new DatabaseFetchError ( {
578+ message : `Failed to get leads for org ${ orgName } .` ,
579+ } ) ;
580+ }
543581 } ) ;
544- const results = await dynamoClient . send ( command ) ;
545- if ( ! results || ! results . Items || results . Items . length === 0 ) {
546- return null ;
547- }
548- const entries = results . Items . map ( ( x ) => unmarshall ( x ) ) as {
549- primaryKey : string ;
550- leadsEntraGroupId : string ;
551- } [ ] ;
552- const groupIds = entries
553- . filter ( ( x ) => x . primaryKey . startsWith ( "DEFINE#" ) )
554- . map ( ( x ) => x . leadsEntraGroupId ) ;
555-
556- if ( groupIds . length === 0 ) {
557- return null ;
582+
583+ const results = await Promise . all ( queryPromises ) ;
584+ return results . flat ( ) ;
585+ }
586+
587+ /**
588+ * Checks if a user should remain in exec council by verifying they are a voting lead of at least one org.
589+ * Uses consistent reads to avoid eventual consistency issues.
590+ * @param username The username to check.
591+ * @param dynamoClient A DynamoDB client.
592+ * @param logger A logger instance.
593+ */
594+ export async function shouldBeInExecCouncil ( {
595+ username,
596+ dynamoClient,
597+ logger,
598+ } : {
599+ username : string ;
600+ dynamoClient : DynamoDBClient ;
601+ logger : ValidLoggers ;
602+ } ) : Promise < boolean > {
603+ // Query all orgs to see if this user is a voting lead of any org
604+ for ( const orgName of AllOrganizationNameList ) {
605+ const leadsQuery = new QueryCommand ( {
606+ TableName : genericConfig . SigInfoTableName ,
607+ KeyConditionExpression : "primaryKey = :leadName AND entryId = :username" ,
608+ ExpressionAttributeValues : {
609+ ":leadName" : { S : `LEAD#${ orgName } ` } ,
610+ ":username" : { S : username } ,
611+ } ,
612+ ConsistentRead : true ,
613+ } ) ;
614+
615+ try {
616+ const responseMarshall = await dynamoClient . send ( leadsQuery ) ;
617+ if ( responseMarshall . Items && responseMarshall . Items . length > 0 ) {
618+ const lead = unmarshall ( responseMarshall . Items [ 0 ] ) ;
619+ // If they're a lead and not a non-voting member, they should be in exec
620+ if ( ! lead . nonVotingMember ) {
621+ return true ;
622+ }
623+ }
624+ } catch ( e ) {
625+ if ( e instanceof BaseError ) {
626+ throw e ;
627+ }
628+ logger . error ( e ) ;
629+ throw new DatabaseFetchError ( {
630+ message : `Failed to check lead status for ${ username } in org ${ orgName } .` ,
631+ } ) ;
632+ }
558633 }
559634
560- const formattedGroupIds = [
561- ...new Set ( [ ...( includeGroupIds || [ ] ) , ...groupIds ] ) ,
562- ]
563- . map ( ( id ) => `'${ id } '` )
564- . join ( ", " ) ;
565- return `user.memberOf -any (group.objectId -in [${ formattedGroupIds } ])` ;
635+ return false ;
566636}
0 commit comments