Skip to content

Commit a875de0

Browse files
committed
Address check-in SLA review feedback
1 parent 21d0747 commit a875de0

7 files changed

Lines changed: 40 additions & 43 deletions

File tree

internal/storage/subjective_checkin.go

Lines changed: 8 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -368,27 +368,8 @@ func (s *DB) GetCheckinCoverage(today, source string, days int) (*CheckinCoverag
368368
}
369369
defer rows.Close()
370370

371-
var entries []CheckinRow
372-
for rows.Next() {
373-
row := CheckinRow{}
374-
var answer *string
375-
var msgID *int64
376-
var answeredAt *time.Time
377-
if err := rows.Scan(&row.Date, &row.Source, &row.Status, &answer, &msgID, &row.PromptedAt, &answeredAt, &row.ExpiresAt); err != nil {
378-
return nil, err
379-
}
380-
if answer != nil {
381-
row.Answer = *answer
382-
}
383-
if msgID != nil {
384-
row.PromptMessageID = *msgID
385-
}
386-
if answeredAt != nil {
387-
row.AnsweredAt = *answeredAt
388-
}
389-
entries = append(entries, row)
390-
}
391-
if err := rows.Err(); err != nil {
371+
entries, err := scanCheckinRows(rows)
372+
if err != nil {
392373
return nil, err
393374
}
394375
coverage, err := buildCheckinCoverage(today, source, days, entries)
@@ -420,7 +401,10 @@ func (s *DB) getCheckinRowsBetween(ctx context.Context, source, from, to string)
420401
return nil, err
421402
}
422403
defer rows.Close()
404+
return scanCheckinRows(rows)
405+
}
423406

