Skip to content

Commit 99759b9

Browse files
authored
Merge pull request #347 from traceroot-ai/xinwei_support_multi_trace_ui_select_v3
[UI] Support multiple trace selection through boxes [1/n]
2 parents f643703 + c587ca9 commit 99759b9

File tree

10 files changed

+322
-298
lines changed

10 files changed

+322
-298
lines changed

ui/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
"@lobehub/icons": "^2.26.0",
1616
"@lobehub/ui": "^2.8.3",
1717
"@radix-ui/react-avatar": "^1.1.10",
18+
"@radix-ui/react-checkbox": "^1.3.3",
1819
"@radix-ui/react-collapsible": "^1.1.12",
1920
"@radix-ui/react-dialog": "^1.1.14",
2021
"@radix-ui/react-dropdown-menu": "^2.1.15",

ui/src/app/explore/page.tsx

Lines changed: 133 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,19 @@
22

33
import { useState, useCallback, useEffect } from "react";
44
import Trace from "@/components/explore/Trace";
5+
import ExploreHeader from "@/components/explore/ExploreHeader";
6+
import { TimeRange, TIME_RANGES } from "@/components/explore/TimeButton";
7+
import {
8+
CustomTimeRange,
9+
TimezoneMode,
10+
} from "@/components/explore/CustomTimeRangeDialog";
11+
import { SearchCriterion } from "@/components/explore/SearchBar";
512
import ResizablePanel from "@/components/resizable/ResizablePanel";
613
import RightPanelSwitch from "@/components/right-panel/RightPanelSwitch";
14+
import { ViewType } from "@/components/right-panel/ModeToggle";
715
import AgentPanel from "@/components/agent-panel/AgentPanel";
816
import { Span, Trace as TraceType } from "@/models/trace";
9-
import {
10-
initializeProviders,
11-
loadProviderSelection,
12-
getProviderRegion,
13-
} from "@/utils/provider";
17+
import { initializeProviders } from "@/utils/provider";
1418

