Merge pull request #4944 from nora99/filter-out-clusters-with-no-resources
Don't list the cluster if there are no resources deployed there
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
---
|
||||
'@backstage/plugin-kubernetes-backend': patch
|
||||
'@backstage/plugin-kubernetes': patch
|
||||
---
|
||||
|
||||
Filter out k8s cluster with no resources or errors
|
||||
@@ -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: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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)),
|
||||
),
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) => {
|
||||
<Typography variant="h3">Your Clusters</Typography>
|
||||
</Grid>
|
||||
<Grid item container>
|
||||
{kubernetesObjects?.items.map((item, i) => {
|
||||
const podsWithErrors = new Set<string>(
|
||||
detectedErrors
|
||||
.get(item.cluster.name)
|
||||
?.filter(de => de.kind === 'Pod')
|
||||
.map(de => de.names)
|
||||
.flat() ?? [],
|
||||
);
|
||||
|
||||
return (
|
||||
<Grid item key={i} xs={12}>
|
||||
<Cluster
|
||||
clusterObjects={item}
|
||||
podsWithErrors={podsWithErrors}
|
||||
{kubernetesObjects?.items.length <= 0 && (
|
||||
<Grid
|
||||
container
|
||||
justify="space-around"
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
spacing={2}
|
||||
>
|
||||
<Grid item xs={4}>
|
||||
<Typography variant="h5">
|
||||
No resources on any known clusters for{' '}
|
||||
{entity.metadata.name}
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={4}>
|
||||
<img
|
||||
src={EmptyStateImage}
|
||||
alt="EmptyState"
|
||||
data-testid="emptyStateImg"
|
||||
/>
|
||||
</Grid>
|
||||
);
|
||||
})}
|
||||
</Grid>
|
||||
)}
|
||||
{kubernetesObjects?.items.length > 0 &&
|
||||
kubernetesObjects?.items.map((item, i) => {
|
||||
const podsWithErrors = new Set<string>(
|
||||
detectedErrors
|
||||
.get(item.cluster.name)
|
||||
?.filter(de => de.kind === 'Pod')
|
||||
.map(de => de.names)
|
||||
.flat() ?? [],
|
||||
);
|
||||
|
||||
return (
|
||||
<Grid item key={i} xs={12}>
|
||||
<Cluster
|
||||
clusterObjects={item}
|
||||
podsWithErrors={podsWithErrors}
|
||||
/>
|
||||
</Grid>
|
||||
);
|
||||
})}
|
||||
</Grid>
|
||||
</Grid>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user