From 9f8dd192c99f06e64986e506d42f3f36b41650a6 Mon Sep 17 00:00:00 2001 From: Tomas Aschan Date: Mon, 5 Sep 2022 22:20:42 +0200 Subject: [PATCH 1/8] Reset error on success path If a re-render - or simply the intervalMs parameter - causes a retry that turns a previously failed operation into a successful one, the error was previously not reset, resulting in the somewhat odd combination of an error message and a full, successful, response data object. This change ensures that the error state is reset every time an operation succeeds, so the UI state remains consistent. Signed-off-by: Tomas Aschan --- plugins/kubernetes/src/hooks/useKubernetesObjects.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugins/kubernetes/src/hooks/useKubernetesObjects.ts b/plugins/kubernetes/src/hooks/useKubernetesObjects.ts index 8e2597c1d2..70281d8d49 100644 --- a/plugins/kubernetes/src/hooks/useKubernetesObjects.ts +++ b/plugins/kubernetes/src/hooks/useKubernetesObjects.ts @@ -47,6 +47,7 @@ export const useKubernetesObjects = ( try { clusters = await kubernetesApi.getClusters(); + setError(null); } catch (e) { setError(e.message); return; @@ -75,6 +76,7 @@ export const useKubernetesObjects = ( authProviderStr, requestBody, ); + setError(null); } catch (e) { setError(e.message); return; @@ -83,6 +85,7 @@ export const useKubernetesObjects = ( try { setKubernetesObjects(await kubernetesApi.getObjectsByEntity(requestBody)); + setError(null); } catch (e) { setError(e.message); return; From 19a27929fbce94e15c8c6b258b02e025b448c414 Mon Sep 17 00:00:00 2001 From: Tomas Aschan Date: Mon, 5 Sep 2022 22:28:04 +0200 Subject: [PATCH 2/8] Create changeset Signed-off-by: Tomas Aschan --- .changeset/rare-tips-glow.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .changeset/rare-tips-glow.md diff --git a/.changeset/rare-tips-glow.md b/.changeset/rare-tips-glow.md new file mode 100644 index 0000000000..74ff48f361 --- /dev/null +++ b/.changeset/rare-tips-glow.md @@ -0,0 +1,7 @@ +--- +'@backstage/plugin-kubernetes': patch +--- + +[Bugfix][bugfix]: reset error state on success + +[bugfix]: https://github.com/backstage/backstage/pull/13539 From 8da70d0fc1cd6cd680a40625da29d8b540cb6157 Mon Sep 17 00:00:00 2001 From: Tomas Aschan Date: Mon, 5 Sep 2022 23:28:59 +0200 Subject: [PATCH 3/8] Implement regression tests Signed-off-by: Tomas Aschan --- .../src/hooks/useKubernetesObjects.test.ts | 136 ++++++++++++++++++ 1 file changed, 136 insertions(+) diff --git a/plugins/kubernetes/src/hooks/useKubernetesObjects.test.ts b/plugins/kubernetes/src/hooks/useKubernetesObjects.test.ts index e5efafd477..c7a8b78fe9 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,138 @@ 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() + }) + }); }); From 5d0eff2e005d5d74c4558de08edf50311928c1dd Mon Sep 17 00:00:00 2001 From: Tomas Aschan Date: Mon, 5 Sep 2022 23:29:27 +0200 Subject: [PATCH 4/8] Set data and error atomically Signed-off-by: Tomas Aschan --- .../src/hooks/useKubernetesObjects.ts | 30 ++++++++----------- 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/plugins/kubernetes/src/hooks/useKubernetesObjects.ts b/plugins/kubernetes/src/hooks/useKubernetesObjects.ts index 70281d8d49..161bba2ecf 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,20 +36,18 @@ 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 = []; try { clusters = await kubernetesApi.getClusters(); - setError(null); } catch (e) { - setError(e.message); + setResult({ error: e.message }); return; } @@ -76,18 +74,17 @@ export const useKubernetesObjects = ( authProviderStr, requestBody, ); - setError(null); } catch (e) { - setError(e.message); + setResult({ error: e.message }); return; } } try { - setKubernetesObjects(await kubernetesApi.getObjectsByEntity(requestBody)); - setError(null); + const objects = await kubernetesApi.getObjectsByEntity(requestBody); + setResult({ kubernetesObjects: objects }) } catch (e) { - setError(e.message); + setResult({ error: e.message }) return; } }; @@ -102,8 +99,5 @@ export const useKubernetesObjects = ( getObjects(); }, intervalMs); - return { - kubernetesObjects, - error, - }; + return result; }; From a638348c8bdd3e98fa6d0cc72ad68d9a9edfad9c Mon Sep 17 00:00:00 2001 From: Tomas Aschan Date: Mon, 5 Sep 2022 23:29:42 +0200 Subject: [PATCH 5/8] Automated whitespace changes Signed-off-by: Tomas Aschan --- plugins/kubernetes/src/hooks/useKubernetesObjects.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/plugins/kubernetes/src/hooks/useKubernetesObjects.ts b/plugins/kubernetes/src/hooks/useKubernetesObjects.ts index 161bba2ecf..f2c8fa14e6 100644 --- a/plugins/kubernetes/src/hooks/useKubernetesObjects.ts +++ b/plugins/kubernetes/src/hooks/useKubernetesObjects.ts @@ -55,8 +55,7 @@ export const useKubernetesObjects = ( ...new Set( clusters.map( c => - `${c.authProvider}${ - c.oidcTokenProvider ? `.${c.oidcTokenProvider}` : '' + `${c.authProvider}${c.oidcTokenProvider ? `.${c.oidcTokenProvider}` : '' }`, ), ), From 2340880b05a707b010504bf065a4c49f3f1722d1 Mon Sep 17 00:00:00 2001 From: Tomas Aschan Date: Mon, 5 Sep 2022 23:54:13 +0200 Subject: [PATCH 6/8] Format with prettier Signed-off-by: Tomas Aschan --- .../src/hooks/useKubernetesObjects.test.ts | 121 ++++++++++-------- .../src/hooks/useKubernetesObjects.ts | 9 +- 2 files changed, 76 insertions(+), 54 deletions(-) diff --git a/plugins/kubernetes/src/hooks/useKubernetesObjects.test.ts b/plugins/kubernetes/src/hooks/useKubernetesObjects.test.ts index c7a8b78fe9..4b66467403 100644 --- a/plugins/kubernetes/src/hooks/useKubernetesObjects.test.ts +++ b/plugins/kubernetes/src/hooks/useKubernetesObjects.test.ts @@ -208,22 +208,26 @@ describe('useKubernetesObjects', () => { getClusters: mockGetClusters .mockRejectedValueOnce({ message: 'some-error' }) .mockResolvedValue(getClustersResponse), - decorateRequestBodyForAuth: mockDecorateRequestBodyForAuth.mockResolvedValue(entityWithAuthToken), - getObjectsByEntity: mockGetObjectsByEntity.mockResolvedValue(mockResponse) + decorateRequestBodyForAuth: + mockDecorateRequestBodyForAuth.mockResolvedValue(entityWithAuthToken), + getObjectsByEntity: + mockGetObjectsByEntity.mockResolvedValue(mockResponse), }); - const { result, waitForNextUpdate } = renderHook(() => useKubernetesObjects(entity, 100)) + const { result, waitForNextUpdate } = renderHook(() => + useKubernetesObjects(entity, 100), + ); await waitForNextUpdate(); - expect(result.current.error).toBe('some-error') - expect(result.current.kubernetesObjects).toBeUndefined() + 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() - }) + 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({ @@ -231,64 +235,74 @@ describe('useKubernetesObjects', () => { decorateRequestBodyForAuth: mockDecorateRequestBodyForAuth .mockRejectedValueOnce({ message: 'decoration failed' }) .mockResolvedValue(entityWithAuthToken), - getObjectsByEntity: mockGetObjectsByEntity.mockResolvedValue(mockResponse) + getObjectsByEntity: + mockGetObjectsByEntity.mockResolvedValue(mockResponse), }); - const { result, waitForNextUpdate } = renderHook(() => useKubernetesObjects(entity, 100)) + const { result, waitForNextUpdate } = renderHook(() => + useKubernetesObjects(entity, 100), + ); await waitForNextUpdate(); - expect(result.current.error).toBe('decoration failed') - expect(result.current.kubernetesObjects).toBeUndefined() + 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() - }) + 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) + decorateRequestBodyForAuth: + mockDecorateRequestBodyForAuth.mockResolvedValue(entityWithAuthToken), + getObjectsByEntity: mockGetObjectsByEntity + .mockRejectedValueOnce({ message: 'failed to fetch' }) + .mockResolvedValue(mockResponse), }); - const { result, waitForNextUpdate } = renderHook(() => useKubernetesObjects(entity, 100)) + const { result, waitForNextUpdate } = renderHook(() => + useKubernetesObjects(entity, 100), + ); await waitForNextUpdate(); - expect(result.current.error).toBe('failed to fetch') - expect(result.current.kubernetesObjects).toBeUndefined() + 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() - }) + 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)) + .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() + 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() - }) + 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({ @@ -298,41 +312,48 @@ describe('useKubernetesObjects', () => { .mockResolvedValueOnce(entityWithAuthToken) .mockResolvedValueOnce(entityWithAuthToken) .mockRejectedValue({ message: 'decorate failed' }), - getObjectsByEntity: mockGetObjectsByEntity.mockResolvedValue(mockResponse) + getObjectsByEntity: + mockGetObjectsByEntity.mockResolvedValue(mockResponse), }); - const { result, waitForNextUpdate } = renderHook(() => useKubernetesObjects(entity, 100)) + const { result, waitForNextUpdate } = renderHook(() => + useKubernetesObjects(entity, 100), + ); await waitForNextUpdate(); - expect(result.current.error).toBeUndefined() - expect(result.current.kubernetesObjects).not.toBeUndefined() + 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() + 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' }) + decorateRequestBodyForAuth: + mockDecorateRequestBodyForAuth.mockResolvedValue(entityWithAuthToken), + getObjectsByEntity: mockGetObjectsByEntity + .mockResolvedValueOnce(mockResponse) + .mockRejectedValue({ message: 'failed to fetch' }), }); - const { result, waitForNextUpdate } = renderHook(() => useKubernetesObjects(entity, 100)) + const { result, waitForNextUpdate } = renderHook(() => + useKubernetesObjects(entity, 100), + ); await waitForNextUpdate(); - expect(result.current.error).toBeUndefined() - expect(result.current.kubernetesObjects).not.toBeUndefined() + 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() - }) + 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 f2c8fa14e6..421ed787da 100644 --- a/plugins/kubernetes/src/hooks/useKubernetesObjects.ts +++ b/plugins/kubernetes/src/hooks/useKubernetesObjects.ts @@ -38,7 +38,7 @@ export const useKubernetesObjects = ( const kubernetesAuthProvidersApi = useApi(kubernetesAuthProvidersApiRef); const [result, setResult] = useState({ kubernetesObjects: undefined, - error: undefined + error: undefined, }); const getObjects = async () => { @@ -55,7 +55,8 @@ export const useKubernetesObjects = ( ...new Set( clusters.map( c => - `${c.authProvider}${c.oidcTokenProvider ? `.${c.oidcTokenProvider}` : '' + `${c.authProvider}${ + c.oidcTokenProvider ? `.${c.oidcTokenProvider}` : '' }`, ), ), @@ -81,9 +82,9 @@ export const useKubernetesObjects = ( try { const objects = await kubernetesApi.getObjectsByEntity(requestBody); - setResult({ kubernetesObjects: objects }) + setResult({ kubernetesObjects: objects }); } catch (e) { - setResult({ error: e.message }) + setResult({ error: e.message }); return; } }; From 2f4e64782f35308de14b8e6619d214580c0abeb5 Mon Sep 17 00:00:00 2001 From: Tomas Aschan Date: Mon, 5 Sep 2022 23:55:36 +0200 Subject: [PATCH 7/8] Remove dictionary violation from changeset Signed-off-by: Tomas Aschan --- .changeset/rare-tips-glow.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.changeset/rare-tips-glow.md b/.changeset/rare-tips-glow.md index 74ff48f361..6667fd5871 100644 --- a/.changeset/rare-tips-glow.md +++ b/.changeset/rare-tips-glow.md @@ -2,6 +2,4 @@ '@backstage/plugin-kubernetes': patch --- -[Bugfix][bugfix]: reset error state on success - -[bugfix]: https://github.com/backstage/backstage/pull/13539 +Reset error state on success From d12becfc009ce5d8e107148797c300739a2b5cc9 Mon Sep 17 00:00:00 2001 From: Tomas Aschan Date: Tue, 6 Sep 2022 14:44:13 +0200 Subject: [PATCH 8/8] Document (non-breaking) API changes Signed-off-by: Tomas Aschan --- plugins/kubernetes/api-report.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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)