blob: 949ed7b987036c67223587c878e8ae0100b30f5e [file] [log] [blame]
// Copyright 2022 The Chromium 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 weekly
import (
"context"
"fmt"
"net/http"
"strings"
"sync"
"text/template"
"github.com/maruel/subcommands"
"google.golang.org/grpc/credentials"
"go.chromium.org/luci/auth"
"go.chromium.org/luci/auth/client/authcli"
"go.chromium.org/luci/common/cli"
"go.chromium.org/luci/common/errors"
"infra/monorail"
)
var (
MonorailAPIURL = "https://monorail-prod.appspot.com/_ah/api/monorail/v1"
markdownTempl = template.Must(template.New("test").Parse(`
| Pri | Issue ID | Summary |
| --- | -------- | ------- |
{{- range .}}
| {{.Pri}} | [{{.ProjectID}}:{{.ID}}](https://bugs.chromium.org/p/{{.ProjectID}}/issues/detail?id={{.ID}}) | {{.Summary}} |
{{- end}}
`))
)
// clientFactory encapsulates the dialing and caching of http transports.
type clientFactory struct {
tlsCreds credentials.TransportCredentials
baseAuth *auth.Authenticator
idTokenAuth *auth.Authenticator
initOnce sync.Once
}
func (f *clientFactory) init(ctx context.Context, opts auth.Options) {
f.initOnce.Do(func() {
f.tlsCreds = credentials.NewTLS(nil)
f.baseAuth = auth.NewAuthenticator(ctx, auth.SilentLogin, opts)
opts.UseIDTokens = true
f.idTokenAuth = auth.NewAuthenticator(ctx, auth.InteractiveLogin, opts)
})
}
func (f *clientFactory) http() (*http.Client, error) {
return f.baseAuth.Client()
}
type reportCmd struct {
subcommands.CommandRunBase
clientFactory clientFactory
authFlags authcli.Flags
params Param
days int
Monorail monorail.MonorailClient
}
func (r *reportCmd) httpClient(ctx context.Context) (*http.Client, error) {
opts, err := r.authFlags.Options()
if err != nil {
return nil, err
}
r.clientFactory.init(ctx, opts)
httpClient, err := r.clientFactory.http()
switch {
case errors.Is(err, auth.ErrLoginRequired):
return nil, errors.New("Login required: run `weekly auth-login` or use the -service-account-json flag")
case err != nil:
return nil, err
}
return httpClient, nil
}
func (dc *reportCmd) RegisterFlags(p Param) {
dc.Flags.IntVar(&dc.days, "days", 7, "number of days to look back")
dc.authFlags.Register(&dc.Flags, p.Auth)
}
type tmplItem struct {
ProjectID string
ID int32
Summary string
Pri string
}
func (dc *reportCmd) Run(a subcommands.Application, args []string, env subcommands.Env) int {
ctx := cli.GetContext(a, nil, env)
htc, err := dc.httpClient(ctx)
if err != nil {
fmt.Fprintf(a.GetErr(), "ERROR: %s\n", err)
return -1
}
dc.Monorail = monorail.NewEndpointsClient(htc, MonorailAPIURL)
req := &monorail.IssuesListRequest{
ProjectId: "chromium",
Can: monorail.IssuesListRequest_ALL,
// This URL is borrowed from the link at go/berf-triage-queue and modified to:
// - include all issue statuses
// - limit results to issues modified in the past N (default 7) days as set by the -days CLI flag.
Q: fmt.Sprintf("component:Speed>Dashboard,Speed>Bisection,Speed>Benchmarks>Waterfall pri:0,1,2,3 opened>2021-8-1 -label:Browser-Perf-EngProd modified>today-%d", dc.days),
}
resp, err := dc.Monorail.IssuesList(ctx, req)
if err != nil {
fmt.Fprintf(a.GetErr(), "ERROR: %s\n", err)
return -1
}
data := []tmplItem{}
for _, issue := range resp.Items {
pri := ""
for _, l := range issue.Labels {
if strings.HasPrefix(l, "Pri-") {
pri = l
}
}
data = append(data, tmplItem{issue.ProjectId, issue.Id, issue.Summary, pri})
}
markdownTempl.Execute(a.GetOut(), data)
if err != nil {
fmt.Fprintf(a.GetErr(), "ERROR: %s\n", err)
return -1
}
return 0
}
func cmdReportIncoming(p Param) *subcommands.Command {
return &subcommands.Command{
UsageLine: "report-incoming",
ShortDesc: "Summarize issues from the past week's on-call rotation formatted as a markdown table.",
CommandRun: func() subcommands.CommandRun {
c := &reportCmd{params: p}
c.RegisterFlags(p)
return c
},
}
}