Start of refactoring based on feedback

Signed-off-by: Andre Wanlin <awanlin@rapidrtc.com>
This commit is contained in:
Andre Wanlin
2021-10-07 19:55:26 -05:00
parent a44d32d46c
commit 9953dc1b44
15 changed files with 72 additions and 40 deletions
@@ -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 = (
</EntitySwitch.Case>
<EntitySwitch.Case if={isAzureDevOpsAvailable}>
<EntityAzureDevOpsContent />
<EntityAzurePipelinesContent />
</EntitySwitch.Case>
<EntitySwitch.Case>
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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.

Before

Width:  |  Height:  |  Size: 173 KiB

After

Width:  |  Height:  |  Size: 173 KiB

+5 -5
View File
@@ -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",
@@ -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<any> {
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();
@@ -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}`);
}
}
+1 -1
View File
@@ -22,7 +22,7 @@ import {
export type RepoBuild = {
id?: number;
title: string;
link: string;
link?: string;
status?: BuildStatus;
result?: BuildResult;
queueTime?: Date;
@@ -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 <StatusError />;
case 2: // Succeeded
case BuildResult.Succeeded:
return <StatusOK />;
case 4: // PartiallySucceeded
case BuildResult.PartiallySucceeded:
return <StatusWarning />;
case 8: // Failed
case BuildResult.Failed:
return <StatusError />;
case 32: // Canceled
case BuildResult.Canceled:
return <StatusAborted />;
default:
return <StatusWarning />;
@@ -75,7 +77,9 @@ const columns: TableColumn[] = [
render: (row: Partial<RepoBuild>) => (
<Box display="flex" alignItems="center">
<Box mr={1} />
<Typography variant="button">{BuildStatus[row.status || 0]}</Typography>
<Typography variant="button">
{BuildStatus[row.status || BuildStatus.None]}
</Typography>
</Box>
),
},
@@ -86,7 +90,9 @@ const columns: TableColumn[] = [
<Box display="flex" alignItems="center">
{getBuildResultComponent(row.result)}
<Box mr={1} />
<Typography variant="button">{BuildResult[row.result || 0]}</Typography>
<Typography variant="button">
{BuildResult[row.result || BuildResult.None]}
</Typography>
</Box>
),
},
@@ -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 || []}
/>
);
};
@@ -29,5 +29,5 @@ export const EntityPageAzureDevOps = (_props: Props) => {
const { entity } = useEntity();
const { items, loading, error } = useRepoBuilds(entity);
return <BuildTable items={items || []} loading={loading} error={error} />;
return <BuildTable items={items} loading={loading} error={error} />;
};
+1
View File
@@ -15,3 +15,4 @@
*/
export const AZURE_DEVOPS_ANNOTATION = 'dev.azure.com/project-repo';
export const AZURE_DEVOPS_DEFAULT_TOP: number = 10;
@@ -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('/');
@@ -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(() => {
+1 -1
View File
@@ -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';
+4 -9
View File
@@ -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,
}),
);