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: 2 additions & 0 deletions .goreleaser.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ before:
builds:
- env:
- CGO_ENABLED=0
ldflags:
- -X github.com/clcollins/srepd/pkg/tui.GitSHA={{.ShortCommit}}
goos:
- linux
- darwin
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ build: ## Build the application
.PHONY: install
install: ## Install the application to $(GOPATH)/bin
@echo "Installing the application..."
go build -o ${BIN_DIR}/srepd .
go build -ldflags "-X github.com/clcollins/srepd/pkg/tui.GitSHA=$$(git rev-parse --short HEAD)" -o ${BIN_DIR}/srepd .

.PHONY: install-local
install-local: build ## Install the application locally to ~/.local/bin
Expand Down
28 changes: 0 additions & 28 deletions TODO.md

This file was deleted.

63 changes: 61 additions & 2 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,72 @@
log.Fatal(err)
}

f, err := os.OpenFile(home+"/.config/srepd/debug.log", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0o600) //nolint:gomnd
// Open log file, truncating if it exists to prevent unbounded growth
// TODO: Implement proper log rotation
f, err := os.OpenFile(home+"/.config/srepd/debug.log", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o600) //nolint:gomnd
if err != nil {
log.Fatal(err)
}
defer f.Close() //nolint:errcheck

log.SetOutput(f)
// Use async writer to prevent log I/O from blocking the UI
asyncWriter := newAsyncWriter(f, 1000) // Buffer up to 1000 log messages
defer asyncWriter.Close()

Check failure on line 47 in main.go

View workflow job for this annotation

GitHub Actions / Lint Code

Error return value of `asyncWriter.Close` is not checked (errcheck)

log.SetOutput(asyncWriter)

cmd.Execute()
}

// asyncWriter wraps an io.Writer and writes asynchronously via a channel
type asyncWriter struct {
out chan []byte
done chan struct{}
closed bool
}

func newAsyncWriter(w *os.File, bufferSize int) *asyncWriter {
aw := &asyncWriter{
out: make(chan []byte, bufferSize),
done: make(chan struct{}),
}

// Start background goroutine to write logs
go func() {
for msg := range aw.out {
w.Write(msg) //nolint:errcheck
}
close(aw.done)
}()

return aw
}

func (aw *asyncWriter) Write(p []byte) (n int, err error) {
if aw.closed {
return 0, os.ErrClosed
}

// Make a copy since the caller might reuse the buffer
msg := make([]byte, len(p))
copy(msg, p)

// Non-blocking send - if buffer is full, drop the message
// This prevents blocking the UI if logging falls behind
select {
case aw.out <- msg:
return len(p), nil
default:
// Buffer full - drop message to avoid blocking
return len(p), nil
}
}

func (aw *asyncWriter) Close() error {
if !aw.closed {
aw.closed = true
close(aw.out)
<-aw.done // Wait for goroutine to finish
}
return nil
}
26 changes: 20 additions & 6 deletions pkg/tui/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ func renderIncident(m *model) tea.Cmd {
return errMsg{err}
}

content, err := renderIncidentMarkdown(t, m.incidentViewer.Width)
content, err := renderIncidentMarkdown(m, t)
if err != nil {
return errMsg{err}
}
Expand Down Expand Up @@ -574,6 +574,24 @@ func reEscalateIncidents(p *pd.Config, i []pagerduty.Incident, e *pagerduty.Esca
}
}

func fetchEscalationPolicyAndReEscalate(p *pd.Config, incidents []pagerduty.Incident, policyID string, level uint) tea.Cmd {
return func() tea.Msg {
// Fetch the full escalation policy details
policy, err := pd.GetEscalationPolicy(p.Client, policyID, pagerduty.GetEscalationPolicyOptions{})
if err != nil {
log.Error("tui.fetchEscalationPolicyAndReEscalate", "failed to fetch escalation policy", "policy_id", policyID, "error", err)
return errMsg{err}
}

// Now re-escalate with the fetched policy
r, err := pd.ReEscalateIncidents(p.Client, incidents, p.CurrentUser, policy, level)
if err != nil {
return errMsg{err}
}
return reEscalatedIncidentsMsg(r)
}
}

