Merge pull request #7484 from awanlin/awanlin/azure-devops-frontend-plugin
Adding Azure DevOps frontend plugin
This commit is contained in:
@@ -11,6 +11,7 @@
|
||||
"@backstage/core-plugin-api": "^0.1.10",
|
||||
"@backstage/integration-react": "^0.1.11",
|
||||
"@backstage/plugin-api-docs": "^0.6.11",
|
||||
"@backstage/plugin-azure-devops": "^0.1.0",
|
||||
"@backstage/plugin-badges": "^0.2.12",
|
||||
"@backstage/plugin-catalog": "^0.7.0",
|
||||
"@backstage/plugin-catalog-graph": "^0.1.3",
|
||||
|
||||
@@ -33,6 +33,10 @@ import {
|
||||
EntityProvidedApisCard,
|
||||
EntityProvidingComponentsCard,
|
||||
} from '@backstage/plugin-api-docs';
|
||||
import {
|
||||
EntityAzurePipelinesContent,
|
||||
isAzureDevOpsAvailable,
|
||||
} from '@backstage/plugin-azure-devops';
|
||||
import { EntityBadgesDialog } from '@backstage/plugin-badges';
|
||||
import {
|
||||
EntityAboutCard,
|
||||
@@ -183,6 +187,10 @@ export const cicdContent = (
|
||||
<EntityGithubActionsContent />
|
||||
</EntitySwitch.Case>
|
||||
|
||||
<EntitySwitch.Case if={isAzureDevOpsAvailable}>
|
||||
<EntityAzurePipelinesContent defaultLimit={25} />
|
||||
</EntitySwitch.Case>
|
||||
|
||||
<EntitySwitch.Case>
|
||||
<EmptyState
|
||||
title="No CI/CD available for this entity"
|
||||
|
||||
@@ -38,7 +38,7 @@ import { Config } from '@backstage/config';
|
||||
import healthcheck from './plugins/healthcheck';
|
||||
import { metricsInit, metricsHandler } from './metrics';
|
||||
import auth from './plugins/auth';
|
||||
import azureDevOps from './plugins/azuredevops';
|
||||
import azureDevOps from './plugins/azure-devops';
|
||||
import catalog from './plugins/catalog';
|
||||
import codeCoverage from './plugins/codecoverage';
|
||||
import kubernetes from './plugins/kubernetes';
|
||||
|
||||
+2
-2
@@ -18,9 +18,9 @@ import { createRouter } from '@backstage/plugin-azure-devops-backend';
|
||||
import { Router } from 'express';
|
||||
import type { PluginEnvironment } from '../types';
|
||||
|
||||
export default async function createPlugin({
|
||||
export default function createPlugin({
|
||||
logger,
|
||||
config,
|
||||
}: PluginEnvironment): Promise<Router> {
|
||||
return await createRouter({ logger, config });
|
||||
return createRouter({ logger, config });
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
extends: [require.resolve('@backstage/cli/config/eslint')],
|
||||
};
|
||||
@@ -0,0 +1,135 @@
|
||||
# Azure DevOps Plugin
|
||||
|
||||
Website: [https://dev.azure.com/](https://dev.azure.com/)
|
||||
|
||||

|
||||
|
||||
## Setup
|
||||
|
||||
The following sections will help you get the Azure DevOps plugin setup and running
|
||||
|
||||
### Configuration
|
||||
|
||||
The Azure DevOps plugin requires the following YAML to be added to your app-config.yaml:
|
||||
|
||||
```yaml
|
||||
azureDevOps:
|
||||
host: dev.azure.com
|
||||
token: ${AZURE_TOKEN}
|
||||
organization: my-company
|
||||
```
|
||||
|
||||
Configuration Details:
|
||||
|
||||
- `host` and `token` can be the same as the ones used for the `integration` section
|
||||
- `AZURE_TOKEN` environment variable must be set to a [Personal Access Token](https://docs.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate?view=azure-devops&tabs=preview-page) with read access to both Code and Build
|
||||
- `organization` is your Azure DevOps Organization name or for Azure DevOps Server (on-premise) this will be your Collection name
|
||||
|
||||
### Backend
|
||||
|
||||
Here's how to get the backend up and running:
|
||||
|
||||
1. First we need to add the `@backstage/plugin-azure-devops-backend` package to your backend:
|
||||
|
||||
```sh
|
||||
# From the Backstage root directory
|
||||
cd packages/backend
|
||||
yarn add @backstage/plugin-azure-devops-backend
|
||||
```
|
||||
|
||||
2. Then we will create a new file named `packages/backend/src/plugins/azure-devops.ts`, and add the
|
||||
following to it:
|
||||
|
||||
```ts
|
||||
import { createRouter } from '@backstage/plugin-azure-devops-backend';
|
||||
import { Router } from 'express';
|
||||
import type { PluginEnvironment } from '../types';
|
||||
|
||||
export default function createPlugin({
|
||||
logger,
|
||||
config,
|
||||
}: PluginEnvironment): Promise<Router> {
|
||||
return createRouter({ logger, config });
|
||||
}
|
||||
```
|
||||
|
||||
3. Next we wire this into the overall backend router, edit `packages/backend/src/index.ts`:
|
||||
|
||||
```ts
|
||||
import azureDevOps from './plugins/azuredevops';
|
||||
// ...
|
||||
async function main() {
|
||||
// ...
|
||||
const azureDevOpsEnv = useHotMemoize(module, () => createEnv('azure-devops'));
|
||||
apiRouter.use('/azure-devops', await azureDevOps(azureDevOpsEnv));
|
||||
```
|
||||
|
||||
4. Now run `yarn start-backend` from the repo root
|
||||
5. Finally open `http://localhost:7000/api/azure-devops/health` in a browser and it should return `{"status":"ok"}`
|
||||
|
||||
### Frontend
|
||||
|
||||
To get the frontend working you'll need to do the following two steps:
|
||||
|
||||
1. First we need to add the @backstage/plugin-azure-devops package to your frontend app:
|
||||
|
||||
```bash
|
||||
# From your Backstage root directory
|
||||
cd packages/app
|
||||
yarn add @backstage/plugin-azure-devops
|
||||
```
|
||||
|
||||
2. Second we need to add the `EntityAzurePipelinesContent` extension to the entity page in your app:
|
||||
|
||||
```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}>
|
||||
// Set defaultLimit to the max number of builds you would like to be able to see
|
||||
// the default if not set is 10
|
||||
<EntityAzurePipelinesContent defaultLimit={25} />
|
||||
</EntitySwitch.Case>
|
||||
// ...
|
||||
</EntitySwitch>
|
||||
```
|
||||
|
||||
### Entity Annotation
|
||||
|
||||
You need to add the following annotation to any entities you want to be able to use the Azure Devops plugin with:
|
||||
|
||||
```yaml
|
||||
dev.azure.com/project-repo: <project-name>/<repo-name>
|
||||
```
|
||||
|
||||
Let's break this down a little: `<project-name>` will be the name of your Team Project and `<repo-name>` will be the name of your repository which needs to be part of the Team Project you entered for `<project-name>`.
|
||||
|
||||
Here's what that will look like in action:
|
||||
|
||||
```yaml
|
||||
# Example catalog-info.yaml entity definition file
|
||||
apiVersion: backstage.io/v1alpha1
|
||||
kind: Component
|
||||
metadata:
|
||||
# ...
|
||||
annotations:
|
||||
dev.azure.com/project-repo: my-project/my-repo
|
||||
spec:
|
||||
type: service
|
||||
# ...
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
- Lists the top _n_ builds for a given repository where _n_ is the value configured for `top`
|
||||
|
||||
## Limitations
|
||||
|
||||
- Currently multiple organizations is not supported
|
||||
@@ -0,0 +1,37 @@
|
||||
## API Report File for "@backstage/plugin-azure-devops"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
/// <reference types="react" />
|
||||
|
||||
import { BackstagePlugin } from '@backstage/core-plugin-api';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { RouteRef } from '@backstage/core-plugin-api';
|
||||
|
||||
// Warning: (ae-missing-release-tag) "azureDevOpsPlugin" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export const azureDevOpsPlugin: BackstagePlugin<
|
||||
{
|
||||
entityContent: RouteRef<undefined>;
|
||||
},
|
||||
{}
|
||||
>;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "EntityAzurePipelinesContent" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export const EntityAzurePipelinesContent: ({
|
||||
defaultLimit,
|
||||
}: {
|
||||
defaultLimit?: number | undefined;
|
||||
}) => JSX.Element;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "isAzureDevOpsAvailable" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export const isAzureDevOpsAvailable: (entity: Entity) => boolean;
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
```
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* 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 { createDevApp } from '@backstage/dev-utils';
|
||||
import { azureDevOpsPlugin } from '../src/plugin';
|
||||
|
||||
createDevApp().registerPlugin(azureDevOpsPlugin).render();
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 173 KiB |
@@ -0,0 +1,62 @@
|
||||
{
|
||||
"name": "@backstage/plugin-azure-devops",
|
||||
"version": "0.1.0",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
"private": false,
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"main": "dist/index.esm.js",
|
||||
"types": "dist/index.d.ts"
|
||||
},
|
||||
"homepage": "https://backstage.io",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/backstage/backstage",
|
||||
"directory": "plugins/azure-devops"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "backstage-cli plugin:build",
|
||||
"start": "backstage-cli plugin:serve",
|
||||
"lint": "backstage-cli lint",
|
||||
"test": "backstage-cli test",
|
||||
"diff": "backstage-cli plugin:diff",
|
||||
"prepack": "backstage-cli prepack",
|
||||
"postpack": "backstage-cli postpack",
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^0.9.3",
|
||||
"@backstage/core-components": "^0.6.1",
|
||||
"@backstage/core-plugin-api": "^0.1.10",
|
||||
"@backstage/errors": "^0.1.2",
|
||||
"@backstage/plugin-catalog-react": "^0.5.1",
|
||||
"@backstage/theme": "^0.2.10",
|
||||
"@material-ui/core": "^4.12.2",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@material-ui/lab": "4.0.0-alpha.57",
|
||||
"azure-devops-node-api": "^11.0.1",
|
||||
"luxon": "^2.0.2",
|
||||
"react": "^16.13.1",
|
||||
"react-dom": "^16.13.1",
|
||||
"react-router": "6.0.0-beta.0",
|
||||
"react-use": "^17.2.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.7.15",
|
||||
"@backstage/core-app-api": "^0.1.16",
|
||||
"@backstage/dev-utils": "^0.2.11",
|
||||
"@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",
|
||||
"@types/jest": "^26.0.7",
|
||||
"@types/node": "^14.14.32",
|
||||
"cross-fetch": "^3.0.6",
|
||||
"msw": "^0.29.0"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* 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 { RepoBuild, RepoBuildOptions } from './types';
|
||||
import { createApiRef } from '@backstage/core-plugin-api';
|
||||
|
||||
export const azureDevOpsApiRef = createApiRef<AzureDevOpsApi>({
|
||||
id: 'plugin.azure-devops.service',
|
||||
description:
|
||||
'Used by the Azure DevOps plugin to make requests to accompanying backend',
|
||||
});
|
||||
|
||||
export interface AzureDevOpsApi {
|
||||
getRepoBuilds(
|
||||
projectName: string,
|
||||
repoName: string,
|
||||
options?: RepoBuildOptions,
|
||||
): Promise<{ items: RepoBuild[] }>;
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* 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 { AzureDevOpsApi } from './AzureDevOpsApi';
|
||||
import { RepoBuild, RepoBuildOptions } from './types';
|
||||
import { DiscoveryApi, IdentityApi } from '@backstage/core-plugin-api';
|
||||
import { ResponseError } from '@backstage/errors';
|
||||
|
||||
export class AzureDevOpsClient implements AzureDevOpsApi {
|
||||
private readonly discoveryApi: DiscoveryApi;
|
||||
private readonly identityApi: IdentityApi;
|
||||
|
||||
constructor(options: {
|
||||
discoveryApi: DiscoveryApi;
|
||||
identityApi: IdentityApi;
|
||||
}) {
|
||||
this.discoveryApi = options.discoveryApi;
|
||||
this.identityApi = options.identityApi;
|
||||
}
|
||||
|
||||
async getRepoBuilds(
|
||||
projectName: string,
|
||||
repoName: string,
|
||||
options?: RepoBuildOptions,
|
||||
): Promise<{ items: RepoBuild[] }> {
|
||||
const items = await this.get<RepoBuild[]>(
|
||||
`repo-builds/${projectName}/${repoName}?top=${options?.top}`,
|
||||
);
|
||||
return { items };
|
||||
}
|
||||
|
||||
private async get<T>(path: string): Promise<T> {
|
||||
const baseUrl = `${await this.discoveryApi.getBaseUrl('azure-devops')}/`;
|
||||
const url = new URL(path, baseUrl);
|
||||
|
||||
const idToken = await this.identityApi.getIdToken();
|
||||
const response = await fetch(url.toString(), {
|
||||
headers: idToken ? { Authorization: `Bearer ${idToken}` } : {},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw await ResponseError.fromResponse(response);
|
||||
}
|
||||
|
||||
return response.json() as Promise<T>;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* 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 * from './AzureDevOpsApi';
|
||||
export * from './AzureDevOpsClient';
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* 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 {
|
||||
BuildResult,
|
||||
BuildStatus,
|
||||
} from 'azure-devops-node-api/interfaces/BuildInterfaces';
|
||||
|
||||
export type RepoBuild = {
|
||||
id?: number;
|
||||
title: string;
|
||||
link?: string;
|
||||
status?: BuildStatus;
|
||||
result?: BuildResult;
|
||||
queueTime?: Date;
|
||||
source: string;
|
||||
};
|
||||
|
||||
export type RepoBuildOptions = {
|
||||
top?: number;
|
||||
};
|
||||
@@ -0,0 +1,186 @@
|
||||
/*
|
||||
* 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 React from 'react';
|
||||
import { DateTime } from 'luxon';
|
||||
import {
|
||||
Link,
|
||||
Table,
|
||||
TableColumn,
|
||||
StatusError,
|
||||
StatusOK,
|
||||
StatusWarning,
|
||||
StatusAborted,
|
||||
StatusRunning,
|
||||
StatusPending,
|
||||
ResponseErrorPanel,
|
||||
} from '@backstage/core-components';
|
||||
import { Box, Typography } from '@material-ui/core';
|
||||
import { RepoBuild } from '../../api/types';
|
||||
import {
|
||||
BuildResult,
|
||||
BuildStatus,
|
||||
} from 'azure-devops-node-api/interfaces/BuildInterfaces';
|
||||
|
||||
const getBuildResultComponent = (result: number | undefined) => {
|
||||
switch (result) {
|
||||
case BuildResult.Succeeded:
|
||||
return (
|
||||
<span>
|
||||
<StatusOK /> Succeeded
|
||||
</span>
|
||||
);
|
||||
case BuildResult.PartiallySucceeded:
|
||||
return (
|
||||
<span>
|
||||
<StatusWarning /> Partially Succeeded
|
||||
</span>
|
||||
);
|
||||
case BuildResult.Failed:
|
||||
return (
|
||||
<span>
|
||||
<StatusError /> Failed
|
||||
</span>
|
||||
);
|
||||
case BuildResult.Canceled:
|
||||
return (
|
||||
<span>
|
||||
<StatusAborted /> Canceled
|
||||
</span>
|
||||
);
|
||||
case BuildResult.None:
|
||||
default:
|
||||
return (
|
||||
<span>
|
||||
<StatusWarning /> Unknown
|
||||
</span>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const getBuildStateComponent = (
|
||||
status: number | undefined,
|
||||
result: number | undefined,
|
||||
) => {
|
||||
switch (status) {
|
||||
case BuildStatus.InProgress:
|
||||
return (
|
||||
<span>
|
||||
<StatusRunning /> In Progress
|
||||
</span>
|
||||
);
|
||||
case BuildStatus.Completed:
|
||||
return getBuildResultComponent(result);
|
||||
case BuildStatus.Cancelling:
|
||||
return (
|
||||
<span>
|
||||
<StatusAborted /> Cancelling
|
||||
</span>
|
||||
);
|
||||
case BuildStatus.Postponed:
|
||||
return (
|
||||
<span>
|
||||
<StatusPending /> Postponed
|
||||
</span>
|
||||
);
|
||||
case BuildStatus.NotStarted:
|
||||
return (
|
||||
<span>
|
||||
<StatusAborted /> Not Started
|
||||
</span>
|
||||
);
|
||||
case BuildStatus.None:
|
||||
default:
|
||||
return (
|
||||
<span>
|
||||
<StatusWarning /> Unknown
|
||||
</span>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const columns: TableColumn[] = [
|
||||
{
|
||||
title: 'ID',
|
||||
field: 'id',
|
||||
highlight: false,
|
||||
width: 'auto',
|
||||
},
|
||||
{
|
||||
title: 'Build',
|
||||
field: 'title',
|
||||
width: 'auto',
|
||||
render: (row: Partial<RepoBuild>) => (
|
||||
<Link to={row.link || ''}>{row.title}</Link>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Source',
|
||||
field: 'source',
|
||||
width: 'auto',
|
||||
},
|
||||
{
|
||||
title: 'State',
|
||||
width: 'auto',
|
||||
render: (row: Partial<RepoBuild>) => (
|
||||
<Box display="flex" alignItems="center">
|
||||
<Typography variant="button">
|
||||
{getBuildStateComponent(row.status, row.result)}
|
||||
</Typography>
|
||||
</Box>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Age',
|
||||
field: 'queueTime',
|
||||
width: 'auto',
|
||||
render: (row: Partial<RepoBuild>) =>
|
||||
DateTime.fromISO(
|
||||
row.queueTime ? row.queueTime.toString() : new Date().toString(),
|
||||
).toRelative(),
|
||||
},
|
||||
];
|
||||
|
||||
type Props = {
|
||||
items?: RepoBuild[];
|
||||
loading: boolean;
|
||||
error?: any;
|
||||
};
|
||||
|
||||
export const BuildTable = ({ items, loading, error }: Props) => {
|
||||
if (error) {
|
||||
return (
|
||||
<div>
|
||||
<ResponseErrorPanel error={error} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Table
|
||||
isLoading={loading}
|
||||
columns={columns}
|
||||
options={{
|
||||
search: true,
|
||||
paging: true,
|
||||
pageSize: 5,
|
||||
showEmptyDataSourceMessage: !loading,
|
||||
}}
|
||||
title={`Builds (${items ? items.length : 0})`}
|
||||
data={items || []}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* 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 { BuildTable } from './BuildTable';
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* 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 { useEntity } from '@backstage/plugin-catalog-react';
|
||||
import React from 'react';
|
||||
import { useRepoBuilds } from '../../hooks/useRepoBuilds';
|
||||
import { BuildTable } from '../BuildTable/BuildTable';
|
||||
|
||||
export const EntityPageAzurePipelines = ({
|
||||
defaultLimit,
|
||||
}: {
|
||||
defaultLimit?: number;
|
||||
}) => {
|
||||
const { entity } = useEntity();
|
||||
const { items, loading, error } = useRepoBuilds(entity, defaultLimit);
|
||||
|
||||
return <BuildTable items={items} loading={loading} error={error} />;
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* 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 { EntityPageAzurePipelines } from './EntityPageAzurePipelines';
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* 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 React from 'react';
|
||||
import { Routes, Route } from 'react-router';
|
||||
import { azureDevOpsRouteRef } from '../routes';
|
||||
import { EntityPageAzurePipelines } from './EntityPageAzurePipelines';
|
||||
import { AZURE_DEVOPS_ANNOTATION } from '../constants';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { useEntity } from '@backstage/plugin-catalog-react';
|
||||
import { MissingAnnotationEmptyState } from '@backstage/core-components';
|
||||
|
||||
export const isAzureDevOpsAvailable = (entity: Entity) =>
|
||||
Boolean(entity.metadata.annotations?.[AZURE_DEVOPS_ANNOTATION]);
|
||||
|
||||
export const Router = ({ defaultLimit }: { defaultLimit?: number }) => {
|
||||
const { entity } = useEntity();
|
||||
|
||||
if (!isAzureDevOpsAvailable(entity)) {
|
||||
return <MissingAnnotationEmptyState annotation={AZURE_DEVOPS_ANNOTATION} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Routes>
|
||||
<Route
|
||||
path={`/${azureDevOpsRouteRef.path}`}
|
||||
element={<EntityPageAzurePipelines defaultLimit={defaultLimit} />}
|
||||
/>
|
||||
</Routes>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* 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 const AZURE_DEVOPS_ANNOTATION = 'dev.azure.com/project-repo';
|
||||
export const AZURE_DEVOPS_DEFAULT_TOP: number = 10;
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* 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_ANNOTATION } from '../constants';
|
||||
|
||||
export function useProjectRepoFromEntity(entity: Entity): {
|
||||
project: string;
|
||||
repo: string;
|
||||
} {
|
||||
const [project, repo] = (
|
||||
entity.metadata.annotations?.[AZURE_DEVOPS_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>',
|
||||
);
|
||||
}
|
||||
|
||||
if (!project) {
|
||||
throw new Error(
|
||||
'Project Name for annotation dev.azure.com/project-repo was not found; expected format is: <project-name>/<repo-name>',
|
||||
);
|
||||
}
|
||||
|
||||
if (!repo) {
|
||||
throw new Error(
|
||||
'Repo Name for annotation dev.azure.com/project-repo was not found; expected format is: <project-name>/<repo-name>',
|
||||
);
|
||||
}
|
||||
|
||||
return { project, repo };
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* 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 { useAsync } from 'react-use';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { useApi } from '@backstage/core-plugin-api';
|
||||
import { azureDevOpsApiRef } from '../api';
|
||||
import { RepoBuild, RepoBuildOptions } from '../api/types';
|
||||
import { useProjectRepoFromEntity } from './useProjectRepoFromEntity';
|
||||
import { AZURE_DEVOPS_DEFAULT_TOP } from '../constants';
|
||||
|
||||
export function useRepoBuilds(
|
||||
entity: Entity,
|
||||
defaultLimit?: number,
|
||||
): {
|
||||
items: RepoBuild[] | undefined;
|
||||
loading: boolean;
|
||||
error: any;
|
||||
} {
|
||||
const top = defaultLimit ?? AZURE_DEVOPS_DEFAULT_TOP;
|
||||
const options: RepoBuildOptions = {
|
||||
top: top,
|
||||
};
|
||||
const api = useApi(azureDevOpsApiRef);
|
||||
const { project, repo } = useProjectRepoFromEntity(entity);
|
||||
const { value, loading, error } = useAsync(() => {
|
||||
return api.getRepoBuilds(project, repo, options);
|
||||
}, [api, project, repo, entity]);
|
||||
|
||||
return {
|
||||
items: value?.items,
|
||||
loading,
|
||||
error,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* 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 { azureDevOpsPlugin, EntityAzurePipelinesContent } from './plugin';
|
||||
export { isAzureDevOpsAvailable } from './components/Router';
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* 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 { azureDevOpsPlugin } from './plugin';
|
||||
|
||||
describe('azure-devops', () => {
|
||||
it('should export plugin', () => {
|
||||
expect(azureDevOpsPlugin).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* 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 { azureDevOpsApiRef } from './api/AzureDevOpsApi';
|
||||
import { AzureDevOpsClient } from './api/AzureDevOpsClient';
|
||||
import {
|
||||
createApiFactory,
|
||||
createPlugin,
|
||||
createRoutableExtension,
|
||||
discoveryApiRef,
|
||||
identityApiRef,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import { azureDevOpsRouteRef } from './routes';
|
||||
|
||||
export const azureDevOpsPlugin = createPlugin({
|
||||
id: 'azureDevOps',
|
||||
apis: [
|
||||
createApiFactory({
|
||||
api: azureDevOpsApiRef,
|
||||
deps: { discoveryApi: discoveryApiRef, identityApi: identityApiRef },
|
||||
factory: ({ discoveryApi, identityApi }) =>
|
||||
new AzureDevOpsClient({ discoveryApi, identityApi }),
|
||||
}),
|
||||
],
|
||||
routes: {
|
||||
entityContent: azureDevOpsRouteRef,
|
||||
},
|
||||
});
|
||||
|
||||
export const EntityAzurePipelinesContent = azureDevOpsPlugin.provide(
|
||||
createRoutableExtension({
|
||||
name: 'EntityAzurePipelinesContent',
|
||||
component: () => import('./components/Router').then(m => m.Router),
|
||||
mountPoint: azureDevOpsRouteRef,
|
||||
}),
|
||||
);
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* 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 { createRouteRef } from '@backstage/core-plugin-api';
|
||||
|
||||
export const azureDevOpsRouteRef = createRouteRef({
|
||||
title: 'azure-devops',
|
||||
});
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* 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 '@testing-library/jest-dom';
|
||||
import 'cross-fetch/polyfill';
|
||||
Reference in New Issue
Block a user