Skip to content

Commit ba4c79c

Browse files
committed
Add option to repeatedly send traces
1 parent c84ca10 commit ba4c79c

1 file changed

Lines changed: 134 additions & 18 deletions

File tree

src/frontend/components/TraceGenerationForm.tsx

Lines changed: 134 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { TraceGenerationRequest } from '../../backend/shared/types';
2-
import { Info, Plus, Send, Trash2 } from 'lucide-react';
3-
import React, { useState } from 'react';
2+
import { Info, Plus, Play, Send, Square, Trash2 } from 'lucide-react';
3+
import React, { useEffect, useRef, useState } from 'react';
44
import { apiClient } from '../api/client';
55
import { ResetButton } from './ResetButton';
66

@@ -38,6 +38,9 @@ export function TraceGenerationForm({ onError, onSuccess }: TraceGenerationFormP
3838
);
3939
const [nextAttrId, setNextAttrId] = useState(DEFAULT_CUSTOM_ATTRIBUTES.length + 1);
4040
const [loading, setLoading] = useState(false);
41+
const [isRepeating, setIsRepeating] = useState(false);
42+
const [intervalMs, setIntervalMs] = useState(1000);
43+
const intervalRef = useRef<NodeJS.Timeout | null>(null);
4144

4245
const updateValue = <K extends keyof typeof formData>(field: K, value: (typeof formData)[K]) => {
4346
setFormData((prev) => ({ ...prev, [field]: value }));
@@ -78,27 +81,66 @@ export function TraceGenerationForm({ onError, onSuccess }: TraceGenerationFormP
7881
}
7982
};
8083

81-
const handleSubmit = async (e: React.FormEvent) => {
82-
e.preventDefault();
83-
setLoading(true);
84+
const stopRepeating = () => {
85+
setIsRepeating(false);
86+
if (intervalRef.current) {
87+
clearInterval(intervalRef.current);
88+
intervalRef.current = null;
89+
}
90+
};
8491

85-
try {
86-
const customAttrs: Record<string, string> = {};
87-
customAttributes.forEach((attr) => {
88-
if (attr.key.trim() && attr.value.trim()) {
89-
customAttrs[attr.key.trim()] = attr.value.trim();
90-
}
91-
});
92+
const sendTrace = async () => {
93+
const customAttrs: Record<string, string> = {};
94+
customAttributes.forEach((attr) => {
95+
if (attr.key.trim() && attr.value.trim()) {
96+
customAttrs[attr.key.trim()] = attr.value.trim();
97+
}
98+
});
9299

93-
const request: TraceGenerationRequest = {
94-
...formData,
95-
customAttributes: customAttrs,
96-
};
100+
const request: TraceGenerationRequest = {
101+
...formData,
102+
customAttributes: customAttrs,
103+
};
97104

105+
try {
98106
await apiClient.generateTrace(request);
99107
onSuccess?.('Trace generated and sent successfully!');
100108
} catch (err: any) {
101109
onError(err.message || 'Failed to generate trace');
110+
// Stop repeating on error
111+
stopRepeating();
112+
}
113+
};
114+
115+
const startRepeating = () => {
116+
if (intervalMs < 100) {
117+
onError('Interval must be at least 100ms');
118+
return;
119+
}
120+
setIsRepeating(true);
121+
// Send immediately
122+
sendTrace();
123+
// Then set up interval
124+
intervalRef.current = setInterval(() => {
125+
sendTrace();
126+
}, intervalMs);
127+
};
128+
129+
// Cleanup interval on unmount
130+
useEffect(() => {
131+
return () => {
132+
if (intervalRef.current) {
133+
clearInterval(intervalRef.current);
134+
}
135+
};
136+
}, []);
137+
138+
const handleSubmit = async (e: React.FormEvent) => {
139+
e.preventDefault();
140+
setLoading(true);
141+
142+
try {
143+
await sendTrace();
102144
} finally {
103145
setLoading(false);
104146
}
@@ -362,10 +404,84 @@ export function TraceGenerationForm({ onError, onSuccess }: TraceGenerationFormP
362404
</div>
363405
</div>
364406

365-
<button type="submit" disabled={loading} className="material-button w-full md:w-auto flex items-center gap-2">
407+
<button
408+
type="submit"
409+
disabled={loading || isRepeating}
410+
className="material-button w-full md:w-auto flex items-center gap-2"
411+
>
366412
<Send className="w-5 h-5" />
367-
Generate and Send Trace
413+
{isRepeating ? 'Stop Repeating First' : 'Generate and Send Trace'}
368414
</button>
415+
416+
{/* Repeated Sending Settings */}
417+
<div>
418+
<h3 className="text-xl font-bold text-primary mb-4">Repeated Sending</h3>
419+
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
420+
<div className="space-y-2">
421+
<label className="flex items-center gap-2 text-sm font-medium text-primary">
422+
Interval (ms)
423+
<div className="group relative">
424+
<span className="info-icon">
425+
<Info className="w-4 h-4" />
426+
</span>
427+
<span className="tooltip w-64">Time interval between trace sends in milliseconds (minimum 100ms)</span>
428+
</div>
429+
</label>
430+
<div className="range-slider-container">
431+
<input
432+
type="range"
433+
min="100"
434+
max="60000"
435+
step="100"
436+
value={Math.min(Math.max(intervalMs, 100), 60000)}
437+
onChange={(e) => setIntervalMs(parseInt(e.target.value))}
438+
disabled={isRepeating}
439+
/>
440+
<input
441+
type="number"
442+
value={intervalMs}
443+
onChange={(e) => {
444+
const value = parseInt(e.target.value, 10);
445+
if (!isNaN(value) && value >= 100) {
446+
setIntervalMs(value);
447+
}
448+
}}
449+
className="material-input text-center"
450+
min="100"
451+
disabled={isRepeating}
452+
/>
453+
</div>
454+
</div>
455+
<div className="flex items-end gap-3">
456+
{!isRepeating ? (
457+
<button
458+
type="button"
459+
onClick={startRepeating}
460+
disabled={loading || intervalMs < 100}
461+
className="material-button flex items-center gap-2"
462+
>
463+
<Play className="w-5 h-5" />
464+
Start Repeating
465+
</button>
466+
) : (
467+
<button
468+
type="button"
469+
onClick={stopRepeating}
470+
className="material-button bg-danger hover:bg-danger-dark flex items-center gap-2"
471+
>
472+
<Square className="w-5 h-5" />
473+
Stop Repeating
474+
</button>
475+
)}
476+
{isRepeating && (
477+
<div className="flex items-center gap-2 text-sm text-primary">
478+
<div className="w-2 h-2 bg-danger rounded-full animate-pulse"></div>
479+
<span>Sending every {intervalMs}ms</span>
480+
</div>
481+
)}
482+
</div>
483+
</div>
484+
</div>
369485
</form>
370486
);
371487
}

0 commit comments

Comments
 (0)