From 1dba0d476f810dbcc1a424b8a5ba0147d5a6939c Mon Sep 17 00:00:00 2001 From: kr172t Date: Tue, 9 Jan 2024 00:04:52 +0200 Subject: [PATCH 01/35] add message size parameter to api and curl --- api/src/api/analyze_api.py | 7 ++-- api/src/services/analyze_service.py | 37 +++++++++---------- api/src/services/test_suites_service.py | 4 +-- api/tests/test_analyze_api.py | 45 ++++++++++++++++++------ curl/package-lock.json | 39 +++++++++++++++----- curl/package.json | 7 ++-- curl/scripts/run-curl-loop.sh | 5 +-- curl/src/curl/curl.controller.spec.ts | 3 ++ curl/src/curl/curl.service.spec.ts | 13 +++++-- curl/src/curl/curl.service.ts | 12 ++++--- curl/src/dto/curl-request.dto.spec.ts | 31 ++++++++++++++-- curl/src/dto/curl-request.dto.ts | 7 +++- curl/src/utils/message.generator.spec.ts | 28 +++++++++++++++ curl/src/utils/message.generator.ts | 10 ++++++ run/docker/docker-compose.yml | 4 +-- 15 files changed, 189 insertions(+), 63 deletions(-) create mode 100644 curl/src/utils/message.generator.spec.ts create mode 100644 curl/src/utils/message.generator.ts diff --git a/api/src/api/analyze_api.py b/api/src/api/analyze_api.py index f430356e..9a576b2a 100644 --- a/api/src/api/analyze_api.py +++ b/api/src/api/analyze_api.py @@ -36,11 +36,14 @@ def analyze(): return jsonify({'error': 'An error occurred while processing the request', 'message':''}), HTTP_STATUS_INTERNAL_SERVER_ERROR def __validate(data): - if not data or 'algorithms' not in data or 'iterationsCount' not in data or 'experimentName' not in data or 'description' not in data: - raise ApiException('Missing properties, required properties: algorithms, iterationsCount, experimentName, description', INVALID_DATA_MESSAGE, HTTP_STATUS_BAD_REQUEST) + if not data or 'algorithms' not in data or 'iterationsCount' not in data or 'experimentName' not in data or 'description' not in data or 'messageSizes' not in data: + raise ApiException('Missing properties, required properties: algorithms, iterationsCount, experimentName, description, messageSizes', INVALID_DATA_MESSAGE, HTTP_STATUS_BAD_REQUEST) for iterations in data['iterationsCount']: if iterations <= 0: raise ApiException('The number of iterations should be greater than 0', INVALID_DATA_MESSAGE, HTTP_STATUS_BAD_REQUEST) + for message_size in data['messageSizes']: + if message_size <= 0: + raise ApiException('The message size should be greater than 0', INVALID_DATA_MESSAGE, HTTP_STATUS_BAD_REQUEST) if process_is_running: raise ApiException('The previous test is still running. Please try again in few minutes', 'Current test is still running', HTTP_STATUS_LOCKED) for algorithm in data['algorithms']: diff --git a/api/src/services/analyze_service.py b/api/src/services/analyze_service.py index 3e89fcaf..650b34d6 100644 --- a/api/src/services/analyze_service.py +++ b/api/src/services/analyze_service.py @@ -1,5 +1,3 @@ -import os -import uuid import time import requests import logging @@ -9,13 +7,7 @@ from flask import jsonify, current_app import src.services.test_suites_service as tests_service import src.services.metrics_service as metrics_service -from src.models.env_info import EnvInfo -from src.models.test_suite import TestSuite -from src.models.test_run import TestRun from src.enums.status import Status -from src.exceptions.exceptions import ApiException - - # constants WAIT_MS = 15 @@ -26,14 +18,16 @@ def analyze(data): start_time = int(datetime.timestamp(datetime.now() - timedelta(seconds=60)) * 1000) iterations_count = data['iterationsCount'] algorithms = data['algorithms'] + message_sizes = data['messageSizes'] first_run = True for algorithm in algorithms: for iterations in iterations_count: - if not first_run: - time.sleep(WAIT_MS) - else: - first_run = False - __create_test_run(algorithm, iterations, test_suite.id) + for message_size in message_sizes: + if not first_run: + time.sleep(WAIT_MS) + else: + first_run = False + __create_test_run(algorithm, iterations, message_size, test_suite.id) # end time is now + 90 sec, to show the graph after the test for sure finished running end_time = int(datetime.timestamp(datetime.now() + timedelta(seconds=90)) * 1000) @@ -45,22 +39,23 @@ def analyze(data): return jsonify({'test_suite_id': test_suite.id}) -def __create_test_run(algorithm, iterations, test_suite_id): - start_time=datetime.now() +def __create_test_run(algorithm, iterations, message_size, test_suite_id): + start_time = datetime.now() metrics_service.start_collecting() - status, status_message = __run(algorithm, iterations) + status, status_message = __run(algorithm, iterations, message_size) metrics_service.stop_collecting() end_time=datetime.now() - tests_service.create_test_run(start_time, end_time, algorithm, iterations, test_suite_id, status, status_message, *metrics_service.get_metrics()) - + tests_service.create_test_run(start_time, end_time, algorithm, iterations, message_size, test_suite_id, status, status_message, *metrics_service.get_metrics()) + -def __run(algorithm, iterations): +def __run(algorithm, iterations, message_sizes): logging.debug('Running test for algorithm: %s ', algorithm) payload = { 'algorithm': algorithm, - 'iterationsCount': iterations + 'iterationsCount': iterations, + 'messageSizes': message_sizes } - headers = { 'Content-Type': 'application/json' } + headers = {'Content-Type': 'application/json'} response = requests.post(current_app.configurations.curl_url + "/curl", headers=headers, json=payload, timeout=int(current_app.configurations.request_timeout)) return __validate_response(response) diff --git a/api/src/services/test_suites_service.py b/api/src/services/test_suites_service.py index d8ee1cc9..9f4de261 100644 --- a/api/src/services/test_suites_service.py +++ b/api/src/services/test_suites_service.py @@ -34,7 +34,7 @@ def create_test_suite(data): current_app.database_manager.create(test_suite) return test_suite -def create_test_run(start_time, end_time, algorithm, iterations, test_suite_id, status, status_message, client_metrics, server_metrics): +def create_test_run(start_time, end_time, algorithm, iterations, message_size, test_suite_id, status, status_message, client_metrics, server_metrics): test_run = TestRun( start_time=start_time, end_time=end_time, @@ -42,7 +42,7 @@ def create_test_run(start_time, end_time, algorithm, iterations, test_suite_id, iterations=iterations, status=status, status_message=status_message, - # message_size=1024, + message_size=message_size, test_suite_id=test_suite_id ) current_app.database_manager.create(test_run) diff --git a/api/tests/test_analyze_api.py b/api/tests/test_analyze_api.py index 9003567d..86943031 100644 --- a/api/tests/test_analyze_api.py +++ b/api/tests/test_analyze_api.py @@ -39,7 +39,8 @@ def test_analyze(self, mock_start_collecting, mock_stop_collecting, mock_get_met "algorithms":["kyber512"], "iterationsCount": [1000, 2000], "experimentName": "name", - "description": "name" + "description": "name", + "messageSizes": [100] } # Mock the requests.post call with patch(POST_REQUEST) as mock_post: @@ -78,7 +79,8 @@ def test_analyze_return_general_error(self, mock_start_collecting, mock_stop_col "algorithms":["kyber512"], "iterationsCount": [1000], "experimentName": "name", - "description": "name" + "description": "name", + "messageSizes": [100] } # Mock the requests.post call to raise an exception @@ -98,7 +100,8 @@ def test_analyze_with_invalid_iterations_count(self, mock_start_collecting, mock "algorithms": ["kyber512"], "iterationsCount": [-1], "experimentName": "name", - "description": "name" + "description": "name", + "messageSizes": [100] } response = self.client.post(PATH, data=json.dumps(input_data), @@ -108,13 +111,30 @@ def test_analyze_with_invalid_iterations_count(self, mock_start_collecting, mock self.assertEqual(response_json["error"], INVALID_DATA_PROVIDED) self.assertEqual(response_json["message"], "The number of iterations should be greater than 0") + def test_analyze_with_invalid_message_sizes(self, mock_start_collecting, mock_stop_collecting, mock_get_metrics): + input_data = { + "algorithms": ["kyber512"], + "iterationsCount": [10], + "experimentName": "name", + "description": "name", + "messageSizes": [0] + } + response = self.client.post(PATH, + data=json.dumps(input_data), + content_type=CONTENT_TYPE) + self.assertEqual(response.status_code, 400) + response_json = json.loads(response.data) + self.assertEqual(response_json["error"], INVALID_DATA_PROVIDED) + self.assertEqual(response_json["message"], "The message size should be greater than 0") + def test_analyze_with_invalid_algorithm(self, mock_start_collecting, mock_stop_collecting, mock_get_metrics): input_data = { "algorithms":["invalid_algorithm"], "iterationsCount": [1000], "experimentName": "name", - "description": "name" + "description": "name", + "messageSizes": [100] } response = self.client.post(PATH, data=json.dumps(input_data), @@ -137,14 +157,15 @@ def test_analyze_with_invalid_body(self, mock_start_collecting, mock_stop_collec self.assertEqual(response.status_code, 400) response_json = json.loads(response.data) self.assertEqual(response_json["error"], INVALID_DATA_PROVIDED) - self.assertEqual(response_json["message"], "Missing properties, required properties: algorithms, iterationsCount, experimentName, description") + self.assertEqual(response_json["message"], "Missing properties, required properties: algorithms, iterationsCount, experimentName, description, messageSizes") def test_analyze_with_curl_failure(self, mock_start_collecting, mock_stop_collecting, mock_get_metrics): input_data = { "algorithms":["kyber512"], "iterationsCount": [1000], "experimentName": "name", - "description": "name" + "description": "name", + "messageSizes": [100] } # Mock the requests.post call with patch(POST_REQUEST) as mock_post: @@ -160,13 +181,13 @@ def test_analyze_with_curl_failure(self, mock_start_collecting, mock_stop_collec self.assertEqual(actual_test_run[0].status_message, '{"result": "failed"}') - def test_analyze_with_missing_env_info(self, mock_start_collecting, mock_stop_collecting, mock_get_metrics): input_data = { - "algorithms":["kyber512"], + "algorithms": ["kyber512"], "iterationsCount": [1000], "experimentName": "name", - "description": "name" + "description": "name", + "messageSizes": [100] } self.app.database_manager.get_latest.return_value = None response = self.client.post(PATH, @@ -185,7 +206,8 @@ def test_analyze_with_423(self, mock_start_collecting, mock_stop_collecting, moc "algorithms":["kyber512"], "iterationsCount": [1000], "experimentName": "name", - "description": "name" + "description": "name", + "messageSizes": [100] } analyze_api.process_is_running = True # Mock the requests.post call @@ -203,7 +225,8 @@ def test_analyze_sleep_between_tests(self, mock_start_collecting, mock_stop_coll "algorithms":["kyber512","frodo640aes"], "iterationsCount": [1000], "experimentName": "name", - "description": "name" + "description": "name", + "messageSizes": [100] } with patch(GET_REQUEST) as mock_get: mock_get.return_value.status_code = 200 diff --git a/curl/package-lock.json b/curl/package-lock.json index 776a55a5..f591d6e0 100644 --- a/curl/package-lock.json +++ b/curl/package-lock.json @@ -9,13 +9,14 @@ "version": "0.0.1", "license": "UNLICENSED", "dependencies": { - "@nestjs/common": "^9.0.0", - "@nestjs/config": "3.0.0", + "@nestjs/common": "^9.4.3", + "@nestjs/config": "^3.0.0", "@nestjs/core": "^9.0.0", "@nestjs/mapped-types": "*", "@nestjs/platform-express": "^9.0.0", "class-transformer": "^0.5.1", "class-validator": "^0.14.0", + "crypto": "^1.0.1", "reflect-metadata": "^0.1.13", "rxjs": "^7.2.0" }, @@ -25,7 +26,7 @@ "@nestjs/testing": "^9.4.3", "@types/express": "^4.17.13", "@types/jest": "29.2.4", - "@types/node": "18.11.18", + "@types/node": "^18.11.18", "@types/supertest": "^2.0.11", "@typescript-eslint/eslint-plugin": "^5.0.0", "@typescript-eslint/parser": "^5.0.0", @@ -1544,13 +1545,13 @@ } }, "node_modules/@nestjs/common": { - "version": "9.3.12", - "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-9.3.12.tgz", - "integrity": "sha512-NtrUG2VgCbhmZEO1yRt/Utq16uFRV+xeHAOtdYIsfHGG0ssAV2lVLlvFFAQYh0SQ+KuYY1Gsxd3GK2JFoJCNqQ==", + "version": "9.4.3", + "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-9.4.3.tgz", + "integrity": "sha512-Gd6D4IaYj01o14Bwv81ukidn4w3bPHCblMUq+SmUmWLyosK+XQmInCS09SbDDZyL8jy86PngtBLTdhJ2bXSUig==", "dependencies": { "iterare": "1.2.1", - "tslib": "2.5.0", - "uid": "2.0.1" + "tslib": "2.5.3", + "uid": "2.0.2" }, "funding": { "type": "opencollective", @@ -1575,6 +1576,22 @@ } } }, + "node_modules/@nestjs/common/node_modules/tslib": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.3.tgz", + "integrity": "sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w==" + }, + "node_modules/@nestjs/common/node_modules/uid": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/uid/-/uid-2.0.2.tgz", + "integrity": "sha512-u3xV3X7uzvi5b1MncmZo3i2Aw222Zk1keqLA1YkHldREkAhAqi65wuPfe7lHx8H/Wzy+8CE7S7uS3jekIM5s8g==", + "dependencies": { + "@lukeed/csprng": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/@nestjs/config": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@nestjs/config/-/config-3.0.0.tgz", @@ -3484,6 +3501,12 @@ "node": ">= 8" } }, + "node_modules/crypto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/crypto/-/crypto-1.0.1.tgz", + "integrity": "sha512-VxBKmeNcqQdiUQUW2Tzq0t377b54N2bMtXO/qiLa+6eRRmmC4qT3D4OnTGoT/U6O9aklQ/jTwbOtRMTTY8G0Ig==", + "deprecated": "This package is no longer supported. It's now a built-in Node module. If you've depended on crypto, you should switch to the one that's built-in." + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", diff --git a/curl/package.json b/curl/package.json index 3188ee74..d3803f75 100644 --- a/curl/package.json +++ b/curl/package.json @@ -19,13 +19,14 @@ "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand" }, "dependencies": { - "@nestjs/common": "^9.0.0", - "@nestjs/config": "3.0.0", + "@nestjs/common": "^9.4.3", + "@nestjs/config": "^3.0.0", "@nestjs/core": "^9.0.0", "@nestjs/mapped-types": "*", "@nestjs/platform-express": "^9.0.0", "class-transformer": "^0.5.1", "class-validator": "^0.14.0", + "crypto": "^1.0.1", "reflect-metadata": "^0.1.13", "rxjs": "^7.2.0" }, @@ -35,7 +36,7 @@ "@nestjs/testing": "^9.4.3", "@types/express": "^4.17.13", "@types/jest": "29.2.4", - "@types/node": "18.11.18", + "@types/node": "^18.11.18", "@types/supertest": "^2.0.11", "@typescript-eslint/eslint-plugin": "^5.0.0", "@typescript-eslint/parser": "^5.0.0", diff --git a/curl/scripts/run-curl-loop.sh b/curl/scripts/run-curl-loop.sh index 8cf367cf..52b590e7 100755 --- a/curl/scripts/run-curl-loop.sh +++ b/curl/scripts/run-curl-loop.sh @@ -1,10 +1,11 @@ #!/bin/bash -# This script expects four arguments +# This script expects five arguments nginx_host="$1" nginx_port="$2" iteration_count="$3" algorithm="$4" +payload="$5" num_processes=$(($(getconf _NPROCESSORS_ONLN) * 2)) -seq ${iteration_count} | xargs -P $num_processes -n 1 -I % curl https://${nginx_host}:${nginx_port} -k --curves ${algorithm} -so /dev/null \ No newline at end of file +seq ${iteration_count} | xargs -P $num_processes -n 1 -I % curl https://${nginx_host}:${nginx_port} -k --curves ${algorithm} -XPOST -d "$payload" -H "Content-Type: text/plain" -o /dev/null \ No newline at end of file diff --git a/curl/src/curl/curl.controller.spec.ts b/curl/src/curl/curl.controller.spec.ts index 308f8a45..3f5b4f41 100644 --- a/curl/src/curl/curl.controller.spec.ts +++ b/curl/src/curl/curl.controller.spec.ts @@ -29,6 +29,7 @@ describe('CurlController', () => { const curlRequest: CurlRequest = { algorithm: 'kyber512', iterationsCount: 500, + messageSize: 10 }; const runSpy = jest.spyOn(curlService, 'run'); await curlController.create(curlRequest); @@ -38,6 +39,7 @@ describe('CurlController', () => { const curlRequest: CurlRequest = { algorithm: 'kyber512', iterationsCount: 500, + messageSize: 10 }; const expectedResult = undefined; jest.spyOn(curlService, 'run').mockResolvedValue(expectedResult); @@ -48,6 +50,7 @@ describe('CurlController', () => { const curlRequest: CurlRequest = { algorithm: 'kyber512', iterationsCount: 500, + messageSize: 10 }; const error = new HttpException('Exception', 409); jest.spyOn(curlService, 'run').mockRejectedValue(error); diff --git a/curl/src/curl/curl.service.spec.ts b/curl/src/curl/curl.service.spec.ts index 7c18540e..af771882 100644 --- a/curl/src/curl/curl.service.spec.ts +++ b/curl/src/curl/curl.service.spec.ts @@ -5,6 +5,7 @@ import { ConfigModule, ConfigService } from '@nestjs/config'; import { HttpException, HttpStatus } from '@nestjs/common'; import * as shellJS from 'shelljs'; +import {MessageGenerator} from "../utils/message.generator"; jest.mock('shelljs', () => ({ exec: jest.fn(), })); @@ -49,18 +50,20 @@ describe('CurlService', () => { const curlRequest: CurlRequest = { algorithm: 'kyber512', iterationsCount: 1000, + messageSize: 10 }; const validateSpy = jest.spyOn(curlService, 'validate'); const runCurlsSpy = jest.spyOn(curlService, 'runCurls').mockResolvedValue(undefined); await curlService.run(curlRequest); expect(validateSpy).toHaveBeenCalledWith(curlRequest); - expect(runCurlsSpy).toHaveBeenCalledWith(curlRequest.iterationsCount, curlRequest.algorithm); + expect(runCurlsSpy).toHaveBeenCalledWith(curlRequest.iterationsCount, curlRequest.algorithm, expect.any(String)); }); it('should throw an HttpException with status INTERNAL_SERVER_ERROR when runCurls throws an error', async () => { const curlRequest: CurlRequest = { algorithm: 'kyber512', iterationsCount: 1000, + messageSize: 10 }; jest.spyOn(curlService, 'runCurls').mockRejectedValue(new HttpException('runCurls error', 500)); await expect(curlService.run(curlRequest)).rejects.toThrow(HttpException); @@ -74,6 +77,7 @@ describe('CurlService', () => { const curlRequest: CurlRequest = { algorithm: 'unsupported_algorithm', iterationsCount: 1000, + messageSize: 10 }; jest.spyOn(configService, 'get').mockReturnValue(['test_algorithm']); await expect(curlService.run(curlRequest)).rejects.toThrow(HttpException); @@ -86,6 +90,7 @@ describe('CurlService', () => { const curlRequest: CurlRequest = { algorithm: 'kyber512', iterationsCount: 1000, + messageSize: 10 }; jest.spyOn(configService, 'get').mockReturnValue(['test_algorithm']); jest.spyOn(curlService, 'runCurls').mockImplementation(() => { @@ -108,6 +113,7 @@ describe('CurlService', () => { const curlRequest: CurlRequest = { algorithm: 'kyber512', iterationsCount: 1000, + messageSize: 10 }; jest.spyOn(configService, 'get').mockReturnValue(['test_algorithm']); expect(() => curlService['validate'](curlRequest)).not.toThrow(); @@ -118,9 +124,10 @@ describe('CurlService', () => { it('should call execAsync with the correct command', async () => { const iterationsCount = 1000; const algorithm = 'kyber512'; + const message = MessageGenerator.generate(8); const execAsyncSpy = jest.spyOn(curlService, 'execAsync').mockResolvedValue(undefined); - await curlService['runCurls'](iterationsCount, algorithm); - const expectedCommand = curlService['format'](`./scripts/run-curl-loop.sh ${configService.get('nginx.host')} ${configService.get('nginx.port')} ${iterationsCount} ${algorithm}`); + await curlService['runCurls'](iterationsCount, algorithm, message); + const expectedCommand = curlService['format'](`./scripts/run-curl-loop.sh ${configService.get('nginx.host')} ${configService.get('nginx.port')} ${iterationsCount} ${algorithm} ${message}`); expect(execAsyncSpy).toHaveBeenCalledWith(expectedCommand); }); // Add more test cases for error handling in runCurls. diff --git a/curl/src/curl/curl.service.ts b/curl/src/curl/curl.service.ts index 35267fc3..0fe1bdfe 100644 --- a/curl/src/curl/curl.service.ts +++ b/curl/src/curl/curl.service.ts @@ -2,7 +2,8 @@ import { Injectable } from '@nestjs/common'; import * as shellJS from 'shelljs'; import { CurlRequest } from '../dto/curl-request.dto'; import { ConfigService } from '@nestjs/config'; -import { HttpException, HttpStatus, NotFoundException } from '@nestjs/common'; +import { HttpException, HttpStatus } from '@nestjs/common'; +import {MessageGenerator} from '../utils/message.generator'; @Injectable() export class CurlService { @@ -19,8 +20,9 @@ export class CurlService { async run(curlRequest: CurlRequest): Promise { this.validate(curlRequest); - try { - await this.runCurls(curlRequest.iterationsCount, curlRequest.algorithm); + try { + const message = MessageGenerator.generate(curlRequest.messageSize); + await this.runCurls(curlRequest.iterationsCount, curlRequest.algorithm, message); } catch (err) { this.processIsRunning = false; console.error('[CurlService:run] Error occurred: ', err); @@ -39,8 +41,8 @@ export class CurlService { } } - private async runCurls(iterationsCount: number, algorithm: String) { - const curlCommand = this.format(`${this.CURL_SCRIPT_PATH} ${this.configService.get('nginx.host')} ${this.configService.get('nginx.port')} ${iterationsCount} ${algorithm}`); + private async runCurls(iterationsCount: number, algorithm: String, message: String) { + const curlCommand = this.format(`${this.CURL_SCRIPT_PATH} ${this.configService.get('nginx.host')} ${this.configService.get('nginx.port')} ${iterationsCount} ${algorithm} ${message}`); this.processIsRunning = true; await this.execAsync(curlCommand); console.log('[CurlService:run] Finished taking all curl samples'); diff --git a/curl/src/dto/curl-request.dto.spec.ts b/curl/src/dto/curl-request.dto.spec.ts index ddfffb6a..4d110e00 100644 --- a/curl/src/dto/curl-request.dto.spec.ts +++ b/curl/src/dto/curl-request.dto.spec.ts @@ -1,10 +1,12 @@ import { CurlRequest } from './curl-request.dto'; import { validate } from 'class-validator'; + describe('CurlRequest', () => { it('should pass validation when all properties are valid', async () => { const curlRequest = new CurlRequest(); curlRequest.algorithm = 'ExampleAlgorithm'; curlRequest.iterationsCount = 1000; + curlRequest.messageSize = 10; const validationErrors = await validate(curlRequest); expect(validationErrors).toHaveLength(0); }); @@ -13,6 +15,7 @@ describe('CurlRequest', () => { const curlRequest = new CurlRequest(); curlRequest.algorithm = ''; curlRequest.iterationsCount = 1000; + curlRequest.messageSize = 10; const validationErrors = await validate(curlRequest); expect(validationErrors).toHaveLength(1); expect(validationErrors[0].constraints.isNotEmpty).toBeDefined(); @@ -21,18 +24,40 @@ describe('CurlRequest', () => { it('should fail validation when iterationsCount is not a number', async () => { const curlRequest = new CurlRequest(); curlRequest.algorithm = 'ExampleAlgorithm'; - (curlRequest.iterationsCount as any) = 'not a number'; + curlRequest.iterationsCount = 'not a number' as any; + curlRequest.messageSize = 10; const validationErrors = await validate(curlRequest); expect(validationErrors).toHaveLength(1); expect(validationErrors[0].constraints.isNumber).toBeDefined(); }); - + it('should fail validation when iterationsCount is not from the list', async () => { const curlRequest = new CurlRequest(); curlRequest.algorithm = 'ExampleAlgorithm'; curlRequest.iterationsCount = -3; + curlRequest.messageSize = 10; + const validationErrors = await validate(curlRequest); + expect(validationErrors).toHaveLength(1); + expect(validationErrors[0].constraints).toBeDefined(); + }); + + it('should fail validation when messageSize is not a number', async () => { + const curlRequest = new CurlRequest(); + curlRequest.algorithm = 'ExampleAlgorithm'; + curlRequest.iterationsCount = 1000; + curlRequest.messageSize = 'not a number' as any; + const validationErrors = await validate(curlRequest); + expect(validationErrors).toHaveLength(1); + expect(validationErrors[0].constraints.isNumber).toBeDefined(); + }); + + it('should fail validation when messageSize is not from the list', async () => { + const curlRequest = new CurlRequest(); + curlRequest.algorithm = 'ExampleAlgorithm'; + curlRequest.iterationsCount = 1000; + curlRequest.messageSize = -3; const validationErrors = await validate(curlRequest); expect(validationErrors).toHaveLength(1); expect(validationErrors[0].constraints).toBeDefined(); }); -}); \ No newline at end of file +}); diff --git a/curl/src/dto/curl-request.dto.ts b/curl/src/dto/curl-request.dto.ts index 1b6fbae3..269b4287 100644 --- a/curl/src/dto/curl-request.dto.ts +++ b/curl/src/dto/curl-request.dto.ts @@ -1,4 +1,4 @@ -import { IsIn, IsNotEmpty, IsNumber, Min } from 'class-validator'; +import { IsNotEmpty, IsNumber, Min } from 'class-validator'; export class CurlRequest { @@ -9,4 +9,9 @@ export class CurlRequest { @IsNumber() @Min(1) iterationsCount: number; + + @IsNotEmpty() + @IsNumber() + @Min(1) + messageSize: number; } diff --git a/curl/src/utils/message.generator.spec.ts b/curl/src/utils/message.generator.spec.ts new file mode 100644 index 00000000..084bd7c1 --- /dev/null +++ b/curl/src/utils/message.generator.spec.ts @@ -0,0 +1,28 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { MessageGenerator } from './message.generator'; + +describe('MessageGenerator', () => { + let service: MessageGenerator; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [MessageGenerator], + }).compile(); + + service = module.get(MessageGenerator); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); + + describe('generate', () => { + it('should generate a message with the specified size', () => { + const sizeInBytes = 10; + const generatedMessage = MessageGenerator.generate(sizeInBytes); + + // The length of the generated message in hex should be double the size in bytes + expect(generatedMessage.length).toBe(sizeInBytes * 2); + }); + }); +}); diff --git a/curl/src/utils/message.generator.ts b/curl/src/utils/message.generator.ts new file mode 100644 index 00000000..dab42852 --- /dev/null +++ b/curl/src/utils/message.generator.ts @@ -0,0 +1,10 @@ +import { Injectable } from '@nestjs/common'; +import * as crypto from 'crypto'; + +@Injectable() +export class MessageGenerator { + static generate(sizeInBytes: number) { + const randomBytes = crypto.randomBytes(sizeInBytes); + return randomBytes.toString('hex'); + } +} diff --git a/run/docker/docker-compose.yml b/run/docker/docker-compose.yml index 51cbd20e..9c1ce1f1 100644 --- a/run/docker/docker-compose.yml +++ b/run/docker/docker-compose.yml @@ -23,7 +23,7 @@ services: - DEFAULT_GROUPS=prime256v1:secp384r1:frodo640aes:frodo640shake:frodo976aes:frodo976shake:frodo1344aes:frodo1344shake:kyber512:p256_kyber512:kyber768:p384_kyber768:x25519_kyber768:kyber1024:bikel1:bikel3:bikel5:hqc128:hqc192:hqc256 curl: - image: qujata/curl:1.1.0 + image: qujata/curl:1.2.0 container_name: qujata-curl ports: - 3010:3010 @@ -31,7 +31,7 @@ services: - DEFAULT_GROUPS=prime256v1:secp384r1:frodo640aes:frodo640shake:frodo976aes:frodo976shake:frodo1344aes:frodo1344shake:kyber512:p256_kyber512:kyber768:p384_kyber768:x25519_kyber768:kyber1024:bikel1:bikel3:bikel5:hqc128:hqc192:hqc256 api: - image: qujata/api:1.1.0 + image: qujata/api:1.2.0 container_name: qujata-api ports: - 3020:3020 From e76120b428da5ff4305b1638ea80304837761a37 Mon Sep 17 00:00:00 2001 From: kr172t Date: Sun, 14 Jan 2024 13:27:38 +0200 Subject: [PATCH 02/35] change api+curl versions in charts --- api/main.py | 2 +- api/src/services/analyze_service.py | 8 +++++--- api/src/services/metrics_service.py | 2 -- curl/src/dto/curl-request.dto.ts | 2 +- curl/tsconfig.json | 4 +++- run/kubernetes/charts/api/values.yaml | 2 +- run/kubernetes/charts/curl/values.yaml | 2 +- 7 files changed, 12 insertions(+), 10 deletions(-) diff --git a/api/main.py b/api/main.py index e4500af3..5a79c5de 100644 --- a/api/main.py +++ b/api/main.py @@ -5,7 +5,7 @@ from src.api.tests_api import api as tests_blueprint import src.services.k8s_service as k8s_service import src.services.cadvisor_service as cadvisor_service -from src.enums.environemnt import Environment +from src.enums.environment import Environment from config.settings import load_config from flask_cors import CORS from src.utils.database_manager import DatabaseManager diff --git a/api/src/services/analyze_service.py b/api/src/services/analyze_service.py index 0ff556e6..46aeaadf 100644 --- a/api/src/services/analyze_service.py +++ b/api/src/services/analyze_service.py @@ -20,7 +20,9 @@ def analyze(data): start_time = int(datetime.timestamp(datetime.now() - timedelta(seconds=60)) * 1000) iterations_count = data['iterationsCount'] algorithms = data['algorithms'] - message_sizes = data['messageSizes'] + message_sizes = [0] + if 'messageSizes' in data: + message_sizes = data['messageSizes'] first_run = True for algorithm in algorithms: for iterations in iterations_count: @@ -50,12 +52,12 @@ def __create_test_run(algorithm, iterations, message_size, test_suite_id): test_suites_service.create_test_run(start_time, end_time, algorithm, iterations, message_size, test_suite_id, status, status_message, *metrics_service.get_metrics()) -def __run(algorithm, iterations, message_sizes): +def __run(algorithm, iterations, message_size): logging.debug('Running test for algorithm: %s ', algorithm) payload = { 'algorithm': algorithm, 'iterationsCount': iterations, - 'messageSizes': message_sizes + 'messageSize': message_size } headers = { 'Content-Type': 'application/json' } response = requests.post(current_app.configurations.curl_url + "/curl", headers=headers, json=payload, timeout=int(current_app.configurations.request_timeout)) diff --git a/api/src/services/metrics_service.py b/api/src/services/metrics_service.py index a53f51a1..37f0a8d8 100644 --- a/api/src/services/metrics_service.py +++ b/api/src/services/metrics_service.py @@ -1,5 +1,3 @@ -from flask import current_app -from src.models.test_run_metric import TestRunMetric from src.utils.metrics_collector import MetricsCollector import logging diff --git a/curl/src/dto/curl-request.dto.ts b/curl/src/dto/curl-request.dto.ts index 2685753c..f30b7ed8 100644 --- a/curl/src/dto/curl-request.dto.ts +++ b/curl/src/dto/curl-request.dto.ts @@ -12,6 +12,6 @@ export class CurlRequest { @IsNotEmpty() @IsNumber() - @Min(1) + @Min(0) messageSize: number; } diff --git a/curl/tsconfig.json b/curl/tsconfig.json index adb614ca..fd6d15a5 100644 --- a/curl/tsconfig.json +++ b/curl/tsconfig.json @@ -17,5 +17,7 @@ "strictBindCallApply": false, "forceConsistentCasingInFileNames": false, "noFallthroughCasesInSwitch": false - } + }, + "include": ["src/**/*.ts"], + "exclude": ["node_modules"] } diff --git a/run/kubernetes/charts/api/values.yaml b/run/kubernetes/charts/api/values.yaml index cfe564e8..8f8fab8e 100644 --- a/run/kubernetes/charts/api/values.yaml +++ b/run/kubernetes/charts/api/values.yaml @@ -6,7 +6,7 @@ replicaCount: 1 image: repository: qujata/api - tag: "1.1.0" + tag: "1.2.0" pullPolicy: Always imagePullSecrets: [] diff --git a/run/kubernetes/charts/curl/values.yaml b/run/kubernetes/charts/curl/values.yaml index 5d71536d..8347c9a0 100644 --- a/run/kubernetes/charts/curl/values.yaml +++ b/run/kubernetes/charts/curl/values.yaml @@ -6,7 +6,7 @@ replicaCount: 1 image: repository: qujata/curl - tag: "1.1.0" + tag: "1.2.0" pullPolicy: Always From e92e1431c82afc23d8d9cae94f3f5f51d5662bd3 Mon Sep 17 00:00:00 2001 From: kr172t Date: Mon, 15 Jan 2024 10:38:10 +0200 Subject: [PATCH 03/35] fix tests --- api/tests/test_analyze_api.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/tests/test_analyze_api.py b/api/tests/test_analyze_api.py index 97c9f790..3a9b325d 100644 --- a/api/tests/test_analyze_api.py +++ b/api/tests/test_analyze_api.py @@ -117,7 +117,7 @@ def test_analyze_with_invalid_message_sizes(self, mock_start_collecting, mock_st "iterationsCount": [10], "experimentName": "name", "description": "name", - "messageSizes": [0] + "messageSizes": [-1] } response = self.client.post(PATH, data=json.dumps(input_data), @@ -125,7 +125,7 @@ def test_analyze_with_invalid_message_sizes(self, mock_start_collecting, mock_st self.assertEqual(response.status_code, 400) response_json = json.loads(response.data) self.assertEqual(response_json["error"], INVALID_DATA_PROVIDED) - self.assertEqual(response_json["message"], "The message size should be greater than 0") + self.assertEqual(response_json["message"], "The message size should be greater than -1") def test_analyze_with_invalid_algorithm(self, mock_start_collecting, mock_stop_collecting, mock_get_metrics): From 4e207c277cbe62604d3b245b0372e8ae68096809 Mon Sep 17 00:00:00 2001 From: Ohad Koren Date: Tue, 16 Jan 2024 09:58:56 +0200 Subject: [PATCH 04/35] Initial all experiments --- portal/mock-server/src/all-experiments.json | 19 +++++++++ portal/mock-server/src/router.ts | 8 ++++ .../all-experiments/Experiments.module.scss | 26 ++++++++++++ .../all-experiments/Experiments.test.tsx | 7 ++++ .../all-experiments/Experiments.tsx | 34 +++++++++++++++ .../components/all-experiments/hooks/index.ts | 1 + .../hooks/useExperimentsData.test.ts | 35 ++++++++++++++++ .../hooks/useExperimentsData.ts | 42 +++++++++++++++++++ .../navigation-tab/NavigationTab.module.scss | 2 +- .../shared/constants/navigation-tabs.const.ts | 9 ++-- portal/src/routes/index.jsx | 36 ++++++++-------- 11 files changed, 195 insertions(+), 24 deletions(-) create mode 100644 portal/mock-server/src/all-experiments.json create mode 100644 portal/src/app/components/all-experiments/Experiments.module.scss create mode 100644 portal/src/app/components/all-experiments/Experiments.test.tsx create mode 100644 portal/src/app/components/all-experiments/Experiments.tsx create mode 100644 portal/src/app/components/all-experiments/hooks/index.ts create mode 100644 portal/src/app/components/all-experiments/hooks/useExperimentsData.test.ts create mode 100644 portal/src/app/components/all-experiments/hooks/useExperimentsData.ts diff --git a/portal/mock-server/src/all-experiments.json b/portal/mock-server/src/all-experiments.json new file mode 100644 index 00000000..d01b785b --- /dev/null +++ b/portal/mock-server/src/all-experiments.json @@ -0,0 +1,19 @@ +{ + "experiments": [ + { + "id": 1, + "name": "Experiment 1", + "description": "This is the first experiment" + }, + { + "id": 2, + "name": "Experiment 2", + "description": "This is the second experiment" + }, + { + "id": 3, + "name": "Experiment 3", + "description": "This is the third experiment" + } + ] +} \ No newline at end of file diff --git a/portal/mock-server/src/router.ts b/portal/mock-server/src/router.ts index b6c112c6..15c0a10a 100644 --- a/portal/mock-server/src/router.ts +++ b/portal/mock-server/src/router.ts @@ -37,6 +37,14 @@ router.get('/qujata-api/test_suites/:testSuiteId', async (req: Request, res: Res }, 1500); }); +router.get('/qujata-api/experiments', async (req: Request, res: Response) => { + console.log(`-${req.method} ${req.url}`); + const data = (await import('./all-experiments.json')).default; + setTimeout(() => { + res.json(data); + }, 1500); +}); + router.put('/qujata-api/test_suites/:testSuiteId', async (req: Request, res: Response) => { console.log(`-${req.method} ${req.url}`); setTimeout(() => { diff --git a/portal/src/app/components/all-experiments/Experiments.module.scss b/portal/src/app/components/all-experiments/Experiments.module.scss new file mode 100644 index 00000000..3f713aa2 --- /dev/null +++ b/portal/src/app/components/all-experiments/Experiments.module.scss @@ -0,0 +1,26 @@ +@import "src/styles/variables-keys"; + +.app_wrapper { + padding-block-start: 20px; + padding-inline-start: 80px; + padding-block-end: 40px; +} + +.spinner_wrapper { + position: sticky; + inset-block-start: 50%; + inset-inline-start: 50%; + text-align: center; +} + +.spinner_overlay { + inline-size: 100%; + block-size: 100%; + position: absolute; + background-color: #fff; + opacity: 0.6; + inset-block-start: 0; + inset-inline-start: 0; + z-index: 4; +} + diff --git a/portal/src/app/components/all-experiments/Experiments.test.tsx b/portal/src/app/components/all-experiments/Experiments.test.tsx new file mode 100644 index 00000000..a63b4138 --- /dev/null +++ b/portal/src/app/components/all-experiments/Experiments.test.tsx @@ -0,0 +1,7 @@ +import { Experiments } from './Experiments'; + +describe('Experiments', () => { + test('should render Experiments', async () => { + expect(true).toBe(true); + }); +}); diff --git a/portal/src/app/components/all-experiments/Experiments.tsx b/portal/src/app/components/all-experiments/Experiments.tsx new file mode 100644 index 00000000..f1003ad5 --- /dev/null +++ b/portal/src/app/components/all-experiments/Experiments.tsx @@ -0,0 +1,34 @@ +import { useEffect } from 'react'; +import styles from './Experiments.module.scss'; +import { useNavigate } from "react-router-dom"; +import { IUseExperimentsData, useExperimentsData } from './hooks'; +import { FetchDataStatus } from '../../shared/hooks/useFetch'; +import { Spinner, SpinnerSize } from '../../shared/components/att-spinner'; + +export const Experiments: React.FC = () => { + const { experiments, status }: IUseExperimentsData = useExperimentsData(); + // const navigate = useNavigate(); + + // useEffect(() => { + // if (status === FetchDataStatus.Success && experiments) { + // navigate(`qujata-api/experiments`); + // } + // }, [navigate, status, experiments]); + + return ( +
+ {status === FetchDataStatus.Fetching ? renderSpinner() : {'Hello World!!!!'}} +
+ ); + +} + +function renderSpinner() { + return ( +
+
+ +
+
+ ); +} diff --git a/portal/src/app/components/all-experiments/hooks/index.ts b/portal/src/app/components/all-experiments/hooks/index.ts new file mode 100644 index 00000000..e1607221 --- /dev/null +++ b/portal/src/app/components/all-experiments/hooks/index.ts @@ -0,0 +1 @@ +export * from './useExperimentsData'; diff --git a/portal/src/app/components/all-experiments/hooks/useExperimentsData.test.ts b/portal/src/app/components/all-experiments/hooks/useExperimentsData.test.ts new file mode 100644 index 00000000..439b6f65 --- /dev/null +++ b/portal/src/app/components/all-experiments/hooks/useExperimentsData.test.ts @@ -0,0 +1,35 @@ +import { renderHook } from '@testing-library/react'; +import { useFetch } from '../../../shared/hooks/useFetch'; +import { Experiment, useExperimentsData } from './useExperimentsData'; + +jest.mock('../../../shared/hooks/useFetch', () => ({ + useFetch: jest.fn(), +})); + +describe('useExperimentsData', () => { + test('Should be in Success mode', () => { + const allExperimentsMockData = { + experiments: [ + { + id: '1', + name: 'Experiment 1', + description: 'Experiment 1 description', + }, + { + id: '2', + name: 'Experiment 2', + description: 'Experiment 2 description', + }, + ] + }; + + (useFetch as jest.Mock).mockReturnValue({ + get: jest.fn(), + data: allExperimentsMockData, + cancelRequest: jest.fn(), + }); + + const { result } = renderHook(() => useExperimentsData()); + expect(result.current.experiments.length).toEqual(allExperimentsMockData.experiments.length); + }); +}); \ No newline at end of file diff --git a/portal/src/app/components/all-experiments/hooks/useExperimentsData.ts b/portal/src/app/components/all-experiments/hooks/useExperimentsData.ts new file mode 100644 index 00000000..0f78fcc2 --- /dev/null +++ b/portal/src/app/components/all-experiments/hooks/useExperimentsData.ts @@ -0,0 +1,42 @@ +import { FetchDataStatus, IHttp, useFetch } from "../../../shared/hooks/useFetch"; +import { useEffect, useState } from "react"; +import { APIS } from "../../../apis"; +import { useFetchSpinner } from "../../../shared/hooks/useFetchSpinner"; +import { useErrorMessage } from "../../../hooks/useErrorMessage"; + +export interface IUseExperimentsData { + experiments: Experiment[]; + status: FetchDataStatus; +} + +export interface Experiment { + id: string; + name: string; + description: string; +} + +export function useExperimentsData(): IUseExperimentsData { + const [allExperiments, setAllExperiments] = useState([]); + const { get, data, status, cancelRequest, error }: IHttp = useFetch({ url: APIS.allExperiments }); + + useFetchSpinner(status); + useErrorMessage(error); + useEffect(() => { + get(); + return cancelRequest; + }, [get, cancelRequest]); + + + useEffect(() => { + if (status === FetchDataStatus.Success && data) { + const allExperiments: Experiment[] = data.experiments.map((experiment: Experiment) => ({ + id: experiment.id, + name: experiment.name, + description: experiment.description + })); + setAllExperiments(allExperiments); + } + }, [data, status]); + + return { experiments: allExperiments, status }; +} diff --git a/portal/src/app/shared/components/navigation-tab/NavigationTab.module.scss b/portal/src/app/shared/components/navigation-tab/NavigationTab.module.scss index 9e633f09..02d548dc 100644 --- a/portal/src/app/shared/components/navigation-tab/NavigationTab.module.scss +++ b/portal/src/app/shared/components/navigation-tab/NavigationTab.module.scss @@ -7,7 +7,7 @@ .tab { color: var($primaryWhite); - margin-inline-end: 20px; + margin-inline-end: 30px; text-decoration: none; } diff --git a/portal/src/app/shared/constants/navigation-tabs.const.ts b/portal/src/app/shared/constants/navigation-tabs.const.ts index 12ff222a..e697af6d 100644 --- a/portal/src/app/shared/constants/navigation-tabs.const.ts +++ b/portal/src/app/shared/constants/navigation-tabs.const.ts @@ -5,9 +5,8 @@ export const tabs = [ link: '/qujata', title: SHARED_EN.NAVIGATION_TABS.HOME, }, - // { - // link: '/all-experiments', - // title: SHARED_EN.NAVIGATION_TABS.ALL_EXPERIMENTS, - // disabled: true, - // } + { + link: '/experiments', + title: SHARED_EN.NAVIGATION_TABS.ALL_EXPERIMENTS, + } ]; diff --git a/portal/src/routes/index.jsx b/portal/src/routes/index.jsx index a84f73d0..6f466369 100644 --- a/portal/src/routes/index.jsx +++ b/portal/src/routes/index.jsx @@ -2,26 +2,26 @@ import { createBrowserRouter } from 'react-router-dom'; import Root from './Root'; import { Home } from '../app/components/home/Home'; import { Experiment } from '../app/components/home/components/experiment/Experiment'; +import { Experiments } from '../app/components/all-experiments/Experiments'; -const isAllExperimentTabEnabled = false; export const router = createBrowserRouter([ { - path: '/qujata', - element: , - children: [ - { - path: '', - index: true, - element: , - }, - { - path: 'experiment/:testSuiteId', - element: , - }, - ...(isAllExperimentTabEnabled ? [{ - path: 'All-Experiments', - element:
All Experiments
, - }] : []), - ], + path: '/qujata', + element: , + children: [ + { + path: '', + index: true, + element: , + }, + { + path: 'experiment/:testSuiteId', + element: , + }, + { + path: 'experiments', + element: , + }, + ], }, ]); From 555a972049ae847d65ced0950899a0e1bc57fbd1 Mon Sep 17 00:00:00 2001 From: Ohad Koren Date: Tue, 16 Jan 2024 17:33:54 +0200 Subject: [PATCH 05/35] Experiments table with dummy duplicate --- portal/mock-server/src/all-experiments.json | 45 +++++++-- .../all-experiments/Experiments.module.scss | 22 ++++- .../all-experiments/Experiments.tsx | 91 ++++++++++++++++--- .../hooks/useExperimentsData.ts | 14 ++- .../all-experiments/translate/en.ts | 28 ++++++ .../ExperimentTable.module.scss | 8 +- .../experiment-table/ExperimentTable.tsx | 4 +- .../shared/components/table/Table.module.scss | 5 - .../src/app/shared/components/table/Table.tsx | 31 ++++--- .../shared/constants/navigation-tabs.const.ts | 2 +- portal/src/assets/images/duplicate.svg | 11 +++ 11 files changed, 211 insertions(+), 50 deletions(-) create mode 100644 portal/src/app/components/all-experiments/translate/en.ts create mode 100644 portal/src/assets/images/duplicate.svg diff --git a/portal/mock-server/src/all-experiments.json b/portal/mock-server/src/all-experiments.json index d01b785b..202f22a9 100644 --- a/portal/mock-server/src/all-experiments.json +++ b/portal/mock-server/src/all-experiments.json @@ -2,18 +2,51 @@ "experiments": [ { "id": 1, - "name": "Experiment 1", - "description": "This is the first experiment" + "experimentName": "Experiment 1", + "algorithms": ["kyber512", "hqc128"], + "iterations": [2000, 2500, 10000], + "messageSizes": [512, 1024, 2048], + "date": "2022-11-11" }, { "id": 2, - "name": "Experiment 2", - "description": "This is the second experiment" + "experimentName": "Experiment 2", + "algorithms": ["hqc128", "prime256v1"], + "iterations": [1500, 1700, 20000], + "messageSizes": [1024, 2048], + "date": "2023-02-09" }, { "id": 3, - "name": "Experiment 3", - "description": "This is the third experiment" + "experimentName": "Experiment 3", + "algorithms": ["secp384r1"], + "iterations": [100000], + "messageSizes": [512], + "date": "2024-01-01" + }, + { + "id": 4, + "experimentName": "Experiment 4", + "algorithms": ["kyber768", "kyber1024", "secp384r1"], + "iterations": [2000, 1500], + "messageSizes": [1024], + "date": "2020-10-07" + }, + { + "id": 5, + "experimentName": "Experiment 5", + "algorithms": ["hqc256"], + "iterations": [100, 500], + "messageSizes": [2048], + "date": "2021-11-12" + }, + { + "id": 6, + "experimentName": "Experiment 6", + "algorithms": ["hqc256"], + "iterations": [], + "messageSizes": [2048], + "date": "2021-11-12" } ] } \ No newline at end of file diff --git a/portal/src/app/components/all-experiments/Experiments.module.scss b/portal/src/app/components/all-experiments/Experiments.module.scss index 3f713aa2..3d4872e0 100644 --- a/portal/src/app/components/all-experiments/Experiments.module.scss +++ b/portal/src/app/components/all-experiments/Experiments.module.scss @@ -1,9 +1,25 @@ @import "src/styles/variables-keys"; -.app_wrapper { - padding-block-start: 20px; - padding-inline-start: 80px; +.experiments_wrapper { + padding-block-start: 40px; padding-block-end: 40px; + padding-inline-start: 80px; + padding-inline-end: 80px; +} + +.experiments_title { + font-size: 20px; + font-family: var($fontMedium); + margin-inline-start: 150px; +} + +.experiments_table { + margin-block-start: 60px; + margin-block-end: 60px; + + // .duplicate_icon { + // margin-inline-end: 20px; + // } } .spinner_wrapper { diff --git a/portal/src/app/components/all-experiments/Experiments.tsx b/portal/src/app/components/all-experiments/Experiments.tsx index f1003ad5..a8c414ee 100644 --- a/portal/src/app/components/all-experiments/Experiments.tsx +++ b/portal/src/app/components/all-experiments/Experiments.tsx @@ -1,26 +1,91 @@ -import { useEffect } from 'react'; +import { ReactNode, useCallback, useMemo } from 'react'; import styles from './Experiments.module.scss'; -import { useNavigate } from "react-router-dom"; -import { IUseExperimentsData, useExperimentsData } from './hooks'; +import { Experiment, IUseExperimentsData, useExperimentsData } from './hooks'; import { FetchDataStatus } from '../../shared/hooks/useFetch'; import { Spinner, SpinnerSize } from '../../shared/components/att-spinner'; +import { ALL_EXPERIMENTS_TABLE_EN } from './translate/en'; +import { CellContext } from '@tanstack/react-table'; +import { Table } from '../../shared/components/table'; +import DuplicateSvg from '../../../assets/images/duplicate.svg'; +import { Button, ButtonActionType, ButtonSize, ButtonStyleType } from '../../shared/components/att-button'; +import { useNavigate } from 'react-router-dom'; +import { isEmpty } from 'lodash'; + +const DuplicateAriaLabel: string = ALL_EXPERIMENTS_TABLE_EN.TABLE_COLUMNS.LINKS.DUPLICATE; export const Experiments: React.FC = () => { const { experiments, status }: IUseExperimentsData = useExperimentsData(); - // const navigate = useNavigate(); + const experimentsData = useMemo(() => (experiments ?? []), [experiments]); + const navigate = useNavigate(); + + const handleButtonClick = useCallback((row: Experiment) => { + console.log('Button clicked on row:', row); + // need to naviage with the entire row data, so we can duplicate it properly + navigate('/qujata'); + }, [navigate]); - // useEffect(() => { - // if (status === FetchDataStatus.Success && experiments) { - // navigate(`qujata-api/experiments`); - // } - // }, [navigate, status, experiments]); + const headers = useMemo(() => { + const columnDefs = [ + { + id: ALL_EXPERIMENTS_TABLE_EN.TABLE_COLUMNS.EXPERIMENT_NAME.ID, + name: ALL_EXPERIMENTS_TABLE_EN.TABLE_COLUMNS.EXPERIMENT_NAME.NAME, + accessor: (row: Experiment) => row.experimentName + }, + { + id: ALL_EXPERIMENTS_TABLE_EN.TABLE_COLUMNS.ALGORITHMS.ID, + name: ALL_EXPERIMENTS_TABLE_EN.TABLE_COLUMNS.ALGORITHMS.NAME, + accessor: (row: Experiment) => row.algorithms.join(', ') + }, + { + id: ALL_EXPERIMENTS_TABLE_EN.TABLE_COLUMNS.ITERATIONS.ID, + name: ALL_EXPERIMENTS_TABLE_EN.TABLE_COLUMNS.ITERATIONS.NAME, + accessor: (row: Experiment) => row.iterations.join(', ') + }, + { + id: ALL_EXPERIMENTS_TABLE_EN.TABLE_COLUMNS.MESSAGE_SIZE.ID, + name: ALL_EXPERIMENTS_TABLE_EN.TABLE_COLUMNS.MESSAGE_SIZE.NAME, + accessor: (row: Experiment) => row.messageSizes.join(', ') + }, + { + id: ALL_EXPERIMENTS_TABLE_EN.TABLE_COLUMNS.DATE.ID, + name: ALL_EXPERIMENTS_TABLE_EN.TABLE_COLUMNS.DATE.NAME, + accessor: (row: Experiment) => row.date + }, + { + id: ALL_EXPERIMENTS_TABLE_EN.TABLE_COLUMNS.LINKS.DUPLICATE, + accessor: () => null, + cell: (cellInfo: CellContext) => ( + + ) + }, + ]; + + return columnDefs.map(({ id, name, accessor, cell }) => ({ + id, + header: () => {name}, + accessor, + cell: cell || ((cellInfo: CellContext) => {cellInfo.getValue() as ReactNode}) + })); + }, [handleButtonClick]); return ( -
- {status === FetchDataStatus.Fetching ? renderSpinner() : {'Hello World!!!!'}} +
+ {status === FetchDataStatus.Fetching ? renderSpinner() : ( +
+ + + + )} - ); - + ); } function renderSpinner() { diff --git a/portal/src/app/components/all-experiments/hooks/useExperimentsData.ts b/portal/src/app/components/all-experiments/hooks/useExperimentsData.ts index 0f78fcc2..d82a0f83 100644 --- a/portal/src/app/components/all-experiments/hooks/useExperimentsData.ts +++ b/portal/src/app/components/all-experiments/hooks/useExperimentsData.ts @@ -11,8 +11,11 @@ export interface IUseExperimentsData { export interface Experiment { id: string; - name: string; - description: string; + experimentName: string; + algorithms: string[]; + iterations: number[]; + messageSizes: number[]; + date: string; } export function useExperimentsData(): IUseExperimentsData { @@ -31,8 +34,11 @@ export function useExperimentsData(): IUseExperimentsData { if (status === FetchDataStatus.Success && data) { const allExperiments: Experiment[] = data.experiments.map((experiment: Experiment) => ({ id: experiment.id, - name: experiment.name, - description: experiment.description + experimentName: experiment.experimentName, + algorithms: experiment.algorithms, + iterations: experiment.iterations, + messageSizes: experiment.messageSizes, + date: experiment.date, })); setAllExperiments(allExperiments); } diff --git a/portal/src/app/components/all-experiments/translate/en.ts b/portal/src/app/components/all-experiments/translate/en.ts new file mode 100644 index 00000000..cb118024 --- /dev/null +++ b/portal/src/app/components/all-experiments/translate/en.ts @@ -0,0 +1,28 @@ +export const ALL_EXPERIMENTS_TABLE_EN = { + TITLE: 'All Experiments', + TABLE_COLUMNS: { + EXPERIMENT_NAME: { + NAME: 'Experiment Name', + ID: 'experimentName' + }, + ALGORITHMS: { + NAME: 'Algorithms', + ID: 'algorithms' + }, + ITERATIONS: { + NAME: 'Iterations', + ID: 'iterations' + }, + MESSAGE_SIZE: { + NAME: 'Message Size (KB)', + ID: 'messageSizes' + }, + DATE: { + NAME: 'Date', + ID: 'date' + }, + LINKS: { + DUPLICATE: 'duplicate', + } + } +} diff --git a/portal/src/app/components/home/components/experiment/components/experiment-table/ExperimentTable.module.scss b/portal/src/app/components/home/components/experiment/components/experiment-table/ExperimentTable.module.scss index 19614b4a..ec5a99f8 100644 --- a/portal/src/app/components/home/components/experiment/components/experiment-table/ExperimentTable.module.scss +++ b/portal/src/app/components/home/components/experiment/components/experiment-table/ExperimentTable.module.scss @@ -1,5 +1,4 @@ @import "src/styles/variables-keys"; -@import "src/styles/z-index"; .experiment_table_wrapper { font-size: 14px; @@ -8,3 +7,10 @@ display: flex; flex-wrap: wrap; } + +.experiment_table { + th:first-child, + td:first-child { + inline-size: 80px; + } +} \ No newline at end of file diff --git a/portal/src/app/components/home/components/experiment/components/experiment-table/ExperimentTable.tsx b/portal/src/app/components/home/components/experiment/components/experiment-table/ExperimentTable.tsx index d468223b..c873d676 100644 --- a/portal/src/app/components/home/components/experiment/components/experiment-table/ExperimentTable.tsx +++ b/portal/src/app/components/home/components/experiment/components/experiment-table/ExperimentTable.tsx @@ -14,7 +14,7 @@ export interface ExperimentTableProps { export const ExperimentTable: React.FC = (props: ExperimentTableProps) => { const data = useMemo(() => (props.data ? props.data.testRuns : []), [props.data]); - const headers: TableColumn[] = useMemo(() => [ + const headers: TableColumn[] = useMemo(() => [ { id: 'hashtag', header: () => {EXPERIMENT_TABLE_EN.TABLE_TITLES.HASHTAG}, @@ -44,7 +44,7 @@ export const ExperimentTable: React.FC = (props: Experimen return (
-
+
); }; diff --git a/portal/src/app/shared/components/table/Table.module.scss b/portal/src/app/shared/components/table/Table.module.scss index ca419a35..8a7154d4 100644 --- a/portal/src/app/shared/components/table/Table.module.scss +++ b/portal/src/app/shared/components/table/Table.module.scss @@ -8,11 +8,6 @@ table { table-layout: fixed; } -th:first-child, -td:first-child { - inline-size: 80px; -} - .table_titles { padding: 16px; background-color: var($attPurple); diff --git a/portal/src/app/shared/components/table/Table.tsx b/portal/src/app/shared/components/table/Table.tsx index 70766ac8..29bf6d29 100644 --- a/portal/src/app/shared/components/table/Table.tsx +++ b/portal/src/app/shared/components/table/Table.tsx @@ -1,4 +1,5 @@ import styles from './Table.module.scss'; +import cn from 'classnames'; import { useMemo, useState } from 'react'; import { Cell, @@ -14,28 +15,28 @@ import { getCoreRowModel, useReactTable, } from '@tanstack/react-table'; -import { ITestRunResultData } from '../../models/test-run-result.interface'; import SortascendingSvg from '../../../../assets/images/sort-ascending.svg'; import SortDescendingSvg from '../../../../assets/images/sort-descending.svg'; const SortAscendingLabel: string = 'ascending'; const SortDescendingLabel: string = 'descending'; -export interface TableColumn { +export interface TableColumn { id: string; - header: (context: HeaderContext) => React.ReactNode; - accessor: (row: ITestRunResultData) => any; - cell?: (cellInfo: CellContext) => JSX.Element; + header: (context: HeaderContext) => React.ReactNode; + accessor: (row: T) => any; + cell?: (cellInfo: CellContext, row?: T) => JSX.Element; } -export interface TableProps { - headers: TableColumn[]; - data: ITestRunResultData[]; +export interface TableProps { + className?: string; + headers: TableColumn[]; + data: T[]; } -export const Table: React.FC = ({ headers, data }) => { +export const Table = ({ headers, data, className }: TableProps) => { const [sorting, setSorting] = useState([]) - const columns: ColumnDef[] = useMemo(() => { + const columns: ColumnDef[] = useMemo(() => { return headers.map(header => ({ id: header.id, header: header.header, @@ -56,11 +57,11 @@ export const Table: React.FC = ({ headers, data }) => { }); return ( -
+
- {table.getHeaderGroups().map((headerGroup: HeaderGroup) => ( + {table.getHeaderGroups().map((headerGroup: HeaderGroup) => ( - {headerGroup.headers.map((header: Header) => ( + {headerGroup.headers.map((header: Header) => ( - {table.getRowModel().rows.map((row: Row) => ( + {table.getRowModel().rows.map((row: Row) => ( - {row.getVisibleCells().map((cell: Cell) => ( + {row.getVisibleCells().map((cell: Cell) => ( diff --git a/portal/src/app/shared/constants/navigation-tabs.const.ts b/portal/src/app/shared/constants/navigation-tabs.const.ts index e697af6d..81e5b310 100644 --- a/portal/src/app/shared/constants/navigation-tabs.const.ts +++ b/portal/src/app/shared/constants/navigation-tabs.const.ts @@ -6,7 +6,7 @@ export const tabs = [ title: SHARED_EN.NAVIGATION_TABS.HOME, }, { - link: '/experiments', + link: '/qujata/experiments', title: SHARED_EN.NAVIGATION_TABS.ALL_EXPERIMENTS, } ]; diff --git a/portal/src/assets/images/duplicate.svg b/portal/src/assets/images/duplicate.svg new file mode 100644 index 00000000..214d3d93 --- /dev/null +++ b/portal/src/assets/images/duplicate.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + From d1a1a92d9e07147aa75ca22037dca65c122b139d Mon Sep 17 00:00:00 2001 From: Ohad Koren Date: Wed, 17 Jan 2024 16:02:47 +0200 Subject: [PATCH 06/35] using new mock data for all experiment and adjust new api --- portal/mock-server/src/all-experiments.json | 156 +++++++++++++----- portal/mock-server/src/router.ts | 2 +- .../all-experiments/Experiments.tsx | 33 ++-- .../hooks/useExperimentsData.test.ts | 60 +++++-- .../hooks/useExperimentsData.ts | 50 +++--- .../all-experiments/translate/en.ts | 4 - .../parse-experiments-data.utils.test.ts | 33 ++++ .../utils/parse-experiments-data.utils.ts | 25 +++ portal/src/app/components/home/Home.test.tsx | 2 + portal/src/app/components/home/Home.tsx | 16 +- .../protocol-query/ProtocolQuery.tsx | 10 +- .../shared/constants/navigation-tabs.const.ts | 2 +- portal/src/routes/index.jsx | 2 +- 13 files changed, 281 insertions(+), 114 deletions(-) create mode 100644 portal/src/app/components/all-experiments/utils/parse-experiments-data.utils.test.ts create mode 100644 portal/src/app/components/all-experiments/utils/parse-experiments-data.utils.ts diff --git a/portal/mock-server/src/all-experiments.json b/portal/mock-server/src/all-experiments.json index 202f22a9..dde5dd71 100644 --- a/portal/mock-server/src/all-experiments.json +++ b/portal/mock-server/src/all-experiments.json @@ -1,52 +1,122 @@ -{ - "experiments": [ +{ + "test_suites": [ { - "id": 1, - "experimentName": "Experiment 1", - "algorithms": ["kyber512", "hqc128"], - "iterations": [2000, 2500, 10000], - "messageSizes": [512, 1024, 2048], - "date": "2022-11-11" + "id": 15, + "name": "Experiment 1", + "end_time": 1705240065192, + "testRuns": [ + { + "id": 354, + "algorithm": "prime256v1", + "iterations": 100 + }, + { + "id": 355, + "algorithm": "prime256v1", + "iterations": 500 + }, + { + "id": 356, + "algorithm": "prime256v1", + "iterations": 1000 + }, + { + "id": 357, + "algorithm": "p256_kyber512", + "iterations": 100 + }, + { + "id": 358, + "algorithm": "p256_kyber512", + "iterations": 500 + }, + { + "id": 359, + "algorithm": "p256_kyber512", + "iterations": 1000 + }, + { + "id": 360, + "algorithm": "bikel3", + "iterations": 100 + }, + { + "id": 361, + "algorithm": "bikel3", + "iterations": 500 + }, + { + "id": 362, + "algorithm": "bikel3", + "iterations": 1000 + } + ] }, { - "id": 2, - "experimentName": "Experiment 2", - "algorithms": ["hqc128", "prime256v1"], - "iterations": [1500, 1700, 20000], - "messageSizes": [1024, 2048], - "date": "2023-02-09" + "id": 16, + "name": "Experiment 2", + "end_time": 1705389926549, + "testRuns": [ + { + "id": 363, + "algorithm": "kyber512", + "iterations": 1000 + }, + { + "id": 364, + "algorithm": "bikel3", + "iterations": 1000 + }, + { + "id": 365, + "algorithm": "prime256v1", + "iterations": 1000 + } + ] }, { - "id": 3, - "experimentName": "Experiment 3", - "algorithms": ["secp384r1"], - "iterations": [100000], - "messageSizes": [512], - "date": "2024-01-01" + "id": 17, + "name": "Experiment 3", + "end_time": 1705389926549, + "testRuns": [ + { + "id": 366, + "algorithm": "prime256v1", + "iterations": 500 + }, + { + "id": 367, + "algorithm": "bikel3", + "iterations": 1000 + }, + { + "id": 368, + "algorithm": "bikel3", + "iterations": 1000 + }, + { + "id": 369, + "algorithm": "prime256v1", + "iterations": 5000 + } + ] }, { - "id": 4, - "experimentName": "Experiment 4", - "algorithms": ["kyber768", "kyber1024", "secp384r1"], - "iterations": [2000, 1500], - "messageSizes": [1024], - "date": "2020-10-07" - }, - { - "id": 5, - "experimentName": "Experiment 5", - "algorithms": ["hqc256"], - "iterations": [100, 500], - "messageSizes": [2048], - "date": "2021-11-12" - }, - { - "id": 6, - "experimentName": "Experiment 6", - "algorithms": ["hqc256"], - "iterations": [], - "messageSizes": [2048], - "date": "2021-11-12" + "id": 18, + "name": "Experiment 4", + "end_time": 1705389926549, + "testRuns": [ + { + "id": 370, + "algorithm": "kyber512", + "iterations": 500 + }, + { + "id": 371, + "algorithm": "kyber512", + "iterations": 1000 + } + ] } ] -} \ No newline at end of file +} diff --git a/portal/mock-server/src/router.ts b/portal/mock-server/src/router.ts index 15c0a10a..8c3b27e9 100644 --- a/portal/mock-server/src/router.ts +++ b/portal/mock-server/src/router.ts @@ -37,7 +37,7 @@ router.get('/qujata-api/test_suites/:testSuiteId', async (req: Request, res: Res }, 1500); }); -router.get('/qujata-api/experiments', async (req: Request, res: Response) => { +router.get('/qujata-api/test_suites', async (req: Request, res: Response) => { console.log(`-${req.method} ${req.url}`); const data = (await import('./all-experiments.json')).default; setTimeout(() => { diff --git a/portal/src/app/components/all-experiments/Experiments.tsx b/portal/src/app/components/all-experiments/Experiments.tsx index a8c414ee..b46c05af 100644 --- a/portal/src/app/components/all-experiments/Experiments.tsx +++ b/portal/src/app/components/all-experiments/Experiments.tsx @@ -1,6 +1,6 @@ import { ReactNode, useCallback, useMemo } from 'react'; import styles from './Experiments.module.scss'; -import { Experiment, IUseExperimentsData, useExperimentsData } from './hooks'; +import { Experiment, ExperimentData, IUseExperimentsData, useExperimentsData } from './hooks'; import { FetchDataStatus } from '../../shared/hooks/useFetch'; import { Spinner, SpinnerSize } from '../../shared/components/att-spinner'; import { ALL_EXPERIMENTS_TABLE_EN } from './translate/en'; @@ -9,19 +9,17 @@ import { Table } from '../../shared/components/table'; import DuplicateSvg from '../../../assets/images/duplicate.svg'; import { Button, ButtonActionType, ButtonSize, ButtonStyleType } from '../../shared/components/att-button'; import { useNavigate } from 'react-router-dom'; -import { isEmpty } from 'lodash'; const DuplicateAriaLabel: string = ALL_EXPERIMENTS_TABLE_EN.TABLE_COLUMNS.LINKS.DUPLICATE; export const Experiments: React.FC = () => { - const { experiments, status }: IUseExperimentsData = useExperimentsData(); - const experimentsData = useMemo(() => (experiments ?? []), [experiments]); + const { test_suites, status }: IUseExperimentsData = useExperimentsData(); + const experimentsData = useMemo(() => (test_suites ?? []), [test_suites]); const navigate = useNavigate(); - const handleButtonClick = useCallback((row: Experiment) => { - console.log('Button clicked on row:', row); - // need to naviage with the entire row data, so we can duplicate it properly - navigate('/qujata'); + const handleDuplicateClick = useCallback((row: Experiment) => { + // Navigate to the Home Page + navigate('/qujata', { state: { row } }); }, [navigate]); const headers = useMemo(() => { @@ -29,27 +27,22 @@ export const Experiments: React.FC = () => { { id: ALL_EXPERIMENTS_TABLE_EN.TABLE_COLUMNS.EXPERIMENT_NAME.ID, name: ALL_EXPERIMENTS_TABLE_EN.TABLE_COLUMNS.EXPERIMENT_NAME.NAME, - accessor: (row: Experiment) => row.experimentName + accessor: (row: ExperimentData) => row.name }, { id: ALL_EXPERIMENTS_TABLE_EN.TABLE_COLUMNS.ALGORITHMS.ID, name: ALL_EXPERIMENTS_TABLE_EN.TABLE_COLUMNS.ALGORITHMS.NAME, - accessor: (row: Experiment) => row.algorithms.join(', ') + accessor: (row: ExperimentData) => row.algorithms?.join(', ') }, { id: ALL_EXPERIMENTS_TABLE_EN.TABLE_COLUMNS.ITERATIONS.ID, name: ALL_EXPERIMENTS_TABLE_EN.TABLE_COLUMNS.ITERATIONS.NAME, - accessor: (row: Experiment) => row.iterations.join(', ') - }, - { - id: ALL_EXPERIMENTS_TABLE_EN.TABLE_COLUMNS.MESSAGE_SIZE.ID, - name: ALL_EXPERIMENTS_TABLE_EN.TABLE_COLUMNS.MESSAGE_SIZE.NAME, - accessor: (row: Experiment) => row.messageSizes.join(', ') + accessor: (row: ExperimentData) => row.iterations?.join(', ') }, { id: ALL_EXPERIMENTS_TABLE_EN.TABLE_COLUMNS.DATE.ID, name: ALL_EXPERIMENTS_TABLE_EN.TABLE_COLUMNS.DATE.NAME, - accessor: (row: Experiment) => row.date + accessor: (row: ExperimentData) => row.end_time }, { id: ALL_EXPERIMENTS_TABLE_EN.TABLE_COLUMNS.LINKS.DUPLICATE, @@ -60,7 +53,7 @@ export const Experiments: React.FC = () => { size={ButtonSize.NONE} styleType={ButtonStyleType.WRAPPER} actionType={ButtonActionType.BUTTON} - onButtonClick={() => handleButtonClick(cellInfo.row.original)} + onButtonClick={() => handleDuplicateClick(cellInfo.row.original)} > {DuplicateAriaLabel} @@ -74,13 +67,13 @@ export const Experiments: React.FC = () => { accessor, cell: cell || ((cellInfo: CellContext) => {cellInfo.getValue() as ReactNode}) })); - }, [handleButtonClick]); + }, [handleDuplicateClick]); return (
{status === FetchDataStatus.Fetching ? renderSpinner() : (
- +
= ({ headers, data }) => { ))}
{flexRender(cell.column.columnDef.cell, cell.getContext())}
)} diff --git a/portal/src/app/components/all-experiments/hooks/useExperimentsData.test.ts b/portal/src/app/components/all-experiments/hooks/useExperimentsData.test.ts index 439b6f65..a79296f7 100644 --- a/portal/src/app/components/all-experiments/hooks/useExperimentsData.test.ts +++ b/portal/src/app/components/all-experiments/hooks/useExperimentsData.test.ts @@ -1,26 +1,62 @@ import { renderHook } from '@testing-library/react'; import { useFetch } from '../../../shared/hooks/useFetch'; -import { Experiment, useExperimentsData } from './useExperimentsData'; +import { useExperimentsData } from './useExperimentsData'; jest.mock('../../../shared/hooks/useFetch', () => ({ useFetch: jest.fn(), })); +jest.mock('../../../shared/hooks/useFetchSpinner'); +jest.mock('../../../hooks/useErrorMessage'); describe('useExperimentsData', () => { test('Should be in Success mode', () => { const allExperimentsMockData = { - experiments: [ + test_suites: [ { - id: '1', - name: 'Experiment 1', - description: 'Experiment 1 description', + id: 17, + name: "Experiment 3", + end_time: 1705389926549, + test_runs: [ + { + id: 366, + algorithm: "prime256v1", + iterations: 500 + }, + { + id: 367, + algorithm: "bikel3", + iterations: 1000 + }, + { + id: 368, + algorithm: "p256_kyber512", + iterations: 10000 + }, + { + id: 369, + algorithm: "prime256v1", + iterations: 5000 + } + ] }, - { - id: '2', - name: 'Experiment 2', - description: 'Experiment 2 description', - }, - ] + { + id: 18, + name: "Experiment 4", + end_time: 1705389926549, + test_runs: [ + { + id: 370, + algorithm: "kyber512", + iterations: 500 + }, + { + id: 371, + algorithm: "kyber512", + iterations: 1000 + } + ] + } + ] }; (useFetch as jest.Mock).mockReturnValue({ @@ -30,6 +66,6 @@ describe('useExperimentsData', () => { }); const { result } = renderHook(() => useExperimentsData()); - expect(result.current.experiments.length).toEqual(allExperimentsMockData.experiments.length); + expect(result.current.test_suites.length).toEqual(allExperimentsMockData.test_suites.length); }); }); \ No newline at end of file diff --git a/portal/src/app/components/all-experiments/hooks/useExperimentsData.ts b/portal/src/app/components/all-experiments/hooks/useExperimentsData.ts index d82a0f83..b84a3eb9 100644 --- a/portal/src/app/components/all-experiments/hooks/useExperimentsData.ts +++ b/portal/src/app/components/all-experiments/hooks/useExperimentsData.ts @@ -1,26 +1,27 @@ -import { FetchDataStatus, IHttp, useFetch } from "../../../shared/hooks/useFetch"; -import { useEffect, useState } from "react"; -import { APIS } from "../../../apis"; -import { useFetchSpinner } from "../../../shared/hooks/useFetchSpinner"; -import { useErrorMessage } from "../../../hooks/useErrorMessage"; +import { FetchDataStatus, IHttp, useFetch } from '../../../shared/hooks/useFetch'; +import { useEffect, useState } from 'react'; +import { APIS } from '../../../apis'; +import { useFetchSpinner } from '../../../shared/hooks/useFetchSpinner'; +import { useErrorMessage } from '../../../hooks/useErrorMessage'; +import { ITestRunResult } from '../../../shared/models/test-run-result.interface'; +import { parseExperimentsData } from '../utils/parse-experiments-data.utils'; +export type Experiment = Partial; export interface IUseExperimentsData { - experiments: Experiment[]; + test_suites: Experiment[]; status: FetchDataStatus; } - -export interface Experiment { - id: string; - experimentName: string; - algorithms: string[]; - iterations: number[]; - messageSizes: number[]; - date: string; -} +export interface ExperimentData { + id?: number; + name?: string; + algorithms?: string[]; + iterations?: number[]; + end_time?: string; +}; export function useExperimentsData(): IUseExperimentsData { - const [allExperiments, setAllExperiments] = useState([]); - const { get, data, status, cancelRequest, error }: IHttp = useFetch({ url: APIS.allExperiments }); + const [allExperiments, setAllExperiments] = useState([]); + const { get, data, cancelRequest, status, error }: IHttp = useFetch({ url: APIS.allExperiments }); useFetchSpinner(status); useErrorMessage(error); @@ -31,18 +32,11 @@ export function useExperimentsData(): IUseExperimentsData { useEffect(() => { - if (status === FetchDataStatus.Success && data) { - const allExperiments: Experiment[] = data.experiments.map((experiment: Experiment) => ({ - id: experiment.id, - experimentName: experiment.experimentName, - algorithms: experiment.algorithms, - iterations: experiment.iterations, - messageSizes: experiment.messageSizes, - date: experiment.date, - })); - setAllExperiments(allExperiments); + if (data && data.test_suites) { + const experimentsData: ExperimentData[] = parseExperimentsData(data.test_suites); + setAllExperiments(experimentsData); } }, [data, status]); - return { experiments: allExperiments, status }; + return { test_suites: allExperiments, status }; } diff --git a/portal/src/app/components/all-experiments/translate/en.ts b/portal/src/app/components/all-experiments/translate/en.ts index cb118024..943ea9ce 100644 --- a/portal/src/app/components/all-experiments/translate/en.ts +++ b/portal/src/app/components/all-experiments/translate/en.ts @@ -13,10 +13,6 @@ export const ALL_EXPERIMENTS_TABLE_EN = { NAME: 'Iterations', ID: 'iterations' }, - MESSAGE_SIZE: { - NAME: 'Message Size (KB)', - ID: 'messageSizes' - }, DATE: { NAME: 'Date', ID: 'date' diff --git a/portal/src/app/components/all-experiments/utils/parse-experiments-data.utils.test.ts b/portal/src/app/components/all-experiments/utils/parse-experiments-data.utils.test.ts new file mode 100644 index 00000000..8100e25e --- /dev/null +++ b/portal/src/app/components/all-experiments/utils/parse-experiments-data.utils.test.ts @@ -0,0 +1,33 @@ +import { parseExperimentsData } from './parse-experiments-data.utils'; +import { ITestRunResultData } from '../../../shared/models/test-run-result.interface'; +import { Experiment, ExperimentData } from '../hooks'; + +describe('parseExperimentsData', () => { + it('should parse experiments data correctly', () => { + const mockExperiments: Experiment[] = [ + { + id: 1, + name: 'Experiment 1', + testRuns: [ + { algorithm: 'Algorithm 1', iterations: 1000 } as ITestRunResultData, + { algorithm: 'Algorithm 2', iterations: 5000 } as ITestRunResultData, + ], + end_time: '2022-01-01T00:00:00Z', + }, + ]; + + const expectedOutput: ExperimentData[] = [ + { + id: 1, + name: 'Experiment 1', + algorithms: ['Algorithm 1', 'Algorithm 2'], + iterations: [1000, 5000], + end_time: '2022-01-01T00:00:00Z', + }, + ]; + + const result = parseExperimentsData(mockExperiments); + + expect(result).toEqual(expectedOutput); + }); +}); diff --git a/portal/src/app/components/all-experiments/utils/parse-experiments-data.utils.ts b/portal/src/app/components/all-experiments/utils/parse-experiments-data.utils.ts new file mode 100644 index 00000000..8af93fd3 --- /dev/null +++ b/portal/src/app/components/all-experiments/utils/parse-experiments-data.utils.ts @@ -0,0 +1,25 @@ +import { ITestRunResultData } from '../../../shared/models/test-run-result.interface'; +import { Experiment, ExperimentData } from '../hooks'; + +export function parseExperimentsData(test_suites: Experiment[]) { + const experimentsData: ExperimentData[] = []; + + test_suites.forEach((experiment: Experiment) => { + const algorithms = new Set(); + const iterations = new Set(); + experiment.testRuns?.forEach((testRun: ITestRunResultData) => { + algorithms.add(testRun.algorithm); + iterations.add(testRun.iterations); + }); + + experimentsData.push({ + id: experiment.id ?? 0, + name: experiment.name ?? '', + algorithms: Array.from(algorithms), + iterations: Array.from(iterations), + end_time: experiment.end_time ?? '', + }); + }); + + return experimentsData; +} diff --git a/portal/src/app/components/home/Home.test.tsx b/portal/src/app/components/home/Home.test.tsx index e5b75f25..c76bf9f1 100644 --- a/portal/src/app/components/home/Home.test.tsx +++ b/portal/src/app/components/home/Home.test.tsx @@ -4,6 +4,7 @@ import { SubHeader, SubHeaderProps } from '../sub-header'; import { ProtocolQuery, ProtocolQueryProps } from '../protocol-query'; const mockUseNavigate = jest.fn(); +const mockUseLocation = jest.fn(); jest.mock('../sub-header'); jest.mock('../protocol-query'); @@ -17,6 +18,7 @@ jest.mock('../../hooks/useDashboardData', () => ({ jest.mock('react-router-dom', () => ({ ...jest.requireActual('react-router-dom'), useNavigate: () => mockUseNavigate, + useLocation: () => mockUseLocation, })); describe('Home', () => { diff --git a/portal/src/app/components/home/Home.tsx b/portal/src/app/components/home/Home.tsx index ab4e551b..b844e5f4 100644 --- a/portal/src/app/components/home/Home.tsx +++ b/portal/src/app/components/home/Home.tsx @@ -5,7 +5,8 @@ import { ProtocolQuery } from "../protocol-query"; import { SubHeader } from "../sub-header"; import { useCallback, useEffect, useState } from 'react'; import styles from './Home.module.scss'; -import { useNavigate } from "react-router-dom"; +import { useLocation, useNavigate } from "react-router-dom"; +import { Experiment } from "../all-experiments/hooks"; export const Home: React.FC = () => { const [isSubHeaderOpen, setIsSubHeaderOpen] = useState(true); @@ -25,6 +26,13 @@ export const Home: React.FC = () => { export const HomeContent: React.FC = () => { const { handleRunQueryClick, status, testSuiteId }: IUseDashboardData = useDashboardData(); const navigate = useNavigate(); + const location = useLocation(); + const duplicateData: Experiment = location.state?.row; + + useEffect(() => { + // after the duplicate data has been created, we need to clear the state + location.state = undefined; + }, [location]); useEffect(() => { if (status === FetchDataStatus.Success && testSuiteId) { @@ -41,7 +49,11 @@ export const HomeContent: React.FC = () => { return (
- +
); }; diff --git a/portal/src/app/components/protocol-query/ProtocolQuery.tsx b/portal/src/app/components/protocol-query/ProtocolQuery.tsx index 0e87ab5a..6b923418 100644 --- a/portal/src/app/components/protocol-query/ProtocolQuery.tsx +++ b/portal/src/app/components/protocol-query/ProtocolQuery.tsx @@ -1,5 +1,5 @@ import { noop } from 'lodash'; -import React, { useCallback, useState } from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; import { Options } from 'react-select'; import { ITestParams } from '../../shared/models/quantum.interface'; import { Button, ButtonActionType, ButtonSize, ButtonStyleType } from '../../shared/components/att-button'; @@ -10,6 +10,7 @@ import { Spinner, SpinnerSize } from '../../shared/components/att-spinner'; import { useGetAlgorithms, useGetIterations } from './hooks'; import { handleAlgorithmsSelection } from './utils'; import { AlgorithmsSelectorCustomOption, IterationsSelectorCustomOption } from '../../shared/components/selector-custom-option'; +import { Experiment } from '../all-experiments/hooks'; export type SelectOptionType = AttSelectOption | Options | null; type onTextChangedEvent = (e: React.ChangeEvent) => void; @@ -21,10 +22,11 @@ export interface ProtocolQueryProps { canExportFile?: boolean; onRunClick: (data: ITestParams) => void; onDownloadDataClicked?: () => void; + duplicateData?: Experiment; } export const ProtocolQuery: React.FC = (props: ProtocolQueryProps) => { - const { isFetching, canExportFile, onRunClick, onDownloadDataClicked } = props; + const { isFetching, canExportFile, onRunClick, onDownloadDataClicked, duplicateData } = props; const { algorithmOptions, algosBySection } = useGetAlgorithms(); const { iterationsOptions } = useGetIterations(); @@ -37,6 +39,10 @@ export const ProtocolQuery: React.FC = (props: ProtocolQuery const [showInputOption, setShowInputOption] = useState(false); const [inputValue, setInputValue] = useState(''); const [iterationsMenuIsOpen, setIterationsMenuIsOpen] = useState(false); + + useEffect(() => { + console.log('duplicateData ProtocolQuery', duplicateData); + }, [duplicateData]); const onSubmitHandler = (event: React.FormEvent) => { event.preventDefault(); diff --git a/portal/src/app/shared/constants/navigation-tabs.const.ts b/portal/src/app/shared/constants/navigation-tabs.const.ts index 81e5b310..0a3d00e2 100644 --- a/portal/src/app/shared/constants/navigation-tabs.const.ts +++ b/portal/src/app/shared/constants/navigation-tabs.const.ts @@ -6,7 +6,7 @@ export const tabs = [ title: SHARED_EN.NAVIGATION_TABS.HOME, }, { - link: '/qujata/experiments', + link: '/qujata/test_suites', title: SHARED_EN.NAVIGATION_TABS.ALL_EXPERIMENTS, } ]; diff --git a/portal/src/routes/index.jsx b/portal/src/routes/index.jsx index 6f466369..c9dabd47 100644 --- a/portal/src/routes/index.jsx +++ b/portal/src/routes/index.jsx @@ -19,7 +19,7 @@ export const router = createBrowserRouter([ element: , }, { - path: 'experiments', + path: 'test_suites', element: , }, ], From b9395701d711b4ec4809cc79ba55181edf18949b Mon Sep 17 00:00:00 2001 From: Ohad Koren Date: Wed, 17 Jan 2024 17:41:07 +0200 Subject: [PATCH 07/35] duplication implementation --- portal/mock-server/src/all-experiments.json | 2 +- portal/src/app/components/home/Home.tsx | 8 +++-- .../protocol-query/ProtocolQuery.test.tsx | 4 +-- .../protocol-query/ProtocolQuery.tsx | 34 +++++++++++++++---- 4 files changed, 34 insertions(+), 14 deletions(-) diff --git a/portal/mock-server/src/all-experiments.json b/portal/mock-server/src/all-experiments.json index dde5dd71..522f9d47 100644 --- a/portal/mock-server/src/all-experiments.json +++ b/portal/mock-server/src/all-experiments.json @@ -114,7 +114,7 @@ { "id": 371, "algorithm": "kyber512", - "iterations": 1000 + "iterations": 1500 } ] } diff --git a/portal/src/app/components/home/Home.tsx b/portal/src/app/components/home/Home.tsx index b844e5f4..ebc822b7 100644 --- a/portal/src/app/components/home/Home.tsx +++ b/portal/src/app/components/home/Home.tsx @@ -6,7 +6,7 @@ import { SubHeader } from "../sub-header"; import { useCallback, useEffect, useState } from 'react'; import styles from './Home.module.scss'; import { useLocation, useNavigate } from "react-router-dom"; -import { Experiment } from "../all-experiments/hooks"; +import { Experiment, ExperimentData } from "../all-experiments/hooks"; export const Home: React.FC = () => { const [isSubHeaderOpen, setIsSubHeaderOpen] = useState(true); @@ -27,11 +27,12 @@ export const HomeContent: React.FC = () => { const { handleRunQueryClick, status, testSuiteId }: IUseDashboardData = useDashboardData(); const navigate = useNavigate(); const location = useLocation(); - const duplicateData: Experiment = location.state?.row; + const [duplicateData, setDuplicateData] = useState(location.state?.row); + // const duplicateData: Experiment = location.state?.row; useEffect(() => { // after the duplicate data has been created, we need to clear the state - location.state = undefined; + setDuplicateData(undefined); }, [location]); useEffect(() => { @@ -53,6 +54,7 @@ export const HomeContent: React.FC = () => { isFetching={status === FetchDataStatus.Fetching} onRunClick={handleRunClick} duplicateData={duplicateData} + setDuplicateData={setDuplicateData} /> ); diff --git a/portal/src/app/components/protocol-query/ProtocolQuery.test.tsx b/portal/src/app/components/protocol-query/ProtocolQuery.test.tsx index c599c9fc..c444fa76 100644 --- a/portal/src/app/components/protocol-query/ProtocolQuery.test.tsx +++ b/portal/src/app/components/protocol-query/ProtocolQuery.test.tsx @@ -6,12 +6,10 @@ import { PROTOCOL_QUERY_EN } from './translate/en'; describe('ProtocolQuery', () => { let props: ProtocolQueryProps; beforeAll(() => { - // Prepare the props for the ProtocolQuery component props = { isFetching: false, - canExportFile: true, onRunClick: jest.fn(), - onDownloadDataClicked: jest.fn(), + setDuplicateData: jest.fn() }; }); diff --git a/portal/src/app/components/protocol-query/ProtocolQuery.tsx b/portal/src/app/components/protocol-query/ProtocolQuery.tsx index 6b923418..0db92e65 100644 --- a/portal/src/app/components/protocol-query/ProtocolQuery.tsx +++ b/portal/src/app/components/protocol-query/ProtocolQuery.tsx @@ -1,5 +1,5 @@ import { noop } from 'lodash'; -import React, { useCallback, useEffect, useState } from 'react'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { Options } from 'react-select'; import { ITestParams } from '../../shared/models/quantum.interface'; import { Button, ButtonActionType, ButtonSize, ButtonStyleType } from '../../shared/components/att-button'; @@ -10,7 +10,7 @@ import { Spinner, SpinnerSize } from '../../shared/components/att-spinner'; import { useGetAlgorithms, useGetIterations } from './hooks'; import { handleAlgorithmsSelection } from './utils'; import { AlgorithmsSelectorCustomOption, IterationsSelectorCustomOption } from '../../shared/components/selector-custom-option'; -import { Experiment } from '../all-experiments/hooks'; +import { ExperimentData } from '../all-experiments/hooks'; export type SelectOptionType = AttSelectOption | Options | null; type onTextChangedEvent = (e: React.ChangeEvent) => void; @@ -19,14 +19,13 @@ export type OnSelectChanged = (event: SelectOptionType) => void; export interface ProtocolQueryProps { isFetching: boolean; - canExportFile?: boolean; onRunClick: (data: ITestParams) => void; - onDownloadDataClicked?: () => void; - duplicateData?: Experiment; + duplicateData?: ExperimentData; + setDuplicateData: (data?: ExperimentData) => void; } export const ProtocolQuery: React.FC = (props: ProtocolQueryProps) => { - const { isFetching, canExportFile, onRunClick, onDownloadDataClicked, duplicateData } = props; + const { isFetching, onRunClick, duplicateData, setDuplicateData } = props; const { algorithmOptions, algosBySection } = useGetAlgorithms(); const { iterationsOptions } = useGetIterations(); @@ -40,9 +39,29 @@ export const ProtocolQuery: React.FC = (props: ProtocolQuery const [inputValue, setInputValue] = useState(''); const [iterationsMenuIsOpen, setIterationsMenuIsOpen] = useState(false); + // TODO: move this useEffect into different file useEffect(() => { console.log('duplicateData ProtocolQuery', duplicateData); - }, [duplicateData]); + if (duplicateData) { + if (duplicateData && duplicateData.name) { + setExperimentName(duplicateData.name); + } + if (duplicateData && duplicateData.algorithms) { + const algorithmOptions = duplicateData.algorithms.map((algorithm: string) => { + return { label: algorithm, value: algorithm } as AttSelectOption; + }); + setAlgorithms(algorithmOptions); + } + + if (duplicateData && duplicateData.iterations) { + const iterationsOptions = duplicateData.iterations.map((iteration: number) => { + return { label: iteration.toString(), value: iteration.toString() } as AttSelectOption; + }); + setIterationsCount(iterationsOptions); + } + setDuplicateData(undefined); + } + }, [duplicateData, setDuplicateData]); const onSubmitHandler = (event: React.FormEvent) => { event.preventDefault(); @@ -92,6 +111,7 @@ export const ProtocolQuery: React.FC = (props: ProtocolQuery Date: Mon, 22 Jan 2024 14:19:14 +0200 Subject: [PATCH 08/35] finalize all experiments --- portal/mock-server/src/all-experiments.json | 242 +++++++++--------- portal/mock-server/src/router.ts | 7 + portal/mock-server/src/test.json | 2 +- portal/package.json | 1 + portal/src/app/apis.ts | 8 +- .../all-experiments/Experiments.module.scss | 67 ++++- .../all-experiments/Experiments.tsx | 117 ++++++++- .../hooks/useExperimentsData.ts | 28 +- .../all-experiments/translate/en.ts | 4 + .../parse-experiments-data.utils.test.ts | 2 +- .../utils/parse-experiments-data.utils.ts | 11 +- portal/src/app/components/home/Home.tsx | 2 +- .../experiment/components/__mocks__/mocks.ts | 8 +- .../components/charts/__mocks__/mocks.ts | 2 +- .../components/charts/hooks/useChartsData.ts | 4 +- .../DeleteExperimentModal.test.tsx | 4 +- .../DeleteExperimentModal.tsx | 11 +- .../delete-experiment-modal/translate/en.ts | 2 +- .../experiment-table/ExperimentTable.tsx | 2 +- .../hooks/useExperimentData.test.ts | 4 +- .../components/hooks/useExperimentData.ts | 6 +- .../components/sub-header/SubHeader.tsx | 10 +- .../protocol-query/ProtocolQuery.tsx | 3 +- .../models/test-run-result.interface.ts | 2 +- portal/src/assets/images/trash-hover.svg | 4 + portal/yarn.lock | 5 + 26 files changed, 361 insertions(+), 197 deletions(-) create mode 100644 portal/src/assets/images/trash-hover.svg diff --git a/portal/mock-server/src/all-experiments.json b/portal/mock-server/src/all-experiments.json index 522f9d47..4b9a9061 100644 --- a/portal/mock-server/src/all-experiments.json +++ b/portal/mock-server/src/all-experiments.json @@ -1,122 +1,120 @@ -{ - "test_suites": [ - { - "id": 15, - "name": "Experiment 1", - "end_time": 1705240065192, - "testRuns": [ - { - "id": 354, - "algorithm": "prime256v1", - "iterations": 100 - }, - { - "id": 355, - "algorithm": "prime256v1", - "iterations": 500 - }, - { - "id": 356, - "algorithm": "prime256v1", - "iterations": 1000 - }, - { - "id": 357, - "algorithm": "p256_kyber512", - "iterations": 100 - }, - { - "id": 358, - "algorithm": "p256_kyber512", - "iterations": 500 - }, - { - "id": 359, - "algorithm": "p256_kyber512", - "iterations": 1000 - }, - { - "id": 360, - "algorithm": "bikel3", - "iterations": 100 - }, - { - "id": 361, - "algorithm": "bikel3", - "iterations": 500 - }, - { - "id": 362, - "algorithm": "bikel3", - "iterations": 1000 - } - ] - }, - { - "id": 16, - "name": "Experiment 2", - "end_time": 1705389926549, - "testRuns": [ - { - "id": 363, - "algorithm": "kyber512", - "iterations": 1000 - }, - { - "id": 364, - "algorithm": "bikel3", - "iterations": 1000 - }, - { - "id": 365, - "algorithm": "prime256v1", - "iterations": 1000 - } - ] - }, - { - "id": 17, - "name": "Experiment 3", - "end_time": 1705389926549, - "testRuns": [ - { - "id": 366, - "algorithm": "prime256v1", - "iterations": 500 - }, - { - "id": 367, - "algorithm": "bikel3", - "iterations": 1000 - }, - { - "id": 368, - "algorithm": "bikel3", - "iterations": 1000 - }, - { - "id": 369, - "algorithm": "prime256v1", - "iterations": 5000 - } - ] - }, - { - "id": 18, - "name": "Experiment 4", - "end_time": 1705389926549, - "testRuns": [ - { - "id": 370, - "algorithm": "kyber512", - "iterations": 500 - }, - { - "id": 371, - "algorithm": "kyber512", - "iterations": 1500 - } - ] - } - ] -} +[ + { + "id": 15, + "name": "Experiment 1", + "end_time": 1705240065192, + "test_runs": [ + { + "id": 354, + "algorithm": "prime256v1", + "iterations": 100 + }, + { + "id": 355, + "algorithm": "prime256v1", + "iterations": 500 + }, + { + "id": 356, + "algorithm": "prime256v1", + "iterations": 1000 + }, + { + "id": 357, + "algorithm": "p256_kyber512", + "iterations": 100 + }, + { + "id": 358, + "algorithm": "p256_kyber512", + "iterations": 500 + }, + { + "id": 359, + "algorithm": "p256_kyber512", + "iterations": 1000 + }, + { + "id": 360, + "algorithm": "bikel3", + "iterations": 100 + }, + { + "id": 361, + "algorithm": "bikel3", + "iterations": 500 + }, + { + "id": 362, + "algorithm": "bikel3", + "iterations": 1000 + } + ] + }, + { + "id": 16, + "name": "Experiment 2", + "end_time": 1705389926549, + "test_runs": [ + { + "id": 363, + "algorithm": "kyber512", + "iterations": 1000 + }, + { + "id": 364, + "algorithm": "bikel3", + "iterations": 1000 + }, + { + "id": 365, + "algorithm": "prime256v1", + "iterations": 1000 + } + ] + }, + { + "id": 17, + "name": "Experiment 3", + "end_time": 1705389926549, + "test_runs": [ + { + "id": 366, + "algorithm": "prime256v1", + "iterations": 500 + }, + { + "id": 367, + "algorithm": "bikel3", + "iterations": 1000 + }, + { + "id": 368, + "algorithm": "bikel3", + "iterations": 1000 + }, + { + "id": 369, + "algorithm": "prime256v1", + "iterations": 5000 + } + ] + }, + { + "id": 18, + "name": "Experiment 4", + "end_time": 1705389926549, + "test_runs": [ + { + "id": 370, + "algorithm": "kyber512", + "iterations": 500 + }, + { + "id": 371, + "algorithm": "kyber512", + "iterations": 1500 + } + ] + } +] diff --git a/portal/mock-server/src/router.ts b/portal/mock-server/src/router.ts index 8c3b27e9..5db6a10d 100644 --- a/portal/mock-server/src/router.ts +++ b/portal/mock-server/src/router.ts @@ -59,4 +59,11 @@ router.delete('/qujata-api/test_suites/:testSuiteId', async (req: Request, res: }, 1500); }); +router.post('/qujata-api/test_suites/delete', async (req: Request, res: Response) => { + console.log(`-${req.method} ${req.url}`); + setTimeout(() => { + res.status(200).send(); + }, 1500); +}); + export default router; diff --git a/portal/mock-server/src/test.json b/portal/mock-server/src/test.json index 7d9ef1e9..ed4ca2d7 100644 --- a/portal/mock-server/src/test.json +++ b/portal/mock-server/src/test.json @@ -15,7 +15,7 @@ "resourceName": "RELACE_WITH_RESOURCE_NAME" }, - "testRuns": [ + "test_runs": [ { "id":1, "algorithm": "bikel1", diff --git a/portal/package.json b/portal/package.json index 45831012..025ccd6b 100644 --- a/portal/package.json +++ b/portal/package.json @@ -8,6 +8,7 @@ "chart.js": "^4.4.0", "chartjs-plugin-annotation": "^3.0.1", "classnames": "^2.3.2", + "date-fns": "^3.3.0", "lodash": "^4.17.21", "react": "^18.2.0", "react-chartjs-2": "3.1.1", diff --git a/portal/src/app/apis.ts b/portal/src/app/apis.ts index 4897274c..409568eb 100644 --- a/portal/src/app/apis.ts +++ b/portal/src/app/apis.ts @@ -1,5 +1,5 @@ const testSuites = 'test_suites'; - + export const APIS: { [key in keyof typeof API_URLS]: string } = { analyze: 'analyze', algorithms: 'algorithms', @@ -7,8 +7,10 @@ export const APIS: { [key in keyof typeof API_URLS]: string } = { testRunResults: `${testSuites}/:testSuiteId`, editExperiment: `${testSuites}/:testSuiteId`, deleteExperiment: `${testSuites}/:testSuiteId`, + allExperiments: `${testSuites}`, + deleteExperiments: `${testSuites}/delete`, }; - + enum API_URLS { analyze, algorithms, @@ -16,4 +18,6 @@ enum API_URLS { testRunResults, editExperiment, deleteExperiment, + allExperiments, + deleteExperiments } diff --git a/portal/src/app/components/all-experiments/Experiments.module.scss b/portal/src/app/components/all-experiments/Experiments.module.scss index 3d4872e0..7fc3fb5f 100644 --- a/portal/src/app/components/all-experiments/Experiments.module.scss +++ b/portal/src/app/components/all-experiments/Experiments.module.scss @@ -7,19 +7,72 @@ padding-inline-end: 80px; } -.experiments_title { - font-size: 20px; - font-family: var($fontMedium); - margin-inline-start: 150px; +.title_options_container { + display: flex; + justify-content: space-between; + align-items: center; + + .experiments_title { + font-size: 20px; + font-family: var($fontMedium); + margin-inline-start: 150px; + } + + .options_wrapper { + margin-inline-end: 50px; + + .trash_icon { + background-color: #F5F1FF; + inline-size: 34px; + block-size: 34px; + border-radius: 50%; + } + } + + .options_wrapper:hover .hover_image { + display: block; + } + + .options_wrapper:hover .default_image { + display: none; + } + + .default_image { + padding-inline: 11px; + display: block; + } + + .hover_image { + display: none; + } } .experiments_table { margin-block-start: 60px; margin-block-end: 60px; - // .duplicate_icon { - // margin-inline-end: 20px; - // } + th, td { + text-align: left; + } + + th:first-child, + td:first-child { + text-align: center; + inline-size: 80px; + } +} + +.input_form_item { + display: none; +} + +.input_option { + margin-block-end: -5px; + + .input_option_checkbox_icon { + margin-inline-end: 10px; + cursor: pointer; + } } .spinner_wrapper { diff --git a/portal/src/app/components/all-experiments/Experiments.tsx b/portal/src/app/components/all-experiments/Experiments.tsx index b46c05af..53f6e8f9 100644 --- a/portal/src/app/components/all-experiments/Experiments.tsx +++ b/portal/src/app/components/all-experiments/Experiments.tsx @@ -1,29 +1,97 @@ -import { ReactNode, useCallback, useMemo } from 'react'; +import { ReactNode, useCallback, useEffect, useMemo, useState } from 'react'; import styles from './Experiments.module.scss'; -import { Experiment, ExperimentData, IUseExperimentsData, useExperimentsData } from './hooks'; -import { FetchDataStatus } from '../../shared/hooks/useFetch'; +import cn from 'classnames'; +import { ExperimentData, IUseExperimentsData, useExperimentsData } from './hooks'; +import { FetchDataStatus, IHttp, useFetch } from '../../shared/hooks/useFetch'; import { Spinner, SpinnerSize } from '../../shared/components/att-spinner'; import { ALL_EXPERIMENTS_TABLE_EN } from './translate/en'; import { CellContext } from '@tanstack/react-table'; import { Table } from '../../shared/components/table'; -import DuplicateSvg from '../../../assets/images/duplicate.svg'; import { Button, ButtonActionType, ButtonSize, ButtonStyleType } from '../../shared/components/att-button'; +import { APIS } from '../../apis'; import { useNavigate } from 'react-router-dom'; +import { useFetchSpinner } from '../../shared/hooks/useFetchSpinner'; +import { useErrorMessage } from '../../hooks/useErrorMessage'; +import { formatDistanceToNow } from 'date-fns'; +import CheckedSvg from '../../../assets/images/checked.svg'; +import UnCheckedSvg from '../../../assets/images/unchecked.svg'; +import TrashSvg from '../../../assets/images/trash.svg'; +import TrashHoverSvg from '../../../assets/images/trash-hover.svg'; +import DuplicateSvg from '../../../assets/images/duplicate.svg'; +import { DeleteExperimentModal } from '../home/components/experiment/components/delete-experiment-modal'; +import { parseExperimentsData } from './utils/parse-experiments-data.utils'; +const DeleteAriaLabel: string = ALL_EXPERIMENTS_TABLE_EN.BUTTONS.DELETE; const DuplicateAriaLabel: string = ALL_EXPERIMENTS_TABLE_EN.TABLE_COLUMNS.LINKS.DUPLICATE; export const Experiments: React.FC = () => { const { test_suites, status }: IUseExperimentsData = useExperimentsData(); - const experimentsData = useMemo(() => (test_suites ?? []), [test_suites]); + const [openDeleteModal, setOpenDeleteModal] = useState(false); + const [checkedRows, setCheckedRows] = useState>({}); + const experimentsData = useMemo(() => (parseExperimentsData(test_suites) ?? []), [test_suites]); const navigate = useNavigate(); - const handleDuplicateClick = useCallback((row: Experiment) => { + const { post, status: deleteStatus, error: deleteError, cancelRequest: cancelRequestDelete }: IHttp + = useFetch({ url: APIS.deleteExperiments }); + useFetchSpinner(deleteStatus); + useErrorMessage(deleteError); + useEffect(() => cancelRequestDelete, [cancelRequestDelete]); + + const handleDeleteClick: () => void = useCallback((): void => { + setOpenDeleteModal(true); + }, []); + + const handleCloseDeleteExperimentModal: (confirm?: boolean) => void = useCallback((confirm?: boolean): void => { + if (confirm) { + post({ + data: { + ids: Object.keys(checkedRows).map((key: string) => parseInt(key)) + } + }); + } + setOpenDeleteModal(false); + }, [post, checkedRows]); + + const handleCheckboxClick = useCallback((rowInfo: ExperimentData): void => { + const rowId = rowInfo.id as number; + setCheckedRows((prevState: Record) => ({ + ...prevState, + [rowId]: !prevState[rowId], + })); + }, []); + + const handleDuplicateClick = useCallback((row: ExperimentData) => { // Navigate to the Home Page navigate('/qujata', { state: { row } }); }, [navigate]); const headers = useMemo(() => { const columnDefs = [ + { + id: ALL_EXPERIMENTS_TABLE_EN.TABLE_COLUMNS.CHECKBOX, + accessor: () => null, + cell: (cellInfo: CellContext) => { + const rowInfo: ExperimentData = cellInfo.row.original; + return ( +
+ row-option handleCheckboxClick(rowInfo)} + /> + handleCheckboxClick(rowInfo)} + /> +
+ ) + } + }, { id: ALL_EXPERIMENTS_TABLE_EN.TABLE_COLUMNS.EXPERIMENT_NAME.ID, name: ALL_EXPERIMENTS_TABLE_EN.TABLE_COLUMNS.EXPERIMENT_NAME.NAME, @@ -42,12 +110,12 @@ export const Experiments: React.FC = () => { { id: ALL_EXPERIMENTS_TABLE_EN.TABLE_COLUMNS.DATE.ID, name: ALL_EXPERIMENTS_TABLE_EN.TABLE_COLUMNS.DATE.NAME, - accessor: (row: ExperimentData) => row.end_time + accessor: (row: ExperimentData) => formatDistanceToNow(row.end_time as string, { addSuffix: true }) }, { id: ALL_EXPERIMENTS_TABLE_EN.TABLE_COLUMNS.LINKS.DUPLICATE, accessor: () => null, - cell: (cellInfo: CellContext) => ( + cell: (cellInfo: CellContext) => ( ) }, @@ -65,17 +133,38 @@ export const Experiments: React.FC = () => { id, header: () => {name}, accessor, - cell: cell || ((cellInfo: CellContext) => {cellInfo.getValue() as ReactNode}) + cell: cell || ((cellInfo: CellContext) => {cellInfo.getValue() as ReactNode}) })); - }, [handleDuplicateClick]); + }, [checkedRows, handleCheckboxClick, handleDuplicateClick]); + + const checkedExperimentNames = experimentsData + .filter((experiment: ExperimentData) => checkedRows[experiment.id]) + .map((experiment: ExperimentData) => experiment.name); return (
{status === FetchDataStatus.Fetching ? renderSpinner() : ( -
- + <> +
+ + {Object.values(checkedRows).some((value: boolean) => value) && ( + + )} +
- + {openDeleteModal && } + )} ); diff --git a/portal/src/app/components/all-experiments/hooks/useExperimentsData.ts b/portal/src/app/components/all-experiments/hooks/useExperimentsData.ts index b84a3eb9..541b63d0 100644 --- a/portal/src/app/components/all-experiments/hooks/useExperimentsData.ts +++ b/portal/src/app/components/all-experiments/hooks/useExperimentsData.ts @@ -3,25 +3,26 @@ import { useEffect, useState } from 'react'; import { APIS } from '../../../apis'; import { useFetchSpinner } from '../../../shared/hooks/useFetchSpinner'; import { useErrorMessage } from '../../../hooks/useErrorMessage'; -import { ITestRunResult } from '../../../shared/models/test-run-result.interface'; -import { parseExperimentsData } from '../utils/parse-experiments-data.utils'; +import { ITestRunResult, ITestRunResultData } from '../../../shared/models/test-run-result.interface'; + +export type TestRunSubset = Pick; +export type Experiment = Pick & { test_runs: TestRunSubset[] }; -export type Experiment = Partial; export interface IUseExperimentsData { test_suites: Experiment[]; status: FetchDataStatus; } export interface ExperimentData { - id?: number; - name?: string; - algorithms?: string[]; - iterations?: number[]; - end_time?: string; + id: number; + name: string; + algorithms: string[]; + iterations: number[]; + end_time: string; }; export function useExperimentsData(): IUseExperimentsData { - const [allExperiments, setAllExperiments] = useState([]); - const { get, data, cancelRequest, status, error }: IHttp = useFetch({ url: APIS.allExperiments }); + const [allExperiments, setAllExperiments] = useState([]); + const { get, data, cancelRequest, status, error }: IHttp = useFetch({ url: APIS.allExperiments }); useFetchSpinner(status); useErrorMessage(error); @@ -32,11 +33,10 @@ export function useExperimentsData(): IUseExperimentsData { useEffect(() => { - if (data && data.test_suites) { - const experimentsData: ExperimentData[] = parseExperimentsData(data.test_suites); - setAllExperiments(experimentsData); + if (status === FetchDataStatus.Success && data) { + setAllExperiments(data); } - }, [data, status]); + }, [data, status, allExperiments]); return { test_suites: allExperiments, status }; } diff --git a/portal/src/app/components/all-experiments/translate/en.ts b/portal/src/app/components/all-experiments/translate/en.ts index 943ea9ce..993412fa 100644 --- a/portal/src/app/components/all-experiments/translate/en.ts +++ b/portal/src/app/components/all-experiments/translate/en.ts @@ -1,6 +1,7 @@ export const ALL_EXPERIMENTS_TABLE_EN = { TITLE: 'All Experiments', TABLE_COLUMNS: { + CHECKBOX: 'checkbox', EXPERIMENT_NAME: { NAME: 'Experiment Name', ID: 'experimentName' @@ -20,5 +21,8 @@ export const ALL_EXPERIMENTS_TABLE_EN = { LINKS: { DUPLICATE: 'duplicate', } + }, + BUTTONS: { + DELETE: 'Delete', } } diff --git a/portal/src/app/components/all-experiments/utils/parse-experiments-data.utils.test.ts b/portal/src/app/components/all-experiments/utils/parse-experiments-data.utils.test.ts index 8100e25e..309d952f 100644 --- a/portal/src/app/components/all-experiments/utils/parse-experiments-data.utils.test.ts +++ b/portal/src/app/components/all-experiments/utils/parse-experiments-data.utils.test.ts @@ -8,7 +8,7 @@ describe('parseExperimentsData', () => { { id: 1, name: 'Experiment 1', - testRuns: [ + test_runs: [ { algorithm: 'Algorithm 1', iterations: 1000 } as ITestRunResultData, { algorithm: 'Algorithm 2', iterations: 5000 } as ITestRunResultData, ], diff --git a/portal/src/app/components/all-experiments/utils/parse-experiments-data.utils.ts b/portal/src/app/components/all-experiments/utils/parse-experiments-data.utils.ts index 8af93fd3..f1abd602 100644 --- a/portal/src/app/components/all-experiments/utils/parse-experiments-data.utils.ts +++ b/portal/src/app/components/all-experiments/utils/parse-experiments-data.utils.ts @@ -1,5 +1,4 @@ -import { ITestRunResultData } from '../../../shared/models/test-run-result.interface'; -import { Experiment, ExperimentData } from '../hooks'; +import { Experiment, ExperimentData, TestRunSubset } from '../hooks'; export function parseExperimentsData(test_suites: Experiment[]) { const experimentsData: ExperimentData[] = []; @@ -7,17 +6,17 @@ export function parseExperimentsData(test_suites: Experiment[]) { test_suites.forEach((experiment: Experiment) => { const algorithms = new Set(); const iterations = new Set(); - experiment.testRuns?.forEach((testRun: ITestRunResultData) => { + experiment.test_runs?.forEach((testRun: TestRunSubset) => { algorithms.add(testRun.algorithm); iterations.add(testRun.iterations); }); experimentsData.push({ - id: experiment.id ?? 0, - name: experiment.name ?? '', + id: experiment.id, + name: experiment.name, algorithms: Array.from(algorithms), iterations: Array.from(iterations), - end_time: experiment.end_time ?? '', + end_time: experiment.end_time }); }); diff --git a/portal/src/app/components/home/Home.tsx b/portal/src/app/components/home/Home.tsx index ebc822b7..bd2132f0 100644 --- a/portal/src/app/components/home/Home.tsx +++ b/portal/src/app/components/home/Home.tsx @@ -6,7 +6,7 @@ import { SubHeader } from "../sub-header"; import { useCallback, useEffect, useState } from 'react'; import styles from './Home.module.scss'; import { useLocation, useNavigate } from "react-router-dom"; -import { Experiment, ExperimentData } from "../all-experiments/hooks"; +import { ExperimentData } from "../all-experiments/hooks"; export const Home: React.FC = () => { const [isSubHeaderOpen, setIsSubHeaderOpen] = useState(true); diff --git a/portal/src/app/components/home/components/experiment/components/__mocks__/mocks.ts b/portal/src/app/components/home/components/experiment/components/__mocks__/mocks.ts index 04658e61..8337a1aa 100644 --- a/portal/src/app/components/home/components/experiment/components/__mocks__/mocks.ts +++ b/portal/src/app/components/home/components/experiment/components/__mocks__/mocks.ts @@ -17,7 +17,7 @@ export const MOCK_DATA_FOR_EXPERIMENT: ITestRunResult = { nodeSize: "Standard_D4s_v5", codeRelease: "1.1.0", }, - testRuns: [ + test_runs: [ { id: 1, algorithm: "Algorithm1", @@ -68,7 +68,7 @@ export const MOCK_DATA_FOR_EXPERIMENT_TABLE: ExperimentTableProps = { nodeSize: "Standard_D4s_v5", codeRelease: "1.1.0", }, - testRuns: [ + test_runs: [ { id: 1, algorithm: "Algorithm1", @@ -138,7 +138,7 @@ export const MOCK_DATA_FOR_EXPERIMENT_WITH_NO_TEST_RUNS: ExperimentTableProps = nodeSize: "Standard_D4s_v5", codeRelease: "1.1.0", }, - testRuns: [] + test_runs: [] }, selectedColumns: [ { @@ -176,7 +176,7 @@ export const MOCK_SUB_HEADER: ITestRunResult = { operatingSystem: 'codeRelease', resourceName: 'codeRelease', }, - testRuns: [ + test_runs: [ { id:1, algorithm: "bikel1", diff --git a/portal/src/app/components/home/components/experiment/components/charts/__mocks__/mocks.ts b/portal/src/app/components/home/components/experiment/components/charts/__mocks__/mocks.ts index 690e7966..5d2a5260 100644 --- a/portal/src/app/components/home/components/experiment/components/charts/__mocks__/mocks.ts +++ b/portal/src/app/components/home/components/experiment/components/charts/__mocks__/mocks.ts @@ -17,7 +17,7 @@ export const MOCK_DATA_FOR_CHARTS: IExperimentData = { nodeSize: "Standard_D4s_v5", codeRelease: "1.1.0", }, - testRuns: [ + test_runs: [ { id: 1, algorithm: "Algorithm1", diff --git a/portal/src/app/components/home/components/experiment/components/charts/hooks/useChartsData.ts b/portal/src/app/components/home/components/experiment/components/charts/hooks/useChartsData.ts index 40475047..73eb95ed 100644 --- a/portal/src/app/components/home/components/experiment/components/charts/hooks/useChartsData.ts +++ b/portal/src/app/components/home/components/experiment/components/charts/hooks/useChartsData.ts @@ -62,8 +62,8 @@ export function useChartsData(props: IExperimentData): IUseChartsData { const [lineChartData, setLineChartData] = useState(); useEffect(() => { - if(props.data && props.data.testRuns.length > 0) { - const testRuns: ITestRunResultData[] = props.data.testRuns; + if(props.data && props.data.test_runs.length > 0) { + const testRuns: ITestRunResultData[] = props.data.test_runs; setBarChartData(testRuns); const labels: string[] = getLabels(testRuns); setBarChartLabels(labels); diff --git a/portal/src/app/components/home/components/experiment/components/delete-experiment-modal/DeleteExperimentModal.test.tsx b/portal/src/app/components/home/components/experiment/components/delete-experiment-modal/DeleteExperimentModal.test.tsx index 4a45342e..ada5f26b 100644 --- a/portal/src/app/components/home/components/experiment/components/delete-experiment-modal/DeleteExperimentModal.test.tsx +++ b/portal/src/app/components/home/components/experiment/components/delete-experiment-modal/DeleteExperimentModal.test.tsx @@ -5,7 +5,7 @@ import { DeleteExperimentModal, DeleteExperimentModalProps } from './DeleteExper describe('EditExperimentModal', () => { test('renders edit Experiment modal correctly', () => { const props: DeleteExperimentModalProps = { - name: 'Test', + name: ['Test'], onClose: jest.fn(), }; const { baseElement }: RenderResult = render(TestMe); @@ -15,7 +15,7 @@ describe('EditExperimentModal', () => { test('click submit button', () => { const handleClose = jest.fn(); const props: DeleteExperimentModalProps = { - name: 'Test', + name: ['Test'], onClose: handleClose, }; const { getByRole }: RenderResult = render(TestMe); diff --git a/portal/src/app/components/home/components/experiment/components/delete-experiment-modal/DeleteExperimentModal.tsx b/portal/src/app/components/home/components/experiment/components/delete-experiment-modal/DeleteExperimentModal.tsx index 70f5b935..2083c2fa 100644 --- a/portal/src/app/components/home/components/experiment/components/delete-experiment-modal/DeleteExperimentModal.tsx +++ b/portal/src/app/components/home/components/experiment/components/delete-experiment-modal/DeleteExperimentModal.tsx @@ -4,18 +4,16 @@ import { BaseModal } from '../../../../../../shared/components/modal'; import { ButtonActionType, ButtonSize, ButtonStyleType, IButton } from '../../../../../../shared/components/att-button'; import { DELETE_EXPERIMENT_MODAL_EN } from './translate/en'; import { BaseModalSize } from '../../../../../../shared/components/modal/base-modal.const'; -import { translateParserService } from '../../../../../../shared/utils/translate-parser'; export interface DeleteExperimentModalProps { onClose: (confirm?: boolean) => void; - name: string; + name: string[]; } export const DeleteExperimentModal: React.FC = (props: DeleteExperimentModalProps) => { const { name, onClose } = props; const [actionButtons, setActionButtons] = useState([]); - const description: string = translateParserService.interpolateString(DELETE_EXPERIMENT_MODAL_EN.DESCRIPTION, { name }); - + const experimentToDelete = name.map((experimentName, index) =>
  • {experimentName}
  • ); useLayoutEffect(() => { const submitButton: IButton = { @@ -37,7 +35,10 @@ export const DeleteExperimentModal: React.FC = (prop actionButton={actionButtons} size={BaseModalSize.SMALL} > -
    {description}
    +
    +

    {DELETE_EXPERIMENT_MODAL_EN.DESCRIPTION}

    +
      {experimentToDelete}
    +
    ); }; diff --git a/portal/src/app/components/home/components/experiment/components/delete-experiment-modal/translate/en.ts b/portal/src/app/components/home/components/experiment/components/delete-experiment-modal/translate/en.ts index 37e635ca..5af4202e 100644 --- a/portal/src/app/components/home/components/experiment/components/delete-experiment-modal/translate/en.ts +++ b/portal/src/app/components/home/components/experiment/components/delete-experiment-modal/translate/en.ts @@ -1,5 +1,5 @@ export const DELETE_EXPERIMENT_MODAL_EN = { SUBMIT_ACTION: 'Confirm', TITLE: 'Delete Experiment', - DESCRIPTION: 'Are you sure you want to delete "{{name}}" experiment?', + DESCRIPTION: 'Are you sure you want to delete the following experiment(s)?' }; diff --git a/portal/src/app/components/home/components/experiment/components/experiment-table/ExperimentTable.tsx b/portal/src/app/components/home/components/experiment/components/experiment-table/ExperimentTable.tsx index c873d676..49fa4dc2 100644 --- a/portal/src/app/components/home/components/experiment/components/experiment-table/ExperimentTable.tsx +++ b/portal/src/app/components/home/components/experiment/components/experiment-table/ExperimentTable.tsx @@ -12,7 +12,7 @@ export interface ExperimentTableProps { } export const ExperimentTable: React.FC = (props: ExperimentTableProps) => { - const data = useMemo(() => (props.data ? props.data.testRuns : []), [props.data]); + const data = useMemo(() => (props.data ? props.data.test_runs : []), [props.data]); const headers: TableColumn[] = useMemo(() => [ { diff --git a/portal/src/app/components/home/components/experiment/components/hooks/useExperimentData.test.ts b/portal/src/app/components/home/components/experiment/components/hooks/useExperimentData.test.ts index c59d722d..38a54179 100644 --- a/portal/src/app/components/home/components/experiment/components/hooks/useExperimentData.test.ts +++ b/portal/src/app/components/home/components/experiment/components/hooks/useExperimentData.test.ts @@ -21,10 +21,10 @@ describe('useExperimentData', () => { (useFetchSpinner as jest.Mock).mockImplementation(() => undefined); (useErrorMessage as jest.Mock).mockImplementation(() => undefined); - const mockDataNumOfTestRuns = MOCK_DATA_FOR_EXPERIMENT.testRuns.length; + const mockDataNumOfTestRuns = MOCK_DATA_FOR_EXPERIMENT.test_runs.length; const { result } = renderHook(() => useExperimentData()); - expect(result.current.data.testRuns.length).toEqual(mockDataNumOfTestRuns); + expect(result.current.data.test_runs.length).toEqual(mockDataNumOfTestRuns); }); test('Should not render data', () => { diff --git a/portal/src/app/components/home/components/experiment/components/hooks/useExperimentData.ts b/portal/src/app/components/home/components/experiment/components/hooks/useExperimentData.ts index 6039ed60..d1c0bf57 100644 --- a/portal/src/app/components/home/components/experiment/components/hooks/useExperimentData.ts +++ b/portal/src/app/components/home/components/experiment/components/hooks/useExperimentData.ts @@ -29,9 +29,9 @@ export function useExperimentData(): IUseExperimentData { }, [get, cancelRequest]); useEffect(() => { - if (data && data.testRuns) { - const sortedData: ITestRunResultData[] = sortDataByAlgorithm(data.testRuns); - setTestRunData({ ...data, testRuns: sortedData }); + if (data && data.test_runs) { + const sortedData: ITestRunResultData[] = sortDataByAlgorithm(data.test_runs); + setTestRunData({ ...data, test_runs: sortedData }); } }, [data]); diff --git a/portal/src/app/components/home/components/experiment/components/sub-header/SubHeader.tsx b/portal/src/app/components/home/components/experiment/components/sub-header/SubHeader.tsx index 6818c4f3..6c884ace 100644 --- a/portal/src/app/components/home/components/experiment/components/sub-header/SubHeader.tsx +++ b/portal/src/app/components/home/components/experiment/components/sub-header/SubHeader.tsx @@ -62,8 +62,8 @@ export const SubHeader: React.FC = (props: SubHeaderProps) => { const handleDownloadClick: () => void = useCallback((): void => { const csvFileName: string = `${SUB_HEADER_EN.CSV_REPORT.FILE_NAME}-${name || ''}.csv`; - downloadCsvFile(mapExperimentDataToCsvDataType(data.testRuns), csvFileName); - }, [data.testRuns, name]); + downloadCsvFile(mapExperimentDataToCsvDataType(data.test_runs), csvFileName); + }, [data.test_runs, name]); const handleCloseEditExperimentModal: (editData?: EditExperimentModalData) => void = useCallback((editData?: EditExperimentModalData): void => { if (editData) { @@ -103,11 +103,11 @@ export const SubHeader: React.FC = (props: SubHeaderProps) => {
    {SUB_HEADER_EN.ALGORITHM}
    -
    {getAlgorithmsName(data.testRuns)}
    +
    {getAlgorithmsName(data.test_runs)}
    {SUB_HEADER_EN.ITERATIONS}
    -
    {getIterations(data.testRuns)}
    +
    {getIterations(data.test_runs)}
    {experimentDescription}
    @@ -136,7 +136,7 @@ export const SubHeader: React.FC = (props: SubHeaderProps) => { {openEditModal && } - {openDeleteModal && } + {openDeleteModal && } ); } diff --git a/portal/src/app/components/protocol-query/ProtocolQuery.tsx b/portal/src/app/components/protocol-query/ProtocolQuery.tsx index 0db92e65..e35be1ab 100644 --- a/portal/src/app/components/protocol-query/ProtocolQuery.tsx +++ b/portal/src/app/components/protocol-query/ProtocolQuery.tsx @@ -1,5 +1,5 @@ import { noop } from 'lodash'; -import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; import { Options } from 'react-select'; import { ITestParams } from '../../shared/models/quantum.interface'; import { Button, ButtonActionType, ButtonSize, ButtonStyleType } from '../../shared/components/att-button'; @@ -41,7 +41,6 @@ export const ProtocolQuery: React.FC = (props: ProtocolQuery // TODO: move this useEffect into different file useEffect(() => { - console.log('duplicateData ProtocolQuery', duplicateData); if (duplicateData) { if (duplicateData && duplicateData.name) { setExperimentName(duplicateData.name); diff --git a/portal/src/app/shared/models/test-run-result.interface.ts b/portal/src/app/shared/models/test-run-result.interface.ts index 809f3bd6..d81f30e3 100644 --- a/portal/src/app/shared/models/test-run-result.interface.ts +++ b/portal/src/app/shared/models/test-run-result.interface.ts @@ -27,5 +27,5 @@ export interface ITestRunResult { start_time: string; end_time: string; environment_info: IEnvironmentInfo; - testRuns: ITestRunResultData[]; + test_runs: ITestRunResultData[]; } diff --git a/portal/src/assets/images/trash-hover.svg b/portal/src/assets/images/trash-hover.svg new file mode 100644 index 00000000..81fe1736 --- /dev/null +++ b/portal/src/assets/images/trash-hover.svg @@ -0,0 +1,4 @@ + + + + diff --git a/portal/yarn.lock b/portal/yarn.lock index ddc70411..fcf80b7b 100644 --- a/portal/yarn.lock +++ b/portal/yarn.lock @@ -4034,6 +4034,11 @@ data-urls@^2.0.0: whatwg-mimetype "^2.3.0" whatwg-url "^8.0.0" +date-fns@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-3.3.0.tgz#c1681691cf751a1d371279099a45e71409c7c761" + integrity sha512-xuouT0GuI2W8yXhCMn/AXbSl1Av3wu2hJXxMnnILTY3bYY0UgNK0qOwVXqdFBrobW5qbX1TuOTgMw7c2H2OuhA== + debug@2.6.9, debug@^2.6.0: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" From d92704f0c932322ca4db0f56264c2884872ad963 Mon Sep 17 00:00:00 2001 From: Ohad Koren Date: Mon, 22 Jan 2024 14:52:52 +0200 Subject: [PATCH 09/35] time from string to number --- .../all-experiments/Experiments.tsx | 2 +- .../hooks/useExperimentsData.test.ts | 64 +++++++++---------- .../hooks/useExperimentsData.ts | 4 +- .../parse-experiments-data.utils.test.ts | 4 +- .../components/experiment/Experiment.test.tsx | 2 +- .../experiment/components/__mocks__/mocks.ts | 30 ++++----- .../components/charts/__mocks__/mocks.ts | 4 +- .../__snapshots__/TableOptions.test.tsx.snap | 2 +- .../__snapshots__/ProtocolQuery.test.tsx.snap | 1 + .../models/test-run-result.interface.ts | 4 +- 10 files changed, 58 insertions(+), 59 deletions(-) diff --git a/portal/src/app/components/all-experiments/Experiments.tsx b/portal/src/app/components/all-experiments/Experiments.tsx index 53f6e8f9..d2505f60 100644 --- a/portal/src/app/components/all-experiments/Experiments.tsx +++ b/portal/src/app/components/all-experiments/Experiments.tsx @@ -110,7 +110,7 @@ export const Experiments: React.FC = () => { { id: ALL_EXPERIMENTS_TABLE_EN.TABLE_COLUMNS.DATE.ID, name: ALL_EXPERIMENTS_TABLE_EN.TABLE_COLUMNS.DATE.NAME, - accessor: (row: ExperimentData) => formatDistanceToNow(row.end_time as string, { addSuffix: true }) + accessor: (row: ExperimentData) => formatDistanceToNow(row.end_time, { addSuffix: true }) }, { id: ALL_EXPERIMENTS_TABLE_EN.TABLE_COLUMNS.LINKS.DUPLICATE, diff --git a/portal/src/app/components/all-experiments/hooks/useExperimentsData.test.ts b/portal/src/app/components/all-experiments/hooks/useExperimentsData.test.ts index a79296f7..cee957b7 100644 --- a/portal/src/app/components/all-experiments/hooks/useExperimentsData.test.ts +++ b/portal/src/app/components/all-experiments/hooks/useExperimentsData.test.ts @@ -1,6 +1,6 @@ import { renderHook } from '@testing-library/react'; import { useFetch } from '../../../shared/hooks/useFetch'; -import { useExperimentsData } from './useExperimentsData'; +import { Experiment, useExperimentsData } from './useExperimentsData'; jest.mock('../../../shared/hooks/useFetch', () => ({ useFetch: jest.fn(), @@ -10,35 +10,34 @@ jest.mock('../../../hooks/useErrorMessage'); describe('useExperimentsData', () => { test('Should be in Success mode', () => { - const allExperimentsMockData = { - test_suites: [ - { - id: 17, - name: "Experiment 3", - end_time: 1705389926549, - test_runs: [ - { - id: 366, - algorithm: "prime256v1", - iterations: 500 - }, - { - id: 367, - algorithm: "bikel3", - iterations: 1000 - }, - { - id: 368, - algorithm: "p256_kyber512", - iterations: 10000 - }, - { - id: 369, - algorithm: "prime256v1", - iterations: 5000 - } - ] - }, + const allExperimentsMockData: Experiment[] = [ + { + id: 17, + name: "Experiment 3", + end_time: 1705389926549, + test_runs: [ + { + id: 366, + algorithm: "prime256v1", + iterations: 500 + }, + { + id: 367, + algorithm: "bikel3", + iterations: 1000 + }, + { + id: 368, + algorithm: "p256_kyber512", + iterations: 10000 + }, + { + id: 369, + algorithm: "prime256v1", + iterations: 5000 + } + ] + }, { id: 18, name: "Experiment 4", @@ -56,8 +55,7 @@ describe('useExperimentsData', () => { } ] } - ] - }; + ]; (useFetch as jest.Mock).mockReturnValue({ get: jest.fn(), @@ -66,6 +64,6 @@ describe('useExperimentsData', () => { }); const { result } = renderHook(() => useExperimentsData()); - expect(result.current.test_suites.length).toEqual(allExperimentsMockData.test_suites.length); + expect(result.current.test_suites.length).toEqual(allExperimentsMockData.length); }); }); \ No newline at end of file diff --git a/portal/src/app/components/all-experiments/hooks/useExperimentsData.ts b/portal/src/app/components/all-experiments/hooks/useExperimentsData.ts index 541b63d0..b10563e2 100644 --- a/portal/src/app/components/all-experiments/hooks/useExperimentsData.ts +++ b/portal/src/app/components/all-experiments/hooks/useExperimentsData.ts @@ -17,7 +17,7 @@ export interface ExperimentData { name: string; algorithms: string[]; iterations: number[]; - end_time: string; + end_time: number; }; export function useExperimentsData(): IUseExperimentsData { @@ -33,7 +33,7 @@ export function useExperimentsData(): IUseExperimentsData { useEffect(() => { - if (status === FetchDataStatus.Success && data) { + if (data) { setAllExperiments(data); } }, [data, status, allExperiments]); diff --git a/portal/src/app/components/all-experiments/utils/parse-experiments-data.utils.test.ts b/portal/src/app/components/all-experiments/utils/parse-experiments-data.utils.test.ts index 309d952f..241ed2ff 100644 --- a/portal/src/app/components/all-experiments/utils/parse-experiments-data.utils.test.ts +++ b/portal/src/app/components/all-experiments/utils/parse-experiments-data.utils.test.ts @@ -12,7 +12,7 @@ describe('parseExperimentsData', () => { { algorithm: 'Algorithm 1', iterations: 1000 } as ITestRunResultData, { algorithm: 'Algorithm 2', iterations: 5000 } as ITestRunResultData, ], - end_time: '2022-01-01T00:00:00Z', + end_time: 1705240065192, }, ]; @@ -22,7 +22,7 @@ describe('parseExperimentsData', () => { name: 'Experiment 1', algorithms: ['Algorithm 1', 'Algorithm 2'], iterations: [1000, 5000], - end_time: '2022-01-01T00:00:00Z', + end_time: 1705240065192, }, ]; diff --git a/portal/src/app/components/home/components/experiment/Experiment.test.tsx b/portal/src/app/components/home/components/experiment/Experiment.test.tsx index 784fcfcd..d6c31b20 100644 --- a/portal/src/app/components/home/components/experiment/Experiment.test.tsx +++ b/portal/src/app/components/home/components/experiment/Experiment.test.tsx @@ -1,4 +1,4 @@ -import { fireEvent, render, waitFor, within } from '@testing-library/react'; +import { render, waitFor } from '@testing-library/react'; import { SubHeader } from './components/sub-header'; import { Charts } from './components/charts'; import { Experiment, ExperimentContent } from './Experiment'; diff --git a/portal/src/app/components/home/components/experiment/components/__mocks__/mocks.ts b/portal/src/app/components/home/components/experiment/components/__mocks__/mocks.ts index 8337a1aa..4308d519 100644 --- a/portal/src/app/components/home/components/experiment/components/__mocks__/mocks.ts +++ b/portal/src/app/components/home/components/experiment/components/__mocks__/mocks.ts @@ -5,8 +5,8 @@ export const MOCK_DATA_FOR_EXPERIMENT: ITestRunResult = { id: 1, name: "TestRun1", description: "TestRun1", - start_time: "2021-07-26T12:00:00.000Z", - end_time: "2021-07-26T12:00:00.000Z", + start_time: 1705240065192, + end_time: 1705240065192, environment_info: { resourceName: "gddn-aks", operatingSystem: "Linux", @@ -56,8 +56,8 @@ export const MOCK_DATA_FOR_EXPERIMENT_TABLE: ExperimentTableProps = { id: 1, name: "TestRun1", description: "TestRun1", - start_time: "2021-07-26T12:00:00.000Z", - end_time: "2021-07-26T12:00:00.000Z", + start_time: 1705240065192, + end_time: 1705240065192, environment_info: { resourceName: "gddn-aks", operatingSystem: "Linux", @@ -126,8 +126,8 @@ export const MOCK_DATA_FOR_EXPERIMENT_WITH_NO_TEST_RUNS: ExperimentTableProps = id: 1, name: "TestRun1", description: "TestRun1", - start_time: "2021-07-26T12:00:00.000Z", - end_time: "2021-07-26T12:00:00.000Z", + start_time: 1705240065192, + end_time: 1705240065192, environment_info: { resourceName: "gddn-aks", operatingSystem: "Linux", @@ -163,18 +163,18 @@ export const MOCK_DATA_FOR_EXPERIMENT_WITH_NO_TEST_RUNS: ExperimentTableProps = export const MOCK_SUB_HEADER: ITestRunResult = { id: 1, name: 'name', - description: 'name', - start_time: 'name', - end_time: 'name', + description: 'description', + start_time: 1705240065192, + end_time: 1705240065192, environment_info: { codeRelease: 'codeRelease', - cpu: 'codeRelease', - cpuArchitecture: 'codeRelease', - cpuClockSpeed: 'codeRelease', + cpu: 'cpu', + cpuArchitecture: 'cpuArchitecture', + cpuClockSpeed: 'cpuClockSpeed', cpuCores: 2, - nodeSize: 'codeRelease', - operatingSystem: 'codeRelease', - resourceName: 'codeRelease', + nodeSize: 'nodeSize', + operatingSystem: 'operatingSystem', + resourceName: 'resourceName', }, test_runs: [ { diff --git a/portal/src/app/components/home/components/experiment/components/charts/__mocks__/mocks.ts b/portal/src/app/components/home/components/experiment/components/charts/__mocks__/mocks.ts index 5d2a5260..102dd207 100644 --- a/portal/src/app/components/home/components/experiment/components/charts/__mocks__/mocks.ts +++ b/portal/src/app/components/home/components/experiment/components/charts/__mocks__/mocks.ts @@ -5,8 +5,8 @@ export const MOCK_DATA_FOR_CHARTS: IExperimentData = { id: 1, name: "TestRun1", description: "TestRun1", - start_time: "2021-07-26T12:00:00.000Z", - end_time: "2021-07-26T12:00:00.000Z", + start_time: 1705240065192, + end_time: 1705240065192, environment_info: { resourceName: "gddn-aks", operatingSystem: "Linux", diff --git a/portal/src/app/components/home/components/experiment/components/table-options/__snapshots__/TableOptions.test.tsx.snap b/portal/src/app/components/home/components/experiment/components/table-options/__snapshots__/TableOptions.test.tsx.snap index 7ebde19e..da190153 100644 --- a/portal/src/app/components/home/components/experiment/components/table-options/__snapshots__/TableOptions.test.tsx.snap +++ b/portal/src/app/components/home/components/experiment/components/table-options/__snapshots__/TableOptions.test.tsx.snap @@ -6,7 +6,7 @@ exports[`TableOptions renders without crashing 1`] = ` > diff --git a/portal/src/app/components/protocol-query/__snapshots__/ProtocolQuery.test.tsx.snap b/portal/src/app/components/protocol-query/__snapshots__/ProtocolQuery.test.tsx.snap index f4846208..73a8f54e 100644 --- a/portal/src/app/components/protocol-query/__snapshots__/ProtocolQuery.test.tsx.snap +++ b/portal/src/app/components/protocol-query/__snapshots__/ProtocolQuery.test.tsx.snap @@ -45,6 +45,7 @@ exports[`ProtocolQuery should render ProtocolQuery 1`] = ` class="input_form_item" placeholder="" required="" + value="" />
    Date: Mon, 22 Jan 2024 15:35:38 +0200 Subject: [PATCH 10/35] useDuplicateData export --- portal/src/app/components/home/Home.tsx | 3 +- .../protocol-query/ProtocolQuery.tsx | 36 +------- .../components/protocol-query/hooks/index.ts | 1 + .../hooks/useDuplicateData.test.ts | 92 +++++++++++++++++++ .../protocol-query/hooks/useDuplicateData.ts | 35 +++++++ 5 files changed, 132 insertions(+), 35 deletions(-) create mode 100644 portal/src/app/components/protocol-query/hooks/useDuplicateData.test.ts create mode 100644 portal/src/app/components/protocol-query/hooks/useDuplicateData.ts diff --git a/portal/src/app/components/home/Home.tsx b/portal/src/app/components/home/Home.tsx index bd2132f0..d762aa59 100644 --- a/portal/src/app/components/home/Home.tsx +++ b/portal/src/app/components/home/Home.tsx @@ -28,10 +28,9 @@ export const HomeContent: React.FC = () => { const navigate = useNavigate(); const location = useLocation(); const [duplicateData, setDuplicateData] = useState(location.state?.row); - // const duplicateData: Experiment = location.state?.row; useEffect(() => { - // after the duplicate data has been created, we need to clear the state + // Clear the state after the duplicate data has been created setDuplicateData(undefined); }, [location]); diff --git a/portal/src/app/components/protocol-query/ProtocolQuery.tsx b/portal/src/app/components/protocol-query/ProtocolQuery.tsx index e35be1ab..f03a41cc 100644 --- a/portal/src/app/components/protocol-query/ProtocolQuery.tsx +++ b/portal/src/app/components/protocol-query/ProtocolQuery.tsx @@ -1,5 +1,5 @@ import { noop } from 'lodash'; -import React, { useCallback, useEffect, useState } from 'react'; +import React, { useCallback, useState } from 'react'; import { Options } from 'react-select'; import { ITestParams } from '../../shared/models/quantum.interface'; import { Button, ButtonActionType, ButtonSize, ButtonStyleType } from '../../shared/components/att-button'; @@ -11,6 +11,7 @@ import { useGetAlgorithms, useGetIterations } from './hooks'; import { handleAlgorithmsSelection } from './utils'; import { AlgorithmsSelectorCustomOption, IterationsSelectorCustomOption } from '../../shared/components/selector-custom-option'; import { ExperimentData } from '../all-experiments/hooks'; +import { useDuplicateData } from './hooks'; export type SelectOptionType = AttSelectOption | Options | null; type onTextChangedEvent = (e: React.ChangeEvent) => void; @@ -39,29 +40,8 @@ export const ProtocolQuery: React.FC = (props: ProtocolQuery const [inputValue, setInputValue] = useState(''); const [iterationsMenuIsOpen, setIterationsMenuIsOpen] = useState(false); - // TODO: move this useEffect into different file - useEffect(() => { - if (duplicateData) { - if (duplicateData && duplicateData.name) { - setExperimentName(duplicateData.name); - } - if (duplicateData && duplicateData.algorithms) { - const algorithmOptions = duplicateData.algorithms.map((algorithm: string) => { - return { label: algorithm, value: algorithm } as AttSelectOption; - }); - setAlgorithms(algorithmOptions); - } + useDuplicateData({ data: duplicateData, setDuplicateData, setExperimentName, setAlgorithms, setIterationsCount }); - if (duplicateData && duplicateData.iterations) { - const iterationsOptions = duplicateData.iterations.map((iteration: number) => { - return { label: iteration.toString(), value: iteration.toString() } as AttSelectOption; - }); - setIterationsCount(iterationsOptions); - } - setDuplicateData(undefined); - } - }, [duplicateData, setDuplicateData]); - const onSubmitHandler = (event: React.FormEvent) => { event.preventDefault(); onRunClick({ @@ -190,16 +170,6 @@ export const ProtocolQuery: React.FC = (props: ProtocolQuery
    } - {/* */} ); }; diff --git a/portal/src/app/components/protocol-query/hooks/index.ts b/portal/src/app/components/protocol-query/hooks/index.ts index b7318e3e..f5861164 100644 --- a/portal/src/app/components/protocol-query/hooks/index.ts +++ b/portal/src/app/components/protocol-query/hooks/index.ts @@ -1,2 +1,3 @@ export * from './useGetAlgorithms'; export * from './useGetIterations'; +export * from './useDuplicateData'; diff --git a/portal/src/app/components/protocol-query/hooks/useDuplicateData.test.ts b/portal/src/app/components/protocol-query/hooks/useDuplicateData.test.ts new file mode 100644 index 00000000..775bda54 --- /dev/null +++ b/portal/src/app/components/protocol-query/hooks/useDuplicateData.test.ts @@ -0,0 +1,92 @@ +import { renderHook, act } from '@testing-library/react-hooks'; +import { useDuplicateData, DuplicateData } from './useDuplicateData'; +import { AttSelectOption } from '../../../shared/components/att-select'; +import { ExperimentData } from '../../all-experiments/hooks'; + +describe('useDuplicateData', () => { + // it('should set experiment name, algorithms, and iterations count when duplicate data is provided', () => { + // const setExperimentName = jest.fn(); + // const setAlgorithms = jest.fn(); + // const setIterationsCount = jest.fn(); + // const setDuplicateData = jest.fn(); + + // const duplicateData: ExperimentData = { + // id: 1111, + // name: 'test', + // algorithms: ['algorithm1', 'algorithm2'], + // iterations: [1, 2, 3], + // end_time: 1705240065192, + // }; + + // const { rerender } = renderHook((props: DuplicateData) => useDuplicateData(props), { + // initialProps: { + // data: undefined, + // setDuplicateData, + // setExperimentName, + // setAlgorithms, + // setIterationsCount, + // } as DuplicateData, + // }); + + // expect(setExperimentName).not.toHaveBeenCalled(); + // expect(setAlgorithms).not.toHaveBeenCalled(); + // expect(setIterationsCount).not.toHaveBeenCalled(); + // expect(setDuplicateData).not.toHaveBeenCalled(); + + // rerender({ + // data: duplicateData, + // setDuplicateData, + // setExperimentName, + // setAlgorithms, + // setIterationsCount, + // }); + + // expect(setExperimentName).toHaveBeenCalledWith(duplicateData.name); + // expect(setAlgorithms).toHaveBeenCalledWith(duplicateData.algorithms.map(algorithm => ({ label: algorithm, value: algorithm } as AttSelectOption))); + // expect(setIterationsCount).toHaveBeenCalledWith(duplicateData.iterations.map(iteration => ({ label: iteration.toString(), value: iteration.toString() } as AttSelectOption))); + // expect(setDuplicateData).toHaveBeenCalledWith(undefined); + // }); + + it('should set experiment name, algorithms, and iterations count when duplicate data is provided', () => { + const setExperimentName = jest.fn(); + const setAlgorithms = jest.fn(); + const setIterationsCount = jest.fn(); + const setDuplicateData = jest.fn(); + + const duplicateData: ExperimentData = { + id: 1111, + name: 'test', + algorithms: ['algorithm1', 'algorithm2'], + iterations: [1, 2, 3], + end_time: 1705240065192, + }; + + const { rerender } = renderHook((props: DuplicateData) => useDuplicateData(props), { + initialProps: { + data: undefined, + setDuplicateData, + setExperimentName, + setAlgorithms, + setIterationsCount, + } as DuplicateData, + }); + + expect(setExperimentName).not.toHaveBeenCalled(); + expect(setAlgorithms).not.toHaveBeenCalled(); + expect(setIterationsCount).not.toHaveBeenCalled(); + expect(setDuplicateData).not.toHaveBeenCalled(); + + rerender({ + data: duplicateData, + setDuplicateData, + setExperimentName, + setAlgorithms, + setIterationsCount, + }); + + expect(setExperimentName).toHaveBeenCalledWith(duplicateData.name); + expect(setAlgorithms).toHaveBeenCalledWith(duplicateData.algorithms.map(algorithm => ({ label: algorithm, value: algorithm } as AttSelectOption))); + expect(setIterationsCount).toHaveBeenCalledWith(duplicateData.iterations.map(iteration => ({ label: iteration.toString(), value: iteration.toString() } as AttSelectOption))); + expect(setDuplicateData).toHaveBeenCalledWith(undefined); + }); +}); diff --git a/portal/src/app/components/protocol-query/hooks/useDuplicateData.ts b/portal/src/app/components/protocol-query/hooks/useDuplicateData.ts new file mode 100644 index 00000000..d2e9b7c3 --- /dev/null +++ b/portal/src/app/components/protocol-query/hooks/useDuplicateData.ts @@ -0,0 +1,35 @@ +import { useEffect } from 'react'; +import { AttSelectOption } from '../../../shared/components/att-select'; +import { ExperimentData } from '../../all-experiments/hooks'; + +export type DuplicateData = { + data: ExperimentData | undefined, + setDuplicateData: (data: any) => void, + setExperimentName: (name: string) => void, + setAlgorithms: (options: AttSelectOption[]) => void, + setIterationsCount: (options: AttSelectOption[]) => void +} +export const useDuplicateData = (duplicate: DuplicateData) => { + useEffect(() => { + if (duplicate.data) { + const duplicateData = duplicate.data; + if (duplicateData.name) { + duplicate.setExperimentName(duplicateData.name); + } + if (duplicateData.algorithms) { + const algorithmOptions = duplicateData.algorithms.map((algorithm: string) => { + return { label: algorithm, value: algorithm } as AttSelectOption; + }); + duplicate.setAlgorithms(algorithmOptions); + } + + if (duplicateData.iterations) { + const iterationsOptions = duplicateData.iterations.map((iteration: number) => { + return { label: iteration.toString(), value: iteration.toString() } as AttSelectOption; + }); + duplicate.setIterationsCount(iterationsOptions); + } + duplicate.setDuplicateData(undefined); + } + }, [duplicate]); +}; From 19cceb0eea293934014c6ec954c3b592b6959449 Mon Sep 17 00:00:00 2001 From: Ohad Koren Date: Mon, 22 Jan 2024 16:05:35 +0200 Subject: [PATCH 11/35] no experiments handling --- portal/src/app/components/all-experiments/Experiments.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/portal/src/app/components/all-experiments/Experiments.tsx b/portal/src/app/components/all-experiments/Experiments.tsx index d2505f60..5ebb8243 100644 --- a/portal/src/app/components/all-experiments/Experiments.tsx +++ b/portal/src/app/components/all-experiments/Experiments.tsx @@ -162,7 +162,7 @@ export const Experiments: React.FC = () => { )} -
    + {experimentsData.length > 0 &&
    } {openDeleteModal && } )} From 76743bc903ed433fd5b9da4322e83d890210d127 Mon Sep 17 00:00:00 2001 From: Ohad Koren Date: Mon, 22 Jan 2024 16:58:00 +0200 Subject: [PATCH 12/35] unitests --- .../all-experiments/Experiments.test.tsx | 43 +++++++++++++++++-- .../all-experiments/Experiments.tsx | 7 ++- .../__snapshots__/Experiments.test.tsx.snap | 41 ++++++++++++++++++ .../hooks/useDuplicateData.test.ts | 43 ------------------- 4 files changed, 84 insertions(+), 50 deletions(-) create mode 100644 portal/src/app/components/all-experiments/__snapshots__/Experiments.test.tsx.snap diff --git a/portal/src/app/components/all-experiments/Experiments.test.tsx b/portal/src/app/components/all-experiments/Experiments.test.tsx index a63b4138..8999f49b 100644 --- a/portal/src/app/components/all-experiments/Experiments.test.tsx +++ b/portal/src/app/components/all-experiments/Experiments.test.tsx @@ -1,7 +1,44 @@ +import { render, fireEvent } from '@testing-library/react'; import { Experiments } from './Experiments'; +import { useExperimentsData } from './hooks'; +import { FetchDataStatus, useFetch } from '../../shared/hooks/useFetch'; + +jest.mock('./hooks'); +jest.mock('../../shared/hooks/useFetch'); +jest.mock('react-router-dom', () => ({ + useNavigate: jest.fn(), +})); describe('Experiments', () => { - test('should render Experiments', async () => { - expect(true).toBe(true); + it('renders correctly', () => { + (useExperimentsData as jest.Mock).mockReturnValue({ + test_suites: [{ + id: 15, + name: "Experiment 1", + end_time: 1705240065192, + test_runs: [ + { + id: 354, + algorithm: "prime256v1", + iterations: 100 + }, + { + id: 355, + algorithm: "prime256v1", + iterations: 500 + } + ] + }], + status: FetchDataStatus.Fetching, + }); + (useFetch as jest.Mock).mockReturnValue({ + post: jest.fn(), + status: FetchDataStatus.Fetching, + error: null, + cancelRequest: jest.fn(), + }); + + const { container } = render(); + expect(container).toMatchSnapshot(); }); -}); +}); \ No newline at end of file diff --git a/portal/src/app/components/all-experiments/Experiments.tsx b/portal/src/app/components/all-experiments/Experiments.tsx index 5ebb8243..67f76dd5 100644 --- a/portal/src/app/components/all-experiments/Experiments.tsx +++ b/portal/src/app/components/all-experiments/Experiments.tsx @@ -28,7 +28,7 @@ export const Experiments: React.FC = () => { const { test_suites, status }: IUseExperimentsData = useExperimentsData(); const [openDeleteModal, setOpenDeleteModal] = useState(false); const [checkedRows, setCheckedRows] = useState>({}); - const experimentsData = useMemo(() => (parseExperimentsData(test_suites) ?? []), [test_suites]); + const experimentsData = useMemo(() => (parseExperimentsData(test_suites)), [test_suites]); const navigate = useNavigate(); const { post, status: deleteStatus, error: deleteError, cancelRequest: cancelRequestDelete }: IHttp @@ -43,10 +43,9 @@ export const Experiments: React.FC = () => { const handleCloseDeleteExperimentModal: (confirm?: boolean) => void = useCallback((confirm?: boolean): void => { if (confirm) { + const ids: number[] = Object.keys(checkedRows).map((key: string) => parseInt(key)) post({ - data: { - ids: Object.keys(checkedRows).map((key: string) => parseInt(key)) - } + data: { ids } }); } setOpenDeleteModal(false); diff --git a/portal/src/app/components/all-experiments/__snapshots__/Experiments.test.tsx.snap b/portal/src/app/components/all-experiments/__snapshots__/Experiments.test.tsx.snap new file mode 100644 index 00000000..ac0e0190 --- /dev/null +++ b/portal/src/app/components/all-experiments/__snapshots__/Experiments.test.tsx.snap @@ -0,0 +1,41 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Experiments renders correctly 1`] = ` +
    +
    +
    +
    + + + + + + +
    +
    +
    +
    +`; diff --git a/portal/src/app/components/protocol-query/hooks/useDuplicateData.test.ts b/portal/src/app/components/protocol-query/hooks/useDuplicateData.test.ts index 775bda54..ad54f078 100644 --- a/portal/src/app/components/protocol-query/hooks/useDuplicateData.test.ts +++ b/portal/src/app/components/protocol-query/hooks/useDuplicateData.test.ts @@ -4,49 +4,6 @@ import { AttSelectOption } from '../../../shared/components/att-select'; import { ExperimentData } from '../../all-experiments/hooks'; describe('useDuplicateData', () => { - // it('should set experiment name, algorithms, and iterations count when duplicate data is provided', () => { - // const setExperimentName = jest.fn(); - // const setAlgorithms = jest.fn(); - // const setIterationsCount = jest.fn(); - // const setDuplicateData = jest.fn(); - - // const duplicateData: ExperimentData = { - // id: 1111, - // name: 'test', - // algorithms: ['algorithm1', 'algorithm2'], - // iterations: [1, 2, 3], - // end_time: 1705240065192, - // }; - - // const { rerender } = renderHook((props: DuplicateData) => useDuplicateData(props), { - // initialProps: { - // data: undefined, - // setDuplicateData, - // setExperimentName, - // setAlgorithms, - // setIterationsCount, - // } as DuplicateData, - // }); - - // expect(setExperimentName).not.toHaveBeenCalled(); - // expect(setAlgorithms).not.toHaveBeenCalled(); - // expect(setIterationsCount).not.toHaveBeenCalled(); - // expect(setDuplicateData).not.toHaveBeenCalled(); - - // rerender({ - // data: duplicateData, - // setDuplicateData, - // setExperimentName, - // setAlgorithms, - // setIterationsCount, - // }); - - // expect(setExperimentName).toHaveBeenCalledWith(duplicateData.name); - // expect(setAlgorithms).toHaveBeenCalledWith(duplicateData.algorithms.map(algorithm => ({ label: algorithm, value: algorithm } as AttSelectOption))); - // expect(setIterationsCount).toHaveBeenCalledWith(duplicateData.iterations.map(iteration => ({ label: iteration.toString(), value: iteration.toString() } as AttSelectOption))); - // expect(setDuplicateData).toHaveBeenCalledWith(undefined); - // }); - it('should set experiment name, algorithms, and iterations count when duplicate data is provided', () => { const setExperimentName = jest.fn(); const setAlgorithms = jest.fn(); From 89df52ba7da2c88d18cb73e32113d00078bc4f42 Mon Sep 17 00:00:00 2001 From: Ohad Koren Date: Mon, 22 Jan 2024 17:16:26 +0200 Subject: [PATCH 13/35] remove irrelevant --- portal/src/app/components/all-experiments/Experiments.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/portal/src/app/components/all-experiments/Experiments.test.tsx b/portal/src/app/components/all-experiments/Experiments.test.tsx index 8999f49b..6106dd28 100644 --- a/portal/src/app/components/all-experiments/Experiments.test.tsx +++ b/portal/src/app/components/all-experiments/Experiments.test.tsx @@ -1,4 +1,4 @@ -import { render, fireEvent } from '@testing-library/react'; +import { render } from '@testing-library/react'; import { Experiments } from './Experiments'; import { useExperimentsData } from './hooks'; import { FetchDataStatus, useFetch } from '../../shared/hooks/useFetch'; From 74d50024a0b69912a307c59bba562a51d95dd3bd Mon Sep 17 00:00:00 2001 From: kr172t Date: Tue, 23 Jan 2024 10:50:11 +0200 Subject: [PATCH 14/35] change message generator method --- curl/src/utils/message.generator.spec.ts | 4 +--- curl/src/utils/message.generator.ts | 9 ++++----- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/curl/src/utils/message.generator.spec.ts b/curl/src/utils/message.generator.spec.ts index 084bd7c1..a687fba4 100644 --- a/curl/src/utils/message.generator.spec.ts +++ b/curl/src/utils/message.generator.spec.ts @@ -20,9 +20,7 @@ describe('MessageGenerator', () => { it('should generate a message with the specified size', () => { const sizeInBytes = 10; const generatedMessage = MessageGenerator.generate(sizeInBytes); - - // The length of the generated message in hex should be double the size in bytes - expect(generatedMessage.length).toBe(sizeInBytes * 2); + expect(generatedMessage.length).toBe(sizeInBytes); }); }); }); diff --git a/curl/src/utils/message.generator.ts b/curl/src/utils/message.generator.ts index dab42852..05c19fc7 100644 --- a/curl/src/utils/message.generator.ts +++ b/curl/src/utils/message.generator.ts @@ -1,10 +1,9 @@ -import { Injectable } from '@nestjs/common'; -import * as crypto from 'crypto'; +import {Injectable} from '@nestjs/common'; @Injectable() export class MessageGenerator { static generate(sizeInBytes: number) { - const randomBytes = crypto.randomBytes(sizeInBytes); - return randomBytes.toString('hex'); - } + // Generate the string by repeating the character 'a' + return 'a'.repeat(sizeInBytes); } + } From 314020428d968e024b527d122d581a01e9986b38 Mon Sep 17 00:00:00 2001 From: kr172t Date: Wed, 24 Jan 2024 11:16:29 +0200 Subject: [PATCH 15/35] update example in README.md --- api/README.md | 5 ++++- api/src/services/analyze_service.py | 4 +--- api/src/utils/test_suite_serializer.py | 4 ++-- api/tests/test_tests_api.py | 2 +- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/api/README.md b/api/README.md index bd2a2ca4..9e6e8a8d 100644 --- a/api/README.md +++ b/api/README.md @@ -31,8 +31,11 @@ python3 -m src.main curl --location 'http://localhost:3020/qujata-api/analyze' \ --header 'Content-Type: application/json' \ --data '{ + "experimentName": "name", + "description" : "test description", "algorithms": ["kyber512"], - "iterationsCount": 5 + "iterationsCount": [5], + "messageSizes": [10] }' ``` diff --git a/api/src/services/analyze_service.py b/api/src/services/analyze_service.py index 46aeaadf..ef07c43d 100644 --- a/api/src/services/analyze_service.py +++ b/api/src/services/analyze_service.py @@ -20,9 +20,7 @@ def analyze(data): start_time = int(datetime.timestamp(datetime.now() - timedelta(seconds=60)) * 1000) iterations_count = data['iterationsCount'] algorithms = data['algorithms'] - message_sizes = [0] - if 'messageSizes' in data: - message_sizes = data['messageSizes'] + message_sizes = data['messageSizes'] if 'messageSizes' in data else [0] first_run = True for algorithm in algorithms: for iterations in iterations_count: diff --git a/api/src/utils/test_suite_serializer.py b/api/src/utils/test_suite_serializer.py index eca362f9..502aba23 100644 --- a/api/src/utils/test_suite_serializer.py +++ b/api/src/utils/test_suite_serializer.py @@ -5,11 +5,11 @@ def serialize(test_suite): "id": test_suite.id, "name": test_suite.name, "description": test_suite.description, - "codeRelease": test_suite.code_release, + "code_release": test_suite.code_release, "start_time": test_suite.start_time, "end_time": test_suite.end_time, "environment_info": __get_environment_info(test_suite.env_info), - "testRuns": __get_test_runs_metrics(test_suite.test_runs) + "test_runs": __get_test_runs_metrics(test_suite.test_runs) } return response_data diff --git a/api/tests/test_tests_api.py b/api/tests/test_tests_api.py index ff1e1bd5..08989c3e 100644 --- a/api/tests/test_tests_api.py +++ b/api/tests/test_tests_api.py @@ -70,7 +70,7 @@ def test_get_test_suite(self): self.app.database_manager.get_by_id.return_value = test_suite response = self.client.get(TEST_SUITES_GET_URL) result = json.loads(response.data) - expected = {'codeRelease': '1.1.0', 'description': 'description', 'end_time': None, 'environment_info': {'cpu': None, 'cpuArchitecture': None, 'cpuClockSpeed': None, 'cpuCores': None, 'nodeSize': None, 'operatingSystem': None, 'resourceName': None}, 'id': None, 'name': 'name', 'start_time': None, 'testRuns': [{'algorithm': None, 'id': 1, 'iterations': None, 'results': {'averageCPU': 9.0, 'averageMemory': 14}}]} + expected = {'code_release': '1.1.0', 'description': 'description', 'end_time': None, 'environment_info': {'cpu': None, 'cpuArchitecture': None, 'cpuClockSpeed': None, 'cpuCores': None, 'nodeSize': None, 'operatingSystem': None, 'resourceName': None}, 'id': None, 'name': 'name', 'start_time': None, 'test_runs': [{'algorithm': None, 'id': 1, 'iterations': None, 'results': {'averageCPU': 9.0, 'averageMemory': 14}}]} self.assertEqual(result, expected) def test_get_test_suite_return_not_found(self): From 7eadad64e0c78fb1b5e3ab3e66938f6360a2cd22 Mon Sep 17 00:00:00 2001 From: iadibar Date: Thu, 25 Jan 2024 13:29:02 +0200 Subject: [PATCH 16/35] add custom selector component --- .../experiment/components/charts/Charts.tsx | 6 +- .../dynamic-chart/DynamicChart.module.scss | 28 +++++++ .../components/dynamic-chart/DynamicChart.tsx | 79 +++++++++++++++++++ .../CustomDropdownIndicator.tsx | 24 ++++++ .../custom-dropdown-indicator/index.ts | 1 + .../custom-option/CustomOption.module.scss | 4 + .../components/custom-option/CustomOption.tsx | 24 ++++++ .../components/custom-option/index.ts | 1 + .../CustomValueContainer.module.scss | 17 ++++ .../CustomValueContainer.tsx | 52 ++++++++++++ .../custom-value-container/index.ts | 1 + .../hooks/useDynamicChartData.ts | 29 +++++++ .../charts/components/dynamic-chart/index.ts | 1 + .../models/dynamic-chart.interface.ts | 20 +++++ .../components/dynamic-chart/translate/en.ts | 14 ++++ .../utils/dynamic-chart.utils.ts | 11 +++ .../src/assets/images/arrow-down-selector.svg | 3 + portal/src/assets/images/bar.svg | 7 ++ portal/src/assets/images/line.svg | 5 ++ 19 files changed, 325 insertions(+), 2 deletions(-) create mode 100644 portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/DynamicChart.module.scss create mode 100644 portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/DynamicChart.tsx create mode 100644 portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/components/custom-dropdown-indicator/CustomDropdownIndicator.tsx create mode 100644 portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/components/custom-dropdown-indicator/index.ts create mode 100644 portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/components/custom-option/CustomOption.module.scss create mode 100644 portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/components/custom-option/CustomOption.tsx create mode 100644 portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/components/custom-option/index.ts create mode 100644 portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/components/custom-value-container/CustomValueContainer.module.scss create mode 100644 portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/components/custom-value-container/CustomValueContainer.tsx create mode 100644 portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/components/custom-value-container/index.ts create mode 100644 portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/hooks/useDynamicChartData.ts create mode 100644 portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/index.ts create mode 100644 portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/models/dynamic-chart.interface.ts create mode 100644 portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/translate/en.ts create mode 100644 portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/utils/dynamic-chart.utils.ts create mode 100644 portal/src/assets/images/arrow-down-selector.svg create mode 100644 portal/src/assets/images/bar.svg create mode 100644 portal/src/assets/images/line.svg diff --git a/portal/src/app/components/home/components/experiment/components/charts/Charts.tsx b/portal/src/app/components/home/components/experiment/components/charts/Charts.tsx index 2eab6ea3..af190114 100644 --- a/portal/src/app/components/home/components/experiment/components/charts/Charts.tsx +++ b/portal/src/app/components/home/components/experiment/components/charts/Charts.tsx @@ -3,6 +3,7 @@ import { BarChart } from '../../../../../dashboard/components/charts/BarChart'; import { LineChart } from '../../../../../dashboard/components/charts/LineChart'; import { IExperimentData } from '../../Experiment'; import styles from './Charts.module.scss'; +import { DynamicChart } from './components/dynamic-chart'; import { useChartsData } from './hooks/useChartsData'; import { tooltipKeys, tooltipLabels } from './models/bar-chart.const'; import { CHARTS_EN } from './translate/en'; @@ -13,7 +14,8 @@ export const Charts: React.FC = (props: IExperimentData) => { return (
    -
    {CHARTS_EN.TITLE}
    + + {/*
    {CHARTS_EN.TITLE}
    <>
    {barChartKeysOfData.map((key, index) => ( @@ -45,7 +47,7 @@ export const Charts: React.FC = (props: IExperimentData) => { ); })}
    - + */}
    ); } diff --git a/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/DynamicChart.module.scss b/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/DynamicChart.module.scss new file mode 100644 index 00000000..0e8150a5 --- /dev/null +++ b/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/DynamicChart.module.scss @@ -0,0 +1,28 @@ +@import "src/styles/variables-keys"; + +.chart_wrapper { + inline-size: 880px; + block-size: 640px; + background-color: var($primaryWhite); + border: 1px solid #BDC2C7; + padding: 36px; +} + +.chart_filters { + display: flex; + justify-content: space-between; +} + +.select_item { + inline-size: 260px; + margin-inline-start: 16px; +} + +.select_type_item { + inline-size: 187px; +} + +.select_item_wrapper { + display: flex; + align-items: center; +} diff --git a/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/DynamicChart.tsx b/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/DynamicChart.tsx new file mode 100644 index 00000000..873f2a7b --- /dev/null +++ b/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/DynamicChart.tsx @@ -0,0 +1,79 @@ +import { chartTypeOptions, xAxisTypeOptions } from "./models/dynamic-chart.interface"; +import styles from './DynamicChart.module.scss'; +import { AttSelect, AttSelectOption, OnSelectChanged } from "../../../../../../../../shared/components/att-select"; +import { useCallback, useState } from "react"; +import { SelectOptionType } from "../../../../../../../protocol-query"; +import { ITestRunResult } from "../../../../../../../../shared/models/test-run-result.interface"; +import { useDynamicChartData } from "./hooks/useDynamicChartData"; +import { DYNAMIC_CHART_EN } from "./translate/en"; +import { CustomValueContainer } from "./components/custom-value-container"; +import { CustomOption } from "./components/custom-option"; +import { CustomDropdownIndicator } from "./components/custom-dropdown-indicator"; + +export interface DynamicChartProps { + chartData: ITestRunResult; +} +export const DynamicChart: React.FC = (props: DynamicChartProps) => { + const { chartData } = props; + const { yAxiosOptions } = useDynamicChartData(chartData); + const [chartType, setChartType] = useState(); + const [xAxisValue, setXAxisValue] = useState(); + const [yAxisValue, setYAxisValue] = useState(); + + const onChartTypeChanged: OnSelectChanged = useCallback((options: SelectOptionType): void => { + const selectedChartType: AttSelectOption = options as AttSelectOption; + setChartType(selectedChartType); + }, []); + + const onXAxisValueChanged: OnSelectChanged = useCallback((options: SelectOptionType): void => { + const selectedXAxisValue: AttSelectOption = options as AttSelectOption; + setXAxisValue(selectedXAxisValue); + }, []); + + const onYAxisValueChanged: OnSelectChanged = useCallback((options: SelectOptionType): void => { + const selectedYAxisValue: AttSelectOption = options as AttSelectOption; + setYAxisValue(selectedYAxisValue); + }, []); + + return ( +
    +
    +
    + + +
    +
    + + +
    +
    + +
    +
    +
    + ); +} diff --git a/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/components/custom-dropdown-indicator/CustomDropdownIndicator.tsx b/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/components/custom-dropdown-indicator/CustomDropdownIndicator.tsx new file mode 100644 index 00000000..83d903f9 --- /dev/null +++ b/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/components/custom-dropdown-indicator/CustomDropdownIndicator.tsx @@ -0,0 +1,24 @@ +import { useCallback } from "react"; +import { DropdownIndicatorProps, components } from "react-select"; +import { AttSelectOption } from "../../../../../../../../../../shared/components/att-select"; +import { ReactComponent as ArrowDownSelectorSvg } from '../../../../../../../../../../../assets/images/arrow-down-selector.svg'; + +export const CustomDropdownIndicator: React.FC> = (props) => { + const { selectProps } = props; + + const handleClick: () => void = useCallback((): void => { + if (selectProps.menuIsOpen) { + selectProps.onMenuClose(); + } else { + selectProps.onMenuOpen(); + } + }, [selectProps]); + + return ( +
    + + + +
    + ); +}; diff --git a/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/components/custom-dropdown-indicator/index.ts b/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/components/custom-dropdown-indicator/index.ts new file mode 100644 index 00000000..ec30bda8 --- /dev/null +++ b/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/components/custom-dropdown-indicator/index.ts @@ -0,0 +1 @@ +export * from './CustomDropdownIndicator'; diff --git a/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/components/custom-option/CustomOption.module.scss b/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/components/custom-option/CustomOption.module.scss new file mode 100644 index 00000000..21dbc02b --- /dev/null +++ b/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/components/custom-option/CustomOption.module.scss @@ -0,0 +1,4 @@ +.icon { + inline-size: 14px; + margin-inline-end: 12px; +} diff --git a/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/components/custom-option/CustomOption.tsx b/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/components/custom-option/CustomOption.tsx new file mode 100644 index 00000000..71ec0987 --- /dev/null +++ b/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/components/custom-option/CustomOption.tsx @@ -0,0 +1,24 @@ +import { GroupBase, OptionProps, components } from "react-select"; +import styles from './CustomOption.module.scss'; +import { AttSelectOption } from "../../../../../../../../../../shared/components/att-select"; +import { capitalizeFirstLetter, getIconByValue } from "../../utils/dynamic-chart.utils"; + +export type SelectorCustomOptionProps = OptionProps, true, GroupBase>> & { + onOptionChanged: (option: AttSelectOption) => void; + showInputOption: boolean; + setShowInputOption: (show: boolean) => void; + inputValue: string; + setInputValue: (value: string) => void; + setMenuIsOpen: (isOpen: boolean) => void; +}; + +export const CustomOption: React.FC = (props: SelectorCustomOptionProps) => { + const option: any = props.data; + + return ( + + {option.value} + {capitalizeFirstLetter(option.value)} + + ); +}; diff --git a/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/components/custom-option/index.ts b/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/components/custom-option/index.ts new file mode 100644 index 00000000..e3f9e38d --- /dev/null +++ b/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/components/custom-option/index.ts @@ -0,0 +1 @@ +export * from './CustomOption'; diff --git a/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/components/custom-value-container/CustomValueContainer.module.scss b/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/components/custom-value-container/CustomValueContainer.module.scss new file mode 100644 index 00000000..3144f3ce --- /dev/null +++ b/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/components/custom-value-container/CustomValueContainer.module.scss @@ -0,0 +1,17 @@ +.icon { + inline-size: 14px; + margin-inline-end: 12px; +} + +.input_wrapper { + display: flex; + align-items: center; +} + +.value { + margin-block-start: 5px; +} + +.placeholder { + color: #878c94; +} diff --git a/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/components/custom-value-container/CustomValueContainer.tsx b/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/components/custom-value-container/CustomValueContainer.tsx new file mode 100644 index 00000000..be5e5869 --- /dev/null +++ b/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/components/custom-value-container/CustomValueContainer.tsx @@ -0,0 +1,52 @@ +import { ValueContainerProps, components } from "react-select" +import { ChartType } from "../../models/dynamic-chart.interface"; +import styles from './CustomValueContainer.module.scss'; +import { AttSelectOption } from "../../../../../../../../../../shared/components/att-select"; +import { PropsWithChildren, RefObject, useCallback, useEffect, useRef } from "react"; +import cn from 'classnames'; +import { capitalizeFirstLetter, getIconByValue } from "../../utils/dynamic-chart.utils"; + +export const CustomValueContainer: React.FC> = (props: PropsWithChildren>) => { + const placeholder: string = props.selectProps?.placeholder as string; + const inputValue: string = props.selectProps?.inputValue as string; + const containerRef = useRef(null); + + useOutsideClick(containerRef, () => { + if (props.selectProps.menuIsOpen) { + props.selectProps.onMenuClose(); + } + }); + + const handleClick: () => void = useCallback((): void => { + if (!props.selectProps.menuIsOpen) { + props.selectProps.onMenuOpen(); + } + }, [props.selectProps]); + + return ( +
    + +
    + {props.hasValue && {inputValue}} + {props.hasValue && props.getValue() ? capitalizeFirstLetter(props.getValue()[0]?.value) : placeholder} +
    +
    +
    + ) +} + +const useOutsideClick = (ref: RefObject, callback: () => void) => { + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if (ref.current && !ref.current.contains(event.target as Node)) { + callback(); + } + }; + + document.addEventListener('mousedown', handleClickOutside); + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; + }, [ref, callback]); +}; + \ No newline at end of file diff --git a/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/components/custom-value-container/index.ts b/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/components/custom-value-container/index.ts new file mode 100644 index 00000000..351a4ecc --- /dev/null +++ b/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/components/custom-value-container/index.ts @@ -0,0 +1 @@ +export * from './CustomValueContainer'; diff --git a/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/hooks/useDynamicChartData.ts b/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/hooks/useDynamicChartData.ts new file mode 100644 index 00000000..dab39c7f --- /dev/null +++ b/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/hooks/useDynamicChartData.ts @@ -0,0 +1,29 @@ +import { useEffect, useState } from "react"; +import { AttSelectOption } from "../../../../../../../../../shared/components/att-select"; +import { ITestRunResult } from "../../../../../../../../../shared/models/test-run-result.interface"; + +export interface IUseDynamicChartData { + yAxiosOptions: AttSelectOption[]; +} +export function useDynamicChartData(chartData: ITestRunResult): IUseDynamicChartData { + const [yAxiosOptions, setYAxiosOptions] = useState([]); + + useEffect(() => { + const uniqueKeys = new Set(); + + for (const testRun of chartData.testRuns) { + const results = testRun.results; + for (const key in results) { + uniqueKeys.add(key); + } + } + + if (uniqueKeys.size > 0) { + setYAxiosOptions(Array.from(uniqueKeys).map(key => ({ label: key, value: key }))); + } + }, [chartData]); + + return { + yAxiosOptions, + }; +} diff --git a/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/index.ts b/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/index.ts new file mode 100644 index 00000000..53135f2a --- /dev/null +++ b/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/index.ts @@ -0,0 +1 @@ +export * from './DynamicChart'; diff --git a/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/models/dynamic-chart.interface.ts b/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/models/dynamic-chart.interface.ts new file mode 100644 index 00000000..8b1f0a15 --- /dev/null +++ b/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/models/dynamic-chart.interface.ts @@ -0,0 +1,20 @@ +import { AttSelectOption } from "../../../../../../../../../shared/components/att-select"; + +export enum ChartType { + LINE = 'line', + BAR = 'bar', +} + +export const chartTypeOptions: AttSelectOption[] = Object.keys(ChartType).map((key) => ({ + value: ChartType[key as keyof typeof ChartType], + label: key, +})); + +enum XAxisType { + NUMBER_OF_ITERATIONS = 'number of iterations', +} + +export const xAxisTypeOptions: AttSelectOption[] = Object.keys(XAxisType).map((key) => ({ + value: XAxisType[key as keyof typeof XAxisType], + label: key, +})); diff --git a/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/translate/en.ts b/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/translate/en.ts new file mode 100644 index 00000000..f96454f8 --- /dev/null +++ b/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/translate/en.ts @@ -0,0 +1,14 @@ +export const DYNAMIC_CHART_EN = { + SELECTORS: { + LABELS: { + Y_AXIOS: 'Y:', + X_AXIOS: 'X:', + }, + PLACEHOLDERS: { + Y_AXIOS: 'Select Y axios', + X_AXIOS: 'Select X axios', + CHART_TYPE: 'Select chart type', + }, + }, +}; + \ No newline at end of file diff --git a/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/utils/dynamic-chart.utils.ts b/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/utils/dynamic-chart.utils.ts new file mode 100644 index 00000000..ab18b8d9 --- /dev/null +++ b/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/utils/dynamic-chart.utils.ts @@ -0,0 +1,11 @@ +import { ChartType } from "../models/dynamic-chart.interface"; +import LineSvg from '../../../../../../../../../../../src/assets/images/line.svg'; +import BarSvg from '../../../../../../../../../../../src/assets/images/bar.svg'; + +export function getIconByValue(value: ChartType): string { + return value === ChartType.LINE ? LineSvg : BarSvg; +} + +export function capitalizeFirstLetter(str: string): string { + return str.charAt(0).toUpperCase() + str.slice(1); +} diff --git a/portal/src/assets/images/arrow-down-selector.svg b/portal/src/assets/images/arrow-down-selector.svg new file mode 100644 index 00000000..70254ea4 --- /dev/null +++ b/portal/src/assets/images/arrow-down-selector.svg @@ -0,0 +1,3 @@ + diff --git a/portal/src/assets/images/bar.svg b/portal/src/assets/images/bar.svg new file mode 100644 index 00000000..bd0f62e7 --- /dev/null +++ b/portal/src/assets/images/bar.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/portal/src/assets/images/line.svg b/portal/src/assets/images/line.svg new file mode 100644 index 00000000..5eaef2ea --- /dev/null +++ b/portal/src/assets/images/line.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file From 0c8a97080926cddabb23da5c9f8f82cf0ce5e868 Mon Sep 17 00:00:00 2001 From: iadibar Date: Thu, 25 Jan 2024 13:30:27 +0200 Subject: [PATCH 17/35] new hook useOutsideClick --- .../CustomValueContainer.tsx | 18 ++---------------- portal/src/app/hooks/useOutsideClick.ts | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 16 deletions(-) create mode 100644 portal/src/app/hooks/useOutsideClick.ts diff --git a/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/components/custom-value-container/CustomValueContainer.tsx b/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/components/custom-value-container/CustomValueContainer.tsx index be5e5869..907852eb 100644 --- a/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/components/custom-value-container/CustomValueContainer.tsx +++ b/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/components/custom-value-container/CustomValueContainer.tsx @@ -2,9 +2,10 @@ import { ValueContainerProps, components } from "react-select" import { ChartType } from "../../models/dynamic-chart.interface"; import styles from './CustomValueContainer.module.scss'; import { AttSelectOption } from "../../../../../../../../../../shared/components/att-select"; -import { PropsWithChildren, RefObject, useCallback, useEffect, useRef } from "react"; +import { PropsWithChildren, useCallback, useRef } from "react"; import cn from 'classnames'; import { capitalizeFirstLetter, getIconByValue } from "../../utils/dynamic-chart.utils"; +import { useOutsideClick } from "../../../../../../../../../../hooks/useOutsideClick"; export const CustomValueContainer: React.FC> = (props: PropsWithChildren>) => { const placeholder: string = props.selectProps?.placeholder as string; @@ -34,19 +35,4 @@ export const CustomValueContainer: React.FC ) } - -const useOutsideClick = (ref: RefObject, callback: () => void) => { - useEffect(() => { - const handleClickOutside = (event: MouseEvent) => { - if (ref.current && !ref.current.contains(event.target as Node)) { - callback(); - } - }; - - document.addEventListener('mousedown', handleClickOutside); - return () => { - document.removeEventListener('mousedown', handleClickOutside); - }; - }, [ref, callback]); -}; \ No newline at end of file diff --git a/portal/src/app/hooks/useOutsideClick.ts b/portal/src/app/hooks/useOutsideClick.ts new file mode 100644 index 00000000..45794b9d --- /dev/null +++ b/portal/src/app/hooks/useOutsideClick.ts @@ -0,0 +1,16 @@ +import { RefObject, useEffect } from "react"; + +export const useOutsideClick = (ref: RefObject, callback: () => void) => { + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if (ref.current && !ref.current.contains(event.target as Node)) { + callback(); + } + }; + + document.addEventListener('mousedown', handleClickOutside); + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; + }, [ref, callback]); +}; From 4d0eec5412ca585446962e5f48f517c101ed44bc Mon Sep 17 00:00:00 2001 From: Ohad Koren Date: Thu, 25 Jan 2024 14:22:17 +0200 Subject: [PATCH 18/35] after review --- .../all-experiments/Experiments.module.scss | 11 +++---- .../all-experiments/Experiments.tsx | 32 +++++++++++-------- .../hooks/useExperimentsData.test.ts | 5 +-- .../hooks/useExperimentsData.ts | 16 ++-------- .../models/experiments.interface.ts | 12 +++++++ .../parse-experiments-data.utils.test.ts | 2 +- .../utils/parse-experiments-data.utils.ts | 2 +- portal/src/app/components/home/Home.tsx | 4 +-- .../protocol-query/ProtocolQuery.tsx | 2 +- .../hooks/useDuplicateData.test.ts | 4 +-- .../protocol-query/hooks/useDuplicateData.ts | 2 +- 11 files changed, 48 insertions(+), 44 deletions(-) create mode 100644 portal/src/app/components/all-experiments/models/experiments.interface.ts diff --git a/portal/src/app/components/all-experiments/Experiments.module.scss b/portal/src/app/components/all-experiments/Experiments.module.scss index 7fc3fb5f..866d147f 100644 --- a/portal/src/app/components/all-experiments/Experiments.module.scss +++ b/portal/src/app/components/all-experiments/Experiments.module.scss @@ -1,10 +1,8 @@ @import "src/styles/variables-keys"; .experiments_wrapper { - padding-block-start: 40px; - padding-block-end: 40px; - padding-inline-start: 80px; - padding-inline-end: 80px; + padding-inline: 80px; + padding-block: 40px; } .title_options_container { @@ -48,8 +46,7 @@ } .experiments_table { - margin-block-start: 60px; - margin-block-end: 60px; + margin-block: 60px; th, td { text-align: left; @@ -86,7 +83,7 @@ inline-size: 100%; block-size: 100%; position: absolute; - background-color: #fff; + background-color: var($primaryWhite); opacity: 0.6; inset-block-start: 0; inset-inline-start: 0; diff --git a/portal/src/app/components/all-experiments/Experiments.tsx b/portal/src/app/components/all-experiments/Experiments.tsx index 67f76dd5..0d8c83e5 100644 --- a/portal/src/app/components/all-experiments/Experiments.tsx +++ b/portal/src/app/components/all-experiments/Experiments.tsx @@ -1,7 +1,7 @@ import { ReactNode, useCallback, useEffect, useMemo, useState } from 'react'; import styles from './Experiments.module.scss'; import cn from 'classnames'; -import { ExperimentData, IUseExperimentsData, useExperimentsData } from './hooks'; +import { IUseExperimentsData, useExperimentsData } from './hooks'; import { FetchDataStatus, IHttp, useFetch } from '../../shared/hooks/useFetch'; import { Spinner, SpinnerSize } from '../../shared/components/att-spinner'; import { ALL_EXPERIMENTS_TABLE_EN } from './translate/en'; @@ -20,15 +20,18 @@ import TrashHoverSvg from '../../../assets/images/trash-hover.svg'; import DuplicateSvg from '../../../assets/images/duplicate.svg'; import { DeleteExperimentModal } from '../home/components/experiment/components/delete-experiment-modal'; import { parseExperimentsData } from './utils/parse-experiments-data.utils'; +import { ISpinner, useSpinnerContext } from '../../shared/context/spinner'; +import { ExperimentData } from './models/experiments.interface'; const DeleteAriaLabel: string = ALL_EXPERIMENTS_TABLE_EN.BUTTONS.DELETE; const DuplicateAriaLabel: string = ALL_EXPERIMENTS_TABLE_EN.TABLE_COLUMNS.LINKS.DUPLICATE; export const Experiments: React.FC = () => { - const { test_suites, status }: IUseExperimentsData = useExperimentsData(); + const { testSuites, status }: IUseExperimentsData = useExperimentsData(); const [openDeleteModal, setOpenDeleteModal] = useState(false); const [checkedRows, setCheckedRows] = useState>({}); - const experimentsData = useMemo(() => (parseExperimentsData(test_suites)), [test_suites]); + const experimentsData = useMemo(() => (testSuites ? parseExperimentsData(testSuites): []), [testSuites]); + const { isSpinnerOn }: ISpinner = useSpinnerContext(); const navigate = useNavigate(); const { post, status: deleteStatus, error: deleteError, cancelRequest: cancelRequestDelete }: IHttp @@ -142,10 +145,11 @@ export const Experiments: React.FC = () => { return (
    - {status === FetchDataStatus.Fetching ? renderSpinner() : ( - <> + <> + {isSpinnerOn && renderSpinner()} + { status === FetchDataStatus.Success &&
    - + {Object.values(checkedRows).some((value: boolean) => value) && ( )}
    - {experimentsData.length > 0 &&
    } - {openDeleteModal && } - - )} + } + {experimentsData.length > 0 &&
    } + {openDeleteModal && } + ); } function renderSpinner() { return ( -
    -
    - +
    +
    + +
    -
    ); } diff --git a/portal/src/app/components/all-experiments/hooks/useExperimentsData.test.ts b/portal/src/app/components/all-experiments/hooks/useExperimentsData.test.ts index cee957b7..9318385d 100644 --- a/portal/src/app/components/all-experiments/hooks/useExperimentsData.test.ts +++ b/portal/src/app/components/all-experiments/hooks/useExperimentsData.test.ts @@ -1,6 +1,7 @@ import { renderHook } from '@testing-library/react'; import { useFetch } from '../../../shared/hooks/useFetch'; -import { Experiment, useExperimentsData } from './useExperimentsData'; +import { Experiment } from '../models/experiments.interface'; +import { useExperimentsData } from './useExperimentsData'; jest.mock('../../../shared/hooks/useFetch', () => ({ useFetch: jest.fn(), @@ -64,6 +65,6 @@ describe('useExperimentsData', () => { }); const { result } = renderHook(() => useExperimentsData()); - expect(result.current.test_suites.length).toEqual(allExperimentsMockData.length); + expect(result.current.testSuites.length).toEqual(allExperimentsMockData.length); }); }); \ No newline at end of file diff --git a/portal/src/app/components/all-experiments/hooks/useExperimentsData.ts b/portal/src/app/components/all-experiments/hooks/useExperimentsData.ts index b10563e2..4e872e5e 100644 --- a/portal/src/app/components/all-experiments/hooks/useExperimentsData.ts +++ b/portal/src/app/components/all-experiments/hooks/useExperimentsData.ts @@ -3,22 +3,12 @@ import { useEffect, useState } from 'react'; import { APIS } from '../../../apis'; import { useFetchSpinner } from '../../../shared/hooks/useFetchSpinner'; import { useErrorMessage } from '../../../hooks/useErrorMessage'; -import { ITestRunResult, ITestRunResultData } from '../../../shared/models/test-run-result.interface'; - -export type TestRunSubset = Pick; -export type Experiment = Pick & { test_runs: TestRunSubset[] }; +import { Experiment } from '../models/experiments.interface'; export interface IUseExperimentsData { - test_suites: Experiment[]; + testSuites: Experiment[]; status: FetchDataStatus; } -export interface ExperimentData { - id: number; - name: string; - algorithms: string[]; - iterations: number[]; - end_time: number; -}; export function useExperimentsData(): IUseExperimentsData { const [allExperiments, setAllExperiments] = useState([]); @@ -38,5 +28,5 @@ export function useExperimentsData(): IUseExperimentsData { } }, [data, status, allExperiments]); - return { test_suites: allExperiments, status }; + return { testSuites: allExperiments, status }; } diff --git a/portal/src/app/components/all-experiments/models/experiments.interface.ts b/portal/src/app/components/all-experiments/models/experiments.interface.ts new file mode 100644 index 00000000..ab9f8f73 --- /dev/null +++ b/portal/src/app/components/all-experiments/models/experiments.interface.ts @@ -0,0 +1,12 @@ +import { ITestRunResult, ITestRunResultData } from '../../../shared/models/test-run-result.interface'; + +export type TestRunSubset = Pick; +export type Experiment = Pick & { test_runs: TestRunSubset[] }; + +export interface ExperimentData { + id: number; + name: string; + algorithms: string[]; + iterations: number[]; + end_time: number; +}; diff --git a/portal/src/app/components/all-experiments/utils/parse-experiments-data.utils.test.ts b/portal/src/app/components/all-experiments/utils/parse-experiments-data.utils.test.ts index 241ed2ff..6695e3da 100644 --- a/portal/src/app/components/all-experiments/utils/parse-experiments-data.utils.test.ts +++ b/portal/src/app/components/all-experiments/utils/parse-experiments-data.utils.test.ts @@ -1,6 +1,6 @@ import { parseExperimentsData } from './parse-experiments-data.utils'; import { ITestRunResultData } from '../../../shared/models/test-run-result.interface'; -import { Experiment, ExperimentData } from '../hooks'; +import { Experiment, ExperimentData } from '../models/experiments.interface'; describe('parseExperimentsData', () => { it('should parse experiments data correctly', () => { diff --git a/portal/src/app/components/all-experiments/utils/parse-experiments-data.utils.ts b/portal/src/app/components/all-experiments/utils/parse-experiments-data.utils.ts index f1abd602..3ddfa6ac 100644 --- a/portal/src/app/components/all-experiments/utils/parse-experiments-data.utils.ts +++ b/portal/src/app/components/all-experiments/utils/parse-experiments-data.utils.ts @@ -1,4 +1,4 @@ -import { Experiment, ExperimentData, TestRunSubset } from '../hooks'; +import { Experiment, ExperimentData, TestRunSubset } from '../models/experiments.interface'; export function parseExperimentsData(test_suites: Experiment[]) { const experimentsData: ExperimentData[] = []; diff --git a/portal/src/app/components/home/Home.tsx b/portal/src/app/components/home/Home.tsx index d762aa59..e05f1b2b 100644 --- a/portal/src/app/components/home/Home.tsx +++ b/portal/src/app/components/home/Home.tsx @@ -6,7 +6,7 @@ import { SubHeader } from "../sub-header"; import { useCallback, useEffect, useState } from 'react'; import styles from './Home.module.scss'; import { useLocation, useNavigate } from "react-router-dom"; -import { ExperimentData } from "../all-experiments/hooks"; +import { ExperimentData } from "../all-experiments/models/experiments.interface"; export const Home: React.FC = () => { const [isSubHeaderOpen, setIsSubHeaderOpen] = useState(true); @@ -32,7 +32,7 @@ export const HomeContent: React.FC = () => { useEffect(() => { // Clear the state after the duplicate data has been created setDuplicateData(undefined); - }, [location]); + }, []); useEffect(() => { if (status === FetchDataStatus.Success && testSuiteId) { diff --git a/portal/src/app/components/protocol-query/ProtocolQuery.tsx b/portal/src/app/components/protocol-query/ProtocolQuery.tsx index f03a41cc..c4865ea5 100644 --- a/portal/src/app/components/protocol-query/ProtocolQuery.tsx +++ b/portal/src/app/components/protocol-query/ProtocolQuery.tsx @@ -10,7 +10,7 @@ import { Spinner, SpinnerSize } from '../../shared/components/att-spinner'; import { useGetAlgorithms, useGetIterations } from './hooks'; import { handleAlgorithmsSelection } from './utils'; import { AlgorithmsSelectorCustomOption, IterationsSelectorCustomOption } from '../../shared/components/selector-custom-option'; -import { ExperimentData } from '../all-experiments/hooks'; +import { ExperimentData } from '../all-experiments/models/experiments.interface'; import { useDuplicateData } from './hooks'; export type SelectOptionType = AttSelectOption | Options | null; diff --git a/portal/src/app/components/protocol-query/hooks/useDuplicateData.test.ts b/portal/src/app/components/protocol-query/hooks/useDuplicateData.test.ts index ad54f078..b1eefe96 100644 --- a/portal/src/app/components/protocol-query/hooks/useDuplicateData.test.ts +++ b/portal/src/app/components/protocol-query/hooks/useDuplicateData.test.ts @@ -1,7 +1,7 @@ -import { renderHook, act } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react-hooks'; import { useDuplicateData, DuplicateData } from './useDuplicateData'; import { AttSelectOption } from '../../../shared/components/att-select'; -import { ExperimentData } from '../../all-experiments/hooks'; +import { ExperimentData } from '../../all-experiments/models/experiments.interface'; describe('useDuplicateData', () => { it('should set experiment name, algorithms, and iterations count when duplicate data is provided', () => { diff --git a/portal/src/app/components/protocol-query/hooks/useDuplicateData.ts b/portal/src/app/components/protocol-query/hooks/useDuplicateData.ts index d2e9b7c3..8cf59d88 100644 --- a/portal/src/app/components/protocol-query/hooks/useDuplicateData.ts +++ b/portal/src/app/components/protocol-query/hooks/useDuplicateData.ts @@ -1,6 +1,6 @@ import { useEffect } from 'react'; import { AttSelectOption } from '../../../shared/components/att-select'; -import { ExperimentData } from '../../all-experiments/hooks'; +import { ExperimentData } from '../../all-experiments/models/experiments.interface'; export type DuplicateData = { data: ExperimentData | undefined, From b15de99d9133ae8b1afefd7749be4009895ff162 Mon Sep 17 00:00:00 2001 From: iadibar Date: Thu, 25 Jan 2024 14:27:24 +0200 Subject: [PATCH 19/35] get color my algorithm name & set graph by selectors values --- .../charts/BarChart/BarChart.module.scss | 8 ---- .../components/charts/BarChart/BarChart.tsx | 17 +++++---- .../charts/BarChart/barChart.const.ts | 2 +- .../charts/LineChart/LineChart.module.scss | 8 ---- .../components/charts/LineChart/LineChart.tsx | 8 ++-- .../components/charts/utils/charts.utils.ts | 15 ++++++++ .../dynamic-chart/DynamicChart.module.scss | 2 +- .../components/dynamic-chart/DynamicChart.tsx | 37 +++++++++++++++++-- .../models/dynamic-chart.interface.ts | 6 +-- .../components/dynamic-chart/translate/en.ts | 3 ++ .../utils/dynamic-chart.utils.ts | 5 +++ .../components/charts/hooks/useChartsData.ts | 5 ++- 12 files changed, 78 insertions(+), 38 deletions(-) create mode 100644 portal/src/app/components/dashboard/components/charts/utils/charts.utils.ts diff --git a/portal/src/app/components/dashboard/components/charts/BarChart/BarChart.module.scss b/portal/src/app/components/dashboard/components/charts/BarChart/BarChart.module.scss index b1f95ab7..e69de29b 100644 --- a/portal/src/app/components/dashboard/components/charts/BarChart/BarChart.module.scss +++ b/portal/src/app/components/dashboard/components/charts/BarChart/BarChart.module.scss @@ -1,8 +0,0 @@ -@import "src/styles/variables-keys"; - -.bar { - background-color: var($primaryWhite); - border: 1px solid #BDC2C7; - max-block-size: 450px; - padding: 18px; -} diff --git a/portal/src/app/components/dashboard/components/charts/BarChart/BarChart.tsx b/portal/src/app/components/dashboard/components/charts/BarChart/BarChart.tsx index e1cb5bb3..016929f0 100644 --- a/portal/src/app/components/dashboard/components/charts/BarChart/BarChart.tsx +++ b/portal/src/app/components/dashboard/components/charts/BarChart/BarChart.tsx @@ -2,9 +2,9 @@ import { ChartData, ChartOptions, TooltipItem, Chart, LegendItem, ChartDataset } import { Bar } from 'react-chartjs-2'; import { useEffect, useRef, useState } from 'react'; import { IDatasets } from './models/BarChart.model'; -import { TITLE_PREFIX, colors, defaultOptions } from './barChart.const'; -import styles from './BarChart.module.scss'; +import { TITLE_PREFIX, defaultOptions } from './barChart.const'; import { uniq } from 'lodash'; +import { getColorByName } from '../utils/charts.utils'; export interface BarChartProps { labels: string[]; @@ -13,10 +13,11 @@ export interface BarChartProps { tooltipKeys: string[]; tooltipLabels: string[]; title?: string; + xAxiosTitle?: string; } export const BarChart: React.FC = (props: BarChartProps) => { - const { labels, data, tooltipKeys, tooltipLabels, keyOfData, title } = props; + const { labels, data, tooltipKeys, tooltipLabels, keyOfData, title, xAxiosTitle } = props; const [dataValues, setDataValues] = useState(); const [datasets, setDatasets] = useState([]); const [algorithmsColors, setAlgorithmsColors] = useState<{[key: string]: string}>(); @@ -28,8 +29,8 @@ export const BarChart: React.FC = (props: BarChartProps) => { const algorithms: string[] = uniq(data.map((item: any) => item.algorithm)); const algorithmColors: {[key: string]: string} = {}; - algorithms.forEach((algorithm, index) => { - algorithmColors[algorithm] = colors[index % colors.length]; + algorithms.forEach((algorithm) => { + algorithmColors[algorithm] = getColorByName(algorithm); }); setAlgorithmsColors(algorithmColors); }, [data, keyOfData]); @@ -99,8 +100,8 @@ export const BarChart: React.FC = (props: BarChartProps) => { }, title: { display: true, - text: title, - align: 'start', + text: xAxiosTitle, + align: 'end', font: { size: 18, weight: '500', @@ -153,7 +154,7 @@ export const BarChart: React.FC = (props: BarChartProps) => { (event.currentTarget as HTMLElement).style.cursor = 'default'; }} > - +
    ); } diff --git a/portal/src/app/components/dashboard/components/charts/BarChart/barChart.const.ts b/portal/src/app/components/dashboard/components/charts/BarChart/barChart.const.ts index 71d8f560..a703e5c7 100644 --- a/portal/src/app/components/dashboard/components/charts/BarChart/barChart.const.ts +++ b/portal/src/app/components/dashboard/components/charts/BarChart/barChart.const.ts @@ -1,7 +1,7 @@ import { ChartOptions } from 'chart.js'; import { CHARTS_EN } from '../../../../home/components/experiment/components/charts/translate/en'; -export const colors: string[] = ['#086CE1', '#FF8500', '#05BBFF', '#6D3FFC']; +export const colors: string[] = ['#086CE1', '#FF8500', '#05BBFF', '#6D3FFC', '#E2180B', '#8208E1', '#08E145', '#9AB2C9', '#EB00FF', '#FFC700', '#8F5C47', '#05FFFF', '#FA9BF7']; export let defaultOptions: ChartOptions = { responsive: true, diff --git a/portal/src/app/components/dashboard/components/charts/LineChart/LineChart.module.scss b/portal/src/app/components/dashboard/components/charts/LineChart/LineChart.module.scss index 604c501b..e69de29b 100644 --- a/portal/src/app/components/dashboard/components/charts/LineChart/LineChart.module.scss +++ b/portal/src/app/components/dashboard/components/charts/LineChart/LineChart.module.scss @@ -1,8 +0,0 @@ -@import "src/styles/variables-keys"; - -.line_chart { - background-color: var($primaryWhite); - border: 1px solid #BDC2C7; - max-block-size: 450px; - padding: 18px; -} diff --git a/portal/src/app/components/dashboard/components/charts/LineChart/LineChart.tsx b/portal/src/app/components/dashboard/components/charts/LineChart/LineChart.tsx index 03fed0a5..1f478d00 100644 --- a/portal/src/app/components/dashboard/components/charts/LineChart/LineChart.tsx +++ b/portal/src/app/components/dashboard/components/charts/LineChart/LineChart.tsx @@ -1,17 +1,17 @@ import { Line } from 'react-chartjs-2'; import { ChartOptions, Chart, TooltipItem } from 'chart.js'; import { TITLE_PREFIX, defaultOptions } from './LineChart.const'; -import styles from './LineChart.module.scss'; import { useRef } from 'react'; export interface LineChartProps { data: any; tooltipLabel?: string; title?: string; + xAxiosTitle?: string; } export const LineChart: React.FC = (props: LineChartProps) => { - const { data, title, tooltipLabel } = props; + const { data, title, tooltipLabel, xAxiosTitle } = props; const chartRef = useRef>(null); const options: ChartOptions = { @@ -29,7 +29,7 @@ export const LineChart: React.FC = (props: LineChartProps) => { plugins: { title: { display: true, - text: title, + text: xAxiosTitle, align: 'start', font: { size: 18, @@ -86,7 +86,7 @@ export const LineChart: React.FC = (props: LineChartProps) => { (event.currentTarget as HTMLElement).style.cursor = 'default'; }} > - + ); } diff --git a/portal/src/app/components/dashboard/components/charts/utils/charts.utils.ts b/portal/src/app/components/dashboard/components/charts/utils/charts.utils.ts new file mode 100644 index 00000000..158b5246 --- /dev/null +++ b/portal/src/app/components/dashboard/components/charts/utils/charts.utils.ts @@ -0,0 +1,15 @@ +import { colors } from "../BarChart/barChart.const"; + +export function getColorByName(name: string): string { + const firstLetter: string = name.trim().substring(0, 1); + if (isLetter(firstLetter)) { + const numberFromStr: number = firstLetter.toLowerCase().charCodeAt(0) - 97; + return colors[numberFromStr % colors.length]; + } + + return colors[0]; +} + +function isLetter(str: string) { + return str.length === 1 && str.match(/[a-z]/i); +} diff --git a/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/DynamicChart.module.scss b/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/DynamicChart.module.scss index 0e8150a5..5ab99e00 100644 --- a/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/DynamicChart.module.scss +++ b/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/DynamicChart.module.scss @@ -2,7 +2,7 @@ .chart_wrapper { inline-size: 880px; - block-size: 640px; + min-block-size: 550px; background-color: var($primaryWhite); border: 1px solid #BDC2C7; padding: 36px; diff --git a/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/DynamicChart.tsx b/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/DynamicChart.tsx index 873f2a7b..39a54ca0 100644 --- a/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/DynamicChart.tsx +++ b/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/DynamicChart.tsx @@ -1,7 +1,7 @@ -import { chartTypeOptions, xAxisTypeOptions } from "./models/dynamic-chart.interface"; +import { ChartType, chartTypeOptions, xAxisTypeOptions } from "./models/dynamic-chart.interface"; import styles from './DynamicChart.module.scss'; import { AttSelect, AttSelectOption, OnSelectChanged } from "../../../../../../../../shared/components/att-select"; -import { useCallback, useState } from "react"; +import { useCallback, useEffect, useState } from "react"; import { SelectOptionType } from "../../../../../../../protocol-query"; import { ITestRunResult } from "../../../../../../../../shared/models/test-run-result.interface"; import { useDynamicChartData } from "./hooks/useDynamicChartData"; @@ -9,6 +9,12 @@ import { DYNAMIC_CHART_EN } from "./translate/en"; import { CustomValueContainer } from "./components/custom-value-container"; import { CustomOption } from "./components/custom-option"; import { CustomDropdownIndicator } from "./components/custom-dropdown-indicator"; +import { BarChart } from "../../../../../../../dashboard/components/charts/BarChart"; +import { useChartsData } from "../../hooks/useChartsData"; +import { tooltipKeys, tooltipLabels } from "../../models/bar-chart.const"; +import { getTitleByXAxiosValue } from "./utils/dynamic-chart.utils"; +import { LineChart } from "../../../../../../../dashboard/components/charts/LineChart"; +import { getChartTitleByType } from "../../utils/chart.utils"; export interface DynamicChartProps { chartData: ITestRunResult; @@ -19,7 +25,25 @@ export const DynamicChart: React.FC = (props: DynamicChartPro const [chartType, setChartType] = useState(); const [xAxisValue, setXAxisValue] = useState(); const [yAxisValue, setYAxisValue] = useState(); - + const { barChartData, barChartLabels, lineChartData } = useChartsData({ data: chartData }); + const [lineChartConvertData, setLineChartConvertData] = useState<{labels: number[], datasets: unknown}>(); + + useEffect(() => { + if (lineChartData) { + const datasets = lineChartData.datasets + .filter(dataset => dataset.data[yAxisValue?.value as string]) + .map(dataset => ({ + ...dataset, + data: dataset.data[yAxisValue?.value as string] + })); + + setLineChartConvertData({ + labels: lineChartData.labels, + datasets: datasets.length === 0 ? null : datasets, + }); + } + }, [lineChartData, yAxisValue?.value]); + const onChartTypeChanged: OnSelectChanged = useCallback((options: SelectOptionType): void => { const selectedChartType: AttSelectOption = options as AttSelectOption; setChartType(selectedChartType); @@ -74,6 +98,13 @@ export const DynamicChart: React.FC = (props: DynamicChartPro /> + + {xAxisValue?.value && chartType?.value && yAxisValue?.value && + <> + {chartType?.value === ChartType.BAR && barChartData && } + {chartType?.value === ChartType.LINE && lineChartConvertData && } + + } ); } diff --git a/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/models/dynamic-chart.interface.ts b/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/models/dynamic-chart.interface.ts index 8b1f0a15..e12aeeeb 100644 --- a/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/models/dynamic-chart.interface.ts +++ b/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/models/dynamic-chart.interface.ts @@ -10,11 +10,11 @@ export const chartTypeOptions: AttSelectOption[] = Object.keys(ChartType).map((k label: key, })); -enum XAxisType { +export enum XAxisType { NUMBER_OF_ITERATIONS = 'number of iterations', } export const xAxisTypeOptions: AttSelectOption[] = Object.keys(XAxisType).map((key) => ({ - value: XAxisType[key as keyof typeof XAxisType], - label: key, + value: key, + label: XAxisType[key as keyof typeof XAxisType], })); diff --git a/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/translate/en.ts b/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/translate/en.ts index f96454f8..a2016c96 100644 --- a/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/translate/en.ts +++ b/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/translate/en.ts @@ -10,5 +10,8 @@ export const DYNAMIC_CHART_EN = { CHART_TYPE: 'Select chart type', }, }, + X_VALUES_TITLE: { + ITERATIONS: 'Iterations', + } }; \ No newline at end of file diff --git a/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/utils/dynamic-chart.utils.ts b/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/utils/dynamic-chart.utils.ts index ab18b8d9..416baf5e 100644 --- a/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/utils/dynamic-chart.utils.ts +++ b/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/utils/dynamic-chart.utils.ts @@ -1,6 +1,7 @@ import { ChartType } from "../models/dynamic-chart.interface"; import LineSvg from '../../../../../../../../../../../src/assets/images/line.svg'; import BarSvg from '../../../../../../../../../../../src/assets/images/bar.svg'; +import { DYNAMIC_CHART_EN } from "../translate/en"; export function getIconByValue(value: ChartType): string { return value === ChartType.LINE ? LineSvg : BarSvg; @@ -9,3 +10,7 @@ export function getIconByValue(value: ChartType): string { export function capitalizeFirstLetter(str: string): string { return str.charAt(0).toUpperCase() + str.slice(1); } + +export function getTitleByXAxiosValue(value: string): string { + return value === 'NUMBER_OF_ITERATIONS' ? DYNAMIC_CHART_EN.X_VALUES_TITLE.ITERATIONS : ''; +} \ No newline at end of file diff --git a/portal/src/app/components/home/components/experiment/components/charts/hooks/useChartsData.ts b/portal/src/app/components/home/components/experiment/components/charts/hooks/useChartsData.ts index 40475047..27a91197 100644 --- a/portal/src/app/components/home/components/experiment/components/charts/hooks/useChartsData.ts +++ b/portal/src/app/components/home/components/experiment/components/charts/hooks/useChartsData.ts @@ -4,6 +4,7 @@ import { ITestRunResultData } from "../../../../../../../shared/models/test-run- import { ILineChartData } from "../models/line-chart-data.interface"; import { colors } from "../../../../../../dashboard/components/charts/LineChart/LineChart.const"; import { IExperimentData } from "../../../Experiment"; +import { getColorByName } from "../../../../../../dashboard/components/charts/utils/charts.utils"; export interface IUseChartsData { barChartLabels: string[]; @@ -45,8 +46,8 @@ function processedLineChartData(data: ITestRunResultData[], keysOfData: string[] label: `${algorithm} `, data: data, fill: false, - backgroundColor: colors[index % colors.length], - borderColor: colors[index % colors.length], + backgroundColor: getColorByName(algorithm), + borderColor: getColorByName(algorithm), borderWidth: 1, }; }) From 9196a56403de68e04a85ecd713377e8ecc23cee0 Mon Sep 17 00:00:00 2001 From: Ohad Koren Date: Thu, 25 Jan 2024 14:45:18 +0200 Subject: [PATCH 20/35] table alignment --- .../components/all-experiments/Experiments.module.scss | 9 +++------ .../experiment-table/ExperimentTable.module.scss | 2 ++ .../src/app/shared/components/table/Table.module.scss | 10 +++------- portal/src/app/shared/components/table/Table.tsx | 2 +- 4 files changed, 9 insertions(+), 14 deletions(-) diff --git a/portal/src/app/components/all-experiments/Experiments.module.scss b/portal/src/app/components/all-experiments/Experiments.module.scss index 866d147f..0a9e228d 100644 --- a/portal/src/app/components/all-experiments/Experiments.module.scss +++ b/portal/src/app/components/all-experiments/Experiments.module.scss @@ -3,6 +3,7 @@ .experiments_wrapper { padding-inline: 80px; padding-block: 40px; + margin-block: 60px; } .title_options_container { @@ -46,12 +47,8 @@ } .experiments_table { - margin-block: 60px; - - th, td { - text-align: left; - } - + text-align: left; + th:first-child, td:first-child { text-align: center; diff --git a/portal/src/app/components/home/components/experiment/components/experiment-table/ExperimentTable.module.scss b/portal/src/app/components/home/components/experiment/components/experiment-table/ExperimentTable.module.scss index ec5a99f8..f474ca8c 100644 --- a/portal/src/app/components/home/components/experiment/components/experiment-table/ExperimentTable.module.scss +++ b/portal/src/app/components/home/components/experiment/components/experiment-table/ExperimentTable.module.scss @@ -9,6 +9,8 @@ } .experiment_table { + text-align: center; + th:first-child, td:first-child { inline-size: 80px; diff --git a/portal/src/app/shared/components/table/Table.module.scss b/portal/src/app/shared/components/table/Table.module.scss index 8a7154d4..670fff67 100644 --- a/portal/src/app/shared/components/table/Table.module.scss +++ b/portal/src/app/shared/components/table/Table.module.scss @@ -20,11 +20,7 @@ table { .table_content { background-color: var($backgroundColorWhite); - - td { - padding: 16px; - border-block-end: 1px solid var($backgroundColorGray); - text-align: center; - vertical-align: middle; - } + padding: 16px; + border-block-end: 1px solid var($backgroundColorGray); + vertical-align: middle; } diff --git a/portal/src/app/shared/components/table/Table.tsx b/portal/src/app/shared/components/table/Table.tsx index 29bf6d29..37386ed5 100644 --- a/portal/src/app/shared/components/table/Table.tsx +++ b/portal/src/app/shared/components/table/Table.tsx @@ -86,7 +86,7 @@ export const Table = ({ headers, data, className }: TableProps {table.getRowModel().rows.map((row: Row) => (
    {row.getVisibleCells().map((cell: Cell) => ( - ))} From 1a0194ecb71ca6869d377f2db91176bcc58d9dd6 Mon Sep 17 00:00:00 2001 From: Ohad Koren Date: Thu, 25 Jan 2024 14:47:52 +0200 Subject: [PATCH 21/35] Test fixes --- .../__snapshots__/Experiments.test.tsx.snap | 34 +------------------ 1 file changed, 1 insertion(+), 33 deletions(-) diff --git a/portal/src/app/components/all-experiments/__snapshots__/Experiments.test.tsx.snap b/portal/src/app/components/all-experiments/__snapshots__/Experiments.test.tsx.snap index ac0e0190..8afcf59a 100644 --- a/portal/src/app/components/all-experiments/__snapshots__/Experiments.test.tsx.snap +++ b/portal/src/app/components/all-experiments/__snapshots__/Experiments.test.tsx.snap @@ -4,38 +4,6 @@ exports[`Experiments renders correctly 1`] = `
    -
    -
    - - - - - - -
    -
    -
    + />
    `; From 5af7570704b3f4d348e103eea8a6286677dcc65d Mon Sep 17 00:00:00 2001 From: Ohad Koren Date: Thu, 25 Jan 2024 15:43:00 +0200 Subject: [PATCH 22/35] Spinner + Style fixes --- .../all-experiments/Experiments.module.scss | 86 +++++++------------ .../all-experiments/Experiments.tsx | 14 --- .../experiment/Experiment.module.scss | 18 ---- .../home/components/experiment/Experiment.tsx | 19 +--- .../components/hooks/useExperimentData.ts | 6 +- portal/src/routes/Root.jsx | 15 ++++ portal/src/routes/Root.module.scss | 19 ++++ 7 files changed, 70 insertions(+), 107 deletions(-) create mode 100644 portal/src/routes/Root.module.scss diff --git a/portal/src/app/components/all-experiments/Experiments.module.scss b/portal/src/app/components/all-experiments/Experiments.module.scss index 0a9e228d..f60ea790 100644 --- a/portal/src/app/components/all-experiments/Experiments.module.scss +++ b/portal/src/app/components/all-experiments/Experiments.module.scss @@ -3,46 +3,43 @@ .experiments_wrapper { padding-inline: 80px; padding-block: 40px; - margin-block: 60px; -} -.title_options_container { - display: flex; - justify-content: space-between; - align-items: center; + .title_options_container { + display: flex; + justify-content: space-between; + align-items: center; - .experiments_title { - font-size: 20px; - font-family: var($fontMedium); - margin-inline-start: 150px; - } + .experiments_title { + font-size: 20px; + font-family: var($fontMedium); + margin-block-end: 40px; + } - .options_wrapper { - margin-inline-end: 50px; + .options_wrapper { + .trash_icon { + background-color: #F5F1FF; + inline-size: 34px; + block-size: 34px; + border-radius: 50%; + } + } - .trash_icon { - background-color: #F5F1FF; - inline-size: 34px; - block-size: 34px; - border-radius: 50%; + .options_wrapper:hover .hover_image { + display: block; + } + + .options_wrapper:hover .default_image { + display: none; + } + + .default_image { + padding-inline: 11px; + display: block; + } + + .hover_image { + display: none; } - } - - .options_wrapper:hover .hover_image { - display: block; - } - - .options_wrapper:hover .default_image { - display: none; - } - - .default_image { - padding-inline: 11px; - display: block; - } - - .hover_image { - display: none; } } @@ -68,22 +65,3 @@ cursor: pointer; } } - -.spinner_wrapper { - position: sticky; - inset-block-start: 50%; - inset-inline-start: 50%; - text-align: center; -} - -.spinner_overlay { - inline-size: 100%; - block-size: 100%; - position: absolute; - background-color: var($primaryWhite); - opacity: 0.6; - inset-block-start: 0; - inset-inline-start: 0; - z-index: 4; -} - diff --git a/portal/src/app/components/all-experiments/Experiments.tsx b/portal/src/app/components/all-experiments/Experiments.tsx index 0d8c83e5..024f3b52 100644 --- a/portal/src/app/components/all-experiments/Experiments.tsx +++ b/portal/src/app/components/all-experiments/Experiments.tsx @@ -3,7 +3,6 @@ import styles from './Experiments.module.scss'; import cn from 'classnames'; import { IUseExperimentsData, useExperimentsData } from './hooks'; import { FetchDataStatus, IHttp, useFetch } from '../../shared/hooks/useFetch'; -import { Spinner, SpinnerSize } from '../../shared/components/att-spinner'; import { ALL_EXPERIMENTS_TABLE_EN } from './translate/en'; import { CellContext } from '@tanstack/react-table'; import { Table } from '../../shared/components/table'; @@ -20,7 +19,6 @@ import TrashHoverSvg from '../../../assets/images/trash-hover.svg'; import DuplicateSvg from '../../../assets/images/duplicate.svg'; import { DeleteExperimentModal } from '../home/components/experiment/components/delete-experiment-modal'; import { parseExperimentsData } from './utils/parse-experiments-data.utils'; -import { ISpinner, useSpinnerContext } from '../../shared/context/spinner'; import { ExperimentData } from './models/experiments.interface'; const DeleteAriaLabel: string = ALL_EXPERIMENTS_TABLE_EN.BUTTONS.DELETE; @@ -31,7 +29,6 @@ export const Experiments: React.FC = () => { const [openDeleteModal, setOpenDeleteModal] = useState(false); const [checkedRows, setCheckedRows] = useState>({}); const experimentsData = useMemo(() => (testSuites ? parseExperimentsData(testSuites): []), [testSuites]); - const { isSpinnerOn }: ISpinner = useSpinnerContext(); const navigate = useNavigate(); const { post, status: deleteStatus, error: deleteError, cancelRequest: cancelRequestDelete }: IHttp @@ -146,7 +143,6 @@ export const Experiments: React.FC = () => { return (
    <> - {isSpinnerOn && renderSpinner()} { status === FetchDataStatus.Success &&
    @@ -172,13 +168,3 @@ export const Experiments: React.FC = () => {
    ); } - -function renderSpinner() { - return ( -
    -
    - -
    -
    - ); -} diff --git a/portal/src/app/components/home/components/experiment/Experiment.module.scss b/portal/src/app/components/home/components/experiment/Experiment.module.scss index 28406f9f..8fd1714a 100644 --- a/portal/src/app/components/home/components/experiment/Experiment.module.scss +++ b/portal/src/app/components/home/components/experiment/Experiment.module.scss @@ -19,21 +19,3 @@ .table_options_wrapper { position: relative; } - -.spinner_wrapper { - position: sticky; - inset-block-start: 50%; - inset-inline-start: 50%; - text-align: center; -} - -.spinner_overlay { - inline-size: 100%; - block-size: 100%; - position: absolute; - background-color: #fff; - opacity: 0.6; - inset-block-start: 0; - inset-inline-start: 0; - z-index: 4; -} diff --git a/portal/src/app/components/home/components/experiment/Experiment.tsx b/portal/src/app/components/home/components/experiment/Experiment.tsx index b9d36347..24b1b6f8 100644 --- a/portal/src/app/components/home/components/experiment/Experiment.tsx +++ b/portal/src/app/components/home/components/experiment/Experiment.tsx @@ -4,13 +4,10 @@ import { Charts } from './components/charts'; import { SubHeader } from './components/sub-header'; import { useExperimentData } from './components/hooks/useExperimentData'; import { ITestRunResult } from '../../../../shared/models/test-run-result.interface'; -import { FetchDataStatus } from '../../../../shared/hooks/useFetch'; -import { Spinner, SpinnerSize } from '../../../../shared/components/att-spinner'; import { useEffect, useRef, useState } from 'react'; import { EXPERIMENT_EN } from './translate/en'; import { ExperimentTabs } from './components/experiment-tabs'; import { handleSectionScrolling } from './utils'; -import { ISpinner, useSpinnerContext } from '../../../../shared/context/spinner'; import { TableOptions } from './components/table-options'; import { SelectColumnsPopup } from './components/table-options/components/select-columns-popup'; import { SelectedColumnsDefaultData, TableOptionsData } from './components/table-options/constants/table-options.const'; @@ -22,11 +19,11 @@ export type IExperimentData = { } export const Experiment: React.FC = () => { - const { data: testRunData, status } = useExperimentData(); + const { data: testRunData } = useExperimentData(); return (
    - {status === FetchDataStatus.Fetching ? renderSpinner() : testRunData && } + {testRunData && }
    ); } @@ -36,7 +33,6 @@ export const ExperimentContent: React.FC = (props: IExperimentD const [currentSection, setCurrentSection] = useState(EXPERIMENT_EN.TABS.RESULTS_DATA); const [selectedColumns, setSelectedColumns] = useState(SelectedColumnsDefaultData); - const { isSpinnerOn }: ISpinner = useSpinnerContext(); const resultsDataRef = useRef(null); const visualizationRef = useRef(null); const tableOptionsRef = useRef(null); @@ -71,7 +67,6 @@ export const ExperimentContent: React.FC = (props: IExperimentD return ( <> - {isSpinnerOn && renderSpinner()}
    @@ -99,13 +94,3 @@ export const ExperimentContent: React.FC = (props: IExperimentD ); } - -function renderSpinner() { - return ( -
    -
    - -
    -
    - ); -} diff --git a/portal/src/app/components/home/components/experiment/components/hooks/useExperimentData.ts b/portal/src/app/components/home/components/experiment/components/hooks/useExperimentData.ts index d1c0bf57..1c0898a7 100644 --- a/portal/src/app/components/home/components/experiment/components/hooks/useExperimentData.ts +++ b/portal/src/app/components/home/components/experiment/components/hooks/useExperimentData.ts @@ -4,14 +4,13 @@ import { replaceParams } from "../../../../../../shared/utils/replaceParams"; import { useParams } from "react-router-dom"; import { ITestRunResult, ITestRunResultData } from "../../../../../../shared/models/test-run-result.interface"; import { TestRunUrlParams } from "../../../../../../shared/models/url-params.interface"; -import { FetchDataStatus, IHttp, useFetch } from "../../../../../../shared/hooks/useFetch"; +import { IHttp, useFetch } from "../../../../../../shared/hooks/useFetch"; import { sortDataByAlgorithm } from "../charts/utils/test-run.utils"; import { useFetchSpinner } from "../../../../../../shared/hooks/useFetchSpinner"; import { useErrorMessage } from "../../../../../../hooks/useErrorMessage"; export interface IUseExperimentData { data: ITestRunResult; - status: FetchDataStatus; } export function useExperimentData(): IUseExperimentData { @@ -36,7 +35,6 @@ export function useExperimentData(): IUseExperimentData { }, [data]); return { - data: testRunData, - status, + data: testRunData } as IUseExperimentData; } diff --git a/portal/src/routes/Root.jsx b/portal/src/routes/Root.jsx index a4964046..c6bf76ca 100644 --- a/portal/src/routes/Root.jsx +++ b/portal/src/routes/Root.jsx @@ -1,12 +1,27 @@ +import styles from './Root.module.scss'; import { GlobalHeader } from '../app/shared/components/global-header/index'; import { Outlet } from 'react-router-dom'; import { tabs } from '../app/shared/constants/navigation-tabs.const'; +import { Spinner, SpinnerSize } from '../app/shared/components/att-spinner'; +import { useSpinnerContext } from '../app/shared/context/spinner'; export default function Root() { + const { isSpinnerOn } = useSpinnerContext(); return ( <> + {isSpinnerOn && renderSpinner()} ); } + +function renderSpinner() { + return ( +
    +
    + +
    +
    + ); +} diff --git a/portal/src/routes/Root.module.scss b/portal/src/routes/Root.module.scss new file mode 100644 index 00000000..a08a79d2 --- /dev/null +++ b/portal/src/routes/Root.module.scss @@ -0,0 +1,19 @@ +@import "src/styles/variables-keys"; + +.spinner_wrapper { + position: sticky; + inset-block-start: 50%; + inset-inline-start: 50%; + text-align: center; +} + +.spinner_overlay { + inline-size: 100%; + block-size: 100%; + position: absolute; + background-color: var($primaryWhite); + opacity: 0.6; + inset-block-start: 0; + inset-inline-start: 0; + z-index: 4; +} From 487fdd7a5ca3eb30acbf2e23ddbe505c49142c0e Mon Sep 17 00:00:00 2001 From: litalmason <142991359+litalmason@users.noreply.github.com> Date: Tue, 30 Jan 2024 18:33:50 +0200 Subject: [PATCH 23/35] Create static.yml --- .github/workflows/static.yml | 43 ++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 .github/workflows/static.yml diff --git a/.github/workflows/static.yml b/.github/workflows/static.yml new file mode 100644 index 00000000..d8aee46c --- /dev/null +++ b/.github/workflows/static.yml @@ -0,0 +1,43 @@ +# Simple workflow for deploying static content to GitHub Pages +name: Deploy static content to Pages + +on: + # Runs on pushes targeting the default branch + push: + branches: ["main"] + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages +permissions: + contents: read + pages: write + id-token: write + +# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. +# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + # Single deploy job since we're just deploying + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Setup Pages + uses: actions/configure-pages@v4 + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + # Upload entire repository + path: './portal' + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 From 06b2d970ebba0bf66b3242b1cc3a902c1bcb0ae1 Mon Sep 17 00:00:00 2001 From: litalmason <142991359+litalmason@users.noreply.github.com> Date: Tue, 30 Jan 2024 18:41:50 +0200 Subject: [PATCH 24/35] Create docker-image.yml --- .github/workflows/docker-image.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 .github/workflows/docker-image.yml diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml new file mode 100644 index 00000000..c0e3cec5 --- /dev/null +++ b/.github/workflows/docker-image.yml @@ -0,0 +1,19 @@ +name: Docker Image CI + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - name: Build the Docker image + run: | + REPO_NAME=$(echo $GITHUB_REPOSITORY | tr '/' '-') + docker build ./portal --file ./portal/Dockerfile --tag $REPO_NAME:$(date +%s) From ec4a261c20ed0987193d165c56e555f02cea1730 Mon Sep 17 00:00:00 2001 From: litalmason <142991359+litalmason@users.noreply.github.com> Date: Tue, 30 Jan 2024 18:42:04 +0200 Subject: [PATCH 25/35] Delete .github/workflows/static.yml --- .github/workflows/static.yml | 43 ------------------------------------ 1 file changed, 43 deletions(-) delete mode 100644 .github/workflows/static.yml diff --git a/.github/workflows/static.yml b/.github/workflows/static.yml deleted file mode 100644 index d8aee46c..00000000 --- a/.github/workflows/static.yml +++ /dev/null @@ -1,43 +0,0 @@ -# Simple workflow for deploying static content to GitHub Pages -name: Deploy static content to Pages - -on: - # Runs on pushes targeting the default branch - push: - branches: ["main"] - - # Allows you to run this workflow manually from the Actions tab - workflow_dispatch: - -# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages -permissions: - contents: read - pages: write - id-token: write - -# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. -# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. -concurrency: - group: "pages" - cancel-in-progress: false - -jobs: - # Single deploy job since we're just deploying - deploy: - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Setup Pages - uses: actions/configure-pages@v4 - - name: Upload artifact - uses: actions/upload-pages-artifact@v3 - with: - # Upload entire repository - path: './portal' - - name: Deploy to GitHub Pages - id: deployment - uses: actions/deploy-pages@v4 From db0273fbfca18d91161cb06bb9fa9ffa89d83173 Mon Sep 17 00:00:00 2001 From: litalmason <142991359+litalmason@users.noreply.github.com> Date: Tue, 30 Jan 2024 18:49:56 +0200 Subject: [PATCH 26/35] Update docker-image.yml --- .github/workflows/docker-image.yml | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index c0e3cec5..f6f3e62e 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -1,4 +1,4 @@ -name: Docker Image CI +name: Deploy to GitHub Pages on: push: @@ -7,13 +7,28 @@ on: branches: [ "main" ] jobs: - build: + build-and-deploy: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v2 - - name: Build the Docker image - run: | - REPO_NAME=$(echo $GITHUB_REPOSITORY | tr '/' '-') - docker build ./portal --file ./portal/Dockerfile --tag $REPO_NAME:$(date +%s) + # Build your Docker image + - name: Build Docker image + run: docker build ./portal --file ./portal/Dockerfile --tag my-image + + # Run the Docker container and generate static content + # This step assumes your Docker container outputs static files to /usr/share/nginx/html + # Adjust the command according to how your container serves static content + - name: Run Docker container + run: docker run --name my-container my-image + + - name: Copy static content from Docker container + run: docker cp my-container:/usr/share/nginx/html ./static-content + + # Deploy to GitHub Pages + - name: Deploy to GitHub Pages + uses: peaceiris/actions-gh-pages@v3 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./static-content From b1b57a2f9e3e6b9b2a2f3d4b1b437964368f999e Mon Sep 17 00:00:00 2001 From: litalmason <142991359+litalmason@users.noreply.github.com> Date: Tue, 30 Jan 2024 18:59:58 +0200 Subject: [PATCH 27/35] Update docker-image.yml --- .github/workflows/docker-image.yml | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index f6f3e62e..6213c8f2 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -1,4 +1,4 @@ -name: Deploy to GitHub Pages +name: Build and Deploy to GitHub Pages on: push: @@ -11,24 +11,30 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - name: Checkout code + uses: actions/checkout@v2 - # Build your Docker image - name: Build Docker image - run: docker build ./portal --file ./portal/Dockerfile --tag my-image + run: docker build ./portal --file ./portal/Dockerfile --tag my-website-image - # Run the Docker container and generate static content - # This step assumes your Docker container outputs static files to /usr/share/nginx/html - # Adjust the command according to how your container serves static content - name: Run Docker container - run: docker run --name my-container my-image + run: | + docker run --name my-website-container -d my-website-image + # Wait a few seconds to ensure the web server inside the container is fully up and running + sleep 10 - name: Copy static content from Docker container - run: docker cp my-container:/usr/share/nginx/html ./static-content + run: | + mkdir -p static-content + docker cp my-website-container:/usr/share/nginx/html/qujata ./static-content + + - name: Stop and remove Docker container + run: | + docker stop my-website-container + docker rm my-website-container - # Deploy to GitHub Pages - name: Deploy to GitHub Pages uses: peaceiris/actions-gh-pages@v3 with: github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ./static-content + publish_dir: ./static-content/qujata From 210e933470f85188941c393dc8a804eea134e4a7 Mon Sep 17 00:00:00 2001 From: kr172t Date: Wed, 31 Jan 2024 10:54:54 +0200 Subject: [PATCH 28/35] add message_size to test suites api response --- api/src/utils/test_suite_serializer.py | 1 + api/tests/test_tests_api.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/api/src/utils/test_suite_serializer.py b/api/src/utils/test_suite_serializer.py index 502aba23..1d13f018 100644 --- a/api/src/utils/test_suite_serializer.py +++ b/api/src/utils/test_suite_serializer.py @@ -35,6 +35,7 @@ def __get_test_runs_metrics(test_runs): "id": test_run.id, "algorithm": test_run.algorithm, "iterations": test_run.iterations, + "message_size": test_run.message_size, "results": { "averageCPU": round(cpu_avg, 2), "averageMemory": int(memory_avg), diff --git a/api/tests/test_tests_api.py b/api/tests/test_tests_api.py index 08989c3e..8e52b5bb 100644 --- a/api/tests/test_tests_api.py +++ b/api/tests/test_tests_api.py @@ -70,7 +70,7 @@ def test_get_test_suite(self): self.app.database_manager.get_by_id.return_value = test_suite response = self.client.get(TEST_SUITES_GET_URL) result = json.loads(response.data) - expected = {'code_release': '1.1.0', 'description': 'description', 'end_time': None, 'environment_info': {'cpu': None, 'cpuArchitecture': None, 'cpuClockSpeed': None, 'cpuCores': None, 'nodeSize': None, 'operatingSystem': None, 'resourceName': None}, 'id': None, 'name': 'name', 'start_time': None, 'test_runs': [{'algorithm': None, 'id': 1, 'iterations': None, 'results': {'averageCPU': 9.0, 'averageMemory': 14}}]} + expected = {'code_release': '1.1.0', 'description': 'description', 'end_time': None, 'environment_info': {'cpu': None, 'cpuArchitecture': None, 'cpuClockSpeed': None, 'cpuCores': None, 'nodeSize': None, 'operatingSystem': None, 'resourceName': None}, 'id': None, 'name': 'name', 'start_time': None, 'test_runs': [{'algorithm': None, 'id': 1, 'iterations': None, 'message_size': None, 'results': {'averageCPU': 9.0, 'averageMemory': 14}}]} self.assertEqual(result, expected) def test_get_test_suite_return_not_found(self): From bc92a2753e29c54508796ff0291fa71ae904708d Mon Sep 17 00:00:00 2001 From: Ohad Koren Date: Wed, 31 Jan 2024 11:12:40 +0200 Subject: [PATCH 29/35] text fix --- portal/src/app/components/sub-header/SubHeader.test.tsx | 2 +- .../components/sub-header/__snapshots__/SubHeader.test.tsx.snap | 2 +- portal/src/app/components/sub-header/translate/en.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/portal/src/app/components/sub-header/SubHeader.test.tsx b/portal/src/app/components/sub-header/SubHeader.test.tsx index 5ee886f9..08570f2b 100644 --- a/portal/src/app/components/sub-header/SubHeader.test.tsx +++ b/portal/src/app/components/sub-header/SubHeader.test.tsx @@ -10,6 +10,6 @@ describe('SubHeader', () => { (Button as jest.Mock).mockImplementation(() =>
    Button
    ); const { container, getByText }: RenderResult = render(, { wrapper: MemoryRouter }); expect(container.firstChild).toMatchSnapshot(); - expect(getByText('That’s what are we doing on each iteration:')).toBeTruthy(); + expect(getByText('That’s what we are doing on each iteration:')).toBeTruthy(); }); }); \ No newline at end of file diff --git a/portal/src/app/components/sub-header/__snapshots__/SubHeader.test.tsx.snap b/portal/src/app/components/sub-header/__snapshots__/SubHeader.test.tsx.snap index f1d10960..23777f36 100644 --- a/portal/src/app/components/sub-header/__snapshots__/SubHeader.test.tsx.snap +++ b/portal/src/app/components/sub-header/__snapshots__/SubHeader.test.tsx.snap @@ -10,7 +10,7 @@ exports[`SubHeader renders sub header 1`] = `
    - That’s what are we doing on each iteration: + That’s what we are doing on each iteration:
    Date: Wed, 31 Jan 2024 13:04:21 +0200 Subject: [PATCH 30/35] clean code --- .../experiment/components/charts/Charts.tsx | 42 ------------------- .../CustomValueContainer.tsx | 1 - .../components/dynamic-chart/translate/en.ts | 1 - .../utils/dynamic-chart.utils.ts | 2 +- 4 files changed, 1 insertion(+), 45 deletions(-) diff --git a/portal/src/app/components/home/components/experiment/components/charts/Charts.tsx b/portal/src/app/components/home/components/experiment/components/charts/Charts.tsx index af190114..929e33b5 100644 --- a/portal/src/app/components/home/components/experiment/components/charts/Charts.tsx +++ b/portal/src/app/components/home/components/experiment/components/charts/Charts.tsx @@ -1,53 +1,11 @@ -/* eslint-disable no-null/no-null */ -import { BarChart } from '../../../../../dashboard/components/charts/BarChart'; -import { LineChart } from '../../../../../dashboard/components/charts/LineChart'; import { IExperimentData } from '../../Experiment'; import styles from './Charts.module.scss'; import { DynamicChart } from './components/dynamic-chart'; -import { useChartsData } from './hooks/useChartsData'; -import { tooltipKeys, tooltipLabels } from './models/bar-chart.const'; -import { CHARTS_EN } from './translate/en'; -import { getChartTitleByType } from './utils/chart.utils'; export const Charts: React.FC = (props: IExperimentData) => { - const { barChartData, barChartLabels, barChartKeysOfData, lineChartData } = useChartsData(props); - return (
    - {/*
    {CHARTS_EN.TITLE}
    - <> -
    - {barChartKeysOfData.map((key, index) => ( -
    - -
    - ))} -
    -
    - {barChartKeysOfData.map((key, index) => { - const datasets = lineChartData.datasets - .filter(dataset => dataset.data[key]) - .map(dataset => ({ - ...dataset, - data: dataset.data[key] - })); - - if (datasets.length === 0) return null; - - const data = { - labels: lineChartData.labels, - datasets: datasets - }; - - return ( -
    - -
    - ); - })} -
    - */}
    ); } diff --git a/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/components/custom-value-container/CustomValueContainer.tsx b/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/components/custom-value-container/CustomValueContainer.tsx index 907852eb..44768c58 100644 --- a/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/components/custom-value-container/CustomValueContainer.tsx +++ b/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/components/custom-value-container/CustomValueContainer.tsx @@ -35,4 +35,3 @@ export const CustomValueContainer: React.FC ) } - \ No newline at end of file diff --git a/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/translate/en.ts b/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/translate/en.ts index a2016c96..ea6c046b 100644 --- a/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/translate/en.ts +++ b/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/translate/en.ts @@ -14,4 +14,3 @@ export const DYNAMIC_CHART_EN = { ITERATIONS: 'Iterations', } }; - \ No newline at end of file diff --git a/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/utils/dynamic-chart.utils.ts b/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/utils/dynamic-chart.utils.ts index 416baf5e..4f20c0f7 100644 --- a/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/utils/dynamic-chart.utils.ts +++ b/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/utils/dynamic-chart.utils.ts @@ -13,4 +13,4 @@ export function capitalizeFirstLetter(str: string): string { export function getTitleByXAxiosValue(value: string): string { return value === 'NUMBER_OF_ITERATIONS' ? DYNAMIC_CHART_EN.X_VALUES_TITLE.ITERATIONS : ''; -} \ No newline at end of file +} From ae7ca720a055f8d217760b3a9be102691ea035ed Mon Sep 17 00:00:00 2001 From: iadibar Date: Sun, 4 Feb 2024 09:34:21 +0200 Subject: [PATCH 31/35] add UT --- .../charts/utils/charts.utils.test.ts | 7 ++++ .../dynamic-chart/DynamicChart.test.tsx | 33 +++++++++++++++ .../CustomDropdownIndicator.test.tsx | 30 +++++++++++++ .../CustomDropdownIndicator.test.tsx.snap | 13 ++++++ .../custom-option/CustomOption.test.tsx | 42 +++++++++++++++++++ .../__snapshots__/CustomOption.test.tsx.snap | 17 ++++++++ .../CustomValueContainer.test.tsx | 29 +++++++++++++ .../CustomValueContainer.test.tsx.snap | 17 ++++++++ .../hooks/useDynamicChartData.test.ts | 13 ++++++ .../utils/dynamic-chart.utils.test.ts | 19 +++++++++ .../charts/hooks/useChartsData.test.ts | 4 +- portal/src/app/hooks/useOutsideClick.test.tsx | 30 +++++++++++++ 12 files changed, 252 insertions(+), 2 deletions(-) create mode 100644 portal/src/app/components/dashboard/components/charts/utils/charts.utils.test.ts create mode 100644 portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/DynamicChart.test.tsx create mode 100644 portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/components/custom-dropdown-indicator/CustomDropdownIndicator.test.tsx create mode 100644 portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/components/custom-dropdown-indicator/__snapshots__/CustomDropdownIndicator.test.tsx.snap create mode 100644 portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/components/custom-option/CustomOption.test.tsx create mode 100644 portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/components/custom-option/__snapshots__/CustomOption.test.tsx.snap create mode 100644 portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/components/custom-value-container/CustomValueContainer.test.tsx create mode 100644 portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/components/custom-value-container/__snapshots__/CustomValueContainer.test.tsx.snap create mode 100644 portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/hooks/useDynamicChartData.test.ts create mode 100644 portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/utils/dynamic-chart.utils.test.ts create mode 100644 portal/src/app/hooks/useOutsideClick.test.tsx diff --git a/portal/src/app/components/dashboard/components/charts/utils/charts.utils.test.ts b/portal/src/app/components/dashboard/components/charts/utils/charts.utils.test.ts new file mode 100644 index 00000000..4d698d56 --- /dev/null +++ b/portal/src/app/components/dashboard/components/charts/utils/charts.utils.test.ts @@ -0,0 +1,7 @@ +import { getColorByName } from './charts.utils'; + +describe('Charts Util Test', () => { + test('should get color by name', () => { + expect(getColorByName('bikel1')).toBe('#FF8500'); + }); +}); diff --git a/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/DynamicChart.test.tsx b/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/DynamicChart.test.tsx new file mode 100644 index 00000000..6d6fc957 --- /dev/null +++ b/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/DynamicChart.test.tsx @@ -0,0 +1,33 @@ +import { render } from '@testing-library/react'; +import { useDynamicChartData } from './hooks/useDynamicChartData'; +import { BarChart } from '../../../../../../../dashboard/components/charts/BarChart/BarChart'; +import { LineChart } from '../../../../../../../dashboard/components/charts/LineChart/LineChart'; + +jest.mock('../../../../../../../dashboard/components/charts/BarChart/BarChart'); +jest.mock('../../../../../../../dashboard/components/charts/LineChart/LineChart'); +jest.mock('../../../../../../../../shared/components/att-select/AttSelect', () => ({ + AttSelect: jest.fn(() =>
    Mocked AttSelect
    ), +})); +jest.mock('./hooks/useDynamicChartData'); + +describe('DynamicChart', () => { + test('should render Charts', async () => { + (BarChart as jest.Mock).mockImplementation(() =>
    BarChart
    ); + (LineChart as jest.Mock).mockImplementation(() =>
    LineChart
    ); + + + (useDynamicChartData as jest.Mock).mockReturnValue({ + yAxiosOptions: [{ + label: 'averageCPU', + value: 'averageCPU' + }, + { + label: 'averageMemory', + value: 'averageMemory' + }], + }); + + const { container } = render(); + expect(container).toBeTruthy(); + }); +}); diff --git a/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/components/custom-dropdown-indicator/CustomDropdownIndicator.test.tsx b/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/components/custom-dropdown-indicator/CustomDropdownIndicator.test.tsx new file mode 100644 index 00000000..029bdf4b --- /dev/null +++ b/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/components/custom-dropdown-indicator/CustomDropdownIndicator.test.tsx @@ -0,0 +1,30 @@ +import { render, RenderResult } from '@testing-library/react'; +import { CustomDropdownIndicator } from './CustomDropdownIndicator'; +import { DropdownIndicatorProps } from 'react-select'; +import { AttSelectOption } from '../../../../../../../../../../shared/components/att-select'; + +describe('CustomDropdownIndicator', () => { + const mockProps: DropdownIndicatorProps = { + innerProps: undefined as any, + isFocused: false, + isDisabled: false, + clearValue: jest.fn(), + cx: jest.fn(), + getStyles: jest.fn(), + getClassNames: jest.fn(), + getValue: jest.fn(), + hasValue: false, + isMulti: false, + isRtl: false, + options: [], + selectOption: jest.fn(), + selectProps: undefined as any, + setValue: jest.fn(), + theme: undefined as any, + }; + + it('should render CustomDropdownIndicator', () => { + const { container }: RenderResult = render(); + expect(container.firstChild).toMatchSnapshot(); + }); +}); diff --git a/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/components/custom-dropdown-indicator/__snapshots__/CustomDropdownIndicator.test.tsx.snap b/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/components/custom-dropdown-indicator/__snapshots__/CustomDropdownIndicator.test.tsx.snap new file mode 100644 index 00000000..ad0f259f --- /dev/null +++ b/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/components/custom-dropdown-indicator/__snapshots__/CustomDropdownIndicator.test.tsx.snap @@ -0,0 +1,13 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`CustomDropdownIndicator should render CustomDropdownIndicator 1`] = ` +
    +
    + + arrow-down-selector.svg + +
    +
    +`; diff --git a/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/components/custom-option/CustomOption.test.tsx b/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/components/custom-option/CustomOption.test.tsx new file mode 100644 index 00000000..52b12c7c --- /dev/null +++ b/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/components/custom-option/CustomOption.test.tsx @@ -0,0 +1,42 @@ +import { render, RenderResult } from '@testing-library/react'; +import { CustomOption } from './CustomOption'; +import { SelectorCustomOptionProps } from '../../../../../../../../../../shared/components/selector-custom-option'; + +describe('CustomOption', () => { + const mockOption = { value: 'option1', label: 'Option 1' }; + const mockProps: SelectorCustomOptionProps = { + data: mockOption, + isSelected: false, + selectOption: jest.fn(), + label: 'Option 1', + innerProps: {}, + innerRef: jest.fn(), + children: null, + type: 'option', + isDisabled: false, + isFocused: false, + clearValue: jest.fn(), + cx: jest.fn(), + getStyles: jest.fn(), + getClassNames: jest.fn(), + getValue: jest.fn().mockReturnValue([{ label: 'Option 1', value: 'option1' }]), + hasValue: true, + isMulti: true, + isRtl: false, + options: [], + selectProps: expect.any(Object), + setValue: jest.fn(), + theme: expect.any(Object), + onOptionChanged: jest.fn(), + showInputOption: false, + setShowInputOption: jest.fn(), + inputValue: '1111', + setInputValue: jest.fn(), + setMenuIsOpen: jest.fn(), + }; + + it('should render CustomOption', () => { + const { container }: RenderResult = render(); + expect(container.firstChild).toMatchSnapshot(); + }); +}); diff --git a/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/components/custom-option/__snapshots__/CustomOption.test.tsx.snap b/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/components/custom-option/__snapshots__/CustomOption.test.tsx.snap new file mode 100644 index 00000000..6848c4f2 --- /dev/null +++ b/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/components/custom-option/__snapshots__/CustomOption.test.tsx.snap @@ -0,0 +1,17 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`CustomOption should render CustomOption 1`] = ` +
    + option1 + + Option1 + +
    +`; diff --git a/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/components/custom-value-container/CustomValueContainer.test.tsx b/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/components/custom-value-container/CustomValueContainer.test.tsx new file mode 100644 index 00000000..34bccade --- /dev/null +++ b/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/components/custom-value-container/CustomValueContainer.test.tsx @@ -0,0 +1,29 @@ +import { render, RenderResult } from '@testing-library/react'; +import { CustomValueContainer } from './CustomValueContainer'; +import { GroupBase, SetValueAction, ValueContainerProps } from 'react-select'; +import { AttSelectOption } from '../../../../../../../../../../shared/components/att-select'; + +describe('CustomValueContainer', () => { + const mockProps: ValueContainerProps, boolean, GroupBase>> = { + children: undefined, + isDisabled: false, + clearValue: jest.fn(), + cx: jest.fn(), + getStyles: jest.fn(), + getClassNames: jest.fn(), + getValue: jest.fn(), + hasValue: false, + isMulti: false, + isRtl: false, + options: [], + selectOption: jest.fn(), + selectProps: undefined as any, + setValue: jest.fn(), + theme: undefined as any + }; + + it('should render CustomValueContainer', () => { + const { container }: RenderResult = render(); + expect(container.firstChild).toMatchSnapshot(); + }); +}); diff --git a/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/components/custom-value-container/__snapshots__/CustomValueContainer.test.tsx.snap b/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/components/custom-value-container/__snapshots__/CustomValueContainer.test.tsx.snap new file mode 100644 index 00000000..3de4a472 --- /dev/null +++ b/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/components/custom-value-container/__snapshots__/CustomValueContainer.test.tsx.snap @@ -0,0 +1,17 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`CustomValueContainer should render CustomValueContainer 1`] = ` +
    +
    +
    + +
    +
    +
    +`; diff --git a/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/hooks/useDynamicChartData.test.ts b/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/hooks/useDynamicChartData.test.ts new file mode 100644 index 00000000..f9ea9782 --- /dev/null +++ b/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/hooks/useDynamicChartData.test.ts @@ -0,0 +1,13 @@ +import { act, renderHook } from "@testing-library/react"; +import { useDynamicChartData } from "./useDynamicChartData"; +import { MOCK_DATA_FOR_EXPERIMENT } from "../../../../__mocks__/mocks"; + +describe('useDynamicChartData', () => { + test('should get data', async () => { + + const { result } = renderHook(() => useDynamicChartData(MOCK_DATA_FOR_EXPERIMENT)); + act(() => { + expect(result.current).toEqual( {yAxiosOptions: [{label: "averageCPU", value: "averageCPU"}, {label: "averageMemory", value: "averageMemory"}]}); + }); + }); +}); diff --git a/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/utils/dynamic-chart.utils.test.ts b/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/utils/dynamic-chart.utils.test.ts new file mode 100644 index 00000000..0058e309 --- /dev/null +++ b/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/utils/dynamic-chart.utils.test.ts @@ -0,0 +1,19 @@ +import { ChartType } from '../models/dynamic-chart.interface'; +import { capitalizeFirstLetter, getIconByValue, getTitleByXAxiosValue } from './dynamic-chart.utils'; +import LineSvg from '../../../../../../../../../../../src/assets/images/line.svg'; +import BarSvg from '../../../../../../../../../../../src/assets/images/bar.svg'; + +describe('Dynamic chart util test', () => { + test('should get icon by value', () => { + expect(getIconByValue(ChartType.LINE)).toBe(LineSvg); + expect(getIconByValue(ChartType.BAR)).toBe(BarSvg); + }); + + test('should capitalize first letter', () => { + expect(capitalizeFirstLetter('test')).toBe('Test'); + }); + + test('should get title by XAxios value', () => { + expect(getTitleByXAxiosValue('NUMBER_OF_ITERATIONS')).toBe('Iterations'); + }); +}); diff --git a/portal/src/app/components/home/components/experiment/components/charts/hooks/useChartsData.test.ts b/portal/src/app/components/home/components/experiment/components/charts/hooks/useChartsData.test.ts index 868b2319..fa0ee268 100644 --- a/portal/src/app/components/home/components/experiment/components/charts/hooks/useChartsData.test.ts +++ b/portal/src/app/components/home/components/experiment/components/charts/hooks/useChartsData.test.ts @@ -53,8 +53,8 @@ describe('useChartsData', () => { ], lineChartData: { labels: [104, 1024], datasets: [ { - backgroundColor: "#05BBFF", - borderColor: "#05BBFF", + backgroundColor: "#086CE1", + borderColor: "#086CE1", borderWidth: 1, data: { averageCPU: [25.5, 2], diff --git a/portal/src/app/hooks/useOutsideClick.test.tsx b/portal/src/app/hooks/useOutsideClick.test.tsx new file mode 100644 index 00000000..2aa48728 --- /dev/null +++ b/portal/src/app/hooks/useOutsideClick.test.tsx @@ -0,0 +1,30 @@ +import { act, cleanup, fireEvent, render } from '@testing-library/react'; +import { MutableRefObject, useRef } from 'react'; +import { useOutsideClick } from './useOutsideClick'; + +interface TestComponentProps { + onClickOutside: () => void +} + +describe('useOutsideClick', () => { + let TestComponent: React.FC; + + beforeEach(() => { + TestComponent = function Component({ onClickOutside }: TestComponentProps) { + const innerElementRef: MutableRefObject = useRef(null); + useOutsideClick(innerElementRef, onClickOutside); + return
    Test Component
    ; + }; + }); + afterEach(cleanup); + + test('should not trigger event when inside element is clicked', () => { + const onClickOutside: jest.Mock = jest.fn(); + const { getByTestId } = render(); + const insideElement: HTMLElement = getByTestId('inside'); + act(() => { + fireEvent.mouseDown(insideElement); + }); + expect(onClickOutside).toHaveBeenCalledTimes(0); + }); +}); From 526f83f673a07ec3b5072a666fa16310429f8dc7 Mon Sep 17 00:00:00 2001 From: Yehudit Hartman Date: Sun, 4 Feb 2024 23:33:45 +0200 Subject: [PATCH 32/35] Update docker-image.yml --- .github/workflows/docker-image.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index 6213c8f2..51140d6f 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -2,7 +2,7 @@ name: Build and Deploy to GitHub Pages on: push: - branches: [ "main" ] + branches: [ "main", "static-web-page/US63/conditional-main-page" ] pull_request: branches: [ "main" ] From dbcbc6912854949ab62ad14c6ca2397853eb8cc6 Mon Sep 17 00:00:00 2001 From: Yehudit Hartman Date: Mon, 5 Feb 2024 22:40:04 +0200 Subject: [PATCH 33/35] Update docker-image.yml --- .github/workflows/docker-image.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index 51140d6f..6213c8f2 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -2,7 +2,7 @@ name: Build and Deploy to GitHub Pages on: push: - branches: [ "main", "static-web-page/US63/conditional-main-page" ] + branches: [ "main" ] pull_request: branches: [ "main" ] From 86bc40cd28196200bc6307ef9edad56463cec713 Mon Sep 17 00:00:00 2001 From: iadibar Date: Tue, 6 Feb 2024 11:33:14 +0200 Subject: [PATCH 34/35] fix comments --- .../dynamic-chart/hooks/useDynamicChartData.ts | 14 +++++++++++++- .../models/dynamic-chart.interface.ts | 2 +- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/hooks/useDynamicChartData.ts b/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/hooks/useDynamicChartData.ts index dab39c7f..64b0c20c 100644 --- a/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/hooks/useDynamicChartData.ts +++ b/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/hooks/useDynamicChartData.ts @@ -19,7 +19,7 @@ export function useDynamicChartData(chartData: ITestRunResult): IUseDynamicChart } if (uniqueKeys.size > 0) { - setYAxiosOptions(Array.from(uniqueKeys).map(key => ({ label: key, value: key }))); + setYAxiosOptions(Array.from(uniqueKeys).map(key => ({ label: convertLabelByCapitalLetter(key), value: key }))); } }, [chartData]); @@ -27,3 +27,15 @@ export function useDynamicChartData(chartData: ITestRunResult): IUseDynamicChart yAxiosOptions, }; } + +function convertLabelByCapitalLetter(str: string): string { + let isFirstCapital = true; + const result = str.replace(/([A-Z])/g, (match) => { + if (isFirstCapital) { + isFirstCapital = false; + return ` ${match}`; + } + return match; + }).trim(); + return result.charAt(0).toUpperCase() + result.slice(1); +} diff --git a/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/models/dynamic-chart.interface.ts b/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/models/dynamic-chart.interface.ts index e12aeeeb..e501378d 100644 --- a/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/models/dynamic-chart.interface.ts +++ b/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/models/dynamic-chart.interface.ts @@ -11,7 +11,7 @@ export const chartTypeOptions: AttSelectOption[] = Object.keys(ChartType).map((k })); export enum XAxisType { - NUMBER_OF_ITERATIONS = 'number of iterations', + NUMBER_OF_ITERATIONS = 'Number of Iterations', } export const xAxisTypeOptions: AttSelectOption[] = Object.keys(XAxisType).map((key) => ({ From 580ac260243dd229cb898e7facd74f42a8cdbf21 Mon Sep 17 00:00:00 2001 From: iadibar Date: Tue, 6 Feb 2024 11:50:55 +0200 Subject: [PATCH 35/35] rename testRuns to test_runs --- portal/mock-server/src/test.json | 2 +- .../components/experiment/components/__mocks__/mocks.ts | 8 ++++---- .../experiment/components/charts/__mocks__/mocks.ts | 2 +- .../dynamic-chart/hooks/useDynamicChartData.test.ts | 2 +- .../components/dynamic-chart/hooks/useDynamicChartData.ts | 2 +- .../experiment/components/charts/hooks/useChartsData.ts | 4 ++-- .../components/experiment-table/ExperimentTable.tsx | 2 +- .../experiment/components/hooks/useExperimentData.test.ts | 4 ++-- .../experiment/components/hooks/useExperimentData.ts | 6 +++--- .../experiment/components/sub-header/SubHeader.tsx | 8 ++++---- portal/src/app/shared/models/test-run-result.interface.ts | 2 +- 11 files changed, 21 insertions(+), 21 deletions(-) diff --git a/portal/mock-server/src/test.json b/portal/mock-server/src/test.json index 7d9ef1e9..ed4ca2d7 100644 --- a/portal/mock-server/src/test.json +++ b/portal/mock-server/src/test.json @@ -15,7 +15,7 @@ "resourceName": "RELACE_WITH_RESOURCE_NAME" }, - "testRuns": [ + "test_runs": [ { "id":1, "algorithm": "bikel1", diff --git a/portal/src/app/components/home/components/experiment/components/__mocks__/mocks.ts b/portal/src/app/components/home/components/experiment/components/__mocks__/mocks.ts index 04658e61..8337a1aa 100644 --- a/portal/src/app/components/home/components/experiment/components/__mocks__/mocks.ts +++ b/portal/src/app/components/home/components/experiment/components/__mocks__/mocks.ts @@ -17,7 +17,7 @@ export const MOCK_DATA_FOR_EXPERIMENT: ITestRunResult = { nodeSize: "Standard_D4s_v5", codeRelease: "1.1.0", }, - testRuns: [ + test_runs: [ { id: 1, algorithm: "Algorithm1", @@ -68,7 +68,7 @@ export const MOCK_DATA_FOR_EXPERIMENT_TABLE: ExperimentTableProps = { nodeSize: "Standard_D4s_v5", codeRelease: "1.1.0", }, - testRuns: [ + test_runs: [ { id: 1, algorithm: "Algorithm1", @@ -138,7 +138,7 @@ export const MOCK_DATA_FOR_EXPERIMENT_WITH_NO_TEST_RUNS: ExperimentTableProps = nodeSize: "Standard_D4s_v5", codeRelease: "1.1.0", }, - testRuns: [] + test_runs: [] }, selectedColumns: [ { @@ -176,7 +176,7 @@ export const MOCK_SUB_HEADER: ITestRunResult = { operatingSystem: 'codeRelease', resourceName: 'codeRelease', }, - testRuns: [ + test_runs: [ { id:1, algorithm: "bikel1", diff --git a/portal/src/app/components/home/components/experiment/components/charts/__mocks__/mocks.ts b/portal/src/app/components/home/components/experiment/components/charts/__mocks__/mocks.ts index 690e7966..5d2a5260 100644 --- a/portal/src/app/components/home/components/experiment/components/charts/__mocks__/mocks.ts +++ b/portal/src/app/components/home/components/experiment/components/charts/__mocks__/mocks.ts @@ -17,7 +17,7 @@ export const MOCK_DATA_FOR_CHARTS: IExperimentData = { nodeSize: "Standard_D4s_v5", codeRelease: "1.1.0", }, - testRuns: [ + test_runs: [ { id: 1, algorithm: "Algorithm1", diff --git a/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/hooks/useDynamicChartData.test.ts b/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/hooks/useDynamicChartData.test.ts index f9ea9782..e7ec775b 100644 --- a/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/hooks/useDynamicChartData.test.ts +++ b/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/hooks/useDynamicChartData.test.ts @@ -7,7 +7,7 @@ describe('useDynamicChartData', () => { const { result } = renderHook(() => useDynamicChartData(MOCK_DATA_FOR_EXPERIMENT)); act(() => { - expect(result.current).toEqual( {yAxiosOptions: [{label: "averageCPU", value: "averageCPU"}, {label: "averageMemory", value: "averageMemory"}]}); + expect(result.current).toEqual( {yAxiosOptions: [{label: "Average CPU", value: "averageCPU"}, {label: "Average Memory", value: "averageMemory"}]}); }); }); }); diff --git a/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/hooks/useDynamicChartData.ts b/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/hooks/useDynamicChartData.ts index 64b0c20c..f271beee 100644 --- a/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/hooks/useDynamicChartData.ts +++ b/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/hooks/useDynamicChartData.ts @@ -11,7 +11,7 @@ export function useDynamicChartData(chartData: ITestRunResult): IUseDynamicChart useEffect(() => { const uniqueKeys = new Set(); - for (const testRun of chartData.testRuns) { + for (const testRun of chartData.test_runs) { const results = testRun.results; for (const key in results) { uniqueKeys.add(key); diff --git a/portal/src/app/components/home/components/experiment/components/charts/hooks/useChartsData.ts b/portal/src/app/components/home/components/experiment/components/charts/hooks/useChartsData.ts index 27a91197..33b57c23 100644 --- a/portal/src/app/components/home/components/experiment/components/charts/hooks/useChartsData.ts +++ b/portal/src/app/components/home/components/experiment/components/charts/hooks/useChartsData.ts @@ -63,8 +63,8 @@ export function useChartsData(props: IExperimentData): IUseChartsData { const [lineChartData, setLineChartData] = useState(); useEffect(() => { - if(props.data && props.data.testRuns.length > 0) { - const testRuns: ITestRunResultData[] = props.data.testRuns; + if(props.data && props.data.test_runs.length > 0) { + const testRuns: ITestRunResultData[] = props.data.test_runs; setBarChartData(testRuns); const labels: string[] = getLabels(testRuns); setBarChartLabels(labels); diff --git a/portal/src/app/components/home/components/experiment/components/experiment-table/ExperimentTable.tsx b/portal/src/app/components/home/components/experiment/components/experiment-table/ExperimentTable.tsx index d468223b..ed80af21 100644 --- a/portal/src/app/components/home/components/experiment/components/experiment-table/ExperimentTable.tsx +++ b/portal/src/app/components/home/components/experiment/components/experiment-table/ExperimentTable.tsx @@ -12,7 +12,7 @@ export interface ExperimentTableProps { } export const ExperimentTable: React.FC = (props: ExperimentTableProps) => { - const data = useMemo(() => (props.data ? props.data.testRuns : []), [props.data]); + const data = useMemo(() => (props.data ? props.data.test_runs : []), [props.data]); const headers: TableColumn[] = useMemo(() => [ { diff --git a/portal/src/app/components/home/components/experiment/components/hooks/useExperimentData.test.ts b/portal/src/app/components/home/components/experiment/components/hooks/useExperimentData.test.ts index c59d722d..38a54179 100644 --- a/portal/src/app/components/home/components/experiment/components/hooks/useExperimentData.test.ts +++ b/portal/src/app/components/home/components/experiment/components/hooks/useExperimentData.test.ts @@ -21,10 +21,10 @@ describe('useExperimentData', () => { (useFetchSpinner as jest.Mock).mockImplementation(() => undefined); (useErrorMessage as jest.Mock).mockImplementation(() => undefined); - const mockDataNumOfTestRuns = MOCK_DATA_FOR_EXPERIMENT.testRuns.length; + const mockDataNumOfTestRuns = MOCK_DATA_FOR_EXPERIMENT.test_runs.length; const { result } = renderHook(() => useExperimentData()); - expect(result.current.data.testRuns.length).toEqual(mockDataNumOfTestRuns); + expect(result.current.data.test_runs.length).toEqual(mockDataNumOfTestRuns); }); test('Should not render data', () => { diff --git a/portal/src/app/components/home/components/experiment/components/hooks/useExperimentData.ts b/portal/src/app/components/home/components/experiment/components/hooks/useExperimentData.ts index 6039ed60..d1c0bf57 100644 --- a/portal/src/app/components/home/components/experiment/components/hooks/useExperimentData.ts +++ b/portal/src/app/components/home/components/experiment/components/hooks/useExperimentData.ts @@ -29,9 +29,9 @@ export function useExperimentData(): IUseExperimentData { }, [get, cancelRequest]); useEffect(() => { - if (data && data.testRuns) { - const sortedData: ITestRunResultData[] = sortDataByAlgorithm(data.testRuns); - setTestRunData({ ...data, testRuns: sortedData }); + if (data && data.test_runs) { + const sortedData: ITestRunResultData[] = sortDataByAlgorithm(data.test_runs); + setTestRunData({ ...data, test_runs: sortedData }); } }, [data]); diff --git a/portal/src/app/components/home/components/experiment/components/sub-header/SubHeader.tsx b/portal/src/app/components/home/components/experiment/components/sub-header/SubHeader.tsx index 6818c4f3..66edca03 100644 --- a/portal/src/app/components/home/components/experiment/components/sub-header/SubHeader.tsx +++ b/portal/src/app/components/home/components/experiment/components/sub-header/SubHeader.tsx @@ -62,8 +62,8 @@ export const SubHeader: React.FC = (props: SubHeaderProps) => { const handleDownloadClick: () => void = useCallback((): void => { const csvFileName: string = `${SUB_HEADER_EN.CSV_REPORT.FILE_NAME}-${name || ''}.csv`; - downloadCsvFile(mapExperimentDataToCsvDataType(data.testRuns), csvFileName); - }, [data.testRuns, name]); + downloadCsvFile(mapExperimentDataToCsvDataType(data.test_runs), csvFileName); + }, [data.test_runs, name]); const handleCloseEditExperimentModal: (editData?: EditExperimentModalData) => void = useCallback((editData?: EditExperimentModalData): void => { if (editData) { @@ -103,11 +103,11 @@ export const SubHeader: React.FC = (props: SubHeaderProps) => {
    {SUB_HEADER_EN.ALGORITHM}
    -
    {getAlgorithmsName(data.testRuns)}
    +
    {getAlgorithmsName(data.test_runs)}
    {SUB_HEADER_EN.ITERATIONS}
    -
    {getIterations(data.testRuns)}
    +
    {getIterations(data.test_runs)}
    {experimentDescription}
    diff --git a/portal/src/app/shared/models/test-run-result.interface.ts b/portal/src/app/shared/models/test-run-result.interface.ts index 809f3bd6..d81f30e3 100644 --- a/portal/src/app/shared/models/test-run-result.interface.ts +++ b/portal/src/app/shared/models/test-run-result.interface.ts @@ -27,5 +27,5 @@ export interface ITestRunResult { start_time: string; end_time: string; environment_info: IEnvironmentInfo; - testRuns: ITestRunResultData[]; + test_runs: ITestRunResultData[]; }
    + {flexRender(cell.column.columnDef.cell, cell.getContext())}