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
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ tidy: ## Tidy up go modules
go mod tidy

.PHONY: test
test: ## Run tests
test: lint ## Run tests (after linting)
@echo "Running tests..."
go test ./... -v $(TESTOPTS)

Expand Down
18 changes: 13 additions & 5 deletions pkg/pd/pd.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"strings"

"github.com/PagerDuty/go-pagerduty"
"github.com/charmbracelet/log"
)

const (
Expand Down Expand Up @@ -115,21 +116,26 @@ func NewListIncidentOptsFromDefaults() pagerduty.ListIncidentsOptions {

}

func AcknowledgeIncident(client PagerDutyClient, incidents []pagerduty.Incident, user *pagerduty.User) ([]pagerduty.Incident, error) {
func AcknowledgeIncident(client PagerDutyClient, incidents []pagerduty.Incident, user *pagerduty.User, currentUser *pagerduty.User) ([]pagerduty.Incident, error) {
var ctx = context.Background()
var email string

opts := []pagerduty.ManageIncidentsOptions{}

// Use currentUser's email for API call authentication
if currentUser != nil {
email = currentUser.Email
}

for _, incident := range incidents {
if user == nil {
email = ""
// Un-acknowledge: set escalation level without assignment
opts = append(opts, pagerduty.ManageIncidentsOptions{
ID: incident.ID,
EscalationLevel: 1,
})
} else {
email = user.Email
// Acknowledge: assign to specific user
opts = append(opts, pagerduty.ManageIncidentsOptions{
ID: incident.ID,
Status: "acknowledged",
Expand Down Expand Up @@ -302,8 +308,10 @@ func GetUserOnCalls(client PagerDutyClient, id string, opts pagerduty.ListOnCall

func loopManageIncidents(client PagerDutyClient, ctx context.Context, email string, opts []pagerduty.ManageIncidentsOptions) (incidentList []pagerduty.Incident, err error) {
for {
log.Debug("pd.loopManageIncidents", "email", email, "opts_count", len(opts))
response, err := client.ManageIncidentsWithContext(ctx, email, opts)
if err != nil {
log.Error("pd.loopManageIncidents", "error", err, "email", email)
return incidentList, err
}

Expand Down Expand Up @@ -347,7 +355,7 @@ func ReassignIncidents(client PagerDutyClient, incidents []pagerduty.Incident, u
}

// ReEscalateIncidents re-escalates a list of incidents to an escalation policy at a specific level
func ReEscalateIncidents(client PagerDutyClient, incidents []pagerduty.Incident, policy *pagerduty.EscalationPolicy, level uint) ([]pagerduty.Incident, error) {
func ReEscalateIncidents(client PagerDutyClient, incidents []pagerduty.Incident, user *pagerduty.User, policy *pagerduty.EscalationPolicy, level uint) ([]pagerduty.Incident, error) {
var ctx = context.Background()

opts := []pagerduty.ManageIncidentsOptions{}
Expand All @@ -364,7 +372,7 @@ func ReEscalateIncidents(client PagerDutyClient, incidents []pagerduty.Incident,
})
}

return loopManageIncidents(client, ctx, "", opts)
return loopManageIncidents(client, ctx, user.Email, opts)
}

func PostNote(client PagerDutyClient, id string, user *pagerduty.User, content string) (*pagerduty.IncidentNote, error) {
Expand Down
27 changes: 10 additions & 17 deletions pkg/tui/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,13 @@ func ShouldBeAcknowledged(p *pd.Config, i pagerduty.Incident, id string, autoAck
"userIsOnCall", userIsOnCall,
"doIt", doIt,
)
return AssignedToUser(i, id) && !AcknowledgedByUser(i, id) && autoAcknowledge
return doIt
}

// ShouldBeAcknowledgedCached checks if an incident should be auto-acknowledged using a cached userIsOnCall result.
// This avoids repeated calls to UserIsOnCall() when checking multiple incidents in the same update cycle.
func ShouldBeAcknowledgedCached(i pagerduty.Incident, id string, userIsOnCall bool) bool {
return AssignedToUser(i, id) && !AcknowledgedByUser(i, id) && userIsOnCall
}

// AssignedToUser returns true if the incident is assigned to the given user
Expand Down Expand Up @@ -523,22 +529,9 @@ type acknowledgedIncidentsMsg struct {
incidents []pagerduty.Incident
err error
}
type unAcknowledgedIncidentsMsg struct {
incidents []pagerduty.Incident
err error
}

func acknowledgeIncidents(p *pd.Config, incidents []pagerduty.Incident, reEscalate bool) tea.Cmd {
func acknowledgeIncidents(p *pd.Config, incidents []pagerduty.Incident) tea.Cmd {
return func() tea.Msg {
var err error

if reEscalate {
a, err := pd.AcknowledgeIncident(p.Client, incidents, &pagerduty.User{})
return unAcknowledgedIncidentsMsg{a, err}
}

a, err := pd.AcknowledgeIncident(p.Client, incidents, p.CurrentUser)

a, err := pd.AcknowledgeIncident(p.Client, incidents, p.CurrentUser, p.CurrentUser)
return acknowledgedIncidentsMsg{a, err}
}
}
Expand Down Expand Up @@ -573,7 +566,7 @@ type reEscalatedIncidentsMsg []pagerduty.Incident

func reEscalateIncidents(p *pd.Config, i []pagerduty.Incident, e *pagerduty.EscalationPolicy, l uint) tea.Cmd {
return func() tea.Msg {
r, err := pd.ReEscalateIncidents(p.Client, i, e, l)
r, err := pd.ReEscalateIncidents(p.Client, i, p.CurrentUser, e, l)
if err != nil {
return errMsg{err}
}
Expand Down
36 changes: 8 additions & 28 deletions pkg/tui/commands_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,41 +30,21 @@ func TestAcknowledgeIncident(t *testing.T) {
}

tests := []struct {
name string
incidents []pagerduty.Incident
reEscalate bool
expected tea.Msg
name string
incidents []pagerduty.Incident
expected tea.Msg
}{
{
name: "return unAcknowledgedIncidentMsg with non-nil error if error occurs while re-escalating",
incidents: errIncidents,
reEscalate: true,
expected: unAcknowledgedIncidentsMsg{
incidents: []pagerduty.Incident(nil),
err: pd.ErrMockError,
},
},
{
name: "return unAcknowledgedIncidentMsg with an incident list if no error occurs while re-escalating",
incidents: incidents,
reEscalate: true,
expected: unAcknowledgedIncidentsMsg{
incidents: incidents,
},
},
{
name: "return acknowledgedIncidentMsg with non-nil error if error occurs while acknowledging",
incidents: errIncidents,
reEscalate: false,
name: "return acknowledgedIncidentMsg with non-nil error if error occurs while acknowledging",
incidents: errIncidents,
expected: acknowledgedIncidentsMsg{
incidents: []pagerduty.Incident(nil),
err: pd.ErrMockError,
},
},
{
name: "return acknowledgedIncidentMsg with an incident list if no error occurs while acknowledging",
incidents: incidents,
reEscalate: false,
name: "return acknowledgedIncidentMsg with an incident list if no error occurs while acknowledging",
incidents: incidents,
expected: acknowledgedIncidentsMsg{
incidents: incidents,
},
Expand All @@ -73,7 +53,7 @@ func TestAcknowledgeIncident(t *testing.T) {

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
cmd := acknowledgeIncidents(mockConfig, test.incidents, test.reEscalate)
cmd := acknowledgeIncidents(mockConfig, test.incidents)
actual := cmd()
assert.Equal(t, test.expected, actual)
})
Expand Down
23 changes: 17 additions & 6 deletions pkg/tui/msgHandlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ func switchTableFocusMode(m model, msg tea.Msg) (tea.Model, tea.Cmd) {
return waitForSelectedIncidentThenDoMsg{
msg: "acknowledge",
action: func() tea.Msg {
return acknowledgeIncidentsMsg{incidents: []pagerduty.Incident{*m.selectedIncident}}
return acknowledgeIncidentsMsg{}
},
}
},
Expand All @@ -282,7 +282,7 @@ func switchTableFocusMode(m model, msg tea.Msg) (tea.Model, tea.Cmd) {
return waitForSelectedIncidentThenDoMsg{
msg: "un-acknowledge",
action: func() tea.Msg {
return unAcknowledgeIncidentsMsg{incidents: []pagerduty.Incident{*m.selectedIncident}}
return unAcknowledgeIncidentsMsg{}
},
}
},
Expand Down Expand Up @@ -346,24 +346,31 @@ func switchIncidentFocusMode(m model, msg tea.Msg) (tea.Model, tea.Cmd) {
var cmd tea.Cmd
var cmds []tea.Cmd

// Track if we handled the key ourselves
handledKey := false

switch msg := msg.(type) {
case tea.KeyMsg:
switch {
case key.Matches(msg, defaultKeyMap.Help):
m.toggleHelp()
handledKey = true

// This un-sets the selected incident and returns to the table view
case key.Matches(msg, defaultKeyMap.Back):
m.clearSelectedIncident(msg.String() + " (back)")
m.table.Focus() // Ensure table regains focus immediately
// Return immediately - no need to process anything else or update viewport
return m, nil

case key.Matches(msg, defaultKeyMap.Refresh):
return m, func() tea.Msg { return getIncidentMsg(m.selectedIncident.ID) }

case key.Matches(msg, defaultKeyMap.Ack):
return m, func() tea.Msg { return acknowledgeIncidentsMsg{incidents: []pagerduty.Incident{*m.selectedIncident}} }
return m, func() tea.Msg { return acknowledgeIncidentsMsg{} }

case key.Matches(msg, defaultKeyMap.UnAck):
return m, func() tea.Msg { return unAcknowledgeIncidentsMsg{incidents: []pagerduty.Incident{*m.selectedIncident}} }
return m, func() tea.Msg { return unAcknowledgeIncidentsMsg{} }

case key.Matches(msg, defaultKeyMap.Silence):
return m, func() tea.Msg { return silenceSelectedIncidentMsg{} }
Expand Down Expand Up @@ -395,8 +402,12 @@ func switchIncidentFocusMode(m model, msg tea.Msg) (tea.Model, tea.Cmd) {
}
}

m.incidentViewer, cmd = m.incidentViewer.Update(msg)
cmds = append(cmds, cmd)
// Only pass the message to the viewport if we didn't handle it as a key command
// This prevents the viewport from consuming ESC and other navigation keys
if !handledKey {
m.incidentViewer, cmd = m.incidentViewer.Update(msg)
cmds = append(cmds, cmd)
}

return m, tea.Batch(cmds...)
}
Expand Down
Loading
Loading