diff --git a/packages/app/src/components/catalog/EntityPage.tsx b/packages/app/src/components/catalog/EntityPage.tsx index b79bd2c4aa..4147874e65 100644 --- a/packages/app/src/components/catalog/EntityPage.tsx +++ b/packages/app/src/components/catalog/EntityPage.tsx @@ -34,7 +34,7 @@ import { EntityProvidingComponentsCard, } from '@backstage/plugin-api-docs'; import { - EntityAzureDevOpsContent, + EntityAzurePipelinesContent, isAzureDevOpsAvailable, } from '@backstage/plugin-azure-devops'; import { EntityBadgesDialog } from '@backstage/plugin-badges'; @@ -188,7 +188,7 @@ export const cicdContent = ( - + diff --git a/plugins/azure-devops/README.md b/plugins/azure-devops/README.md index 376a630873..97a415d8d0 100644 --- a/plugins/azure-devops/README.md +++ b/plugins/azure-devops/README.md @@ -2,7 +2,7 @@ Website: [https://dev.azure.com/](https://dev.azure.com/) -![Azure DevOps Builds Example](./doc/azure-devops-builds.png) +![Azure DevOps Builds Example](./docs/azure-devops-builds.png) ## Setup diff --git a/plugins/azure-devops/config.d.ts b/plugins/azure-devops/config.d.ts index 827e64d5bd..62b1488e49 100644 --- a/plugins/azure-devops/config.d.ts +++ b/plugins/azure-devops/config.d.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 The Backstage Authors + * 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. diff --git a/plugins/azure-devops/doc/azure-devops-builds.png b/plugins/azure-devops/docs/azure-devops-builds.png similarity index 100% rename from plugins/azure-devops/doc/azure-devops-builds.png rename to plugins/azure-devops/docs/azure-devops-builds.png diff --git a/plugins/azure-devops/package.json b/plugins/azure-devops/package.json index c823924c9d..16e8f35991 100644 --- a/plugins/azure-devops/package.json +++ b/plugins/azure-devops/package.json @@ -27,8 +27,8 @@ }, "dependencies": { "@backstage/catalog-model": "^0.9.3", - "@backstage/core-components": "^0.6.0", - "@backstage/core-plugin-api": "^0.1.9", + "@backstage/core-components": "^0.6.1", + "@backstage/core-plugin-api": "^0.1.10", "@backstage/plugin-catalog-react": "^0.5.1", "@backstage/theme": "^0.2.10", "@material-ui/core": "^4.12.2", @@ -42,10 +42,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.14", - "@backstage/core-app-api": "^0.1.15", + "@backstage/cli": "^0.7.15", + "@backstage/core-app-api": "^0.1.16", "@backstage/dev-utils": "^0.2.11", - "@backstage/test-utils": "^0.1.17", + "@backstage/test-utils": "^0.1.18", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/azure-devops/src/api/AzureDevOpsClient.ts b/plugins/azure-devops/src/api/AzureDevOpsClient.ts index c9e44109aa..32a563f429 100644 --- a/plugins/azure-devops/src/api/AzureDevOpsClient.ts +++ b/plugins/azure-devops/src/api/AzureDevOpsClient.ts @@ -17,6 +17,7 @@ import { AzureDevOpsApi } from './AzureDevOpsApi'; import { RepoBuild } from './types'; import { DiscoveryApi, IdentityApi } from '@backstage/core-plugin-api'; +import { AzureDevOpsClientError } from './AzureDevOpsClientError'; export class AzureDevOpsClient implements AzureDevOpsApi { private readonly discoveryApi: DiscoveryApi; @@ -39,7 +40,9 @@ export class AzureDevOpsClient implements AzureDevOpsApi { } private async get(path: string): Promise { - const url = `${await this.discoveryApi.getBaseUrl('azure-devops')}${path}`; + const baseUrl = await this.discoveryApi.getBaseUrl('azure-devops'); + const url = `${baseUrl}/${path}`; + const idToken = await this.identityApi.getIdToken(); const response = await fetch(url, { headers: idToken ? { Authorization: `Bearer ${idToken}` } : {}, @@ -47,8 +50,7 @@ export class AzureDevOpsClient implements AzureDevOpsApi { if (!response.ok) { const payload = await response.text(); - const message = `Request failed with ${response.status} ${response.statusText}, ${payload}`; - throw new Error(message); + throw new AzureDevOpsClientError(response, payload); } return await response.json(); diff --git a/plugins/azure-devops/src/api/AzureDevOpsClientError.ts b/plugins/azure-devops/src/api/AzureDevOpsClientError.ts new file mode 100644 index 0000000000..f9a2f37b41 --- /dev/null +++ b/plugins/azure-devops/src/api/AzureDevOpsClientError.ts @@ -0,0 +1,21 @@ +/* + * 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. + */ + +export class AzureDevOpsClientError extends Error { + public constructor({ status, statusText }: Response, payload: string) { + super(`Request failed with ${status} ${statusText}, ${payload}`); + } +} diff --git a/plugins/azure-devops/src/api/types.ts b/plugins/azure-devops/src/api/types.ts index 35d4e38ae1..b3562f76c6 100644 --- a/plugins/azure-devops/src/api/types.ts +++ b/plugins/azure-devops/src/api/types.ts @@ -22,7 +22,7 @@ import { export type RepoBuild = { id?: number; title: string; - link: string; + link?: string; status?: BuildStatus; result?: BuildResult; queueTime?: Date; diff --git a/plugins/azure-devops/src/components/BuildTable/BuildTable.tsx b/plugins/azure-devops/src/components/BuildTable/BuildTable.tsx index 26f365c5e9..e853662fbf 100644 --- a/plugins/azure-devops/src/components/BuildTable/BuildTable.tsx +++ b/plugins/azure-devops/src/components/BuildTable/BuildTable.tsx @@ -32,17 +32,19 @@ import { BuildStatus, } from 'azure-devops-node-api/interfaces/BuildInterfaces'; -const getBuildResultComponent = (result: number | undefined = 0) => { +const getBuildResultComponent = ( + result: number | undefined = BuildResult.None, +) => { switch (result) { - case 0: // None + case BuildResult.None: return ; - case 2: // Succeeded + case BuildResult.Succeeded: return ; - case 4: // PartiallySucceeded + case BuildResult.PartiallySucceeded: return ; - case 8: // Failed + case BuildResult.Failed: return ; - case 32: // Canceled + case BuildResult.Canceled: return ; default: return ; @@ -75,7 +77,9 @@ const columns: TableColumn[] = [ render: (row: Partial) => ( - {BuildStatus[row.status || 0]} + + {BuildStatus[row.status || BuildStatus.None]} + ), }, @@ -86,7 +90,9 @@ const columns: TableColumn[] = [ {getBuildResultComponent(row.result)} - {BuildResult[row.result || 0]} + + {BuildResult[row.result || BuildResult.None]} + ), }, @@ -98,7 +104,7 @@ const columns: TableColumn[] = [ ]; type Props = { - items: RepoBuild[]; + items?: RepoBuild[]; loading: boolean; error?: any; }; @@ -125,8 +131,8 @@ export const BuildTable = ({ items, loading, error }: Props) => { pageSize: 5, showEmptyDataSourceMessage: !loading, }} - title={`Builds (${(items && items.length) || 0})`} - data={items} + title={`Builds (${items ? items.length : 0})`} + data={items || []} /> ); }; diff --git a/plugins/azure-devops/src/components/EntityPageAzureDevOps/EntityPageAzureDevOps.tsx b/plugins/azure-devops/src/components/EntityPageAzureDevOps/EntityPageAzureDevOps.tsx index fbeac53569..8e102e647d 100644 --- a/plugins/azure-devops/src/components/EntityPageAzureDevOps/EntityPageAzureDevOps.tsx +++ b/plugins/azure-devops/src/components/EntityPageAzureDevOps/EntityPageAzureDevOps.tsx @@ -29,5 +29,5 @@ export const EntityPageAzureDevOps = (_props: Props) => { const { entity } = useEntity(); const { items, loading, error } = useRepoBuilds(entity); - return ; + return ; }; diff --git a/plugins/azure-devops/src/constants.ts b/plugins/azure-devops/src/constants.ts index 6abc6449e9..d6e67ff7ed 100644 --- a/plugins/azure-devops/src/constants.ts +++ b/plugins/azure-devops/src/constants.ts @@ -15,3 +15,4 @@ */ export const AZURE_DEVOPS_ANNOTATION = 'dev.azure.com/project-repo'; +export const AZURE_DEVOPS_DEFAULT_TOP: number = 10; diff --git a/plugins/azure-devops/src/hooks/useProjectRepoFromEntity.ts b/plugins/azure-devops/src/hooks/useProjectRepoFromEntity.ts index 721fa52781..dfbee81d62 100644 --- a/plugins/azure-devops/src/hooks/useProjectRepoFromEntity.ts +++ b/plugins/azure-devops/src/hooks/useProjectRepoFromEntity.ts @@ -17,7 +17,10 @@ import { Entity } from '@backstage/catalog-model'; import { AZURE_DEVOPS_ANNOTATION } from '../constants'; -export function useProjectRepoFromEntity(entity: Entity) { +export function useProjectRepoFromEntity(entity: Entity): { + project: string; + repo: string; +} { const [project, repo] = ( entity.metadata.annotations?.[AZURE_DEVOPS_ANNOTATION] ?? '' ).split('/'); diff --git a/plugins/azure-devops/src/hooks/useRepoBuilds.ts b/plugins/azure-devops/src/hooks/useRepoBuilds.ts index e6d2964770..1e68490ea6 100644 --- a/plugins/azure-devops/src/hooks/useRepoBuilds.ts +++ b/plugins/azure-devops/src/hooks/useRepoBuilds.ts @@ -20,12 +20,16 @@ import { useApi, configApiRef } from '@backstage/core-plugin-api'; import { azureDevOpsApiRef } from '../api'; import { RepoBuild } from '../api/types'; import { useProjectRepoFromEntity } from './useProjectRepoFromEntity'; +import { AZURE_DEVOPS_DEFAULT_TOP } from '../constants'; -const DEFAULT_TOP: number = 10; - -export function useRepoBuilds(entity: Entity) { +export function useRepoBuilds(entity: Entity): { + items: RepoBuild[]; + loading: boolean; + error: any; +} { const config = useApi(configApiRef); - const top = config.getOptionalNumber('azureDevOps.top') ?? DEFAULT_TOP; + const top = + config.getOptionalNumber('azureDevOps.top') ?? AZURE_DEVOPS_DEFAULT_TOP; const api = useApi(azureDevOpsApiRef); const { project, repo } = useProjectRepoFromEntity(entity); const { value, loading, error } = useAsync(() => { diff --git a/plugins/azure-devops/src/index.ts b/plugins/azure-devops/src/index.ts index b654aebab7..9a90d6ed6a 100644 --- a/plugins/azure-devops/src/index.ts +++ b/plugins/azure-devops/src/index.ts @@ -13,5 +13,5 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export { azureDevOpsPlugin, EntityAzureDevOpsContent } from './plugin'; +export { azureDevOpsPlugin, EntityAzurePipelinesContent } from './plugin'; export { isAzureDevOpsAvailable } from './components/Router'; diff --git a/plugins/azure-devops/src/plugin.ts b/plugins/azure-devops/src/plugin.ts index f37fab6d62..9ec5ebbb09 100644 --- a/plugins/azure-devops/src/plugin.ts +++ b/plugins/azure-devops/src/plugin.ts @@ -20,15 +20,10 @@ import { createApiFactory, createPlugin, createRoutableExtension, - createRouteRef, discoveryApiRef, identityApiRef, } from '@backstage/core-plugin-api'; - -export const rootRouteRef = createRouteRef({ - path: '', - title: 'AzureDevOps', -}); +import { azureDevOpsRouteRef } from './routes'; export const azureDevOpsPlugin = createPlugin({ id: 'azureDevOps', @@ -41,14 +36,14 @@ export const azureDevOpsPlugin = createPlugin({ }), ], routes: { - entityContent: rootRouteRef, + entityContent: azureDevOpsRouteRef, }, }); -export const EntityAzureDevOpsContent = azureDevOpsPlugin.provide( +export const EntityAzurePipelinesContent = azureDevOpsPlugin.provide( createRoutableExtension({ name: 'EntityAzureDevOpsContent', component: () => import('./components/Router').then(m => m.Router), - mountPoint: rootRouteRef, + mountPoint: azureDevOpsRouteRef, }), );