Skip to content

Commit

Permalink
add time/timezone/reporting related info and fix reported time
Browse files Browse the repository at this point in the history
  • Loading branch information
davidmasek committed Jan 13, 2025
1 parent 20549ee commit 0ea949f
Show file tree
Hide file tree
Showing 6 changed files with 116 additions and 39 deletions.
30 changes: 30 additions & 0 deletions handlers/report.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,36 @@ import (
"github.com/davidmasek/beacon/storage"
)

// Calculate when the next report should happen based on last report time.
// No previous reports or failed reporting tasks are not considered here.
// See `ShouldReport` for more complex logic.
//
// Since timezones are hard, there might be some edge cases with weird behavior.
// Suppose lastReportTime is 2025-01-01 01:00 in Brisbane (UTC+10)
// and we want the next report time in Honolulu (UTC-10).
// The result will be 2025-01-01 09:00:00 -1000 HST (in Pacific/Honolulu).
// Is that what you expect? Note that adding the 24 hours first and
// then converting to (target) local time leads to different result.
//
// TODO: refactor packages
// - it does not make sense for this one report scheduling function to be here
// if all the others are in scheduler
// - moving it to scheduler creates circular dependency
// - probably makes more sense to move stuff from scheduler here anyway
// - but at that moment `handlers` is too big of a package (which it is anyway by now)
// -> split handlers, create:
// - reporting/report
// - server / web_gui / smth
// - keep scheduler with reduced functionality ?
func NextReportTime(config *conf.Config, lastReportTime time.Time) time.Time {
lastReportTime = lastReportTime.In(&config.Timezone)
nextReportDay := lastReportTime.Add(24 * time.Hour)
nextReportTime := time.Date(
nextReportDay.Year(), nextReportDay.Month(), nextReportDay.Day(),
config.ReportAfter, 0, 0, 0, nextReportDay.Location())
return nextReportTime
}

func GenerateReport(db storage.Storage) ([]ServiceReport, error) {
reports := make([]ServiceReport, 0)

Expand Down
16 changes: 16 additions & 0 deletions handlers/report_test.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package handlers

import (
"fmt"
"slices"
"strings"
"testing"
"time"

"github.com/davidmasek/beacon/conf"
"github.com/davidmasek/beacon/monitor"
"github.com/davidmasek/beacon/storage"
"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -80,3 +82,17 @@ func TestWriteReport(t *testing.T) {
assert.Equal(t, expected, reported)
}
}

func TestNextReportTime(t *testing.T) {
config := conf.NewConfig()
config.ReportAfter = 10
timezone, err := time.LoadLocation("America/New_York")
// timezone, err := time.LoadLocation("")
require.NoError(t, err)
config.Timezone = *timezone

now := time.Now()
next := NextReportTime(config, now).In(timezone)
assert.Equal(t, 10, next.Hour(), next.In(time.UTC))
assert.Equal(t, now.In(timezone).Day()+1, next.Day(), fmt.Sprintf("now: %s, next: %s\n", now, next))
}
36 changes: 30 additions & 6 deletions handlers/server_gui.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,15 +108,39 @@ func handleAbout(db storage.Storage, config *conf.Config) http.HandlerFunc {
}

timeFormat := "15:04 Monday 02 January"
lastReportTime := "never"
lastReport, err := db.LatestTaskLog("report")
if err != nil {
log.Println("DB error", err)
http.Error(w, "Server error, please try again later", http.StatusInternalServerError)
return
}
var lastReportTime, nextReportAfter string
if lastReport == nil {
lastReportTime = "never"
nextReportAfter = "error"
log.Println("DB not properly initialized! No report task found")
} else if lastReport.Status == string(TASK_SENTINEL) {
lastReportTime = "never"
nextReportAfter = NextReportTime(config, lastReport.Timestamp).
In(&config.Timezone).Format(timeFormat)
} else {
lastReportTime = lastReport.Timestamp.In(&config.Timezone).Format(timeFormat)
nextReportAfter = NextReportTime(config, lastReport.Timestamp).
In(&config.Timezone).Format(timeFormat)
}
serverTime := time.Now().In(&config.Timezone).Format(timeFormat)
nextReportAfter := "TODO"

zone, offset := time.Now().In(&config.Timezone).Zone()

