Skip to content

Commit 8d49b6e

Browse files
Copilottembleking
andauthored
feat: Display MissingAnnotationEmptyState when Sysdig annotations are missing (#32)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: tembleking <2988780+tembleking@users.noreply.github.com>
1 parent c851181 commit 8d49b6e

File tree

9 files changed

+15785
-21716
lines changed

9 files changed

+15785
-21716
lines changed

src/components/SysdigPostureFetchComponent/SysdigPostureFetchComponent.test.tsx

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,18 @@ import { configApiRef } from '@backstage/core-plugin-api';
2323
import { ConfigReader } from '@backstage/config';
2424

2525
const mockEntity = {
26+
apiVersion: 'backstage.io/v1alpha1',
27+
kind: 'Component',
28+
metadata: {
29+
name: 'test-component',
30+
annotations: {
31+
'sysdigcloud.com/resource-name': 'test-resource',
32+
'sysdigcloud.com/platform': 'AWS',
33+
},
34+
},
35+
};
36+
37+
const mockEntityWithoutAnnotations = {
2638
apiVersion: 'backstage.io/v1alpha1',
2739
kind: 'Component',
2840
metadata: {
@@ -49,7 +61,7 @@ const mockConfig = new ConfigReader({
4961
});
5062

5163
describe('SysdigPostureFetchComponent', () => {
52-
it('renders the user table', async () => {
64+
it('renders the posture table with annotations', async () => {
5365
await renderInTestApp(
5466
<TestApiProvider apis={[
5567
[sysdigApiRef, mockSysdigApi],
@@ -65,4 +77,21 @@ describe('SysdigPostureFetchComponent', () => {
6577
const table = await screen.findByText('Posture Overview');
6678
expect(table).toBeInTheDocument();
6779
});
80+
81+
it('renders missing annotation state when annotations are missing', async () => {
82+
await renderInTestApp(
83+
<TestApiProvider apis={[
84+
[sysdigApiRef, mockSysdigApi],
85+
[configApiRef, mockConfig],
86+
]}>
87+
<EntityProvider entity={mockEntityWithoutAnnotations}>
88+
<SysdigPostureFetchComponent />
89+
</EntityProvider>
90+
</TestApiProvider>
91+
);
92+
93+
// Wait for the missing annotation message to render
94+
const message = await screen.findByText(/missing annotation/i);
95+
expect(message).toBeInTheDocument();
96+
});
6897
});

src/components/SysdigPostureFetchComponent/SysdigPostureFetchComponent.tsx

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import React from 'react';
1717
import { Table, TableColumn, Progress } from '@backstage/core-components';
1818
import useAsync from 'react-use/lib/useAsync';
1919
import Alert from '@mui/material/Alert';
20-
import { useEntity } from '@backstage/plugin-catalog-react';
20+
import { useEntity, MissingAnnotationEmptyState } from '@backstage/plugin-catalog-react';
2121
import { useApi, configApiRef } from '@backstage/core-plugin-api';
2222
import { sysdigApiRef } from '../../api';
2323

@@ -216,14 +216,16 @@ export const SysdigPostureFetchComponent = () => {
216216

217217
const annotations = entity.metadata.annotations;
218218

219-
const { filter, backlink } = React.useMemo(() => {
219+
const { filter, backlink, hasSysdigAnnotations } = React.useMemo(() => {
220220
let currentFilter = '?filter=';
221221
let currentBacklink = getBacklink(endpoint, backlink_config, "inventory");
222222
let name: string | undefined;
223+
let hasAnnotations = false;
223224

224225
if (annotations) {
225226
if (SYSDIG_CUSTOM_FILTER_ANNOTATION in annotations) {
226227
currentFilter += annotations[SYSDIG_CUSTOM_FILTER_ANNOTATION];
228+
hasAnnotations = true;
227229
} else {
228230
const filters: string[] = [];
229231

@@ -348,26 +350,39 @@ export const SysdigPostureFetchComponent = () => {
348350
}
349351

350352
if (filters.length === 0) {
351-
return { filter: '', backlink: '' }; // No annotations, no filter
353+
return { filter: '', backlink: '', hasSysdigAnnotations: false }; // No Sysdig annotations
352354
}
353355

356+
hasAnnotations = true;
354357
currentFilter += filters.join(' and ');
355358
currentBacklink += currentFilter;
356359
}
357360
}
358-
return { filter: currentFilter, backlink: currentBacklink };
361+
return { filter: currentFilter, backlink: currentBacklink, hasSysdigAnnotations: hasAnnotations };
359362
}, [annotations, endpoint, backlink_config]);
360363

361364
const { value, loading, error } = useAsync(async (): Promise<PostureScan[]> => {
362-
if (!annotations) {
363-
return []; // No annotations, so no data to fetch
365+
if (!hasSysdigAnnotations) {
366+
return []; // No Sysdig annotations, so no data to fetch
364367
}
365368
const data = await sysdigApiClient.fetchInventory(filter);
366369
return data.data;
367-
}, [sysdigApiClient, filter, annotations]); // Depend on filter and annotations
368-
369-
if (!annotations) {
370-
return <Alert severity="warning">Please, add annotations to the entity.</Alert>;
370+
}, [sysdigApiClient, filter, hasSysdigAnnotations]);
371+
372+
if (!hasSysdigAnnotations) {
373+
return (
374+
<MissingAnnotationEmptyState
375+
annotation={[
376+
SYSDIG_RESOURCE_NAME_ANNOTATION,
377+
SYSDIG_RESOURCE_TYPE_ANNOTATION,
378+
SYSDIG_PLATFORM_ANNOTATION,
379+
SYSDIG_ACCOUNT_ANNOTATION,
380+
SYSDIG_REGION_ANNOTATION,
381+
SYSDIG_CUSTOM_FILTER_ANNOTATION
382+
]}
383+
readMoreUrl="https://github.com/sysdiglabs/backstage-plugin-sysdig#how-to-annotate-services"
384+
/>
385+
);
371386
}
372387

373388
if (loading) {

src/components/SysdigVMPipelineFetchComponent/SysdigVMPipelineFetchComponent.test.tsx

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,17 @@ import { configApiRef } from '@backstage/core-plugin-api';
2323
import { ConfigReader } from '@backstage/config';
2424

2525
const mockEntity = {
26+
apiVersion: 'backstage.io/v1alpha1',
27+
kind: 'Component',
28+
metadata: {
29+
name: 'test-component',
30+
annotations: {
31+
'sysdigcloud.com/image-freetext': 'ghcr.io/sysdiglabs',
32+
},
33+
},
34+
};
35+
36+
const mockEntityWithoutAnnotations = {
2637
apiVersion: 'backstage.io/v1alpha1',
2738
kind: 'Component',
2839
metadata: {
@@ -49,7 +60,7 @@ const mockConfig = new ConfigReader({
4960
});
5061

5162
describe('SysdigVMPipelineFetchComponent', () => {
52-
it('renders the user table', async () => {
63+
it('renders the pipeline table with annotations', async () => {
5364
await renderInTestApp(
5465
<TestApiProvider apis={[
5566
[sysdigApiRef, mockSysdigApi],
@@ -65,4 +76,21 @@ describe('SysdigVMPipelineFetchComponent', () => {
6576
const table = await screen.findByText('Pipeline Scan Overview');
6677
expect(table).toBeInTheDocument();
6778
});
79+
80+
it('renders missing annotation state when annotations are missing', async () => {
81+
await renderInTestApp(
82+
<TestApiProvider apis={[
83+
[sysdigApiRef, mockSysdigApi],
84+
[configApiRef, mockConfig],
85+
]}>
86+
<EntityProvider entity={mockEntityWithoutAnnotations}>
87+
<SysdigVMPipelineFetchComponent />
88+
</EntityProvider>
89+
</TestApiProvider>
90+
);
91+
92+
// Wait for the missing annotation message to render
93+
const message = await screen.findByText(/missing annotation/i);
94+
expect(message).toBeInTheDocument();
95+
});
6896
});

src/components/SysdigVMPipelineFetchComponent/SysdigVMPipelineFetchComponent.tsx

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import React from 'react';
1717
import { Table, TableColumn, Progress } from '@backstage/core-components';
1818
import useAsync from 'react-use/lib/useAsync';
1919
import Alert from '@mui/material/Alert';
20-
import { useEntity } from '@backstage/plugin-catalog-react';
20+
import { useEntity, MissingAnnotationEmptyState } from '@backstage/plugin-catalog-react';
2121
import { useApi, configApiRef } from '@backstage/core-plugin-api';
2222
import {
2323
// annotations
@@ -117,14 +117,16 @@ export const SysdigVMPipelineFetchComponent = () => {
117117

118118
const annotations = entity.metadata.annotations;
119119

120-
const { filter, backlink } = React.useMemo(() => {
120+
const { filter, backlink, hasSysdigAnnotations } = React.useMemo(() => {
121121
let currentFilter = '?filter=';
122122
let currentBacklink = getBacklink(endpoint, backlink_config, "vm-pipeline");
123123
let name: string | undefined;
124+
let hasAnnotations = false;
124125

125126
if (annotations) {
126127
if (SYSDIG_CUSTOM_FILTER_ANNOTATION in annotations) {
127128
currentFilter += annotations[SYSDIG_CUSTOM_FILTER_ANNOTATION];
129+
hasAnnotations = true;
128130
} else {
129131
const filters: string[] = [];
130132

@@ -134,26 +136,35 @@ export const SysdigVMPipelineFetchComponent = () => {
134136
}
135137

136138
if (filters.length === 0) {
137-
return { filter: '', backlink: '' }; // No annotations, no filter
139+
return { filter: '', backlink: '', hasSysdigAnnotations: false }; // No Sysdig annotations
138140
}
139141

142+
hasAnnotations = true;
140143
currentFilter += filters.join(' and ');
141144
currentBacklink += currentFilter;
142145
}
143146
}
144-
return { filter: currentFilter, backlink: currentBacklink };
147+
return { filter: currentFilter, backlink: currentBacklink, hasSysdigAnnotations: hasAnnotations };
145148
}, [annotations, endpoint, backlink_config]);
146149

147150
const { value, loading, error } = useAsync(async (): Promise<PipelineScan[]> => {
148-
if (!annotations) {
149-
return []; // No annotations, so no data to fetch
151+
if (!hasSysdigAnnotations) {
152+
return []; // No Sysdig annotations, so no data to fetch
150153
}
151154
const data = await sysdigApiClient.fetchVulnPipeline(filter);
152155
return data.data;
153-
}, [sysdigApiClient, filter, annotations]);
154-
155-
if (!annotations) {
156-
return <Alert severity="warning">Please, add annotations to the entity.</Alert>;
156+
}, [sysdigApiClient, filter, hasSysdigAnnotations]);
157+
158+
if (!hasSysdigAnnotations) {
159+
return (
160+
<MissingAnnotationEmptyState
161+
annotation={[
162+
SYSDIG_IMAGE_FREETEXT_ANNOTATION,
163+
SYSDIG_CUSTOM_FILTER_ANNOTATION
164+
]}
165+
readMoreUrl="https://github.com/sysdiglabs/backstage-plugin-sysdig#how-to-annotate-services"
166+
/>
167+
);
157168
}
158169

159170
if (loading) {

src/components/SysdigVMRegistryFetchComponent/SysdigVMRegistryFetchComponent.test.tsx

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,18 @@ import { configApiRef } from '@backstage/core-plugin-api';
2323
import { ConfigReader } from '@backstage/config';
2424

2525
const mockEntity = {
26+
apiVersion: 'backstage.io/v1alpha1',
27+
kind: 'Component',
28+
metadata: {
29+
name: 'test-component',
30+
annotations: {
31+
'sysdigcloud.com/registry-name': 'test-registry',
32+
'sysdigcloud.com/registry-vendor': 'harbor',
33+
},
34+
},
35+
};
36+
37+
const mockEntityWithoutAnnotations = {
2638
apiVersion: 'backstage.io/v1alpha1',
2739
kind: 'Component',
2840
metadata: {
@@ -49,7 +61,7 @@ const mockConfig = new ConfigReader({
4961
});
5062

5163
describe('SysdigVMRegistryFetchComponent', () => {
52-
it('renders the user table', async () => {
64+
it('renders the registry table with annotations', async () => {
5365
await renderInTestApp(
5466
<TestApiProvider apis={[
5567
[sysdigApiRef, mockSysdigApi],
@@ -65,4 +77,21 @@ describe('SysdigVMRegistryFetchComponent', () => {
6577
const table = await screen.findByText('Registry Scan Overview');
6678
expect(table).toBeInTheDocument();
6779
});
80+
81+
it('renders missing annotation state when annotations are missing', async () => {
82+
await renderInTestApp(
83+
<TestApiProvider apis={[
84+
[sysdigApiRef, mockSysdigApi],
85+
[configApiRef, mockConfig],
86+
]}>
87+
<EntityProvider entity={mockEntityWithoutAnnotations}>
88+
<SysdigVMRegistryFetchComponent />
89+
</EntityProvider>
90+
</TestApiProvider>
91+
);
92+
93+
// Wait for the missing annotation message to render
94+
const message = await screen.findByText(/missing annotation/i);
95+
expect(message).toBeInTheDocument();
96+
});
6897
});

src/components/SysdigVMRegistryFetchComponent/SysdigVMRegistryFetchComponent.tsx

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import React from 'react';
1717
import { Table, TableColumn, Progress } from '@backstage/core-components';
1818
import useAsync from 'react-use/lib/useAsync';
1919
import Alert from '@mui/material/Alert';
20-
import { useEntity } from '@backstage/plugin-catalog-react';
20+
import { useEntity, MissingAnnotationEmptyState } from '@backstage/plugin-catalog-react';
2121
import { useApi, configApiRef } from '@backstage/core-plugin-api';
2222
import {
2323
// annotations
@@ -106,14 +106,16 @@ export const SysdigVMRegistryFetchComponent = () => {
106106

107107
const annotations = entity.metadata.annotations;
108108

109-
const { filter, backlink } = React.useMemo(() => {
109+
const { filter, backlink, hasSysdigAnnotations } = React.useMemo(() => {
110110
let currentFilter = '?filter=';
111111
let currentBacklink = getBacklink(endpoint, backlink_config, "vm-registry");
112112
let name: string | undefined;
113+
let hasAnnotations = false;
113114

114115
if (annotations) {
115116
if (SYSDIG_CUSTOM_FILTER_ANNOTATION in annotations) {
116117
currentFilter += annotations[SYSDIG_CUSTOM_FILTER_ANNOTATION];
118+
hasAnnotations = true;
117119
} else {
118120
const filters: string[] = [];
119121

@@ -133,26 +135,37 @@ export const SysdigVMRegistryFetchComponent = () => {
133135
}
134136

135137
if (filters.length === 0) {
136-
return { filter: '', backlink: '' }; // No annotations, no filter
138+
return { filter: '', backlink: '', hasSysdigAnnotations: false }; // No Sysdig annotations
137139
}
138140

141+
hasAnnotations = true;
139142
currentFilter += filters.join(' and ');
140143
currentBacklink += currentFilter;
141144
}
142145
}
143-
return { filter: currentFilter, backlink: currentBacklink };
146+
return { filter: currentFilter, backlink: currentBacklink, hasSysdigAnnotations: hasAnnotations };
144147
}, [annotations, endpoint, backlink_config]);
145148

146149
const { value, loading, error } = useAsync(async (): Promise<RegistryScan[]> => {
147-
if (!annotations) {
148-
return []; // No annotations, so no data to fetch
150+
if (!hasSysdigAnnotations) {
151+
return []; // No Sysdig annotations, so no data to fetch
149152
}
150153
const data = await sysdigApiClient.fetchVulnRegistry(filter);
151154
return data.data;
152-
}, [sysdigApiClient, filter, annotations]);
153-
154-
if (!annotations) {
155-
return <Alert severity="warning">Please, add annotations to the entity.</Alert>;
155+
}, [sysdigApiClient, filter, hasSysdigAnnotations]);
156+
157+
if (!hasSysdigAnnotations) {
158+
return (
159+
<MissingAnnotationEmptyState
160+
annotation={[
161+
SYSDIG_REGISTRY_NAME_ANNOTATION,
162+
SYSDIG_REGISTRY_VENDOR_ANNOTATION,
163+
SYSDIG_REGISTRY_REPOSITORY_ANNOTATION,
164+
SYSDIG_CUSTOM_FILTER_ANNOTATION
165+
]}
166+
readMoreUrl="https://github.com/sysdiglabs/backstage-plugin-sysdig#how-to-annotate-services"
167+
/>
168+
);
156169
}
157170

158171
if (loading) {

0 commit comments

Comments
 (0)