-
Notifications
You must be signed in to change notification settings - Fork 221
Optimize notification configuration to reduce Azure DevOps API calls #13031
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
- Add subscription cache to avoid redundant GetSubscriptionsAsync calls - Add Distinct() filter for contacts before descriptor resolution - Filter out null/empty contacts early in processing - Cache null descriptor results to prevent re-requests - Add early equality check to skip processing when no changes needed - Filter null descriptors before set operations - Add GetMemberDescriptorsAsync batch method to AzureDevOpsService - Add ResolveContactDescriptorsAsync helper method - Add GetTeamMemberDescriptorsAsync helper with batch fallback - Improve dry-run behavior logging (show "would" actions) - Add aggregate logging for add/remove operations with counts - Add TODO comments for future batch optimization methods - Invalidate subscription cache after create/update operations Co-authored-by: weshaggard <[email protected]>
…hod logic - Move teamDescriptor fetch and logging inside the changes conditional - Improve batch method fallback to only trigger on error (null), not empty results - Add clarifying comments about batch method design as future optimization hook Co-authored-by: weshaggard <[email protected]>
| // A cache on the team member to member descriptor. | ||
| private readonly Dictionary<string, string> teamMemberCache = new Dictionary<string, string>(); | ||
| // A cache on team subscriptions to avoid redundant API calls | ||
| private readonly Dictionary<Guid, IEnumerable<NotificationSubscription>> subscriptionsCache = new Dictionary<Guid, IEnumerable<NotificationSubscription>>(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@copilot I don't think this cache is interesting because we will not be querying the subscriptions more than once for a given team.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're correct. Since each pipeline has a unique team (team name includes pipeline.Id) and EnsureScheduledBuildFailSubscriptionExists is called only once per team, the cache provides no benefit. Removed in 32234e5.
…back - Change return type from Enumerable.Empty<string>() to null on error - Update method documentation to clarify null return indicates error - Ensures fallback logic in NotificationConfigurator works correctly Co-authored-by: weshaggard <[email protected]>
Each pipeline has a unique team (team name includes pipeline.Id), and EnsureScheduledBuildFailSubscriptionExists is called only once per team, making the subscription cache unnecessary. Co-authored-by: weshaggard <[email protected]>
| return null; | ||
| } | ||
|
|
||
| // TODO: Future batch optimization method |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@copilot Remove any added TODO code that isn't being used.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed all unused TODO code including commented-out batch method implementations in GitHubToAADConverter and AzureDevOpsService, and TODO comments in NotificationConfigurator. Changes in 95e6c34.
| } | ||
| else | ||
| { | ||
| // Fallback to individual calls with caching only on error |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@copilot remove this fallback it just complicates the code if the batch descriptor calls actually work in most contexts.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed the fallback logic and simplified GetTeamMemberDescriptorsAsync to directly use the batch method. Also removed the unused teamMemberCache. Changes in b991644.
|
|
||
| logger.LogInformation("Creating Subscription PipelineId = {0}, TeamId = {1}", pipeline.Id, team.Id); | ||
|
|
||
| // F. Dry-Run Behavior: Skip external operations when not persisting |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@copilot remove these comments as they don't provide any real value.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed all lettered comments (A., B., C., E., F.) that were prefixing code sections. Changes in b991644.
Removed commented-out batch method implementations and TODO comments that were not being actively used in the codebase. Co-authored-by: weshaggard <[email protected]>
- Remove fallback to individual GetUserFromId calls in GetTeamMemberDescriptorsAsync - Remove unused teamMemberCache since fallback was removed - Remove lettered comments (A., B., C., E., F.) that don't provide real value - Simplify GetTeamMemberDescriptorsAsync to just call batch method directly Co-authored-by: weshaggard <[email protected]>
| { | ||
| try | ||
| { | ||
| var identity = await identityClient.ReadIdentityAsync(new Guid(member.Identity.Id)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@copilot why is this more efficient the original GetUserFromId? It seems like we are still making an api call per member to get the descriptor. If so please revert this and go back to what were doing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're correct - GetMemberDescriptorsAsync provided no optimization as it still made one ReadIdentityAsync call per member. Reverted to the original GetUserFromId approach with teamMemberCache in 1719bc1.
| } | ||
| if (result != default) | ||
| { | ||
| await EnsureScheduledBuildFailSubscriptionExists(pipeline, result, persistChanges); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@copilot please only call this schedule update function when we are adding or updating a new team.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Modified to only call EnsureScheduledBuildFailSubscriptionExists when a team is created or its description is updated. This avoids unnecessary subscription checks for existing, unchanged teams. Changes in 82eb63d.
The GetMemberDescriptorsAsync method didn't provide any optimization as it still made one API call per member (ReadIdentityAsync). Reverted to the original implementation that uses GetUserFromId with teamMemberCache for per-member caching. This maintains the same performance characteristics while being simpler. Co-authored-by: weshaggard <[email protected]>
…ated or updated Modified EnsureTeamExists to track whether a team was created or updated, and only call EnsureScheduledBuildFailSubscriptionExists in those cases. This avoids unnecessary subscription checks for teams that already exist and haven't changed. Co-authored-by: weshaggard <[email protected]>
| }; | ||
|
|
||
| logger.LogInformation("Creating Subscription PipelineId = {0}, TeamId = {1}", pipeline.Id, team.Id); | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
|
|
||
| if (contactsSet.SetEquals(teamSet)) | ||
| { | ||
| logger.LogInformation("Team membership is already synchronized. No changes needed."); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| logger.LogInformation("Team membership is already synchronized. No changes needed."); |
| if (contactsToRemove.Any()) | ||
| { | ||
| logger.LogInformation("Removing {count} contact(s) from team", contactsToRemove.Count); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| if (contactsToRemove.Any()) | |
| { | |
| logger.LogInformation("Removing {count} contact(s) from team", contactsToRemove.Count); | |
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@copilot remove this extra logging you added similarly for the add code path
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed the aggregate logging for both remove and add operations, and the team sync status message. Changes in 9f9b142.
| else | ||
| { | ||
| logger.LogInformation("Would delete Contact TeamDescriptor = {0}, ContactDescriptor = {1}", teamDescriptor, descriptor); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| else | |
| { | |
| logger.LogInformation("Would delete Contact TeamDescriptor = {0}, ContactDescriptor = {1}", teamDescriptor, descriptor); | |
| } |
Removed aggregate logging for add/remove operations and team sync status messages to reduce log noise. Co-authored-by: weshaggard <[email protected]>
✅ Completed: Notification Configuration Workflow Optimizations
Successfully implemented optimizations to reduce unnecessary Azure DevOps API calls and help mitigate rate limiting:
A. Contacts Processing Optimization
B. Team Member Descriptor Retrieval
C. Set Delta Optimization
D. Subscription Management
E. Logging
F. Dry-Run Behavior Enhancement
Build and test changes - ✅ Build succeeds with no warnings
Address code review feedback - ✅ All feedback addressed
Security scan - CodeQL timed out (common for large repos)
Key Changes Made
Files Modified:
Optimizations Summary:
Original prompt
Implement optimizations in the notification configuration workflow to reduce unnecessary Azure DevOps API calls and help mitigate rate limiting.
Target area:
File: tools/notification-configuration/notification-creator/NotificationConfigurator.cs
Current permalink: https://github.com/Azure/azure-sdk-tools/blob/1978e769de8fa4b8273ec6a66748cf9190f7c002/tools/notification-configuration/notification-creator/NotificationConfigurator.cs
Goals:
Required Changes:
A. Contacts Processing
B. Team Member Descriptor Retrieval
C. Set Delta Optimization
D. Subscription Retrieval Optimization
E. Logging Adjustments
F. Dry-Run Behavior (persistChanges == false)
G. Future Batching Hooks
Implementation Steps:
Task<IEnumerable> GetMemberDescriptorsAsync(WebApiTeam team)
Acceptance Criteria:
Out of Scope (documentation only):
Please implement the refactor and create the new or u...
This pull request was created as a result of the following prompt from Copilot chat.
✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.