blob: 1dd7fe2edfff2159b7919c6665aea31eb5e47757 [file] [log] [blame]
// Copyright 2022 The LUCI Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package userhtml
import (
"fmt"
"strings"
"time"
bbutil "go.chromium.org/luci/buildbucket/protoutil"
"go.chromium.org/luci/common/errors"
"go.chromium.org/luci/cv/internal/run"
"go.chromium.org/luci/cv/internal/tryjob"
)
type uiLogEntry struct {
runLog *run.LogEntry
tryjobLog *tryjob.ExecutionLogEntry
// Context info to help render message
run *run.Run
cls []*run.RunCL
}
func (ul *uiLogEntry) HasTryjobChips() bool {
switch {
case ul.runLog != nil: // legacy CQDaemon way
return ul.runLog.GetTryjobsUpdated() != nil
case ul.tryjobLog != nil:
switch ul.tryjobLog.GetKind().(type) {
case *tryjob.ExecutionLogEntry_TryjobsLaunched_:
case *tryjob.ExecutionLogEntry_TryjobsReused_:
case *tryjob.ExecutionLogEntry_TryjobsEnded_:
case *tryjob.ExecutionLogEntry_TryjobDiscarded_:
case *tryjob.ExecutionLogEntry_RetryDenied_:
default:
return false
}
return true
default:
panic(fmt.Errorf("either Run Log or Tryjob Execution Log must be present"))
}
}
func (ul *uiLogEntry) Time() time.Time {
switch {
case ul.runLog != nil:
return ul.runLog.GetTime().AsTime().UTC()
case ul.tryjobLog != nil:
return ul.tryjobLog.GetTime().AsTime().UTC()
default:
panic(fmt.Errorf("either Run Log or Tryjob Execution Log must be present"))
}
}
func (ul *uiLogEntry) EventType() string {
switch {
case ul.runLog != nil:
return runLogEventName(ul.runLog)
case ul.tryjobLog != nil:
return tryjobLogEventName(ul.tryjobLog)
default:
panic(fmt.Errorf("either Run Log or Tryjob Execution Log must be present"))
}
}
func runLogEventName(rle *run.LogEntry) string {
switch v := rle.GetKind().(type) {
case *run.LogEntry_TryjobsUpdated_:
return "Tryjob Updated"
case *run.LogEntry_ConfigChanged_:
return "Config Changed"
case *run.LogEntry_Started_:
return "Run Started"
case *run.LogEntry_Created_:
return "Run Created"
case *run.LogEntry_TreeChecked_:
return "Tree Status Check"
case *run.LogEntry_Info_:
return v.Info.Label
case *run.LogEntry_AcquiredSubmitQueue_:
return "Acquired Submit Queue"
case *run.LogEntry_ReleasedSubmitQueue_:
return "Released Submit Queue"
case *run.LogEntry_Waitlisted_:
return "Waiting For Submission"
case *run.LogEntry_SubmissionFailure_:
return "CL Submission Failed"
case *run.LogEntry_ClSubmitted:
return "CL Submission"
case *run.LogEntry_RunEnded_:
return "Run Ended"
default:
panic(fmt.Errorf("unknown Run log kind %T", v))
}
}
func tryjobLogEventName(tle *tryjob.ExecutionLogEntry) string {
switch v := tle.GetKind().(type) {
case *tryjob.ExecutionLogEntry_RequirementChanged_:
return "Tryjob Requirement Changed"
case *tryjob.ExecutionLogEntry_TryjobsLaunched_:
return "Tryjob Launched"
case *tryjob.ExecutionLogEntry_TryjobsLaunchFailed_:
return "Tryjob Launch Failure"
case *tryjob.ExecutionLogEntry_TryjobsReused_:
return "Tryjob Reused"
case *tryjob.ExecutionLogEntry_TryjobsEnded_:
return "Tryjob Ended"
case *tryjob.ExecutionLogEntry_TryjobDiscarded_:
return "Tryjob Discarded"
case *tryjob.ExecutionLogEntry_RetryDenied_:
return "Retry Denied"
default:
panic(fmt.Errorf("unknown Tryjob execution log kind %T", v))
}
}
func (ul *uiLogEntry) Message() string {
switch {
case ul.runLog != nil:
return ul.runLogMessage(ul.runLog)
case ul.tryjobLog != nil:
return tryjobLogMessage(ul.tryjobLog)
default:
panic(fmt.Errorf("either Run Log or Tryjob Execution Log must be present"))
}
}
func (ul *uiLogEntry) runLogMessage(rle *run.LogEntry) string {
switch v := rle.GetKind().(type) {
case *run.LogEntry_Info_:
return v.Info.Message
case *run.LogEntry_ClSubmitted:
return StringifySubmissionSuccesses(ul.cls, v.ClSubmitted)
case *run.LogEntry_SubmissionFailure_:
return StringifySubmissionFailureReason(ul.cls, v.SubmissionFailure.Event)
case *run.LogEntry_RunEnded_:
// This assumes the status of a Run won't change after transitioning to
// one of the terminal statuses. If the assumption is no longer valid.
// End status and cancellation reason need to be stored explicitly in
// the log entry.
if ul.run.Status == run.Status_CANCELLED {
switch len(ul.run.CancellationReasons) {
case 0:
case 1:
return fmt.Sprintf("Run is cancelled. Reason: %s", ul.run.CancellationReasons[0])
default:
var sb strings.Builder
sb.WriteString("Run is cancelled. Reasons:")
for _, reason := range ul.run.CancellationReasons {
sb.WriteRune('\n')
sb.WriteString(" * ")
sb.WriteString(strings.TrimSpace(reason))
}
return sb.String()
}
}
return ""
default:
return ""
}
}
func tryjobLogMessage(tle *tryjob.ExecutionLogEntry) string {
switch v := tle.GetKind().(type) {
case *tryjob.ExecutionLogEntry_RequirementChanged_:
// TODO(yiwzhang): describe what has changed.
return ""
case *tryjob.ExecutionLogEntry_TryjobsLaunchFailed_:
var sb strings.Builder
for i, tj := range v.TryjobsLaunchFailed.GetTryjobs() {
if i != 0 {
sb.WriteString("\n")
}
sb.WriteString("* ")
sb.WriteString(bbutil.FormatBuilderID(tj.Definition.GetBuildbucket().GetBuilder()))
sb.WriteString(": ")
sb.WriteString(tj.GetReason())
}
return sb.String()
case *tryjob.ExecutionLogEntry_TryjobDiscarded_:
return fmt.Sprintf("Reason: %s", v.TryjobDiscarded.GetReason())
case *tryjob.ExecutionLogEntry_RetryDenied_:
return fmt.Sprintf("Can't retry following tryjob(s) because %s", v.RetryDenied.GetReason())
default:
return ""
}
}
func (ul *uiLogEntry) LegacyTryjobsByStatus() map[string][]*uiTryjob {
if ul.runLog.GetTryjobsUpdated() == nil {
return nil
}
tjs := ul.runLog.GetTryjobsUpdated().GetTryjobs()
ret := make(map[string][]*uiTryjob, len(tjs))
for _, tj := range tjs {
k := strings.Title(strings.ToLower(tj.Status.String()))
ret[k] = append(ret[k], &uiTryjob{
ExternalID: tryjob.ExternalID(tj.ExternalId),
Definition: tj.Definition,
Status: tj.Status,
Result: tj.Result,
Reused: tj.Reused,
})
}
return ret
}
func (ul *uiLogEntry) Tryjobs() []*uiTryjob {
if !ul.HasTryjobChips() {
panic(errors.Reason("requested tryjobs for log entry that doesn't have tryjob chip %T", ul.tryjobLog.GetKind()).Err())
}
switch v := ul.tryjobLog.GetKind().(type) {
case *tryjob.ExecutionLogEntry_TryjobsLaunched_:
return makeUITryjobsFromSnapshots(v.TryjobsLaunched.GetTryjobs())
case *tryjob.ExecutionLogEntry_TryjobsReused_:
return makeUITryjobsFromSnapshots(v.TryjobsReused.GetTryjobs())
case *tryjob.ExecutionLogEntry_TryjobsEnded_:
return makeUITryjobsFromSnapshots(v.TryjobsEnded.GetTryjobs())
case *tryjob.ExecutionLogEntry_TryjobDiscarded_:
return makeUITryjobsFromSnapshots([]*tryjob.ExecutionLogEntry_TryjobSnapshot{v.TryjobDiscarded.GetSnapshot()})
case *tryjob.ExecutionLogEntry_RetryDenied_:
return makeUITryjobsFromSnapshots(v.RetryDenied.GetTryjobs())
default:
panic(fmt.Errorf("not supported tryjob log kind %T", v))
}
}