fix filters

This commit is contained in:
Ishaan Jaff 2025-03-08 13:38:32 -08:00
parent ca88a41155
commit ee8402fcce
3 changed files with 282 additions and 17 deletions

View file

@ -12,6 +12,9 @@ import { FilterOption } from "./common_components/filter";
import { Organization, userListCall } from "./networking";
import { createTeamSearchFunction } from "./key_team_helpers/team_search_fn";
import { createOrgSearchFunction } from "./key_team_helpers/organization_search_fn";
import { keyListCall } from "./networking";
import { fetchAllKeyAliases, fetchAllTeams, fetchAllOrganizations } from './key_team_helpers/filter_helpers';
interface AllKeysTableProps {
keys: KeyResponse[];
isLoading?: boolean;
@ -95,17 +98,66 @@ export function AllKeysTable({
userID,
userRole,
organizations,
setCurrentOrg
setCurrentOrg,
}: AllKeysTableProps) {
const [selectedKeyId, setSelectedKeyId] = useState<string | null>(null);
const [filters, setFilters] = useState<{
'Team ID': string;
'Organization ID': string;
'Key Alias': string;
}>({
'Team ID': '',
'Organization ID': ''
'Organization ID': '',
'Key Alias': ''
});
const [userList, setUserList] = useState<UserResponse[]>([]);
const [allKeyAliases, setAllKeyAliases] = useState<string[]>([]);
const [allTeams, setAllTeams] = useState<Team[]>(teams || []);
const [allOrganizations, setAllOrganizations] = useState<Organization[]>(organizations || []);
// Fetch all data for filters when component mounts
useEffect(() => {
const loadAllFilterData = async () => {
// Load all key aliases
const aliases = await fetchAllKeyAliases(accessToken);
setAllKeyAliases(aliases);
// Load all teams - no organization filter needed here
const teamsData = await fetchAllTeams(accessToken);
if (teamsData.length > 0) {
setAllTeams(teamsData);
}
// Load all organizations
const orgsData = await fetchAllOrganizations(accessToken);
if (orgsData.length > 0) {
setAllOrganizations(orgsData);
}
};
if (accessToken) {
loadAllFilterData();
}
}, [accessToken]);
// Update teams and organizations when props change
useEffect(() => {
if (teams && teams.length > 0) {
setAllTeams(prevTeams => {
// Only update if we don't already have a larger set of teams
return prevTeams.length < teams.length ? teams : prevTeams;
});
}
}, [teams]);
useEffect(() => {
if (organizations && organizations.length > 0) {
setAllOrganizations(prevOrgs => {
// Only update if we don't already have a larger set of organizations
return prevOrgs.length < organizations.length ? organizations : prevOrgs;
});
}
}, [organizations]);
useEffect(() => {
if (accessToken) {
@ -122,7 +174,8 @@ export function AllKeysTable({
// Update filters state
setFilters({
'Team ID': newFilters['Team ID'] || '',
'Organization ID': newFilters['Organization ID'] || ''
'Organization ID': newFilters['Organization ID'] || '',
'Key Alias': newFilters['Key Alias'] || ''
});
// Handle Team change
@ -146,12 +199,13 @@ export function AllKeysTable({
// Reset filters state
setFilters({
'Team ID': '',
'Organization ID': ''
'Organization ID': '',
'Key Alias': ''
});
// Reset team and org selections
setSelectedTeam(null); // or whatever your default value should be
setCurrentOrg(null); // or whatever your default value should be
setSelectedTeam(null);
setCurrentOrg(null);
};
@ -323,8 +377,59 @@ export function AllKeysTable({
];
const filterOptions: FilterOption[] = [
{ name: 'Team ID', label: 'Team ID', isSearchable: true, searchFn: createTeamSearchFunction(teams) },
{ name: 'Organization ID', label: 'Organization ID', isSearchable: true, searchFn: createOrgSearchFunction(organizations) }
{
name: 'Team ID',
label: 'Team ID',
isSearchable: true,
searchFn: async (searchText: string) => {
if (!allTeams || allTeams.length === 0) return [];
const filteredTeams = allTeams.filter(team =>
team.team_id.toLowerCase().includes(searchText.toLowerCase()) ||
(team.team_alias && team.team_alias.toLowerCase().includes(searchText.toLowerCase()))
);
return filteredTeams.map(team => ({
label: `${team.team_alias || team.team_id} (${team.team_id})`,
value: team.team_id
}));
}
},
{
name: 'Organization ID',
label: 'Organization ID',
isSearchable: true,
searchFn: async (searchText: string) => {
if (!allOrganizations || allOrganizations.length === 0) return [];
const filteredOrgs = allOrganizations.filter(org =>
org.organization_id.toLowerCase().includes(searchText.toLowerCase()) ||
(org.organization_name && org.organization_name.toLowerCase().includes(searchText.toLowerCase()))
);
return filteredOrgs.map(org => ({
label: `${org.organization_name || 'Unknown'} (${org.organization_id})`,
value: org.organization_id
}));
}
},
{
name: 'Key Alias',
label: 'Key Alias',
isSearchable: true,
searchFn: async (searchText: string) => {
if (!allKeyAliases || allKeyAliases.length === 0) return [];
const filteredAliases = allKeyAliases.filter(alias =>
alias && alias.toLowerCase().includes(searchText.toLowerCase())
);
return filteredAliases.map(alias => ({
label: alias,
value: alias
}));
}
}
];

View file

@ -62,9 +62,48 @@ const FilterComponent: React.FC<FilterComponentProps> = ({
return () => document.removeEventListener('mousedown', handleClickOutside);
}, []);
useEffect(() => {
if (options.length > 0 && options[0].isSearchable && options[0].searchFn) {
loadInitialOptions(options[0]);
}
}, []);
const loadInitialOptions = async (option: FilterOption) => {
if (!option.isSearchable || !option.searchFn) return;
setSearchLoading(true);
try {
const results = await option.searchFn('');
setSearchOptions(results);
} catch (error) {
console.error('Error loading initial options:', error);
setSearchOptions([]);
} finally {
setSearchLoading(false);
}
};
useEffect(() => {
if (showFilters && currentOption?.isSearchable && currentOption?.searchFn) {
loadInitialOptions(currentOption);
}
}, [showFilters, selectedFilter]);
const handleFilterSelect = (key: string) => {
setSelectedFilter(key);
setDropdownOpen(false);
const newOption = options.find(opt => opt.name === key);
if (newOption?.isSearchable && newOption?.searchFn) {
loadInitialOptions(newOption);
} else {
setSearchOptions([]);
}
};
const debouncedSearch = useCallback(
debounce(async (value: string, option: FilterOption) => {
if (!value || !option.isSearchable || !option.searchFn) return;
if (!option.isSearchable || !option.searchFn) return;
setSearchLoading(true);
try {
@ -124,7 +163,7 @@ const FilterComponent: React.FC<FilterComponentProps> = ({
setFilterValues(emptyValues);
setSearchInputValue('');
setSearchOptions([]);
onResetFilters(); // Call the parent's reset function
onResetFilters();
};
return (
@ -147,11 +186,7 @@ const FilterComponent: React.FC<FilterComponentProps> = ({
<Dropdown
menu={{
items: dropdownItems,
onClick: ({ key }) => {
setSelectedFilter(key);
setDropdownOpen(false);
setSearchOptions([]);
}
onClick: ({ key }) => handleFilterSelect(key)
}}
onOpenChange={setDropdownOpen}
open={dropdownOpen}
@ -179,7 +214,6 @@ const FilterComponent: React.FC<FilterComponentProps> = ({
}}
onInputKeyDown={(e) => {
if (e.key === 'Enter' && searchInputValue) {
// Allow manual entry of the value on Enter
handleFilterChange(searchInputValue);
e.preventDefault();
}
@ -198,7 +232,6 @@ const FilterComponent: React.FC<FilterComponentProps> = ({
className="p-0 mt-1"
onClick={() => {
handleFilterChange(searchInputValue);
// Close the dropdown/select after selecting the value
const selectElement = document.activeElement as HTMLElement;
if (selectElement) {
selectElement.blur();

View file

@ -0,0 +1,127 @@
import { keyListCall, teamListCall, organizationListCall } from '../networking';
import { Team } from './key_list';
import { Organization } from '../networking';
/**
* Fetches all key aliases across all pages
* @param accessToken The access token for API authentication
* @returns Array of all unique key aliases
*/
export const fetchAllKeyAliases = async (accessToken: string | null): Promise<string[]> => {
if (!accessToken) return [];
try {
// Fetch all pages of keys to extract aliases
let allAliases: string[] = [];
let currentPage = 1;
let hasMorePages = true;
while (hasMorePages) {
const response = await keyListCall(
accessToken,
null, // organization_id
"", // team_id
currentPage,
100 // larger page size to reduce number of requests
);
// Extract aliases from this page
const pageAliases = response.keys
.map(key => key.key_alias)
.filter(Boolean) as string[];
allAliases = [...allAliases, ...pageAliases];
// Check if there are more pages
if (currentPage < response.total_pages) {
currentPage++;
} else {
hasMorePages = false;
}
}
// Remove duplicates
return [...new Set(allAliases)];
} catch (error) {
console.error("Error fetching all key aliases:", error);
return [];
}
};
/**
* Fetches all teams across all pages
* @param accessToken The access token for API authentication
* @param organizationId Optional organization ID to filter teams
* @returns Array of all teams
*/
export const fetchAllTeams = async (accessToken: string | null, organizationId?: string | null): Promise<Team[]> => {
if (!accessToken) return [];
try {
let allTeams: Team[] = [];
let currentPage = 1;
let hasMorePages = true;
while (hasMorePages) {
const response = await teamListCall(
accessToken,
organizationId || null,
currentPage,
100 // larger page size to reduce number of requests
);
// Add teams from this page
allTeams = [...allTeams, ...response.teams];
// Check if there are more pages
if (currentPage < response.total_pages) {
currentPage++;
} else {
hasMorePages = false;
}
}
return allTeams;
} catch (error) {
console.error("Error fetching all teams:", error);
return [];
}
};
/**
* Fetches all organizations across all pages
* @param accessToken The access token for API authentication
* @returns Array of all organizations
*/
export const fetchAllOrganizations = async (accessToken: string | null): Promise<Organization[]> => {
if (!accessToken) return [];
try {
let allOrganizations: Organization[] = [];
let currentPage = 1;
let hasMorePages = true;
while (hasMorePages) {
const response = await organizationListCall(
accessToken,
currentPage,
100 // larger page size to reduce number of requests
);
// Add organizations from this page
allOrganizations = [...allOrganizations, ...response.organizations];
// Check if there are more pages
if (currentPage < response.total_pages) {
currentPage++;
} else {
hasMorePages = false;
}
}
return allOrganizations;
} catch (error) {
console.error("Error fetching all organizations:", error);
return [];
}
};