From 6cc5f13c9fccc3ae42602bffd9ca123ee4116800 Mon Sep 17 00:00:00 2001 From: Chris Collins Date: Wed, 24 Jul 2024 11:47:23 -1000 Subject: [PATCH] Add incident summary as initial content when adding a note Adds an incident summary (format below) to the initial content when opening the editor to add a note to an incident, and drops comment lines ("#") before writing the note, mirroring the behavior of a git commit message. Fixes #37 Format: ``` ``` Signed-off-by: Chris Collins --- README.md | 2 +- pkg/tui/commands.go | 58 +++++++++++++++++++++++++++++++++++++----- pkg/tui/msgHandlers.go | 6 ++--- pkg/tui/tui.go | 12 +++++++++ pkg/tui/views.go | 35 +++++++++++++++++++++++++ 5 files changed, 101 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 6f6cdf9..fd57bcb 100644 --- a/README.md +++ b/README.md @@ -12,10 +12,10 @@ Features: * Reassign incidents to a (configured) "silent" user (ie. silence the alert) * Acknowledge incidents * Resizes nicely(-ish) when the terminal is resized +* Un-Acknowledge incidents (re-assign to the Escalation Policy) Planned Features: -* Un-Acknowledge incidents (re-assign to the Escalation Policy) * View arbitrary incidents * Assign incidents to any PagerDuty User ID * Edit incident titles diff --git a/pkg/tui/commands.go b/pkg/tui/commands.go index 840561f..01f22f9 100644 --- a/pkg/tui/commands.go +++ b/pkg/tui/commands.go @@ -8,6 +8,7 @@ import ( "os" "os/exec" "slices" + "strings" "time" "github.com/PagerDuty/go-pagerduty" @@ -301,7 +302,7 @@ type editorFinishedMsg struct { file *os.File } -func openEditorCmd(editor []string) tea.Cmd { +func openEditorCmd(editor []string, initialMsg ...string) tea.Cmd { log.Debug("tui.openEditorCmd(): opening editor") var args []string @@ -313,6 +314,18 @@ func openEditorCmd(editor []string) tea.Cmd { } } + if len(initialMsg) > 0 { + for _, msg := range initialMsg { + _, err = file.WriteString(msg) + if err != nil { + log.Debug(fmt.Sprintf("tui.openEditorCmd(): error: %v", err)) + return func() tea.Msg { + return errMsg{error: err} + } + } + } + } + args = append(args, editor[1:]...) args = append(args, file.Name()) @@ -328,6 +341,19 @@ func openEditorCmd(editor []string) tea.Cmd { }) } +type parseTemplateForNoteMsg string +type parsedTemplateForNoteMsg struct { + content string + err error +} + +func parseTemplateForNote(p *pagerduty.Incident) tea.Cmd { + return func() tea.Msg { + content, err := addNoteTemplate(p.HTMLURL, p.Title, p.Service.Summary) + return parsedTemplateForNoteMsg{content, err} + } +} + type loginMsg string type loginFinishedMsg struct { err error @@ -469,23 +495,24 @@ type addedIncidentNoteMsg struct { err error } -func addNoteToIncident(p *pd.Config, incident *pagerduty.Incident, content *os.File) tea.Cmd { +func addNoteToIncident(p *pd.Config, incident *pagerduty.Incident, file *os.File) tea.Cmd { return func() tea.Msg { - defer content.Close() + defer file.Close() - bytes, err := os.ReadFile(content.Name()) + bytes, err := os.ReadFile(file.Name()) if err != nil { return errMsg{err} } - content := string(bytes[:]) + + note := removeCommentsFromBytes(bytes, "#") u, err := p.Client.GetCurrentUserWithContext(context.Background(), pagerduty.GetCurrentUserOptions{}) if err != nil { return errMsg{err} } - if content != "" { - n, err := pd.PostNote(p.Client, incident.ID, u, content) + if note != "" { + n, err := pd.PostNote(p.Client, incident.ID, u, note) return addedIncidentNoteMsg{n, err} } @@ -493,6 +520,23 @@ func addNoteToIncident(p *pd.Config, incident *pagerduty.Incident, content *os.F } } +// removeCommentsFromBytes removes any lines beginning with any of the provided prefixes []byte and returns a string. +func removeCommentsFromBytes(b []byte, prefixes ...string) string { + var content strings.Builder + + lines := strings.Split(string(b[:]), "\n") + + for _, c := range lines { + for _, a := range prefixes { + if !strings.HasPrefix(c, a) { + content.WriteString(c) + } + } + } + + return content.String() +} + // getTeamsAsStrings returns a slice of team IDs as strings from the []*pagerduty.Teams in a *pd.Config func getTeamsAsStrings(p *pd.Config) []string { var teams []string diff --git a/pkg/tui/msgHandlers.go b/pkg/tui/msgHandlers.go index 83ffbc8..0c70292 100644 --- a/pkg/tui/msgHandlers.go +++ b/pkg/tui/msgHandlers.go @@ -231,7 +231,8 @@ func switchTableFocusMode(m model, msg tea.Msg) (tea.Model, tea.Cmd) { return m, doIfIncidentSelected(&m, tea.Sequence( func() tea.Msg { return getIncidentMsg(incidentID) }, func() tea.Msg { - return waitForSelectedIncidentThenDoMsg{action: openEditorCmd(m.editor), msg: "add note"} + msg := "add note" + return waitForSelectedIncidentThenDoMsg{action: func() tea.Msg { return parseTemplateForNoteMsg(msg) }, msg: msg} }, )) @@ -300,9 +301,6 @@ func switchIncidentFocusMode(m model, msg tea.Msg) (tea.Model, tea.Cmd) { case key.Matches(msg, defaultKeyMap.Silence): return m, func() tea.Msg { return silenceSelectedIncidentMsg{} } - case key.Matches(msg, defaultKeyMap.Note): - cmds = append(cmds, openEditorCmd(m.editor)) - case key.Matches(msg, defaultKeyMap.Refresh): return m, func() tea.Msg { return getIncidentMsg(m.selectedIncident.ID) } diff --git a/pkg/tui/tui.go b/pkg/tui/tui.go index 28d826c..08e9c21 100644 --- a/pkg/tui/tui.go +++ b/pkg/tui/tui.go @@ -253,6 +253,18 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.setStatus(fmt.Sprintf("showing %d/%d incidents...", len(m.table.Rows()), totalIncidentCount)) } + case parseTemplateForNoteMsg: + if m.selectedIncident == nil { + m.setStatus("failed to open editor - no selected incident") + } + cmds = append(cmds, parseTemplateForNote(m.selectedIncident)) + + case parsedTemplateForNoteMsg: + if msg.err != nil { + return m, func() tea.Msg { return errMsg{msg.err} } + } + cmds = append(cmds, openEditorCmd(m.editor, msg.content)) + case editorFinishedMsg: if msg.err != nil { return m, func() tea.Msg { return errMsg{msg.err} } diff --git a/pkg/tui/views.go b/pkg/tui/views.go index cf437a9..92387b9 100644 --- a/pkg/tui/views.go +++ b/pkg/tui/views.go @@ -96,6 +96,7 @@ var ( paddedStyle = mainStyle.Padding(0, 2, 0, 1) + //lint:ignore U1000 - future proofing warningStyle = lipgloss.NewStyle().Foreground(srepdPallet.warning.text).Background(srepdPallet.warning.background) tableContainerStyle = lipgloss.NewStyle().Border(lipgloss.RoundedBorder(), true) @@ -248,6 +249,29 @@ func (m model) template() (string, error) { return o.String(), nil } +func addNoteTemplate(id string, title string, service string) (string, error) { + template, err := template.New("note").Parse(noteTemplate) + if err != nil { + return "", err + } + + o := new(bytes.Buffer) + err = template.Execute(o, struct { + ID string + Title string + Service string + }{ + ID: id, + Title: title, + Service: service, + }) + if err != nil { + return "", err + } + + return o.String(), nil +} + func summarize(i *pagerduty.Incident, a []pagerduty.IncidentAlert, n []pagerduty.IncidentNote) incidentSummary { summary := summarizeIncident(i) summary.Alerts = summarizeAlerts(a) @@ -456,3 +480,14 @@ func renderIncidentMarkdown(content string) (string, error) { return str, nil } + +const noteTemplate = ` + +# Please enter the note message content above. Lines starting +# with '#' will be ignored and an empty message aborts the note. +# +# Incident: {{ .ID }} +# Summary: {{ .Title }} +# Service: {{ .Service }} +# +`