Skip to content

Commit

Permalink
lift Repeat out of the base alert config
Browse files Browse the repository at this point in the history
Through discussions it was confirmed that Repeat is not universal to all
alerts. So it's lifted out of the Base alert and re-inserted into those alerts
where it should be present (namely Low and High alerts only).

BACK-2554
  • Loading branch information
ewollesen committed Jun 6, 2024
1 parent 747d67b commit e926564
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 61 deletions.
28 changes: 17 additions & 11 deletions alerts/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,16 +67,10 @@ func (a Alerts) Validate(validator structure.Validator) {
type Base struct {
// Enabled controls whether notifications should be sent for this alert.
Enabled bool `json:"enabled" bson:"enabled"`
// Repeat is measured in minutes.
//
// A value of 0 (the default) disables repeat notifications.
Repeat DurationMinutes `json:"repeat,omitempty" bson:"repeat"`
}

func (b Base) Validate(validator structure.Validator) {
validator.Bool("enabled", &b.Enabled)
dur := b.Repeat.Duration()
validator.Duration("repeat", &dur).Using(validateRepeat)
}

const (
Expand Down Expand Up @@ -105,7 +99,7 @@ type UrgentLowAlert struct {
Base `bson:",inline"`
// Threshold is compared the current value to determine if an alert should
// be triggered.
Threshold `json:"threshold"`
Threshold `json:"threshold" bson:"threshold"`
}

func (a UrgentLowAlert) Validate(validator structure.Validator) {
Expand Down Expand Up @@ -144,13 +138,19 @@ type LowAlert struct {
// be triggered.
Threshold `json:"threshold"`
Delay DurationMinutes `json:"delay,omitempty"`
// Repeat is measured in minutes.
//
// A value of 0 (the default) disables repeat notifications.
Repeat DurationMinutes `json:"repeat,omitempty" bson:"repeat"`
}

func (a LowAlert) Validate(validator structure.Validator) {
a.Base.Validate(validator)
dur := a.Delay.Duration()
validator.Duration("delay", &dur).InRange(0, 2*time.Hour)
delayDur := a.Delay.Duration()
validator.Duration("delay", &delayDur).InRange(0, 2*time.Hour)
a.Threshold.Validate(validator)
repeatDur := a.Repeat.Duration()
validator.Duration("repeat", &repeatDur).Using(validateRepeat)
}

// HighAlert extends Base with a threshold and a delay.
Expand All @@ -160,13 +160,19 @@ type HighAlert struct {
// be triggered.
Threshold `json:"threshold"`
Delay DurationMinutes `json:"delay,omitempty"`
// Repeat is measured in minutes.
//
// A value of 0 (the default) disables repeat notifications.
Repeat DurationMinutes `json:"repeat,omitempty" bson:"repeat"`
}

func (a HighAlert) Validate(validator structure.Validator) {
a.Base.Validate(validator)
a.Threshold.Validate(validator)
dur := a.Delay.Duration()
validator.Duration("delay", &dur).InRange(0, 6*time.Hour)
delayDur := a.Delay.Duration()
validator.Duration("delay", &delayDur).InRange(0, 6*time.Hour)
repeatDur := a.Repeat.Duration()
validator.Duration("repeat", &repeatDur).Using(validateRepeat)
}

// DurationMinutes reads a JSON integer and converts it to a time.Duration.
Expand Down
103 changes: 53 additions & 50 deletions alerts/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ var _ = Describe("Config", func() {
},
"urgentLow": {
"enabled": false,
"repeat": 30,
"threshold": {
"units": "mg/dL",
"value": 47.5
Expand All @@ -58,12 +57,10 @@ var _ = Describe("Config", func() {
},
"notLooping": {
"enabled": true,
"repeat": 32,
"delay": 4
},
"noCommunication": {
"enabled": true,
"repeat": 33,
"delay": 6
}
}`, mockUserID1, mockUserID2)
Expand All @@ -83,14 +80,11 @@ var _ = Describe("Config", func() {
Expect(conf.Low.Threshold.Value).To(Equal(80.0))
Expect(conf.Low.Threshold.Units).To(Equal(glucose.MgdL))
Expect(conf.UrgentLow.Enabled).To(Equal(false))
Expect(conf.UrgentLow.Repeat).To(Equal(DurationMinutes(30 * time.Minute)))
Expect(conf.UrgentLow.Threshold.Value).To(Equal(47.5))
Expect(conf.UrgentLow.Threshold.Units).To(Equal(glucose.MgdL))
Expect(conf.NotLooping.Enabled).To(Equal(true))
Expect(conf.NotLooping.Repeat).To(Equal(DurationMinutes(32 * time.Minute)))
Expect(conf.NotLooping.Delay).To(Equal(DurationMinutes(4 * time.Minute)))
Expect(conf.NoCommunication.Enabled).To(Equal(true))
Expect(conf.NoCommunication.Repeat).To(Equal(DurationMinutes(33 * time.Minute)))
Expect(conf.NoCommunication.Delay).To(Equal(DurationMinutes(6 * time.Minute)))
})

Expand Down Expand Up @@ -285,32 +279,41 @@ var _ = Describe("Config", func() {
})

Context("repeat", func() {
var defaultAlert = LowAlert{
Threshold: Threshold{Value: 11, Units: glucose.MmolL},
}

It("accepts values of 0 (indicating disabled)", func() {
val := validator.New()
b := Base{Repeat: 0}
b.Validate(val)
l := defaultAlert
l.Repeat = 0
l.Validate(val)
Expect(val.Error()).To(Succeed())
})

It("accepts values of 15 minutes to 4 hours (inclusive)", func() {
val := validator.New()
b := Base{Repeat: DurationMinutes(15 * time.Minute)}
b.Validate(val)
l := defaultAlert
l.Repeat = DurationMinutes(15 * time.Minute)
l.Validate(val)
Expect(val.Error()).To(Succeed())

val = validator.New()
b = Base{Repeat: DurationMinutes(4 * time.Hour)}
b.Validate(val)
l = defaultAlert
l.Repeat = DurationMinutes(4 * time.Hour)
l.Validate(val)
Expect(val.Error()).To(Succeed())

val = validator.New()
b = Base{Repeat: DurationMinutes(4*time.Hour + 1)}
b.Validate(val)
l = defaultAlert
l.Repeat = DurationMinutes(4*time.Hour + 1)
l.Validate(val)
Expect(val.Error()).NotTo(Succeed())

val = validator.New()
b = Base{Repeat: DurationMinutes(15*time.Minute - 1)}
b.Validate(val)
l = defaultAlert
l.Repeat = DurationMinutes(15*time.Minute - 1)
l.Validate(val)
Expect(val.Error()).NotTo(Succeed())
})
})
Expand All @@ -322,65 +325,65 @@ var _ = Describe("Config", func() {
err := request.DecodeObject(nil, buf, threshold)
Expect(err).To(MatchError("json is malformed"))
})
It("validates repeat minutes (negative)", func() {
})

Context("low", func() {
It("accepts a blank repeat", func() {
buf := buff(`{
"userId": "%s",
"followedUserId": "%s",
"urgentLow": {
"enabled": false,
"repeat": -11,
"low": {
"enabled": true,
"delay": 10,
"threshold": {
"units": "%s",
"value": 47.5
"units": "mg/dL",
"value": 80
}
}
}`, mockUserID1, mockUserID2, glucose.MgdL)
cfg := &Config{}
err := request.DecodeObject(nil, buf, cfg)
Expect(err).To(MatchError("value -11m0s is not greater than or equal to 15m0s"))
}`, mockUserID1, mockUserID2)
conf := &Config{}
err := request.DecodeObject(nil, buf, conf)
Expect(err).To(Succeed())
Expect(conf.Low.Repeat).To(Equal(DurationMinutes(0)))
})
It("validates repeat minutes (string)", func() {
buf := buff(`{
})
It("validates repeat minutes (negative)", func() {
buf := buff(`{
"userId": "%s",
"followedUserId": "%s",
"urgentLow": {
"low": {
"enabled": false,
"repeat": "a",
"repeat": -11,
"threshold": {
"units": "%s",
"value": 1
"value": 47.5
}
}
}`, mockUserID1, mockUserID2, glucose.MgdL)
cfg := &Config{}
err := request.DecodeObject(nil, buf, cfg)
Expect(err).To(MatchError("json is malformed"))
})
cfg := &Config{}
err := request.DecodeObject(nil, buf, cfg)
Expect(err).To(MatchError("value -11m0s is not greater than or equal to 15m0s"))
})

Context("low", func() {
It("accepts a blank repeat", func() {
buf := buff(`{
It("validates repeat minutes (string)", func() {
buf := buff(`{
"userId": "%s",
"followedUserId": "%s",
"low": {
"enabled": true,
"delay": 10,
"enabled": false,
"repeat": "a",
"threshold": {
"units": "mg/dL",
"value": 80
"units": "%s",
"value": 1
}
}
}`, mockUserID1, mockUserID2)
conf := &Config{}
err := request.DecodeObject(nil, buf, conf)
Expect(err).To(Succeed())
Expect(conf.Low.Repeat).To(Equal(DurationMinutes(0)))
})
}`, mockUserID1, mockUserID2, glucose.MgdL)
cfg := &Config{}
err := request.DecodeObject(nil, buf, cfg)
Expect(err).To(MatchError("json is malformed"))
})
})

var _ = Describe("Duration", func() {
var _ = Describe("DurationMinutes", func() {
It("parses 42", func() {
d := DurationMinutes(0)
err := d.UnmarshalJSON([]byte(`42`))
Expand Down

0 comments on commit e926564

Please sign in to comment.