Skip to content

Commit b12ba71

Browse files
authored
fix: support teams responder in incident_summary view (#194)
* fix: support teams responder in incident_summary view * test: added more test cases * incident with both team and person responders * a person being a responder to multiple incidents * use the team's icon as the avatar
1 parent 3f21678 commit b12ba71

File tree

5 files changed

+188
-57
lines changed

5 files changed

+188
-57
lines changed

fixtures/dummy/all.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,11 @@ func PopulateDBWithDummyModels(gormDB *gorm.DB) error {
6161
return err
6262
}
6363
}
64+
for _, c := range AllTeams {
65+
if err := gormDB.Create(&c).Error; err != nil {
66+
return err
67+
}
68+
}
6469
for _, c := range AllDummyIncidents {
6570
err = gormDB.Create(&c).Error
6671
if err != nil {
@@ -173,6 +178,11 @@ func DeleteDummyModelsFromDB(gormDB *gorm.DB) error {
173178
return err
174179
}
175180
}
181+
for _, c := range AllTeams {
182+
if err := gormDB.Delete(&c).Error; err != nil {
183+
return err
184+
}
185+
}
176186
for _, c := range AllDummyConfigChanges {
177187
err = gormDB.Delete(&c).Error
178188
if err != nil {

fixtures/dummy/incidents.go

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,17 @@ var LogisticsAPIDownIncident = models.Incident{
1717
CommanderID: &JohnDoe.ID,
1818
}
1919

20-
var AllDummyIncidents = []models.Incident{LogisticsAPIDownIncident}
20+
var UIDownIncident = models.Incident{
21+
ID: uuid.MustParse("0c00b8a6-5bf8-42a4-98fe-2d39ddcb67cb"),
22+
Title: "UI is down",
23+
CreatedBy: JohnDoe.ID,
24+
Type: models.IncidentTypeAvailability,
25+
Status: models.IncidentStatusOpen,
26+
Severity: "Blocker",
27+
CommanderID: &JohnWick.ID,
28+
}
29+
30+
var AllDummyIncidents = []models.Incident{LogisticsAPIDownIncident, UIDownIncident}
2131

2232
var FirstComment = models.Comment{
2333
ID: uuid.New(),
@@ -70,12 +80,52 @@ var GitHubIssueResponder = models.Responder{
7080

7181
var SlackResponder = models.Responder{
7282
ID: uuid.New(),
73-
IncidentID: LogisticsAPIDownIncident.ID,
83+
IncidentID: UIDownIncident.ID,
7484
Type: "Slack",
85+
TeamID: &BackendTeam.ID,
86+
CreatedBy: JohnDoe.ID,
87+
CreatedAt: time.Now(),
88+
UpdatedAt: time.Now(),
89+
}
90+
91+
var MsPlannerResponder = models.Responder{
92+
ID: uuid.New(),
93+
IncidentID: UIDownIncident.ID,
94+
Type: "MSPlanner",
95+
PersonID: &JohnWick.ID,
96+
CreatedBy: JohnDoe.ID,
97+
CreatedAt: time.Now(),
98+
UpdatedAt: time.Now(),
99+
}
100+
101+
var TelegramResponder = models.Responder{
102+
ID: uuid.New(),
103+
IncidentID: UIDownIncident.ID,
104+
Type: "Telegram",
75105
PersonID: &JohnDoe.ID,
76106
CreatedBy: JohnDoe.ID,
77107
CreatedAt: time.Now(),
78108
UpdatedAt: time.Now(),
79109
}
80110

81-
var AllDummyResponders = []models.Responder{JiraResponder, GitHubIssueResponder, SlackResponder}
111+
var AllDummyResponders = []models.Responder{JiraResponder, GitHubIssueResponder, SlackResponder, MsPlannerResponder, TelegramResponder}
112+
113+
var BackendTeam = models.Team{
114+
ID: uuid.New(),
115+
Name: "Backend",
116+
Icon: "backend",
117+
CreatedBy: JohnDoe.ID,
118+
CreatedAt: time.Now(),
119+
UpdatedAt: time.Now(),
120+
}
121+
122+
var FrontendTeam = models.Team{
123+
ID: uuid.New(),
124+
Name: "Frontend",
125+
Icon: "frontend",
126+
CreatedBy: JohnDoe.ID,
127+
CreatedAt: time.Now(),
128+
UpdatedAt: time.Now(),
129+
}
130+
131+
var AllTeams = []models.Team{BackendTeam, FrontendTeam}

incident_summary_test.go

Lines changed: 94 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
package duty
22

33
import (
4-
"context"
5-
"encoding/json"
4+
"database/sql/driver"
5+
"fmt"
6+
"time"
67

78
"github.com/flanksource/duty/fixtures/dummy"
9+
"github.com/flanksource/duty/models"
810
"github.com/flanksource/duty/testutils"
11+
"github.com/flanksource/duty/types"
12+
"github.com/google/uuid"
913
ginkgo "github.com/onsi/ginkgo/v2"
1014
. "github.com/onsi/gomega"
1115
)
@@ -17,58 +21,100 @@ type actor struct {
1721
Avatar string `json:"avatar"`
1822
}
1923

24+
func (t actor) Value() (driver.Value, error) {
25+
return types.GenericStructValue(t, true)
26+
}
27+
28+
func (t *actor) Scan(val any) error {
29+
return types.GenericStructScan(&t, val)
30+
}
31+
32+
func (t *actor) FromPerson(p models.Person) {
33+
t.Avatar = p.Avatar
34+
t.Name = p.Name
35+
t.ID = p.ID.String()
36+
}
37+
38+
func actorFromPerson(p models.Person) actor {
39+
var a actor
40+
a.FromPerson(p)
41+
return a
42+
}
43+
44+
type actors []actor
45+
46+
func (t actors) Value() (driver.Value, error) {
47+
return types.GenericStructValue(t, true)
48+
}
49+
50+
func (t *actors) Scan(val any) error {
51+
return types.GenericStructScan(&t, val)
52+
}
53+
54+
// IncidentSummary represents the incident_summary view
55+
type IncidentSummary struct {
56+
ID uuid.UUID
57+
IncidentID string
58+
Title string
59+
Severity string
60+
Type models.IncidentType
61+
Status models.IncidentStatus
62+
CreatedAt time.Time
63+
UpdatedAt time.Time
64+
Responders actors
65+
Commander actor
66+
Commenters actors
67+
}
68+
2069
var _ = ginkgo.Describe("Check incident_summary view", ginkgo.Ordered, func() {
2170
ginkgo.It("Should query incident_summary view", func() {
22-
row := testutils.TestDBPGPool.QueryRow(context.Background(), "SELECT id, incident_id, title, responders, commenters, commander FROM incident_summary")
23-
var id, incidentID, title string
24-
var respondersRaw, commentersRaw, commanderRaw json.RawMessage
25-
26-
err := row.Scan(&id, &incidentID, &title, &respondersRaw, &commentersRaw, &commanderRaw)
71+
var incidents []IncidentSummary
72+
err := testutils.TestDB.Raw("SELECT * FROM incident_summary").Scan(&incidents).Error
2773
Expect(err).ToNot(HaveOccurred())
2874

29-
Expect(id).To(Equal(dummy.LogisticsAPIDownIncident.ID.String()))
30-
Expect(title).To(Equal(dummy.LogisticsAPIDownIncident.Title))
75+
Expect(len(incidents)).To(Equal(len(dummy.AllDummyIncidents)))
3176

32-
Expect(incidentID).To(Equal("INC0000001"))
77+
for _, incidentSummary := range incidents {
78+
var (
79+
incident models.Incident
80+
commander actor
81+
responders actors
82+
commenters actors
83+
)
3384

34-
var commander actor
35-
err = json.Unmarshal(commanderRaw, &commander)
36-
Expect(err).ToNot(HaveOccurred())
37-
Expect(commander.ID).To(Equal(dummy.JohnDoe.ID.String()))
38-
Expect(commander.Name).To(Equal(dummy.JohnDoe.Name))
85+
switch incidentSummary.ID {
86+
case dummy.LogisticsAPIDownIncident.ID:
87+
incident = dummy.LogisticsAPIDownIncident
88+
commander.FromPerson(dummy.JohnDoe)
89+
responders = []actor{actorFromPerson(dummy.JohnDoe), actorFromPerson(dummy.JohnWick)}
90+
commenters = []actor{actorFromPerson(dummy.JohnDoe), actorFromPerson(dummy.JohnWick)}
3991

40-
var responders []actor
41-
err = json.Unmarshal(respondersRaw, &responders)
42-
Expect(err).ToNot(HaveOccurred())
43-
Expect(len(responders)).To(Equal(2))
44-
Expect(responders).To(Equal([]actor{
45-
{
46-
ID: dummy.JohnDoe.ID.String(),
47-
Name: dummy.JohnDoe.Name,
48-
Avatar: dummy.JohnDoe.Avatar,
49-
},
50-
{
51-
ID: dummy.JohnWick.ID.String(),
52-
Name: dummy.JohnWick.Name,
53-
Avatar: dummy.JohnWick.Avatar,
54-
},
55-
}))
56-
57-
var commenters []actor
58-
err = json.Unmarshal(commentersRaw, &commenters)
59-
Expect(err).ToNot(HaveOccurred())
60-
Expect(len(commenters)).To(Equal(2))
61-
Expect(commenters).To(Equal([]actor{
62-
{
63-
ID: dummy.JohnDoe.ID.String(),
64-
Name: dummy.JohnDoe.Name,
65-
Avatar: dummy.JohnDoe.Avatar,
66-
},
67-
{
68-
ID: dummy.JohnWick.ID.String(),
69-
Name: dummy.JohnWick.Name,
70-
Avatar: dummy.JohnWick.Avatar,
71-
},
72-
}))
92+
case dummy.UIDownIncident.ID:
93+
incident = dummy.UIDownIncident
94+
commander.FromPerson(dummy.JohnWick)
95+
responders = []actor{
96+
actorFromPerson(dummy.JohnDoe),
97+
actorFromPerson(dummy.JohnWick),
98+
{
99+
ID: dummy.BackendTeam.ID.String(),
100+
Avatar: dummy.BackendTeam.Icon,
101+
Name: dummy.BackendTeam.Name,
102+
},
103+
}
104+
105+
default:
106+
ginkgo.Fail(fmt.Sprintf("unexpected incident: %s", incidentSummary.Title))
107+
}
108+
109+
Expect(incidentSummary.ID).To(Equal(incident.ID))
110+
Expect(incidentSummary.IncidentID).To(BeElementOf([]string{"INC0000001", "INC0000002"}))
111+
Expect(incidentSummary.Title).To(Equal(incident.Title))
112+
Expect(incidentSummary.Severity).To(Equal(incident.Severity))
113+
Expect(incidentSummary.Type).To(Equal(incident.Type))
114+
Expect(incidentSummary.Status).To(Equal(incident.Status))
115+
Expect(incidentSummary.Commander).To(Equal(commander))
116+
Expect(incidentSummary.Responders).To(ConsistOf(responders))
117+
Expect(incidentSummary.Commenters).To(ConsistOf(commenters))
118+
}
73119
})
74120
})

models/teams.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package models
2+
3+
import (
4+
"time"
5+
6+
"github.com/flanksource/duty/types"
7+
"github.com/google/uuid"
8+
)
9+
10+
type Team struct {
11+
ID uuid.UUID `gorm:"default:generate_ulid()"`
12+
Name string `gorm:"not null" json:"name"`
13+
Icon string `json:"icon,omitempty"`
14+
Spec types.JSON `json:"spec,omitempty"`
15+
Source string `json:"source,omitempty"`
16+
CreatedBy uuid.UUID `gorm:"not null" json:"created_by"`
17+
CreatedAt time.Time `json:"created_at"`
18+
UpdatedAt time.Time `json:"updated_at"`
19+
DeletedAt *time.Time `json:"deleted_at,omitempty"`
20+
}

views/009_incident_views.sql

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -85,17 +85,22 @@ CREATE OR REPLACE VIEW incident_summary AS
8585
),
8686
distinct_responder AS (
8787
SELECT
88-
DISTINCT ON (people.id) people.id,
88+
DISTINCT ON (people.id, responders.incident_id) people.id as id,
8989
people.avatar,
9090
people.name,
9191
responders.incident_id
9292
FROM
9393
responders
94-
LEFT JOIN people ON responders.person_id = people.id
95-
WHERE
96-
people.id IS NOT NULL
97-
ORDER BY
98-
people.id
94+
INNER JOIN people ON responders.person_id = people.id
95+
UNION
96+
SELECT
97+
DISTINCT ON (teams.id, responders.incident_id) teams.id as id,
98+
teams.icon as avatar,
99+
teams.name,
100+
responders.incident_id
101+
FROM
102+
responders
103+
INNER JOIN teams ON responders.team_id = teams.id
99104
),
100105
responders AS (
101106
SELECT

0 commit comments

Comments
 (0)