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
67 changes: 44 additions & 23 deletions internal/api/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,36 @@ func parseIncidentData(d incidentResponseData) Incident {
return incident
}

// labelEntry represents a single key-value label.
type labelEntry struct {
Key string `json:"key"`
Value interface{} `json:"value"`
}

// flexibleLabels handles the API returning labels as either an array of {key,value}
// objects or as a plain object/map (e.g. empty {} or {"key":"value"}).
type flexibleLabels []labelEntry

func (f *flexibleLabels) UnmarshalJSON(data []byte) error {
// Try array first (normal case)
var arr []labelEntry
if err := json.Unmarshal(data, &arr); err == nil {
*f = arr
return nil
}
// Fall back to object/map
var m map[string]interface{}
if err := json.Unmarshal(data, &m); err != nil {
return err
}
result := make(flexibleLabels, 0, len(m))
for k, v := range m {
result = append(result, labelEntry{Key: k, Value: v})
}
*f = result
return nil
}

// incidentDetailResponse is the full JSON:API response for a single incident with includes.
type incidentDetailResponse struct {
Data struct {
Expand Down Expand Up @@ -427,10 +457,7 @@ type incidentDetailAttributes struct {
RetrospectiveProgressStatus *string `json:"retrospective_progress_status"`
SlackChannelName *string `json:"slack_channel_name"`
// Labels
Labels []struct {
Key string `json:"key"`
Value interface{} `json:"value"`
} `json:"labels"`
Labels flexibleLabels `json:"labels"`
// Integration links
SlackChannelURL *string `json:"slack_channel_url"`
JiraIssueURL *string `json:"jira_issue_url"`
Expand Down Expand Up @@ -1195,10 +1222,7 @@ type alertResponseData struct {
Groups []struct {
Name string `json:"name"`
} `json:"groups"`
Labels []struct {
Key string `json:"key"`
Value interface{} `json:"value"`
} `json:"labels"`
Labels flexibleLabels `json:"labels"`
} `json:"attributes"`
}

Expand Down Expand Up @@ -1401,21 +1425,18 @@ func (c *Client) GetAlertByID(ctx context.Context, id string) (*Alert, error) {
Data struct {
ID string `json:"id"`
Attributes struct {
ShortID *string `json:"short_id"`
Summary string `json:"summary"`
Description *string `json:"description"`
Status string `json:"status"`
Source *string `json:"source"`
ExternalURL *string `json:"external_url"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
StartedAt *string `json:"started_at"`
EndedAt *string `json:"ended_at"`
Labels []struct {
Key string `json:"key"`
Value interface{} `json:"value"`
} `json:"labels"`
Services []struct {
ShortID *string `json:"short_id"`
Summary string `json:"summary"`
Description *string `json:"description"`
Status string `json:"status"`
Source *string `json:"source"`
ExternalURL *string `json:"external_url"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
StartedAt *string `json:"started_at"`
EndedAt *string `json:"ended_at"`
Labels flexibleLabels `json:"labels"`
Services []struct {
Name string `json:"name"`
} `json:"services"`
Environments []struct {
Expand Down
51 changes: 47 additions & 4 deletions internal/api/client_resources_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1316,10 +1316,7 @@ func TestParseAlertData(t *testing.T) {
d.Attributes.Groups = []struct {
Name string `json:"name"`
}{{Name: "SRE"}}
d.Attributes.Labels = []struct {
Key string `json:"key"`
Value interface{} `json:"value"`
}{{Key: "host", Value: "web-1"}}
d.Attributes.Labels = flexibleLabels{{Key: "host", Value: "web-1"}}

alert := parseAlertData(d)

Expand Down Expand Up @@ -1365,3 +1362,49 @@ func TestParseAlertDataMinimal(t *testing.T) {
t.Errorf("Status = %q, want empty", alert.Status)
}
}

func TestFlexibleLabels_Array(t *testing.T) {
input := `[{"key":"host","value":"web-1"},{"key":"count","value":42}]`
var labels flexibleLabels
if err := json.Unmarshal([]byte(input), &labels); err != nil {
t.Fatalf("Unmarshal array: %v", err)
}
if len(labels) != 2 {
t.Fatalf("got %d labels, want 2", len(labels))
}
if labels[0].Key != "host" || labels[0].Value != "web-1" {
t.Errorf("labels[0] = %+v, want {host web-1}", labels[0])
}
}

func TestFlexibleLabels_EmptyObject(t *testing.T) {
input := `{}`
var labels flexibleLabels
if err := json.Unmarshal([]byte(input), &labels); err != nil {
t.Fatalf("Unmarshal empty object: %v", err)
}
if len(labels) != 0 {
t.Errorf("got %d labels, want 0", len(labels))
}
}

func TestFlexibleLabels_ObjectWithKeys(t *testing.T) {
input := `{"env":"prod","region":"us-east-1"}`
var labels flexibleLabels
if err := json.Unmarshal([]byte(input), &labels); err != nil {
t.Fatalf("Unmarshal object: %v", err)
}
if len(labels) != 2 {
t.Fatalf("got %d labels, want 2", len(labels))
}
m := make(map[string]interface{})
for _, l := range labels {
m[l.Key] = l.Value
}
if m["env"] != "prod" {
t.Errorf("env = %v, want prod", m["env"])
}
if m["region"] != "us-east-1" {
t.Errorf("region = %v, want us-east-1", m["region"])
}
}