Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 21 additions & 15 deletions app-catalog/src/components/releases/List.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@ import {
DateLabel,
Link,
SectionBox,
SectionHeader,
SectionFilterHeader,
SimpleTable,
StatusLabel,
} from '@kinvolk/headlamp-plugin/lib/CommonComponents';
import { Box, Checkbox } from '@mui/material';
import { useSnackbar } from 'notistack';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { useSelector } from 'react-redux';
import { fetchLatestAppVersion } from '../../api/charts';
import {
deleteRelease,
Expand Down Expand Up @@ -100,6 +101,20 @@ function parseReleaseKey(key: string): { namespace: string; name: string } | nul
return { namespace: parts[0], name: parts[1] };
}

/**
* The globally selected namespaces (empty means all), read from Headlamp's shared
* filter store the way its list views do. Uses react-redux directly because the
* internal filter module is not exposed to plugins at runtime.
*
* @returns The selected namespace names, empty when no namespace filter is set.
*/
function useSelectedNamespaces(): string[] {
const namespaces = useSelector(
(state: { filter?: { namespaces?: Set<string> } }) => state.filter?.namespaces
);
return useMemo(() => (namespaces ? [...namespaces] : []), [namespaces]);
}

/**
* @returns formatted version string
* @param v - version string
Expand All @@ -126,7 +141,7 @@ export default function ReleaseList({ fetchReleases = listReleases }: ReleaseLis
const [releases, setReleases] = useState<Release[] | null>(null);
const [latestMap, setLatestMap] = useState<Record<string, string>>({});
const [nameFilter, setNameFilter] = useState('');
const [namespaceFilter, setNamespaceFilter] = useState('');
const selectedNamespaces = useSelectedNamespaces();
const [openDeleteAlert, setOpenDeleteAlert] = useState<boolean>(false);
const [rollbackPopup, setRollbackPopup] = useState<boolean>(false);
const [selectedRelease, setSelectedRelease] = useState<Release | null>(null);
Expand Down Expand Up @@ -191,18 +206,12 @@ export default function ReleaseList({ fetchReleases = listReleases }: ReleaseLis
return releases.filter(release => {
const matchesName =
!nameFilter || release.name.toLowerCase().includes(nameFilter.toLowerCase());
const matchesNamespace = !namespaceFilter || release.namespace === namespaceFilter;
const matchesNamespace =
selectedNamespaces.length === 0 || selectedNamespaces.includes(release.namespace);

return matchesName && matchesNamespace;
});
}, [releases, nameFilter, namespaceFilter]);

const availableNamespaces = useMemo(() => {
if (!releases) return [];

const namespaces = new Set(releases.map(r => r.namespace));
return Array.from(namespaces).sort();
}, [releases]);
}, [releases, nameFilter, selectedNamespaces]);

// Count how many of the currently visible (filtered) releases are selected
const selectedVisibleCount = useMemo(() => {
Expand Down Expand Up @@ -583,16 +592,13 @@ export default function ReleaseList({ fetchReleases = listReleases }: ReleaseLis
onConfirm={handleConfirmRollback}
onCancel={() => setRollbackPopup(false)}
/>
<SectionHeader
<SectionFilterHeader
title={t('Installed')}
actions={[
<ReleaseFilters
key="filters"
nameFilter={nameFilter}
namespaceFilter={namespaceFilter}
availableNamespaces={availableNamespaces}
onNameFilterChange={setNameFilter}
onNamespaceFilterChange={setNamespaceFilter}
/>,
]}
/>
Expand Down
50 changes: 14 additions & 36 deletions app-catalog/src/components/releases/ReleaseFilters.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,15 @@
import { Autocomplete, Box, TextField } from '@mui/material';
import { useTranslation } from '@kinvolk/headlamp-plugin/lib';
import { TextField } from '@mui/material';
import { useEffect, useRef, useState } from 'react';

/** Props for the filter bar rendered in the releases list header. */
interface ReleaseFiltersProps {
nameFilter: string;
namespaceFilter: string;
/** Namespace strings derived from the currently loaded releases, used to populate the dropdown. */
availableNamespaces: string[];
onNameFilterChange: (value: string) => void;
onNamespaceFilterChange: (value: string) => void;
}

export function ReleaseFilters({
nameFilter,
namespaceFilter,
availableNamespaces,
onNameFilterChange,
onNamespaceFilterChange,
}: ReleaseFiltersProps) {
export function ReleaseFilters({ nameFilter, onNameFilterChange }: ReleaseFiltersProps) {
const { t } = useTranslation();
const [inputValue, setInputValue] = useState(nameFilter);
const timeoutRef = useRef<NodeJS.Timeout>();

Expand All @@ -42,29 +34,15 @@ export function ReleaseFilters({
}, [inputValue, onNameFilterChange]);

return (
<Box display="flex" gap={2} alignItems="center">
<TextField
sx={{
width: { xs: '100%', sm: '200px', md: '250px' },
margin: { xs: '0.5rem 0', sm: '0 1rem' },
}}
id="outlined-basic"
label="Search"
value={inputValue}
onChange={event => {
setInputValue(event.target.value);
}}
/>
<Autocomplete
sx={{ width: { xs: '100%', sm: '200px', md: '250px' } }}
options={availableNamespaces}
getOptionLabel={option => option || 'All'}
value={namespaceFilter || null}
onChange={(event, newValue) => {
onNamespaceFilterChange(newValue || '');
}}
renderInput={params => <TextField {...params} label="Namespace" size="small" />}
/>
</Box>
<TextField
sx={{ width: { xs: '100%', sm: '200px', md: '250px' } }}
id="outlined-basic"
label={t('Search')}
size="small"
value={inputValue}
onChange={event => {
setInputValue(event.target.value);
}}
/>
);
}
3 changes: 3 additions & 0 deletions app-catalog/src/components/releases/ReleaseList.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ const mockReleases = [
];

const initialState = {
filter: {
namespaces: new Set<string>(),
},
config: {
settings: {
tableRowsPerPageOptions: [15, 25, 50],
Expand Down
Loading