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

Method RetryIssueApproval

backend/api/v1/issue_service.go:893–968  ·  view source on GitHub ↗

RetryIssueApproval re-runs approval-template finding for an issue stuck in CHECKING. The synchronous post-create path in `postCreateIssue` swallows errors (e.g. CEL evaluation failure against a malformed workspace approval rule), and there is no event-driven retry for non-DATABASE_CHANGE issue types

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

Source from the content-addressed store, hash-verified

891// Idempotent: returns the existing issue unchanged when approval-finding
892// has already completed.
893func (s *IssueService) RetryIssueApproval(ctx context.Context, req *connect.Request[v1pb.RetryIssueApprovalRequest]) (*connect.Response[v1pb.Issue], error) {
894 issue, err := s.getIssueMessage(ctx, req.Msg.Name)
895 if err != nil {
896 return nil, err
897 }
898
899 user, ok := GetUserFromContext(ctx)
900 if !ok {
901 return nil, connect.NewError(connect.CodeInternal, errors.New("user not found"))
902 }
903 if !canRequestIssue(issue.CreatorEmail, user) {
904 return nil, connect.NewError(connect.CodePermissionDenied, errors.New("only the issue creator can retry approval finding"))
905 }
906
907 // No-op fast path: nothing to retry if the previous attempt completed.
908 if issue.Payload.GetApproval().GetApprovalFindingDone() {
909 issueV1, err := s.convertToIssue(issue)
910 if err != nil {
911 return nil, connect.NewError(connect.CodeInternal, errors.Wrapf(err, "failed to convert to issue"))
912 }
913 return connect.NewResponse(issueV1), nil
914 }
915
916 if err := approval.FindAndApplyApprovalTemplate(ctx, s.store, s.webhookManager, s.licenseService, issue); err != nil {
917 // Surface the underlying cause (e.g. CEL error in the workspace
918 // approval rule) so the operator can fix it.
919 return nil, connect.NewError(connect.CodeFailedPrecondition, errors.Wrap(err, "approval finding still failing"))
920 }
921
922 uid := issue.UID
923 refreshed, err := s.store.GetIssue(ctx, &store.FindIssueMessage{
924 Workspace: common.GetWorkspaceIDFromContext(ctx),
925 ProjectIDs: []string{issue.ProjectID},
926 UID: &uid,
927 })
928 if err != nil {
929 return nil, connect.NewError(connect.CodeInternal, errors.Wrap(err, "failed to refresh issue"))
930 }
931 if refreshed == nil {
932 return nil, connect.NewError(connect.CodeNotFound, errors.New("issue not found after retry"))
933 }
934
935 // Mirror the post-approval side effects that `postCreateIssue` and the
936 // approval runner perform when finding completes — without these, an
937 // auto-approved (template-less / SKIPPED) retry result would leave the
938 // issue out of CHECKING but skip the actual work:
939 // * ACCESS_GRANT / ROLE_GRANT → activate the grant via
940 // `completeAccessRequestIssue`.
941 // * DATABASE_CHANGE with a plan → enqueue rollout creation.
942 approved, err := utils.CheckIssueApproved(refreshed)
943 if err != nil {
944 return nil, connect.NewError(connect.CodeInternal, errors.Wrap(err, "failed to check approval state"))
945 }
946 if approved {
947 switch refreshed.Type {
948 case storepb.Issue_ACCESS_GRANT, storepb.Issue_ROLE_GRANT:
949 if completed, completeErr := completeAccessRequestIssue(ctx, s.store, user.Email, refreshed); completeErr != nil {
950 slog.Warn("failed to complete access request issue after retry", log.BBError(completeErr))

Callers

nothing calls this directly

Calls 12

getIssueMessageMethod · 0.95
convertToIssueMethod · 0.95
CheckIssueApprovedFunction · 0.92
BBErrorFunction · 0.92
GetUserFromContextFunction · 0.85
canRequestIssueFunction · 0.85
GetIssueMethod · 0.65
GetApprovalMethod · 0.45

Tested by

no test coverage detected