diff --git a/server/plugin/api.go b/server/plugin/api.go index d8fd5ca30..7a8014846 100644 --- a/server/plugin/api.go +++ b/server/plugin/api.go @@ -21,6 +21,8 @@ import ( "github.com/mattermost/mattermost/server/public/pluginapi" "github.com/mattermost/mattermost/server/public/pluginapi/experimental/bot/logger" "github.com/mattermost/mattermost/server/public/pluginapi/experimental/flow" + + "github.com/mattermost/mattermost-plugin-github/server/plugin/graphql" ) const ( @@ -63,10 +65,10 @@ type FilteredNotification struct { } type SidebarContent struct { - PRs []*github.Issue `json:"prs"` - Reviews []*github.Issue `json:"reviews"` - Assignments []*github.Issue `json:"assignments"` - Unreads []*FilteredNotification `json:"unreads"` + PRs []*graphql.GithubPRDetails `json:"prs"` + Reviews []*graphql.GithubPRDetails `json:"reviews"` + Assignments []*github.Issue `json:"assignments"` + Unreads []*FilteredNotification `json:"unreads"` } type Context struct { @@ -942,12 +944,12 @@ func (p *Plugin) createIssueComment(c *UserContext, w http.ResponseWriter, r *ht p.writeJSON(w, result) } -func (p *Plugin) getLHSData(c *UserContext) (reviewResp []*github.Issue, assignmentResp []*github.Issue, openPRResp []*github.Issue, err error) { +func (p *Plugin) getLHSData(c *UserContext) (reviewResp []*graphql.GithubPRDetails, assignmentResp []*github.Issue, openPRResp []*graphql.GithubPRDetails, err error) { graphQLClient := p.graphQLConnect(c.GHInfo) reviewResp, assignmentResp, openPRResp, err = graphQLClient.GetLHSData(c.Context.Ctx) if err != nil { - return []*github.Issue{}, []*github.Issue{}, []*github.Issue{}, err + return []*graphql.GithubPRDetails{}, []*github.Issue{}, []*graphql.GithubPRDetails{}, err } return reviewResp, assignmentResp, openPRResp, nil diff --git a/server/plugin/graphql/lhs_query.go b/server/plugin/graphql/lhs_query.go index 3de215e0a..26d83550b 100644 --- a/server/plugin/graphql/lhs_query.go +++ b/server/plugin/graphql/lhs_query.go @@ -38,6 +38,9 @@ type ( Milestone struct { Title githubv4.String } + Additions githubv4.Int + Deletions githubv4.Int + ChangedFiles githubv4.Int } `graphql:"... on PullRequest"` } ) @@ -80,6 +83,9 @@ type ( Milestone struct { Title githubv4.String } + Additions githubv4.Int + Deletions githubv4.Int + ChangedFiles githubv4.Int } `graphql:"... on PullRequest"` } ) diff --git a/server/plugin/graphql/lhs_request.go b/server/plugin/graphql/lhs_request.go index f36df5921..3285763c5 100644 --- a/server/plugin/graphql/lhs_request.go +++ b/server/plugin/graphql/lhs_request.go @@ -19,9 +19,17 @@ const ( queryParamAssigneeQueryArg = "assigneeQueryArg" ) -func (c *Client) GetLHSData(ctx context.Context) ([]*github.Issue, []*github.Issue, []*github.Issue, error) { +type GithubPRDetails struct { + *github.Issue + Additions *githubv4.Int `json:"additions,omitempty"` + Deletions *githubv4.Int `json:"deletions,omitempty"` + ChangedFiles *githubv4.Int `json:"changed_files,omitempty"` +} + +func (c *Client) GetLHSData(ctx context.Context) ([]*GithubPRDetails, []*github.Issue, []*GithubPRDetails, error) { orgsList := c.getOrganizations() - var resultReview, resultAssignee, resultOpenPR []*github.Issue + var resultAssignee []*github.Issue + var resultReview, resultOpenPR []*GithubPRDetails for _, org := range orgsList { params := map[string]interface{}{ queryParamOpenPRQueryArg: githubv4.String(fmt.Sprintf("author:%s is:pr is:%s archived:false", c.username, githubv4.PullRequestStateOpen)), @@ -94,11 +102,39 @@ func (c *Client) GetLHSData(ctx context.Context) ([]*github.Issue, []*github.Iss return resultReview, resultAssignee, resultOpenPR, nil } -func getPR(prResp *prSearchNodes) *github.Issue { +func getPR(prResp *prSearchNodes) *GithubPRDetails { resp := prResp.PullRequest labels := getGithubLabels(resp.Labels.Nodes) - return newGithubIssue(resp.Number, resp.Title, resp.Author.Login, resp.Repository.URL, resp.URL, resp.CreatedAt, resp.UpdatedAt, labels, resp.Milestone.Title) + number := int(resp.Number) + repoURL := resp.Repository.URL.String() + issuetitle := string(resp.Title) + userLogin := string(resp.Author.Login) + milestoneTitle := string(resp.Milestone.Title) + url := resp.URL.String() + createdAtTime := github.Timestamp{Time: resp.CreatedAt.Time} + updatedAtTime := github.Timestamp{Time: resp.UpdatedAt.Time} + + return &GithubPRDetails{ + Issue: &github.Issue{ + Number: &number, + RepositoryURL: &repoURL, + Title: &issuetitle, + CreatedAt: &createdAtTime, + UpdatedAt: &updatedAtTime, + User: &github.User{ + Login: &userLogin, + }, + Milestone: &github.Milestone{ + Title: &milestoneTitle, + }, + HTMLURL: &url, + Labels: labels, + }, + Additions: &resp.Additions, + Deletions: &resp.Deletions, + ChangedFiles: &resp.ChangedFiles, + } } func newIssueFromAssignmentResponse(assignmentResp *assignmentSearchNodes) *github.Issue { diff --git a/webapp/src/components/sidebar_right/github_items.tsx b/webapp/src/components/sidebar_right/github_items.tsx index ae7e3ac14..64266ca2d 100644 --- a/webapp/src/components/sidebar_right/github_items.tsx +++ b/webapp/src/components/sidebar_right/github_items.tsx @@ -7,7 +7,7 @@ import * as CSS from 'csstype'; import {Badge, Tooltip, OverlayTrigger} from 'react-bootstrap'; import {makeStyleFromTheme, changeOpacity} from 'mattermost-redux/utils/theme_utils'; -import {GitPullRequestIcon, IssueOpenedIcon, IconProps} from '@primer/octicons-react'; +import {GitPullRequestIcon, IssueOpenedIcon, IconProps, CalendarIcon, PersonIcon, FileDiffIcon} from '@primer/octicons-react'; import {GithubItemsProps, GithubLabel, GithubItem, Review} from '../../types/github_types'; @@ -227,6 +227,37 @@ function GithubItems(props: GithubItemsProps) { labels = getGithubLabels(item.labels); } + let pullRequestDetails: JSX.Element | null = null; + if (item.additions || item.deletions) { + const additions = item?.additions; + const deletions = item?.deletions; + const changedFiles = item?.changed_files; + + pullRequestDetails = ( +
+ + {changedFiles && ( + + {' '}{changedFiles} {changedFiles === 1 ? 'File' : 'Files'} {' Changed'} + + )} + {additions != null && ( + + {' +'}{additions} + + )} + {deletions != null && ( + + {' -'}{deletions} + + )} +
+ ); + } + return (
- {item.created_at && ('Opened ' + formatTimeSince(item.created_at) + ' ago')} + {item.created_at && ( + <> + {'Opened '} {formatTimeSince(item.created_at)} {' ago'} + + )} {userName && ' by ' + userName} {(item.created_at || userName) && '.'} {milestone} @@ -256,6 +291,7 @@ function GithubItems(props: GithubItemsProps) { ) : null }
{reviews} + {pullRequestDetails} ); }) :
{'You have no active items'}
; @@ -311,6 +347,20 @@ const getStyle = makeStyleFromTheme((theme) => { alignItems: 'center', color: theme.centerChannelColor, }, + additionNumber: { + color: 'green', + }, + deletionNumber: { + color: 'red', + }, + pullRequestDetails: { + margin: '5px 0 0 0', + fontSize: '13px', + fontWeight: 'normal', + }, + prOpenSince: { + margin: '5px 0 0 0', + }, }; }); @@ -385,7 +435,14 @@ function getReviewText(item: GithubItem, style: any, secondLine: boolean) { } else { reviewName = 'reviews'; } - reviews = ({approved + ' out of ' + totalReviewers + ' ' + reviewName + ' complete.'}); + reviews = ( +
+ {(totalReviewers - approved) + ' pending reviewer'} +
+ ); } if (changesRequested > 0) { diff --git a/webapp/src/types/github_types.ts b/webapp/src/types/github_types.ts index 3bccd581e..e872acad0 100644 --- a/webapp/src/types/github_types.ts +++ b/webapp/src/types/github_types.ts @@ -42,6 +42,9 @@ export type GithubItem = PrsDetailsData & { title: string; } reason?: string; + additions?: number; + deletions?: number; + changed_files?: number; } export type GithubItemsProps = {