From 3bc4b2714f41b21b525694ee86b7effd0104dbf7 Mon Sep 17 00:00:00 2001 From: Uzondu Enudeme Date: Thu, 15 Dec 2022 03:47:23 +0100 Subject: [PATCH] ui: add end-to-end request/response via grcp-web fixes #8 --- ui/package-lock.json | 15 +++++ ui/package.json | 3 + ui/src/components/inputs/Inputs.tsx | 24 +++++-- ui/src/components/load-tester/Fields.tsx | 28 ++++---- ui/src/components/load-tester/LoadTester.tsx | 68 ++++++++++++++------ ui/src/components/output/Outputs.tsx | 36 ++++++++--- 6 files changed, 128 insertions(+), 46 deletions(-) diff --git a/ui/package-lock.json b/ui/package-lock.json index 4691f6e..cf201a0 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -27,6 +27,9 @@ "react-scripts": "5.0.1", "typescript": "^4.9.3", "web-vitals": "^2.1.4" + }, + "devDependencies": { + "@types/google-protobuf": "^3.15.6" } }, "node_modules/@adobe/css-tools": { @@ -4173,6 +4176,12 @@ "@types/range-parser": "*" } }, + "node_modules/@types/google-protobuf": { + "version": "3.15.6", + "resolved": "https://registry.npmjs.org/@types/google-protobuf/-/google-protobuf-3.15.6.tgz", + "integrity": "sha512-pYVNNJ+winC4aek+lZp93sIKxnXt5qMkuKmaqS3WGuTq0Bw1ZDYNBgzG5kkdtwcv+GmYJGo3yEg6z2cKKAiEdw==", + "dev": true + }, "node_modules/@types/graceful-fs": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz", @@ -20074,6 +20083,12 @@ "@types/range-parser": "*" } }, + "@types/google-protobuf": { + "version": "3.15.6", + "resolved": "https://registry.npmjs.org/@types/google-protobuf/-/google-protobuf-3.15.6.tgz", + "integrity": "sha512-pYVNNJ+winC4aek+lZp93sIKxnXt5qMkuKmaqS3WGuTq0Bw1ZDYNBgzG5kkdtwcv+GmYJGo3yEg6z2cKKAiEdw==", + "dev": true + }, "@types/graceful-fs": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz", diff --git a/ui/package.json b/ui/package.json index 192ab0a..63b1daf 100644 --- a/ui/package.json +++ b/ui/package.json @@ -46,5 +46,8 @@ "last 1 firefox version", "last 1 safari version" ] + }, + "devDependencies": { + "@types/google-protobuf": "^3.15.6" } } diff --git a/ui/src/components/inputs/Inputs.tsx b/ui/src/components/inputs/Inputs.tsx index 666c09d..f13095d 100644 --- a/ui/src/components/inputs/Inputs.tsx +++ b/ui/src/components/inputs/Inputs.tsx @@ -10,6 +10,7 @@ export enum FieldType { LIST_BASED = 'list-based-field', OBJECT_BASED = 'object-based-field', SINGLE_SELECTION_BASED = 'single-selection-field', + TIME_BASED = 'time-based-field' // append 's' to the output representing duration in seconds }; export type FormField = { @@ -20,7 +21,7 @@ export type FormField = { fieldType: FieldType, keys?: string[], required?: string | boolean; - options?: { name: string, value: string, info?: string }[], + options?: { name: string, value: string|number, info?: string }[], }; interface Props { @@ -50,7 +51,7 @@ export default function Inputs(props: Props) { const { control, handleSubmit } = useForm({ defaultValues }); - const onSubmit = (data: any) => { + const onSubmit = (data: any) => { handleFormSubmission?.(data); }; @@ -64,7 +65,20 @@ export default function Inputs(props: Props) { name={f.name} control={control} rules={{ required: f.required }} - render={({ field, fieldState: { error } }) => { + render={({ field, fieldState: { error } }) => { + if (f.fieldType === FieldType.TIME_BASED) { + return ( + + ); + } if (f.fieldType === FieldType.SINGLE_SELECTION_BASED) { return ( )} - ) + ); } return ( - ) + ); }} /> diff --git a/ui/src/components/load-tester/Fields.tsx b/ui/src/components/load-tester/Fields.tsx index e8ba193..aa2b017 100644 --- a/ui/src/components/load-tester/Fields.tsx +++ b/ui/src/components/load-tester/Fields.tsx @@ -1,9 +1,9 @@ import { FieldType, FormField } from 'src/components/inputs/Inputs'; -import { - RunLoadtestRequestBroadcastTxMethod as BroadcastMethod, - RunLoadtestRequestEndpointSelectMethod as SelectMethod, -} from 'src/gen/LoadtestApi'; +import { RunLoadtestRequest } from 'src/gen/orijtech/cosmosloadtester/v1/loadtest_service_pb'; + +const BroadcastTxMethod = RunLoadtestRequest.BroadcastTxMethod; +const SelectMethod = RunLoadtestRequest.EndpointSelectMethod; export const fields: FormField[] = [ { @@ -11,11 +11,11 @@ export const fields: FormField[] = [ label: 'Broadcast TX Method', info: 'The broadcast tx method to use when submitting transactions, can be async, sync, or commit', fieldType: FieldType.SINGLE_SELECTION_BASED, - default: BroadcastMethod.BROADCAST_TX_METHOD_ASYNC, + default: BroadcastTxMethod.BROADCAST_TX_METHOD_ASYNC, options: [ - { name: 'Async', value: BroadcastMethod.BROADCAST_TX_METHOD_ASYNC }, - { name: 'Sync', value: BroadcastMethod.BROADCAST_TX_METHOD_SYNC }, - { name: 'Commit', value: BroadcastMethod.BROADCAST_TX_METHOD_COMMIT }, + { name: 'Async', value: BroadcastTxMethod.BROADCAST_TX_METHOD_ASYNC }, + { name: 'Sync', value: BroadcastTxMethod.BROADCAST_TX_METHOD_SYNC }, + { name: 'Commit', value: BroadcastTxMethod.BROADCAST_TX_METHOD_COMMIT }, ], }, { @@ -34,6 +34,7 @@ export const fields: FormField[] = [ name: 'clientFactory', required: 'Client factory field is required.', label: 'Client factory', + default: 'test-cosmos-client-factory', info: 'The identifier of the client factory to use for generating load testing transactions. Maps to --client-factory in tm-load-test', fieldType: FieldType.VALUE_BASED, }, @@ -42,7 +43,8 @@ export const fields: FormField[] = [ label: 'Duration', info: 'The duration (in seconds) for which to handle the load test. Maps to --time in tm-load-test.', required: 'Duration to handle load test is required', - fieldType: FieldType.VALUE_BASED, + default: 20, + fieldType: FieldType.TIME_BASED, }, { name: 'endpoints', @@ -69,15 +71,15 @@ export const fields: FormField[] = [ name: 'peerConnectTimeout', label: 'Peer connect timeout', info: 'The number of seconds to wait for all required peers to connect if expect-peers > 0. Maps to --peer-connect-timeout in tm-load-test.', - default: 2, - fieldType: FieldType.VALUE_BASED, + default: 20, + fieldType: FieldType.TIME_BASED, }, { name: 'sendPeriod', label: 'Send period', info: 'The period (in seconds) at which to send batches of transactions. Maps to --send-period in tm-load-test.', - default: 1, - fieldType: FieldType.VALUE_BASED, + default: 20, + fieldType: FieldType.TIME_BASED, }, { name: 'connectionCount', diff --git a/ui/src/components/load-tester/LoadTester.tsx b/ui/src/components/load-tester/LoadTester.tsx index 0902fe2..792ecaf 100644 --- a/ui/src/components/load-tester/LoadTester.tsx +++ b/ui/src/components/load-tester/LoadTester.tsx @@ -4,29 +4,59 @@ import Typography from '@mui/material/Typography'; import Alert from '@mui/material/Alert'; import Button from '@mui/material/Button'; -import { V1RunLoadtestResponse as LoadTestResult } from 'src/gen/LoadtestApi'; +import { + LoadtestServiceClient, +} from 'src/gen/orijtech/cosmosloadtester/v1/Loadtest_serviceServiceClientPb'; +import { + RunLoadtestRequest, + RunLoadtestResponse, +} from 'src/gen/orijtech/cosmosloadtester/v1/loadtest_service_pb'; + +import * as timestamp_pb from 'google-protobuf/google/protobuf/timestamp_pb'; + import Inputs from 'src/components/inputs/Inputs'; import Outputs from 'src/components/output/Outputs'; import { fields } from './Fields'; +const service = new LoadtestServiceClient(''); + export default function LoadTester() { const [running, setRunning] = React.useState(false); const [error, setError] = React.useState(''); - const [data, setData] = React.useState(); - + const [data, setData] = React.useState(); + const submitRef = React.useRef(null); const onFormSubmit = async (data: any) => { try { + // reset previous response & error after new form submission setData(undefined); + setError(''); + setRunning(true); - await new Promise((resolve) => { - setTimeout(() => { - resolve(data); - setData(data as LoadTestResult); - }, 2000); - }); + const endpoints: string[] = data.endpoints?.split(',') || []; + const request = new RunLoadtestRequest(); + request + .setBroadcastTxMethod(data.broadcastTxMethod) + .setClientFactory(data.clientFactory) + .setDuration(new timestamp_pb.Timestamp().setSeconds(data.duration)) + .setEndpointSelectMethod(data.endpointSelectMethod) + .setEndpointsList(endpoints) + .setExpectPeersCount(data.expectPeersCount) + .setMaxEndpointCount(data.maxEndpointCount) + .setMinPeerConnectivityCount(data.minPeerConnectivityCount) + .setPeerConnectTimeout(new timestamp_pb.Timestamp().setSeconds(data.peerConnectTimeout)) + .setSendPeriod(new timestamp_pb.Timestamp().setSeconds(data.sendPeriod)) + .setStatsOutputFilePath(data.statsOutputFilePath) + .setTransactionCount(data.transactionCount) + .setTransactionSizeBytes(data.transactionSizeBytes) + .setTransactionsPerSecond(data.transactionsPerSecond); + + const result = await service.runLoadtest(request, null); + setData(result.toObject()); + console.log(result.toObject()); } catch (e: any) { + console.log(e); setError(e.message); } finally { setRunning(false); @@ -57,18 +87,18 @@ export default function LoadTester() { {/* display errors if any occured */} - {error !== '' && {error}} + {error !== '' && {error}} {/* render data if it exists */} { - data !== undefined && - <> - - - Load Testing Results - - - - + data !== undefined && + <> + + + Load Testing Results + + + + } ); diff --git a/ui/src/components/output/Outputs.tsx b/ui/src/components/output/Outputs.tsx index 22348aa..1c7f69a 100644 --- a/ui/src/components/output/Outputs.tsx +++ b/ui/src/components/output/Outputs.tsx @@ -1,20 +1,38 @@ -import Typography from '@mui/material/Typography'; import Box from '@mui/material/Box'; +import Chip from '@mui/material/Chip'; -import { V1RunLoadtestResponse as Result } from 'src/gen/LoadtestApi'; +import { + RunLoadtestResponse as Result, +} from 'src/gen/orijtech/cosmosloadtester/v1/loadtest_service_pb'; interface OutputProps { - data: Result; + data: Result.AsObject; }; +interface ResultBarProps { + title: string; + value: string | number; +}; + +function ResultBar(props: ResultBarProps) { + const { title, value } = props; + return ( + + + + + ); +} + export default function Inputs(props: OutputProps) { + const { data } = props; return ( - -
-                    {JSON.stringify(props.data, null, 2)}
-                
-
+ + + + +
- ) + ); }