Skip to content

Commit 7582f5f

Browse files
authored
[#700] Add Page Limit Input and Improve Case-Insensitive DAG Search (#702)
1 parent 6801654 commit 7582f5f

File tree

8 files changed

+200
-72
lines changed

8 files changed

+200
-72
lines changed

.gitignore

+4-1
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,7 @@ tmp/*
2323
.local/
2424

2525
# Coverage files
26-
coverage.*
26+
coverage.*
27+
28+
# Debug files
29+
__debug_bin*

internal/persistence/local/dag_store.go

+2-3
Original file line numberDiff line numberDiff line change
@@ -166,10 +166,9 @@ func (d *dagStoreImpl) searchName(fileName string, searchText *string) bool {
166166
if searchText == nil {
167167
return true
168168
}
169-
170169
fileName = strings.TrimSuffix(fileName, path.Ext(fileName))
171-
172-
return strings.Contains(fileName, *searchText)
170+
ret := strings.Contains(strings.ToLower(fileName), strings.ToLower(*searchText))
171+
return ret
173172
}
174173

175174
func (d *dagStoreImpl) searchTags(tags []string, searchTag *string) bool {

ui/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@
7575
"monaco-react": "^1.1.0",
7676
"monaco-yaml": "^4.0.4",
7777
"prism": "^4.1.2",
78-
"react": "^18.1.0",
78+
"react": "^18.3.0",
7979
"react-cookie": "^4.1.1",
8080
"react-dom": "^18.1.0",
8181
"react-monaco-editor": "^0.54.0",

ui/src/App.tsx

+15-12
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { AppBarContext } from './contexts/AppBarContext';
88
import { SWRConfig } from 'swr';
99
import fetchJson from './lib/fetchJson';
1010
import Search from './pages/search';
11+
import { UserPreferencesProvider } from './contexts/UserPreference';
1112

1213
export type Config = {
1314
apiURL: string;
@@ -38,18 +39,20 @@ function App({ config }: Props) {
3839
setTitle,
3940
}}
4041
>
41-
<BrowserRouter>
42-
<Layout {...config}>
43-
<Routes>
44-
<Route path="/" element={<Dashboard />} />
45-
<Route path="/dashboard" element={<Dashboard />} />
46-
<Route path="/dags/" element={<DAGs />} />
47-
<Route path="/dags/:name/:tab" element={<DAGDetails />} />
48-
<Route path="/dags/:name/" element={<DAGDetails />} />
49-
<Route path="/search/" element={<Search />} />
50-
</Routes>
51-
</Layout>
52-
</BrowserRouter>
42+
<UserPreferencesProvider>
43+
<BrowserRouter>
44+
<Layout {...config}>
45+
<Routes>
46+
<Route path="/" element={<Dashboard />} />
47+
<Route path="/dashboard" element={<Dashboard />} />
48+
<Route path="/dags/" element={<DAGs />} />
49+
<Route path="/dags/:name/:tab" element={<DAGDetails />} />
50+
<Route path="/dags/:name/" element={<DAGDetails />} />
51+
<Route path="/search/" element={<Search />} />
52+
</Routes>
53+
</Layout>
54+
</BrowserRouter>
55+
</UserPreferencesProvider>
5356
</AppBarContext.Provider>
5457
</SWRConfig>
5558
);
+79-30
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,83 @@
1-
import { Box, Pagination } from "@mui/material";
2-
import React from "react";
1+
import { Box, Pagination, TextField } from '@mui/material';
2+
import React from 'react';
33

44
type DAGPaginationProps = {
5-
totalPages: number;
6-
page: number;
7-
pageChange: (page: number) => void;
5+
totalPages: number;
6+
page: number;
7+
pageLimit: number;
8+
pageChange: (page: number) => void;
9+
onPageLimitChange: (pageLimit: number) => void;
810
};
911

10-
const DAGPagination = ({ totalPages, page, pageChange }: DAGPaginationProps) => {
11-
const handleChange = (event: React.ChangeEvent<unknown>, value: number) => {
12-
pageChange(value);
13-
};
14-
15-
return (
16-
<Box
17-
sx={{
18-
display: 'flex',
19-
flexDirection: 'row',
20-
alignItems: 'center',
21-
justifyContent: 'center',
22-
mt: 2,
23-
}}
24-
>
25-
<Pagination
26-
count={totalPages}
27-
page={page}
28-
onChange={handleChange}
29-
/>
30-
</Box>
31-
);
32-
}
33-
34-
export default DAGPagination;
12+
const DAGPagination = ({
13+
totalPages,
14+
page,
15+
pageChange,
16+
pageLimit,
17+
onPageLimitChange,
18+
}: DAGPaginationProps) => {
19+
const [inputValue, setInputValue] = React.useState(pageLimit.toString());
20+
21+
React.useEffect(() => {
22+
setInputValue(pageLimit.toString());
23+
}, [pageLimit]);
24+
25+
const handleChange = (event: React.ChangeEvent<unknown>, value: number) => {
26+
pageChange(value);
27+
};
28+
29+
const handleLimitChange = (event: React.ChangeEvent<HTMLInputElement>) => {
30+
const value = event.target.value;
31+
setInputValue(value);
32+
};
33+
34+
const commitChange = () => {
35+
const numValue = parseInt(inputValue);
36+
if (!isNaN(numValue) && numValue > 0) {
37+
onPageLimitChange(numValue);
38+
} else {
39+
setInputValue(pageLimit.toString());
40+
}
41+
};
42+
43+
const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
44+
if (event.key === 'Enter') {
45+
commitChange();
46+
event.preventDefault();
47+
(event.target as HTMLInputElement).blur(); // Remove focus after Enter
48+
}
49+
};
50+
51+
return (
52+
<Box
53+
sx={{
54+
display: 'flex',
55+
flexDirection: 'row',
56+
alignItems: 'center',
57+
justifyContent: 'center',
58+
gap: 3,
59+
mt: 2,
60+
}}
61+
>
62+
<TextField
63+
size="small"
64+
label="Items per page"
65+
value={inputValue}
66+
onChange={handleLimitChange}
67+
onBlur={commitChange}
68+
onKeyDown={handleKeyDown}
69+
inputProps={{
70+
type: 'number',
71+
min: '1',
72+
style: {
73+
width: '100px',
74+
textAlign: 'left',
75+
},
76+
}}
77+
/>
78+
<Pagination count={totalPages} page={page} onChange={handleChange} />
79+
</Box>
80+
);
81+
};
82+
83+
export default DAGPagination;

ui/src/contexts/UserPreference.tsx

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import React, { createContext, useCallback, useContext, useState } from 'react';
2+
3+
export type UserPreferences = {
4+
pageLimit: number;
5+
}
6+
7+
const UserPreferencesContext = createContext<{
8+
preferences: UserPreferences;
9+
updatePreference: <K extends keyof UserPreferences>(
10+
key: K,
11+
value: UserPreferences[K]
12+
) => void;
13+
}>(null!);
14+
15+
16+
export function UserPreferencesProvider({ children }: { children: React.ReactNode }) {
17+
const [preferences, setPreferences] = useState<UserPreferences>(() => {
18+
try {
19+
const saved = localStorage.getItem('user_preferences');
20+
return saved ? JSON.parse(saved) : { pageLimit: 50, theme: 'light' };
21+
} catch {
22+
return { pageLimit: 50, theme: 'light' };
23+
}
24+
});
25+
26+
const updatePreference = useCallback(<K extends keyof UserPreferences>(
27+
key: K,
28+
value: UserPreferences[K]
29+
) => {
30+
setPreferences(prev => {
31+
const next = { ...prev, [key]: value };
32+
localStorage.setItem('user_preferences', JSON.stringify(next));
33+
return next;
34+
});
35+
}, []);
36+
37+
return (
38+
<UserPreferencesContext.Provider value={{ preferences, updatePreference }}>
39+
{children}
40+
</UserPreferencesContext.Provider>
41+
);
42+
43+
}
44+
45+
export function useUserPreferences() {
46+
return useContext(UserPreferencesContext);
47+
}

ui/src/pages/dags/index.tsx

+48-21
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { AppBarContext } from '../../contexts/AppBarContext';
1212
import useSWR, { useSWRConfig } from 'swr';
1313
import DAGPagination from '../../components/molecules/DAGPagination';
1414
import { debounce } from 'lodash';
15+
import { useUserPreferences } from '../../contexts/UserPreference';
1516

1617
function DAGs() {
1718
const useQuery = () => new URLSearchParams(useLocation().search);
@@ -21,18 +22,26 @@ function DAGs() {
2122
const [searchText, setSearchText] = React.useState(query.get('search') || '');
2223
const [searchTag, setSearchTag] = React.useState(query.get('tag') || '');
2324
const [page, setPage] = React.useState(parseInt(query.get('page') || '1'));
24-
const [apiSearchText, setAPISearchText] = React.useState(query.get('search') || '');
25-
const [apiSearchTag, setAPISearchTag] = React.useState(query.get('tag') || '');
25+
const [apiSearchText, setAPISearchText] = React.useState(
26+
query.get('search') || ''
27+
);
28+
const [apiSearchTag, setAPISearchTag] = React.useState(
29+
query.get('tag') || ''
30+
);
31+
32+
const { preferences, updatePreference } = useUserPreferences();
33+
// Use preferences.pageLimit instead of local state
34+
const handlePageLimitChange = (newLimit: number) => {
35+
updatePreference('pageLimit', newLimit);
36+
};
2637

2738
const { cache, mutate } = useSWRConfig();
28-
const endPoint =`/dags?${new URLSearchParams(
29-
{
30-
page: page.toString(),
31-
limit: '50',
32-
searchName: apiSearchText,
33-
searchTag: apiSearchTag,
34-
}
35-
).toString()}`
39+
const endPoint = `/dags?${new URLSearchParams({
40+
page: page.toString(),
41+
limit: preferences.pageLimit.toString(),
42+
searchName: apiSearchText,
43+
searchTag: apiSearchTag,
44+
}).toString()}`;
3645
const { data } = useSWR<ListWorkflowsResponse>(endPoint, null, {
3746
refreshInterval: 10000,
3847
revalidateIfStale: false,
@@ -41,8 +50,12 @@ function DAGs() {
4150
const addSearchParam = (key: string, value: string) => {
4251
const locationQuery = new URLSearchParams(window.location.search);
4352
locationQuery.set(key, value);
44-
window.history.pushState({}, '', `${window.location.pathname}?${locationQuery.toString()}`);
45-
}
53+
window.history.pushState(
54+
{},
55+
'',
56+
`${window.location.pathname}?${locationQuery.toString()}`
57+
);
58+
};
4659

4760
const refreshFn = React.useCallback(() => {
4861
setTimeout(() => mutate(endPoint), 500);
@@ -73,27 +86,35 @@ function DAGs() {
7386
setPage(page);
7487
};
7588

76-
const debouncedAPISearchText = React.useMemo(() => debounce((searchText: string) => {
77-
setAPISearchText(searchText);
78-
}, 500), []);
89+
const debouncedAPISearchText = React.useMemo(
90+
() =>
91+
debounce((searchText: string) => {
92+
setAPISearchText(searchText);
93+
}, 500),
94+
[]
95+
);
7996

80-
const debouncedAPISearchTag = React.useMemo(() => debounce((searchTag: string) => {
81-
setAPISearchTag(searchTag);
82-
}, 500), []);
97+
const debouncedAPISearchTag = React.useMemo(
98+
() =>
99+
debounce((searchTag: string) => {
100+
setAPISearchTag(searchTag);
101+
}, 500),
102+
[]
103+
);
83104

84105
const searchTextChange = (searchText: string) => {
85106
addSearchParam('search', searchText);
86107
setSearchText(searchText);
87108
setPage(1);
88109
debouncedAPISearchText(searchText);
89-
}
110+
};
90111

91112
const searchTagChange = (searchTag: string) => {
92113
addSearchParam('tag', searchTag);
93114
setSearchTag(searchTag);
94115
setPage(1);
95116
debouncedAPISearchTag(searchTag);
96-
}
117+
};
97118

98119
return (
99120
<Box
@@ -134,7 +155,13 @@ function DAGs() {
134155
searchTag={searchTag}
135156
handleSearchTagChange={searchTagChange}
136157
></DAGTable>
137-
<DAGPagination totalPages={data.PageCount} page={page} pageChange={pageChange} />
158+
<DAGPagination
159+
totalPages={data.PageCount}
160+
page={page}
161+
pageChange={pageChange}
162+
onPageLimitChange={handlePageLimitChange}
163+
pageLimit={preferences.pageLimit}
164+
/>
138165
</React.Fragment>
139166
)}
140167
</WithLoading>

ui/yarn.lock

+4-4
Original file line numberDiff line numberDiff line change
@@ -4103,10 +4103,10 @@ react@^16.3.2:
41034103
object-assign "^4.1.1"
41044104
prop-types "^15.6.2"
41054105

4106-
react@^18.1.0:
4107-
version "18.2.0"
4108-
resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5"
4109-
integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==
4106+
react@^18.3.0:
4107+
version "18.3.1"
4108+
resolved "https://registry.yarnpkg.com/react/-/react-18.3.1.tgz#49ab892009c53933625bd16b2533fc754cab2891"
4109+
integrity sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==
41104110
dependencies:
41114111
loose-envify "^1.1.0"
41124112

0 commit comments

Comments
 (0)