Skip to content

Commit

Permalink
frontend: Adds filter feature for app catalog
Browse files Browse the repository at this point in the history
Signed-off-by: Vincent T <[email protected]>
  • Loading branch information
vyncent-t committed Jun 13, 2024
1 parent f3e8b82 commit f2f4f25
Show file tree
Hide file tree
Showing 4 changed files with 411 additions and 46 deletions.
145 changes: 136 additions & 9 deletions app-catalog/src/api/charts.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,46 @@
import { PAGE_OFFSET_COUNT_FOR_CHARTS } from '../components/charts/List';

export function fetchChartsFromArtifact(
export async function fetchChartsFromArtifact(
search: string = '',
verified: boolean,
cncf: boolean,
loadNonMatchingCharts: boolean,
category: { title: string; value: number },
page: number,
limit: number = PAGE_OFFSET_COUNT_FOR_CHARTS
) {
// note: we are currently defaulting to search by verified and official as default

// if we navigate to the last page we make a request to filter by false?

// function to filter the seen charts

// this array will be made of charts sourced from "response.packages"


if (!category || category.value === 0) {
return fetch(
`https://artifacthub.io/api/v1/packages/search?kind=0&ts_query_web=${search}&sort=relevance&facets=true&limit=${limit}&offset=${
(page - 1) * limit
}`
).then(response => response.json());
if (cncf) {
const dataResponse = await fetch(
`https://artifacthub.io/api/v1/packages/search?kind=0&ts_query_web=${search}&sort=relevance&facets=true&limit=${limit}&offset=${(page - 1) * limit}&verified_publisher=${verified}&cncf=${cncf}`
).then(response => response.json());

console.log("dataResponse", dataResponse)


return fetch(
`https://artifacthub.io/api/v1/packages/search?kind=0&ts_query_web=${search}&sort=relevance&facets=true&limit=${limit}&offset=${(page - 1) * limit}&verified_publisher=${verified}&cncf=${cncf}`
).then(response => response.json());
} else {
return fetch(
`https://artifacthub.io/api/v1/packages/search?kind=0&ts_query_web=${search}&sort=relevance&facets=true&limit=${limit}&offset=${(page - 1) * limit}&verified_publisher=${verified}`
).then(response => response.json());
}
}

return fetch(
`https://artifacthub.io/api/v1/packages/search?kind=0&ts_query_web=${search}&category=${
category.value
}&sort=relevance&facets=true&limit=${limit}&offset=${(page - 1) * limit}`
`https://artifacthub.io/api/v1/packages/search?kind=0&ts_query_web=${search}&category=${category.value
}&sort=relevance&facets=true&limit=${limit}&offset=${(page - 1) * limit
}&verified_publisher=${verified}&cncf=${cncf}`
).then(response => response.json());
}

Expand All @@ -35,3 +59,106 @@ export function fetchChartValues(packageID: string, packageVersion: string) {
},
}).then(response => response.text());
}



// }


// maybe we dont need all this? maybe I can just use a useEffect for a layout that
// filters the parameters FIRST and then when the last of it is rendered we normal search the rest?


// The way to do this:
// - use a [loadNonMatch, setLoadNonMatch] = useState(false)
// - this will be used to follow the navigation, maybe if we use the math.floor num and say
// if we use the math.floor num and say that the last page of charts that match the filter parameters
// - after the charts that are all rendered by filter, we set the loadnonmatch to true and load the other charts
// - is there a way to filter out the ones that have already been seen while navigating?

// - !!! Maybe I can create some middleware that will note the current rendered charts and put them in the seen list
// and while navigating the tabs we add more charts to the seen list, and when we hit the end of the tabs
// that were available for that filter parameter (verified or "search" word) we then create a function
// that isplaced in between the return of the incoming response data and the function will filter
// the data that has been seen already and return the "new data", this way we can nav through
// the charts from "matches filter" to "non-matches filter" and back again without having to reload

// - !!! we need a way to mathmatically track as to when we should be hitting the last of the charts
// that match the filter parameters, we can do this by using the total count of the charts that match
// - !!! when the number of charts that match has been reached i.e we have already rendered the charts
// then we can set the loadnonmatch to true and then we can start loading the other charts

// - !!! we will load the other charts my making a generic search request with the same parameters
// only this time it will kick off the if (loadnonmatch) and then we will be able to navigate the normals
// within that if block the function of filterSeenCharts will be called and the data will be filtered



