Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions api/seqapi/v1/seq_api.proto
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,7 @@ message FetchAsyncSearchResultResponse {
double progress = 7;
uint64 disk_usage = 8;
string meta = 9;
Error error = 10;
}

message GetAsyncSearchesListRequest {
Expand All @@ -305,6 +306,7 @@ message GetAsyncSearchesListResponse {
}

repeated ListItem searches = 1;
Error error = 2;
}

message CancelAsyncSearchRequest {
Expand Down
50 changes: 50 additions & 0 deletions internal/api/seqapi/v1/grpc/fetch_async_search_result_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,56 @@ func TestServeFetchAsyncSearchResult(t *testing.T) {
Meta: meta,
},
},
{
name: "partial_response",
req: &seqapi.FetchAsyncSearchResultRequest{
SearchId: mockSearchID,
Limit: 2,
Offset: 10,
Order: seqapi.Order_ORDER_DESC,
},
resp: &seqapi.FetchAsyncSearchResultResponse{
Status: seqapi.AsyncSearchStatus_ASYNC_SEARCH_STATUS_DONE,
Request: &seqapi.StartAsyncSearchRequest{
Retention: durationpb.New(60 * time.Second),
Query: "message:error",
From: timestamppb.New(mockTime.Add(-15 * time.Minute)),
To: timestamppb.New(mockTime),
WithDocs: true,
Size: 100,
},
Response: &seqapi.SearchResponse{
Events: []*seqapi.Event{
{
Id: "017a854298010000-850287cfa326a7fc",
Data: map[string]string{
"level": "3",
"message": "some error",
"x": "2",
},
Time: timestamppb.New(mockTime.Add(-1 * time.Minute)),
},
},
Total: 1,
Error: &seqapi.Error{
Code: seqapi.ErrorCode_ERROR_CODE_NO,
},
},
StartedAt: timestamppb.New(mockTime.Add(-30 * time.Second)),
ExpiresAt: timestamppb.New(mockTime.Add(30 * time.Second)),
Progress: 1,
DiskUsage: 512,
Meta: meta,
Error: &seqapi.Error{
Code: seqapi.ErrorCode_ERROR_CODE_PARTIAL_RESPONSE,
Message: "partial response",
},
},
repoResp: types.AsyncSearchInfo{
SearchID: mockSearchID,
Meta: meta,
},
},
{
name: "invalid id",
req: &seqapi.FetchAsyncSearchResultRequest{
Expand Down
40 changes: 40 additions & 0 deletions internal/api/seqapi/v1/grpc/get_async_searches_list_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,46 @@ func TestServeGetAsyncSearchesList(t *testing.T) {
searchIDs: []string{mockSearchID1},
},
},
{
name: "partial_response",
req: &seqapi.GetAsyncSearchesListRequest{},
resp: &seqapi.GetAsyncSearchesListResponse{
Searches: []*seqapi.GetAsyncSearchesListResponse_ListItem{
{
SearchId: mockSearchID1,
Status: seqapi.AsyncSearchStatus_ASYNC_SEARCH_STATUS_DONE,
Request: &seqapi.StartAsyncSearchRequest{
Retention: durationpb.New(60 * time.Second),
Query: "message:error",
From: timestamppb.New(mockTime.Add(-15 * time.Minute)),
To: timestamppb.New(mockTime),
WithDocs: true,
Size: 100,
},
StartedAt: timestamppb.New(mockTime.Add(-30 * time.Second)),
ExpiresAt: timestamppb.New(mockTime.Add(30 * time.Second)),
Progress: 1,
DiskUsage: 512,
OwnerName: mockUserName1,
},
},
Error: &seqapi.Error{
Code: seqapi.ErrorCode_ERROR_CODE_PARTIAL_RESPONSE,
Message: "partial response",
},
},
mockArgs: &mockArgs{
repoReq: types.GetAsyncSearchesListRequest{},
repoResp: []types.AsyncSearchInfo{
{
SearchID: mockSearchID1,
OwnerID: mockProfileID1,
OwnerName: mockUserName1,
},
},
searchIDs: []string{mockSearchID1},
},
},
{
name: "err_limit",
req: &seqapi.GetAsyncSearchesListRequest{
Expand Down
24 changes: 12 additions & 12 deletions internal/api/seqapi/v1/http/fetch_async_search_result.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,16 +105,16 @@ type fetchAsyncSearchResultResponse struct {
Progress float64 `json:"progress"`
DiskUsage string `json:"disk_usage" format:"int64"`
Meta string `json:"meta"`
Error apiError `json:"error"`
} // @name seqapi.v1.FetchAsyncSearchResultResponse

type asyncSearchResponse struct {
Events events `json:"events"`
Histogram *histogram `json:"histogram,omitempty"`
Aggregations aggregations `json:"aggregations,omitempty"`
AggregationsTs aggregationsTs `json:"aggregations_ts,omitempty"`
Total string `json:"total,omitempty" format:"int64"`
Error apiError `json:"error"`
PartialResponse bool `json:"partialResponse"`
Events events `json:"events"`
Histogram *histogram `json:"histogram,omitempty"`
Aggregations aggregations `json:"aggregations,omitempty"`
AggregationsTs aggregationsTs `json:"aggregations_ts,omitempty"`
Total string `json:"total,omitempty" format:"int64"`
Error apiError `json:"error"`
} // @name seqapi.v1.AsyncSearchResponse

func fetchAsyncSearchResultResponseFromProto(resp *seqapi.FetchAsyncSearchResultResponse) fetchAsyncSearchResultResponse {
Expand All @@ -134,16 +134,16 @@ func fetchAsyncSearchResultResponseFromProto(resp *seqapi.FetchAsyncSearchResult
Progress: resp.Progress,
DiskUsage: strconv.FormatUint(resp.DiskUsage, 10),
Meta: resp.Meta,
Error: apiErrorFromProto(resp.GetError()),
}
}

func asyncSearchResponseFromProto(proto *seqapi.SearchResponse, reqAggs []*seqapi.AggregationQuery) asyncSearchResponse {
sr := asyncSearchResponse{
Events: eventsFromProto(proto.GetEvents()),
Histogram: histogramFromProto(proto.GetHistogram(), false),
Error: apiErrorFromProto(proto.GetError()),
PartialResponse: proto.GetPartialResponse(),
Total: strconv.FormatInt(proto.GetTotal(), 10),
Events: eventsFromProto(proto.GetEvents()),
Histogram: histogramFromProto(proto.GetHistogram(), false),
Error: apiErrorFromProto(proto.GetError()),
Total: strconv.FormatInt(proto.GetTotal(), 10),
}

// split aggs and aggs with timeseries
Expand Down
66 changes: 65 additions & 1 deletion internal/api/seqapi/v1/http/fetch_async_search_result_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,71 @@ func TestServeFetchAsyncSearchResult(t *testing.T) {
Meta: meta,
},
},
wantRespBody: `{"status":"done","request":{"retention":"seconds:60","query":"message:error","from":"2025-08-06T17:37:12.000000123Z","to":"2025-08-06T17:52:12.000000123Z","aggregations":[{"field":"x","group_by":"level","agg_func":"avg","quantiles":[0.9,0.5]},{"field":"y","group_by":"level","agg_func":"sum","interval":"30s"}],"histogram":{"interval":"1s"},"with_docs":true,"size":100},"response":{"events":[{"id":"017a854298010000-850287cfa326a7fc","data":{"level":"3","message":"some error","x":"2"},"time":"2025-08-06T17:51:12.000000123Z"},{"id":"017a854298010000-8502fe7f2aa33df3","data":{"level":"2","message":"some error 2","x":"8"},"time":"2025-08-06T17:50:12.000000123Z"}],"histogram":{"buckets":[{"key":"1","docCount":"7"},{"key":"2","docCount":"9"}]},"aggregations":[{"buckets":[{"key":"3","value":2,"quantiles":[2,1]},{"key":"2","value":8,"not_exists":1,"quantiles":[7,4]}]}],"aggregations_ts":[{"data":{"result":[{"metric":{"level":"33"},"values":[{"timestamp":1754502702,"value":2},{"timestamp":1754502732,"value":5}]},{"metric":{"level":"22"},"values":[{"timestamp":1754502672,"value":8}]}]}}],"total":"2","error":{"code":"ERROR_CODE_NO","message":"some error"},"partialResponse":false},"started_at":"2025-08-06T17:51:42.000000123Z","expires_at":"2025-08-06T17:52:42.000000123Z","progress":1,"disk_usage":"512","meta":"{\"some\":\"meta\"}"}`,
wantRespBody: `{"status":"done","request":{"retention":"seconds:60","query":"message:error","from":"2025-08-06T17:37:12.000000123Z","to":"2025-08-06T17:52:12.000000123Z","aggregations":[{"field":"x","group_by":"level","agg_func":"avg","quantiles":[0.9,0.5]},{"field":"y","group_by":"level","agg_func":"sum","interval":"30s"}],"histogram":{"interval":"1s"},"with_docs":true,"size":100},"response":{"events":[{"id":"017a854298010000-850287cfa326a7fc","data":{"level":"3","message":"some error","x":"2"},"time":"2025-08-06T17:51:12.000000123Z"},{"id":"017a854298010000-8502fe7f2aa33df3","data":{"level":"2","message":"some error 2","x":"8"},"time":"2025-08-06T17:50:12.000000123Z"}],"histogram":{"buckets":[{"key":"1","docCount":"7"},{"key":"2","docCount":"9"}]},"aggregations":[{"buckets":[{"key":"3","value":2,"quantiles":[2,1]},{"key":"2","value":8,"not_exists":1,"quantiles":[7,4]}]}],"aggregations_ts":[{"data":{"result":[{"metric":{"level":"33"},"values":[{"timestamp":1754502702,"value":2},{"timestamp":1754502732,"value":5}]},{"metric":{"level":"22"},"values":[{"timestamp":1754502672,"value":8}]}]}}],"total":"2","error":{"code":"ERROR_CODE_NO","message":"some error"}},"started_at":"2025-08-06T17:51:42.000000123Z","expires_at":"2025-08-06T17:52:42.000000123Z","progress":1,"disk_usage":"512","meta":"{\"some\":\"meta\"}","error":{"code":"ERROR_CODE_NO"}}`,
wantStatus: http.StatusOK,
},
{
name: "partial_response",
reqBody: `{"search_id":"c9a34cf8-4c66-484e-9cc2-42979d848656","limit":2,"offset":10,"order":"desc"}`,
mockArgs: &mockArgs{
proxyReq: &seqapi.FetchAsyncSearchResultRequest{
SearchId: mockSearchID,
Limit: 2,
Offset: 10,
Order: seqapi.Order_ORDER_DESC,
},
proxyResp: &seqapi.FetchAsyncSearchResultResponse{
Status: seqapi.AsyncSearchStatus_ASYNC_SEARCH_STATUS_DONE,
Request: &seqapi.StartAsyncSearchRequest{
Retention: durationpb.New(60 * time.Second),
Query: "message:error",
From: timestamppb.New(mockTime.Add(-15 * time.Minute)),
To: timestamppb.New(mockTime),
WithDocs: true,
Size: 100,
},
Response: &seqapi.SearchResponse{
Events: []*seqapi.Event{
{
Id: "017a854298010000-850287cfa326a7fc",
Data: map[string]string{
"level": "3",
"message": "some error",
"x": "2",
},
Time: timestamppb.New(mockTime.Add(-1 * time.Minute)),
},
{
Id: "017a854298010000-8502fe7f2aa33df3",
Data: map[string]string{
"level": "2",
"message": "some error 2",
"x": "8",
},
Time: timestamppb.New(mockTime.Add(-2 * time.Minute)),
},
},
Total: 2,
Error: &seqapi.Error{
Code: seqapi.ErrorCode_ERROR_CODE_NO,
Message: "some error",
},
},
StartedAt: timestamppb.New(mockTime.Add(-30 * time.Second)),
ExpiresAt: timestamppb.New(mockTime.Add(30 * time.Second)),
Progress: 1,
DiskUsage: 512,
Error: &seqapi.Error{
Code: seqapi.ErrorCode_ERROR_CODE_PARTIAL_RESPONSE,
Message: "partial response",
},
},
repoResp: types.AsyncSearchInfo{
SearchID: mockSearchID,
Meta: meta,
},
},
wantRespBody: `{"status":"done","request":{"retention":"seconds:60","query":"message:error","from":"2025-08-06T17:37:12.000000123Z","to":"2025-08-06T17:52:12.000000123Z","with_docs":true,"size":100},"response":{"events":[{"id":"017a854298010000-850287cfa326a7fc","data":{"level":"3","message":"some error","x":"2"},"time":"2025-08-06T17:51:12.000000123Z"},{"id":"017a854298010000-8502fe7f2aa33df3","data":{"level":"2","message":"some error 2","x":"8"},"time":"2025-08-06T17:50:12.000000123Z"}],"total":"2","error":{"code":"ERROR_CODE_NO","message":"some error"}},"started_at":"2025-08-06T17:51:42.000000123Z","expires_at":"2025-08-06T17:52:42.000000123Z","progress":1,"disk_usage":"512","meta":"{\"some\":\"meta\"}","error":{"code":"ERROR_CODE_PARTIAL_RESPONSE","message":"partial response"}}`,
wantStatus: http.StatusOK,
},
{
Expand Down
2 changes: 2 additions & 0 deletions internal/api/seqapi/v1/http/get_async_searches_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ func (r getAsyncSearchesListRequest) toProto() (*seqapi.GetAsyncSearchesListRequ

type getAsyncSearchesListResponse struct {
Searches []asyncSearchesListItem `json:"searches"`
Error apiError `json:"error"`
} // @name seqapi.v1.GetAsyncSearchesListResponse

type asyncSearchesListItem struct {
Expand Down Expand Up @@ -154,6 +155,7 @@ func getAsyncSearchesListResponseFromProto(resp *seqapi.GetAsyncSearchesListResp

return getAsyncSearchesListResponse{
Searches: searches,
Error: apiErrorFromProto(resp.GetError()),
}
}

Expand Down
47 changes: 45 additions & 2 deletions internal/api/seqapi/v1/http/get_async_searches_list_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ func TestServeGetAsyncSearchesList(t *testing.T) {
},
searchIDs: []string{mockSearchID1, mockSearchID2},
},
wantRespBody: `{"searches":[{"search_id":"c9a34cf8-4c66-484e-9cc2-42979d848656","status":"done","request":{"retention":"seconds:60","query":"message:error","from":"2025-08-06T17:37:12.000000123Z","to":"2025-08-06T17:52:12.000000123Z","with_docs":true,"size":100},"started_at":"2025-08-06T17:51:42.000000123Z","expires_at":"2025-08-06T17:52:42.000000123Z","progress":1,"disk_usage":"512","owner_name":"some_user_1","error":"some error"},{"search_id":"9e4c068e-d4f4-4a5d-be27-a6524a70d70d","status":"canceled","request":{"retention":"seconds:360","query":"message:error and level:3","from":"2025-08-06T16:52:12.000000123Z","to":"2025-08-06T17:52:12.000000123Z","aggregations":[{"field":"x","group_by":"level","agg_func":"avg","interval":"30s"}],"histogram":{"interval":"1s"},"with_docs":false,"size":0},"started_at":"2025-08-06T17:51:12.000000123Z","expires_at":"2025-08-06T17:57:12.000000123Z","canceled_at":"2025-08-06T17:52:12.000000123Z","progress":1,"disk_usage":"256","owner_name":"some_user_2"}]}`,
wantRespBody: `{"searches":[{"search_id":"c9a34cf8-4c66-484e-9cc2-42979d848656","status":"done","request":{"retention":"seconds:60","query":"message:error","from":"2025-08-06T17:37:12.000000123Z","to":"2025-08-06T17:52:12.000000123Z","with_docs":true,"size":100},"started_at":"2025-08-06T17:51:42.000000123Z","expires_at":"2025-08-06T17:52:42.000000123Z","progress":1,"disk_usage":"512","owner_name":"some_user_1","error":"some error"},{"search_id":"9e4c068e-d4f4-4a5d-be27-a6524a70d70d","status":"canceled","request":{"retention":"seconds:360","query":"message:error and level:3","from":"2025-08-06T16:52:12.000000123Z","to":"2025-08-06T17:52:12.000000123Z","aggregations":[{"field":"x","group_by":"level","agg_func":"avg","interval":"30s"}],"histogram":{"interval":"1s"},"with_docs":false,"size":0},"started_at":"2025-08-06T17:51:12.000000123Z","expires_at":"2025-08-06T17:57:12.000000123Z","canceled_at":"2025-08-06T17:52:12.000000123Z","progress":1,"disk_usage":"256","owner_name":"some_user_2"}],"error":{"code":"ERROR_CODE_NO"}}`,
wantStatus: http.StatusOK,
},
{
Expand Down Expand Up @@ -168,7 +168,50 @@ func TestServeGetAsyncSearchesList(t *testing.T) {
},
searchIDs: []string{mockSearchID1},
},
wantRespBody: `{"searches":[{"search_id":"c9a34cf8-4c66-484e-9cc2-42979d848656","status":"done","request":{"retention":"seconds:60","query":"message:error","from":"2025-08-06T17:37:12.000000123Z","to":"2025-08-06T17:52:12.000000123Z","with_docs":true,"size":100},"started_at":"2025-08-06T17:51:42.000000123Z","expires_at":"2025-08-06T17:52:42.000000123Z","progress":1,"disk_usage":"512","owner_name":"some_user_1"}]}`,
wantRespBody: `{"searches":[{"search_id":"c9a34cf8-4c66-484e-9cc2-42979d848656","status":"done","request":{"retention":"seconds:60","query":"message:error","from":"2025-08-06T17:37:12.000000123Z","to":"2025-08-06T17:52:12.000000123Z","with_docs":true,"size":100},"started_at":"2025-08-06T17:51:42.000000123Z","expires_at":"2025-08-06T17:52:42.000000123Z","progress":1,"disk_usage":"512","owner_name":"some_user_1"}],"error":{"code":"ERROR_CODE_NO"}}`,
wantStatus: http.StatusOK,
},
{
name: "partial_response",
reqBody: `{}`,
mockArgs: &mockArgs{
proxyReq: &seqapi.GetAsyncSearchesListRequest{},
proxyResp: &seqapi.GetAsyncSearchesListResponse{
Searches: []*seqapi.GetAsyncSearchesListResponse_ListItem{
{
SearchId: mockSearchID1,
Status: seqapi.AsyncSearchStatus_ASYNC_SEARCH_STATUS_DONE,
Request: &seqapi.StartAsyncSearchRequest{
Retention: durationpb.New(60 * time.Second),
Query: "message:error",
From: timestamppb.New(mockTime.Add(-15 * time.Minute)),
To: timestamppb.New(mockTime),
WithDocs: true,
Size: 100,
},
StartedAt: timestamppb.New(mockTime.Add(-30 * time.Second)),
ExpiresAt: timestamppb.New(mockTime.Add(30 * time.Second)),
Progress: 1,
DiskUsage: 512,
OwnerName: mockUserName1,
},
},
Error: &seqapi.Error{
Code: seqapi.ErrorCode_ERROR_CODE_PARTIAL_RESPONSE,
Message: "partial response",
},
},
repoReq: types.GetAsyncSearchesListRequest{},
repoResp: []types.AsyncSearchInfo{
{
SearchID: mockSearchID1,
OwnerID: mockProfileID1,
OwnerName: mockUserName1,
},
},
searchIDs: []string{mockSearchID1},
},
wantRespBody: `{"searches":[{"search_id":"c9a34cf8-4c66-484e-9cc2-42979d848656","status":"done","request":{"retention":"seconds:60","query":"message:error","from":"2025-08-06T17:37:12.000000123Z","to":"2025-08-06T17:52:12.000000123Z","with_docs":true,"size":100},"started_at":"2025-08-06T17:51:42.000000123Z","expires_at":"2025-08-06T17:52:42.000000123Z","progress":1,"disk_usage":"512","owner_name":"some_user_1"}],"error":{"code":"ERROR_CODE_PARTIAL_RESPONSE","message":"partial response"}}`,
wantStatus: http.StatusOK,
},
{
Expand Down
2 changes: 2 additions & 0 deletions internal/pkg/client/seqdb/grpc_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,7 @@ func (p *proxyFetchAsyncSearchResultResp) toProto() (*seqapi.FetchAsyncSearchRes
CanceledAt: p.CanceledAt,
Progress: p.Progress,
DiskUsage: p.DiskUsage,
Error: (*proxyError)(p.Error).toProto(),
}, nil
}

Expand Down Expand Up @@ -435,6 +436,7 @@ func (p *proxyGetAsyncSearchesListResp) toProto() *seqapi.GetAsyncSearchesListRe

return &seqapi.GetAsyncSearchesListResponse{
Searches: searches,
Error: (*proxyError)(p.Error).toProto(),
}
}

Expand Down
Loading