diff --git a/frontend/src/components/OrderBy/ListViewOrderBy.styles.scss b/frontend/src/components/OrderBy/ListViewOrderBy.styles.scss
new file mode 100644
index 000000000000..9210406a5539
--- /dev/null
+++ b/frontend/src/components/OrderBy/ListViewOrderBy.styles.scss
@@ -0,0 +1,7 @@
+.order-by-loading-container {
+ padding: 4px;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+}
diff --git a/frontend/src/components/OrderBy/ListViewOrderBy.tsx b/frontend/src/components/OrderBy/ListViewOrderBy.tsx
index cf95a209258e..b390aa11077f 100644
--- a/frontend/src/components/OrderBy/ListViewOrderBy.tsx
+++ b/frontend/src/components/OrderBy/ListViewOrderBy.tsx
@@ -1,6 +1,8 @@
-import { Select } from 'antd';
+import './ListViewOrderBy.styles.scss';
+
+import { Select, Spin } from 'antd';
import { getKeySuggestions } from 'api/querySuggestions/getKeySuggestions';
-import { useMemo, useState } from 'react';
+import { useEffect, useRef, useState } from 'react';
import { useQuery } from 'react-query';
import { QueryKeyDataSuggestionsProps } from 'types/api/querySuggestions/types';
import { DataSource } from 'types/common/queryBuilder';
@@ -11,56 +13,101 @@ interface ListViewOrderByProps {
dataSource: DataSource;
}
+// Loader component for the dropdown when loading or no results
+function Loader({ isLoading }: { isLoading: boolean }): JSX.Element {
+ return (
+
+ {isLoading ? : 'No results found'}
+
+ );
+}
+
function ListViewOrderBy({
value,
onChange,
dataSource,
}: ListViewOrderByProps): JSX.Element {
- const [searchText, setSearchText] = useState('');
+ const [searchInput, setSearchInput] = useState('');
+ const [debouncedInput, setDebouncedInput] = useState('');
+ const [selectOptions, setSelectOptions] = useState<
+ { label: string; value: string }[]
+ >([]);
+ const debounceTimer = useRef | null>(null);
- const { data } = useQuery(
- ['orderByKeySuggestions', dataSource, searchText],
- async () => {
+ // Fetch key suggestions based on debounced input
+ const { data, isLoading } = useQuery({
+ queryKey: ['orderByKeySuggestions', dataSource, debouncedInput],
+ queryFn: async () => {
const response = await getKeySuggestions({
signal: dataSource,
- searchText,
+ searchText: debouncedInput,
});
return response.data;
},
+ });
+
+ useEffect(
+ () => (): void => {
+ if (debounceTimer.current) {
+ clearTimeout(debounceTimer.current);
+ }
+ },
+ [],
);
- const options = useMemo(() => {
- const keys: QueryKeyDataSuggestionsProps[] = data?.data.keys
+ // Update options when API data changes
+ useEffect(() => {
+ const rawKeys: QueryKeyDataSuggestionsProps[] = data?.data.keys
? Object.values(data.data.keys).flat()
: [];
- let displayKeys: string[];
+ const keyNames = rawKeys.map((key) => key.name);
+ const uniqueKeys = [
+ ...new Set(searchInput ? keyNames : ['timestamp', ...keyNames]),
+ ];
- if (searchText) {
- displayKeys = [...new Set(keys.map((k) => k.name))];
- } else {
- displayKeys = [
- 'timestamp',
- ...keys.map((k) => k.name).filter((k) => k !== 'timestamp'),
- ];
- }
-
- return displayKeys.flatMap((key) => [
+ const updatedOptions = uniqueKeys.flatMap((key) => [
{ label: `${key} (desc)`, value: `${key}:desc` },
{ label: `${key} (asc)`, value: `${key}:asc` },
]);
- }, [data, searchText]);
+
+ setSelectOptions(updatedOptions);
+ }, [data, searchInput]);
+
+ // Handle search input with debounce
+ const handleSearch = (input: string): void => {
+ setSearchInput(input);
+
+ // Filter current options for instant client-side match
+ const filteredOptions = selectOptions.filter((option) =>
+ option.value.toLowerCase().includes(input.trim().toLowerCase()),
+ );
+
+ // If no match found or input is empty, trigger debounced fetch
+ if (filteredOptions.length === 0 || input === '') {
+ if (debounceTimer.current) {
+ clearTimeout(debounceTimer.current);
+ }
+
+ debounceTimer.current = setTimeout(() => {
+ setDebouncedInput(input);
+ }, 100);
+ }
+ };
return (
}
placeholder="Select an attribute"
style={{ width: 200 }}
- options={options}
- filterOption={false}
+ options={selectOptions}
+ filterOption={(input, option): boolean =>
+ (option?.value ?? '').toLowerCase().includes(input.trim().toLowerCase())
+ }
/>
);
}