MCPcopy Create free account
hub / github.com/bytebase/bytebase / UpdateIssue

Method UpdateIssue

backend/api/v1/issue_service.go:971–1110  ·  view source on GitHub ↗

UpdateIssue updates the issue.

(ctx context.Context, req *connect.Request[v1pb.UpdateIssueRequest])

Source from the content-addressed store, hash-verified

969
970// UpdateIssue updates the issue.
971func (s *IssueService) UpdateIssue(ctx context.Context, req *connect.Request[v1pb.UpdateIssueRequest]) (*connect.Response[v1pb.Issue], error) {
972 user, ok := GetUserFromContext(ctx)
973 if !ok {
974 return nil, connect.NewError(connect.CodeInternal, errors.Errorf("user not found"))
975 }
976 if req.Msg.UpdateMask == nil {
977 return nil, connect.NewError(connect.CodeInvalidArgument, errors.Errorf("update_mask must be set"))
978 }
979
980 issue, err := s.getIssueMessage(ctx, req.Msg.Issue.Name)
981 if err != nil {
982 if connect.CodeOf(err) == connect.CodeNotFound && req.Msg.AllowMissing {
983 // When allow_missing is true and issue doesn't exist, create a new one
984 ok, err := s.iamManager.CheckPermission(ctx, permission.IssuesCreate, user, common.GetWorkspaceIDFromContext(ctx))
985 if err != nil {
986 return nil, connect.NewError(connect.CodeInternal, errors.Wrap(err, "failed to check permission"))
987 }
988 if !ok {
989 return nil, connect.NewError(connect.CodePermissionDenied, errors.Errorf("user does not have permission %q", permission.IssuesCreate))
990 }
991
992 // Extract project ID from the issue name (format: projects/{project}/issues/{issue})
993 parts := strings.Split(req.Msg.Issue.Name, "/")
994 if len(parts) < 4 || parts[0] != "projects" || parts[2] != "issues" {
995 return nil, connect.NewError(connect.CodeInvalidArgument, errors.Errorf("invalid issue name format: %q", req.Msg.Issue.Name))
996 }
997 projectID := parts[1]
998
999 return s.CreateIssue(ctx, connect.NewRequest(&v1pb.CreateIssueRequest{
1000 Parent: fmt.Sprintf("projects/%s", projectID),
1001 Issue: req.Msg.Issue,
1002 }))
1003 }
1004 return nil, err
1005 }
1006 project, err := s.store.GetProject(ctx, &store.FindProjectMessage{Workspace: common.GetWorkspaceIDFromContext(ctx), ResourceID: &issue.ProjectID})
1007 if err != nil {
1008 return nil, connect.NewError(connect.CodeInternal, errors.Wrapf(err, "failed to get project"))
1009 }
1010 if project == nil {
1011 return nil, connect.NewError(connect.CodeNotFound, errors.Errorf("project %s not found", issue.ProjectID))
1012 }
1013
1014 updateMasks := map[string]bool{}
1015
1016 patch := &store.UpdateIssueMessage{}
1017 var issueCommentCreates []*store.IssueCommentMessage
1018 for _, path := range req.Msg.UpdateMask.Paths {
1019 updateMasks[path] = true
1020 switch path {
1021 case "title":
1022 trimmed := strings.TrimSpace(req.Msg.Issue.Title)
1023 if trimmed == "" {
1024 return nil, connect.NewError(connect.CodeInvalidArgument, errors.Errorf("title cannot be empty"))
1025 }
1026 if trimmed == issue.Title {
1027 // No-op update — skip both the patch and the audit comment so we
1028 // don't pollute the timeline with "changed name from X to X".

Callers

nothing calls this directly

Calls 13

getIssueMessageMethod · 0.95
CreateIssueMethod · 0.95
convertToIssueMethod · 0.95
CanonicalizeIssueLabelsFunction · 0.92
BBErrorFunction · 0.92
GetUserFromContextFunction · 0.85
ErrorfMethod · 0.80
CheckPermissionMethod · 0.80
CreateIssueCommentsMethod · 0.80
GetProjectMethod · 0.65
EqualMethod · 0.65

Tested by

no test coverage detected