Add ordering options for List* methods (#318)

This commit is contained in:
Zachary Wasserman 2016-10-17 10:01:14 -04:00 committed by GitHub
parent b57514e9d2
commit f9bb9de665
12 changed files with 202 additions and 10 deletions

8
glide.lock generated
View file

@ -1,5 +1,5 @@
hash: 0ed5871e7b062bd45449c789a58a5b8d2df91c275af984b344b109b232ca1bb3
updated: 2016-09-24T21:21:01.057881898-04:00
hash: 18323336ff6ef09b5aa4c35766dea76ad4986f955c3f0b2598772322a8f72f7f
updated: 2016-10-14T15:44:13.911342299-07:00
imports:
- name: github.com/alecthomas/template
version: a0175ee3bccc567396460bf5acd36800cb10c49c
@ -17,8 +17,6 @@ imports:
- spew
- name: github.com/dgrijalva/jwt-go
version: 01aeca54ebda6e0fbfafd0a524d234159c05ec20
subpackages:
- request
- name: github.com/elazarl/go-bindata-assetfs
version: 9a6736ed45b44bf3835afeebb3034b57ed329f3e
- name: github.com/fsnotify/fsnotify
@ -97,6 +95,8 @@ imports:
- pbutil
- name: github.com/mitchellh/mapstructure
version: ca63d7c062ee3c9f34db231e352b60012b4fd0c1
- name: github.com/patrickmn/sortutil
version: abeda66eb583fac2d8d98d3d2e6fbd5c67af7947
- name: github.com/pelletier/go-buffruneio
version: df1e16fde7fc330a0ca68167c23bf7ed6ac31d6d
- name: github.com/pelletier/go-toml

View file

@ -83,3 +83,4 @@ import:
version: ~0.8.0
subpackages:
- prometheus
- package: github.com/patrickmn/sortutil

View file

@ -88,11 +88,23 @@ func openGORM(driver, conn string, maxAttempts int) (*gorm.DB, error) {
// applyLimitOffset applies the appropriate limit and offset parameters to the
// gorm.DB instance, returning a DB that can be chained as usual with *gorm.DB.
func (orm *gormDB) applyListOptions(opt kolide.ListOptions) *gorm.DB {
if opt.PerPage == 0 {
db := orm.DB
if opt.PerPage != 0 {
// PerPage value of 0 indicates unlimited
return orm.DB
offset := opt.Page * opt.PerPage
db = db.Limit(opt.PerPage).Offset(offset)
}
offset := opt.Page * opt.PerPage
return orm.DB.Limit(opt.PerPage).Offset(offset)
if opt.OrderKey != "" {
var dir string
if opt.OrderDirection == kolide.OrderDescending {
dir = "DESC"
} else {
dir = "ASC"
}
db = db.Order(opt.OrderKey + " " + dir)
}
return db
}

View file

@ -1,10 +1,12 @@
package datastore
import (
"errors"
"reflect"
"sync"
"github.com/kolide/kolide-ose/server/kolide"
"github.com/patrickmn/sortutil"
)
type inmem struct {
@ -70,6 +72,21 @@ func (orm *inmem) getLimitOffsetSliceBounds(opt kolide.ListOptions, length int)
return offset, max
}
func sortResults(slice interface{}, opt kolide.ListOptions, fields map[string]string) error {
field, ok := fields[opt.OrderKey]
if !ok {
return errors.New("cannot sort on unknown key: " + opt.OrderKey)
}
if opt.OrderDirection == kolide.OrderDescending {
sortutil.DescByField(slice, field)
} else {
sortutil.AscByField(slice, field)
}
return nil
}
// nextID returns the next ID value that should be used for a struct of the
// given type
func (orm *inmem) nextID(val interface{}) uint {

View file

@ -76,6 +76,28 @@ func (orm *inmem) ListHosts(opt kolide.ListOptions) ([]*kolide.Host, error) {
hosts = append(hosts, orm.hosts[uint(k)])
}
// Apply ordering
if opt.OrderKey != "" {
var fields = map[string]string{
"id": "ID",
"created_at": "CreatedAt",
"updated_at": "UpdatedAt",
"detail_update_time": "DetailUpdateTime",
"hostname": "HostName",
"uuid": "UUID",
"platform": "Platform",
"osquery_version": "OsqueryVersion",
"os_version": "OSVersion",
"uptime": "Uptime",
"memory": "PhysicalMemory",
"mac": "PrimaryMAC",
"ip": "PrimaryIP",
}
if err := sortResults(hosts, opt, fields); err != nil {
return nil, err
}
}
// Apply limit/offset
low, high := orm.getLimitOffsetSliceBounds(opt, len(hosts))
hosts = hosts[low:high]

View file

@ -17,7 +17,7 @@ func (orm *inmem) NewInvite(invite *kolide.Invite) (*kolide.Invite, error) {
}
}
invite.ID = orm.nextID(invite)
invite.ID = uint(len(orm.invites) + 1)
orm.invites[invite.ID] = invite
return invite, nil
}
@ -39,6 +39,23 @@ func (orm *inmem) ListInvites(opt kolide.ListOptions) ([]*kolide.Invite, error)
invites = append(invites, orm.invites[uint(k)])
}
// Apply ordering
if opt.OrderKey != "" {
var fields = map[string]string{
"id": "ID",
"created_at": "CreatedAt",
"updated_at": "UpdatedAt",
"detail_update_time": "DetailUpdateTime",
"email": "Email",
"admin": "Admin",
"name": "Name",
"position": "Position",
}
if err := sortResults(invites, opt, fields); err != nil {
return nil, err
}
}
// Apply limit/offset
low, high := orm.getLimitOffsetSliceBounds(opt, len(invites))
invites = invites[low:high]

View file

@ -76,6 +76,20 @@ func (orm *inmem) ListPacks(opt kolide.ListOptions) ([]*kolide.Pack, error) {
packs = append(packs, orm.packs[uint(k)])
}
// Apply ordering
if opt.OrderKey != "" {
var fields = map[string]string{
"id": "ID",
"created_at": "CreatedAt",
"updated_at": "UpdatedAt",
"name": "Name",
"platform": "Platform",
}
if err := sortResults(packs, opt, fields); err != nil {
return nil, err
}
}
// Apply limit/offset
low, high := orm.getLimitOffsetSliceBounds(opt, len(packs))
packs = packs[low:high]

View file

@ -76,6 +76,25 @@ func (orm *inmem) ListQueries(opt kolide.ListOptions) ([]*kolide.Query, error) {
queries = append(queries, orm.queries[uint(k)])
}
// Apply ordering
if opt.OrderKey != "" {
var fields = map[string]string{
"id": "ID",
"created_at": "CreatedAt",
"updated_at": "UpdatedAt",
"name": "Name",
"query": "Query",
"interval": "Interval",
"snapshot": "Snapshot",
"differential": "Differential",
"platform": "Platform",
"version": "Version",
}
if err := sortResults(queries, opt, fields); err != nil {
return nil, err
}
}
// Apply limit/offset
low, high := orm.getLimitOffsetSliceBounds(opt, len(queries))
queries = queries[low:high]

View file

@ -51,6 +51,24 @@ func (orm *inmem) ListUsers(opt kolide.ListOptions) ([]*kolide.User, error) {
users = append(users, orm.users[uint(k)])
}
// Apply ordering
if opt.OrderKey != "" {
var fields = map[string]string{
"id": "ID",
"created_at": "CreatedAt",
"updated_at": "UpdatedAt",
"username": "Username",
"name": "Name",
"email": "Email",
"admin": "Admin",
"enabled": "Enabled",
"position": "Position",
}
if err := sortResults(users, opt, fields); err != nil {
return nil, err
}
}
// Apply limit/offset
low, high := orm.getLimitOffsetSliceBounds(opt, len(users))
users = users[low:high]

View file

@ -33,6 +33,13 @@ type OrgInfoPayload struct {
OrgLogoURL *string `json:"org_logo_url"`
}
type OrderDirection int
const (
OrderAscending OrderDirection = iota
OrderDescending
)
// ListOptions defines options related to paging and ordering to be used when
// listing objects
type ListOptions struct {
@ -41,4 +48,8 @@ type ListOptions struct {
// How many results per page (must be positive integer, 0 indicates
// unlimited)
PerPage uint
// Key to use for ordering
OrderKey string
// Direction of ordering
OrderDirection OrderDirection
}

View file

@ -62,6 +62,8 @@ func listOptionsFromRequest(r *http.Request) (kolide.ListOptions, error) {
pageString := r.URL.Query().Get("page")
perPageString := r.URL.Query().Get("per_page")
orderKey := r.URL.Query().Get("order_key")
orderDirectionString := r.URL.Query().Get("order_direction")
var page int = 0
if pageString != "" {
@ -94,7 +96,31 @@ func listOptionsFromRequest(r *http.Request) (kolide.ListOptions, error) {
perPage = defaultPerPage
}
return kolide.ListOptions{Page: uint(page), PerPage: uint(perPage)}, nil
if orderKey == "" && orderDirectionString != "" {
return kolide.ListOptions{},
errors.New("order_key must be specified with order_direction")
}
var orderDirection kolide.OrderDirection
switch orderDirectionString {
case "desc":
orderDirection = kolide.OrderDescending
case "asc":
orderDirection = kolide.OrderAscending
case "":
orderDirection = kolide.OrderAscending
default:
return kolide.ListOptions{},
errors.New("unknown order_direction: " + orderDirectionString)
}
return kolide.ListOptions{
Page: uint(page),
PerPage: uint(perPage),
OrderKey: orderKey,
OrderDirection: orderDirection,
}, nil
}
func decodeNoParamsRequest(ctx context.Context, r *http.Request) (interface{}, error) {

View file

@ -40,6 +40,33 @@ func TestListOptionsFromRequest(t *testing.T) {
listOptions: kolide.ListOptions{},
},
// Both order params provided
{
url: "/foo?order_key=foo&order_direction=desc",
listOptions: kolide.ListOptions{OrderKey: "foo", OrderDirection: kolide.OrderDescending},
},
// Both order params provided (asc)
{
url: "/foo?order_key=bar&order_direction=asc",
listOptions: kolide.ListOptions{OrderKey: "bar", OrderDirection: kolide.OrderAscending},
},
// Default order direction
{
url: "/foo?order_key=foo",
listOptions: kolide.ListOptions{OrderKey: "foo", OrderDirection: kolide.OrderAscending},
},
// All params defined
{
url: "/foo?order_key=foo&order_direction=desc&page=1&per_page=100",
listOptions: kolide.ListOptions{
OrderKey: "foo",
OrderDirection: kolide.OrderDescending,
Page: 1,
PerPage: 100,
},
},
// various error cases
{
url: "/foo?page=foo&per_page=10",
@ -57,6 +84,14 @@ func TestListOptionsFromRequest(t *testing.T) {
url: "/foo?page=-1&per_page=-10",
shouldErr: true,
},
{
url: "/foo?page=1&order_direction=desc",
shouldErr: true,
},
{
url: "/foo?&order_direction=foo&order_key=",
shouldErr: true,
},
}
for _, tt := range listOptionsTests {