err = tmpl.Execute(w, map[string]any{
"lastReportTime": lastReportTime,
"serverTime": serverTime,
"nextReportTime": nextReportAfter,
"CurrentPage": "about",
"lastReportTime": lastReportTime,
"serverTime": serverTime,
"nextReportTime": nextReportAfter,
"ReportAfter": config.ReportAfter,
"CurrentPage": "about",
"Timezone": config.Timezone.String(),
"TimezoneAlt": zone,
"TimezoneOffsetMinutes": offset / 60,
})
if err != nil {
log.Println("Failed to render", err)
Expand Down
46 changes: 39 additions & 7 deletions handlers/templates/about.html
Original file line number Diff line number Diff line change
Expand Up @@ -23,30 +23,40 @@
.block a:hover {
text-decoration: underline;
}
.time-info {
.rows {
display: flex;
flex-direction: column;
gap: 10px;
}
.time-info .row {
.rows .row {
display: flex;
justify-content: space-between;
padding: 10px;
border-bottom: 1px solid #ddd;
}
.time-info .row:last-child {
.rows .row:last-child {
border-bottom: none;
}
.time-info .label {
.rows .label {
font-weight: bold;
color: #555;
}
.rows .value {
color: #333;
}
.rows-short-labels .label {
flex: 1;
max-width: 40%;
}
.time-info .value {
color: #333;
.rows-short-labels .value {
flex: 2;
}
.rows-long-labels .label {
flex: 1;
}
.rows-long-labels .value {
flex: 1;
}
</style>
</head>
<body>
Expand All @@ -60,7 +70,7 @@ <h2>Beacon</h2>
</div>

<!-- Time Info Block -->
<div class="block time-info">
<div class="block rows rows-short-labels">
<div class="row">
<span class="label">Last Report:</span>
<span class="value">{{ .lastReportTime }}</span>
Expand All @@ -74,6 +84,28 @@ <h2>Beacon</h2>
<span class="value">{{ .nextReportTime }}</span>
</div>
</div>


<!-- Configuration Block -->
<div class="block rows rows-long-labels">
<h2>Config</h2>
<div class="row">
<span class="label">ReportAfter:</span>
<span class="value">{{ .ReportAfter }}</span>
</div>
<div class="row">
<span class="label">Timezone:</span>
<span class="value">{{ .Timezone }}</span>
</div>
<div class="row">
<span class="label">Timezone (alt name):</span>
<span class="value">{{ .TimezoneAlt }}</span>
</div>
<div class="row">
<span class="label">Timezone offset (minutes)</span>
<span class="value">{{ .TimezoneOffsetMinutes }}</span>
</div>
</div>
</div>
</body>
</html>
13 changes: 1 addition & 12 deletions scheduler/scheduler.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,17 +40,6 @@ func CheckWebServices(db storage.Storage, services []conf.ServiceConfig) error {
return monitor.CheckWebsites(db, websites)
}

// Calculate when the next report should happen based on last report time.
// No previous reports or failed reporting tasks are not considered here.
// See `ShouldReport` for more complex logic.
func NextReportTime(config *conf.Config, lastReportTime time.Time) time.Time {
nextReportDay := lastReportTime.Add(24 * time.Hour)
nextReportTime := time.Date(
nextReportDay.Year(), nextReportDay.Month(), nextReportDay.Day(),
config.ReportAfter, 0, 0, 0, nextReportDay.Location())
return nextReportTime
}

// Add placeholder (sentinel) "report" task to bootstrap calculation of next report time.
func InitializeSentinel(db storage.Storage, now time.Time) error {
task, err := db.LatestTaskLog("report")
Expand Down Expand Up @@ -84,7 +73,7 @@ func ShouldReport(db storage.Storage, config *conf.Config, query time.Time) (boo
return true, nil
}

nextReportTime := NextReportTime(config, task.Timestamp)
nextReportTime := handlers.NextReportTime(config, task.Timestamp)
isAfter := query.After(nextReportTime)
return isAfter, nil
}
Expand Down
14 changes: 0 additions & 14 deletions scheduler/scheduler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package scheduler

import (
"context"
"fmt"
"os"
"testing"
"time"
Expand Down Expand Up @@ -85,19 +84,6 @@ func TestSentinelCreatedOnlyIfNeeded(t *testing.T) {
require.Equal(t, taskStatus, task.Status)
}

func TestNextReportTime(t *testing.T) {
config := conf.NewConfig()
config.ReportAfter = 10
timezone, err := time.LoadLocation("America/New_York")
require.NoError(t, err)
config.Timezone = *timezone

now := time.Now()
next := NextReportTime(config, now)
assert.Equal(t, 10, next.Hour())
assert.Equal(t, now.In(timezone).Day()+1, next.Day(), fmt.Sprintf("now: %s, next: %s\n", now, next))
}

func TestShouldReport(t *testing.T) {
db := storage.NewTestDb(t)
defer db.Close()
Expand Down

0 comments on commit 0ea949f

Please sign in to comment.