diff --git a/api/openapi-spec/swagger.json b/api/openapi-spec/swagger.json index b64039b2f8d8..d24eb88ef6ed 100644 --- a/api/openapi-spec/swagger.json +++ b/api/openapi-spec/swagger.json @@ -78,6 +78,11 @@ "description": "The continue option should be set when retrieving more results from the server. Since this value is\nserver defined, clients may only use the continue value from a previous query result with identical\nquery parameters (except for the value of continue) and the server may reject a continue value it\ndoes not recognize. If the specified continue value is no longer valid whether due to expiration\n(generally five to fifteen minutes) or a configuration change on the server, the server will\nrespond with a 410 ResourceExpired error together with a continue token. If the client needs a\nconsistent list, it must restart their list without the continue field. Otherwise, the client may\nsend another list request with the token received with the 410 error, the server will respond with\na list starting from the next key, but from the latest snapshot, which is inconsistent from the\nprevious list results - objects that are created, modified, or deleted after the first list request\nwill be included in the response, as long as their keys are after the \"next key\".\n\nThis field is not supported when watch is true. Clients may start a watch from the last\nresourceVersion value returned by the server and not miss any modifications.", "name": "listOptions.continue", "in": "query" + }, + { + "type": "string", + "name": "namePrefix", + "in": "query" } ], "responses": { diff --git a/persist/sqldb/migrate.go b/persist/sqldb/migrate.go index 8c166dfe9bd1..b4c4eb1bab8d 100644 --- a/persist/sqldb/migrate.go +++ b/persist/sqldb/migrate.go @@ -251,6 +251,8 @@ func (m migrate) Exec(ctx context.Context) (err error) { ansiSQLChange(`create index ` + m.tableName + `_i1 on ` + m.tableName + ` (clustername,namespace,updatedat)`), // index to find records that need deleting, this omits namespaces as this might be null ansiSQLChange(`create index argo_archived_workflows_i2 on argo_archived_workflows (clustername,instanceid,finishedat)`), + // add argo_archived_workflows name index for prefix searching performance + ansiSQLChange(`create index argo_archived_workflows_i3 on argo_archived_workflows (clustername,instanceid,name)`), } { err := m.applyChange(ctx, changeSchemaVersion, change) if err != nil { diff --git a/persist/sqldb/mocks/WorkflowArchive.go b/persist/sqldb/mocks/WorkflowArchive.go index 5336fd4d2c5d..2d77c8335861 100644 --- a/persist/sqldb/mocks/WorkflowArchive.go +++ b/persist/sqldb/mocks/WorkflowArchive.go @@ -95,13 +95,13 @@ func (_m *WorkflowArchive) IsEnabled() bool { return r0 } -// ListWorkflows provides a mock function with given fields: namespace, name, minStartAt, maxStartAt, labelRequirements, limit, offset -func (_m *WorkflowArchive) ListWorkflows(namespace string, name string, minStartAt time.Time, maxStartAt time.Time, labelRequirements labels.Requirements, limit int, offset int) (v1alpha1.Workflows, error) { - ret := _m.Called(namespace, name, minStartAt, maxStartAt, labelRequirements, limit, offset) +// ListWorkflows provides a mock function with given fields: namespace, name, namePrefix, minStartAt, maxStartAt, labelRequirements, limit, offset +func (_m *WorkflowArchive) ListWorkflows(namespace string, name string, namePrefix string, minStartAt time.Time, maxStartAt time.Time, labelRequirements labels.Requirements, limit int, offset int) (v1alpha1.Workflows, error) { + ret := _m.Called(namespace, name, namePrefix, minStartAt, maxStartAt, labelRequirements, limit, offset) var r0 v1alpha1.Workflows - if rf, ok := ret.Get(0).(func(string, string, time.Time, time.Time, labels.Requirements, int, int) v1alpha1.Workflows); ok { - r0 = rf(namespace, name, minStartAt, maxStartAt, labelRequirements, limit, offset) + if rf, ok := ret.Get(0).(func(string, string, string, time.Time, time.Time, labels.Requirements, int, int) v1alpha1.Workflows); ok { + r0 = rf(namespace, name, namePrefix, minStartAt, maxStartAt, labelRequirements, limit, offset) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(v1alpha1.Workflows) @@ -109,8 +109,8 @@ func (_m *WorkflowArchive) ListWorkflows(namespace string, name string, minStart } var r1 error - if rf, ok := ret.Get(1).(func(string, string, time.Time, time.Time, labels.Requirements, int, int) error); ok { - r1 = rf(namespace, name, minStartAt, maxStartAt, labelRequirements, limit, offset) + if rf, ok := ret.Get(1).(func(string, string, string, time.Time, time.Time, labels.Requirements, int, int) error); ok { + r1 = rf(namespace, name, namePrefix, minStartAt, maxStartAt, labelRequirements, limit, offset) } else { r1 = ret.Error(1) } diff --git a/persist/sqldb/null_workflow_archive.go b/persist/sqldb/null_workflow_archive.go index 31d78c5db45a..74218279ee64 100644 --- a/persist/sqldb/null_workflow_archive.go +++ b/persist/sqldb/null_workflow_archive.go @@ -21,7 +21,7 @@ func (r *nullWorkflowArchive) ArchiveWorkflow(*wfv1.Workflow) error { return nil } -func (r *nullWorkflowArchive) ListWorkflows(string, string, time.Time, time.Time, labels.Requirements, int, int) (wfv1.Workflows, error) { +func (r *nullWorkflowArchive) ListWorkflows(string, string, string, time.Time, time.Time, labels.Requirements, int, int) (wfv1.Workflows, error) { return wfv1.Workflows{}, nil } diff --git a/persist/sqldb/workflow_archive.go b/persist/sqldb/workflow_archive.go index 2e228479669d..bbbdea4e8fa8 100644 --- a/persist/sqldb/workflow_archive.go +++ b/persist/sqldb/workflow_archive.go @@ -51,7 +51,7 @@ type archivedWorkflowLabelRecord struct { type WorkflowArchive interface { ArchiveWorkflow(wf *wfv1.Workflow) error // list workflows, with the most recently started workflows at the beginning (i.e. index 0 is the most recent) - ListWorkflows(namespace string, name string, minStartAt, maxStartAt time.Time, labelRequirements labels.Requirements, limit, offset int) (wfv1.Workflows, error) + ListWorkflows(namespace string, name string, namePrefix string, minStartAt, maxStartAt time.Time, labelRequirements labels.Requirements, limit, offset int) (wfv1.Workflows, error) GetWorkflow(uid string) (*wfv1.Workflow, error) DeleteWorkflow(uid string) error DeleteExpiredWorkflows(ttl time.Duration) error @@ -136,7 +136,7 @@ func (r *workflowArchive) ArchiveWorkflow(wf *wfv1.Workflow) error { }) } -func (r *workflowArchive) ListWorkflows(namespace string, name string, minStartedAt, maxStartedAt time.Time, labelRequirements labels.Requirements, limit int, offset int) (wfv1.Workflows, error) { +func (r *workflowArchive) ListWorkflows(namespace string, name string, namePrefix string, minStartedAt, maxStartedAt time.Time, labelRequirements labels.Requirements, limit int, offset int) (wfv1.Workflows, error) { var archivedWfs []archivedWorkflowMetadata clause, err := labelsClause(r.dbType, labelRequirements) if err != nil { @@ -156,6 +156,7 @@ func (r *workflowArchive) ListWorkflows(namespace string, name string, minStarte Where(r.clusterManagedNamespaceAndInstanceID()). And(namespaceEqual(namespace)). And(nameEqual(name)). + And(namePrefixClause(namePrefix)). And(startedAtClause(minStartedAt, maxStartedAt)). And(clause). OrderBy("-startedat"). @@ -219,6 +220,14 @@ func nameEqual(name string) db.Cond { } } +func namePrefixClause(namePrefix string) db.Cond { + if namePrefix == "" { + return db.Cond{} + } else { + return db.Cond{"name LIKE ": namePrefix + "%"} + } +} + func (r *workflowArchive) GetWorkflow(uid string) (*wfv1.Workflow, error) { archivedWf := &archivedWorkflowRecord{} err := r.session. diff --git a/pkg/apiclient/workflowarchive/workflow-archive.pb.go b/pkg/apiclient/workflowarchive/workflow-archive.pb.go index 32efb6c69e45..b1f7b5c5fab9 100644 --- a/pkg/apiclient/workflowarchive/workflow-archive.pb.go +++ b/pkg/apiclient/workflowarchive/workflow-archive.pb.go @@ -31,6 +31,7 @@ const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package type ListArchivedWorkflowsRequest struct { ListOptions *v1.ListOptions `protobuf:"bytes,1,opt,name=listOptions,proto3" json:"listOptions,omitempty"` + NamePrefix string `protobuf:"bytes,2,opt,name=namePrefix,proto3" json:"namePrefix,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` @@ -76,6 +77,13 @@ func (m *ListArchivedWorkflowsRequest) GetListOptions() *v1.ListOptions { return nil } +func (m *ListArchivedWorkflowsRequest) GetNamePrefix() string { + if m != nil { + return m.NamePrefix + } + return "" +} + type GetArchivedWorkflowRequest struct { Uid string `protobuf:"bytes,1,opt,name=uid,proto3" json:"uid,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` @@ -311,42 +319,44 @@ func init() { } var fileDescriptor_95ca9a2d33e8bb19 = []byte{ - // 560 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x95, 0xcd, 0x6e, 0xd3, 0x40, - 0x10, 0xc7, 0xb5, 0x20, 0x81, 0xd8, 0x1e, 0x40, 0x8b, 0xf8, 0x90, 0x95, 0xa6, 0x65, 0x05, 0x6d, - 0x01, 0x79, 0x17, 0xb7, 0x45, 0x70, 0x04, 0x84, 0x84, 0x44, 0xd3, 0x22, 0xa5, 0x12, 0x48, 0x5c, - 0xd0, 0x26, 0x1e, 0x9c, 0x25, 0x8e, 0xd7, 0x78, 0x37, 0xae, 0x2a, 0xd4, 0x0b, 0xaf, 0xc0, 0x2b, - 0xf0, 0x10, 0x88, 0x27, 0xe0, 0x88, 0xe0, 0xc6, 0x09, 0x45, 0x9c, 0x78, 0x0a, 0xe4, 0x4d, 0x9c, - 0x14, 0xdb, 0xf9, 0x90, 0x28, 0xb7, 0xcd, 0x64, 0xe6, 0x3f, 0xbf, 0x19, 0xff, 0xd7, 0xc6, 0xdb, - 0x71, 0x37, 0xe0, 0x22, 0x96, 0xed, 0x50, 0x42, 0x64, 0xf8, 0x81, 0x4a, 0xba, 0xaf, 0x43, 0x75, - 0x20, 0x92, 0x76, 0x47, 0xa6, 0x30, 0xfe, 0xed, 0x8e, 0x02, 0x2c, 0x4e, 0x94, 0x51, 0xe4, 0x7c, - 0x21, 0xcf, 0xa9, 0x05, 0x4a, 0x05, 0x21, 0x64, 0x4a, 0x5c, 0x44, 0x91, 0x32, 0xc2, 0x48, 0x15, - 0xe9, 0x61, 0xba, 0xb3, 0xdd, 0xbd, 0xaf, 0x99, 0x54, 0xd9, 0xbf, 0x3d, 0xd1, 0xee, 0xc8, 0x08, - 0x92, 0x43, 0x3e, 0x6a, 0xac, 0x79, 0x0f, 0x8c, 0xe0, 0xa9, 0xc7, 0x03, 0x88, 0x20, 0x11, 0x06, - 0xfc, 0x51, 0xd5, 0x6e, 0x20, 0x4d, 0xa7, 0xdf, 0x62, 0x6d, 0xd5, 0xe3, 0x22, 0x09, 0x54, 0x9c, - 0xa8, 0x37, 0xf6, 0xe0, 0xe6, 0xdd, 0xf5, 0x44, 0x24, 0x0f, 0xf1, 0xd4, 0x13, 0x61, 0xdc, 0x11, - 0x25, 0x39, 0xaa, 0x71, 0xad, 0x21, 0xb5, 0x79, 0x38, 0x24, 0xf6, 0x5f, 0xe4, 0x1a, 0x4d, 0x78, - 0xdb, 0x07, 0x6d, 0xc8, 0x3e, 0x5e, 0x0a, 0xa5, 0x36, 0xcf, 0x62, 0x4b, 0x7e, 0x15, 0xad, 0xa2, - 0x8d, 0xa5, 0x4d, 0x8f, 0x0d, 0xd1, 0xd9, 0x71, 0x74, 0x16, 0x77, 0x83, 0x2c, 0xa0, 0x59, 0x86, - 0xce, 0x52, 0x8f, 0x35, 0x26, 0x85, 0xcd, 0xe3, 0x2a, 0x94, 0x61, 0xe7, 0x09, 0x94, 0x7a, 0xe6, - 0x2d, 0x2f, 0xe0, 0xd3, 0x7d, 0xe9, 0xdb, 0x56, 0xe7, 0x9a, 0xd9, 0x91, 0x7a, 0x78, 0xf9, 0x31, - 0x84, 0x60, 0x60, 0xf1, 0x92, 0x6b, 0x78, 0xa5, 0x98, 0x3c, 0x94, 0xf0, 0x9b, 0xa0, 0x63, 0x15, - 0x69, 0xa0, 0x6b, 0xf8, 0x7a, 0xd5, 0xe8, 0x0d, 0xd1, 0x82, 0x70, 0x07, 0x0e, 0xf3, 0x15, 0xd0, - 0x23, 0xbc, 0x36, 0x35, 0xef, 0xb9, 0x08, 0xfb, 0xf0, 0x5f, 0x97, 0xb5, 0xf9, 0xfb, 0x2c, 0xbe, - 0x52, 0xec, 0xbd, 0x0f, 0x49, 0x2a, 0xdb, 0x40, 0x3e, 0x23, 0x7c, 0xa9, 0xf2, 0xf1, 0x11, 0x97, - 0x15, 0xcc, 0xc8, 0x66, 0x3d, 0x66, 0x67, 0x8f, 0x4d, 0x6c, 0xc5, 0x72, 0x5b, 0xd9, 0xc3, 0xab, - 0xb1, 0xad, 0x58, 0xba, 0x35, 0xc1, 0xce, 0xa3, 0x2c, 0x77, 0x16, 0x1b, 0xef, 0x45, 0x6a, 0x43, - 0xe9, 0xfb, 0xef, 0xbf, 0x3e, 0x9c, 0xaa, 0x11, 0xc7, 0x7a, 0x3f, 0xf5, 0xf8, 0x88, 0xc2, 0x9f, - 0xb8, 0x94, 0x7c, 0x42, 0xf8, 0x62, 0x85, 0x0d, 0xc8, 0xed, 0x12, 0xfa, 0x74, 0xb3, 0x38, 0x4f, - 0x4f, 0x0e, 0x9c, 0x6e, 0x58, 0x68, 0x4a, 0x56, 0xa7, 0x43, 0xf3, 0x77, 0x7d, 0xe9, 0x1f, 0x91, - 0x8f, 0x08, 0x5f, 0xae, 0x76, 0x24, 0x61, 0x25, 0xfa, 0x99, 0xd6, 0x75, 0xee, 0x94, 0xf2, 0xe7, - 0xf9, 0x76, 0x84, 0x79, 0x6b, 0x3e, 0xe6, 0x37, 0x84, 0x97, 0x67, 0x5a, 0x9c, 0xdc, 0x5d, 0xc8, - 0x26, 0xc5, 0x2b, 0xe1, 0xec, 0xfc, 0xfb, 0xd6, 0xc7, 0x9a, 0xd4, 0xb5, 0xf3, 0xac, 0x93, 0x1b, - 0xd3, 0xe7, 0x71, 0xc3, 0x2c, 0xdb, 0xed, 0x66, 0xc8, 0x3f, 0x10, 0x5e, 0x99, 0x73, 0x1f, 0xc9, - 0xbd, 0xc5, 0xc7, 0xfa, 0xeb, 0x06, 0x3b, 0xbb, 0x27, 0x34, 0xd8, 0x50, 0x95, 0x72, 0x3b, 0xda, - 0x4d, 0xb2, 0x3e, 0x77, 0xb4, 0xd4, 0x16, 0x3c, 0xda, 0xfb, 0x32, 0xa8, 0xa3, 0xaf, 0x83, 0x3a, - 0xfa, 0x39, 0xa8, 0xa3, 0x97, 0x0f, 0x16, 0x7f, 0xd7, 0x57, 0x7f, 0xa9, 0x5a, 0x67, 0xec, 0x5b, - 0x7e, 0xeb, 0x4f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x6b, 0xca, 0x08, 0x53, 0xd1, 0x06, 0x00, 0x00, + // 579 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x95, 0xdf, 0x6a, 0x13, 0x4f, + 0x14, 0xc7, 0x99, 0xfe, 0xe0, 0x27, 0x9d, 0x5e, 0x28, 0x23, 0x6a, 0x59, 0xd2, 0x34, 0x0e, 0xda, + 0x46, 0x65, 0x67, 0xdc, 0xb6, 0xa2, 0x97, 0x2a, 0x82, 0x60, 0xd3, 0x2a, 0x29, 0x28, 0x78, 0x23, + 0x93, 0xec, 0xe9, 0x66, 0xcc, 0x66, 0x67, 0xdd, 0x99, 0x6c, 0x2d, 0xd2, 0x1b, 0x5f, 0xa1, 0xaf, + 0xe0, 0x43, 0x88, 0x4f, 0xe0, 0xa5, 0xe8, 0x9d, 0x57, 0x12, 0xbc, 0xf2, 0x29, 0x64, 0x27, 0xd9, + 0x24, 0xe6, 0x3f, 0x58, 0xef, 0x26, 0x27, 0xe7, 0x7c, 0xe7, 0x73, 0xce, 0x7e, 0x0f, 0x83, 0x77, + 0xe2, 0x66, 0xc0, 0x45, 0x2c, 0xeb, 0xa1, 0x84, 0xc8, 0xf0, 0x23, 0x95, 0x34, 0x0f, 0x43, 0x75, + 0x24, 0x92, 0x7a, 0x43, 0xa6, 0xd0, 0xff, 0xed, 0xf6, 0x02, 0x2c, 0x4e, 0x94, 0x51, 0xe4, 0xfc, + 0x48, 0x9e, 0x53, 0x08, 0x94, 0x0a, 0x42, 0xc8, 0x94, 0xb8, 0x88, 0x22, 0x65, 0x84, 0x91, 0x2a, + 0xd2, 0xdd, 0x74, 0x67, 0xa7, 0x79, 0x4f, 0x33, 0xa9, 0xb2, 0x7f, 0x5b, 0xa2, 0xde, 0x90, 0x11, + 0x24, 0xc7, 0xbc, 0x77, 0xb1, 0xe6, 0x2d, 0x30, 0x82, 0xa7, 0x1e, 0x0f, 0x20, 0x82, 0x44, 0x18, + 0xf0, 0x7b, 0x55, 0x7b, 0x81, 0x34, 0x8d, 0x76, 0x8d, 0xd5, 0x55, 0x8b, 0x8b, 0x24, 0x50, 0x71, + 0xa2, 0x5e, 0xdb, 0x83, 0x9b, 0xdf, 0xae, 0x07, 0x22, 0x79, 0x88, 0xa7, 0x9e, 0x08, 0xe3, 0x86, + 0x18, 0x93, 0xa3, 0xa7, 0x08, 0x17, 0x2a, 0x52, 0x9b, 0x07, 0x5d, 0x64, 0xff, 0x45, 0x2e, 0x52, + 0x85, 0x37, 0x6d, 0xd0, 0x86, 0x1c, 0xe0, 0x95, 0x50, 0x6a, 0xf3, 0x34, 0xb6, 0xe8, 0xab, 0xa8, + 0x84, 0xca, 0x2b, 0x5b, 0x1e, 0xeb, 0xb2, 0xb3, 0x61, 0x76, 0x16, 0x37, 0x83, 0x2c, 0xa0, 0x59, + 0xc6, 0xce, 0x52, 0x8f, 0x55, 0x06, 0x85, 0xd5, 0x61, 0x15, 0x52, 0xc4, 0x38, 0x12, 0x2d, 0x78, + 0x96, 0xc0, 0xa1, 0x7c, 0xbb, 0xba, 0x54, 0x42, 0xe5, 0xe5, 0xea, 0x50, 0x84, 0x32, 0xec, 0x3c, + 0x86, 0x31, 0xa6, 0x1c, 0xe9, 0x02, 0xfe, 0xaf, 0x2d, 0x7d, 0x8b, 0xb2, 0x5c, 0xcd, 0x8e, 0xd4, + 0xc3, 0x6b, 0x8f, 0x20, 0x04, 0x03, 0x8b, 0x97, 0x5c, 0xc5, 0xeb, 0xa3, 0xc9, 0x5d, 0x09, 0xbf, + 0x0a, 0x3a, 0x56, 0x91, 0x06, 0xba, 0x81, 0xaf, 0x4d, 0x1a, 0x4d, 0x45, 0xd4, 0x20, 0xdc, 0x85, + 0xe3, 0x7c, 0x44, 0xf4, 0x04, 0x6f, 0x4c, 0xcd, 0x7b, 0x2e, 0xc2, 0x36, 0xfc, 0xd3, 0x61, 0x6e, + 0xfd, 0x3a, 0x87, 0xaf, 0x8c, 0xde, 0x7d, 0x00, 0x49, 0x2a, 0xeb, 0x40, 0x3e, 0x21, 0x7c, 0x69, + 0xe2, 0xe7, 0x25, 0x2e, 0x1b, 0x71, 0x2b, 0x9b, 0x65, 0x03, 0x67, 0x9f, 0x0d, 0x7c, 0xc7, 0x72, + 0xdf, 0xd9, 0xc3, 0xab, 0xbe, 0xef, 0x58, 0xba, 0x3d, 0xc0, 0xce, 0xa3, 0x2c, 0xb7, 0x1e, 0xeb, + 0xcf, 0x45, 0x6a, 0x43, 0xe9, 0xfb, 0x6f, 0x3f, 0x4f, 0x97, 0x0a, 0xc4, 0xb1, 0xcb, 0x91, 0x7a, + 0xbc, 0x47, 0xe1, 0x0f, 0x6c, 0x4c, 0x3e, 0x22, 0x7c, 0x71, 0x82, 0x0d, 0xc8, 0xad, 0x31, 0xf4, + 0xe9, 0x66, 0x71, 0x9e, 0x9c, 0x1d, 0x38, 0x2d, 0x5b, 0x68, 0x4a, 0x4a, 0xd3, 0xa1, 0xf9, 0xbb, + 0xb6, 0xf4, 0x4f, 0xc8, 0x07, 0x84, 0x2f, 0x4f, 0x76, 0x24, 0x61, 0x63, 0xf4, 0x33, 0xad, 0xeb, + 0xdc, 0x1e, 0xcb, 0x9f, 0xe7, 0xdb, 0x1e, 0xe6, 0xcd, 0xf9, 0x98, 0x5f, 0x11, 0x5e, 0x9b, 0x69, + 0x71, 0x72, 0x67, 0x21, 0x9b, 0x8c, 0xae, 0x84, 0xb3, 0xfb, 0xf7, 0x53, 0xef, 0x6b, 0x52, 0xd7, + 0xf6, 0xb3, 0x49, 0xae, 0x4f, 0xef, 0xc7, 0x0d, 0xb3, 0x6c, 0xb7, 0x99, 0x21, 0x7f, 0x47, 0x78, + 0x7d, 0xce, 0x3e, 0x92, 0xbb, 0x8b, 0xb7, 0xf5, 0xc7, 0x06, 0x3b, 0x7b, 0x67, 0xd4, 0x58, 0x57, + 0x95, 0x72, 0xdb, 0xda, 0x0d, 0xb2, 0x39, 0xb7, 0xb5, 0xd4, 0x16, 0x3c, 0xdc, 0xff, 0xdc, 0x29, + 0xa2, 0x2f, 0x9d, 0x22, 0xfa, 0xd1, 0x29, 0xa2, 0x97, 0xf7, 0x17, 0x7f, 0x0c, 0x26, 0x3f, 0x65, + 0xb5, 0xff, 0xed, 0x33, 0xb0, 0xfd, 0x3b, 0x00, 0x00, 0xff, 0xff, 0x42, 0xe1, 0x0d, 0xc1, 0xf2, + 0x06, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -597,6 +607,13 @@ func (m *ListArchivedWorkflowsRequest) MarshalToSizedBuffer(dAtA []byte) (int, e i -= len(m.XXX_unrecognized) copy(dAtA[i:], m.XXX_unrecognized) } + if len(m.NamePrefix) > 0 { + i -= len(m.NamePrefix) + copy(dAtA[i:], m.NamePrefix) + i = encodeVarintWorkflowArchive(dAtA, i, uint64(len(m.NamePrefix))) + i-- + dAtA[i] = 0x12 + } if m.ListOptions != nil { { size, err := m.ListOptions.MarshalToSizedBuffer(dAtA[:i]) @@ -794,6 +811,10 @@ func (m *ListArchivedWorkflowsRequest) Size() (n int) { l = m.ListOptions.Size() n += 1 + l + sovWorkflowArchive(uint64(l)) } + l = len(m.NamePrefix) + if l > 0 { + n += 1 + l + sovWorkflowArchive(uint64(l)) + } if m.XXX_unrecognized != nil { n += len(m.XXX_unrecognized) } @@ -943,6 +964,38 @@ func (m *ListArchivedWorkflowsRequest) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field NamePrefix", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowWorkflowArchive + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthWorkflowArchive + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthWorkflowArchive + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.NamePrefix = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipWorkflowArchive(dAtA[iNdEx:]) diff --git a/pkg/apiclient/workflowarchive/workflow-archive.proto b/pkg/apiclient/workflowarchive/workflow-archive.proto index 76e83469055f..e520d0afa021 100644 --- a/pkg/apiclient/workflowarchive/workflow-archive.proto +++ b/pkg/apiclient/workflowarchive/workflow-archive.proto @@ -9,6 +9,7 @@ package workflowarchive; message ListArchivedWorkflowsRequest { k8s.io.apimachinery.pkg.apis.meta.v1.ListOptions listOptions = 1; + string namePrefix = 2; } message GetArchivedWorkflowRequest { string uid = 1; diff --git a/server/workflowarchive/archived_workflow_server.go b/server/workflowarchive/archived_workflow_server.go index 3c2dfbbcc71a..24e84cd37f99 100644 --- a/server/workflowarchive/archived_workflow_server.go +++ b/server/workflowarchive/archived_workflow_server.go @@ -32,6 +32,7 @@ func NewWorkflowArchiveServer(wfArchive sqldb.WorkflowArchive) workflowarchivepk func (w *archivedWorkflowServer) ListArchivedWorkflows(ctx context.Context, req *workflowarchivepkg.ListArchivedWorkflowsRequest) (*wfv1.WorkflowList, error) { options := req.ListOptions + namePrefix := req.NamePrefix if options == nil { options = &metav1.ListOptions{} } @@ -98,7 +99,7 @@ func (w *archivedWorkflowServer) ListArchivedWorkflows(ctx context.Context, req limitWithMore = limit + 1 } - items, err := w.wfArchive.ListWorkflows(namespace, name, minStartedAt, maxStartedAt, requirements, limitWithMore, offset) + items, err := w.wfArchive.ListWorkflows(namespace, name, namePrefix, minStartedAt, maxStartedAt, requirements, limitWithMore, offset) if err != nil { return nil, err } diff --git a/server/workflowarchive/archived_workflow_server_test.go b/server/workflowarchive/archived_workflow_server_test.go index 9fc42c4582e5..072b2e5ba60a 100644 --- a/server/workflowarchive/archived_workflow_server_test.go +++ b/server/workflowarchive/archived_workflow_server_test.go @@ -46,12 +46,14 @@ func Test_archivedWorkflowServer(t *testing.T) { }, nil }) // two pages of results for limit 1 - repo.On("ListWorkflows", "", "", time.Time{}, time.Time{}, labels.Requirements(nil), 2, 0).Return(wfv1.Workflows{{}, {}}, nil) - repo.On("ListWorkflows", "", "", time.Time{}, time.Time{}, labels.Requirements(nil), 2, 1).Return(wfv1.Workflows{{}}, nil) + repo.On("ListWorkflows", "", "", "", time.Time{}, time.Time{}, labels.Requirements(nil), 2, 0).Return(wfv1.Workflows{{}, {}}, nil) + repo.On("ListWorkflows", "", "", "", time.Time{}, time.Time{}, labels.Requirements(nil), 2, 1).Return(wfv1.Workflows{{}}, nil) minStartAt, _ := time.Parse(time.RFC3339, "2020-01-01T00:00:00Z") maxStartAt, _ := time.Parse(time.RFC3339, "2020-01-02T00:00:00Z") - repo.On("ListWorkflows", "", "", minStartAt, maxStartAt, labels.Requirements(nil), 2, 0).Return(wfv1.Workflows{{}}, nil) - repo.On("ListWorkflows", "", "my-name", minStartAt, maxStartAt, labels.Requirements(nil), 2, 0).Return(wfv1.Workflows{{}}, nil) + repo.On("ListWorkflows", "", "", "", minStartAt, maxStartAt, labels.Requirements(nil), 2, 0).Return(wfv1.Workflows{{}}, nil) + repo.On("ListWorkflows", "", "my-name", "", minStartAt, maxStartAt, labels.Requirements(nil), 2, 0).Return(wfv1.Workflows{{}}, nil) + repo.On("ListWorkflows", "", "", "my-", minStartAt, maxStartAt, labels.Requirements(nil), 2, 0).Return(wfv1.Workflows{{}}, nil) + repo.On("ListWorkflows", "", "my-name", "my-", minStartAt, maxStartAt, labels.Requirements(nil), 2, 0).Return(wfv1.Workflows{{}}, nil) repo.On("GetWorkflow", "").Return(nil, nil) repo.On("GetWorkflow", "my-uid").Return(&wfv1.Workflow{ ObjectMeta: metav1.ObjectMeta{Name: "my-name"}, @@ -101,6 +103,16 @@ func Test_archivedWorkflowServer(t *testing.T) { assert.Len(t, resp.Items, 1) assert.Empty(t, resp.Continue) } + resp, err = w.ListArchivedWorkflows(ctx, &workflowarchivepkg.ListArchivedWorkflowsRequest{ListOptions: &metav1.ListOptions{FieldSelector: "spec.startedAt>2020-01-01T00:00:00Z,spec.startedAt<2020-01-02T00:00:00Z", Limit: 1}, NamePrefix: "my-"}) + if assert.NoError(t, err) { + assert.Len(t, resp.Items, 1) + assert.Empty(t, resp.Continue) + } + resp, err = w.ListArchivedWorkflows(ctx, &workflowarchivepkg.ListArchivedWorkflowsRequest{ListOptions: &metav1.ListOptions{FieldSelector: "metadata.name=my-name,spec.startedAt>2020-01-01T00:00:00Z,spec.startedAt<2020-01-02T00:00:00Z", Limit: 1}, NamePrefix: "my-"}) + if assert.NoError(t, err) { + assert.Len(t, resp.Items, 1) + assert.Empty(t, resp.Continue) + } }) t.Run("GetArchivedWorkflow", func(t *testing.T) { allowed = false diff --git a/test/e2e/fixtures/e2e_suite.go b/test/e2e/fixtures/e2e_suite.go index c34d969db5da..26e8407a462c 100644 --- a/test/e2e/fixtures/e2e_suite.go +++ b/test/e2e/fixtures/e2e_suite.go @@ -145,7 +145,7 @@ func (s *E2ESuite) DeleteResources() { archive := s.Persistence.workflowArchive parse, err := labels.ParseToRequirements(Label) s.CheckError(err) - workflows, err := archive.ListWorkflows(Namespace, "", time.Time{}, time.Time{}, parse, 0, 0) + workflows, err := archive.ListWorkflows(Namespace, "", "", time.Time{}, time.Time{}, parse, 0, 0) s.CheckError(err) for _, w := range workflows { err := archive.DeleteWorkflow(string(w.UID)) diff --git a/ui/src/app/archived-workflows/components/archived-workflow-filters/archived-workflow-filters.tsx b/ui/src/app/archived-workflows/components/archived-workflow-filters/archived-workflow-filters.tsx index adfd1bc0caa5..6e7fdcfc78f9 100644 --- a/ui/src/app/archived-workflows/components/archived-workflow-filters/archived-workflow-filters.tsx +++ b/ui/src/app/archived-workflows/components/archived-workflow-filters/archived-workflow-filters.tsx @@ -15,12 +15,13 @@ interface ArchivedWorkflowFilterProps { workflows: models.Workflow[]; namespace: string; name: string; + namePrefix: string; phaseItems: string[]; selectedPhases: string[]; selectedLabels: string[]; minStartedAt?: Date; maxStartedAt?: Date; - onChange: (namespace: string, name: string, selectedPhases: string[], labels: string[], minStartedAt: Date, maxStartedAt: Date) => void; + onChange: (namespace: string, name: string, namePrefix: string, selectedPhases: string[], labels: string[], minStartedAt: Date, maxStartedAt: Date) => void; } interface State { @@ -48,7 +49,15 @@ export class ArchivedWorkflowFilters extends React.Component { - this.props.onChange(ns, this.props.name, this.props.selectedPhases, this.props.selectedLabels, this.props.minStartedAt, this.props.maxStartedAt); + this.props.onChange( + ns, + this.props.name, + this.props.namePrefix, + this.props.selectedPhases, + this.props.selectedLabels, + this.props.minStartedAt, + this.props.maxStartedAt + ); }} /> @@ -61,6 +70,25 @@ export class ArchivedWorkflowFilters extends React.Component + +
+

Name Prefix

+ { + this.props.onChange( + this.props.namespace, + this.props.name, + wfnamePrefix, this.props.selectedPhases, this.props.selectedLabels, this.props.minStartedAt, @@ -77,7 +105,15 @@ export class ArchivedWorkflowFilters extends React.Component { - this.props.onChange(this.props.namespace, this.props.name, this.props.selectedPhases, tags, this.props.minStartedAt, this.props.maxStartedAt); + this.props.onChange( + this.props.namespace, + this.props.name, + this.props.namePrefix, + this.props.selectedPhases, + tags, + this.props.minStartedAt, + this.props.maxStartedAt + ); }} />
@@ -86,7 +122,15 @@ export class ArchivedWorkflowFilters extends React.Component { - this.props.onChange(this.props.namespace, this.props.name, selected, this.props.selectedLabels, this.props.minStartedAt, this.props.maxStartedAt); + this.props.onChange( + this.props.namespace, + this.props.name, + this.props.namePrefix, + selected, + this.props.selectedLabels, + this.props.minStartedAt, + this.props.maxStartedAt + ); }} items={this.getPhaseItems(this.props.workflows)} type='phase' @@ -97,7 +141,15 @@ export class ArchivedWorkflowFilters extends React.Component { - this.props.onChange(this.props.namespace, this.props.name, this.props.selectedPhases, this.props.selectedLabels, date, this.props.maxStartedAt); + this.props.onChange( + this.props.namespace, + this.props.name, + this.props.namePrefix, + this.props.selectedPhases, + this.props.selectedLabels, + date, + this.props.maxStartedAt + ); }} placeholderText='From' dateFormat='dd MMM yyyy' @@ -107,7 +159,15 @@ export class ArchivedWorkflowFilters extends React.Component { - this.props.onChange(this.props.namespace, this.props.name, this.props.selectedPhases, this.props.selectedLabels, this.props.minStartedAt, date); + this.props.onChange( + this.props.namespace, + this.props.name, + this.props.namePrefix, + this.props.selectedPhases, + this.props.selectedLabels, + this.props.minStartedAt, + date + ); }} placeholderText='To' dateFormat='dd MMM yyyy' diff --git a/ui/src/app/archived-workflows/components/archived-workflow-list/archived-workflow-list.tsx b/ui/src/app/archived-workflows/components/archived-workflow-list/archived-workflow-list.tsx index 3ae49217302d..21c9eac45b4f 100644 --- a/ui/src/app/archived-workflows/components/archived-workflow-list/archived-workflow-list.tsx +++ b/ui/src/app/archived-workflows/components/archived-workflow-list/archived-workflow-list.tsx @@ -21,8 +21,9 @@ import {ArchivedWorkflowFilters} from '../archived-workflow-filters/archived-wor interface State { pagination: Pagination; - name: string; namespace: string; + name: string; + namePrefix: string; selectedPhases: string[]; selectedLabels: string[]; minStartedAt?: Date; @@ -50,6 +51,7 @@ export class ArchivedWorkflowList extends BasePage, Sta pagination: {offset: this.queryParam('offset'), limit: parseLimit(this.queryParam('limit')) || savedOptions.pagination.limit}, namespace: Utils.getNamespace(this.props.match.params.namespace) || '', name: this.queryParams('name').toString() || '', + namePrefix: this.queryParams('namePrefix').toString() || '', selectedPhases: phaseQueryParam.length > 0 ? phaseQueryParam : savedOptions.selectedPhases, selectedLabels: labelQueryParam.length > 0 ? labelQueryParam : savedOptions.selectedLabels, minStartedAt: this.parseTime(this.queryParam('minStartedAt')) || this.lastMonth(), @@ -61,6 +63,7 @@ export class ArchivedWorkflowList extends BasePage, Sta this.fetchArchivedWorkflows( this.state.namespace, this.state.name, + this.state.namePrefix, this.state.selectedPhases, this.state.selectedLabels, this.state.minStartedAt, @@ -86,13 +89,16 @@ export class ArchivedWorkflowList extends BasePage, Sta workflows={this.state.workflows || []} namespace={this.state.namespace} name={this.state.name} + namePrefix={this.state.namePrefix} phaseItems={Object.values([models.NODE_PHASE.SUCCEEDED, models.NODE_PHASE.FAILED, models.NODE_PHASE.ERROR])} selectedPhases={this.state.selectedPhases} selectedLabels={this.state.selectedLabels} minStartedAt={this.state.minStartedAt} maxStartedAt={this.state.maxStartedAt} - onChange={(namespace, name, selectedPhases, selectedLabels, minStartedAt, maxStartedAt) => - this.changeFilters(namespace, name, selectedPhases, selectedLabels, minStartedAt, maxStartedAt, {limit: this.state.pagination.limit}) + onChange={(namespace, name, namePrefix, selectedPhases, selectedLabels, minStartedAt, maxStartedAt) => + this.changeFilters(namespace, name, namePrefix, selectedPhases, selectedLabels, minStartedAt, maxStartedAt, { + limit: this.state.pagination.limit + }) } /> @@ -123,8 +129,17 @@ export class ArchivedWorkflowList extends BasePage, Sta } } - private changeFilters(namespace: string, name: string, selectedPhases: string[], selectedLabels: string[], minStartedAt: Date, maxStartedAt: Date, pagination: Pagination) { - this.fetchArchivedWorkflows(namespace, name, selectedPhases, selectedLabels, minStartedAt, maxStartedAt, pagination); + private changeFilters( + namespace: string, + name: string, + namePrefix: string, + selectedPhases: string[], + selectedLabels: string[], + minStartedAt: Date, + maxStartedAt: Date, + pagination: Pagination + ) { + this.fetchArchivedWorkflows(namespace, name, namePrefix, selectedPhases, selectedLabels, minStartedAt, maxStartedAt, pagination); } private get filterParams() { @@ -142,6 +157,9 @@ export class ArchivedWorkflowList extends BasePage, Sta if (this.state.name) { params.append('name', this.state.name); } + if (this.state.namePrefix) { + params.append('namePrefix', this.state.namePrefix); + } params.append('minStartedAt', this.state.minStartedAt.toISOString()); params.append('maxStartedAt', this.state.maxStartedAt.toISOString()); if (this.state.pagination.offset) { @@ -163,6 +181,7 @@ export class ArchivedWorkflowList extends BasePage, Sta private fetchArchivedWorkflows( namespace: string, name: string, + namePrefix: string, selectedPhases: string[], selectedLabels: string[], minStartedAt: Date, @@ -170,13 +189,14 @@ export class ArchivedWorkflowList extends BasePage, Sta pagination: Pagination ): void { services.archivedWorkflows - .list(namespace, name, selectedPhases, selectedLabels, minStartedAt, maxStartedAt, pagination) + .list(namespace, name, namePrefix, selectedPhases, selectedLabels, minStartedAt, maxStartedAt, pagination) .then(list => { this.setState( { error: null, namespace, name, + namePrefix, workflows: list.items || [], selectedPhases, selectedLabels, @@ -244,6 +264,7 @@ export class ArchivedWorkflowList extends BasePage, Sta this.changeFilters( this.state.namespace, this.state.name, + this.state.namePrefix, this.state.selectedPhases, this.state.selectedLabels, this.state.minStartedAt, diff --git a/ui/src/app/reports/components/reports.tsx b/ui/src/app/reports/components/reports.tsx index 656b98ae2852..8492f0478199 100644 --- a/ui/src/app/reports/components/reports.tsx +++ b/ui/src/app/reports/components/reports.tsx @@ -104,7 +104,7 @@ export class Reports extends BasePage, State> { return; } (archivedWorkflows - ? services.archivedWorkflows.list(namespace, '', [], labels, null, null, {limit}) + ? services.archivedWorkflows.list(namespace, '', '', [], labels, null, null, {limit}) : services.workflows.list(namespace, [], labels, {limit}, [ 'items.metadata.name', 'items.status.phase', diff --git a/ui/src/app/shared/services/archived-workflows-service.ts b/ui/src/app/shared/services/archived-workflows-service.ts index 27ef7819da3a..48563dd9b6cb 100644 --- a/ui/src/app/shared/services/archived-workflows-service.ts +++ b/ui/src/app/shared/services/archived-workflows-service.ts @@ -3,9 +3,9 @@ import {Pagination} from '../pagination'; import requests from './requests'; export class ArchivedWorkflowsService { - public list(namespace: string, name: string, phases: string[], labels: string[], minStartedAt: Date, maxStartedAt: Date, pagination: Pagination) { + public list(namespace: string, name: string, namePrefix: string, phases: string[], labels: string[], minStartedAt: Date, maxStartedAt: Date, pagination: Pagination) { return requests - .get(`api/v1/archived-workflows?${this.queryParams({namespace, name, phases, labels, minStartedAt, maxStartedAt, pagination}).join('&')}`) + .get(`api/v1/archived-workflows?${this.queryParams({namespace, name, namePrefix, phases, labels, minStartedAt, maxStartedAt, pagination}).join('&')}`) .then(res => res.body as models.WorkflowList); } @@ -28,6 +28,7 @@ export class ArchivedWorkflowsService { private queryParams(filter: { namespace?: string; name?: string; + namePrefix?: string; phases?: Array; labels?: Array; minStartedAt?: Date; @@ -49,6 +50,9 @@ export class ArchivedWorkflowsService { if (filter.pagination.limit) { queryParams.push(`listOptions.limit=${filter.pagination.limit}`); } + if (filter.namePrefix) { + queryParams.push(`namePrefix=${filter.namePrefix}`); + } return queryParams; } diff --git a/workflow/controller/estimation/estimator_factory.go b/workflow/controller/estimation/estimator_factory.go index 56fc5cd555c8..60311eb8f5c3 100644 --- a/workflow/controller/estimation/estimator_factory.go +++ b/workflow/controller/estimation/estimator_factory.go @@ -76,7 +76,7 @@ func (f *estimatorFactory) NewEstimator(wf *wfv1.Workflow) (Estimator, error) { if err != nil { return defaultEstimator, fmt.Errorf("failed to parse selector to requirements: %v", err) } - workflows, err := f.wfArchive.ListWorkflows(wf.Namespace, "", time.Time{}, time.Time{}, requirements, 1, 0) + workflows, err := f.wfArchive.ListWorkflows(wf.Namespace, "", "", time.Time{}, time.Time{}, requirements, 1, 0) if err != nil { return defaultEstimator, fmt.Errorf("failed to list archived workflows: %v", err) } diff --git a/workflow/controller/estimation/estimator_factory_test.go b/workflow/controller/estimation/estimator_factory_test.go index e778094644ee..aeef2a7128c3 100644 --- a/workflow/controller/estimation/estimator_factory_test.go +++ b/workflow/controller/estimation/estimator_factory_test.go @@ -53,7 +53,7 @@ metadata: wfArchive := &sqldbmocks.WorkflowArchive{} r, err := labels.ParseToRequirements("workflows.argoproj.io/phase=Succeeded,workflows.argoproj.io/workflow-template=my-archived-wftmpl") assert.NoError(t, err) - wfArchive.On("ListWorkflows", "my-ns", "", time.Time{}, time.Time{}, labels.Requirements(r), 1, 0).Return(wfv1.Workflows{ + wfArchive.On("ListWorkflows", "my-ns", "", "", time.Time{}, time.Time{}, labels.Requirements(r), 1, 0).Return(wfv1.Workflows{ *testutil.MustUnmarshalWorkflow(` metadata: name: my-archived-wftmpl-baseline`),