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

Unified Diff: milo/appengine/buildbot/builder.go

Issue 2810113002: Milo buildbot builder page: Add pagnation with cursors. (Closed)
Patch Set: train 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 side-by-side diff with in-line comments
Download patch
« no previous file with comments | « milo/api/resp/builder.go ('k') | milo/appengine/buildbot/console.go » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: milo/appengine/buildbot/builder.go
diff --git a/milo/appengine/buildbot/builder.go b/milo/appengine/buildbot/builder.go
index 8bb9be08e902d1099f4d44a3509e81e135302115..b89682a45e450c66ea6f93037d5009f836b58b4e 100644
--- a/milo/appengine/buildbot/builder.go
+++ b/milo/appengine/buildbot/builder.go
@@ -5,6 +5,8 @@
package buildbot
import (
+ "crypto/sha1"
+ "encoding/base64"
"errors"
"fmt"
"sort"
@@ -12,6 +14,7 @@ import (
"time"
"github.com/luci/gae/service/datastore"
+ "github.com/luci/gae/service/memcache"
"github.com/luci/luci-go/common/clock"
"github.com/luci/luci-go/common/logging"
@@ -85,8 +88,8 @@ func getBuildSummary(b *buildbotBuild) *resp.BuildSummary {
// getBuilds fetches all of the recent builds from the . Note that
// getBuilds() does not perform ACL checks.
func getBuilds(
- c context.Context, masterName, builderName string, finished bool, limit int) (
- []*resp.BuildSummary, error) {
+ c context.Context, masterName, builderName string, finished bool, limit int, cursor *datastore.Cursor) (
+ []*resp.BuildSummary, *datastore.Cursor, error) {
// TODO(hinoka): Builder specific structs.
result := []*resp.BuildSummary{}
@@ -94,19 +97,55 @@ func getBuilds(
q = q.Eq("finished", finished)
q = q.Eq("master", masterName)
q = q.Eq("builder", builderName)
- if limit != 0 {
- q = q.Limit(int32(limit))
- }
q = q.Order("-number")
- buildbots := []*buildbotBuild{}
- err := getBuildQueryBatcher(c).GetAll(c, q, &buildbots)
+ if cursor != nil {
+ q = q.Start(*cursor)
+ }
+ buildbots, nextCursor, err := runBuildsQuery(c, q, int32(limit))
if err != nil {
- return nil, err
+ return nil, nil, err
}
for _, b := range buildbots {
result = append(result, getBuildSummary(b))
}
- return result, nil
+ return result, nextCursor, nil
+}
+
+// maybeSetGetCursor is a cheesy way to implement bidirectional paging with forward-only
+// datastore cursor by creating a mapping of nextCursor -> thisCursor
+// in memcache. maybeSetGetCursor stores the future mapping, then returns prevCursor
+// in the mapping for thisCursor -> prevCursor, if available.
+func maybeSetGetCursor(c context.Context, thisCursor, nextCursor *datastore.Cursor, limit int) (*datastore.Cursor, bool) {
+ key := func(c datastore.Cursor) string {
+ // Memcache key limit is 250 bytes, hash our cursor to get under this limit.
+ blob := sha1.Sum([]byte(c.String()))
+ return fmt.Sprintf("v2:cursors:buildbot_builders:%d:%s", limit, base64.StdEncoding.EncodeToString(blob[:]))
+ }
+ // Set the next cursor to this cursor mapping, if available.
+ if nextCursor != nil {
+ item := memcache.NewItem(c, key(*nextCursor))
+ if thisCursor == nil {
+ // Make sure we know it exists, just empty
+ item.SetValue([]byte{})
+ } else {
+ item.SetValue([]byte((*thisCursor).String()))
+ }
+ item.SetExpiration(24 * time.Hour)
+ memcache.Set(c, item)
+ }
+ // Try to get the last cursor, if valid and available.
+ if thisCursor == nil {
+ return nil, false
+ }
+ if item, err := memcache.GetKey(c, key(*thisCursor)); err == nil {
+ if len(item.Value()) == 0 {
+ return nil, true
+ }
+ if prevCursor, err := datastore.DecodeCursor(c, string(item.Value())); err == nil {
+ return &prevCursor, true
+ }
+ }
+ return nil, false
}
var errMasterNotFound = errors.New(
@@ -117,7 +156,19 @@ var errNotAuth = errors.New("You are not authenticated, try logging in")
// This gets:
// * Current Builds from querying the master json from the datastore.
// * Recent Builds from a cron job that backfills the recent builds.
-func builderImpl(c context.Context, masterName, builderName string, limit int) (*resp.Builder, error) {
+func builderImpl(
+ c context.Context, masterName, builderName string, limit int, cursor string) (
+ *resp.Builder, error) {
+
+ var thisCursor *datastore.Cursor
+ if cursor != "" {
+ tmpCur, err := datastore.DecodeCursor(c, cursor)
+ if err != nil {
+ return nil, fmt.Errorf("bad cursor: %s", err)
+ }
+ thisCursor = &tmpCur
+ }
+
result := &resp.Builder{
Name: builderName,
}
@@ -167,11 +218,23 @@ func builderImpl(c context.Context, masterName, builderName string, limit int) (
}
// This is CPU bound anyways, so there's no need to do this in parallel.
- finishedBuilds, err := getBuilds(c, masterName, builderName, true, limit)
+ finishedBuilds, nextCursor, err := getBuilds(c, masterName, builderName, true, limit, thisCursor)
if err != nil {
return nil, err
}
- currentBuilds, err := getBuilds(c, masterName, builderName, false, 0)
+ if prevCursor, ok := maybeSetGetCursor(c, thisCursor, nextCursor, limit); ok {
+ if prevCursor == nil {
+ // Magic string to signal display prev without cursor
+ result.PrevCursor = "EMPTY"
+ } else {
+ result.PrevCursor = (*prevCursor).String()
+ }
+ }
+ if nextCursor != nil {
+ result.NextCursor = (*nextCursor).String()
+ }
+ // Cursor is not needed for current builds.
+ currentBuilds, _, err := getBuilds(c, masterName, builderName, false, 0, nil)
if err != nil {
return nil, err
}
« no previous file with comments | « milo/api/resp/builder.go ('k') | milo/appengine/buildbot/console.go » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698