Merge pull request #23634 from QuadmanSWE/patch-plugin-azure-devops-backstage-getAllTeams-topTeams

Add optional limit parameter for getAllTeams method, default 100
This commit is contained in:
Andre Wanlin
2024-04-08 08:01:14 -05:00
committed by GitHub
14 changed files with 150 additions and 24 deletions
+9
View File
@@ -0,0 +1,9 @@
---
'@backstage/plugin-azure-devops-backend': patch
'@backstage/plugin-azure-devops': patch
'@backstage/plugin-azure-common': patch
---
`getAllTeams` now accepts an optional `limit` parameter which can be used to return more than the default limit of 100 teams from the Azure DevOps API
`pullRequestOptions` have been equipped with `teamsLimit` so that the property can be used with `getAllTeams`
+1 -1
View File
@@ -56,7 +56,7 @@ export class AzureDevOpsApi {
},
): AzureDevOpsApi;
// (undocumented)
getAllTeams(): Promise<Team[]>;
getAllTeams(options?: { limit?: number }): Promise<Team[]>;
// (undocumented)
getBuildDefinitions(
projectName: string,
@@ -397,12 +397,18 @@ export class AzureDevOpsApi {
.filter((policy): policy is Policy => Boolean(policy));
}
public async getAllTeams(): Promise<Team[]> {
public async getAllTeams(options?: { limit?: number }): Promise<Team[]> {
this.logger?.debug('Getting all teams.');
const webApi = await this.getWebApi();
const client = await webApi.getCoreApi();
const webApiTeams: WebApiTeam[] = await client.getAllTeams();
const webApiTeams: WebApiTeam[] = await client.getAllTeams(
undefined,
options?.limit,
undefined,
undefined,
);
const teams: Team[] = webApiTeams.map(team => ({
id: team.id,
@@ -25,6 +25,8 @@ import { AzureDevOpsApi } from './AzureDevOpsApi';
import { Logger } from 'winston';
import limiterFactory from 'p-limit';
export const DEFAULT_TEAMS_LIMIT = 100;
export class PullRequestsDashboardProvider {
private teams = new Map<string, Team>();
@@ -43,10 +45,10 @@ export class PullRequestsDashboardProvider {
return provider;
}
public async readTeams(): Promise<void> {
public async readTeams(limit?: number): Promise<void> {
this.logger.info('Reading teams.');
let teams = await this.azureDevOpsApi.getAllTeams();
let teams = await this.azureDevOpsApi.getAllTeams({ limit });
// This is used to filter out the default Azure Devops project teams.
teams = teams.filter(team =>
@@ -110,7 +112,7 @@ export class PullRequestsDashboardProvider {
const dashboardPullRequests =
await this.azureDevOpsApi.getDashboardPullRequests(projectName, options);
await this.getAllTeams(); // Make sure team members are loaded
await this.getAllTeams({ limit: options.teamsLimit }); // Make sure team members are loaded
return dashboardPullRequests.map(pr => {
if (pr.createdBy?.id) {
@@ -126,7 +128,7 @@ export class PullRequestsDashboardProvider {
}
public async getUserTeamIds(email: string): Promise<string[]> {
await this.getAllTeams(); // Make sure team members are loaded
await this.getAllTeams({}); // Make sure team members are loaded
return (
Array.from(this.teamMembers.values()).find(
teamMember => teamMember.uniqueName === email,
@@ -134,9 +136,10 @@ export class PullRequestsDashboardProvider {
);
}
public async getAllTeams(): Promise<Team[]> {
public async getAllTeams(options: { limit?: number }): Promise<Team[]> {
if (!this.teams.size) {
await this.readTeams();
const maxTeams = options?.limit ?? DEFAULT_TEAMS_LIMIT;
await this.readTeams(maxTeams);
}
return Array.from(this.teams.values());
@@ -348,7 +348,74 @@ describe('createRouter', () => {
expect(azureDevOpsApi.getPullRequests).toHaveBeenCalledWith(
'myProject',
'myRepo',
{ status: 1, top: 50 },
{ status: 1, top: 50, teamsLimit: 100 },
undefined,
undefined,
);
expect(response.status).toEqual(200);
expect(response.body).toEqual(pullRequests);
});
it('fetches a list of pull requests when using teamsLimit', async () => {
const firstPullRequest: PullRequest = {
pullRequestId: 7181,
repoName: 'super-feature-repo',
title: 'My Awesome New Feature',
createdBy: 'Jane Doe',
creationDate: '2020-09-12T06:10:23.932Z',
sourceRefName: 'refs/heads/topic/super-awesome-feature',
targetRefName: 'refs/heads/main',
status: PullRequestStatus.Active,
isDraft: false,
link: 'https://host.com/myOrg/_git/super-feature-repo/pullrequest/7181',
};
const secondPullRequest: PullRequest = {
pullRequestId: 7182,
repoName: 'super-feature-repo',
title: 'Refactoring My Awesome New Feature',
createdBy: 'Jane Doe',
creationDate: '2020-09-12T06:10:23.932Z',
sourceRefName: 'refs/heads/topic/refactor-super-awesome-feature',
targetRefName: 'refs/heads/main',
status: PullRequestStatus.Active,
isDraft: false,
link: 'https://host.com/myOrg/_git/super-feature-repo/pullrequest/7182',
};
const thirdPullRequest: PullRequest = {
pullRequestId: 7183,
repoName: 'super-feature-repo',
title: 'Bug Fix for My Awesome New Feature',
createdBy: 'Jane Doe',
creationDate: '2020-09-12T06:10:23.932Z',
sourceRefName: 'refs/heads/topic/fix-super-awesome-feature',
targetRefName: 'refs/heads/main',
status: PullRequestStatus.Active,
isDraft: false,
link: 'https://host.com/myOrg/_git/super-feature-repo/pullrequest/7183',
};
const pullRequests: PullRequest[] = [
firstPullRequest,
secondPullRequest,
thirdPullRequest,
];
mockedAuthorize.mockImplementationOnce(async () => [
{ result: AuthorizeResult.ALLOW },
]);
azureDevOpsApi.getPullRequests.mockResolvedValueOnce(pullRequests);
const response = await request(app)
.get('/pull-requests/myProject/myRepo')
.query({ entityRef: 'component:default/mycomponent' })
.query({ top: '50', status: 1, teamsLimit: 50 });
expect(azureDevOpsApi.getPullRequests).toHaveBeenCalledWith(
'myProject',
'myRepo',
{ status: 1, top: 50, teamsLimit: 50 },
undefined,
undefined,
);
@@ -23,7 +23,10 @@ import {
import { AzureDevOpsApi } from '../api';
import { Config } from '@backstage/config';
import { Logger } from 'winston';
import { PullRequestsDashboardProvider } from '../api/PullRequestsDashboardProvider';
import {
PullRequestsDashboardProvider,
DEFAULT_TEAMS_LIMIT,
} from '../api/PullRequestsDashboardProvider';
import Router from 'express-promise-router';
import { errorHandler, UrlReader } from '@backstage/backend-common';
import express from 'express';
@@ -175,6 +178,9 @@ export async function createRouter(
const { projectName, repoName } = req.params;
const top = req.query.top ? Number(req.query.top) : DEFAULT_TOP;
const teamsLimit = req.query.teamsLimit
? Number(req.query.teamsLimit)
: DEFAULT_TEAMS_LIMIT;
const host = req.query.host?.toString();
const org = req.query.org?.toString();
const status = req.query.status
@@ -184,6 +190,7 @@ export async function createRouter(
const pullRequestOptions: PullRequestOptions = {
top: top,
status: status,
teamsLimit: teamsLimit,
};
const entityRef = req.query.entityRef;
@@ -227,6 +234,9 @@ export async function createRouter(
const { projectName } = req.params;
const top = req.query.top ? Number(req.query.top) : DEFAULT_TOP;
const teamsLimit = req.query.teamsLimit
? Number(req.query.teamsLimit)
: DEFAULT_TEAMS_LIMIT;
const status = req.query.status
? Number(req.query.status)
@@ -235,6 +245,7 @@ export async function createRouter(
const pullRequestOptions: PullRequestOptions = {
top: top,
status: status,
teamsLimit: teamsLimit,
};
const token = getBearerTokenFromAuthorizationHeader(
@@ -266,8 +277,9 @@ export async function createRouter(
res.status(200).json(pullRequests);
});
router.get('/all-teams', async (_req, res) => {
const allTeams = await pullRequestsDashboardProvider.getAllTeams();
router.get('/all-teams', async (req, res) => {
const limit = req.query.limit ? Number(req.query.limit) : undefined;
const allTeams = await pullRequestsDashboardProvider.getAllTeams({ limit });
res.status(200).json(allTeams);
});
@@ -215,6 +215,7 @@ export type PullRequest = {
export type PullRequestOptions = {
top: number;
status: PullRequestStatus;
teamsLimit?: number;
};
// @public (undocumented)
+1
View File
@@ -142,6 +142,7 @@ export type PullRequest = {
export type PullRequestOptions = {
top: number;
status: PullRequestStatus;
teamsLimit?: number;
};
/** @public */
+5 -2
View File
@@ -65,7 +65,7 @@ export type AssignedToUserFilter = BaseFilter &
// @public (undocumented)
export interface AzureDevOpsApi {
// (undocumented)
getAllTeams(): Promise<Team[]>;
getAllTeams(limit?: number): Promise<Team[]>;
// (undocumented)
getBuildRuns(
projectName: string,
@@ -81,6 +81,7 @@ export interface AzureDevOpsApi {
// (undocumented)
getDashboardPullRequests(
projectName: string,
teamsLimit?: number,
): Promise<DashboardPullRequest[]>;
// (undocumented)
getGitTags(
@@ -126,7 +127,7 @@ export const azureDevOpsApiRef: ApiRef<AzureDevOpsApi>;
export class AzureDevOpsClient implements AzureDevOpsApi {
constructor(options: { discoveryApi: DiscoveryApi; fetchApi: FetchApi });
// (undocumented)
getAllTeams(): Promise<Team[]>;
getAllTeams(limit?: number): Promise<Team[]>;
// (undocumented)
getBuildRuns(
projectName: string,
@@ -142,6 +143,7 @@ export class AzureDevOpsClient implements AzureDevOpsApi {
// (undocumented)
getDashboardPullRequests(
projectName: string,
teamsLimit?: number,
): Promise<DashboardPullRequest[]>;
// (undocumented)
getGitTags(
@@ -193,6 +195,7 @@ export const AzurePullRequestsPage: (props: {
projectName?: string | undefined;
pollingInterval?: number | undefined;
defaultColumnConfigs?: PullRequestColumnConfig[] | undefined;
teamsLimit?: number | undefined;
}) => JSX_2.Element;
// @public (undocumented)
@@ -64,9 +64,10 @@ export interface AzureDevOpsApi {
getDashboardPullRequests(
projectName: string,
teamsLimit?: number,
): Promise<DashboardPullRequest[]>;
getAllTeams(): Promise<Team[]>;
getAllTeams(limit?: number): Promise<Team[]>;
getUserTeamIds(userId: string): Promise<string[]>;
@@ -107,6 +107,9 @@ export class AzureDevOpsClient implements AzureDevOpsApi {
if (options?.status) {
queryString.append('status', options.status.toString());
}
if (options?.teamsLimit) {
queryString.append('teamsLimit', options.teamsLimit.toString());
}
if (host) {
queryString.append('host', host);
}
@@ -124,14 +127,27 @@ export class AzureDevOpsClient implements AzureDevOpsApi {
public getDashboardPullRequests(
projectName: string,
teamsLimit?: number,
): Promise<DashboardPullRequest[]> {
return this.get<DashboardPullRequest[]>(
`dashboard-pull-requests/${projectName}?top=100`,
);
const queryString = new URLSearchParams();
queryString.append('top', '100');
if (teamsLimit) {
queryString.append('teamsLimit', teamsLimit.toString());
}
const urlSegment = `dashboard-pull-requests/${projectName}?${queryString}`;
return this.get<DashboardPullRequest[]>(urlSegment);
}
public getAllTeams(): Promise<Team[]> {
return this.get<Team[]>('all-teams');
public getAllTeams(limit?: number): Promise<Team[]> {
const queryString = new URLSearchParams();
if (limit) {
queryString.append('limit', limit.toString());
}
let urlSegment = 'all-teams';
if (queryString.toString()) {
urlSegment += `?${queryString}`;
}
return this.get<Team[]>(urlSegment);
}
public getUserTeamIds(userId: string): Promise<string[]> {
@@ -70,14 +70,17 @@ type PullRequestsPageProps = {
projectName?: string;
pollingInterval?: number;
defaultColumnConfigs?: PullRequestColumnConfig[];
teamsLimit?: number;
};
export const PullRequestsPage = (props: PullRequestsPageProps) => {
const { projectName, pollingInterval, defaultColumnConfigs } = props;
const { projectName, pollingInterval, defaultColumnConfigs, teamsLimit } =
props;
const { pullRequests, loading, error } = useDashboardPullRequests(
projectName,
pollingInterval,
teamsLimit,
);
const [columnConfigs] = useState(
@@ -27,6 +27,7 @@ const POLLING_INTERVAL = 10000;
export function useDashboardPullRequests(
project?: string,
pollingInterval: number = POLLING_INTERVAL,
teamsLimit?: number,
): {
pullRequests?: DashboardPullRequest[];
loading: boolean;
@@ -43,7 +44,7 @@ export function useDashboardPullRequests(
}
try {
return await api.getDashboardPullRequests(project);
return await api.getDashboardPullRequests(project, teamsLimit);
} catch (error) {
if (error instanceof Error) {
errorApi.post(error);
@@ -51,7 +52,7 @@ export function useDashboardPullRequests(
return Promise.reject(error);
}
}, [project, api, errorApi]);
}, [project, api, teamsLimit, errorApi]);
const {
value: pullRequests,
@@ -31,16 +31,19 @@ export function usePullRequests(
entity: Entity,
defaultLimit?: number,
requestedStatus?: PullRequestStatus,
defaultTeamsLimit?: number,
): {
items?: PullRequest[];
loading: boolean;
error?: Error;
} {
const top = defaultLimit ?? AZURE_DEVOPS_DEFAULT_TOP;
const teamsLimit = defaultTeamsLimit ?? undefined;
const status = requestedStatus ?? PullRequestStatus.Active;
const options: PullRequestOptions = {
top,
status,
teamsLimit,
};
const api = useApi(azureDevOpsApiRef);