Skip to content

Commit 506bb91

Browse files
committed
fix: make sla metric timestamps nullable
1 parent d1478e1 commit 506bb91

File tree

5 files changed

+27
-26
lines changed

5 files changed

+27
-26
lines changed

cmd/sla.go

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ func validateSLA(app *App, sla *smodels.SLAPolicy) error {
110110
if sla.Name == "" {
111111
return envelope.NewError(envelope.InputError, app.i18n.Ts("globals.messages.empty", "name", "`name`"), nil)
112112
}
113-
if sla.FirstResponseTime == "" && sla.NextResponseTime == "" && sla.ResolutionTime == "" {
113+
if sla.FirstResponseTime.String == "" && sla.NextResponseTime.String == "" && sla.ResolutionTime.String == "" {
114114
return envelope.NewError(envelope.InputError, app.i18n.Ts("globals.messages.empty", "name", "At least one of `first_response_time`, `next_response_time`, or `resolution_time` must be provided."), nil)
115115
}
116116

@@ -144,8 +144,8 @@ func validateSLA(app *App, sla *smodels.SLAPolicy) error {
144144
}
145145

146146
// Validate first response time duration string if not empty.
147-
if sla.FirstResponseTime != "" {
148-
frt, err := time.ParseDuration(sla.FirstResponseTime)
147+
if sla.FirstResponseTime.String != "" {
148+
frt, err := time.ParseDuration(sla.FirstResponseTime.String)
149149
if err != nil {
150150
return envelope.NewError(envelope.InputError, app.i18n.Ts("globals.messages.invalid", "name", "`first_response_time`"), nil)
151151
}
@@ -155,26 +155,26 @@ func validateSLA(app *App, sla *smodels.SLAPolicy) error {
155155
}
156156

157157
// Validate resolution time duration string if not empty.
158-
if sla.ResolutionTime != "" {
159-
rt, err := time.ParseDuration(sla.ResolutionTime)
158+
if sla.ResolutionTime.String != "" {
159+
rt, err := time.ParseDuration(sla.ResolutionTime.String)
160160
if err != nil {
161161
return envelope.NewError(envelope.InputError, app.i18n.Ts("globals.messages.invalid", "name", "`resolution_time`"), nil)
162162
}
163163
if rt.Minutes() < 1 {
164164
return envelope.NewError(envelope.InputError, app.i18n.Ts("globals.messages.invalid", "name", "`resolution_time`"), nil)
165165
}
166166
// Compare with first response time if both are present.
167-
if sla.FirstResponseTime != "" {
168-
frt, _ := time.ParseDuration(sla.FirstResponseTime)
167+
if sla.FirstResponseTime.String != "" {
168+
frt, _ := time.ParseDuration(sla.FirstResponseTime.String)
169169
if frt > rt {
170170
return envelope.NewError(envelope.InputError, app.i18n.T("sla.firstResponseTimeAfterResolution"), nil)
171171
}
172172
}
173173
}
174174

175175
// Validate next response time duration string if not empty.
176-
if sla.NextResponseTime != "" {
177-
nrt, err := time.ParseDuration(sla.NextResponseTime)
176+
if sla.NextResponseTime.String != "" {
177+
nrt, err := time.ParseDuration(sla.NextResponseTime.String)
178178
if err != nil {
179179
return envelope.NewError(envelope.InputError, app.i18n.Ts("globals.messages.invalid", "name", "`next_response_time`"), nil)
180180
}

frontend/src/features/admin/sla/formSchema.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,13 @@ export const createFormSchema = (t) =>
1212
.string()
1313
.min(1, { message: t('admin.sla.description.valid') })
1414
.max(255, { message: t('admin.sla.description.valid') }),
15-
first_response_time: z.string().optional().refine(val => !val || isGoHourMinuteDuration(val), {
15+
first_response_time: z.string().nullable().optional().refine(val => !val || isGoHourMinuteDuration(val), {
1616
message: t('globals.messages.goHourMinuteDuration'),
1717
}),
18-
resolution_time: z.string().optional().refine(val => !val || isGoHourMinuteDuration(val), {
18+
resolution_time: z.string().nullable().optional().refine(val => !val || isGoHourMinuteDuration(val), {
1919
message: t('globals.messages.goHourMinuteDuration'),
2020
}),
21-
next_response_time: z.string().optional().refine(val => !val || isGoHourMinuteDuration(val), {
21+
next_response_time: z.string().nullable().optional().refine(val => !val || isGoHourMinuteDuration(val), {
2222
message: t('globals.messages.goHourMinuteDuration'),
2323
}),
2424
notifications: z

internal/conversation/message.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -205,8 +205,8 @@ func (m *Manager) sendOutgoingMessage(message models.Message) {
205205

206206
// Mark latest SLA event for next response as met.
207207
metAt, err := m.slaStore.SetLatestSLAEventMetAt(conversation.AppliedSLAID.Int, sla.MetricNextResponse)
208-
if err != nil {
209-
m.lo.Error("error setting next response SLA event met at", "conversation_id", conversation.ID, "error", err)
208+
if err != nil && !errors.Is(err, sla.ErrLatestSLAEventNotFound) {
209+
m.lo.Error("error setting next response SLA event `met_at`", "conversation_id", conversation.ID, "metric", sla.MetricNextResponse, "applied_sla_id", conversation.AppliedSLAID.Int, "error", err)
210210
} else if !metAt.IsZero() {
211211
m.BroadcastConversationUpdate(message.ConversationUUID, "next_response_met_at", metAt.Format(time.RFC3339))
212212
}

internal/sla/models/models.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,11 @@ type SLAPolicy struct {
1616
CreatedAt time.Time `db:"created_at" json:"created_at"`
1717
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
1818
Name string `db:"name" json:"name"`
19-
Description string `db:"description" json:"description,omitempty"`
20-
FirstResponseTime string `db:"first_response_time" json:"first_response_time,omitempty"`
21-
NextResponseTime string `db:"next_response_time" json:"next_response_time,omitempty"`
22-
ResolutionTime string `db:"resolution_time" json:"resolution_time,omitempty"`
23-
Notifications SlaNotifications `db:"notifications" json:"notifications,omitempty"`
19+
Description string `db:"description" json:"description"`
20+
FirstResponseTime null.String `db:"first_response_time" json:"first_response_time"`
21+
NextResponseTime null.String `db:"next_response_time" json:"next_response_time"`
22+
ResolutionTime null.String `db:"resolution_time" json:"resolution_time"`
23+
Notifications SlaNotifications `db:"notifications" json:"notifications"`
2424
}
2525

2626
type SlaNotifications []SlaNotification

internal/sla/sla.go

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ var (
3434
//go:embed queries.sql
3535
efs embed.FS
3636
ErrUnmetSLAEventAlreadyExists = errors.New("unmet SLA event already exists, cannot create a new one for the same applied SLA and metric")
37+
ErrLatestSLAEventNotFound = errors.New("latest SLA event not found for the applied SLA and metric")
3738
)
3839

3940
const (
@@ -162,7 +163,7 @@ func (m *Manager) GetAll() ([]models.SLAPolicy, error) {
162163
}
163164

164165
// Create creates a new SLA policy.
165-
func (m *Manager) Create(name, description string, firstResponseTime, resolutionTime, nextResponseTime string, notifications models.SlaNotifications) error {
166+
func (m *Manager) Create(name, description string, firstResponseTime, resolutionTime, nextResponseTime null.String, notifications models.SlaNotifications) error {
166167
if _, err := m.q.InsertSLA.Exec(name, description, firstResponseTime, resolutionTime, nextResponseTime, notifications); err != nil {
167168
m.lo.Error("error inserting SLA", "error", err)
168169
return envelope.NewError(envelope.GeneralError, m.i18n.Ts("globals.messages.errorCreating", "name", "{globals.terms.sla}"), nil)
@@ -171,7 +172,7 @@ func (m *Manager) Create(name, description string, firstResponseTime, resolution
171172
}
172173

173174
// Update updates a SLA policy.
174-
func (m *Manager) Update(id int, name, description string, firstResponseTime, resolutionTime, nextResponseTime string, notifications models.SlaNotifications) error {
175+
func (m *Manager) Update(id int, name, description string, firstResponseTime, resolutionTime, nextResponseTime null.String, notifications models.SlaNotifications) error {
175176
if _, err := m.q.UpdateSLA.Exec(id, name, description, firstResponseTime, resolutionTime, nextResponseTime, notifications); err != nil {
176177
m.lo.Error("error updating SLA", "error", err)
177178
return envelope.NewError(envelope.GeneralError, m.i18n.Ts("globals.messages.errorUpdating", "name", "{globals.terms.sla}"), nil)
@@ -220,13 +221,13 @@ func (m *Manager) GetDeadlines(startTime time.Time, slaPolicyID, assignedTeamID
220221
return deadline, nil
221222
}
222223

223-
if deadlines.FirstResponse, err = calculateDeadline(sla.FirstResponseTime); err != nil {
224+
if deadlines.FirstResponse, err = calculateDeadline(sla.FirstResponseTime.String); err != nil {
224225
return deadlines, err
225226
}
226-
if deadlines.Resolution, err = calculateDeadline(sla.ResolutionTime); err != nil {
227+
if deadlines.Resolution, err = calculateDeadline(sla.ResolutionTime.String); err != nil {
227228
return deadlines, err
228229
}
229-
if deadlines.NextResponse, err = calculateDeadline(sla.NextResponseTime); err != nil {
230+
if deadlines.NextResponse, err = calculateDeadline(sla.NextResponseTime.String); err != nil {
230231
return deadlines, err
231232
}
232233
return deadlines, nil
@@ -278,7 +279,7 @@ func (m *Manager) CreateNextResponseSLAEvent(conversationID, appliedSLAID, slaPo
278279
return time.Time{}, fmt.Errorf("fetching SLA policy: %w", err)
279280
}
280281

281-
if slaPolicy.NextResponseTime == "" {
282+
if slaPolicy.NextResponseTime.String == "" {
282283
m.lo.Info("no next response time set for SLA policy, skipping event creation",
283284
"conversation_id", conversationID,
284285
"policy_id", slaPolicyID,
@@ -345,7 +346,7 @@ func (m *Manager) SetLatestSLAEventMetAt(appliedSLAID int, metric string) (time.
345346
if err := m.q.SetLatestSLAEventMetAt.QueryRow(appliedSLAID, metric).Scan(&metAt); err != nil {
346347
if err == sql.ErrNoRows {
347348
m.lo.Warn("no SLA event found for applied SLA and metric to update met at", "applied_sla_id", appliedSLAID, "metric", metric)
348-
return metAt, fmt.Errorf("no SLA event found for applied SLA ID: %d and metric: %s to update met at", appliedSLAID, metric)
349+
return metAt, ErrLatestSLAEventNotFound
349350
}
350351
m.lo.Error("error marking SLA event as met", "error", err)
351352
return metAt, fmt.Errorf("marking SLA event as met: %w", err)

0 commit comments

Comments
 (0)