Skip to content

Commit 01e77fd

Browse files
🩹 [Patch]: Performance improvement by saving the GitHub App info in GitHubAppContext (#531)
## Description This pull request introduces several improvements for handling GitHub object construction and context management, focusing on increased robustness and compatibility with varied input shapes. The main changes include more flexible property assignment using null coalescing, refactoring constructors for consistency, and enhancing context propagation for GitHub Apps and Installations. ### Main change Remove API calls to get the Application for the authenticated app when getting app installations. Instead, we store the application info for the app in its app context, and transfer that to the app as we configure the app installations. In short, less API calls => better performance. ### Improved object construction and property assignment * All major classes (`GitHubApp`, `GitHubAppInstallation`, `GitHubOwner`, `GitHubUser`, `GitHubOrganization`, `GitHubEnterprise`, and `GitHubPlan`) now use null coalescing (`??`) for property assignment, allowing them to accept alternate property names and fallback values when constructing from different input shapes. This increases compatibility with API responses and internal/stored objects. * Refactored the `GitHubOrganization` constructor to use an `InitializeFromObject` method, improving code reuse and making it easier to support multiple initialization patterns. ### Improved handling of GitHub App and Installation context * The `GitHubAppContext` class now has a dedicated `$App` property, and its constructor initializes it when available, ensuring the authenticated app context is propagated correctly. * Functions that create `GitHubAppInstallation` objects now consistently pass the authenticated app from context (`$Context.App`), rather than fetching it separately, ensuring correct permission comparison and status setting. * The `GitHubAppInstallation` constructor and related logic now support both direct object assignment and fallback property mapping for the `App` and `Target` properties, increasing flexibility and correctness. ### Miscellaneous improvements * The organization retrieval function now constructs organization URLs directly from context, removing dependency on context objects and improving reliability. * Minor bug fix in `Remove-GitHubContext` for variable casing. * When setting context, the authenticated app is now stored in the context object for downstream use. ## Type of change <!-- Use the check-boxes [x] on the options that are relevant. --> - [ ] 📖 [Docs] - [ ] 🪲 [Fix] - [x] 🩹 [Patch] - [ ] ⚠️ [Security fix] - [ ] 🚀 [Feature] - [ ] 🌟 [Breaking change] ## Checklist <!-- Use the check-boxes [x] on the options that are relevant. --> - [x] I have performed a self-review of my own code - [x] I have commented my code, particularly in hard-to-understand areas
1 parent c851625 commit 01e77fd

13 files changed

+136
-101
lines changed

src/classes/public/App/GitHubApp.ps1

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -45,19 +45,19 @@
4545

4646
GitHubApp([object]$Object) {
4747
$this.ID = $Object.id
48-
$this.ClientID = $Object.client_id
48+
$this.ClientID = $Object.client_id ?? $Object.ClientID
4949
$this.Slug = $Object.app_slug ?? $Object.slug
50-
$this.NodeID = $Object.node_id
50+
$this.NodeID = $Object.node_id ?? $Object.NodeID
5151
$this.Owner = [GitHubOwner]::new($Object.owner)
5252
$this.Name = $Object.name
5353
$this.Description = $Object.description
54-
$this.ExternalUrl = $Object.external_url
55-
$this.Url = $Object.html_url
56-
$this.CreatedAt = $Object.created_at
57-
$this.UpdatedAt = $Object.updated_at
54+
$this.ExternalUrl = $Object.external_url ?? $Object.ExternalUrl
55+
$this.Url = $Object.html_url ?? $Object.Url
56+
$this.CreatedAt = $Object.created_at ?? $Object.createdAt
57+
$this.UpdatedAt = $Object.updated_at ?? $Object.updatedAt
5858
$this.Permissions = [GitHubPermission]::NewPermissionList($Object.permissions)
5959
$this.Events = , ($Object.events)
60-
$this.Installations = $Object.installations_count
60+
$this.Installations = $Object.installations_count ?? $Object.Installations
6161
}
6262

6363
[string] ToString() {

src/classes/public/App/GitHubAppInstallation.ps1

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -48,13 +48,21 @@
4848

4949
GitHubAppInstallation([PSCustomObject] $Object) {
5050
$this.ID = $Object.id
51-
$this.App = [GitHubApp]::new(
52-
[PSCustomObject]@{
53-
client_id = $Object.client_id
54-
app_slug = $Object.app_slug
51+
$this.App = if ($null -ne $Object.App) {
52+
$Object.App
53+
} else {
54+
[GitHubApp]@{
55+
ClientID = $Object.client_id
56+
Slug = $Object.app_slug
5557
}
56-
)
57-
$this.Target = [GitHubOwner]::new($Object.account)
58+
}
59+
$this.Target = if ($null -ne $Object.Target) {
60+
[GitHubOwner]::new($Object.Target)
61+
} elseif ($null -ne $Object.Account) {
62+
[GitHubOwner]::new($Object.Account)
63+
} else {
64+
$null
65+
}
5866
$this.Type = $Object.target_type
5967
$this.RepositorySelection = $Object.repository_selection
6068
$this.Permissions = [GitHubPermission]::NewPermissionList($Object.permissions, $this.Type)
@@ -65,7 +73,7 @@
6573
$this.SuspendedAt = $Object.suspended_at
6674
$this.SuspendedBy = [GitHubUser]::new($Object.suspended_by)
6775
$this.Url = $Object.html_url
68-
$this.Status = 'Unknown'
76+
$this.Status = $Object.Status ?? 'Unknown'
6977
}
7078

7179
GitHubAppInstallation([PSCustomObject] $Object, [GitHubApp] $App) {
@@ -87,12 +95,10 @@
8795

8896
GitHubAppInstallation([PSCustomObject] $Object, [string] $Target, [string] $Type, [string] $HostName) {
8997
$this.ID = $Object.id
90-
$this.App = [GitHubApp]::new(
91-
[PSCustomObject]@{
92-
client_id = $Object.client_id
93-
app_slug = $Object.app_slug
94-
}
95-
)
98+
$this.App = [GitHubApp]@{
99+
ClientID = $Object.client_id
100+
Slug = $Object.app_slug
101+
}
96102
$this.Target = [GitHubOwner]@{
97103
Name = $Target
98104
Type = $Type
@@ -111,7 +117,7 @@
111117
$this.Status = 'Unknown'
112118
}
113119

114-
# Updates the Status property by comparing installation permissions with app permissions
120+
# Sets the Status property by comparing installation permissions with app permissions
115121
# filtered by the appropriate scope based on installation type
116122
[void] SetStatus() {
117123
if (-not $this.App -or -not $this.App.Permissions) {

src/classes/public/Context/GitHubContext/GitHubAppContext.ps1

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
class GitHubAppContext : GitHubContext {
2+
# The App that this context represents.
3+
[GitHubApp] $App
4+
25
# Client ID for GitHub Apps
36
[string] $ClientID
47

@@ -51,5 +54,8 @@
5154
$this.Permissions = [GitHubPermission]::NewPermissionList($Object.Permissions)
5255
}
5356
$this.Events = , ($Object.Events)
57+
if ($Object.App) {
58+
$this.App = [GitHubApp]::New($Object.App)
59+
}
5460
}
5561
}

src/classes/public/GitHubPlan.ps1

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,11 @@
2727

2828
GitHubPlan([PSCustomObject]$Object) {
2929
$this.Name = $Object.name
30-
$this.PrivateRepos = $Object.private_repos
30+
$this.PrivateRepos = $Object.private_repos ?? $Object.PrivateRepos
3131
$this.Collaborators = $Object.collaborators
3232
$this.Space = $Object.space
3333
$this.Seats = $Object.seats
34-
$this.FilledSeats = $Object.filled_seats
34+
$this.FilledSeats = $Object.filled_seats ?? $Object.FilledSeats
3535
}
3636

3737
[string] ToString() {

src/classes/public/Owner/GitHubOwner.ps1

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -43,20 +43,20 @@
4343

4444
GitHubOwner([PSCustomObject]$Object) {
4545
# From GitHubNode
46-
$this.ID = $Object.id
47-
$this.NodeID = $Object.node_id
46+
$this.ID = $Object.databaseId ?? $Object.id
47+
$this.NodeID = $Object.node_id ?? $Object.NodeID ?? $Object.id
4848

4949
# From GitHubOwner
50-
$this.Name = $Object.slug ?? $Object.login
51-
$this.DisplayName = $Object.name
52-
$this.AvatarUrl = $Object.avatar_url
50+
$this.Name = $Object.slug ?? $Object.login ?? $Object.name
51+
$this.DisplayName = $Object.DisplayName ?? $Object.name
52+
$this.AvatarUrl = $Object.avatar_url ?? $Object.AvatarUrl
5353
$this.Url = $Object.html_url ?? $Object.url
5454
$this.Type = $Object.type
5555
$this.Location = $Object.location
5656
$this.Description = $Object.description ?? $Object.bio
57-
$this.Website = $Object.websiteUrl ?? $Object.blog
58-
$this.CreatedAt = $Object.created_at
59-
$this.UpdatedAt = $Object.updated_at
57+
$this.Website = $Object.websiteUrl ?? $Object.blog ?? $Object.Website
58+
$this.CreatedAt = $Object.created_at ?? $Object.createdAt
59+
$this.UpdatedAt = $Object.updated_at ?? $Object.updatedAt
6060
}
6161

6262
[string] ToString() {

src/classes/public/Owner/GitHubOwner/GitHubEnterprise.ps1

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,18 +27,18 @@
2727

2828
GitHubEnterprise([PSCustomObject] $Object) {
2929
# From GitHubNode
30-
$this.ID = $Object.databaseId
31-
$this.NodeID = $Object.id
30+
$this.ID = $Object.databaseId ?? $Object.id
31+
$this.NodeID = $Object.node_id ?? $Object.NodeID ?? $Object.id
3232

3333
# From GitHubOwner
34-
$this.Name = $Object.slug
35-
$this.DisplayName = $Object.name
34+
$this.Name = $Object.slug ?? $Object.Name
35+
$this.DisplayName = $Object.name ?? $Object.DisplayName
3636
$this.AvatarUrl = $Object.avatarUrl
3737
$this.Url = $Object.url
3838
$this.Type = $Object.type ?? 'Enterprise'
3939
$this.Location = $Object.location
4040
$this.Description = $Object.description
41-
$this.Website = $Object.websiteUrl
41+
$this.Website = $Object.websiteUrl ?? $Object.Website
4242
$this.CreatedAt = $Object.createdAt
4343
$this.UpdatedAt = $Object.updatedAt
4444

src/classes/public/Owner/GitHubOwner/GitHubOrganization.ps1

Lines changed: 57 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -181,16 +181,25 @@
181181

182182
GitHubOrganization() {}
183183

184+
GitHubOrganization([PSCustomObject] $Object) {
185+
$this.InitializeFromObject($Object)
186+
}
187+
184188
GitHubOrganization([PSCustomObject] $Object, [GitHubContext] $Context) {
189+
$this.InitializeFromObject($Object)
190+
$this.Url = "https://$($Context.HostName)/$($Object.login)"
191+
}
192+
193+
hidden [void] InitializeFromObject([PSCustomObject] $Object) {
185194
# From GitHubNode
186195
$this.ID = $Object.databaseId ?? $Object.id
187-
$this.NodeID = $Object.node_id ?? $Object.id
196+
$this.NodeID = $Object.node_id ?? $Object.NodeID ?? $Object.id
188197

189198
# From GitHubOwner
190-
$this.Name = $Object.login
191-
$this.DisplayName = $Object.name
199+
$this.Name = $Object.login ?? $Object.Name
200+
$this.DisplayName = $Object.name ?? $Object.DisplayName
192201
$this.AvatarUrl = $Object.avatar_url ?? $Object.avatarUrl
193-
$this.Url = $Object.html_url ?? $Object.url ?? "https://$($Context.HostName)/$($Object.login)"
202+
$this.Url = $Object.html_url ?? $Object.url
194203
$this.Type = $Object.type ?? 'Organization'
195204
$this.Location = $Object.location
196205
$this.Description = $Object.description
@@ -202,44 +211,56 @@
202211
$this.Email = $Object.email
203212
$this.TwitterUsername = $Object.twitter_username ?? $Object.twitterUsername
204213
$this.Plan = [GitHubPlan]::New($Object.plan)
205-
$this.PublicRepos = $Object.public_repos
206-
$this.PublicGists = $Object.public_gists
214+
$this.PublicRepos = $Object.public_repos ?? $Object.PublicRepos
215+
$this.PublicGists = $Object.public_gists ?? $Object.PublicGists
207216
$this.Followers = $Object.followers
208217
$this.Following = $Object.following
209-
$this.PrivateGists = $Object.total_private_gists
210-
$this.TotalPrivateRepos = $Object.total_private_repos
211-
$this.OwnedPrivateRepos = $Object.owned_private_repos
212-
if ($null -ne $Object.disk_usage) {
213-
$this.Size = [uint64]($Object.disk_usage * 1KB)
218+
$this.PrivateGists = $Object.total_private_gists ?? $Object.PrivateGists
219+
$this.TotalPrivateRepos = $Object.total_private_repos ?? $Object.TotalPrivateRepos
220+
$this.OwnedPrivateRepos = $Object.owned_private_repos ?? $Object.OwnedPrivateRepos
221+
$this.Size = if ($null -ne $Object.disk_usage) {
222+
[uint64]($Object.disk_usage * 1KB)
223+
} else {
224+
$Object.Size
214225
}
215226
$this.Collaborators = $Object.collaborators
216227
$this.IsVerified = $Object.is_verified ?? $Object.isVerified
217-
$this.HasOrganizationProjects = $Object.has_organization_projects
218-
$this.HasRepositoryProjects = $Object.has_repository_projects
219-
$this.BillingEmail = $Object.billing_email
220-
$this.DefaultRepositoryPermission = $Object.default_repository_permission
221-
$this.MembersCanCreateRepositories = $Object.members_can_create_repositories
222-
$this.RequiresTwoFactorAuthentication = $Object.two_factor_requirement_enabled ?? $Object.requiresTwoFactorAuthentication
223-
$this.MembersAllowedRepositoryCreationType = $Object.members_allowed_repository_creation_type
224-
$this.MembersCanCreatePublicRepositories = $Object.members_can_create_public_repositories
225-
$this.MembersCanCreatePrivateRepositories = $Object.members_can_create_private_repositories
226-
$this.MembersCanCreateInternalRepositories = $Object.members_can_create_internal_repositories
227-
$this.MembersCanInviteCollaborators = $Object.members_can_invite_collaborators
228-
$this.MembersCanCreatePages = $Object.members_can_create_pages
228+
$this.HasOrganizationProjects = $Object.has_organization_projects ?? $Object.HasOrganizationProjects
229+
$this.HasRepositoryProjects = $Object.has_repository_projects ?? $Object.HasRepositoryProjects
230+
$this.BillingEmail = $Object.billing_email ?? $Object.BillingEmail
231+
$this.DefaultRepositoryPermission = $Object.default_repository_permission ?? $Object.DefaultRepositoryPermission
232+
$this.MembersCanCreateRepositories = $Object.members_can_create_repositories ?? $Object.MembersCanCreateRepositories
233+
$this.RequiresTwoFactorAuthentication = $Object.two_factor_requirement_enabled ?? $Object.requiresTwoFactorAuthentication ??
234+
$Object.RequiresTwoFactorAuthentication
235+
$this.MembersAllowedRepositoryCreationType = $Object.members_allowed_repository_creation_type ?? $Object.MembersAllowedRepositoryCreationType
236+
$this.MembersCanCreatePublicRepositories = $Object.members_can_create_public_repositories ?? $Object.MembersCanCreatePublicRepositories
237+
$this.MembersCanCreatePrivateRepositories = $Object.members_can_create_private_repositories ?? $Object.MembersCanCreatePrivateRepositories
238+
$this.MembersCanCreateInternalRepositories = $Object.members_can_create_internal_repositories ?? $Object.MembersCanCreateInternalRepositories
239+
$this.MembersCanInviteCollaborators = $Object.members_can_invite_collaborators ?? $Object.MembersCanInviteCollaborators
240+
$this.MembersCanCreatePages = $Object.members_can_create_pages ?? $Object.MembersCanCreatePages
229241
$this.MembersCanForkPrivateRepositories = $Object.members_can_fork_private_repositories ?? $Object.membersCanForkPrivateRepositories
230-
$this.RequireWebCommitSignoff = $Object.web_commit_signoff_required ?? $Object.webCommitSignoffRequired
231-
$this.DeployKeysEnabledForRepositories = $Object.deploy_keys_enabled_for_repositories
232-
$this.MembersCanCreatePublicPages = $Object.members_can_create_public_pages
233-
$this.MembersCanCreatePrivatePages = $Object.members_can_create_private_pages
234-
$this.AdvancedSecurityEnabledForNewRepositories = $Object.advanced_security_enabled_for_new_repositories
235-
$this.DependabotAlertsEnabledForNewRepositories = $Object.dependabot_alerts_enabled_for_new_repositories
236-
$this.DependabotSecurityUpdatesEnabledForNewRepositories = $Object.dependabot_security_updates_enabled_for_new_repositories
237-
$this.DependencyGraphEnabledForNewRepositories = $Object.dependency_graph_enabled_for_new_repositories
238-
$this.SecretScanningEnabledForNewRepositories = $Object.secret_scanning_enabled_for_new_repositories
239-
$this.SecretScanningPushProtectionEnabledForNewRepositories = $Object.secret_scanning_push_protection_enabled_for_new_repositories
240-
$this.SecretScanningPushProtectionCustomLinkEnabled = $Object.secret_scanning_push_protection_custom_link_enabled
241-
$this.SecretScanningPushProtectionCustomLink = $Object.secret_scanning_push_protection_custom_link
242-
$this.SecretScanningValidityChecksEnabled = $Object.secret_scanning_validity_checks_enabled
242+
$this.RequireWebCommitSignoff = $Object.web_commit_signoff_required ?? $Object.webCommitSignoffRequired ?? $Object.RequireWebCommitSignoff
243+
$this.DeployKeysEnabledForRepositories = $Object.deploy_keys_enabled_for_repositories ?? $Object.deployKeysEnabledForRepositories
244+
$this.MembersCanCreatePublicPages = $Object.members_can_create_public_pages ?? $Object.MembersCanCreatePublicPages
245+
$this.MembersCanCreatePrivatePages = $Object.members_can_create_private_pages ?? $Object.MembersCanCreatePrivatePages
246+
$this.AdvancedSecurityEnabledForNewRepositories = $Object.advanced_security_enabled_for_new_repositories ??
247+
$Object.advancedSecurityEnabledForNewRepositories
248+
$this.DependabotAlertsEnabledForNewRepositories = $Object.dependabot_alerts_enabled_for_new_repositories ??
249+
$Object.dependabotAlertsEnabledForNewRepositories
250+
$this.DependabotSecurityUpdatesEnabledForNewRepositories = $Object.dependabot_security_updates_enabled_for_new_repositories ??
251+
$Object.dependabotSecurityUpdatesEnabledForNewRepositories
252+
$this.DependencyGraphEnabledForNewRepositories = $Object.dependency_graph_enabled_for_new_repositories ??
253+
$Object.dependencyGraphEnabledForNewRepositories
254+
$this.SecretScanningEnabledForNewRepositories = $Object.secret_scanning_enabled_for_new_repositories ??
255+
$Object.secretScanningEnabledForNewRepositories
256+
$this.SecretScanningPushProtectionEnabledForNewRepositories = $Object.secret_scanning_push_protection_enabled_for_new_repositories ??
257+
$Object.secretScanningPushProtectionEnabledForNewRepositories
258+
$this.SecretScanningPushProtectionCustomLinkEnabled = $Object.secret_scanning_push_protection_custom_link_enabled ??
259+
$Object.secretScanningPushProtectionCustomLinkEnabled
260+
$this.SecretScanningPushProtectionCustomLink = $Object.secret_scanning_push_protection_custom_link ??
261+
$Object.secretScanningPushProtectionCustomLink
262+
$this.SecretScanningValidityChecksEnabled = $Object.secret_scanning_validity_checks_enabled ??
263+
$Object.secretScanningValidityChecksEnabled
243264
$this.ArchivedAt = $Object.archived_at ?? $Object.archivedAt
244265
}
245266

src/classes/public/Owner/GitHubOwner/GitHubUser.ps1

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -42,31 +42,31 @@
4242

4343
GitHubUser([PSCustomObject]$Object) {
4444
# From GitHubNode
45-
$this.ID = $Object.id
46-
$this.NodeID = $Object.node_id
45+
$this.ID = $Object.databaseId ?? $Object.id
46+
$this.NodeID = $Object.node_id ?? $Object.NodeID ?? $Object.id
4747

4848
# From GitHubOwner
49-
$this.Name = $Object.login
50-
$this.DisplayName = $Object.name
51-
$this.AvatarUrl = $Object.avatar_url
52-
$this.Url = $Object.html_url
49+
$this.Name = $Object.login ?? $Object.Name
50+
$this.DisplayName = $Object.name ?? $Object.DisplayName
51+
$this.AvatarUrl = $Object.avatar_url ?? $Object.AvatarUrl
52+
$this.Url = $Object.html_url ?? $Object.Url
5353
$this.Type = $Object.type
5454
$this.Location = $Object.location
55-
$this.Description = $Object.bio
56-
$this.Website = $Object.blog
57-
$this.CreatedAt = $Object.created_at
58-
$this.UpdatedAt = $Object.updated_at
55+
$this.Description = $Object.bio ?? $Object.Description
56+
$this.Website = $Object.blog ?? $Object.Website
57+
$this.CreatedAt = $Object.created_at ?? $Object.CreatedAt
58+
$this.UpdatedAt = $Object.updated_at ?? $Object.UpdatedAt
5959

6060
# From GitHubUser
6161
$this.Email = $Object.email
6262
$this.Hireable = $Object.hireable
6363
$this.Company = $Object.company
64-
$this.TwitterUsername = $Object.twitter_username
65-
$this.PublicRepos = $Object.public_repos
66-
$this.PublicGists = $Object.public_gists
64+
$this.TwitterUsername = $Object.twitter_username ?? $Object.TwitterUsername
65+
$this.PublicRepos = $Object.public_repos ?? $Object.PublicRepos
66+
$this.PublicGists = $Object.public_gists ?? $Object.PublicGists
6767
$this.Followers = $Object.followers
6868
$this.Following = $Object.following
69-
$this.NotificationEmail = $Object.notification_email
69+
$this.NotificationEmail = $Object.notification_email ?? $Object.NotificationEmail
7070
$this.Plan = [GitHubPlan]::New($Object.plan)
7171
}
7272

src/functions/private/Apps/GitHub Apps/Get-GitHubAppInstallationForAuthenticatedAppAsList.ps1

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,9 @@
4646
Context = $Context
4747
}
4848

49-
# Get the authenticated app to compare permissions and events
50-
$authenticatedApp = Get-GitHubAppAsAuthenticatedApp -Context $Context
51-
5249
Invoke-GitHubAPI @apiParams | ForEach-Object {
5350
foreach ($installation in $_.Response) {
54-
[GitHubAppInstallation]::new($installation, $authenticatedApp)
51+
[GitHubAppInstallation]::new($installation, $Context.App)
5552
}
5653
}
5754
}

src/functions/private/Apps/GitHub Apps/Get-GitHubAppInstallationForAuthenticatedAppByID.ps1

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,8 @@
4545
Context = $Context
4646
}
4747

48-
# Get the authenticated app to compare permissions and events
49-
$authenticatedApp = Get-GitHubAppAsAuthenticatedApp -Context $Context
50-
5148
Invoke-GitHubAPI @apiParams | ForEach-Object {
52-
[GitHubAppInstallation]::new($_.Response, $authenticatedApp)
49+
[GitHubAppInstallation]::new($_.Response, $Context.App)
5350
}
5451
}
5552

0 commit comments

Comments
 (0)