Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions funcs/business_hours_cel.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package funcs

import (
"github.com/google/cel-go/cel"
"github.com/google/cel-go/common/types"
"github.com/google/cel-go/common/types/ref"
)

var inBusinessHourGen = cel.Function("in_business_hours",
cel.Overload("in_business_hours_string",
[]*cel.Type{
cel.StringType,
},
cel.DynType,
cel.FunctionBinding(func(args ...ref.Val) ref.Val {
in, err := inBusinessHour(args[0].Value().(string))
if err != nil {
return types.WrapErr(err)
}
if in == nil {
return types.NullValue
}
return types.Bool(*in)
}),
),
)
2 changes: 2 additions & 0 deletions funcs/cel_exports.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

38 changes: 36 additions & 2 deletions funcs/time.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@ package funcs
import (
"context"
"fmt"
"github.com/flanksource/commons/duration"
"os"
"strconv"
"strings"
gotime "time"

"github.com/flanksource/commons/duration"
"github.com/flanksource/commons/properties"
"github.com/flanksource/commons/utils"

"github.com/flanksource/gomplate/v3/conv"
"github.com/flanksource/gomplate/v3/time"
)
Expand Down Expand Up @@ -65,7 +68,8 @@ func CreateTimeFuncs(ctx context.Context) map[string]interface{} {
}

return map[string]interface{}{
"time": func() interface{} { return ns },
"time": func() interface{} { return ns },
"in_business_hours": ns.InBusinessHour,
}
}

Expand Down Expand Up @@ -184,6 +188,36 @@ func (TimeFuncs) Until(n gotime.Time) gotime.Duration {
return gotime.Until(n)
}

// InBusinessHour returns nil when no business hours are configured.
func (TimeFuncs) InBusinessHour(value string) (any, error) {
in, err := inBusinessHour(value)
if err != nil {
return nil, err
}
if in == nil {
return nil, nil
}
return *in, nil
}

func inBusinessHour(value string) (*bool, error) {
intervals, err := properties.BusinessHours()
if err != nil {
return nil, err
}
if len(intervals) == 0 {
return nil, nil
}

t := utils.ParseTime(value)
if t == nil {
return nil, fmt.Errorf("failed to parse time %q", value)
}

in := intervals.ContainsTime(*t)
return &in, nil
}

// convert a number input to a pair of int64s, representing the integer portion and the decimal remainder
// this can handle a string as well as any integer or float type
// precision is at the "nano" level (i.e. 1e+9)
Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -116,3 +116,5 @@ require (
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
)

// replace github.com/flanksource/commons => ../commons
53 changes: 53 additions & 0 deletions tests/cel_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"testing"
"time"

. "github.com/onsi/gomega"
"github.com/stretchr/testify/assert"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"

Expand Down Expand Up @@ -103,6 +104,58 @@ func TestCelGoTemplateFunction(t *testing.T) {
runTests(t, testCases)
}

func TestCelInBusinessHourDefault(t *testing.T) {
g := NewWithT(t)
out, err := gomplate.RunExpression(nil, gomplate.Template{
Expression: `in_business_hours("2024-01-02T10:00:00Z")`,
})
g.Expect(err).ToNot(HaveOccurred())
g.Expect(out).To(Equal(true))
}

func TestCelInBusinessHour(t *testing.T) {
loadTestProperties(t)

tests := []struct {
name string
expression string
expect any
}{
{
name: "inside business hours",
expression: `in_business_hours("2024-01-02T10:00:00Z")`, // 2 January 2024, Tuesday
expect: true,
},
{
name: "inside business hours datetime",
expression: `in_business_hours("2024-01-02 10:00:00")`, // DateTime format (no timezone)
expect: true,
},
{
name: "outside business hours",
expression: `in_business_hours("2024-01-02T20:00:00Z")`, // 2 January 2024, Tuesday
expect: false,
},
{
name: "outside business hours weekend",
expression: `in_business_hours("2025-01-05T10:00:00Z")`, // 5 January 2025, Sunday
expect: false,
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
g := NewWithT(t)

out, err := gomplate.RunExpression(nil, gomplate.Template{
Expression: tc.expression,
})
g.Expect(err).ToNot(HaveOccurred())
g.Expect(out).To(Equal(tc.expect))
})
}
}

// unstructure marshalls a struct to and from JSON to remove any type details
func unstructure(o any) interface{} {
data, err := json.Marshal(o)
Expand Down
28 changes: 26 additions & 2 deletions tests/gomplate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@ import (
"testing"
"time"

_ "github.com/robertkrimen/otto/underscore"
"github.com/stretchr/testify/assert"

"github.com/flanksource/gomplate/v3"
_ "github.com/flanksource/gomplate/v3/js"
"github.com/flanksource/gomplate/v3/kubernetes"
_ "github.com/robertkrimen/otto/underscore"
"github.com/stretchr/testify/assert"
)

func TestGomplateFunctions(t *testing.T) {
Expand Down Expand Up @@ -146,6 +147,29 @@ func TestGomplateHeaders(t *testing.T) {
}
}

func TestInBusinessHourTemplate(t *testing.T) {
loadTestProperties(t)

tests := []struct {
timeStr string
out string
}{
{"2024-01-02T10:00:00Z", "true"}, // 2 January 2024, Tuesday
{"2024-01-02 10:00:00", "true"}, // DateTime format (no timezone)
{"2024-01-02T20:00:00Z", "false"}, // 2 January 2024, Tuesday
{"2025-01-05T10:00:00Z", "false"}, // 5 January 2025, Sunday
}

for _, tc := range tests {
template := fmt.Sprintf(`{{ in_business_hours %q }}`, tc.timeStr)
out, err := gomplate.RunTemplate(nil, gomplate.Template{
Template: template,
})
assert.NoError(t, err)
assert.Equal(t, tc.out, out)
}
}

func readFile(t *testing.T, filename string) string {
t.Helper()
data, err := os.ReadFile(filename)
Expand Down
15 changes: 15 additions & 0 deletions tests/properties_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package tests

import (
"testing"

"github.com/flanksource/commons/properties"
"github.com/stretchr/testify/require"
)

func loadTestProperties(t *testing.T) {
t.Helper()

err := properties.LoadFile("testdata/test.properties")
require.NoError(t, err)
}
4 changes: 4 additions & 0 deletions tests/testdata/test.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Monday-Friday 09:00-17:00 UTC
# Property value is a JSON array of time intervals

time_interval.business_hours=[{"weekdays":["monday:friday"],"times":[{"start_time":"09:00","end_time":"17:00"}]}]
Loading