Skip to content

Commit 385c3ae

Browse files
authored
Merge pull request #1593 from FalkorDB/add-max-items-for-search
Fix #1522 feat: add max items for search functionality in graph settings and UI
2 parents 6be57b3 + 702cd0b commit 385c3ae

File tree

4 files changed

+115
-44
lines changed

4 files changed

+115
-44
lines changed

app/components/provider.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ type BrowserSettingsContextType = {
5454
graphInfo: {
5555
newRefreshInterval: number;
5656
setNewRefreshInterval: Dispatch<SetStateAction<number>>;
57+
newMaxItemsForSearch: number;
58+
setNewMaxItemsForSearch: Dispatch<SetStateAction<number>>;
5759
};
5860
};
5961
settings: {
@@ -109,6 +111,8 @@ type BrowserSettingsContextType = {
109111
showMemoryUsage: boolean;
110112
refreshInterval: number;
111113
setRefreshInterval: Dispatch<SetStateAction<number>>;
114+
maxItemsForSearch: number;
115+
setMaxItemsForSearch: Dispatch<SetStateAction<number>>;
112116
};
113117
};
114118
hasChanges: boolean;
@@ -257,6 +261,8 @@ export const BrowserSettingsContext = createContext<BrowserSettingsContextType>(
257261
graphInfo: {
258262
newRefreshInterval: 0,
259263
setNewRefreshInterval: () => { },
264+
newMaxItemsForSearch: 0,
265+
setNewMaxItemsForSearch: () => { },
260266
},
261267
},
262268
settings: {
@@ -306,6 +312,8 @@ export const BrowserSettingsContext = createContext<BrowserSettingsContextType>(
306312
showMemoryUsage: false,
307313
refreshInterval: 0,
308314
setRefreshInterval: () => { },
315+
maxItemsForSearch: 0,
316+
setMaxItemsForSearch: () => { },
309317
},
310318
},
311319
hasChanges: false,

app/graph/graphInfo.tsx

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
import { Dispatch, SetStateAction, useContext } from "react";
2-
import { Loader2, X, Palette, Network } from "lucide-react";
1+
import { Dispatch, SetStateAction, useContext, useEffect, useState } from "react";
2+
import { Loader2, X, Palette, Network, Search } from "lucide-react";
33
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
44
import { cn, InfoLabel } from "@/lib/utils";
55
import { getContrastTextColor } from "@falkordb/canvas";
66
import Button from "../components/ui/Button";
77
import { BrowserSettingsContext, GraphContext, QueryLoadingContext } from "../components/provider";
88
import CustomizeStylePanel from "./CustomizeStylePanel";
9+
import Input from "../components/ui/Input";
910