export async function fetchAllItemCount(verified: boolean) {

const response = await fetch(`https://artifacthub.io/api/v1/packages/search?offset=0&limit=4&facets=true&kind=0&deprecated=false&sort=relevance&verified_publisher=${verified}`)
const data = await response.json()

console.log("data for item", data)

// find the number within response.facets.options.[where the id field is 0].total
const total = data.facets.find((facet) => facet.filter_key === "kind").options.find((option) => option.id === 0).total
console.log("item total for helm", total)

return data

}

export async function fetchChart(limit, offset, verified, cncf, search) {
const response = await fetch(`https://artifacthub.io/api/v1/packages/search?kind=0&ts_query_web=${search}&sort=relevance&facets=true&limit=${limit}&offset=${offset}&verified_publisher=${verified}&cncf=${cncf}`)
const data = await response.json()
console.log("data", data.packages)
return data
}

async function fetchAllCharts(total, verified, cncf, search) {
let allCharts = []
let offset = 0

// we need to take the total and divide it by 60 to get the number of times we need to make the request
// we can then use the offset to keep track of the number of requests we have made
const totalRequests = Math.ceil(total / 60)


for (let i = 0; i < totalRequests; i++) {
const limit = Math.min(60, total - offset)
const data = await fetchChart(limit, offset, verified, cncf, search)
console.log("data", data.packages)

if (data.packages.length < 9) {
console.log("LAST ITEMS OF CHARTS")
}

if (data.packages.length > 0) {
allCharts = allCharts.concat(data.packages)
offset += limit
}

console.log(`current length of allCharts ${allCharts.length} and offset ${offset} and limit ${limit} and totalRequests ${totalRequests} and total ${total}`)
}


console.log("length of allCharts", allCharts.length)
return allCharts
}

export async function returnAllCharts(totalCharts,
official: boolean,
verified: boolean,
cncf: boolean,
search: string) {
try {
const charts = await fetchAllCharts(totalCharts, verified, cncf, search)
console.log("length of charts", charts.length)
// console log the last chart to see if we have all the charts
console.log("last chart", charts[charts.length - 1])
} catch (error) {
console.log("error", error)
}
}

172 changes: 136 additions & 36 deletions app-catalog/src/components/charts/List.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,10 @@ import {
Tooltip,
Typography,
} from '@mui/material';
import { Autocomplete, Pagination } from '@mui/material';
import { Autocomplete, ButtonGroup, Pagination } from '@mui/material';
import { useEffect, useState } from 'react';
//import { jsonToYAML, yamlToJSON } from '../../helpers';
import { fetchChartsFromArtifact } from '../../api/charts';
import CNCFLight from './cncf-icon-color.svg';
import { fetchAllItemCount, fetchChartsFromArtifact } from '../../api/charts';
//import { createRelease } from '../../api/releases';
import { EditorDialog } from './EditorDialog';

Expand All @@ -45,46 +44,92 @@ export function ChartsList({ fetchCharts = fetchChartsFromArtifact }) {
const [chartsTotalCount, setChartsTotalCount] = useState(0);
const [chartCategory, setChartCategory] = useState(helmChartCategoryList[0]);
const [search, setSearch] = useState('');
const [chartOfficial, setChartOfficial] = useState();
const [showVerified, setShowVerified] = useState<boolean>(() => JSON.parse(localStorage.getItem('showVerified') ?? 'true'));
const [isCNCF, setIsCNCF] = useState(false);
const [loadNonMatching, setLoadNonMatching] = useState(false);
const [seenCharts, setSeenCharts] = useState<any[]>([]);
const [selectedChartForInstall, setSelectedChartForInstall] = useState<any | null>(null);


function filterSeenCharts(responseData) {
responseData.forEach(
(chart) => {
if (!seenCharts.includes(chart)) {
seenCharts.push(chart)
}
}
)

// later add an if statement here that if the math number of seenCharts.length matches the total of num chart total
// we set the loadNonMatching to true
if (Math.floor(chartsTotalCount / PAGE_OFFSET_COUNT_FOR_CHARTS) === page) {
setLoadNonMatching(true)
// returnAllCharts(chartsTotalCount, chartOfficial, chartVerified, isCNCF, search)

}

return responseData
}


useEffect(() => {

setCharts(null);
fetchCharts(search, chartCategory, page).then(response => {
setCharts(response.packages);
const facets = response.facets;
const categoryOptions = facets.find(
(facet: {
title: string;
options: {
name: string;
total: number;
}[];
}) => facet.title === 'Category'
).options;
if (chartCategory.title === 'All') {
const totalCount = categoryOptions.reduce(
(
acc: number,
option: {

fetchCharts(search, showVerified, isCNCF, loadNonMatching, chartCategory, page).then(
response => {
setCharts(filterSeenCharts(response.packages));
const facets = response.facets;
const categoryOptions = facets.find(
(facet: {
title: string;
options: {
name: string;
total: number;
}
) => acc + option.total,
0
);
}[];
}) => facet.title === 'Category'
).options;

// total number of charts
if (chartCategory.title === 'All') {
const totalCount = categoryOptions.reduce(
(
acc: number,
option: {
name: string;
total: number;
}
) => acc + option.total,
0
);
setChartsTotalCount(totalCount);
return;
}

// total number of charts
const totalCount = categoryOptions.find(
(option: { name: string; total: number }) => option.name === chartCategory?.title
).total;
setChartsTotalCount(totalCount);
return;
}
const totalCount = categoryOptions.find(
(option: { name: string; total: number }) => option.name === chartCategory?.title
).total;
setChartsTotalCount(totalCount);
});
}, [page, chartCategory, search]);
);

// how to turn boolean into string
fetchAllItemCount(showVerified)

}, [chartOfficial, showVerified, showVerified, isCNCF, page, chartCategory, search]);

