From efa78f401be2d7a2f9ee728bff68c31298962f51 Mon Sep 17 00:00:00 2001 From: majiayu000 <1835304752@qq.com> Date: Wed, 31 Dec 2025 08:30:53 +0800 Subject: [PATCH] fix: add close PR button in PR view MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add functionality to close pull requests from the PR detail view: - Backend: Add ClosePullRequest method in Gitness client, service and controller - Backend: Add /close API route for pull requests - Frontend: Add closePullRequest API function - Frontend: Add Close button next to Re-Build and Merge buttons - Fix: Align CLOSED status constant between frontend and backend 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 Signed-off-by: majiayu000 <1835304752@qq.com> --- .../git_provider/gitness_git_provider.go | 26 +++ app/constants/pr_status.go | 2 +- app/controllers/pull_request_controller.go | 33 +++- app/models/dtos/gitness/types.go | 4 + .../gitness_git_provider_service.go | 10 ++ app/services/pull_request_service.go | 160 +++++++++++------- gui/src/api/DashboardService.tsx | 4 + .../pull_request/[pr_id]/page.tsx | 20 +++ server.go | 1 + 9 files changed, 196 insertions(+), 64 deletions(-) diff --git a/app/client/git_provider/gitness_git_provider.go b/app/client/git_provider/gitness_git_provider.go index 053f5dfa..e81c280b 100644 --- a/app/client/git_provider/gitness_git_provider.go +++ b/app/client/git_provider/gitness_git_provider.go @@ -356,3 +356,29 @@ func (c *GitnessClient) GetBranchCommits(repoPath string, branchName ...string) return &getMainBranchCommitResponse, nil } + +func (c *GitnessClient) ClosePullRequest(repoPath string, pullRequestID int) error { + url := fmt.Sprintf("%s/api/v1/repos/%s/+/pullreq/%d/state", c.baseURL, repoPath, pullRequestID) + + payload := gitness.UpdatePullRequestStatePayload{ + State: "closed", + } + + headers := map[string]string{ + "Accept": "*/*", + "Content-Type": "application/json", + "Authorization": "Bearer " + c.authToken, + } + + response, err := c.httpClient.Post(url, payload, headers) + if err != nil { + return err + } + defer response.Body.Close() + + if response.StatusCode != http.StatusOK { + return fmt.Errorf("failed to close pull request, status code: %d", response.StatusCode) + } + + return nil +} diff --git a/app/constants/pr_status.go b/app/constants/pr_status.go index 3e7c0415..ea04486f 100644 --- a/app/constants/pr_status.go +++ b/app/constants/pr_status.go @@ -2,6 +2,6 @@ package constants const ( Open = "OPEN" - Close = "CLOSE" + Close = "CLOSED" Merged = "MERGED" ) diff --git a/app/controllers/pull_request_controller.go b/app/controllers/pull_request_controller.go index 48b7d347..9b84a5f8 100644 --- a/app/controllers/pull_request_controller.go +++ b/app/controllers/pull_request_controller.go @@ -67,6 +67,33 @@ func (ctrl *PullRequestController) MergePullRequest(c *gin.Context) { } c.JSON(http.StatusOK, gin.H{"merge_sha": mergeSHA}) } + +func (ctrl *PullRequestController) ClosePullRequest(c *gin.Context) { + pullRequestIdStr := c.Param("pull_request_id") + pullRequestID, err := strconv.Atoi(pullRequestIdStr) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid pull request ID"}) + return + } + email, _ := c.Get("email") + user, err := ctrl.userService.GetUserByEmail(email.(string)) + if err != nil { + c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + if user == nil { + c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": "User not found"}) + return + } + organisationId := user.OrganisationID + err = ctrl.pullRequestService.ClosePullRequestByID(pullRequestID, organisationId) + if err != nil { + fmt.Println("Error while closing Pull Request", err.Error()) + c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, gin.H{"message": "Pull Request closed successfully"}) +} func (ctrl *PullRequestController) FetchPullRequestCommits(c *gin.Context) { pullRequestIdStr := c.Param("pull_request_id") pullRequestID, err := strconv.Atoi(pullRequestIdStr) @@ -121,10 +148,10 @@ func (ctrl *PullRequestController) CreateManualPullRequest(c *gin.Context) { Description := createPRRequest.Description fmt.Println("project id _____", ProjectID) prId, err := ctrl.pullRequestService.CreateManualPullRequest(ProjectID, Title, Description) - if err !=nil{ + if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create Pull Request"}) - return + return } c.JSON(http.StatusOK, gin.H{"pull_request_id": prId}) -} \ No newline at end of file +} diff --git a/app/models/dtos/gitness/types.go b/app/models/dtos/gitness/types.go index fee776e6..b8b60236 100644 --- a/app/models/dtos/gitness/types.go +++ b/app/models/dtos/gitness/types.go @@ -242,3 +242,7 @@ type GetMainBranchCommitResponse struct { } `json:"rename_details"` TotalCommits int `json:"total_commits"` } + +type UpdatePullRequestStatePayload struct { + State string `json:"state"` +} diff --git a/app/services/git_providers/gitness_git_provider_service.go b/app/services/git_providers/gitness_git_provider_service.go index c945700d..26973014 100644 --- a/app/services/git_providers/gitness_git_provider_service.go +++ b/app/services/git_providers/gitness_git_provider_service.go @@ -134,3 +134,13 @@ func (s *GitnessService) GetAllCommitsOfProjectBranch(organisation *models.Organ } return commitResponse, nil } + +func (s *GitnessService) ClosePullRequest(projectName, repoName string, pullRequestID int) error { + repoPath := fmt.Sprintf("%s/%s", projectName, repoName) + + err := s.client.ClosePullRequest(repoPath, pullRequestID) + if err != nil { + return err + } + return nil +} diff --git a/app/services/pull_request_service.go b/app/services/pull_request_service.go index 686948da..33019b20 100644 --- a/app/services/pull_request_service.go +++ b/app/services/pull_request_service.go @@ -44,13 +44,13 @@ func NewPullRequestService(pullRequestRepo *repositories.PullRequestRepository, func (s *PullRequestService) GetAllPullRequests(projectID int, status string) ([]*response.GetAllPullRequests, error) { backendStories, err := s.storyRepo.GetStoriesByProjectIdAndStoryType(projectID, constants.Backend) - if err != nil{ + if err != nil { return nil, err } frontendStories, err := s.storyRepo.GetStoriesByProjectIdAndStoryType(projectID, constants.Frontend) - if err!= nil { - return nil, err - } + if err != nil { + return nil, err + } stories := append(backendStories, frontendStories...) storyIDs := make([]uint, len(stories)) for i, story := range stories { @@ -124,11 +124,51 @@ func (s *PullRequestService) MergePullRequestByID(pullRequestID int, organisatio return mergeSHA, nil } +func (s *PullRequestService) ClosePullRequestByID(pullRequestID int, organisationID uint) error { + organisation, err := s.organisationRepo.GetOrganisationByID(organisationID) + if err != nil { + fmt.Println("Error fetching Organisation by ID") + return err + } + pullRequest, err := s.pullRequestRepo.GetPullRequestByID(uint(pullRequestID)) + if err != nil { + fmt.Println("Error fetching Pull Request by ID") + return err + } + if pullRequest == nil { + fmt.Println("Pull Request not found") + return errors.New("Pull Request not found") + } + story, err := s.storyRepo.GetStoryById(int(pullRequest.StoryID)) + if err != nil { + fmt.Println("Error fetching Story by ID") + return err + } + project, err := s.projectRepo.GetProjectById(int(story.ProjectID)) + if err != nil { + fmt.Println("Error fetching Project by ID") + return err + } + spaceOrProjectName := s.gitService.GetSpaceOrProjectName(organisation) + err = s.gitService.ClosePullRequest(spaceOrProjectName, project.Name, pullRequest.PullRequestNumber) + if err != nil { + fmt.Println("Error closing pull request") + return err + } + fmt.Println("PR Closed Successfully") + err = s.pullRequestRepo.UpdatePullRequestStatus(pullRequest, constants.Close) + if err != nil { + fmt.Println("Error updating pull request status") + return err + } + return nil +} + func (s *PullRequestService) GetPullRequestsCommits(pullRequestID int, organisationID int) ([]*response.GetAllCommitsResponse, error) { organisation, err := s.organisationRepo.GetOrganisationByID(uint(organisationID)) - if err!=nil{ + if err != nil { fmt.Println("Error fetching Organisation by ID") - return nil, err + return nil, err } pullRequest, err := s.pullRequestRepo.GetPullRequestByID(uint(pullRequestID)) if err != nil { @@ -151,7 +191,7 @@ func (s *PullRequestService) GetPullRequestsCommits(pullRequestID int, organisat } spaceOrProjectName := s.gitService.GetSpaceOrProjectName(organisation) commitsResponse, err := s.gitService.FetchPullRequestCommits(spaceOrProjectName, project.Name, pullRequest.PullRequestNumber) - if err!=nil{ + if err != nil { return nil, err } commits, err := s.FetchCommitsResponse(commitsResponse) @@ -163,7 +203,7 @@ func (s *PullRequestService) GetPullRequestsCommits(pullRequestID int, organisat func (s *PullRequestService) GetPullRequestDiffByPullRequestID(pullRequestID uint) (string, error) { pullRequest, err := s.pullRequestRepo.GetPullRequestByID(pullRequestID) - if err!=nil{ + if err != nil { return "", err } story, err := s.storyRepo.GetStoryById(int(pullRequest.StoryID)) @@ -179,10 +219,10 @@ func (s *PullRequestService) GetPullRequestDiffByPullRequestID(pullRequestID uin } organisation, err := s.organisationRepo.GetOrganisationByID(uint(int(project.OrganisationID))) - if err!= nil { - fmt.Println("Error getting organisation by ID: ", err) - return "", err - } + if err != nil { + fmt.Println("Error getting organisation by ID: ", err) + return "", err + } spaceOrProjectName := s.gitService.GetSpaceOrProjectName(organisation) fmt.Printf("Project: %v\n", project) diff, err := s.gitService.GetPullRequestDiff( @@ -268,44 +308,44 @@ func (s *PullRequestService) GetPullRequestByID(pullRequestId uint) (*models.Pul func (s *PullRequestService) UpdatePullRequestSourceSHA(pullRequest *models.PullRequest, sourceSHA string) error { return s.pullRequestRepo.UpdatePullRequestSourceSHA(pullRequest, sourceSHA) } -func (s *PullRequestService) CreateManualPullRequest(projectID int, title string, description string) (int, error){ +func (s *PullRequestService) CreateManualPullRequest(projectID int, title string, description string) (int, error) { project, err := s.projectRepo.GetProjectById(projectID) - if err!= nil { + if err != nil { fmt.Println("failed to fetch project", err) - return -1, err - } + return -1, err + } - workingDir := "/workspaces/"+project.HashID + workingDir := "/workspaces/" + project.HashID err = utils.ConfigureGitUserName(workingDir) - if err!= nil { - fmt.Println("failed to configure git user name", err) - return -1, err - } + if err != nil { + fmt.Println("failed to configure git user name", err) + return -1, err + } err = utils.ConfigGitUserEmail(workingDir) - if err!= nil { - fmt.Println("failed to configure git user email", err) - return -1, err - } + if err != nil { + fmt.Println("failed to configure git user email", err) + return -1, err + } err = utils.ConfigGitSafeDir(workingDir) - if err!= nil { - fmt.Println("failed to configure git safe dir", err) - return -1, err - } + if err != nil { + fmt.Println("failed to configure git safe dir", err) + return -1, err + } currentBranch, err := utils.GetCurrentBranch(workingDir) - if err!=nil{ + if err != nil { fmt.Println("failed to get current branch", err) - return -1, err + return -1, err } fmt.Printf("-------Current branch: %s----- ", currentBranch) - if currentBranch=="main"{ + if currentBranch == "main" { return -1, errors.New("current branch is main can not raise a pr") } execution, err := s.executionRepo.GetExecutionsByBranchName(currentBranch) - if err!= nil { - fmt.Println("failed to fetch executions by branch name", err) - return -1, err - } + if err != nil { + fmt.Println("failed to fetch executions by branch name", err) + return -1, err + } storyID := execution.StoryID output, err := utils.GitAddToTrackFiles(workingDir, nil) @@ -316,7 +356,7 @@ func (s *PullRequestService) CreateManualPullRequest(projectID int, title string fmt.Printf("Git add output: %s\n", output) commitMsg := fmt.Sprintf("commiting for project id: %s\n", strconv.Itoa(projectID)) - output, err =utils.GitCommitWithMessage( + output, err = utils.GitCommitWithMessage( workingDir, commitMsg, nil, @@ -329,16 +369,16 @@ func (s *PullRequestService) CreateManualPullRequest(projectID int, title string organisationID := project.OrganisationID organisation, err := s.organisationRepo.GetOrganisationByID(uint(organisationID)) - if err!= nil { + if err != nil { fmt.Println("failed to fetch organisation", err) return -1, err } spaceOrProjectName := s.gitService.GetSpaceOrProjectName(organisation) openPullRequest, err := s.pullRequestRepo.GetOpenPullRequestsByStoryID(int(storyID)) - if err!= nil { - fmt.Println("failed to fetch open pull requests by story id", err) - return -1, err - } + if err != nil { + fmt.Println("failed to fetch open pull requests by story id", err) + return -1, err + } httpPrefix := "https" if config.AppEnv() == constants.Development { @@ -346,17 +386,17 @@ func (s *PullRequestService) CreateManualPullRequest(projectID int, title string } origin := fmt.Sprintf("%s://%s:%s@%s/git/%s/%s.git", httpPrefix, config.GitnessUser(), config.GitnessToken(), config.GitnessHost(), spaceOrProjectName, project.Name) err = utils.GitPush(workingDir, origin, currentBranch) - if err!=nil{ + if err != nil { fmt.Printf("Error pushing changes: %s\n", err.Error()) return -1, err } if openPullRequest == nil { err := utils.PullOriginMain(workingDir, origin) - if err!= nil { - fmt.Printf("Error pulling origin main: %s\n", err.Error()) - return -1, err - } + if err != nil { + fmt.Printf("Error pulling origin main: %s\n", err.Error()) + return -1, err + } fmt.Println("____no open pull requests, creating a new one____") pr, err := s.gitService.CreatePullRequest(spaceOrProjectName, project.Name, currentBranch, "main", "Pull Request: "+title, description) if err != nil { @@ -365,14 +405,14 @@ func (s *PullRequestService) CreateManualPullRequest(projectID int, title string } prType := constants.Manual pullRequest, err := s.CreatePullRequest(pr.Title, pr.Description, strconv.Itoa(pr.Number), "GITNESS", pr.SourceSHA, "sample", pr.MergeBaseSHA, pr.Number, storyID, 0, prType) - if err!= nil { + if err != nil { fmt.Printf("Error creating pull request in database: %s\n", err.Error()) return -1, err } fmt.Println("Pull Request created successfully", pullRequest) err = utils.ConfigGitSafeDir("/workspaces") - if err!= nil { + if err != nil { fmt.Println("failed to configure git safe dir", err) return -1, err } @@ -381,22 +421,22 @@ func (s *PullRequestService) CreateManualPullRequest(projectID int, title string } else { fmt.Println("______found an open pull request pushing changes in it______") latestCommitID, err := utils.GetLatestCommitID(workingDir, err) - if err!= nil{ - fmt.Printf("Error getting latest commit id: %s\n", err.Error()) - return -1, err - } + if err != nil { + fmt.Printf("Error getting latest commit id: %s\n", err.Error()) + return -1, err + } err = s.UpdatePullRequestSourceSHA(openPullRequest, latestCommitID) - if err!= nil { - fmt.Printf("Error updating pull request source sha: %s\n", err.Error()) - return -1, err - } + if err != nil { + fmt.Printf("Error updating pull request source sha: %s\n", err.Error()) + return -1, err + } err = utils.ConfigGitSafeDir("/workspaces") - if err!= nil { + if err != nil { fmt.Println("failed to configure git safe dir", err) return -1, err } - + return int(openPullRequest.ID), nil } -} \ No newline at end of file +} diff --git a/gui/src/api/DashboardService.tsx b/gui/src/api/DashboardService.tsx index 8c05a824..a935e3c9 100644 --- a/gui/src/api/DashboardService.tsx +++ b/gui/src/api/DashboardService.tsx @@ -113,6 +113,10 @@ export const mergePullRequest = (pull_request_id: number) => { }); }; +export const closePullRequest = (pull_request_id: number) => { + return api.post(`/pull-requests/${pull_request_id}/close`, {}); +}; + export const getCommitsPullRequest = (pr_id: number) => { return api.get(`/pull-requests/${pr_id}/commits`); }; diff --git a/gui/src/app/(programmer)/pull_request/[pr_id]/page.tsx b/gui/src/app/(programmer)/pull_request/[pr_id]/page.tsx index 9f49b791..0221ee18 100644 --- a/gui/src/app/(programmer)/pull_request/[pr_id]/page.tsx +++ b/gui/src/app/(programmer)/pull_request/[pr_id]/page.tsx @@ -14,6 +14,7 @@ import CustomTabs from '@/components/CustomTabs/CustomTabs'; import FilesChanged from '@/app/(programmer)/pull_request/[pr_id]/FilesChanged'; import { usePullRequestsContext } from '@/context/PullRequests'; import { + closePullRequest, commentRebuildStory, getCommitsPullRequest, getPullRequestDiff, @@ -149,6 +150,19 @@ export default function PRDetails(props) { } } + async function toClosePullRequest() { + try { + const response = await closePullRequest(Number(selectedPRId)); + if (response) { + toast.success('PR has been Successfully Closed'); + router.push(`/pull_request`); + } + } catch (error) { + console.error('Error while closing pull request: ', error); + toast.error('Error occurred while Closing the PR'); + } + } + async function toGetCommitsPullRequest() { try { const response = await getCommitsPullRequest(Number(selectedPRId)); @@ -237,6 +251,12 @@ export default function PRDetails(props) { > Re-Build +