diff --git a/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.test.ts b/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.test.ts
index a45b1252fe..e76a114d54 100644
--- a/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.test.ts
+++ b/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.test.ts
@@ -24,43 +24,93 @@ const getClustersByServiceId = jest.fn();
const mockFetch = (mock: jest.Mock) => {
mock.mockImplementation((params: ObjectFetchParams) =>
- Promise.resolve({
+ Promise.resolve(
+ generateMockResourcesAndErrors(
+ params.serviceId,
+ params.clusterDetails.name,
+ ),
+ ),
+ );
+};
+
+function generateMockResourcesAndErrors(
+ serviceId: String,
+ clusterName: String,
+) {
+ if (clusterName === 'empty-cluster') {
+ return {
errors: [],
responses: [
{
type: 'pods',
- resources: [
- {
- metadata: {
- name: `my-pods-${params.serviceId}-${params.clusterDetails.name}`,
- },
- },
- ],
+ resources: [],
},
{
type: 'configmaps',
- resources: [
- {
- metadata: {
- name: `my-configmaps-${params.serviceId}-${params.clusterDetails.name}`,
- },
- },
- ],
+ resources: [],
},
{
type: 'services',
- resources: [
- {
- metadata: {
- name: `my-services-${params.serviceId}-${params.clusterDetails.name}`,
- },
- },
- ],
+ resources: [],
},
],
- }),
- );
-};
+ };
+ } else if (clusterName === 'error-cluster') {
+ return {
+ errors: ['some random cluster error'],
+ responses: [
+ {
+ type: 'pods',
+ resources: [],
+ },
+ {
+ type: 'configmaps',
+ resources: [],
+ },
+ {
+ type: 'services',
+ resources: [],
+ },
+ ],
+ };
+ }
+
+ return {
+ errors: [],
+ responses: [
+ {
+ type: 'pods',
+ resources: [
+ {
+ metadata: {
+ name: `my-pods-${serviceId}-${clusterName}`,
+ },
+ },
+ ],
+ },
+ {
+ type: 'configmaps',
+ resources: [
+ {
+ metadata: {
+ name: `my-configmaps-${serviceId}-${clusterName}`,
+ },
+ },
+ ],
+ },
+ {
+ type: 'services',
+ resources: [
+ {
+ metadata: {
+ name: `my-services-${serviceId}-${clusterName}`,
+ },
+ },
+ ],
+ },
+ ],
+ };
+}
describe('handleGetKubernetesObjectsForService', () => {
beforeEach(() => {
@@ -287,4 +337,300 @@ describe('handleGetKubernetesObjectsForService', () => {
],
});
});
+ it('retrieve objects for three clusters, only two have resources and show in ui', async () => {
+ getClustersByServiceId.mockImplementation(() =>
+ Promise.resolve([
+ {
+ name: 'test-cluster',
+ authProvider: 'serviceAccount',
+ },
+ {
+ name: 'other-cluster',
+ authProvider: 'google',
+ },
+ {
+ name: 'empty-cluster',
+ authProvider: 'google',
+ },
+ ]),
+ );
+
+ mockFetch(fetchObjectsForService);
+
+ const sut = new KubernetesFanOutHandler(
+ getVoidLogger(),
+ {
+ fetchObjectsForService,
+ },
+ {
+ getClustersByServiceId,
+ },
+ [],
+ );
+
+ const result = await sut.getKubernetesObjectsByEntity({
+ auth: {
+ google: 'google_token_123',
+ },
+ entity: {
+ apiVersion: 'backstage.io/v1beta1',
+ kind: 'Component',
+ metadata: {
+ name: 'test-component',
+ annotations: {
+ 'backstage.io/kubernetes-labels-selector':
+ 'backstage.io/test-label=test-component',
+ },
+ },
+ spec: {
+ type: 'service',
+ lifecycle: 'production',
+ owner: 'joe',
+ },
+ },
+ });
+
+ expect(getClustersByServiceId.mock.calls.length).toBe(1);
+ expect(fetchObjectsForService.mock.calls.length).toBe(3);
+ expect(result).toStrictEqual({
+ items: [
+ {
+ cluster: {
+ name: 'test-cluster',
+ },
+ errors: [],
+ resources: [
+ {
+ resources: [
+ {
+ metadata: {
+ name: 'my-pods-test-component-test-cluster',
+ },
+ },
+ ],
+ type: 'pods',
+ },
+ {
+ resources: [
+ {
+ metadata: {
+ name: 'my-configmaps-test-component-test-cluster',
+ },
+ },
+ ],
+ type: 'configmaps',
+ },
+ {
+ resources: [
+ {
+ metadata: {
+ name: 'my-services-test-component-test-cluster',
+ },
+ },
+ ],
+ type: 'services',
+ },
+ ],
+ },
+ {
+ cluster: {
+ name: 'other-cluster',
+ },
+ errors: [],
+ resources: [
+ {
+ resources: [
+ {
+ metadata: {
+ name: 'my-pods-test-component-other-cluster',
+ },
+ },
+ ],
+ type: 'pods',
+ },
+ {
+ resources: [
+ {
+ metadata: {
+ name: 'my-configmaps-test-component-other-cluster',
+ },
+ },
+ ],
+ type: 'configmaps',
+ },
+ {
+ resources: [
+ {
+ metadata: {
+ name: 'my-services-test-component-other-cluster',
+ },
+ },
+ ],
+ type: 'services',
+ },
+ ],
+ },
+ ],
+ });
+ });
+ it('retrieve objects for four clusters, two have resources and one error cluster', async () => {
+ getClustersByServiceId.mockImplementation(() =>
+ Promise.resolve([
+ {
+ name: 'test-cluster',
+ authProvider: 'serviceAccount',
+ },
+ {
+ name: 'other-cluster',
+ authProvider: 'google',
+ },
+ {
+ name: 'empty-cluster',
+ authProvider: 'google',
+ },
+ {
+ name: 'error-cluster',
+ authProvider: 'google',
+ },
+ ]),
+ );
+
+ mockFetch(fetchObjectsForService);
+
+ const sut = new KubernetesFanOutHandler(
+ getVoidLogger(),
+ {
+ fetchObjectsForService,
+ },
+ {
+ getClustersByServiceId,
+ },
+ [],
+ );
+
+ const result = await sut.getKubernetesObjectsByEntity({
+ auth: {
+ google: 'google_token_123',
+ },
+ entity: {
+ apiVersion: 'backstage.io/v1beta1',
+ kind: 'Component',
+ metadata: {
+ name: 'test-component',
+ annotations: {
+ 'backstage.io/kubernetes-labels-selector':
+ 'backstage.io/test-label=test-component',
+ },
+ },
+ spec: {
+ type: 'service',
+ lifecycle: 'production',
+ owner: 'joe',
+ },
+ },
+ });
+
+ expect(getClustersByServiceId.mock.calls.length).toBe(1);
+ expect(fetchObjectsForService.mock.calls.length).toBe(4);
+ expect(result).toStrictEqual({
+ items: [
+ {
+ cluster: {
+ name: 'test-cluster',
+ },
+ errors: [],
+ resources: [
+ {
+ resources: [
+ {
+ metadata: {
+ name: 'my-pods-test-component-test-cluster',
+ },
+ },
+ ],
+ type: 'pods',
+ },
+ {
+ resources: [
+ {
+ metadata: {
+ name: 'my-configmaps-test-component-test-cluster',
+ },
+ },
+ ],
+ type: 'configmaps',
+ },
+ {
+ resources: [
+ {
+ metadata: {
+ name: 'my-services-test-component-test-cluster',
+ },
+ },
+ ],
+ type: 'services',
+ },
+ ],
+ },
+ {
+ cluster: {
+ name: 'other-cluster',
+ },
+ errors: [],
+ resources: [
+ {
+ resources: [
+ {
+ metadata: {
+ name: 'my-pods-test-component-other-cluster',
+ },
+ },
+ ],
+ type: 'pods',
+ },
+ {
+ resources: [
+ {
+ metadata: {
+ name: 'my-configmaps-test-component-other-cluster',
+ },
+ },
+ ],
+ type: 'configmaps',
+ },
+ {
+ resources: [
+ {
+ metadata: {
+ name: 'my-services-test-component-other-cluster',
+ },
+ },
+ ],
+ type: 'services',
+ },
+ ],
+ },
+ {
+ cluster: {
+ name: 'error-cluster',
+ },
+ errors: ['some random cluster error'],
+ resources: [
+ {
+ type: 'pods',
+ resources: [],
+ },
+ {
+ type: 'configmaps',
+ resources: [],
+ },
+ {
+ type: 'services',
+ resources: [],
+ },
+ ],
+ },
+ ],
+ });
+ });
});
diff --git a/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.ts b/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.ts
index 489b0631f6..9427ce9553 100644
--- a/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.ts
+++ b/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.ts
@@ -109,6 +109,14 @@ export class KubernetesFanOutHandler {
};
});
}),
- ).then(r => ({ items: r }));
+ ).then(r => ({
+ items: r.filter(
+ item =>
+ (item.errors !== undefined && item.errors.length >= 1) ||
+ (item.resources !== undefined &&
+ item.resources.length >= 1 &&
+ item.resources.some(fr => fr.resources.length >= 1)),
+ ),
+ }));
}
}
diff --git a/plugins/kubernetes/src/components/KubernetesContent/KubernetesContent.tsx b/plugins/kubernetes/src/components/KubernetesContent/KubernetesContent.tsx
index aa9df3e8fa..cda8dd7e6b 100644
--- a/plugins/kubernetes/src/components/KubernetesContent/KubernetesContent.tsx
+++ b/plugins/kubernetes/src/components/KubernetesContent/KubernetesContent.tsx
@@ -41,6 +41,8 @@ import { DetectedError, detectErrors } from '../../error-detection';
import { IngressesAccordions } from '../IngressesAccordions';
import { ServicesAccordions } from '../ServicesAccordions';
import { CustomResources } from '../CustomResources';
+import EmptyStateImage from '../../assets/emptystate.svg';
+
import {
GroupedResponsesContext,
PodNamesWithErrorsContext,
@@ -203,24 +205,48 @@ export const KubernetesContent = ({ entity }: KubernetesContentProps) => {
Your Clusters
- {kubernetesObjects?.items.map((item, i) => {
- const podsWithErrors = new Set(
- detectedErrors
- .get(item.cluster.name)
- ?.filter(de => de.kind === 'Pod')
- .map(de => de.names)
- .flat() ?? [],
- );
-
- return (
-
-
+
+
+ No resources on any known clusters for{' '}
+ {entity.metadata.name}
+
+
+
+
- );
- })}
+
+ )}
+ {kubernetesObjects?.items.length > 0 &&
+ kubernetesObjects?.items.map((item, i) => {
+ const podsWithErrors = new Set(
+ detectedErrors
+ .get(item.cluster.name)
+ ?.filter(de => de.kind === 'Pod')
+ .map(de => de.names)
+ .flat() ?? [],
+ );
+
+ return (
+
+
+
+ );
+ })}
)}