Merge pull request #8716 from awanlin/topic/use-definition-name
Updated to add definition name support
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-azure-devops': patch
|
||||
---
|
||||
|
||||
Updated to support cases where only Azure Pipelines to see Builds. You can use this new feature by adding the `dev.azure.com/project` and `dev.azure.com/build-definition` annotations to your `catalog-info.yaml` files. The Azure DevOps plugin [README has more detailed instructions](https://github.com/backstage/backstage/tree/master/plugins/azure-devops#setup).
|
||||
@@ -37,6 +37,7 @@ import {
|
||||
EntityAzurePipelinesContent,
|
||||
EntityAzurePullRequestsContent,
|
||||
isAzureDevOpsAvailable,
|
||||
isAzurePipelinesAvailable,
|
||||
} from '@backstage/plugin-azure-devops';
|
||||
import { EntityBadgesDialog } from '@backstage/plugin-badges';
|
||||
import {
|
||||
@@ -202,7 +203,7 @@ export const cicdContent = (
|
||||
<EntityGithubActionsContent />
|
||||
</EntitySwitch.Case>
|
||||
|
||||
<EntitySwitch.Case if={isAzureDevOpsAvailable}>
|
||||
<EntitySwitch.Case if={isAzurePipelinesAvailable}>
|
||||
<EntityAzurePipelinesContent defaultLimit={25} />
|
||||
</EntitySwitch.Case>
|
||||
|
||||
|
||||
@@ -49,6 +49,17 @@ spec:
|
||||
# ...
|
||||
```
|
||||
|
||||
#### Azure Pipelines Only
|
||||
|
||||
If you are only using Azure Pipelines along with a different SCM tool then you can use the following two annotations to see Builds:
|
||||
|
||||
```yaml
|
||||
dev.azure.com/project: <project-name>
|
||||
dev.azure.com/build-definition: <build-definition-name>
|
||||
```
|
||||
|
||||
In this case `<project-name>` will be the name of your Team Project and `<build-definition-name>` will be the name of the Build Definition you would like to see Builds for. If the Build Definition name has spaces in it make sure to put quotes around it
|
||||
|
||||
### Azure Pipelines Component
|
||||
|
||||
To get the Azure Pipelines component working you'll need to do the following two steps:
|
||||
@@ -61,25 +72,47 @@ To get the Azure Pipelines component working you'll need to do the following two
|
||||
yarn add @backstage/plugin-azure-devops
|
||||
```
|
||||
|
||||
2. Second we need to add the `EntityAzurePipelinesContent` extension to the entity page in your app:
|
||||
2. Second we need to add the `EntityAzurePipelinesContent` extension to the entity page in your app. How to do this will depend on which annotation you are using in your entities:
|
||||
|
||||
```tsx
|
||||
// In packages/app/src/components/catalog/EntityPage.tsx
|
||||
import {
|
||||
EntityAzurePipelinesContent,
|
||||
isAzureDevOpsAvailable,
|
||||
} from '@backstage/plugin-azure-devops';
|
||||
1. If you are using the `dev.azure.com/project-repo` annotation then you'll want to do the following:
|
||||
|
||||
// For example in the CI/CD section
|
||||
const cicdContent = (
|
||||
<EntitySwitch>
|
||||
// ...
|
||||
<EntitySwitch.Case if={isAzureDevOpsAvailable}>
|
||||
<EntityAzurePipelinesContent defaultLimit={25} />
|
||||
</EntitySwitch.Case>
|
||||
// ...
|
||||
</EntitySwitch>
|
||||
```
|
||||
```tsx
|
||||
// In packages/app/src/components/catalog/EntityPage.tsx
|
||||
import {
|
||||
EntityAzurePipelinesContent,
|
||||
isAzureDevOpsAvailable,
|
||||
} from '@backstage/plugin-azure-devops';
|
||||
|
||||
// For example in the CI/CD section
|
||||
const cicdContent = (
|
||||
<EntitySwitch>
|
||||
// ...
|
||||
<EntitySwitch.Case if={isAzureDevOpsAvailable}>
|
||||
<EntityAzurePipelinesContent defaultLimit={25} />
|
||||
</EntitySwitch.Case>
|
||||
// ...
|
||||
</EntitySwitch>
|
||||
```
|
||||
|
||||
2. If you are using the ``dev.azure.com/project` and `dev.azure.com/build-definition` annotations then you'll want to do this:
|
||||
|
||||
```tsx
|
||||
// In packages/app/src/components/catalog/EntityPage.tsx
|
||||
import {
|
||||
EntityAzurePipelinesContent,
|
||||
isAzurePipelinesAvailable,
|
||||
} from '@backstage/plugin-azure-devops';
|
||||
|
||||
// For example in the CI/CD section
|
||||
const cicdContent = (
|
||||
<EntitySwitch>
|
||||
// ...
|
||||
<EntitySwitch.Case if={isAzurePipelinesAvailable}>
|
||||
<EntityAzurePipelinesContent defaultLimit={25} />
|
||||
</EntitySwitch.Case>
|
||||
// ...
|
||||
</EntitySwitch>
|
||||
```
|
||||
|
||||
**Notes:**
|
||||
|
||||
|
||||
@@ -199,6 +199,11 @@ export enum FilterType {
|
||||
// @public (undocumented)
|
||||
export const isAzureDevOpsAvailable: (entity: Entity) => boolean;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "isAzurePipelinesAvailable" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export const isAzurePipelinesAvailable: (entity: Entity) => boolean;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "PullRequestColumnConfig" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
BuildRun,
|
||||
BuildRunOptions,
|
||||
DashboardPullRequest,
|
||||
PullRequest,
|
||||
PullRequestOptions,
|
||||
@@ -49,4 +51,11 @@ export interface AzureDevOpsApi {
|
||||
getAllTeams(): Promise<Team[]>;
|
||||
|
||||
getUserTeamIds(userId: string): Promise<string[]>;
|
||||
|
||||
getBuildRuns(
|
||||
projectName: string,
|
||||
repoName?: string,
|
||||
definitionName?: string,
|
||||
options?: BuildRunOptions,
|
||||
): Promise<{ items: BuildRun[] }>;
|
||||
}
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
BuildRun,
|
||||
BuildRunOptions,
|
||||
DashboardPullRequest,
|
||||
PullRequest,
|
||||
PullRequestOptions,
|
||||
@@ -92,6 +94,29 @@ export class AzureDevOpsClient implements AzureDevOpsApi {
|
||||
return this.get<string[]>(`users/${userId}/team-ids`);
|
||||
}
|
||||
|
||||
public async getBuildRuns(
|
||||
projectName: string,
|
||||
repoName?: string,
|
||||
definitionName?: string,
|
||||
options?: BuildRunOptions,
|
||||
): Promise<{ items: BuildRun[] }> {
|
||||
const queryString = new URLSearchParams();
|
||||
if (repoName) {
|
||||
queryString.append('repoName', repoName);
|
||||
}
|
||||
if (definitionName) {
|
||||
queryString.append('definitionName', definitionName);
|
||||
}
|
||||
if (options?.top) {
|
||||
queryString.append('top', options.top.toString());
|
||||
}
|
||||
const urlSegment = `builds/${encodeURIComponent(
|
||||
projectName,
|
||||
)}?${queryString}`;
|
||||
const items = await this.get<BuildRun[]>(urlSegment);
|
||||
return { items };
|
||||
}
|
||||
|
||||
private async get<T>(path: string): Promise<T> {
|
||||
const baseUrl = `${await this.discoveryApi.getBaseUrl('azure-devops')}/`;
|
||||
const url = new URL(path, baseUrl);
|
||||
|
||||
@@ -16,8 +16,8 @@
|
||||
|
||||
import {
|
||||
BuildResult,
|
||||
BuildRun,
|
||||
BuildStatus,
|
||||
RepoBuild,
|
||||
} from '@backstage/plugin-azure-devops-common';
|
||||
|
||||
import { BuildTable } from './BuildTable';
|
||||
@@ -42,8 +42,8 @@ const buildStatuses: Array<[BuildStatus, BuildResult]> = [
|
||||
[BuildStatus.None, BuildResult.None], // Unknown
|
||||
];
|
||||
|
||||
const generateTestData = (rows = 10): RepoBuild[] => {
|
||||
const repoBuilds: RepoBuild[] = [];
|
||||
const generateTestData = (rows = 10): BuildRun[] => {
|
||||
const buildRuns: BuildRun[] = [];
|
||||
|
||||
for (let i = 0; i < rows; i++) {
|
||||
const [status, result] = buildStatuses[i] ?? [
|
||||
@@ -51,7 +51,7 @@ const generateTestData = (rows = 10): RepoBuild[] => {
|
||||
BuildResult.Succeeded,
|
||||
];
|
||||
|
||||
repoBuilds.push({
|
||||
buildRuns.push({
|
||||
id: rows - i + 12534,
|
||||
title: `backstage ci - 1.0.0-preview-${rows - i}`,
|
||||
status,
|
||||
@@ -62,7 +62,7 @@ const generateTestData = (rows = 10): RepoBuild[] => {
|
||||
});
|
||||
}
|
||||
|
||||
return repoBuilds;
|
||||
return buildRuns;
|
||||
};
|
||||
|
||||
export const Default = () => (
|
||||
|
||||
@@ -17,8 +17,8 @@
|
||||
import { Box, Typography } from '@material-ui/core';
|
||||
import {
|
||||
BuildResult,
|
||||
BuildRun,
|
||||
BuildStatus,
|
||||
RepoBuild,
|
||||
} from '@backstage/plugin-azure-devops-common';
|
||||
import {
|
||||
Link,
|
||||
@@ -126,7 +126,7 @@ const columns: TableColumn[] = [
|
||||
title: 'Build',
|
||||
field: 'title',
|
||||
width: 'auto',
|
||||
render: (row: Partial<RepoBuild>) => (
|
||||
render: (row: Partial<BuildRun>) => (
|
||||
<Link to={row.link || ''}>{row.title}</Link>
|
||||
),
|
||||
},
|
||||
@@ -138,7 +138,7 @@ const columns: TableColumn[] = [
|
||||
{
|
||||
title: 'State',
|
||||
width: 'auto',
|
||||
render: (row: Partial<RepoBuild>) => (
|
||||
render: (row: Partial<BuildRun>) => (
|
||||
<Box display="flex" alignItems="center">
|
||||
<Typography variant="button">
|
||||
{getBuildStateComponent(row.status, row.result)}
|
||||
@@ -150,7 +150,7 @@ const columns: TableColumn[] = [
|
||||
title: 'Duration',
|
||||
field: 'queueTime',
|
||||
width: 'auto',
|
||||
render: (row: Partial<RepoBuild>) => (
|
||||
render: (row: Partial<BuildRun>) => (
|
||||
<Box display="flex" alignItems="center">
|
||||
<Typography>
|
||||
{getDurationFromDates(row.startTime, row.finishTime)}
|
||||
@@ -162,7 +162,7 @@ const columns: TableColumn[] = [
|
||||
title: 'Age',
|
||||
field: 'queueTime',
|
||||
width: 'auto',
|
||||
render: (row: Partial<RepoBuild>) =>
|
||||
render: (row: Partial<BuildRun>) =>
|
||||
(row.queueTime
|
||||
? DateTime.fromISO(row.queueTime)
|
||||
: DateTime.now()
|
||||
@@ -171,7 +171,7 @@ const columns: TableColumn[] = [
|
||||
];
|
||||
|
||||
type BuildTableProps = {
|
||||
items?: RepoBuild[];
|
||||
items?: BuildRun[];
|
||||
loading: boolean;
|
||||
error?: Error;
|
||||
};
|
||||
|
||||
+13
-4
@@ -14,10 +14,11 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { useEntity } from '@backstage/plugin-catalog-react';
|
||||
import React from 'react';
|
||||
import { useRepoBuilds } from '../../hooks/useRepoBuilds';
|
||||
import { BuildTable } from '../BuildTable/BuildTable';
|
||||
import React from 'react';
|
||||
import { getAnnotationFromEntity } from '../../utils/getAnnotationFromEntity';
|
||||
import { useBuildRuns } from '../../hooks/useBuildRuns';
|
||||
import { useEntity } from '@backstage/plugin-catalog-react';
|
||||
|
||||
export const EntityPageAzurePipelines = ({
|
||||
defaultLimit,
|
||||
@@ -25,7 +26,15 @@ export const EntityPageAzurePipelines = ({
|
||||
defaultLimit?: number;
|
||||
}) => {
|
||||
const { entity } = useEntity();
|
||||
const { items, loading, error } = useRepoBuilds(entity, defaultLimit);
|
||||
|
||||
const { project, repo, definition } = getAnnotationFromEntity(entity);
|
||||
|
||||
const { items, loading, error } = useBuildRuns(
|
||||
project,
|
||||
defaultLimit,
|
||||
repo,
|
||||
definition,
|
||||
);
|
||||
|
||||
return <BuildTable items={items} loading={loading} error={error} />;
|
||||
};
|
||||
|
||||
@@ -14,5 +14,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export const AZURE_DEVOPS_ANNOTATION = 'dev.azure.com/project-repo';
|
||||
export const AZURE_DEVOPS_BUILD_DEFINITION_ANNOTATION =
|
||||
'dev.azure.com/build-definition';
|
||||
export const AZURE_DEVOPS_PROJECT_ANNOTATION = 'dev.azure.com/project';
|
||||
export const AZURE_DEVOPS_REPO_ANNOTATION = 'dev.azure.com/project-repo';
|
||||
export const AZURE_DEVOPS_DEFAULT_TOP: number = 10;
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* 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 {
|
||||
BuildRun,
|
||||
BuildRunOptions,
|
||||
} from '@backstage/plugin-azure-devops-common';
|
||||
|
||||
import { AZURE_DEVOPS_DEFAULT_TOP } from '../constants';
|
||||
import { azureDevOpsApiRef } from '../api';
|
||||
import { useApi } from '@backstage/core-plugin-api';
|
||||
import useAsync from 'react-use/lib/useAsync';
|
||||
|
||||
export function useBuildRuns(
|
||||
projectName: string,
|
||||
defaultLimit?: number,
|
||||
repoName?: string,
|
||||
definitionName?: string,
|
||||
): {
|
||||
items?: BuildRun[];
|
||||
loading: boolean;
|
||||
error?: Error;
|
||||
} {
|
||||
const top = defaultLimit ?? AZURE_DEVOPS_DEFAULT_TOP;
|
||||
const options: BuildRunOptions = {
|
||||
top: top,
|
||||
};
|
||||
|
||||
const api = useApi(azureDevOpsApiRef);
|
||||
|
||||
const { value, loading, error } = useAsync(() => {
|
||||
return api.getBuildRuns(projectName, repoName, definitionName, options);
|
||||
}, [api, projectName, repoName, definitionName]);
|
||||
|
||||
return {
|
||||
items: value?.items,
|
||||
loading,
|
||||
error,
|
||||
};
|
||||
}
|
||||
@@ -15,14 +15,14 @@
|
||||
*/
|
||||
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { AZURE_DEVOPS_ANNOTATION } from '../constants';
|
||||
import { AZURE_DEVOPS_REPO_ANNOTATION } from '../constants';
|
||||
|
||||
export function useProjectRepoFromEntity(entity: Entity): {
|
||||
project: string;
|
||||
repo: string;
|
||||
} {
|
||||
const [project, repo] = (
|
||||
entity.metadata.annotations?.[AZURE_DEVOPS_ANNOTATION] ?? ''
|
||||
entity.metadata.annotations?.[AZURE_DEVOPS_REPO_ANNOTATION] ?? ''
|
||||
).split('/');
|
||||
|
||||
if (!project && !repo) {
|
||||
|
||||
@@ -19,6 +19,7 @@ export {
|
||||
EntityAzurePipelinesContent,
|
||||
EntityAzurePullRequestsContent,
|
||||
isAzureDevOpsAvailable,
|
||||
isAzurePipelinesAvailable,
|
||||
AzurePullRequestsPage,
|
||||
} from './plugin';
|
||||
|
||||
|
||||
@@ -14,6 +14,11 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
AZURE_DEVOPS_BUILD_DEFINITION_ANNOTATION,
|
||||
AZURE_DEVOPS_PROJECT_ANNOTATION,
|
||||
AZURE_DEVOPS_REPO_ANNOTATION,
|
||||
} from './constants';
|
||||
import {
|
||||
azurePipelinesEntityContentRouteRef,
|
||||
azurePullRequestDashboardRouteRef,
|
||||
@@ -27,13 +32,19 @@ import {
|
||||
identityApiRef,
|
||||
} from '@backstage/core-plugin-api';
|
||||
|
||||
import { AZURE_DEVOPS_ANNOTATION } from './constants';
|
||||
import { AzureDevOpsClient } from './api/AzureDevOpsClient';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { azureDevOpsApiRef } from './api/AzureDevOpsApi';
|
||||
|
||||
export const isAzureDevOpsAvailable = (entity: Entity) =>
|
||||
Boolean(entity.metadata.annotations?.[AZURE_DEVOPS_ANNOTATION]);
|
||||
Boolean(entity.metadata.annotations?.[AZURE_DEVOPS_REPO_ANNOTATION]);
|
||||
|
||||
export const isAzurePipelinesAvailable = (entity: Entity) =>
|
||||
Boolean(entity.metadata.annotations?.[AZURE_DEVOPS_REPO_ANNOTATION]) ||
|
||||
(Boolean(entity.metadata.annotations?.[AZURE_DEVOPS_PROJECT_ANNOTATION]) &&
|
||||
Boolean(
|
||||
entity.metadata.annotations?.[AZURE_DEVOPS_BUILD_DEFINITION_ANNOTATION],
|
||||
));
|
||||
|
||||
export const azureDevOpsPlugin = createPlugin({
|
||||
id: 'azureDevOps',
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* 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 {
|
||||
AZURE_DEVOPS_BUILD_DEFINITION_ANNOTATION,
|
||||
AZURE_DEVOPS_PROJECT_ANNOTATION,
|
||||
AZURE_DEVOPS_REPO_ANNOTATION,
|
||||
} from '../constants';
|
||||
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
|
||||
export function getAnnotationFromEntity(entity: Entity): {
|
||||
project: string;
|
||||
repo?: string;
|
||||
definition?: string;
|
||||
} {
|
||||
const annotation =
|
||||
entity.metadata.annotations?.[AZURE_DEVOPS_REPO_ANNOTATION];
|
||||
if (annotation) {
|
||||
const { project, repo } = getProjectRepo(annotation);
|
||||
const definition = undefined;
|
||||
return { project, repo, definition };
|
||||
}
|
||||
|
||||
const project =
|
||||
entity.metadata.annotations?.[AZURE_DEVOPS_PROJECT_ANNOTATION];
|
||||
if (!project) {
|
||||
throw new Error('Value for annotation dev.azure.com/project was not found');
|
||||
}
|
||||
|
||||
const definition =
|
||||
entity.metadata.annotations?.[AZURE_DEVOPS_BUILD_DEFINITION_ANNOTATION];
|
||||
if (!definition) {
|
||||
throw new Error(
|
||||
'Value for annotation dev.azure.com/build-definition was not found',
|
||||
);
|
||||
}
|
||||
|
||||
const repo = undefined;
|
||||
return { project, repo, definition };
|
||||
}
|
||||
|
||||
function getProjectRepo(annotation: string): {
|
||||
project: string;
|
||||
repo: string;
|
||||
} {
|
||||
const [project, repo] = annotation.split('/');
|
||||
|
||||
if (!project && !repo) {
|
||||
throw new Error(
|
||||
'Value for annotation dev.azure.com/project-repo was not in the correct format: <project-name>/<repo-name>',
|
||||
);
|
||||
}
|
||||
|
||||
return { project, repo };
|
||||
}
|
||||
@@ -17,3 +17,4 @@
|
||||
export * from './arrayHas';
|
||||
export * from './equalsIgnoreCase';
|
||||
export * from './getDurationFromDates';
|
||||
export * from './getAnnotationFromEntity';
|
||||
|
||||
Reference in New Issue
Block a user