mirror of
https://github.com/fleetdm/fleet
synced 2026-05-23 00:49:03 +00:00
Add ordering options for List* methods (#318)
This commit is contained in:
parent
b57514e9d2
commit
f9bb9de665
12 changed files with 202 additions and 10 deletions
8
glide.lock
generated
8
glide.lock
generated
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -83,3 +83,4 @@ import:
|
|||
version: ~0.8.0
|
||||
subpackages:
|
||||
- prometheus
|
||||
- package: github.com/patrickmn/sortutil
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Reference in a new issue