From 604e95240957d0b3749558e98b106f0d1d5ba5f6 Mon Sep 17 00:00:00 2001 From: amalhanaja Date: Sat, 29 Jun 2024 10:15:18 +0700 Subject: [PATCH 1/6] feat: add gh repository --- go.mod | 14 +- go.sum | 2 + .../factories/gitrepo/repo_factory_impl.go | 7 + .../infrastructure/http/bitbucket/client.go | 4 +- internal/infrastructure/http/github/client.go | 33 ++ .../http/github/get_pull_request.go | 68 +++ internal/infrastructure/http/github/helper.go | 27 + .../infrastructure/http/github/payload.go | 502 ++++++++++++++++++ 8 files changed, 649 insertions(+), 8 deletions(-) create mode 100644 internal/infrastructure/http/github/client.go create mode 100644 internal/infrastructure/http/github/get_pull_request.go create mode 100644 internal/infrastructure/http/github/helper.go create mode 100644 internal/infrastructure/http/github/payload.go diff --git a/go.mod b/go.mod index b9aa58a..1185214 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,14 @@ module codebleu go 1.22.4 -require github.com/google/generative-ai-go v0.14.0 +require ( + github.com/go-chi/chi/v5 v5.0.14 + github.com/go-playground/webhooks/v6 v6.3.0 + github.com/google/generative-ai-go v0.14.0 + github.com/urfave/cli/v2 v2.27.2 + go.uber.org/fx v1.22.0 + google.golang.org/api v0.180.0 +) require ( cloud.google.com/go v0.113.0 // indirect @@ -13,10 +20,8 @@ require ( cloud.google.com/go/longrunning v0.5.7 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/go-chi/chi/v5 v5.0.14 // indirect github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/go-playground/webhooks/v6 v6.3.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/s2a-go v0.1.7 // indirect @@ -24,7 +29,6 @@ require ( github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect github.com/googleapis/gax-go/v2 v2.12.4 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect - github.com/urfave/cli/v2 v2.27.2 // indirect github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 // indirect go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.51.0 // indirect @@ -33,7 +37,6 @@ require ( go.opentelemetry.io/otel/metric v1.26.0 // indirect go.opentelemetry.io/otel/trace v1.26.0 // indirect go.uber.org/dig v1.17.1 // indirect - go.uber.org/fx v1.22.0 // indirect go.uber.org/multierr v1.10.0 // indirect go.uber.org/zap v1.26.0 // indirect golang.org/x/crypto v0.23.0 // indirect @@ -43,7 +46,6 @@ require ( golang.org/x/sys v0.20.0 // indirect golang.org/x/text v0.15.0 // indirect golang.org/x/time v0.5.0 // indirect - google.golang.org/api v0.180.0 // indirect google.golang.org/genproto v0.0.0-20240401170217-c3f982113cda // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240513163218-0867130af1f8 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240509183442-62759503f434 // indirect diff --git a/go.sum b/go.sum index 7b17934..6b6c303 100644 --- a/go.sum +++ b/go.sum @@ -105,6 +105,8 @@ go.uber.org/dig v1.17.1 h1:Tga8Lz8PcYNsWsyHMZ1Vm0OQOUaJNDyvPImgbAu9YSc= go.uber.org/dig v1.17.1/go.mod h1:Us0rSJiThwCv2GteUN0Q7OKvU7n5J4dxZ9JKUXozFdE= go.uber.org/fx v1.22.0 h1:pApUK7yL0OUHMd8vkunWSlLxZVFFk70jR2nKde8X2NM= go.uber.org/fx v1.22.0/go.mod h1:HT2M7d7RHo+ebKGh9NRcrsrHHfpZ60nW3QRubMRfv48= +go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= +go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo= go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= diff --git a/internal/infrastructure/factories/gitrepo/repo_factory_impl.go b/internal/infrastructure/factories/gitrepo/repo_factory_impl.go index 1e2477f..367e55b 100644 --- a/internal/infrastructure/factories/gitrepo/repo_factory_impl.go +++ b/internal/infrastructure/factories/gitrepo/repo_factory_impl.go @@ -3,6 +3,7 @@ package gitrepo import ( "codebleu/internal/domain/gitrepo" "codebleu/internal/infrastructure/http/bitbucket" + "codebleu/internal/infrastructure/http/github" "codebleu/pkg/env" "fmt" ) @@ -15,6 +16,12 @@ func NewRepository(provider string) (gitrepo.Repository, error) { env.MustString("BITBUCKET_REPO_SLUG"), env.MustString("BITBUCKET_ACCESS_TOKEN"), ), nil + case "github": + return github.NewClient( + env.MustString("GH_OWNER"), + env.MustString("GH_REPO_SLUG"), + env.MustString("GH_ACCESS_TOKEN"), + ), nil default: return nil, fmt.Errorf("unsupported remote repository provider %s", provider) } diff --git a/internal/infrastructure/http/bitbucket/client.go b/internal/infrastructure/http/bitbucket/client.go index 4289956..220078d 100644 --- a/internal/infrastructure/http/bitbucket/client.go +++ b/internal/infrastructure/http/bitbucket/client.go @@ -6,14 +6,14 @@ import ( ) type client struct { - httpClient http.Client + httpClient *http.Client workspace string repoSlug string accessToken string } func NewClient(workspace string, repoSlug string, accessToken string) domain.Repository { - httpClient := http.Client{} + httpClient := &http.Client{} return &client{ httpClient: httpClient, workspace: workspace, diff --git a/internal/infrastructure/http/github/client.go b/internal/infrastructure/http/github/client.go new file mode 100644 index 0000000..5d3df56 --- /dev/null +++ b/internal/infrastructure/http/github/client.go @@ -0,0 +1,33 @@ +package github + +import ( + "codebleu/internal/domain/gitrepo" + "context" + "net/http" +) + +type client struct { + accessToken string + owner string + repoSlug string + httpClient *http.Client +} + +// PostPullRequestComment implements gitrepo.Repository. +func (c *client) PostPullRequestComment(ctx context.Context, input gitrepo.PostPullRequestCommentInput) error { + panic("unimplemented") +} + +func NewClient( + owner string, + repoSlug string, + accessToken string, +) gitrepo.Repository { + httpClient := &http.Client{} + return &client{ + owner: owner, + repoSlug: repoSlug, + accessToken: accessToken, + httpClient: httpClient, + } +} diff --git a/internal/infrastructure/http/github/get_pull_request.go b/internal/infrastructure/http/github/get_pull_request.go new file mode 100644 index 0000000..b816cc4 --- /dev/null +++ b/internal/infrastructure/http/github/get_pull_request.go @@ -0,0 +1,68 @@ +package github + +import ( + "codebleu/internal/domain/gitrepo" + infraHttp "codebleu/internal/infrastructure/http" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" +) + +// GetPullRequest implements gitrepo.Repository. +func (c *client) GetPullRequest(ctx context.Context, id string) (*gitrepo.PullRequest, error) { + pullRequest, err := c.getPullRequest(ctx, id) + if err != nil { + return nil, err + } + diff, err := c.getPullRequestDiff(ctx, id) + if err != nil { + return nil, err + } + return &gitrepo.PullRequest{ + Id: id, + Title: pullRequest.Title, + Description: pullRequest.Body, + DiffPatch: diff, + }, nil +} + +func (c *client) getPullRequestDiff(ctx context.Context, id string) (string, error) { + url := fmt.Sprintf("%s/repos/%s/%s/pulls/%s", c.getBaseUrl(), c.owner, c.repoSlug, id) + req, err := c.buildRequest(ctx, http.MethodGet, url, nil) + if err != nil { + return "", err + } + req.Header.Add("Accept", "application/vnd.github.v3.diff") + httpResponse, err := c.httpClient.Do(req) + if err != nil { + return "", err + } + defer httpResponse.Body.Close() + body, err := io.ReadAll(httpResponse.Body) + if err != nil { + return "", err + } + return string(body), nil +} + +func (c *client) getPullRequest(ctx context.Context, id string) (*PullRequestResponse, error) { + url := fmt.Sprintf("%s/repos/%s/%s/pulls/%s", c.getBaseUrl(), c.owner, c.repoSlug, id) + req, err := c.buildRequest(ctx, http.MethodGet, url, nil) + if err != nil { + return nil, err + } + httpResponse, err := c.httpClient.Do(req) + if err != nil { + return nil, err + } + defer httpResponse.Body.Close() + var responseBody *PullRequestResponse + err = json.NewDecoder(httpResponse.Body).Decode(&responseBody) + if err != nil { + return nil, errors.Join(infraHttp.NewHttpClientError("failed decode repsonse", url)) + } + return responseBody, nil +} diff --git a/internal/infrastructure/http/github/helper.go b/internal/infrastructure/http/github/helper.go new file mode 100644 index 0000000..2a2876b --- /dev/null +++ b/internal/infrastructure/http/github/helper.go @@ -0,0 +1,27 @@ +package github + +import ( + infraHttp "codebleu/internal/infrastructure/http" + "context" + "errors" + "fmt" + "io" + "net/http" +) + +func (c *client) getBaseUrl() string { + return "https://api.github.com/" +} + +func (c *client) buildRequest(ctx context.Context, method string, requestUrl string, bodyPayload io.Reader) (*http.Request, error) { + request, err := http.NewRequestWithContext(ctx, method, requestUrl, bodyPayload) + if err != nil { + return nil, errors.Join(infraHttp.NewHttpClientError("failed construct request", requestUrl), err) + } + if bodyPayload != nil { + request.Header.Add("Accept", "application/vnd.github+json") + request.Header.Add("X-GitHub-Api-Version", "2022-11-28") + } + request.Header.Add("Authorization", fmt.Sprintf("Bearer %s", c.accessToken)) + return request, nil +} diff --git a/internal/infrastructure/http/github/payload.go b/internal/infrastructure/http/github/payload.go new file mode 100644 index 0000000..79cf119 --- /dev/null +++ b/internal/infrastructure/http/github/payload.go @@ -0,0 +1,502 @@ +package github + +import "time" + +type PullRequestResponse struct { + URL string `json:"url"` + ID int `json:"id"` + NodeID string `json:"node_id"` + HTMLURL string `json:"html_url"` + DiffURL string `json:"diff_url"` + PatchURL string `json:"patch_url"` + IssueURL string `json:"issue_url"` + CommitsURL string `json:"commits_url"` + ReviewCommentsURL string `json:"review_comments_url"` + ReviewCommentURL string `json:"review_comment_url"` + CommentsURL string `json:"comments_url"` + StatusesURL string `json:"statuses_url"` + Number int `json:"number"` + State string `json:"state"` + Locked bool `json:"locked"` + Title string `json:"title"` + User struct { + Login string `json:"login"` + ID int `json:"id"` + NodeID string `json:"node_id"` + AvatarURL string `json:"avatar_url"` + GravatarID string `json:"gravatar_id"` + URL string `json:"url"` + HTMLURL string `json:"html_url"` + FollowersURL string `json:"followers_url"` + FollowingURL string `json:"following_url"` + GistsURL string `json:"gists_url"` + StarredURL string `json:"starred_url"` + SubscriptionsURL string `json:"subscriptions_url"` + OrganizationsURL string `json:"organizations_url"` + ReposURL string `json:"repos_url"` + EventsURL string `json:"events_url"` + ReceivedEventsURL string `json:"received_events_url"` + Type string `json:"type"` + SiteAdmin bool `json:"site_admin"` + } `json:"user"` + Body string `json:"body"` + Labels []struct { + ID int `json:"id"` + NodeID string `json:"node_id"` + URL string `json:"url"` + Name string `json:"name"` + Description string `json:"description"` + Color string `json:"color"` + Default bool `json:"default"` + } `json:"labels"` + Milestone struct { + URL string `json:"url"` + HTMLURL string `json:"html_url"` + LabelsURL string `json:"labels_url"` + ID int `json:"id"` + NodeID string `json:"node_id"` + Number int `json:"number"` + State string `json:"state"` + Title string `json:"title"` + Description string `json:"description"` + Creator struct { + Login string `json:"login"` + ID int `json:"id"` + NodeID string `json:"node_id"` + AvatarURL string `json:"avatar_url"` + GravatarID string `json:"gravatar_id"` + URL string `json:"url"` + HTMLURL string `json:"html_url"` + FollowersURL string `json:"followers_url"` + FollowingURL string `json:"following_url"` + GistsURL string `json:"gists_url"` + StarredURL string `json:"starred_url"` + SubscriptionsURL string `json:"subscriptions_url"` + OrganizationsURL string `json:"organizations_url"` + ReposURL string `json:"repos_url"` + EventsURL string `json:"events_url"` + ReceivedEventsURL string `json:"received_events_url"` + Type string `json:"type"` + SiteAdmin bool `json:"site_admin"` + } `json:"creator"` + OpenIssues int `json:"open_issues"` + ClosedIssues int `json:"closed_issues"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + ClosedAt time.Time `json:"closed_at"` + DueOn time.Time `json:"due_on"` + } `json:"milestone"` + ActiveLockReason string `json:"active_lock_reason"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + ClosedAt time.Time `json:"closed_at"` + MergedAt time.Time `json:"merged_at"` + MergeCommitSha string `json:"merge_commit_sha"` + Assignee struct { + Login string `json:"login"` + ID int `json:"id"` + NodeID string `json:"node_id"` + AvatarURL string `json:"avatar_url"` + GravatarID string `json:"gravatar_id"` + URL string `json:"url"` + HTMLURL string `json:"html_url"` + FollowersURL string `json:"followers_url"` + FollowingURL string `json:"following_url"` + GistsURL string `json:"gists_url"` + StarredURL string `json:"starred_url"` + SubscriptionsURL string `json:"subscriptions_url"` + OrganizationsURL string `json:"organizations_url"` + ReposURL string `json:"repos_url"` + EventsURL string `json:"events_url"` + ReceivedEventsURL string `json:"received_events_url"` + Type string `json:"type"` + SiteAdmin bool `json:"site_admin"` + } `json:"assignee"` + Assignees []struct { + Login string `json:"login"` + ID int `json:"id"` + NodeID string `json:"node_id"` + AvatarURL string `json:"avatar_url"` + GravatarID string `json:"gravatar_id"` + URL string `json:"url"` + HTMLURL string `json:"html_url"` + FollowersURL string `json:"followers_url"` + FollowingURL string `json:"following_url"` + GistsURL string `json:"gists_url"` + StarredURL string `json:"starred_url"` + SubscriptionsURL string `json:"subscriptions_url"` + OrganizationsURL string `json:"organizations_url"` + ReposURL string `json:"repos_url"` + EventsURL string `json:"events_url"` + ReceivedEventsURL string `json:"received_events_url"` + Type string `json:"type"` + SiteAdmin bool `json:"site_admin"` + } `json:"assignees"` + RequestedReviewers []struct { + Login string `json:"login"` + ID int `json:"id"` + NodeID string `json:"node_id"` + AvatarURL string `json:"avatar_url"` + GravatarID string `json:"gravatar_id"` + URL string `json:"url"` + HTMLURL string `json:"html_url"` + FollowersURL string `json:"followers_url"` + FollowingURL string `json:"following_url"` + GistsURL string `json:"gists_url"` + StarredURL string `json:"starred_url"` + SubscriptionsURL string `json:"subscriptions_url"` + OrganizationsURL string `json:"organizations_url"` + ReposURL string `json:"repos_url"` + EventsURL string `json:"events_url"` + ReceivedEventsURL string `json:"received_events_url"` + Type string `json:"type"` + SiteAdmin bool `json:"site_admin"` + } `json:"requested_reviewers"` + RequestedTeams []struct { + ID int `json:"id"` + NodeID string `json:"node_id"` + URL string `json:"url"` + HTMLURL string `json:"html_url"` + Name string `json:"name"` + Slug string `json:"slug"` + Description string `json:"description"` + Privacy string `json:"privacy"` + NotificationSetting string `json:"notification_setting"` + Permission string `json:"permission"` + MembersURL string `json:"members_url"` + RepositoriesURL string `json:"repositories_url"` + } `json:"requested_teams"` + Head struct { + Label string `json:"label"` + Ref string `json:"ref"` + Sha string `json:"sha"` + User struct { + Login string `json:"login"` + ID int `json:"id"` + NodeID string `json:"node_id"` + AvatarURL string `json:"avatar_url"` + GravatarID string `json:"gravatar_id"` + URL string `json:"url"` + HTMLURL string `json:"html_url"` + FollowersURL string `json:"followers_url"` + FollowingURL string `json:"following_url"` + GistsURL string `json:"gists_url"` + StarredURL string `json:"starred_url"` + SubscriptionsURL string `json:"subscriptions_url"` + OrganizationsURL string `json:"organizations_url"` + ReposURL string `json:"repos_url"` + EventsURL string `json:"events_url"` + ReceivedEventsURL string `json:"received_events_url"` + Type string `json:"type"` + SiteAdmin bool `json:"site_admin"` + } `json:"user"` + Repo struct { + ID int `json:"id"` + NodeID string `json:"node_id"` + Name string `json:"name"` + FullName string `json:"full_name"` + Owner struct { + Login string `json:"login"` + ID int `json:"id"` + NodeID string `json:"node_id"` + AvatarURL string `json:"avatar_url"` + GravatarID string `json:"gravatar_id"` + URL string `json:"url"` + HTMLURL string `json:"html_url"` + FollowersURL string `json:"followers_url"` + FollowingURL string `json:"following_url"` + GistsURL string `json:"gists_url"` + StarredURL string `json:"starred_url"` + SubscriptionsURL string `json:"subscriptions_url"` + OrganizationsURL string `json:"organizations_url"` + ReposURL string `json:"repos_url"` + EventsURL string `json:"events_url"` + ReceivedEventsURL string `json:"received_events_url"` + Type string `json:"type"` + SiteAdmin bool `json:"site_admin"` + } `json:"owner"` + Private bool `json:"private"` + HTMLURL string `json:"html_url"` + Description string `json:"description"` + Fork bool `json:"fork"` + URL string `json:"url"` + ArchiveURL string `json:"archive_url"` + AssigneesURL string `json:"assignees_url"` + BlobsURL string `json:"blobs_url"` + BranchesURL string `json:"branches_url"` + CollaboratorsURL string `json:"collaborators_url"` + CommentsURL string `json:"comments_url"` + CommitsURL string `json:"commits_url"` + CompareURL string `json:"compare_url"` + ContentsURL string `json:"contents_url"` + ContributorsURL string `json:"contributors_url"` + DeploymentsURL string `json:"deployments_url"` + DownloadsURL string `json:"downloads_url"` + EventsURL string `json:"events_url"` + ForksURL string `json:"forks_url"` + GitCommitsURL string `json:"git_commits_url"` + GitRefsURL string `json:"git_refs_url"` + GitTagsURL string `json:"git_tags_url"` + GitURL string `json:"git_url"` + IssueCommentURL string `json:"issue_comment_url"` + IssueEventsURL string `json:"issue_events_url"` + IssuesURL string `json:"issues_url"` + KeysURL string `json:"keys_url"` + LabelsURL string `json:"labels_url"` + LanguagesURL string `json:"languages_url"` + MergesURL string `json:"merges_url"` + MilestonesURL string `json:"milestones_url"` + NotificationsURL string `json:"notifications_url"` + PullsURL string `json:"pulls_url"` + ReleasesURL string `json:"releases_url"` + SSHURL string `json:"ssh_url"` + StargazersURL string `json:"stargazers_url"` + StatusesURL string `json:"statuses_url"` + SubscribersURL string `json:"subscribers_url"` + SubscriptionURL string `json:"subscription_url"` + TagsURL string `json:"tags_url"` + TeamsURL string `json:"teams_url"` + TreesURL string `json:"trees_url"` + CloneURL string `json:"clone_url"` + MirrorURL string `json:"mirror_url"` + HooksURL string `json:"hooks_url"` + SvnURL string `json:"svn_url"` + Homepage string `json:"homepage"` + Language interface{} `json:"language"` + ForksCount int `json:"forks_count"` + StargazersCount int `json:"stargazers_count"` + WatchersCount int `json:"watchers_count"` + Size int `json:"size"` + DefaultBranch string `json:"default_branch"` + OpenIssuesCount int `json:"open_issues_count"` + Topics []string `json:"topics"` + HasIssues bool `json:"has_issues"` + HasProjects bool `json:"has_projects"` + HasWiki bool `json:"has_wiki"` + HasPages bool `json:"has_pages"` + HasDownloads bool `json:"has_downloads"` + HasDiscussions bool `json:"has_discussions"` + Archived bool `json:"archived"` + Disabled bool `json:"disabled"` + PushedAt time.Time `json:"pushed_at"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + Permissions struct { + Admin bool `json:"admin"` + Push bool `json:"push"` + Pull bool `json:"pull"` + } `json:"permissions"` + AllowRebaseMerge bool `json:"allow_rebase_merge"` + TempCloneToken string `json:"temp_clone_token"` + AllowSquashMerge bool `json:"allow_squash_merge"` + AllowMergeCommit bool `json:"allow_merge_commit"` + AllowForking bool `json:"allow_forking"` + Forks int `json:"forks"` + OpenIssues int `json:"open_issues"` + License struct { + Key string `json:"key"` + Name string `json:"name"` + URL string `json:"url"` + SpdxID string `json:"spdx_id"` + NodeID string `json:"node_id"` + } `json:"license"` + Watchers int `json:"watchers"` + } `json:"repo"` + } `json:"head"` + Base struct { + Label string `json:"label"` + Ref string `json:"ref"` + Sha string `json:"sha"` + User struct { + Login string `json:"login"` + ID int `json:"id"` + NodeID string `json:"node_id"` + AvatarURL string `json:"avatar_url"` + GravatarID string `json:"gravatar_id"` + URL string `json:"url"` + HTMLURL string `json:"html_url"` + FollowersURL string `json:"followers_url"` + FollowingURL string `json:"following_url"` + GistsURL string `json:"gists_url"` + StarredURL string `json:"starred_url"` + SubscriptionsURL string `json:"subscriptions_url"` + OrganizationsURL string `json:"organizations_url"` + ReposURL string `json:"repos_url"` + EventsURL string `json:"events_url"` + ReceivedEventsURL string `json:"received_events_url"` + Type string `json:"type"` + SiteAdmin bool `json:"site_admin"` + } `json:"user"` + Repo struct { + ID int `json:"id"` + NodeID string `json:"node_id"` + Name string `json:"name"` + FullName string `json:"full_name"` + Owner struct { + Login string `json:"login"` + ID int `json:"id"` + NodeID string `json:"node_id"` + AvatarURL string `json:"avatar_url"` + GravatarID string `json:"gravatar_id"` + URL string `json:"url"` + HTMLURL string `json:"html_url"` + FollowersURL string `json:"followers_url"` + FollowingURL string `json:"following_url"` + GistsURL string `json:"gists_url"` + StarredURL string `json:"starred_url"` + SubscriptionsURL string `json:"subscriptions_url"` + OrganizationsURL string `json:"organizations_url"` + ReposURL string `json:"repos_url"` + EventsURL string `json:"events_url"` + ReceivedEventsURL string `json:"received_events_url"` + Type string `json:"type"` + SiteAdmin bool `json:"site_admin"` + } `json:"owner"` + Private bool `json:"private"` + HTMLURL string `json:"html_url"` + Description string `json:"description"` + Fork bool `json:"fork"` + URL string `json:"url"` + ArchiveURL string `json:"archive_url"` + AssigneesURL string `json:"assignees_url"` + BlobsURL string `json:"blobs_url"` + BranchesURL string `json:"branches_url"` + CollaboratorsURL string `json:"collaborators_url"` + CommentsURL string `json:"comments_url"` + CommitsURL string `json:"commits_url"` + CompareURL string `json:"compare_url"` + ContentsURL string `json:"contents_url"` + ContributorsURL string `json:"contributors_url"` + DeploymentsURL string `json:"deployments_url"` + DownloadsURL string `json:"downloads_url"` + EventsURL string `json:"events_url"` + ForksURL string `json:"forks_url"` + GitCommitsURL string `json:"git_commits_url"` + GitRefsURL string `json:"git_refs_url"` + GitTagsURL string `json:"git_tags_url"` + GitURL string `json:"git_url"` + IssueCommentURL string `json:"issue_comment_url"` + IssueEventsURL string `json:"issue_events_url"` + IssuesURL string `json:"issues_url"` + KeysURL string `json:"keys_url"` + LabelsURL string `json:"labels_url"` + LanguagesURL string `json:"languages_url"` + MergesURL string `json:"merges_url"` + MilestonesURL string `json:"milestones_url"` + NotificationsURL string `json:"notifications_url"` + PullsURL string `json:"pulls_url"` + ReleasesURL string `json:"releases_url"` + SSHURL string `json:"ssh_url"` + StargazersURL string `json:"stargazers_url"` + StatusesURL string `json:"statuses_url"` + SubscribersURL string `json:"subscribers_url"` + SubscriptionURL string `json:"subscription_url"` + TagsURL string `json:"tags_url"` + TeamsURL string `json:"teams_url"` + TreesURL string `json:"trees_url"` + CloneURL string `json:"clone_url"` + MirrorURL string `json:"mirror_url"` + HooksURL string `json:"hooks_url"` + SvnURL string `json:"svn_url"` + Homepage string `json:"homepage"` + Language interface{} `json:"language"` + ForksCount int `json:"forks_count"` + StargazersCount int `json:"stargazers_count"` + WatchersCount int `json:"watchers_count"` + Size int `json:"size"` + DefaultBranch string `json:"default_branch"` + OpenIssuesCount int `json:"open_issues_count"` + Topics []string `json:"topics"` + HasIssues bool `json:"has_issues"` + HasProjects bool `json:"has_projects"` + HasWiki bool `json:"has_wiki"` + HasPages bool `json:"has_pages"` + HasDownloads bool `json:"has_downloads"` + HasDiscussions bool `json:"has_discussions"` + Archived bool `json:"archived"` + Disabled bool `json:"disabled"` + PushedAt time.Time `json:"pushed_at"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + Permissions struct { + Admin bool `json:"admin"` + Push bool `json:"push"` + Pull bool `json:"pull"` + } `json:"permissions"` + AllowRebaseMerge bool `json:"allow_rebase_merge"` + TempCloneToken string `json:"temp_clone_token"` + AllowSquashMerge bool `json:"allow_squash_merge"` + AllowMergeCommit bool `json:"allow_merge_commit"` + Forks int `json:"forks"` + OpenIssues int `json:"open_issues"` + License struct { + Key string `json:"key"` + Name string `json:"name"` + URL string `json:"url"` + SpdxID string `json:"spdx_id"` + NodeID string `json:"node_id"` + } `json:"license"` + Watchers int `json:"watchers"` + } `json:"repo"` + } `json:"base"` + Links struct { + Self struct { + Href string `json:"href"` + } `json:"self"` + HTML struct { + Href string `json:"href"` + } `json:"html"` + Issue struct { + Href string `json:"href"` + } `json:"issue"` + Comments struct { + Href string `json:"href"` + } `json:"comments"` + ReviewComments struct { + Href string `json:"href"` + } `json:"review_comments"` + ReviewComment struct { + Href string `json:"href"` + } `json:"review_comment"` + Commits struct { + Href string `json:"href"` + } `json:"commits"` + Statuses struct { + Href string `json:"href"` + } `json:"statuses"` + } `json:"_links"` + AuthorAssociation string `json:"author_association"` + AutoMerge interface{} `json:"auto_merge"` + Draft bool `json:"draft"` + Merged bool `json:"merged"` + Mergeable bool `json:"mergeable"` + Rebaseable bool `json:"rebaseable"` + MergeableState string `json:"mergeable_state"` + MergedBy struct { + Login string `json:"login"` + ID int `json:"id"` + NodeID string `json:"node_id"` + AvatarURL string `json:"avatar_url"` + GravatarID string `json:"gravatar_id"` + URL string `json:"url"` + HTMLURL string `json:"html_url"` + FollowersURL string `json:"followers_url"` + FollowingURL string `json:"following_url"` + GistsURL string `json:"gists_url"` + StarredURL string `json:"starred_url"` + SubscriptionsURL string `json:"subscriptions_url"` + OrganizationsURL string `json:"organizations_url"` + ReposURL string `json:"repos_url"` + EventsURL string `json:"events_url"` + ReceivedEventsURL string `json:"received_events_url"` + Type string `json:"type"` + SiteAdmin bool `json:"site_admin"` + } `json:"merged_by"` + Comments int `json:"comments"` + ReviewComments int `json:"review_comments"` + MaintainerCanModify bool `json:"maintainer_can_modify"` + Commits int `json:"commits"` + Additions int `json:"additions"` + Deletions int `json:"deletions"` + ChangedFiles int `json:"changed_files"` +} From a8df929d0e055a95824b5e04636cf97a750fff02 Mon Sep 17 00:00:00 2001 From: amalhanaja Date: Sat, 29 Jun 2024 10:42:23 +0700 Subject: [PATCH 2/6] feat: reduce unecessary comment response --- internal/infrastructure/http/github/client.go | 4 +- .../http/github/get_pull_request.go | 3 + internal/infrastructure/http/github/helper.go | 2 +- .../infrastructure/http/github/payload.go | 501 +----------------- 4 files changed, 10 insertions(+), 500 deletions(-) diff --git a/internal/infrastructure/http/github/client.go b/internal/infrastructure/http/github/client.go index 5d3df56..fe859a1 100644 --- a/internal/infrastructure/http/github/client.go +++ b/internal/infrastructure/http/github/client.go @@ -3,6 +3,7 @@ package github import ( "codebleu/internal/domain/gitrepo" "context" + "fmt" "net/http" ) @@ -15,7 +16,8 @@ type client struct { // PostPullRequestComment implements gitrepo.Repository. func (c *client) PostPullRequestComment(ctx context.Context, input gitrepo.PostPullRequestCommentInput) error { - panic("unimplemented") + fmt.Println(input.Comment) + return nil } func NewClient( diff --git a/internal/infrastructure/http/github/get_pull_request.go b/internal/infrastructure/http/github/get_pull_request.go index b816cc4..ec197e0 100644 --- a/internal/infrastructure/http/github/get_pull_request.go +++ b/internal/infrastructure/http/github/get_pull_request.go @@ -45,6 +45,9 @@ func (c *client) getPullRequestDiff(ctx context.Context, id string) (string, err if err != nil { return "", err } + if httpResponse.StatusCode != http.StatusOK { + return "", infraHttp.NewHttpClientError(fmt.Sprintf("response failed %s", string(body)), url) + } return string(body), nil } diff --git a/internal/infrastructure/http/github/helper.go b/internal/infrastructure/http/github/helper.go index 2a2876b..178682a 100644 --- a/internal/infrastructure/http/github/helper.go +++ b/internal/infrastructure/http/github/helper.go @@ -10,7 +10,7 @@ import ( ) func (c *client) getBaseUrl() string { - return "https://api.github.com/" + return "https://api.github.com" } func (c *client) buildRequest(ctx context.Context, method string, requestUrl string, bodyPayload io.Reader) (*http.Request, error) { diff --git a/internal/infrastructure/http/github/payload.go b/internal/infrastructure/http/github/payload.go index 79cf119..cad4b41 100644 --- a/internal/infrastructure/http/github/payload.go +++ b/internal/infrastructure/http/github/payload.go @@ -1,502 +1,7 @@ package github -import "time" - type PullRequestResponse struct { - URL string `json:"url"` - ID int `json:"id"` - NodeID string `json:"node_id"` - HTMLURL string `json:"html_url"` - DiffURL string `json:"diff_url"` - PatchURL string `json:"patch_url"` - IssueURL string `json:"issue_url"` - CommitsURL string `json:"commits_url"` - ReviewCommentsURL string `json:"review_comments_url"` - ReviewCommentURL string `json:"review_comment_url"` - CommentsURL string `json:"comments_url"` - StatusesURL string `json:"statuses_url"` - Number int `json:"number"` - State string `json:"state"` - Locked bool `json:"locked"` - Title string `json:"title"` - User struct { - Login string `json:"login"` - ID int `json:"id"` - NodeID string `json:"node_id"` - AvatarURL string `json:"avatar_url"` - GravatarID string `json:"gravatar_id"` - URL string `json:"url"` - HTMLURL string `json:"html_url"` - FollowersURL string `json:"followers_url"` - FollowingURL string `json:"following_url"` - GistsURL string `json:"gists_url"` - StarredURL string `json:"starred_url"` - SubscriptionsURL string `json:"subscriptions_url"` - OrganizationsURL string `json:"organizations_url"` - ReposURL string `json:"repos_url"` - EventsURL string `json:"events_url"` - ReceivedEventsURL string `json:"received_events_url"` - Type string `json:"type"` - SiteAdmin bool `json:"site_admin"` - } `json:"user"` - Body string `json:"body"` - Labels []struct { - ID int `json:"id"` - NodeID string `json:"node_id"` - URL string `json:"url"` - Name string `json:"name"` - Description string `json:"description"` - Color string `json:"color"` - Default bool `json:"default"` - } `json:"labels"` - Milestone struct { - URL string `json:"url"` - HTMLURL string `json:"html_url"` - LabelsURL string `json:"labels_url"` - ID int `json:"id"` - NodeID string `json:"node_id"` - Number int `json:"number"` - State string `json:"state"` - Title string `json:"title"` - Description string `json:"description"` - Creator struct { - Login string `json:"login"` - ID int `json:"id"` - NodeID string `json:"node_id"` - AvatarURL string `json:"avatar_url"` - GravatarID string `json:"gravatar_id"` - URL string `json:"url"` - HTMLURL string `json:"html_url"` - FollowersURL string `json:"followers_url"` - FollowingURL string `json:"following_url"` - GistsURL string `json:"gists_url"` - StarredURL string `json:"starred_url"` - SubscriptionsURL string `json:"subscriptions_url"` - OrganizationsURL string `json:"organizations_url"` - ReposURL string `json:"repos_url"` - EventsURL string `json:"events_url"` - ReceivedEventsURL string `json:"received_events_url"` - Type string `json:"type"` - SiteAdmin bool `json:"site_admin"` - } `json:"creator"` - OpenIssues int `json:"open_issues"` - ClosedIssues int `json:"closed_issues"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` - ClosedAt time.Time `json:"closed_at"` - DueOn time.Time `json:"due_on"` - } `json:"milestone"` - ActiveLockReason string `json:"active_lock_reason"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` - ClosedAt time.Time `json:"closed_at"` - MergedAt time.Time `json:"merged_at"` - MergeCommitSha string `json:"merge_commit_sha"` - Assignee struct { - Login string `json:"login"` - ID int `json:"id"` - NodeID string `json:"node_id"` - AvatarURL string `json:"avatar_url"` - GravatarID string `json:"gravatar_id"` - URL string `json:"url"` - HTMLURL string `json:"html_url"` - FollowersURL string `json:"followers_url"` - FollowingURL string `json:"following_url"` - GistsURL string `json:"gists_url"` - StarredURL string `json:"starred_url"` - SubscriptionsURL string `json:"subscriptions_url"` - OrganizationsURL string `json:"organizations_url"` - ReposURL string `json:"repos_url"` - EventsURL string `json:"events_url"` - ReceivedEventsURL string `json:"received_events_url"` - Type string `json:"type"` - SiteAdmin bool `json:"site_admin"` - } `json:"assignee"` - Assignees []struct { - Login string `json:"login"` - ID int `json:"id"` - NodeID string `json:"node_id"` - AvatarURL string `json:"avatar_url"` - GravatarID string `json:"gravatar_id"` - URL string `json:"url"` - HTMLURL string `json:"html_url"` - FollowersURL string `json:"followers_url"` - FollowingURL string `json:"following_url"` - GistsURL string `json:"gists_url"` - StarredURL string `json:"starred_url"` - SubscriptionsURL string `json:"subscriptions_url"` - OrganizationsURL string `json:"organizations_url"` - ReposURL string `json:"repos_url"` - EventsURL string `json:"events_url"` - ReceivedEventsURL string `json:"received_events_url"` - Type string `json:"type"` - SiteAdmin bool `json:"site_admin"` - } `json:"assignees"` - RequestedReviewers []struct { - Login string `json:"login"` - ID int `json:"id"` - NodeID string `json:"node_id"` - AvatarURL string `json:"avatar_url"` - GravatarID string `json:"gravatar_id"` - URL string `json:"url"` - HTMLURL string `json:"html_url"` - FollowersURL string `json:"followers_url"` - FollowingURL string `json:"following_url"` - GistsURL string `json:"gists_url"` - StarredURL string `json:"starred_url"` - SubscriptionsURL string `json:"subscriptions_url"` - OrganizationsURL string `json:"organizations_url"` - ReposURL string `json:"repos_url"` - EventsURL string `json:"events_url"` - ReceivedEventsURL string `json:"received_events_url"` - Type string `json:"type"` - SiteAdmin bool `json:"site_admin"` - } `json:"requested_reviewers"` - RequestedTeams []struct { - ID int `json:"id"` - NodeID string `json:"node_id"` - URL string `json:"url"` - HTMLURL string `json:"html_url"` - Name string `json:"name"` - Slug string `json:"slug"` - Description string `json:"description"` - Privacy string `json:"privacy"` - NotificationSetting string `json:"notification_setting"` - Permission string `json:"permission"` - MembersURL string `json:"members_url"` - RepositoriesURL string `json:"repositories_url"` - } `json:"requested_teams"` - Head struct { - Label string `json:"label"` - Ref string `json:"ref"` - Sha string `json:"sha"` - User struct { - Login string `json:"login"` - ID int `json:"id"` - NodeID string `json:"node_id"` - AvatarURL string `json:"avatar_url"` - GravatarID string `json:"gravatar_id"` - URL string `json:"url"` - HTMLURL string `json:"html_url"` - FollowersURL string `json:"followers_url"` - FollowingURL string `json:"following_url"` - GistsURL string `json:"gists_url"` - StarredURL string `json:"starred_url"` - SubscriptionsURL string `json:"subscriptions_url"` - OrganizationsURL string `json:"organizations_url"` - ReposURL string `json:"repos_url"` - EventsURL string `json:"events_url"` - ReceivedEventsURL string `json:"received_events_url"` - Type string `json:"type"` - SiteAdmin bool `json:"site_admin"` - } `json:"user"` - Repo struct { - ID int `json:"id"` - NodeID string `json:"node_id"` - Name string `json:"name"` - FullName string `json:"full_name"` - Owner struct { - Login string `json:"login"` - ID int `json:"id"` - NodeID string `json:"node_id"` - AvatarURL string `json:"avatar_url"` - GravatarID string `json:"gravatar_id"` - URL string `json:"url"` - HTMLURL string `json:"html_url"` - FollowersURL string `json:"followers_url"` - FollowingURL string `json:"following_url"` - GistsURL string `json:"gists_url"` - StarredURL string `json:"starred_url"` - SubscriptionsURL string `json:"subscriptions_url"` - OrganizationsURL string `json:"organizations_url"` - ReposURL string `json:"repos_url"` - EventsURL string `json:"events_url"` - ReceivedEventsURL string `json:"received_events_url"` - Type string `json:"type"` - SiteAdmin bool `json:"site_admin"` - } `json:"owner"` - Private bool `json:"private"` - HTMLURL string `json:"html_url"` - Description string `json:"description"` - Fork bool `json:"fork"` - URL string `json:"url"` - ArchiveURL string `json:"archive_url"` - AssigneesURL string `json:"assignees_url"` - BlobsURL string `json:"blobs_url"` - BranchesURL string `json:"branches_url"` - CollaboratorsURL string `json:"collaborators_url"` - CommentsURL string `json:"comments_url"` - CommitsURL string `json:"commits_url"` - CompareURL string `json:"compare_url"` - ContentsURL string `json:"contents_url"` - ContributorsURL string `json:"contributors_url"` - DeploymentsURL string `json:"deployments_url"` - DownloadsURL string `json:"downloads_url"` - EventsURL string `json:"events_url"` - ForksURL string `json:"forks_url"` - GitCommitsURL string `json:"git_commits_url"` - GitRefsURL string `json:"git_refs_url"` - GitTagsURL string `json:"git_tags_url"` - GitURL string `json:"git_url"` - IssueCommentURL string `json:"issue_comment_url"` - IssueEventsURL string `json:"issue_events_url"` - IssuesURL string `json:"issues_url"` - KeysURL string `json:"keys_url"` - LabelsURL string `json:"labels_url"` - LanguagesURL string `json:"languages_url"` - MergesURL string `json:"merges_url"` - MilestonesURL string `json:"milestones_url"` - NotificationsURL string `json:"notifications_url"` - PullsURL string `json:"pulls_url"` - ReleasesURL string `json:"releases_url"` - SSHURL string `json:"ssh_url"` - StargazersURL string `json:"stargazers_url"` - StatusesURL string `json:"statuses_url"` - SubscribersURL string `json:"subscribers_url"` - SubscriptionURL string `json:"subscription_url"` - TagsURL string `json:"tags_url"` - TeamsURL string `json:"teams_url"` - TreesURL string `json:"trees_url"` - CloneURL string `json:"clone_url"` - MirrorURL string `json:"mirror_url"` - HooksURL string `json:"hooks_url"` - SvnURL string `json:"svn_url"` - Homepage string `json:"homepage"` - Language interface{} `json:"language"` - ForksCount int `json:"forks_count"` - StargazersCount int `json:"stargazers_count"` - WatchersCount int `json:"watchers_count"` - Size int `json:"size"` - DefaultBranch string `json:"default_branch"` - OpenIssuesCount int `json:"open_issues_count"` - Topics []string `json:"topics"` - HasIssues bool `json:"has_issues"` - HasProjects bool `json:"has_projects"` - HasWiki bool `json:"has_wiki"` - HasPages bool `json:"has_pages"` - HasDownloads bool `json:"has_downloads"` - HasDiscussions bool `json:"has_discussions"` - Archived bool `json:"archived"` - Disabled bool `json:"disabled"` - PushedAt time.Time `json:"pushed_at"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` - Permissions struct { - Admin bool `json:"admin"` - Push bool `json:"push"` - Pull bool `json:"pull"` - } `json:"permissions"` - AllowRebaseMerge bool `json:"allow_rebase_merge"` - TempCloneToken string `json:"temp_clone_token"` - AllowSquashMerge bool `json:"allow_squash_merge"` - AllowMergeCommit bool `json:"allow_merge_commit"` - AllowForking bool `json:"allow_forking"` - Forks int `json:"forks"` - OpenIssues int `json:"open_issues"` - License struct { - Key string `json:"key"` - Name string `json:"name"` - URL string `json:"url"` - SpdxID string `json:"spdx_id"` - NodeID string `json:"node_id"` - } `json:"license"` - Watchers int `json:"watchers"` - } `json:"repo"` - } `json:"head"` - Base struct { - Label string `json:"label"` - Ref string `json:"ref"` - Sha string `json:"sha"` - User struct { - Login string `json:"login"` - ID int `json:"id"` - NodeID string `json:"node_id"` - AvatarURL string `json:"avatar_url"` - GravatarID string `json:"gravatar_id"` - URL string `json:"url"` - HTMLURL string `json:"html_url"` - FollowersURL string `json:"followers_url"` - FollowingURL string `json:"following_url"` - GistsURL string `json:"gists_url"` - StarredURL string `json:"starred_url"` - SubscriptionsURL string `json:"subscriptions_url"` - OrganizationsURL string `json:"organizations_url"` - ReposURL string `json:"repos_url"` - EventsURL string `json:"events_url"` - ReceivedEventsURL string `json:"received_events_url"` - Type string `json:"type"` - SiteAdmin bool `json:"site_admin"` - } `json:"user"` - Repo struct { - ID int `json:"id"` - NodeID string `json:"node_id"` - Name string `json:"name"` - FullName string `json:"full_name"` - Owner struct { - Login string `json:"login"` - ID int `json:"id"` - NodeID string `json:"node_id"` - AvatarURL string `json:"avatar_url"` - GravatarID string `json:"gravatar_id"` - URL string `json:"url"` - HTMLURL string `json:"html_url"` - FollowersURL string `json:"followers_url"` - FollowingURL string `json:"following_url"` - GistsURL string `json:"gists_url"` - StarredURL string `json:"starred_url"` - SubscriptionsURL string `json:"subscriptions_url"` - OrganizationsURL string `json:"organizations_url"` - ReposURL string `json:"repos_url"` - EventsURL string `json:"events_url"` - ReceivedEventsURL string `json:"received_events_url"` - Type string `json:"type"` - SiteAdmin bool `json:"site_admin"` - } `json:"owner"` - Private bool `json:"private"` - HTMLURL string `json:"html_url"` - Description string `json:"description"` - Fork bool `json:"fork"` - URL string `json:"url"` - ArchiveURL string `json:"archive_url"` - AssigneesURL string `json:"assignees_url"` - BlobsURL string `json:"blobs_url"` - BranchesURL string `json:"branches_url"` - CollaboratorsURL string `json:"collaborators_url"` - CommentsURL string `json:"comments_url"` - CommitsURL string `json:"commits_url"` - CompareURL string `json:"compare_url"` - ContentsURL string `json:"contents_url"` - ContributorsURL string `json:"contributors_url"` - DeploymentsURL string `json:"deployments_url"` - DownloadsURL string `json:"downloads_url"` - EventsURL string `json:"events_url"` - ForksURL string `json:"forks_url"` - GitCommitsURL string `json:"git_commits_url"` - GitRefsURL string `json:"git_refs_url"` - GitTagsURL string `json:"git_tags_url"` - GitURL string `json:"git_url"` - IssueCommentURL string `json:"issue_comment_url"` - IssueEventsURL string `json:"issue_events_url"` - IssuesURL string `json:"issues_url"` - KeysURL string `json:"keys_url"` - LabelsURL string `json:"labels_url"` - LanguagesURL string `json:"languages_url"` - MergesURL string `json:"merges_url"` - MilestonesURL string `json:"milestones_url"` - NotificationsURL string `json:"notifications_url"` - PullsURL string `json:"pulls_url"` - ReleasesURL string `json:"releases_url"` - SSHURL string `json:"ssh_url"` - StargazersURL string `json:"stargazers_url"` - StatusesURL string `json:"statuses_url"` - SubscribersURL string `json:"subscribers_url"` - SubscriptionURL string `json:"subscription_url"` - TagsURL string `json:"tags_url"` - TeamsURL string `json:"teams_url"` - TreesURL string `json:"trees_url"` - CloneURL string `json:"clone_url"` - MirrorURL string `json:"mirror_url"` - HooksURL string `json:"hooks_url"` - SvnURL string `json:"svn_url"` - Homepage string `json:"homepage"` - Language interface{} `json:"language"` - ForksCount int `json:"forks_count"` - StargazersCount int `json:"stargazers_count"` - WatchersCount int `json:"watchers_count"` - Size int `json:"size"` - DefaultBranch string `json:"default_branch"` - OpenIssuesCount int `json:"open_issues_count"` - Topics []string `json:"topics"` - HasIssues bool `json:"has_issues"` - HasProjects bool `json:"has_projects"` - HasWiki bool `json:"has_wiki"` - HasPages bool `json:"has_pages"` - HasDownloads bool `json:"has_downloads"` - HasDiscussions bool `json:"has_discussions"` - Archived bool `json:"archived"` - Disabled bool `json:"disabled"` - PushedAt time.Time `json:"pushed_at"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` - Permissions struct { - Admin bool `json:"admin"` - Push bool `json:"push"` - Pull bool `json:"pull"` - } `json:"permissions"` - AllowRebaseMerge bool `json:"allow_rebase_merge"` - TempCloneToken string `json:"temp_clone_token"` - AllowSquashMerge bool `json:"allow_squash_merge"` - AllowMergeCommit bool `json:"allow_merge_commit"` - Forks int `json:"forks"` - OpenIssues int `json:"open_issues"` - License struct { - Key string `json:"key"` - Name string `json:"name"` - URL string `json:"url"` - SpdxID string `json:"spdx_id"` - NodeID string `json:"node_id"` - } `json:"license"` - Watchers int `json:"watchers"` - } `json:"repo"` - } `json:"base"` - Links struct { - Self struct { - Href string `json:"href"` - } `json:"self"` - HTML struct { - Href string `json:"href"` - } `json:"html"` - Issue struct { - Href string `json:"href"` - } `json:"issue"` - Comments struct { - Href string `json:"href"` - } `json:"comments"` - ReviewComments struct { - Href string `json:"href"` - } `json:"review_comments"` - ReviewComment struct { - Href string `json:"href"` - } `json:"review_comment"` - Commits struct { - Href string `json:"href"` - } `json:"commits"` - Statuses struct { - Href string `json:"href"` - } `json:"statuses"` - } `json:"_links"` - AuthorAssociation string `json:"author_association"` - AutoMerge interface{} `json:"auto_merge"` - Draft bool `json:"draft"` - Merged bool `json:"merged"` - Mergeable bool `json:"mergeable"` - Rebaseable bool `json:"rebaseable"` - MergeableState string `json:"mergeable_state"` - MergedBy struct { - Login string `json:"login"` - ID int `json:"id"` - NodeID string `json:"node_id"` - AvatarURL string `json:"avatar_url"` - GravatarID string `json:"gravatar_id"` - URL string `json:"url"` - HTMLURL string `json:"html_url"` - FollowersURL string `json:"followers_url"` - FollowingURL string `json:"following_url"` - GistsURL string `json:"gists_url"` - StarredURL string `json:"starred_url"` - SubscriptionsURL string `json:"subscriptions_url"` - OrganizationsURL string `json:"organizations_url"` - ReposURL string `json:"repos_url"` - EventsURL string `json:"events_url"` - ReceivedEventsURL string `json:"received_events_url"` - Type string `json:"type"` - SiteAdmin bool `json:"site_admin"` - } `json:"merged_by"` - Comments int `json:"comments"` - ReviewComments int `json:"review_comments"` - MaintainerCanModify bool `json:"maintainer_can_modify"` - Commits int `json:"commits"` - Additions int `json:"additions"` - Deletions int `json:"deletions"` - ChangedFiles int `json:"changed_files"` + ID int `json:"id"` + Title string `json:"title"` + Body string `json:"body"` } From db5df2d138c766fecc434d4c951a19846dc54dcd Mon Sep 17 00:00:00 2001 From: amalhanaja Date: Sun, 30 Jun 2024 22:29:15 +0700 Subject: [PATCH 3/6] feat: enhance prompt by separate system instruction --- internal/domain/codeassistant/entity.go | 8 +-- internal/domain/gitrepo/entity.go | 2 + internal/domain/llm/entity.go | 6 +++ internal/domain/llm/repository.go | 2 +- .../http/bitbucket/get_pull_request.go | 1 + .../bitbucket/post_pull_request_comment.go | 2 - internal/infrastructure/http/github/client.go | 8 --- .../http/github/get_pull_request.go | 1 + internal/infrastructure/http/github/helper.go | 13 +++++ .../infrastructure/http/github/payload.go | 10 ++++ .../http/github/post_pull_request_comment.go | 42 +++++++++++++++ .../thirdparty/gemini/client.go | 19 +++++-- .../codeassistant/review_pull_request.go | 52 ++++++++++++------- internal/usecase/llm/send_prompt.go | 4 +- 14 files changed, 130 insertions(+), 40 deletions(-) create mode 100644 internal/domain/llm/entity.go create mode 100644 internal/infrastructure/http/github/post_pull_request_comment.go diff --git a/internal/domain/codeassistant/entity.go b/internal/domain/codeassistant/entity.go index 7a2a1b6..33b7f76 100644 --- a/internal/domain/codeassistant/entity.go +++ b/internal/domain/codeassistant/entity.go @@ -1,8 +1,10 @@ package codeassistant -import "codebleu/internal/domain/gitrepo" +import ( + "codebleu/internal/domain/gitrepo" +) type PullRequestReviewInput struct { - PullRequest *gitrepo.PullRequest - ReviewPrompt string + PullRequest *gitrepo.PullRequest + SystemInstruction string } diff --git a/internal/domain/gitrepo/entity.go b/internal/domain/gitrepo/entity.go index b347a02..e228892 100644 --- a/internal/domain/gitrepo/entity.go +++ b/internal/domain/gitrepo/entity.go @@ -2,6 +2,7 @@ package gitrepo type PostPullRequestCommentInput struct { PullRequestId string + CommitHash string Comment string } @@ -10,4 +11,5 @@ type PullRequest struct { Title string Description string DiffPatch string + CommitHash string } diff --git a/internal/domain/llm/entity.go b/internal/domain/llm/entity.go new file mode 100644 index 0000000..a7c3ee9 --- /dev/null +++ b/internal/domain/llm/entity.go @@ -0,0 +1,6 @@ +package llm + +type PromptInput struct { + SystemInstruction string + Prompt string +} diff --git a/internal/domain/llm/repository.go b/internal/domain/llm/repository.go index 8a9b152..ef6e951 100644 --- a/internal/domain/llm/repository.go +++ b/internal/domain/llm/repository.go @@ -3,5 +3,5 @@ package llm import "context" type Repository interface { - SendPrompt(ctx context.Context, prompt string) (string, error) + SendPrompt(ctx context.Context, input PromptInput) (string, error) } diff --git a/internal/infrastructure/http/bitbucket/get_pull_request.go b/internal/infrastructure/http/bitbucket/get_pull_request.go index 1e76036..16f8d6e 100644 --- a/internal/infrastructure/http/bitbucket/get_pull_request.go +++ b/internal/infrastructure/http/bitbucket/get_pull_request.go @@ -26,6 +26,7 @@ func (c *client) GetPullRequest(ctx context.Context, id string) (*domain.PullReq Title: pullRequestResponse.Title, Description: pullRequestResponse.Description, DiffPatch: diff, + CommitHash: pullRequestResponse.Source.Commit.Hash, }, nil } diff --git a/internal/infrastructure/http/bitbucket/post_pull_request_comment.go b/internal/infrastructure/http/bitbucket/post_pull_request_comment.go index b7f8bfa..7a0ffa2 100644 --- a/internal/infrastructure/http/bitbucket/post_pull_request_comment.go +++ b/internal/infrastructure/http/bitbucket/post_pull_request_comment.go @@ -18,7 +18,5 @@ func (c *client) PostPullRequestComment(ctx context.Context, input domain.PostPu if err := c.doRequest(ctx, http.MethodPost, fmt.Sprintf("/pullrequests/%s/comments", input.PullRequestId), payload, &response); err != nil { return err } - // TODO: REMOVE THIS - fmt.Printf("Response\n%+v\n", response) return nil } diff --git a/internal/infrastructure/http/github/client.go b/internal/infrastructure/http/github/client.go index fe859a1..56c6f9a 100644 --- a/internal/infrastructure/http/github/client.go +++ b/internal/infrastructure/http/github/client.go @@ -2,8 +2,6 @@ package github import ( "codebleu/internal/domain/gitrepo" - "context" - "fmt" "net/http" ) @@ -14,12 +12,6 @@ type client struct { httpClient *http.Client } -// PostPullRequestComment implements gitrepo.Repository. -func (c *client) PostPullRequestComment(ctx context.Context, input gitrepo.PostPullRequestCommentInput) error { - fmt.Println(input.Comment) - return nil -} - func NewClient( owner string, repoSlug string, diff --git a/internal/infrastructure/http/github/get_pull_request.go b/internal/infrastructure/http/github/get_pull_request.go index ec197e0..88e59e3 100644 --- a/internal/infrastructure/http/github/get_pull_request.go +++ b/internal/infrastructure/http/github/get_pull_request.go @@ -26,6 +26,7 @@ func (c *client) GetPullRequest(ctx context.Context, id string) (*gitrepo.PullRe Title: pullRequest.Title, Description: pullRequest.Body, DiffPatch: diff, + CommitHash: pullRequest.Head.Sha, }, nil } diff --git a/internal/infrastructure/http/github/helper.go b/internal/infrastructure/http/github/helper.go index 178682a..e806436 100644 --- a/internal/infrastructure/http/github/helper.go +++ b/internal/infrastructure/http/github/helper.go @@ -1,8 +1,10 @@ package github import ( + "bytes" infraHttp "codebleu/internal/infrastructure/http" "context" + "encoding/json" "errors" "fmt" "io" @@ -25,3 +27,14 @@ func (c *client) buildRequest(ctx context.Context, method string, requestUrl str request.Header.Add("Authorization", fmt.Sprintf("Bearer %s", c.accessToken)) return request, nil } + +func (c *client) buildRequestPayload(payload interface{}) (io.Reader, error) { + if payload == nil { + return nil, nil + } + jsonPayload, err := json.Marshal(payload) + if err != nil { + return nil, err + } + return bytes.NewReader(jsonPayload), nil +} diff --git a/internal/infrastructure/http/github/payload.go b/internal/infrastructure/http/github/payload.go index cad4b41..ace97bc 100644 --- a/internal/infrastructure/http/github/payload.go +++ b/internal/infrastructure/http/github/payload.go @@ -4,4 +4,14 @@ type PullRequestResponse struct { ID int `json:"id"` Title string `json:"title"` Body string `json:"body"` + Head struct { + Sha string `json:"sha"` + } `json:"Head"` +} + +type PostPullRequestCommentRequest struct { + Body string `json:"body"` + CommitId string `json:"commit_id"` + Path string `json:"path"` + Line int `json:"line"` } diff --git a/internal/infrastructure/http/github/post_pull_request_comment.go b/internal/infrastructure/http/github/post_pull_request_comment.go new file mode 100644 index 0000000..dca28d5 --- /dev/null +++ b/internal/infrastructure/http/github/post_pull_request_comment.go @@ -0,0 +1,42 @@ +package github + +import ( + "codebleu/internal/domain/gitrepo" + infraHttp "codebleu/internal/infrastructure/http" + "context" + "errors" + "fmt" + "io" + "net/http" +) + +// PostPullRequestComment implements gitrepo.Repository. +func (c *client) PostPullRequestComment(ctx context.Context, input gitrepo.PostPullRequestCommentInput) error { + url := fmt.Sprintf("%s/repos/%s/%s/pulls/%s/comments", c.getBaseUrl(), c.owner, c.repoSlug, input.PullRequestId) + println(input.CommitHash, input.Comment) + payload := &PostPullRequestCommentRequest{ + Body: input.Comment, + CommitId: "a8df929d0e055a95824b5e04636cf97a750fff02", + Path: "go.mod", + Line: 0, + } + jsonPayload, err := c.buildRequestPayload(payload) + if err != nil { + return err + } + req, err := c.buildRequest(ctx, http.MethodPost, url, jsonPayload) + if err != nil { + return err + } + httpResponse, err := c.httpClient.Do(req) + if err != nil { + return err + } + defer httpResponse.Body.Close() + if httpResponse.StatusCode >= 300 { + body, err := io.ReadAll(httpResponse.Body) + httpError := infraHttp.NewHttpClientError(fmt.Sprintf("request failed: httpstatus = %d\n%s", httpResponse.StatusCode, string(body)), url) + return errors.Join(httpError, err) + } + return nil +} diff --git a/internal/infrastructure/thirdparty/gemini/client.go b/internal/infrastructure/thirdparty/gemini/client.go index 1a1e232..88fb413 100644 --- a/internal/infrastructure/thirdparty/gemini/client.go +++ b/internal/infrastructure/thirdparty/gemini/client.go @@ -15,16 +15,27 @@ type client struct { } // SendPrompt implements llm.Repository. -func (c *client) SendPrompt(ctx context.Context, prompt string) (string, error) { +func (c *client) SendPrompt(ctx context.Context, input domain.PromptInput) (string, error) { genaiClient, err := c.getClient(ctx) if err != nil { return "", err } + systemOutputInstruction := ` + #IMPORTANT INSTRUCTION:\n + Returns using this JSON scheme: [{path: ,comment_in_markdown: }] + ` defer genaiClient.Close() model := genaiClient.GenerativeModel(c.model) - chat := model.StartChat() - chat.History = []*genai.Content{} - resp, err := chat.SendMessage(ctx, genai.Text(prompt)) + model.GenerationConfig = genai.GenerationConfig{ + ResponseMIMEType: "application/json", + } + model.SystemInstruction = &genai.Content{ + Parts: []genai.Part{ + genai.Text(input.SystemInstruction), + genai.Text(systemOutputInstruction), + }, + } + resp, err := model.GenerateContent(ctx, genai.Text(input.Prompt)) if err != nil { return "", err } diff --git a/internal/usecase/codeassistant/review_pull_request.go b/internal/usecase/codeassistant/review_pull_request.go index f9851b6..6637615 100644 --- a/internal/usecase/codeassistant/review_pull_request.go +++ b/internal/usecase/codeassistant/review_pull_request.go @@ -3,56 +3,68 @@ package codeassistant import ( "bytes" domain "codebleu/internal/domain/codeassistant" + llmDomain "codebleu/internal/domain/llm" u "codebleu/internal/usecase" "context" "text/template" ) type reviewPullRequest struct { - sendPromptUseCase u.UseCase[string, string] + sendPromptUseCase u.UseCase[llmDomain.PromptInput, string] } // Invoke implements usecase.UseCase. func (u *reviewPullRequest) Invoke(ctx context.Context, input domain.PullRequestReviewInput) (string, error) { - promptTemplateString := input.ReviewPrompt - if promptTemplateString == "" { - promptTemplateString = u.getDefaultPromptTemplate() + systemInstruction := input.SystemInstruction + if systemInstruction == "" { + systemInstruction = u.getDefaultSystemInstruction() } - promptTemplate := template.Must(template.New("prompt").Parse(promptTemplateString)) + promptTemplate := template.Must(template.New("prompt").Parse(u.getDefaultPromptTemplate())) var promptBuffer bytes.Buffer - promptTemplate.Execute(&promptBuffer, input.PullRequest) - res, err := u.sendPromptUseCase.Invoke(ctx, promptBuffer.String()) + err := promptTemplate.Execute(&promptBuffer, input.PullRequest) + if err != nil { + return "", err + } + res, err := u.sendPromptUseCase.Invoke(ctx, llmDomain.PromptInput{ + SystemInstruction: systemInstruction, + Prompt: promptBuffer.String(), + }) if err != nil { return "", err } return res, nil } +func (u *reviewPullRequest) getDefaultSystemInstruction() string { + return ` + Act as Software Enginner Expert with experience in multiple programming language. + Always follow best practices by writing clean, modular code with proper security measures, + and leveraging design patterns. Follow a software development principles: SOLID, DRY, KISS, YAGNI. Skip compliments. + + Input: Changes Patch. Changes patch changes between source branch and target branches in Pull request. + Additional Context: PR Title, PR Description. + + Task: Review a file of source code, and the git diff of a set of changes made to that file on a Pull Request. + - Do NOT provide general feedback, summaries, explanations of changes, or praises for making good additions. + - Focus solely on offering specific, objective insights based on the given context and refrain from making broad comments about potential impacts on the system or question intentions behind the changes. + ` +} + func (u *reviewPullRequest) getDefaultPromptTemplate() string { return ` + Here is detail of pull request: + ## PR Title: {{.Title}} ## Description: {{.Description}} - ## Instructions: - Act as Software Enginner Expert with experience in multiple programming language. - Always follow best practices by writing clean, modular code with proper security measures, - and leveraging design patterns. - - Input: Changes Patch. Changes patch changes between source branch and target branches in Pull request. - Additional Context: PR title, description. - Task: Review changes patch for substantive issues using provided context and respond with comments if necessary. - Output: Review comments in markdown format. - - Use fenced code blocks using the relevant language identifier where applicable. - - Don't annotate code snippets with line numbers. Format and indent code correctly. - ## Changes Patch: {{.DiffPatch}} ` } func ReviewPullRequest( - sendPromptUseCase u.UseCase[string, string], + sendPromptUseCase u.UseCase[llmDomain.PromptInput, string], ) u.UseCase[domain.PullRequestReviewInput, string] { return &reviewPullRequest{ sendPromptUseCase: sendPromptUseCase, diff --git a/internal/usecase/llm/send_prompt.go b/internal/usecase/llm/send_prompt.go index 6e864e4..99bfeee 100644 --- a/internal/usecase/llm/send_prompt.go +++ b/internal/usecase/llm/send_prompt.go @@ -11,13 +11,13 @@ type sendPromptUseCase struct { } // Invoke implements usecase.UseCase. -func (s *sendPromptUseCase) Invoke(ctx context.Context, input string) (string, error) { +func (s *sendPromptUseCase) Invoke(ctx context.Context, input domain.PromptInput) (string, error) { return s.repository.SendPrompt(ctx, input) } func SendPromptUseCase( repository domain.Repository, -) usecase.UseCase[string, string] { +) usecase.UseCase[domain.PromptInput, string] { return &sendPromptUseCase{ repository: repository, } From 588020cddb4ce5c3e399702c86b2899ba72a2145 Mon Sep 17 00:00:00 2001 From: amalhanaja Date: Mon, 1 Jul 2024 12:35:01 +0700 Subject: [PATCH 4/6] feat: comment PR per file --- internal/domain/codeassistant/entity.go | 5 ++++ internal/domain/gitrepo/entity.go | 1 + .../infrastructure/http/github/payload.go | 8 +++--- .../http/github/post_pull_request_comment.go | 8 +++--- .../review_and_comment_pull_request.go | 28 ++++++++++++++----- .../codeassistant/review_pull_request.go | 20 +++++++------ 6 files changed, 47 insertions(+), 23 deletions(-) diff --git a/internal/domain/codeassistant/entity.go b/internal/domain/codeassistant/entity.go index 33b7f76..b1bc2dd 100644 --- a/internal/domain/codeassistant/entity.go +++ b/internal/domain/codeassistant/entity.go @@ -8,3 +8,8 @@ type PullRequestReviewInput struct { PullRequest *gitrepo.PullRequest SystemInstruction string } + +type ReviewResult struct { + Path string `json:"path"` + Comment string `json:"comment_in_markdown"` +} diff --git a/internal/domain/gitrepo/entity.go b/internal/domain/gitrepo/entity.go index e228892..92ba4c7 100644 --- a/internal/domain/gitrepo/entity.go +++ b/internal/domain/gitrepo/entity.go @@ -4,6 +4,7 @@ type PostPullRequestCommentInput struct { PullRequestId string CommitHash string Comment string + Path string } type PullRequest struct { diff --git a/internal/infrastructure/http/github/payload.go b/internal/infrastructure/http/github/payload.go index ace97bc..89103a9 100644 --- a/internal/infrastructure/http/github/payload.go +++ b/internal/infrastructure/http/github/payload.go @@ -10,8 +10,8 @@ type PullRequestResponse struct { } type PostPullRequestCommentRequest struct { - Body string `json:"body"` - CommitId string `json:"commit_id"` - Path string `json:"path"` - Line int `json:"line"` + Body string `json:"body"` + CommitId string `json:"commit_id"` + Path string `json:"path"` + SubjectType string `json:"subject_type"` } diff --git a/internal/infrastructure/http/github/post_pull_request_comment.go b/internal/infrastructure/http/github/post_pull_request_comment.go index dca28d5..ed3d299 100644 --- a/internal/infrastructure/http/github/post_pull_request_comment.go +++ b/internal/infrastructure/http/github/post_pull_request_comment.go @@ -15,10 +15,10 @@ func (c *client) PostPullRequestComment(ctx context.Context, input gitrepo.PostP url := fmt.Sprintf("%s/repos/%s/%s/pulls/%s/comments", c.getBaseUrl(), c.owner, c.repoSlug, input.PullRequestId) println(input.CommitHash, input.Comment) payload := &PostPullRequestCommentRequest{ - Body: input.Comment, - CommitId: "a8df929d0e055a95824b5e04636cf97a750fff02", - Path: "go.mod", - Line: 0, + Body: input.Comment, + CommitId: input.CommitHash, + Path: input.Path, + SubjectType: "file", } jsonPayload, err := c.buildRequestPayload(payload) if err != nil { diff --git a/internal/usecase/codeassistant/review_and_comment_pull_request.go b/internal/usecase/codeassistant/review_and_comment_pull_request.go index b8abe53..366fae2 100644 --- a/internal/usecase/codeassistant/review_and_comment_pull_request.go +++ b/internal/usecase/codeassistant/review_and_comment_pull_request.go @@ -5,11 +5,12 @@ import ( gitRepoDomain "codebleu/internal/domain/gitrepo" "codebleu/internal/usecase" "context" + "errors" ) type reviewAndCommentPullRequest struct { getPullRequest usecase.UseCase[string, *gitRepoDomain.PullRequest] - reviewPullRequest usecase.UseCase[domain.PullRequestReviewInput, string] + reviewPullRequest usecase.UseCase[domain.PullRequestReviewInput, []domain.ReviewResult] postPullRequestComment usecase.UseCase[gitRepoDomain.PostPullRequestCommentInput, interface{}] } @@ -19,22 +20,35 @@ func (r *reviewAndCommentPullRequest) Invoke(ctx context.Context, input string) if err != nil { return nil, err } - reviewResult, err := r.reviewPullRequest.Invoke(ctx, domain.PullRequestReviewInput{ + reviewResults, err := r.reviewPullRequest.Invoke(ctx, domain.PullRequestReviewInput{ PullRequest: pullRequest, }) if err != nil { return nil, err } - postPullRequestCommentInput := gitRepoDomain.PostPullRequestCommentInput{ - PullRequestId: input, - Comment: reviewResult, + for _, reviewResult := range reviewResults { + _, postCommentErr := r.postPullRequestComment.Invoke(ctx, gitRepoDomain.PostPullRequestCommentInput{ + PullRequestId: input, + CommitHash: pullRequest.CommitHash, + Path: reviewResult.Path, + Comment: reviewResult.Comment, + }) + if postCommentErr != nil { + if err == nil { + err = errors.New("failed post comment") + } + err = errors.Join(err, postCommentErr) + } } - return r.postPullRequestComment.Invoke(ctx, postPullRequestCommentInput) + if err != nil { + return nil, err + } + return nil, nil } func ReviewAndCommentPullRequest( getPullRequest usecase.UseCase[string, *gitRepoDomain.PullRequest], - reviewPullRequest usecase.UseCase[domain.PullRequestReviewInput, string], + reviewPullRequest usecase.UseCase[domain.PullRequestReviewInput, []domain.ReviewResult], postPullRequestComment usecase.UseCase[gitRepoDomain.PostPullRequestCommentInput, interface{}], ) usecase.UseCase[string, interface{}] { return &reviewAndCommentPullRequest{ diff --git a/internal/usecase/codeassistant/review_pull_request.go b/internal/usecase/codeassistant/review_pull_request.go index 6637615..7e910e0 100644 --- a/internal/usecase/codeassistant/review_pull_request.go +++ b/internal/usecase/codeassistant/review_pull_request.go @@ -6,6 +6,7 @@ import ( llmDomain "codebleu/internal/domain/llm" u "codebleu/internal/usecase" "context" + "encoding/json" "text/template" ) @@ -14,7 +15,7 @@ type reviewPullRequest struct { } // Invoke implements usecase.UseCase. -func (u *reviewPullRequest) Invoke(ctx context.Context, input domain.PullRequestReviewInput) (string, error) { +func (u *reviewPullRequest) Invoke(ctx context.Context, input domain.PullRequestReviewInput) ([]domain.ReviewResult, error) { systemInstruction := input.SystemInstruction if systemInstruction == "" { systemInstruction = u.getDefaultSystemInstruction() @@ -23,16 +24,21 @@ func (u *reviewPullRequest) Invoke(ctx context.Context, input domain.PullRequest var promptBuffer bytes.Buffer err := promptTemplate.Execute(&promptBuffer, input.PullRequest) if err != nil { - return "", err + return nil, err } - res, err := u.sendPromptUseCase.Invoke(ctx, llmDomain.PromptInput{ + rawResult, err := u.sendPromptUseCase.Invoke(ctx, llmDomain.PromptInput{ SystemInstruction: systemInstruction, Prompt: promptBuffer.String(), }) if err != nil { - return "", err + return nil, err } - return res, nil + var result []domain.ReviewResult + err = json.Unmarshal([]byte(rawResult), &result) + if err != nil { + return nil, err + } + return result, nil } func (u *reviewPullRequest) getDefaultSystemInstruction() string { @@ -45,8 +51,6 @@ func (u *reviewPullRequest) getDefaultSystemInstruction() string { Additional Context: PR Title, PR Description. Task: Review a file of source code, and the git diff of a set of changes made to that file on a Pull Request. - - Do NOT provide general feedback, summaries, explanations of changes, or praises for making good additions. - - Focus solely on offering specific, objective insights based on the given context and refrain from making broad comments about potential impacts on the system or question intentions behind the changes. ` } @@ -65,7 +69,7 @@ func (u *reviewPullRequest) getDefaultPromptTemplate() string { func ReviewPullRequest( sendPromptUseCase u.UseCase[llmDomain.PromptInput, string], -) u.UseCase[domain.PullRequestReviewInput, string] { +) u.UseCase[domain.PullRequestReviewInput, []domain.ReviewResult] { return &reviewPullRequest{ sendPromptUseCase: sendPromptUseCase, } From df579aaf106c5c02e746b8a90230f8f81ebe84e4 Mon Sep 17 00:00:00 2001 From: amalhanaja Date: Thu, 4 Jul 2024 00:05:00 +0700 Subject: [PATCH 5/6] feat: custom system instruction --- cmd/cli/main.go | 5 ---- internal/app/cli/app.go | 26 ----------------- internal/app/cli/cli.go | 28 +++++++++++++++++-- internal/domain/codeassistant/entity.go | 5 ++++ .../review_and_comment_pull_request.go | 11 ++++---- 5 files changed, 37 insertions(+), 38 deletions(-) diff --git a/cmd/cli/main.go b/cmd/cli/main.go index cf60594..502dc54 100644 --- a/cmd/cli/main.go +++ b/cmd/cli/main.go @@ -6,9 +6,4 @@ import ( func main() { cli.Run() - // pullRequestId := os.Getenv("BITBUCKET_PR_ID") - // if pullRequestId == "" { - // panic("Failed Get PULL REQUEST ID") - // } - // cli.RunE(pullRequestId) } diff --git a/internal/app/cli/app.go b/internal/app/cli/app.go index a3725d6..70b338f 100644 --- a/internal/app/cli/app.go +++ b/internal/app/cli/app.go @@ -1,12 +1,6 @@ package cli import ( - infraBitbucket "codebleu/internal/infrastructure/http/bitbucket" - infraGemini "codebleu/internal/infrastructure/thirdparty/gemini" - codeAssistantUseCase "codebleu/internal/usecase/codeassistant" - gitRepoUseCase "codebleu/internal/usecase/gitrepo" - llmUseCase "codebleu/internal/usecase/llm" - "context" "log" "os" ) @@ -16,23 +10,3 @@ func Run() { log.Fatalln(err) } } - -func RunE(pullRequestId string) { - cfg := NewConfigFromEnv() - bitbucketClient := infraBitbucket.NewClient( - cfg.BitbucketRepositoryAccessConfig.Workspace, - cfg.BitbucketRepositoryAccessConfig.RepoSlug, - cfg.BitbucketRepositoryAccessConfig.AccessToken, - ) - geminiClient := infraGemini.NewClient("gemini-1.5-flash", cfg.GeminiConfig.ApiKey) - getPullRequest := gitRepoUseCase.GetPullRequest(bitbucketClient) - postPullRequestComment := gitRepoUseCase.PostPullRequestComment(bitbucketClient) - sendPromptUseCase := llmUseCase.SendPromptUseCase(geminiClient) - reviewPullRequest := codeAssistantUseCase.ReviewPullRequest(sendPromptUseCase) - reviewAndCommentPullRequest := codeAssistantUseCase.ReviewAndCommentPullRequest(getPullRequest, reviewPullRequest, postPullRequestComment) - - _, err := reviewAndCommentPullRequest.Invoke(context.Background(), pullRequestId) - if err != nil { - panic(err) - } -} diff --git a/internal/app/cli/cli.go b/internal/app/cli/cli.go index 5863ff1..58832e8 100644 --- a/internal/app/cli/cli.go +++ b/internal/app/cli/cli.go @@ -1,10 +1,13 @@ package cli import ( + "codebleu/internal/domain/codeassistant" "codebleu/internal/infrastructure/factories/gitrepo" "codebleu/internal/infrastructure/factories/llm" "context" "errors" + "fmt" + "io/ioutil" codeAssistantUseCase "codebleu/internal/usecase/codeassistant" gitRepoUseCase "codebleu/internal/usecase/gitrepo" @@ -22,9 +25,10 @@ func NewCliApp() *cli.App { cliApp.Flags = []cli.Flag{ &cli.StringFlag{ Name: "model", - Usage: `uses model to review pull request (options: "gemini-1.5-flash", "gemini-1.5-pro", "gemini-1.0-pro")`, + Usage: `uses model to review pull request (options: "gemini-1.5-flash" (default), "gemini-1.5-pro", "gemini-1.0-pro")`, Aliases: []string{"m"}, EnvVars: []string{"MODEL"}, + Value: "gemini-1.5-flash", Required: true, }, &cli.StringFlag{ @@ -40,6 +44,11 @@ func NewCliApp() *cli.App { EnvVars: []string{"PULL_REQUEST_ID"}, Required: true, }, + &cli.StringFlag{ + Name: "system-instruction", + Usage: "Custom system instruction for review pull request diff chages", + EnvVars: []string{"SYSTEM_INSTRUCTION"}, + }, } cliApp.Action = action @@ -63,12 +72,27 @@ func action(ctx *cli.Context) error { if err != nil { return err } + systemInstructionPath := ctx.String("system-instruction") + systemInstruction := "" + if systemInstructionPath != "" { + content, err := ioutil.ReadFile(systemInstructionPath) + if err != nil { + return errors.Join(err, fmt.Errorf("failed read file %s", systemInstructionPath)) + } + systemInstruction = string(content) + } getPullRequest := gitRepoUseCase.GetPullRequest(remoteRepo) postPullRequestComment := gitRepoUseCase.PostPullRequestComment(remoteRepo) sendPromptUseCase := llmUseCase.SendPromptUseCase(llmRepo) reviewPullRequest := codeAssistantUseCase.ReviewPullRequest(sendPromptUseCase) reviewAndCommentPullRequest := codeAssistantUseCase.ReviewAndCommentPullRequest(getPullRequest, reviewPullRequest, postPullRequestComment) - if _, err := reviewAndCommentPullRequest.Invoke(context.Background(), ctx.String("id")); err != nil { + if _, err := reviewAndCommentPullRequest.Invoke( + context.Background(), + codeassistant.ReviewAndCommentPullRequestInput{ + PullRequestId: ctx.String("id"), + SystemInstruction: systemInstruction, + }, + ); err != nil { return err } return nil diff --git a/internal/domain/codeassistant/entity.go b/internal/domain/codeassistant/entity.go index b1bc2dd..65c5dc7 100644 --- a/internal/domain/codeassistant/entity.go +++ b/internal/domain/codeassistant/entity.go @@ -9,6 +9,11 @@ type PullRequestReviewInput struct { SystemInstruction string } +type ReviewAndCommentPullRequestInput struct { + PullRequestId string + SystemInstruction string +} + type ReviewResult struct { Path string `json:"path"` Comment string `json:"comment_in_markdown"` diff --git a/internal/usecase/codeassistant/review_and_comment_pull_request.go b/internal/usecase/codeassistant/review_and_comment_pull_request.go index 366fae2..442315a 100644 --- a/internal/usecase/codeassistant/review_and_comment_pull_request.go +++ b/internal/usecase/codeassistant/review_and_comment_pull_request.go @@ -15,20 +15,21 @@ type reviewAndCommentPullRequest struct { } // Invoke implements usecase.UseCase. -func (r *reviewAndCommentPullRequest) Invoke(ctx context.Context, input string) (interface{}, error) { - pullRequest, err := r.getPullRequest.Invoke(ctx, input) +func (r *reviewAndCommentPullRequest) Invoke(ctx context.Context, input domain.ReviewAndCommentPullRequestInput) (interface{}, error) { + pullRequest, err := r.getPullRequest.Invoke(ctx, input.PullRequestId) if err != nil { return nil, err } reviewResults, err := r.reviewPullRequest.Invoke(ctx, domain.PullRequestReviewInput{ - PullRequest: pullRequest, + PullRequest: pullRequest, + SystemInstruction: input.SystemInstruction, }) if err != nil { return nil, err } for _, reviewResult := range reviewResults { _, postCommentErr := r.postPullRequestComment.Invoke(ctx, gitRepoDomain.PostPullRequestCommentInput{ - PullRequestId: input, + PullRequestId: input.PullRequestId, CommitHash: pullRequest.CommitHash, Path: reviewResult.Path, Comment: reviewResult.Comment, @@ -50,7 +51,7 @@ func ReviewAndCommentPullRequest( getPullRequest usecase.UseCase[string, *gitRepoDomain.PullRequest], reviewPullRequest usecase.UseCase[domain.PullRequestReviewInput, []domain.ReviewResult], postPullRequestComment usecase.UseCase[gitRepoDomain.PostPullRequestCommentInput, interface{}], -) usecase.UseCase[string, interface{}] { +) usecase.UseCase[domain.ReviewAndCommentPullRequestInput, interface{}] { return &reviewAndCommentPullRequest{ reviewPullRequest: reviewPullRequest, postPullRequestComment: postPullRequestComment, From 05742973b61cc487ae9b255a5e13b3e97616fb91 Mon Sep 17 00:00:00 2001 From: amalhanaja Date: Thu, 4 Jul 2024 07:52:01 +0700 Subject: [PATCH 6/6] feat: enhance comment bitbucket --- internal/infrastructure/http/bitbucket/payload.go | 5 +++++ .../http/bitbucket/post_pull_request_comment.go | 3 +++ 2 files changed, 8 insertions(+) diff --git a/internal/infrastructure/http/bitbucket/payload.go b/internal/infrastructure/http/bitbucket/payload.go index b3e8258..f701ae3 100644 --- a/internal/infrastructure/http/bitbucket/payload.go +++ b/internal/infrastructure/http/bitbucket/payload.go @@ -2,12 +2,17 @@ package bitbucket type PostPullRequestCommentRequest struct { Content *PullRequestCommentContent `json:"content"` + Inline *PullRequestCommentInline `json:"inline"` } type PullRequestCommentContent struct { Raw string `json:"raw"` } +type PullRequestCommentInline struct { + Path string `json:"path"` +} + type PullRequestResponse struct { CommentCount int64 `json:"comment_count"` TaskCount int64 `json:"task_count"` diff --git a/internal/infrastructure/http/bitbucket/post_pull_request_comment.go b/internal/infrastructure/http/bitbucket/post_pull_request_comment.go index 7a0ffa2..d25f40f 100644 --- a/internal/infrastructure/http/bitbucket/post_pull_request_comment.go +++ b/internal/infrastructure/http/bitbucket/post_pull_request_comment.go @@ -14,6 +14,9 @@ func (c *client) PostPullRequestComment(ctx context.Context, input domain.PostPu Content: &PullRequestCommentContent{ Raw: input.Comment, }, + Inline: &PullRequestCommentInline{ + Path: input.Path, + }, } if err := c.doRequest(ctx, http.MethodPost, fmt.Sprintf("/pullrequests/%s/comments", input.PullRequestId), payload, &response); err != nil { return err