Skip to content

Commit

Permalink
provider/pd: fixup schedule checking to find closest allowed check-in…
Browse files Browse the repository at this point in the history
… time
  • Loading branch information
adamdecaf committed Oct 18, 2024
1 parent 5d4bc85 commit 74ffac5
Show file tree
Hide file tree
Showing 4 changed files with 142 additions and 22 deletions.
6 changes: 3 additions & 3 deletions internal/provider/pd/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,13 +165,13 @@ func (c *client) CheckIn(ctx context.Context, check config.Check) (time.Time, er
}
}

future := now.Add(wait)
_, wait, err = snooze.Calculate(future, check.Schedule)
// future := now.Add(wait)
_, wait, err = snooze.Calculate(scheduleTime, check.Schedule)
if err != nil {
return time.Time{}, fmt.Errorf("calculating second snooze: %w", err)
}

future = future.Add(wait)
future := scheduleTime.Add(wait)
logger.Info().Logf("snoozing incident %s until %v", inc.ID, future.Format(time.RFC3339))

err = c.snoozeIncident(ctx, logger, inc, service, now, future.Sub(now))
Expand Down
52 changes: 45 additions & 7 deletions internal/provider/pd/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package pd

import (
"context"
"fmt"
"os"
"testing"
"time"
Expand Down Expand Up @@ -105,23 +106,60 @@ func TestClient_RejectedLateCheckIn(t *testing.T) {
require.NoError(t, err)
})

nextCheckInExpected, err := pdc.CheckIn(ctx, config.Check{
conf := config.Check{
Name: t.Name(),
Schedule: config.ScheduleConfig{
Weekdays: &config.PartialDay{
Timezone: "America/New_York",
Times: []string{
// Never allow the current time to check-in
now.Add(-1*time.Hour - 30*time.Minute).Format("15:04"),
// Add a future time that's too far in the future
now.Add(2*time.Hour + 30*time.Minute).Format("15:04"),
},
Tolerance: "1m",
Tolerance: "5m",
},
},
})
require.ErrorContains(t, err, "check-in is late by 1h29m")
}
nextCheckInExpected, err := pdc.CheckIn(ctx, conf)
require.ErrorContains(t, err, fmt.Sprintf("%s check-in is late by 1h25m", conf.Schedule.Weekdays.Times[0]))
require.True(t, nextCheckInExpected.IsZero())
}

func TestClient_CheckInJustBefore(t *testing.T) {
ctx := context.Background()

pdc := newTestClient(t)

loc, err := time.LoadLocation("America/New_York")
require.NoError(t, err)

timeService := stime.NewStaticTimeService()
timeService.Change(time.Date(2024, time.October, 16, 13, 59, 30, 0, loc)) // just before expected check-in
pdc.timeService = timeService

t.Cleanup(func() {
service, err := pdc.findService(ctx, t.Name())
require.NoError(t, err)

err = pdc.deleteService(ctx, service)
require.NoError(t, err)
})

nextCheckInExpected, err := pdc.CheckIn(ctx, config.Check{
Name: t.Name(),
Schedule: config.ScheduleConfig{
Weekdays: &config.PartialDay{
Timezone: "America/New_York",
Times: []string{"13:30", "14:00", "14:30"},
Tolerance: "5m",
},
},
})
require.NoError(t, err)
require.Equal(t, "14:35", nextCheckInExpected.Format("15:04"))
}

