diff --git a/.changeset/rare-tips-glow.md b/.changeset/rare-tips-glow.md new file mode 100644 index 0000000000..6667fd5871 --- /dev/null +++ b/.changeset/rare-tips-glow.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-kubernetes': patch +--- + +Reset error state on success diff --git a/plugins/kubernetes/api-report.md b/plugins/kubernetes/api-report.md index 5cf1c3ed34..81ce386200 100644 --- a/plugins/kubernetes/api-report.md +++ b/plugins/kubernetes/api-report.md @@ -337,9 +337,9 @@ export const KubernetesDrawer: ({ // @public (undocumented) export interface KubernetesObjects { // (undocumented) - error: string | undefined; + error?: string; // (undocumented) - kubernetesObjects: ObjectsByEntityResponse | undefined; + kubernetesObjects?: ObjectsByEntityResponse; } // Warning: (ae-missing-release-tag) "kubernetesPlugin" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) diff --git a/plugins/kubernetes/src/hooks/useKubernetesObjects.test.ts b/plugins/kubernetes/src/hooks/useKubernetesObjects.test.ts index e5efafd477..4b66467403 100644 --- a/plugins/kubernetes/src/hooks/useKubernetesObjects.test.ts +++ b/plugins/kubernetes/src/hooks/useKubernetesObjects.test.ts @@ -125,6 +125,8 @@ describe('useKubernetesObjects', () => { ); await waitForNextUpdate(); + expect(result.current.error).toBeUndefined(); + await waitForNextUpdate(); expect(result.current.error).toBeUndefined(); @@ -199,4 +201,159 @@ describe('useKubernetesObjects', () => { }); expect(mockGetObjectsByEntity).toHaveBeenCalledTimes(0); }); + + describe('when retrying', () => { + it('should reset error after getClusters has failed and then succeeded', async () => { + (useApi as any).mockReturnValue({ + getClusters: mockGetClusters + .mockRejectedValueOnce({ message: 'some-error' }) + .mockResolvedValue(getClustersResponse), + decorateRequestBodyForAuth: + mockDecorateRequestBodyForAuth.mockResolvedValue(entityWithAuthToken), + getObjectsByEntity: + mockGetObjectsByEntity.mockResolvedValue(mockResponse), + }); + + const { result, waitForNextUpdate } = renderHook(() => + useKubernetesObjects(entity, 100), + ); + + await waitForNextUpdate(); + + expect(result.current.error).toBe('some-error'); + expect(result.current.kubernetesObjects).toBeUndefined(); + + await waitForNextUpdate(); + + expect(result.current.error).toBeUndefined(); + expect(result.current.kubernetesObjects).not.toBeUndefined(); + }); + + it('should reset error after decorateRequestBodyForAuth has failed and then succeeded', async () => { + (useApi as any).mockReturnValue({ + getClusters: mockGetClusters.mockResolvedValue(getClustersResponse), + decorateRequestBodyForAuth: mockDecorateRequestBodyForAuth + .mockRejectedValueOnce({ message: 'decoration failed' }) + .mockResolvedValue(entityWithAuthToken), + getObjectsByEntity: + mockGetObjectsByEntity.mockResolvedValue(mockResponse), + }); + + const { result, waitForNextUpdate } = renderHook(() => + useKubernetesObjects(entity, 100), + ); + + await waitForNextUpdate(); + + expect(result.current.error).toBe('decoration failed'); + expect(result.current.kubernetesObjects).toBeUndefined(); + + await waitForNextUpdate(); + + expect(result.current.error).toBeUndefined(); + expect(result.current.kubernetesObjects).not.toBeUndefined(); + }); + + it('should reset error after getObjectsByEntity has failed and then succeeded', async () => { + (useApi as any).mockReturnValue({ + getClusters: mockGetClusters.mockResolvedValue(getClustersResponse), + decorateRequestBodyForAuth: + mockDecorateRequestBodyForAuth.mockResolvedValue(entityWithAuthToken), + getObjectsByEntity: mockGetObjectsByEntity + .mockRejectedValueOnce({ message: 'failed to fetch' }) + .mockResolvedValue(mockResponse), + }); + + const { result, waitForNextUpdate } = renderHook(() => + useKubernetesObjects(entity, 100), + ); + + await waitForNextUpdate(); + + expect(result.current.error).toBe('failed to fetch'); + expect(result.current.kubernetesObjects).toBeUndefined(); + + await waitForNextUpdate(); + + expect(result.current.error).toBeUndefined(); + expect(result.current.kubernetesObjects).not.toBeUndefined(); + }); + + it('should reset data after getClusters succeeded then failed', async () => { + (useApi as any).mockReturnValue({ + getClusters: mockGetClusters + .mockResolvedValueOnce(getClustersResponse) + .mockRejectedValue({ message: 'fetch clusters failed' }), + decorateRequestBodyForAuth: + mockDecorateRequestBodyForAuth.mockResolvedValue(entityWithAuthToken), + getObjectsByEntity: + mockGetObjectsByEntity.mockResolvedValue(mockResponse), + }); + const { result, waitForNextUpdate } = renderHook(() => + useKubernetesObjects(entity, 100), + ); + + await waitForNextUpdate(); + + expect(result.current.error).toBeUndefined(); + expect(result.current.kubernetesObjects).not.toBeUndefined(); + + await waitForNextUpdate(); + + expect(result.current.error).toBe('fetch clusters failed'); + expect(result.current.kubernetesObjects).toBeUndefined(); + }); + + it('should reset data after decorateBodyForAuth succeeded then failed', async () => { + (useApi as any).mockReturnValue({ + getClusters: mockGetClusters.mockResolvedValue(getClustersResponse), + decorateRequestBodyForAuth: mockDecorateRequestBodyForAuth + // this call happens twice per successful hook render + .mockResolvedValueOnce(entityWithAuthToken) + .mockResolvedValueOnce(entityWithAuthToken) + .mockRejectedValue({ message: 'decorate failed' }), + getObjectsByEntity: + mockGetObjectsByEntity.mockResolvedValue(mockResponse), + }); + + const { result, waitForNextUpdate } = renderHook(() => + useKubernetesObjects(entity, 100), + ); + + await waitForNextUpdate(); + + expect(result.current.error).toBeUndefined(); + expect(result.current.kubernetesObjects).not.toBeUndefined(); + + await waitForNextUpdate(); + + expect(result.current.error).toBe('decorate failed'); + expect(result.current.kubernetesObjects).toBeUndefined(); + }); + + it('should reset data after getObjectsByEntity succeeded then failed', async () => { + (useApi as any).mockReturnValue({ + getClusters: mockGetClusters.mockResolvedValue(getClustersResponse), + decorateRequestBodyForAuth: + mockDecorateRequestBodyForAuth.mockResolvedValue(entityWithAuthToken), + getObjectsByEntity: mockGetObjectsByEntity + .mockResolvedValueOnce(mockResponse) + .mockRejectedValue({ message: 'failed to fetch' }), + }); + + const { result, waitForNextUpdate } = renderHook(() => + useKubernetesObjects(entity, 100), + ); + + await waitForNextUpdate(); + + expect(result.current.error).toBeUndefined(); + expect(result.current.kubernetesObjects).not.toBeUndefined(); + + await waitForNextUpdate(); + + expect(result.current.error).toBe('failed to fetch'); + expect(result.current.kubernetesObjects).toBeUndefined(); + }); + }); }); diff --git a/plugins/kubernetes/src/hooks/useKubernetesObjects.ts b/plugins/kubernetes/src/hooks/useKubernetesObjects.ts index 8e2597c1d2..421ed787da 100644 --- a/plugins/kubernetes/src/hooks/useKubernetesObjects.ts +++ b/plugins/kubernetes/src/hooks/useKubernetesObjects.ts @@ -26,8 +26,8 @@ import { import { useApi } from '@backstage/core-plugin-api'; export interface KubernetesObjects { - kubernetesObjects: ObjectsByEntityResponse | undefined; - error: string | undefined; + kubernetesObjects?: ObjectsByEntityResponse; + error?: string; } export const useKubernetesObjects = ( @@ -36,11 +36,10 @@ export const useKubernetesObjects = ( ): KubernetesObjects => { const kubernetesApi = useApi(kubernetesApiRef); const kubernetesAuthProvidersApi = useApi(kubernetesAuthProvidersApiRef); - const [kubernetesObjects, setKubernetesObjects] = useState< - ObjectsByEntityResponse | undefined - >(undefined); - - const [error, setError] = useState(undefined); + const [result, setResult] = useState({ + kubernetesObjects: undefined, + error: undefined, + }); const getObjects = async () => { let clusters = []; @@ -48,7 +47,7 @@ export const useKubernetesObjects = ( try { clusters = await kubernetesApi.getClusters(); } catch (e) { - setError(e.message); + setResult({ error: e.message }); return; } @@ -76,15 +75,16 @@ export const useKubernetesObjects = ( requestBody, ); } catch (e) { - setError(e.message); + setResult({ error: e.message }); return; } } try { - setKubernetesObjects(await kubernetesApi.getObjectsByEntity(requestBody)); + const objects = await kubernetesApi.getObjectsByEntity(requestBody); + setResult({ kubernetesObjects: objects }); } catch (e) { - setError(e.message); + setResult({ error: e.message }); return; } }; @@ -99,8 +99,5 @@ export const useKubernetesObjects = ( getObjects(); }, intervalMs); - return { - kubernetesObjects, - error, - }; + return result; };