diff --git a/data/issueapi.go b/data/issueapi.go index b1c385a2..c722fbab 100644 --- a/data/issueapi.go +++ b/data/issueapi.go @@ -21,11 +21,24 @@ type IssueData struct { Url string Repository Repository Assignees Assignees `graphql:"assignees(first: 3)"` - Comments Comments `graphql:"comments(first: 15)"` + Comments IssueComments `graphql:"comments(first: 15)"` Reactions IssueReactions `graphql:"reactions(first: 1)"` Labels IssueLabels `graphql:"labels(first: 3)"` } +type IssueComments struct { + Nodes []IssueComment + TotalCount int +} + +type IssueComment struct { + Author struct { + Login string + } + Body string + UpdatedAt time.Time +} + type IssueReactions struct { TotalCount int } diff --git a/data/prapi.go b/data/prapi.go index bdf40903..6bbcd73f 100644 --- a/data/prapi.go +++ b/data/prapi.go @@ -34,9 +34,10 @@ type PullRequestData struct { Name string } Repository Repository - Assignees Assignees `graphql:"assignees(first: 3)"` - Comments Comments `graphql:"comments(last: 5, orderBy: { field: UPDATED_AT, direction: DESC })"` - LatestReviews Reviews `graphql:"latestReviews(last: 3)"` + Assignees Assignees `graphql:"assignees(first: 3)"` + Comments Comments `graphql:"comments(last: 5, orderBy: { field: UPDATED_AT, direction: DESC })"` + LatestReviews Reviews `graphql:"latestReviews(last: 3)"` + ReviewThreads ReviewThreads `graphql:"reviewThreads(last: 100)"` IsDraft bool Commits Commits `graphql:"commits(last: 1)"` } @@ -96,6 +97,21 @@ type Comment struct { UpdatedAt time.Time } +type ReviewComment struct { + Author struct { + Login string + } + Body string + UpdatedAt time.Time + StartLine int + Line int +} + +type ReviewComments struct { + Nodes []ReviewComment + TotalCount int +} + type Comments struct { Nodes []Comment TotalCount int @@ -114,6 +130,18 @@ type Reviews struct { Nodes []Review } +type ReviewThreads struct { + Nodes []struct { + Id string + IsOutdated bool + OriginalLine int + StartLine int + Line int + Path string + Comments ReviewComments `graphql:"comments(first: 100)"` + } +} + type PageInfo struct { HasNextPage bool StartCursor string diff --git a/ui/components/issuesidebar/activity.go b/ui/components/issuesidebar/activity.go index 04e68581..4c22222e 100644 --- a/ui/components/issuesidebar/activity.go +++ b/ui/components/issuesidebar/activity.go @@ -7,6 +7,7 @@ import ( "github.com/charmbracelet/glamour" "github.com/charmbracelet/lipgloss" + "github.com/dlvhdr/gh-dash/data" "github.com/dlvhdr/gh-dash/ui/markdown" "github.com/dlvhdr/gh-dash/utils" @@ -63,7 +64,7 @@ func renderEmptyState() string { return lipgloss.NewStyle().Italic(true).Render("No comments...") } -func (m *Model) renderComment(comment data.Comment, markdownRenderer glamour.TermRenderer) (string, error) { +func (m *Model) renderComment(comment data.IssueComment, markdownRenderer glamour.TermRenderer) (string, error) { header := lipgloss.JoinHorizontal(lipgloss.Top, m.ctx.Styles.Common.MainTextStyle.Copy().Render(comment.Author.Login), " ", diff --git a/ui/components/issuesidebar/comment.go b/ui/components/issuesidebar/comment.go index 20c74e27..9f5b0d7d 100644 --- a/ui/components/issuesidebar/comment.go +++ b/ui/components/issuesidebar/comment.go @@ -6,6 +6,7 @@ import ( "time" tea "github.com/charmbracelet/bubbletea" + "github.com/dlvhdr/gh-dash/data" "github.com/dlvhdr/gh-dash/ui/components/issuessection" "github.com/dlvhdr/gh-dash/ui/constants" @@ -44,7 +45,7 @@ func (m *Model) comment(body string) tea.Cmd { Err: err, Msg: issuessection.UpdateIssueMsg{ IssueNumber: issueNumber, - NewComment: &data.Comment{ + NewComment: &data.IssueComment{ Author: struct{ Login string }{Login: m.ctx.User}, Body: body, UpdatedAt: time.Now(), diff --git a/ui/components/issuessection/issuessection.go b/ui/components/issuessection/issuessection.go index 9e0190a1..c2894fa3 100644 --- a/ui/components/issuessection/issuessection.go +++ b/ui/components/issuessection/issuessection.go @@ -344,7 +344,7 @@ type SectionIssuesFetchedMsg struct { type UpdateIssueMsg struct { IssueNumber int - NewComment *data.Comment + NewComment *data.IssueComment IsClosed *bool AddedAssignees *data.Assignees RemovedAssignees *data.Assignees diff --git a/ui/components/prsidebar/activity.go b/ui/components/prsidebar/activity.go index 61eb9922..0c82cc4a 100644 --- a/ui/components/prsidebar/activity.go +++ b/ui/components/prsidebar/activity.go @@ -1,12 +1,14 @@ package prsidebar import ( + "fmt" "regexp" "sort" "time" "github.com/charmbracelet/glamour" "github.com/charmbracelet/lipgloss" + "github.com/dlvhdr/gh-dash/data" "github.com/dlvhdr/gh-dash/ui/markdown" "github.com/dlvhdr/gh-dash/utils" @@ -21,13 +23,37 @@ func (m *Model) renderActivity() string { width := m.getIndentedContentWidth() - 2 markdownRenderer := markdown.GetMarkdownRenderer(width) - var activity []RenderedActivity - for _, comment := range m.pr.Data.Comments.Nodes { + var activities []RenderedActivity + var comments []comment + + for _, review := range m.pr.Data.ReviewThreads.Nodes { + path := review.Path + line := review.Line + for _, c := range review.Comments.Nodes { + comments = append(comments, comment{ + Author: c.Author.Login, + Body: c.Body, + UpdatedAt: c.UpdatedAt, + Path: &path, + Line: &line, + }) + } + } + + for _, c := range m.pr.Data.Comments.Nodes { + comments = append(comments, comment{ + Author: c.Author.Login, + Body: c.Body, + UpdatedAt: c.UpdatedAt, + }) + } + + for _, comment := range comments { renderedComment, err := m.renderComment(comment, markdownRenderer) if err != nil { continue } - activity = append(activity, RenderedActivity{ + activities = append(activities, RenderedActivity{ UpdatedAt: comment.UpdatedAt, RenderedString: renderedComment, }) @@ -38,23 +64,23 @@ func (m *Model) renderActivity() string { if err != nil { continue } - activity = append(activity, RenderedActivity{ + activities = append(activities, RenderedActivity{ UpdatedAt: review.UpdatedAt, RenderedString: renderedReview, }) } - sort.Slice(activity, func(i, j int) bool { - return activity[i].UpdatedAt.Before(activity[j].UpdatedAt) + sort.Slice(activities, func(i, j int) bool { + return activities[i].UpdatedAt.Before(activities[j].UpdatedAt) }) body := "" bodyStyle := lipgloss.NewStyle().PaddingLeft(2) - if len(activity) == 0 { + if len(activities) == 0 { body = renderEmptyState() } else { var renderedActivities []string - for _, activity := range activity { + for _, activity := range activities { renderedActivities = append(renderedActivities, activity.RenderedString) } body = lipgloss.JoinVertical(lipgloss.Left, renderedActivities...) @@ -74,12 +100,39 @@ func renderEmptyState() string { return lipgloss.NewStyle().Italic(true).Render("No comments...") } -func (m *Model) renderComment(comment data.Comment, markdownRenderer glamour.TermRenderer) (string, error) { - header := lipgloss.JoinHorizontal(lipgloss.Top, - m.ctx.Styles.Common.MainTextStyle.Copy().Render(comment.Author.Login), - " ", - lipgloss.NewStyle().Foreground(m.ctx.Theme.FaintText).Render(utils.TimeElapsed(comment.UpdatedAt)), - ) +type comment struct { + Author string + UpdatedAt time.Time + Body string + Path *string + Line *int +} + +func (m *Model) renderComment(comment comment, markdownRenderer glamour.TermRenderer) (string, error) { + width := m.getIndentedContentWidth() + authorAndTime := lipgloss.NewStyle(). + Width(width). + BorderStyle(lipgloss.RoundedBorder()). + BorderForeground(m.ctx.Theme.FaintBorder).Render( + lipgloss.JoinHorizontal(lipgloss.Top, + m.ctx.Styles.Common.MainTextStyle.Copy().Render(comment.Author), + " ", + lipgloss.NewStyle().Foreground(m.ctx.Theme.FaintText).Render(utils.TimeElapsed(comment.UpdatedAt)), + )) + + var header string + if comment.Path != nil && comment.Line != nil { + filePath := lipgloss.NewStyle().Foreground(m.ctx.Theme.FaintText).Width(width).Render( + fmt.Sprintf( + "%s#l%d", + *comment.Path, + *comment.Line, + ), + ) + header = lipgloss.JoinVertical(lipgloss.Left, authorAndTime, filePath, "") + } else { + header = authorAndTime + } regex := regexp.MustCompile(`((\n)+|^)([^\r\n]*\|[^\r\n]*(\n)?)+`) body := regex.ReplaceAllString(comment.Body, "") diff --git a/ui/ui.go b/ui/ui.go index b2e69a67..faf240fc 100644 --- a/ui/ui.go +++ b/ui/ui.go @@ -361,6 +361,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { if ok { log.Debug("Task finished", "id", task.Id) if msg.Err != nil { + log.Error("Task finished with error", "id", task.Id, "err", msg.Err) task.State = context.TaskError task.Error = msg.Err } else {