Skip to content

Commit b0df567

Browse files
committed
fix(coderd): humanize duration on notifications
1 parent 7142cbb commit b0df567

File tree

6 files changed

+109
-5
lines changed

6 files changed

+109
-5
lines changed

coderd/autobuild/lifecycle_executor.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"github.com/coder/coder/v2/coderd/database/pubsub"
2323
"github.com/coder/coder/v2/coderd/notifications"
2424
"github.com/coder/coder/v2/coderd/schedule"
25+
duration "github.com/coder/coder/v2/coderd/util/time"
2526
"github.com/coder/coder/v2/coderd/wsbuilder"
2627
)
2728

@@ -330,7 +331,7 @@ func (e *Executor) runOnce(t time.Time) Stats {
330331
map[string]string{
331332
"name": ws.Name,
332333
"reason": "inactivity exceeded the dormancy threshold",
333-
"timeTilDormant": time.Duration(tmpl.TimeTilDormant).String(),
334+
"timeTilDormant": duration.Humanize(time.Duration(tmpl.TimeTilDormant)),
334335
},
335336
"lifecycle_executor",
336337
ws.ID,

coderd/notifications/notifications_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -691,7 +691,7 @@ func TestNotificationTemplatesCanRender(t *testing.T) {
691691
"reason": "breached the template's threshold for inactivity",
692692
"initiator": "autobuild",
693693
"dormancyHours": "24",
694-
"timeTilDormant": "24h",
694+
"timeTilDormant": "24 hours",
695695
},
696696
},
697697
},
@@ -716,7 +716,7 @@ func TestNotificationTemplatesCanRender(t *testing.T) {
716716
"name": "bobby-workspace",
717717
"reason": "template updated to new dormancy policy",
718718
"dormancyHours": "24",
719-
"timeTilDormant": "24h",
719+
"timeTilDormant": "24 hours",
720720
},
721721
},
722722
},

coderd/util/time/duration.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package duration
2+
3+
import (
4+
"fmt"
5+
"time"
6+
)
7+
8+
type Unit struct {
9+
value int
10+
unit string
11+
}
12+
13+
func Humanize(d time.Duration) string {
14+
units := []Unit{
15+
{int(d.Hours() / 24), "day"},
16+
{int(d.Hours()) % 24, "hour"},
17+
{int(d.Minutes()) % 60, "minute"},
18+
{int(d.Seconds()) % 60, "second"},
19+
}
20+
nonZeroUnits := []Unit{}
21+
for _, unit := range units {
22+
if unit.value > 0 {
23+
nonZeroUnits = append(nonZeroUnits, unit)
24+
}
25+
}
26+
if len(nonZeroUnits) == 0 {
27+
return "0 seconds"
28+
}
29+
var result string
30+
for i, unit := range nonZeroUnits {
31+
if i > 0 {
32+
if i == len(nonZeroUnits)-1 {
33+
result += " and "
34+
} else {
35+
result += ", "
36+
}
37+
}
38+
result += fmt.Sprintf("%d %s", unit.value, unit.unit)
39+
if unit.value > 1 {
40+
result += "s"
41+
}
42+
}
43+
return result
44+
}

coderd/util/time/duration_test.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package duration_test
2+
3+
import (
4+
"testing"
5+
"time"
6+
7+
duration "github.com/coder/coder/v2/coderd/util/time"
8+
)
9+
10+
func TestHumanize(t *testing.T) {
11+
t.Parallel()
12+
13+
testCases := []struct {
14+
duration time.Duration
15+
expected string
16+
}{
17+
{
18+
duration: time.Duration(0),
19+
expected: "0 seconds",
20+
},
21+
{
22+
duration: time.Duration(1 * time.Second),
23+
expected: "1 second",
24+
},
25+
{
26+
duration: time.Duration(45 * time.Second),
27+
expected: "45 seconds",
28+
},
29+
{
30+
duration: time.Duration(30 * time.Minute),
31+
expected: "30 minutes",
32+
},
33+
{
34+
duration: time.Duration(30*time.Minute + 10*time.Second),
35+
expected: "30 minutes and 10 seconds",
36+
},
37+
{
38+
duration: time.Duration(2*time.Hour + 25*time.Minute + 10*time.Second),
39+
expected: "2 hours, 25 minutes and 10 seconds",
40+
},
41+
{
42+
duration: time.Duration(2400 * time.Hour),
43+
expected: "100 days",
44+
},
45+
}
46+
47+
for _, tc := range testCases {
48+
t.Run(tc.expected, func(t *testing.T) {
49+
t.Parallel()
50+
51+
actual := duration.Humanize(tc.duration)
52+
if actual != tc.expected {
53+
t.Errorf("expected: %s, got: %s", tc.expected, actual)
54+
}
55+
})
56+
}
57+
}

coderd/workspaces.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import (
3232
"github.com/coder/coder/v2/coderd/searchquery"
3333
"github.com/coder/coder/v2/coderd/telemetry"
3434
"github.com/coder/coder/v2/coderd/util/ptr"
35+
duration "github.com/coder/coder/v2/coderd/util/time"
3536
"github.com/coder/coder/v2/coderd/wsbuilder"
3637
"github.com/coder/coder/v2/codersdk"
3738
"github.com/coder/coder/v2/codersdk/agentsdk"
@@ -1062,7 +1063,7 @@ func (api *API) putWorkspaceDormant(rw http.ResponseWriter, r *http.Request) {
10621063
map[string]string{
10631064
"name": workspace.Name,
10641065
"reason": "a " + initiator.Username + " request",
1065-
"timeTilDormant": time.Duration(tmpl.TimeTilDormant).String(),
1066+
"timeTilDormant": duration.Humanize(time.Duration(tmpl.TimeTilDormant)),
10661067
},
10671068
"api",
10681069
workspace.ID,

enterprise/coderd/schedule/template.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
"github.com/coder/coder/v2/coderd/notifications"
2020
agpl "github.com/coder/coder/v2/coderd/schedule"
2121
"github.com/coder/coder/v2/coderd/tracing"
22+
duration "github.com/coder/coder/v2/coderd/util/time"
2223
"github.com/coder/coder/v2/codersdk"
2324
)
2425

@@ -212,7 +213,7 @@ func (s *EnterpriseTemplateScheduleStore) Set(ctx context.Context, db database.S
212213
map[string]string{
213214
"name": ws.Name,
214215
"reason": "an update to the template's dormancy",
215-
"timeTilDormant": opts.TimeTilDormantAutoDelete.String(),
216+
"timeTilDormant": duration.Humanize(opts.TimeTilDormantAutoDelete),
216217
},
217218
"scheduletemplate",
218219
// Associate this notification with all the related entities.

0 commit comments

Comments
 (0)