diff --git a/src/common/models/repo.go b/src/common/models/repo.go index adf18924b..efcabecaf 100644 --- a/src/common/models/repo.go +++ b/src/common/models/repo.go @@ -20,6 +20,7 @@ import ( "time" "github.com/astaxie/beego/orm" + "github.com/goharbor/harbor/src/lib/q" "github.com/lib/pq" "github.com/theupdateframework/notary/tuf/data" ) @@ -37,7 +38,7 @@ type RepoRecord struct { Description string `orm:"column(description)" json:"description"` PullCount int64 `orm:"column(pull_count)" json:"pull_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"` } @@ -61,6 +62,20 @@ func (r *RepoRecord) TableName() string { 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 type RepositoryQuery struct { Name string diff --git a/src/lib/orm/metadata.go b/src/lib/orm/metadata.go index 412cfed21..9272cba0e 100644 --- a/src/lib/orm/metadata.go +++ b/src/lib/orm/metadata.go @@ -38,8 +38,8 @@ type key struct { } type metadata struct { - Keys map[string]*key - DefaultSort *q.Sort + Keys map[string]*key + DefaultSorts []*q.Sort } func (m *metadata) Filterable(key string) (*key, bool) { @@ -98,11 +98,11 @@ func parseModel(model interface{}) *metadata { Sortable: sortable, } 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++ { methodName := ptr.Method(i).Name if !strings.HasPrefix(methodName, "FilterBy") { @@ -129,6 +129,18 @@ func parseModel(model interface{}) *metadata { 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) return metadata } diff --git a/src/lib/orm/metadata_test.go b/src/lib/orm/metadata_test.go index b97b9315c..ba09169dc 100644 --- a/src/lib/orm/metadata_test.go +++ b/src/lib/orm/metadata_test.go @@ -19,6 +19,7 @@ import ( "testing" "github.com/astaxie/beego/orm" + "github.com/goharbor/harbor/src/lib/q" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -36,6 +37,24 @@ func (f *foo) FilterByField5(context.Context, orm.QuerySeter, string, interface{ 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) { require := require.New(t) assert := assert.New(t) @@ -91,9 +110,18 @@ func TestParseQueryObject(t *testing.T) { assert.True(key.Filterable) assert.False(key.Sortable) - require.NotNil(metadata.DefaultSort) - assert.Equal("Field4", metadata.DefaultSort.Key) - assert.True(metadata.DefaultSort.DESC) + require.Len(metadata.DefaultSorts, 1) + assert.Equal("Field4", metadata.DefaultSorts[0].Key) + 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) { diff --git a/src/lib/orm/query.go b/src/lib/orm/query.go index a5ed30c2b..9fb4a031f 100644 --- a/src/lib/orm/query.go +++ b/src/lib/orm/query.go @@ -38,6 +38,25 @@ import ( // ... // 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) { t := reflect.TypeOf(model) 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) } // if no sorts are specified, apply the default sort setting if exists - if len(sortings) == 0 && meta.DefaultSort != nil { - sorting := meta.DefaultSort.Key - if meta.DefaultSort.DESC { - sorting = fmt.Sprintf("-%s", sorting) + if len(sortings) == 0 { + for _, ds := range meta.DefaultSorts { + sorting := ds.Key + if ds.DESC { + sorting = fmt.Sprintf("-%s", sorting) + } + sortings = append(sortings, sorting) } - sortings = append(sortings, sorting) } if len(sortings) > 0 { qs = qs.OrderBy(sortings...) diff --git a/src/pkg/artifact/dao/model.go b/src/pkg/artifact/dao/model.go index 8f3694f54..d83da8bd8 100644 --- a/src/pkg/artifact/dao/model.go +++ b/src/pkg/artifact/dao/model.go @@ -18,6 +18,7 @@ import ( "time" "github.com/astaxie/beego/orm" + "github.com/goharbor/harbor/src/lib/q" ) func init() { @@ -37,7 +38,7 @@ type Artifact struct { Digest string `orm:"column(digest)"` Size int64 `orm:"column(size)"` 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)"` ExtraAttrs string `orm:"column(extra_attrs)"` // json string Annotations string `orm:"column(annotations);type(jsonb)"` // json string @@ -48,6 +49,20 @@ func (a *Artifact) TableName() string { 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 type ArtifactReference struct { ID int64 `orm:"pk;auto;column(id)"` diff --git a/src/pkg/p2p/preheat/models/policy/policy.go b/src/pkg/p2p/preheat/models/policy/policy.go index 4931d5ab1..483aab9e0 100644 --- a/src/pkg/p2p/preheat/models/policy/policy.go +++ b/src/pkg/p2p/preheat/models/policy/policy.go @@ -23,6 +23,7 @@ import ( beego_orm "github.com/astaxie/beego/orm" "github.com/astaxie/beego/validation" "github.com/goharbor/harbor/src/lib/errors" + "github.com/goharbor/harbor/src/lib/q" "github.com/robfig/cron" ) @@ -73,7 +74,7 @@ type Schema struct { TriggerStr string `orm:"column(trigger)" json:"-"` Enabled bool `orm:"column(enabled)" json:"enabled"` 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. @@ -81,6 +82,19 @@ func (s *Schema) TableName() string { 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. type FilterType = string diff --git a/src/pkg/tag/model/tag/model.go b/src/pkg/tag/model/tag/model.go index 1982786d1..e58134152 100644 --- a/src/pkg/tag/model/tag/model.go +++ b/src/pkg/tag/model/tag/model.go @@ -16,6 +16,8 @@ package tag import ( "time" + + "github.com/goharbor/harbor/src/lib/q" ) // 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 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"` - 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"` } + +// GetDefaultSorts specifies the default sorts +func (t *Tag) GetDefaultSorts() []*q.Sort { + return []*q.Sort{ + { + Key: "PushTime", + DESC: true, + }, + { + Key: "ID", + DESC: true, + }, + } +} diff --git a/src/pkg/task/dao/model.go b/src/pkg/task/dao/model.go index 12ecd4f51..49eab52f7 100644 --- a/src/pkg/task/dao/model.go +++ b/src/pkg/task/dao/model.go @@ -18,6 +18,7 @@ import ( "time" "github.com/astaxie/beego/orm" + "github.com/goharbor/harbor/src/lib/q" ) func init() { @@ -67,11 +68,26 @@ type Task struct { RunCount int32 `orm:"column(run_count)"` ExtraAttrs string `orm:"column(extra_attrs)"` // json string 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)"` 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 type StatusCount struct { Status string `orm:"column(status)"`