1011
/**
1112
* Render a side panel showing graph metadata and interactive controls to run representative queries.
@@ -16,10 +17,18 @@ import CustomizeStylePanel from "./CustomizeStylePanel";
1617
export default function GraphInfoPanel({ onClose, customizingLabel, setCustomizingLabel }: { onClose: () => void, customizingLabel: InfoLabel | null, setCustomizingLabel: Dispatch<SetStateAction<InfoLabel | null>> }) {
1718
const { graphInfo: { Labels, Relationships, PropertyKeys, MemoryUsage }, nodesCount, edgesCount, runQuery, graphName } = useContext(GraphContext);
1819
const { isQueryLoading } = useContext(QueryLoadingContext);
19-
const { settings: { graphInfo: { showMemoryUsage } } } = useContext(BrowserSettingsContext);
20+
const { settings: { graphInfo: { showMemoryUsage, maxItemsForSearch } } } = useContext(BrowserSettingsContext);
21+
22+
const [nodesSearch, setNodesSearch] = useState("");
23+
const [edgesSearch, setEdgesSearch] = useState("");
24+
const [propertyKeysSearch, setPropertyKeysSearch] = useState("");
25+
26+
useEffect(() => { setNodesSearch(""); }, [Labels]);
27+
useEffect(() => { setEdgesSearch(""); }, [Relationships]);
28+
useEffect(() => { setPropertyKeysSearch(""); }, [PropertyKeys]);
2029

2130
return (
22-
<div aria-disabled={nodesCount === undefined || edgesCount === undefined} data-testid="graphInfoPanel" className={cn(`relative h-full w-full p-2 grid grid-rows-[max-content_max-content_minmax(0,max-content)_minmax(0,max-content)_minmax(0,max-content)] gap-2`)}>
31+
<div aria-disabled={nodesCount === undefined || edgesCount === undefined} data-testid="graphInfoPanel" className={cn(`relative h-full w-full p-2 grid grid-rows-[max-content_max-content_max-content_1fr_1fr_1fr] gap-2`)}>
2332
{
2433
!customizingLabel ? (
2534
<>
@@ -85,6 +94,13 @@ export default function GraphInfoPanel({ onClose, customizingLabel, setCustomizi
8594
</Tooltip>
8695
: <Loader2 data-testid="nodesCountLoader" className="animate-spin" />
8796
}
97+
{
98+
Labels.size > maxItemsForSearch &&
99+
<div className="basis-0 grow flex gap-1 items-center">
100+
<Search size={16} />
101+
<Input aria-label="Search node labels" value={nodesSearch} onChange={(e) => setNodesSearch(e.target.value)} className="w-1 grow" />
102+
</div>
103+
}
88104
</div>
89105
<ul className="flex flex-wrap gap-2 p-2 overflow-auto">
90106
<li className="max-w-full">
@@ -97,7 +113,7 @@ export default function GraphInfoPanel({ onClose, customizingLabel, setCustomizi
97113
disabled={isQueryLoading}
98114
/>
99115
</li>
100-
{Array.from(Labels.values()).sort((a, b) => b.count - a.count).map((label) => {
116+
{Array.from(Labels.values()).filter(label => label.name.toLowerCase().includes(nodesSearch.toLowerCase())).sort((a, b) => b.count - a.count).map((label) => {
101117
const name = label.name || "Empty";
102118
const labelColor = label.style.color;
103119

@@ -157,6 +173,13 @@ export default function GraphInfoPanel({ onClose, customizingLabel, setCustomizi
157173
:
158174
<Loader2 data-testid="edgesCountLoader" className="animate-spin" />
159175
}
176+
{
177+
Relationships.size > maxItemsForSearch &&
178+
<div className="basis-0 grow flex gap-1 items-center">
179+
<Search size={16} />
180+
<Input aria-label="Search relationship types" value={edgesSearch} onChange={(e) => setEdgesSearch(e.target.value)} className="w-1 grow" />
181+
</div>
182+
}
160183
</div>
161184
<ul className="flex flex-wrap gap-2 p-2 overflow-auto">
162185
<li className="max-w-full">
@@ -169,7 +192,7 @@ export default function GraphInfoPanel({ onClose, customizingLabel, setCustomizi
169192
disabled={isQueryLoading}
170193
/>
171194
</li>
172-
{Array.from(Relationships.values()).sort((a, b) => b.count - a.count).map((relationship) => {
195+
{Array.from(Relationships.values()).filter(relationship => relationship.name.toLowerCase().includes(edgesSearch.toLowerCase())).sort((a, b) => b.count - a.count).map((relationship) => {
173196
const relationshipColor = relationship.style.color;
174197
const textColor = getContrastTextColor(relationshipColor);
175198

@@ -215,10 +238,17 @@ export default function GraphInfoPanel({ onClose, customizingLabel, setCustomizi
215238
:
216239
<Loader2 className="animate-spin" />
217240
}
241+
{
242+
PropertyKeys && PropertyKeys.length > maxItemsForSearch &&
243+
<div className="basis-0 grow flex gap-1 items-center">
244+
<Search size={16} />
245+
<Input aria-label="Search property keys" value={propertyKeysSearch} onChange={(e) => setPropertyKeysSearch(e.target.value)} className="w-1 grow" />
246+
</div>
247+
}
218248
</div>
219249
<ul className="flex flex-wrap gap-2 p-2 overflow-auto">
220250
{
221-
PropertyKeys && PropertyKeys.map((key) => (
251+
PropertyKeys && PropertyKeys.filter(key => key.toLowerCase().includes(propertyKeysSearch.toLowerCase())).map((key) => (
222252
<li key={key} className="max-w-full">
223253
<Button
224254
title={`MATCH (e) WHERE e.${key} IS NOT NULL RETURN e\nUNION\nMATCH ()-[e]-() WHERE e.${key} IS NOT NULL RETURN e`}
@@ -230,7 +260,8 @@ export default function GraphInfoPanel({ onClose, customizingLabel, setCustomizi
230260
disabled={isQueryLoading}
231261
/>
232262
</li>
233-
))}
263+
))
264+
}
234265
</ul>
235266
</div>
236267
</>

app/providers.tsx

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -119,13 +119,15 @@ function ProvidersWithSession({ children }: { children: React.ReactNode }) {
119119
const [cypherOnly, setCypherOnly] = useState<boolean>(false);
120120
const [udfList, setUdfList] = useState<UDFEntry[]>([]);
121121
const [selectedUdf, setSelectedUdf] = useState<UDFEntryWithCode>();
122-
const [columnWidth, setColumnWidth] = useState<number>(25);
123-
const [rowHeight, setRowHeight] = useState<number>(40);
124-
const [newColumnWidth, setNewColumnWidth] = useState<number>(25);
125-
const [newRowHeight, setNewRowHeight] = useState<number>(40);
126-
const [newRowHeightExpandMultiple, setNewRowHeightExpandMultiple] = useState<number>(3);
127-
const [rowHeightExpandMultiple, setRowHeightExpandMultiple] = useState<number>(3);
122+
const [columnWidth, setColumnWidth] = useState<number>(0);
123+
const [rowHeight, setRowHeight] = useState<number>(0);
124+
const [newColumnWidth, setNewColumnWidth] = useState<number>(0);
125+
const [newRowHeight, setNewRowHeight] = useState<number>(0);
126+
const [newRowHeightExpandMultiple, setNewRowHeightExpandMultiple] = useState<number>(0);
127+
const [rowHeightExpandMultiple, setRowHeightExpandMultiple] = useState<number>(0);
128128
const [showUDF, setShowUDF] = useState<boolean>(true);
129+
const [maxItemsForSearch, setMaxItemsForSearch] = useState<number>(0);
130+
const [newMaxItemsForSearch, setNewMaxItemsForSearch] = useState<number>(0);
129131

130132
const replayTutorial = useCallback(() => {
131133
router.push("/graph");
@@ -149,7 +151,7 @@ function ProvidersWithSession({ children }: { children: React.ReactNode }) {
149151
captionsKeysSettings: { newCaptionsKeys, setNewCaptionsKeys },
150152
showPropertyKeyPrefixSettings: { newShowPropertyKeyPrefix, setNewShowPropertyKeyPrefix },
151153
chatSettings: { newSecretKey, setNewSecretKey, newModel, setNewModel, newMaxSavedMessages, setNewMaxSavedMessages, newCypherOnly, setNewCypherOnly },
152-
graphInfo: { newRefreshInterval, setNewRefreshInterval },
154+
graphInfo: { newRefreshInterval, setNewRefreshInterval, newMaxItemsForSearch, setNewMaxItemsForSearch },
153155
tableViewSettings: { newColumnWidth, setNewColumnWidth, newRowHeight, setNewRowHeight, newRowHeightExpandMultiple, setNewRowHeightExpandMultiple }
154156
},
155157
settings: {
@@ -161,7 +163,7 @@ function ProvidersWithSession({ children }: { children: React.ReactNode }) {
161163
captionsKeysSettings: { captionsKeys, setCaptionsKeys },
162164
showPropertyKeyPrefixSettings: { showPropertyKeyPrefix, setShowPropertyKeyPrefix },
163165
chatSettings: { secretKey, setSecretKey, model, setModel, maxSavedMessages, setMaxSavedMessages, cypherOnly, setCypherOnly },
164-
graphInfo: { showMemoryUsage, refreshInterval, setRefreshInterval },
166+
graphInfo: { showMemoryUsage, refreshInterval, setRefreshInterval, maxItemsForSearch, setMaxItemsForSearch },
165167
tableViewSettings: { columnWidth, setColumnWidth, rowHeight, setRowHeight, rowHeightExpandMultiple, setRowHeightExpandMultiple }
166168
},
167169
hasChanges,
@@ -184,6 +186,7 @@ function ProvidersWithSession({ children }: { children: React.ReactNode }) {
184186
localStorage.setItem("columnWidth", newColumnWidth.toString());
185187
localStorage.setItem("rowHeight", newRowHeight.toString());
186188
localStorage.setItem("rowHeightExpandMultiple", newRowHeightExpandMultiple.toString());
189+
localStorage.setItem("maxItemsForSearch", newMaxItemsForSearch.toString());
187190

188191
// Only encrypt and save secret key if it has changed
189192
if (newSecretKey !== secretKey) {
@@ -241,6 +244,7 @@ function ProvidersWithSession({ children }: { children: React.ReactNode }) {
241244
setColumnWidth(newColumnWidth);
242245
setRowHeight(newRowHeight);
243246
setRowHeightExpandMultiple(newRowHeightExpandMultiple);
247+
setMaxItemsForSearch(newMaxItemsForSearch);
244248
// Reset has changes
245249
setHasChanges(false);
246250

@@ -266,10 +270,11 @@ function ProvidersWithSession({ children }: { children: React.ReactNode }) {
266270
setNewColumnWidth(columnWidth);
267271
setNewRowHeight(rowHeight);
268272
setNewRowHeightExpandMultiple(rowHeightExpandMultiple);
273+
setNewMaxItemsForSearch(maxItemsForSearch);
269274
setHasChanges(false);
270275
}
271276
// eslint-disable-next-line react-hooks/exhaustive-deps
272-
}), [contentPersistence, defaultQuery, hasChanges, lastLimit, limit, model, newContentPersistence, newDefaultQuery, newLimit, newModel, newRefreshInterval, newRunDefaultQuery, newSecretKey, newTimeout, refreshInterval, runDefaultQuery, secretKey, timeout, replayTutorial, tutorialOpen, showMemoryUsage, newMaxSavedMessages, maxSavedMessages, newCaptionsKeys, captionsKeys, newShowPropertyKeyPrefix, showPropertyKeyPrefix, newCypherOnly, cypherOnly, newColumnWidth, columnWidth, newRowHeight, rowHeight, newRowHeightExpandMultiple, rowHeightExpandMultiple, toast]);
277+
}), [contentPersistence, defaultQuery, hasChanges, lastLimit, limit, model, newContentPersistence, newDefaultQuery, newLimit, newModel, newRefreshInterval, newRunDefaultQuery, newSecretKey, newTimeout, refreshInterval, runDefaultQuery, secretKey, timeout, replayTutorial, tutorialOpen, showMemoryUsage, newMaxSavedMessages, maxSavedMessages, newCaptionsKeys, captionsKeys, newShowPropertyKeyPrefix, showPropertyKeyPrefix, newCypherOnly, cypherOnly, newColumnWidth, columnWidth, newRowHeight, rowHeight, newRowHeightExpandMultiple, rowHeightExpandMultiple, newMaxItemsForSearch, maxItemsForSearch, toast]);
273278

274279
const historyQueryContext = useMemo(() => ({
275280
historyQuery,
@@ -629,7 +634,7 @@ function ProvidersWithSession({ children }: { children: React.ReactNode }) {
629634
setColumnWidth(parseInt(localStorage.getItem("columnWidth") || "25", 10));
630635
setRowHeight(parseInt(localStorage.getItem("rowHeight") || "40", 10));
631636
setRowHeightExpandMultiple(parseInt(localStorage.getItem("rowHeightExpandMultiple") || "3", 10));
632-
637+
setMaxItemsForSearch(parseInt(localStorage.getItem("maxItemsForSearch") || "20", 10));
633638
// Decrypt secret key if encrypted, or migrate plain text keys to encrypted format
634639
const storedSecretKey = localStorage.getItem("secretKey") || "";
635640
if (storedSecretKey) {

0 commit comments

Comments
 (0)