type silenceSelectedIncidentMsg struct{}
type silenceIncidentsMsg struct {
incidents []pagerduty.Incident
Expand Down Expand Up @@ -668,20 +686,16 @@ func getDetailFieldFromAlert(f string, a pagerduty.IncidentAlert) string {
if a.Body["details"].(map[string]interface{})[f] != nil {
return a.Body["details"].(map[string]interface{})[f].(string)
}
log.Debug(fmt.Sprintf("tui.getDetailFieldFromAlert(): alert body \"details\" does not contain field %s", f))
return ""
}
log.Debug("tui.getDetailFieldFromAlert(): alert body \"details\" is nil")
return ""
}

// getEscalationPolicyKey is a helper function to determine the escalation policy key
func getEscalationPolicyKey(serviceID string, policies map[string]*pagerduty.EscalationPolicy) string {
if policy, ok := policies[serviceID]; ok {
log.Debug("Update", "getEscalationPolicyKey", "escalation policy override found for service", "service", serviceID, "policy", policy.Name)
if _, ok := policies[serviceID]; ok {
return serviceID
}
log.Debug("Update", "getEscalationPolicyKey", "no escalation policy override for service; using default", "service", serviceID, "policy", silentDefaultPolicyKey)
return silentDefaultPolicyKey
}

Expand Down
90 changes: 64 additions & 26 deletions pkg/tui/keymap.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,34 +10,68 @@ func (k keymap) FullHelp() [][]key.Binding {
// TODO: Return a pop-over window here instead
return [][]key.Binding{
// Each slice here is a column in the help window
{k.Up, k.Down, k.Enter, k.Back},
{k.Ack, k.UnAck, k.Note, k.Silence},
{k.Login, k.Open},
{k.Team, k.Refresh, k.AutoRefresh, k.AutoAck},
{k.Quit, k.Help},
{k.Up, k.Down, k.Top, k.Bottom, k.Enter, k.Back},
{k.Ack, k.Login, k.Open, k.Note},
{k.UnAck, k.Silence},
{k.Team, k.Refresh},
{k.AutoRefresh, k.AutoAck, k.ToggleActionLog, k.Quit, k.Help},
}
}

type keymap struct {
Up key.Binding
Down key.Binding
Top key.Binding
Bottom key.Binding
Back key.Binding
Enter key.Binding
Quit key.Binding
Help key.Binding
Team key.Binding
Refresh key.Binding
AutoRefresh key.Binding
Note key.Binding
Silence key.Binding
Ack key.Binding
UnAck key.Binding
AutoAck key.Binding
Input key.Binding
Login key.Binding
Open key.Binding
Up key.Binding
Down key.Binding
Top key.Binding
Bottom key.Binding
Back key.Binding
Enter key.Binding
Quit key.Binding
Help key.Binding
Team key.Binding
Refresh key.Binding
AutoRefresh key.Binding
Note key.Binding
Silence key.Binding
Ack key.Binding
UnAck key.Binding
AutoAck key.Binding
ToggleActionLog key.Binding
Input key.Binding
Login key.Binding
Open key.Binding
}

type inputKeymap struct {
Quit key.Binding
Back key.Binding
Enter key.Binding
}

func (k inputKeymap) ShortHelp() []key.Binding {
return []key.Binding{k.Back, k.Enter, k.Quit}
}

func (k inputKeymap) FullHelp() [][]key.Binding {
return [][]key.Binding{
{k.Back, k.Enter},
{k.Quit},
}
}

// inputModeKeyMap contains only the keys that work in input mode
var inputModeKeyMap = inputKeymap{
Quit: key.NewBinding(
key.WithKeys("ctrl+c", "ctrl+q"),
key.WithHelp("ctrl+q/ctrl+c", "quit"),
),
Back: key.NewBinding(
key.WithKeys("esc"),
key.WithHelp("esc", "back"),
),
Enter: key.NewBinding(
key.WithKeys("enter"),
key.WithHelp("enter", "n/a"),
),
}

var defaultKeyMap = keymap{
Expand Down Expand Up @@ -105,9 +139,13 @@ var defaultKeyMap = keymap{
key.WithKeys("ctrl+a"),
key.WithHelp("ctrl+a", "toggle auto-acknowledge"),
),
ToggleActionLog: key.NewBinding(
key.WithKeys("ctrl+l"),
key.WithHelp("ctrl+l", "toggle action log"),
),
Input: key.NewBinding(
key.WithKeys("i"),
key.WithHelp("i", "input"),
key.WithKeys("i", ":"),
key.WithHelp("i/:", "input"),
),
Login: key.NewBinding(
key.WithKeys("l"),
Expand Down
Loading
Loading