From 6392fbe3c00d733ad1329f8ccf0bb5a6f9929a26 Mon Sep 17 00:00:00 2001 From: mo Date: Thu, 28 Apr 2022 07:53:08 -0500 Subject: [PATCH 01/27] hacking from pullrequests Signed-off-by: mo --- .../app/src/components/catalog/EntityPage.tsx | 13 ++ .../EntityPageAzureGitTags.tsx | 26 ++++ .../EntityPageAzureGitTags/index.ts | 17 +++ .../components/GitTagTable/GitTagTable.tsx | 139 ++++++++++++++++++ .../src/components/GitTagTable/index.ts | 17 +++ plugins/azure-devops/src/index.ts | 1 + plugins/azure-devops/src/plugin.ts | 12 ++ plugins/azure-devops/src/routes.ts | 4 + 8 files changed, 229 insertions(+) create mode 100644 plugins/azure-devops/src/components/EntityPageAzureGitTags/EntityPageAzureGitTags.tsx create mode 100644 plugins/azure-devops/src/components/EntityPageAzureGitTags/index.ts create mode 100644 plugins/azure-devops/src/components/GitTagTable/GitTagTable.tsx create mode 100644 plugins/azure-devops/src/components/GitTagTable/index.ts diff --git a/packages/app/src/components/catalog/EntityPage.tsx b/packages/app/src/components/catalog/EntityPage.tsx index 26068c6c8f..2b1ba6e492 100644 --- a/packages/app/src/components/catalog/EntityPage.tsx +++ b/packages/app/src/components/catalog/EntityPage.tsx @@ -36,6 +36,7 @@ import { } from '@backstage/plugin-api-docs'; import { EntityAzurePipelinesContent, + EntityAzureGitTagsContent, EntityAzurePullRequestsContent, isAzureDevOpsAvailable, isAzurePipelinesAvailable, @@ -281,6 +282,14 @@ const errorsContent = ( ); +const gitTagsContent = ( + + + + + +); + const pullRequestsContent = ( @@ -478,6 +487,10 @@ const websiteEntityPage = ( + + {gitTagsContent} + + {pullRequestsContent} diff --git a/plugins/azure-devops/src/components/EntityPageAzureGitTags/EntityPageAzureGitTags.tsx b/plugins/azure-devops/src/components/EntityPageAzureGitTags/EntityPageAzureGitTags.tsx new file mode 100644 index 0000000000..8f997e079e --- /dev/null +++ b/plugins/azure-devops/src/components/EntityPageAzureGitTags/EntityPageAzureGitTags.tsx @@ -0,0 +1,26 @@ +/* + * 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 { GitTagTable } from '../GitTagTable/GitTagTable'; +import React from 'react'; + +export const EntityPageAzureGitTags = ({ + defaultLimit, +}: { + defaultLimit?: number; +}) => { + return ; +}; diff --git a/plugins/azure-devops/src/components/EntityPageAzureGitTags/index.ts b/plugins/azure-devops/src/components/EntityPageAzureGitTags/index.ts new file mode 100644 index 0000000000..b88fb6c467 --- /dev/null +++ b/plugins/azure-devops/src/components/EntityPageAzureGitTags/index.ts @@ -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 { EntityPageAzureGitTags } from './EntityPageAzureGitTags'; diff --git a/plugins/azure-devops/src/components/GitTagTable/GitTagTable.tsx b/plugins/azure-devops/src/components/GitTagTable/GitTagTable.tsx new file mode 100644 index 0000000000..b281cd0759 --- /dev/null +++ b/plugins/azure-devops/src/components/GitTagTable/GitTagTable.tsx @@ -0,0 +1,139 @@ +/* + * 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 { Box, Chip } from '@material-ui/core'; +import { + Link, + ResponseErrorPanel, + Table, + TableColumn, +} from '@backstage/core-components'; +import { + PullRequest, + PullRequestStatus, +} from '@backstage/plugin-azure-devops-common'; +import React, { useState } from 'react'; + +import { AzurePullRequestsIcon } from '../AzurePullRequestsIcon'; +import { DateTime } from 'luxon'; +import { PullRequestStatusButtonGroup } from '../PullRequestStatusButtonGroup'; +import { useEntity } from '@backstage/plugin-catalog-react'; +import { usePullRequests } from '../../hooks/usePullRequests'; + +const columns: TableColumn[] = [ + { + title: 'ID', + field: 'pullRequestId', + highlight: false, + width: 'auto', + }, + { + title: 'Title', + field: 'title', + width: 'auto', + render: (row: Partial) => ( + + {row.title} + {row.isDraft && ( + + + + )} + + ), + }, + { + title: 'Source', + field: 'sourceRefName', + width: 'auto', + }, + { + title: 'Target', + field: 'targetRefName', + width: 'auto', + }, + { + title: 'Created By', + field: 'createdBy', + width: 'auto', + }, + { + title: 'Created', + field: 'creationDate', + width: 'auto', + render: (row: Partial) => + (row.creationDate + ? DateTime.fromISO(row.creationDate) + : DateTime.now() + ).toRelative(), + }, +]; + +type PullRequestTableProps = { + defaultLimit?: number; +}; + +export const GitTagTable = ({ defaultLimit }: PullRequestTableProps) => { + const [pullRequestStatusState, setPullRequestStatusState] = + useState(PullRequestStatus.Active); + const { entity } = useEntity(); + + const { items, loading, error } = usePullRequests( + entity, + defaultLimit, + pullRequestStatusState, + ); + + if (error) { + return ( +
+ +
+ ); + } + + return ( + + + + Azure Repos - Pull Requests ({items ? items.length : 0}) + + + + + } + data={items ?? []} + /> + ); +}; diff --git a/plugins/azure-devops/src/components/GitTagTable/index.ts b/plugins/azure-devops/src/components/GitTagTable/index.ts new file mode 100644 index 0000000000..32933241e6 --- /dev/null +++ b/plugins/azure-devops/src/components/GitTagTable/index.ts @@ -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 { GitTagTable } from './GitTagTable'; diff --git a/plugins/azure-devops/src/index.ts b/plugins/azure-devops/src/index.ts index aa052975de..48177ee830 100644 --- a/plugins/azure-devops/src/index.ts +++ b/plugins/azure-devops/src/index.ts @@ -17,6 +17,7 @@ export { azureDevOpsPlugin, EntityAzurePipelinesContent, + EntityAzureGitTagsContent, EntityAzurePullRequestsContent, isAzureDevOpsAvailable, isAzurePipelinesAvailable, diff --git a/plugins/azure-devops/src/plugin.ts b/plugins/azure-devops/src/plugin.ts index c2ecb7457a..ccd183eee9 100644 --- a/plugins/azure-devops/src/plugin.ts +++ b/plugins/azure-devops/src/plugin.ts @@ -22,6 +22,7 @@ import { import { azurePipelinesEntityContentRouteRef, azurePullRequestDashboardRouteRef, + azureGitTagsEntityContentRouteRef, azurePullRequestsEntityContentRouteRef, } from './routes'; import { @@ -78,6 +79,17 @@ export const EntityAzurePipelinesContent = azureDevOpsPlugin.provide( }), ); +export const EntityAzureGitTagsContent = azureDevOpsPlugin.provide( + createRoutableExtension({ + name: 'EntityAzureGitTagsContent', + component: () => + import('./components/EntityPageAzureGitTags').then( + m => m.EntityPageAzureGitTags, + ), + mountPoint: azureGitTagsEntityContentRouteRef, + }), +); + export const EntityAzurePullRequestsContent = azureDevOpsPlugin.provide( createRoutableExtension({ name: 'EntityAzurePullRequestsContent', diff --git a/plugins/azure-devops/src/routes.ts b/plugins/azure-devops/src/routes.ts index 0d20c12beb..4ed35bbe12 100644 --- a/plugins/azure-devops/src/routes.ts +++ b/plugins/azure-devops/src/routes.ts @@ -24,6 +24,10 @@ export const azurePipelinesEntityContentRouteRef = createRouteRef({ id: 'azure-pipelines-entity-content', }); +export const azureGitTagsEntityContentRouteRef = createRouteRef({ + id: 'azure-git-tags-entity-content', +}); + export const azurePullRequestsEntityContentRouteRef = createRouteRef({ id: 'azure-pull-requests-entity-content', }); From 1000bdb2ca1e55de9bb7167ad97368b692a861fd Mon Sep 17 00:00:00 2001 From: mo Date: Thu, 28 Apr 2022 08:42:58 -0500 Subject: [PATCH 02/27] wee Signed-off-by: mo --- plugins/azure-devops/src/api/AzureDevOpsApi.ts | 6 ++++++ .../azure-devops/src/components/GitTagTable/GitTagTable.tsx | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/plugins/azure-devops/src/api/AzureDevOpsApi.ts b/plugins/azure-devops/src/api/AzureDevOpsApi.ts index 09b48356c8..63e6dc3125 100644 --- a/plugins/azure-devops/src/api/AzureDevOpsApi.ts +++ b/plugins/azure-devops/src/api/AzureDevOpsApi.ts @@ -38,6 +38,12 @@ export interface AzureDevOpsApi { options?: RepoBuildOptions, ): Promise<{ items: RepoBuild[] }>; + getGitTags( + projectName: string, + repoName: string, + options?: PullRequestOptions, + ): Promise<{ items: PullRequest[] }>; + getPullRequests( projectName: string, repoName: string, diff --git a/plugins/azure-devops/src/components/GitTagTable/GitTagTable.tsx b/plugins/azure-devops/src/components/GitTagTable/GitTagTable.tsx index b281cd0759..9d7d7db07f 100644 --- a/plugins/azure-devops/src/components/GitTagTable/GitTagTable.tsx +++ b/plugins/azure-devops/src/components/GitTagTable/GitTagTable.tsx @@ -124,7 +124,7 @@ export const GitTagTable = ({ defaultLimit }: PullRequestTableProps) => { - Azure Repos - Pull Requests ({items ? items.length : 0}) + Azure Repos - Git Tags ({items ? items.length : 0}) Date: Fri, 29 Apr 2022 06:54:54 -0500 Subject: [PATCH 03/27] useGitTags Signed-off-by: mo --- .../components/GitTagTable/GitTagTable.tsx | 4 +- plugins/azure-devops/src/hooks/useGitTags.ts | 58 +++++++++++++++++++ 2 files changed, 60 insertions(+), 2 deletions(-) create mode 100644 plugins/azure-devops/src/hooks/useGitTags.ts diff --git a/plugins/azure-devops/src/components/GitTagTable/GitTagTable.tsx b/plugins/azure-devops/src/components/GitTagTable/GitTagTable.tsx index 9d7d7db07f..7b4929bfb9 100644 --- a/plugins/azure-devops/src/components/GitTagTable/GitTagTable.tsx +++ b/plugins/azure-devops/src/components/GitTagTable/GitTagTable.tsx @@ -31,7 +31,7 @@ import { AzurePullRequestsIcon } from '../AzurePullRequestsIcon'; import { DateTime } from 'luxon'; import { PullRequestStatusButtonGroup } from '../PullRequestStatusButtonGroup'; import { useEntity } from '@backstage/plugin-catalog-react'; -import { usePullRequests } from '../../hooks/usePullRequests'; +import { useGitTags } from '../../hooks/useGitTags'; const columns: TableColumn[] = [ { @@ -96,7 +96,7 @@ export const GitTagTable = ({ defaultLimit }: PullRequestTableProps) => { useState(PullRequestStatus.Active); const { entity } = useEntity(); - const { items, loading, error } = usePullRequests( + const { items, loading, error } = useGitTags( entity, defaultLimit, pullRequestStatusState, diff --git a/plugins/azure-devops/src/hooks/useGitTags.ts b/plugins/azure-devops/src/hooks/useGitTags.ts new file mode 100644 index 0000000000..b55012c7f0 --- /dev/null +++ b/plugins/azure-devops/src/hooks/useGitTags.ts @@ -0,0 +1,58 @@ +/* + * 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 { + PullRequest, + PullRequestOptions, + PullRequestStatus, +} from '@backstage/plugin-azure-devops-common'; + +import { AZURE_DEVOPS_DEFAULT_TOP } from '../constants'; +import { Entity } from '@backstage/catalog-model'; +import { azureDevOpsApiRef } from '../api'; +import { useApi } from '@backstage/core-plugin-api'; +import useAsync from 'react-use/lib/useAsync'; +import { useProjectRepoFromEntity } from './useProjectRepoFromEntity'; + +export function useGitTags( + entity: Entity, + defaultLimit?: number, + requestedStatus?: PullRequestStatus, +): { + items?: PullRequest[]; + loading: boolean; + error?: Error; +} { + const top = defaultLimit ?? AZURE_DEVOPS_DEFAULT_TOP; + const status = requestedStatus ?? PullRequestStatus.Active; + const options: PullRequestOptions = { + top, + status, + }; + + const api = useApi(azureDevOpsApiRef); + const { project, repo } = useProjectRepoFromEntity(entity); + + const { value, loading, error } = useAsync(() => { + return api.getPullRequests(project, repo, options); + }, [api, project, repo, top, status]); + + return { + items: value?.items, + loading, + error, + }; +} From 00939d3b38d2ec1ed7c310262b8a3f888fdd8a8b Mon Sep 17 00:00:00 2001 From: mo Date: Fri, 29 Apr 2022 07:34:17 -0500 Subject: [PATCH 04/27] TODO: Signed-off-by: mo --- plugins/azure-devops/src/api/AzureDevOpsApi.ts | 1 + plugins/azure-devops/src/api/AzureDevOpsClient.ts | 1 + plugins/azure-devops/src/hooks/useGitTags.ts | 1 + 3 files changed, 3 insertions(+) diff --git a/plugins/azure-devops/src/api/AzureDevOpsApi.ts b/plugins/azure-devops/src/api/AzureDevOpsApi.ts index 63e6dc3125..e4f4e32b6b 100644 --- a/plugins/azure-devops/src/api/AzureDevOpsApi.ts +++ b/plugins/azure-devops/src/api/AzureDevOpsApi.ts @@ -39,6 +39,7 @@ export interface AzureDevOpsApi { ): Promise<{ items: RepoBuild[] }>; getGitTags( + // TODO: Keep iterating down this rabbit hole on the client once the service has caught up projectName: string, repoName: string, options?: PullRequestOptions, diff --git a/plugins/azure-devops/src/api/AzureDevOpsClient.ts b/plugins/azure-devops/src/api/AzureDevOpsClient.ts index 96c10a7b2e..87a6c7e404 100644 --- a/plugins/azure-devops/src/api/AzureDevOpsClient.ts +++ b/plugins/azure-devops/src/api/AzureDevOpsClient.ts @@ -29,6 +29,7 @@ import { DiscoveryApi, IdentityApi } from '@backstage/core-plugin-api'; import { AzureDevOpsApi } from './AzureDevOpsApi'; import { ResponseError } from '@backstage/errors'; +// TODO: See red squiggly below export class AzureDevOpsClient implements AzureDevOpsApi { private readonly discoveryApi: DiscoveryApi; private readonly identityApi: IdentityApi; diff --git a/plugins/azure-devops/src/hooks/useGitTags.ts b/plugins/azure-devops/src/hooks/useGitTags.ts index b55012c7f0..88930109a9 100644 --- a/plugins/azure-devops/src/hooks/useGitTags.ts +++ b/plugins/azure-devops/src/hooks/useGitTags.ts @@ -28,6 +28,7 @@ import useAsync from 'react-use/lib/useAsync'; import { useProjectRepoFromEntity } from './useProjectRepoFromEntity'; export function useGitTags( + // TODO: Keep iterating down this rabbit hole on the client once the service has caught up entity: Entity, defaultLimit?: number, requestedStatus?: PullRequestStatus, From b2d04967ba8a20722d62947c7106636c2a5b9a73 Mon Sep 17 00:00:00 2001 From: mo Date: Fri, 29 Apr 2022 07:50:03 -0500 Subject: [PATCH 05/27] client getGitTags, still calls /pull-requests/ Signed-off-by: mo --- .../azure-devops/src/api/AzureDevOpsApi.ts | 1 - .../azure-devops/src/api/AzureDevOpsClient.ts | 21 ++++++++++++++++++- plugins/azure-devops/src/hooks/useGitTags.ts | 2 +- 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/plugins/azure-devops/src/api/AzureDevOpsApi.ts b/plugins/azure-devops/src/api/AzureDevOpsApi.ts index e4f4e32b6b..63e6dc3125 100644 --- a/plugins/azure-devops/src/api/AzureDevOpsApi.ts +++ b/plugins/azure-devops/src/api/AzureDevOpsApi.ts @@ -39,7 +39,6 @@ export interface AzureDevOpsApi { ): Promise<{ items: RepoBuild[] }>; getGitTags( - // TODO: Keep iterating down this rabbit hole on the client once the service has caught up projectName: string, repoName: string, options?: PullRequestOptions, diff --git a/plugins/azure-devops/src/api/AzureDevOpsClient.ts b/plugins/azure-devops/src/api/AzureDevOpsClient.ts index 87a6c7e404..e25ea1e457 100644 --- a/plugins/azure-devops/src/api/AzureDevOpsClient.ts +++ b/plugins/azure-devops/src/api/AzureDevOpsClient.ts @@ -29,7 +29,6 @@ import { DiscoveryApi, IdentityApi } from '@backstage/core-plugin-api'; import { AzureDevOpsApi } from './AzureDevOpsApi'; import { ResponseError } from '@backstage/errors'; -// TODO: See red squiggly below export class AzureDevOpsClient implements AzureDevOpsApi { private readonly discoveryApi: DiscoveryApi; private readonly identityApi: IdentityApi; @@ -59,6 +58,26 @@ export class AzureDevOpsClient implements AzureDevOpsApi { return { items }; } + public async getGitTags( + projectName: string, + repoName: string, + options?: PullRequestOptions, + ): Promise<{ items: PullRequest[] }> { + const queryString = new URLSearchParams(); + if (options?.top) { + queryString.append('top', options.top.toString()); + } + if (options?.status) { + queryString.append('status', options.status.toString()); + } + const urlSegment = `pull-requests/${encodeURIComponent( + projectName, + )}/${encodeURIComponent(repoName)}?${queryString}`; + + const items = await this.get(urlSegment); + return { items }; + } + public async getPullRequests( projectName: string, repoName: string, diff --git a/plugins/azure-devops/src/hooks/useGitTags.ts b/plugins/azure-devops/src/hooks/useGitTags.ts index 88930109a9..a20d73d940 100644 --- a/plugins/azure-devops/src/hooks/useGitTags.ts +++ b/plugins/azure-devops/src/hooks/useGitTags.ts @@ -48,7 +48,7 @@ export function useGitTags( const { project, repo } = useProjectRepoFromEntity(entity); const { value, loading, error } = useAsync(() => { - return api.getPullRequests(project, repo, options); + return api.getGitTags(project, repo, options); }, [api, project, repo, top, status]); return { From f1412da0082bd1feb45d9e976040428cbe568514 Mon Sep 17 00:00:00 2001 From: mo Date: Fri, 29 Apr 2022 07:57:13 -0500 Subject: [PATCH 06/27] Added git-tags/ route to server. Client now calls /git-tags/ Signed-off-by: mo --- .../src/api/AzureDevOpsApi.ts | 32 +++++++++++++++++++ .../src/service/router.ts | 23 +++++++++++++ .../azure-devops/src/api/AzureDevOpsClient.ts | 2 +- 3 files changed, 56 insertions(+), 1 deletion(-) diff --git a/plugins/azure-devops-backend/src/api/AzureDevOpsApi.ts b/plugins/azure-devops-backend/src/api/AzureDevOpsApi.ts index ff63af183e..b8667db0f0 100644 --- a/plugins/azure-devops-backend/src/api/AzureDevOpsApi.ts +++ b/plugins/azure-devops-backend/src/api/AzureDevOpsApi.ts @@ -123,6 +123,38 @@ export class AzureDevOpsApi { return repoBuilds; } + public async getGitTags( + projectName: string, + repoName: string, + options: PullRequestOptions, + ): Promise { + this.logger?.debug( + `Calling Azure DevOps REST API, getting up to ${options.top} Pull Requests for Repository ${repoName} for Project ${projectName}`, + ); + + const gitRepository = await this.getGitRepository(projectName, repoName); + const client = await this.webApi.getGitApi(); + const searchCriteria: GitPullRequestSearchCriteria = { + status: options.status, + }; + const gitPullRequests = await client.getPullRequests( + gitRepository.id as string, + searchCriteria, + projectName, + undefined, + undefined, + options.top, + ); + const linkBaseUrl = `${this.webApi.serverUrl}/${encodeURIComponent( + projectName, + )}/_git/${encodeURIComponent(repoName)}/pullrequest`; + const pullRequests: PullRequest[] = gitPullRequests.map(gitPullRequest => { + return mappedPullRequest(gitPullRequest, linkBaseUrl); + }); + + return pullRequests; + } + public async getPullRequests( projectName: string, repoName: string, diff --git a/plugins/azure-devops-backend/src/service/router.ts b/plugins/azure-devops-backend/src/service/router.ts index f41209e4ff..e810ca87db 100644 --- a/plugins/azure-devops-backend/src/service/router.ts +++ b/plugins/azure-devops-backend/src/service/router.ts @@ -97,6 +97,29 @@ export async function createRouter( res.status(200).json(gitRepository); }); + router.get('/git-tags/:projectName/:repoName', async (req, res) => { + const { projectName, repoName } = req.params; + + const top = req.query.top ? Number(req.query.top) : DEFAULT_TOP; + + const status = req.query.status + ? Number(req.query.status) + : PullRequestStatus.Active; + + const pullRequestOptions: PullRequestOptions = { + top: top, + status: status, + }; + + const gitPullRequest = await azureDevOpsApi.getGitTags( + projectName, + repoName, + pullRequestOptions, + ); + + res.status(200).json(gitPullRequest); + }); + router.get('/pull-requests/:projectName/:repoName', async (req, res) => { const { projectName, repoName } = req.params; diff --git a/plugins/azure-devops/src/api/AzureDevOpsClient.ts b/plugins/azure-devops/src/api/AzureDevOpsClient.ts index e25ea1e457..4ebb72fcf9 100644 --- a/plugins/azure-devops/src/api/AzureDevOpsClient.ts +++ b/plugins/azure-devops/src/api/AzureDevOpsClient.ts @@ -70,7 +70,7 @@ export class AzureDevOpsClient implements AzureDevOpsApi { if (options?.status) { queryString.append('status', options.status.toString()); } - const urlSegment = `pull-requests/${encodeURIComponent( + const urlSegment = `git-tags/${encodeURIComponent( projectName, )}/${encodeURIComponent(repoName)}?${queryString}`; From a2dc735943e68f86a7dc43edca6c8b8e6d92ad50 Mon Sep 17 00:00:00 2001 From: mo Date: Fri, 29 Apr 2022 08:49:23 -0500 Subject: [PATCH 07/27] poc getting actual tags worksgit diff Signed-off-by: mo --- plugins/azure-devops-backend/src/api/AzureDevOpsApi.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/plugins/azure-devops-backend/src/api/AzureDevOpsApi.ts b/plugins/azure-devops-backend/src/api/AzureDevOpsApi.ts index b8667db0f0..045672136a 100644 --- a/plugins/azure-devops-backend/src/api/AzureDevOpsApi.ts +++ b/plugins/azure-devops-backend/src/api/AzureDevOpsApi.ts @@ -137,6 +137,14 @@ export class AzureDevOpsApi { const searchCriteria: GitPullRequestSearchCriteria = { status: options.status, }; + + const tagRefs = await client.getRefs( + gitRepository.id as string, + projectName, + 'tags', + ); + this.logger?.warn(JSON.stringify(tagRefs)); + const gitPullRequests = await client.getPullRequests( gitRepository.id as string, searchCriteria, From 8dfbd66c4abc9ebd688a03957c3b813ad1dc6a11 Mon Sep 17 00:00:00 2001 From: mo Date: Mon, 2 May 2022 08:11:26 -0500 Subject: [PATCH 08/27] iteratively transforming PullRequest[] into GitTag[] Signed-off-by: mo --- .../src/api/AzureDevOpsApi.ts | 35 ++++++++++++------- plugins/azure-devops-common/src/types.ts | 14 ++++++++ 2 files changed, 37 insertions(+), 12 deletions(-) diff --git a/plugins/azure-devops-backend/src/api/AzureDevOpsApi.ts b/plugins/azure-devops-backend/src/api/AzureDevOpsApi.ts index 045672136a..ac95aa0281 100644 --- a/plugins/azure-devops-backend/src/api/AzureDevOpsApi.ts +++ b/plugins/azure-devops-backend/src/api/AzureDevOpsApi.ts @@ -23,9 +23,11 @@ import { BuildRun, BuildStatus, DashboardPullRequest, + GitTag, Policy, PullRequest, PullRequestOptions, + PullRequestStatus, RepoBuild, Team, TeamMember, @@ -33,6 +35,7 @@ import { import { GitPullRequest, GitPullRequestSearchCriteria, + GitRef, GitRepository, } from 'azure-devops-node-api/interfaces/GitInterfaces'; import { @@ -138,29 +141,21 @@ export class AzureDevOpsApi { status: options.status, }; - const tagRefs = await client.getRefs( + const tagRefs: GitRef[] = await client.getRefs( gitRepository.id as string, projectName, 'tags', ); this.logger?.warn(JSON.stringify(tagRefs)); - const gitPullRequests = await client.getPullRequests( - gitRepository.id as string, - searchCriteria, - projectName, - undefined, - undefined, - options.top, - ); const linkBaseUrl = `${this.webApi.serverUrl}/${encodeURIComponent( projectName, )}/_git/${encodeURIComponent(repoName)}/pullrequest`; - const pullRequests: PullRequest[] = gitPullRequests.map(gitPullRequest => { - return mappedPullRequest(gitPullRequest, linkBaseUrl); + const gitTags: GitTag[] = tagRefs.map(tagRef => { + return mappedGitTag(tagRef, linkBaseUrl); }); - return pullRequests; + return gitTags; } public async getPullRequests( @@ -399,6 +394,22 @@ export function mappedRepoBuild(build: Build): RepoBuild { }; } +export function mappedGitTag(pullRequest: GitRef, linkBaseUrl: string): GitTag { + return { + pullRequestId: 5, + repoName: 'sup', + title: 'hello title', + uniqueName: 'N/A', + createdBy: 'N/A', + creationDate: 'plop', + sourceRefName: 'sourceRefName', + targetRefName: 'targetRefName', + status: PullRequestStatus.NotSet, + isDraft: false, + link: `${linkBaseUrl}/5`, + }; +} + export function mappedPullRequest( pullRequest: GitPullRequest, linkBaseUrl: string, diff --git a/plugins/azure-devops-common/src/types.ts b/plugins/azure-devops-common/src/types.ts index 7978e3326a..7391b7e41e 100644 --- a/plugins/azure-devops-common/src/types.ts +++ b/plugins/azure-devops-common/src/types.ts @@ -108,6 +108,20 @@ export enum PullRequestStatus { All = 4, } +export type GitTag = { + pullRequestId?: number; + repoName?: string; + title?: string; + uniqueName?: string; + createdBy?: string; + creationDate?: string; + sourceRefName?: string; + targetRefName?: string; + status?: PullRequestStatus; + isDraft?: boolean; + link: string; +}; + export type PullRequest = { pullRequestId?: number; repoName?: string; From 0c16cef574d808ee0b01c759db94650536551464 Mon Sep 17 00:00:00 2001 From: mo Date: Mon, 2 May 2022 10:11:53 -0500 Subject: [PATCH 09/27] server side now actually returning git tags shaped like git tags Signed-off-by: mo --- .../src/api/AzureDevOpsApi.ts | 21 +++++++++---------- plugins/azure-devops-common/src/types.ts | 12 +++-------- 2 files changed, 13 insertions(+), 20 deletions(-) diff --git a/plugins/azure-devops-backend/src/api/AzureDevOpsApi.ts b/plugins/azure-devops-backend/src/api/AzureDevOpsApi.ts index ac95aa0281..06b9eded36 100644 --- a/plugins/azure-devops-backend/src/api/AzureDevOpsApi.ts +++ b/plugins/azure-devops-backend/src/api/AzureDevOpsApi.ts @@ -145,6 +145,11 @@ export class AzureDevOpsApi { gitRepository.id as string, projectName, 'tags', + false, + false, + false, + false, + true, ); this.logger?.warn(JSON.stringify(tagRefs)); @@ -394,18 +399,12 @@ export function mappedRepoBuild(build: Build): RepoBuild { }; } -export function mappedGitTag(pullRequest: GitRef, linkBaseUrl: string): GitTag { +export function mappedGitTag(gitRef: GitRef, linkBaseUrl: string): GitTag { return { - pullRequestId: 5, - repoName: 'sup', - title: 'hello title', - uniqueName: 'N/A', - createdBy: 'N/A', - creationDate: 'plop', - sourceRefName: 'sourceRefName', - targetRefName: 'targetRefName', - status: PullRequestStatus.NotSet, - isDraft: false, + objectId: gitRef.objectId, + peeledObjectId: gitRef.peeledObjectId, + name: gitRef.name, + createdBy: gitRef.creator?.displayName ?? 'N/A', link: `${linkBaseUrl}/5`, }; } diff --git a/plugins/azure-devops-common/src/types.ts b/plugins/azure-devops-common/src/types.ts index 7391b7e41e..3cf3a9fa9c 100644 --- a/plugins/azure-devops-common/src/types.ts +++ b/plugins/azure-devops-common/src/types.ts @@ -109,16 +109,10 @@ export enum PullRequestStatus { } export type GitTag = { - pullRequestId?: number; - repoName?: string; - title?: string; - uniqueName?: string; + objectId?: string; + peeledObjectId?: string; + name?: string; createdBy?: string; - creationDate?: string; - sourceRefName?: string; - targetRefName?: string; - status?: PullRequestStatus; - isDraft?: boolean; link: string; }; From 5f1a43c8540fdc9f5d4b707a040b0980645634ec Mon Sep 17 00:00:00 2001 From: mo Date: Mon, 2 May 2022 10:33:23 -0500 Subject: [PATCH 10/27] render actual git tag data in table in client Signed-off-by: mo --- .../azure-devops/src/api/AzureDevOpsApi.ts | 3 +- .../azure-devops/src/api/AzureDevOpsClient.ts | 5 ++- .../components/GitTagTable/GitTagTable.tsx | 43 ++----------------- plugins/azure-devops/src/hooks/useGitTags.ts | 4 +- 4 files changed, 11 insertions(+), 44 deletions(-) diff --git a/plugins/azure-devops/src/api/AzureDevOpsApi.ts b/plugins/azure-devops/src/api/AzureDevOpsApi.ts index 63e6dc3125..49cb7f9644 100644 --- a/plugins/azure-devops/src/api/AzureDevOpsApi.ts +++ b/plugins/azure-devops/src/api/AzureDevOpsApi.ts @@ -18,6 +18,7 @@ import { BuildRun, BuildRunOptions, DashboardPullRequest, + GitTag, PullRequest, PullRequestOptions, RepoBuild, @@ -42,7 +43,7 @@ export interface AzureDevOpsApi { projectName: string, repoName: string, options?: PullRequestOptions, - ): Promise<{ items: PullRequest[] }>; + ): Promise<{ items: GitTag[] }>; getPullRequests( projectName: string, diff --git a/plugins/azure-devops/src/api/AzureDevOpsClient.ts b/plugins/azure-devops/src/api/AzureDevOpsClient.ts index 4ebb72fcf9..a0fe8d8791 100644 --- a/plugins/azure-devops/src/api/AzureDevOpsClient.ts +++ b/plugins/azure-devops/src/api/AzureDevOpsClient.ts @@ -18,6 +18,7 @@ import { BuildRun, BuildRunOptions, DashboardPullRequest, + GitTag, PullRequest, PullRequestOptions, RepoBuild, @@ -62,7 +63,7 @@ export class AzureDevOpsClient implements AzureDevOpsApi { projectName: string, repoName: string, options?: PullRequestOptions, - ): Promise<{ items: PullRequest[] }> { + ): Promise<{ items: GitTag[] }> { const queryString = new URLSearchParams(); if (options?.top) { queryString.append('top', options.top.toString()); @@ -74,7 +75,7 @@ export class AzureDevOpsClient implements AzureDevOpsApi { projectName, )}/${encodeURIComponent(repoName)}?${queryString}`; - const items = await this.get(urlSegment); + const items = await this.get(urlSegment); return { items }; } diff --git a/plugins/azure-devops/src/components/GitTagTable/GitTagTable.tsx b/plugins/azure-devops/src/components/GitTagTable/GitTagTable.tsx index 7b4929bfb9..3d37f1af31 100644 --- a/plugins/azure-devops/src/components/GitTagTable/GitTagTable.tsx +++ b/plugins/azure-devops/src/components/GitTagTable/GitTagTable.tsx @@ -35,39 +35,14 @@ import { useGitTags } from '../../hooks/useGitTags'; const columns: TableColumn[] = [ { - title: 'ID', - field: 'pullRequestId', + title: 'Tag', + field: 'name', highlight: false, width: 'auto', }, { - title: 'Title', - field: 'title', - width: 'auto', - render: (row: Partial) => ( - - {row.title} - {row.isDraft && ( - - - - )} - - ), - }, - { - title: 'Source', - field: 'sourceRefName', - width: 'auto', - }, - { - title: 'Target', - field: 'targetRefName', + title: 'Commit', + field: 'peeledObjectId', width: 'auto', }, { @@ -75,16 +50,6 @@ const columns: TableColumn[] = [ field: 'createdBy', width: 'auto', }, - { - title: 'Created', - field: 'creationDate', - width: 'auto', - render: (row: Partial) => - (row.creationDate - ? DateTime.fromISO(row.creationDate) - : DateTime.now() - ).toRelative(), - }, ]; type PullRequestTableProps = { diff --git a/plugins/azure-devops/src/hooks/useGitTags.ts b/plugins/azure-devops/src/hooks/useGitTags.ts index a20d73d940..2a513f1492 100644 --- a/plugins/azure-devops/src/hooks/useGitTags.ts +++ b/plugins/azure-devops/src/hooks/useGitTags.ts @@ -15,7 +15,7 @@ */ import { - PullRequest, + GitTag, PullRequestOptions, PullRequestStatus, } from '@backstage/plugin-azure-devops-common'; @@ -33,7 +33,7 @@ export function useGitTags( defaultLimit?: number, requestedStatus?: PullRequestStatus, ): { - items?: PullRequest[]; + items?: GitTag[]; loading: boolean; error?: Error; } { From 75b0a51d1a24b4626874a0fa7c8118774146e18c Mon Sep 17 00:00:00 2001 From: mo Date: Mon, 2 May 2022 11:40:23 -0500 Subject: [PATCH 11/27] cleaning up server side Signed-off-by: mo --- .../src/api/AzureDevOpsApi.ts | 19 +++++++---------- .../src/service/router.ts | 21 ++----------------- 2 files changed, 9 insertions(+), 31 deletions(-) diff --git a/plugins/azure-devops-backend/src/api/AzureDevOpsApi.ts b/plugins/azure-devops-backend/src/api/AzureDevOpsApi.ts index 06b9eded36..d4551fc0d2 100644 --- a/plugins/azure-devops-backend/src/api/AzureDevOpsApi.ts +++ b/plugins/azure-devops-backend/src/api/AzureDevOpsApi.ts @@ -129,18 +129,13 @@ export class AzureDevOpsApi { public async getGitTags( projectName: string, repoName: string, - options: PullRequestOptions, - ): Promise { + ): Promise { this.logger?.debug( - `Calling Azure DevOps REST API, getting up to ${options.top} Pull Requests for Repository ${repoName} for Project ${projectName}`, + `Calling Azure DevOps REST API, getting Git Tags for Repository ${repoName} for Project ${projectName}`, ); const gitRepository = await this.getGitRepository(projectName, repoName); const client = await this.webApi.getGitApi(); - const searchCriteria: GitPullRequestSearchCriteria = { - status: options.status, - }; - const tagRefs: GitRef[] = await client.getRefs( gitRepository.id as string, projectName, @@ -151,11 +146,9 @@ export class AzureDevOpsApi { false, true, ); - this.logger?.warn(JSON.stringify(tagRefs)); - const linkBaseUrl = `${this.webApi.serverUrl}/${encodeURIComponent( projectName, - )}/_git/${encodeURIComponent(repoName)}/pullrequest`; + )}/_git/${encodeURIComponent(repoName)}?version=GT`; const gitTags: GitTag[] = tagRefs.map(tagRef => { return mappedGitTag(tagRef, linkBaseUrl); }); @@ -403,9 +396,11 @@ export function mappedGitTag(gitRef: GitRef, linkBaseUrl: string): GitTag { return { objectId: gitRef.objectId, peeledObjectId: gitRef.peeledObjectId, - name: gitRef.name, + name: gitRef.name?.replace('refs/tags/', ''), createdBy: gitRef.creator?.displayName ?? 'N/A', - link: `${linkBaseUrl}/5`, + link: `${linkBaseUrl}${encodeURIComponent( + gitRef.name?.replace('refs/tags/', '') ?? '', + )}`, }; } diff --git a/plugins/azure-devops-backend/src/service/router.ts b/plugins/azure-devops-backend/src/service/router.ts index e810ca87db..23ed7f146d 100644 --- a/plugins/azure-devops-backend/src/service/router.ts +++ b/plugins/azure-devops-backend/src/service/router.ts @@ -99,25 +99,8 @@ export async function createRouter( router.get('/git-tags/:projectName/:repoName', async (req, res) => { const { projectName, repoName } = req.params; - - const top = req.query.top ? Number(req.query.top) : DEFAULT_TOP; - - const status = req.query.status - ? Number(req.query.status) - : PullRequestStatus.Active; - - const pullRequestOptions: PullRequestOptions = { - top: top, - status: status, - }; - - const gitPullRequest = await azureDevOpsApi.getGitTags( - projectName, - repoName, - pullRequestOptions, - ); - - res.status(200).json(gitPullRequest); + const gitTags = await azureDevOpsApi.getGitTags(projectName, repoName); + res.status(200).json(gitTags); }); router.get('/pull-requests/:projectName/:repoName', async (req, res) => { From 86e94970230d88893371caca90562dfe4b2d4a2c Mon Sep 17 00:00:00 2001 From: mo Date: Mon, 2 May 2022 12:16:42 -0500 Subject: [PATCH 12/27] cleaning up frontend Signed-off-by: mo --- .../src/api/AzureDevOpsApi.ts | 1 - .../azure-devops/src/api/AzureDevOpsApi.ts | 1 - .../azure-devops/src/api/AzureDevOpsClient.ts | 10 +----- .../EntityPageAzureGitTags.tsx | 2 +- .../components/GitTagTable/GitTagTable.tsx | 36 ++++++------------- plugins/azure-devops/src/hooks/useGitTags.ts | 25 +++---------- 6 files changed, 16 insertions(+), 59 deletions(-) diff --git a/plugins/azure-devops-backend/src/api/AzureDevOpsApi.ts b/plugins/azure-devops-backend/src/api/AzureDevOpsApi.ts index d4551fc0d2..d77de5d528 100644 --- a/plugins/azure-devops-backend/src/api/AzureDevOpsApi.ts +++ b/plugins/azure-devops-backend/src/api/AzureDevOpsApi.ts @@ -27,7 +27,6 @@ import { Policy, PullRequest, PullRequestOptions, - PullRequestStatus, RepoBuild, Team, TeamMember, diff --git a/plugins/azure-devops/src/api/AzureDevOpsApi.ts b/plugins/azure-devops/src/api/AzureDevOpsApi.ts index 49cb7f9644..ccedc401f1 100644 --- a/plugins/azure-devops/src/api/AzureDevOpsApi.ts +++ b/plugins/azure-devops/src/api/AzureDevOpsApi.ts @@ -42,7 +42,6 @@ export interface AzureDevOpsApi { getGitTags( projectName: string, repoName: string, - options?: PullRequestOptions, ): Promise<{ items: GitTag[] }>; getPullRequests( diff --git a/plugins/azure-devops/src/api/AzureDevOpsClient.ts b/plugins/azure-devops/src/api/AzureDevOpsClient.ts index a0fe8d8791..61c8479d71 100644 --- a/plugins/azure-devops/src/api/AzureDevOpsClient.ts +++ b/plugins/azure-devops/src/api/AzureDevOpsClient.ts @@ -62,18 +62,10 @@ export class AzureDevOpsClient implements AzureDevOpsApi { public async getGitTags( projectName: string, repoName: string, - options?: PullRequestOptions, ): Promise<{ items: GitTag[] }> { - const queryString = new URLSearchParams(); - if (options?.top) { - queryString.append('top', options.top.toString()); - } - if (options?.status) { - queryString.append('status', options.status.toString()); - } const urlSegment = `git-tags/${encodeURIComponent( projectName, - )}/${encodeURIComponent(repoName)}?${queryString}`; + )}/${encodeURIComponent(repoName)}`; const items = await this.get(urlSegment); return { items }; diff --git a/plugins/azure-devops/src/components/EntityPageAzureGitTags/EntityPageAzureGitTags.tsx b/plugins/azure-devops/src/components/EntityPageAzureGitTags/EntityPageAzureGitTags.tsx index 8f997e079e..139549966e 100644 --- a/plugins/azure-devops/src/components/EntityPageAzureGitTags/EntityPageAzureGitTags.tsx +++ b/plugins/azure-devops/src/components/EntityPageAzureGitTags/EntityPageAzureGitTags.tsx @@ -22,5 +22,5 @@ export const EntityPageAzureGitTags = ({ }: { defaultLimit?: number; }) => { - return ; + return ; }; diff --git a/plugins/azure-devops/src/components/GitTagTable/GitTagTable.tsx b/plugins/azure-devops/src/components/GitTagTable/GitTagTable.tsx index 3d37f1af31..4b8f63e187 100644 --- a/plugins/azure-devops/src/components/GitTagTable/GitTagTable.tsx +++ b/plugins/azure-devops/src/components/GitTagTable/GitTagTable.tsx @@ -14,22 +14,17 @@ * limitations under the License. */ -import { Box, Chip } from '@material-ui/core'; +import { Box } from '@material-ui/core'; import { Link, ResponseErrorPanel, Table, TableColumn, } from '@backstage/core-components'; -import { - PullRequest, - PullRequestStatus, -} from '@backstage/plugin-azure-devops-common'; -import React, { useState } from 'react'; +import { GitTag } from '@backstage/plugin-azure-devops-common'; +import React from 'react'; import { AzurePullRequestsIcon } from '../AzurePullRequestsIcon'; -import { DateTime } from 'luxon'; -import { PullRequestStatusButtonGroup } from '../PullRequestStatusButtonGroup'; import { useEntity } from '@backstage/plugin-catalog-react'; import { useGitTags } from '../../hooks/useGitTags'; @@ -39,6 +34,11 @@ const columns: TableColumn[] = [ field: 'name', highlight: false, width: 'auto', + render: (row: Partial) => ( + + {row.name} + + ), }, { title: 'Commit', @@ -52,20 +52,10 @@ const columns: TableColumn[] = [ }, ]; -type PullRequestTableProps = { - defaultLimit?: number; -}; - -export const GitTagTable = ({ defaultLimit }: PullRequestTableProps) => { - const [pullRequestStatusState, setPullRequestStatusState] = - useState(PullRequestStatus.Active); +export const GitTagTable = () => { const { entity } = useEntity(); - const { items, loading, error } = useGitTags( - entity, - defaultLimit, - pullRequestStatusState, - ); + const { items, loading, error } = useGitTags(entity); if (error) { return ( @@ -90,12 +80,6 @@ export const GitTagTable = ({ defaultLimit }: PullRequestTableProps) => { Azure Repos - Git Tags ({items ? items.length : 0}) - - - } data={items ?? []} diff --git a/plugins/azure-devops/src/hooks/useGitTags.ts b/plugins/azure-devops/src/hooks/useGitTags.ts index 2a513f1492..b940a7105d 100644 --- a/plugins/azure-devops/src/hooks/useGitTags.ts +++ b/plugins/azure-devops/src/hooks/useGitTags.ts @@ -14,42 +14,25 @@ * limitations under the License. */ -import { - GitTag, - PullRequestOptions, - PullRequestStatus, -} from '@backstage/plugin-azure-devops-common'; +import { GitTag } from '@backstage/plugin-azure-devops-common'; -import { AZURE_DEVOPS_DEFAULT_TOP } from '../constants'; import { Entity } from '@backstage/catalog-model'; import { azureDevOpsApiRef } from '../api'; import { useApi } from '@backstage/core-plugin-api'; import useAsync from 'react-use/lib/useAsync'; import { useProjectRepoFromEntity } from './useProjectRepoFromEntity'; -export function useGitTags( - // TODO: Keep iterating down this rabbit hole on the client once the service has caught up - entity: Entity, - defaultLimit?: number, - requestedStatus?: PullRequestStatus, -): { +export function useGitTags(entity: Entity): { items?: GitTag[]; loading: boolean; error?: Error; } { - const top = defaultLimit ?? AZURE_DEVOPS_DEFAULT_TOP; - const status = requestedStatus ?? PullRequestStatus.Active; - const options: PullRequestOptions = { - top, - status, - }; - const api = useApi(azureDevOpsApiRef); const { project, repo } = useProjectRepoFromEntity(entity); const { value, loading, error } = useAsync(() => { - return api.getGitTags(project, repo, options); - }, [api, project, repo, top, status]); + return api.getGitTags(project, repo); + }, [api, project, repo]); return { items: value?.items, From f786570c0ebfdb8245dac9c96966506833117e6f Mon Sep 17 00:00:00 2001 From: mo Date: Mon, 2 May 2022 13:47:35 -0500 Subject: [PATCH 13/27] hyperlink the commit hash Signed-off-by: mo --- .../azure-devops-backend/src/api/AzureDevOpsApi.ts | 14 ++++++++++++-- plugins/azure-devops-common/src/types.ts | 1 + .../src/components/GitTagTable/GitTagTable.tsx | 5 +++++ 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/plugins/azure-devops-backend/src/api/AzureDevOpsApi.ts b/plugins/azure-devops-backend/src/api/AzureDevOpsApi.ts index d77de5d528..b6cc174056 100644 --- a/plugins/azure-devops-backend/src/api/AzureDevOpsApi.ts +++ b/plugins/azure-devops-backend/src/api/AzureDevOpsApi.ts @@ -148,8 +148,11 @@ export class AzureDevOpsApi { const linkBaseUrl = `${this.webApi.serverUrl}/${encodeURIComponent( projectName, )}/_git/${encodeURIComponent(repoName)}?version=GT`; + const commitBaseUrl = `${this.webApi.serverUrl}/${encodeURIComponent( + projectName, + )}/_git/${encodeURIComponent(repoName)}/commit`; const gitTags: GitTag[] = tagRefs.map(tagRef => { - return mappedGitTag(tagRef, linkBaseUrl); + return mappedGitTag(tagRef, linkBaseUrl, commitBaseUrl); }); return gitTags; @@ -391,7 +394,11 @@ export function mappedRepoBuild(build: Build): RepoBuild { }; } -export function mappedGitTag(gitRef: GitRef, linkBaseUrl: string): GitTag { +export function mappedGitTag( + gitRef: GitRef, + linkBaseUrl: string, + commitBaseUrl: string, +): GitTag { return { objectId: gitRef.objectId, peeledObjectId: gitRef.peeledObjectId, @@ -400,6 +407,9 @@ export function mappedGitTag(gitRef: GitRef, linkBaseUrl: string): GitTag { link: `${linkBaseUrl}${encodeURIComponent( gitRef.name?.replace('refs/tags/', '') ?? '', )}`, + commitLink: `${commitBaseUrl}/${encodeURIComponent( + gitRef.peeledObjectId ?? '', + )}`, }; } diff --git a/plugins/azure-devops-common/src/types.ts b/plugins/azure-devops-common/src/types.ts index 3cf3a9fa9c..0ff5e897ed 100644 --- a/plugins/azure-devops-common/src/types.ts +++ b/plugins/azure-devops-common/src/types.ts @@ -114,6 +114,7 @@ export type GitTag = { name?: string; createdBy?: string; link: string; + commitLink: string; }; export type PullRequest = { diff --git a/plugins/azure-devops/src/components/GitTagTable/GitTagTable.tsx b/plugins/azure-devops/src/components/GitTagTable/GitTagTable.tsx index 4b8f63e187..5ed304ee7d 100644 --- a/plugins/azure-devops/src/components/GitTagTable/GitTagTable.tsx +++ b/plugins/azure-devops/src/components/GitTagTable/GitTagTable.tsx @@ -44,6 +44,11 @@ const columns: TableColumn[] = [ title: 'Commit', field: 'peeledObjectId', width: 'auto', + render: (row: Partial) => ( + + {row.peeledObjectId} + + ), }, { title: 'Created By', From 716da38f7dc867534ac9fcae2285a1ff707385eb Mon Sep 17 00:00:00 2001 From: mo Date: Mon, 2 May 2022 14:13:51 -0500 Subject: [PATCH 14/27] default sort Signed-off-by: mo --- plugins/azure-devops/src/components/GitTagTable/GitTagTable.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/azure-devops/src/components/GitTagTable/GitTagTable.tsx b/plugins/azure-devops/src/components/GitTagTable/GitTagTable.tsx index 5ed304ee7d..b3fa05d6db 100644 --- a/plugins/azure-devops/src/components/GitTagTable/GitTagTable.tsx +++ b/plugins/azure-devops/src/components/GitTagTable/GitTagTable.tsx @@ -33,6 +33,7 @@ const columns: TableColumn[] = [ title: 'Tag', field: 'name', highlight: false, + defaultSort: 'desc', width: 'auto', render: (row: Partial) => ( From 1183f1d667bbd314b153fb4b029988aa8f5b38da Mon Sep 17 00:00:00 2001 From: mo Date: Tue, 3 May 2022 08:02:38 -0500 Subject: [PATCH 15/27] mappedGitTag happy path Signed-off-by: mo --- .../src/api/AzureDevOpsApi.test.ts | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/plugins/azure-devops-backend/src/api/AzureDevOpsApi.test.ts b/plugins/azure-devops-backend/src/api/AzureDevOpsApi.test.ts index 76b5089bbc..dade60f1ed 100644 --- a/plugins/azure-devops-backend/src/api/AzureDevOpsApi.test.ts +++ b/plugins/azure-devops-backend/src/api/AzureDevOpsApi.test.ts @@ -21,16 +21,19 @@ import { import { BuildResult, BuildStatus, + GitTag, PullRequest, PullRequestStatus, RepoBuild, } from '@backstage/plugin-azure-devops-common'; import { GitPullRequest, + GitRef, GitRepository, } from 'azure-devops-node-api/interfaces/GitInterfaces'; import { mappedBuildRun, + mappedGitTag, mappedPullRequest, mappedRepoBuild, } from './AzureDevOpsApi'; @@ -263,6 +266,38 @@ describe('AzureDevOpsApi', () => { }); }); + describe('mappedGitTag', () => { + describe('mappedGitTag happy path', () => { + it('should return GitTag from GitRef', () => { + const inputIdentityRef: IdentityRef = { + displayName: 'Jane Doe', + }; + const inputGitRef: GitRef = { + name: 'refs/tags/v1.1.2', + creator: inputIdentityRef, + objectId: '1111aaaa2222bbbb3333cccc4444dddd5555eeee', + peeledObjectId: '1234567890abcdef1234567890abcdef12345678', + }; + const inputLinkBaseUrl = + 'https://host.com/myOrg/_git/super-feature-repo?version=GT'; + const inputCommitBaseUrl = + 'https://host.com/myOrg/_git/super-feature-repo/commit'; + const outputGitTag: GitTag = { + name: 'v1.1.2', + createdBy: 'Jane Doe', + commitLink: + 'https://host.com/myOrg/_git/super-feature-repo/commit/1234567890abcdef1234567890abcdef12345678', + objectId: '1111aaaa2222bbbb3333cccc4444dddd5555eeee', + peeledObjectId: '1234567890abcdef1234567890abcdef12345678', + link: 'https://host.com/myOrg/_git/super-feature-repo?version=GTv1.1.2', + }; + expect( + mappedGitTag(inputGitRef, inputLinkBaseUrl, inputCommitBaseUrl), + ).toEqual(outputGitTag); + }); + }); + }); + describe('mappedPullRequest', () => { describe('mappedPullRequest happy path', () => { it('should return PullRequest from GitPullRequest', () => { From def3dd7b9a625a2e70dde4f33b4041e29af23c88 Mon Sep 17 00:00:00 2001 From: mo Date: Tue, 3 May 2022 10:18:07 -0500 Subject: [PATCH 16/27] test: GET /git-tags/:projectName/:repoName Signed-off-by: mo --- .../src/service/router.test.ts | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/plugins/azure-devops-backend/src/service/router.test.ts b/plugins/azure-devops-backend/src/service/router.test.ts index e6cd856c0a..3bf18de6a2 100644 --- a/plugins/azure-devops-backend/src/service/router.test.ts +++ b/plugins/azure-devops-backend/src/service/router.test.ts @@ -22,6 +22,7 @@ import { BuildResult, BuildRun, BuildStatus, + GitTag, PullRequest, PullRequestStatus, RepoBuild, @@ -46,6 +47,7 @@ describe('createRouter', () => { getBuildDefinitions: jest.fn(), getRepoBuilds: jest.fn(), getDefinitionBuilds: jest.fn(), + getGitTags: jest.fn(), getPullRequests: jest.fn(), getBuilds: jest.fn(), getBuildRuns: jest.fn(), @@ -208,6 +210,43 @@ describe('createRouter', () => { }); }); + describe('GET /git-tags/:projectName/:repoName', () => { + it('fetches a list of git tags', async () => { + const firstGitTag: GitTag = { + name: 'v1.1.2', + createdBy: 'Jane Doe', + commitLink: + 'https://host.com/myOrg/_git/super-feature-repo/commit/1234567890abcdef1234567890abcdef12345678', + objectId: '1111aaaa2222bbbb3333cccc4444dddd5555eeee', + peeledObjectId: '1234567890abcdef1234567890abcdef12345678', + link: 'https://host.com/myOrg/_git/super-feature-repo?version=GTv1.1.2', + }; + + const secondGitTag: GitTag = { + name: 'v1.2.0', + createdBy: 'Jane Doe', + commitLink: + 'https://host.com/myOrg/_git/super-feature-repo/commit/2222222222222222222222222222222222222222', + objectId: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', + peeledObjectId: '2222222222222222222222222222222222222222', + link: 'https://host.com/myOrg/_git/super-feature-repo?version=GTv1.2.0', + }; + + const gitTags: GitTag[] = [firstGitTag, secondGitTag]; + + azureDevOpsApi.getGitTags.mockResolvedValueOnce(gitTags); + + const response = await request(app).get('/git-tags/myProject/myRepo'); + + expect(azureDevOpsApi.getGitTags).toHaveBeenCalledWith( + 'myProject', + 'myRepo', + ); + expect(response.status).toEqual(200); + expect(response.body).toEqual(gitTags); + }); + }); + describe('GET /pull-requests/:projectName/:repoName', () => { it('fetches a list of pull requests', async () => { const firstPullRequest: PullRequest = { From 960d2360aa59034e5e41b7600595e2666e1317d2 Mon Sep 17 00:00:00 2001 From: mo Date: Tue, 3 May 2022 10:54:03 -0500 Subject: [PATCH 17/27] hide git tags tab if={isAzureDevOpsAvailable} Signed-off-by: mo --- packages/app/src/components/catalog/EntityPage.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/app/src/components/catalog/EntityPage.tsx b/packages/app/src/components/catalog/EntityPage.tsx index 2b1ba6e492..e3a9a467a9 100644 --- a/packages/app/src/components/catalog/EntityPage.tsx +++ b/packages/app/src/components/catalog/EntityPage.tsx @@ -487,7 +487,11 @@ const websiteEntityPage = ( - + {gitTagsContent} From cf3b0fb622c5e94fa6909ba99e51c435293439cf Mon Sep 17 00:00:00 2001 From: mo Date: Tue, 3 May 2022 11:21:58 -0500 Subject: [PATCH 18/27] Making EntityPage better match the existing plugin-azure-devops README documentation Signed-off-by: mo --- packages/app/src/components/catalog/EntityPage.tsx | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/packages/app/src/components/catalog/EntityPage.tsx b/packages/app/src/components/catalog/EntityPage.tsx index e3a9a467a9..22d6cc3ab1 100644 --- a/packages/app/src/components/catalog/EntityPage.tsx +++ b/packages/app/src/components/catalog/EntityPage.tsx @@ -282,14 +282,6 @@ const errorsContent = ( ); -const gitTagsContent = ( - - - - - -); - const pullRequestsContent = ( @@ -492,7 +484,7 @@ const websiteEntityPage = ( path="/git-tags" title="Git Tags" > - {gitTagsContent} + From d8d46777c8be67b007f747d7f1ac0d1b84c334ae Mon Sep 17 00:00:00 2001 From: mo Date: Tue, 3 May 2022 11:24:13 -0500 Subject: [PATCH 19/27] README git tags Signed-off-by: mo --- plugins/azure-devops/README.md | 38 ++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/plugins/azure-devops/README.md b/plugins/azure-devops/README.md index a55ed983b7..fc97001338 100644 --- a/plugins/azure-devops/README.md +++ b/plugins/azure-devops/README.md @@ -16,6 +16,8 @@ Lists the top _n_ Active, Completed, or Abandoned Pull Requests for a given repo ![Azure Repos Pull Requests Example](./docs/azure-devops-pull-requests.png) +Additional Git Tags component that lists all git tags for a given Azure Repo + ## Setup The following sections will help you get the Azure DevOps plugin setup and running @@ -155,6 +157,42 @@ To get the Azure Repos component working you'll need to do the following two ste - The `if` prop is optional on the `EntityLayout.Route`, you can remove it if you always want to see the tab even if the entity being viewed does not have the needed annotation - The `defaultLimit` property on the `EntityAzurePullRequestsContent` will set the max number of Pull Requests you would like to see, if not set this will default to 10 +### Git Tags Component + +To get the Git Tags component 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 + yarn add --cwd packages/app @backstage/plugin-azure-devops + ``` + +2. Second we need to add the `EntityAzureGitTagsContent` extension to the entity page in your app: + + ```tsx + // In packages/app/src/components/catalog/EntityPage.tsx + import { + EntityAzureGitTagsContent, + isAzureDevOpsAvailable, + } from '@backstage/plugin-azure-devops'; + + // For example in the Service section + const serviceEntityPage = ( + + // ... + + + + // ... + + ``` + +**Notes:** + +- You'll need to add the `EntityLayout.Route` above from step 2 to all the entity sections you want to see Git Tags in. For example if you wanted to see Pull Requests when looking at Website entities then you would need to add this to the `websiteEntityPage` section. +- The `if` prop is optional on the `EntityLayout.Route`, you can remove it if you always want to see the tab even if the entity being viewed does not have the needed annotation + ## Limitations - Currently multiple organizations are not supported From 0afc01197174196c6b58fbcd81e6aa227fc348c4 Mon Sep 17 00:00:00 2001 From: mo Date: Tue, 3 May 2022 14:39:04 -0500 Subject: [PATCH 20/27] AzureGitTagsIcon Signed-off-by: mo --- .../AzureGitTagsIcon/AzureGitTagsIcon.tsx | 26 +++++++++++++++++++ .../src/components/AzureGitTagsIcon/index.ts | 17 ++++++++++++ .../components/GitTagTable/GitTagTable.tsx | 4 +-- 3 files changed, 45 insertions(+), 2 deletions(-) create mode 100644 plugins/azure-devops/src/components/AzureGitTagsIcon/AzureGitTagsIcon.tsx create mode 100644 plugins/azure-devops/src/components/AzureGitTagsIcon/index.ts diff --git a/plugins/azure-devops/src/components/AzureGitTagsIcon/AzureGitTagsIcon.tsx b/plugins/azure-devops/src/components/AzureGitTagsIcon/AzureGitTagsIcon.tsx new file mode 100644 index 0000000000..c8b63e3259 --- /dev/null +++ b/plugins/azure-devops/src/components/AzureGitTagsIcon/AzureGitTagsIcon.tsx @@ -0,0 +1,26 @@ +/* + * 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 { SvgIcon, SvgIconProps } from '@material-ui/core'; + +import React from 'react'; + +// From https://github.com/microsoft/fluentui-system-icons/blob/30a3e3c458883a1d63a43a951407f30e6b32b60c/assets/Tag/SVG/ic_fluent_tag_32_regular.svg +export const AzureGitTagsIcon = (props: SvgIconProps) => ( + + + +); diff --git a/plugins/azure-devops/src/components/AzureGitTagsIcon/index.ts b/plugins/azure-devops/src/components/AzureGitTagsIcon/index.ts new file mode 100644 index 0000000000..92660244a5 --- /dev/null +++ b/plugins/azure-devops/src/components/AzureGitTagsIcon/index.ts @@ -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 { AzureGitTagsIcon } from './AzureGitTagsIcon'; diff --git a/plugins/azure-devops/src/components/GitTagTable/GitTagTable.tsx b/plugins/azure-devops/src/components/GitTagTable/GitTagTable.tsx index b3fa05d6db..55a5f6602e 100644 --- a/plugins/azure-devops/src/components/GitTagTable/GitTagTable.tsx +++ b/plugins/azure-devops/src/components/GitTagTable/GitTagTable.tsx @@ -24,7 +24,7 @@ import { import { GitTag } from '@backstage/plugin-azure-devops-common'; import React from 'react'; -import { AzurePullRequestsIcon } from '../AzurePullRequestsIcon'; +import { AzureGitTagsIcon } from '../AzureGitTagsIcon'; import { useEntity } from '@backstage/plugin-catalog-react'; import { useGitTags } from '../../hooks/useGitTags'; @@ -83,7 +83,7 @@ export const GitTagTable = () => { }} title={ - + Azure Repos - Git Tags ({items ? items.length : 0}) From be578ff78a20e392d3f7fcf2da4fb0fc3f712754 Mon Sep 17 00:00:00 2001 From: mo Date: Tue, 3 May 2022 14:42:08 -0500 Subject: [PATCH 21/27] Added screenshot to documentation Signed-off-by: mo --- plugins/azure-devops/README.md | 6 +++++- .../azure-devops/docs/azure-devops-git-tags.png | Bin 0 -> 56216 bytes 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 plugins/azure-devops/docs/azure-devops-git-tags.png diff --git a/plugins/azure-devops/README.md b/plugins/azure-devops/README.md index fc97001338..1479590870 100644 --- a/plugins/azure-devops/README.md +++ b/plugins/azure-devops/README.md @@ -16,7 +16,11 @@ Lists the top _n_ Active, Completed, or Abandoned Pull Requests for a given repo ![Azure Repos Pull Requests Example](./docs/azure-devops-pull-requests.png) -Additional Git Tags component that lists all git tags for a given Azure Repo +### Azure Git Tags + +Lists all Git Tags for a given repository + +![Azure Repos Pull Requests Example](./docs/azure-devops-git-tags.png) ## Setup diff --git a/plugins/azure-devops/docs/azure-devops-git-tags.png b/plugins/azure-devops/docs/azure-devops-git-tags.png new file mode 100644 index 0000000000000000000000000000000000000000..a992273298108da657ef104e6efd6da4e8726ac1 GIT binary patch literal 56216 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

G`612#l4!70V7`do7%Am|yG&mpN+O-M)JLa#!< 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 t=30GZTey8bs4=vGFYV;6o5}ru^h_9kKub literal 0 HcmV?d00001 From ac14fcaf38cf079d96860da213c7ba608e687873 Mon Sep 17 00:00:00 2001 From: mo Date: Tue, 3 May 2022 15:07:51 -0500 Subject: [PATCH 22/27] changeset Signed-off-by: mo --- .changeset/wise-countries-watch.md | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .changeset/wise-countries-watch.md diff --git a/.changeset/wise-countries-watch.md b/.changeset/wise-countries-watch.md new file mode 100644 index 0000000000..30e8cdeb7b --- /dev/null +++ b/.changeset/wise-countries-watch.md @@ -0,0 +1,8 @@ +--- +'example-app': patch +'@backstage/plugin-azure-devops': patch +'@backstage/plugin-azure-devops-backend': patch +'@backstage/plugin-azure-devops-common': patch +--- + +Added entity view for Azure Git Tags, based on existing Pull Requests view From bf65abfcb0db94a7cd0577927708fbf789d51511 Mon Sep 17 00:00:00 2001 From: mo Date: Wed, 4 May 2022 10:38:17 -0500 Subject: [PATCH 23/27] tsc feedback Signed-off-by: mo --- .../EntityPageAzureGitTags/EntityPageAzureGitTags.tsx | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/plugins/azure-devops/src/components/EntityPageAzureGitTags/EntityPageAzureGitTags.tsx b/plugins/azure-devops/src/components/EntityPageAzureGitTags/EntityPageAzureGitTags.tsx index 139549966e..2d94d735df 100644 --- a/plugins/azure-devops/src/components/EntityPageAzureGitTags/EntityPageAzureGitTags.tsx +++ b/plugins/azure-devops/src/components/EntityPageAzureGitTags/EntityPageAzureGitTags.tsx @@ -17,10 +17,4 @@ import { GitTagTable } from '../GitTagTable/GitTagTable'; import React from 'react'; -export const EntityPageAzureGitTags = ({ - defaultLimit, -}: { - defaultLimit?: number; -}) => { - return ; -}; +export const EntityPageAzureGitTags = () => ; From 5171b45f96f60b7def102ca2e7c91afc849911d7 Mon Sep 17 00:00:00 2001 From: mo Date: Wed, 4 May 2022 14:21:55 -0500 Subject: [PATCH 24/27] generated api-report.md files Signed-off-by: mo --- plugins/azure-devops-common/api-report.md | 12 ++++++++++++ plugins/azure-devops/api-report.md | 5 +++++ 2 files changed, 17 insertions(+) diff --git a/plugins/azure-devops-common/api-report.md b/plugins/azure-devops-common/api-report.md index 308fd54c61..bdb4cc424d 100644 --- a/plugins/azure-devops-common/api-report.md +++ b/plugins/azure-devops-common/api-report.md @@ -98,6 +98,18 @@ export interface DashboardPullRequest { title?: string; } +// Warning: (ae-missing-release-tag) "GitTag" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export type GitTag = { + objectId?: string; + peeledObjectId?: string; + name?: string; + createdBy?: string; + link: string; + commitLink: string; +}; + // Warning: (ae-missing-release-tag) "Policy" 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/api-report.md b/plugins/azure-devops/api-report.md index eaba90664b..9760340771 100644 --- a/plugins/azure-devops/api-report.md +++ b/plugins/azure-devops/api-report.md @@ -136,6 +136,11 @@ export type CreatedByUserFilter = BaseFilter & } ); +// Warning: (ae-missing-release-tag) "EntityAzureGitTagsContent" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const EntityAzureGitTagsContent: () => JSX.Element; + // 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) From 0674f294e33291bb78fdf3cc5d5f8dda4d565c25 Mon Sep 17 00:00:00 2001 From: mo Date: Wed, 4 May 2022 15:10:36 -0500 Subject: [PATCH 25/27] additional api-report.md after fixing my local tree Signed-off-by: mo --- plugins/azure-devops-backend/api-report.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugins/azure-devops-backend/api-report.md b/plugins/azure-devops-backend/api-report.md index f9b8b75b82..e3e3d9e161 100644 --- a/plugins/azure-devops-backend/api-report.md +++ b/plugins/azure-devops-backend/api-report.md @@ -10,6 +10,7 @@ import { Config } from '@backstage/config'; import { DashboardPullRequest } from '@backstage/plugin-azure-devops-common'; import express from 'express'; import { GitRepository } from 'azure-devops-node-api/interfaces/GitInterfaces'; +import { GitTag } from '@backstage/plugin-azure-devops-common'; import { Logger } from 'winston'; import { PullRequest } from '@backstage/plugin-azure-devops-common'; import { PullRequestOptions } from '@backstage/plugin-azure-devops-common'; @@ -61,6 +62,8 @@ export class AzureDevOpsApi { repoName: string, ): Promise; // (undocumented) + getGitTags(projectName: string, repoName: string): Promise; + // (undocumented) getPullRequests( projectName: string, repoName: string, From 282f9b8c7eae35978acc0af4891b8a63ab044975 Mon Sep 17 00:00:00 2001 From: mo Date: Wed, 4 May 2022 15:26:19 -0500 Subject: [PATCH 26/27] removed extraneous package in changeset Signed-off-by: mo --- .changeset/wise-countries-watch.md | 1 - 1 file changed, 1 deletion(-) diff --git a/.changeset/wise-countries-watch.md b/.changeset/wise-countries-watch.md index 30e8cdeb7b..e48e7bbecb 100644 --- a/.changeset/wise-countries-watch.md +++ b/.changeset/wise-countries-watch.md @@ -1,5 +1,4 @@ --- -'example-app': patch '@backstage/plugin-azure-devops': patch '@backstage/plugin-azure-devops-backend': patch '@backstage/plugin-azure-devops-common': patch From 1ea0a9db4ed736f90090f70c4709602652da8ebf Mon Sep 17 00:00:00 2001 From: mo Date: Fri, 6 May 2022 15:01:15 -0500 Subject: [PATCH 27/27] PR feedback Signed-off-by: mo --- plugins/azure-devops/README.md | 6 +++--- .../azure-devops/src/components/GitTagTable/GitTagTable.tsx | 6 +----- .../src/components/PullRequestTable/PullRequestTable.tsx | 6 +----- 3 files changed, 5 insertions(+), 13 deletions(-) diff --git a/plugins/azure-devops/README.md b/plugins/azure-devops/README.md index 1479590870..68d2b57afc 100644 --- a/plugins/azure-devops/README.md +++ b/plugins/azure-devops/README.md @@ -16,11 +16,11 @@ Lists the top _n_ Active, Completed, or Abandoned Pull Requests for a given repo ![Azure Repos Pull Requests Example](./docs/azure-devops-pull-requests.png) -### Azure Git Tags +### Azure Repos Git Tags Lists all Git Tags for a given repository -![Azure Repos Pull Requests Example](./docs/azure-devops-git-tags.png) +![Azure Repos Git Tags Example](./docs/azure-devops-git-tags.png) ## Setup @@ -194,7 +194,7 @@ To get the Git Tags component working you'll need to do the following two steps: **Notes:** -- You'll need to add the `EntityLayout.Route` above from step 2 to all the entity sections you want to see Git Tags in. For example if you wanted to see Pull Requests when looking at Website entities then you would need to add this to the `websiteEntityPage` section. +- You'll need to add the `EntityLayout.Route` above from step 2 to all the entity sections you want to see Git Tags in. For example if you wanted to see Git Tags when looking at Website entities then you would need to add this to the `websiteEntityPage` section. - The `if` prop is optional on the `EntityLayout.Route`, you can remove it if you always want to see the tab even if the entity being viewed does not have the needed annotation ## Limitations diff --git a/plugins/azure-devops/src/components/GitTagTable/GitTagTable.tsx b/plugins/azure-devops/src/components/GitTagTable/GitTagTable.tsx index 55a5f6602e..c6abba55e1 100644 --- a/plugins/azure-devops/src/components/GitTagTable/GitTagTable.tsx +++ b/plugins/azure-devops/src/components/GitTagTable/GitTagTable.tsx @@ -64,11 +64,7 @@ export const GitTagTable = () => { const { items, loading, error } = useGitTags(entity); if (error) { - return ( -

- -
- ); + return ; } return ( diff --git a/plugins/azure-devops/src/components/PullRequestTable/PullRequestTable.tsx b/plugins/azure-devops/src/components/PullRequestTable/PullRequestTable.tsx index 4365b796bc..b37ba6e73c 100644 --- a/plugins/azure-devops/src/components/PullRequestTable/PullRequestTable.tsx +++ b/plugins/azure-devops/src/components/PullRequestTable/PullRequestTable.tsx @@ -103,11 +103,7 @@ export const PullRequestTable = ({ defaultLimit }: PullRequestTableProps) => { ); if (error) { - return ( -
- -
- ); + return ; } return (