Don't list the cluster if there are no resources deployed there

Signed-off-by: nliesenfeld <nliesenfeld@spotify.com>
This commit is contained in:
nliesenfeld
2021-03-11 18:21:28 -05:00
parent e07b41751e
commit bb848ac5e8
3 changed files with 422 additions and 42 deletions
@@ -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>
)}