diff --git a/.changeset/old-months-attack.md b/.changeset/old-months-attack.md
new file mode 100644
index 0000000000..75a81e0c27
--- /dev/null
+++ b/.changeset/old-months-attack.md
@@ -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).
diff --git a/packages/app/src/components/catalog/EntityPage.tsx b/packages/app/src/components/catalog/EntityPage.tsx
index 8af61062a3..ba0eacf97e 100644
--- a/packages/app/src/components/catalog/EntityPage.tsx
+++ b/packages/app/src/components/catalog/EntityPage.tsx
@@ -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 = (
-
+
diff --git a/plugins/azure-devops/README.md b/plugins/azure-devops/README.md
index 7b39dae443..e4b81fe97c 100644
--- a/plugins/azure-devops/README.md
+++ b/plugins/azure-devops/README.md
@@ -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:
+dev.azure.com/build-definition:
+```
+
+In this case `` will be the name of your Team Project and `` 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 = (
-
- // ...
-
-
-
- // ...
-
- ```
+ ```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 = (
+
+ // ...
+
+
+
+ // ...
+
+ ```
+
+ 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 = (
+
+ // ...
+
+
+
+ // ...
+
+ ```
**Notes:**
diff --git a/plugins/azure-devops/api-report.md b/plugins/azure-devops/api-report.md
index 11e238e6f5..eaba90664b 100644
--- a/plugins/azure-devops/api-report.md
+++ b/plugins/azure-devops/api-report.md
@@ -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)
diff --git a/plugins/azure-devops/src/api/AzureDevOpsApi.ts b/plugins/azure-devops/src/api/AzureDevOpsApi.ts
index 89eec1bbbc..09b48356c8 100644
--- a/plugins/azure-devops/src/api/AzureDevOpsApi.ts
+++ b/plugins/azure-devops/src/api/AzureDevOpsApi.ts
@@ -15,6 +15,8 @@
*/
import {
+ BuildRun,
+ BuildRunOptions,
DashboardPullRequest,
PullRequest,
PullRequestOptions,
@@ -49,4 +51,11 @@ export interface AzureDevOpsApi {
getAllTeams(): Promise;
getUserTeamIds(userId: string): Promise;
+
+ getBuildRuns(
+ projectName: string,
+ repoName?: string,
+ definitionName?: string,
+ options?: BuildRunOptions,
+ ): Promise<{ items: BuildRun[] }>;
}
diff --git a/plugins/azure-devops/src/api/AzureDevOpsClient.ts b/plugins/azure-devops/src/api/AzureDevOpsClient.ts
index 84ade8800d..96c10a7b2e 100644
--- a/plugins/azure-devops/src/api/AzureDevOpsClient.ts
+++ b/plugins/azure-devops/src/api/AzureDevOpsClient.ts
@@ -15,6 +15,8 @@
*/
import {
+ BuildRun,
+ BuildRunOptions,
DashboardPullRequest,
PullRequest,
PullRequestOptions,
@@ -92,6 +94,29 @@ export class AzureDevOpsClient implements AzureDevOpsApi {
return this.get(`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(urlSegment);
+ return { items };
+ }
+
private async get(path: string): Promise {
const baseUrl = `${await this.discoveryApi.getBaseUrl('azure-devops')}/`;
const url = new URL(path, baseUrl);
diff --git a/plugins/azure-devops/src/components/BuildTable/BuildTable.stories.tsx b/plugins/azure-devops/src/components/BuildTable/BuildTable.stories.tsx
index 84980b0b51..63d646b06a 100644
--- a/plugins/azure-devops/src/components/BuildTable/BuildTable.stories.tsx
+++ b/plugins/azure-devops/src/components/BuildTable/BuildTable.stories.tsx
@@ -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 = () => (
diff --git a/plugins/azure-devops/src/components/BuildTable/BuildTable.tsx b/plugins/azure-devops/src/components/BuildTable/BuildTable.tsx
index 463bf15d78..68bf4102fc 100644
--- a/plugins/azure-devops/src/components/BuildTable/BuildTable.tsx
+++ b/plugins/azure-devops/src/components/BuildTable/BuildTable.tsx
@@ -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) => (
+ render: (row: Partial) => (
{row.title}
),
},
@@ -138,7 +138,7 @@ const columns: TableColumn[] = [
{
title: 'State',
width: 'auto',
- render: (row: Partial) => (
+ render: (row: Partial) => (
{getBuildStateComponent(row.status, row.result)}
@@ -150,7 +150,7 @@ const columns: TableColumn[] = [
title: 'Duration',
field: 'queueTime',
width: 'auto',
- render: (row: Partial) => (
+ render: (row: Partial) => (
{getDurationFromDates(row.startTime, row.finishTime)}
@@ -162,7 +162,7 @@ const columns: TableColumn[] = [
title: 'Age',
field: 'queueTime',
width: 'auto',
- render: (row: Partial) =>
+ render: (row: Partial) =>
(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;
};
diff --git a/plugins/azure-devops/src/components/EntityPageAzurePipelines/EntityPageAzurePipelines.tsx b/plugins/azure-devops/src/components/EntityPageAzurePipelines/EntityPageAzurePipelines.tsx
index c993922105..3e8babdd25 100644
--- a/plugins/azure-devops/src/components/EntityPageAzurePipelines/EntityPageAzurePipelines.tsx
+++ b/plugins/azure-devops/src/components/EntityPageAzurePipelines/EntityPageAzurePipelines.tsx
@@ -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 ;
};
diff --git a/plugins/azure-devops/src/constants.ts b/plugins/azure-devops/src/constants.ts
index d6e67ff7ed..95e5be4930 100644
--- a/plugins/azure-devops/src/constants.ts
+++ b/plugins/azure-devops/src/constants.ts
@@ -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;
diff --git a/plugins/azure-devops/src/hooks/useBuildRuns.ts b/plugins/azure-devops/src/hooks/useBuildRuns.ts
new file mode 100644
index 0000000000..ae0273de44
--- /dev/null
+++ b/plugins/azure-devops/src/hooks/useBuildRuns.ts
@@ -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,
+ };
+}
diff --git a/plugins/azure-devops/src/hooks/useProjectRepoFromEntity.ts b/plugins/azure-devops/src/hooks/useProjectRepoFromEntity.ts
index 418badc630..b484bca009 100644
--- a/plugins/azure-devops/src/hooks/useProjectRepoFromEntity.ts
+++ b/plugins/azure-devops/src/hooks/useProjectRepoFromEntity.ts
@@ -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) {
diff --git a/plugins/azure-devops/src/index.ts b/plugins/azure-devops/src/index.ts
index 8289a93a5c..aa052975de 100644
--- a/plugins/azure-devops/src/index.ts
+++ b/plugins/azure-devops/src/index.ts
@@ -19,6 +19,7 @@ export {
EntityAzurePipelinesContent,
EntityAzurePullRequestsContent,
isAzureDevOpsAvailable,
+ isAzurePipelinesAvailable,
AzurePullRequestsPage,
} from './plugin';
diff --git a/plugins/azure-devops/src/plugin.ts b/plugins/azure-devops/src/plugin.ts
index 003c7b7eee..c2ecb7457a 100644
--- a/plugins/azure-devops/src/plugin.ts
+++ b/plugins/azure-devops/src/plugin.ts
@@ -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',
diff --git a/plugins/azure-devops/src/utils/getAnnotationFromEntity.ts b/plugins/azure-devops/src/utils/getAnnotationFromEntity.ts
new file mode 100644
index 0000000000..41cc1e6628
--- /dev/null
+++ b/plugins/azure-devops/src/utils/getAnnotationFromEntity.ts
@@ -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: /',
+ );
+ }
+
+ return { project, repo };
+}
diff --git a/plugins/azure-devops/src/utils/index.ts b/plugins/azure-devops/src/utils/index.ts
index f2214e8106..8655b261c3 100644
--- a/plugins/azure-devops/src/utils/index.ts
+++ b/plugins/azure-devops/src/utils/index.ts
@@ -17,3 +17,4 @@
export * from './arrayHas';
export * from './equalsIgnoreCase';
export * from './getDurationFromDates';
+export * from './getAnnotationFromEntity';