Skip to content

Commit

Permalink
Pass explicit list of users to GetIncidents
Browse files Browse the repository at this point in the history
This will replace the client-side filtering of issues to remove issues
assigned to ignored users with an explicit list of userIDs to retrieve
issues for,  retrieved from the membership of the Teams specified in the config,
reducing the total volume of issues returned from PagerDuty.

Fixes #4

Signed-off-by: Chris Collins <[email protected]>
  • Loading branch information
clcollins committed Mar 5, 2024
1 parent 648bdc9 commit 3ef8cd7
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 57 deletions.
60 changes: 44 additions & 16 deletions pkg/pd/pd.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ type PagerDutyClientInterface interface {
GetCurrentUserWithContext(ctx context.Context, opts pagerduty.GetCurrentUserOptions) (*pagerduty.User, error)
GetIncidentWithContext(ctx context.Context, id string) (*pagerduty.Incident, error)
GetTeamWithContext(ctx context.Context, id string) (*pagerduty.Team, error)
ListMembersWithContext(ctx context.Context, id string, opts pagerduty.ListTeamMembersOptions) (*pagerduty.ListTeamMembersResponse, error)
GetUserWithContext(ctx context.Context, id string, opts pagerduty.GetUserOptions) (*pagerduty.User, error)
ListIncidentAlertsWithContext(ctx context.Context, id string, opts pagerduty.ListIncidentAlertsOptions) (*pagerduty.ListAlertsResponse, error)
ListIncidentsWithContext(ctx context.Context, opts pagerduty.ListIncidentsOptions) (*pagerduty.ListIncidentsResponse, error)
Expand All @@ -37,9 +38,13 @@ type PagerDutyClient interface {
// Config is a struct that holds the PagerDuty client used for all the PagerDuty calls, and the config info for
// teams, silent user, and ignored users
type Config struct {
Client PagerDutyClient
CurrentUser *pagerduty.User
Teams []*pagerduty.Team
Client PagerDutyClient
CurrentUser *pagerduty.User

// List of the users in the Teams
TeamsMemberIDs []string
Teams []*pagerduty.Team

SilentUser *pagerduty.User
IgnoredUsers []*pagerduty.User
}
Expand All @@ -52,23 +57,28 @@ func NewConfig(token string, teams []string, silentUser string, ignoredUsers []s

c.CurrentUser, err = c.Client.GetCurrentUserWithContext(context.Background(), pagerduty.GetCurrentUserOptions{})
if err != nil {
return &c, fmt.Errorf("NewConfig(): failed to retrieve PagerDuty user: %v", err)
return &c, fmt.Errorf("pd.NewConfig(): failed to retrieve PagerDuty user: %v", err)
}

c.Teams, err = GetTeams(c.Client, teams)
if err != nil {
return &c, fmt.Errorf("NewConfig(): failed to get team(s) `%v`: %v", teams, err)
return &c, fmt.Errorf("pd.NewConfig(): failed to get team(s) `%v`: %v", teams, err)
}

c.TeamsMemberIDs, err = GetTeamMemberIDs(c.Client, c.Teams)
if err != nil {
return &c, fmt.Errorf("pd.NewConfig(): failed to get users(s) from teams: %v", err)
}

c.SilentUser, err = GetUser(c.Client, silentUser, pagerduty.GetUserOptions{})
if err != nil {
return &c, fmt.Errorf("NewConfig(): failed to get silent user: %v", err)
return &c, fmt.Errorf("pd.NewConfig(): failed to get silent user: %v", err)
}

for _, i := range ignoredUsers {
user, err := GetUser(c.Client, i, pagerduty.GetUserOptions{})
if err != nil {
return &c, fmt.Errorf("NewConfig(): failed to get user for ignore list `%v`: %v", i, err)
return &c, fmt.Errorf("pd.NewConfig(): failed to get user for ignore list `%v`: %v", i, err)
}
c.IgnoredUsers = append(c.IgnoredUsers, user)
}
Expand Down Expand Up @@ -108,13 +118,13 @@ func AcknowledgeIncident(client PagerDutyClient, incidents []*pagerduty.Incident
for {
response, err := client.ManageIncidentsWithContext(ctx, user.Email, opts)
if err != nil {
return i, fmt.Errorf("AcknowledgeIncident(): failed to acknowledge incident(s) `%v`: %v", incidents, err)
return i, fmt.Errorf("pd.AcknowledgeIncident(): failed to acknowledge incident(s) `%v`: %v", incidents, err)
}

i = append(i, response.Incidents...)

if response.More {
panic("AcknowledgeIncident(): PagerDuty response indicated more data available")
panic("pd.AcknowledgeIncident(): PagerDuty response indicated more data available")
}

if !response.More {
Expand All @@ -133,7 +143,7 @@ func GetAlerts(client PagerDutyClient, id string, opts pagerduty.ListIncidentAle
for {
response, err := client.ListIncidentAlertsWithContext(ctx, id, opts)
if err != nil {
return a, fmt.Errorf("GetAlerts(): failed to get alerts for incident `%v`: %v", id, err)
return a, fmt.Errorf("pd.GetAlerts(): failed to get alerts for incident `%v`: %v", id, err)
}

a = append(a, response.Alerts...)
Expand All @@ -154,7 +164,7 @@ func GetIncident(client PagerDutyClient, id string) (*pagerduty.Incident, error)

i, err := client.GetIncidentWithContext(ctx, id)
if err != nil {
return i, fmt.Errorf("GetIncident(): failed to get incident `%v`: %v", id, err)
return i, fmt.Errorf("pd.GetIncident(): failed to get incident `%v`: %v", id, err)
}

return i, nil
Expand All @@ -167,7 +177,7 @@ func GetIncidents(client PagerDutyClient, opts pagerduty.ListIncidentsOptions) (
for {
response, err := client.ListIncidentsWithContext(ctx, opts)
if err != nil {
return i, fmt.Errorf("GetIncidents(): failed to get incidents : %v", err)
return i, fmt.Errorf("pd.GetIncidents(): failed to get incidents : %v", err)
}

i = append(i, response.Incidents...)
Expand All @@ -188,7 +198,7 @@ func GetNotes(client PagerDutyClient, id string) ([]pagerduty.IncidentNote, erro

n, err := client.ListIncidentNotesWithContext(ctx, id)
if err != nil {
return n, fmt.Errorf("GetNotes(): failed to get incident notes `%v`: %v", id, err)
return n, fmt.Errorf("pd.GetNotes(): failed to get incident notes `%v`: %v", id, err)
}

return n, nil
Expand All @@ -201,21 +211,39 @@ func GetTeams(client PagerDutyClient, teams []string) ([]*pagerduty.Team, error)
for _, i := range teams {
team, err := client.GetTeamWithContext(ctx, i)
if err != nil {
return t, fmt.Errorf("GetTeams(): failed to find PagerDuty team `%v`: %v", i, err)
return t, fmt.Errorf("pd.GetTeams(): failed to find PagerDuty team `%v`: %v", i, err)
}
t = append(t, team)
}

return t, nil
}

func GetTeamMemberIDs(client PagerDutyClient, teams []*pagerduty.Team) ([]string, error) {
var ctx = context.Background()
var u []string

for _, team := range teams {
response, err := client.ListMembersWithContext(ctx, team.ID, pagerduty.ListTeamMembersOptions{})
if err != nil {
return u, fmt.Errorf("pd.GetUsers(): failed to retrieve users for PagerDuty team `%v`: %v", team.ID, err)
}

for _, member := range response.Members {
u = append(u, member.User.ID)
}
}

return u, nil
}

func GetUser(client PagerDutyClient, id string, opts pagerduty.GetUserOptions) (*pagerduty.User, error) {
var ctx = context.Background()
var u *pagerduty.User

u, err := client.GetUserWithContext(ctx, id, opts)
if err != nil {
return u, fmt.Errorf("GetUser(): failed to find PagerDuty user `%v`: %v", id, err)
return u, fmt.Errorf("pd.GetUser(): failed to find PagerDuty user `%v`: %v", id, err)
}

return u, nil
Expand Down Expand Up @@ -251,7 +279,7 @@ func ReassignIncidents(client PagerDutyClient, incidents []*pagerduty.Incident,

if response.More {
// If we ever do get a "More" response, we we need to handle it, so panic to call attention to the problem
panic("ReassignIncidents(): PagerDuty response indicated more data available")
panic("pd.ReassignIncidents(): PagerDuty response indicated more data available")
}

i = append(i, response.Incidents...)
Expand Down
61 changes: 36 additions & 25 deletions pkg/tui/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"log"
"os"
"os/exec"
"slices"

"github.com/PagerDuty/go-pagerduty"
tea "github.com/charmbracelet/bubbletea"
Expand Down Expand Up @@ -61,11 +62,33 @@ type updatedIncidentListMsg struct {
}

func updateIncidentList(p *pd.Config) tea.Cmd {
debug("updateIncidentList")
return func() tea.Msg {
opts := pd.NewListIncidentOptsFromDefaults()
opts.TeamIDs = getTeamsAsStrings(p)

// Convert the list of *pagerduty.User to a slice of user IDs
ignoredUserIDs := func(u []*pagerduty.User) []string {
var l []string
for _, i := range u {
l = append(l, i.ID)
}
return l
}(p.IgnoredUsers)

// If the UserID from p.TeamMemberIDs is not in the ignoredUserIDs slice, add it to the opts.UserIDs slice
opts.UserIDs = func(a []string, i []string) []string {
var l []string
for _, u := range a {
if !slices.Contains(i, u) {
l = append(l, u)
}
}
return l
}(p.TeamsMemberIDs, ignoredUserIDs)

// Retrieve incidents assigned to the TeamIDs and filtered UserIDs
i, err := pd.GetIncidents(p.Client, opts)
debug(fmt.Sprintf("tui.updateIncidentList(): retrieved %v incidents after filtering", len(i)))
return updatedIncidentListMsg{i, err}
}
}
Expand All @@ -78,7 +101,6 @@ type renderedIncidentMsg struct {
}

func renderIncident(m *model) tea.Cmd {
debug("renderIncident")
return func() tea.Msg {
t, err := m.template()
if err != nil {
Expand All @@ -105,7 +127,6 @@ type gotIncidentMsg struct {
}

func getIncident(p *pd.Config, id string) tea.Cmd {
debug("getIncident")
return func() tea.Msg {
ctx := context.Background()
i, err := p.Client.GetIncidentWithContext(ctx, id)
Expand All @@ -119,7 +140,6 @@ type gotIncidentAlertsMsg struct {
}

func getIncidentAlerts(p *pd.Config, id string) tea.Cmd {
debug("getIncidentAlerts")
return func() tea.Msg {
a, err := pd.GetAlerts(p.Client, id, pagerduty.ListIncidentAlertsOptions{})
return gotIncidentAlertsMsg{a, err}
Expand Down Expand Up @@ -167,7 +187,6 @@ type editorFinishedMsg struct {
}

func openEditorCmd(editor []string) tea.Cmd {
debug("openEditorCmd")
var args []string

file, err := os.CreateTemp(os.TempDir(), "")
Expand Down Expand Up @@ -196,26 +215,24 @@ type ClusterLauncher struct {
}

func login(cluster string, launcher ClusterLauncher) tea.Cmd {
debug("login")

// Check if we have the necessary info to try to login
errs := []error{}
if launcher.Terminal == nil {
debug("Terminal is not set")
debug("tui.login(): Terminal is not set")
errs = append(errs, errors.New("terminal is not set"))
}
if launcher.Shell == nil {
debug("Shell is not set")
debug("tui.login(): Shell is not set")
errs = append(errs, errors.New("shell is not set"))
}
if launcher.ClusterLoginCommand == nil {
debug("ClusterLoginCommand is not set")
debug("tui.login(): ClusterLoginCommand is not set")
errs = append(errs, errors.New("ClusterLoginCommand is not set"))
}

if len(errs) > 0 {
err := fmt.Errorf("login error: %v", errs)
debug(err.Error())
debug(fmt.Sprintf("tui.login(): %v", err.Error()))
return func() tea.Msg {
return loginFinishedMsg(err)
}
Expand All @@ -232,33 +249,33 @@ func login(cluster string, launcher ClusterLauncher) tea.Cmd {
// This handles if folks use, eg: flatpak run <some package> as a terminal.
c := exec.Command(launcher.Terminal[0], args...)

debug(c.String())
debug(fmt.Sprintf("tui.login(): %v", c.String()))
stderr, pipeErr := c.StderrPipe()
if pipeErr != nil {
debug(pipeErr.Error())
debug(fmt.Sprintf("tui.login(): %v", pipeErr.Error()))
return func() tea.Msg {
return loginFinishedMsg(pipeErr)
}
}

err := c.Start()
if err != nil {
debug(err.Error())
debug(fmt.Sprintf("tui.login(): %v", err.Error()))
return func() tea.Msg {
return loginFinishedMsg(err)
}
}

out, err := io.ReadAll(stderr)
if err != nil {
debug(err.Error())
debug(fmt.Sprintf("tui.login(): %v", err.Error()))
return func() tea.Msg {
return loginFinishedMsg(err)
}
}

if len(out) > 0 {
debug(fmt.Sprintf("login error: %s", out))
debug(fmt.Sprintf("tui.login(): error: %s", out))
return func() tea.Msg {
return loginFinishedMsg(fmt.Errorf("%s", out))
}
Expand All @@ -282,7 +299,6 @@ type acknowledgedIncidentsMsg struct {
type waitForSelectedIncidentsThenAcknowledgeMsg string

func acknowledgeIncidents(p *pd.Config, incidents []*pagerduty.Incident) tea.Cmd {
debug("acknowledgeIncidents")
return func() tea.Msg {
u, err := p.Client.GetCurrentUserWithContext(context.Background(), pagerduty.GetCurrentUserOptions{})
if err != nil {
Expand All @@ -303,7 +319,6 @@ type reassignIncidentsMsg struct {
type reassignedIncidentsMsg []pagerduty.Incident

func reassignIncidents(p *pd.Config, i []*pagerduty.Incident, users []*pagerduty.User) tea.Cmd {
debug("reassignIncidents")
return func() tea.Msg {
u, err := p.Client.GetCurrentUserWithContext(context.Background(), pagerduty.GetCurrentUserOptions{})
if err != nil {
Expand All @@ -325,7 +340,6 @@ type waitForSelectedIncidentsThenSilenceMsg string
var errSilenceIncidentInvalidArgs = errors.New("silenceIncidents: invalid arguments")

func silenceIncidents(i []*pagerduty.Incident, u []*pagerduty.User) tea.Cmd {
debug("silenceIncidents")
// SilenceIncidents doesn't have it's own "silencedIncidentsMessage"
// because it's really just a reassignment
log.Printf("silence requested for incident(s) %v; reassigning to %v", i, u)
Expand All @@ -346,7 +360,6 @@ type addedIncidentNoteMsg struct {
}

func addNoteToIncident(p *pd.Config, incident *pagerduty.Incident, content *os.File) tea.Cmd {
debug("addNoteToIncident")
return func() tea.Msg {
defer content.Close()

Expand All @@ -366,8 +379,8 @@ func addNoteToIncident(p *pd.Config, incident *pagerduty.Incident, content *os.F
}
}

// getTeamsAsStrings returns a slice of team IDs as strings from the []*pagerduty.Teams in a *pd.Config
func getTeamsAsStrings(p *pd.Config) []string {
debug("getTeamsAsStrings")
var teams []string
for _, t := range p.Teams {
teams = append(teams, t.ID)
Expand All @@ -376,22 +389,20 @@ func getTeamsAsStrings(p *pd.Config) []string {
}

func getDetailFieldFromAlert(f string, a pagerduty.IncidentAlert) string {
debug("getDetailFieldFromAlert")
if a.Body["details"] != nil {

if a.Body["details"].(map[string]interface{})[f] != nil {
return a.Body["details"].(map[string]interface{})[f].(string)
}
debug(fmt.Sprintf("alert body \"details\" does not contain field %s", f))
debug(fmt.Sprintf("tui.getDetailFieldFromAlert(): alert body \"details\" does not contain field %s", f))
return ""
}
debug("alert body \"details\" is nil")
debug("tui.getDetailFieldFromAlert(): alert body \"details\" is nil")
return ""
}

// acknowledged returns "A" for "acknowledged" if the incident has been acknowledged, or a dot for "triggered" otherwise
func acknowledged(a []pagerduty.Acknowledgement) string {
debug("acknowledged")
if len(a) > 0 {
return "A"
}
Expand Down
Loading

0 comments on commit 3ef8cd7

Please sign in to comment.