wip-devtools

This commit is contained in:
ZacharyZhang-NY committed 2026-10-05 14:53:36 -04:00
1 parent 9903aff52b
commit 3c8d15b97d
6 files changed
+182 -2

No files matched your search

+95
View File
@@ -0,0 +1,95 @@
package mujica
import (
"fmt"
"log/slog"
"slices"
"time"
"github.com/egoist/mygo/ui"
"mujicaui/icons"
)
// DashboardFilters is what a dashboard is filtered by; the zero value filters nothing.
type DashboardFilters struct {
// Range is how far back to look; zero means the caller's default.
Range time.Duration
Env string
Tags []string
}
// DashboardFilterBarOptions configure a DashboardFilterBar.
type DashboardFilterBarOptions struct {
// Ranges are the offered look-backs; nil uses 15 min, 1 h, 24 h and 7 d.
Ranges []time.Duration
// Envs and Tags are the offered environments and tags.
Envs, Tags []string
Disabled bool
}
var dashboardRanges = []time.Duration{15 * time.Minute, time.Hour, 24 * time.Hour, 7 * 24 * time.Hour}
// DashboardFilterBar filters by time range, environment and tags, with a clear button.
func DashboardFilterBar(c *ui.Context, f *DashboardFilters, opts DashboardFilterBarOptions) ChoiceResult {
if f == nil {
panic("mujica: DashboardFilterBar needs filters")
}
ranges := opts.Ranges
if ranges == nil {
ranges = dashboardRanges
}
if f.Env != "" && !slices.Contains(opts.Envs, f.Env) {
panic("mujica: DashboardFilterBar Env is not offered")
}
u := Density(c).Unit()
var r ChoiceResult
r.Element = ui.Row(c).Shrink(0).Wrap().Gap(u * 3).AlignItems(ui.Center).Disabled(opts.Disabled).
Label(msg(c, "dashboard_filter_bar.label"))
locked := r.Element.IsDisabled()
r.Element.Children(func() {
rs := []SelectOption[time.Duration]{{Value: 0, Label: msg(c, "dashboard_filter_bar.default_range")}}
for _, d := range ranges {
rs = append(rs, SelectOption[time.Duration]{Value: d, Label: fmt.Sprintf(msg(c, "dashboard_filter_bar.last"), dashboardRangeLabel(c, d))})
}
if f.Range != 0 && !slices.Contains(ranges, f.Range) {
panic("mujica: DashboardFilterBar Range is not offered")
}
r.changed = Select(c, &f.Range, rs, SelectOptions{Label: msg(c, "dashboard_filter_bar.range"), Disabled: locked}).Changed()
envs := []SelectOption[string]{{Value: "", Label: msg(c, "dashboard_filter_bar.all_envs")}}
for _, e := range opts.Envs {
envs = append(envs, SelectOption[string]{Value: e, Label: e})
}
r.changed = Select(c, &f.Env, envs, SelectOptions{Label: msg(c, "dashboard_filter_bar.env"), Disabled: locked}).Changed() || r.changed
if len(opts.Tags) > 0 {
tags := make([]SelectOption[string], len(opts.Tags))
for i, t := range opts.Tags {
tags[i] = SelectOption[string]{Value: t, Label: t}
}
r.changed = MultiSelect(c, &f.Tags, tags, MultiSelectOptions{Placeholder: msg(c, "dashboard_filter_bar.tags"),
Label: msg(c, "dashboard_filter_bar.tags"), Disabled: locked}).Changed() || r.changed
}
empty := f.Range == 0 && f.Env == "" && len(f.Tags) == 0
if Button(c, msg(c, "dashboard_filter_bar.clear"), ButtonOptions{Variant: Ghost, Icon: icons.Must("x"), Disabled: locked || empty}).Clicked() && !locked {
*f, r.changed = DashboardFilters{}, true
}
})
if r.changed {
slog.Debug("mujica: dashboard_filter_bar change", "range", f.Range, "env", f.Env, "tags", len(f.Tags))
}
return r
}
// dashboardRangeLabel names a look-back, as "15 min" or "7 d".
func dashboardRangeLabel(c *ui.Context, d time.Duration) string {
switch {
case d <= 0:
case d%(24*time.Hour) == 0:
return fmt.Sprintf(msg(c, "dashboard_filter_bar.days"), int(d/(24*time.Hour)))
case d%time.Hour == 0:
return fmt.Sprintf(msg(c, "dashboard_filter_bar.hours"), int(d/time.Hour))
case d%time.Minute == 0:
return fmt.Sprintf(msg(c, "dashboard_filter_bar.minutes"), int(d/time.Minute))
}
panic("mujica: DashboardFilterBar ranges must be positive whole minutes")
}
+73
View File
@@ -0,0 +1,73 @@
package mujica
import (
"testing"
"time"
"github.com/egoist/mygo/ui"
)
func filterTester(f *DashboardFilters, opts DashboardFilterBarOptions, changes *int) *ui.Tester {
return ui.NewTester(func(c *ui.Context) {
Use(c, Settings{})
ui.Column(c).Padding(8).Children(func() {
if DashboardFilterBar(c, f, opts).Changed() {
*changes++
}
})
}, 900, 420)
}
var filterOpts = DashboardFilterBarOptions{Envs: []string{"production", "staging"}, Tags: []string{"api", "web", "db"}}
func TestDashboardFilterBarChooseAndClear(t *testing.T) {
var f DashboardFilters
n := 0
tt := filterTester(&f, filterOpts, &n)
_ = tt.Click("Time range")
_ = tt.Click("Last 1 d")
if f.Range != 24*time.Hour || n != 1 {
t.Fatalf("range %v changes %d", f.Range, n)
}
_ = tt.Click("Environment")
_ = tt.Click("staging")
if f.Env != "staging" {
t.Fatalf("env %q", f.Env)
}
_ = tt.Click("Tags")
_ = tt.Click("db")
if len(f.Tags) != 1 || f.Tags[0] != "db" {
t.Fatalf("tags %v", f.Tags)
}
tt.Key(0, ui.KeyEscape)
_ = tt.Click("Clear filters")
if f.Range != 0 || f.Env != "" || len(f.Tags) != 0 || !tt.HasText("All environments") {
t.Fatalf("clear: %+v", f)
}
}
func TestDashboardFilterBarDisabledAndRules(t *testing.T) {
f := DashboardFilters{Env: "production"}
n := 0
tt := filterTester(&f, DashboardFilterBarOptions{Envs: filterOpts.Envs, Disabled: true}, &n)
_ = tt.Click("Clear filters")
_ = tt.Click("Time range")
_ = tt.Click("Last 1 h")
if n != 0 || f.Env != "production" || f.Range != 0 {
t.Fatalf("disabled changed: %+v", f)
}
defer func() {
if recover() == nil {
t.Fatal("unoffered env did not panic")
}
}()
bad := DashboardFilters{Env: "moon"}
filterTester(&bad, filterOpts, &n)
}
func TestDashboardFilterBarSnapshot(t *testing.T) {
snapshot(t, "dashboard_filter_bar", 860, 90, func(c *ui.Context) {
f := DashboardFilters{Range: time.Hour, Env: "production", Tags: []string{"api"}}
ui.Column(c).Padding(16).Children(func() { DashboardFilterBar(c, &f, filterOpts) })
})
}
+12
View File
@@ -54,6 +54,18 @@ func init() {
"refresh_interval_selector.minutes": {"Every %d min", "每 %d 分钟"},
"refresh_interval_selector.label": {"Refresh interval", "刷新间隔"},
"dashboard_filter_bar.label": {"Dashboard filters", "仪表盘过滤"},
"dashboard_filter_bar.range": {"Time range", "时间范围"},
"dashboard_filter_bar.default_range": {"Default range", "默认范围"},
"dashboard_filter_bar.last": {"Last %s", "最近 %s"},
"dashboard_filter_bar.minutes": {"%d min", "%d 分钟"},
"dashboard_filter_bar.hours": {"%d h", "%d 小时"},
"dashboard_filter_bar.days": {"%d d", "%d 天"},
"dashboard_filter_bar.env": {"Environment", "环境"},
"dashboard_filter_bar.all_envs": {"All environments", "全部环境"},
"dashboard_filter_bar.tags": {"Tags", "标签"},
"dashboard_filter_bar.clear": {"Clear filters", "清除过滤"},
"alert_list.empty": {"No alerts", "没有告警"},
})
}
+2 -2
View File
@@ -44,8 +44,8 @@ func RefreshIntervalSelector(c *ui.Context, interval *time.Duration, opts Refres
options := []SelectOption[time.Duration]{{Value: 0, Label: msg(c, "refresh_interval_selector.off")}}
known := *interval == 0
for _, d := range choices {
if d < time.Second {
panic("mujica: RefreshIntervalSelector intervals must be at least a second")
if d < time.Second || d%time.Second != 0 {
panic("mujica: RefreshIntervalSelector intervals must be whole seconds")
}
known = known || d == *interval
label := fmt.Sprintf(msg(c, "refresh_interval_selector.seconds"), int(d/time.Second))
Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB