mirror of
https://github.com/onepanelio/onepanel.git
synced 2025-10-30 00:11:45 +08:00
feat: added support for sorting and filtering workspaces.
* Also added a LabelFilter interface
This commit is contained in:
28
pkg/filter.go
Normal file
28
pkg/filter.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package v1
|
||||
|
||||
import sq "github.com/Masterminds/squirrel"
|
||||
|
||||
// LabelFilter represents a filter that has labels
|
||||
type LabelFilter interface {
|
||||
// GetLabels returns the labels to filter by. These are assumed to be ANDed together.
|
||||
GetLabels() []*Label
|
||||
}
|
||||
|
||||
// ApplyLabelSelectQuery returns a query builder that adds where statements to filter by labels in the filter, if there are any
|
||||
// labelSelector is the database column that has the labels, such as "we.labels" for workflowExecutions aliased by "we".
|
||||
func ApplyLabelSelectQuery(labelSelector string, sb sq.SelectBuilder, filter LabelFilter) (sq.SelectBuilder, error) {
|
||||
labels := filter.GetLabels()
|
||||
|
||||
if len(labels) == 0 {
|
||||
return sb, nil
|
||||
}
|
||||
|
||||
labelsJSON, err := LabelsToJSONString(labels)
|
||||
if err != nil {
|
||||
return sb, err
|
||||
}
|
||||
|
||||
sb = sb.Where("%v @> ?", labelSelector, labelsJSON)
|
||||
|
||||
return sb, nil
|
||||
}
|
||||
@@ -65,24 +65,11 @@ func typeWorkflow(wf *wfv1.Workflow) (workflow *WorkflowExecution) {
|
||||
// WorkflowExecutionFilter represents the available ways we can filter WorkflowExecutions
|
||||
type WorkflowExecutionFilter struct {
|
||||
Labels []*Label
|
||||
Phase string //empty string means none
|
||||
Phase string // empty string means none
|
||||
}
|
||||
|
||||
// applyWorkflowExecutionLabelSelectQuery returns a query builder that adds where statements to filter by labels in the request,
|
||||
// if there are any
|
||||
func applyWorkflowExecutionLabelSelectQuery(sb sq.SelectBuilder, filter *WorkflowExecutionFilter) (sq.SelectBuilder, error) {
|
||||
if len(filter.Labels) == 0 {
|
||||
return sb, nil
|
||||
}
|
||||
|
||||
labelsJSON, err := LabelsToJSONString(filter.Labels)
|
||||
if err != nil {
|
||||
return sb, err
|
||||
}
|
||||
|
||||
sb = sb.Where("we.labels @> ?", labelsJSON)
|
||||
|
||||
return sb, nil
|
||||
func (wf *WorkflowExecutionFilter) GetLabels() []*Label {
|
||||
return wf.Labels
|
||||
}
|
||||
|
||||
func applyWorkflowExecutionFilter(sb sq.SelectBuilder, request *request.Request) (sq.SelectBuilder, error) {
|
||||
@@ -95,7 +82,7 @@ func applyWorkflowExecutionFilter(sb sq.SelectBuilder, request *request.Request)
|
||||
return sb, nil
|
||||
}
|
||||
|
||||
sb, err := applyWorkflowExecutionLabelSelectQuery(sb, &filter)
|
||||
sb, err := ApplyLabelSelectQuery("we.labels", sb, &filter)
|
||||
if err != nil {
|
||||
return sb, err
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
"github.com/lib/pq"
|
||||
"github.com/onepanelio/core/pkg/util"
|
||||
"github.com/onepanelio/core/pkg/util/ptr"
|
||||
"github.com/onepanelio/core/pkg/util/request/pagination"
|
||||
"github.com/onepanelio/core/pkg/util/request"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"google.golang.org/grpc/codes"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
@@ -20,6 +20,35 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// WorkspaceFilter represents the available ways we can filter Workspaces
|
||||
type WorkspaceFilter struct {
|
||||
Labels []*Label
|
||||
Phase string // empty string means none
|
||||
}
|
||||
|
||||
// GetLabels gets the labels of the filter
|
||||
func (wf *WorkspaceFilter) GetLabels() []*Label {
|
||||
return wf.Labels
|
||||
}
|
||||
|
||||
func applyWorkspaceFilter(sb sq.SelectBuilder, request *request.Request) (sq.SelectBuilder, error) {
|
||||
if request.Filter == nil {
|
||||
return sb, nil
|
||||
}
|
||||
|
||||
filter, ok := request.Filter.(WorkspaceFilter)
|
||||
if !ok {
|
||||
return sb, nil
|
||||
}
|
||||
|
||||
sb, err := ApplyLabelSelectQuery("w.labels", sb, &filter)
|
||||
if err != nil {
|
||||
return sb, err
|
||||
}
|
||||
|
||||
return sb, nil
|
||||
}
|
||||
|
||||
func (c *Client) workspacesSelectBuilder(namespace string) sq.SelectBuilder {
|
||||
sb := sb.Select(getWorkspaceColumns("w")...).
|
||||
Columns(getWorkspaceStatusColumns("w", "status")...).
|
||||
@@ -562,13 +591,12 @@ func (c *Client) ListWorkspacesByTemplateID(namespace string, templateID uint64)
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Client) ListWorkspaces(namespace string, paginator *pagination.PaginationRequest) (workspaces []*Workspace, err error) {
|
||||
func (c *Client) ListWorkspaces(namespace string, request *request.Request) (workspaces []*Workspace, err error) {
|
||||
sb := sb.Select(getWorkspaceColumns("w")...).
|
||||
Columns(getWorkspaceStatusColumns("w", "status")...).
|
||||
Columns(getWorkspaceTemplateColumns("wt", "workspace_template")...).
|
||||
From("workspaces w").
|
||||
Join("workspace_templates wt ON wt.id = w.workspace_template_id").
|
||||
OrderBy("w.created_at DESC").
|
||||
Where(sq.And{
|
||||
sq.Eq{
|
||||
"w.namespace": namespace,
|
||||
@@ -577,7 +605,28 @@ func (c *Client) ListWorkspaces(namespace string, paginator *pagination.Paginati
|
||||
"phase": WorkspaceTerminated,
|
||||
},
|
||||
})
|
||||
sb = *paginator.ApplyToSelect(&sb)
|
||||
|
||||
if request.HasSorting() {
|
||||
properties := getWorkspaceColumnsMap(true)
|
||||
for _, order := range request.Sort.Properties {
|
||||
if columnName, ok := properties[order.Property]; ok {
|
||||
nullSort := "NULLS FIRST"
|
||||
if order.Direction == "desc" {
|
||||
nullSort = "NULLS LAST" // default in postgres, but let's be explicit
|
||||
}
|
||||
sb = sb.OrderBy(fmt.Sprintf("w.%v %v %v", columnName, order.Direction, nullSort))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
sb = sb.OrderBy("w.created_at DESC")
|
||||
}
|
||||
|
||||
sb, err = applyWorkspaceFilter(sb, request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sb = *request.Pagination.ApplyToSelect(&sb)
|
||||
|
||||
if err := c.DB.Selectx(&workspaces, sb); err != nil {
|
||||
return nil, err
|
||||
@@ -587,8 +636,8 @@ func (c *Client) ListWorkspaces(namespace string, paginator *pagination.Paginati
|
||||
}
|
||||
|
||||
// CountWorkspaces returns the total number of workspaces in the given namespace that are not terminated
|
||||
func (c *Client) CountWorkspaces(namespace string) (count int, err error) {
|
||||
err = sb.Select("COUNT( DISTINCT( w.id ))").
|
||||
func (c *Client) CountWorkspaces(namespace string, request *request.Request) (count int, err error) {
|
||||
query := sb.Select("COUNT( DISTINCT( w.id ))").
|
||||
From("workspaces w").
|
||||
Join("workspace_templates wt ON w.workspace_template_id = wt.id").
|
||||
Where(sq.And{
|
||||
@@ -598,8 +647,14 @@ func (c *Client) CountWorkspaces(namespace string) (count int, err error) {
|
||||
sq.NotEq{
|
||||
"phase": WorkspaceTerminated,
|
||||
},
|
||||
}).
|
||||
RunWith(c.DB).
|
||||
})
|
||||
|
||||
query, err = applyWorkspaceFilter(query, request)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
err = query.RunWith(c.DB).
|
||||
QueryRow().
|
||||
Scan(&count)
|
||||
|
||||
|
||||
@@ -122,3 +122,27 @@ func WorkspacesToIDs(resources []*Workspace) (ids []uint64) {
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// getWorkspaceColumnsMap returns a map where the keys are the columns of the workspaces table
|
||||
// the value is the raw column name as it is in the database
|
||||
func getWorkspaceColumnsMap(camelCase bool) map[string]string {
|
||||
result := map[string]string{
|
||||
"id": "id",
|
||||
"labels": "labels",
|
||||
"name": "name",
|
||||
"uid": "uid",
|
||||
"namespace": "namespace",
|
||||
"phase": "phase",
|
||||
"parameters": "parameters",
|
||||
}
|
||||
|
||||
if camelCase {
|
||||
result["createdAt"] = "created_at"
|
||||
result["modifiedAt"] = "modified_at"
|
||||
} else {
|
||||
result["created_at"] = "created_at"
|
||||
result["modified_at"] = "modified_at"
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -7,7 +7,9 @@ import (
|
||||
v1 "github.com/onepanelio/core/pkg"
|
||||
"github.com/onepanelio/core/pkg/util"
|
||||
"github.com/onepanelio/core/pkg/util/ptr"
|
||||
"github.com/onepanelio/core/pkg/util/request"
|
||||
"github.com/onepanelio/core/pkg/util/request/pagination"
|
||||
requestSort "github.com/onepanelio/core/pkg/util/request/sort"
|
||||
"github.com/onepanelio/core/server/auth"
|
||||
"github.com/onepanelio/core/server/converter"
|
||||
log "github.com/sirupsen/logrus"
|
||||
@@ -213,8 +215,24 @@ func (s *WorkspaceServer) ListWorkspaces(ctx context.Context, req *api.ListWorks
|
||||
return nil, err
|
||||
}
|
||||
|
||||
paginator := pagination.NewRequest(req.Page, req.PageSize)
|
||||
workspaces, err := client.ListWorkspaces(req.Namespace, &paginator)
|
||||
labelFilter, err := v1.LabelsFromString(req.Labels)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
reqSort, err := requestSort.New(req.Order)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resourceRequest := &request.Request{
|
||||
Pagination: pagination.New(req.Page, req.PageSize),
|
||||
Filter: v1.WorkspaceFilter{
|
||||
Labels: labelFilter,
|
||||
},
|
||||
Sort: reqSort,
|
||||
}
|
||||
|
||||
workspaces, err := client.ListWorkspaces(req.Namespace, resourceRequest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -228,11 +246,12 @@ func (s *WorkspaceServer) ListWorkspaces(ctx context.Context, req *api.ListWorks
|
||||
apiWorkspaces = append(apiWorkspaces, apiWorkspace(w, sysConfig))
|
||||
}
|
||||
|
||||
count, err := client.CountWorkspaces(req.Namespace)
|
||||
count, err := client.CountWorkspaces(req.Namespace, resourceRequest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
paginator := resourceRequest.Pagination
|
||||
return &api.ListWorkspaceResponse{
|
||||
Count: int32(len(apiWorkspaces)),
|
||||
Workspaces: apiWorkspaces,
|
||||
|
||||
Reference in New Issue
Block a user