From b01c86c2b5fb8b2c74ff87093e82f2bf5b1e0e3f Mon Sep 17 00:00:00 2001 From: Jamie Klassen Date: Wed, 24 Jan 2024 10:46:42 -0500 Subject: [PATCH] surface cluster title in ErrorReporting table Signed-off-by: Jamie Klassen --- .changeset/healthy-goats-jump.md | 7 + plugins/kubernetes-react/api-report.md | 2 + .../ErrorReporting/ErrorReporting.test.tsx | 150 ++++++++++++++++++ .../ErrorReporting/ErrorReporting.tsx | 13 +- plugins/kubernetes/src/KubernetesContent.tsx | 7 +- 5 files changed, 174 insertions(+), 5 deletions(-) create mode 100644 .changeset/healthy-goats-jump.md create mode 100644 plugins/kubernetes-react/src/components/ErrorReporting/ErrorReporting.test.tsx diff --git a/.changeset/healthy-goats-jump.md b/.changeset/healthy-goats-jump.md new file mode 100644 index 0000000000..e5b43d3f8c --- /dev/null +++ b/.changeset/healthy-goats-jump.md @@ -0,0 +1,7 @@ +--- +'@backstage/plugin-kubernetes': patch +'@backstage/plugin-kubernetes-react': patch +--- + +The `ErrorReporting` component's cluster column now displays cluster titles when +specified. diff --git a/plugins/kubernetes-react/api-report.md b/plugins/kubernetes-react/api-report.md index 93086efde7..e02cb97992 100644 --- a/plugins/kubernetes-react/api-report.md +++ b/plugins/kubernetes-react/api-report.md @@ -176,11 +176,13 @@ export type ErrorPanelProps = { // @public (undocumented) export const ErrorReporting: ({ detectedErrors, + clusters, }: ErrorReportingProps) => React_3.JSX.Element; // @public (undocumented) export type ErrorReportingProps = { detectedErrors: DetectedErrorsByCluster; + clusters: ClusterAttributes[]; }; // @public diff --git a/plugins/kubernetes-react/src/components/ErrorReporting/ErrorReporting.test.tsx b/plugins/kubernetes-react/src/components/ErrorReporting/ErrorReporting.test.tsx new file mode 100644 index 0000000000..6da259d7fa --- /dev/null +++ b/plugins/kubernetes-react/src/components/ErrorReporting/ErrorReporting.test.tsx @@ -0,0 +1,150 @@ +/* + * Copyright 2024 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 { DetectedError } from '@backstage/plugin-kubernetes-common'; +import { renderInTestApp } from '@backstage/test-utils'; +import { MatcherFunction, screen } from '@testing-library/react'; +import { ErrorReporting } from './ErrorReporting'; + +describe('ErrorReporting', () => { + const matchTextContent = + (text: string): MatcherFunction => + (_, node) => + node?.textContent?.includes(text) ?? false; + + it('sorts errors by severity', async () => { + await renderInTestApp( + ([ + [ + 'cluster', + [ + { + type: 'readiness-probe-taking-too-long', + message: + 'The container my-container failed to start properly, but is not crashing', + severity: 4, + proposedFix: undefined, + sourceRef: { + name: 'my-pod', + namespace: 'default', + kind: 'Pod', + apiGroup: 'v1', + }, + occurrenceCount: 1, + }, + { + type: 'condition-message-present', + message: 'some condition message', + severity: 6, + sourceRef: { + name: 'my-deployment', + namespace: 'default', + kind: 'Deployment', + apiGroup: 'apps/v1', + }, + occurrenceCount: 1, + }, + ], + ], + ]) + } + clusters={[{ name: 'cluster' }]} + />, + ); + + expect(screen.getAllByRole('row')).toEqual([ + expect.anything(), + screen.getByText(matchTextContent('some condition message'), { + selector: 'tr', + }), + screen.getByText( + matchTextContent( + 'The container my-container failed to start properly, but is not crashing', + ), + { selector: 'tr' }, + ), + expect.anything(), + ]); + }); + + it('displays cluster name', async () => { + await renderInTestApp( + ([ + [ + 'my-cluster', + [ + { + type: 'condition-message-present', + message: 'some condition message', + severity: 6, + sourceRef: { + name: 'my-deployment', + namespace: 'default', + kind: 'Deployment', + apiGroup: 'apps/v1', + }, + occurrenceCount: 1, + }, + ], + ], + ]) + } + clusters={[{ name: 'my-cluster' }]} + />, + ); + + expect( + screen.getByRole('cell', { name: 'my-cluster' }), + ).toBeInTheDocument(); + }); + + it('displays cluster title when specified', async () => { + await renderInTestApp( + ([ + [ + 'my-cluster', + [ + { + type: 'condition-message-present', + message: 'some condition message', + severity: 6, + sourceRef: { + name: 'my-deployment', + namespace: 'default', + kind: 'Deployment', + apiGroup: 'apps/v1', + }, + occurrenceCount: 1, + }, + ], + ], + ]) + } + clusters={[{ name: 'my-cluster', title: 'cluster-title' }]} + />, + ); + + expect( + screen.getByRole('cell', { name: 'cluster-title' }), + ).toBeInTheDocument(); + }); +}); diff --git a/plugins/kubernetes-react/src/components/ErrorReporting/ErrorReporting.tsx b/plugins/kubernetes-react/src/components/ErrorReporting/ErrorReporting.tsx index 64345662ed..5bf6cff50e 100644 --- a/plugins/kubernetes-react/src/components/ErrorReporting/ErrorReporting.tsx +++ b/plugins/kubernetes-react/src/components/ErrorReporting/ErrorReporting.tsx @@ -15,6 +15,7 @@ */ import * as React from 'react'; import { + ClusterAttributes, DetectedError, DetectedErrorsByCluster, } from '@backstage/plugin-kubernetes-common'; @@ -27,13 +28,14 @@ import { Table, TableColumn } from '@backstage/core-components'; */ export type ErrorReportingProps = { detectedErrors: DetectedErrorsByCluster; + clusters: ClusterAttributes[]; }; const columns: TableColumn[] = [ { title: 'cluster', width: '10%', - render: (row: Row) => row.clusterName, + render: (row: Row) => row.cluster.title || row.cluster.name, }, { title: 'namespace', @@ -60,7 +62,7 @@ const columns: TableColumn[] = [ ]; interface Row { - clusterName: string; + cluster: ClusterAttributes; error: DetectedError; } @@ -78,11 +80,14 @@ const sortBySeverity = (a: Row, b: Row) => { * * @public */ -export const ErrorReporting = ({ detectedErrors }: ErrorReportingProps) => { +export const ErrorReporting = ({ + detectedErrors, + clusters, +}: ErrorReportingProps) => { const errors = Array.from(detectedErrors.entries()) .flatMap(([clusterName, resourceErrors]) => { return resourceErrors.map(e => ({ - clusterName, + cluster: clusters.find(c => c.name === clusterName)!, error: e, })); }) diff --git a/plugins/kubernetes/src/KubernetesContent.tsx b/plugins/kubernetes/src/KubernetesContent.tsx index 9c266b1854..ed00cb9a13 100644 --- a/plugins/kubernetes/src/KubernetesContent.tsx +++ b/plugins/kubernetes/src/KubernetesContent.tsx @@ -50,6 +50,8 @@ export const KubernetesContent = ({ refreshIntervalMs, ); + const clusters = kubernetesObjects?.items.map(item => item.cluster) ?? []; + const clustersWithErrors = kubernetesObjects?.items.filter(r => r.errors.length > 0) ?? []; @@ -93,7 +95,10 @@ export const KubernetesContent = ({ {kubernetesObjects && ( - + Your Clusters