Skip to content

Commit

Permalink
feat: Add TimeRange to Replay [PC-13699] (#528)
Browse files Browse the repository at this point in the history
Add support for `TimeRange` in Replay to specify the `startDate`.

---------

Co-authored-by: marcinlawnik <marcin.lawniczak@nobl9.com>
Co-authored-by: Mateusz Karasiewicz <100848248+mkaras-nobl9@users.noreply.github.com>
  • Loading branch information
3 people authored Sep 20, 2024
1 parent 7132d7e commit ab0bac7
Show file tree
Hide file tree
Showing 2 changed files with 128 additions and 6 deletions.
65 changes: 59 additions & 6 deletions sdk/models/replay.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,22 @@ const maximumAllowedReplayDuration = time.Hour * 24 * 30

// Replay Struct used for posting replay entity.
type Replay struct {
Project string `json:"project"`
Slo string `json:"slo"`
Duration ReplayDuration `json:"duration"`
Project string `json:"project"`
Slo string `json:"slo"`
Duration ReplayDuration `json:"duration,omitempty"`
TimeRange ReplayTimeRange `json:"timeRange,omitempty"`
}

type ReplayDuration struct {
Unit string `json:"unit"`
Value int `json:"value"`
}

type ReplayTimeRange struct {
StartDate time.Time `json:"startDate,omitempty"`
EndDate time.Time `json:"endDate,omitempty"` // not supported yet
}

// ReplayWithStatus used for returning Replay data with status.
type ReplayWithStatus struct {
Project string `json:"project"`
Expand Down Expand Up @@ -72,10 +78,30 @@ var replayValidation = govy.New[Replay](
Required(),
govy.For(func(r Replay) ReplayDuration { return r.Duration }).
WithName("duration").
Required().
When(
func(r Replay) bool {
return !isEmpty(r.Duration) || (r.TimeRange.StartDate.IsZero() && isEmpty(r.Duration))
},
).
Cascade(govy.CascadeModeStop).
Include(replayDurationValidation).
Rules(replayDurationValidationRule()),
govy.For(func(r Replay) time.Time { return r.TimeRange.StartDate }).
WithName("startDate").
When(
func(r Replay) bool { return !r.TimeRange.StartDate.IsZero() },
).
Rules(
replayStartTimeValidationRule(),
replayStartTimeNotInFutureValidationRule(),
),
govy.For(func(r Replay) Replay { return r }).
Rules(govy.NewRule(func(r Replay) error {
if !isEmpty(r.Duration) && !r.TimeRange.StartDate.IsZero() {
return errors.New("only one of duration or startDate can be set")
}
return nil
}).WithErrorCode(replayDurationAndStartDateValidationError)),
)

var replayDurationValidation = govy.New[ReplayDuration](
Expand All @@ -98,8 +124,10 @@ func (r Replay) Validate() error {
}

const (
replayDurationValidationErrorCode = "replay_duration"
replayDurationUnitValidationErrorCode = "replay_duration_unit"
replayDurationValidationErrorCode = "replay_duration"
replayDurationUnitValidationErrorCode = "replay_duration_unit"
replayDurationAndStartDateValidationError = "replay_duration_or_start_date"
replayStartDateInTheFutureValidationError = "replay_duration_or_start_date_future"
)

func replayDurationValidationRule() govy.Rule[ReplayDuration] {
Expand All @@ -116,6 +144,27 @@ func replayDurationValidationRule() govy.Rule[ReplayDuration] {
}).WithErrorCode(replayDurationValidationErrorCode)
}

func replayStartTimeValidationRule() govy.Rule[time.Time] {
return govy.NewRule(func(v time.Time) error {
duration := time.Since(v)
if duration > maximumAllowedReplayDuration {
return errors.Errorf("%s duration must not be greater than %s",
duration, maximumAllowedReplayDuration)
}
return nil
}).WithErrorCode(replayDurationValidationErrorCode)
}

func replayStartTimeNotInFutureValidationRule() govy.Rule[time.Time] {
return govy.NewRule(func(v time.Time) error {
now := time.Now()
if v.After(now) {
return errors.Errorf("startDate %s must not be in the future", v)
}
return nil
}).WithErrorCode(replayStartDateInTheFutureValidationError)
}

// ParseJSONToReplayStruct parse raw json into v1alpha.Replay struct with govy.
func ParseJSONToReplayStruct(data io.Reader) (Replay, error) {
replay := Replay{}
Expand Down Expand Up @@ -168,3 +217,7 @@ func ValidateReplayDurationUnit(unit string) error {
}
return ErrInvalidReplayDurationUnit
}

func isEmpty(duration ReplayDuration) bool {
return duration.Unit == "" || duration.Value == 0
}
69 changes: 69 additions & 0 deletions sdk/models/replay_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,75 @@ func TestReplayStructDatesValidation(t *testing.T) {
isValid: false,
ErrorCode: rules.ErrorCodeRequired,
},
{
name: "correct struct start date",
replay: Replay{
Project: "project",
Slo: "slo",
TimeRange: ReplayTimeRange{
StartDate: time.Now().Add(-time.Hour * 24),
},
},
isValid: true,
},
{
name: "only one of duration or start date can be set",
replay: Replay{
Project: "project",
Slo: "slo",
Duration: ReplayDuration{
Unit: "Day",
Value: 30,
},
TimeRange: ReplayTimeRange{
StartDate: time.Now().Add(-time.Hour * 24),
},
},
isValid: false,
ErrorCode: replayDurationAndStartDateValidationError,
},
{
name: "start date cannot be in the future",
replay: Replay{
Project: "project",
Slo: "slo",
TimeRange: ReplayTimeRange{
StartDate: time.Now().Add(time.Minute * 1),
},
},
isValid: false,
ErrorCode: replayStartDateInTheFutureValidationError,
},
{
name: "use start date without duration",
replay: Replay{
Project: "project",
Slo: "slo",
Duration: ReplayDuration{
Unit: "",
Value: 0,
},
TimeRange: ReplayTimeRange{
StartDate: time.Now().Add(-time.Hour * 24),
},
},
isValid: true,
},
{
name: "only one of duration",
replay: Replay{
Project: "project",
Slo: "slo",
Duration: ReplayDuration{
Unit: "Day",
Value: 30,
},
TimeRange: ReplayTimeRange{
StartDate: time.Date(1, 1, 1, 0, 0, 0, 0, time.UTC),
},
},
isValid: true,
},
}

for _, tt := range tests {
Expand Down

0 comments on commit ab0bac7

Please sign in to comment.