Skip to content

Commit

Permalink
feat: starred repos implementation
Browse files Browse the repository at this point in the history
Signed-off-by: Andreea-Lupu <[email protected]>
  • Loading branch information
Andreea-Lupu committed Nov 29, 2023
1 parent e97e04e commit f1bea22
Show file tree
Hide file tree
Showing 9 changed files with 165 additions and 18 deletions.
8 changes: 5 additions & 3 deletions src/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ const endpoints = {
repoList: ({ pageNumber = 1, pageSize = 15 } = {}) =>
`/v2/_zot/ext/search?query={RepoListWithNewestImage(requestedPage: {limit:${pageSize} offset:${
(pageNumber - 1) * pageSize
}}){Results {Name LastUpdated Size Platforms {Os Arch} NewestImage { Tag Vulnerabilities {MaxSeverity Count} Description Licenses Title Source IsSigned SignatureInfo { Tool IsTrusted Author } Documentation Vendor Labels} IsStarred IsBookmarked DownloadCount}}}`,
}}){Results {Name LastUpdated Size Platforms {Os Arch} NewestImage { Tag Vulnerabilities {MaxSeverity Count} Description Licenses Title Source IsSigned SignatureInfo { Tool IsTrusted Author } Documentation Vendor Labels} IsStarred IsBookmarked StarCount DownloadCount}}}`,
detailedRepoInfo: (name) =>
`/v2/_zot/ext/search?query={ExpandedRepoInfo(repo:"${name}"){Images {Manifests {Digest Platform {Os Arch} Size} Vulnerabilities {MaxSeverity Count} Tag LastUpdated Vendor } Summary {Name LastUpdated Size Platforms {Os Arch} Vendors IsStarred IsBookmarked NewestImage {RepoName IsSigned SignatureInfo { Tool IsTrusted Author } Vulnerabilities {MaxSeverity Count} Manifests {Digest} Tag Vendor Title Documentation DownloadCount Source Description Licenses}}}}`,
detailedImageInfo: (name, tag) =>
Expand Down Expand Up @@ -134,9 +134,10 @@ const endpoints = {
if (filter.Arch) filterParam += ` Arch:${!isEmpty(filter.Arch) ? `${JSON.stringify(filter.Arch)}` : '""'}`;
if (filter.HasToBeSigned) filterParam += ` HasToBeSigned: ${filter.HasToBeSigned}`;
if (filter.IsBookmarked) filterParam += ` IsBookmarked: ${filter.IsBookmarked}`;
if (filter.IsStarred) filterParam += ` IsStarred: ${filter.IsStarred}`;
filterParam += '}';
if (Object.keys(filter).length === 0) filterParam = '';
return `/v2/_zot/ext/search?query={GlobalSearch(${searchParam}, ${paginationParam} ${filterParam}) {Page {TotalCount ItemCount} Repos {Name LastUpdated Size Platforms { Os Arch } IsStarred IsBookmarked NewestImage { Tag Vulnerabilities {MaxSeverity Count} Description IsSigned SignatureInfo { Tool IsTrusted Author } Licenses Vendor Labels } DownloadCount}}}`;
return `/v2/_zot/ext/search?query={GlobalSearch(${searchParam}, ${paginationParam} ${filterParam}) {Page {TotalCount ItemCount} Repos {Name LastUpdated Size Platforms { Os Arch } IsStarred IsBookmarked NewestImage { Tag Vulnerabilities {MaxSeverity Count} Description IsSigned SignatureInfo { Tool IsTrusted Author } Licenses Vendor Labels } StarCount DownloadCount}}}`;
},
imageSuggestions: ({ searchQuery = '""', pageNumber = 1, pageSize = 15 }) => {
const searchParam = searchQuery !== '' ? `query:"${searchQuery}"` : `query:""`;
Expand All @@ -145,7 +146,8 @@ const endpoints = {
},
referrers: ({ repo, digest, type = '' }) =>
`/v2/_zot/ext/search?query={Referrers(repo: "${repo}" digest: "${digest}" type: "${type}"){MediaType ArtifactType Size Digest Annotations{Key Value}}}`,
bookmarkToggle: (repo) => `/v2/_zot/ext/userprefs?repo=${repo}&action=toggleBookmark`
bookmarkToggle: (repo) => `/v2/_zot/ext/userprefs?repo=${repo}&action=toggleBookmark`,
starToggle: (repo) => `/v2/_zot/ext/userprefs?repo=${repo}&action=toggleStar`
};

export { api, endpoints };
2 changes: 2 additions & 0 deletions src/components/Explore/Explore.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -220,9 +220,11 @@ function Explore({ searchInputValue }) {
version={item.latestVersion}
description={item.description}
downloads={item.downloads}
stars={item.stars}
isSigned={item.isSigned}
signatureInfo={item.signatureInfo}
isBookmarked={item.isBookmarked}
isStarred={item.isStarred}
vendor={item.vendor}
platforms={item.platforms}
key={index}
Expand Down
65 changes: 64 additions & 1 deletion src/components/Home/Home.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,12 @@ import { mapToRepo } from 'utilities/objectModels';
import Loading from '../Shared/Loading';
import { useNavigate, createSearchParams } from 'react-router-dom';
import { sortByCriteria } from 'utilities/sortCriteria';
import { HOME_POPULAR_PAGE_SIZE, HOME_RECENT_PAGE_SIZE, HOME_BOOKMARKS_PAGE_SIZE } from 'utilities/paginationConstants';
import {
HOME_POPULAR_PAGE_SIZE,
HOME_RECENT_PAGE_SIZE,
HOME_BOOKMARKS_PAGE_SIZE,
HOME_STARS_PAGE_SIZE
} from 'utilities/paginationConstants';
import { isEmpty } from 'lodash';
import NoDataComponent from 'components/Shared/NoDataComponent';

Expand Down Expand Up @@ -89,6 +94,8 @@ function Home() {
const [isLoadingRecent, setIsLoadingRecent] = useState(true);
const [bookmarkData, setBookmarkData] = useState([]);
const [isLoadingBookmarks, setIsLoadingBookmarks] = useState(true);
const [starData, setStarData] = useState([]);
const [isLoadingStars, setIsLoadingStars] = useState(true);

const navigate = useNavigate();
const abortController = useMemo(() => new AbortController(), []);
Expand Down Expand Up @@ -185,12 +192,44 @@ function Home() {
});
};

const getStars = () => {
setIsLoadingStars(true);
api
.get(
`${host()}${endpoints.globalSearch({
searchQuery: '',
pageNumber: 1,
pageSize: HOME_STARS_PAGE_SIZE,
sortBy: sortByCriteria.relevance?.value,
filter: { IsStarred: true }
})}`,
abortController.signal
)
.then((response) => {
if (response.data && response.data.data) {
let repoList = response.data.data.GlobalSearch.Repos;
let repoData = repoList.map((responseRepo) => {
return mapToRepo(responseRepo);
});
setStarData(repoData);
setIsLoading(false);
setIsLoadingStars(false);
}
})
.catch((e) => {
setIsLoading(false);
setIsLoadingStars(false);
console.error(e);
});
};

useEffect(() => {
window.scrollTo(0, 0);
setIsLoading(true);
getPopularData();
getRecentData();
getBookmarks();
getStars();
return () => {
abortController.abort();
};
Expand All @@ -203,9 +242,11 @@ function Home() {
const isNoData = () =>
!isLoading &&
!isLoadingBookmarks &&
!isLoadingStars &&
!isLoadingPopular &&
!isLoadingRecent &&
bookmarkData.length === 0 &&
starData.length === 0 &&
popularData.length === 0 &&
recentData.length === 0;

Expand All @@ -222,6 +263,7 @@ function Home() {
isSigned={item.isSigned}
signatureInfo={item.signatureInfo}
isBookmarked={item.isBookmarked}
isStarred={item.isStarred}
vendor={item.vendor}
platforms={item.platforms}
key={index}
Expand Down Expand Up @@ -294,6 +336,27 @@ function Home() {
{isLoadingBookmarks ? <Loading /> : renderCards(bookmarkData, isLoadingBookmarks)}
</>
)}
{!isEmpty(starData) && (
<>
<Stack className={classes.sectionHeaderContainer}>
<div>
<Typography variant="h4" align="left" className={classes.sectionTitle}>
Stars
</Typography>
</div>
<div>
<Typography
variant="body2"
className={classes.viewAll}
onClick={() => handleClickViewAll('filter', 'IsStarred')}
>
View all
</Typography>
</div>
</Stack>
{isLoadingStars ? <Loading /> : renderCards(starData, isLoadingStars)}
</>
)}
</Stack>
);
};
Expand Down
42 changes: 33 additions & 9 deletions src/components/Repo/RepoDetails.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import { useParams, useNavigate, createSearchParams } from 'react-router-dom';
import { Card, CardContent, CardMedia, Chip, Grid, Stack, Tooltip, Typography, IconButton } from '@mui/material';
import BookmarkIcon from '@mui/icons-material/Bookmark';
import BookmarkBorderIcon from '@mui/icons-material/BookmarkBorder';
import StarIcon from '@mui/icons-material/Star';
import StarBorderIcon from '@mui/icons-material/StarBorder';
import makeStyles from '@mui/styles/makeStyles';

// placeholder images
Expand Down Expand Up @@ -230,6 +232,17 @@ function RepoDetails() {
});
};

const handleStarClick = () => {
api.put(`${host()}${endpoints.starToggle(name)}`, abortController.signal).then((response) => {
if (response.status === 200) {
setRepoDetailData((prevState) => ({
...prevState,
isStarred: !prevState.isStarred
}));
}
});
};

const getVendor = () => {
return `${repoDetailData.newestTag?.Vendor || 'Vendor not available'} •`;
};
Expand Down Expand Up @@ -276,15 +289,26 @@ function RepoDetails() {
signatureInfo={repoDetailData.signatureInfo}
/>
</Stack>
{isAuthenticated() && (
<IconButton component="span" onClick={handleBookmarkClick} data-testid="bookmark-button">
{repoDetailData?.isBookmarked ? (
<BookmarkIcon data-testid="bookmarked" />
) : (
<BookmarkBorderIcon data-testid="not-bookmarked" />
)}
</IconButton>
)}
<Stack alignItems="center" sx={{ width: { xs: '100%', md: 'auto' } }} direction="row" spacing={1}>
{isAuthenticated() && (
<IconButton component="span" onClick={handleStarClick} data-testid="star-button">
{repoDetailData?.isStarred ? (
<StarIcon data-testid="starred" />
) : (
<StarBorderIcon data-testid="not-starred" />
)}
</IconButton>
)}
{isAuthenticated() && (
<IconButton component="span" onClick={handleBookmarkClick} data-testid="bookmark-button">
{repoDetailData?.isBookmarked ? (
<BookmarkIcon data-testid="bookmarked" />
) : (
<BookmarkBorderIcon data-testid="not-bookmarked" />
)}
</IconButton>
)}
</Stack>
</Stack>
<Typography gutterBottom className={classes.repoTitle}>
{repoDetailData?.title || 'Title not available'}
Expand Down
47 changes: 46 additions & 1 deletion src/components/Shared/RepoCard.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ import {
import makeStyles from '@mui/styles/makeStyles';
import BookmarkIcon from '@mui/icons-material/Bookmark';
import BookmarkBorderIcon from '@mui/icons-material/BookmarkBorder';
import StarIcon from '@mui/icons-material/Star';
import StarBorderIcon from '@mui/icons-material/StarBorder';
import { useTheme } from '@emotion/react';

// placeholder images
Expand Down Expand Up @@ -183,17 +185,24 @@ function RepoCard(props) {
platforms,
description,
downloads,
stars,
isSigned,
signatureInfo,
lastUpdated,
version,
vulnerabilityData,
isBookmarked
isBookmarked,
isStarred
} = props;

// keep a local bookmark state to display in the ui dynamically on updates
const [currentBookmarkValue, setCurrentBookmarkValue] = useState(isBookmarked);

// keep a local star state to display in the ui dynamically on updates
const [currentStarValue, setCurrentStarValue] = useState(isStarred);

const [currentStarCount, setCurrentStarCount] = useState(stars);

const goToDetails = () => {
navigate(`/image/${encodeURIComponent(name)}`);
};
Expand All @@ -215,6 +224,23 @@ function RepoCard(props) {
});
};

const handleStarClick = (event) => {
event.stopPropagation();
event.preventDefault();
api.put(`${host()}${endpoints.starToggle(name)}`, abortController.signal).then((response) => {
if (response.status === 200) {
setCurrentStarValue((prevState) => !prevState);
currentStarValue
? setCurrentStarCount((prevState) => {
return !isNaN(prevState) ? prevState - 1 : prevState;
})
: setCurrentStarCount((prevState) => {
return !isNaN(prevState) ? prevState + 1 : prevState;
});
}
});
};

const platformChips = () => {
const filteredPlatforms = uniq(platforms?.flatMap((platform) => [platform.Os, platform.Arch]));
const hiddenChips = filteredPlatforms.length - MAX_PLATFORM_CHIPS;
Expand Down Expand Up @@ -260,6 +286,16 @@ function RepoCard(props) {
);
};

const renderStar = () => {
return (
isAuthenticated() && (
<IconButton component="span" onClick={handleStarClick} data-testid="star-button">
{currentStarValue ? <StarIcon data-testid="starred" /> : <StarBorderIcon data-testid="not-starred" />}
</IconButton>
)
);
};

return (
<Card variant="outlined" className={classes.card} data-testid="repo-card">
<CardActionArea
Expand Down Expand Up @@ -337,6 +373,15 @@ function RepoCard(props) {
#1
</Typography>
</Grid> */}
<Grid item xs={12}>
{renderStar()}
<Typography variant="body2" component="span" className={classes.contentRightLabel}>
Stars •
</Typography>
<Typography variant="body2" component="span" className={classes.contentRightValue}>
{!isNaN(currentStarCount) ? currentStarCount : `not available`}
</Typography>
</Grid>
<Grid container item xs={12} className={classes.contentRightActions}>
<Grid item>{renderBookmark()}</Grid>
</Grid>
Expand Down
5 changes: 5 additions & 0 deletions src/utilities/filterConstants.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ const imageFilters = [
label: 'Bookmarks',
value: 'IsBookmarked',
type: 'boolean'
},
{
label: 'Starred Repositories',
value: 'IsStarred',
type: 'boolean'
}
];

Expand Down
4 changes: 4 additions & 0 deletions src/utilities/objectModels.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const mapToRepo = (responseRepo) => {
logo: responseRepo.NewestImage?.Logo,
lastUpdated: responseRepo.LastUpdated,
downloads: responseRepo.DownloadCount,
stars: responseRepo.StarCount,
vulnerabiltySeverity: responseRepo.NewestImage?.Vulnerabilities?.MaxSeverity,
vulnerabilityCount: responseRepo.NewestImage?.Vulnerabilities?.Count
};
Expand All @@ -33,6 +34,7 @@ const mapToRepoFromRepoInfo = (responseRepoInfo) => {
title: responseRepoInfo.Summary?.NewestImage?.Title,
source: responseRepoInfo.Summary?.NewestImage?.Source,
downloads: responseRepoInfo.Summary?.NewestImage?.DownloadCount,
stars: responseRepoInfo.Summary?.NewestImage?.StarCount,
overview: responseRepoInfo.Summary?.NewestImage?.Documentation,
license: responseRepoInfo.Summary?.NewestImage?.Licenses,
vulnerabilitySeverity: responseRepoInfo.Summary?.NewestImage?.Vulnerabilities?.MaxSeverity,
Expand All @@ -53,6 +55,7 @@ const mapToImage = (responseImage) => {
referrers: responseImage.Referrers,
size: responseImage.Size,
downloadCount: responseImage.DownloadCount,
starCount: responseImage.StarCount,
lastUpdated: responseImage.LastUpdated,
description: responseImage.Description,
isSigned: responseImage.IsSigned,
Expand All @@ -79,6 +82,7 @@ const mapToManifest = (responseManifest) => {
size: responseManifest.Size,
platform: responseManifest.Platform,
downloadCount: responseManifest.DownloadCount,
starCount: responseManifest.StarCount,
layers: responseManifest.Layers,
history: responseManifest.History,
vulnerabilities: responseManifest.Vulnerabilities
Expand Down
4 changes: 3 additions & 1 deletion src/utilities/paginationConstants.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const HOME_PAGE_SIZE = 10;
const HOME_POPULAR_PAGE_SIZE = 3;
const HOME_RECENT_PAGE_SIZE = 2;
const HOME_BOOKMARKS_PAGE_SIZE = 2;
const HOME_STARS_PAGE_SIZE = 2;
const CVE_FIXEDIN_PAGE_SIZE = 5;

export {
Expand All @@ -13,5 +14,6 @@ export {
CVE_FIXEDIN_PAGE_SIZE,
HOME_POPULAR_PAGE_SIZE,
HOME_RECENT_PAGE_SIZE,
HOME_BOOKMARKS_PAGE_SIZE
HOME_BOOKMARKS_PAGE_SIZE,
HOME_STARS_PAGE_SIZE
};
6 changes: 3 additions & 3 deletions tests/values/test-constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,13 @@ const pageSizes = {
};

const endpoints = {
repoList: `/v2/_zot/ext/search?query={RepoListWithNewestImage(requestedPage:%20{limit:15%20offset:0}){Results%20{Name%20LastUpdated%20Size%20Platforms%20{Os%20Arch}%20%20NewestImage%20{%20Tag%20Vulnerabilities%20{MaxSeverity%20Count}%20Description%20%20Licenses%20Logo%20Title%20Source%20IsSigned%20Documentation%20Vendor%20Labels}%20DownloadCount}}}`,
repoList: `/v2/_zot/ext/search?query={RepoListWithNewestImage(requestedPage:%20{limit:15%20offset:0}){Results%20{Name%20LastUpdated%20Size%20Platforms%20{Os%20Arch}%20%20NewestImage%20{%20Tag%20Vulnerabilities%20{MaxSeverity%20Count}%20Description%20%20Licenses%20Logo%20Title%20Source%20IsSigned%20Documentation%20Vendor%20Labels}%20StarCount%20DownloadCount}}}`,
detailedRepoInfo: (name) =>
`/v2/_zot/ext/search?query={ExpandedRepoInfo(repo:%22${name}%22){Images%20{Manifests%20{Digest%20Platform%20{Os%20Arch}%20Size}%20Vulnerabilities%20{MaxSeverity%20Count}%20Tag%20LastUpdated%20Vendor%20}%20Summary%20{Name%20LastUpdated%20Size%20Platforms%20{Os%20Arch}%20Vendors%20NewestImage%20{RepoName%20IsSigned%20Vulnerabilities%20{MaxSeverity%20Count}%20Manifests%20{Digest}%20Tag%20Title%20Documentation%20DownloadCount%20Source%20Description%20Licenses}}}}`,
`/v2/_zot/ext/search?query={ExpandedRepoInfo(repo:%22${name}%22){Images%20{Manifests%20{Digest%20Platform%20{Os%20Arch}%20Size}%20Vulnerabilities%20{MaxSeverity%20Count}%20Tag%20LastUpdated%20Vendor%20}%20Summary%20{Name%20LastUpdated%20Size%20Platforms%20{Os%20Arch}%20Vendors%20NewestImage%20{RepoName%20IsSigned%20Vulnerabilities%20{MaxSeverity%20Count}%20Manifests%20{Digest}%20Tag%20Title%20Documentation%20StarCount%20DownloadCount%20Source%20Description%20Licenses}}}}`,
globalSearch: (searchTerm, sortCriteria, pageNumber = 1, pageSize = 10) =>
`/v2/_zot/ext/search?query={GlobalSearch(query:%22${searchTerm}%22,%20requestedPage:%20{limit:${pageSize}%20offset:${
10 * (pageNumber - 1)
}%20sortBy:%20${sortCriteria}}%20)%20{Page%20{TotalCount%20ItemCount}%20Repos%20{Name%20LastUpdated%20Size%20Platforms%20{%20Os%20Arch%20}%20IsStarred%20IsBookmarked%20NewestImage%20{%20Tag%20Vulnerabilities%20{MaxSeverity%20Count}%20Description%20IsSigned%20SignatureInfo%20{%20Tool%20IsTrusted%20Author%20}%20Licenses%20Vendor%20Labels%20}%20DownloadCount}}}`,
}%20sortBy:%20${sortCriteria}}%20)%20{Page%20{TotalCount%20ItemCount}%20Repos%20{Name%20LastUpdated%20Size%20Platforms%20{%20Os%20Arch%20}%20IsStarred%20IsBookmarked%20NewestImage%20{%20Tag%20Vulnerabilities%20{MaxSeverity%20Count}%20Description%20IsSigned%20SignatureInfo%20{%20Tool%20IsTrusted%20Author%20}%20Licenses%20Vendor%20Labels%20}%20StarCount%20DownloadCount}}}`,
image: (name) =>
`/v2/_zot/ext/search?query={Image(image:%20%22${name}%22){RepoName%20IsSigned%20SignatureInfo%20{%20Tool%20IsTrusted%20Author%20}%20Vulnerabilities%20{MaxSeverity%20Count}%20%20Referrers%20{MediaType%20ArtifactType%20Size%20Digest%20Annotations{Key%20Value}}%20Tag%20Manifests%20{History%20{Layer%20{Size%20Digest}%20HistoryDescription%20{CreatedBy%20EmptyLayer}}%20Digest%20ConfigDigest%20LastUpdated%20Size%20Platform%20{Os%20Arch}}%20Vendor%20Licenses%20}}`
};
Expand Down

0 comments on commit f1bea22

Please sign in to comment.