From 0f104ecc4dd0c715c02c4fad5962e6b430a9703d Mon Sep 17 00:00:00 2001 From: Andre Wanlin Date: Fri, 31 Dec 2021 13:11:46 -0600 Subject: [PATCH 1/5] Updated to add definition name support Signed-off-by: Andre Wanlin --- .changeset/old-months-attack.md | 5 + .../app/src/components/catalog/EntityPage.tsx | 3 +- plugins/azure-devops/README.md | 10 ++ plugins/azure-devops/api-report.md | 5 + .../azure-devops/src/api/AzureDevOpsApi.ts | 9 ++ .../azure-devops/src/api/AzureDevOpsClient.ts | 25 +++++ .../BuildTable/BuildTable.stories.tsx | 10 +- .../src/components/BuildTable/BuildTable.tsx | 12 +-- .../EntityPageAzurePipelines.tsx | 17 +++- plugins/azure-devops/src/constants.ts | 4 +- .../src/hooks/useAnnotationFromEntity.ts | 95 +++++++++++++++++++ .../azure-devops/src/hooks/useBuildRuns.ts | 53 +++++++++++ .../src/hooks/useProjectRepoFromEntity.ts | 4 +- plugins/azure-devops/src/index.ts | 1 + plugins/azure-devops/src/plugin.ts | 11 ++- 15 files changed, 243 insertions(+), 21 deletions(-) create mode 100644 .changeset/old-months-attack.md create mode 100644 plugins/azure-devops/src/hooks/useAnnotationFromEntity.ts create mode 100644 plugins/azure-devops/src/hooks/useBuildRuns.ts diff --git a/.changeset/old-months-attack.md b/.changeset/old-months-attack.md new file mode 100644 index 0000000000..c97d7d8f46 --- /dev/null +++ b/.changeset/old-months-attack.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-azure-devops': minor +--- + +Updated to support cases where only Azure Pipelines are being used by adding an annotation that get builds by definition name 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..db1af06a01 100644 --- a/plugins/azure-devops/README.md +++ b/plugins/azure-devops/README.md @@ -49,6 +49,16 @@ spec: # ... ``` +#### Azure Pipelines Only + +If you are only using Azure Pipelines along with a different SCM tool then you can use the following annotation to see Builds: + +```yaml +dev.azure.com/project-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: 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..b727ce7dc9 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..688e5aa426 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, @@ -91,6 +93,29 @@ export class AzureDevOpsClient implements AzureDevOpsApi { public getUserTeamIds(userId: string): Promise { 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')}/`; 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..5957e82eef 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 { useAnnotationFromEntity } from '../../hooks/useAnnotationFromEntity'; +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 } = useAnnotationFromEntity(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..8a920b59e2 100644 --- a/plugins/azure-devops/src/constants.ts +++ b/plugins/azure-devops/src/constants.ts @@ -14,5 +14,7 @@ * limitations under the License. */ -export const AZURE_DEVOPS_ANNOTATION = 'dev.azure.com/project-repo'; +export const AZURE_DEVOPS_REPO_ANNOTATION = 'dev.azure.com/project-repo'; +export const AZURE_DEVOPS_DEFINITION_ANNOTATION = + 'dev.azure.com/project-definition'; export const AZURE_DEVOPS_DEFAULT_TOP: number = 10; diff --git a/plugins/azure-devops/src/hooks/useAnnotationFromEntity.ts b/plugins/azure-devops/src/hooks/useAnnotationFromEntity.ts new file mode 100644 index 0000000000..06e3f58549 --- /dev/null +++ b/plugins/azure-devops/src/hooks/useAnnotationFromEntity.ts @@ -0,0 +1,95 @@ +/* + * 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 { Entity } from '@backstage/catalog-model'; +import { + AZURE_DEVOPS_DEFINITION_ANNOTATION, + AZURE_DEVOPS_REPO_ANNOTATION, +} from '../constants'; + +export function useAnnotationFromEntity(entity: Entity): { + project: string; + repo?: string; + definition?: string; +} { + if (entity.metadata.annotations?.[AZURE_DEVOPS_DEFINITION_ANNOTATION]) { + const { project, definition } = getProjectDefinition( + entity.metadata.annotations?.[AZURE_DEVOPS_DEFINITION_ANNOTATION], + ); + const repo = undefined; + return { project, repo, definition }; + } + + const { project, repo } = getProjectRepo( + entity.metadata.annotations?.[AZURE_DEVOPS_REPO_ANNOTATION] ?? '', + ); + const definition = undefined; + return { project, repo, definition }; +} + +function getProjectDefinition(annotation: string): { + project: string; + definition: string; +} { + const [project, definition] = annotation.split('/'); + + if (!project && !definition) { + throw new Error( + 'Value for annotation dev.azure.com/project-definition was not in the correct format: /', + ); + } + + if (!project) { + throw new Error( + 'Project Name for annotation dev.azure.com/project-definition was not found; expected format is: /', + ); + } + + if (!definition) { + throw new Error( + 'Definition Name for annotation dev.azure.com/project-definition was not found; expected format is: /', + ); + } + + return { project, 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: /', + ); + } + + if (!project) { + throw new Error( + 'Project Name for annotation dev.azure.com/project-repo was not found; expected format is: /', + ); + } + + if (!repo) { + throw new Error( + 'Repo Name for annotation dev.azure.com/project-repo was not found; expected format is: /', + ); + } + + return { project, repo }; +} diff --git a/plugins/azure-devops/src/hooks/useBuildRuns.ts b/plugins/azure-devops/src/hooks/useBuildRuns.ts new file mode 100644 index 0000000000..f19ebaf23f --- /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'; + +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..d22dff2863 100644 --- a/plugins/azure-devops/src/plugin.ts +++ b/plugins/azure-devops/src/plugin.ts @@ -14,6 +14,10 @@ * limitations under the License. */ +import { + AZURE_DEVOPS_DEFINITION_ANNOTATION, + AZURE_DEVOPS_REPO_ANNOTATION, +} from './constants'; import { azurePipelinesEntityContentRouteRef, azurePullRequestDashboardRouteRef, @@ -27,13 +31,16 @@ 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_DEFINITION_ANNOTATION]); export const azureDevOpsPlugin = createPlugin({ id: 'azureDevOps', From 539d686c937d3d9a02760289a30124f09aea4d84 Mon Sep 17 00:00:00 2001 From: Andre Wanlin Date: Fri, 7 Jan 2022 09:40:54 -0600 Subject: [PATCH 2/5] Changes based on feedback Signed-off-by: Andre Wanlin --- plugins/azure-devops/README.md | 56 +++++++++++++------ .../EntityPageAzurePipelines.tsx | 4 +- .../getAnnotationFromEntity.ts} | 50 +++++------------ plugins/azure-devops/src/utils/index.ts | 1 + 4 files changed, 57 insertions(+), 54 deletions(-) rename plugins/azure-devops/src/{hooks/useAnnotationFromEntity.ts => utils/getAnnotationFromEntity.ts} (57%) diff --git a/plugins/azure-devops/README.md b/plugins/azure-devops/README.md index db1af06a01..1737b72af7 100644 --- a/plugins/azure-devops/README.md +++ b/plugins/azure-devops/README.md @@ -71,25 +71,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-definition` annotation 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/src/components/EntityPageAzurePipelines/EntityPageAzurePipelines.tsx b/plugins/azure-devops/src/components/EntityPageAzurePipelines/EntityPageAzurePipelines.tsx index 5957e82eef..3e8babdd25 100644 --- a/plugins/azure-devops/src/components/EntityPageAzurePipelines/EntityPageAzurePipelines.tsx +++ b/plugins/azure-devops/src/components/EntityPageAzurePipelines/EntityPageAzurePipelines.tsx @@ -16,7 +16,7 @@ import { BuildTable } from '../BuildTable/BuildTable'; import React from 'react'; -import { useAnnotationFromEntity } from '../../hooks/useAnnotationFromEntity'; +import { getAnnotationFromEntity } from '../../utils/getAnnotationFromEntity'; import { useBuildRuns } from '../../hooks/useBuildRuns'; import { useEntity } from '@backstage/plugin-catalog-react'; @@ -27,7 +27,7 @@ export const EntityPageAzurePipelines = ({ }) => { const { entity } = useEntity(); - const { project, repo, definition } = useAnnotationFromEntity(entity); + const { project, repo, definition } = getAnnotationFromEntity(entity); const { items, loading, error } = useBuildRuns( project, diff --git a/plugins/azure-devops/src/hooks/useAnnotationFromEntity.ts b/plugins/azure-devops/src/utils/getAnnotationFromEntity.ts similarity index 57% rename from plugins/azure-devops/src/hooks/useAnnotationFromEntity.ts rename to plugins/azure-devops/src/utils/getAnnotationFromEntity.ts index 06e3f58549..5509a72fef 100644 --- a/plugins/azure-devops/src/hooks/useAnnotationFromEntity.ts +++ b/plugins/azure-devops/src/utils/getAnnotationFromEntity.ts @@ -14,30 +14,34 @@ * limitations under the License. */ -import { Entity } from '@backstage/catalog-model'; import { AZURE_DEVOPS_DEFINITION_ANNOTATION, AZURE_DEVOPS_REPO_ANNOTATION, } from '../constants'; -export function useAnnotationFromEntity(entity: Entity): { +import { Entity } from '@backstage/catalog-model'; + +export function getAnnotationFromEntity(entity: Entity): { project: string; repo?: string; definition?: string; } { - if (entity.metadata.annotations?.[AZURE_DEVOPS_DEFINITION_ANNOTATION]) { - const { project, definition } = getProjectDefinition( - entity.metadata.annotations?.[AZURE_DEVOPS_DEFINITION_ANNOTATION], - ); + let annotation = + entity.metadata.annotations?.[AZURE_DEVOPS_DEFINITION_ANNOTATION]; + if (annotation) { + const { project, definition } = getProjectDefinition(annotation); const repo = undefined; return { project, repo, definition }; } - const { project, repo } = getProjectRepo( - entity.metadata.annotations?.[AZURE_DEVOPS_REPO_ANNOTATION] ?? '', - ); - const definition = undefined; - return { project, repo, definition }; + annotation = entity.metadata.annotations?.[AZURE_DEVOPS_REPO_ANNOTATION]; + if (annotation) { + const { project, repo } = getProjectRepo(annotation); + const definition = undefined; + return { project, repo, definition }; + } + + throw new Error('No supported Azure DevOps annotation was found'); } function getProjectDefinition(annotation: string): { @@ -52,18 +56,6 @@ function getProjectDefinition(annotation: string): { ); } - if (!project) { - throw new Error( - 'Project Name for annotation dev.azure.com/project-definition was not found; expected format is: /', - ); - } - - if (!definition) { - throw new Error( - 'Definition Name for annotation dev.azure.com/project-definition was not found; expected format is: /', - ); - } - return { project, definition }; } @@ -79,17 +71,5 @@ function getProjectRepo(annotation: string): { ); } - if (!project) { - throw new Error( - 'Project Name for annotation dev.azure.com/project-repo was not found; expected format is: /', - ); - } - - if (!repo) { - throw new Error( - 'Repo Name for annotation dev.azure.com/project-repo was not found; expected format is: /', - ); - } - 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'; From 520007b8b084a111b90d7ca207601621e5d40420 Mon Sep 17 00:00:00 2001 From: Andre Wanlin Date: Sat, 22 Jan 2022 15:28:39 -0600 Subject: [PATCH 3/5] Changes based on latest round of feedback Signed-off-by: Andre Wanlin --- .changeset/old-months-attack.md | 2 +- plugins/azure-devops/README.md | 9 ++--- .../azure-devops/src/api/AzureDevOpsApi.ts | 2 +- .../azure-devops/src/api/AzureDevOpsClient.ts | 2 +- plugins/azure-devops/src/constants.ts | 5 +-- plugins/azure-devops/src/plugin.ts | 8 +++-- .../src/utils/getAnnotationFromEntity.ts | 36 ++++++++----------- 7 files changed, 32 insertions(+), 32 deletions(-) diff --git a/.changeset/old-months-attack.md b/.changeset/old-months-attack.md index c97d7d8f46..8f0810cbc3 100644 --- a/.changeset/old-months-attack.md +++ b/.changeset/old-months-attack.md @@ -2,4 +2,4 @@ '@backstage/plugin-azure-devops': minor --- -Updated to support cases where only Azure Pipelines are being used by adding an annotation that get builds by definition name +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/plugins/azure-devops/README.md b/plugins/azure-devops/README.md index 1737b72af7..e4b81fe97c 100644 --- a/plugins/azure-devops/README.md +++ b/plugins/azure-devops/README.md @@ -51,13 +51,14 @@ spec: #### Azure Pipelines Only -If you are only using Azure Pipelines along with a different SCM tool then you can use the following annotation to see Builds: +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-definition: / +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 +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 @@ -93,7 +94,7 @@ To get the Azure Pipelines component working you'll need to do the following two ``` - 2. If you are using the `dev.azure.com/project-definition` annotation then you'll want to do this: + 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 diff --git a/plugins/azure-devops/src/api/AzureDevOpsApi.ts b/plugins/azure-devops/src/api/AzureDevOpsApi.ts index b727ce7dc9..09b48356c8 100644 --- a/plugins/azure-devops/src/api/AzureDevOpsApi.ts +++ b/plugins/azure-devops/src/api/AzureDevOpsApi.ts @@ -51,7 +51,7 @@ export interface AzureDevOpsApi { getAllTeams(): Promise; getUserTeamIds(userId: string): Promise; - + getBuildRuns( projectName: string, repoName?: string, diff --git a/plugins/azure-devops/src/api/AzureDevOpsClient.ts b/plugins/azure-devops/src/api/AzureDevOpsClient.ts index 688e5aa426..96c10a7b2e 100644 --- a/plugins/azure-devops/src/api/AzureDevOpsClient.ts +++ b/plugins/azure-devops/src/api/AzureDevOpsClient.ts @@ -93,7 +93,7 @@ export class AzureDevOpsClient implements AzureDevOpsApi { public getUserTeamIds(userId: string): Promise { return this.get(`users/${userId}/team-ids`); } - + public async getBuildRuns( projectName: string, repoName?: string, diff --git a/plugins/azure-devops/src/constants.ts b/plugins/azure-devops/src/constants.ts index 8a920b59e2..95e5be4930 100644 --- a/plugins/azure-devops/src/constants.ts +++ b/plugins/azure-devops/src/constants.ts @@ -14,7 +14,8 @@ * limitations under the License. */ +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_DEFINITION_ANNOTATION = - 'dev.azure.com/project-definition'; export const AZURE_DEVOPS_DEFAULT_TOP: number = 10; diff --git a/plugins/azure-devops/src/plugin.ts b/plugins/azure-devops/src/plugin.ts index d22dff2863..c2ecb7457a 100644 --- a/plugins/azure-devops/src/plugin.ts +++ b/plugins/azure-devops/src/plugin.ts @@ -15,7 +15,8 @@ */ import { - AZURE_DEVOPS_DEFINITION_ANNOTATION, + AZURE_DEVOPS_BUILD_DEFINITION_ANNOTATION, + AZURE_DEVOPS_PROJECT_ANNOTATION, AZURE_DEVOPS_REPO_ANNOTATION, } from './constants'; import { @@ -40,7 +41,10 @@ export const isAzureDevOpsAvailable = (entity: Entity) => export const isAzurePipelinesAvailable = (entity: Entity) => Boolean(entity.metadata.annotations?.[AZURE_DEVOPS_REPO_ANNOTATION]) || - Boolean(entity.metadata.annotations?.[AZURE_DEVOPS_DEFINITION_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 index 5509a72fef..41cc1e6628 100644 --- a/plugins/azure-devops/src/utils/getAnnotationFromEntity.ts +++ b/plugins/azure-devops/src/utils/getAnnotationFromEntity.ts @@ -15,7 +15,8 @@ */ import { - AZURE_DEVOPS_DEFINITION_ANNOTATION, + AZURE_DEVOPS_BUILD_DEFINITION_ANNOTATION, + AZURE_DEVOPS_PROJECT_ANNOTATION, AZURE_DEVOPS_REPO_ANNOTATION, } from '../constants'; @@ -26,37 +27,30 @@ export function getAnnotationFromEntity(entity: Entity): { repo?: string; definition?: string; } { - let annotation = - entity.metadata.annotations?.[AZURE_DEVOPS_DEFINITION_ANNOTATION]; - if (annotation) { - const { project, definition } = getProjectDefinition(annotation); - const repo = undefined; - return { project, repo, definition }; - } - - annotation = entity.metadata.annotations?.[AZURE_DEVOPS_REPO_ANNOTATION]; + const annotation = + entity.metadata.annotations?.[AZURE_DEVOPS_REPO_ANNOTATION]; if (annotation) { const { project, repo } = getProjectRepo(annotation); const definition = undefined; return { project, repo, definition }; } - throw new Error('No supported Azure DevOps annotation was found'); -} + const project = + entity.metadata.annotations?.[AZURE_DEVOPS_PROJECT_ANNOTATION]; + if (!project) { + throw new Error('Value for annotation dev.azure.com/project was not found'); + } -function getProjectDefinition(annotation: string): { - project: string; - definition: string; -} { - const [project, definition] = annotation.split('/'); - - if (!project && !definition) { + const definition = + entity.metadata.annotations?.[AZURE_DEVOPS_BUILD_DEFINITION_ANNOTATION]; + if (!definition) { throw new Error( - 'Value for annotation dev.azure.com/project-definition was not in the correct format: /', + 'Value for annotation dev.azure.com/build-definition was not found', ); } - return { project, definition }; + const repo = undefined; + return { project, repo, definition }; } function getProjectRepo(annotation: string): { From c1767859c3d892e3538373c19652a2c5d69f320f Mon Sep 17 00:00:00 2001 From: Andre Wanlin Date: Sat, 22 Jan 2022 15:29:53 -0600 Subject: [PATCH 4/5] Updated to patch Signed-off-by: Andre Wanlin --- .changeset/old-months-attack.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/old-months-attack.md b/.changeset/old-months-attack.md index 8f0810cbc3..75a81e0c27 100644 --- a/.changeset/old-months-attack.md +++ b/.changeset/old-months-attack.md @@ -1,5 +1,5 @@ --- -'@backstage/plugin-azure-devops': minor +'@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). From 4ffc16b9c65ebbee67557a401112ac03c0d9876e Mon Sep 17 00:00:00 2001 From: Andre Wanlin Date: Mon, 24 Jan 2022 08:03:26 -0600 Subject: [PATCH 5/5] Updated useAsync import Signed-off-by: Andre Wanlin --- plugins/azure-devops/src/hooks/useBuildRuns.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/azure-devops/src/hooks/useBuildRuns.ts b/plugins/azure-devops/src/hooks/useBuildRuns.ts index f19ebaf23f..ae0273de44 100644 --- a/plugins/azure-devops/src/hooks/useBuildRuns.ts +++ b/plugins/azure-devops/src/hooks/useBuildRuns.ts @@ -22,7 +22,7 @@ import { import { AZURE_DEVOPS_DEFAULT_TOP } from '../constants'; import { azureDevOpsApiRef } from '../api'; import { useApi } from '@backstage/core-plugin-api'; -import { useAsync } from 'react-use'; +import useAsync from 'react-use/lib/useAsync'; export function useBuildRuns( projectName: string,