github.com/quickfeed/quickfeed@v0.0.0-20240507093252-ed8ca812a09c/ci/record_results.go (about) 1 package ci 2 3 import ( 4 "fmt" 5 6 "github.com/quickfeed/quickfeed/database" 7 "github.com/quickfeed/quickfeed/kit/score" 8 "github.com/quickfeed/quickfeed/qf" 9 "go.uber.org/zap" 10 "google.golang.org/protobuf/types/known/timestamppb" 11 "gorm.io/gorm" 12 ) 13 14 // RecordResults for the course and assignment given by the run data structure. 15 // If the results argument is nil, then the submission is considered to be a manual review. 16 func (r RunData) RecordResults(logger *zap.SugaredLogger, db database.Database, results *score.Results) (*qf.Submission, error) { 17 logger.Debugf("Fetching (if any) previous submission for %s", r) 18 previous, err := r.previousSubmission(db) 19 if err != nil && err != gorm.ErrRecordNotFound { 20 return nil, fmt.Errorf("failed to get previous submission: %w", err) 21 } 22 if previous == nil { 23 logger.Debugf("Recording new submission for %s", r) 24 } else { 25 logger.Debugf("Updating submission %d for %s", previous.GetID(), r) 26 } 27 28 resType, newSubmission := r.newSubmission(previous, results) 29 if err = db.CreateSubmission(newSubmission); err != nil { 30 return nil, fmt.Errorf("failed to record submission %d for %s: %w", previous.GetID(), r, err) 31 } 32 logger.Debugf("Recorded %s for %s with status %s and score %d", resType, r, newSubmission.GetStatus(), newSubmission.GetScore()) 33 34 if !r.Rebuild { 35 if err := r.updateSlipDays(db, newSubmission); err != nil { 36 return nil, fmt.Errorf("failed to update slip days for %s: %w", r, err) 37 } 38 logger.Debugf("Updated slip days for %s", r) 39 } 40 return newSubmission, nil 41 } 42 43 func (r RunData) previousSubmission(db database.Database) (*qf.Submission, error) { 44 submissionQuery := &qf.Submission{ 45 AssignmentID: r.Assignment.GetID(), 46 UserID: r.Repo.GetUserID(), 47 GroupID: r.Repo.GetGroupID(), 48 } 49 return db.GetSubmission(submissionQuery) 50 } 51 52 func (r RunData) newSubmission(previous *qf.Submission, results *score.Results) (string, *qf.Submission) { 53 if results != nil { 54 return "test execution", r.newTestRunSubmission(previous, results) 55 } 56 return "manual review", r.newManualReviewSubmission(previous) 57 } 58 59 func (r RunData) newManualReviewSubmission(previous *qf.Submission) *qf.Submission { 60 return &qf.Submission{ 61 ID: previous.GetID(), 62 AssignmentID: r.Assignment.GetID(), 63 UserID: r.Repo.GetUserID(), 64 GroupID: r.Repo.GetGroupID(), 65 CommitHash: r.CommitID, 66 Score: previous.GetScore(), 67 Status: previous.GetStatus(), 68 Released: previous.GetReleased(), 69 BuildInfo: &score.BuildInfo{ 70 SubmissionDate: timestamppb.Now(), 71 BuildDate: timestamppb.Now(), 72 BuildLog: "No automated tests for this assignment", 73 ExecTime: 1, 74 }, 75 } 76 } 77 78 func (r RunData) newTestRunSubmission(previous *qf.Submission, results *score.Results) *qf.Submission { 79 if r.Rebuild && previous != nil && previous.BuildInfo != nil { 80 // Keep previous submission's delivery date if this is a rebuild. 81 results.BuildInfo.SubmissionDate = previous.BuildInfo.SubmissionDate 82 } 83 score := results.Sum() 84 return &qf.Submission{ 85 ID: previous.GetID(), 86 AssignmentID: r.Assignment.GetID(), 87 UserID: r.Repo.GetUserID(), 88 GroupID: r.Repo.GetGroupID(), 89 CommitHash: r.CommitID, 90 Score: score, 91 Status: r.Assignment.IsApproved(previous, score), 92 BuildInfo: results.BuildInfo, 93 Scores: results.Scores, 94 } 95 } 96 97 func (r RunData) updateSlipDays(db database.Database, submission *qf.Submission) error { 98 enrollments := make([]*qf.Enrollment, 0) 99 if submission.GroupID > 0 { 100 group, err := db.GetGroup(submission.GroupID) 101 if err != nil { 102 return fmt.Errorf("failed to get group %d: %w", submission.GroupID, err) 103 } 104 enrollments = append(enrollments, group.Enrollments...) 105 } else { 106 enrol, err := db.GetEnrollmentByCourseAndUser(r.Assignment.CourseID, submission.UserID) 107 if err != nil { 108 return fmt.Errorf("failed to get enrollment for user %d in course %d: %w", submission.UserID, r.Assignment.CourseID, err) 109 } 110 enrollments = append(enrollments, enrol) 111 } 112 113 for _, enrol := range enrollments { 114 if err := enrol.UpdateSlipDays(r.Assignment, submission); err != nil { 115 return fmt.Errorf("failed to update slip days for user %d in course %d: %w", enrol.UserID, r.Assignment.CourseID, err) 116 } 117 if err := db.UpdateSlipDays(enrol.UsedSlipDays); err != nil { 118 return fmt.Errorf("failed to update slip days for enrollment %d (user %d) (course %d): %w", enrol.ID, enrol.UserID, enrol.CourseID, err) 119 } 120 } 121 return nil 122 } 123 124 // GetOwners returns the UserIDs of a user or group repository's owners. 125 // Returns an error if no owners could be found. 126 // This method should only be called for a user or group repository. 127 func (r RunData) GetOwners(db database.Database) ([]uint64, error) { 128 var owners []uint64 129 if r.Repo.IsUserRepo() { 130 owners = []uint64{r.Repo.GetUserID()} 131 } 132 if r.Repo.IsGroupRepo() { 133 group, err := db.GetGroup(r.Repo.GetGroupID()) 134 if err == nil { 135 owners = group.UserIDs() 136 } 137 } 138 if len(owners) == 0 { 139 return nil, fmt.Errorf("failed to get owners for %s", r) 140 } 141 return owners, nil 142 }