in prow/plugins/bugzilla/bugzilla.go [580:792]
func handle(e event, gc githubClient, bc bugzilla.Client, options plugins.BugzillaBranchOptions, log *logrus.Entry, allRepos sets.String) error {
comment := e.comment(gc)
// check if bug is part of a restricted group
if !e.missing {
bug, err := getBug(bc, e.bugId, log, comment)
if err != nil || bug == nil {
return err
}
if !isBugAllowed(bug, options.AllowedGroups) {
// ignore bugs that are in non-allowed groups for this repo
if e.opened || refreshCommandMatch.MatchString(e.body) {
response := fmt.Sprintf(bugLink+" is in a bug group that is not in the allowed groups for this repo.", e.bugId, bc.Endpoint(), e.bugId)
if len(options.AllowedGroups) > 0 {
response += "\nAllowed groups for this repo are:"
for _, group := range options.AllowedGroups {
response += "\n- " + group
}
} else {
response += " There are no allowed bug groups configured for this repo."
}
return comment(response)
}
return nil
}
}
// merges follow a different pattern from the normal validation
if e.merged {
return handleMerge(e, gc, bc, options, log, allRepos)
}
// close events follow a different pattern from the normal validation
if e.closed && !e.merged {
return handleClose(e, gc, bc, options, log)
}
// cherrypicks follow a different pattern than normal validation
if e.cherrypick {
return handleCherrypick(e, gc, bc, options, log)
}
var needsValidLabel, needsInvalidLabel bool
var response, severityLabel string
if e.missing {
log.WithField("bugMissing", true)
log.Debug("No bug referenced.")
needsValidLabel, needsInvalidLabel = false, false
response = `No Bugzilla bug is referenced in the title of this pull request.
To reference a bug, add 'Bug XXX:' to the title of this pull request and request another bug refresh with <code>/bugzilla refresh</code>.`
} else {
log = log.WithField("bugId", e.bugId)
bug, err := getBug(bc, e.bugId, log, comment)
if err != nil || bug == nil {
return err
}
severityLabel = getSeverityLabel(bug.Severity)
var dependents []bugzilla.Bug
if options.DependentBugStates != nil || options.DependentBugTargetReleases != nil {
for _, id := range bug.DependsOn {
dependent, err := bc.GetBug(id)
if err != nil {
return comment(formatError(fmt.Sprintf("searching for dependent bug %d", id), bc.Endpoint(), e.bugId, err))
}
dependents = append(dependents, *dependent)
}
}
valid, validationsRun, why := validateBug(*bug, dependents, options, bc.Endpoint())
needsValidLabel, needsInvalidLabel = valid, !valid
if valid {
log.Debug("Valid bug found.")
response = fmt.Sprintf(`This pull request references `+bugLink+`, which is valid.`, e.bugId, bc.Endpoint(), e.bugId)
// if configured, move the bug to the new state
if update := options.StateAfterValidation.AsBugUpdate(bug); update != nil {
if err := bc.UpdateBug(e.bugId, *update); err != nil {
log.WithError(err).Warn("Unexpected error updating Bugzilla bug.")
return comment(formatError(fmt.Sprintf("updating to the %s state", options.StateAfterValidation), bc.Endpoint(), e.bugId, err))
}
response += fmt.Sprintf(" The bug has been moved to the %s state.", options.StateAfterValidation)
}
if options.AddExternalLink != nil && *options.AddExternalLink {
changed, err := bc.AddPullRequestAsExternalBug(e.bugId, e.org, e.repo, e.number)
if err != nil {
log.WithError(err).Warn("Unexpected error adding external tracker bug to Bugzilla bug.")
return comment(formatError("adding this pull request to the external tracker bugs", bc.Endpoint(), e.bugId, err))
}
if changed {
response += " The bug has been updated to refer to the pull request using the external bug tracker."
}
}
response += "\n\n<details>"
if len(validationsRun) == 0 {
response += "<summary>No validations were run on this bug</summary>"
} else {
response += fmt.Sprintf("<summary>%d validation(s) were run on this bug</summary>\n", len(validationsRun))
}
for _, validation := range validationsRun {
response += fmt.Sprint("\n* ", validation)
}
response += "</details>"
// identify qa contact via email if possible
explicitQARequest := e.assign || e.cc
if bug.QAContactDetail == nil {
if explicitQARequest {
response += fmt.Sprintf(bugLink+" does not have a QA contact, skipping assignment", e.bugId, bc.Endpoint(), e.bugId)
}
} else if bug.QAContactDetail.Email == "" {
if explicitQARequest {
response += fmt.Sprintf("QA contact for "+bugLink+" does not have a listed email, skipping assignment", e.bugId, bc.Endpoint(), e.bugId)
}
} else {
query := &emailToLoginQuery{}
email := bug.QAContactDetail.Email
queryVars := map[string]interface{}{
"email": githubql.String(email),
}
err := gc.Query(context.Background(), query, queryVars)
if err != nil {
log.WithError(err).Error("Failed to run graphql github query")
return comment(formatError(fmt.Sprintf("querying GitHub for users with public email (%s)", email), bc.Endpoint(), e.bugId, err))
}
response += fmt.Sprint("\n\n", processQuery(query, email, log))
if e.assign {
response += "\n\n**DEPRECATION NOTICE**: The command `assign-qa` has been deprecated. Please use the `cc-qa` command instead."
}
}
} else {
log.Debug("Invalid bug found.")
var formattedReasons string
for _, reason := range why {
formattedReasons += fmt.Sprintf(" - %s\n", reason)
}
response = fmt.Sprintf(`This pull request references `+bugLink+`, which is invalid:
%s
Comment <code>/bugzilla refresh</code> to re-evaluate validity if changes to the Bugzilla bug are made, or edit the title of this pull request to link to a different bug.`, e.bugId, bc.Endpoint(), e.bugId, formattedReasons)
}
}
// ensure label state is correct. Do not propagate errors
// as it is more important to report to the user than to
// fail early on a label check.
currentLabels, err := gc.GetIssueLabels(e.org, e.repo, e.number)
if err != nil {
log.WithError(err).Warn("Could not list labels on PR")
}
var hasValidLabel, hasInvalidLabel bool
var severityLabelToRemove string
for _, l := range currentLabels {
if l.Name == labels.ValidBug {
hasValidLabel = true
}
if l.Name == labels.InvalidBug {
hasInvalidLabel = true
}
if l.Name == labels.BugzillaSeverityHigh ||
l.Name == labels.BugzillaSeverityUrgent ||
l.Name == labels.BugzillaSeverityMed ||
l.Name == labels.BugzillaSeverityLow ||
l.Name == labels.BugzillaSeverityUnspecified {
severityLabelToRemove = l.Name
}
}
if severityLabelToRemove != "" && severityLabel != severityLabelToRemove {
if err := gc.RemoveLabel(e.org, e.repo, e.number, severityLabelToRemove); err != nil {
log.WithError(err).Error("Failed to remove severity bug label.")
}
}
if severityLabel != "" && severityLabel != severityLabelToRemove {
if err := gc.AddLabel(e.org, e.repo, e.number, severityLabel); err != nil {
log.WithError(err).Error("Failed to add severity bug label.")
}
}
if hasValidLabel && !needsValidLabel {
humanLabelled, err := gc.WasLabelAddedByHuman(e.org, e.repo, e.number, labels.ValidBug)
if err != nil {
// Return rather than potentially doing the wrong thing. The user can re-trigger us.
return fmt.Errorf("failed to check if %s label was added by a human: %w", labels.ValidBug, err)
}
if humanLabelled {
// This will make us remove the invalid label if it exists but saves us another check if it was
// added by a human. It is reasonable to assume that it should be absent if the valid label was
// manually added.
needsInvalidLabel = false
needsValidLabel = true
response += fmt.Sprintf("\n\nRetaining the %s label as it was manually added.", labels.ValidBug)
}
}
if needsValidLabel && !hasValidLabel {
if err := gc.AddLabel(e.org, e.repo, e.number, labels.ValidBug); err != nil {
log.WithError(err).Error("Failed to add valid bug label.")
}
} else if !needsValidLabel && hasValidLabel {
if err := gc.RemoveLabel(e.org, e.repo, e.number, labels.ValidBug); err != nil {
log.WithError(err).Error("Failed to remove valid bug label.")
}
}
if needsInvalidLabel && !hasInvalidLabel {
if err := gc.AddLabel(e.org, e.repo, e.number, labels.InvalidBug); err != nil {
log.WithError(err).Error("Failed to add invalid bug label.")
}
} else if !needsInvalidLabel && hasInvalidLabel {
if err := gc.RemoveLabel(e.org, e.repo, e.number, labels.InvalidBug); err != nil {
log.WithError(err).Error("Failed to remove invalid bug label.")
}
}
return comment(response)
}