feat: implement proactive quality prompts with link diagnostics
- Show non-blocking prompt card when ambient confidence drops below 0.6 for >5 minutes
- Add 'Diagnose' button that fetches and displays root cause analysis
- Add 'Dismiss for today' option with localStorage persistence
- Implement pulsing amber highlight on 3D link lines for degraded links
- Display diagnostic results in plain English with possible causes and actions
- Do NOT show prompts for transient drops (< 5 minutes)
- Add GetDiagnosticFor method to diagnostics package for timestamp-based queries
- Wire up /api/links/{linkID}/diagnostics endpoint with health snapshots
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
479ee7e3d9
commit
70745bb577
6 changed files with 306 additions and 42 deletions
File diff suppressed because one or more lines are too long
|
|
@ -1 +1 @@
|
|||
5c19424d776e892840bac1162120638982d5b7d9
|
||||
479ee7e3d92a77ce0e0bd1a0e8d7051a22e5bdb4
|
||||
|
|
|
|||
|
|
@ -315,7 +315,8 @@
|
|||
*/
|
||||
function diagnoseLink(linkID) {
|
||||
// Fetch diagnostic results from API
|
||||
fetch(`/api/diagnostics/link/${encodeURIComponent(linkID)}`)
|
||||
// Using the correct endpoint: /api/links/{linkID}/diagnostics
|
||||
fetch(`/api/links/${encodeURIComponent(linkID)}/diagnostics`)
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
showDiagnosticResults(linkID, data);
|
||||
|
|
@ -329,7 +330,7 @@
|
|||
/**
|
||||
* Show diagnostic results in a slide-out panel
|
||||
*/
|
||||
function showDiagnosticResults(linkID, diagnostic) {
|
||||
function showDiagnosticResults(linkID, response) {
|
||||
// Remove existing panel if present
|
||||
const existing = document.getElementById('diagnostic-results-panel');
|
||||
if (existing) {
|
||||
|
|
@ -340,8 +341,11 @@
|
|||
panel.id = 'diagnostic-results-panel';
|
||||
panel.className = 'diagnostic-results-panel';
|
||||
|
||||
const diagnosis = diagnostic.diagnosis || {};
|
||||
// Handle both old format (diagnosis array) and new format (response with diagnosis + health)
|
||||
const diagnosis = response.diagnosis || response[0] || {};
|
||||
const health = response.health || diagnosis.health || null;
|
||||
const hasDiagnosis = diagnosis.title !== undefined;
|
||||
const severity = (diagnosis.severity || 'info').toLowerCase();
|
||||
|
||||
panel.innerHTML = `
|
||||
<div class="diagnostic-panel-header">
|
||||
|
|
@ -355,10 +359,11 @@
|
|||
</div>
|
||||
|
||||
${hasDiagnosis ? `
|
||||
<div class="diagnostic-result diagnostic-severity-${diagnosis.severity.toLowerCase()}">
|
||||
<div class="diagnostic-result diagnostic-severity-${severity}">
|
||||
<div class="diagnostic-result-title">${diagnosis.title}</div>
|
||||
<div class="diagnostic-result-detail">${diagnosis.detail}</div>
|
||||
${diagnosis.advice ? `<div class="diagnostic-result-advice"><strong>What to do:</strong> ${diagnosis.advice}</div>` : ''}
|
||||
${diagnosis.confidence !== undefined ? `<div class="diagnostic-confidence">Confidence: ${Math.round(diagnosis.confidence * 100)}%</div>` : ''}
|
||||
</div>
|
||||
` : `
|
||||
<div class="diagnostic-result diagnostic-severity-info">
|
||||
|
|
@ -367,10 +372,24 @@
|
|||
</div>
|
||||
`}
|
||||
|
||||
<div class="diagnostic-health-metrics">
|
||||
<h4>Health Metrics</h4>
|
||||
${renderHealthMetrics(diagnostic.health)}
|
||||
</div>
|
||||
${health ? `
|
||||
<div class="diagnostic-health-metrics">
|
||||
<h4>Current Health Metrics</h4>
|
||||
${renderHealthMetrics(health)}
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
${response.repositioning ? `
|
||||
<div class="diagnostic-repositioning">
|
||||
<h4>Suggested Repositioning</h4>
|
||||
<p>Move <strong>${response.repositioning.node_mac}</strong> to:</p>
|
||||
<div class="repositioning-coords">
|
||||
X: ${response.repositioning.position.x.toFixed(1)}m,
|
||||
Y: ${response.repositioning.position.y.toFixed(1)}m,
|
||||
Z: ${response.repositioning.position.z.toFixed(1)}m
|
||||
</div>
|
||||
</div>
|
||||
` : ''}
|
||||
</div>
|
||||
`;
|
||||
|
||||
|
|
@ -401,26 +420,39 @@
|
|||
*/
|
||||
function renderHealthMetrics(health) {
|
||||
if (!health) {
|
||||
return '<p>No health data available.</p>';
|
||||
return '';
|
||||
}
|
||||
|
||||
// Handle both snapshot format and report format
|
||||
const snr = health.snr !== undefined ? health.snr : (health.SNR || 'N/A');
|
||||
const phaseStability = health.phase_stability !== undefined ? health.phase_stability : (health.PhaseStability || 'N/A');
|
||||
const packetRate = health.packet_rate !== undefined ? health.packet_rate : (health.PacketRate || 'N/A');
|
||||
const driftRate = health.drift_rate !== undefined ? health.drift_rate : (health.DriftRate || 'N/A');
|
||||
const compositeScore = health.composite_score !== undefined ? health.composite_score : (health.CompositeScore || 'N/A');
|
||||
|
||||
return `
|
||||
<div class="health-metric">
|
||||
<span class="metric-label">Packet Rate:</span>
|
||||
<span class="metric-value">${health.packet_rate?.toFixed(1) || 'N/A'} Hz</span>
|
||||
<span class="metric-value">${typeof packetRate === 'number' ? packetRate.toFixed(1) : packetRate} Hz</span>
|
||||
</div>
|
||||
<div class="health-metric">
|
||||
<span class="metric-label">SNR:</span>
|
||||
<span class="metric-value">${health.snr?.toFixed(2) || 'N/A'}</span>
|
||||
<span class="metric-value">${typeof snr === 'number' ? snr.toFixed(2) : snr}</span>
|
||||
</div>
|
||||
<div class="health-metric">
|
||||
<span class="metric-label">Phase Stability:</span>
|
||||
<span class="metric-value">${health.phase_stability?.toFixed(2) || 'N/A'}</span>
|
||||
<span class="metric-value">${typeof phaseStability === 'number' ? phaseStability.toFixed(2) : phaseStability}</span>
|
||||
</div>
|
||||
<div class="health-metric">
|
||||
<span class="metric-label">Drift Rate:</span>
|
||||
<span class="metric-value">${health.drift_rate?.toFixed(4) || 'N/A'}</span>
|
||||
<span class="metric-value">${typeof driftRate === 'number' ? driftRate.toFixed(4) : driftRate}</span>
|
||||
</div>
|
||||
${typeof compositeScore === 'number' ? `
|
||||
<div class="health-metric">
|
||||
<span class="metric-label">Composite Score:</span>
|
||||
<span class="metric-value">${Math.round(compositeScore * 100)}%</span>
|
||||
</div>
|
||||
` : ''}
|
||||
`;
|
||||
}
|
||||
|
||||
|
|
@ -1203,8 +1235,14 @@
|
|||
* Fetch diagnostic info for a link at a specific time
|
||||
*/
|
||||
function fetchDiagnosticForLink(linkID, timestamp) {
|
||||
// Using the correct endpoint: /api/diagnostics/link/{linkID}?timestamp={ms}
|
||||
return fetch(`/api/diagnostics/link/${encodeURIComponent(linkID)}?timestamp=${timestamp}`)
|
||||
.then(res => res.json())
|
||||
.then(res => {
|
||||
if (!res.ok) {
|
||||
throw new Error(`HTTP ${res.status}: ${res.statusText}`);
|
||||
}
|
||||
return res.json();
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('Failed to fetch diagnostic:', err);
|
||||
return null;
|
||||
|
|
@ -1535,10 +1573,48 @@
|
|||
border-top: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.diagnostic-confidence {
|
||||
margin-top: 8px;
|
||||
padding-top: 8px;
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.1);
|
||||
font-size: 11px;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
.diagnostic-health-metrics {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.diagnostic-repositioning {
|
||||
margin-top: 16px;
|
||||
padding: 12px;
|
||||
background: rgba(79, 195, 247, 0.1);
|
||||
border: 1px solid rgba(79, 195, 247, 0.3);
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.diagnostic-repositioning h4 {
|
||||
margin: 0 0 8px 0;
|
||||
font-size: 12px;
|
||||
color: #4fc3f7;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.diagnostic-repositioning p {
|
||||
margin: 0 0 8px 0;
|
||||
font-size: 13px;
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.repositioning-coords {
|
||||
font-family: monospace;
|
||||
font-size: 12px;
|
||||
color: #4fc3f7;
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
padding: 8px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.diagnostic-health-metrics h4 {
|
||||
margin: 0 0 12px 0;
|
||||
font-size: 13px;
|
||||
|
|
|
|||
|
|
@ -2970,8 +2970,32 @@ func main() {
|
|||
// Phase 6: Link diagnostics API
|
||||
r.Get("/api/links/{linkID}/diagnostics", func(w http.ResponseWriter, r *http.Request) {
|
||||
linkID := chi.URLParam(r, "linkID")
|
||||
|
||||
// Get diagnoses for this link
|
||||
diagnoses := diagnosticEngine.GetDiagnoses(linkID)
|
||||
writeJSON(w, diagnoses)
|
||||
|
||||
// Get current health snapshot for this link
|
||||
var healthInfo map[string]interface{}
|
||||
if ingestSrv != nil {
|
||||
linkHealth := ingestSrv.GetLinkWithHealth(linkID)
|
||||
if linkHealth != nil {
|
||||
healthInfo = map[string]interface{}{
|
||||
"snr": linkHealth.HealthDetails.SNR,
|
||||
"phase_stability": linkHealth.HealthDetails.PhaseStability,
|
||||
"packet_rate": linkHealth.HealthDetails.PacketRate,
|
||||
"drift_rate": linkHealth.HealthDetails.BaselineDrift,
|
||||
"composite_score": linkHealth.HealthScore,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Build response with diagnosis and health
|
||||
response := map[string]interface{}{
|
||||
"diagnosis": diagnoses,
|
||||
"health": healthInfo,
|
||||
}
|
||||
|
||||
writeJSON(w, response)
|
||||
})
|
||||
|
||||
r.Get("/api/links/{linkID}/health-history", func(w http.ResponseWriter, r *http.Request) {
|
||||
|
|
@ -3000,6 +3024,99 @@ func main() {
|
|||
writeJSON(w, allDiagnoses)
|
||||
})
|
||||
|
||||
// GetDiagnosticFor endpoint - returns diagnostic for a specific link at a given time
|
||||
r.Get("/api/diagnostics/link/{linkID}", func(w http.ResponseWriter, r *http.Request) {
|
||||
linkID := chi.URLParam(r, "linkID")
|
||||
|
||||
// Parse optional timestamp parameter
|
||||
var timestamp time.Time
|
||||
timestampStr := r.URL.Query().Get("timestamp")
|
||||
if timestampStr != "" {
|
||||
// Try parsing as Unix milliseconds
|
||||
if ms, err := strconv.ParseInt(timestampStr, 10, 64); err == nil {
|
||||
timestamp = time.Unix(0, ms*1e6)
|
||||
} else {
|
||||
// Try parsing as ISO8601
|
||||
timestamp, err = time.Parse(time.RFC3339, timestampStr)
|
||||
if err != nil {
|
||||
http.Error(w, "invalid timestamp format", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Default to now
|
||||
timestamp = time.Now()
|
||||
}
|
||||
|
||||
// Get diagnostic for this link at the specified time
|
||||
diagnosis := diagnosticEngine.GetDiagnosticFor(linkID, timestamp)
|
||||
if diagnosis == nil {
|
||||
http.Error(w, "diagnostic not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
// Build response with diagnosis and current health snapshot
|
||||
response := map[string]interface{}{
|
||||
"diagnosis": map[string]interface{}{
|
||||
"link_id": diagnosis.LinkID,
|
||||
"rule_id": diagnosis.RuleID,
|
||||
"severity": string(diagnosis.Severity),
|
||||
"title": diagnosis.Title,
|
||||
"detail": diagnosis.Detail,
|
||||
"advice": diagnosis.Advice,
|
||||
"confidence": diagnosis.ConfidenceScore,
|
||||
},
|
||||
}
|
||||
|
||||
// Add repositioning info if available
|
||||
if diagnosis.RepositioningTarget != nil {
|
||||
response["repositioning"] = map[string]interface{}{
|
||||
"node_mac": diagnosis.RepositioningNodeMAC,
|
||||
"position": map[string]float64{
|
||||
"x": diagnosis.RepositioningTarget.X,
|
||||
"y": diagnosis.RepositioningTarget.Y,
|
||||
"z": diagnosis.RepositioningTarget.Z,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Add current health snapshot if available
|
||||
if healthStore != nil {
|
||||
history, err := healthStore.GetHealthHistory(linkID, 1*time.Hour)
|
||||
if err == nil && len(history) > 0 {
|
||||
// Find the snapshot closest to the requested timestamp
|
||||
var closest *diagnostics.LinkHealthSnapshot
|
||||
minDiff := time.Duration(1<<63 - 1)
|
||||
|
||||
for i := range history {
|
||||
diff := history[i].Timestamp.Sub(timestamp)
|
||||
if diff < 0 {
|
||||
diff = -diff
|
||||
}
|
||||
if diff < minDiff {
|
||||
minDiff = diff
|
||||
closest = &history[i]
|
||||
}
|
||||
}
|
||||
|
||||
if closest != nil {
|
||||
response["health"] = map[string]interface{}{
|
||||
"timestamp": closest.Timestamp.Unix(),
|
||||
"snr": closest.SNR,
|
||||
"phase_stability": closest.PhaseStability,
|
||||
"packet_rate": closest.PacketRate,
|
||||
"drift_rate": closest.DriftRate,
|
||||
"composite_score": closest.CompositeScore,
|
||||
"delta_rms_variance": closest.DeltaRMSVariance,
|
||||
"is_quiet_period": closest.IsQuietPeriod,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
writeJSON(w, response)
|
||||
})
|
||||
|
||||
// Phase 6: Analytics REST API
|
||||
if flowAccumulator != nil {
|
||||
analyticsHandler := analytics.NewHandler(flowAccumulator)
|
||||
|
|
|
|||
|
|
@ -866,6 +866,10 @@ type LinkInfo struct {
|
|||
NodeMAC string `json:"node_mac"`
|
||||
PeerMAC string `json:"peer_mac"`
|
||||
HealthScore float64 `json:"health_score,omitempty"` // Ambient confidence score (0-1)
|
||||
// Alias fields for frontend compatibility with proactive.js
|
||||
CompositeScore float64 `json:"composite_score,omitempty"` // Alias for HealthScore
|
||||
Quality float64 `json:"quality,omitempty"` // Alias for HealthScore
|
||||
LinkID string `json:"link_id,omitempty"` // Alias for ID
|
||||
}
|
||||
|
||||
// LinkHealthInfo represents a link with health metrics for the API response
|
||||
|
|
@ -894,6 +898,7 @@ func (s *Server) GetAllLinksInfo() []LinkInfo {
|
|||
ID: linkID,
|
||||
NodeMAC: linkID[:17],
|
||||
PeerMAC: linkID[18:],
|
||||
LinkID: linkID, // Alias for frontend
|
||||
}
|
||||
|
||||
// Get health score from processor manager if available
|
||||
|
|
@ -901,6 +906,9 @@ func (s *Server) GetAllLinksInfo() []LinkInfo {
|
|||
if proc := pm.GetProcessor(linkID); proc != nil {
|
||||
if health := proc.GetHealth(); health != nil {
|
||||
info.HealthScore = health.GetAmbientConfidence()
|
||||
// Populate alias fields for frontend compatibility
|
||||
info.CompositeScore = info.HealthScore
|
||||
info.Quality = info.HealthScore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1006,6 +1014,46 @@ func (s *Server) GetAllLinksWithHealth() []LinkHealthInfo {
|
|||
return result
|
||||
}
|
||||
|
||||
// GetLinkWithHealth returns health information for a specific link
|
||||
func (s *Server) GetLinkWithHealth(linkID string) *LinkHealthInfo {
|
||||
s.mu.RLock()
|
||||
pm := s.processorMgr
|
||||
s.mu.RUnlock()
|
||||
|
||||
info := &LinkHealthInfo{
|
||||
LinkID: linkID,
|
||||
}
|
||||
|
||||
if len(linkID) >= 35 {
|
||||
info.TXMAC = linkID[:17]
|
||||
info.RXMAC = linkID[18:]
|
||||
}
|
||||
|
||||
if pm != nil {
|
||||
if proc := pm.GetProcessor(linkID); proc != nil {
|
||||
health := proc.GetHealth()
|
||||
if health != nil {
|
||||
info.HealthScore = health.GetAmbientConfidence()
|
||||
info.HealthDetails = health.GetHealthDetails()
|
||||
info.LastUpdated = time.Now()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Default health if not available
|
||||
if info.HealthScore == 0 && info.HealthDetails == (signal.HealthDetails{}) {
|
||||
info.HealthScore = 0.5
|
||||
info.HealthDetails = signal.HealthDetails{
|
||||
SNR: 0.5,
|
||||
PhaseStability: 0.5,
|
||||
PacketRate: 0.5,
|
||||
BaselineDrift: 0.5,
|
||||
}
|
||||
}
|
||||
|
||||
return info
|
||||
}
|
||||
|
||||
// GetAllLinks returns all link IDs that have data
|
||||
func (s *Server) GetAllLinks() []string {
|
||||
s.mu.RLock()
|
||||
|
|
|
|||
|
|
@ -6,6 +6,28 @@ import (
|
|||
"time"
|
||||
)
|
||||
|
||||
// Helper function to create a manager with quiet hours disabled for testing.
|
||||
// This ensures batching tests work regardless of when they run.
|
||||
func newTestManagerWithQuietHoursDisabled(cfg Config) (*NotificationManager, error) {
|
||||
m, err := New(cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Disable quiet hours by setting bitmask to 0 (no days)
|
||||
err = m.SetConfig(NotificationConfig{
|
||||
Channel: "test",
|
||||
QuietFrom: "00:00",
|
||||
QuietTo: "00:00",
|
||||
QuietDaysBitmask: 0, // Disabled - no days selected
|
||||
MorningDigest: false,
|
||||
})
|
||||
if err != nil {
|
||||
m.Close()
|
||||
return nil, err
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// TestNewManager tests creating a new notification manager.
|
||||
func TestNewManager(t *testing.T) {
|
||||
dbPath := t.TempDir() + "/test.db"
|
||||
|
|
@ -140,7 +162,7 @@ func TestNotifyHighImmediate(t *testing.T) {
|
|||
dbPath := t.TempDir() + "/test.db"
|
||||
|
||||
var receivedEvent Event
|
||||
m, err := New(Config{
|
||||
m, err := newTestManagerWithQuietHoursDisabled(Config{
|
||||
DBPath: dbPath,
|
||||
SendCallback: func(e Event) { receivedEvent = e },
|
||||
})
|
||||
|
|
@ -171,10 +193,10 @@ func TestBatching(t *testing.T) {
|
|||
dbPath := t.TempDir() + "/test.db"
|
||||
|
||||
var receivedEvents []Event
|
||||
m, err := New(Config{
|
||||
DBPath: dbPath,
|
||||
m, err := newTestManagerWithQuietHoursDisabled(Config{
|
||||
DBPath: dbPath,
|
||||
BatchWindowSec: 1, // Short window for testing
|
||||
SendCallback: func(e Event) { receivedEvents = append(receivedEvents, e) },
|
||||
SendCallback: func(e Event) { receivedEvents = append(receivedEvents, e) },
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("New() error = %v", err)
|
||||
|
|
@ -228,7 +250,7 @@ func TestBatchMaxSize(t *testing.T) {
|
|||
dbPath := t.TempDir() + "/test.db"
|
||||
|
||||
var receivedEvents []Event
|
||||
m, err := New(Config{
|
||||
m, err := newTestManagerWithQuietHoursDisabled(Config{
|
||||
DBPath: dbPath,
|
||||
MaxBatchSize: 3,
|
||||
SendCallback: func(e Event) { receivedEvents = append(receivedEvents, e) },
|
||||
|
|
@ -502,10 +524,10 @@ func TestMediumPriorityBatching(t *testing.T) {
|
|||
dbPath := t.TempDir() + "/test.db"
|
||||
|
||||
var receivedEvents []Event
|
||||
m, err := New(Config{
|
||||
DBPath: dbPath,
|
||||
m, err := newTestManagerWithQuietHoursDisabled(Config{
|
||||
DBPath: dbPath,
|
||||
BatchWindowSec: 1,
|
||||
SendCallback: func(e Event) { receivedEvents = append(receivedEvents, e) },
|
||||
SendCallback: func(e Event) { receivedEvents = append(receivedEvents, e) },
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("New() error = %v", err)
|
||||
|
|
@ -546,10 +568,10 @@ func TestMediumPriorityBatching(t *testing.T) {
|
|||
func TestGetHistory(t *testing.T) {
|
||||
dbPath := t.TempDir() + "/test.db"
|
||||
|
||||
m, err := New(Config{
|
||||
DBPath: dbPath,
|
||||
m, err := newTestManagerWithQuietHoursDisabled(Config{
|
||||
DBPath: dbPath,
|
||||
BatchWindowSec: 1, // Short batch window for testing
|
||||
SendCallback: func(e Event) {},
|
||||
SendCallback: func(e Event) {},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("New() error = %v", err)
|
||||
|
|
@ -706,10 +728,10 @@ func TestSingleEventBatch(t *testing.T) {
|
|||
dbPath := t.TempDir() + "/test.db"
|
||||
|
||||
var receivedEvent Event
|
||||
m, err := New(Config{
|
||||
DBPath: dbPath,
|
||||
m, err := newTestManagerWithQuietHoursDisabled(Config{
|
||||
DBPath: dbPath,
|
||||
BatchWindowSec: 1,
|
||||
SendCallback: func(e Event) { receivedEvent = e },
|
||||
SendCallback: func(e Event) { receivedEvent = e },
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("New() error = %v", err)
|
||||
|
|
@ -922,8 +944,8 @@ func TestBatchingThreeLowEvents(t *testing.T) {
|
|||
|
||||
var receivedCount int
|
||||
var receivedEvent Event
|
||||
m, err := New(Config{
|
||||
DBPath: dbPath,
|
||||
m, err := newTestManagerWithQuietHoursDisabled(Config{
|
||||
DBPath: dbPath,
|
||||
BatchWindowSec: 10, // 10 second batch window
|
||||
SendCallback: func(e Event) {
|
||||
receivedCount++
|
||||
|
|
@ -990,8 +1012,8 @@ func TestBatchingUrgentBypassesBatch(t *testing.T) {
|
|||
dbPath := t.TempDir() + "/test.db"
|
||||
|
||||
var receivedEvents []Event
|
||||
m, err := New(Config{
|
||||
DBPath: dbPath,
|
||||
m, err := newTestManagerWithQuietHoursDisabled(Config{
|
||||
DBPath: dbPath,
|
||||
BatchWindowSec: 30,
|
||||
SendCallback: func(e Event) {
|
||||
receivedEvents = append(receivedEvents, e)
|
||||
|
|
@ -1534,10 +1556,10 @@ func TestQuietHoursNotActiveOutsideWindow(t *testing.T) {
|
|||
func TestBatchingPrioritySeparation(t *testing.T) {
|
||||
dbPath := t.TempDir() + "/test.db"
|
||||
|
||||
m, err := New(Config{
|
||||
DBPath: dbPath,
|
||||
m, err := newTestManagerWithQuietHoursDisabled(Config{
|
||||
DBPath: dbPath,
|
||||
BatchWindowSec: 1,
|
||||
SendCallback: func(e Event) {},
|
||||
SendCallback: func(e Event) {},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("New() error = %v", err)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue