diff --git a/plugins/quay/src/components/QuayRepository/QuayRepository.test.tsx b/plugins/quay/src/components/QuayRepository/QuayRepository.test.tsx
index 236a409c02..40c12c54a3 100644
--- a/plugins/quay/src/components/QuayRepository/QuayRepository.test.tsx
+++ b/plugins/quay/src/components/QuayRepository/QuayRepository.test.tsx
@@ -96,4 +96,28 @@ describe('QuayRepository', () => {
expect(queryByText(/Quay repository/i)).toBeInTheDocument();
expect(queryByTestId('quay-repo-security-scan-progress')).not.toBeNull();
});
+
+ it('should show table if loaded and data is present but shows unsupported if security scan is not supported', () => {
+ (useTags as jest.Mock).mockReturnValue({
+ loading: false,
+ data: [
+ {
+ name: 'latest',
+ manifest_digest: undefined,
+ securityStatus: 'unsupported',
+ size: null,
+ last_modified: 'Wed, 15 Mar 2023 18:22:18 -0000',
+ },
+ ],
+ });
+ const { queryByTestId, queryByText } = render(
+
+
+ ,
+ );
+ expect(queryByTestId('quay-repo-table')).not.toBeNull();
+ expect(queryByTestId('quay-repo-table-empty')).toBeNull();
+ expect(queryByText(/Quay repository/i)).toBeInTheDocument();
+ expect(queryByTestId('quay-repo-security-scan-unsupported')).not.toBeNull();
+ });
});
diff --git a/plugins/quay/src/components/QuayRepository/tableHeading.tsx b/plugins/quay/src/components/QuayRepository/tableHeading.tsx
index 0573baa92f..c455771d96 100644
--- a/plugins/quay/src/components/QuayRepository/tableHeading.tsx
+++ b/plugins/quay/src/components/QuayRepository/tableHeading.tsx
@@ -2,6 +2,7 @@ import React from 'react';
import { Link, Progress, TableColumn } from '@backstage/core-components';
+import { Tooltip } from '@material-ui/core';
import makeStyles from '@material-ui/core/styles/makeStyles';
import type { Layer } from '../../types';
@@ -41,13 +42,24 @@ export const columns: TableColumn[] = [
title: 'Security Scan',
field: 'securityScan',
render: (rowData: any): React.ReactNode => {
- if (!rowData.securityDetails) {
+ if (!rowData.securityStatus && !rowData.securityDetails) {
return (
);
}
+
+ if (rowData.securityStatus === 'unsupported') {
+ return (
+
+
+ Unsupported
+
+
+ );
+ }
+
const tagManifest = rowData.manifest_digest_raw;
const retStr = vulnerabilitySummary(rowData.securityDetails as Layer);
return {retStr};
diff --git a/plugins/quay/src/hooks/quay.tsx b/plugins/quay/src/hooks/quay.tsx
index 5035d20891..3f48c60d70 100644
--- a/plugins/quay/src/hooks/quay.tsx
+++ b/plugins/quay/src/hooks/quay.tsx
@@ -29,6 +29,9 @@ export const useTags = (organization: string, repository: string) => {
const [tagManifestLayers, setTagManifestLayers] = React.useState<
Record
>({});
+ const [tagManifestStatuses, setTagManifestStatuses] = React.useState<
+ Record
+ >({});
const localClasses = useLocalStyles();
const fetchSecurityDetails = async (tag: Tag) => {
@@ -46,13 +49,19 @@ export const useTags = (organization: string, repository: string) => {
tagsResponse.tags.map(async tag => {
const securityDetails = await fetchSecurityDetails(tag);
const securityData = securityDetails.data;
- if (!securityData) {
- return;
- }
- setTagManifestLayers(prevState => ({
+ const securityStatus = securityDetails.status;
+
+ setTagManifestStatuses(prevState => ({
...prevState,
- [tag.manifest_digest]: securityData.Layer,
+ [tag.manifest_digest]: securityStatus,
}));
+
+ if (securityData) {
+ setTagManifestLayers(prevState => ({
+ ...prevState,
+ [tag.manifest_digest]: securityData.Layer,
+ }));
+ }
}),
);
setTags(prevTags => [...prevTags, ...tagsResponse.tags]);
@@ -76,6 +85,7 @@ export const useTags = (organization: string, repository: string) => {
),
expiration: tag.expiration,
securityDetails: tagManifestLayers[tag.manifest_digest],
+ securityStatus: tagManifestStatuses[tag.manifest_digest],
manifest_digest_raw: tag.manifest_digest,
// is_manifest_list: tag.is_manifest_list,
// reversion: tag.reversion,
@@ -84,7 +94,7 @@ export const useTags = (organization: string, repository: string) => {
// manifest_list: tag.manifest_list,
};
});
- }, [tags, tagManifestLayers, localClasses.chip]);
+ }, [tags, localClasses.chip, tagManifestLayers, tagManifestStatuses]);
return { loading, data };
};
diff --git a/plugins/quay/src/hooks/useTags.test.ts b/plugins/quay/src/hooks/useTags.test.ts
index 61dc7871f3..15c398877a 100644
--- a/plugins/quay/src/hooks/useTags.test.ts
+++ b/plugins/quay/src/hooks/useTags.test.ts
@@ -1,3 +1,5 @@
+import { useApi } from '@backstage/core-plugin-api';
+
import { waitFor } from '@testing-library/react';
import { renderHook } from '@testing-library/react-hooks';
@@ -21,4 +23,40 @@ describe('useTags', () => {
expect(result.current.data).toHaveLength(1);
});
});
+
+ it('should return security status for tags', async () => {
+ (useApi as jest.Mock).mockReturnValue({
+ getSecurityDetails: jest
+ .fn()
+ .mockReturnValue({ data: null, status: 'unsupported' }),
+ getTags: jest.fn().mockReturnValue({
+ tags: [{ name: 'tag1', manifest_digest: 'manifestDigest' }],
+ }),
+ });
+ const { result } = renderHook(() => useTags('foo', 'bar'));
+ await waitFor(() => {
+ expect(result.current.loading).toBeFalsy();
+ expect(result.current.data).toHaveLength(1);
+ expect(result.current.data[0].securityStatus).toBe('unsupported');
+ expect(result.current.data[0].securityDetails).toBeUndefined();
+ });
+ });
+
+ it('should return tag layers as security details for tags', async () => {
+ (useApi as jest.Mock).mockReturnValue({
+ getSecurityDetails: jest
+ .fn()
+ .mockReturnValue({ data: { Layer: {} }, status: 'scanned' }),
+ getTags: jest.fn().mockReturnValue({
+ tags: [{ name: 'tag1', manifest_digest: 'manifestDigest' }],
+ }),
+ });
+ const { result } = renderHook(() => useTags('foo', 'bar'));
+ await waitFor(() => {
+ expect(result.current.loading).toBeFalsy();
+ expect(result.current.data).toHaveLength(1);
+ expect(result.current.data[0].securityStatus).toBe('scanned');
+ expect(result.current.data[0].securityDetails).toEqual({});
+ });
+ });
});