diff --git a/ydb/core/viewer/json_local_rpc.h b/ydb/core/viewer/json_local_rpc.h index 59c25ef62f64..7da7a195a77d 100644 --- a/ydb/core/viewer/json_local_rpc.h +++ b/ydb/core/viewer/json_local_rpc.h @@ -130,8 +130,7 @@ class TJsonLocalRpc : public TViewerPipeClient { return false; } } - const auto& params(Event->Get()->Request.GetParams()); - Params2Proto(params, request); + Params2Proto(Params, request); if (!ValidateRequest(request)) { return false; } diff --git a/ydb/core/viewer/operation_list.h b/ydb/core/viewer/operation_list.h index 201cc21b3150..a84ce33acd90 100644 --- a/ydb/core/viewer/operation_list.h +++ b/ydb/core/viewer/operation_list.h @@ -23,6 +23,27 @@ class TOperationList : public TOperationListRpc { AllowedMethods = {HTTP_METHOD_GET}; } + bool ValidateRequest(Ydb::Operations::ListOperationsRequest& request) override { + if (!TBase::ValidateRequest(request)) { + return false; + } + ui64 offset = FromStringWithDefault(Params.Get("offset"), 0); + ui64 limit = FromStringWithDefault(Params.Get("limit"), 0); + if (offset >= 0 && limit > 0) { + if (offset % limit != 0) { + ReplyAndPassAway(GetHTTPBADREQUEST("text/plain", "offset must be a multiple of limit")); + return false; + } + if (limit > 100) { + ReplyAndPassAway(GetHTTPBADREQUEST("text/plain", "limit must be less than or equal to 100")); + return false; + } + request.set_page_size(limit); + request.set_page_token(std::to_string(offset / limit + 1)); + } + return true; + } + static YAML::Node GetSwagger() { YAML::Node node = YAML::Load(R"___( get: @@ -42,7 +63,8 @@ class TOperationList : public TOperationListRpc { kind: * `ss/backgrounds` * `export` - * `import` + * `import/S3` + * `import/YT` * `buildindex` * `scriptexec` required: true @@ -57,6 +79,16 @@ class TOperationList : public TOperationListRpc { description: page token required: false type: string + - name: offset + in: query + description: offset. must be a multiple of limit + required: false + type: integer + - name: limit + in: query + description: limit. must be less than or equal to 100 + required: false + type: integer responses: 200: description: OK diff --git a/ydb/core/viewer/tests/canondata/result.json b/ydb/core/viewer/tests/canondata/result.json index e04934d68e3f..b2bab0a640d7 100644 --- a/ydb/core/viewer/tests/canondata/result.json +++ b/ydb/core/viewer/tests/canondata/result.json @@ -3,6 +3,18 @@ "status_code": 200, "text": "1\n" }, + "test.test_operations_list": { + "next_page_token": "1", + "status": "SUCCESS" + }, + "test.test_operations_list_page": { + "next_page_token": "1", + "status": "SUCCESS" + }, + "test.test_operations_list_page_bad": { + "status_code": 400, + "text": "offset must be a multiple of limit" + }, "test.test_pqrb_tablet": { "response_create_topic": { "version": "not-zero-number" @@ -506,6 +518,142 @@ ], "TotalGroups": 5 }, + "test.test_topic_data": { + "response_compressed": { + "EndOffset": 21, + "Messages": [ + { + "Codec": 1, + "Message": "Y29tcHJlc3NlZC1tZXNzYWdlLTA=", + "Offset": 11, + "OriginalSize": 20, + "SeqNo": 12, + "StorageSize": 38 + }, + { + "Codec": 1, + "Message": "Y29tcHJlc3NlZC1tZXNzYWdlLTE=", + "Offset": 12, + "OriginalSize": 20, + "SeqNo": 13, + "StorageSize": 38 + }, + { + "Codec": 1, + "Message": "Y29tcHJlc3NlZC1tZXNzYWdlLTI=", + "Offset": 13, + "OriginalSize": 20, + "SeqNo": 14, + "StorageSize": 38 + }, + { + "Codec": 1, + "Message": "Y29tcHJlc3NlZC1tZXNzYWdlLTM=", + "Offset": 14, + "OriginalSize": 20, + "SeqNo": 15, + "StorageSize": 38 + }, + { + "Codec": 1, + "Message": "Y29tcHJlc3NlZC1tZXNzYWdlLTQ=", + "Offset": 15, + "OriginalSize": 20, + "SeqNo": 16, + "StorageSize": 38 + } + ], + "StartOffset": 0, + "Truncated": true + }, + "response_metadata": { + "EndOffset": 21, + "Messages": [ + { + "Codec": 1, + "Message": "bWVzc2FnZV93aXRoX21ldGE=", + "MessageMetadata": [ + { + "Key": "key1", + "Value": "value1" + }, + { + "Key": "key2", + "Value": "value2" + } + ], + "Offset": 10, + "OriginalSize": 17, + "SeqNo": 11, + "StorageSize": 37 + } + ], + "StartOffset": 0, + "Truncated": true + }, + "response_not_truncated": { + "EndOffset": 21, + "Messages": [ + { + "Codec": 1, + "Message": "Y29tcHJlc3NlZC1tZXNzYWdlLTk=", + "Offset": 20, + "OriginalSize": 20, + "SeqNo": 21, + "StorageSize": 38 + } + ], + "StartOffset": 0, + "Truncated": false + }, + "response_read": { + "EndOffset": 21, + "Messages": [ + { + "Codec": 0, + "Message": "bWVzc2FnZS0w", + "Offset": 0, + "OriginalSize": 9, + "SeqNo": 1, + "StorageSize": 9 + }, + { + "Codec": 0, + "Message": "bWVzc2FnZS0x", + "Offset": 1, + "OriginalSize": 9, + "SeqNo": 2, + "StorageSize": 9 + }, + { + "Codec": 0, + "Message": "bWVzc2FnZS0y", + "Offset": 2, + "OriginalSize": 9, + "SeqNo": 3, + "StorageSize": 9 + }, + { + "Codec": 0, + "Message": "bWVzc2FnZS0z", + "Offset": 3, + "OriginalSize": 9, + "SeqNo": 4, + "StorageSize": 9 + }, + { + "Codec": 0, + "Message": "bWVzc2FnZS00", + "Offset": 4, + "OriginalSize": 9, + "SeqNo": 5, + "StorageSize": 9 + } + ], + "StartOffset": 0, + "Truncated": true + } + }, "test.test_viewer_acl": { "/Root": { "Common": { @@ -4943,75 +5091,5 @@ }, "test.test_wait_for_cluster_ready": { "wait_good": true - }, - "test.test_topic_data": { - "response_read": { - "Messages": [ - { - "Codec": 0, "StorageSize": 9, "SeqNo": 1, "Message": "bWVzc2FnZS0w", "OriginalSize": 9, "Offset": 0 - }, - { - "Codec": 0, "StorageSize": 9, "SeqNo": 2, "Message": "bWVzc2FnZS0x", "OriginalSize": 9, "Offset": 1 - }, - { - "Codec": 0, "StorageSize": 9, "SeqNo": 3, "Message": "bWVzc2FnZS0y", "OriginalSize": 9, "Offset": 2 - }, - { - "Codec": 0, "StorageSize": 9, "SeqNo": 4, "Message": "bWVzc2FnZS0z", "OriginalSize": 9, "Offset": 3 - }, - { - "Codec": 0, "StorageSize": 9, "SeqNo": 5, "Message": "bWVzc2FnZS00", "OriginalSize": 9, "Offset": 4 - } - ], - "StartOffset": 0, - "EndOffset": 21, - "Truncated": true - }, - "response_metadata": { - "Messages": [ - { - "Codec": 1, "StorageSize": 37, "SeqNo": 11, - "MessageMetadata": [ - {"Value": "value1", "Key": "key1"}, - {"Value": "value2", "Key": "key2"}], - "Message": "bWVzc2FnZV93aXRoX21ldGE=", "OriginalSize": 17, "Offset": 10 - } - ], - "StartOffset": 0, - "EndOffset": 21, - "Truncated": true - }, - "response_compressed": { - "Messages": [ - { - "Codec": 1, "StorageSize": 38, "SeqNo": 12, "Message": "Y29tcHJlc3NlZC1tZXNzYWdlLTA=", "OriginalSize": 20, "Offset": 11 - }, - { - "Codec": 1, "StorageSize": 38, "SeqNo": 13, "Message": "Y29tcHJlc3NlZC1tZXNzYWdlLTE=", "OriginalSize": 20, "Offset": 12 - }, - { - "Codec": 1, "StorageSize": 38, "SeqNo": 14, "Message": "Y29tcHJlc3NlZC1tZXNzYWdlLTI=", "OriginalSize": 20, "Offset": 13 - }, - { - "Codec": 1, "StorageSize": 38, "SeqNo": 15, "Message": "Y29tcHJlc3NlZC1tZXNzYWdlLTM=", "OriginalSize": 20, "Offset": 14 - }, - { - "Codec": 1, "StorageSize": 38, "SeqNo": 16, "Message": "Y29tcHJlc3NlZC1tZXNzYWdlLTQ=", "OriginalSize": 20, "Offset": 15 - } - ], - "StartOffset": 0, - "EndOffset": 21, - "Truncated": true - }, - "response_not_truncated": { - "Messages": [ - { - "Codec": 1, "StorageSize": 38, "SeqNo": 21, "Message": "Y29tcHJlc3NlZC1tZXNzYWdlLTk=", "OriginalSize": 20, "Offset": 20 - } - ], - "StartOffset": 0, - "EndOffset": 21, - "Truncated": false - } } } diff --git a/ydb/core/viewer/tests/test.py b/ydb/core/viewer/tests/test.py index 761802164e4e..6eb849b74ec0 100644 --- a/ydb/core/viewer/tests/test.py +++ b/ydb/core/viewer/tests/test.py @@ -577,6 +577,31 @@ def test_viewer_nodes_issue_14992(): return result +def test_operations_list(): + return get_viewer_normalized("/operation/list", { + 'database': dedicated_db, + 'kind': 'import/s3' + }) + + +def test_operations_list_page(): + return get_viewer_normalized("/operation/list", { + 'database': dedicated_db, + 'kind': 'import/s3', + 'offset': 50, + 'limit': 50 + }) + + +def test_operations_list_page_bad(): + return get_viewer_normalized("/operation/list", { + 'database': dedicated_db, + 'kind': 'import/s3', + 'offset': 10, + 'limit': 50 + }) + + def test_topic_data(): grpc_port = cluster.nodes[1].grpc_port