Merge pull request #20729 from awanlin/topic/use-azure-devops-credentials-provider
[Revisit] Use AzureDevOpsCredentialsProvider
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
---
|
||||
'@backstage/plugin-azure-devops-backend': minor
|
||||
---
|
||||
|
||||
**BREAKING** New `fromConfig` static method must be used now when creating an instance of the `AzureDevOpsApi`
|
||||
|
||||
Added support for using the `AzureDevOpsCredentialsProvider`
|
||||
@@ -20,11 +20,17 @@ import { RepoBuild } from '@backstage/plugin-azure-devops-common';
|
||||
import { Team } from '@backstage/plugin-azure-devops-common';
|
||||
import { TeamMember } from '@backstage/plugin-azure-devops-common';
|
||||
import { UrlReader } from '@backstage/backend-common';
|
||||
import { WebApi } from 'azure-devops-node-api';
|
||||
|
||||
// @public (undocumented)
|
||||
export class AzureDevOpsApi {
|
||||
constructor(logger: Logger, webApi: WebApi, urlReader: UrlReader);
|
||||
// (undocumented)
|
||||
static fromConfig(
|
||||
config: Config,
|
||||
options: {
|
||||
logger: Logger;
|
||||
urlReader: UrlReader;
|
||||
},
|
||||
): AzureDevOpsApi;
|
||||
// (undocumented)
|
||||
getAllTeams(): Promise<Team[]>;
|
||||
// (undocumented)
|
||||
|
||||
+3
-1
@@ -15,7 +15,9 @@
|
||||
*/
|
||||
|
||||
export interface Config {
|
||||
/** Configuration options for the azure-devops-backend plugin */
|
||||
/**
|
||||
* Configuration options for the azure-devops-backend plugin
|
||||
*/
|
||||
azureDevOps: {
|
||||
/**
|
||||
* The hostname of the given Azure instance
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
"@backstage/backend-common": "workspace:^",
|
||||
"@backstage/backend-plugin-api": "workspace:^",
|
||||
"@backstage/config": "workspace:^",
|
||||
"@backstage/integration": "workspace:^",
|
||||
"@backstage/plugin-azure-devops-common": "workspace:^",
|
||||
"@types/express": "^4.17.6",
|
||||
"azure-devops-node-api": "^11.0.1",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -19,9 +19,7 @@ import {
|
||||
BuildDefinitionReference,
|
||||
} from 'azure-devops-node-api/interfaces/BuildInterfaces';
|
||||
import {
|
||||
BuildResult,
|
||||
BuildRun,
|
||||
BuildStatus,
|
||||
DashboardPullRequest,
|
||||
GitTag,
|
||||
Policy,
|
||||
@@ -49,23 +47,92 @@ import {
|
||||
import { TeamMember as AdoTeamMember } from 'azure-devops-node-api/interfaces/common/VSSInterfaces';
|
||||
import { Logger } from 'winston';
|
||||
import { PolicyEvaluationRecord } from 'azure-devops-node-api/interfaces/PolicyInterfaces';
|
||||
import { WebApi } from 'azure-devops-node-api';
|
||||
import {
|
||||
WebApi,
|
||||
getHandlerFromToken,
|
||||
getPersonalAccessTokenHandler,
|
||||
} from 'azure-devops-node-api';
|
||||
import {
|
||||
TeamProjectReference,
|
||||
WebApiTeam,
|
||||
} from 'azure-devops-node-api/interfaces/CoreInterfaces';
|
||||
import { UrlReader } from '@backstage/backend-common';
|
||||
import { Config } from '@backstage/config';
|
||||
import {
|
||||
AzureDevOpsCredentialsProvider,
|
||||
DefaultAzureDevOpsCredentialsProvider,
|
||||
ScmIntegrations,
|
||||
} from '@backstage/integration';
|
||||
import {
|
||||
mappedBuildRun,
|
||||
mappedGitTag,
|
||||
mappedPullRequest,
|
||||
mappedRepoBuild,
|
||||
} from './mappers';
|
||||
|
||||
/** @public */
|
||||
export class AzureDevOpsApi {
|
||||
public constructor(
|
||||
private readonly logger: Logger,
|
||||
private readonly webApi: WebApi,
|
||||
private readonly urlReader: UrlReader,
|
||||
) {}
|
||||
private readonly logger: Logger;
|
||||
private readonly urlReader: UrlReader;
|
||||
private readonly config: Config;
|
||||
private readonly credentialsProvider: AzureDevOpsCredentialsProvider;
|
||||
|
||||
private constructor(
|
||||
logger: Logger,
|
||||
urlReader: UrlReader,
|
||||
config: Config,
|
||||
credentialsProvider: AzureDevOpsCredentialsProvider,
|
||||
) {
|
||||
this.logger = logger;
|
||||
this.urlReader = urlReader;
|
||||
this.config = config;
|
||||
this.credentialsProvider = credentialsProvider;
|
||||
}
|
||||
|
||||
static fromConfig(
|
||||
config: Config,
|
||||
options: { logger: Logger; urlReader: UrlReader },
|
||||
) {
|
||||
const scmIntegrations = ScmIntegrations.fromConfig(config);
|
||||
const credentialsProvider =
|
||||
DefaultAzureDevOpsCredentialsProvider.fromIntegrations(scmIntegrations);
|
||||
return new AzureDevOpsApi(
|
||||
options.logger,
|
||||
options.urlReader,
|
||||
config,
|
||||
credentialsProvider,
|
||||
);
|
||||
}
|
||||
|
||||
private async getWebApi(host?: string, org?: string): Promise<WebApi> {
|
||||
// If no host or org is provided we fall back to the values from the `azureDevOps` config section
|
||||
// these may have been setup in the `integrations.azure` config section
|
||||
// which is why use them here and not just falling back on them entirely
|
||||
const validHost = host ?? this.config.getString('azureDevOps.host');
|
||||
const validOrg = org ?? this.config.getString('azureDevOps.organization');
|
||||
const url = `https://${validHost}/${encodeURI(validOrg)}`;
|
||||
|
||||
const credentials = await this.credentialsProvider.getCredentials({
|
||||
url,
|
||||
});
|
||||
|
||||
let authHandler;
|
||||
if (!credentials) {
|
||||
// No credentials found for the provided host and org in the `integrations.azure` config section
|
||||
// use the fall back personal access token from `azureDevOps.token`
|
||||
const token = this.config.getString('azureDevOps.token');
|
||||
authHandler = getPersonalAccessTokenHandler(token);
|
||||
} else {
|
||||
authHandler = getHandlerFromToken(credentials.token);
|
||||
}
|
||||
|
||||
const webApi = new WebApi(url, authHandler);
|
||||
return webApi;
|
||||
}
|
||||
|
||||
public async getProjects(): Promise<Project[]> {
|
||||
const client = await this.webApi.getCoreApi();
|
||||
const webApi = await this.getWebApi();
|
||||
const client = await webApi.getCoreApi();
|
||||
const projectList: TeamProjectReference[] = await client.getProjects();
|
||||
|
||||
const projects: Project[] = projectList.map(project => ({
|
||||
@@ -87,7 +154,8 @@ export class AzureDevOpsApi {
|
||||
`Calling Azure DevOps REST API, getting Repository ${repoName} for Project ${projectName}`,
|
||||
);
|
||||
|
||||
const client = await this.webApi.getGitApi();
|
||||
const webApi = await this.getWebApi();
|
||||
const client = await webApi.getGitApi();
|
||||
return client.getRepository(repoName, projectName);
|
||||
}
|
||||
|
||||
@@ -100,7 +168,8 @@ export class AzureDevOpsApi {
|
||||
`Calling Azure DevOps REST API, getting up to ${top} Builds for Repository Id ${repoId} for Project ${projectName}`,
|
||||
);
|
||||
|
||||
const client = await this.webApi.getBuildApi();
|
||||
const webApi = await this.getWebApi();
|
||||
const client = await webApi.getBuildApi();
|
||||
return client.getBuilds(
|
||||
projectName,
|
||||
undefined,
|
||||
@@ -158,7 +227,8 @@ export class AzureDevOpsApi {
|
||||
);
|
||||
|
||||
const gitRepository = await this.getGitRepository(projectName, repoName);
|
||||
const client = await this.webApi.getGitApi();
|
||||
const webApi = await this.getWebApi();
|
||||
const client = await webApi.getGitApi();
|
||||
const tagRefs: GitRef[] = await client.getRefs(
|
||||
gitRepository.id as string,
|
||||
projectName,
|
||||
@@ -169,10 +239,10 @@ export class AzureDevOpsApi {
|
||||
false,
|
||||
true,
|
||||
);
|
||||
const linkBaseUrl = `${this.webApi.serverUrl}/${encodeURIComponent(
|
||||
const linkBaseUrl = `${webApi.serverUrl}/${encodeURIComponent(
|
||||
projectName,
|
||||
)}/_git/${encodeURIComponent(repoName)}?version=GT`;
|
||||
const commitBaseUrl = `${this.webApi.serverUrl}/${encodeURIComponent(
|
||||
const commitBaseUrl = `${webApi.serverUrl}/${encodeURIComponent(
|
||||
projectName,
|
||||
)}/_git/${encodeURIComponent(repoName)}/commit`;
|
||||
const gitTags: GitTag[] = tagRefs.map(tagRef => {
|
||||
@@ -192,7 +262,8 @@ export class AzureDevOpsApi {
|
||||
);
|
||||
|
||||
const gitRepository = await this.getGitRepository(projectName, repoName);
|
||||
const client = await this.webApi.getGitApi();
|
||||
const webApi = await this.getWebApi();
|
||||
const client = await webApi.getGitApi();
|
||||
const searchCriteria: GitPullRequestSearchCriteria = {
|
||||
status: options.status,
|
||||
};
|
||||
@@ -204,7 +275,7 @@ export class AzureDevOpsApi {
|
||||
undefined,
|
||||
options.top,
|
||||
);
|
||||
const linkBaseUrl = `${this.webApi.serverUrl}/${encodeURIComponent(
|
||||
const linkBaseUrl = `${webApi.serverUrl}/${encodeURIComponent(
|
||||
projectName,
|
||||
)}/_git/${encodeURIComponent(repoName)}/pullrequest`;
|
||||
const pullRequests: PullRequest[] = gitPullRequests.map(gitPullRequest => {
|
||||
@@ -222,7 +293,8 @@ export class AzureDevOpsApi {
|
||||
`Getting dashboard pull requests for project '${projectName}'.`,
|
||||
);
|
||||
|
||||
const client = await this.webApi.getGitApi();
|
||||
const webApi = await this.getWebApi();
|
||||
const client = await webApi.getGitApi();
|
||||
|
||||
const searchCriteria: GitPullRequestSearchCriteria = {
|
||||
status: options.status,
|
||||
@@ -254,7 +326,7 @@ export class AzureDevOpsApi {
|
||||
|
||||
return convertDashboardPullRequest(
|
||||
gitPullRequest,
|
||||
this.webApi.serverUrl,
|
||||
webApi.serverUrl,
|
||||
policies,
|
||||
);
|
||||
}),
|
||||
@@ -270,7 +342,8 @@ export class AzureDevOpsApi {
|
||||
`Getting pull request policies for pull request id '${pullRequestId}'.`,
|
||||
);
|
||||
|
||||
const client = await this.webApi.getPolicyApi();
|
||||
const webApi = await this.getWebApi();
|
||||
const client = await webApi.getPolicyApi();
|
||||
|
||||
const artifactId = getArtifactId(projectId, pullRequestId);
|
||||
|
||||
@@ -285,7 +358,8 @@ export class AzureDevOpsApi {
|
||||
public async getAllTeams(): Promise<Team[]> {
|
||||
this.logger?.debug('Getting all teams.');
|
||||
|
||||
const client = await this.webApi.getCoreApi();
|
||||
const webApi = await this.getWebApi();
|
||||
const client = await webApi.getCoreApi();
|
||||
const webApiTeams: WebApiTeam[] = await client.getAllTeams();
|
||||
|
||||
const teams: Team[] = webApiTeams.map(team => ({
|
||||
@@ -307,7 +381,8 @@ export class AzureDevOpsApi {
|
||||
const { projectId, teamId } = options;
|
||||
this.logger?.debug(`Getting team member ids for team '${teamId}'.`);
|
||||
|
||||
const client = await this.webApi.getCoreApi();
|
||||
const webApi = await this.getWebApi();
|
||||
const client = await webApi.getCoreApi();
|
||||
|
||||
const teamMembers: AdoTeamMember[] =
|
||||
await client.getTeamMembersWithExtendedProperties(projectId, teamId);
|
||||
@@ -327,7 +402,8 @@ export class AzureDevOpsApi {
|
||||
`Calling Azure DevOps REST API, getting Build Definitions for ${definitionName} in Project ${projectName}`,
|
||||
);
|
||||
|
||||
const client = await this.webApi.getBuildApi();
|
||||
const webApi = await this.getWebApi();
|
||||
const client = await webApi.getBuildApi();
|
||||
return client.getDefinitions(projectName, definitionName);
|
||||
}
|
||||
|
||||
@@ -341,7 +417,8 @@ export class AzureDevOpsApi {
|
||||
`Calling Azure DevOps REST API, getting up to ${top} Builds for Repository Id ${repoId} for Project ${projectName}`,
|
||||
);
|
||||
|
||||
const client = await this.webApi.getBuildApi();
|
||||
const webApi = await this.getWebApi();
|
||||
const client = await webApi.getBuildApi();
|
||||
return client.getBuilds(
|
||||
projectName,
|
||||
definitions,
|
||||
@@ -421,75 +498,3 @@ export class AzureDevOpsApi {
|
||||
return { url, content };
|
||||
}
|
||||
}
|
||||
|
||||
export function mappedRepoBuild(build: Build): RepoBuild {
|
||||
return {
|
||||
id: build.id,
|
||||
title: [build.definition?.name, build.buildNumber]
|
||||
.filter(Boolean)
|
||||
.join(' - '),
|
||||
link: build._links?.web.href ?? '',
|
||||
status: build.status ?? BuildStatus.None,
|
||||
result: build.result ?? BuildResult.None,
|
||||
queueTime: build.queueTime?.toISOString(),
|
||||
startTime: build.startTime?.toISOString(),
|
||||
finishTime: build.finishTime?.toISOString(),
|
||||
source: `${build.sourceBranch} (${build.sourceVersion?.slice(0, 8)})`,
|
||||
uniqueName: build.requestedFor?.uniqueName ?? 'N/A',
|
||||
};
|
||||
}
|
||||
|
||||
export function mappedGitTag(
|
||||
gitRef: GitRef,
|
||||
linkBaseUrl: string,
|
||||
commitBaseUrl: string,
|
||||
): GitTag {
|
||||
return {
|
||||
objectId: gitRef.objectId,
|
||||
peeledObjectId: gitRef.peeledObjectId,
|
||||
name: gitRef.name?.replace('refs/tags/', ''),
|
||||
createdBy: gitRef.creator?.displayName ?? 'N/A',
|
||||
link: `${linkBaseUrl}${encodeURIComponent(
|
||||
gitRef.name?.replace('refs/tags/', '') ?? '',
|
||||
)}`,
|
||||
commitLink: `${commitBaseUrl}/${encodeURIComponent(
|
||||
gitRef.peeledObjectId ?? '',
|
||||
)}`,
|
||||
};
|
||||
}
|
||||
|
||||
export function mappedPullRequest(
|
||||
pullRequest: GitPullRequest,
|
||||
linkBaseUrl: string,
|
||||
): PullRequest {
|
||||
return {
|
||||
pullRequestId: pullRequest.pullRequestId,
|
||||
repoName: pullRequest.repository?.name,
|
||||
title: pullRequest.title,
|
||||
uniqueName: pullRequest.createdBy?.uniqueName ?? 'N/A',
|
||||
createdBy: pullRequest.createdBy?.displayName ?? 'N/A',
|
||||
creationDate: pullRequest.creationDate?.toISOString(),
|
||||
sourceRefName: pullRequest.sourceRefName,
|
||||
targetRefName: pullRequest.targetRefName,
|
||||
status: pullRequest.status,
|
||||
isDraft: pullRequest.isDraft,
|
||||
link: `${linkBaseUrl}/${pullRequest.pullRequestId}`,
|
||||
};
|
||||
}
|
||||
|
||||
export function mappedBuildRun(build: Build): BuildRun {
|
||||
return {
|
||||
id: build.id,
|
||||
title: [build.definition?.name, build.buildNumber]
|
||||
.filter(Boolean)
|
||||
.join(' - '),
|
||||
link: build._links?.web.href ?? '',
|
||||
status: build.status ?? BuildStatus.None,
|
||||
result: build.result ?? BuildResult.None,
|
||||
queueTime: build.queueTime?.toISOString(),
|
||||
startTime: build.startTime?.toISOString(),
|
||||
finishTime: build.finishTime?.toISOString(),
|
||||
source: `${build.sourceBranch} (${build.sourceVersion?.slice(0, 8)})`,
|
||||
uniqueName: build.requestedFor?.uniqueName ?? 'N/A',
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,573 @@
|
||||
/*
|
||||
* Copyright 2021 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
Build,
|
||||
DefinitionReference,
|
||||
} from 'azure-devops-node-api/interfaces/BuildInterfaces';
|
||||
import {
|
||||
BuildResult,
|
||||
BuildStatus,
|
||||
GitTag,
|
||||
PullRequest,
|
||||
PullRequestStatus,
|
||||
RepoBuild,
|
||||
} from '@backstage/plugin-azure-devops-common';
|
||||
import {
|
||||
GitPullRequest,
|
||||
GitRef,
|
||||
GitRepository,
|
||||
} from 'azure-devops-node-api/interfaces/GitInterfaces';
|
||||
import {
|
||||
mappedBuildRun,
|
||||
mappedGitTag,
|
||||
mappedPullRequest,
|
||||
mappedRepoBuild,
|
||||
} from './mappers';
|
||||
|
||||
import { IdentityRef } from 'azure-devops-node-api/interfaces/common/VSSInterfaces';
|
||||
|
||||
describe('mappers', () => {
|
||||
describe('mappedRepoBuild', () => {
|
||||
describe('mappedRepoBuild happy path', () => {
|
||||
it('should return RepoBuild from Build', () => {
|
||||
const inputBuildDefinition: DefinitionReference = {
|
||||
name: 'My Build Definition',
|
||||
};
|
||||
|
||||
const inputLinks: any = {
|
||||
web: {
|
||||
href: 'https://host.com/myOrg/0bcc0c0d-2d02/_build/results?buildId=1',
|
||||
},
|
||||
};
|
||||
|
||||
const inputIdentityRef: IdentityRef = {
|
||||
displayName: 'Jane Doe',
|
||||
uniqueName: 'DOMAIN\\jdoe',
|
||||
};
|
||||
|
||||
const inputBuild: Build = {
|
||||
id: 1,
|
||||
buildNumber: 'Build-1',
|
||||
status: BuildStatus.Completed,
|
||||
result: BuildResult.Succeeded,
|
||||
queueTime: new Date('2020-09-12T06:10:23.932Z'),
|
||||
startTime: new Date('2020-09-12T06:15:23.932Z'),
|
||||
finishTime: new Date('2020-09-12T06:20:23.932Z'),
|
||||
sourceBranch: 'refs/heads/develop',
|
||||
sourceVersion: 'f4f78b3100b2923982bdf60c89c57ce6fd2d9a1c',
|
||||
definition: inputBuildDefinition,
|
||||
_links: inputLinks,
|
||||
requestedFor: inputIdentityRef,
|
||||
};
|
||||
|
||||
const outputRepoBuild: RepoBuild = {
|
||||
id: 1,
|
||||
title: 'My Build Definition - Build-1',
|
||||
link: 'https://host.com/myOrg/0bcc0c0d-2d02/_build/results?buildId=1',
|
||||
status: BuildStatus.Completed,
|
||||
result: BuildResult.Succeeded,
|
||||
queueTime: '2020-09-12T06:10:23.932Z',
|
||||
startTime: '2020-09-12T06:15:23.932Z',
|
||||
finishTime: '2020-09-12T06:20:23.932Z',
|
||||
source: 'refs/heads/develop (f4f78b31)',
|
||||
uniqueName: 'DOMAIN\\jdoe',
|
||||
};
|
||||
|
||||
expect(mappedRepoBuild(inputBuild)).toEqual(outputRepoBuild);
|
||||
});
|
||||
});
|
||||
|
||||
describe('mappedRepoBuild with no Build definition name', () => {
|
||||
it('should return RepoBuild with only Build Number for title', () => {
|
||||
const inputLinks: any = {
|
||||
web: {
|
||||
href: 'https://host.com/myOrg/0bcc0c0d-2d02/_build/results?buildId=1',
|
||||
},
|
||||
};
|
||||
|
||||
const inputIdentityRef: IdentityRef = {
|
||||
displayName: 'Jane Doe',
|
||||
uniqueName: 'DOMAIN\\jdoe',
|
||||
};
|
||||
|
||||
const inputBuild: Build = {
|
||||
id: 1,
|
||||
buildNumber: 'Build-1',
|
||||
status: BuildStatus.Completed,
|
||||
result: BuildResult.Succeeded,
|
||||
queueTime: new Date('2020-09-12T06:10:23.932Z'),
|
||||
startTime: new Date('2020-09-12T06:15:23.932Z'),
|
||||
finishTime: new Date('2020-09-12T06:20:23.932Z'),
|
||||
sourceBranch: 'refs/heads/develop',
|
||||
sourceVersion: 'f4f78b3100b2923982bdf60c89c57ce6fd2d9a1c',
|
||||
definition: undefined,
|
||||
_links: inputLinks,
|
||||
requestedFor: inputIdentityRef,
|
||||
};
|
||||
|
||||
const outputRepoBuild: RepoBuild = {
|
||||
id: 1,
|
||||
title: 'Build-1',
|
||||
link: 'https://host.com/myOrg/0bcc0c0d-2d02/_build/results?buildId=1',
|
||||
status: BuildStatus.Completed,
|
||||
result: BuildResult.Succeeded,
|
||||
queueTime: '2020-09-12T06:10:23.932Z',
|
||||
startTime: '2020-09-12T06:15:23.932Z',
|
||||
finishTime: '2020-09-12T06:20:23.932Z',
|
||||
source: 'refs/heads/develop (f4f78b31)',
|
||||
uniqueName: 'DOMAIN\\jdoe',
|
||||
};
|
||||
|
||||
expect(mappedRepoBuild(inputBuild)).toEqual(outputRepoBuild);
|
||||
});
|
||||
});
|
||||
|
||||
describe('mappedRepoBuild with undefined status', () => {
|
||||
it('should return BuildStatus of None for status', () => {
|
||||
const inputLinks: any = {
|
||||
web: {
|
||||
href: 'https://host.com/myOrg/0bcc0c0d-2d02/_build/results?buildId=1',
|
||||
},
|
||||
};
|
||||
|
||||
const inputIdentityRef: IdentityRef = {
|
||||
displayName: 'Jane Doe',
|
||||
uniqueName: 'DOMAIN\\jdoe',
|
||||
};
|
||||
|
||||
const inputBuild: Build = {
|
||||
id: 1,
|
||||
buildNumber: 'Build-1',
|
||||
status: undefined,
|
||||
result: BuildResult.Succeeded,
|
||||
queueTime: new Date('2020-09-12T06:10:23.932Z'),
|
||||
startTime: new Date('2020-09-12T06:15:23.932Z'),
|
||||
finishTime: new Date('2020-09-12T06:20:23.932Z'),
|
||||
sourceBranch: 'refs/heads/develop',
|
||||
sourceVersion: 'f4f78b3100b2923982bdf60c89c57ce6fd2d9a1c',
|
||||
definition: undefined,
|
||||
_links: inputLinks,
|
||||
requestedFor: inputIdentityRef,
|
||||
};
|
||||
|
||||
const outputRepoBuild: RepoBuild = {
|
||||
id: 1,
|
||||
title: 'Build-1',
|
||||
link: 'https://host.com/myOrg/0bcc0c0d-2d02/_build/results?buildId=1',
|
||||
status: BuildStatus.None,
|
||||
result: BuildResult.Succeeded,
|
||||
queueTime: '2020-09-12T06:10:23.932Z',
|
||||
startTime: '2020-09-12T06:15:23.932Z',
|
||||
finishTime: '2020-09-12T06:20:23.932Z',
|
||||
source: 'refs/heads/develop (f4f78b31)',
|
||||
uniqueName: 'DOMAIN\\jdoe',
|
||||
};
|
||||
|
||||
expect(mappedRepoBuild(inputBuild)).toEqual(outputRepoBuild);
|
||||
});
|
||||
});
|
||||
|
||||
describe('mappedRepoBuild with undefined result', () => {
|
||||
it('should return BuildResult of None for result', () => {
|
||||
const inputLinks: any = {
|
||||
web: {
|
||||
href: 'https://host.com/myOrg/0bcc0c0d-2d02/_build/results?buildId=1',
|
||||
},
|
||||
};
|
||||
|
||||
const inputIdentityRef: IdentityRef = {
|
||||
displayName: 'Jane Doe',
|
||||
uniqueName: 'DOMAIN\\jdoe',
|
||||
};
|
||||
|
||||
const inputBuild: Build = {
|
||||
id: 1,
|
||||
buildNumber: 'Build-1',
|
||||
status: BuildStatus.InProgress,
|
||||
result: undefined,
|
||||
queueTime: new Date('2020-09-12T06:10:23.932Z'),
|
||||
startTime: new Date('2020-09-12T06:15:23.932Z'),
|
||||
finishTime: new Date('2020-09-12T06:20:23.932Z'),
|
||||
sourceBranch: 'refs/heads/develop',
|
||||
sourceVersion: 'f4f78b3100b2923982bdf60c89c57ce6fd2d9a1c',
|
||||
definition: undefined,
|
||||
_links: inputLinks,
|
||||
requestedFor: inputIdentityRef,
|
||||
};
|
||||
|
||||
const outputRepoBuild: RepoBuild = {
|
||||
id: 1,
|
||||
title: 'Build-1',
|
||||
link: 'https://host.com/myOrg/0bcc0c0d-2d02/_build/results?buildId=1',
|
||||
status: BuildStatus.InProgress,
|
||||
result: BuildResult.None,
|
||||
queueTime: '2020-09-12T06:10:23.932Z',
|
||||
startTime: '2020-09-12T06:15:23.932Z',
|
||||
finishTime: '2020-09-12T06:20:23.932Z',
|
||||
source: 'refs/heads/develop (f4f78b31)',
|
||||
uniqueName: 'DOMAIN\\jdoe',
|
||||
};
|
||||
|
||||
expect(mappedRepoBuild(inputBuild)).toEqual(outputRepoBuild);
|
||||
});
|
||||
});
|
||||
|
||||
describe('mappedRepoBuild with undefined link', () => {
|
||||
it('should return empty string for link', () => {
|
||||
const inputIdentityRef: IdentityRef = {
|
||||
displayName: 'Jane Doe',
|
||||
uniqueName: 'DOMAIN\\jdoe',
|
||||
};
|
||||
|
||||
const inputBuild: Build = {
|
||||
id: 1,
|
||||
buildNumber: 'Build-1',
|
||||
status: BuildStatus.InProgress,
|
||||
result: undefined,
|
||||
queueTime: new Date('2020-09-12T06:10:23.932Z'),
|
||||
startTime: new Date('2020-09-12T06:15:23.932Z'),
|
||||
finishTime: new Date('2020-09-12T06:20:23.932Z'),
|
||||
sourceBranch: 'refs/heads/develop',
|
||||
sourceVersion: 'f4f78b3100b2923982bdf60c89c57ce6fd2d9a1c',
|
||||
definition: undefined,
|
||||
_links: undefined,
|
||||
requestedFor: inputIdentityRef,
|
||||
};
|
||||
|
||||
const outputRepoBuild: RepoBuild = {
|
||||
id: 1,
|
||||
title: 'Build-1',
|
||||
link: '',
|
||||
status: BuildStatus.InProgress,
|
||||
result: BuildResult.None,
|
||||
queueTime: '2020-09-12T06:10:23.932Z',
|
||||
startTime: '2020-09-12T06:15:23.932Z',
|
||||
finishTime: '2020-09-12T06:20:23.932Z',
|
||||
source: 'refs/heads/develop (f4f78b31)',
|
||||
uniqueName: 'DOMAIN\\jdoe',
|
||||
};
|
||||
|
||||
expect(mappedRepoBuild(inputBuild)).toEqual(outputRepoBuild);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('mappedGitTag', () => {
|
||||
describe('mappedGitTag happy path', () => {
|
||||
it('should return GitTag from GitRef', () => {
|
||||
const inputIdentityRef: IdentityRef = {
|
||||
displayName: 'Jane Doe',
|
||||
};
|
||||
const inputGitRef: GitRef = {
|
||||
name: 'refs/tags/v1.1.2',
|
||||
creator: inputIdentityRef,
|
||||
objectId: '1111aaaa2222bbbb3333cccc4444dddd5555eeee',
|
||||
peeledObjectId: '1234567890abcdef1234567890abcdef12345678',
|
||||
};
|
||||
const inputLinkBaseUrl =
|
||||
'https://host.com/myOrg/_git/super-feature-repo?version=GT';
|
||||
const inputCommitBaseUrl =
|
||||
'https://host.com/myOrg/_git/super-feature-repo/commit';
|
||||
const outputGitTag: GitTag = {
|
||||
name: 'v1.1.2',
|
||||
createdBy: 'Jane Doe',
|
||||
commitLink:
|
||||
'https://host.com/myOrg/_git/super-feature-repo/commit/1234567890abcdef1234567890abcdef12345678',
|
||||
objectId: '1111aaaa2222bbbb3333cccc4444dddd5555eeee',
|
||||
peeledObjectId: '1234567890abcdef1234567890abcdef12345678',
|
||||
link: 'https://host.com/myOrg/_git/super-feature-repo?version=GTv1.1.2',
|
||||
};
|
||||
expect(
|
||||
mappedGitTag(inputGitRef, inputLinkBaseUrl, inputCommitBaseUrl),
|
||||
).toEqual(outputGitTag);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('mappedPullRequest', () => {
|
||||
describe('mappedPullRequest happy path', () => {
|
||||
it('should return PullRequest from GitPullRequest', () => {
|
||||
const inputGitRepository: GitRepository = {
|
||||
name: 'super-feature-repo',
|
||||
};
|
||||
|
||||
const inputIdentityRef: IdentityRef = {
|
||||
displayName: 'Jane Doe',
|
||||
uniqueName: 'DOMAIN\\jdoe',
|
||||
};
|
||||
|
||||
const inputPullRequest: GitPullRequest = {
|
||||
pullRequestId: 7181,
|
||||
repository: inputGitRepository,
|
||||
title: 'My Awesome New Feature',
|
||||
createdBy: inputIdentityRef,
|
||||
creationDate: new Date('2020-09-12T06:10:23.932Z'),
|
||||
sourceRefName: 'refs/heads/topic/super-awesome-feature',
|
||||
targetRefName: 'refs/heads/main',
|
||||
status: PullRequestStatus.Active,
|
||||
isDraft: false,
|
||||
};
|
||||
|
||||
const inputBaseUrl =
|
||||
'https://host.com/myOrg/_git/super-feature-repo/pullrequest';
|
||||
|
||||
const outputPullRequest: PullRequest = {
|
||||
pullRequestId: 7181,
|
||||
repoName: 'super-feature-repo',
|
||||
title: 'My Awesome New Feature',
|
||||
uniqueName: 'DOMAIN\\jdoe',
|
||||
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',
|
||||
};
|
||||
|
||||
expect(mappedPullRequest(inputPullRequest, inputBaseUrl)).toEqual(
|
||||
outputPullRequest,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('mappedBuildRun', () => {
|
||||
describe('mappedBuildRun happy path', () => {
|
||||
it('should return RepoBuild from Build', () => {
|
||||
const inputBuildDefinition: DefinitionReference = {
|
||||
name: 'My Build Definition',
|
||||
};
|
||||
|
||||
const inputLinks: any = {
|
||||
web: {
|
||||
href: 'https://host.com/myOrg/0bcc0c0d-2d02/_build/results?buildId=1',
|
||||
},
|
||||
};
|
||||
|
||||
const inputIdentityRef: IdentityRef = {
|
||||
displayName: 'Jane Doe',
|
||||
uniqueName: 'DOMAIN\\jdoe',
|
||||
};
|
||||
|
||||
const inputBuild: Build = {
|
||||
id: 1,
|
||||
buildNumber: 'Build-1',
|
||||
status: BuildStatus.Completed,
|
||||
result: BuildResult.Succeeded,
|
||||
queueTime: new Date('2020-09-12T06:10:23.932Z'),
|
||||
startTime: new Date('2020-09-12T06:15:23.932Z'),
|
||||
finishTime: new Date('2020-09-12T06:20:23.932Z'),
|
||||
sourceBranch: 'refs/heads/develop',
|
||||
sourceVersion: 'f4f78b3100b2923982bdf60c89c57ce6fd2d9a1c',
|
||||
definition: inputBuildDefinition,
|
||||
_links: inputLinks,
|
||||
requestedFor: inputIdentityRef,
|
||||
};
|
||||
|
||||
const outputRepoBuild: RepoBuild = {
|
||||
id: 1,
|
||||
title: 'My Build Definition - Build-1',
|
||||
link: 'https://host.com/myOrg/0bcc0c0d-2d02/_build/results?buildId=1',
|
||||
status: BuildStatus.Completed,
|
||||
result: BuildResult.Succeeded,
|
||||
queueTime: '2020-09-12T06:10:23.932Z',
|
||||
startTime: '2020-09-12T06:15:23.932Z',
|
||||
finishTime: '2020-09-12T06:20:23.932Z',
|
||||
source: 'refs/heads/develop (f4f78b31)',
|
||||
uniqueName: 'DOMAIN\\jdoe',
|
||||
};
|
||||
|
||||
expect(mappedBuildRun(inputBuild)).toEqual(outputRepoBuild);
|
||||
});
|
||||
});
|
||||
|
||||
describe('mappedBuildRun with no Build definition name', () => {
|
||||
it('should return RepoBuild with only Build Number for title', () => {
|
||||
const inputLinks: any = {
|
||||
web: {
|
||||
href: 'https://host.com/myOrg/0bcc0c0d-2d02/_build/results?buildId=1',
|
||||
},
|
||||
};
|
||||
|
||||
const inputIdentityRef: IdentityRef = {
|
||||
displayName: 'Jane Doe',
|
||||
uniqueName: 'DOMAIN\\jdoe',
|
||||
};
|
||||
|
||||
const inputBuild: Build = {
|
||||
id: 1,
|
||||
buildNumber: 'Build-1',
|
||||
status: BuildStatus.Completed,
|
||||
result: BuildResult.Succeeded,
|
||||
queueTime: new Date('2020-09-12T06:10:23.932Z'),
|
||||
startTime: new Date('2020-09-12T06:15:23.932Z'),
|
||||
finishTime: new Date('2020-09-12T06:20:23.932Z'),
|
||||
sourceBranch: 'refs/heads/develop',
|
||||
sourceVersion: 'f4f78b3100b2923982bdf60c89c57ce6fd2d9a1c',
|
||||
definition: undefined,
|
||||
_links: inputLinks,
|
||||
requestedFor: inputIdentityRef,
|
||||
};
|
||||
|
||||
const outputRepoBuild: RepoBuild = {
|
||||
id: 1,
|
||||
title: 'Build-1',
|
||||
link: 'https://host.com/myOrg/0bcc0c0d-2d02/_build/results?buildId=1',
|
||||
status: BuildStatus.Completed,
|
||||
result: BuildResult.Succeeded,
|
||||
queueTime: '2020-09-12T06:10:23.932Z',
|
||||
startTime: '2020-09-12T06:15:23.932Z',
|
||||
finishTime: '2020-09-12T06:20:23.932Z',
|
||||
source: 'refs/heads/develop (f4f78b31)',
|
||||
uniqueName: 'DOMAIN\\jdoe',
|
||||
};
|
||||
|
||||
expect(mappedBuildRun(inputBuild)).toEqual(outputRepoBuild);
|
||||
});
|
||||
});
|
||||
|
||||
describe('mappedBuildRun with undefined status', () => {
|
||||
it('should return BuildStatus of None for status', () => {
|
||||
const inputLinks: any = {
|
||||
web: {
|
||||
href: 'https://host.com/myOrg/0bcc0c0d-2d02/_build/results?buildId=1',
|
||||
},
|
||||
};
|
||||
|
||||
const inputIdentityRef: IdentityRef = {
|
||||
displayName: 'Jane Doe',
|
||||
uniqueName: 'DOMAIN\\jdoe',
|
||||
};
|
||||
|
||||
const inputBuild: Build = {
|
||||
id: 1,
|
||||
buildNumber: 'Build-1',
|
||||
status: undefined,
|
||||
result: BuildResult.Succeeded,
|
||||
queueTime: new Date('2020-09-12T06:10:23.932Z'),
|
||||
startTime: new Date('2020-09-12T06:15:23.932Z'),
|
||||
finishTime: new Date('2020-09-12T06:20:23.932Z'),
|
||||
sourceBranch: 'refs/heads/develop',
|
||||
sourceVersion: 'f4f78b3100b2923982bdf60c89c57ce6fd2d9a1c',
|
||||
definition: undefined,
|
||||
_links: inputLinks,
|
||||
requestedFor: inputIdentityRef,
|
||||
};
|
||||
|
||||
const outputRepoBuild: RepoBuild = {
|
||||
id: 1,
|
||||
title: 'Build-1',
|
||||
link: 'https://host.com/myOrg/0bcc0c0d-2d02/_build/results?buildId=1',
|
||||
status: BuildStatus.None,
|
||||
result: BuildResult.Succeeded,
|
||||
queueTime: '2020-09-12T06:10:23.932Z',
|
||||
startTime: '2020-09-12T06:15:23.932Z',
|
||||
finishTime: '2020-09-12T06:20:23.932Z',
|
||||
source: 'refs/heads/develop (f4f78b31)',
|
||||
uniqueName: 'DOMAIN\\jdoe',
|
||||
};
|
||||
|
||||
expect(mappedBuildRun(inputBuild)).toEqual(outputRepoBuild);
|
||||
});
|
||||
});
|
||||
|
||||
describe('mappedBuildRun with undefined result', () => {
|
||||
it('should return BuildResult of None for result', () => {
|
||||
const inputLinks: any = {
|
||||
web: {
|
||||
href: 'https://host.com/myOrg/0bcc0c0d-2d02/_build/results?buildId=1',
|
||||
},
|
||||
};
|
||||
|
||||
const inputIdentityRef: IdentityRef = {
|
||||
displayName: 'Jane Doe',
|
||||
uniqueName: 'DOMAIN\\jdoe',
|
||||
};
|
||||
|
||||
const inputBuild: Build = {
|
||||
id: 1,
|
||||
buildNumber: 'Build-1',
|
||||
status: BuildStatus.InProgress,
|
||||
result: undefined,
|
||||
queueTime: new Date('2020-09-12T06:10:23.932Z'),
|
||||
startTime: new Date('2020-09-12T06:15:23.932Z'),
|
||||
finishTime: new Date('2020-09-12T06:20:23.932Z'),
|
||||
sourceBranch: 'refs/heads/develop',
|
||||
sourceVersion: 'f4f78b3100b2923982bdf60c89c57ce6fd2d9a1c',
|
||||
definition: undefined,
|
||||
_links: inputLinks,
|
||||
requestedFor: inputIdentityRef,
|
||||
};
|
||||
|
||||
const outputRepoBuild: RepoBuild = {
|
||||
id: 1,
|
||||
title: 'Build-1',
|
||||
link: 'https://host.com/myOrg/0bcc0c0d-2d02/_build/results?buildId=1',
|
||||
status: BuildStatus.InProgress,
|
||||
result: BuildResult.None,
|
||||
queueTime: '2020-09-12T06:10:23.932Z',
|
||||
startTime: '2020-09-12T06:15:23.932Z',
|
||||
finishTime: '2020-09-12T06:20:23.932Z',
|
||||
source: 'refs/heads/develop (f4f78b31)',
|
||||
uniqueName: 'DOMAIN\\jdoe',
|
||||
};
|
||||
|
||||
expect(mappedBuildRun(inputBuild)).toEqual(outputRepoBuild);
|
||||
});
|
||||
});
|
||||
|
||||
describe('mappedBuildRun with undefined link', () => {
|
||||
it('should return empty string for link', () => {
|
||||
const inputIdentityRef: IdentityRef = {
|
||||
displayName: 'Jane Doe',
|
||||
uniqueName: 'DOMAIN\\jdoe',
|
||||
};
|
||||
|
||||
const inputBuild: Build = {
|
||||
id: 1,
|
||||
buildNumber: 'Build-1',
|
||||
status: BuildStatus.InProgress,
|
||||
result: undefined,
|
||||
queueTime: new Date('2020-09-12T06:10:23.932Z'),
|
||||
startTime: new Date('2020-09-12T06:15:23.932Z'),
|
||||
finishTime: new Date('2020-09-12T06:20:23.932Z'),
|
||||
sourceBranch: 'refs/heads/develop',
|
||||
sourceVersion: 'f4f78b3100b2923982bdf60c89c57ce6fd2d9a1c',
|
||||
definition: undefined,
|
||||
_links: undefined,
|
||||
requestedFor: inputIdentityRef,
|
||||
};
|
||||
|
||||
const outputRepoBuild: RepoBuild = {
|
||||
id: 1,
|
||||
title: 'Build-1',
|
||||
link: '',
|
||||
status: BuildStatus.InProgress,
|
||||
result: BuildResult.None,
|
||||
queueTime: '2020-09-12T06:10:23.932Z',
|
||||
startTime: '2020-09-12T06:15:23.932Z',
|
||||
finishTime: '2020-09-12T06:20:23.932Z',
|
||||
source: 'refs/heads/develop (f4f78b31)',
|
||||
uniqueName: 'DOMAIN\\jdoe',
|
||||
};
|
||||
|
||||
expect(mappedBuildRun(inputBuild)).toEqual(outputRepoBuild);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* Copyright 2023 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
RepoBuild,
|
||||
BuildStatus,
|
||||
BuildResult,
|
||||
GitTag,
|
||||
PullRequest,
|
||||
BuildRun,
|
||||
} from '@backstage/plugin-azure-devops-common';
|
||||
import { Build } from 'azure-devops-node-api/interfaces/BuildInterfaces';
|
||||
import {
|
||||
GitRef,
|
||||
GitPullRequest,
|
||||
} from 'azure-devops-node-api/interfaces/GitInterfaces';
|
||||
|
||||
export function mappedRepoBuild(build: Build): RepoBuild {
|
||||
return {
|
||||
id: build.id,
|
||||
title: [build.definition?.name, build.buildNumber]
|
||||
.filter(Boolean)
|
||||
.join(' - '),
|
||||
link: build._links?.web.href ?? '',
|
||||
status: build.status ?? BuildStatus.None,
|
||||
result: build.result ?? BuildResult.None,
|
||||
queueTime: build.queueTime?.toISOString(),
|
||||
startTime: build.startTime?.toISOString(),
|
||||
finishTime: build.finishTime?.toISOString(),
|
||||
source: `${build.sourceBranch} (${build.sourceVersion?.slice(0, 8)})`,
|
||||
uniqueName: build.requestedFor?.uniqueName ?? 'N/A',
|
||||
};
|
||||
}
|
||||
|
||||
export function mappedGitTag(
|
||||
gitRef: GitRef,
|
||||
linkBaseUrl: string,
|
||||
commitBaseUrl: string,
|
||||
): GitTag {
|
||||
return {
|
||||
objectId: gitRef.objectId,
|
||||
peeledObjectId: gitRef.peeledObjectId,
|
||||
name: gitRef.name?.replace('refs/tags/', ''),
|
||||
createdBy: gitRef.creator?.displayName ?? 'N/A',
|
||||
link: `${linkBaseUrl}${encodeURIComponent(
|
||||
gitRef.name?.replace('refs/tags/', '') ?? '',
|
||||
)}`,
|
||||
commitLink: `${commitBaseUrl}/${encodeURIComponent(
|
||||
gitRef.peeledObjectId ?? '',
|
||||
)}`,
|
||||
};
|
||||
}
|
||||
|
||||
export function mappedPullRequest(
|
||||
pullRequest: GitPullRequest,
|
||||
linkBaseUrl: string,
|
||||
): PullRequest {
|
||||
return {
|
||||
pullRequestId: pullRequest.pullRequestId,
|
||||
repoName: pullRequest.repository?.name,
|
||||
title: pullRequest.title,
|
||||
uniqueName: pullRequest.createdBy?.uniqueName ?? 'N/A',
|
||||
createdBy: pullRequest.createdBy?.displayName ?? 'N/A',
|
||||
creationDate: pullRequest.creationDate?.toISOString(),
|
||||
sourceRefName: pullRequest.sourceRefName,
|
||||
targetRefName: pullRequest.targetRefName,
|
||||
status: pullRequest.status,
|
||||
isDraft: pullRequest.isDraft,
|
||||
link: `${linkBaseUrl}/${pullRequest.pullRequestId}`,
|
||||
};
|
||||
}
|
||||
|
||||
export function mappedBuildRun(build: Build): BuildRun {
|
||||
return {
|
||||
id: build.id,
|
||||
title: [build.definition?.name, build.buildNumber]
|
||||
.filter(Boolean)
|
||||
.join(' - '),
|
||||
link: build._links?.web.href ?? '',
|
||||
status: build.status ?? BuildStatus.None,
|
||||
result: build.result ?? BuildResult.None,
|
||||
queueTime: build.queueTime?.toISOString(),
|
||||
startTime: build.startTime?.toISOString(),
|
||||
finishTime: build.finishTime?.toISOString(),
|
||||
source: `${build.sourceBranch} (${build.sourceVersion?.slice(0, 8)})`,
|
||||
uniqueName: build.requestedFor?.uniqueName ?? 'N/A',
|
||||
};
|
||||
}
|
||||
@@ -19,7 +19,6 @@ import {
|
||||
PullRequestOptions,
|
||||
PullRequestStatus,
|
||||
} from '@backstage/plugin-azure-devops-common';
|
||||
import { WebApi, getPersonalAccessTokenHandler } from 'azure-devops-node-api';
|
||||
|
||||
import { AzureDevOpsApi } from '../api';
|
||||
import { Config } from '@backstage/config';
|
||||
@@ -43,18 +42,11 @@ export interface RouterOptions {
|
||||
export async function createRouter(
|
||||
options: RouterOptions,
|
||||
): Promise<express.Router> {
|
||||
const { logger, reader } = options;
|
||||
const config = options.config.getConfig('azureDevOps');
|
||||
|
||||
const token = config.getString('token');
|
||||
const host = config.getString('host');
|
||||
const organization = config.getString('organization');
|
||||
|
||||
const authHandler = getPersonalAccessTokenHandler(token);
|
||||
const webApi = new WebApi(`https://${host}/${organization}`, authHandler);
|
||||
const { logger, reader, config } = options;
|
||||
|
||||
const azureDevOpsApi =
|
||||
options.azureDevOpsApi || new AzureDevOpsApi(logger, webApi, reader);
|
||||
options.azureDevOpsApi ||
|
||||
AzureDevOpsApi.fromConfig(config, { logger, urlReader: reader });
|
||||
|
||||
const pullRequestsDashboardProvider =
|
||||
await PullRequestsDashboardProvider.create(logger, azureDevOpsApi);
|
||||
@@ -195,6 +187,8 @@ export async function createRouter(
|
||||
});
|
||||
|
||||
router.get('/readme/:projectName/:repoName', async (req, res) => {
|
||||
const host = config.getString('azureDevOps.host');
|
||||
const organization = config.getString('azureDevOps.organization');
|
||||
const { projectName, repoName } = req.params;
|
||||
const readme = await azureDevOpsApi.getReadme(
|
||||
host,
|
||||
|
||||
@@ -5051,6 +5051,7 @@ __metadata:
|
||||
"@backstage/backend-plugin-api": "workspace:^"
|
||||
"@backstage/cli": "workspace:^"
|
||||
"@backstage/config": "workspace:^"
|
||||
"@backstage/integration": "workspace:^"
|
||||
"@backstage/plugin-azure-devops-common": "workspace:^"
|
||||
"@types/express": ^4.17.6
|
||||
"@types/supertest": ^2.0.8
|
||||
|
||||
Reference in New Issue
Block a user