useEffect(() => {
setPage(1);
}, [chartCategory, search]);
}, [chartOfficial, showVerified, showVerified, isCNCF, chartCategory, search]);


function toggleShowVerified() {
setShowVerified(!showVerified)
console.log("SWITCH current showVerified", showVerified)
localStorage.setItem('showVerified', `${!showVerified}`)
}

function Search() {
return (
Expand All @@ -106,6 +151,59 @@ export function ChartsList({ fetchCharts = fetchChartsFromArtifact }) {
);
}

function ToggleOptions() {
return (
<>
<Typography>
current num of seen charts {chartsTotalCount}
</Typography>
<Typography>
current local showUnverified is {`${showVerified}`}
</Typography>
<Typography>
current num of seen charts {chartsTotalCount}
</Typography>
<Typography>current loadNonMatch is {`${loadNonMatching}`}</Typography>
<ButtonGroup variant="contained" size="small" color="primary" style={{ margin: '1rem' }}>
{/* <Button
onClick={() => {
setChartOfficial(!chartOfficial);
}}
style={{
backgroundColor: chartOfficial ? 'black' : 'white',
color: chartOfficial ? 'white' : '#000',
}}
>
Official
</Button> */}
<Button
onClick={() => {
toggleShowVerified()
}}
style={{
backgroundColor: showVerified ? 'black' : 'white',
color: showVerified ? 'white' : '#000',
}}
>
Verified
</Button>
<Button
onClick={() => {
setIsCNCF(!isCNCF);
}}
style={{
backgroundColor: isCNCF ? 'black' : 'white',
color: isCNCF ? 'white' : '#000',
}}
>
CNCF
</Button>
</ButtonGroup>
</>

);
}

function CategoryForCharts() {
return (
<Autocomplete
Expand Down Expand Up @@ -147,7 +245,10 @@ export function ChartsList({ fetchCharts = fetchChartsFromArtifact }) {
chart={selectedChartForInstall}
handleEditor={(open: boolean) => setEditorOpen(open)}
/>
<SectionHeader title="Applications" actions={[<Search />, <CategoryForCharts />]} />
<SectionHeader
title="Applications"
actions={[<Search />, <CategoryForCharts />, <ToggleOptions />]}
/>
<Box display="flex" flexWrap="wrap" justifyContent="space-between" alignContent="start">
{!charts ? (
<Box
Expand All @@ -160,9 +261,8 @@ export function ChartsList({ fetchCharts = fetchChartsFromArtifact }) {
) : charts.length === 0 ? (
<Box mt={2} mx={2}>
<Typography variant="h5" component="h2">
{`No charts found for ${
search ? `search term: ${search}` : `category: ${chartCategory.title}`
}`}
{`No charts found for ${search ? `search term: ${search}` : `category: ${chartCategory.title}`
}`}
</Typography>
</Box>
) : (
Expand Down
5 changes: 4 additions & 1 deletion app-catalog/src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { registerRoute, registerSidebarEntry } from '@kinvolk/headlamp-plugin/lib';
import { registerPluginSettings, registerRoute, registerSidebarEntry } from '@kinvolk/headlamp-plugin/lib';
import ChartDetails from './components/charts/Details';
import { ChartsList } from './components/charts/List';
import ReleaseDetail from './components/releases/Detail';
import ReleaseList from './components/releases/List';
import { AppCatalogSettings } from './settings';

export function isElectron(): boolean {
// Renderer process
Expand Down Expand Up @@ -92,3 +93,5 @@ if (isElectron()) {
component: () => <ChartDetails />,
});
}

registerPluginSettings('app-catalog', AppCatalogSettings, true);
Loading

0 comments on commit f2f4f25

Please sign in to comment.