1519
export default function Explore() {
1620
const [selectedTraceIds, setSelectedTraceIds] = useState<string[]>([]);
@@ -25,11 +29,28 @@ export default function Explore() {
2529
{ category: string; value: string }[]
2630
>([]);
2731

32+
// Header state - lifted from Trace.tsx and RightPanelSwitch.tsx
33+
const [viewType, setViewType] = useState<ViewType>("log");
34+
const [selectedTimeRange, setSelectedTimeRange] = useState<TimeRange>(
35+
TIME_RANGES[0],
36+
);
37+
const [timezone, setTimezone] = useState<TimezoneMode>("utc");
38+
const [searchCriteria, setSearchCriteria] = useState<SearchCriterion[]>([]);
39+
const [loading, setLoading] = useState(true);
40+
const [hasTraceIdInUrl, setHasTraceIdInUrl] = useState(false);
41+
2842
// Initialize providers
2943
useEffect(() => {
3044
initializeProviders();
3145
}, []);
3246

47+
// Check if trace_id is in URL on mount
48+
useEffect(() => {
49+
const urlParams = new URLSearchParams(window.location.search);
50+
const traceIdParam = urlParams.get("trace_id");
51+
setHasTraceIdInUrl(!!traceIdParam);
52+
}, []);
53+
3354
// Helper function to get all span IDs from a trace recursively
3455
const getAllSpanIds = (spans: Span[]): string[] => {
3556
const spanIds: string[] = [];
@@ -106,6 +127,58 @@ export default function Explore() {
106127
[],
107128
);
108129

130+
// Header handlers
131+
const handleTimeRangeSelect = useCallback((range: TimeRange) => {
132+
setSelectedTimeRange(range);
133+
setSelectedTraceIds([]);
134+
setSelectedSpanIds([]);
135+
setLoading(true);
136+
}, []);
137+
138+
const handleCustomTimeRangeSelect = useCallback(
139+
(customRange: CustomTimeRange, selectedTimezone: TimezoneMode) => {
140+
setTimezone(selectedTimezone);
141+
142+
const customTimeRange: TimeRange = {
143+
label: customRange.label,
144+
isCustom: true,
145+
customRange: customRange,
146+
};
147+
148+
if (customRange.type === "relative") {
149+
customTimeRange.minutes = customRange.minutes;
150+
}
151+
152+
setSelectedTimeRange(customTimeRange);
153+
setSelectedTraceIds([]);
154+
setSelectedSpanIds([]);
155+
setLoading(true);
156+
},
157+
[],
158+
);
159+
160+
const handleSearch = useCallback((criteria: SearchCriterion[]) => {
161+
setSearchCriteria(criteria);
162+
setLoading(true);
163+
}, []);
164+
165+
const handleClearSearch = useCallback(() => {
166+
setSearchCriteria([]);
167+
setLogSearchValue("");
168+
handleLogSearchValueChange("");
169+
setLoading(true);
170+
}, [handleLogSearchValueChange]);
171+
172+
const handleRefresh = useCallback(() => {
173+
setSelectedTraceIds([]);
174+
setSelectedSpanIds([]);
175+
setLoading(true);
176+
}, []);
177+
178+
const handleLoadingChange = useCallback((isLoading: boolean) => {
179+
setLoading(isLoading);
180+
}, []);
181+
109182
// TODO (xinwei): Add ProtectedRoute
110183
return (
111184
<AgentPanel
@@ -116,38 +189,62 @@ export default function Explore() {
116189
queryEndTime={timeRange?.end}
117190
onSpanSelect={(spanId) => handleSpanSelect([spanId])}
118191
>
119-
<ResizablePanel
120-
leftPanel={
121-
<Trace
122-
onTraceSelect={handleTraceSelect}
123-
onSpanSelect={handleSpanSelect}
124-
onTraceData={handleTraceData}
125-
onTracesUpdate={handleTracesUpdate}
126-
onLogSearchValueChange={handleLogSearchValueChange}
127-
onMetadataSearchTermsChange={handleMetadataSearchTermsChange}
128-
selectedTraceIds={selectedTraceIds}
129-
selectedSpanIds={selectedSpanIds}
192+
<div className="h-screen flex flex-col">
193+
<ExploreHeader
194+
onSearch={handleSearch}
195+
onClearSearch={handleClearSearch}
196+
onLogSearchValueChange={handleLogSearchValueChange}
197+
onMetadataSearchTermsChange={handleMetadataSearchTermsChange}
198+
searchDisabled={loading || hasTraceIdInUrl}
199+
selectedTimeRange={selectedTimeRange}
200+
onTimeRangeSelect={handleTimeRangeSelect}
201+
onCustomTimeRangeSelect={handleCustomTimeRangeSelect}
202+
currentTimezone={timezone}
203+
timeDisabled={loading || hasTraceIdInUrl}
204+
onRefresh={handleRefresh}
205+
refreshDisabled={loading || hasTraceIdInUrl}
206+
viewType={viewType}
207+
onViewTypeChange={setViewType}
208+
/>
209+
<div className="flex-1 overflow-hidden">
210+
<ResizablePanel
211+
leftPanel={
212+
<Trace
213+
onTraceSelect={handleTraceSelect}
214+
onSpanSelect={handleSpanSelect}
215+
onTraceData={handleTraceData}
216+
onTracesUpdate={handleTracesUpdate}
217+
selectedTraceIds={selectedTraceIds}
218+
selectedSpanIds={selectedSpanIds}
219+
selectedTimeRange={selectedTimeRange}
220+
timezone={timezone}
221+
searchCriteria={searchCriteria}
222+
loading={loading}
223+
onLoadingChange={handleLoadingChange}
224+
/>
225+
}
226+
rightPanel={
227+
<RightPanelSwitch
228+
traceIds={selectedTraceIds}
229+
spanIds={selectedSpanIds}
230+
traceQueryStartTime={timeRange?.start}
231+
traceQueryEndTime={timeRange?.end}
232+
allTraces={allTraces}
233+
logSearchValue={logSearchValue}
234+
metadataSearchTerms={metadataSearchTerms}
235+
onTraceSelect={handleTraceSelect}
236+
onSpanClear={handleSpanClear}
237+
onTraceSpansUpdate={handleTraceSpansUpdate}
238+
onSpanSelect={handleSpanSelect}
239+
viewType={viewType}
240+
/>
241+
}
242+
minLeftWidth={35}
243+
maxLeftWidth={60}
244+
defaultLeftWidth={46}
130245
/>
131-
}
132-
rightPanel={
133-
<RightPanelSwitch
134-
traceIds={selectedTraceIds}
135-
spanIds={selectedSpanIds}
136-
traceQueryStartTime={timeRange?.start}
137-
traceQueryEndTime={timeRange?.end}
138-
allTraces={allTraces}
139-
logSearchValue={logSearchValue}
140-
metadataSearchTerms={metadataSearchTerms}
141-
onTraceSelect={handleTraceSelect}
142-
onSpanClear={handleSpanClear}
143-
onTraceSpansUpdate={handleTraceSpansUpdate}
144-
onSpanSelect={handleSpanSelect}
145-
/>
146-
}
147-
minLeftWidth={35}
148-
maxLeftWidth={60}
149-
defaultLeftWidth={46}
150-
/>
246+
</div>
247+
</div>
151248
</AgentPanel>
152249
);
153250
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
"use client";
2+
3+
import React from "react";
4+
import SearchBar, { SearchCriterion } from "./SearchBar";
5+
import RefreshButton from "./RefreshButton";
6+
import TimeButton, { TimeRange } from "./TimeButton";
7+
import { CustomTimeRange, TimezoneMode } from "./CustomTimeRangeDialog";
8+
import ModeToggle, { ViewType } from "../right-panel/ModeToggle";
9+
10+
interface ExploreHeaderProps {
11+
// Search props
12+
onSearch: (criteria: SearchCriterion[]) => void;
13+
onClearSearch: () => void;
14+
onLogSearchValueChange?: (value: string) => void;
15+
onMetadataSearchTermsChange?: (
16+
terms: { category: string; value: string }[],
17+
) => void;
18+
searchDisabled?: boolean;
19+
20+
// Time range props
21+
selectedTimeRange: TimeRange;
22+
onTimeRangeSelect: (range: TimeRange) => void;
23+
onCustomTimeRangeSelect: (
24+
customRange: CustomTimeRange,
25+
timezone: TimezoneMode,
26+
) => void;
27+
currentTimezone: TimezoneMode;
28+
timeDisabled?: boolean;
29+
30+
// Refresh props
31+
onRefresh: () => void;
32+
refreshDisabled?: boolean;
33+
34+
// Mode toggle props
35+
viewType: ViewType;
36+
onViewTypeChange: (type: ViewType) => void;
37+
}
38+
39+
export default function ExploreHeader({
40+
onSearch,
41+
onClearSearch,
42+
onLogSearchValueChange,
43+
onMetadataSearchTermsChange,
44+
searchDisabled = false,
45+
selectedTimeRange,
46+
onTimeRangeSelect,
47+
onCustomTimeRangeSelect,
48+
currentTimezone,
49+
timeDisabled = false,
50+
onRefresh,
51+
refreshDisabled = false,
52+
viewType,
53+
onViewTypeChange,
54+
}: ExploreHeaderProps) {
55+
return (
56+
<div className="sticky top-0 z-10 bg-white dark:bg-zinc-950 pt-1 pl-6 pr-2 pb-1 border-b border-zinc-200 dark:border-zinc-700">
57+
<div className="flex flex-row justify-between items-center gap-2">
58+
<div className="flex-1 min-w-0">
59+
<SearchBar
60+
onSearch={onSearch}
61+
onClear={onClearSearch}
62+
onLogSearchValueChange={onLogSearchValueChange}
63+
onMetadataSearchTermsChange={onMetadataSearchTermsChange}
64+
disabled={searchDisabled}
65+
/>
66+
</div>
67+
<div className="flex items-center space-x-2 flex-shrink-0 justify-end">
68+
<RefreshButton onRefresh={onRefresh} disabled={refreshDisabled} />
69+
<TimeButton
70+
selectedTimeRange={selectedTimeRange}
71+
onTimeRangeSelect={onTimeRangeSelect}
72+
onCustomTimeRangeSelect={onCustomTimeRangeSelect}
73+
currentTimezone={currentTimezone}
74+
disabled={timeDisabled}
75+
/>
76+
<ModeToggle viewType={viewType} onViewTypeChange={onViewTypeChange} />
77+
</div>
78+
</div>
79+
</div>
80+
);
81+
}

0 commit comments

Comments
 (0)