Skip to content

Commit e7667a4

Browse files
fix(backend): instrument all Bitbucket API calls with fetchWithRetry
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 35d7f61 commit e7667a4

File tree

1 file changed

+44
-35
lines changed

1 file changed

+44
-35
lines changed

packages/backend/src/bitbucket.ts

Lines changed: 44 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -248,26 +248,29 @@ async function cloudGetReposForProjects(client: BitbucketClient, projects: strin
248248

249249
logger.debug(`Fetching all repos for project ${project} for workspace ${workspace}...`);
250250
try {
251-
const repos = await getPaginatedCloud<CloudRepository>(`/repositories/${workspace}` as CloudGetRequestPath, async (path, query) => {
252-
const response = await client.apiClient.GET(path, {
253-
params: {
254-
path: {
255-
workspace,
256-
},
257-
query: {
258-
...query,
259-
q: `project.key="${project_name}"`
251+
const { durationMs, data: repos } = await measure(async () => {
252+
const fetchFn = () => getPaginatedCloud<CloudRepository>(`/repositories/${workspace}` as CloudGetRequestPath, async (path, query) => {
253+
const response = await client.apiClient.GET(path, {
254+
params: {
255+
path: {
256+
workspace,
257+
},
258+
query: {
259+
...query,
260+
q: `project.key="${project_name}"`
261+
}
260262
}
263+
});
264+
const { data, error } = response;
265+
if (error) {
266+
throw new Error (`Failed to fetch projects for workspace ${workspace}: ${error.type}`);
261267
}
268+
return data;
262269
});
263-
const { data, error } = response;
264-
if (error) {
265-
throw new Error (`Failed to fetch projects for workspace ${workspace}: ${error.type}`);
266-
}
267-
return data;
270+
return fetchWithRetry(fetchFn, `project ${project_name} in workspace ${workspace}`, logger);
268271
});
269272

270-
logger.debug(`Found ${repos.length} repos for project ${project_name} for workspace ${workspace}.`);
273+
logger.debug(`Found ${repos.length} repos for project ${project_name} for workspace ${workspace} in ${durationMs}ms.`);
271274
return {
272275
type: 'valid' as const,
273276
data: repos
@@ -312,11 +315,14 @@ async function cloudGetRepos(client: BitbucketClient, repoList: string[]): Promi
312315
logger.debug(`Fetching repo ${repo_slug} for workspace ${workspace}...`);
313316
try {
314317
const path = `/repositories/${workspace}/${repo_slug}` as CloudGetRequestPath;
315-
const response = await client.apiClient.GET(path);
316-
const { data, error } = response;
317-
if (error) {
318-
throw new Error(`Failed to fetch repo ${repo}: ${error.type}`);
319-
}
318+
const data = await fetchWithRetry(async () => {
319+
const response = await client.apiClient.GET(path);
320+
const { data, error } = response;
321+
if (error) {
322+
throw new Error(`Failed to fetch repo ${repo}: ${error.type}`);
323+
}
324+
return data;
325+
}, `repo ${repo}`, logger);
320326
return {
321327
type: 'valid' as const,
322328
data: [data]
@@ -520,11 +526,14 @@ async function serverGetRepos(client: BitbucketClient, repoList: string[]): Prom
520526
logger.debug(`Fetching repo ${repo_slug} for project ${project}...`);
521527
try {
522528
const path = `/rest/api/1.0/projects/${project}/repos/${repo_slug}` as ServerGetRequestPath;
523-
const response = await client.apiClient.GET(path);
524-
const { data, error } = response;
525-
if (error) {
526-
throw new Error(`Failed to fetch repo ${repo}: ${error.type}`);
527-
}
529+
const data = await fetchWithRetry(async () => {
530+
const response = await client.apiClient.GET(path);
531+
const { data, error } = response;
532+
if (error) {
533+
throw new Error(`Failed to fetch repo ${repo}: ${error.type}`);
534+
}
535+
return data;
536+
}, `repo ${repo}`, logger);
528537
return {
529538
type: 'valid' as const,
530539
data: [data]
@@ -609,7 +618,7 @@ export const getExplicitUserPermissionsForCloudRepo = async (
609618
): Promise<Array<{ accountId: string }>> => {
610619
const path = `/repositories/${workspace}/${repoSlug}/permissions-config/users` as CloudGetRequestPath;
611620

612-
const users = await getPaginatedCloud<CloudRepositoryUserPermission>(path, async (p, query) => {
621+
const users = await fetchWithRetry(() => getPaginatedCloud<CloudRepositoryUserPermission>(path, async (p, query) => {
613622
const response = await client.apiClient.GET(p, {
614623
params: {
615624
path: { workspace, repo_slug: repoSlug },
@@ -621,7 +630,7 @@ export const getExplicitUserPermissionsForCloudRepo = async (
621630
throw new Error(`Failed to get explicit user permissions for ${workspace}/${repoSlug}: ${JSON.stringify(error)}`);
622631
}
623632
return data;
624-
});
633+
}), `permissions for ${workspace}/${repoSlug}`, logger);
625634

626635
return users
627636
.filter(u => u.user?.account_id != null)
@@ -639,7 +648,7 @@ export const getReposForAuthenticatedBitbucketCloudUser = async (
639648
): Promise<Array<{ uuid: string }>> => {
640649
const path = `/user/permissions/repositories` as CloudGetRequestPath;
641650

642-
const permissions = await getPaginatedCloud<CloudRepositoryPermission>(path, async (p, query) => {
651+
const permissions = await fetchWithRetry(() => getPaginatedCloud<CloudRepositoryPermission>(path, async (p, query) => {
643652
const response = await client.apiClient.GET(p, {
644653
params: { query },
645654
});
@@ -648,7 +657,7 @@ export const getReposForAuthenticatedBitbucketCloudUser = async (
648657
throw new Error(`Failed to get user repository permissions: ${JSON.stringify(error)}`);
649658
}
650659
return data;
651-
});
660+
}), 'user repository permissions', logger);
652661

653662
return permissions
654663
.filter(p => p.repository?.uuid != null)
@@ -664,7 +673,7 @@ export const getReposForAuthenticatedBitbucketCloudUser = async (
664673
export const getReposForAuthenticatedBitbucketServerUser = async (
665674
client: BitbucketClient,
666675
): Promise<Array<{ id: string }>> => {
667-
const repos = await getPaginatedServer<{ id: number }>(
676+
const repos = await fetchWithRetry(() => getPaginatedServer<{ id: number }>(
668677
`/rest/api/1.0/repos` as ServerGetRequestPath,
669678
async (url, start) => {
670679
const response = await client.apiClient.GET(url, {
@@ -682,7 +691,7 @@ export const getReposForAuthenticatedBitbucketServerUser = async (
682691
}
683692
return data;
684693
}
685-
);
694+
), 'repos for authenticated Bitbucket Server user', logger);
686695

687696
return repos.map(r => ({ id: String(r.id) }));
688697
};
@@ -706,7 +715,7 @@ export const getUserPermissionsForServerRepo = async (
706715
const userIdSet = new Set<string>();
707716

708717
// Fetch repo-level permissions
709-
const repoUsers = await getPaginatedServer<{ user: { id: number } }>(
718+
const repoUsers = await fetchWithRetry(() => getPaginatedServer<{ user: { id: number } }>(
710719
`/rest/api/1.0/projects/${projectKey}/repos/${repoSlug}/permissions/users` as ServerGetRequestPath,
711720
async (url, start) => {
712721
const response = await client.apiClient.GET(url, {
@@ -718,15 +727,15 @@ export const getUserPermissionsForServerRepo = async (
718727
}
719728
return data;
720729
}
721-
);
730+
), `repo-level permissions for ${projectKey}/${repoSlug}`, logger);
722731
for (const entry of repoUsers) {
723732
if (entry.user?.id != null) {
724733
userIdSet.add(String(entry.user.id));
725734
}
726735
}
727736

728737
// Fetch project-level permissions (inherited by all repos in the project)
729-
const projectUsers = await getPaginatedServer<{ user: { id: number } }>(
738+
const projectUsers = await fetchWithRetry(() => getPaginatedServer<{ user: { id: number } }>(
730739
`/rest/api/1.0/projects/${projectKey}/permissions/users` as ServerGetRequestPath,
731740
async (url, start) => {
732741
const response = await client.apiClient.GET(url, {
@@ -738,7 +747,7 @@ export const getUserPermissionsForServerRepo = async (
738747
}
739748
return data;
740749
}
741-
);
750+
), `project-level permissions for ${projectKey}`, logger);
742751
for (const entry of projectUsers) {
743752
if (entry.user?.id != null) {
744753
userIdSet.add(String(entry.user.id));

0 commit comments

Comments
 (0)