Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(80)

Side by Side Diff: milo/appengine/buildbot/builder.go

Issue 2810113002: Milo buildbot builder page: Add pagnation with cursors. (Closed)
Patch Set: Touchups Created 3 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright 2016 The LUCI Authors. All rights reserved. 1 // Copyright 2016 The LUCI Authors. All rights reserved.
2 // Use of this source code is governed under the Apache License, Version 2.0 2 // Use of this source code is governed under the Apache License, Version 2.0
3 // that can be found in the LICENSE file. 3 // that can be found in the LICENSE file.
4 4
5 package buildbot 5 package buildbot
6 6
7 import ( 7 import (
8 "crypto/sha1"
9 "encoding/base64"
8 "errors" 10 "errors"
9 "fmt" 11 "fmt"
10 "sort" 12 "sort"
11 "strings" 13 "strings"
12 "time" 14 "time"
13 15
14 "github.com/luci/gae/service/datastore" 16 "github.com/luci/gae/service/datastore"
17 "github.com/luci/gae/service/memcache"
15 18
16 "github.com/luci/luci-go/common/clock" 19 "github.com/luci/luci-go/common/clock"
17 "github.com/luci/luci-go/common/logging" 20 "github.com/luci/luci-go/common/logging"
18 "github.com/luci/luci-go/milo/api/resp" 21 "github.com/luci/luci-go/milo/api/resp"
19 "golang.org/x/net/context" 22 "golang.org/x/net/context"
20 ) 23 )
21 24
22 // builderRef is used for keying specific builds in a master json. 25 // builderRef is used for keying specific builds in a master json.
23 type builderRef struct { 26 type builderRef struct {
24 builder string 27 builder string
(...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after
78 }, 81 },
79 Text: mergeText(b.Text), 82 Text: mergeText(b.Text),
80 Blame: blame(b), 83 Blame: blame(b),
81 Revision: b.Sourcestamp.Revision, 84 Revision: b.Sourcestamp.Revision,
82 } 85 }
83 } 86 }
84 87
85 // getBuilds fetches all of the recent builds from the . Note that 88 // getBuilds fetches all of the recent builds from the . Note that
86 // getBuilds() does not perform ACL checks. 89 // getBuilds() does not perform ACL checks.
87 func getBuilds( 90 func getBuilds(
88 » c context.Context, masterName, builderName string, finished bool, limit int) ( 91 » c context.Context, masterName, builderName string, finished bool, limit int, cursor *datastore.Cursor) (
89 » []*resp.BuildSummary, error) { 92 » []*resp.BuildSummary, *datastore.Cursor, error) {
90 93
91 // TODO(hinoka): Builder specific structs. 94 // TODO(hinoka): Builder specific structs.
92 result := []*resp.BuildSummary{} 95 result := []*resp.BuildSummary{}
93 q := datastore.NewQuery("buildbotBuild") 96 q := datastore.NewQuery("buildbotBuild")
94 q = q.Eq("finished", finished) 97 q = q.Eq("finished", finished)
95 q = q.Eq("master", masterName) 98 q = q.Eq("master", masterName)
96 q = q.Eq("builder", builderName) 99 q = q.Eq("builder", builderName)
97 » if limit != 0 { 100 » q = q.Order("-number")
98 » » q = q.Limit(int32(limit)) 101 » if cursor != nil {
102 » » q = q.Start(*cursor)
99 } 103 }
100 » q = q.Order("-number") 104 » buildbots, nextCursor, err := runBuildsQuery(c, q, int32(limit))
101 » buildbots := []*buildbotBuild{}
102 » err := getBuildQueryBatcher(c).GetAll(c, q, &buildbots)
103 if err != nil { 105 if err != nil {
104 » » return nil, err 106 » » return nil, nil, err
105 } 107 }
106 for _, b := range buildbots { 108 for _, b := range buildbots {
107 result = append(result, getBuildSummary(b)) 109 result = append(result, getBuildSummary(b))
108 } 110 }
109 » return result, nil 111 » return result, nextCursor, nil
112 }
113
114 // maybeSetGetCursor is a cheesy way to implement bidirectional paging with forw ard-only
115 // datastore cursor by creating a mapping of nextCursor -> thisCursor
116 // in memcache. maybeSetGetCursor stores the future mapping, then returns prevC ursor
117 // in the mapping for thisCursor -> prevCursor, if available.
118 func maybeSetGetCursor(c context.Context, thisCursor, nextCursor *datastore.Curs or) (*datastore.Cursor, bool) {
119 » key := func(c datastore.Cursor) string {
120 » » // Memcache key limit is 250 bytes, hash our cursor to get under this limit.
121 » » blob := sha1.Sum([]byte(c.String()))
122 » » return "v2:cursors:builders:" + base64.StdEncoding.EncodeToStrin g(blob[:])
nodir 2017/04/18 20:27:49 are you sure cursors for buildbucket builders will
nodir 2017/04/18 20:27:49 limit has to be included in the cache key. Bug rep
hinoka 2017/04/20 21:37:08 Done.
hinoka 2017/04/20 21:37:08 Blarg.... basically you can't actually go back onc
123 » }
124 » // Set the next cursor to this cursor mapping, if available.
125 » if nextCursor != nil {
126 » » thisKey := key(*nextCursor)
nodir 2017/04/18 20:27:49 nextKey? it is used only once, consider inlining i
hinoka 2017/04/20 21:37:08 Done.
127 » » item := memcache.NewItem(c, thisKey)
128 » » if thisCursor == nil {
129 » » » // Make sure we know it exists, just empty
130 » » » item.SetValue([]byte{})
131 » » } else {
132 » » » item.SetValue([]byte((*thisCursor).String()))
133 » » }
134 » » item.SetExpiration(24 * time.Hour)
135 » » memcache.Set(c, item)
136 » }
137 » // Try to get the last cursor, if valid and available.
138 » if thisCursor == nil {
139 » » return nil, false
140 » }
141 » if item, err := memcache.GetKey(c, key(*thisCursor)); err == nil {
142 » » if len(item.Value()) == 0 {
143 » » » return nil, true
144 » » }
145 » » if prevCursor, err := datastore.DecodeCursor(c, string(item.Valu e())); err == nil {
146 » » » return &prevCursor, true
147 » » }
148 » }
149 » return nil, false
110 } 150 }
111 151
112 var errMasterNotFound = errors.New( 152 var errMasterNotFound = errors.New(
113 "Either the request resource was not found or you have insufficient perm issions") 153 "Either the request resource was not found or you have insufficient perm issions")
114 var errNotAuth = errors.New("You are not authenticated, try logging in") 154 var errNotAuth = errors.New("You are not authenticated, try logging in")
115 155
116 // builderImpl is the implementation for getting a milo builder page from buildb ot. 156 // builderImpl is the implementation for getting a milo builder page from buildb ot.
117 // This gets: 157 // This gets:
118 // * Current Builds from querying the master json from the datastore. 158 // * Current Builds from querying the master json from the datastore.
119 // * Recent Builds from a cron job that backfills the recent builds. 159 // * Recent Builds from a cron job that backfills the recent builds.
120 func builderImpl(c context.Context, masterName, builderName string, limit int) ( *resp.Builder, error) { 160 func builderImpl(
161 » c context.Context, masterName, builderName string, limit int,
162 » cursor *datastore.Cursor) (*resp.Builder, error) {
nodir 2017/04/18 20:27:49 consider making cursor a string here. datastore is
hinoka 2017/04/20 21:37:08 Done.
163
121 result := &resp.Builder{ 164 result := &resp.Builder{
122 Name: builderName, 165 Name: builderName,
123 } 166 }
124 master, t, err := getMasterJSON(c, masterName) 167 master, t, err := getMasterJSON(c, masterName)
125 if err != nil { 168 if err != nil {
126 return nil, err 169 return nil, err
127 } 170 }
128 if clock.Now(c).Sub(t) > 2*time.Minute { 171 if clock.Now(c).Sub(t) > 2*time.Minute {
129 warning := fmt.Sprintf( 172 warning := fmt.Sprintf(
130 "WARNING: Master data is stale (last updated %s)", t) 173 "WARNING: Master data is stale (last updated %s)", t)
(...skipping 29 matching lines...) Expand all
160 result.PendingBuilds[i].Blame = make([]*resp.Commit, len(pb.Sour ce.Changes)) 203 result.PendingBuilds[i].Blame = make([]*resp.Commit, len(pb.Sour ce.Changes))
161 for j, cm := range pb.Source.Changes { 204 for j, cm := range pb.Source.Changes {
162 result.PendingBuilds[i].Blame[j] = &resp.Commit{ 205 result.PendingBuilds[i].Blame[j] = &resp.Commit{
163 AuthorEmail: cm.Who, 206 AuthorEmail: cm.Who,
164 CommitURL: cm.Revlink, 207 CommitURL: cm.Revlink,
165 } 208 }
166 } 209 }
167 } 210 }
168 211
169 // This is CPU bound anyways, so there's no need to do this in parallel. 212 // This is CPU bound anyways, so there's no need to do this in parallel.
170 » finishedBuilds, err := getBuilds(c, masterName, builderName, true, limit ) 213 » finishedBuilds, nextCursor, err := getBuilds(c, masterName, builderName, true, limit, cursor)
171 if err != nil { 214 if err != nil {
172 return nil, err 215 return nil, err
173 } 216 }
174 » currentBuilds, err := getBuilds(c, masterName, builderName, false, 0) 217 » if prevCursor, ok := maybeSetGetCursor(c, cursor, nextCursor); ok {
218 » » if prevCursor == nil {
219 » » » // Magic string to signal display prev without cursor
220 » » » result.PrevCursor = "EMPTY"
221 » » } else {
222 » » » result.PrevCursor = (*prevCursor).String()
223 » » }
224 » }
225 » if nextCursor != nil {
226 » » result.NextCursor = (*nextCursor).String()
227 » }
228 » // Cursor is not needed for current builds.
229 » currentBuilds, _, err := getBuilds(c, masterName, builderName, false, 0, nil)
175 if err != nil { 230 if err != nil {
176 return nil, err 231 return nil, err
177 } 232 }
178 // currentBuilds is presented in reversed order, so flip it 233 // currentBuilds is presented in reversed order, so flip it
179 for i, j := 0, len(currentBuilds)-1; i < j; i, j = i+1, j-1 { 234 for i, j := 0, len(currentBuilds)-1; i < j; i, j = i+1, j-1 {
180 currentBuilds[i], currentBuilds[j] = currentBuilds[j], currentBu ilds[i] 235 currentBuilds[i], currentBuilds[j] = currentBuilds[j], currentBu ilds[i]
181 } 236 }
182 result.CurrentBuilds = currentBuilds 237 result.CurrentBuilds = currentBuilds
183 238
184 for _, fb := range finishedBuilds { 239 for _, fb := range finishedBuilds {
185 if fb != nil { 240 if fb != nil {
186 result.FinishedBuilds = append(result.FinishedBuilds, fb ) 241 result.FinishedBuilds = append(result.FinishedBuilds, fb )
187 } 242 }
188 } 243 }
189 return result, nil 244 return result, nil
190 } 245 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698