diff --git a/api/stovepipe/proto/stovepipe.proto b/api/stovepipe/proto/stovepipe.proto index 5acb2b6e4..1901b93b8 100644 --- a/api/stovepipe/proto/stovepipe.proto +++ b/api/stovepipe/proto/stovepipe.proto @@ -375,6 +375,43 @@ message ListOpenIssuesResponse { string next_page_token = 2; } +// RequestIssueSummary describes one accepted issue update, not the queue's open-issue set. +message RequestIssueSummary { + // Logical validation task. + string task_id = 1; + // Validation unit; empty identifies a task-wide failure. + string unit_id = 2; + // "new" (including reopened), "existing" (reported and already open), or "closed". + string kind = 3; + // Build whose accepted update produced this result. + string build_id = 4; + // Build where this occurrence first opened. + string first_seen_build_id = 5; +} + +message GetIssuesByRequestIDRequest { + // Required configured queue. + string queue = 1; + // Required canonical positive decimal request ID within the queue. + string request_id = 2; + // Maximum results to return. Zero selects 50; otherwise 1 through 200. + int32 page_size = 3; + // Opaque continuation bound to this queue and request. + string page_token = 4; +} + +// Completed and abandoned results are stable; in-progress pages are not a snapshot. +message GetIssuesByRequestIDResponse { + // Results in ascending bytewise task/unit order, classified as new, existing, or closed. + repeated RequestIssueSummary issues = 1; + // "in_progress", "complete", "incomplete" (abandoned), or "unavailable". + // Unavailable means recording has not started or predates retained coverage. + // Complete describes reconciliation, not build success or absence of open issues. + string status = 2; + // Empty ends this scan. Restart in-progress scans after completion to include late results. + string next_page_token = 3; +} + // Stovepipe provides the Stovepipe API. service Stovepipe { // Ping returns a response indicating the service is alive @@ -396,4 +433,6 @@ service Stovepipe { rpc GetQueueStatus(GetQueueStatusRequest) returns (GetQueueStatusResponse) {} // ListOpenIssues returns current open issues validated against their summaries. rpc ListOpenIssues(ListOpenIssuesRequest) returns (ListOpenIssuesResponse) {} + // GetIssuesByRequestID returns accepted issue results for one request. + rpc GetIssuesByRequestID(GetIssuesByRequestIDRequest) returns (GetIssuesByRequestIDResponse) {} } diff --git a/api/stovepipe/protopb/stovepipe.pb.go b/api/stovepipe/protopb/stovepipe.pb.go index b86449c4a..6399ebc2b 100644 --- a/api/stovepipe/protopb/stovepipe.pb.go +++ b/api/stovepipe/protopb/stovepipe.pb.go @@ -2320,6 +2320,226 @@ func (x *ListOpenIssuesResponse) GetNextPageToken() string { return "" } +// RequestIssueSummary describes one accepted issue update, not the queue's open-issue set. +type RequestIssueSummary struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Logical validation task. + TaskId string `protobuf:"bytes,1,opt,name=task_id,json=taskId,proto3" json:"task_id,omitempty"` + // Validation unit; empty identifies a task-wide failure. + UnitId string `protobuf:"bytes,2,opt,name=unit_id,json=unitId,proto3" json:"unit_id,omitempty"` + // "new" (including reopened), "existing" (reported and already open), or "closed". + Kind string `protobuf:"bytes,3,opt,name=kind,proto3" json:"kind,omitempty"` + // Build whose accepted update produced this result. + BuildId string `protobuf:"bytes,4,opt,name=build_id,json=buildId,proto3" json:"build_id,omitempty"` + // Build where this occurrence first opened. + FirstSeenBuildId string `protobuf:"bytes,5,opt,name=first_seen_build_id,json=firstSeenBuildId,proto3" json:"first_seen_build_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RequestIssueSummary) Reset() { + *x = RequestIssueSummary{} + mi := &file_stovepipe_proto_msgTypes[29] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RequestIssueSummary) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RequestIssueSummary) ProtoMessage() {} + +func (x *RequestIssueSummary) ProtoReflect() protoreflect.Message { + mi := &file_stovepipe_proto_msgTypes[29] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RequestIssueSummary.ProtoReflect.Descriptor instead. +func (*RequestIssueSummary) Descriptor() ([]byte, []int) { + return file_stovepipe_proto_rawDescGZIP(), []int{29} +} + +func (x *RequestIssueSummary) GetTaskId() string { + if x != nil { + return x.TaskId + } + return "" +} + +func (x *RequestIssueSummary) GetUnitId() string { + if x != nil { + return x.UnitId + } + return "" +} + +func (x *RequestIssueSummary) GetKind() string { + if x != nil { + return x.Kind + } + return "" +} + +func (x *RequestIssueSummary) GetBuildId() string { + if x != nil { + return x.BuildId + } + return "" +} + +func (x *RequestIssueSummary) GetFirstSeenBuildId() string { + if x != nil { + return x.FirstSeenBuildId + } + return "" +} + +type GetIssuesByRequestIDRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Required configured queue. + Queue string `protobuf:"bytes,1,opt,name=queue,proto3" json:"queue,omitempty"` + // Required canonical positive decimal request ID within the queue. + RequestId string `protobuf:"bytes,2,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` + // Maximum results to return. Zero selects 50; otherwise 1 through 200. + PageSize int32 `protobuf:"varint,3,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"` + // Opaque continuation bound to this queue and request. + PageToken string `protobuf:"bytes,4,opt,name=page_token,json=pageToken,proto3" json:"page_token,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetIssuesByRequestIDRequest) Reset() { + *x = GetIssuesByRequestIDRequest{} + mi := &file_stovepipe_proto_msgTypes[30] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetIssuesByRequestIDRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetIssuesByRequestIDRequest) ProtoMessage() {} + +func (x *GetIssuesByRequestIDRequest) ProtoReflect() protoreflect.Message { + mi := &file_stovepipe_proto_msgTypes[30] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetIssuesByRequestIDRequest.ProtoReflect.Descriptor instead. +func (*GetIssuesByRequestIDRequest) Descriptor() ([]byte, []int) { + return file_stovepipe_proto_rawDescGZIP(), []int{30} +} + +func (x *GetIssuesByRequestIDRequest) GetQueue() string { + if x != nil { + return x.Queue + } + return "" +} + +func (x *GetIssuesByRequestIDRequest) GetRequestId() string { + if x != nil { + return x.RequestId + } + return "" +} + +func (x *GetIssuesByRequestIDRequest) GetPageSize() int32 { + if x != nil { + return x.PageSize + } + return 0 +} + +func (x *GetIssuesByRequestIDRequest) GetPageToken() string { + if x != nil { + return x.PageToken + } + return "" +} + +// Completed and abandoned results are stable; in-progress pages are not a snapshot. +type GetIssuesByRequestIDResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Results in ascending bytewise task/unit order, classified as new, existing, or closed. + Issues []*RequestIssueSummary `protobuf:"bytes,1,rep,name=issues,proto3" json:"issues,omitempty"` + // "in_progress", "complete", "incomplete" (abandoned), or "unavailable". + // Unavailable means recording has not started or predates retained coverage. + // Complete describes reconciliation, not build success or absence of open issues. + Status string `protobuf:"bytes,2,opt,name=status,proto3" json:"status,omitempty"` + // Empty ends this scan. Restart in-progress scans after completion to include late results. + NextPageToken string `protobuf:"bytes,3,opt,name=next_page_token,json=nextPageToken,proto3" json:"next_page_token,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetIssuesByRequestIDResponse) Reset() { + *x = GetIssuesByRequestIDResponse{} + mi := &file_stovepipe_proto_msgTypes[31] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetIssuesByRequestIDResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetIssuesByRequestIDResponse) ProtoMessage() {} + +func (x *GetIssuesByRequestIDResponse) ProtoReflect() protoreflect.Message { + mi := &file_stovepipe_proto_msgTypes[31] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetIssuesByRequestIDResponse.ProtoReflect.Descriptor instead. +func (*GetIssuesByRequestIDResponse) Descriptor() ([]byte, []int) { + return file_stovepipe_proto_rawDescGZIP(), []int{31} +} + +func (x *GetIssuesByRequestIDResponse) GetIssues() []*RequestIssueSummary { + if x != nil { + return x.Issues + } + return nil +} + +func (x *GetIssuesByRequestIDResponse) GetStatus() string { + if x != nil { + return x.Status + } + return "" +} + +func (x *GetIssuesByRequestIDResponse) GetNextPageToken() string { + if x != nil { + return x.NextPageToken + } + return "" +} + var File_stovepipe_proto protoreflect.FileDescriptor const file_stovepipe_proto_rawDesc = "" + @@ -2492,11 +2712,28 @@ const file_stovepipe_proto_rawDesc = "" + "page_token\x18\x03 \x01(\tR\tpageToken\"\x82\x01\n" + "\x16ListOpenIssuesResponse\x12@\n" + "\x06issues\x18\x01 \x03(\v2(.uber.submitqueue.stovepipe.IssueSummaryR\x06issues\x12&\n" + - "\x0fnext_page_token\x18\x02 \x01(\tR\rnextPageToken*l\n" + + "\x0fnext_page_token\x18\x02 \x01(\tR\rnextPageToken\"\xa5\x01\n" + + "\x13RequestIssueSummary\x12\x17\n" + + "\atask_id\x18\x01 \x01(\tR\x06taskId\x12\x17\n" + + "\aunit_id\x18\x02 \x01(\tR\x06unitId\x12\x12\n" + + "\x04kind\x18\x03 \x01(\tR\x04kind\x12\x19\n" + + "\bbuild_id\x18\x04 \x01(\tR\abuildId\x12-\n" + + "\x13first_seen_build_id\x18\x05 \x01(\tR\x10firstSeenBuildId\"\x8e\x01\n" + + "\x1bGetIssuesByRequestIDRequest\x12\x14\n" + + "\x05queue\x18\x01 \x01(\tR\x05queue\x12\x1d\n" + + "\n" + + "request_id\x18\x02 \x01(\tR\trequestId\x12\x1b\n" + + "\tpage_size\x18\x03 \x01(\x05R\bpageSize\x12\x1d\n" + + "\n" + + "page_token\x18\x04 \x01(\tR\tpageToken\"\xa7\x01\n" + + "\x1cGetIssuesByRequestIDResponse\x12G\n" + + "\x06issues\x18\x01 \x03(\v2/.uber.submitqueue.stovepipe.RequestIssueSummaryR\x06issues\x12\x16\n" + + "\x06status\x18\x02 \x01(\tR\x06status\x12&\n" + + "\x0fnext_page_token\x18\x03 \x01(\tR\rnextPageToken*l\n" + "\x0fQueueEnablement\x12\x1c\n" + "\x18QUEUE_ENABLEMENT_UNKNOWN\x10\x00\x12\x1c\n" + "\x18QUEUE_ENABLEMENT_ENABLED\x10\x01\x12\x1d\n" + - "\x19QUEUE_ENABLEMENT_DISABLED\x10\x022\xe2\b\n" + + "\x19QUEUE_ENABLEMENT_DISABLED\x10\x022\xf0\t\n" + "\tStovepipe\x12[\n" + "\x04Ping\x12'.uber.submitqueue.stovepipe.PingRequest\x1a(.uber.submitqueue.stovepipe.PingResponse\"\x00\x12a\n" + "\x06Ingest\x12).uber.submitqueue.stovepipe.IngestRequest\x1a*.uber.submitqueue.stovepipe.IngestResponse\"\x00\x12\x8e\x01\n" + @@ -2506,7 +2743,8 @@ const file_stovepipe_proto_rawDesc = "" + "\x15GetProjectStatusByURI\x128.uber.submitqueue.stovepipe.GetProjectStatusByURIRequest\x1a9.uber.submitqueue.stovepipe.GetProjectStatusByURIResponse\"\x00\x12[\n" + "\x04List\x12'.uber.submitqueue.stovepipe.ListRequest\x1a(.uber.submitqueue.stovepipe.ListResponse\"\x00\x12y\n" + "\x0eGetQueueStatus\x121.uber.submitqueue.stovepipe.GetQueueStatusRequest\x1a2.uber.submitqueue.stovepipe.GetQueueStatusResponse\"\x00\x12y\n" + - "\x0eListOpenIssues\x121.uber.submitqueue.stovepipe.ListOpenIssuesRequest\x1a2.uber.submitqueue.stovepipe.ListOpenIssuesResponse\"\x00Be\n" + + "\x0eListOpenIssues\x121.uber.submitqueue.stovepipe.ListOpenIssuesRequest\x1a2.uber.submitqueue.stovepipe.ListOpenIssuesResponse\"\x00\x12\x8b\x01\n" + + "\x14GetIssuesByRequestID\x127.uber.submitqueue.stovepipe.GetIssuesByRequestIDRequest\x1a8.uber.submitqueue.stovepipe.GetIssuesByRequestIDResponse\"\x00Be\n" + "\x1ecom.uber.submitqueue.stovepipeB\x0eStovepipeProtoP\x01Z1github.com/uber/submitqueue/api/stovepipe/protopbb\x06proto3" var ( @@ -2522,7 +2760,7 @@ func file_stovepipe_proto_rawDescGZIP() []byte { } var file_stovepipe_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_stovepipe_proto_msgTypes = make([]protoimpl.MessageInfo, 29) +var file_stovepipe_proto_msgTypes = make([]protoimpl.MessageInfo, 32) var file_stovepipe_proto_goTypes = []any{ (QueueEnablement)(0), // 0: uber.submitqueue.stovepipe.QueueEnablement (*PingRequest)(nil), // 1: uber.submitqueue.stovepipe.PingRequest @@ -2554,6 +2792,9 @@ var file_stovepipe_proto_goTypes = []any{ (*IssueSummary)(nil), // 27: uber.submitqueue.stovepipe.IssueSummary (*ListOpenIssuesRequest)(nil), // 28: uber.submitqueue.stovepipe.ListOpenIssuesRequest (*ListOpenIssuesResponse)(nil), // 29: uber.submitqueue.stovepipe.ListOpenIssuesResponse + (*RequestIssueSummary)(nil), // 30: uber.submitqueue.stovepipe.RequestIssueSummary + (*GetIssuesByRequestIDRequest)(nil), // 31: uber.submitqueue.stovepipe.GetIssuesByRequestIDRequest + (*GetIssuesByRequestIDResponse)(nil), // 32: uber.submitqueue.stovepipe.GetIssuesByRequestIDResponse } var file_stovepipe_proto_depIdxs = []int32{ 0, // 0: uber.submitqueue.stovepipe.IngestRequest.enablement:type_name -> uber.submitqueue.stovepipe.QueueEnablement @@ -2576,29 +2817,32 @@ var file_stovepipe_proto_depIdxs = []int32{ 0, // 17: uber.submitqueue.stovepipe.QueueEnablementStatus.state:type_name -> uber.submitqueue.stovepipe.QueueEnablement 25, // 18: uber.submitqueue.stovepipe.GetQueueStatusResponse.enablement:type_name -> uber.submitqueue.stovepipe.QueueEnablementStatus 27, // 19: uber.submitqueue.stovepipe.ListOpenIssuesResponse.issues:type_name -> uber.submitqueue.stovepipe.IssueSummary - 1, // 20: uber.submitqueue.stovepipe.Stovepipe.Ping:input_type -> uber.submitqueue.stovepipe.PingRequest - 3, // 21: uber.submitqueue.stovepipe.Stovepipe.Ingest:input_type -> uber.submitqueue.stovepipe.IngestRequest - 5, // 22: uber.submitqueue.stovepipe.Stovepipe.GetRequestHistoryByID:input_type -> uber.submitqueue.stovepipe.GetRequestHistoryByIDRequest - 6, // 23: uber.submitqueue.stovepipe.Stovepipe.GetRequestHistoryByURI:input_type -> uber.submitqueue.stovepipe.GetRequestHistoryByURIRequest - 16, // 24: uber.submitqueue.stovepipe.Stovepipe.GetProjectStatusByID:input_type -> uber.submitqueue.stovepipe.GetProjectStatusByIDRequest - 17, // 25: uber.submitqueue.stovepipe.Stovepipe.GetProjectStatusByURI:input_type -> uber.submitqueue.stovepipe.GetProjectStatusByURIRequest - 22, // 26: uber.submitqueue.stovepipe.Stovepipe.List:input_type -> uber.submitqueue.stovepipe.ListRequest - 24, // 27: uber.submitqueue.stovepipe.Stovepipe.GetQueueStatus:input_type -> uber.submitqueue.stovepipe.GetQueueStatusRequest - 28, // 28: uber.submitqueue.stovepipe.Stovepipe.ListOpenIssues:input_type -> uber.submitqueue.stovepipe.ListOpenIssuesRequest - 2, // 29: uber.submitqueue.stovepipe.Stovepipe.Ping:output_type -> uber.submitqueue.stovepipe.PingResponse - 4, // 30: uber.submitqueue.stovepipe.Stovepipe.Ingest:output_type -> uber.submitqueue.stovepipe.IngestResponse - 14, // 31: uber.submitqueue.stovepipe.Stovepipe.GetRequestHistoryByID:output_type -> uber.submitqueue.stovepipe.GetRequestHistoryByIDResponse - 15, // 32: uber.submitqueue.stovepipe.Stovepipe.GetRequestHistoryByURI:output_type -> uber.submitqueue.stovepipe.GetRequestHistoryByURIResponse - 19, // 33: uber.submitqueue.stovepipe.Stovepipe.GetProjectStatusByID:output_type -> uber.submitqueue.stovepipe.GetProjectStatusByIDResponse - 20, // 34: uber.submitqueue.stovepipe.Stovepipe.GetProjectStatusByURI:output_type -> uber.submitqueue.stovepipe.GetProjectStatusByURIResponse - 23, // 35: uber.submitqueue.stovepipe.Stovepipe.List:output_type -> uber.submitqueue.stovepipe.ListResponse - 26, // 36: uber.submitqueue.stovepipe.Stovepipe.GetQueueStatus:output_type -> uber.submitqueue.stovepipe.GetQueueStatusResponse - 29, // 37: uber.submitqueue.stovepipe.Stovepipe.ListOpenIssues:output_type -> uber.submitqueue.stovepipe.ListOpenIssuesResponse - 29, // [29:38] is the sub-list for method output_type - 20, // [20:29] is the sub-list for method input_type - 20, // [20:20] is the sub-list for extension type_name - 20, // [20:20] is the sub-list for extension extendee - 0, // [0:20] is the sub-list for field type_name + 30, // 20: uber.submitqueue.stovepipe.GetIssuesByRequestIDResponse.issues:type_name -> uber.submitqueue.stovepipe.RequestIssueSummary + 1, // 21: uber.submitqueue.stovepipe.Stovepipe.Ping:input_type -> uber.submitqueue.stovepipe.PingRequest + 3, // 22: uber.submitqueue.stovepipe.Stovepipe.Ingest:input_type -> uber.submitqueue.stovepipe.IngestRequest + 5, // 23: uber.submitqueue.stovepipe.Stovepipe.GetRequestHistoryByID:input_type -> uber.submitqueue.stovepipe.GetRequestHistoryByIDRequest + 6, // 24: uber.submitqueue.stovepipe.Stovepipe.GetRequestHistoryByURI:input_type -> uber.submitqueue.stovepipe.GetRequestHistoryByURIRequest + 16, // 25: uber.submitqueue.stovepipe.Stovepipe.GetProjectStatusByID:input_type -> uber.submitqueue.stovepipe.GetProjectStatusByIDRequest + 17, // 26: uber.submitqueue.stovepipe.Stovepipe.GetProjectStatusByURI:input_type -> uber.submitqueue.stovepipe.GetProjectStatusByURIRequest + 22, // 27: uber.submitqueue.stovepipe.Stovepipe.List:input_type -> uber.submitqueue.stovepipe.ListRequest + 24, // 28: uber.submitqueue.stovepipe.Stovepipe.GetQueueStatus:input_type -> uber.submitqueue.stovepipe.GetQueueStatusRequest + 28, // 29: uber.submitqueue.stovepipe.Stovepipe.ListOpenIssues:input_type -> uber.submitqueue.stovepipe.ListOpenIssuesRequest + 31, // 30: uber.submitqueue.stovepipe.Stovepipe.GetIssuesByRequestID:input_type -> uber.submitqueue.stovepipe.GetIssuesByRequestIDRequest + 2, // 31: uber.submitqueue.stovepipe.Stovepipe.Ping:output_type -> uber.submitqueue.stovepipe.PingResponse + 4, // 32: uber.submitqueue.stovepipe.Stovepipe.Ingest:output_type -> uber.submitqueue.stovepipe.IngestResponse + 14, // 33: uber.submitqueue.stovepipe.Stovepipe.GetRequestHistoryByID:output_type -> uber.submitqueue.stovepipe.GetRequestHistoryByIDResponse + 15, // 34: uber.submitqueue.stovepipe.Stovepipe.GetRequestHistoryByURI:output_type -> uber.submitqueue.stovepipe.GetRequestHistoryByURIResponse + 19, // 35: uber.submitqueue.stovepipe.Stovepipe.GetProjectStatusByID:output_type -> uber.submitqueue.stovepipe.GetProjectStatusByIDResponse + 20, // 36: uber.submitqueue.stovepipe.Stovepipe.GetProjectStatusByURI:output_type -> uber.submitqueue.stovepipe.GetProjectStatusByURIResponse + 23, // 37: uber.submitqueue.stovepipe.Stovepipe.List:output_type -> uber.submitqueue.stovepipe.ListResponse + 26, // 38: uber.submitqueue.stovepipe.Stovepipe.GetQueueStatus:output_type -> uber.submitqueue.stovepipe.GetQueueStatusResponse + 29, // 39: uber.submitqueue.stovepipe.Stovepipe.ListOpenIssues:output_type -> uber.submitqueue.stovepipe.ListOpenIssuesResponse + 32, // 40: uber.submitqueue.stovepipe.Stovepipe.GetIssuesByRequestID:output_type -> uber.submitqueue.stovepipe.GetIssuesByRequestIDResponse + 31, // [31:41] is the sub-list for method output_type + 21, // [21:31] is the sub-list for method input_type + 21, // [21:21] is the sub-list for extension type_name + 21, // [21:21] is the sub-list for extension extendee + 0, // [0:21] is the sub-list for field type_name } func init() { file_stovepipe_proto_init() } @@ -2637,7 +2881,7 @@ func file_stovepipe_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_stovepipe_proto_rawDesc), len(file_stovepipe_proto_rawDesc)), NumEnums: 1, - NumMessages: 29, + NumMessages: 32, NumExtensions: 0, NumServices: 1, }, diff --git a/api/stovepipe/protopb/stovepipe.pb.yarpc.go b/api/stovepipe/protopb/stovepipe.pb.yarpc.go index e67b9806e..f15d009c1 100644 --- a/api/stovepipe/protopb/stovepipe.pb.yarpc.go +++ b/api/stovepipe/protopb/stovepipe.pb.yarpc.go @@ -30,6 +30,7 @@ type StovepipeYARPCClient interface { List(context.Context, *ListRequest, ...yarpc.CallOption) (*ListResponse, error) GetQueueStatus(context.Context, *GetQueueStatusRequest, ...yarpc.CallOption) (*GetQueueStatusResponse, error) ListOpenIssues(context.Context, *ListOpenIssuesRequest, ...yarpc.CallOption) (*ListOpenIssuesResponse, error) + GetIssuesByRequestID(context.Context, *GetIssuesByRequestIDRequest, ...yarpc.CallOption) (*GetIssuesByRequestIDResponse, error) } func newStovepipeYARPCClient(clientConfig transport.ClientConfig, anyResolver v2.AnyResolver, options ...v2.ClientOption) StovepipeYARPCClient { @@ -59,6 +60,7 @@ type StovepipeYARPCServer interface { List(context.Context, *ListRequest) (*ListResponse, error) GetQueueStatus(context.Context, *GetQueueStatusRequest) (*GetQueueStatusResponse, error) ListOpenIssues(context.Context, *ListOpenIssuesRequest) (*ListOpenIssuesResponse, error) + GetIssuesByRequestID(context.Context, *GetIssuesByRequestIDRequest) (*GetIssuesByRequestIDResponse, error) } type buildStovepipeYARPCProceduresParams struct { @@ -162,6 +164,16 @@ func buildStovepipeYARPCProcedures(params buildStovepipeYARPCProceduresParams) [ }, ), }, + { + MethodName: "GetIssuesByRequestID", + Handler: v2.NewUnaryHandler( + v2.UnaryHandlerParams{ + Handle: handler.GetIssuesByRequestID, + NewRequest: newStovepipeServiceGetIssuesByRequestIDYARPCRequest, + AnyResolver: params.AnyResolver, + }, + ), + }, }, OnewayHandlerParams: []v2.BuildProceduresOnewayHandlerParams{}, StreamHandlerParams: []v2.BuildProceduresStreamHandlerParams{}, @@ -382,6 +394,18 @@ func (c *_StovepipeYARPCCaller) ListOpenIssues(ctx context.Context, request *Lis return response, err } +func (c *_StovepipeYARPCCaller) GetIssuesByRequestID(ctx context.Context, request *GetIssuesByRequestIDRequest, options ...yarpc.CallOption) (*GetIssuesByRequestIDResponse, error) { + responseMessage, err := c.streamClient.Call(ctx, "GetIssuesByRequestID", request, newStovepipeServiceGetIssuesByRequestIDYARPCResponse, options...) + if responseMessage == nil { + return nil, err + } + response, ok := responseMessage.(*GetIssuesByRequestIDResponse) + if !ok { + return nil, v2.CastError(emptyStovepipeServiceGetIssuesByRequestIDYARPCResponse, responseMessage) + } + return response, err +} + type _StovepipeYARPCHandler struct { server StovepipeYARPCServer } @@ -530,6 +554,22 @@ func (h *_StovepipeYARPCHandler) ListOpenIssues(ctx context.Context, requestMess return response, err } +func (h *_StovepipeYARPCHandler) GetIssuesByRequestID(ctx context.Context, requestMessage proto.Message) (proto.Message, error) { + var request *GetIssuesByRequestIDRequest + var ok bool + if requestMessage != nil { + request, ok = requestMessage.(*GetIssuesByRequestIDRequest) + if !ok { + return nil, v2.CastError(emptyStovepipeServiceGetIssuesByRequestIDYARPCRequest, requestMessage) + } + } + response, err := h.server.GetIssuesByRequestID(ctx, request) + if response == nil { + return nil, err + } + return response, err +} + func newStovepipeServicePingYARPCRequest() proto.Message { return &PingRequest{} } @@ -602,6 +642,14 @@ func newStovepipeServiceListOpenIssuesYARPCResponse() proto.Message { return &ListOpenIssuesResponse{} } +func newStovepipeServiceGetIssuesByRequestIDYARPCRequest() proto.Message { + return &GetIssuesByRequestIDRequest{} +} + +func newStovepipeServiceGetIssuesByRequestIDYARPCResponse() proto.Message { + return &GetIssuesByRequestIDResponse{} +} + var ( emptyStovepipeServicePingYARPCRequest = &PingRequest{} emptyStovepipeServicePingYARPCResponse = &PingResponse{} @@ -621,126 +669,134 @@ var ( emptyStovepipeServiceGetQueueStatusYARPCResponse = &GetQueueStatusResponse{} emptyStovepipeServiceListOpenIssuesYARPCRequest = &ListOpenIssuesRequest{} emptyStovepipeServiceListOpenIssuesYARPCResponse = &ListOpenIssuesResponse{} + emptyStovepipeServiceGetIssuesByRequestIDYARPCRequest = &GetIssuesByRequestIDRequest{} + emptyStovepipeServiceGetIssuesByRequestIDYARPCResponse = &GetIssuesByRequestIDResponse{} ) var yarpcFileDescriptorClosurefabdb6b3c0b09022 = [][]byte{ // stovepipe.proto []byte{ - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0x19, 0xcb, 0x6e, 0x1b, 0xd7, - 0x55, 0x43, 0x4a, 0x14, 0x79, 0x28, 0x51, 0xca, 0xb5, 0x4c, 0xd3, 0x8c, 0x9d, 0xba, 0x93, 0xa6, - 0x51, 0xdc, 0x58, 0xaa, 0x95, 0x3e, 0xec, 0xa2, 0x28, 0x22, 0x5a, 0xb2, 0x44, 0xc4, 0x92, 0xe5, - 0x91, 0xd5, 0x00, 0xc9, 0x62, 0x70, 0x39, 0x73, 0x44, 0x4d, 0xcc, 0x79, 0x78, 0xee, 0x1d, 0xb5, - 0xca, 0xa2, 0x40, 0x03, 0x64, 0x55, 0xa0, 0x40, 0x7f, 0xa3, 0xdd, 0xf7, 0x13, 0xfa, 0x03, 0xdd, - 0xb4, 0xdb, 0x02, 0xfd, 0x82, 0x2e, 0xb2, 0x2d, 0xee, 0x63, 0x86, 0xc3, 0x87, 0x28, 0xd2, 0x6a, - 0x80, 0x16, 0xe8, 0x8e, 0xf7, 0xbc, 0xef, 0x79, 0xde, 0x33, 0x84, 0x15, 0xc6, 0xc3, 0x73, 0x8c, - 0xbc, 0x08, 0x37, 0xa2, 0x38, 0xe4, 0x21, 0x69, 0x26, 0x1d, 0x8c, 0x37, 0x58, 0xd2, 0xf1, 0x3d, - 0xfe, 0x3a, 0xc1, 0x04, 0x37, 0x32, 0x0a, 0xf3, 0x7d, 0xa8, 0x1e, 0x79, 0x41, 0xd7, 0xc2, 0xd7, - 0x09, 0x32, 0x4e, 0x1a, 0xb0, 0xe8, 0x23, 0x63, 0xb4, 0x8b, 0x0d, 0xe3, 0x9e, 0xb1, 0x5e, 0xb1, - 0xd2, 0xa3, 0xf9, 0xb5, 0x01, 0x4b, 0x8a, 0x92, 0x45, 0x61, 0xc0, 0xf0, 0x72, 0x52, 0xf2, 0x5d, - 0x58, 0x62, 0x18, 0x9f, 0x7b, 0x0e, 0xda, 0x01, 0xf5, 0xb1, 0x51, 0x90, 0xe8, 0xaa, 0x86, 0x1d, - 0x52, 0x1f, 0xc9, 0x1d, 0xa8, 0x70, 0xcf, 0x47, 0xc6, 0xa9, 0x1f, 0x35, 0x8a, 0xf7, 0x8c, 0xf5, - 0xa2, 0xd5, 0x07, 0x90, 0x26, 0x94, 0xcf, 0x42, 0xc6, 0x25, 0xf3, 0xbc, 0x64, 0xce, 0xce, 0x66, - 0x0c, 0xcb, 0xed, 0xa0, 0x8b, 0x8c, 0xa7, 0x26, 0xaf, 0xc1, 0x82, 0xbc, 0x94, 0xb6, 0x42, 0x1d, - 0xc8, 0x27, 0x00, 0x18, 0xd0, 0x4e, 0x0f, 0x7d, 0x0c, 0xb8, 0xb4, 0xa0, 0xb6, 0xf5, 0x83, 0x8d, - 0xcb, 0x1d, 0xb1, 0xf1, 0x42, 0x9c, 0x77, 0x33, 0x16, 0x2b, 0xc7, 0x6e, 0x32, 0xa8, 0xa5, 0x3a, - 0xf5, 0xe5, 0x6b, 0x50, 0xf0, 0x5c, 0xad, 0xb1, 0xe0, 0xb9, 0xe4, 0xc5, 0x88, 0xba, 0xea, 0xd6, - 0xc3, 0x19, 0xd4, 0x1d, 0x73, 0xca, 0x13, 0x36, 0xa0, 0xf4, 0x18, 0xee, 0xec, 0x61, 0x7a, 0xcb, - 0x7d, 0x8f, 0xf1, 0x30, 0xbe, 0x68, 0x5d, 0xb4, 0x77, 0x26, 0xdf, 0xfb, 0x2e, 0x40, 0xac, 0x08, - 0x6c, 0xcf, 0xd5, 0x9e, 0xaf, 0x68, 0x48, 0xdb, 0x35, 0xf7, 0xe0, 0xee, 0x18, 0xa1, 0x27, 0x56, - 0x7b, 0xb2, 0xd4, 0x55, 0x28, 0x26, 0xb1, 0xa7, 0xc5, 0x89, 0x9f, 0xe6, 0x57, 0x05, 0x58, 0xd2, - 0xfc, 0xbb, 0xe7, 0x18, 0x70, 0x72, 0x1b, 0xca, 0x28, 0x7e, 0xd8, 0x99, 0x5f, 0x16, 0xe5, 0xb9, - 0xed, 0x8a, 0x7c, 0xc8, 0x62, 0x6b, 0xfb, 0x4c, 0x8a, 0x29, 0x5a, 0xd5, 0x0c, 0x76, 0xc0, 0xc8, - 0x7b, 0xb0, 0x9c, 0x9a, 0xcd, 0x38, 0xe5, 0x28, 0x73, 0xa2, 0xb2, 0x3f, 0x67, 0x2d, 0x69, 0xb0, - 0x70, 0x10, 0x92, 0x3a, 0x2c, 0x48, 0xa1, 0x2a, 0x2b, 0xf6, 0xe7, 0x2c, 0x75, 0x24, 0xef, 0x41, - 0x2d, 0x4c, 0xb8, 0x13, 0xfa, 0x68, 0xc7, 0x48, 0x59, 0x18, 0x34, 0x16, 0xa4, 0x09, 0xcb, 0x1a, - 0x6a, 0x49, 0x20, 0xf9, 0x18, 0xe6, 0x5d, 0xca, 0x69, 0xa3, 0x24, 0xe3, 0xf3, 0xe1, 0xa4, 0xf8, - 0xe4, 0xef, 0xb6, 0x43, 0x39, 0xb5, 0x24, 0x67, 0x6b, 0x09, 0x20, 0x74, 0x9c, 0x24, 0x8e, 0x31, - 0x70, 0xd0, 0xfc, 0xd7, 0x3c, 0xac, 0x0e, 0x13, 0x92, 0xcf, 0x86, 0xaf, 0x62, 0x48, 0x6d, 0x1f, - 0x4d, 0xa1, 0xcd, 0xca, 0xdd, 0x55, 0xc8, 0x1a, 0xb9, 0xff, 0xa7, 0xb0, 0xd2, 0x49, 0xbc, 0x9e, - 0x6b, 0xf3, 0xd8, 0xeb, 0x76, 0x31, 0x46, 0x57, 0xe7, 0xda, 0x34, 0x77, 0x69, 0x09, 0x4e, 0x2d, - 0xb6, 0x26, 0xc5, 0xbc, 0x4c, 0xa5, 0x90, 0x13, 0x50, 0x10, 0xfb, 0xd4, 0x0b, 0x3c, 0x76, 0x86, - 0xae, 0x0c, 0xc0, 0xec, 0x72, 0x97, 0xa5, 0x94, 0xa7, 0x5a, 0x08, 0x79, 0x0d, 0x8d, 0x73, 0xda, - 0xf3, 0x5c, 0xca, 0xbd, 0x30, 0xb0, 0x4f, 0xa9, 0xc3, 0xed, 0x18, 0x9d, 0x30, 0x76, 0xd1, 0x95, - 0x21, 0xac, 0x6e, 0xfd, 0x78, 0x0a, 0x05, 0xbf, 0xcc, 0x44, 0x3c, 0xa5, 0x0e, 0xd7, 0x9a, 0xea, - 0xe7, 0x03, 0x50, 0x4b, 0x8b, 0x25, 0xaf, 0xa0, 0x1e, 0xc5, 0xe1, 0x17, 0xe8, 0x70, 0xa9, 0x8f, - 0xf5, 0x15, 0x2e, 0x4c, 0x1d, 0x87, 0x23, 0x25, 0x40, 0xc8, 0x65, 0x5a, 0xdd, 0x5a, 0x94, 0x83, - 0x65, 0xca, 0xce, 0xa0, 0xa1, 0xdc, 0x46, 0x03, 0xda, 0xbb, 0x60, 0x1e, 0xb3, 0x69, 0x87, 0x06, - 0x6e, 0x18, 0xa0, 0x3b, 0x43, 0x92, 0xe5, 0x1d, 0x58, 0x97, 0xf2, 0xb6, 0xb5, 0xb8, 0xed, 0x54, - 0x5a, 0xab, 0x02, 0x8b, 0x11, 0xbd, 0xe8, 0x85, 0xd4, 0x35, 0x7f, 0x04, 0xb7, 0x2e, 0xc9, 0x17, - 0x51, 0x84, 0xca, 0x9e, 0x7e, 0x11, 0xca, 0x73, 0xdb, 0x35, 0x1f, 0x64, 0xa9, 0x9a, 0xa9, 0x9b, - 0x44, 0xfe, 0x73, 0xb8, 0x7d, 0xa9, 0xf7, 0xc9, 0x77, 0xa0, 0x2a, 0x63, 0xe9, 0x62, 0x37, 0x46, - 0x95, 0xe0, 0x86, 0x05, 0x02, 0xb4, 0x23, 0x21, 0xe6, 0x5e, 0x66, 0xe2, 0xb0, 0x2b, 0xc9, 0x87, - 0x40, 0xf2, 0xf1, 0xb1, 0x9d, 0x30, 0x09, 0xb8, 0x14, 0x31, 0x6f, 0xad, 0xe6, 0x9c, 0xfc, 0x44, - 0xc0, 0xcd, 0xbf, 0x18, 0x50, 0x1b, 0xec, 0x56, 0x43, 0x1d, 0xce, 0x18, 0xea, 0x70, 0xe4, 0x63, - 0x28, 0xc9, 0x9e, 0x20, 0xda, 0x4c, 0x71, 0xbd, 0xba, 0xb5, 0x3e, 0x6d, 0x95, 0x5b, 0x9a, 0x8f, - 0x1c, 0xc3, 0x4a, 0x56, 0xc0, 0x89, 0xef, 0xd3, 0xf8, 0x42, 0x17, 0xc3, 0xfd, 0x49, 0xa2, 0xd2, - 0x58, 0x28, 0x0e, 0xab, 0x16, 0x0f, 0x9c, 0xcd, 0x3f, 0x1b, 0x63, 0x3b, 0xaf, 0x68, 0xe7, 0x7a, - 0xa4, 0xf4, 0x0d, 0x37, 0xfe, 0x73, 0x86, 0x17, 0xae, 0x6d, 0xf8, 0x17, 0xf0, 0xce, 0x65, 0x13, - 0x43, 0x1b, 0xbe, 0x0f, 0x95, 0x33, 0x09, 0xf7, 0x30, 0xb5, 0x7d, 0x1a, 0x85, 0x69, 0x0e, 0xf7, - 0x99, 0xcd, 0x3f, 0x1a, 0xf0, 0xf6, 0x1e, 0x72, 0x9d, 0x33, 0x6a, 0x26, 0x5e, 0x77, 0xe4, 0x89, - 0xc7, 0x84, 0x4e, 0x2b, 0xd6, 0x28, 0xde, 0x2b, 0x8a, 0xc7, 0x44, 0x7a, 0x26, 0x6f, 0x43, 0x25, - 0xa2, 0x5d, 0xb4, 0x99, 0xf7, 0xa5, 0x7a, 0x69, 0x2c, 0x58, 0x65, 0x01, 0x38, 0xf6, 0xbe, 0x94, - 0x72, 0x25, 0x92, 0x87, 0xaf, 0x30, 0x1d, 0x28, 0x92, 0xfc, 0xa5, 0x00, 0x98, 0x7f, 0x32, 0xe4, - 0x80, 0x1e, 0x32, 0xf6, 0xca, 0x51, 0x7a, 0x17, 0xc0, 0x39, 0xa3, 0x41, 0x17, 0xed, 0xfe, 0x44, - 0xad, 0x28, 0xc8, 0x49, 0xec, 0x7d, 0x6b, 0xd6, 0x7e, 0x6d, 0xc0, 0x5b, 0xda, 0xd4, 0x7e, 0x41, - 0x8b, 0x37, 0x9c, 0x96, 0x9e, 0xd6, 0xbf, 0x3e, 0x92, 0x0f, 0x60, 0xa5, 0x13, 0x23, 0x7d, 0x25, - 0x44, 0xea, 0x32, 0x17, 0xb6, 0x1a, 0x72, 0x76, 0x68, 0x84, 0x2a, 0x76, 0x31, 0xde, 0xd3, 0x8a, - 0xe6, 0x17, 0x91, 0x1e, 0xdd, 0x56, 0x55, 0xc3, 0x5e, 0x5e, 0x44, 0xd8, 0x2a, 0x43, 0x29, 0x46, - 0x96, 0xf4, 0xb8, 0xf9, 0xb7, 0xe2, 0x38, 0xaf, 0xe5, 0xca, 0xe0, 0x8a, 0xf2, 0xce, 0x9c, 0x5a, - 0xb8, 0xdc, 0xa9, 0xc5, 0x61, 0xa7, 0x8a, 0x3e, 0x47, 0x99, 0x42, 0xce, 0xeb, 0x3e, 0x47, 0x99, - 0x44, 0xbd, 0x3b, 0x3c, 0xad, 0x95, 0xe7, 0x06, 0xc7, 0xae, 0x09, 0xcb, 0x49, 0xe4, 0x52, 0x8e, - 0xae, 0x4d, 0xb9, 0x78, 0xc1, 0x80, 0x7a, 0xc1, 0x68, 0xe0, 0x36, 0x3f, 0x60, 0xe4, 0x17, 0xd0, - 0x8c, 0x31, 0x0a, 0x99, 0x27, 0x92, 0xda, 0x1e, 0xf6, 0x5d, 0x49, 0xfb, 0xae, 0xd1, 0xa7, 0x69, - 0x0d, 0x7a, 0xf1, 0x11, 0x34, 0x52, 0x2f, 0x2a, 0x57, 0x31, 0xdb, 0x09, 0xfd, 0xa8, 0x87, 0x1c, - 0x1b, 0x8b, 0xf7, 0x8c, 0xf5, 0xb2, 0x95, 0xce, 0x35, 0x4b, 0xa1, 0x9f, 0x68, 0x2c, 0x69, 0xe7, - 0x52, 0xa6, 0x2c, 0xcb, 0xef, 0xc1, 0xa4, 0xf2, 0x1b, 0xc9, 0x82, 0x5c, 0x86, 0x7d, 0x1f, 0x56, - 0x02, 0xfc, 0x35, 0xb7, 0x73, 0x99, 0x54, 0x51, 0x0f, 0x29, 0x01, 0x3e, 0x4a, 0xb3, 0xa9, 0x75, - 0x03, 0xde, 0xca, 0x5d, 0x56, 0x87, 0xf6, 0xef, 0x45, 0xd9, 0xe2, 0xc6, 0x15, 0xc4, 0xff, 0x63, - 0xfb, 0x3f, 0x1e, 0xdb, 0x6f, 0x0a, 0xd9, 0x1c, 0xd6, 0x83, 0xe1, 0xbf, 0x32, 0x98, 0x0f, 0xe0, - 0x86, 0x44, 0xda, 0x83, 0x21, 0x2d, 0xc9, 0x90, 0xae, 0x4a, 0xd4, 0x49, 0x2e, 0xae, 0xdf, 0x83, - 0x1a, 0x75, 0x1c, 0x8c, 0xfa, 0x94, 0x8b, 0x92, 0x72, 0x29, 0x85, 0x4a, 0xaa, 0xd1, 0xe5, 0xa2, - 0x3c, 0x6e, 0xb9, 0x78, 0x17, 0x96, 0xd3, 0x20, 0xab, 0x37, 0x4d, 0x45, 0x76, 0xe8, 0xb4, 0x37, - 0xca, 0xf7, 0x0c, 0xf9, 0x21, 0xac, 0x75, 0x62, 0xe1, 0x66, 0x7b, 0x90, 0x16, 0x24, 0x2d, 0x51, - 0xb8, 0xa3, 0x1c, 0x87, 0xf9, 0x8d, 0x01, 0xd5, 0x67, 0xde, 0x55, 0xeb, 0xee, 0x4f, 0xe0, 0x56, - 0xfe, 0x26, 0x61, 0x6c, 0xd3, 0x53, 0x8e, 0x71, 0xb6, 0x6d, 0xed, 0xcf, 0x59, 0x37, 0xfa, 0x97, - 0x7a, 0x1e, 0x6f, 0x0b, 0xec, 0x01, 0x23, 0x1b, 0x40, 0x32, 0xbe, 0x0e, 0x9e, 0x86, 0x31, 0x0a, - 0x16, 0xb9, 0x90, 0xef, 0x1b, 0xd6, 0x6a, 0x8a, 0x6b, 0x49, 0xd4, 0xc1, 0xb5, 0x46, 0x50, 0xab, - 0x0e, 0x6b, 0x99, 0xae, 0x5e, 0xf8, 0x2b, 0x8c, 0xed, 0x4e, 0x98, 0x04, 0xee, 0x00, 0x3c, 0x89, - 0xa2, 0x14, 0x6e, 0xfe, 0x06, 0x96, 0xd4, 0xc5, 0x75, 0xf7, 0x78, 0x0a, 0x65, 0x1d, 0xec, 0x59, - 0x9e, 0x19, 0xe9, 0xbb, 0x26, 0xe3, 0x1d, 0x57, 0x08, 0x85, 0x31, 0x85, 0x60, 0x3e, 0x83, 0x9b, - 0x7b, 0xc8, 0xe5, 0xa2, 0xae, 0xd7, 0xf3, 0x6b, 0x0c, 0x76, 0xf3, 0xaf, 0x06, 0xdc, 0x1c, 0xbb, - 0xf4, 0x8b, 0x21, 0x7c, 0x8e, 0x31, 0xf3, 0xc2, 0x40, 0x0a, 0x2c, 0x5a, 0xe9, 0x91, 0x6c, 0xc3, - 0x82, 0xca, 0xf5, 0x37, 0xf8, 0x7e, 0xa1, 0x38, 0xc9, 0x63, 0xb8, 0x8d, 0xa7, 0xa7, 0xe8, 0x70, - 0xef, 0x1c, 0xed, 0xd3, 0x38, 0xf4, 0x45, 0xe7, 0xf1, 0x3d, 0x9e, 0xab, 0xbf, 0x7a, 0x46, 0xf0, - 0x34, 0x0e, 0xfd, 0x27, 0x12, 0x2d, 0x2a, 0xce, 0x84, 0x65, 0x65, 0x7e, 0x5a, 0x1c, 0xf3, 0xaa, - 0x33, 0x6a, 0xa0, 0xa8, 0x0d, 0xf3, 0xb7, 0x06, 0xd4, 0x87, 0x9d, 0xa4, 0xc3, 0x35, 0xde, 0x4b, - 0xdf, 0xc2, 0x87, 0x92, 0x7f, 0x1a, 0xb0, 0xd4, 0x66, 0x2c, 0xc1, 0xb4, 0x33, 0xdd, 0x82, 0x45, - 0x4e, 0xd9, 0xab, 0x7e, 0x5b, 0x2a, 0x89, 0x63, 0xdb, 0x15, 0x88, 0x24, 0xf0, 0x72, 0xcf, 0xc4, - 0x92, 0x38, 0xb6, 0x5d, 0x52, 0x87, 0x12, 0x93, 0x82, 0xb5, 0x4b, 0xf4, 0x49, 0xf4, 0x93, 0x53, - 0x2f, 0x16, 0x2d, 0x07, 0x31, 0xb0, 0xb3, 0x5d, 0x49, 0xb5, 0xa6, 0x55, 0x89, 0x3a, 0x46, 0x0c, - 0x5a, 0x6a, 0x69, 0x1a, 0x6a, 0x89, 0x0b, 0xc3, 0x2d, 0x31, 0xbf, 0x6e, 0x95, 0x06, 0xd6, 0xad, - 0x51, 0x5f, 0x2f, 0x8e, 0xfa, 0xda, 0x83, 0x9b, 0xa2, 0x1e, 0x9e, 0x47, 0x18, 0xc8, 0xeb, 0x5e, - 0x91, 0x8f, 0x03, 0xa5, 0x5a, 0x98, 0x58, 0xaa, 0xc5, 0xe1, 0xd7, 0xe2, 0x57, 0x06, 0xd4, 0x87, - 0x75, 0xf5, 0xd7, 0x14, 0x4f, 0x42, 0xa6, 0x59, 0x53, 0xf2, 0x61, 0xb1, 0x34, 0xdf, 0xb4, 0xf5, - 0x77, 0xbf, 0x07, 0x2b, 0x43, 0xc1, 0x27, 0x77, 0xa0, 0xf1, 0xe2, 0x64, 0xf7, 0x64, 0xd7, 0xde, - 0x3d, 0xdc, 0x6e, 0x3d, 0xdb, 0x3d, 0xd8, 0x3d, 0x7c, 0x69, 0x9f, 0x1c, 0x7e, 0x72, 0xf8, 0xfc, - 0xd3, 0xc3, 0xd5, 0xb9, 0xb1, 0x58, 0xf5, 0x73, 0x67, 0xd5, 0x20, 0x77, 0xe1, 0xf6, 0x08, 0x76, - 0xa7, 0x7d, 0xac, 0xd0, 0x85, 0xad, 0x7f, 0x94, 0xa1, 0x72, 0x9c, 0x1a, 0x4e, 0x3e, 0x87, 0xf9, - 0x23, 0x2f, 0xe8, 0x92, 0xf7, 0x27, 0x4e, 0xdb, 0xfe, 0x87, 0xd3, 0xe6, 0xfa, 0xd5, 0x84, 0xca, - 0x81, 0xe6, 0x1c, 0xa1, 0x50, 0x52, 0x9f, 0x13, 0xc9, 0x07, 0x13, 0x9d, 0x97, 0xff, 0xcc, 0xd9, - 0xbc, 0x3f, 0x0d, 0x69, 0xa6, 0xe2, 0xf7, 0x86, 0x6c, 0x5e, 0xa3, 0xeb, 0x26, 0x79, 0x34, 0x49, - 0xce, 0xa4, 0x0f, 0x8e, 0xcd, 0xc7, 0x6f, 0xc0, 0x99, 0x19, 0xf4, 0x07, 0xd5, 0x28, 0xc6, 0xec, - 0x91, 0x64, 0x56, 0xb9, 0xfd, 0x15, 0xab, 0xf9, 0xb3, 0x37, 0x61, 0xcd, 0x6c, 0xfa, 0x9d, 0x01, - 0x6b, 0xe3, 0x76, 0x11, 0xf2, 0xd3, 0x2b, 0xc4, 0x5e, 0xb6, 0xa0, 0x36, 0x1f, 0xcd, 0xce, 0x38, - 0x1c, 0xb2, 0xd1, 0xe7, 0x33, 0x99, 0x51, 0x6a, 0xce, 0x3f, 0x8f, 0xdf, 0x80, 0x33, 0x33, 0xe8, - 0x73, 0x98, 0x17, 0x3d, 0x60, 0x72, 0x0d, 0xe4, 0x9e, 0x26, 0x93, 0x6b, 0x20, 0x3f, 0xca, 0xcd, - 0x39, 0x72, 0x01, 0xb5, 0xc1, 0xb9, 0x41, 0x1e, 0x5e, 0x61, 0xeb, 0xe8, 0x20, 0x6e, 0x6e, 0xcd, - 0xc2, 0x92, 0x57, 0x3d, 0xd8, 0xdb, 0x26, 0xab, 0x1e, 0xdb, 0x73, 0x27, 0xab, 0x1e, 0xdf, 0x3a, - 0xcd, 0xb9, 0x16, 0xc2, 0x3b, 0x4e, 0xe8, 0x4f, 0x60, 0x6d, 0xd5, 0xb2, 0x1e, 0x74, 0x14, 0x87, - 0x3c, 0x3c, 0x32, 0x3e, 0x7b, 0xd8, 0xf5, 0xf8, 0x59, 0xd2, 0xd9, 0x70, 0x42, 0x7f, 0x53, 0x30, - 0x6e, 0xe6, 0x18, 0x37, 0x69, 0xe4, 0x6d, 0x66, 0xcc, 0x9b, 0xf2, 0xef, 0x9e, 0xa8, 0xd3, 0x29, - 0xc9, 0x1f, 0x1f, 0xfd, 0x3b, 0x00, 0x00, 0xff, 0xff, 0xa8, 0x1b, 0xa5, 0xdd, 0x0a, 0x1a, 0x00, - 0x00, + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0x19, 0x4d, 0x6f, 0x1b, 0xc7, + 0x55, 0x4b, 0x52, 0x94, 0xf8, 0x28, 0x51, 0xca, 0x58, 0xa6, 0x69, 0xc6, 0x4e, 0xdd, 0x4d, 0xd3, + 0x28, 0x6e, 0x2c, 0xd5, 0x4a, 0x3f, 0xec, 0xa2, 0x28, 0x22, 0x5a, 0xb2, 0x44, 0xc4, 0x92, 0xe5, + 0x95, 0xd5, 0x00, 0xc9, 0x61, 0x31, 0xe4, 0x8e, 0xa8, 0x89, 0xc8, 0xdd, 0xf5, 0xce, 0xac, 0x5a, + 0xe5, 0x50, 0xa0, 0x01, 0x72, 0x2a, 0x10, 0xa0, 0x3f, 0xa2, 0x28, 0xd0, 0xde, 0xfb, 0x13, 0xfa, + 0x07, 0x7a, 0x69, 0x7f, 0x40, 0xef, 0x05, 0x7a, 0xc8, 0xb5, 0x98, 0x8f, 0x5d, 0x2e, 0x97, 0xcb, + 0x25, 0x65, 0x35, 0x40, 0x0b, 0xe4, 0xc6, 0x79, 0x6f, 0xde, 0xc7, 0xbc, 0xef, 0xb7, 0x84, 0x15, + 0xc6, 0xbd, 0x0b, 0xe2, 0x53, 0x9f, 0x6c, 0xf8, 0x81, 0xc7, 0x3d, 0xd4, 0x0c, 0x3b, 0x24, 0xd8, + 0x60, 0x61, 0x67, 0x40, 0xf9, 0xab, 0x90, 0x84, 0x64, 0x23, 0xbe, 0x61, 0xbe, 0x0b, 0xd5, 0x23, + 0xea, 0xf6, 0x2c, 0xf2, 0x2a, 0x24, 0x8c, 0xa3, 0x06, 0x2c, 0x0c, 0x08, 0x63, 0xb8, 0x47, 0x1a, + 0xc6, 0x3d, 0x63, 0xbd, 0x62, 0x45, 0x47, 0xf3, 0x4b, 0x03, 0x96, 0xd4, 0x4d, 0xe6, 0x7b, 0x2e, + 0x23, 0x93, 0xaf, 0xa2, 0xef, 0xc2, 0x12, 0x23, 0xc1, 0x05, 0xed, 0x12, 0xdb, 0xc5, 0x03, 0xd2, + 0x28, 0x48, 0x74, 0x55, 0xc3, 0x0e, 0xf1, 0x80, 0xa0, 0x3b, 0x50, 0xe1, 0x74, 0x40, 0x18, 0xc7, + 0x03, 0xbf, 0x51, 0xbc, 0x67, 0xac, 0x17, 0xad, 0x21, 0x00, 0x35, 0x61, 0xf1, 0xcc, 0x63, 0x5c, + 0x12, 0x97, 0x24, 0x71, 0x7c, 0x36, 0x03, 0x58, 0x6e, 0xbb, 0x3d, 0xc2, 0x78, 0xa4, 0xf2, 0x1a, + 0xcc, 0xcb, 0x47, 0x69, 0x2d, 0xd4, 0x01, 0x7d, 0x04, 0x40, 0x5c, 0xdc, 0xe9, 0x93, 0x01, 0x71, + 0xb9, 0xd4, 0xa0, 0xb6, 0xf5, 0x83, 0x8d, 0xc9, 0x86, 0xd8, 0x78, 0x21, 0xce, 0xbb, 0x31, 0x89, + 0x95, 0x20, 0x37, 0x19, 0xd4, 0x22, 0x99, 0xfa, 0xf1, 0x35, 0x28, 0x50, 0x47, 0x4b, 0x2c, 0x50, + 0x07, 0xbd, 0x18, 0x13, 0x57, 0xdd, 0x7a, 0x78, 0x05, 0x71, 0xc7, 0x1c, 0xf3, 0x90, 0x8d, 0x08, + 0x3d, 0x86, 0x3b, 0x7b, 0x24, 0x7a, 0xe5, 0x3e, 0x65, 0xdc, 0x0b, 0x2e, 0x5b, 0x97, 0xed, 0x9d, + 0xfc, 0x77, 0xdf, 0x05, 0x08, 0xd4, 0x05, 0x9b, 0x3a, 0xda, 0xf2, 0x15, 0x0d, 0x69, 0x3b, 0xe6, + 0x1e, 0xdc, 0xcd, 0x60, 0x7a, 0x62, 0xb5, 0xf3, 0xb9, 0xae, 0x42, 0x31, 0x0c, 0xa8, 0x66, 0x27, + 0x7e, 0x9a, 0x5f, 0x14, 0x60, 0x49, 0xd3, 0xef, 0x5e, 0x10, 0x97, 0xa3, 0xdb, 0xb0, 0x48, 0xc4, + 0x0f, 0x3b, 0xb6, 0xcb, 0x82, 0x3c, 0xb7, 0x1d, 0x11, 0x0f, 0xb1, 0x6f, 0xed, 0x01, 0x93, 0x6c, + 0x8a, 0x56, 0x35, 0x86, 0x1d, 0x30, 0xf4, 0x0e, 0x2c, 0x47, 0x6a, 0x33, 0x8e, 0x39, 0x91, 0x31, + 0x51, 0xd9, 0x9f, 0xb3, 0x96, 0x34, 0x58, 0x18, 0x88, 0xa0, 0x3a, 0xcc, 0x4b, 0xa6, 0x2a, 0x2a, + 0xf6, 0xe7, 0x2c, 0x75, 0x44, 0xef, 0x40, 0xcd, 0x0b, 0x79, 0xd7, 0x1b, 0x10, 0x3b, 0x20, 0x98, + 0x79, 0x6e, 0x63, 0x5e, 0xaa, 0xb0, 0xac, 0xa1, 0x96, 0x04, 0xa2, 0x0f, 0xa1, 0xe4, 0x60, 0x8e, + 0x1b, 0x65, 0xe9, 0x9f, 0xf7, 0xf3, 0xfc, 0x93, 0x7c, 0xdb, 0x0e, 0xe6, 0xd8, 0x92, 0x94, 0xad, + 0x25, 0x00, 0xaf, 0xdb, 0x0d, 0x83, 0x80, 0xb8, 0x5d, 0x62, 0xfe, 0xbb, 0x04, 0xab, 0xe9, 0x8b, + 0xe8, 0x93, 0xf4, 0x53, 0x0c, 0x29, 0xed, 0x83, 0x19, 0xa4, 0x59, 0x89, 0xb7, 0x0a, 0x5e, 0x63, + 0xef, 0xff, 0x18, 0x56, 0x3a, 0x21, 0xed, 0x3b, 0x36, 0x0f, 0x68, 0xaf, 0x47, 0x02, 0xe2, 0xe8, + 0x58, 0x9b, 0xe5, 0x2d, 0x2d, 0x41, 0xa9, 0xd9, 0xd6, 0x24, 0x9b, 0x97, 0x11, 0x17, 0x74, 0x02, + 0x0a, 0x62, 0x9f, 0x52, 0x97, 0xb2, 0x33, 0xe2, 0x48, 0x07, 0x5c, 0x9d, 0xef, 0xb2, 0xe4, 0xf2, + 0x54, 0x33, 0x41, 0xaf, 0xa0, 0x71, 0x81, 0xfb, 0xd4, 0xc1, 0x9c, 0x7a, 0xae, 0x7d, 0x8a, 0xbb, + 0xdc, 0x0e, 0x48, 0xd7, 0x0b, 0x1c, 0xe2, 0x48, 0x17, 0x56, 0xb7, 0x7e, 0x3c, 0x83, 0x80, 0x5f, + 0xc6, 0x2c, 0x9e, 0xe2, 0x2e, 0xd7, 0x92, 0xea, 0x17, 0x23, 0x50, 0x4b, 0xb3, 0x45, 0xe7, 0x50, + 0xf7, 0x03, 0xef, 0x33, 0xd2, 0xe5, 0x52, 0x1e, 0x1b, 0x0a, 0x9c, 0x9f, 0xd9, 0x0f, 0x47, 0x8a, + 0x81, 0xe0, 0xcb, 0xb4, 0xb8, 0x35, 0x3f, 0x01, 0x8b, 0x85, 0x9d, 0x41, 0x43, 0x99, 0x0d, 0xbb, + 0xb8, 0x7f, 0xc9, 0x28, 0xb3, 0x71, 0x07, 0xbb, 0x8e, 0xe7, 0x12, 0xe7, 0x0a, 0x41, 0x96, 0x34, + 0x60, 0x5d, 0xf2, 0xdb, 0xd6, 0xec, 0xb6, 0x23, 0x6e, 0xad, 0x0a, 0x2c, 0xf8, 0xf8, 0xb2, 0xef, + 0x61, 0xc7, 0xfc, 0x11, 0xdc, 0x9a, 0x10, 0x2f, 0x22, 0x09, 0x95, 0x3e, 0xc3, 0x24, 0x94, 0xe7, + 0xb6, 0x63, 0x3e, 0x88, 0x43, 0x35, 0x16, 0x97, 0x77, 0xfd, 0xe7, 0x70, 0x7b, 0xa2, 0xf5, 0xd1, + 0x77, 0xa0, 0x2a, 0x7d, 0xe9, 0x90, 0x5e, 0x40, 0x54, 0x80, 0x1b, 0x16, 0x08, 0xd0, 0x8e, 0x84, + 0x98, 0x7b, 0xb1, 0x8a, 0x69, 0x53, 0xa2, 0xf7, 0x01, 0x25, 0xfd, 0x63, 0x77, 0xbd, 0xd0, 0xe5, + 0x92, 0x45, 0xc9, 0x5a, 0x4d, 0x18, 0xf9, 0x89, 0x80, 0x9b, 0x7f, 0x35, 0xa0, 0x36, 0x5a, 0xad, + 0x52, 0x15, 0xce, 0x48, 0x55, 0x38, 0xf4, 0x21, 0x94, 0x65, 0x4d, 0x10, 0x65, 0xa6, 0xb8, 0x5e, + 0xdd, 0x5a, 0x9f, 0x35, 0xcb, 0x2d, 0x4d, 0x87, 0x8e, 0x61, 0x25, 0x4e, 0xe0, 0x70, 0x30, 0xc0, + 0xc1, 0xa5, 0x4e, 0x86, 0xfb, 0x79, 0xac, 0x22, 0x5f, 0x28, 0x0a, 0xab, 0x16, 0x8c, 0x9c, 0xcd, + 0xbf, 0x18, 0x99, 0x95, 0x57, 0x94, 0x73, 0xdd, 0x52, 0x86, 0x8a, 0x1b, 0xff, 0x3d, 0xc5, 0x0b, + 0xd7, 0x56, 0xfc, 0x33, 0x78, 0x6b, 0x52, 0xc7, 0xd0, 0x8a, 0xef, 0x43, 0xe5, 0x4c, 0xc2, 0x29, + 0x89, 0x74, 0x9f, 0x45, 0x60, 0x14, 0xc3, 0x43, 0x62, 0xf3, 0x4f, 0x06, 0xbc, 0xb9, 0x47, 0xb8, + 0x8e, 0x19, 0xd5, 0x13, 0xaf, 0xdb, 0xf2, 0xc4, 0x30, 0xa1, 0xc3, 0x8a, 0x35, 0x8a, 0xf7, 0x8a, + 0x62, 0x98, 0x88, 0xce, 0xe8, 0x4d, 0xa8, 0xf8, 0xb8, 0x47, 0x6c, 0x46, 0x3f, 0x57, 0x93, 0xc6, + 0xbc, 0xb5, 0x28, 0x00, 0xc7, 0xf4, 0x73, 0xc9, 0x57, 0x22, 0xb9, 0x77, 0x4e, 0xa2, 0x86, 0x22, + 0xaf, 0xbf, 0x14, 0x00, 0xf3, 0xcf, 0x86, 0x6c, 0xd0, 0x29, 0x65, 0xa7, 0xb6, 0xd2, 0xbb, 0x00, + 0xdd, 0x33, 0xec, 0xf6, 0x88, 0x3d, 0xec, 0xa8, 0x15, 0x05, 0x39, 0x09, 0xe8, 0x37, 0xa6, 0xed, + 0x97, 0x06, 0xbc, 0xa1, 0x55, 0x1d, 0x26, 0xb4, 0x98, 0xe1, 0x34, 0xf7, 0x28, 0xff, 0xf5, 0x11, + 0xbd, 0x07, 0x2b, 0x9d, 0x80, 0xe0, 0x73, 0xc1, 0x52, 0xa7, 0xb9, 0xd0, 0xd5, 0x90, 0xbd, 0x43, + 0x23, 0x54, 0xb2, 0x8b, 0xf6, 0x1e, 0x65, 0x34, 0xbf, 0xf4, 0x75, 0xeb, 0xb6, 0xaa, 0x1a, 0xf6, + 0xf2, 0xd2, 0x27, 0xad, 0x45, 0x28, 0x07, 0x84, 0x85, 0x7d, 0x6e, 0xfe, 0xbd, 0x98, 0x65, 0xb5, + 0x44, 0x1a, 0x4c, 0x49, 0xef, 0xd8, 0xa8, 0x85, 0xc9, 0x46, 0x2d, 0xa6, 0x8d, 0x2a, 0xea, 0x1c, + 0x66, 0x0a, 0x59, 0xd2, 0x75, 0x0e, 0x33, 0x89, 0x7a, 0x3b, 0xdd, 0xad, 0x95, 0xe5, 0x46, 0xdb, + 0xae, 0x09, 0xcb, 0xa1, 0xef, 0x60, 0x4e, 0x1c, 0x1b, 0x73, 0x31, 0xc1, 0x80, 0x9a, 0x60, 0x34, + 0x70, 0x9b, 0x1f, 0x30, 0xf4, 0x0b, 0x68, 0x06, 0xc4, 0xf7, 0x18, 0x15, 0x41, 0x6d, 0xa7, 0x6d, + 0x57, 0xd6, 0xb6, 0x6b, 0x0c, 0xef, 0xb4, 0x46, 0xad, 0xf8, 0x08, 0x1a, 0x91, 0x15, 0x95, 0xa9, + 0x98, 0xdd, 0xf5, 0x06, 0x7e, 0x9f, 0x70, 0xd2, 0x58, 0xb8, 0x67, 0xac, 0x2f, 0x5a, 0x51, 0x5f, + 0xb3, 0x14, 0xfa, 0x89, 0xc6, 0xa2, 0x76, 0x22, 0x64, 0x16, 0x65, 0xfa, 0x3d, 0xc8, 0x4b, 0xbf, + 0xb1, 0x28, 0x48, 0x44, 0xd8, 0xf7, 0x61, 0xc5, 0x25, 0xbf, 0xe6, 0x76, 0x22, 0x92, 0x2a, 0x6a, + 0x90, 0x12, 0xe0, 0xa3, 0x28, 0x9a, 0x5a, 0x37, 0xe0, 0x8d, 0xc4, 0x63, 0xb5, 0x6b, 0xff, 0x51, + 0x94, 0x25, 0x2e, 0x2b, 0x21, 0xbe, 0xf5, 0xed, 0xff, 0xb9, 0x6f, 0xbf, 0x2e, 0xc4, 0x7d, 0x58, + 0x37, 0x86, 0xff, 0x49, 0x67, 0x3e, 0x80, 0x1b, 0x12, 0x69, 0x8f, 0xba, 0xb4, 0x2c, 0x5d, 0xba, + 0x2a, 0x51, 0x27, 0x09, 0xbf, 0x7e, 0x0f, 0x6a, 0xb8, 0xdb, 0x25, 0xfe, 0xf0, 0xe6, 0x82, 0xbc, + 0xb9, 0x14, 0x41, 0xe5, 0xad, 0xf1, 0xe5, 0x62, 0x31, 0x6b, 0xb9, 0x78, 0x1b, 0x96, 0x23, 0x27, + 0xab, 0x99, 0xa6, 0x22, 0x2b, 0x74, 0x54, 0x1b, 0xe5, 0x3c, 0x83, 0x7e, 0x08, 0x6b, 0x9d, 0x40, + 0x98, 0xd9, 0x1e, 0xbd, 0x0b, 0xf2, 0x2e, 0x52, 0xb8, 0xa3, 0x04, 0x85, 0xf9, 0xb5, 0x01, 0xd5, + 0x67, 0x74, 0xda, 0xba, 0xfb, 0x13, 0xb8, 0x95, 0x7c, 0x89, 0x17, 0xd8, 0xf8, 0x94, 0x93, 0x20, + 0xde, 0xb6, 0xf6, 0xe7, 0xac, 0x1b, 0xc3, 0x47, 0x3d, 0x0f, 0xb6, 0x05, 0xf6, 0x80, 0xa1, 0x0d, + 0x40, 0x31, 0x5d, 0x87, 0x9c, 0x7a, 0x01, 0x11, 0x24, 0x72, 0x21, 0xdf, 0x37, 0xac, 0xd5, 0x08, + 0xd7, 0x92, 0xa8, 0x83, 0x6b, 0xb5, 0xa0, 0x56, 0x1d, 0xd6, 0x62, 0x59, 0x7d, 0xef, 0x57, 0x24, + 0xb0, 0x3b, 0x5e, 0xe8, 0x3a, 0x23, 0xf0, 0xd0, 0xf7, 0x23, 0xb8, 0xf9, 0x1b, 0x58, 0x52, 0x0f, + 0xd7, 0xd5, 0xe3, 0x29, 0x2c, 0x6a, 0x67, 0x5f, 0x65, 0xcc, 0x88, 0xe6, 0x9a, 0x98, 0x36, 0x2b, + 0x11, 0x0a, 0x19, 0x89, 0x60, 0x3e, 0x83, 0x9b, 0x7b, 0x84, 0xcb, 0x45, 0x5d, 0xaf, 0xe7, 0xd7, + 0x68, 0xec, 0xe6, 0xdf, 0x0c, 0xb8, 0x99, 0xb9, 0xf4, 0x8b, 0x26, 0x7c, 0x41, 0x02, 0x46, 0x3d, + 0x57, 0x32, 0x2c, 0x5a, 0xd1, 0x11, 0x6d, 0xc3, 0xbc, 0x8a, 0xf5, 0xd7, 0xf8, 0x7e, 0xa1, 0x28, + 0xd1, 0x63, 0xb8, 0x4d, 0x4e, 0x4f, 0x49, 0x97, 0xd3, 0x0b, 0x62, 0x9f, 0x06, 0xde, 0x40, 0x54, + 0x9e, 0x01, 0xe5, 0x89, 0xfc, 0xab, 0xc7, 0x17, 0x9e, 0x06, 0xde, 0xe0, 0x89, 0x44, 0x8b, 0x8c, + 0x33, 0x61, 0x59, 0xa9, 0x1f, 0x25, 0x47, 0x49, 0x55, 0x46, 0x0d, 0x14, 0xb9, 0x61, 0xfe, 0xd6, + 0x80, 0x7a, 0xda, 0x48, 0xda, 0x5d, 0xd9, 0x56, 0xfa, 0x06, 0x3e, 0x94, 0xfc, 0xd3, 0x80, 0xa5, + 0x36, 0x63, 0x21, 0x89, 0x2a, 0xd3, 0x2d, 0x58, 0xe0, 0x98, 0x9d, 0x0f, 0xcb, 0x52, 0x59, 0x1c, + 0xdb, 0x8e, 0x40, 0x84, 0x2e, 0x4d, 0x8c, 0x89, 0x65, 0x71, 0x6c, 0x3b, 0xa8, 0x0e, 0x65, 0x26, + 0x19, 0x6b, 0x93, 0xe8, 0x93, 0xa8, 0x27, 0xa7, 0x34, 0x10, 0x25, 0x87, 0x10, 0xd7, 0x8e, 0x77, + 0x25, 0x55, 0x9a, 0x56, 0x25, 0xea, 0x98, 0x10, 0xb7, 0xa5, 0x96, 0xa6, 0x54, 0x49, 0x9c, 0x4f, + 0x97, 0xc4, 0xe4, 0xba, 0x55, 0x1e, 0x59, 0xb7, 0xc6, 0x6d, 0xbd, 0x30, 0x6e, 0x6b, 0x0a, 0x37, + 0x45, 0x3e, 0x3c, 0xf7, 0x89, 0x2b, 0x9f, 0x3b, 0x25, 0x1e, 0x47, 0x52, 0xb5, 0x90, 0x9b, 0xaa, + 0xc5, 0xf4, 0xb4, 0xf8, 0x85, 0x01, 0xf5, 0xb4, 0xac, 0xe1, 0x9a, 0x42, 0x25, 0x64, 0x96, 0x35, + 0x25, 0xe9, 0x16, 0x4b, 0xd3, 0xcd, 0x9c, 0x7f, 0x7f, 0x30, 0xe0, 0x86, 0x7e, 0xe2, 0x35, 0xdd, + 0x8b, 0xa0, 0x74, 0x4e, 0x5d, 0x47, 0xbf, 0x53, 0xfe, 0x1e, 0x71, 0x46, 0x69, 0xd4, 0x19, 0x13, + 0xbc, 0x3e, 0x9f, 0xed, 0x75, 0xf3, 0x2b, 0xb5, 0xb5, 0x28, 0x3b, 0xb5, 0xa2, 0xa5, 0xfc, 0x9a, + 0x5b, 0xcb, 0x88, 0xf7, 0x8a, 0xb9, 0xde, 0x2b, 0xa5, 0xbd, 0xf7, 0x47, 0xb5, 0x99, 0x64, 0x28, + 0xa4, 0x7d, 0xb8, 0x97, 0xf2, 0xe1, 0xe6, 0x0c, 0x75, 0x34, 0xd3, 0x95, 0xc3, 0xbc, 0x29, 0x8c, + 0xe4, 0x4d, 0x86, 0x8b, 0x8b, 0x19, 0x2e, 0xbe, 0xdf, 0x87, 0x95, 0x54, 0x7e, 0xa3, 0x3b, 0xd0, + 0x78, 0x71, 0xb2, 0x7b, 0xb2, 0x6b, 0xef, 0x1e, 0x6e, 0xb7, 0x9e, 0xed, 0x1e, 0xec, 0x1e, 0xbe, + 0xb4, 0x4f, 0x0e, 0x3f, 0x3a, 0x7c, 0xfe, 0xf1, 0xe1, 0xea, 0x5c, 0x26, 0x56, 0xfd, 0xdc, 0x59, + 0x35, 0xd0, 0x5d, 0xb8, 0x3d, 0x86, 0xdd, 0x69, 0x1f, 0x2b, 0x74, 0x61, 0xeb, 0x5f, 0x15, 0xa8, + 0x1c, 0x47, 0xef, 0x42, 0x9f, 0x42, 0xe9, 0x88, 0xba, 0x3d, 0xf4, 0x6e, 0xee, 0x40, 0x35, 0xfc, + 0x36, 0xde, 0x5c, 0x9f, 0x7e, 0x51, 0xd9, 0xd7, 0x9c, 0x43, 0x18, 0xca, 0xea, 0x8b, 0x31, 0x7a, + 0x2f, 0x37, 0x3f, 0x92, 0x5f, 0xb2, 0x9b, 0xf7, 0x67, 0xb9, 0x1a, 0x8b, 0xf8, 0xca, 0x90, 0xfd, + 0x69, 0xfc, 0x8b, 0x02, 0x7a, 0x94, 0xc7, 0x27, 0xef, 0x9b, 0x72, 0xf3, 0xf1, 0x6b, 0x50, 0xc6, + 0x0a, 0xfd, 0x5e, 0xf5, 0x82, 0x8c, 0x4f, 0x05, 0xe8, 0xaa, 0x7c, 0x87, 0x5b, 0x74, 0xf3, 0x67, + 0xaf, 0x43, 0x1a, 0xeb, 0xf4, 0x3b, 0x03, 0xd6, 0xb2, 0xd6, 0x4d, 0xf4, 0xd3, 0x29, 0x6c, 0x27, + 0x7d, 0x83, 0x68, 0x3e, 0xba, 0x3a, 0x61, 0xda, 0x65, 0xe3, 0x1b, 0x12, 0xba, 0x22, 0xd7, 0x84, + 0x7d, 0x1e, 0xbf, 0x06, 0x65, 0xac, 0xd0, 0xa7, 0x50, 0x12, 0x65, 0x3e, 0x3f, 0x07, 0x12, 0xd3, + 0x67, 0x7e, 0x0e, 0x24, 0xa7, 0x35, 0x73, 0x0e, 0x5d, 0x42, 0x6d, 0x74, 0x34, 0x40, 0x0f, 0xa7, + 0xe8, 0x3a, 0x3e, 0x6b, 0x35, 0xb7, 0xae, 0x42, 0x92, 0x14, 0x3d, 0xda, 0xbe, 0xf2, 0x45, 0x67, + 0xb6, 0xd5, 0x7c, 0xd1, 0xd9, 0xdd, 0x71, 0x18, 0x71, 0x63, 0xc5, 0x77, 0x6a, 0xc4, 0x4d, 0xea, + 0x1f, 0x53, 0x23, 0x6e, 0x62, 0x9d, 0x37, 0xe7, 0x5a, 0x04, 0xde, 0xea, 0x7a, 0x83, 0x1c, 0x06, + 0xad, 0x5a, 0x5c, 0x11, 0x8f, 0x02, 0x8f, 0x7b, 0x47, 0xc6, 0x27, 0x0f, 0x7b, 0x94, 0x9f, 0x85, + 0x9d, 0x8d, 0xae, 0x37, 0xd8, 0x14, 0x84, 0x9b, 0x09, 0xc2, 0x4d, 0xec, 0xd3, 0xcd, 0x98, 0x78, + 0x53, 0xfe, 0xbf, 0xe8, 0x77, 0x3a, 0x65, 0xf9, 0xe3, 0x83, 0xff, 0x04, 0x00, 0x00, 0xff, 0xff, + 0x73, 0x0e, 0x1f, 0x65, 0x7b, 0x1c, 0x00, 0x00, }, } diff --git a/api/stovepipe/protopb/stovepipe_grpc.pb.go b/api/stovepipe/protopb/stovepipe_grpc.pb.go index f56a15d6f..5ad16b483 100644 --- a/api/stovepipe/protopb/stovepipe_grpc.pb.go +++ b/api/stovepipe/protopb/stovepipe_grpc.pb.go @@ -43,6 +43,7 @@ const ( Stovepipe_List_FullMethodName = "/uber.submitqueue.stovepipe.Stovepipe/List" Stovepipe_GetQueueStatus_FullMethodName = "/uber.submitqueue.stovepipe.Stovepipe/GetQueueStatus" Stovepipe_ListOpenIssues_FullMethodName = "/uber.submitqueue.stovepipe.Stovepipe/ListOpenIssues" + Stovepipe_GetIssuesByRequestID_FullMethodName = "/uber.submitqueue.stovepipe.Stovepipe/GetIssuesByRequestID" ) // StovepipeClient is the client API for Stovepipe service. @@ -70,6 +71,8 @@ type StovepipeClient interface { GetQueueStatus(ctx context.Context, in *GetQueueStatusRequest, opts ...grpc.CallOption) (*GetQueueStatusResponse, error) // ListOpenIssues returns current open issues validated against their summaries. ListOpenIssues(ctx context.Context, in *ListOpenIssuesRequest, opts ...grpc.CallOption) (*ListOpenIssuesResponse, error) + // GetIssuesByRequestID returns accepted issue results for one request. + GetIssuesByRequestID(ctx context.Context, in *GetIssuesByRequestIDRequest, opts ...grpc.CallOption) (*GetIssuesByRequestIDResponse, error) } type stovepipeClient struct { @@ -170,6 +173,16 @@ func (c *stovepipeClient) ListOpenIssues(ctx context.Context, in *ListOpenIssues return out, nil } +func (c *stovepipeClient) GetIssuesByRequestID(ctx context.Context, in *GetIssuesByRequestIDRequest, opts ...grpc.CallOption) (*GetIssuesByRequestIDResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetIssuesByRequestIDResponse) + err := c.cc.Invoke(ctx, Stovepipe_GetIssuesByRequestID_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + // StovepipeServer is the server API for Stovepipe service. // All implementations must embed UnimplementedStovepipeServer // for forward compatibility. @@ -195,6 +208,8 @@ type StovepipeServer interface { GetQueueStatus(context.Context, *GetQueueStatusRequest) (*GetQueueStatusResponse, error) // ListOpenIssues returns current open issues validated against their summaries. ListOpenIssues(context.Context, *ListOpenIssuesRequest) (*ListOpenIssuesResponse, error) + // GetIssuesByRequestID returns accepted issue results for one request. + GetIssuesByRequestID(context.Context, *GetIssuesByRequestIDRequest) (*GetIssuesByRequestIDResponse, error) mustEmbedUnimplementedStovepipeServer() } @@ -232,6 +247,9 @@ func (UnimplementedStovepipeServer) GetQueueStatus(context.Context, *GetQueueSta func (UnimplementedStovepipeServer) ListOpenIssues(context.Context, *ListOpenIssuesRequest) (*ListOpenIssuesResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method ListOpenIssues not implemented") } +func (UnimplementedStovepipeServer) GetIssuesByRequestID(context.Context, *GetIssuesByRequestIDRequest) (*GetIssuesByRequestIDResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetIssuesByRequestID not implemented") +} func (UnimplementedStovepipeServer) mustEmbedUnimplementedStovepipeServer() {} func (UnimplementedStovepipeServer) testEmbeddedByValue() {} @@ -415,6 +433,24 @@ func _Stovepipe_ListOpenIssues_Handler(srv interface{}, ctx context.Context, dec return interceptor(ctx, in, info, handler) } +func _Stovepipe_GetIssuesByRequestID_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetIssuesByRequestIDRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(StovepipeServer).GetIssuesByRequestID(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Stovepipe_GetIssuesByRequestID_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(StovepipeServer).GetIssuesByRequestID(ctx, req.(*GetIssuesByRequestIDRequest)) + } + return interceptor(ctx, in, info, handler) +} + // Stovepipe_ServiceDesc is the grpc.ServiceDesc for Stovepipe service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -458,6 +494,10 @@ var Stovepipe_ServiceDesc = grpc.ServiceDesc{ MethodName: "ListOpenIssues", Handler: _Stovepipe_ListOpenIssues_Handler, }, + { + MethodName: "GetIssuesByRequestID", + Handler: _Stovepipe_GetIssuesByRequestID_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "stovepipe.proto", diff --git a/doc/rfc/stovepipe/request-issues-api.md b/doc/rfc/stovepipe/request-issues-api.md new file mode 100644 index 000000000..cc22a6e0e --- /dev/null +++ b/doc/rfc/stovepipe/request-issues-api.md @@ -0,0 +1,13 @@ +# Request issues API + +The request-issues lookup returns accepted results for a queue and request ID, not the queue's full open-issue set. Each result is new (opened or reopened), existing (reported but already open), or closed (actually closed by this request). Older issues retained because closure was suppressed are not existing results unless this request reported them. + +The issue log retains openings, repeat reports, and closures. Its materializer resolves the source build to its request and derives New, Existing, or Closed results in the request-issue summary. Repeat reports leave lifecycle status and timestamps unchanged. The retained opening reference supplies historical provenance without consulting current issue state; legacy events without this reference do not create request summaries. + +Results are read from the request-issue summary under the queue/request primary-key prefix, in task/unit order. The default page size is 50 and the maximum is 200. A queue- and request-bound opaque continuation resumes after the last returned key; one extra row establishes whether another page exists. No joins or secondary indexes are required. A closure wins over earlier reports or openings from the same request. + +The response reports in-progress recording, complete reconciliation, incomplete abandoned work, or unavailable coverage. Completion is read before the result page and is written only after all accepted issue events and their projections are durable. Completed or abandoned results remain stable as later requests update the queue. Complete does not mean the build passed or all queue issues closed; canceled builds and task-wide failures can suppress closure. + +In-progress pages are not a snapshot: concurrent recording can add keys behind the cursor. Restart from the first page once recording finishes. Unavailable means recording has not started or the request predates the retained start marker; an empty page then does not establish that the request had no issues. Historical results are not backfilled. + +Recording coverage assumes every issue-tracking worker writes request results. Drain older workers before admitting requests whose summaries need complete coverage; requests started before that deployment boundary are not backfilled. diff --git a/service/stovepipe/server/handler/BUILD.bazel b/service/stovepipe/server/handler/BUILD.bazel index 8b4d0c0d1..b22cac98d 100644 --- a/service/stovepipe/server/handler/BUILD.bazel +++ b/service/stovepipe/server/handler/BUILD.bazel @@ -11,6 +11,7 @@ go_library( "//stovepipe/controller:go_default_library", "//stovepipe/core/queuelog:go_default_library", "//stovepipe/core/queuestate:go_default_library", + "//stovepipe/extension/storage:go_default_library", "@org_golang_google_grpc//codes:go_default_library", "@org_golang_google_grpc//status:go_default_library", ], @@ -19,6 +20,7 @@ go_library( go_test( name = "go_default_test", srcs = [ + "get_issues_by_request_id_test.go", "list_open_issues_test.go", "list_test.go", "queue_status_test.go", diff --git a/service/stovepipe/server/handler/get_issues_by_request_id_test.go b/service/stovepipe/server/handler/get_issues_by_request_id_test.go new file mode 100644 index 000000000..bb91638f8 --- /dev/null +++ b/service/stovepipe/server/handler/get_issues_by_request_id_test.go @@ -0,0 +1,57 @@ +// Copyright (c) 2026 Uber Technologies, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package handler + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + pb "github.com/uber/submitqueue/api/stovepipe/protopb" + "github.com/uber/submitqueue/stovepipe/controller" + controllermock "github.com/uber/submitqueue/stovepipe/controller/mock" + "github.com/uber/submitqueue/stovepipe/entity" + "github.com/uber/submitqueue/stovepipe/extension/storage" + "go.uber.org/mock/gomock" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +func TestGetIssuesByRequestID(t *testing.T) { + for _, tt := range []struct { + name string + err error + code codes.Code + }{ + {"success", nil, codes.OK}, {"invalid", controller.ErrInvalidRequest, codes.InvalidArgument}, + {"missing request", storage.ErrNotFound, codes.NotFound}, {"canceled", context.Canceled, codes.Canceled}, + {"deadline", context.DeadlineExceeded, codes.DeadlineExceeded}, {"dependency", assert.AnError, codes.Unknown}, + } { + t.Run(tt.name, func(t *testing.T) { + c := controllermock.NewMockGetIssuesByRequestIDController(gomock.NewController(t)) + c.EXPECT().GetIssuesByRequestID(gomock.Any(), entity.GetIssuesByRequestIDRequest{Queue: "queue", RequestID: "12", PageSize: 10, PageToken: "token"}).Return(entity.GetIssuesByRequestIDResult{Status: entity.RequestIssuesStatusIncomplete, NextPageToken: "next"}, tt.err) + server := NewStovepipeServer(nil, nil, nil, nil, nil, nil, nil, c) + response, err := server.GetIssuesByRequestID(context.Background(), &pb.GetIssuesByRequestIDRequest{Queue: "queue", RequestId: "12", PageSize: 10, PageToken: "token"}) + require.Equal(t, tt.code, status.Code(err)) + if tt.err == nil { + require.Equal(t, "incomplete", response.Status) + require.Equal(t, "next", response.NextPageToken) + } else { + require.Nil(t, response) + } + }) + } +} diff --git a/service/stovepipe/server/handler/list_test.go b/service/stovepipe/server/handler/list_test.go index 1dbc24222..ae4e07de3 100644 --- a/service/stovepipe/server/handler/list_test.go +++ b/service/stovepipe/server/handler/list_test.go @@ -68,7 +68,7 @@ func TestList(t *testing.T) { }, req) return tt.result, tt.err }} - srv := NewStovepipeServer(nil, nil, nil, nil, fake, nil, nil) + srv := NewStovepipeServer(nil, nil, nil, nil, fake, nil, nil, nil) response, err := srv.List(ctx, &pb.ListRequest{ Queue: "queue", PageSize: 25, PageToken: "token", AcceptedLowerBound: &pb.ListRequest_AcceptedAtOrAfterMs{}, diff --git a/service/stovepipe/server/handler/queue_status_test.go b/service/stovepipe/server/handler/queue_status_test.go index 0f9b3e36a..63f580e52 100644 --- a/service/stovepipe/server/handler/queue_status_test.go +++ b/service/stovepipe/server/handler/queue_status_test.go @@ -53,7 +53,7 @@ func TestGetQueueStatus(t *testing.T) { require.Equal(t, entity.GetQueueStatusRequest{Queue: "repo/main", ChangeURI: "C"}, req) return entity.GetQueueStatusResult{Queue: "repo/main", Enablement: entity.QueueState{Version: 1, State: entity.QueueEnablementDisabled}}, wantErr }} - server := NewStovepipeServer(nil, nil, nil, nil, nil, fake, nil) + server := NewStovepipeServer(nil, nil, nil, nil, nil, fake, nil, nil) response, err := server.GetQueueStatus(context.Background(), &pb.GetQueueStatusRequest{Queue: "repo/main", ChangeUri: "C"}) if wantErr != nil { require.Equal(t, tt.code, status.Code(err)) diff --git a/service/stovepipe/server/handler/server.go b/service/stovepipe/server/handler/server.go index 0f5883ada..af951fb14 100644 --- a/service/stovepipe/server/handler/server.go +++ b/service/stovepipe/server/handler/server.go @@ -21,6 +21,7 @@ import ( "github.com/uber/submitqueue/stovepipe/core/queuelog" "github.com/uber/submitqueue/stovepipe/core/queuestate" + "github.com/uber/submitqueue/stovepipe/extension/storage" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" @@ -38,6 +39,7 @@ type StovepipeServer struct { projectStatusController controller.ProjectStatusController listController controller.ListController openIssuesController controller.ListOpenIssuesController + requestIssuesController controller.GetIssuesByRequestIDController queueStatusController controller.GetQueueStatusController } @@ -50,6 +52,7 @@ func NewStovepipeServer( listController controller.ListController, queueStatusController controller.GetQueueStatusController, openIssuesController controller.ListOpenIssuesController, + requestIssuesController controller.GetIssuesByRequestIDController, ) *StovepipeServer { return &StovepipeServer{ pingController: pingController, @@ -58,6 +61,7 @@ func NewStovepipeServer( projectStatusController: projectStatusController, listController: listController, openIssuesController: openIssuesController, + requestIssuesController: requestIssuesController, queueStatusController: queueStatusController, } } @@ -161,3 +165,21 @@ func (s *StovepipeServer) ListOpenIssues(ctx context.Context, req *pb.ListOpenIs } return mapper.ListOpenIssuesResultToProto(result), nil } + +// GetIssuesByRequestID returns accepted results and their recording status. +func (s *StovepipeServer) GetIssuesByRequestID(ctx context.Context, req *pb.GetIssuesByRequestIDRequest) (*pb.GetIssuesByRequestIDResponse, error) { + result, err := s.requestIssuesController.GetIssuesByRequestID(ctx, mapper.ProtoToGetIssuesByRequestIDRequest(req)) + if err != nil { + switch { + case controller.IsInvalidRequest(err): + return nil, status.Error(codes.InvalidArgument, err.Error()) + case errors.Is(err, storage.ErrNotFound): + return nil, status.Error(codes.NotFound, err.Error()) + case errors.Is(err, context.Canceled), errors.Is(err, context.DeadlineExceeded): + return nil, status.FromContextError(err).Err() + default: + return nil, err + } + } + return mapper.GetIssuesByRequestIDResultToProto(result), nil +} diff --git a/service/stovepipe/server/main.go b/service/stovepipe/server/main.go index 9d388c2df..79ebc7065 100644 --- a/service/stovepipe/server/main.go +++ b/service/stovepipe/server/main.go @@ -304,9 +304,11 @@ func run() error { listController := controller.NewListController(logger.Sugar(), scope, storageFty, tenants) queueStatusController := controller.NewGetQueueStatusController(scope, storageFty, sourceControl) openIssuesController := controller.NewListOpenIssuesController(logger.Sugar(), scope, storageFty, tenants) + requestIssuesController := controller.NewGetIssuesByRequestIDController(logger.Sugar(), scope, storageFty, tenants) srv := handler.NewStovepipeServer( pingController, ingestController, requestHistoryController, projectStatusController, listController, queueStatusController, openIssuesController, + requestIssuesController, ) pb.RegisterStovepipeServer(grpcServer, srv) diff --git a/service/stovepipe/server/mapper/BUILD.bazel b/service/stovepipe/server/mapper/BUILD.bazel index 9b2d7cb0b..36f028710 100644 --- a/service/stovepipe/server/mapper/BUILD.bazel +++ b/service/stovepipe/server/mapper/BUILD.bazel @@ -3,6 +3,7 @@ load("@rules_go//go:def.bzl", "go_library", "go_test") go_library( name = "go_default_library", srcs = [ + "get_issues_by_request_id.go", "ingest.go", "list.go", "list_open_issues.go", @@ -22,6 +23,7 @@ go_library( go_test( name = "go_default_test", srcs = [ + "get_issues_by_request_id_test.go", "ingest_test.go", "list_open_issues_test.go", "list_test.go", diff --git a/service/stovepipe/server/mapper/get_issues_by_request_id.go b/service/stovepipe/server/mapper/get_issues_by_request_id.go new file mode 100644 index 000000000..a1d28cb35 --- /dev/null +++ b/service/stovepipe/server/mapper/get_issues_by_request_id.go @@ -0,0 +1,34 @@ +// Copyright (c) 2026 Uber Technologies, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package mapper + +import ( + pb "github.com/uber/submitqueue/api/stovepipe/protopb" + "github.com/uber/submitqueue/stovepipe/entity" +) + +// ProtoToGetIssuesByRequestIDRequest leaves defaults and validation to the controller. +func ProtoToGetIssuesByRequestIDRequest(req *pb.GetIssuesByRequestIDRequest) entity.GetIssuesByRequestIDRequest { + return entity.GetIssuesByRequestIDRequest{Queue: req.GetQueue(), RequestID: req.GetRequestId(), PageSize: req.GetPageSize(), PageToken: req.GetPageToken()} +} + +// GetIssuesByRequestIDResultToProto preserves request classifications and completion status. +func GetIssuesByRequestIDResultToProto(result entity.GetIssuesByRequestIDResult) *pb.GetIssuesByRequestIDResponse { + issues := make([]*pb.RequestIssueSummary, 0, len(result.Issues)) + for _, issue := range result.Issues { + issues = append(issues, &pb.RequestIssueSummary{TaskId: issue.TaskID, UnitId: issue.UnitID, Kind: string(issue.Kind), BuildId: issue.BuildID, FirstSeenBuildId: issue.FirstSeenBuildID}) + } + return &pb.GetIssuesByRequestIDResponse{Issues: issues, Status: string(result.Status), NextPageToken: result.NextPageToken} +} diff --git a/service/stovepipe/server/mapper/get_issues_by_request_id_test.go b/service/stovepipe/server/mapper/get_issues_by_request_id_test.go new file mode 100644 index 000000000..e66e39188 --- /dev/null +++ b/service/stovepipe/server/mapper/get_issues_by_request_id_test.go @@ -0,0 +1,31 @@ +// Copyright (c) 2026 Uber Technologies, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package mapper + +import ( + "testing" + + "github.com/stretchr/testify/require" + pb "github.com/uber/submitqueue/api/stovepipe/protopb" + "github.com/uber/submitqueue/stovepipe/entity" +) + +func TestRequestIssueMappings(t *testing.T) { + require.Empty(t, ProtoToGetIssuesByRequestIDRequest(nil)) + require.Equal(t, entity.GetIssuesByRequestIDRequest{Queue: "queue", RequestID: "12", PageSize: 10, PageToken: "token"}, ProtoToGetIssuesByRequestIDRequest(&pb.GetIssuesByRequestIDRequest{Queue: "queue", RequestId: "12", PageSize: 10, PageToken: "token"})) + result := entity.GetIssuesByRequestIDResult{Issues: []entity.RequestIssueSummary{{RequestID: "12", TaskID: "task", UnitID: "unit", Kind: entity.RequestIssueKindExisting, BuildID: "b/12", FirstSeenBuildID: "b/10"}}, Status: entity.RequestIssuesStatusComplete, NextPageToken: "next"} + require.Equal(t, &pb.GetIssuesByRequestIDResponse{Issues: []*pb.RequestIssueSummary{{TaskId: "task", UnitId: "unit", Kind: "existing", BuildId: "b/12", FirstSeenBuildId: "b/10"}}, Status: "complete", NextPageToken: "next"}, GetIssuesByRequestIDResultToProto(result)) + require.Empty(t, GetIssuesByRequestIDResultToProto(entity.GetIssuesByRequestIDResult{}).Issues) +} diff --git a/stovepipe/controller/BUILD.bazel b/stovepipe/controller/BUILD.bazel index cd6cbcb87..22db63425 100644 --- a/stovepipe/controller/BUILD.bazel +++ b/stovepipe/controller/BUILD.bazel @@ -3,6 +3,7 @@ load("@rules_go//go:def.bzl", "go_library", "go_test") go_library( name = "go_default_library", srcs = [ + "get_issues_by_request_id.go", "get_queue_status.go", "ingest.go", "list.go", @@ -38,6 +39,7 @@ go_library( go_test( name = "go_default_test", srcs = [ + "get_issues_by_request_id_test.go", "get_queue_status_test.go", "ingest_test.go", "list_consistency_test.go", diff --git a/stovepipe/controller/get_issues_by_request_id.go b/stovepipe/controller/get_issues_by_request_id.go new file mode 100644 index 000000000..8b1882851 --- /dev/null +++ b/stovepipe/controller/get_issues_by_request_id.go @@ -0,0 +1,169 @@ +// Copyright (c) 2026 Uber Technologies, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package controller + +//go:generate mockgen -source=get_issues_by_request_id.go -destination=mock/get_issues_by_request_id_mock.go -package=mock + +import ( + "context" + "encoding/base64" + "encoding/json" + "fmt" + + "github.com/uber-go/tally" + "github.com/uber/submitqueue/platform/errs" + "github.com/uber/submitqueue/platform/metrics" + "github.com/uber/submitqueue/stovepipe/entity" + "github.com/uber/submitqueue/stovepipe/extension/storage" + "go.uber.org/zap" +) + +const requestIssuesPageTokenVersion = 1 + +type requestIssuesPageToken struct { + Version int `json:"version"` + Queue string `json:"queue"` + RequestID string `json:"request_id"` + LastTaskID string `json:"last_task_id"` + LastUnitID string `json:"last_unit_id"` +} + +// GetIssuesByRequestIDController reads accepted issue results for configured queues. +type GetIssuesByRequestIDController interface { + GetIssuesByRequestID(context.Context, entity.GetIssuesByRequestIDRequest) (entity.GetIssuesByRequestIDResult, error) +} + +type getIssuesByRequestIDController struct { + logger *zap.SugaredLogger + scope tally.Scope + stores storage.Factory + configuredQueues map[string]struct{} +} + +// NewGetIssuesByRequestIDController restricts request issue reads to configured queues. +func NewGetIssuesByRequestIDController(logger *zap.SugaredLogger, scope tally.Scope, stores storage.Factory, configuredQueues []string) GetIssuesByRequestIDController { + queues := make(map[string]struct{}, len(configuredQueues)) + for _, queue := range configuredQueues { + queues[queue] = struct{}{} + } + return &getIssuesByRequestIDController{logger: logger, scope: scope.SubScope("get_issues_by_request_id_controller"), stores: stores, configuredQueues: queues} +} + +func (c *getIssuesByRequestIDController) GetIssuesByRequestID(ctx context.Context, req entity.GetIssuesByRequestIDRequest) (result entity.GetIssuesByRequestIDResult, retErr error) { + op := metrics.Begin(c.scope, "get", metrics.StorageLatencyBuckets, metrics.TagsFromContext(ctx)...) + defer func() { op.Complete(retErr) }() + if req.Queue == "" { + return result, fmt.Errorf("GetIssuesByRequestID requires a queue: %w", ErrInvalidRequest) + } + if _, ok := c.configuredQueues[req.Queue]; !ok { + return result, fmt.Errorf("GetIssuesByRequestID queue %q is not configured: %w", req.Queue, ErrInvalidRequest) + } + if err := validateRequestID(req.RequestID); err != nil { + return result, err + } + bounds, err := resolveRequestIssueSummaryRange(req) + if err != nil { + return result, err + } + store, err := c.stores.For(storage.Config{QueueName: req.Queue}) + if err != nil { + return result, fmt.Errorf("resolve request issue storage: %w", err) + } + history, err := store.GetRequestLogStore().List(ctx, req.RequestID) + if storage.IsNotFound(err) { + return result, errs.NewUserError(fmt.Errorf("request issue history is not retained: %w", err)) + } + if err != nil { + return result, fmt.Errorf("read request issue history: %w", err) + } + // Read completion before the page: a complete response must follow all result writes. + result.Status = requestIssuesStatus(history) + issues, err := store.GetRequestIssueSummaryStore().List(ctx, req.RequestID, bounds) + if err != nil { + return entity.GetIssuesByRequestIDResult{}, fmt.Errorf("list request issues: %w", err) + } + pageSize := bounds.Limit - 1 + result.Issues = issues[:min(len(issues), pageSize)] + if len(issues) > pageSize { + last := result.Issues[len(result.Issues)-1] + contents, err := json.Marshal(requestIssuesPageToken{Version: requestIssuesPageTokenVersion, Queue: req.Queue, RequestID: req.RequestID, LastTaskID: last.TaskID, LastUnitID: last.UnitID}) + if err != nil { + return entity.GetIssuesByRequestIDResult{}, fmt.Errorf("encode request issue continuation: %w", err) + } + result.NextPageToken = base64.RawURLEncoding.EncodeToString(contents) + } + c.logger.Debugw("request issues retrieved", "queue", req.Queue, "request_id", req.RequestID, "issue_count", len(result.Issues), "status", result.Status) + return result, nil +} + +func requestIssuesStatus(history []entity.RequestLog) entity.RequestIssuesStatus { + started, completed, abandoned := false, false, false + for _, log := range history { + switch log.Event { + case entity.RequestEventIssueTrackingStarted: + started = true + case entity.RequestEventIssuesReconciled: + completed = true + case entity.RequestEventIssueTrackingAbandoned: + abandoned = true + } + } + if abandoned { + return entity.RequestIssuesStatusIncomplete + } + if !started { + return entity.RequestIssuesStatusUnavailable + } + if completed { + return entity.RequestIssuesStatusComplete + } + return entity.RequestIssuesStatusInProgress +} + +func resolveRequestIssueSummaryRange(req entity.GetIssuesByRequestIDRequest) (storage.RequestIssueSummaryRange, error) { + if req.PageSize < 0 || req.PageSize > maxListPageSize { + return storage.RequestIssueSummaryRange{}, fmt.Errorf("GetIssuesByRequestID page_size must be between 0 and %d: %w", maxListPageSize, ErrInvalidRequest) + } + pageSize := int(req.PageSize) + if pageSize == 0 { + pageSize = defaultListPageSize + } + bounds := storage.RequestIssueSummaryRange{Limit: pageSize + 1} + if req.PageToken == "" { + return bounds, nil + } + contents, err := base64.RawURLEncoding.Strict().DecodeString(req.PageToken) + if err != nil { + return storage.RequestIssueSummaryRange{}, fmt.Errorf("GetIssuesByRequestID invalid page_token encoding: %w", ErrInvalidRequest) + } + var token requestIssuesPageToken + if err := json.Unmarshal(contents, &token); err != nil { + return storage.RequestIssueSummaryRange{}, fmt.Errorf("GetIssuesByRequestID invalid page_token contents: %w", ErrInvalidRequest) + } + if token.Version != requestIssuesPageTokenVersion { + return storage.RequestIssueSummaryRange{}, fmt.Errorf("GetIssuesByRequestID invalid page_token version: %w", ErrInvalidRequest) + } + if token.Queue != req.Queue || token.RequestID != req.RequestID { + return storage.RequestIssueSummaryRange{}, fmt.Errorf("GetIssuesByRequestID page_token does not match the queue and request: %w", ErrInvalidRequest) + } + if token.LastTaskID == "" { + return storage.RequestIssueSummaryRange{}, fmt.Errorf("GetIssuesByRequestID page_token requires a task ID: %w", ErrInvalidRequest) + } + if len(token.LastTaskID) > 255 || len(token.LastUnitID) > 1024 { + return storage.RequestIssueSummaryRange{}, fmt.Errorf("GetIssuesByRequestID page_token key exceeds identifier limits: %w", ErrInvalidRequest) + } + bounds.After = storage.RequestIssueSummaryCursor{TaskID: token.LastTaskID, UnitID: token.LastUnitID} + return bounds, nil +} diff --git a/stovepipe/controller/get_issues_by_request_id_test.go b/stovepipe/controller/get_issues_by_request_id_test.go new file mode 100644 index 000000000..25fc06123 --- /dev/null +++ b/stovepipe/controller/get_issues_by_request_id_test.go @@ -0,0 +1,174 @@ +// Copyright (c) 2026 Uber Technologies, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package controller + +import ( + "context" + "encoding/base64" + "encoding/json" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/uber-go/tally" + "github.com/uber/submitqueue/stovepipe/entity" + "github.com/uber/submitqueue/stovepipe/extension/storage" + storagemock "github.com/uber/submitqueue/stovepipe/extension/storage/mock" + "go.uber.org/mock/gomock" + "go.uber.org/zap" +) + +func TestGetIssuesByRequestIDPagination(t *testing.T) { + mocks := gomock.NewController(t) + factory := storagemock.NewMockFactory(mocks) + store := storagemock.NewMockStorage(mocks) + history := storagemock.NewMockRequestLogStore(mocks) + issues := storagemock.NewMockRequestIssueSummaryStore(mocks) + c := NewGetIssuesByRequestIDController(zap.NewNop().Sugar(), tally.NoopScope, factory, []string{"queue"}) + factory.EXPECT().For(storage.Config{QueueName: "queue"}).Return(store, nil).Times(2) + store.EXPECT().GetRequestLogStore().Return(history).Times(2) + history.EXPECT().List(gomock.Any(), "12").Return([]entity.RequestLog{{Event: entity.RequestEventIssueTrackingStarted}, {Event: entity.RequestEventIssuesReconciled}}, nil).Times(2) + store.EXPECT().GetRequestIssueSummaryStore().Return(issues).Times(2) + rows := []entity.RequestIssueSummary{ + {RequestID: "12", TaskID: "a", Kind: entity.RequestIssueKindNew}, + {RequestID: "12", TaskID: "a", UnitID: "unit", Kind: entity.RequestIssueKindExisting}, + {RequestID: "12", TaskID: "b", UnitID: "unit", Kind: entity.RequestIssueKindClosed}, + } + issues.EXPECT().List(gomock.Any(), "12", storage.RequestIssueSummaryRange{Limit: 3}).Return(rows, nil) + first, err := c.GetIssuesByRequestID(context.Background(), entity.GetIssuesByRequestIDRequest{Queue: "queue", RequestID: "12", PageSize: 2}) + require.NoError(t, err) + require.Equal(t, entity.RequestIssuesStatusComplete, first.Status) + require.Equal(t, rows[:2], first.Issues) + require.NotEmpty(t, first.NextPageToken) + issues.EXPECT().List(gomock.Any(), "12", storage.RequestIssueSummaryRange{After: storage.RequestIssueSummaryCursor{TaskID: "a", UnitID: "unit"}, Limit: 3}).Return(rows[2:], nil) + last, err := c.GetIssuesByRequestID(context.Background(), entity.GetIssuesByRequestIDRequest{Queue: "queue", RequestID: "12", PageSize: 2, PageToken: first.NextPageToken}) + require.NoError(t, err) + require.Equal(t, rows[2:], last.Issues) + require.Empty(t, last.NextPageToken) +} + +func TestRequestIssuesStatus(t *testing.T) { + for _, tt := range []struct { + name string + events []entity.RequestEvent + want entity.RequestIssuesStatus + }{ + {"not started", nil, entity.RequestIssuesStatusUnavailable}, + {"historical completion without coverage", []entity.RequestEvent{entity.RequestEventIssuesReconciled}, entity.RequestIssuesStatusUnavailable}, + {"in progress", []entity.RequestEvent{entity.RequestEventIssueTrackingStarted}, entity.RequestIssuesStatusInProgress}, + {"complete", []entity.RequestEvent{entity.RequestEventIssueTrackingStarted, entity.RequestEventIssuesReconciled}, entity.RequestIssuesStatusComplete}, + {"abandoned", []entity.RequestEvent{entity.RequestEventIssueTrackingStarted, entity.RequestEventIssueTrackingAbandoned}, entity.RequestIssuesStatusIncomplete}, + {"abandoned before starting", []entity.RequestEvent{entity.RequestEventIssueTrackingAbandoned}, entity.RequestIssuesStatusIncomplete}, + } { + t.Run(tt.name, func(t *testing.T) { + var history []entity.RequestLog + for _, event := range tt.events { + history = append(history, entity.RequestLog{Event: event}) + } + require.Equal(t, tt.want, requestIssuesStatus(history)) + }) + } +} + +func TestRequestIssuePageBounds(t *testing.T) { + valid := requestIssuesPageToken{Version: requestIssuesPageTokenVersion, Queue: "queue", RequestID: "12", LastTaskID: "task"} + for _, tt := range []struct { + name string + change func(*requestIssuesPageToken) + }{ + {"other queue", func(t *requestIssuesPageToken) { t.Queue = "other" }}, + {"other request", func(t *requestIssuesPageToken) { t.RequestID = "13" }}, + {"old version", func(t *requestIssuesPageToken) { t.Version = 0 }}, + {"missing task", func(t *requestIssuesPageToken) { t.LastTaskID = "" }}, + {"long task", func(t *requestIssuesPageToken) { t.LastTaskID = strings.Repeat("t", 256) }}, + {"long unit", func(t *requestIssuesPageToken) { t.LastUnitID = strings.Repeat("u", 1025) }}, + } { + t.Run(tt.name, func(t *testing.T) { + token := valid + tt.change(&token) + data, err := json.Marshal(token) + require.NoError(t, err) + _, err = resolveRequestIssueSummaryRange(entity.GetIssuesByRequestIDRequest{Queue: "queue", RequestID: "12", PageToken: base64.RawURLEncoding.EncodeToString(data)}) + require.ErrorIs(t, err, ErrInvalidRequest) + }) + } + for _, pageSize := range []int32{-1, 201} { + _, err := resolveRequestIssueSummaryRange(entity.GetIssuesByRequestIDRequest{PageSize: pageSize}) + require.ErrorIs(t, err, ErrInvalidRequest) + } + for _, token := range []string{"bad!", base64.RawURLEncoding.EncodeToString([]byte("not-json"))} { + _, err := resolveRequestIssueSummaryRange(entity.GetIssuesByRequestIDRequest{PageToken: token}) + require.ErrorIs(t, err, ErrInvalidRequest) + } + bounds, err := resolveRequestIssueSummaryRange(entity.GetIssuesByRequestIDRequest{}) + require.NoError(t, err) + require.Equal(t, 51, bounds.Limit) +} + +func TestGetIssuesByRequestIDRejectsInvalidIdentity(t *testing.T) { + factory := storagemock.NewMockFactory(gomock.NewController(t)) + c := NewGetIssuesByRequestIDController(zap.NewNop().Sugar(), tally.NoopScope, factory, []string{"queue"}) + for _, req := range []entity.GetIssuesByRequestIDRequest{ + {}, {Queue: "other", RequestID: "12"}, {Queue: "queue"}, {Queue: "queue", RequestID: "012"}, + } { + _, err := c.GetIssuesByRequestID(context.Background(), req) + require.ErrorIs(t, err, ErrInvalidRequest) + } +} + +func TestGetIssuesByRequestIDReadFailures(t *testing.T) { + for _, step := range []string{"factory", "history", "issues"} { + t.Run(step, func(t *testing.T) { + mocks := gomock.NewController(t) + factory := storagemock.NewMockFactory(mocks) + store := storagemock.NewMockStorage(mocks) + history := storagemock.NewMockRequestLogStore(mocks) + issues := storagemock.NewMockRequestIssueSummaryStore(mocks) + c := NewGetIssuesByRequestIDController(zap.NewNop().Sugar(), tally.NoopScope, factory, []string{"queue"}) + + failAt := func(name string) error { + if step == name { + return assert.AnError + } + return nil + } + factory.EXPECT().For(gomock.Any()).Return(store, failAt("factory")) + if step != "factory" { + store.EXPECT().GetRequestLogStore().Return(history) + history.EXPECT().List(gomock.Any(), "12").Return(nil, failAt("history")) + } + if step == "issues" { + store.EXPECT().GetRequestIssueSummaryStore().Return(issues) + issues.EXPECT().List(gomock.Any(), "12", gomock.Any()).Return(nil, assert.AnError) + } + _, err := c.GetIssuesByRequestID(context.Background(), entity.GetIssuesByRequestIDRequest{Queue: "queue", RequestID: "12"}) + require.ErrorIs(t, err, assert.AnError) + }) + } +} + +func TestGetIssuesByRequestIDMissingRequest(t *testing.T) { + mocks := gomock.NewController(t) + factory := storagemock.NewMockFactory(mocks) + store := storagemock.NewMockStorage(mocks) + history := storagemock.NewMockRequestLogStore(mocks) + c := NewGetIssuesByRequestIDController(zap.NewNop().Sugar(), tally.NoopScope, factory, []string{"queue"}) + factory.EXPECT().For(gomock.Any()).Return(store, nil) + store.EXPECT().GetRequestLogStore().Return(history) + history.EXPECT().List(gomock.Any(), "12").Return(nil, storage.ErrNotFound) + _, err := c.GetIssuesByRequestID(context.Background(), entity.GetIssuesByRequestIDRequest{Queue: "queue", RequestID: "12"}) + require.ErrorIs(t, err, storage.ErrNotFound) +} diff --git a/stovepipe/controller/mock/BUILD.bazel b/stovepipe/controller/mock/BUILD.bazel index 32f9f1f51..fef8cb127 100644 --- a/stovepipe/controller/mock/BUILD.bazel +++ b/stovepipe/controller/mock/BUILD.bazel @@ -3,6 +3,7 @@ load("@rules_go//go:def.bzl", "go_library") go_library( name = "go_default_library", srcs = [ + "get_issues_by_request_id_mock.go", "list_open_issues_mock.go", "project_status_mock.go", "request_history_mock.go", diff --git a/stovepipe/controller/mock/get_issues_by_request_id_mock.go b/stovepipe/controller/mock/get_issues_by_request_id_mock.go new file mode 100644 index 000000000..06592e271 --- /dev/null +++ b/stovepipe/controller/mock/get_issues_by_request_id_mock.go @@ -0,0 +1,57 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: get_issues_by_request_id.go +// +// Generated by this command: +// +// mockgen -source=get_issues_by_request_id.go -destination=mock/get_issues_by_request_id_mock.go -package=mock +// + +// Package mock is a generated GoMock package. +package mock + +import ( + context "context" + reflect "reflect" + + entity "github.com/uber/submitqueue/stovepipe/entity" + gomock "go.uber.org/mock/gomock" +) + +// MockGetIssuesByRequestIDController is a mock of GetIssuesByRequestIDController interface. +type MockGetIssuesByRequestIDController struct { + ctrl *gomock.Controller + recorder *MockGetIssuesByRequestIDControllerMockRecorder + isgomock struct{} +} + +// MockGetIssuesByRequestIDControllerMockRecorder is the mock recorder for MockGetIssuesByRequestIDController. +type MockGetIssuesByRequestIDControllerMockRecorder struct { + mock *MockGetIssuesByRequestIDController +} + +// NewMockGetIssuesByRequestIDController creates a new mock instance. +func NewMockGetIssuesByRequestIDController(ctrl *gomock.Controller) *MockGetIssuesByRequestIDController { + mock := &MockGetIssuesByRequestIDController{ctrl: ctrl} + mock.recorder = &MockGetIssuesByRequestIDControllerMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockGetIssuesByRequestIDController) EXPECT() *MockGetIssuesByRequestIDControllerMockRecorder { + return m.recorder +} + +// GetIssuesByRequestID mocks base method. +func (m *MockGetIssuesByRequestIDController) GetIssuesByRequestID(arg0 context.Context, arg1 entity.GetIssuesByRequestIDRequest) (entity.GetIssuesByRequestIDResult, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetIssuesByRequestID", arg0, arg1) + ret0, _ := ret[0].(entity.GetIssuesByRequestIDResult) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetIssuesByRequestID indicates an expected call of GetIssuesByRequestID. +func (mr *MockGetIssuesByRequestIDControllerMockRecorder) GetIssuesByRequestID(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetIssuesByRequestID", reflect.TypeOf((*MockGetIssuesByRequestIDController)(nil).GetIssuesByRequestID), arg0, arg1) +} diff --git a/stovepipe/entity/BUILD.bazel b/stovepipe/entity/BUILD.bazel index cca9bc322..7a46174b0 100644 --- a/stovepipe/entity/BUILD.bazel +++ b/stovepipe/entity/BUILD.bazel @@ -5,6 +5,7 @@ go_library( srcs = [ "build.go", "build_issue.go", + "get_issues_by_request_id.go", "ingest.go", "issue_log.go", "issue_state.go", diff --git a/stovepipe/entity/get_issues_by_request_id.go b/stovepipe/entity/get_issues_by_request_id.go new file mode 100644 index 000000000..12bb36af5 --- /dev/null +++ b/stovepipe/entity/get_issues_by_request_id.go @@ -0,0 +1,49 @@ +// Copyright (c) 2026 Uber Technologies, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package entity + +// RequestIssuesStatus describes coverage of a request's retained issue results. +type RequestIssuesStatus string + +const ( + RequestIssuesStatusUnavailable RequestIssuesStatus = "unavailable" + RequestIssuesStatusInProgress RequestIssuesStatus = "in_progress" + RequestIssuesStatusComplete RequestIssuesStatus = "complete" + RequestIssuesStatusIncomplete RequestIssuesStatus = "incomplete" +) + +// GetIssuesByRequestIDRequest selects accepted issue results for one request. +type GetIssuesByRequestIDRequest struct { + // Queue is the required configured queue containing the request. + Queue string + // RequestID is the canonical positive decimal request ID within the queue. + RequestID string + // PageSize bounds returned issues, from 1 to 200; zero selects 50. + PageSize int32 + // PageToken is an opaque continuation bound to this queue and request. + PageToken string +} + +// GetIssuesByRequestIDResult is a page of request results, not all issues left open. +type GetIssuesByRequestIDResult struct { + // Issues contains accepted results in ascending bytewise task/unit order. + Issues []RequestIssueSummary + // Status is complete only after reconciliation and every result write finished. + // Unavailable means recording has not started or predates retained coverage. + Status RequestIssuesStatus + // NextPageToken continues after the last returned result; empty ends this scan. + // In-progress results can gain entries behind the cursor; restart after completion. + NextPageToken string +} diff --git a/test/e2e/stovepipe/BUILD.bazel b/test/e2e/stovepipe/BUILD.bazel index b02c3710a..167cad744 100644 --- a/test/e2e/stovepipe/BUILD.bazel +++ b/test/e2e/stovepipe/BUILD.bazel @@ -9,6 +9,7 @@ go_test( "list_test.go", "open_issues_test.go", "queue_status_test.go", + "request_issues_test.go", "suite_test.go", ], data = [ @@ -29,6 +30,7 @@ go_test( "//platform/consumer:go_default_library", "//platform/extension/messagequeue/mysql:go_default_library", "//platform/publish:go_default_library", + "//stovepipe/core/issuelog:go_default_library", "//stovepipe/core/messagequeue:go_default_library", "//stovepipe/core/queuelog:go_default_library", "//stovepipe/core/queuestate:go_default_library", diff --git a/test/e2e/stovepipe/issuetrack_test.go b/test/e2e/stovepipe/issuetrack_test.go index efcd0e674..2b11d6513 100644 --- a/test/e2e/stovepipe/issuetrack_test.go +++ b/test/e2e/stovepipe/issuetrack_test.go @@ -19,6 +19,7 @@ import ( "github.com/stretchr/testify/require" "github.com/uber-go/tally" + pb "github.com/uber/submitqueue/api/stovepipe/protopb" entityqueue "github.com/uber/submitqueue/platform/base/messagequeue" "github.com/uber/submitqueue/platform/consumer" queuemysql "github.com/uber/submitqueue/platform/extension/messagequeue/mysql" @@ -68,6 +69,12 @@ func (s *StovepipeE2ESuite) TestIssueTracking_FinalizesAfterAnalysis() { issues, err := store.GetIssueStateStore().List(s.ctx, storage.IssueStateRange{Limit: 1}) require.NoError(t, err) require.Empty(t, issues) + pollUntil(convergencePollInterval, func() bool { + result, err := s.client.GetIssuesByRequestID(s.ctx, &pb.GetIssuesByRequestIDRequest{Queue: issueTrackingQueue, RequestId: requestID}) + require.NoError(t, err) + require.Empty(t, result.GetIssues()) + return result.GetStatus() == "complete" + }) } func (s *StovepipeE2ESuite) TestIssueTracking_DLQAbandonsIncompleteAnalysis() { @@ -123,6 +130,10 @@ func (s *StovepipeE2ESuite) TestIssueTracking_DLQAbandonsIncompleteAnalysis() { require.NoError(t, err) require.Equal(t, build, retainedBuild) require.Equal(t, int32(1), s.inFlightCount(queue)) + result, err := s.client.GetIssuesByRequestID(s.ctx, &pb.GetIssuesByRequestIDRequest{Queue: queue, RequestId: request.ID}) + require.NoError(t, err) + require.Equal(t, "incomplete", result.GetStatus()) + require.Empty(t, result.GetIssues()) // The DLQ watermark survives message garbage collection and proves recovery acknowledged. pollUntil(convergencePollInterval, func() bool { diff --git a/test/e2e/stovepipe/request_issues_test.go b/test/e2e/stovepipe/request_issues_test.go new file mode 100644 index 000000000..eaafabad4 --- /dev/null +++ b/test/e2e/stovepipe/request_issues_test.go @@ -0,0 +1,62 @@ +// Copyright (c) 2026 Uber Technologies, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package e2e_test + +import ( + "github.com/stretchr/testify/require" + "github.com/uber-go/tally" + pb "github.com/uber/submitqueue/api/stovepipe/protopb" + "github.com/uber/submitqueue/stovepipe/core/issuelog" + "github.com/uber/submitqueue/stovepipe/core/requestlog" + "github.com/uber/submitqueue/stovepipe/entity" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +func (s *StovepipeE2ESuite) TestGetIssuesByRequestID() { + t := s.T() + const queue, requestID = openIssuesQueue, "900" + stores, err := s.appStorage.For(queue) + require.NoError(t, err) + materializer := issuelog.NewMaterializer(tally.NoopScope) + require.NoError(t, stores.GetBuildStore().Create(s.ctx, entity.Build{ID: "b/900", RequestID: requestID, Status: entity.BuildStatusFailed, Version: 1})) + for _, log := range []entity.IssueLog{ + {TaskID: "a", Event: entity.IssueEventOpened, BuildID: "b/900", FirstSeenBuildID: "b/900", TimestampMs: 1000}, + {TaskID: "a", UnitID: "unit", Event: entity.IssueEventReported, BuildID: "b/900", FirstSeenBuildID: "b/899"}, + {TaskID: "b", UnitID: "unit", Event: entity.IssueEventClosed, BuildID: "b/900", FirstSeenBuildID: "b/899", TimestampMs: 1000}, + } { + require.NoError(t, materializer.PersistLog(s.ctx, stores, log, log.FirstSeenBuildID)) + } + request := entity.Request{ID: requestID, Queue: queue} + requestMaterializer := requestlog.NewMaterializer(tally.NoopScope) + require.NoError(t, requestMaterializer.PersistLog(s.ctx, stores, requestlog.NewRequestEventLog(request, entity.RequestEventIssueTrackingStarted, "", nil))) + first, err := s.client.GetIssuesByRequestID(s.ctx, &pb.GetIssuesByRequestIDRequest{Queue: queue, RequestId: requestID, PageSize: 2}) + require.NoError(t, err) + require.Equal(t, "in_progress", first.GetStatus()) + require.Len(t, first.GetIssues(), 2) + require.Equal(t, "new", first.GetIssues()[0].GetKind()) + require.Equal(t, "existing", first.GetIssues()[1].GetKind()) + require.NotEmpty(t, first.GetNextPageToken()) + require.NoError(t, requestMaterializer.PersistLog(s.ctx, stores, requestlog.NewRequestEventLog(request, entity.RequestEventIssuesReconciled, "b/900", nil))) + last, err := s.client.GetIssuesByRequestID(s.ctx, &pb.GetIssuesByRequestIDRequest{Queue: queue, RequestId: requestID, PageSize: 2, PageToken: first.GetNextPageToken()}) + require.NoError(t, err) + require.Equal(t, "complete", last.GetStatus()) + require.Len(t, last.GetIssues(), 1) + require.Equal(t, "closed", last.GetIssues()[0].GetKind()) + require.Equal(t, "b/899", last.GetIssues()[0].GetFirstSeenBuildId()) + require.Empty(t, last.GetNextPageToken()) + _, err = s.client.GetIssuesByRequestID(s.ctx, &pb.GetIssuesByRequestIDRequest{Queue: queue, RequestId: "901", PageToken: first.GetNextPageToken()}) + require.Equal(t, codes.InvalidArgument, status.Code(err)) +} diff --git a/web/api/src/gen/api/stovepipe/proto/stovepipe_pb.ts b/web/api/src/gen/api/stovepipe/proto/stovepipe_pb.ts index 9e306877e..b201d1cd1 100644 --- a/web/api/src/gen/api/stovepipe/proto/stovepipe_pb.ts +++ b/web/api/src/gen/api/stovepipe/proto/stovepipe_pb.ts @@ -24,7 +24,7 @@ import type { Message } from "@bufbuild/protobuf"; * Describes the file api/stovepipe/proto/stovepipe.proto. */ export const file_api_stovepipe_proto_stovepipe: GenFile = /*@__PURE__*/ - fileDesc("CiNhcGkvc3RvdmVwaXBlL3Byb3RvL3N0b3ZlcGlwZS5wcm90bxIadWJlci5zdWJtaXRxdWV1ZS5zdG92ZXBpcGUiHgoLUGluZ1JlcXVlc3QSDwoHbWVzc2FnZRgBIAEoCSJaCgxQaW5nUmVzcG9uc2USDwoHbWVzc2FnZRgBIAEoCRIUCgxzZXJ2aWNlX25hbWUYAiABKAkSEQoJdGltZXN0YW1wGAMgASgDEhAKCGhvc3RuYW1lGAQgASgJIl8KDUluZ2VzdFJlcXVlc3QSDQoFcXVldWUYASABKAkSPwoKZW5hYmxlbWVudBgCIAEoDjIrLnViZXIuc3VibWl0cXVldWUuc3RvdmVwaXBlLlF1ZXVlRW5hYmxlbWVudCJjCg5Jbmdlc3RSZXNwb25zZRIKCgJpZBgBIAEoCRJFCgplbmFibGVtZW50GAIgASgLMjEudWJlci5zdWJtaXRxdWV1ZS5zdG92ZXBpcGUuUXVldWVFbmFibGVtZW50U3RhdHVzIkEKHEdldFJlcXVlc3RIaXN0b3J5QnlJRFJlcXVlc3QSDQoFcXVldWUYASABKAkSEgoKcmVxdWVzdF9pZBgCIAEoCSI7Ch1HZXRSZXF1ZXN0SGlzdG9yeUJ5VVJJUmVxdWVzdBINCgVxdWV1ZRgBIAEoCRILCgN1cmkYAiABKAkiwgEKDEhpc3RvcnlFdmVudBIQCghldmVudF9pZBgBIAEoCRIUCgx0aW1lc3RhbXBfbXMYAiABKAMSFwoNcmVxdWVzdF9zdGF0ZRgDIAEoCUgAEg8KBWV2ZW50GAQgASgJSAASFgoOb3V0Y29tZV9yZWFzb24YBSABKAkSOgoEZGF0YRgGIAEoCzIsLnViZXIuc3VibWl0cXVldWUuc3RvdmVwaXBlLkhpc3RvcnlFdmVudERhdGFCDAoKb2NjdXJyZW5jZSKABAoQSGlzdG9yeUV2ZW50RGF0YRJMCg1yZXF1ZXN0X3N0YXRlGAEgASgLMjMudWJlci5zdWJtaXRxdWV1ZS5zdG92ZXBpcGUuSGlzdG9yeVJlcXVlc3RTdGF0ZURhdGFIABJHCg9idWlsZF90cmlnZ2VyZWQYAiABKAsyLC51YmVyLnN1Ym1pdHF1ZXVlLnN0b3ZlcGlwZS5IaXN0b3J5QnVpbGREYXRhSAASRgoOYnVpbGRfZmluaXNoZWQYAyABKAsyLC51YmVyLnN1Ym1pdHF1ZXVlLnN0b3ZlcGlwZS5IaXN0b3J5QnVpbGREYXRhSAASWQoYdmFsaWRhdGlvbl9mYWN0X3JlY29yZGVkGAQgASgLMjUudWJlci5zdWJtaXRxdWV1ZS5zdG92ZXBpcGUuSGlzdG9yeVZhbGlkYXRpb25GYWN0RGF0YUgAElUKFnByb2plY3RfZmFjdHNfcmVjb3JkZWQYBSABKAsyMy51YmVyLnN1Ym1pdHF1ZXVlLnN0b3ZlcGlwZS5IaXN0b3J5UHJvamVjdEZhY3RzRGF0YUgAElAKGGJ1aWxkX2FuYWx5c2lzX2FiYW5kb25lZBgGIAEoCzIsLnViZXIuc3VibWl0cXVldWUuc3RvdmVwaXBlLkhpc3RvcnlCdWlsZERhdGFIAEIJCgdwYXlsb2FkIisKF0hpc3RvcnlSZXF1ZXN0U3RhdGVEYXRhEhAKCGJ1aWxkX2lkGAEgASgJIiQKEEhpc3RvcnlCdWlsZERhdGESEAoIYnVpbGRfaWQYASABKAkiMAoZSGlzdG9yeVZhbGlkYXRpb25GYWN0RGF0YRITCgtmYWN0X2RlZ3JlZRgBIAEoASI1ChdIaXN0b3J5UHJvamVjdEZhY3RzRGF0YRIaChJwcm9qZWN0X2ZhY3RfY291bnQYASABKAQiowEKDlJlcXVlc3RIaXN0b3J5EhIKCnJlcXVlc3RfaWQYASABKAkSOAoGZXZlbnRzGAIgAygLMigudWJlci5zdWJtaXRxdWV1ZS5zdG92ZXBpcGUuSGlzdG9yeUV2ZW50EkMKD3JlcXVlc3Rfc3VtbWFyeRgDIAEoCzIqLnViZXIuc3VibWl0cXVldWUuc3RvdmVwaXBlLlJlcXVlc3RTdW1tYXJ5Ip4BCh1HZXRSZXF1ZXN0SGlzdG9yeUJ5SURSZXNwb25zZRI4CgZldmVudHMYASADKAsyKC51YmVyLnN1Ym1pdHF1ZXVlLnN0b3ZlcGlwZS5IaXN0b3J5RXZlbnQSQwoPcmVxdWVzdF9zdW1tYXJ5GAIgASgLMioudWJlci5zdWJtaXRxdWV1ZS5zdG92ZXBpcGUuUmVxdWVzdFN1bW1hcnkiXwoeR2V0UmVxdWVzdEhpc3RvcnlCeVVSSVJlc3BvbnNlEj0KCWhpc3RvcmllcxgBIAMoCzIqLnViZXIuc3VibWl0cXVldWUuc3RvdmVwaXBlLlJlcXVlc3RIaXN0b3J5InkKG0dldFByb2plY3RTdGF0dXNCeUlEUmVxdWVzdBINCgVxdWV1ZRgBIAEoCRISCgpyZXF1ZXN0X2lkGAIgASgJEhAKCHByb2plY3RzGAMgAygJEhEKCXBhZ2Vfc2l6ZRgEIAEoBRISCgpwYWdlX3Rva2VuGAUgASgJInoKHEdldFByb2plY3RTdGF0dXNCeVVSSVJlcXVlc3QSDQoFcXVldWUYASABKAkSEgoKY2hhbmdlX3VyaRgCIAEoCRIQCghwcm9qZWN0cxgDIAMoCRIRCglwYWdlX3NpemUYBCABKAUSEgoKcGFnZV90b2tlbhgFIAEoCSJfChFQcm9qZWN0VmFsaWRhdGlvbhIPCgdwcm9qZWN0GAEgASgJEhkKD2JyZWFrYWdlX2RlZ3JlZRgCIAEoAUgAEhQKDHByb2plY3RfdHlwZRgDIAEoCUIICgZyZXN1bHQizAIKHEdldFByb2plY3RTdGF0dXNCeUlEUmVzcG9uc2USEgoKcmVxdWVzdF9pZBgBIAEoCRINCgVxdWV1ZRgCIAEoCRISCgpjaGFuZ2VfdXJpGAMgASgJEhAKCGJhc2VfdXJpGAQgASgJEhUKDXJlcXVlc3Rfc3RhdGUYBSABKAkSFQoNdXBkYXRlZF9hdF9tcxgKIAEoAxIkChpyZXBvc2l0b3J5X2JyZWFrYWdlX2RlZ3JlZRgGIAEoAUgAEiAKGHByb2plY3RfcmVzdWx0c19jb21wbGV0ZRgHIAEoCBI/Cghwcm9qZWN0cxgIIAMoCzItLnViZXIuc3VibWl0cXVldWUuc3RvdmVwaXBlLlByb2plY3RWYWxpZGF0aW9uEhcKD25leHRfcGFnZV90b2tlbhgJIAEoCUITChFyZXBvc2l0b3J5X3Jlc3VsdCLNAgodR2V0UHJvamVjdFN0YXR1c0J5VVJJUmVzcG9uc2USEgoKcmVxdWVzdF9pZBgBIAEoCRINCgVxdWV1ZRgCIAEoCRISCgpjaGFuZ2VfdXJpGAMgASgJEhAKCGJhc2VfdXJpGAQgASgJEhUKDXJlcXVlc3Rfc3RhdGUYBSABKAkSFQoNdXBkYXRlZF9hdF9tcxgKIAEoAxIkChpyZXBvc2l0b3J5X2JyZWFrYWdlX2RlZ3JlZRgGIAEoAUgAEiAKGHByb2plY3RfcmVzdWx0c19jb21wbGV0ZRgHIAEoCBI/Cghwcm9qZWN0cxgIIAMoCzItLnViZXIuc3VibWl0cXVldWUuc3RvdmVwaXBlLlByb2plY3RWYWxpZGF0aW9uEhcKD25leHRfcGFnZV90b2tlbhgJIAEoCUITChFyZXBvc2l0b3J5X3Jlc3VsdCLyAQoOUmVxdWVzdFN1bW1hcnkSEgoKcmVxdWVzdF9pZBgBIAEoCRINCgVxdWV1ZRgCIAEoCRISCgpjaGFuZ2VfdXJpGAMgASgJEhAKCGJhc2VfdXJpGAQgASgJEhUKDXJlcXVlc3Rfc3RhdGUYBSABKAkSGwoTc3RhdGVfdXBkYXRlZF9hdF9tcxgGIAEoAxIWCg5hY2NlcHRlZF9hdF9tcxgHIAEoAxIWCg5vdXRjb21lX3JlYXNvbhgIIAEoCRIVCg1wcm9qZWN0X2NvdW50GAkgASgFEhwKFGJyb2tlbl9wcm9qZWN0X2NvdW50GAogASgFIrQBCgtMaXN0UmVxdWVzdBINCgVxdWV1ZRgBIAEoCRIhChdhY2NlcHRlZF9hdF9vcl9hZnRlcl9tcxgCIAEoA0gAEhwKEmFjY2VwdGVkX2JlZm9yZV9tcxgDIAEoA0gBEhEKCXBhZ2Vfc2l6ZRgEIAEoBRISCgpwYWdlX3Rva2VuGAUgASgJQhYKFGFjY2VwdGVkX2xvd2VyX2JvdW5kQhYKFGFjY2VwdGVkX3VwcGVyX2JvdW5kImUKDExpc3RSZXNwb25zZRI8CghyZXF1ZXN0cxgBIAMoCzIqLnViZXIuc3VibWl0cXVldWUuc3RvdmVwaXBlLlJlcXVlc3RTdW1tYXJ5EhcKD25leHRfcGFnZV90b2tlbhgCIAEoCSI6ChVHZXRRdWV1ZVN0YXR1c1JlcXVlc3QSDQoFcXVldWUYASABKAkSEgoKY2hhbmdlX3VyaRgCIAEoCSKeAQoVUXVldWVFbmFibGVtZW50U3RhdHVzEg8KB3ZlcnNpb24YASABKAMSOgoFc3RhdGUYAiABKA4yKy51YmVyLnN1Ym1pdHF1ZXVlLnN0b3ZlcGlwZS5RdWV1ZUVuYWJsZW1lbnQSIQoZZWZmZWN0aXZlX2Zyb21fY29tbWl0X3VyaRgDIAEoCRIVCg1jaGFuZ2VkX2F0X21zGAQgASgDIm4KFkdldFF1ZXVlU3RhdHVzUmVzcG9uc2USDQoFcXVldWUYASABKAkSRQoKZW5hYmxlbWVudBgCIAEoCzIxLnViZXIuc3VibWl0cXVldWUuc3RvdmVwaXBlLlF1ZXVlRW5hYmxlbWVudFN0YXR1cyKaAQoMSXNzdWVTdW1tYXJ5Eg8KB3Rhc2tfaWQYASABKAkSDwoHdW5pdF9pZBgCIAEoCRIOCgZzdGF0dXMYAyABKAkSGwoTZmlyc3Rfc2Vlbl9idWlsZF9pZBgEIAEoCRISCgpyZXF1ZXN0X2lkGAUgASgJEhAKCGJ1aWxkX2lkGAYgASgJEhUKDWNoYW5nZWRfYXRfbXMYByABKAMiTQoVTGlzdE9wZW5Jc3N1ZXNSZXF1ZXN0Eg0KBXF1ZXVlGAEgASgJEhEKCXBhZ2Vfc2l6ZRgCIAEoBRISCgpwYWdlX3Rva2VuGAMgASgJImsKFkxpc3RPcGVuSXNzdWVzUmVzcG9uc2USOAoGaXNzdWVzGAEgAygLMigudWJlci5zdWJtaXRxdWV1ZS5zdG92ZXBpcGUuSXNzdWVTdW1tYXJ5EhcKD25leHRfcGFnZV90b2tlbhgCIAEoCSpsCg9RdWV1ZUVuYWJsZW1lbnQSHAoYUVVFVUVfRU5BQkxFTUVOVF9VTktOT1dOEAASHAoYUVVFVUVfRU5BQkxFTUVOVF9FTkFCTEVEEAESHQoZUVVFVUVfRU5BQkxFTUVOVF9ESVNBQkxFRBACMuIICglTdG92ZXBpcGUSWwoEUGluZxInLnViZXIuc3VibWl0cXVldWUuc3RvdmVwaXBlLlBpbmdSZXF1ZXN0GigudWJlci5zdWJtaXRxdWV1ZS5zdG92ZXBpcGUuUGluZ1Jlc3BvbnNlIgASYQoGSW5nZXN0EikudWJlci5zdWJtaXRxdWV1ZS5zdG92ZXBpcGUuSW5nZXN0UmVxdWVzdBoqLnViZXIuc3VibWl0cXVldWUuc3RvdmVwaXBlLkluZ2VzdFJlc3BvbnNlIgASjgEKFUdldFJlcXVlc3RIaXN0b3J5QnlJRBI4LnViZXIuc3VibWl0cXVldWUuc3RvdmVwaXBlLkdldFJlcXVlc3RIaXN0b3J5QnlJRFJlcXVlc3QaOS51YmVyLnN1Ym1pdHF1ZXVlLnN0b3ZlcGlwZS5HZXRSZXF1ZXN0SGlzdG9yeUJ5SURSZXNwb25zZSIAEpEBChZHZXRSZXF1ZXN0SGlzdG9yeUJ5VVJJEjkudWJlci5zdWJtaXRxdWV1ZS5zdG92ZXBpcGUuR2V0UmVxdWVzdEhpc3RvcnlCeVVSSVJlcXVlc3QaOi51YmVyLnN1Ym1pdHF1ZXVlLnN0b3ZlcGlwZS5HZXRSZXF1ZXN0SGlzdG9yeUJ5VVJJUmVzcG9uc2UiABKLAQoUR2V0UHJvamVjdFN0YXR1c0J5SUQSNy51YmVyLnN1Ym1pdHF1ZXVlLnN0b3ZlcGlwZS5HZXRQcm9qZWN0U3RhdHVzQnlJRFJlcXVlc3QaOC51YmVyLnN1Ym1pdHF1ZXVlLnN0b3ZlcGlwZS5HZXRQcm9qZWN0U3RhdHVzQnlJRFJlc3BvbnNlIgASjgEKFUdldFByb2plY3RTdGF0dXNCeVVSSRI4LnViZXIuc3VibWl0cXVldWUuc3RvdmVwaXBlLkdldFByb2plY3RTdGF0dXNCeVVSSVJlcXVlc3QaOS51YmVyLnN1Ym1pdHF1ZXVlLnN0b3ZlcGlwZS5HZXRQcm9qZWN0U3RhdHVzQnlVUklSZXNwb25zZSIAElsKBExpc3QSJy51YmVyLnN1Ym1pdHF1ZXVlLnN0b3ZlcGlwZS5MaXN0UmVxdWVzdBooLnViZXIuc3VibWl0cXVldWUuc3RvdmVwaXBlLkxpc3RSZXNwb25zZSIAEnkKDkdldFF1ZXVlU3RhdHVzEjEudWJlci5zdWJtaXRxdWV1ZS5zdG92ZXBpcGUuR2V0UXVldWVTdGF0dXNSZXF1ZXN0GjIudWJlci5zdWJtaXRxdWV1ZS5zdG92ZXBpcGUuR2V0UXVldWVTdGF0dXNSZXNwb25zZSIAEnkKDkxpc3RPcGVuSXNzdWVzEjEudWJlci5zdWJtaXRxdWV1ZS5zdG92ZXBpcGUuTGlzdE9wZW5Jc3N1ZXNSZXF1ZXN0GjIudWJlci5zdWJtaXRxdWV1ZS5zdG92ZXBpcGUuTGlzdE9wZW5Jc3N1ZXNSZXNwb25zZSIAQmUKHmNvbS51YmVyLnN1Ym1pdHF1ZXVlLnN0b3ZlcGlwZUIOU3RvdmVwaXBlUHJvdG9QAVoxZ2l0aHViLmNvbS91YmVyL3N1Ym1pdHF1ZXVlL2FwaS9zdG92ZXBpcGUvcHJvdG9wYmIGcHJvdG8z"); + fileDesc("CiNhcGkvc3RvdmVwaXBlL3Byb3RvL3N0b3ZlcGlwZS5wcm90bxIadWJlci5zdWJtaXRxdWV1ZS5zdG92ZXBpcGUiHgoLUGluZ1JlcXVlc3QSDwoHbWVzc2FnZRgBIAEoCSJaCgxQaW5nUmVzcG9uc2USDwoHbWVzc2FnZRgBIAEoCRIUCgxzZXJ2aWNlX25hbWUYAiABKAkSEQoJdGltZXN0YW1wGAMgASgDEhAKCGhvc3RuYW1lGAQgASgJIl8KDUluZ2VzdFJlcXVlc3QSDQoFcXVldWUYASABKAkSPwoKZW5hYmxlbWVudBgCIAEoDjIrLnViZXIuc3VibWl0cXVldWUuc3RvdmVwaXBlLlF1ZXVlRW5hYmxlbWVudCJjCg5Jbmdlc3RSZXNwb25zZRIKCgJpZBgBIAEoCRJFCgplbmFibGVtZW50GAIgASgLMjEudWJlci5zdWJtaXRxdWV1ZS5zdG92ZXBpcGUuUXVldWVFbmFibGVtZW50U3RhdHVzIkEKHEdldFJlcXVlc3RIaXN0b3J5QnlJRFJlcXVlc3QSDQoFcXVldWUYASABKAkSEgoKcmVxdWVzdF9pZBgCIAEoCSI7Ch1HZXRSZXF1ZXN0SGlzdG9yeUJ5VVJJUmVxdWVzdBINCgVxdWV1ZRgBIAEoCRILCgN1cmkYAiABKAkiwgEKDEhpc3RvcnlFdmVudBIQCghldmVudF9pZBgBIAEoCRIUCgx0aW1lc3RhbXBfbXMYAiABKAMSFwoNcmVxdWVzdF9zdGF0ZRgDIAEoCUgAEg8KBWV2ZW50GAQgASgJSAASFgoOb3V0Y29tZV9yZWFzb24YBSABKAkSOgoEZGF0YRgGIAEoCzIsLnViZXIuc3VibWl0cXVldWUuc3RvdmVwaXBlLkhpc3RvcnlFdmVudERhdGFCDAoKb2NjdXJyZW5jZSKABAoQSGlzdG9yeUV2ZW50RGF0YRJMCg1yZXF1ZXN0X3N0YXRlGAEgASgLMjMudWJlci5zdWJtaXRxdWV1ZS5zdG92ZXBpcGUuSGlzdG9yeVJlcXVlc3RTdGF0ZURhdGFIABJHCg9idWlsZF90cmlnZ2VyZWQYAiABKAsyLC51YmVyLnN1Ym1pdHF1ZXVlLnN0b3ZlcGlwZS5IaXN0b3J5QnVpbGREYXRhSAASRgoOYnVpbGRfZmluaXNoZWQYAyABKAsyLC51YmVyLnN1Ym1pdHF1ZXVlLnN0b3ZlcGlwZS5IaXN0b3J5QnVpbGREYXRhSAASWQoYdmFsaWRhdGlvbl9mYWN0X3JlY29yZGVkGAQgASgLMjUudWJlci5zdWJtaXRxdWV1ZS5zdG92ZXBpcGUuSGlzdG9yeVZhbGlkYXRpb25GYWN0RGF0YUgAElUKFnByb2plY3RfZmFjdHNfcmVjb3JkZWQYBSABKAsyMy51YmVyLnN1Ym1pdHF1ZXVlLnN0b3ZlcGlwZS5IaXN0b3J5UHJvamVjdEZhY3RzRGF0YUgAElAKGGJ1aWxkX2FuYWx5c2lzX2FiYW5kb25lZBgGIAEoCzIsLnViZXIuc3VibWl0cXVldWUuc3RvdmVwaXBlLkhpc3RvcnlCdWlsZERhdGFIAEIJCgdwYXlsb2FkIisKF0hpc3RvcnlSZXF1ZXN0U3RhdGVEYXRhEhAKCGJ1aWxkX2lkGAEgASgJIiQKEEhpc3RvcnlCdWlsZERhdGESEAoIYnVpbGRfaWQYASABKAkiMAoZSGlzdG9yeVZhbGlkYXRpb25GYWN0RGF0YRITCgtmYWN0X2RlZ3JlZRgBIAEoASI1ChdIaXN0b3J5UHJvamVjdEZhY3RzRGF0YRIaChJwcm9qZWN0X2ZhY3RfY291bnQYASABKAQiowEKDlJlcXVlc3RIaXN0b3J5EhIKCnJlcXVlc3RfaWQYASABKAkSOAoGZXZlbnRzGAIgAygLMigudWJlci5zdWJtaXRxdWV1ZS5zdG92ZXBpcGUuSGlzdG9yeUV2ZW50EkMKD3JlcXVlc3Rfc3VtbWFyeRgDIAEoCzIqLnViZXIuc3VibWl0cXVldWUuc3RvdmVwaXBlLlJlcXVlc3RTdW1tYXJ5Ip4BCh1HZXRSZXF1ZXN0SGlzdG9yeUJ5SURSZXNwb25zZRI4CgZldmVudHMYASADKAsyKC51YmVyLnN1Ym1pdHF1ZXVlLnN0b3ZlcGlwZS5IaXN0b3J5RXZlbnQSQwoPcmVxdWVzdF9zdW1tYXJ5GAIgASgLMioudWJlci5zdWJtaXRxdWV1ZS5zdG92ZXBpcGUuUmVxdWVzdFN1bW1hcnkiXwoeR2V0UmVxdWVzdEhpc3RvcnlCeVVSSVJlc3BvbnNlEj0KCWhpc3RvcmllcxgBIAMoCzIqLnViZXIuc3VibWl0cXVldWUuc3RvdmVwaXBlLlJlcXVlc3RIaXN0b3J5InkKG0dldFByb2plY3RTdGF0dXNCeUlEUmVxdWVzdBINCgVxdWV1ZRgBIAEoCRISCgpyZXF1ZXN0X2lkGAIgASgJEhAKCHByb2plY3RzGAMgAygJEhEKCXBhZ2Vfc2l6ZRgEIAEoBRISCgpwYWdlX3Rva2VuGAUgASgJInoKHEdldFByb2plY3RTdGF0dXNCeVVSSVJlcXVlc3QSDQoFcXVldWUYASABKAkSEgoKY2hhbmdlX3VyaRgCIAEoCRIQCghwcm9qZWN0cxgDIAMoCRIRCglwYWdlX3NpemUYBCABKAUSEgoKcGFnZV90b2tlbhgFIAEoCSJfChFQcm9qZWN0VmFsaWRhdGlvbhIPCgdwcm9qZWN0GAEgASgJEhkKD2JyZWFrYWdlX2RlZ3JlZRgCIAEoAUgAEhQKDHByb2plY3RfdHlwZRgDIAEoCUIICgZyZXN1bHQizAIKHEdldFByb2plY3RTdGF0dXNCeUlEUmVzcG9uc2USEgoKcmVxdWVzdF9pZBgBIAEoCRINCgVxdWV1ZRgCIAEoCRISCgpjaGFuZ2VfdXJpGAMgASgJEhAKCGJhc2VfdXJpGAQgASgJEhUKDXJlcXVlc3Rfc3RhdGUYBSABKAkSFQoNdXBkYXRlZF9hdF9tcxgKIAEoAxIkChpyZXBvc2l0b3J5X2JyZWFrYWdlX2RlZ3JlZRgGIAEoAUgAEiAKGHByb2plY3RfcmVzdWx0c19jb21wbGV0ZRgHIAEoCBI/Cghwcm9qZWN0cxgIIAMoCzItLnViZXIuc3VibWl0cXVldWUuc3RvdmVwaXBlLlByb2plY3RWYWxpZGF0aW9uEhcKD25leHRfcGFnZV90b2tlbhgJIAEoCUITChFyZXBvc2l0b3J5X3Jlc3VsdCLNAgodR2V0UHJvamVjdFN0YXR1c0J5VVJJUmVzcG9uc2USEgoKcmVxdWVzdF9pZBgBIAEoCRINCgVxdWV1ZRgCIAEoCRISCgpjaGFuZ2VfdXJpGAMgASgJEhAKCGJhc2VfdXJpGAQgASgJEhUKDXJlcXVlc3Rfc3RhdGUYBSABKAkSFQoNdXBkYXRlZF9hdF9tcxgKIAEoAxIkChpyZXBvc2l0b3J5X2JyZWFrYWdlX2RlZ3JlZRgGIAEoAUgAEiAKGHByb2plY3RfcmVzdWx0c19jb21wbGV0ZRgHIAEoCBI/Cghwcm9qZWN0cxgIIAMoCzItLnViZXIuc3VibWl0cXVldWUuc3RvdmVwaXBlLlByb2plY3RWYWxpZGF0aW9uEhcKD25leHRfcGFnZV90b2tlbhgJIAEoCUITChFyZXBvc2l0b3J5X3Jlc3VsdCLyAQoOUmVxdWVzdFN1bW1hcnkSEgoKcmVxdWVzdF9pZBgBIAEoCRINCgVxdWV1ZRgCIAEoCRISCgpjaGFuZ2VfdXJpGAMgASgJEhAKCGJhc2VfdXJpGAQgASgJEhUKDXJlcXVlc3Rfc3RhdGUYBSABKAkSGwoTc3RhdGVfdXBkYXRlZF9hdF9tcxgGIAEoAxIWCg5hY2NlcHRlZF9hdF9tcxgHIAEoAxIWCg5vdXRjb21lX3JlYXNvbhgIIAEoCRIVCg1wcm9qZWN0X2NvdW50GAkgASgFEhwKFGJyb2tlbl9wcm9qZWN0X2NvdW50GAogASgFIrQBCgtMaXN0UmVxdWVzdBINCgVxdWV1ZRgBIAEoCRIhChdhY2NlcHRlZF9hdF9vcl9hZnRlcl9tcxgCIAEoA0gAEhwKEmFjY2VwdGVkX2JlZm9yZV9tcxgDIAEoA0gBEhEKCXBhZ2Vfc2l6ZRgEIAEoBRISCgpwYWdlX3Rva2VuGAUgASgJQhYKFGFjY2VwdGVkX2xvd2VyX2JvdW5kQhYKFGFjY2VwdGVkX3VwcGVyX2JvdW5kImUKDExpc3RSZXNwb25zZRI8CghyZXF1ZXN0cxgBIAMoCzIqLnViZXIuc3VibWl0cXVldWUuc3RvdmVwaXBlLlJlcXVlc3RTdW1tYXJ5EhcKD25leHRfcGFnZV90b2tlbhgCIAEoCSI6ChVHZXRRdWV1ZVN0YXR1c1JlcXVlc3QSDQoFcXVldWUYASABKAkSEgoKY2hhbmdlX3VyaRgCIAEoCSKeAQoVUXVldWVFbmFibGVtZW50U3RhdHVzEg8KB3ZlcnNpb24YASABKAMSOgoFc3RhdGUYAiABKA4yKy51YmVyLnN1Ym1pdHF1ZXVlLnN0b3ZlcGlwZS5RdWV1ZUVuYWJsZW1lbnQSIQoZZWZmZWN0aXZlX2Zyb21fY29tbWl0X3VyaRgDIAEoCRIVCg1jaGFuZ2VkX2F0X21zGAQgASgDIm4KFkdldFF1ZXVlU3RhdHVzUmVzcG9uc2USDQoFcXVldWUYASABKAkSRQoKZW5hYmxlbWVudBgCIAEoCzIxLnViZXIuc3VibWl0cXVldWUuc3RvdmVwaXBlLlF1ZXVlRW5hYmxlbWVudFN0YXR1cyKaAQoMSXNzdWVTdW1tYXJ5Eg8KB3Rhc2tfaWQYASABKAkSDwoHdW5pdF9pZBgCIAEoCRIOCgZzdGF0dXMYAyABKAkSGwoTZmlyc3Rfc2Vlbl9idWlsZF9pZBgEIAEoCRISCgpyZXF1ZXN0X2lkGAUgASgJEhAKCGJ1aWxkX2lkGAYgASgJEhUKDWNoYW5nZWRfYXRfbXMYByABKAMiTQoVTGlzdE9wZW5Jc3N1ZXNSZXF1ZXN0Eg0KBXF1ZXVlGAEgASgJEhEKCXBhZ2Vfc2l6ZRgCIAEoBRISCgpwYWdlX3Rva2VuGAMgASgJImsKFkxpc3RPcGVuSXNzdWVzUmVzcG9uc2USOAoGaXNzdWVzGAEgAygLMigudWJlci5zdWJtaXRxdWV1ZS5zdG92ZXBpcGUuSXNzdWVTdW1tYXJ5EhcKD25leHRfcGFnZV90b2tlbhgCIAEoCSJ0ChNSZXF1ZXN0SXNzdWVTdW1tYXJ5Eg8KB3Rhc2tfaWQYASABKAkSDwoHdW5pdF9pZBgCIAEoCRIMCgRraW5kGAMgASgJEhAKCGJ1aWxkX2lkGAQgASgJEhsKE2ZpcnN0X3NlZW5fYnVpbGRfaWQYBSABKAkiZwobR2V0SXNzdWVzQnlSZXF1ZXN0SURSZXF1ZXN0Eg0KBXF1ZXVlGAEgASgJEhIKCnJlcXVlc3RfaWQYAiABKAkSEQoJcGFnZV9zaXplGAMgASgFEhIKCnBhZ2VfdG9rZW4YBCABKAkiiAEKHEdldElzc3Vlc0J5UmVxdWVzdElEUmVzcG9uc2USPwoGaXNzdWVzGAEgAygLMi8udWJlci5zdWJtaXRxdWV1ZS5zdG92ZXBpcGUuUmVxdWVzdElzc3VlU3VtbWFyeRIOCgZzdGF0dXMYAiABKAkSFwoPbmV4dF9wYWdlX3Rva2VuGAMgASgJKmwKD1F1ZXVlRW5hYmxlbWVudBIcChhRVUVVRV9FTkFCTEVNRU5UX1VOS05PV04QABIcChhRVUVVRV9FTkFCTEVNRU5UX0VOQUJMRUQQARIdChlRVUVVRV9FTkFCTEVNRU5UX0RJU0FCTEVEEAIy8AkKCVN0b3ZlcGlwZRJbCgRQaW5nEicudWJlci5zdWJtaXRxdWV1ZS5zdG92ZXBpcGUuUGluZ1JlcXVlc3QaKC51YmVyLnN1Ym1pdHF1ZXVlLnN0b3ZlcGlwZS5QaW5nUmVzcG9uc2UiABJhCgZJbmdlc3QSKS51YmVyLnN1Ym1pdHF1ZXVlLnN0b3ZlcGlwZS5Jbmdlc3RSZXF1ZXN0GioudWJlci5zdWJtaXRxdWV1ZS5zdG92ZXBpcGUuSW5nZXN0UmVzcG9uc2UiABKOAQoVR2V0UmVxdWVzdEhpc3RvcnlCeUlEEjgudWJlci5zdWJtaXRxdWV1ZS5zdG92ZXBpcGUuR2V0UmVxdWVzdEhpc3RvcnlCeUlEUmVxdWVzdBo5LnViZXIuc3VibWl0cXVldWUuc3RvdmVwaXBlLkdldFJlcXVlc3RIaXN0b3J5QnlJRFJlc3BvbnNlIgASkQEKFkdldFJlcXVlc3RIaXN0b3J5QnlVUkkSOS51YmVyLnN1Ym1pdHF1ZXVlLnN0b3ZlcGlwZS5HZXRSZXF1ZXN0SGlzdG9yeUJ5VVJJUmVxdWVzdBo6LnViZXIuc3VibWl0cXVldWUuc3RvdmVwaXBlLkdldFJlcXVlc3RIaXN0b3J5QnlVUklSZXNwb25zZSIAEosBChRHZXRQcm9qZWN0U3RhdHVzQnlJRBI3LnViZXIuc3VibWl0cXVldWUuc3RvdmVwaXBlLkdldFByb2plY3RTdGF0dXNCeUlEUmVxdWVzdBo4LnViZXIuc3VibWl0cXVldWUuc3RvdmVwaXBlLkdldFByb2plY3RTdGF0dXNCeUlEUmVzcG9uc2UiABKOAQoVR2V0UHJvamVjdFN0YXR1c0J5VVJJEjgudWJlci5zdWJtaXRxdWV1ZS5zdG92ZXBpcGUuR2V0UHJvamVjdFN0YXR1c0J5VVJJUmVxdWVzdBo5LnViZXIuc3VibWl0cXVldWUuc3RvdmVwaXBlLkdldFByb2plY3RTdGF0dXNCeVVSSVJlc3BvbnNlIgASWwoETGlzdBInLnViZXIuc3VibWl0cXVldWUuc3RvdmVwaXBlLkxpc3RSZXF1ZXN0GigudWJlci5zdWJtaXRxdWV1ZS5zdG92ZXBpcGUuTGlzdFJlc3BvbnNlIgASeQoOR2V0UXVldWVTdGF0dXMSMS51YmVyLnN1Ym1pdHF1ZXVlLnN0b3ZlcGlwZS5HZXRRdWV1ZVN0YXR1c1JlcXVlc3QaMi51YmVyLnN1Ym1pdHF1ZXVlLnN0b3ZlcGlwZS5HZXRRdWV1ZVN0YXR1c1Jlc3BvbnNlIgASeQoOTGlzdE9wZW5Jc3N1ZXMSMS51YmVyLnN1Ym1pdHF1ZXVlLnN0b3ZlcGlwZS5MaXN0T3Blbklzc3Vlc1JlcXVlc3QaMi51YmVyLnN1Ym1pdHF1ZXVlLnN0b3ZlcGlwZS5MaXN0T3Blbklzc3Vlc1Jlc3BvbnNlIgASiwEKFEdldElzc3Vlc0J5UmVxdWVzdElEEjcudWJlci5zdWJtaXRxdWV1ZS5zdG92ZXBpcGUuR2V0SXNzdWVzQnlSZXF1ZXN0SURSZXF1ZXN0GjgudWJlci5zdWJtaXRxdWV1ZS5zdG92ZXBpcGUuR2V0SXNzdWVzQnlSZXF1ZXN0SURSZXNwb25zZSIAQmUKHmNvbS51YmVyLnN1Ym1pdHF1ZXVlLnN0b3ZlcGlwZUIOU3RvdmVwaXBlUHJvdG9QAVoxZ2l0aHViLmNvbS91YmVyL3N1Ym1pdHF1ZXVlL2FwaS9zdG92ZXBpcGUvcHJvdG9wYmIGcHJvdG8z"); /** * PingRequest is the request for the Ping method @@ -1221,6 +1221,132 @@ export type ListOpenIssuesResponse = Message<"uber.submitqueue.stovepipe.ListOpe export const ListOpenIssuesResponseSchema: GenMessage = /*@__PURE__*/ messageDesc(file_api_stovepipe_proto_stovepipe, 28); +/** + * RequestIssueSummary describes one accepted issue update, not the queue's open-issue set. + * + * @generated from message uber.submitqueue.stovepipe.RequestIssueSummary + */ +export type RequestIssueSummary = Message<"uber.submitqueue.stovepipe.RequestIssueSummary"> & { + /** + * Logical validation task. + * + * @generated from field: string task_id = 1; + */ + taskId: string; + + /** + * Validation unit; empty identifies a task-wide failure. + * + * @generated from field: string unit_id = 2; + */ + unitId: string; + + /** + * "new" (including reopened), "existing" (reported and already open), or "closed". + * + * @generated from field: string kind = 3; + */ + kind: string; + + /** + * Build whose accepted update produced this result. + * + * @generated from field: string build_id = 4; + */ + buildId: string; + + /** + * Build where this occurrence first opened. + * + * @generated from field: string first_seen_build_id = 5; + */ + firstSeenBuildId: string; +}; + +/** + * Describes the message uber.submitqueue.stovepipe.RequestIssueSummary. + * Use `create(RequestIssueSummarySchema)` to create a new message. + */ +export const RequestIssueSummarySchema: GenMessage = /*@__PURE__*/ + messageDesc(file_api_stovepipe_proto_stovepipe, 29); + +/** + * @generated from message uber.submitqueue.stovepipe.GetIssuesByRequestIDRequest + */ +export type GetIssuesByRequestIDRequest = Message<"uber.submitqueue.stovepipe.GetIssuesByRequestIDRequest"> & { + /** + * Required configured queue. + * + * @generated from field: string queue = 1; + */ + queue: string; + + /** + * Required canonical positive decimal request ID within the queue. + * + * @generated from field: string request_id = 2; + */ + requestId: string; + + /** + * Maximum results to return. Zero selects 50; otherwise 1 through 200. + * + * @generated from field: int32 page_size = 3; + */ + pageSize: number; + + /** + * Opaque continuation bound to this queue and request. + * + * @generated from field: string page_token = 4; + */ + pageToken: string; +}; + +/** + * Describes the message uber.submitqueue.stovepipe.GetIssuesByRequestIDRequest. + * Use `create(GetIssuesByRequestIDRequestSchema)` to create a new message. + */ +export const GetIssuesByRequestIDRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_api_stovepipe_proto_stovepipe, 30); + +/** + * Completed and abandoned results are stable; in-progress pages are not a snapshot. + * + * @generated from message uber.submitqueue.stovepipe.GetIssuesByRequestIDResponse + */ +export type GetIssuesByRequestIDResponse = Message<"uber.submitqueue.stovepipe.GetIssuesByRequestIDResponse"> & { + /** + * Results in ascending bytewise task/unit order, classified as new, existing, or closed. + * + * @generated from field: repeated uber.submitqueue.stovepipe.RequestIssueSummary issues = 1; + */ + issues: RequestIssueSummary[]; + + /** + * "in_progress", "complete", "incomplete" (abandoned), or "unavailable". + * Unavailable means recording has not started or predates retained coverage. + * Complete describes reconciliation, not build success or absence of open issues. + * + * @generated from field: string status = 2; + */ + status: string; + + /** + * Empty ends this scan. Restart in-progress scans after completion to include late results. + * + * @generated from field: string next_page_token = 3; + */ + nextPageToken: string; +}; + +/** + * Describes the message uber.submitqueue.stovepipe.GetIssuesByRequestIDResponse. + * Use `create(GetIssuesByRequestIDResponseSchema)` to create a new message. + */ +export const GetIssuesByRequestIDResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_api_stovepipe_proto_stovepipe, 31); + /** * QueueEnablement is independent of validation results and execution controls. * @@ -1348,6 +1474,16 @@ export const Stovepipe: GenService<{ input: typeof ListOpenIssuesRequestSchema; output: typeof ListOpenIssuesResponseSchema; }, + /** + * GetIssuesByRequestID returns accepted issue results for one request. + * + * @generated from rpc uber.submitqueue.stovepipe.Stovepipe.GetIssuesByRequestID + */ + getIssuesByRequestID: { + methodKind: "unary"; + input: typeof GetIssuesByRequestIDRequestSchema; + output: typeof GetIssuesByRequestIDResponseSchema; + }, }> = /*@__PURE__*/ serviceDesc(file_api_stovepipe_proto_stovepipe, 0);