Updated to add definition name support
Signed-off-by: Andre Wanlin <awanlin@rapidrtc.com>
This commit is contained in:
@@ -15,6 +15,8 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
BuildRun,
|
||||
BuildRunOptions,
|
||||
DashboardPullRequest,
|
||||
PullRequest,
|
||||
PullRequestOptions,
|
||||
@@ -49,4 +51,11 @@ export interface AzureDevOpsApi {
|
||||
getAllTeams(): Promise<Team[]>;
|
||||
|
||||
getUserTeamIds(userId: string): Promise<string[]>;
|
||||
|
||||
getBuildRuns(
|
||||
projectName: string,
|
||||
repoName?: string,
|
||||
definitionName?: string,
|
||||
options?: BuildRunOptions,
|
||||
): Promise<{ items: BuildRun[] }>;
|
||||
}
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
BuildRun,
|
||||
BuildRunOptions,
|
||||
DashboardPullRequest,
|
||||
PullRequest,
|
||||
PullRequestOptions,
|
||||
@@ -91,6 +93,29 @@ export class AzureDevOpsClient implements AzureDevOpsApi {
|
||||
public getUserTeamIds(userId: string): Promise<string[]> {
|
||||
return this.get<string[]>(`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<BuildRun[]>(urlSegment);
|
||||
return { items };
|
||||
}
|
||||
|
||||
private async get<T>(path: string): Promise<T> {
|
||||
const baseUrl = `${await this.discoveryApi.getBaseUrl('azure-devops')}/`;
|
||||
|
||||
@@ -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 = () => (
|
||||
|
||||
@@ -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<RepoBuild>) => (
|
||||
render: (row: Partial<BuildRun>) => (
|
||||
<Link to={row.link || ''}>{row.title}</Link>
|
||||
),
|
||||
},
|
||||
@@ -138,7 +138,7 @@ const columns: TableColumn[] = [
|
||||
{
|
||||
title: 'State',
|
||||
width: 'auto',
|
||||
render: (row: Partial<RepoBuild>) => (
|
||||
render: (row: Partial<BuildRun>) => (
|
||||
<Box display="flex" alignItems="center">
|
||||
<Typography variant="button">
|
||||
{getBuildStateComponent(row.status, row.result)}
|
||||
@@ -150,7 +150,7 @@ const columns: TableColumn[] = [
|
||||
title: 'Duration',
|
||||
field: 'queueTime',
|
||||
width: 'auto',
|
||||
render: (row: Partial<RepoBuild>) => (
|
||||
render: (row: Partial<BuildRun>) => (
|
||||
<Box display="flex" alignItems="center">
|
||||
<Typography>
|
||||
{getDurationFromDates(row.startTime, row.finishTime)}
|
||||
@@ -162,7 +162,7 @@ const columns: TableColumn[] = [
|
||||
title: 'Age',
|
||||
field: 'queueTime',
|
||||
width: 'auto',
|
||||
render: (row: Partial<RepoBuild>) =>
|
||||
render: (row: Partial<BuildRun>) =>
|
||||
(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;
|
||||
};
|
||||
|
||||
+13
-4
@@ -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 <BuildTable items={items} loading={loading} error={error} />;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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: <project-name>/<definition-name>',
|
||||
);
|
||||
}
|
||||
|
||||
if (!project) {
|
||||
throw new Error(
|
||||
'Project Name for annotation dev.azure.com/project-definition was not found; expected format is: <project-name>/<definition-name>',
|
||||
);
|
||||
}
|
||||
|
||||
if (!definition) {
|
||||
throw new Error(
|
||||
'Definition Name for annotation dev.azure.com/project-definition was not found; expected format is: <project-name>/<definition-name>',
|
||||
);
|
||||
}
|
||||
|
||||
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: <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,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,
|
||||
};
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -19,6 +19,7 @@ export {
|
||||
EntityAzurePipelinesContent,
|
||||
EntityAzurePullRequestsContent,
|
||||
isAzureDevOpsAvailable,
|
||||
isAzurePipelinesAvailable,
|
||||
AzurePullRequestsPage,
|
||||
} from './plugin';
|
||||
|
||||
|
||||
@@ -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',
|
||||
|
||||
Reference in New Issue
Block a user