mirror of
https://github.com/goharbor/harbor.git
synced 2025-01-14 11:41:31 +01:00
Support specifying multiple fields for default sorting (#14788)
Support specifying multiple fields for default sorting Fixes #14433 Signed-off-by: Wenkai Yin <yinw@vmware.com>
This commit is contained in:
parent
3322716bc6
commit
e006f4bab5
@ -20,6 +20,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/astaxie/beego/orm"
|
"github.com/astaxie/beego/orm"
|
||||||
|
"github.com/goharbor/harbor/src/lib/q"
|
||||||
"github.com/lib/pq"
|
"github.com/lib/pq"
|
||||||
"github.com/theupdateframework/notary/tuf/data"
|
"github.com/theupdateframework/notary/tuf/data"
|
||||||
)
|
)
|
||||||
@ -37,7 +38,7 @@ type RepoRecord struct {
|
|||||||
Description string `orm:"column(description)" json:"description"`
|
Description string `orm:"column(description)" json:"description"`
|
||||||
PullCount int64 `orm:"column(pull_count)" json:"pull_count"`
|
PullCount int64 `orm:"column(pull_count)" json:"pull_count"`
|
||||||
StarCount int64 `orm:"column(star_count)" json:"star_count"`
|
StarCount int64 `orm:"column(star_count)" json:"star_count"`
|
||||||
CreationTime time.Time `orm:"column(creation_time);auto_now_add" json:"creation_time" sort:"default:desc"`
|
CreationTime time.Time `orm:"column(creation_time);auto_now_add" json:"creation_time"`
|
||||||
UpdateTime time.Time `orm:"column(update_time);auto_now" json:"update_time"`
|
UpdateTime time.Time `orm:"column(update_time);auto_now" json:"update_time"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -61,6 +62,20 @@ func (r *RepoRecord) TableName() string {
|
|||||||
return RepoTable
|
return RepoTable
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetDefaultSorts specifies the default sorts
|
||||||
|
func (r *RepoRecord) GetDefaultSorts() []*q.Sort {
|
||||||
|
return []*q.Sort{
|
||||||
|
{
|
||||||
|
Key: "CreationTime",
|
||||||
|
DESC: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: "RepositoryID",
|
||||||
|
DESC: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// RepositoryQuery : query parameters for repository
|
// RepositoryQuery : query parameters for repository
|
||||||
type RepositoryQuery struct {
|
type RepositoryQuery struct {
|
||||||
Name string
|
Name string
|
||||||
|
@ -38,8 +38,8 @@ type key struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type metadata struct {
|
type metadata struct {
|
||||||
Keys map[string]*key
|
Keys map[string]*key
|
||||||
DefaultSort *q.Sort
|
DefaultSorts []*q.Sort
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *metadata) Filterable(key string) (*key, bool) {
|
func (m *metadata) Filterable(key string) (*key, bool) {
|
||||||
@ -98,11 +98,11 @@ func parseModel(model interface{}) *metadata {
|
|||||||
Sortable: sortable,
|
Sortable: sortable,
|
||||||
}
|
}
|
||||||
if defaultSort != nil {
|
if defaultSort != nil {
|
||||||
metadata.DefaultSort = defaultSort
|
metadata.DefaultSorts = []*q.Sort{defaultSort}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse methods of the provided model
|
// parse filter methods of the provided model
|
||||||
for i := 0; i < ptr.NumMethod(); i++ {
|
for i := 0; i < ptr.NumMethod(); i++ {
|
||||||
methodName := ptr.Method(i).Name
|
methodName := ptr.Method(i).Name
|
||||||
if !strings.HasPrefix(methodName, "FilterBy") {
|
if !strings.HasPrefix(methodName, "FilterBy") {
|
||||||
@ -129,6 +129,18 @@ func parseModel(model interface{}) *metadata {
|
|||||||
FilterFunc: filterFunc,
|
FilterFunc: filterFunc,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// parse default sorts method
|
||||||
|
methodValue := v.MethodByName("GetDefaultSorts")
|
||||||
|
if methodValue.IsValid() {
|
||||||
|
values := methodValue.Call(nil)
|
||||||
|
if len(values) == 1 {
|
||||||
|
if sorts, ok := values[0].Interface().([]*q.Sort); ok && len(sorts) > 0 {
|
||||||
|
metadata.DefaultSorts = sorts
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
cache.Store(fullName, metadata)
|
cache.Store(fullName, metadata)
|
||||||
return metadata
|
return metadata
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/astaxie/beego/orm"
|
"github.com/astaxie/beego/orm"
|
||||||
|
"github.com/goharbor/harbor/src/lib/q"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
@ -36,6 +37,24 @@ func (f *foo) FilterByField5(context.Context, orm.QuerySeter, string, interface{
|
|||||||
|
|
||||||
func (f *foo) OtherFunc() {}
|
func (f *foo) OtherFunc() {}
|
||||||
|
|
||||||
|
type bar struct {
|
||||||
|
Field1 string
|
||||||
|
Field2 string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *bar) GetDefaultSorts() []*q.Sort {
|
||||||
|
return []*q.Sort{
|
||||||
|
{
|
||||||
|
Key: "Field1",
|
||||||
|
DESC: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: "Field2",
|
||||||
|
DESC: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestParseQueryObject(t *testing.T) {
|
func TestParseQueryObject(t *testing.T) {
|
||||||
require := require.New(t)
|
require := require.New(t)
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
@ -91,9 +110,18 @@ func TestParseQueryObject(t *testing.T) {
|
|||||||
assert.True(key.Filterable)
|
assert.True(key.Filterable)
|
||||||
assert.False(key.Sortable)
|
assert.False(key.Sortable)
|
||||||
|
|
||||||
require.NotNil(metadata.DefaultSort)
|
require.Len(metadata.DefaultSorts, 1)
|
||||||
assert.Equal("Field4", metadata.DefaultSort.Key)
|
assert.Equal("Field4", metadata.DefaultSorts[0].Key)
|
||||||
assert.True(metadata.DefaultSort.DESC)
|
assert.True(metadata.DefaultSorts[0].DESC)
|
||||||
|
|
||||||
|
metadata = parseModel(&bar{})
|
||||||
|
require.NotNil(metadata)
|
||||||
|
require.Len(metadata.Keys, 4)
|
||||||
|
require.Len(metadata.DefaultSorts, 2)
|
||||||
|
assert.Equal("Field1", metadata.DefaultSorts[0].Key)
|
||||||
|
assert.True(metadata.DefaultSorts[0].DESC)
|
||||||
|
assert.Equal("Field2", metadata.DefaultSorts[1].Key)
|
||||||
|
assert.False(metadata.DefaultSorts[1].DESC)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_snakeCase(t *testing.T) {
|
func Test_snakeCase(t *testing.T) {
|
||||||
|
@ -38,6 +38,25 @@ import (
|
|||||||
// ...
|
// ...
|
||||||
// return qs
|
// return qs
|
||||||
// }
|
// }
|
||||||
|
//
|
||||||
|
// Defining the method "GetDefaultSorts() []*q.Sort" for the model whose default sorting contains more than one fields
|
||||||
|
// type Bar struct{
|
||||||
|
// Field1 string
|
||||||
|
// Field2 string
|
||||||
|
// }
|
||||||
|
// // Sort by "Field1" desc, "Field2"
|
||||||
|
// func (b *Bar) GetDefaultSorts() []*q.Sort {
|
||||||
|
// return []*q.Sort{
|
||||||
|
// {
|
||||||
|
// Key: "Field1",
|
||||||
|
// DESC: true,
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// Key: "Field2",
|
||||||
|
// DESC: false,
|
||||||
|
// },
|
||||||
|
// }
|
||||||
|
// }
|
||||||
func QuerySetter(ctx context.Context, model interface{}, query *q.Query) (orm.QuerySeter, error) {
|
func QuerySetter(ctx context.Context, model interface{}, query *q.Query) (orm.QuerySeter, error) {
|
||||||
t := reflect.TypeOf(model)
|
t := reflect.TypeOf(model)
|
||||||
if t.Kind() != reflect.Ptr {
|
if t.Kind() != reflect.Ptr {
|
||||||
@ -167,12 +186,14 @@ func setSorts(qs orm.QuerySeter, query *q.Query, meta *metadata) orm.QuerySeter
|
|||||||
sortings = append(sortings, sorting)
|
sortings = append(sortings, sorting)
|
||||||
}
|
}
|
||||||
// if no sorts are specified, apply the default sort setting if exists
|
// if no sorts are specified, apply the default sort setting if exists
|
||||||
if len(sortings) == 0 && meta.DefaultSort != nil {
|
if len(sortings) == 0 {
|
||||||
sorting := meta.DefaultSort.Key
|
for _, ds := range meta.DefaultSorts {
|
||||||
if meta.DefaultSort.DESC {
|
sorting := ds.Key
|
||||||
sorting = fmt.Sprintf("-%s", sorting)
|
if ds.DESC {
|
||||||
|
sorting = fmt.Sprintf("-%s", sorting)
|
||||||
|
}
|
||||||
|
sortings = append(sortings, sorting)
|
||||||
}
|
}
|
||||||
sortings = append(sortings, sorting)
|
|
||||||
}
|
}
|
||||||
if len(sortings) > 0 {
|
if len(sortings) > 0 {
|
||||||
qs = qs.OrderBy(sortings...)
|
qs = qs.OrderBy(sortings...)
|
||||||
|
@ -18,6 +18,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/astaxie/beego/orm"
|
"github.com/astaxie/beego/orm"
|
||||||
|
"github.com/goharbor/harbor/src/lib/q"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@ -37,7 +38,7 @@ type Artifact struct {
|
|||||||
Digest string `orm:"column(digest)"`
|
Digest string `orm:"column(digest)"`
|
||||||
Size int64 `orm:"column(size)"`
|
Size int64 `orm:"column(size)"`
|
||||||
Icon string `orm:"column(icon)"`
|
Icon string `orm:"column(icon)"`
|
||||||
PushTime time.Time `orm:"column(push_time)" sort:"default:desc"`
|
PushTime time.Time `orm:"column(push_time)"`
|
||||||
PullTime time.Time `orm:"column(pull_time)"`
|
PullTime time.Time `orm:"column(pull_time)"`
|
||||||
ExtraAttrs string `orm:"column(extra_attrs)"` // json string
|
ExtraAttrs string `orm:"column(extra_attrs)"` // json string
|
||||||
Annotations string `orm:"column(annotations);type(jsonb)"` // json string
|
Annotations string `orm:"column(annotations);type(jsonb)"` // json string
|
||||||
@ -48,6 +49,20 @@ func (a *Artifact) TableName() string {
|
|||||||
return "artifact"
|
return "artifact"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetDefaultSorts specifies the default sorts
|
||||||
|
func (a *Artifact) GetDefaultSorts() []*q.Sort {
|
||||||
|
return []*q.Sort{
|
||||||
|
{
|
||||||
|
Key: "PushTime",
|
||||||
|
DESC: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: "ID",
|
||||||
|
DESC: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ArtifactReference records the child artifact referenced by parent artifact
|
// ArtifactReference records the child artifact referenced by parent artifact
|
||||||
type ArtifactReference struct {
|
type ArtifactReference struct {
|
||||||
ID int64 `orm:"pk;auto;column(id)"`
|
ID int64 `orm:"pk;auto;column(id)"`
|
||||||
|
@ -23,6 +23,7 @@ import (
|
|||||||
beego_orm "github.com/astaxie/beego/orm"
|
beego_orm "github.com/astaxie/beego/orm"
|
||||||
"github.com/astaxie/beego/validation"
|
"github.com/astaxie/beego/validation"
|
||||||
"github.com/goharbor/harbor/src/lib/errors"
|
"github.com/goharbor/harbor/src/lib/errors"
|
||||||
|
"github.com/goharbor/harbor/src/lib/q"
|
||||||
"github.com/robfig/cron"
|
"github.com/robfig/cron"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -73,7 +74,7 @@ type Schema struct {
|
|||||||
TriggerStr string `orm:"column(trigger)" json:"-"`
|
TriggerStr string `orm:"column(trigger)" json:"-"`
|
||||||
Enabled bool `orm:"column(enabled)" json:"enabled"`
|
Enabled bool `orm:"column(enabled)" json:"enabled"`
|
||||||
CreatedAt time.Time `orm:"column(creation_time)" json:"creation_time"`
|
CreatedAt time.Time `orm:"column(creation_time)" json:"creation_time"`
|
||||||
UpdatedTime time.Time `orm:"column(update_time)" json:"update_time" sort:"default"`
|
UpdatedTime time.Time `orm:"column(update_time)" json:"update_time"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// TableName specifies the policy schema table name.
|
// TableName specifies the policy schema table name.
|
||||||
@ -81,6 +82,19 @@ func (s *Schema) TableName() string {
|
|||||||
return "p2p_preheat_policy"
|
return "p2p_preheat_policy"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetDefaultSorts specifies the default sorts
|
||||||
|
func (s *Schema) GetDefaultSorts() []*q.Sort {
|
||||||
|
return []*q.Sort{
|
||||||
|
{
|
||||||
|
Key: "UpdatedTime",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: "ID",
|
||||||
|
DESC: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// FilterType represents the type info of the filter.
|
// FilterType represents the type info of the filter.
|
||||||
type FilterType = string
|
type FilterType = string
|
||||||
|
|
||||||
|
@ -16,6 +16,8 @@ package tag
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/goharbor/harbor/src/lib/q"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Tag model in database
|
// Tag model in database
|
||||||
@ -24,6 +26,20 @@ type Tag struct {
|
|||||||
RepositoryID int64 `orm:"column(repository_id)" json:"repository_id"` // tags are the resources of repository, one repository only contains one same name tag
|
RepositoryID int64 `orm:"column(repository_id)" json:"repository_id"` // tags are the resources of repository, one repository only contains one same name tag
|
||||||
ArtifactID int64 `orm:"column(artifact_id)" json:"artifact_id"` // the artifact ID that the tag attaches to, it changes when pushing a same name but different digest artifact
|
ArtifactID int64 `orm:"column(artifact_id)" json:"artifact_id"` // the artifact ID that the tag attaches to, it changes when pushing a same name but different digest artifact
|
||||||
Name string `orm:"column(name)" json:"name"`
|
Name string `orm:"column(name)" json:"name"`
|
||||||
PushTime time.Time `orm:"column(push_time)" json:"push_time" sort:"default:desc"`
|
PushTime time.Time `orm:"column(push_time)" json:"push_time"`
|
||||||
PullTime time.Time `orm:"column(pull_time)" json:"pull_time"`
|
PullTime time.Time `orm:"column(pull_time)" json:"pull_time"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetDefaultSorts specifies the default sorts
|
||||||
|
func (t *Tag) GetDefaultSorts() []*q.Sort {
|
||||||
|
return []*q.Sort{
|
||||||
|
{
|
||||||
|
Key: "PushTime",
|
||||||
|
DESC: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: "ID",
|
||||||
|
DESC: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -18,6 +18,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/astaxie/beego/orm"
|
"github.com/astaxie/beego/orm"
|
||||||
|
"github.com/goharbor/harbor/src/lib/q"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@ -67,11 +68,26 @@ type Task struct {
|
|||||||
RunCount int32 `orm:"column(run_count)"`
|
RunCount int32 `orm:"column(run_count)"`
|
||||||
ExtraAttrs string `orm:"column(extra_attrs)"` // json string
|
ExtraAttrs string `orm:"column(extra_attrs)"` // json string
|
||||||
CreationTime time.Time `orm:"column(creation_time)"`
|
CreationTime time.Time `orm:"column(creation_time)"`
|
||||||
StartTime time.Time `orm:"column(start_time)" sort:"default:desc"`
|
StartTime time.Time `orm:"column(start_time)"`
|
||||||
UpdateTime time.Time `orm:"column(update_time)"`
|
UpdateTime time.Time `orm:"column(update_time)"`
|
||||||
EndTime time.Time `orm:"column(end_time)"`
|
EndTime time.Time `orm:"column(end_time)"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetDefaultSorts specifies the default sorts
|
||||||
|
func (t *Task) GetDefaultSorts() []*q.Sort {
|
||||||
|
// sort by ID to fix https://github.com/goharbor/harbor/issues/14433
|
||||||
|
return []*q.Sort{
|
||||||
|
{
|
||||||
|
Key: "StartTime",
|
||||||
|
DESC: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: "ID",
|
||||||
|
DESC: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// StatusCount model
|
// StatusCount model
|
||||||
type StatusCount struct {
|
type StatusCount struct {
|
||||||
Status string `orm:"column(status)"`
|
Status string `orm:"column(status)"`
|
||||||
|
Loading…
Reference in New Issue
Block a user