func TestClient_CheckInJustAfter(t *testing.T) {
ctx := context.Background()

Expand All @@ -131,7 +169,7 @@ func TestClient_CheckInJustAfter(t *testing.T) {
require.NoError(t, err)

timeService := stime.NewStaticTimeService()
timeService.Change(time.Date(2024, time.October, 16, 14, 0, 1, 0, loc)) // just after expected check-in
timeService.Change(time.Date(2024, time.October, 16, 14, 1, 1, 0, loc)) // just after expected check-in
pdc.timeService = timeService

t.Cleanup(func() {
Expand All @@ -147,11 +185,11 @@ func TestClient_CheckInJustAfter(t *testing.T) {
Schedule: config.ScheduleConfig{
Weekdays: &config.PartialDay{
Timezone: "America/New_York",
Times: []string{"14:00"},
Times: []string{"13:30", "14:00", "14:30"},
Tolerance: "5m",
},
},
})
require.NoError(t, err)
require.Equal(t, "14:05", nextCheckInExpected.Format("15:04"))
require.Equal(t, "14:35", nextCheckInExpected.Format("15:04"))
}
36 changes: 31 additions & 5 deletions internal/provider/snooze/snooze.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,12 +112,38 @@ func Calculate(now time.Time, schedule config.ScheduleConfig) (time.Time, time.D
tolerance = t
}

// Find the next future hour:minute
// Find the hour:minute (within the tolerance) scheduled check-in which contains the current time.
current := now.Format("15:04")
for _, hourminute := range times {
if hourminute.Format("15:04") > current {
hhmm := time.Date(now.Year(), now.Month(), now.Day(), hourminute.Hour(), hourminute.Minute(), 0, 0, now.Location())
return hhmm, hhmm.Sub(now) + tolerance, nil

for idx, hourminute := range times {
low := hourminute.Add(-1 * tolerance).Format("15:04")
high := hourminute.Add(tolerance).Format("15:04")

scheduledCheckIn := time.Date(now.Year(), now.Month(), now.Day(), hourminute.Hour(), hourminute.Minute(), 0, 0, now.Location())

// If we're before all scheduled check-ins today
if idx == 0 && current < low {
return scheduledCheckIn, scheduledCheckIn.Sub(now) + tolerance, nil
}

// The current time must be within our scheduled time +/- the tolerance
if low <= current && high >= current {
// Based on the scheduledCheckIn calculate how long to sleep for
var nextCheckIn time.Time
if len(times) > idx+1 {
nextCheckIn = times[idx+1] // the next time later today
} else {
nextCheckIn = times[0] // tomorrow's first time
nextCheckIn = nextCheckIn.AddDate(0, 0, 1)
}

next := time.Date(now.Year(), now.Month(), now.Day(), nextCheckIn.Hour(), nextCheckIn.Minute(), 0, 0, now.Location())

if nextCheckIn.Day() > 1 { // zero value of time.Time is Jan 1 1970
next = next.AddDate(0, 0, nextCheckIn.Day()-1)
}

return scheduledCheckIn, next.Sub(now) + tolerance, nil
}
}

Expand Down
70 changes: 63 additions & 7 deletions internal/provider/snooze/snooze_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,13 +107,15 @@ func TestSnooze_Weekdays(t *testing.T) {
})

t.Run("09:00 tomorrow", func(t *testing.T) {
now = now.Add(5 * time.Minute)

schedule.Weekdays.Times = []string{"09:00", "09:10", "09:20"}

clockTime, snooze, err := Calculate(now, schedule)
require.NoError(t, err)

require.Equal(t, "2024-10-07T09:00:00-04:00", clockTime.Format(time.RFC3339))
require.Equal(t, "23h42m55s", snooze.String())
require.Equal(t, "23h37m55s", snooze.String())
require.Equal(t, "2024-10-08T09:05:00-04:00", now.In(nyc).Add(snooze).Format(time.RFC3339))
})
}
Expand All @@ -123,7 +125,7 @@ func TestSnooze_BankingDays(t *testing.T) {
require.NoError(t, err)

t.Run("13:00 tomorrow over weekend + holiday", func(t *testing.T) {
now := time.Date(2024, time.October, 11, 13, 22, 5, 0, time.UTC)
now := time.Date(2024, time.October, 11, 13, 26, 5, 0, time.UTC)

var schedule config.ScheduleConfig
schedule.BankingDays = &config.PartialDay{
Expand All @@ -136,30 +138,84 @@ func TestSnooze_BankingDays(t *testing.T) {
require.NoError(t, err)

require.Equal(t, "2024-10-11T09:00:00-04:00", clockTime.Format(time.RFC3339))
require.Equal(t, "95h42m55s", snooze.String())
require.Equal(t, "95h38m55s", snooze.String())
require.Equal(t, "2024-10-15T09:05:00-04:00", now.In(nyc).Add(snooze).Format(time.RFC3339))
})
}

func TestSnooze_JustAfter(t *testing.T) {
func TestSnooze_Close(t *testing.T) {
nyc, err := time.LoadLocation("America/New_York")
require.NoError(t, err)

t.Run("1s before 14:00 expected check-in", func(t *testing.T) {
now := time.Date(2024, time.October, 17, 13, 59, 59, 0, nyc)

var schedule config.ScheduleConfig
schedule.BankingDays = &config.PartialDay{
Timezone: "America/New_York",
Times: []string{"13:30", "14:00", "14:30"},
Tolerance: "5m",
}

clockTime, snooze, err := Calculate(now, schedule)
require.NoError(t, err)

require.Equal(t, "2024-10-17T14:00:00-04:00", clockTime.Format(time.RFC3339))
require.Equal(t, "35m1s", snooze.String())
require.Equal(t, "2024-10-17T14:35:00-04:00", now.In(nyc).Add(snooze).Format(time.RFC3339))
})

t.Run("1s after 14:00 expected check-in", func(t *testing.T) {
now := time.Date(2024, time.October, 17, 14, 0, 1, 0, nyc)

var schedule config.ScheduleConfig
schedule.BankingDays = &config.PartialDay{
Timezone: "America/New_York",
Times: []string{"14:00"},
Times: []string{"13:30", "14:00", "14:30"},
Tolerance: "5m",
}

clockTime, snooze, err := Calculate(now, schedule)
require.NoError(t, err)

require.Equal(t, "2024-10-17T14:00:00-04:00", clockTime.Format(time.RFC3339))
require.Equal(t, "34m59s", snooze.String())
require.Equal(t, "2024-10-17T14:35:00-04:00", now.In(nyc).Add(snooze).Format(time.RFC3339))
})

t.Run("1m before 14:00 expected check-in", func(t *testing.T) {
now := time.Date(2024, time.October, 17, 13, 59, 0, 0, nyc)

var schedule config.ScheduleConfig
schedule.BankingDays = &config.PartialDay{
Timezone: "America/New_York",
Times: []string{"13:30", "14:00", "14:30"},
Tolerance: "5m",
}

clockTime, snooze, err := Calculate(now, schedule)
require.NoError(t, err)

require.Equal(t, "2024-10-17T14:00:00-04:00", clockTime.Format(time.RFC3339))
require.Equal(t, "36m0s", snooze.String())
require.Equal(t, "2024-10-17T14:35:00-04:00", now.In(nyc).Add(snooze).Format(time.RFC3339))
})

t.Run("1m after 14:00 expected check-in", func(t *testing.T) {
now := time.Date(2024, time.October, 17, 14, 1, 0, 0, nyc)

var schedule config.ScheduleConfig
schedule.BankingDays = &config.PartialDay{
Timezone: "America/New_York",
Times: []string{"13:30", "14:00", "14:30"},
Tolerance: "5m",
}

clockTime, snooze, err := Calculate(now, schedule)
require.NoError(t, err)

require.Equal(t, "2024-10-17T14:00:00-04:00", clockTime.Format(time.RFC3339))
require.Equal(t, "24h4m59s", snooze.String())
require.Equal(t, "2024-10-18T14:05:00-04:00", now.In(nyc).Add(snooze).Format(time.RFC3339))
require.Equal(t, "34m0s", snooze.String())
require.Equal(t, "2024-10-17T14:35:00-04:00", now.In(nyc).Add(snooze).Format(time.RFC3339))
})
}

0 comments on commit 74ffac5

Please sign in to comment.