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  }