From 2cf775e43adb4937082d0132231e6c4eda12afa3 Mon Sep 17 00:00:00 2001 From: Andre Wanlin Date: Sat, 11 Nov 2023 14:19:02 -0600 Subject: [PATCH 1/3] fix: refactored error handling Signed-off-by: Andre Wanlin --- .../EntityPageAzurePipelines.tsx | 10 +- .../src/hooks/useBuildRuns.test.tsx | 126 ++++++++++++++++++ .../azure-devops/src/hooks/useBuildRuns.ts | 11 +- plugins/azure-devops/src/hooks/useGitTags.ts | 8 +- .../azure-devops/src/hooks/usePullRequests.ts | 8 +- plugins/azure-devops/src/hooks/useReadme.ts | 8 +- .../azure-devops/src/hooks/useRepoBuilds.ts | 11 +- 7 files changed, 152 insertions(+), 30 deletions(-) create mode 100644 plugins/azure-devops/src/hooks/useBuildRuns.test.tsx diff --git a/plugins/azure-devops/src/components/EntityPageAzurePipelines/EntityPageAzurePipelines.tsx b/plugins/azure-devops/src/components/EntityPageAzurePipelines/EntityPageAzurePipelines.tsx index 02a61a9a9a..95d1a365b9 100644 --- a/plugins/azure-devops/src/components/EntityPageAzurePipelines/EntityPageAzurePipelines.tsx +++ b/plugins/azure-devops/src/components/EntityPageAzurePipelines/EntityPageAzurePipelines.tsx @@ -16,21 +16,13 @@ import { BuildTable } from '../BuildTable/BuildTable'; import React from 'react'; -import { getAnnotationValuesFromEntity } from '../../utils/getAnnotationValuesFromEntity'; import { useBuildRuns } from '../../hooks/useBuildRuns'; import { useEntity } from '@backstage/plugin-catalog-react'; export const EntityPageAzurePipelines = (props: { defaultLimit?: number }) => { const { entity } = useEntity(); - const { project, repo, definition } = getAnnotationValuesFromEntity(entity); - - const { items, loading, error } = useBuildRuns( - project, - props.defaultLimit, - repo, - definition, - ); + const { items, loading, error } = useBuildRuns(entity, props.defaultLimit); return ; }; diff --git a/plugins/azure-devops/src/hooks/useBuildRuns.test.tsx b/plugins/azure-devops/src/hooks/useBuildRuns.test.tsx new file mode 100644 index 0000000000..409602eec4 --- /dev/null +++ b/plugins/azure-devops/src/hooks/useBuildRuns.test.tsx @@ -0,0 +1,126 @@ +/* + * 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 { renderHook, waitFor } from '@testing-library/react'; +import { Entity } from '@backstage/catalog-model'; +import { BuildRun } from '@backstage/plugin-azure-devops-common'; +import { TestApiProvider } from '@backstage/test-utils'; +import { AzureDevOpsApi, azureDevOpsApiRef } from '../api'; +import { useBuildRuns } from './useBuildRuns'; + +describe('useBuildRuns', () => { + const azureDevOpsApiMock = { + getBuildRuns: jest.fn(), + }; + const azureDevOpsApi = + azureDevOpsApiMock as Partial as AzureDevOpsApi; + + const Wrapper = (props: { children?: React.ReactNode }) => ( + + {props.children} + + ); + + it('should provide an array of BuildRun', async () => { + const entity: Entity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + namespace: 'default', + name: 'project-repo', + annotations: { + 'dev.azure.com/project-repo': 'projectName/repoName', + }, + }, + }; + const buildRuns: BuildRun[] = [ + { + id: 1, + title: 'title-1', + source: 'awanlin', + link: 'https://dev.azure.com/org/project/repo', + }, + { + id: 2, + title: 'title-2', + source: 'awanlin', + link: 'https://dev.azure.com/org/project/repo', + }, + { + id: 3, + title: 'title-3', + source: 'awanlin', + link: 'https://dev.azure.com/org/project/repo', + }, + ]; + azureDevOpsApiMock.getBuildRuns.mockResolvedValue({ + items: buildRuns, + }); + const { result } = renderHook(() => useBuildRuns(entity), { + wrapper: Wrapper, + }); + + expect(result.current.loading).toEqual(true); + + await waitFor(() => { + expect(result.current).toEqual({ + error: undefined, + items: buildRuns, + loading: false, + }); + }); + }); + + it('should return throw when annotation missing', async () => { + const entity: Entity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + namespace: 'default', + name: 'project-repo', + }, + }; + + expect(() => + renderHook(() => useBuildRuns(entity), { + wrapper: Wrapper, + }), + ).toThrow('Value for annotation "dev.azure.com/project" was not found'); + }); + + it('should return throw when annotation invalid', async () => { + const entity: Entity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + namespace: 'default', + name: 'project-repo', + annotations: { + 'dev.azure.com/project-repo': 'fake', + }, + }, + }; + + expect(() => + renderHook(() => useBuildRuns(entity), { + wrapper: Wrapper, + }), + ).toThrow( + 'Invalid value for annotation "dev.azure.com/project-repo"; expected format is: /, found: "fake"', + ); + }); +}); diff --git a/plugins/azure-devops/src/hooks/useBuildRuns.ts b/plugins/azure-devops/src/hooks/useBuildRuns.ts index ae0273de44..2b70ea63a5 100644 --- a/plugins/azure-devops/src/hooks/useBuildRuns.ts +++ b/plugins/azure-devops/src/hooks/useBuildRuns.ts @@ -23,12 +23,12 @@ import { AZURE_DEVOPS_DEFAULT_TOP } from '../constants'; import { azureDevOpsApiRef } from '../api'; import { useApi } from '@backstage/core-plugin-api'; import useAsync from 'react-use/lib/useAsync'; +import { getAnnotationValuesFromEntity } from '../utils'; +import { Entity } from '@backstage/catalog-model'; export function useBuildRuns( - projectName: string, + entity: Entity, defaultLimit?: number, - repoName?: string, - definitionName?: string, ): { items?: BuildRun[]; loading: boolean; @@ -42,8 +42,9 @@ export function useBuildRuns( const api = useApi(azureDevOpsApiRef); const { value, loading, error } = useAsync(() => { - return api.getBuildRuns(projectName, repoName, definitionName, options); - }, [api, projectName, repoName, definitionName]); + const { project, repo, definition } = getAnnotationValuesFromEntity(entity); + return api.getBuildRuns(project, repo, definition, options); + }, [api]); return { items: value?.items, diff --git a/plugins/azure-devops/src/hooks/useGitTags.ts b/plugins/azure-devops/src/hooks/useGitTags.ts index 13c7118934..8a2fb2825d 100644 --- a/plugins/azure-devops/src/hooks/useGitTags.ts +++ b/plugins/azure-devops/src/hooks/useGitTags.ts @@ -28,11 +28,11 @@ export function useGitTags(entity: Entity): { error?: Error; } { const api = useApi(azureDevOpsApiRef); - const { project, repo } = getAnnotationValuesFromEntity(entity); - const { value, loading, error } = useAsync(async () => { - return await api.getGitTags(project, repo as string); - }, [api, project, repo]); + const { value, loading, error } = useAsync(() => { + const { project, repo } = getAnnotationValuesFromEntity(entity); + return api.getGitTags(project, repo as string); + }, [api]); return { items: value?.items, diff --git a/plugins/azure-devops/src/hooks/usePullRequests.ts b/plugins/azure-devops/src/hooks/usePullRequests.ts index c4223871f2..de886919eb 100644 --- a/plugins/azure-devops/src/hooks/usePullRequests.ts +++ b/plugins/azure-devops/src/hooks/usePullRequests.ts @@ -44,11 +44,11 @@ export function usePullRequests( }; const api = useApi(azureDevOpsApiRef); - const { project, repo } = getAnnotationValuesFromEntity(entity); - const { value, loading, error } = useAsync(async () => { - return await api.getPullRequests(project, repo as string, options); - }, [api, project, repo, top, status]); + const { value, loading, error } = useAsync(() => { + const { project, repo } = getAnnotationValuesFromEntity(entity); + return api.getPullRequests(project, repo as string, options); + }, [api, top, status]); return { items: value?.items, diff --git a/plugins/azure-devops/src/hooks/useReadme.ts b/plugins/azure-devops/src/hooks/useReadme.ts index 60b2e21990..67d3c5dfd0 100644 --- a/plugins/azure-devops/src/hooks/useReadme.ts +++ b/plugins/azure-devops/src/hooks/useReadme.ts @@ -28,11 +28,11 @@ export function useReadme(entity: Entity): { error?: Error; } { const api = useApi(azureDevOpsApiRef); - const { project, repo } = getAnnotationValuesFromEntity(entity); - const { value, loading, error } = useAsync(async () => { - return await api.getReadme({ project, repo: repo as string }); - }, [api, project, repo]); + const { value, loading, error } = useAsync(() => { + const { project, repo } = getAnnotationValuesFromEntity(entity); + return api.getReadme({ project, repo: repo as string }); + }, [api]); return { item: value, diff --git a/plugins/azure-devops/src/hooks/useRepoBuilds.ts b/plugins/azure-devops/src/hooks/useRepoBuilds.ts index 9292a6e035..17fc6bbfe5 100644 --- a/plugins/azure-devops/src/hooks/useRepoBuilds.ts +++ b/plugins/azure-devops/src/hooks/useRepoBuilds.ts @@ -26,6 +26,9 @@ import { useApi } from '@backstage/core-plugin-api'; import useAsync from 'react-use/lib/useAsync'; import { getAnnotationValuesFromEntity } from '../utils'; +/** + * @deprecated Use `useBuildRuns` instead + */ export function useRepoBuilds( entity: Entity, defaultLimit?: number, @@ -40,11 +43,11 @@ export function useRepoBuilds( }; const api = useApi(azureDevOpsApiRef); - const { project, repo } = getAnnotationValuesFromEntity(entity); - const { value, loading, error } = useAsync(async () => { - return await api.getRepoBuilds(project, repo as string, options); - }, [api, project, repo, entity]); + const { value, loading, error } = useAsync(() => { + const { project, repo } = getAnnotationValuesFromEntity(entity); + return api.getRepoBuilds(project, repo as string, options); + }, [api, entity]); return { items: value?.items, From ce9e59cbb3147ff5369a38e78977e20943aaf2ae Mon Sep 17 00:00:00 2001 From: Andre Wanlin Date: Sat, 11 Nov 2023 14:35:07 -0600 Subject: [PATCH 2/3] chore: added changeset Signed-off-by: Andre Wanlin --- .changeset/loud-zoos-impress.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/loud-zoos-impress.md diff --git a/.changeset/loud-zoos-impress.md b/.changeset/loud-zoos-impress.md new file mode 100644 index 0000000000..bdc43c368c --- /dev/null +++ b/.changeset/loud-zoos-impress.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-azure-devops': patch +--- + +Refactored the error handling for some of the hooks From 1a67fd8f118d32dafca98c1b45551a94c5a0e85e Mon Sep 17 00:00:00 2001 From: Andre Wanlin Date: Mon, 13 Nov 2023 15:28:54 -0600 Subject: [PATCH 3/3] chore: removed hook Signed-off-by: Andre Wanlin --- .../EntityPageAzurePipelines.tsx | 2 +- plugins/azure-devops/src/hooks/index.ts | 2 +- .../src/hooks/useRepoBuilds.test.tsx | 126 ------------------ .../azure-devops/src/hooks/useRepoBuilds.ts | 57 -------- 4 files changed, 2 insertions(+), 185 deletions(-) delete mode 100644 plugins/azure-devops/src/hooks/useRepoBuilds.test.tsx delete mode 100644 plugins/azure-devops/src/hooks/useRepoBuilds.ts diff --git a/plugins/azure-devops/src/components/EntityPageAzurePipelines/EntityPageAzurePipelines.tsx b/plugins/azure-devops/src/components/EntityPageAzurePipelines/EntityPageAzurePipelines.tsx index 95d1a365b9..c33d849c24 100644 --- a/plugins/azure-devops/src/components/EntityPageAzurePipelines/EntityPageAzurePipelines.tsx +++ b/plugins/azure-devops/src/components/EntityPageAzurePipelines/EntityPageAzurePipelines.tsx @@ -16,7 +16,7 @@ import { BuildTable } from '../BuildTable/BuildTable'; import React from 'react'; -import { useBuildRuns } from '../../hooks/useBuildRuns'; +import { useBuildRuns } from '../../hooks'; import { useEntity } from '@backstage/plugin-catalog-react'; export const EntityPageAzurePipelines = (props: { defaultLimit?: number }) => { diff --git a/plugins/azure-devops/src/hooks/index.ts b/plugins/azure-devops/src/hooks/index.ts index ab1a3c9998..82ca86d055 100644 --- a/plugins/azure-devops/src/hooks/index.ts +++ b/plugins/azure-devops/src/hooks/index.ts @@ -17,7 +17,7 @@ export * from './useAllTeams'; export * from './useDashboardPullRequests'; export * from './usePullRequests'; -export * from './useRepoBuilds'; export * from './useUserEmail'; export * from './useUserTeamIds'; export * from './useReadme'; +export * from './useBuildRuns'; diff --git a/plugins/azure-devops/src/hooks/useRepoBuilds.test.tsx b/plugins/azure-devops/src/hooks/useRepoBuilds.test.tsx deleted file mode 100644 index 99eaeb1ce3..0000000000 --- a/plugins/azure-devops/src/hooks/useRepoBuilds.test.tsx +++ /dev/null @@ -1,126 +0,0 @@ -/* - * 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 { renderHook, waitFor } from '@testing-library/react'; -import { Entity } from '@backstage/catalog-model'; -import { RepoBuild } from '@backstage/plugin-azure-devops-common'; -import { TestApiProvider } from '@backstage/test-utils'; -import { AzureDevOpsApi, azureDevOpsApiRef } from '../api'; -import { useRepoBuilds } from './useRepoBuilds'; - -describe('useRepoBuilds', () => { - const azureDevOpsApiMock = { - getRepoBuilds: jest.fn(), - }; - const azureDevOpsApi = - azureDevOpsApiMock as Partial as AzureDevOpsApi; - - const Wrapper = (props: { children?: React.ReactNode }) => ( - - {props.children} - - ); - - it('should provide an array of RepoBuild', async () => { - const entity: Entity = { - apiVersion: 'backstage.io/v1alpha1', - kind: 'Component', - metadata: { - namespace: 'default', - name: 'project-repo', - annotations: { - 'dev.azure.com/project-repo': 'projectName/repoName', - }, - }, - }; - const repoBuilds: RepoBuild[] = [ - { - id: 1, - title: 'title-1', - source: 'awanlin', - link: 'https://dev.azure.com/org/project/repo', - }, - { - id: 2, - title: 'title-2', - source: 'awanlin', - link: 'https://dev.azure.com/org/project/repo', - }, - { - id: 3, - title: 'title-3', - source: 'awanlin', - link: 'https://dev.azure.com/org/project/repo', - }, - ]; - azureDevOpsApiMock.getRepoBuilds.mockResolvedValue({ - items: repoBuilds, - }); - const { result } = renderHook(() => useRepoBuilds(entity), { - wrapper: Wrapper, - }); - - expect(result.current.loading).toEqual(true); - - await waitFor(() => { - expect(result.current).toEqual({ - error: undefined, - items: repoBuilds, - loading: false, - }); - }); - }); - - it('should return throw when annotation missing', async () => { - const entity: Entity = { - apiVersion: 'backstage.io/v1alpha1', - kind: 'Component', - metadata: { - namespace: 'default', - name: 'project-repo', - }, - }; - - expect(() => - renderHook(() => useRepoBuilds(entity), { - wrapper: Wrapper, - }), - ).toThrow('Value for annotation "dev.azure.com/project" was not found'); - }); - - it('should return throw when annotation invalid', async () => { - const entity: Entity = { - apiVersion: 'backstage.io/v1alpha1', - kind: 'Component', - metadata: { - namespace: 'default', - name: 'project-repo', - annotations: { - 'dev.azure.com/project-repo': 'fake', - }, - }, - }; - - expect(() => - renderHook(() => useRepoBuilds(entity), { - wrapper: Wrapper, - }), - ).toThrow( - 'Invalid value for annotation "dev.azure.com/project-repo"; expected format is: /, found: "fake"', - ); - }); -}); diff --git a/plugins/azure-devops/src/hooks/useRepoBuilds.ts b/plugins/azure-devops/src/hooks/useRepoBuilds.ts deleted file mode 100644 index 17fc6bbfe5..0000000000 --- a/plugins/azure-devops/src/hooks/useRepoBuilds.ts +++ /dev/null @@ -1,57 +0,0 @@ -/* - * 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 '@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 { getAnnotationValuesFromEntity } from '../utils'; - -/** - * @deprecated Use `useBuildRuns` instead - */ -export function useRepoBuilds( - entity: Entity, - defaultLimit?: number, -): { - items?: RepoBuild[]; - loading: boolean; - error?: Error; -} { - const top = defaultLimit ?? AZURE_DEVOPS_DEFAULT_TOP; - const options: RepoBuildOptions = { - top: top, - }; - - const api = useApi(azureDevOpsApiRef); - - const { value, loading, error } = useAsync(() => { - const { project, repo } = getAnnotationValuesFromEntity(entity); - return api.getRepoBuilds(project, repo as string, options); - }, [api, entity]); - - return { - items: value?.items, - loading, - error, - }; -}