diff --git a/api/submitqueue/gateway/proto/gateway.proto b/api/submitqueue/gateway/proto/gateway.proto index d3b387a7e..493b553d8 100644 --- a/api/submitqueue/gateway/proto/gateway.proto +++ b/api/submitqueue/gateway/proto/gateway.proto @@ -103,6 +103,8 @@ message RequestSummary { string last_error = 6; // Display and debugging metadata associated with the current status. map metadata = 7; + // Time the current status was recorded, in Unix milliseconds; equals received_at_ms until the status first changes. + int64 updated_at_ms = 8; } // GetRequestSummaryByIDRequest selects one request by the sqid returned from Land. @@ -149,8 +151,11 @@ message ListRequest { } // Maximum results to return. Zero selects the server default; values above the server maximum are invalid. int32 page_size = 4; - // Opaque continuation token from a previous response, bound to its queue and receipt window. Empty selects the first page. + // Opaque continuation token from a previous response, bound to its queue, receipt window, and statuses. Empty selects the first page. string page_token = 5; + // Current statuses to include, as reported in RequestSummary.status; empty includes every status. With page_token, omitted takes the token's statuses and supplied must equal them. + // A filtered page may hold fewer than page_size requests, or none, while next_page_token is non-empty: each call reads a bounded number of receipts. + repeated string statuses = 6; } // ListResponse defines one page of queue receipt history. diff --git a/api/submitqueue/gateway/protopb/gateway.pb.go b/api/submitqueue/gateway/protopb/gateway.pb.go index 9711be27e..98a4610f4 100644 --- a/api/submitqueue/gateway/protopb/gateway.pb.go +++ b/api/submitqueue/gateway/protopb/gateway.pb.go @@ -14,7 +14,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.10 +// protoc-gen-go v1.36.11 // protoc v5.29.3 // source: gateway.proto @@ -395,7 +395,9 @@ type RequestSummary struct { // Last error associated with the current status. Empty when absent. LastError string `protobuf:"bytes,6,opt,name=last_error,json=lastError,proto3" json:"last_error,omitempty"` // Display and debugging metadata associated with the current status. - Metadata map[string]string `protobuf:"bytes,7,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + Metadata map[string]string `protobuf:"bytes,7,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + // Time the current status was recorded, in Unix milliseconds; equals received_at_ms until the status first changes. + UpdatedAtMs int64 `protobuf:"varint,8,opt,name=updated_at_ms,json=updatedAtMs,proto3" json:"updated_at_ms,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -479,6 +481,13 @@ func (x *RequestSummary) GetMetadata() map[string]string { return nil } +func (x *RequestSummary) GetUpdatedAtMs() int64 { + if x != nil { + return x.UpdatedAtMs + } + return 0 +} + // GetRequestSummaryByIDRequest selects one request by the sqid returned from Land. type GetRequestSummaryByIDRequest struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -698,8 +707,11 @@ type ListRequest struct { ReceivedUpperBound isListRequest_ReceivedUpperBound `protobuf_oneof:"received_upper_bound"` // Maximum results to return. Zero selects the server default; values above the server maximum are invalid. PageSize int32 `protobuf:"varint,4,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"` - // Opaque continuation token from a previous response, bound to its queue and receipt window. Empty selects the first page. - PageToken string `protobuf:"bytes,5,opt,name=page_token,json=pageToken,proto3" json:"page_token,omitempty"` + // Opaque continuation token from a previous response, bound to its queue, receipt window, and statuses. Empty selects the first page. + PageToken string `protobuf:"bytes,5,opt,name=page_token,json=pageToken,proto3" json:"page_token,omitempty"` + // Current statuses to include, as reported in RequestSummary.status; empty includes every status. With page_token, omitted takes the token's statuses and supplied must equal them. + // A filtered page may hold fewer than page_size requests, or none, while next_page_token is non-empty: each call reads a bounded number of receipts. + Statuses []string `protobuf:"bytes,6,rep,name=statuses,proto3" json:"statuses,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -787,6 +799,13 @@ func (x *ListRequest) GetPageToken() string { return "" } +func (x *ListRequest) GetStatuses() []string { + if x != nil { + return x.Statuses + } + return nil +} + type isListRequest_ReceivedLowerBound interface { isListRequest_ReceivedLowerBound() } @@ -1550,7 +1569,7 @@ const file_gateway_proto_rawDesc = "" + "\x04sqid\x18\x01 \x01(\tR\x04sqid\x12\x16\n" + "\x06reason\x18\x02 \x01(\tR\x06reason\x12\x14\n" + "\x05queue\x18\x03 \x01(\tR\x05queue\"\x10\n" + - "\x0eCancelResponse\"\xc9\x02\n" + + "\x0eCancelResponse\"\xed\x02\n" + "\x0eRequestSummary\x12\x12\n" + "\x04sqid\x18\x01 \x01(\tR\x04sqid\x12\x14\n" + "\x05queue\x18\x02 \x01(\tR\x05queue\x12\x1f\n" + @@ -1560,7 +1579,8 @@ const file_gateway_proto_rawDesc = "" + "\x06status\x18\x05 \x01(\tR\x06status\x12\x1d\n" + "\n" + "last_error\x18\x06 \x01(\tR\tlastError\x12R\n" + - "\bmetadata\x18\a \x03(\v26.uber.submitqueue.gateway.RequestSummary.MetadataEntryR\bmetadata\x1a;\n" + + "\bmetadata\x18\a \x03(\v26.uber.submitqueue.gateway.RequestSummary.MetadataEntryR\bmetadata\x12\"\n" + + "\rupdated_at_ms\x18\b \x01(\x03R\vupdatedAtMs\x1a;\n" + "\rMetadataEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"H\n" + @@ -1574,14 +1594,15 @@ const file_gateway_proto_rawDesc = "" + "change_uri\x18\x01 \x01(\tR\tchangeUri\x12\x14\n" + "\x05queue\x18\x02 \x01(\tR\x05queue\"l\n" + "$GetRequestSummaryByChangeURIResponse\x12D\n" + - "\brequests\x18\x01 \x03(\v2(.uber.submitqueue.gateway.RequestSummaryR\brequests\"\xf7\x01\n" + + "\brequests\x18\x01 \x03(\v2(.uber.submitqueue.gateway.RequestSummaryR\brequests\"\x93\x02\n" + "\vListRequest\x12\x14\n" + "\x05queue\x18\x01 \x01(\tR\x05queue\x126\n" + "\x17received_at_or_after_ms\x18\x02 \x01(\x03H\x00R\x13receivedAtOrAfterMs\x12.\n" + "\x12received_before_ms\x18\x03 \x01(\x03H\x01R\x10receivedBeforeMs\x12\x1b\n" + "\tpage_size\x18\x04 \x01(\x05R\bpageSize\x12\x1d\n" + "\n" + - "page_token\x18\x05 \x01(\tR\tpageTokenB\x16\n" + + "page_token\x18\x05 \x01(\tR\tpageToken\x12\x1a\n" + + "\bstatuses\x18\x06 \x03(\tR\bstatusesB\x16\n" + "\x14received_lower_boundB\x16\n" + "\x14received_upper_bound\"\xe0\x01\n" + "\fListResponse\x12D\n" + diff --git a/api/submitqueue/gateway/protopb/gateway.pb.yarpc.go b/api/submitqueue/gateway/protopb/gateway.pb.yarpc.go index 7b0b580cd..a62ce053c 100644 --- a/api/submitqueue/gateway/protopb/gateway.pb.yarpc.go +++ b/api/submitqueue/gateway/protopb/gateway.pb.yarpc.go @@ -626,82 +626,84 @@ var ( var yarpcFileDescriptorClosuref1a937782ebbded5 = [][]byte{ // gateway.proto []byte{ - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x57, 0x5d, 0x6f, 0x13, 0x47, - 0x17, 0xce, 0xfa, 0x2b, 0xf6, 0xb1, 0x93, 0x37, 0xef, 0x90, 0xa6, 0x96, 0x49, 0x44, 0xb2, 0xa5, - 0xc1, 0x52, 0x91, 0x8d, 0x52, 0x0a, 0x55, 0x11, 0x48, 0x18, 0x02, 0x41, 0x6a, 0x68, 0xd8, 0x34, - 0x42, 0x42, 0xaa, 0xac, 0xb1, 0x7d, 0x70, 0x56, 0xc9, 0xee, 0x3a, 0x3b, 0xe3, 0x50, 0xe7, 0xaa, - 0x37, 0xbd, 0xe9, 0x75, 0xef, 0xab, 0xfe, 0x9a, 0xaa, 0xff, 0xa2, 0xff, 0xa2, 0xb7, 0xd5, 0x7c, - 0xec, 0x7a, 0x37, 0xde, 0x5d, 0x4c, 0xe0, 0xca, 0x3b, 0x67, 0xce, 0xe7, 0x73, 0x66, 0x9e, 0x39, - 0x86, 0xa5, 0x21, 0xe5, 0xf8, 0x8e, 0x4e, 0x5a, 0x23, 0xdf, 0xe3, 0x1e, 0xa9, 0x8f, 0x7b, 0xe8, - 0xb7, 0xd8, 0xb8, 0xe7, 0xd8, 0xfc, 0x6c, 0x8c, 0x63, 0x6c, 0xe9, 0xfd, 0x86, 0x49, 0x47, 0x76, - 0xbb, 0x47, 0x19, 0xb6, 0xfb, 0xc7, 0xd4, 0x1d, 0x62, 0x5b, 0x1a, 0xe8, 0x85, 0xb2, 0x6e, 0xdc, - 0x09, 0x75, 0x1c, 0xf4, 0x87, 0xc8, 0xb8, 0x4f, 0x39, 0x0e, 0x27, 0x5a, 0x35, 0x26, 0x53, 0x16, - 0xe6, 0x2d, 0xa8, 0x1e, 0xd8, 0xee, 0xd0, 0xc2, 0xb3, 0x31, 0x32, 0x4e, 0xea, 0xb0, 0xe8, 0x20, - 0x63, 0x74, 0x88, 0x75, 0x63, 0xd3, 0x68, 0x56, 0xac, 0x60, 0x69, 0xfe, 0x6a, 0x40, 0x4d, 0x69, - 0xb2, 0x91, 0xe7, 0x32, 0x4c, 0x57, 0x25, 0x5b, 0x50, 0x63, 0xe8, 0x9f, 0xdb, 0x7d, 0xec, 0xba, - 0xd4, 0xc1, 0x7a, 0x4e, 0x6e, 0x57, 0xb5, 0xec, 0x25, 0x75, 0x90, 0xac, 0x43, 0x85, 0xdb, 0x0e, - 0x32, 0x4e, 0x9d, 0x51, 0x3d, 0xbf, 0x69, 0x34, 0xf3, 0xd6, 0x54, 0x40, 0x1a, 0x50, 0x3e, 0xf6, - 0x18, 0x97, 0xc6, 0x05, 0x69, 0x1c, 0xae, 0xcd, 0xdf, 0x0d, 0xa8, 0x7e, 0x4f, 0xdd, 0x41, 0x90, - 0xf1, 0x2a, 0x14, 0x25, 0x4e, 0x3a, 0x09, 0xb5, 0x20, 0x77, 0xa0, 0xa4, 0x80, 0x91, 0xc1, 0xab, - 0x3b, 0xf5, 0x96, 0xc4, 0x55, 0x40, 0xd3, 0xd2, 0x88, 0x3d, 0x91, 0x3f, 0x96, 0xd6, 0x23, 0x0f, - 0xa1, 0x1c, 0x40, 0x23, 0x63, 0x2e, 0xef, 0x6c, 0x45, 0x6c, 0xe2, 0xd0, 0x1d, 0xea, 0x0f, 0x2b, - 0x34, 0x31, 0x4d, 0xa8, 0xa9, 0xac, 0x34, 0x3a, 0x04, 0x0a, 0xec, 0xcc, 0x1e, 0xe8, 0xac, 0xe4, - 0xb7, 0xf9, 0x0a, 0x96, 0x9e, 0x50, 0xb7, 0x8f, 0xa7, 0x41, 0xee, 0x09, 0x4a, 0x64, 0x0d, 0x4a, - 0x3e, 0x52, 0xe6, 0xb9, 0x1a, 0x36, 0xbd, 0x9a, 0xd6, 0x99, 0x8f, 0xd4, 0x69, 0xae, 0xc0, 0x72, - 0xe0, 0x52, 0x05, 0x36, 0xff, 0xce, 0xc1, 0xb2, 0xf6, 0x7f, 0x38, 0x76, 0x1c, 0xea, 0x4f, 0x12, - 0xc3, 0x84, 0xee, 0x72, 0x51, 0xd8, 0x6e, 0x40, 0x55, 0xc1, 0xd1, 0x1d, 0xfb, 0x36, 0xab, 0xe7, - 0x37, 0xf3, 0xcd, 0x8a, 0x05, 0x4a, 0x74, 0xe4, 0xdb, 0x8c, 0xdc, 0x84, 0x65, 0x1f, 0xfb, 0x68, - 0x9f, 0xe3, 0xa0, 0x4b, 0x79, 0xd7, 0x61, 0x12, 0xab, 0xbc, 0x55, 0x0b, 0xa4, 0x8f, 0xf9, 0x3e, - 0x13, 0x35, 0x30, 0x4e, 0xf9, 0x98, 0xd5, 0x8b, 0xaa, 0x06, 0xb5, 0x22, 0x1b, 0x00, 0xa7, 0x94, - 0xf1, 0x2e, 0xfa, 0xbe, 0xe7, 0xd7, 0x4b, 0x72, 0xaf, 0x22, 0x24, 0xbb, 0x42, 0x40, 0x2c, 0x28, - 0x3b, 0xc8, 0xe9, 0x80, 0x72, 0x5a, 0x5f, 0xdc, 0xcc, 0x37, 0xab, 0x3b, 0xf7, 0x5a, 0x69, 0xd7, - 0xa1, 0x15, 0xaf, 0xb1, 0xb5, 0xaf, 0x0d, 0x77, 0x5d, 0xee, 0x4f, 0xac, 0xd0, 0x4f, 0xe3, 0x01, - 0x2c, 0xc5, 0xb6, 0xc8, 0x0a, 0xe4, 0x4f, 0x70, 0xa2, 0xb1, 0x10, 0x9f, 0x02, 0x8a, 0x73, 0x7a, - 0x3a, 0x85, 0x42, 0x2e, 0xbe, 0xcb, 0x7d, 0x6b, 0x98, 0x7b, 0xb0, 0xfe, 0x1c, 0x79, 0x3c, 0x52, - 0x67, 0xf2, 0xe2, 0x69, 0x56, 0xff, 0x12, 0x81, 0x35, 0xfb, 0xb0, 0x91, 0xe2, 0x49, 0x9f, 0x97, - 0x0e, 0x2c, 0xfa, 0x6a, 0x57, 0x7a, 0xab, 0xee, 0x34, 0xe7, 0x2d, 0xdd, 0x0a, 0x0c, 0xcd, 0x37, - 0xf0, 0x45, 0x42, 0x10, 0x75, 0xce, 0x8f, 0xac, 0x17, 0x41, 0xd6, 0x1b, 0x00, 0xd3, 0x26, 0xeb, - 0xdc, 0x2b, 0x61, 0x8f, 0x53, 0x0a, 0x38, 0x85, 0x9b, 0xd9, 0xbe, 0x75, 0x1d, 0x4f, 0xa1, 0xac, - 0xd3, 0x61, 0x75, 0x43, 0xf6, 0x70, 0xfe, 0x42, 0x42, 0x4b, 0xf3, 0x5f, 0x71, 0xc9, 0x6d, 0xc6, - 0xb3, 0x2f, 0xf9, 0x3d, 0xf8, 0x3c, 0x7a, 0x18, 0x3d, 0xbf, 0x4b, 0xdf, 0x72, 0xf4, 0xc5, 0xa9, - 0x14, 0xb9, 0xe7, 0xf7, 0x16, 0xac, 0x6b, 0xd3, 0x73, 0xf9, 0x83, 0xff, 0x58, 0xec, 0xee, 0x33, - 0xd2, 0x02, 0x12, 0xda, 0xf5, 0xf0, 0xad, 0xe7, 0xa3, 0x30, 0x91, 0x2c, 0xb4, 0x67, 0x58, 0x2b, - 0xc1, 0x5e, 0x47, 0x6e, 0xed, 0x33, 0x72, 0x1d, 0x2a, 0x23, 0x3a, 0xc4, 0x2e, 0xb3, 0x2f, 0x14, - 0x1f, 0x15, 0xad, 0xb2, 0x10, 0x1c, 0xda, 0x17, 0x28, 0xd0, 0x94, 0x9b, 0xdc, 0x3b, 0x41, 0x57, - 0x9f, 0x77, 0xa9, 0xfe, 0xa3, 0x10, 0x74, 0xd6, 0x60, 0x35, 0x8c, 0x75, 0xea, 0xbd, 0x43, 0xbf, - 0xdb, 0xf3, 0xc6, 0xee, 0x20, 0x26, 0x1f, 0x8f, 0x46, 0x81, 0xdc, 0xfc, 0xc7, 0x80, 0x9a, 0xaa, - 0xfc, 0x53, 0x02, 0x4a, 0xb6, 0xe1, 0x7f, 0x2e, 0xfe, 0xcc, 0xbb, 0x91, 0x54, 0x55, 0x7b, 0x97, - 0x84, 0xf8, 0x20, 0x48, 0x97, 0xdc, 0x4d, 0x87, 0x54, 0xb1, 0x74, 0x22, 0xa0, 0xb7, 0x13, 0x01, - 0x55, 0xcc, 0x30, 0x03, 0xa7, 0x79, 0x1d, 0x8a, 0xaf, 0x64, 0xff, 0x08, 0x14, 0x24, 0xc5, 0xeb, - 0xeb, 0x23, 0xe9, 0xfd, 0x1a, 0xfc, 0x5f, 0x94, 0x2f, 0x15, 0x98, 0x2e, 0xc7, 0xdc, 0x07, 0x12, - 0x15, 0x6a, 0x64, 0xee, 0x43, 0x49, 0x56, 0x1f, 0xe0, 0x72, 0x23, 0x1d, 0x17, 0x69, 0x69, 0x69, - 0xf5, 0xf8, 0xb5, 0xde, 0xb3, 0x19, 0xf7, 0xae, 0x7a, 0xad, 0xff, 0xcc, 0x41, 0x4d, 0x3b, 0xd8, - 0x3d, 0x47, 0x97, 0x8b, 0xa7, 0x2f, 0x7c, 0xc6, 0x04, 0x06, 0x86, 0xc4, 0xa0, 0x1a, 0xca, 0x62, - 0xe4, 0x98, 0xcb, 0x20, 0xc7, 0xfc, 0x65, 0x72, 0x3c, 0x88, 0x90, 0x63, 0x41, 0xd6, 0x7b, 0x37, - 0xbd, 0xde, 0x68, 0x4e, 0x69, 0xd4, 0x28, 0xca, 0xe4, 0x93, 0x11, 0xea, 0x33, 0x2b, 0xbf, 0x45, - 0x99, 0x28, 0x8c, 0x34, 0x39, 0xab, 0xc5, 0xc7, 0x91, 0x68, 0x37, 0x4a, 0x7d, 0x31, 0xb4, 0x75, - 0x1f, 0x1f, 0x41, 0x49, 0x86, 0x09, 0xfa, 0xb8, 0x3d, 0x5f, 0x5d, 0x96, 0xb6, 0x8a, 0xd3, 0x5e, - 0x18, 0xe0, 0xd3, 0xd0, 0xde, 0x20, 0x7c, 0x4c, 0xb5, 0xe3, 0xc4, 0xc3, 0x31, 0xad, 0x20, 0x77, - 0xa5, 0x0a, 0xdc, 0x28, 0xb9, 0x26, 0x55, 0xa0, 0x91, 0x7a, 0x06, 0x95, 0x63, 0xb9, 0x6b, 0xe3, - 0xfc, 0x64, 0xa0, 0xfd, 0x59, 0x53, 0x53, 0x73, 0x0b, 0x8a, 0xea, 0x50, 0xa5, 0x8f, 0x7b, 0x08, - 0x6b, 0x47, 0xae, 0x8f, 0x7d, 0x6f, 0xe8, 0xda, 0x17, 0x38, 0x90, 0x17, 0x48, 0xd9, 0x7c, 0x03, - 0x45, 0x75, 0x44, 0xd5, 0x3b, 0x95, 0x71, 0xeb, 0xa4, 0xbe, 0xa5, 0xb4, 0x53, 0xf0, 0xfd, 0xc5, - 0x80, 0x55, 0x9d, 0xe7, 0x4b, 0x8f, 0x3f, 0x13, 0x14, 0xf8, 0x51, 0x51, 0x82, 0xee, 0xe4, 0x22, - 0xdd, 0x89, 0x37, 0x3e, 0x7f, 0xa9, 0xf1, 0x3b, 0x7f, 0x95, 0x81, 0x1c, 0x4a, 0xbf, 0xb2, 0xc8, - 0xe7, 0xca, 0x2d, 0x79, 0x0d, 0x05, 0x31, 0xee, 0x92, 0x2f, 0xd3, 0x23, 0x47, 0x06, 0xe7, 0xc6, - 0xf6, 0xfb, 0xd4, 0xf4, 0x78, 0xb6, 0x40, 0x6c, 0x80, 0x29, 0x99, 0x91, 0xaf, 0xd2, 0xed, 0x66, - 0x78, 0xb0, 0x71, 0x7b, 0x3e, 0xe5, 0x30, 0xd4, 0x6b, 0x28, 0x88, 0xa1, 0x34, 0xab, 0x86, 0xc8, - 0x28, 0x9d, 0x55, 0x43, 0x74, 0xb6, 0x35, 0x17, 0xc8, 0x4f, 0x50, 0x52, 0x63, 0x27, 0xb9, 0x95, - 0x6e, 0x13, 0x9b, 0x75, 0x1b, 0xcd, 0xf7, 0x2b, 0x86, 0xee, 0x7f, 0x33, 0xe0, 0xb3, 0xc4, 0x71, - 0x89, 0x64, 0x0c, 0x84, 0x59, 0x93, 0x5a, 0xe3, 0xfe, 0x07, 0xdb, 0x85, 0xc9, 0xfc, 0x61, 0x24, - 0x4e, 0x81, 0xe1, 0xed, 0x24, 0x0f, 0x3f, 0xc8, 0xf7, 0x65, 0x5e, 0x6a, 0x3c, 0xba, 0xaa, 0x79, - 0xac, 0xcd, 0x36, 0xe3, 0x99, 0x6d, 0x9e, 0x0e, 0x53, 0x99, 0x6d, 0x8e, 0x4c, 0x1e, 0x33, 0x7d, - 0x88, 0x70, 0xf7, 0x7c, 0x7d, 0x98, 0x7d, 0x5a, 0xe7, 0xeb, 0x43, 0xc2, 0x23, 0x31, 0xd3, 0x87, - 0x59, 0x96, 0x9c, 0xaf, 0x0f, 0xa9, 0xef, 0xc3, 0x7c, 0x7d, 0x48, 0x27, 0x67, 0x73, 0xa1, 0x73, - 0x02, 0xeb, 0x7d, 0xcf, 0x49, 0x75, 0xd3, 0xa9, 0x69, 0x6e, 0x39, 0x10, 0xff, 0xbc, 0x0f, 0x8c, - 0x37, 0x0f, 0x86, 0x36, 0x3f, 0x1e, 0xf7, 0x5a, 0x7d, 0xcf, 0x69, 0x0b, 0xa3, 0x76, 0xc4, 0xa8, - 0x2d, 0xfe, 0xc9, 0x47, 0xd7, 0xda, 0x89, 0xfa, 0x2b, 0x3f, 0xea, 0xf5, 0x4a, 0xf2, 0xe3, 0xeb, - 0xff, 0x02, 0x00, 0x00, 0xff, 0xff, 0x9c, 0x8d, 0x26, 0x2d, 0x40, 0x10, 0x00, 0x00, + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x58, 0x5f, 0x6f, 0x13, 0x47, + 0x10, 0xcf, 0xf9, 0x1f, 0xf6, 0xd8, 0x49, 0xd3, 0x25, 0x4d, 0x4f, 0x26, 0x11, 0xc9, 0x95, 0x06, + 0x4b, 0x45, 0x36, 0x4a, 0x29, 0x54, 0x45, 0x20, 0x61, 0x08, 0x04, 0x89, 0xd0, 0x70, 0x69, 0x84, + 0x84, 0x54, 0x59, 0x6b, 0x7b, 0x70, 0x4e, 0xc9, 0xdd, 0x39, 0xb7, 0x7b, 0xa1, 0xce, 0x53, 0x5f, + 0xfa, 0xd2, 0xd7, 0xf6, 0xbd, 0xea, 0xa7, 0xe9, 0xd7, 0xe8, 0x17, 0xe8, 0x77, 0xa8, 0xf6, 0xcf, + 0x9d, 0xef, 0x62, 0x9f, 0x31, 0x81, 0xa7, 0xdc, 0xce, 0xce, 0xcc, 0xce, 0xfc, 0x66, 0xe7, 0xb7, + 0x13, 0xc3, 0xe2, 0x80, 0x72, 0x7c, 0x47, 0x47, 0xcd, 0x61, 0xe0, 0x73, 0x9f, 0x98, 0x61, 0x17, + 0x83, 0x26, 0x0b, 0xbb, 0xae, 0xc3, 0x4f, 0x43, 0x0c, 0xb1, 0xa9, 0xf7, 0xeb, 0x16, 0x1d, 0x3a, + 0xad, 0x2e, 0x65, 0xd8, 0xea, 0x1d, 0x51, 0x6f, 0x80, 0x2d, 0x69, 0xa0, 0x17, 0xca, 0xba, 0x7e, + 0x3b, 0xd6, 0x71, 0x31, 0x18, 0x20, 0xe3, 0x01, 0xe5, 0x38, 0x18, 0x69, 0xd5, 0x94, 0x4c, 0x59, + 0x58, 0x37, 0xa1, 0xba, 0xef, 0x78, 0x03, 0x1b, 0x4f, 0x43, 0x64, 0x9c, 0x98, 0x70, 0xc5, 0x45, + 0xc6, 0xe8, 0x00, 0x4d, 0x63, 0xc3, 0x68, 0x54, 0xec, 0x68, 0x69, 0xfd, 0x66, 0x40, 0x4d, 0x69, + 0xb2, 0xa1, 0xef, 0x31, 0xcc, 0x56, 0x25, 0x9b, 0x50, 0x63, 0x18, 0x9c, 0x39, 0x3d, 0xec, 0x78, + 0xd4, 0x45, 0x33, 0x27, 0xb7, 0xab, 0x5a, 0xf6, 0x92, 0xba, 0x48, 0xd6, 0xa0, 0xc2, 0x1d, 0x17, + 0x19, 0xa7, 0xee, 0xd0, 0xcc, 0x6f, 0x18, 0x8d, 0xbc, 0x3d, 0x16, 0x90, 0x3a, 0x94, 0x8f, 0x7c, + 0xc6, 0xa5, 0x71, 0x41, 0x1a, 0xc7, 0x6b, 0xeb, 0x4f, 0x03, 0xaa, 0x2f, 0xa8, 0xd7, 0x8f, 0x22, + 0x5e, 0x81, 0xa2, 0xc4, 0x49, 0x07, 0xa1, 0x16, 0xe4, 0x36, 0x94, 0x14, 0x30, 0xf2, 0xf0, 0xea, + 0xb6, 0xd9, 0x94, 0xb8, 0x0a, 0x68, 0x9a, 0x1a, 0xb1, 0xc7, 0xf2, 0x8f, 0xad, 0xf5, 0xc8, 0x03, + 0x28, 0x47, 0xd0, 0xc8, 0x33, 0x97, 0xb6, 0x37, 0x13, 0x36, 0x69, 0xe8, 0x0e, 0xf4, 0x87, 0x1d, + 0x9b, 0x58, 0x16, 0xd4, 0x54, 0x54, 0x1a, 0x1d, 0x02, 0x05, 0x76, 0xea, 0xf4, 0x75, 0x54, 0xf2, + 0xdb, 0x7a, 0x05, 0x8b, 0x8f, 0xa9, 0xd7, 0xc3, 0x93, 0x28, 0xf6, 0x29, 0x4a, 0x64, 0x15, 0x4a, + 0x01, 0x52, 0xe6, 0x7b, 0x1a, 0x36, 0xbd, 0x1a, 0xe7, 0x99, 0x4f, 0xe4, 0x69, 0x2d, 0xc3, 0x52, + 0xe4, 0x52, 0x1d, 0x6c, 0xfd, 0x97, 0x83, 0x25, 0xed, 0xff, 0x20, 0x74, 0x5d, 0x1a, 0x8c, 0xa6, + 0x1e, 0x13, 0xbb, 0xcb, 0x25, 0x61, 0xbb, 0x0e, 0x55, 0x05, 0x47, 0x27, 0x0c, 0x1c, 0x66, 0xe6, + 0x37, 0xf2, 0x8d, 0x8a, 0x0d, 0x4a, 0x74, 0x18, 0x38, 0x8c, 0xdc, 0x80, 0xa5, 0x00, 0x7b, 0xe8, + 0x9c, 0x61, 0xbf, 0x43, 0x79, 0xc7, 0x65, 0x12, 0xab, 0xbc, 0x5d, 0x8b, 0xa4, 0x8f, 0xf8, 0x1e, + 0x13, 0x39, 0x30, 0x4e, 0x79, 0xc8, 0xcc, 0xa2, 0xca, 0x41, 0xad, 0xc8, 0x3a, 0xc0, 0x09, 0x65, + 0xbc, 0x83, 0x41, 0xe0, 0x07, 0x66, 0x49, 0xee, 0x55, 0x84, 0x64, 0x47, 0x08, 0x88, 0x0d, 0x65, + 0x17, 0x39, 0xed, 0x53, 0x4e, 0xcd, 0x2b, 0x1b, 0xf9, 0x46, 0x75, 0xfb, 0x6e, 0x33, 0xab, 0x1d, + 0x9a, 0xe9, 0x1c, 0x9b, 0x7b, 0xda, 0x70, 0xc7, 0xe3, 0xc1, 0xc8, 0x8e, 0xfd, 0x10, 0x0b, 0x16, + 0xc3, 0x61, 0x9f, 0xf2, 0x38, 0xde, 0xb2, 0x8c, 0xb7, 0xaa, 0x85, 0x22, 0xdc, 0xfa, 0x7d, 0x58, + 0x4c, 0x99, 0x93, 0x65, 0xc8, 0x1f, 0xe3, 0x48, 0xe3, 0x25, 0x3e, 0x05, 0x5c, 0x67, 0xf4, 0x64, + 0x0c, 0x97, 0x5c, 0xfc, 0x90, 0xfb, 0xde, 0xb0, 0x76, 0x61, 0xed, 0x19, 0xf2, 0x74, 0x34, 0xed, + 0xd1, 0xf3, 0x27, 0xb3, 0x6a, 0x3c, 0x15, 0x7c, 0xab, 0x07, 0xeb, 0x19, 0x9e, 0xf4, 0x9d, 0x6a, + 0xc3, 0x95, 0x40, 0xed, 0x4a, 0x6f, 0xd5, 0xed, 0xc6, 0xbc, 0xf0, 0xd8, 0x91, 0xa1, 0xf5, 0x06, + 0xbe, 0x9a, 0x72, 0x88, 0xea, 0x85, 0x43, 0xfb, 0x79, 0x14, 0xf5, 0x3a, 0xc0, 0xf8, 0x22, 0xe8, + 0xd8, 0x2b, 0xf1, 0x3d, 0xc8, 0x48, 0xe0, 0x04, 0x6e, 0xcc, 0xf6, 0xad, 0xf3, 0x78, 0x02, 0x65, + 0x1d, 0x0e, 0x33, 0x0d, 0x59, 0xe7, 0xf9, 0x13, 0x89, 0x2d, 0xad, 0x3f, 0x72, 0x50, 0x7d, 0xe1, + 0x30, 0x3e, 0x9b, 0x08, 0xee, 0xc2, 0x97, 0xc9, 0x0b, 0xeb, 0x07, 0x1d, 0xfa, 0x96, 0x63, 0x20, + 0x6e, 0x82, 0x88, 0x3d, 0xbf, 0xbb, 0x60, 0x5f, 0x1d, 0xdf, 0xdd, 0x1f, 0x83, 0x47, 0x62, 0x77, + 0x8f, 0x91, 0x26, 0x90, 0xd8, 0xae, 0x8b, 0x6f, 0xfd, 0x00, 0x85, 0x89, 0x64, 0xaa, 0x5d, 0xc3, + 0x5e, 0x8e, 0xf6, 0xda, 0x72, 0x6b, 0x8f, 0x91, 0x6b, 0x50, 0x19, 0xd2, 0x01, 0x76, 0x98, 0x73, + 0xae, 0x38, 0xab, 0x68, 0x97, 0x85, 0xe0, 0xc0, 0x39, 0x47, 0x81, 0xa6, 0xdc, 0xe4, 0xfe, 0x31, + 0x7a, 0xba, 0x27, 0xa4, 0xfa, 0x4f, 0x42, 0x20, 0xe8, 0x4e, 0x35, 0x08, 0x32, 0xb3, 0x24, 0x5b, + 0x2e, 0x5e, 0xb7, 0x57, 0x61, 0x25, 0x8e, 0xe3, 0xc4, 0x7f, 0x87, 0x41, 0xa7, 0xeb, 0x87, 0x5e, + 0x3f, 0x25, 0x0f, 0x87, 0xc3, 0x48, 0x6e, 0xfd, 0x6b, 0x40, 0x4d, 0xa1, 0xf2, 0x29, 0xc1, 0x26, + 0x5b, 0xf0, 0x99, 0x87, 0xbf, 0xf0, 0x4e, 0x22, 0x0d, 0x55, 0xfa, 0x45, 0x21, 0xde, 0x8f, 0x53, + 0xb9, 0x93, 0x0d, 0xb7, 0x62, 0xf9, 0xa9, 0x60, 0xdf, 0x9a, 0x0a, 0xb6, 0x62, 0x96, 0x09, 0xa8, + 0xad, 0x6b, 0x50, 0x7c, 0x25, 0x6b, 0x4b, 0xa0, 0x20, 0x9f, 0x08, 0xdd, 0x5a, 0xf2, 0x79, 0xb8, + 0x0a, 0x9f, 0x8b, 0xf4, 0xa5, 0x02, 0xd3, 0xe9, 0x58, 0x7b, 0x40, 0x92, 0x42, 0x8d, 0xcc, 0x3d, + 0x28, 0xc9, 0xec, 0x23, 0x5c, 0xae, 0x67, 0xe3, 0x22, 0x2d, 0x6d, 0xad, 0x9e, 0x6e, 0xf9, 0x5d, + 0x87, 0x71, 0xff, 0xb2, 0x2d, 0xff, 0x77, 0x0e, 0x6a, 0xda, 0xc1, 0xce, 0x19, 0x7a, 0x5c, 0x3c, + 0x9d, 0xf1, 0x33, 0x28, 0x30, 0x30, 0x14, 0x5b, 0xc5, 0xb2, 0x14, 0xb9, 0xe6, 0x66, 0x90, 0x6b, + 0xfe, 0x22, 0xb9, 0xee, 0x27, 0xc8, 0xb5, 0x20, 0xf3, 0xbd, 0x93, 0x9d, 0x6f, 0x32, 0xa6, 0x4c, + 0x6a, 0x25, 0x50, 0xe0, 0xa3, 0x21, 0xea, 0xfb, 0x2c, 0xbf, 0x45, 0x9a, 0x28, 0x8c, 0x34, 0xb9, + 0xab, 0xc5, 0xc7, 0x11, 0x6c, 0x27, 0x49, 0x8b, 0x29, 0xb4, 0x75, 0x1d, 0x1f, 0x42, 0x49, 0x1e, + 0x13, 0xd5, 0x71, 0x6b, 0xbe, 0xbc, 0x6c, 0x6d, 0x95, 0xa6, 0xc4, 0xf8, 0x80, 0x4f, 0x43, 0x89, + 0xfd, 0xf8, 0x31, 0xd6, 0x8e, 0xa7, 0x5e, 0x8e, 0x71, 0x06, 0xb9, 0x4b, 0x65, 0xe0, 0x25, 0x89, + 0x77, 0x5a, 0x06, 0x1a, 0xa9, 0xa7, 0x50, 0x39, 0x92, 0xbb, 0x0e, 0xce, 0x4f, 0x06, 0xda, 0x9f, + 0x3d, 0x36, 0xb5, 0x36, 0xa1, 0xa8, 0x2e, 0x55, 0xf6, 0xb8, 0x88, 0xb0, 0x7a, 0xe8, 0x05, 0xd8, + 0xf3, 0x07, 0x9e, 0x73, 0x8e, 0x7d, 0xd9, 0x40, 0xca, 0xe6, 0x3b, 0x28, 0xaa, 0x2b, 0xaa, 0xde, + 0xb0, 0x19, 0x5d, 0x27, 0xf5, 0x6d, 0xa5, 0x9d, 0x81, 0xef, 0xaf, 0x06, 0xac, 0xe8, 0x38, 0x5f, + 0xfa, 0xfc, 0xa9, 0xa0, 0xc0, 0x8f, 0x3a, 0x25, 0xaa, 0x4e, 0x2e, 0x51, 0x9d, 0x74, 0xe1, 0xf3, + 0x17, 0x0a, 0xbf, 0xfd, 0x4f, 0x19, 0xc8, 0x81, 0xf4, 0x2b, 0x93, 0x7c, 0xa6, 0xdc, 0x92, 0xd7, + 0x50, 0x10, 0xe3, 0x32, 0xf9, 0x3a, 0xfb, 0xe4, 0xc4, 0xe0, 0x5d, 0xdf, 0x7a, 0x9f, 0x9a, 0x1e, + 0xef, 0x16, 0x88, 0x03, 0x30, 0x26, 0x33, 0xf2, 0x4d, 0xb6, 0xdd, 0x04, 0x0f, 0xd6, 0x6f, 0xcd, + 0xa7, 0x1c, 0x1f, 0xf5, 0x1a, 0x0a, 0x62, 0xa8, 0x9d, 0x95, 0x43, 0x62, 0x14, 0x9f, 0x95, 0x43, + 0x72, 0x36, 0xb6, 0x16, 0xc8, 0xcf, 0x50, 0x52, 0x63, 0x2b, 0xb9, 0x99, 0x6d, 0x93, 0x9a, 0x95, + 0xeb, 0x8d, 0xf7, 0x2b, 0xc6, 0xee, 0x7f, 0x37, 0xe0, 0x8b, 0xa9, 0xa3, 0x14, 0x99, 0x31, 0x50, + 0xce, 0x9a, 0xe2, 0xea, 0xf7, 0x3e, 0xd8, 0x2e, 0x0e, 0xe6, 0x2f, 0x63, 0xea, 0x84, 0x18, 0x77, + 0x27, 0x79, 0xf0, 0x41, 0xbe, 0x2f, 0xf2, 0x52, 0xfd, 0xe1, 0x65, 0xcd, 0x53, 0x65, 0x76, 0x18, + 0x9f, 0x59, 0xe6, 0xf1, 0xa0, 0x35, 0xb3, 0xcc, 0x89, 0xc9, 0x63, 0xa2, 0x0e, 0x09, 0xee, 0x9e, + 0xaf, 0x0e, 0x93, 0x4f, 0xeb, 0x7c, 0x75, 0x98, 0xf2, 0x48, 0x4c, 0xd4, 0x61, 0x92, 0x25, 0xe7, + 0xab, 0x43, 0xe6, 0xfb, 0x30, 0x5f, 0x1d, 0xb2, 0xc9, 0xd9, 0x5a, 0x68, 0x1f, 0xc3, 0x5a, 0xcf, + 0x77, 0x33, 0xdd, 0xb4, 0x6b, 0x9a, 0x5b, 0xf6, 0xc5, 0x7f, 0xee, 0xfb, 0xc6, 0x9b, 0xfb, 0x03, + 0x87, 0x1f, 0x85, 0xdd, 0x66, 0xcf, 0x77, 0x5b, 0xc2, 0xa8, 0x95, 0x30, 0x6a, 0xd1, 0xa1, 0x93, + 0x5a, 0x6b, 0x27, 0xea, 0xa7, 0x80, 0x61, 0xb7, 0x5b, 0x92, 0x1f, 0xdf, 0xfe, 0x1f, 0x00, 0x00, + 0xff, 0xff, 0xa4, 0xbc, 0xb8, 0xf7, 0x80, 0x10, 0x00, 0x00, }, // api/base/change/proto/change.proto []byte{ diff --git a/service/submitqueue/gateway/server/mapper/list.go b/service/submitqueue/gateway/server/mapper/list.go index f16175578..447b23d00 100644 --- a/service/submitqueue/gateway/server/mapper/list.go +++ b/service/submitqueue/gateway/server/mapper/list.go @@ -29,9 +29,21 @@ func ProtoToListRequest(req *pb.ListRequest) entity.ListRequest { HasReceivedBeforeMs: req.GetReceivedUpperBound() != nil, PageSize: req.GetPageSize(), PageToken: req.GetPageToken(), + Statuses: protoToRequestStatuses(req.GetStatuses()), } } +func protoToRequestStatuses(values []string) []entity.RequestStatus { + if len(values) == 0 { + return nil + } + statuses := make([]entity.RequestStatus, len(values)) + for i, value := range values { + statuses[i] = entity.RequestStatus(value) + } + return statuses +} + // ListResultToProto maps the domain result to the wire response. func ListResultToProto(result entity.ListResult) *pb.ListResponse { return &pb.ListResponse{ diff --git a/service/submitqueue/gateway/server/mapper/list_test.go b/service/submitqueue/gateway/server/mapper/list_test.go index 721dc35cf..210e989a6 100644 --- a/service/submitqueue/gateway/server/mapper/list_test.go +++ b/service/submitqueue/gateway/server/mapper/list_test.go @@ -39,6 +39,11 @@ func TestProtoToListRequest(t *testing.T) { }, want: entity.ListRequest{HasReceivedAtOrAfterMs: true, HasReceivedBeforeMs: true}, }, + { + name: "status filter", + req: &pb.ListRequest{Queue: "q", Statuses: []string{"landed", "error"}}, + want: entity.ListRequest{Queue: "q", Statuses: []entity.RequestStatus{entity.RequestStatusLanded, entity.RequestStatusError}}, + }, { name: "explicit window and pagination", req: &pb.ListRequest{ diff --git a/service/submitqueue/gateway/server/mapper/request_summary.go b/service/submitqueue/gateway/server/mapper/request_summary.go index bb7f6eda7..b7e72fc32 100644 --- a/service/submitqueue/gateway/server/mapper/request_summary.go +++ b/service/submitqueue/gateway/server/mapper/request_summary.go @@ -45,6 +45,7 @@ func RequestSummaryToProto(summary entity.RequestSummary) *pb.RequestSummary { Status: string(summary.Status), LastError: summary.LastError, Metadata: cloneStringMap(summary.Metadata), + UpdatedAtMs: summary.StatusTimestampMs, } } diff --git a/service/submitqueue/gateway/server/mapper/request_summary_test.go b/service/submitqueue/gateway/server/mapper/request_summary_test.go index 3c9e838bd..d336b240b 100644 --- a/service/submitqueue/gateway/server/mapper/request_summary_test.go +++ b/service/submitqueue/gateway/server/mapper/request_summary_test.go @@ -38,13 +38,14 @@ func TestProtoToGetRequestSummaryByChangeURIRequest(t *testing.T) { func TestRequestSummaryToProto(t *testing.T) { summary := entity.RequestSummary{ - RequestID: "42", - Queue: "test-queue", - ChangeURIs: []string{"github://uber/repo/pull/1/abc"}, - ReceivedAtMs: 100, - Status: entity.RequestStatusValidating, - LastError: "validation failed", - Metadata: map[string]string{"step": "lint"}, + RequestID: "42", + Queue: "test-queue", + ChangeURIs: []string{"github://uber/repo/pull/1/abc"}, + ReceivedAtMs: 100, + Status: entity.RequestStatusValidating, + LastError: "validation failed", + Metadata: map[string]string{"step": "lint"}, + StatusTimestampMs: 250, } assert.Equal(t, &pb.RequestSummary{ @@ -55,6 +56,7 @@ func TestRequestSummaryToProto(t *testing.T) { Status: string(entity.RequestStatusValidating), LastError: "validation failed", Metadata: map[string]string{"step": "lint"}, + UpdatedAtMs: 250, }, RequestSummaryToProto(summary)) } diff --git a/submitqueue/entity/list.go b/submitqueue/entity/list.go index d867b7de0..b7efce90c 100644 --- a/submitqueue/entity/list.go +++ b/submitqueue/entity/list.go @@ -28,8 +28,10 @@ type ListRequest struct { HasReceivedBeforeMs bool // PageSize is the maximum number of results to return. Zero selects the server default. PageSize int32 - // PageToken is an opaque continuation token from a previous result, bound to its queue and receipt window. + // PageToken is an opaque continuation token from a previous result, bound to its queue, receipt window, and statuses. PageToken string + // Statuses are the current statuses to include; empty includes every status. + Statuses []RequestStatus } // ListResult contains one page of queue receipt history. diff --git a/submitqueue/gateway/controller/list.go b/submitqueue/gateway/controller/list.go index 1587323f9..684669825 100644 --- a/submitqueue/gateway/controller/list.go +++ b/submitqueue/gateway/controller/list.go @@ -18,6 +18,7 @@ import ( "context" "errors" "fmt" + "slices" "github.com/uber-go/tally" "github.com/uber/submitqueue/platform/errs" @@ -32,6 +33,10 @@ import ( const ( defaultListPageSize = 50 maxListPageSize = 200 + // maxListStatuses bounds the filter a page token must carry. + maxListStatuses = 32 + // maxFilteredListReceipts bounds the summary reads one filtered call spends looking for matches. + maxFilteredListReceipts = 500 ) // ListController handles bounded queue receipt-history queries. @@ -80,7 +85,7 @@ func (c *listController) List(ctx context.Context, req entity.ListRequest) (resu if pageSize < 0 || pageSize > maxListPageSize { return entity.ListResult{}, fmt.Errorf("page_size must be between 0 and %d: %w", maxListPageSize, ErrInvalidRequest) } - query, err := resolveListRange(req, pageSize) + query, statuses, err := resolveListRange(req) if err != nil { return entity.ListResult{}, err } @@ -90,34 +95,22 @@ func (c *listController) List(ctx context.Context, req entity.ListRequest) (resu return entity.ListResult{}, fmt.Errorf("failed to resolve storage for queue %q: %w", req.Queue, err) } - receipts, err := store.GetRequestReceiptStore().List(ctx, query) + scan, err := scanListPage(ctx, store, req.Queue, query, statuses, pageSize) if err != nil { - return entity.ListResult{}, fmt.Errorf("failed to list request receipts queue=%q: %w", req.Queue, err) + return entity.ListResult{}, err } - - visible := receipts[:min(len(receipts), pageSize)] result.ReceivedAtOrAfterMs = query.ReceivedAtOrAfterMs result.ReceivedBeforeMs = query.ReceivedBeforeMs - result.Requests = make([]entity.RequestSummary, 0, len(visible)) - if len(visible) > 0 { - summaries := store.GetRequestSummaryStore() - for _, receipt := range visible { - summary, err := readListSummary(ctx, summaries, req.Queue, receipt) - if err != nil { - return entity.ListResult{}, err - } - result.Requests = append(result.Requests, summary) - } - } - if len(receipts) > pageSize { - last := visible[len(visible)-1] + result.Requests = scan.requests + if scan.hasMore { token, err := encodeListPageToken(listPageToken{ Version: listPageTokenVersion, Queue: req.Queue, ReceivedAtOrAfterMs: query.ReceivedAtOrAfterMs, ReceivedBeforeMs: query.ReceivedBeforeMs, - LastReceivedAtMs: last.ReceivedAtMs, - LastRequestID: last.RequestID, + LastReceivedAtMs: scan.last.ReceivedAtMs, + LastRequestID: scan.last.RequestID, + Statuses: statusStrings(statuses), }) if err != nil { return entity.ListResult{}, fmt.Errorf("failed to encode page token queue=%q: %w", req.Queue, err) @@ -128,6 +121,54 @@ func (c *listController) List(ctx context.Context, req entity.ListRequest) (resu return result, nil } +type listPageScan struct { + requests []entity.RequestSummary + // last is the final receipt read, matched or not; a continuation resumes after it. + last entity.RequestReceipt + hasMore bool +} + +// scanListPage reads receipts newest first until pageSize summaries match, the range is exhausted, or the read +// budget is spent. Without a status filter the budget equals pageSize, so it is a single read of pageSize+1 receipts. +func scanListPage(ctx context.Context, store storage.Storage, queue string, query basestorage.RequestReceiptRange, statuses []entity.RequestStatus, pageSize int) (listPageScan, error) { + budget := pageSize + if len(statuses) > 0 { + budget = maxFilteredListReceipts + } + receipts := store.GetRequestReceiptStore() + summaries := store.GetRequestSummaryStore() + scan := listPageScan{requests: make([]entity.RequestSummary, 0, pageSize)} + scanned := 0 + for { + want := min(pageSize-len(scan.requests), budget-scanned) + // One receipt beyond want tells whether the range continues past this read. + query.Limit = want + 1 + batch, err := receipts.List(ctx, query) + if err != nil { + return listPageScan{}, fmt.Errorf("failed to list request receipts queue=%q: %w", queue, err) + } + for _, receipt := range batch[:min(len(batch), want)] { + summary, err := readListSummary(ctx, summaries, queue, receipt) + if err != nil { + return listPageScan{}, err + } + if len(statuses) == 0 || slices.Contains(statuses, summary.Status) { + scan.requests = append(scan.requests, summary) + } + scan.last = receipt + scanned++ + } + if len(batch) <= want { + return scan, nil + } + if len(scan.requests) == pageSize || scanned == budget { + scan.hasMore = true + return scan, nil + } + query.Before = basestorage.RequestReceiptCursor{ReceivedAtMs: scan.last.ReceivedAtMs, RequestID: scan.last.RequestID} + } +} + func readListSummary(ctx context.Context, summaries basestorage.RequestSummaryStore, queue string, receipt entity.RequestReceipt) (entity.RequestSummary, error) { if receipt.Queue != queue || receipt.ReceivedAtMs <= 0 || receipt.RequestID == "" { return entity.RequestSummary{}, &InternalConsistencyError{Message: fmt.Sprintf("invalid request receipt queue=%q request_id=%q", queue, receipt.RequestID)} diff --git a/submitqueue/gateway/controller/list_pagination.go b/submitqueue/gateway/controller/list_pagination.go index e28f15c1d..74c83ca69 100644 --- a/submitqueue/gateway/controller/list_pagination.go +++ b/submitqueue/gateway/controller/list_pagination.go @@ -20,6 +20,7 @@ import ( "encoding/json" "fmt" "net/url" + "slices" "strconv" "github.com/uber/submitqueue/submitqueue/entity" @@ -35,33 +36,74 @@ type listPageToken struct { ReceivedBeforeMs int64 `json:"received_before_ms"` LastReceivedAtMs int64 `json:"last_received_at_ms"` LastRequestID string `json:"last_request_id"` + // Statuses is the canonical status filter; absent in tokens minted without one. + Statuses []string `json:"statuses,omitempty"` } -// resolveListRange takes the window from the page token when one is supplied, so a continuation needs only the -// queue and the token; a bound the caller also supplies must match the token's. -func resolveListRange(req entity.ListRequest, pageSize int) (basestorage.RequestReceiptRange, error) { - query := basestorage.RequestReceiptRange{Limit: pageSize + 1} +// resolveListRange takes the window and status filter from the page token when one is supplied, so a continuation +// needs only the queue and the token; a bound or filter the caller also supplies must match the token's. +func resolveListRange(req entity.ListRequest) (basestorage.RequestReceiptRange, []entity.RequestStatus, error) { + statuses, ok := canonicalListStatuses(req.Statuses) + if !ok { + return basestorage.RequestReceiptRange{}, nil, fmt.Errorf("statuses must be non-empty and at most %d: %w", maxListStatuses, ErrInvalidRequest) + } + var query basestorage.RequestReceiptRange if req.PageToken != "" { token, err := decodeListPageToken(req.PageToken) if err != nil { - return basestorage.RequestReceiptRange{}, fmt.Errorf("invalid page token: %w", ErrInvalidRequest) + return basestorage.RequestReceiptRange{}, nil, fmt.Errorf("invalid page token: %w", ErrInvalidRequest) + } + tokenStatuses, ok := canonicalListStatuses(statusesFromStrings(token.Statuses)) + if !ok || !slices.Equal(statusStrings(tokenStatuses), token.Statuses) { + return basestorage.RequestReceiptRange{}, nil, fmt.Errorf("invalid page token: %w", ErrInvalidRequest) } if token.Queue != req.Queue || (req.HasReceivedAtOrAfterMs && req.ReceivedAtOrAfterMs != token.ReceivedAtOrAfterMs) || - (req.HasReceivedBeforeMs && req.ReceivedBeforeMs != token.ReceivedBeforeMs) { - return basestorage.RequestReceiptRange{}, fmt.Errorf("page token does not match the queue and time bounds: %w", ErrInvalidRequest) + (req.HasReceivedBeforeMs && req.ReceivedBeforeMs != token.ReceivedBeforeMs) || + (len(statuses) > 0 && !slices.Equal(statuses, tokenStatuses)) { + return basestorage.RequestReceiptRange{}, nil, fmt.Errorf("page token does not match the queue, time bounds, and statuses: %w", ErrInvalidRequest) } query.ReceivedAtOrAfterMs = token.ReceivedAtOrAfterMs query.ReceivedBeforeMs = token.ReceivedBeforeMs query.Before = basestorage.RequestReceiptCursor{ReceivedAtMs: token.LastReceivedAtMs, RequestID: token.LastRequestID} - return query, nil + return query, tokenStatuses, nil } if !req.HasReceivedAtOrAfterMs || !req.HasReceivedBeforeMs || req.ReceivedAtOrAfterMs >= req.ReceivedBeforeMs { - return basestorage.RequestReceiptRange{}, fmt.Errorf("first page requires received_at_or_after_ms < received_before_ms: %w", ErrInvalidRequest) + return basestorage.RequestReceiptRange{}, nil, fmt.Errorf("first page requires received_at_or_after_ms < received_before_ms: %w", ErrInvalidRequest) } query.ReceivedAtOrAfterMs = req.ReceivedAtOrAfterMs query.ReceivedBeforeMs = req.ReceivedBeforeMs - return query, nil + return query, statuses, nil +} + +// canonicalListStatuses sorts and deduplicates a filter so equal filters compare equal; ok is false for an empty +// status or a filter above maxListStatuses. +func canonicalListStatuses(statuses []entity.RequestStatus) ([]entity.RequestStatus, bool) { + if len(statuses) > maxListStatuses || slices.Contains(statuses, entity.RequestStatusUnknown) { + return nil, false + } + canonical := slices.Clone(statuses) + slices.Sort(canonical) + return slices.Compact(canonical), true +} + +func statusStrings(statuses []entity.RequestStatus) []string { + if len(statuses) == 0 { + return nil + } + values := make([]string, len(statuses)) + for i, status := range statuses { + values[i] = string(status) + } + return values +} + +func statusesFromStrings(values []string) []entity.RequestStatus { + statuses := make([]entity.RequestStatus, len(values)) + for i, value := range values { + statuses[i] = entity.RequestStatus(value) + } + return statuses } func encodeListPageToken(token listPageToken) (string, error) { diff --git a/submitqueue/gateway/controller/list_test.go b/submitqueue/gateway/controller/list_test.go index 03ecfbf63..0932086a2 100644 --- a/submitqueue/gateway/controller/list_test.go +++ b/submitqueue/gateway/controller/list_test.go @@ -18,6 +18,8 @@ import ( "context" "encoding/base64" "errors" + "fmt" + "slices" "testing" "github.com/stretchr/testify/assert" @@ -220,6 +222,11 @@ func TestList_Errors(t *testing.T) { {name: "token for another queue", request: entity.ListRequest{Queue: "q", PageToken: validToken(func(tok *listPageToken) { tok.Queue = "other" })}, wantInvalid: true}, {name: "mismatched lower bound", request: withToken(boundedListRequest("q", 101, 200), goodToken), wantInvalid: true}, {name: "mismatched upper bound", request: withToken(entity.ListRequest{Queue: "q", ReceivedBeforeMs: 0, HasReceivedBeforeMs: true}, goodToken), wantInvalid: true}, + {name: "empty status", request: withStatuses(boundedListRequest("q", 1, 2), entity.RequestStatusUnknown), wantInvalid: true}, + {name: "too many statuses", request: withStatuses(boundedListRequest("q", 1, 2), slices.Repeat([]entity.RequestStatus{entity.RequestStatusLanded}, maxListStatuses+1)...), wantInvalid: true}, + {name: "token with non-canonical statuses", request: entity.ListRequest{Queue: "q", PageToken: validToken(func(tok *listPageToken) { tok.Statuses = []string{"landed", "error"} })}, wantInvalid: true}, + {name: "statuses differ from token", request: withStatuses(withToken(entity.ListRequest{Queue: "q"}, validToken(func(tok *listPageToken) { tok.Statuses = []string{"landed"} })), entity.RequestStatusError), wantInvalid: true}, + {name: "statuses added to unfiltered token", request: withStatuses(withToken(entity.ListRequest{Queue: "q"}, goodToken), entity.RequestStatusLanded), wantInvalid: true}, { name: "receipt scan failure", request: boundedListRequest("q", 1, 2), wantErr: backendErr, setup: func(f listTestFixture) { @@ -271,3 +278,143 @@ func TestList_StorageResolutionFailure(t *testing.T) { assert.ErrorIs(t, err, backendErr) assert.Equal(t, entity.ListResult{}, result) } + +func withStatuses(req entity.ListRequest, statuses ...entity.RequestStatus) entity.ListRequest { + req.Statuses = statuses + return req +} + +func listedReceipt(id string, receivedAtMs int64) entity.RequestReceipt { + return entity.RequestReceipt{RequestID: id, Queue: "q", ReceivedAtMs: receivedAtMs} +} + +func listedSummary(receipt entity.RequestReceipt, status entity.RequestStatus) entity.RequestSummary { + return entity.RequestSummary{RequestID: receipt.RequestID, Queue: "q", ReceivedAtMs: receipt.ReceivedAtMs, Status: status} +} + +func TestList_StatusFilter(t *testing.T) { + landed, errored := entity.RequestStatusLanded, entity.RequestStatusError + r := []entity.RequestReceipt{ + listedReceipt("6", 160), listedReceipt("5", 150), listedReceipt("4", 140), + listedReceipt("3", 130), listedReceipt("2", 120), listedReceipt("1", 110), + } + cursor := func(receipt entity.RequestReceipt) basestorage.RequestReceiptCursor { + return basestorage.RequestReceiptCursor{ReceivedAtMs: receipt.ReceivedAtMs, RequestID: receipt.RequestID} + } + window := func(limit int, before basestorage.RequestReceiptCursor) basestorage.RequestReceiptRange { + return basestorage.RequestReceiptRange{ReceivedAtOrAfterMs: 100, ReceivedBeforeMs: 200, Limit: limit, Before: before} + } + type read struct { + query basestorage.RequestReceiptRange + receipts []entity.RequestReceipt + } + tests := []struct { + name string + statuses map[string]entity.RequestStatus + reads []read + wantIDs []string + // wantLast is the receipt a continuation resumes after; empty means the range was exhausted. + wantLast string + }{ + { + name: "keeps reading past non-matching receipts until the page fills", + statuses: map[string]entity.RequestStatus{"6": errored, "5": landed, "4": errored, "3": landed, "2": landed}, + reads: []read{ + {query: window(3, basestorage.RequestReceiptCursor{}), receipts: r[0:3]}, + {query: window(2, cursor(r[1])), receipts: r[2:4]}, + {query: window(2, cursor(r[2])), receipts: r[3:5]}, + }, + wantIDs: []string{"5", "3"}, wantLast: "3", + }, + { + name: "returns a partial page without a token when the range is exhausted", + statuses: map[string]entity.RequestStatus{"2": landed, "1": errored}, + reads: []read{{query: window(3, basestorage.RequestReceiptCursor{}), receipts: r[4:6]}}, + wantIDs: []string{"2"}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f := newListTestFixture(t) + f.queueConfigs.EXPECT().Get(gomock.Any(), "q").Return(entity.QueueConfig{}, nil) + var calls []any + for _, read := range tt.reads { + calls = append(calls, f.receipts.EXPECT().List(gomock.Any(), read.query).Return(read.receipts, nil)) + for _, receipt := range read.receipts[:min(len(read.receipts), read.query.Limit-1)] { + f.summaries.EXPECT().Get(gomock.Any(), receipt.RequestID).Return(listedSummary(receipt, tt.statuses[receipt.RequestID]), nil) + } + } + gomock.InOrder(calls...) + req := withStatuses(boundedListRequest("q", 100, 200), landed, landed) + req.PageSize = 2 + + result, err := f.controller.List(context.Background(), req) + + require.NoError(t, err) + ids := make([]string, 0, len(result.Requests)) + for _, summary := range result.Requests { + ids = append(ids, summary.RequestID) + } + assert.Equal(t, tt.wantIDs, ids) + if tt.wantLast == "" { + assert.Empty(t, result.NextPageToken) + return + } + token, err := decodeListPageToken(result.NextPageToken) + require.NoError(t, err) + assert.Equal(t, tt.wantLast, token.LastRequestID) + assert.Equal(t, []string{"landed"}, token.Statuses) + }) + } +} + +func TestList_StatusFilterStopsAtReadBudget(t *testing.T) { + f := newListTestFixture(t) + f.queueConfigs.EXPECT().Get(gomock.Any(), "q").Return(entity.QueueConfig{}, nil) + next := int64(100 + maxFilteredListReceipts + 1) + f.receipts.EXPECT().List(gomock.Any(), gomock.Any()).DoAndReturn( + func(_ context.Context, query basestorage.RequestReceiptRange) ([]entity.RequestReceipt, error) { + batch := make([]entity.RequestReceipt, 0, query.Limit) + for range query.Limit { + batch = append(batch, listedReceipt(fmt.Sprint(next), next)) + next-- + } + return batch, nil + }).AnyTimes() + f.summaries.EXPECT().Get(gomock.Any(), gomock.Any()).DoAndReturn( + func(_ context.Context, id string) (entity.RequestSummary, error) { + var receivedAtMs int64 + _, err := fmt.Sscan(id, &receivedAtMs) + require.NoError(t, err) + return listedSummary(listedReceipt(id, receivedAtMs), entity.RequestStatusError), nil + }).Times(maxFilteredListReceipts) + + result, err := f.controller.List(context.Background(), withStatuses(boundedListRequest("q", 1, 10_000), entity.RequestStatusLanded)) + + require.NoError(t, err) + assert.Empty(t, result.Requests) + require.NotEmpty(t, result.NextPageToken) +} + +func TestList_ContinuationTakesTokenStatuses(t *testing.T) { + f := newListTestFixture(t) + f.queueConfigs.EXPECT().Get(gomock.Any(), "q").Return(entity.QueueConfig{}, nil) + token := mustEncodeListPageToken(t, listPageToken{ + Version: listPageTokenVersion, Queue: "q", ReceivedAtOrAfterMs: 100, ReceivedBeforeMs: 200, + LastReceivedAtMs: 150, LastRequestID: "5", Statuses: []string{"landed"}, + }) + receipts := []entity.RequestReceipt{listedReceipt("4", 140), listedReceipt("3", 130)} + f.receipts.EXPECT().List(gomock.Any(), basestorage.RequestReceiptRange{ + ReceivedAtOrAfterMs: 100, ReceivedBeforeMs: 200, Limit: 51, + Before: basestorage.RequestReceiptCursor{ReceivedAtMs: 150, RequestID: "5"}, + }).Return(receipts, nil) + f.summaries.EXPECT().Get(gomock.Any(), "4").Return(listedSummary(receipts[0], entity.RequestStatusError), nil) + f.summaries.EXPECT().Get(gomock.Any(), "3").Return(listedSummary(receipts[1], entity.RequestStatusLanded), nil) + + result, err := f.controller.List(context.Background(), entity.ListRequest{Queue: "q", PageToken: token}) + + require.NoError(t, err) + require.Len(t, result.Requests, 1) + assert.Equal(t, "3", result.Requests[0].RequestID) + assert.Empty(t, result.NextPageToken) +} diff --git a/test/integration/submitqueue/gateway/suite_test.go b/test/integration/submitqueue/gateway/suite_test.go index a0144000a..46a27865e 100644 --- a/test/integration/submitqueue/gateway/suite_test.go +++ b/test/integration/submitqueue/gateway/suite_test.go @@ -254,6 +254,7 @@ func (s *GatewayIntegrationSuite) TestListAPI() { assert.Equal(t, string(entity.RequestStatusError), resp.Requests[0].Status) assert.Equal(t, "build failed", resp.Requests[0].LastError) assert.Equal(t, map[string]string{"build": "url"}, resp.Requests[0].Metadata) + assert.Equal(t, int64(300), resp.Requests[0].UpdatedAtMs) require.NotEmpty(t, resp.NextPageToken) resp, err = s.client.List(s.ctx, &pb.ListRequest{Queue: "test-queue", ReceivedLowerBound: &pb.ListRequest_ReceivedAtOrAfterMs{ReceivedAtOrAfterMs: 50}, ReceivedUpperBound: &pb.ListRequest_ReceivedBeforeMs{ReceivedBeforeMs: 250}, PageSize: 1, PageToken: resp.NextPageToken}) @@ -267,6 +268,20 @@ func (s *GatewayIntegrationSuite) TestListAPI() { require.NoError(t, err) require.Len(t, resp.Requests, 1) assert.Equal(t, "901", resp.Requests[0].Sqid) + + resp, err = s.client.List(s.ctx, &pb.ListRequest{ + Queue: "test-queue", PageSize: 1, Statuses: []string{string(entity.RequestStatusAccepted), string(entity.RequestStatusError)}, + ReceivedLowerBound: &pb.ListRequest_ReceivedAtOrAfterMs{ReceivedAtOrAfterMs: 50}, ReceivedUpperBound: &pb.ListRequest_ReceivedBeforeMs{ReceivedBeforeMs: 250}, + }) + require.NoError(t, err) + require.Len(t, resp.Requests, 1) + assert.Equal(t, "902", resp.Requests[0].Sqid, "the landed request 903 is newer but filtered out") + require.NotEmpty(t, resp.NextPageToken) + resp, err = s.client.List(s.ctx, &pb.ListRequest{Queue: "test-queue", PageSize: 1, PageToken: resp.NextPageToken}) + require.NoError(t, err) + require.Len(t, resp.Requests, 1) + assert.Equal(t, "901", resp.Requests[0].Sqid) + assert.Empty(t, resp.NextPageToken) } // TestReadAPIErrorCodes verifies controller error classes reach stable gRPC codes. diff --git a/web/api/src/gen/api/submitqueue/gateway/proto/gateway_pb.ts b/web/api/src/gen/api/submitqueue/gateway/proto/gateway_pb.ts index 6212a6a2f..cfba4ab93 100644 --- a/web/api/src/gen/api/submitqueue/gateway/proto/gateway_pb.ts +++ b/web/api/src/gen/api/submitqueue/gateway/proto/gateway_pb.ts @@ -28,7 +28,7 @@ import type { Message } from "@bufbuild/protobuf"; * Describes the file api/submitqueue/gateway/proto/gateway.proto. */ export const file_api_submitqueue_gateway_proto_gateway: GenFile = /*@__PURE__*/ - fileDesc("CithcGkvc3VibWl0cXVldWUvZ2F0ZXdheS9wcm90by9nYXRld2F5LnByb3RvEhh1YmVyLnN1Ym1pdHF1ZXVlLmdhdGV3YXkiHgoLUGluZ1JlcXVlc3QSDwoHbWVzc2FnZRgBIAEoCSJaCgxQaW5nUmVzcG9uc2USDwoHbWVzc2FnZRgBIAEoCRIUCgxzZXJ2aWNlX25hbWUYAiABKAkSEQoJdGltZXN0YW1wGAMgASgDEhAKCGhvc3RuYW1lGAQgASgJInsKC0xhbmRSZXF1ZXN0Eg0KBXF1ZXVlGAEgASgJEigKBmNoYW5nZRgCIAEoCzIYLnViZXIuYmFzZS5jaGFuZ2UuQ2hhbmdlEjMKCHN0cmF0ZWd5GAQgASgOMiEudWJlci5iYXNlLm1lcmdlc3RyYXRlZ3kuU3RyYXRlZ3kiHAoMTGFuZFJlc3BvbnNlEgwKBHNxaWQYASABKAkiPAoNQ2FuY2VsUmVxdWVzdBIMCgRzcWlkGAEgASgJEg4KBnJlYXNvbhgCIAEoCRINCgVxdWV1ZRgDIAEoCSIQCg5DYW5jZWxSZXNwb25zZSL5AQoOUmVxdWVzdFN1bW1hcnkSDAoEc3FpZBgBIAEoCRINCgVxdWV1ZRgCIAEoCRITCgtjaGFuZ2VfdXJpcxgDIAMoCRIWCg5yZWNlaXZlZF9hdF9tcxgEIAEoAxIOCgZzdGF0dXMYBSABKAkSEgoKbGFzdF9lcnJvchgGIAEoCRJICghtZXRhZGF0YRgHIAMoCzI2LnViZXIuc3VibWl0cXVldWUuZ2F0ZXdheS5SZXF1ZXN0U3VtbWFyeS5NZXRhZGF0YUVudHJ5Gi8KDU1ldGFkYXRhRW50cnkSCwoDa2V5GAEgASgJEg0KBXZhbHVlGAIgASgJOgI4ASI7ChxHZXRSZXF1ZXN0U3VtbWFyeUJ5SURSZXF1ZXN0EgwKBHNxaWQYASABKAkSDQoFcXVldWUYAiABKAkiWgodR2V0UmVxdWVzdFN1bW1hcnlCeUlEUmVzcG9uc2USOQoHcmVxdWVzdBgBIAEoCzIoLnViZXIuc3VibWl0cXVldWUuZ2F0ZXdheS5SZXF1ZXN0U3VtbWFyeSJICiNHZXRSZXF1ZXN0U3VtbWFyeUJ5Q2hhbmdlVVJJUmVxdWVzdBISCgpjaGFuZ2VfdXJpGAEgASgJEg0KBXF1ZXVlGAIgASgJImIKJEdldFJlcXVlc3RTdW1tYXJ5QnlDaGFuZ2VVUklSZXNwb25zZRI6CghyZXF1ZXN0cxgBIAMoCzIoLnViZXIuc3VibWl0cXVldWUuZ2F0ZXdheS5SZXF1ZXN0U3VtbWFyeSK0AQoLTGlzdFJlcXVlc3QSDQoFcXVldWUYASABKAkSIQoXcmVjZWl2ZWRfYXRfb3JfYWZ0ZXJfbXMYAiABKANIABIcChJyZWNlaXZlZF9iZWZvcmVfbXMYAyABKANIARIRCglwYWdlX3NpemUYBCABKAUSEgoKcGFnZV90b2tlbhgFIAEoCUIWChRyZWNlaXZlZF9sb3dlcl9ib3VuZEIWChRyZWNlaXZlZF91cHBlcl9ib3VuZCKgAQoMTGlzdFJlc3BvbnNlEjoKCHJlcXVlc3RzGAEgAygLMigudWJlci5zdWJtaXRxdWV1ZS5nYXRld2F5LlJlcXVlc3RTdW1tYXJ5EhcKD25leHRfcGFnZV90b2tlbhgCIAEoCRIfChdyZWNlaXZlZF9hdF9vcl9hZnRlcl9tcxgDIAEoAxIaChJyZWNlaXZlZF9iZWZvcmVfbXMYBCABKAMiFQoFUXVldWUSDAoEbmFtZRgBIAEoCSITChFMaXN0UXVldWVzUmVxdWVzdCJFChJMaXN0UXVldWVzUmVzcG9uc2USLwoGcXVldWVzGAEgAygLMh8udWJlci5zdWJtaXRxdWV1ZS5nYXRld2F5LlF1ZXVlIjsKHEdldFJlcXVlc3RIaXN0b3J5QnlJRFJlcXVlc3QSDAoEc3FpZBgBIAEoCRINCgVxdWV1ZRgCIAEoCSLeAQoMSGlzdG9yeUV2ZW50EhQKDHRpbWVzdGFtcF9tcxgBIAEoAxIOCgZzdGF0dXMYAiABKAkSEgoKbGFzdF9lcnJvchgDIAEoCRJGCghtZXRhZGF0YRgEIAMoCzI0LnViZXIuc3VibWl0cXVldWUuZ2F0ZXdheS5IaXN0b3J5RXZlbnQuTWV0YWRhdGFFbnRyeRIMCgR0eXBlGAUgASgJEg0KBWV2ZW50GAYgASgJGi8KDU1ldGFkYXRhRW50cnkSCwoDa2V5GAEgASgJEg0KBXZhbHVlGAIgASgJOgI4ASJXCh1HZXRSZXF1ZXN0SGlzdG9yeUJ5SURSZXNwb25zZRI2CgZldmVudHMYASADKAsyJi51YmVyLnN1Ym1pdHF1ZXVlLmdhdGV3YXkuSGlzdG9yeUV2ZW50IkgKI0dldFJlcXVlc3RIaXN0b3J5QnlDaGFuZ2VVUklSZXF1ZXN0EhIKCmNoYW5nZV91cmkYASABKAkSDQoFcXVldWUYAiABKAkiVgoOUmVxdWVzdEhpc3RvcnkSDAoEc3FpZBgBIAEoCRI2CgZldmVudHMYAiADKAsyJi51YmVyLnN1Ym1pdHF1ZXVlLmdhdGV3YXkuSGlzdG9yeUV2ZW50ImMKJEdldFJlcXVlc3RIaXN0b3J5QnlDaGFuZ2VVUklSZXNwb25zZRI7CgloaXN0b3JpZXMYASADKAsyKC51YmVyLnN1Ym1pdHF1ZXVlLmdhdGV3YXkuUmVxdWVzdEhpc3RvcnkiGAoFRXJyb3ISDwoHbWVzc2FnZRgBIAEoCSJXChZVbnJlY29nbml6ZWRRdWV1ZUVycm9yEi4KBWVycm9yGAEgASgLMh8udWJlci5zdWJtaXRxdWV1ZS5nYXRld2F5LkVycm9yEg0KBXF1ZXVlGAIgASgJImgKFFJlcXVlc3ROb3RGb3VuZEVycm9yEi4KBWVycm9yGAEgASgLMh8udWJlci5zdWJtaXRxdWV1ZS5nYXRld2F5LkVycm9yEgwKBHNxaWQYAiABKAkSEgoKY2hhbmdlX3VyaRgDIAEoCTLHCAoSU3VibWl0UXVldWVHYXRld2F5ElcKBFBpbmcSJS51YmVyLnN1Ym1pdHF1ZXVlLmdhdGV3YXkuUGluZ1JlcXVlc3QaJi51YmVyLnN1Ym1pdHF1ZXVlLmdhdGV3YXkuUGluZ1Jlc3BvbnNlIgASaQoKTGlzdFF1ZXVlcxIrLnViZXIuc3VibWl0cXVldWUuZ2F0ZXdheS5MaXN0UXVldWVzUmVxdWVzdBosLnViZXIuc3VibWl0cXVldWUuZ2F0ZXdheS5MaXN0UXVldWVzUmVzcG9uc2UiABJXCgRMYW5kEiUudWJlci5zdWJtaXRxdWV1ZS5nYXRld2F5LkxhbmRSZXF1ZXN0GiYudWJlci5zdWJtaXRxdWV1ZS5nYXRld2F5LkxhbmRSZXNwb25zZSIAEl0KBkNhbmNlbBInLnViZXIuc3VibWl0cXVldWUuZ2F0ZXdheS5DYW5jZWxSZXF1ZXN0GigudWJlci5zdWJtaXRxdWV1ZS5nYXRld2F5LkNhbmNlbFJlc3BvbnNlIgASigEKFUdldFJlcXVlc3RTdW1tYXJ5QnlJRBI2LnViZXIuc3VibWl0cXVldWUuZ2F0ZXdheS5HZXRSZXF1ZXN0U3VtbWFyeUJ5SURSZXF1ZXN0GjcudWJlci5zdWJtaXRxdWV1ZS5nYXRld2F5LkdldFJlcXVlc3RTdW1tYXJ5QnlJRFJlc3BvbnNlIgASnwEKHEdldFJlcXVlc3RTdW1tYXJ5QnlDaGFuZ2VVUkkSPS51YmVyLnN1Ym1pdHF1ZXVlLmdhdGV3YXkuR2V0UmVxdWVzdFN1bW1hcnlCeUNoYW5nZVVSSVJlcXVlc3QaPi51YmVyLnN1Ym1pdHF1ZXVlLmdhdGV3YXkuR2V0UmVxdWVzdFN1bW1hcnlCeUNoYW5nZVVSSVJlc3BvbnNlIgASVwoETGlzdBIlLnViZXIuc3VibWl0cXVldWUuZ2F0ZXdheS5MaXN0UmVxdWVzdBomLnViZXIuc3VibWl0cXVldWUuZ2F0ZXdheS5MaXN0UmVzcG9uc2UiABKKAQoVR2V0UmVxdWVzdEhpc3RvcnlCeUlEEjYudWJlci5zdWJtaXRxdWV1ZS5nYXRld2F5LkdldFJlcXVlc3RIaXN0b3J5QnlJRFJlcXVlc3QaNy51YmVyLnN1Ym1pdHF1ZXVlLmdhdGV3YXkuR2V0UmVxdWVzdEhpc3RvcnlCeUlEUmVzcG9uc2UiABKfAQocR2V0UmVxdWVzdEhpc3RvcnlCeUNoYW5nZVVSSRI9LnViZXIuc3VibWl0cXVldWUuZ2F0ZXdheS5HZXRSZXF1ZXN0SGlzdG9yeUJ5Q2hhbmdlVVJJUmVxdWVzdBo+LnViZXIuc3VibWl0cXVldWUuZ2F0ZXdheS5HZXRSZXF1ZXN0SGlzdG9yeUJ5Q2hhbmdlVVJJUmVzcG9uc2UiAEJrChxjb20udWJlci5zdWJtaXRxdWV1ZS5nYXRld2F5QgxHYXRld2F5UHJvdG9QAVo7Z2l0aHViLmNvbS91YmVyL3N1Ym1pdHF1ZXVlL2FwaS9zdWJtaXRxdWV1ZS9nYXRld2F5L3Byb3RvcGJiBnByb3RvMw", [file_api_base_change_proto_change, file_api_base_mergestrategy_proto_mergestrategy]); + fileDesc("CithcGkvc3VibWl0cXVldWUvZ2F0ZXdheS9wcm90by9nYXRld2F5LnByb3RvEhh1YmVyLnN1Ym1pdHF1ZXVlLmdhdGV3YXkiHgoLUGluZ1JlcXVlc3QSDwoHbWVzc2FnZRgBIAEoCSJaCgxQaW5nUmVzcG9uc2USDwoHbWVzc2FnZRgBIAEoCRIUCgxzZXJ2aWNlX25hbWUYAiABKAkSEQoJdGltZXN0YW1wGAMgASgDEhAKCGhvc3RuYW1lGAQgASgJInsKC0xhbmRSZXF1ZXN0Eg0KBXF1ZXVlGAEgASgJEigKBmNoYW5nZRgCIAEoCzIYLnViZXIuYmFzZS5jaGFuZ2UuQ2hhbmdlEjMKCHN0cmF0ZWd5GAQgASgOMiEudWJlci5iYXNlLm1lcmdlc3RyYXRlZ3kuU3RyYXRlZ3kiHAoMTGFuZFJlc3BvbnNlEgwKBHNxaWQYASABKAkiPAoNQ2FuY2VsUmVxdWVzdBIMCgRzcWlkGAEgASgJEg4KBnJlYXNvbhgCIAEoCRINCgVxdWV1ZRgDIAEoCSIQCg5DYW5jZWxSZXNwb25zZSKQAgoOUmVxdWVzdFN1bW1hcnkSDAoEc3FpZBgBIAEoCRINCgVxdWV1ZRgCIAEoCRITCgtjaGFuZ2VfdXJpcxgDIAMoCRIWCg5yZWNlaXZlZF9hdF9tcxgEIAEoAxIOCgZzdGF0dXMYBSABKAkSEgoKbGFzdF9lcnJvchgGIAEoCRJICghtZXRhZGF0YRgHIAMoCzI2LnViZXIuc3VibWl0cXVldWUuZ2F0ZXdheS5SZXF1ZXN0U3VtbWFyeS5NZXRhZGF0YUVudHJ5EhUKDXVwZGF0ZWRfYXRfbXMYCCABKAMaLwoNTWV0YWRhdGFFbnRyeRILCgNrZXkYASABKAkSDQoFdmFsdWUYAiABKAk6AjgBIjsKHEdldFJlcXVlc3RTdW1tYXJ5QnlJRFJlcXVlc3QSDAoEc3FpZBgBIAEoCRINCgVxdWV1ZRgCIAEoCSJaCh1HZXRSZXF1ZXN0U3VtbWFyeUJ5SURSZXNwb25zZRI5CgdyZXF1ZXN0GAEgASgLMigudWJlci5zdWJtaXRxdWV1ZS5nYXRld2F5LlJlcXVlc3RTdW1tYXJ5IkgKI0dldFJlcXVlc3RTdW1tYXJ5QnlDaGFuZ2VVUklSZXF1ZXN0EhIKCmNoYW5nZV91cmkYASABKAkSDQoFcXVldWUYAiABKAkiYgokR2V0UmVxdWVzdFN1bW1hcnlCeUNoYW5nZVVSSVJlc3BvbnNlEjoKCHJlcXVlc3RzGAEgAygLMigudWJlci5zdWJtaXRxdWV1ZS5nYXRld2F5LlJlcXVlc3RTdW1tYXJ5IsYBCgtMaXN0UmVxdWVzdBINCgVxdWV1ZRgBIAEoCRIhChdyZWNlaXZlZF9hdF9vcl9hZnRlcl9tcxgCIAEoA0gAEhwKEnJlY2VpdmVkX2JlZm9yZV9tcxgDIAEoA0gBEhEKCXBhZ2Vfc2l6ZRgEIAEoBRISCgpwYWdlX3Rva2VuGAUgASgJEhAKCHN0YXR1c2VzGAYgAygJQhYKFHJlY2VpdmVkX2xvd2VyX2JvdW5kQhYKFHJlY2VpdmVkX3VwcGVyX2JvdW5kIqABCgxMaXN0UmVzcG9uc2USOgoIcmVxdWVzdHMYASADKAsyKC51YmVyLnN1Ym1pdHF1ZXVlLmdhdGV3YXkuUmVxdWVzdFN1bW1hcnkSFwoPbmV4dF9wYWdlX3Rva2VuGAIgASgJEh8KF3JlY2VpdmVkX2F0X29yX2FmdGVyX21zGAMgASgDEhoKEnJlY2VpdmVkX2JlZm9yZV9tcxgEIAEoAyIVCgVRdWV1ZRIMCgRuYW1lGAEgASgJIhMKEUxpc3RRdWV1ZXNSZXF1ZXN0IkUKEkxpc3RRdWV1ZXNSZXNwb25zZRIvCgZxdWV1ZXMYASADKAsyHy51YmVyLnN1Ym1pdHF1ZXVlLmdhdGV3YXkuUXVldWUiOwocR2V0UmVxdWVzdEhpc3RvcnlCeUlEUmVxdWVzdBIMCgRzcWlkGAEgASgJEg0KBXF1ZXVlGAIgASgJIt4BCgxIaXN0b3J5RXZlbnQSFAoMdGltZXN0YW1wX21zGAEgASgDEg4KBnN0YXR1cxgCIAEoCRISCgpsYXN0X2Vycm9yGAMgASgJEkYKCG1ldGFkYXRhGAQgAygLMjQudWJlci5zdWJtaXRxdWV1ZS5nYXRld2F5Lkhpc3RvcnlFdmVudC5NZXRhZGF0YUVudHJ5EgwKBHR5cGUYBSABKAkSDQoFZXZlbnQYBiABKAkaLwoNTWV0YWRhdGFFbnRyeRILCgNrZXkYASABKAkSDQoFdmFsdWUYAiABKAk6AjgBIlcKHUdldFJlcXVlc3RIaXN0b3J5QnlJRFJlc3BvbnNlEjYKBmV2ZW50cxgBIAMoCzImLnViZXIuc3VibWl0cXVldWUuZ2F0ZXdheS5IaXN0b3J5RXZlbnQiSAojR2V0UmVxdWVzdEhpc3RvcnlCeUNoYW5nZVVSSVJlcXVlc3QSEgoKY2hhbmdlX3VyaRgBIAEoCRINCgVxdWV1ZRgCIAEoCSJWCg5SZXF1ZXN0SGlzdG9yeRIMCgRzcWlkGAEgASgJEjYKBmV2ZW50cxgCIAMoCzImLnViZXIuc3VibWl0cXVldWUuZ2F0ZXdheS5IaXN0b3J5RXZlbnQiYwokR2V0UmVxdWVzdEhpc3RvcnlCeUNoYW5nZVVSSVJlc3BvbnNlEjsKCWhpc3RvcmllcxgBIAMoCzIoLnViZXIuc3VibWl0cXVldWUuZ2F0ZXdheS5SZXF1ZXN0SGlzdG9yeSIYCgVFcnJvchIPCgdtZXNzYWdlGAEgASgJIlcKFlVucmVjb2duaXplZFF1ZXVlRXJyb3ISLgoFZXJyb3IYASABKAsyHy51YmVyLnN1Ym1pdHF1ZXVlLmdhdGV3YXkuRXJyb3ISDQoFcXVldWUYAiABKAkiaAoUUmVxdWVzdE5vdEZvdW5kRXJyb3ISLgoFZXJyb3IYASABKAsyHy51YmVyLnN1Ym1pdHF1ZXVlLmdhdGV3YXkuRXJyb3ISDAoEc3FpZBgCIAEoCRISCgpjaGFuZ2VfdXJpGAMgASgJMscIChJTdWJtaXRRdWV1ZUdhdGV3YXkSVwoEUGluZxIlLnViZXIuc3VibWl0cXVldWUuZ2F0ZXdheS5QaW5nUmVxdWVzdBomLnViZXIuc3VibWl0cXVldWUuZ2F0ZXdheS5QaW5nUmVzcG9uc2UiABJpCgpMaXN0UXVldWVzEisudWJlci5zdWJtaXRxdWV1ZS5nYXRld2F5Lkxpc3RRdWV1ZXNSZXF1ZXN0GiwudWJlci5zdWJtaXRxdWV1ZS5nYXRld2F5Lkxpc3RRdWV1ZXNSZXNwb25zZSIAElcKBExhbmQSJS51YmVyLnN1Ym1pdHF1ZXVlLmdhdGV3YXkuTGFuZFJlcXVlc3QaJi51YmVyLnN1Ym1pdHF1ZXVlLmdhdGV3YXkuTGFuZFJlc3BvbnNlIgASXQoGQ2FuY2VsEicudWJlci5zdWJtaXRxdWV1ZS5nYXRld2F5LkNhbmNlbFJlcXVlc3QaKC51YmVyLnN1Ym1pdHF1ZXVlLmdhdGV3YXkuQ2FuY2VsUmVzcG9uc2UiABKKAQoVR2V0UmVxdWVzdFN1bW1hcnlCeUlEEjYudWJlci5zdWJtaXRxdWV1ZS5nYXRld2F5LkdldFJlcXVlc3RTdW1tYXJ5QnlJRFJlcXVlc3QaNy51YmVyLnN1Ym1pdHF1ZXVlLmdhdGV3YXkuR2V0UmVxdWVzdFN1bW1hcnlCeUlEUmVzcG9uc2UiABKfAQocR2V0UmVxdWVzdFN1bW1hcnlCeUNoYW5nZVVSSRI9LnViZXIuc3VibWl0cXVldWUuZ2F0ZXdheS5HZXRSZXF1ZXN0U3VtbWFyeUJ5Q2hhbmdlVVJJUmVxdWVzdBo+LnViZXIuc3VibWl0cXVldWUuZ2F0ZXdheS5HZXRSZXF1ZXN0U3VtbWFyeUJ5Q2hhbmdlVVJJUmVzcG9uc2UiABJXCgRMaXN0EiUudWJlci5zdWJtaXRxdWV1ZS5nYXRld2F5Lkxpc3RSZXF1ZXN0GiYudWJlci5zdWJtaXRxdWV1ZS5nYXRld2F5Lkxpc3RSZXNwb25zZSIAEooBChVHZXRSZXF1ZXN0SGlzdG9yeUJ5SUQSNi51YmVyLnN1Ym1pdHF1ZXVlLmdhdGV3YXkuR2V0UmVxdWVzdEhpc3RvcnlCeUlEUmVxdWVzdBo3LnViZXIuc3VibWl0cXVldWUuZ2F0ZXdheS5HZXRSZXF1ZXN0SGlzdG9yeUJ5SURSZXNwb25zZSIAEp8BChxHZXRSZXF1ZXN0SGlzdG9yeUJ5Q2hhbmdlVVJJEj0udWJlci5zdWJtaXRxdWV1ZS5nYXRld2F5LkdldFJlcXVlc3RIaXN0b3J5QnlDaGFuZ2VVUklSZXF1ZXN0Gj4udWJlci5zdWJtaXRxdWV1ZS5nYXRld2F5LkdldFJlcXVlc3RIaXN0b3J5QnlDaGFuZ2VVUklSZXNwb25zZSIAQmsKHGNvbS51YmVyLnN1Ym1pdHF1ZXVlLmdhdGV3YXlCDEdhdGV3YXlQcm90b1ABWjtnaXRodWIuY29tL3ViZXIvc3VibWl0cXVldWUvYXBpL3N1Ym1pdHF1ZXVlL2dhdGV3YXkvcHJvdG9wYmIGcHJvdG8z", [file_api_base_change_proto_change, file_api_base_mergestrategy_proto_mergestrategy]); /** * PingRequest is the request for the Ping method @@ -264,6 +264,13 @@ export type RequestSummary = Message<"uber.submitqueue.gateway.RequestSummary"> * @generated from field: map metadata = 7; */ metadata: { [key: string]: string }; + + /** + * Time the current status was recorded, in Unix milliseconds; equals received_at_ms until the status first changes. + * + * @generated from field: int64 updated_at_ms = 8; + */ + updatedAtMs: bigint; }; /** @@ -420,11 +427,19 @@ export type ListRequest = Message<"uber.submitqueue.gateway.ListRequest"> & { pageSize: number; /** - * Opaque continuation token from a previous response, bound to its queue and receipt window. Empty selects the first page. + * Opaque continuation token from a previous response, bound to its queue, receipt window, and statuses. Empty selects the first page. * * @generated from field: string page_token = 5; */ pageToken: string; + + /** + * Current statuses to include, as reported in RequestSummary.status; empty includes every status. With page_token, omitted takes the token's statuses and supplied must equal them. + * A filtered page may hold fewer than page_size requests, or none, while next_page_token is non-empty: each call reads a bounded number of receipts. + * + * @generated from field: repeated string statuses = 6; + */ + statuses: string[]; }; /**