Skip to content

Commit 0c445c6

Browse files
authored
Merge pull request #759 from slok/slok/ui
Simplify service SLO list on UI
2 parents dc3a431 + 7089418 commit 0c445c6

File tree

8 files changed

+70
-411
lines changed

8 files changed

+70
-411
lines changed

internal/http/ui/handler_select_slo.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ import (
77
"github.com/slok/sloth/internal/http/ui/htmx"
88
)
99

10+
const (
11+
queryParamSLOServiceID = "slo-service-id"
12+
)
13+
1014
func (u ui) handlerSelectSLO() http.HandlerFunc {
1115
// Available components
1216
const (
@@ -68,6 +72,7 @@ func (u ui) handlerSelectSLO() http.HandlerFunc {
6872
SLOSearchInput string
6973

7074
// Filter.
75+
FilterServiceID string
7176
SLOFilterURL string
7277
SLOFilterFiringAlerts bool
7378
SLOFilterBurningOverThreshold bool
@@ -132,6 +137,7 @@ func (u ui) handlerSelectSLO() http.HandlerFunc {
132137
data.SLOFilterFiringAlerts = r.URL.Query().Get(queryParamFilterAlertsFiring) == "on"
133138
data.SLOFilterBurningOverThreshold = r.URL.Query().Get(queryParamFilterBurningOverThreshold) == "on"
134139
data.SLOFilterPeriodBudgetConsumed = r.URL.Query().Get(queryParamFilterPeriodBudgetConsumed) == "on"
140+
data.FilterServiceID = r.URL.Query().Get(queryParamSLOServiceID)
135141

136142
currentURL := urls.AppURL("/slos")
137143
currentURL = urls.AddQueryParm(currentURL, queryParamSLOSearch, data.SLOSearchInput)
@@ -145,6 +151,9 @@ func (u ui) handlerSelectSLO() http.HandlerFunc {
145151
if data.SLOFilterPeriodBudgetConsumed {
146152
currentURL = urls.AddQueryParm(currentURL, queryParamFilterPeriodBudgetConsumed, "on")
147153
}
154+
if data.FilterServiceID != "" {
155+
currentURL = urls.AddQueryParm(currentURL, queryParamSLOServiceID, data.FilterServiceID)
156+
}
148157

149158
htmx.NewResponse().WithPushURL(currentURL).SetHeaders(w) // Always push URL with search or no search param.
150159

@@ -221,6 +230,7 @@ func (u ui) handlerSelectSLO() http.HandlerFunc {
221230
}
222231

223232
slosResp, err := u.serviceApp.ListSLOs(ctx, app.ListSLOsRequest{
233+
FilterServiceID: data.FilterServiceID,
224234
Cursor: cursor,
225235
FilterSearchInput: data.SLOSearchInput,
226236
SortMode: sortMode,
@@ -247,6 +257,7 @@ func (u ui) handlerSelectSLO() http.HandlerFunc {
247257
default:
248258
// Get SLOs for service.
249259
slosResp, err := u.serviceApp.ListSLOs(ctx, app.ListSLOsRequest{
260+
FilterServiceID: data.FilterServiceID,
250261
FilterSearchInput: data.SLOSearchInput,
251262
SortMode: sortMode,
252263
FilterAlertFiring: data.SLOFilterFiringAlerts,

internal/http/ui/handler_select_slo_test.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,47 @@ func TestHandlerSelectSLO(t *testing.T) {
142142
},
143143
},
144144

145+
"Listing the slos filtered by a service should render the full page.": {
146+
request: func() *http.Request {
147+
return httptest.NewRequest(http.MethodGet, "/u/app/slos?slo-service-id=test-svc1", nil)
148+
},
149+
mock: func(m mocks) {
150+
expReq := app.ListSLOsRequest{
151+
FilterServiceID: "test-svc1",
152+
SortMode: app.SLOListSortModeSLOIDAsc,
153+
}
154+
m.ServiceApp.On("ListSLOs", mock.Anything, expReq).Once().Return(&app.ListSLOsResponse{
155+
PaginationCursors: app.PaginationCursors{},
156+
SLOs: []app.RealTimeSLODetails{
157+
{
158+
SLO: model.SLO{
159+
ID: "test-svc1-slo1",
160+
ServiceID: "test-svc1",
161+
Name: "Test SLO 1",
162+
},
163+
Alerts: model.SLOAlerts{
164+
FiringPage: &model.Alert{Name: "page-1"},
165+
FiringWarning: &model.Alert{Name: "warn-2"},
166+
},
167+
Budget: model.SLOBudgetDetails{
168+
SLOID: "test-svc1-slo1",
169+
BurningBudgetPercent: 75.0,
170+
BurnedBudgetWindowPercent: 80.0,
171+
},
172+
},
173+
}}, nil)
174+
},
175+
expHeaders: http.Header{
176+
"Content-Type": {"text/html; charset=utf-8"},
177+
"Hx-Push-Url": {"/u/app/slos?slo-search=&slo-sort-mode=slo-name-asc&slo-service-id=test-svc1"},
178+
},
179+
expCode: 200,
180+
expBody: []string{
181+
`<h1><u>test-svc1</u> SLO list</h1>`, // We have the service ID in the title.
182+
`<tr> <td> <span data-tooltip="Individual SLO"><i data-lucide="goal"></i></span> </td> <td> <a href="/u/app/slos/test-svc1-slo1">Test SLO 1</a> </td> <td> </td> <td><a href="/u/app/services/test-svc1">test-svc1</a></td> <td class="is-ok">75%</td> <td class="is-ok">20%</td> <td> <div class="is-critical">Critical</div> </td> </tr>`, // SLO1 should be critical.
183+
},
184+
},
185+
145186
"Listing the SLOs with HTMX on the SLOs list component and forward pagination should render the snippet.": {
146187
request: func() *http.Request {
147188
r := httptest.NewRequest(http.MethodGet, "/u/app/slos?component=slo-list&forward-cursor=eyJzaXplIjozMCwicGFnZSI6Mn0=&slo-search=test", nil)

internal/http/ui/handler_service_details.go

Lines changed: 3 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -4,131 +4,15 @@ import (
44
"net/http"
55

66
"github.com/go-chi/chi/v5"
7-
8-
"github.com/slok/sloth/internal/http/backend/app"
9-
"github.com/slok/sloth/internal/http/ui/htmx"
107
)
118

129
func (u ui) handlerServiceDetails() http.HandlerFunc {
13-
// Available components
14-
const (
15-
componentSLOList = "slo-list"
16-
)
17-
18-
type tplDataSLO struct {
19-
Name string
20-
BurningBudgetPercent float64
21-
RemainingBudgetWindowPercent float64
22-
DetailsURL string
23-
CriticalAlertName string
24-
WarningAlertName string
25-
GroupLabels map[string]string
26-
}
27-
28-
type tplData struct {
29-
ServiceID string
30-
SLOs []tplDataSLO
31-
AutoReloadSLOListSeconds int
32-
AutoReloadSLOListURL string
33-
SLOPagination tplPaginationData
34-
}
35-
36-
mapSLOsToTPL := func(s []app.RealTimeSLODetails) []tplDataSLO {
37-
var slos []tplDataSLO
38-
for _, slo := range s {
39-
critAlert := ""
40-
if slo.Alerts.FiringPage != nil {
41-
critAlert = slo.Alerts.FiringPage.Name
42-
}
43-
warnAlert := ""
44-
if slo.Alerts.FiringWarning != nil {
45-
warnAlert = slo.Alerts.FiringWarning.Name
46-
}
47-
slos = append(slos, tplDataSLO{
48-
Name: slo.SLO.Name,
49-
BurningBudgetPercent: slo.Budget.BurningBudgetPercent,
50-
RemainingBudgetWindowPercent: 100 - slo.Budget.BurnedBudgetWindowPercent,
51-
DetailsURL: urls.AppURL("/slos/" + slo.SLO.ID),
52-
CriticalAlertName: critAlert,
53-
WarningAlertName: warnAlert,
54-
GroupLabels: slo.SLO.GroupLabels,
55-
})
56-
}
57-
return slos
58-
}
59-
6010
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
61-
ctx := r.Context()
62-
isHTMXCall := htmx.NewRequest(r.Header).IsHTMXRequest()
63-
component := urls.ComponentFromRequest(r)
64-
6511
svcID := chi.URLParam(r, URLParamServiceID)
66-
data := tplData{
67-
ServiceID: svcID,
68-
AutoReloadSLOListSeconds: 30,
69-
AutoReloadSLOListURL: urls.URLWithComponent(urls.AppURL("/services/"+svcID), componentSLOList),
70-
}
71-
72-
nextCursor := urls.ForwardCursorFromRequest(r)
73-
prevCursor := urls.BackwardCursorFromRequest(r)
74-
75-
switch {
76-
// Snippet SLO list next.
77-
case isHTMXCall && component == componentSLOList && nextCursor != "":
78-
// Get SLOs for service.
79-
slosResp, err := u.serviceApp.ListSLOs(ctx, app.ListSLOsRequest{
80-
FilterServiceID: data.ServiceID,
81-
Cursor: nextCursor,
82-
})
83-
if err != nil {
84-
u.logger.Errorf("could not get service SLOs: %s", err)
85-
http.Error(w, "could not get service SLOs", http.StatusInternalServerError)
86-
return
87-
}
88-
89-
data.SLOs = mapSLOsToTPL(slosResp.SLOs)
90-
data.SLOPagination = mapPaginationToTPL(slosResp.PaginationCursors, urls.URLWithComponent(urls.AppURL("/services/"+data.ServiceID), componentSLOList))
91-
92-
u.tplRenderer.RenderResponse(ctx, w, r, "app_service_comp_slo_list", data)
93-
94-
// Snippet SLO list previous.
95-
case isHTMXCall && component == componentSLOList && prevCursor != "":
96-
// Get SLOs for service.
97-
slosResp, err := u.serviceApp.ListSLOs(ctx, app.ListSLOsRequest{
98-
FilterServiceID: data.ServiceID,
99-
Cursor: prevCursor,
100-
})
101-
if err != nil {
102-
u.logger.Errorf("could not get service SLOs: %s", err)
103-
http.Error(w, "could not get service SLOs", http.StatusInternalServerError)
104-
return
105-
}
106-
107-
data.SLOs = mapSLOsToTPL(slosResp.SLOs)
108-
data.SLOPagination = mapPaginationToTPL(slosResp.PaginationCursors, urls.URLWithComponent(urls.AppURL("/services/"+data.ServiceID), componentSLOList))
109-
110-
u.tplRenderer.RenderResponse(ctx, w, r, "app_service_comp_slo_list", data)
111-
112-
// Unknown snippet.
113-
case isHTMXCall:
114-
http.Error(w, "Unknown component", http.StatusBadRequest)
115-
116-
// Full page load.
117-
default:
118-
// Get SLOs for service.
119-
slosResp, err := u.serviceApp.ListSLOs(ctx, app.ListSLOsRequest{
120-
FilterServiceID: data.ServiceID,
121-
})
122-
if err != nil {
123-
u.logger.Errorf("could not get service SLOs: %s", err)
124-
http.Error(w, "could not get service SLOs", http.StatusInternalServerError)
125-
return
126-
}
12712

128-
data.SLOs = mapSLOsToTPL(slosResp.SLOs)
129-
data.SLOPagination = mapPaginationToTPL(slosResp.PaginationCursors, urls.URLWithComponent(urls.AppURL("/services/"+data.ServiceID), componentSLOList))
13+
currentURL := urls.AppURL("/slos")
14+
currentURL = urls.AddQueryParm(currentURL, queryParamSLOServiceID, svcID)
13015

131-
u.tplRenderer.RenderResponse(ctx, w, r, "app_service", data)
132-
}
16+
http.Redirect(w, r, currentURL, http.StatusSeeOther)
13317
})
13418
}

0 commit comments

Comments
 (0)