407+
func scanCheckinRows(rows pgx.Rows) ([]CheckinRow, error) {
424408
var entries []CheckinRow
425409
for rows.Next() {
426410
row := CheckinRow{}
@@ -476,25 +460,9 @@ func buildCheckinCoverage(today, source string, days int, entries []CheckinRow)
476460
var latencyN int64
477461
promptedDays := 0
478462
for _, entry := range entries {
479-
promptedAt := entry.PromptedAt
480-
expiresAt := entry.ExpiresAt
481-
row := CheckinCoverageRow{
482-
Date: entry.Date,
483-
Source: entry.Source,
484-
Status: entry.Status,
485-
Answer: entry.Answer,
486-
PromptedAt: &promptedAt,
487-
ExpiresAt: &expiresAt,
488-
}
489-
if !entry.AnsweredAt.IsZero() {
490-
answeredAt := entry.AnsweredAt
491-
row.AnsweredAt = &answeredAt
492-
latency := int64(answeredAt.Sub(promptedAt).Seconds())
493-
if latency < 0 {
494-
latency = 0
495-
}
496-
row.ResponseLatencySeconds = &latency
497-
latencySum += latency
463+
row := checkinCoverageRow(entry)
464+
if row.ResponseLatencySeconds != nil {
465+
latencySum += *row.ResponseLatencySeconds
498466
latencyN++
499467
}
500468
switch entry.Status {

internal/ui/admin_checkin_coverage_test.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,21 @@ func TestAdminCheckinCoverage_PostRejectsBadEnabledSince(t *testing.T) {
141141
}
142142
}
143143

144+
func TestAdminCheckinCoverage_PostRejectsUnknownFields(t *testing.T) {
145+
db, schema, cleanup := testTenantDB(t)
146+
defer cleanup()
147+
148+
h := &Handler{}
149+
w := httptest.NewRecorder()
150+
r := httptest.NewRequest(http.MethodPost, "/api/admin/checkin-coverage", bytes.NewBufferString(`{"enabled_since":"2026-06-15","extra":true}`)).
151+
WithContext(adminContext(db, schema))
152+
h.adminCheckinCoverage(w, r)
153+
154+
if w.Code != http.StatusBadRequest {
155+
t.Fatalf("status = %d, want 400; body=%s", w.Code, w.Body.String())
156+
}
157+
}
158+
144159
func TestAdminCheckinCoverage_RejectsBadDays(t *testing.T) {
145160
w := httptest.NewRecorder()
146161
r := httptest.NewRequest(http.MethodGet, "/api/admin/checkin-coverage?days=0", nil)

internal/ui/handler.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"errors"
77
"fmt"
88
"html/template"
9+
"io"
910
"math"
1011
"net"
1112
"net/http"
@@ -2887,7 +2888,14 @@ func (h *Handler) adminCheckinCoverage(w http.ResponseWriter, r *http.Request) {
28872888
var req struct {
28882889
EnabledSince string `json:"enabled_since"`
28892890
}
2890-
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
2891+
const maxBodyBytes = 1024
2892+
dec := json.NewDecoder(http.MaxBytesReader(w, r.Body, maxBodyBytes))
2893+
dec.DisallowUnknownFields()
2894+
if err := dec.Decode(&req); err != nil {
2895+
http.Error(w, "invalid JSON", http.StatusBadRequest)
2896+
return
2897+
}
2898+
if err := dec.Decode(&struct{}{}); err != io.EOF {
28912899
http.Error(w, "invalid JSON", http.StatusBadRequest)
28922900
return
28932901
}

internal/ui/i18n_en.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,7 @@ var translationsEn = map[string]string{
375375
"admin_checkin_calendar_sla": "Calendar SLA",
376376
"admin_checkin_history": "Latest prompt history",
377377
"admin_checkin_sla_inactive": "Set an enabled-since date to start calendar SLA checks.",
378+
"admin_checkin_save_failed": "Failed to save check-in SLA date",
378379
"admin_checkin_total": "Days",
379380
"admin_checkin_prompted_coverage": "Prompted",
380381
"admin_checkin_answered_coverage": "Answered",
@@ -393,6 +394,7 @@ var translationsEn = map[string]string{
393394
"admin_checkin_status_pending": "Pending",
394395
"admin_save": "Save",
395396
"admin_saved": "Saved",
397+
"admin_saving": "Saving...",
396398

397399
// ─── Admin: AI briefing settings ───────────────────────────
398400
"admin_ai_title": "AI Morning Briefing",

internal/ui/i18n_ru.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,7 @@ var translationsRu = map[string]string{
353353
"admin_checkin_calendar_sla": "Календарный SLA",
354354
"admin_checkin_history": "История промптов",
355355
"admin_checkin_sla_inactive": "Укажите дату включения, чтобы начать календарные SLA-проверки.",
356+
"admin_checkin_save_failed": "Не удалось сохранить дату SLA check-in",
356357
"admin_checkin_total": "Дней",
357358
"admin_checkin_prompted_coverage": "Промпт был",
358359
"admin_checkin_answered_coverage": "Ответ был",
@@ -371,6 +372,7 @@ var translationsRu = map[string]string{
371372
"admin_checkin_status_pending": "Ждём",
372373
"admin_save": "Сохранить",
373374
"admin_saved": "Сохранено",
375+
"admin_saving": "Сохраняем...",
374376

375377
"admin_ai_title": "AI-брифинг утром",
376378
"admin_ai_key": "Gemini API ключ",

internal/ui/i18n_sr.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,7 @@ var translationsSr = map[string]string{
334334
"admin_checkin_calendar_sla": "Kalendarski SLA",
335335
"admin_checkin_history": "Istorija promptova",
336336
"admin_checkin_sla_inactive": "Unesite datum uključivanja da počnu kalendarske SLA provere.",
337+
"admin_checkin_save_failed": "Čuvanje SLA datuma check-ina nije uspelo",
337338
"admin_checkin_total": "Dana",
338339
"admin_checkin_prompted_coverage": "Prompt poslat",
339340
"admin_checkin_answered_coverage": "Odgovoreno",
@@ -352,6 +353,7 @@ var translationsSr = map[string]string{
352353
"admin_checkin_status_pending": "Čeka se",
353354
"admin_save": "Sačuvaj",
354355
"admin_saved": "Sačuvano",
356+
"admin_saving": "Čuvanje...",
355357
"admin_ai_title": "AI jutarnji izveštaj",
356358
"admin_ai_key": "Gemini API ključ",
357359
"admin_ai_model": "Model",

internal/ui/templates/pages/admin.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -885,14 +885,14 @@
885885
var input = $('checkin-enabled-since');
886886
var msg = $('checkin-enabled-msg');
887887
if (!input) return;
888-
if (msg) msg.textContent = '...';
888+
if (msg) msg.textContent = '{{T .Lang "admin_saving"}}';
889889
fetchWithAdminSchema('/api/admin/checkin-coverage?days=14', {
890890
method: 'POST',
891891
headers: {'Content-Type': 'application/json'},
892892
body: JSON.stringify({enabled_since: input.value || ''})
893893
})
894894
.then(function(r){
895-
if (!r.ok) return r.text().then(function(t){ throw new Error(t || 'Failed to save check-in SLA date'); });
895+
if (!r.ok) return r.text().then(function(t){ throw new Error(t || '{{T .Lang "admin_checkin_save_failed"}}'); });
896896
return r.json();
897897
})
898898
.then(function(d) {

0 commit comments

Comments
 (0)