patch: Implement pagination on ImageListWithCVEFixed query
Signed-off-by: Raul Kele <raulkeleblk@gmail.com>
This commit is contained in:
parent
c9c058a350
commit
6db80e4700
@ -4,11 +4,16 @@ import userEvent from '@testing-library/user-event';
|
||||
import { api } from 'api';
|
||||
import TagDetails from 'components/Tag/TagDetails';
|
||||
import MockThemeProvier from '__mocks__/MockThemeProvider';
|
||||
import { BrowserRouter, Routes, Route } from 'react-router-dom';
|
||||
|
||||
const TagDetailsThemeWrapper = () => {
|
||||
return (
|
||||
<MockThemeProvier>
|
||||
<TagDetails />
|
||||
<BrowserRouter>
|
||||
<Routes>
|
||||
<Route path="*" element={<TagDetails />} />
|
||||
</Routes>
|
||||
</BrowserRouter>
|
||||
</MockThemeProvier>
|
||||
);
|
||||
};
|
||||
@ -191,6 +196,48 @@ const mockImageHigh = {
|
||||
}
|
||||
};
|
||||
|
||||
const mockDependenciesList = {
|
||||
data: {
|
||||
BaseImageList: {
|
||||
Page: { ItemCount: 4, TotalCount: 4 },
|
||||
Results: [
|
||||
{
|
||||
RepoName: 'project-stacker/c3/static-ubuntu-amd64',
|
||||
Tag: 'tag1',
|
||||
Vulnerabilities: {
|
||||
MaxSeverity: 'HIGH',
|
||||
Count: 5
|
||||
}
|
||||
},
|
||||
{
|
||||
RepoName: 'tag2',
|
||||
Tag: 'tag2',
|
||||
Vulnerabilities: {
|
||||
MaxSeverity: 'CRITICAL',
|
||||
Count: 2
|
||||
}
|
||||
},
|
||||
{
|
||||
RepoName: 'tag3',
|
||||
Tag: 'tag3',
|
||||
Vulnerabilities: {
|
||||
MaxSeverity: 'LOW',
|
||||
Count: 7
|
||||
}
|
||||
},
|
||||
{
|
||||
RepoName: 'tag4',
|
||||
Tag: 'tag4',
|
||||
Vulnerabilities: {
|
||||
MaxSeverity: 'HIGH',
|
||||
Count: 5
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// mock clipboard copy fn
|
||||
const mockCopyToClipboard = jest.fn();
|
||||
Object.assign(navigator, {
|
||||
@ -232,7 +279,7 @@ describe('Tags details', () => {
|
||||
it('should show tabs and allow nagivation between them', async () => {
|
||||
jest.spyOn(api, 'get').mockResolvedValueOnce({ status: 200, data: { data: mockImage } });
|
||||
render(<TagDetailsThemeWrapper />);
|
||||
jest.spyOn(api, 'get').mockResolvedValue({ status: 500, data: { data: { errors: ['test error'] } } });
|
||||
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: mockDependenciesList });
|
||||
const dependenciesTab = await screen.findByTestId('dependencies-tab');
|
||||
fireEvent.click(dependenciesTab);
|
||||
expect(await screen.findByTestId('depends-on-container')).toBeInTheDocument();
|
||||
|
@ -433,17 +433,35 @@ const mockCVEList = {
|
||||
};
|
||||
|
||||
const mockCVEFixed = {
|
||||
ImageListWithCVEFixed: [
|
||||
{
|
||||
Tag: '1.0.16'
|
||||
},
|
||||
{
|
||||
Tag: '0.4.33'
|
||||
},
|
||||
{
|
||||
Tag: '1.0.17'
|
||||
pageOne: {
|
||||
ImageListWithCVEFixed: {
|
||||
Page: { TotalCount: 5, ItemCount: 3 },
|
||||
Results: [
|
||||
{
|
||||
Tag: '1.0.16'
|
||||
},
|
||||
{
|
||||
Tag: '0.4.33'
|
||||
},
|
||||
{
|
||||
Tag: '1.0.17'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
pageTwo: {
|
||||
ImageListWithCVEFixed: {
|
||||
Page: { TotalCount: 5, ItemCount: 2 },
|
||||
Results: [
|
||||
{
|
||||
Tag: 'slim'
|
||||
},
|
||||
{
|
||||
Tag: 'latest'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
@ -484,7 +502,7 @@ describe('Vulnerabilties page', () => {
|
||||
render(<StateVulnerabilitiesWrapper />);
|
||||
await waitFor(() => expect(screen.getAllByText(/description/i)).toHaveLength(20));
|
||||
const openText = screen.getAllByText(/description/i);
|
||||
fireEvent.click(openText[0]);
|
||||
await fireEvent.click(openText[0]);
|
||||
await waitFor(() =>
|
||||
expect(screen.getAllByText(/CPAN 2.28 allows Signature Verification Bypass./i)).toHaveLength(1)
|
||||
);
|
||||
@ -504,13 +522,30 @@ describe('Vulnerabilties page', () => {
|
||||
it('should find out which version fixes the CVEs', async () => {
|
||||
jest
|
||||
.spyOn(api, 'get')
|
||||
|
||||
.mockResolvedValueOnce({ status: 200, data: { data: mockCVEList } })
|
||||
|
||||
.mockResolvedValue({ status: 200, data: { data: mockCVEFixed } });
|
||||
.mockResolvedValueOnce({ status: 200, data: { data: mockCVEFixed.pageOne } })
|
||||
.mockResolvedValueOnce({ status: 200, data: { data: mockCVEFixed.pageTwo } });
|
||||
render(<StateVulnerabilitiesWrapper />);
|
||||
await waitFor(() => expect(screen.getAllByText('Vulnerabilities')).toHaveLength(1));
|
||||
fireEvent.click(screen.getAllByText(/fixed in/i)[0]);
|
||||
await fireEvent.click(screen.getAllByText(/fixed in/i)[0]);
|
||||
await waitFor(() => expect(screen.getByText('1.0.16')).toBeInTheDocument());
|
||||
const loadMoreBtn = screen.getByText(/load more/i);
|
||||
expect(loadMoreBtn).toBeInTheDocument();
|
||||
await fireEvent.click(loadMoreBtn);
|
||||
await waitFor(() => expect(loadMoreBtn).not.toBeInTheDocument());
|
||||
await expect(await screen.findByText('latest')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should handle fixed CVE query errors', async () => {
|
||||
jest
|
||||
.spyOn(api, 'get')
|
||||
.mockResolvedValueOnce({ status: 200, data: { data: mockCVEList } })
|
||||
.mockRejectedValue({ status: 500, data: {} });
|
||||
render(<StateVulnerabilitiesWrapper />);
|
||||
await waitFor(() => expect(screen.getAllByText('Vulnerabilities')).toHaveLength(1));
|
||||
const error = jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||
await fireEvent.click(screen.getAllByText(/fixed in/i)[0]);
|
||||
await waitFor(() => expect(screen.getByText(/not fixed/i)).toBeInTheDocument());
|
||||
await waitFor(() => expect(error).toBeCalledTimes(1));
|
||||
});
|
||||
});
|
||||
|
@ -86,8 +86,10 @@ const endpoints = {
|
||||
}}){Tag Page {TotalCount ItemCount} CVEList {Id Title Description Severity PackageList {Name InstalledVersion FixedVersion}}}}`,
|
||||
layersDetailsForImage: (name) =>
|
||||
`/v2/_zot/ext/search?query={Image(image: "${name}"){History {Layer {Size Digest Score} HistoryDescription {Created CreatedBy Author Comment EmptyLayer} }}}`,
|
||||
imageListWithCVEFixed: (cveId, repoName) =>
|
||||
`/v2/_zot/ext/search?query={ImageListWithCVEFixed(id:"${cveId}", image:"${repoName}") {Tag}}`,
|
||||
imageListWithCVEFixed: (cveId, repoName, { pageNumber = 1, pageSize = 3 }) =>
|
||||
`/v2/_zot/ext/search?query={ImageListWithCVEFixed(id:"${cveId}", image:"${repoName}", requestedPage: {limit:${pageSize} offset:${
|
||||
(pageNumber - 1) * pageSize
|
||||
}}) {Page {TotalCount ItemCount} Results {Tag}}}`,
|
||||
dependsOnForImage: (name, { pageNumber = 1, pageSize = 15 } = {}) =>
|
||||
`/v2/_zot/ext/search?query={BaseImageList(image: "${name}", requestedPage: {limit:${pageSize} offset:${
|
||||
(pageNumber - 1) * pageSize
|
||||
|
@ -14,7 +14,7 @@ import Loading from '../../Shared/Loading';
|
||||
import { KeyboardArrowDown, KeyboardArrowRight } from '@mui/icons-material';
|
||||
import { VulnerabilityChipCheck } from 'utilities/vulnerabilityAndSignatureCheck';
|
||||
import { mapCVEInfo } from 'utilities/objectModels';
|
||||
import { EXPLORE_PAGE_SIZE } from 'utilities/paginationConstants';
|
||||
import { CVE_FIXEDIN_PAGE_SIZE, EXPLORE_PAGE_SIZE } from 'utilities/paginationConstants';
|
||||
|
||||
const useStyles = makeStyles(() => ({
|
||||
card: {
|
||||
@ -91,25 +91,50 @@ function VulnerabilitiyCard(props) {
|
||||
const [openFixed, setOpenFixed] = useState(false);
|
||||
const [loadingFixed, setLoadingFixed] = useState(true);
|
||||
const [fixedInfo, setFixedInfo] = useState([]);
|
||||
const abortController = useMemo(() => new AbortController(), []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!openFixed || !isEmpty(fixedInfo)) {
|
||||
// pagination props
|
||||
const [pageNumber, setPageNumber] = useState(1);
|
||||
const [isEndOfList, setIsEndOfList] = useState(false);
|
||||
|
||||
const getPaginatedResults = () => {
|
||||
if (!openFixed || isEndOfList) {
|
||||
return;
|
||||
}
|
||||
setLoadingFixed(true);
|
||||
api
|
||||
.get(`${host()}${endpoints.imageListWithCVEFixed(cve.id, name)}`)
|
||||
.get(
|
||||
`${host()}${endpoints.imageListWithCVEFixed(cve.id, name, { pageNumber, pageSize: CVE_FIXEDIN_PAGE_SIZE })}`,
|
||||
abortController.signal
|
||||
)
|
||||
.then((response) => {
|
||||
if (response.data && response.data.data) {
|
||||
const fixedTagsList = response.data.data.ImageListWithCVEFixed?.map((e) => e.Tag);
|
||||
setFixedInfo(fixedTagsList);
|
||||
const fixedTagsList = response.data.data.ImageListWithCVEFixed?.Results?.map((e) => e.Tag);
|
||||
setFixedInfo((previousState) => [...previousState, ...fixedTagsList]);
|
||||
setIsEndOfList(
|
||||
[...fixedInfo, ...fixedTagsList].length >= response.data.data.ImageListWithCVEFixed?.Page?.TotalCount
|
||||
);
|
||||
}
|
||||
setLoadingFixed(false);
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
setIsEndOfList(true);
|
||||
setLoadingFixed(false);
|
||||
});
|
||||
}, [openFixed]);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
getPaginatedResults();
|
||||
return () => {
|
||||
abortController.abort();
|
||||
};
|
||||
}, [openFixed, pageNumber]);
|
||||
|
||||
const loadMore = () => {
|
||||
if (loadingFixed || isEndOfList) return;
|
||||
setPageNumber((pageNumber) => pageNumber + 1);
|
||||
};
|
||||
|
||||
const renderFixedVer = () => {
|
||||
if (!isEmpty(fixedInfo)) {
|
||||
@ -152,8 +177,28 @@ function VulnerabilitiyCard(props) {
|
||||
<Collapse in={openFixed} timeout="auto" unmountOnExit>
|
||||
<Box>
|
||||
<Typography variant="body2" align="left" sx={{ color: '#0F2139', fontSize: '1rem', width: '100%' }}>
|
||||
{' '}
|
||||
{loadingFixed ? 'Loading...' : renderFixedVer()}{' '}
|
||||
{loadingFixed ? (
|
||||
'Loading...'
|
||||
) : (
|
||||
<>
|
||||
{renderFixedVer()}
|
||||
{!isEndOfList && (
|
||||
<Typography
|
||||
sx={{
|
||||
color: '#3366CC',
|
||||
fontSize: '1rem',
|
||||
display: 'inline',
|
||||
textDecorationLine: 'underline',
|
||||
cursor: 'pointer'
|
||||
}}
|
||||
onClick={loadMore}
|
||||
component="span"
|
||||
>
|
||||
Load more
|
||||
</Typography>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Collapse>
|
||||
|
@ -1,5 +1,6 @@
|
||||
const HEADER_SEARCH_PAGE_SIZE = 9;
|
||||
const EXPLORE_PAGE_SIZE = 10;
|
||||
const HOME_PAGE_SIZE = 10;
|
||||
const CVE_FIXEDIN_PAGE_SIZE = 5;
|
||||
|
||||
export { HEADER_SEARCH_PAGE_SIZE, EXPLORE_PAGE_SIZE, HOME_PAGE_SIZE };
|
||||
export { HEADER_SEARCH_PAGE_SIZE, EXPLORE_PAGE_SIZE, HOME_PAGE_SIZE, CVE_FIXEDIN_PAGE_SIZE };
|
||||
|
Loading…
x
Reference in New Issue
Block a user