diff --git a/portal/mock-server/src/all-experiments.json b/portal/mock-server/src/all-experiments.json index 1176ca9b..8c24bc1f 100644 --- a/portal/mock-server/src/all-experiments.json +++ b/portal/mock-server/src/all-experiments.json @@ -37,7 +37,7 @@ { "id": 359, "algorithm": "p256_kyber512", - "iterations": 1000, + "iterations": 10000, "message_size": 1024 }, { diff --git a/portal/package.json b/portal/package.json index 868ded62..ad27ccbd 100644 --- a/portal/package.json +++ b/portal/package.json @@ -117,7 +117,8 @@ "/src/index.tsx", "/src/reportWebVitals.ts", "/src/setupProxy.js", - "/src/environments/*.*" + "/src/environments/*.*", + "/src/gh-pages" ] } } diff --git a/portal/src/app/components/all-experiments/__mocks__/mocks.ts b/portal/src/app/components/all-experiments/__mocks__/mocks.ts new file mode 100644 index 00000000..58a66c1c --- /dev/null +++ b/portal/src/app/components/all-experiments/__mocks__/mocks.ts @@ -0,0 +1,54 @@ +import { Experiment } from "../models/experiments.interface"; + +export const MOCK_DATA_FOR_ALL_EXPERIMENTS: Experiment[] = [ + { + id: 17, + name: "Experiment 3", + end_time: 1705389926549, + test_runs: [ + { + id: 366, + algorithm: "prime256v1", + iterations: 500, + message_size: 256 + }, + { + id: 367, + algorithm: "bikel3", + iterations: 1000, + message_size: 2048 + }, + { + id: 368, + algorithm: "p256_kyber512", + iterations: 10000, + message_size: 1024 + }, + { + id: 369, + algorithm: "prime256v1", + iterations: 5000, + message_size: 512 + } + ] + }, + { + id: 18, + name: "Experiment 4", + end_time: 1705389926549, + test_runs: [ + { + id: 370, + algorithm: "kyber512", + iterations: 500, + message_size: 1024 + }, + { + id: 371, + algorithm: "kyber512", + iterations: 1000, + message_size: 2048 + } + ] + } +]; \ No newline at end of file diff --git a/portal/src/app/components/dashboard/components/charts/BarChart/BarChart.test.tsx b/portal/src/app/components/dashboard/components/charts/BarChart/BarChart.test.tsx index 74d7ffb6..cb1819a5 100644 --- a/portal/src/app/components/dashboard/components/charts/BarChart/BarChart.test.tsx +++ b/portal/src/app/components/dashboard/components/charts/BarChart/BarChart.test.tsx @@ -52,7 +52,7 @@ const data = [ ]; describe('BarChart', () => { test('renders BarChart component', () => { - const { getByTestId }: RenderResult = render(); + const { getByTestId }: RenderResult = render(); const chartElement: HTMLElement = getByTestId('chart'); expect(chartElement).toBeTruthy(); }); 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 016929f0..c7e20f2c 100644 --- a/portal/src/app/components/dashboard/components/charts/BarChart/BarChart.tsx +++ b/portal/src/app/components/dashboard/components/charts/BarChart/BarChart.tsx @@ -12,12 +12,13 @@ export interface BarChartProps { keyOfData: string; tooltipKeys: string[]; tooltipLabels: string[]; - title?: string; + titleX?: string; + titleY?: string; xAxiosTitle?: string; } export const BarChart: React.FC = (props: BarChartProps) => { - const { labels, data, tooltipKeys, tooltipLabels, keyOfData, title, xAxiosTitle } = props; + const { labels, data, tooltipKeys, tooltipLabels, keyOfData, titleX, titleY, xAxiosTitle } = props; const [dataValues, setDataValues] = useState(); const [datasets, setDatasets] = useState([]); const [algorithmsColors, setAlgorithmsColors] = useState<{[key: string]: string}>(); @@ -55,7 +56,18 @@ export const BarChart: React.FC = (props: BarChartProps) => { display: true, title: { display: true, - text: title ? title.replace(TITLE_PREFIX, '').trim() : '', + text: titleX ? titleX.replace(TITLE_PREFIX, '').trim() : '', + padding: { bottom: 30, top: 10 }, + }, + ticks: { + display: false, + }, + }, + y: { + display: true, + title: { + display: true, + text: titleY ? titleY.replace(TITLE_PREFIX, '').trim() : '', padding: { bottom: 30, top: 10 }, }, ticks: { diff --git a/portal/src/app/components/dashboard/components/charts/LineChart/LineChart.const.ts b/portal/src/app/components/dashboard/components/charts/LineChart/LineChart.const.ts index 27f2cb62..ee9017bd 100644 --- a/portal/src/app/components/dashboard/components/charts/LineChart/LineChart.const.ts +++ b/portal/src/app/components/dashboard/components/charts/LineChart/LineChart.const.ts @@ -1,30 +1,3 @@ -import { ChartOptions } from 'chart.js'; -import { CHARTS_EN } from '../../../../home/components/experiment/components/charts/translate/en'; - export const colors: string[] = ['#05BBFF', '#086CE1', '#FF8500', '#36a2eb33']; -export let defaultOptions: ChartOptions = { - responsive: true, - aspectRatio: 2, - scales: { - y: { - title: { - display: true, - text: CHARTS_EN.Y_AXIS_TITLE, - font: { - size: 14, - }, - padding: { bottom: 10 }, - }, - beginAtZero: true, - ticks: { - stepSize: 2, - font: { - size: 14, - }, - }, - }, - }, -}; - export const TITLE_PREFIX = 'Server Memory (%) vs.'; diff --git a/portal/src/app/components/dashboard/components/charts/LineChart/LineChart.test.tsx b/portal/src/app/components/dashboard/components/charts/LineChart/LineChart.test.tsx index ca3ad806..ac7e719c 100644 --- a/portal/src/app/components/dashboard/components/charts/LineChart/LineChart.test.tsx +++ b/portal/src/app/components/dashboard/components/charts/LineChart/LineChart.test.tsx @@ -24,7 +24,7 @@ const mockData = { describe('LineChart', () => { test('renders LineChart', () => { - const { getByTestId }: RenderResult = render(); + const { getByTestId }: RenderResult = render(); const chartElement: HTMLElement = getByTestId('line2'); expect(chartElement).toBeTruthy(); }); 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 1f478d00..4e6e5897 100644 --- a/portal/src/app/components/dashboard/components/charts/LineChart/LineChart.tsx +++ b/portal/src/app/components/dashboard/components/charts/LineChart/LineChart.tsx @@ -1,28 +1,44 @@ import { Line } from 'react-chartjs-2'; import { ChartOptions, Chart, TooltipItem } from 'chart.js'; -import { TITLE_PREFIX, defaultOptions } from './LineChart.const'; +import { TITLE_PREFIX } from './LineChart.const'; import { useRef } from 'react'; export interface LineChartProps { data: any; tooltipLabel?: string; - title?: string; + titleX?: string; + titleY?: string; xAxiosTitle?: string; } export const LineChart: React.FC = (props: LineChartProps) => { - const { data, title, tooltipLabel, xAxiosTitle } = props; + const { data, titleX, titleY, tooltipLabel, xAxiosTitle } = props; const chartRef = useRef>(null); const options: ChartOptions = { - ...defaultOptions, + responsive: true, + aspectRatio: 2, scales: { - ...defaultOptions.scales, x: { display: true, title: { display: true, - text: title ? title.replace(TITLE_PREFIX, '').trim() : '', + text: titleX ? titleX.replace(TITLE_PREFIX, '').trim() : '', + }, + }, + y: { + display: true, + title: { + display: true, + text: titleY ? titleY.replace(TITLE_PREFIX, '').trim() : '', + padding: { bottom: 10 }, + }, + beginAtZero: true, + ticks: { + stepSize: 2, + font: { + size: 14, + }, }, }, }, diff --git a/portal/src/app/components/home/Home.module.scss b/portal/src/app/components/home/Home.module.scss index 7a77a1b9..a06d28f7 100644 --- a/portal/src/app/components/home/Home.module.scss +++ b/portal/src/app/components/home/Home.module.scss @@ -2,6 +2,9 @@ @import "src/styles/z-index"; .app_wrapper { + display: flex; + justify-content: flex-start; + padding-block-start: 20px; padding-inline-start: 80px; padding-block-end: 40px; @@ -49,3 +52,17 @@ background-color: var($attPurple); padding: 14px; } + +.protocolQueryWithDivider { + position: relative; +} + +.protocolQueryWithDivider::after { + content: ""; + position: absolute; + inset-block-start: 35px; + inset-inline-start: 900px; + inline-size: 2px; + block-size: 91%; + background: var($dividerColorGray); +} diff --git a/portal/src/app/components/home/Home.tsx b/portal/src/app/components/home/Home.tsx index e05f1b2b..62210770 100644 --- a/portal/src/app/components/home/Home.tsx +++ b/portal/src/app/components/home/Home.tsx @@ -1,12 +1,14 @@ -import { IUseDashboardData, useDashboardData } from "../../hooks/useDashboardData"; -import { FetchDataStatus } from "../../shared/hooks/useFetch"; -import { ITestParams } from "../../shared/models/quantum.interface"; -import { ProtocolQuery } from "../protocol-query"; -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/models/experiments.interface"; +import cn from 'classnames'; +import { useLocation, useNavigate } from 'react-router-dom'; +import { IUseDashboardData, useDashboardData } from '../../hooks/useDashboardData'; +import { FetchDataStatus } from '../../shared/hooks/useFetch'; +import { ITestParams } from '../../shared/models/quantum.interface'; +import { ProtocolQuery } from '../protocol-query'; +import { SubHeader } from '../sub-header'; +import { useCallback, useEffect, useState } from 'react'; +import { ExperimentData } from '../all-experiments/models/experiments.interface'; +import { LatestExperiments } from './components'; export const Home: React.FC = () => { const [isSubHeaderOpen, setIsSubHeaderOpen] = useState(true); @@ -48,13 +50,14 @@ export const HomeContent: React.FC = () => { }, [handleRunQueryClick]); return ( -
+
+
); }; 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 index 6d6fc957..dbc81736 100644 --- 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 @@ -27,7 +27,7 @@ describe('DynamicChart', () => { }], }); - const { container } = render(); + const { container } = render(); expect(container).toBeTruthy(); }); }); 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 ae625c2f..cf0adc7e 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 @@ -12,7 +12,7 @@ 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 { getTitleByXAxiosValue, getTitleByYAxiosValue } from "./utils/dynamic-chart.utils"; import { LineChart } from "../../../../../../../dashboard/components/charts/LineChart"; import { getChartTitleByType } from "../../utils/chart.utils"; @@ -104,8 +104,8 @@ export const DynamicChart: React.FC = (props: DynamicChartPro {xAxisValue?.value && chartType?.value && yAxisValue?.value && <> - {chartType?.value === ChartType.BAR && barChartData && } - {chartType?.value === ChartType.LINE && lineChartConvertData && } + {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/hooks/useDynamicChartData.test.ts b/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/hooks/useDynamicChartData.test.ts index f243e082..c7225651 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: "Average Cpu", value: "average_cpu"}, {label: "Average Memory", value: "average_memory"}, {label: "Bytes Throughput", value: "bytes_throughput"}, {label: "Request Throughput", value: "request_throughput"}]}); + expect(result.current).toEqual( {yAxisOptions: [{label: "Average Cpu", value: "average_cpu"}, {label: "Average Memory", value: "average_memory"}, {label: "Bytes Throughput", value: "bytes_throughput"}, {label: "Request Throughput", value: "request_throughput"}]}); }); }); }); 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 e501378d..c7c9a3b9 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 @@ -14,6 +14,13 @@ export enum XAxisType { NUMBER_OF_ITERATIONS = 'Number of Iterations', } +export enum YAxisType { + AVERAGE_CPU = 'Average CPU', + AVERAGE_MEMORY = 'Average Memory', + BYTES_THROUGHPUT = 'Bytes Throughput', + REQUEST_THROUGHPUT = 'Request Throughput', +} + export const xAxisTypeOptions: AttSelectOption[] = Object.keys(XAxisType).map((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 ea6c046b..82f3be85 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 @@ -12,5 +12,11 @@ export const DYNAMIC_CHART_EN = { }, X_VALUES_TITLE: { ITERATIONS: 'Iterations', + }, + Y_VALUES_TITLE: { + AVERAGE_CPU: 'Average CPU', + AVERAGE_MEMORY: 'Average Memory', + BYTES_THROUGHPUT: 'Bytes Throughput', + REQUEST_THROUGHPUT: 'Request Throughput', } }; 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 4f20c0f7..76c13166 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,7 +1,7 @@ -import { ChartType } from "../models/dynamic-chart.interface"; +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"; +import { DYNAMIC_CHART_EN } from '../translate/en'; export function getIconByValue(value: ChartType): string { return value === ChartType.LINE ? LineSvg : BarSvg; @@ -14,3 +14,18 @@ export function capitalizeFirstLetter(str: string): string { export function getTitleByXAxiosValue(value: string): string { return value === 'NUMBER_OF_ITERATIONS' ? DYNAMIC_CHART_EN.X_VALUES_TITLE.ITERATIONS : ''; } + +export function getTitleByYAxiosValue(value: string): string { + switch (value) { + case 'average_cpu': + return DYNAMIC_CHART_EN.Y_VALUES_TITLE.AVERAGE_CPU; + case 'average_memory': + return DYNAMIC_CHART_EN.Y_VALUES_TITLE.AVERAGE_MEMORY; + case 'bytes_throughput': + return DYNAMIC_CHART_EN.Y_VALUES_TITLE.BYTES_THROUGHPUT; + case 'request_throughput': + return DYNAMIC_CHART_EN.Y_VALUES_TITLE.REQUEST_THROUGHPUT; + default: + return ''; + } +} diff --git a/portal/src/app/components/home/components/experiment/components/experiment-table/translate/en.ts b/portal/src/app/components/home/components/experiment/components/experiment-table/translate/en.ts index a946f0c9..89aad415 100644 --- a/portal/src/app/components/home/components/experiment/components/experiment-table/translate/en.ts +++ b/portal/src/app/components/home/components/experiment/components/experiment-table/translate/en.ts @@ -3,10 +3,10 @@ export const EXPERIMENT_TABLE_EN = { HASHTAG: '#', ALGORITHM: 'Algorithm', ITERATIONS: 'Iterations', - MESSAGE_SIZE: 'Message Size (KB)', - AVERAGE_CPU: 'Average CPU', - AVERAGE_MEMORY: 'Average Memory', - THROUGHPUT_BYTES: 'Throughput (bytes/sec)', - THROUGHPUT_REQUEST: 'Throughput (message/sec)', + MESSAGE_SIZE: 'Message Size (Bytes)', + AVERAGE_CPU: 'Average CPU (Cores %)', + AVERAGE_MEMORY: 'Average Memory (MB)', + THROUGHPUT_BYTES: 'Throughput (Bytes/sec)', + THROUGHPUT_REQUEST: 'Throughput (Messages/sec)', }, } diff --git a/portal/src/app/components/home/components/index.ts b/portal/src/app/components/home/components/index.ts index 152bd262..9d5f959d 100644 --- a/portal/src/app/components/home/components/index.ts +++ b/portal/src/app/components/home/components/index.ts @@ -1 +1,2 @@ export * from './experiment'; +export * from './latest-experiments'; diff --git a/portal/src/app/components/home/components/latest-experiments/LatestExperiments.module.scss b/portal/src/app/components/home/components/latest-experiments/LatestExperiments.module.scss new file mode 100644 index 00000000..966ace8c --- /dev/null +++ b/portal/src/app/components/home/components/latest-experiments/LatestExperiments.module.scss @@ -0,0 +1,52 @@ +@import "src/styles/variables-keys"; + +.latest_experiments_wrapper { + margin-inline-start: 160px; + margin-inline-end: 80px; + + .title_link_wrapper { + display: flex; + justify-content: space-between; + + .latest_experiments_title { + size: 18px; + } + + .all_experiments_link { + inset-inline-end: 100px; + margin-block-start: 50px; + color: var($attPurple); + cursor: pointer; + text-decoration: none; + } + } + + .latest_experiments_table { + border-radius: 20px; + background-color: var($backgroundColorWhite); + margin-block-start: 30px; + text-align: center; + padding: 10px 30px; + + th { + background-color: var($backgroundColorWhite); + color: var($textBlack); + border-bottom: 1px solid var($dividerColorGray); + } + td { + border: none; + } + + .experiment_name { + color: var($attPurple); + cursor: pointer; + border: none; + } + + .experiment_link { + color: var($attPurple); + cursor: pointer; + text-decoration: none; + } + } +} diff --git a/portal/src/app/components/home/components/latest-experiments/LatestExperiments.test.tsx b/portal/src/app/components/home/components/latest-experiments/LatestExperiments.test.tsx new file mode 100644 index 00000000..ba216fc9 --- /dev/null +++ b/portal/src/app/components/home/components/latest-experiments/LatestExperiments.test.tsx @@ -0,0 +1,51 @@ +import { fireEvent, render, screen, waitFor } from '@testing-library/react'; +import { BrowserRouter } from 'react-router-dom'; +import { LatestExperiments } from './LatestExperiments'; +import { FetchDataStatus } from '../../../../shared/hooks/useFetch'; +import { useExperimentsData } from '../../../all-experiments/hooks'; +import { MOCK_DATA_FOR_ALL_EXPERIMENTS } from '../../../all-experiments/__mocks__/mocks'; +import { QujataInsight, QujataInsightsProps } from '../../../../shared/components/qujata-insight'; + +jest.mock('../../../all-experiments/hooks'); +jest.mock('../../../../shared/components/qujata-insight'); + +describe('LatestExperiments', () => { + beforeEach(() => { + (useExperimentsData as jest.Mock).mockReturnValue({ + testSuites: MOCK_DATA_FOR_ALL_EXPERIMENTS, + status: FetchDataStatus.Success, + }); + }); + + test('renders without crashing', () => { + render( + + + + ); + }); + + test('should click on close insight button', async () => { + (QujataInsight as jest.Mock).mockImplementation((props: QujataInsightsProps) => { + function onInsightClose() { + props.onInsightClose(); + } + return
QujataInsight
; + }); + + const { container, getByTestId } = render( + + + + ); + const submitButtonElement: HTMLElement = getByTestId('close-id'); + + await waitFor(() => { + expect(container).toBeTruthy(); + }); + + await waitFor(() => { + fireEvent.click(submitButtonElement); + }); + }); +}); diff --git a/portal/src/app/components/home/components/latest-experiments/LatestExperiments.tsx b/portal/src/app/components/home/components/latest-experiments/LatestExperiments.tsx new file mode 100644 index 00000000..1dc28339 --- /dev/null +++ b/portal/src/app/components/home/components/latest-experiments/LatestExperiments.tsx @@ -0,0 +1,105 @@ +import { ReactNode, useMemo, useState } from 'react'; +import { Table, TableColumn } from '../../../../shared/components/table'; +import styles from './LatestExperiments.module.scss'; +import { LATEST_EXPERIMENTS_EN } from './translate/en'; +import { IUseExperimentsData, useExperimentsData } from '../../../all-experiments/hooks'; +import { FetchDataStatus } from '../../../../shared/hooks/useFetch'; +import { Experiment, TestRunSubset } from '../../../all-experiments/models/experiments.interface'; +import { Link } from 'react-router-dom'; +import { QujataInsight } from '../../../../shared/components/qujata-insight'; +import CloseSvg from '../../../../../assets/images/close.svg'; + +export const LatestExperiments: React.FC = () => { + const { testSuites, status }: IUseExperimentsData = useExperimentsData(); + const [isInsightShowed, setIsInsightShowed] = useState(true); + + const data = useMemo(() => (status === FetchDataStatus.Success && testSuites + ? testSuites.sort((a, b) => new Date(b.end_time).getTime() - new Date(a.end_time).getTime()) + : [] + ), [status, testSuites]); + + const headers: TableColumn[] = useMemo(() => [ + { + id: LATEST_EXPERIMENTS_EN.COLUMN_IDS.EXPERIMENT_NAME, + header: () => {LATEST_EXPERIMENTS_EN.TABLE_TITLES.EXPERIMENT_NAME}, + accessor: (row: Experiment) => row.name, + cell: cellInfo => ( + + {cellInfo.getValue() as ReactNode} + + ) + }, + { + id: LATEST_EXPERIMENTS_EN.COLUMN_IDS.DATE, + header: () => {LATEST_EXPERIMENTS_EN.TABLE_TITLES.DATE}, + accessor: (row: Experiment) => row.end_time, + cell: cellInfo => { + const date = new Date(cellInfo.getValue() as Date); + const formattedDate = date.toLocaleDateString('en-US', { day: 'numeric', month: 'long', year: 'numeric' }); + return {formattedDate} + } + }, + { + id: LATEST_EXPERIMENTS_EN.COLUMN_IDS.NUMBER_OF_ALGORITHMS, + header: () => {LATEST_EXPERIMENTS_EN.TABLE_TITLES.NUMBER_OF_ALGORITHMS}, + accessor: (row: Experiment) => { + const algorithms = new Set(); + row.test_runs.forEach((testRun: TestRunSubset) => algorithms.add(testRun.algorithm)); + return algorithms.size; + }, + cell: cellInfo => {cellInfo.getValue() as ReactNode} + }, + { + id: LATEST_EXPERIMENTS_EN.COLUMN_IDS.ITERATIONS, + header: () => {LATEST_EXPERIMENTS_EN.TABLE_TITLES.ITERATIONS}, + accessor: (row: Experiment) => { + const iterations = new Set(); + row.test_runs.forEach((testRun: TestRunSubset) => iterations.add(testRun.iterations)); + const iterationsArrayUnique = Array.from(iterations); + + return iterationsArrayUnique.length > 3 + ? `${iterationsArrayUnique.slice(0, 3).sort((a, b) => a - b).join(', ')}...` + : iterationsArrayUnique.sort((a, b) => a - b).join(', '); + }, + cell: cellInfo => {cellInfo.getValue() as ReactNode} + }, + { + id: LATEST_EXPERIMENTS_EN.COLUMN_IDS.MESSAGE_SIZES, + header: () => {LATEST_EXPERIMENTS_EN.TABLE_TITLES.MESSAGE_SIZES}, + accessor: (row: Experiment) => { + const messageSizes = new Set(); + row.test_runs.forEach((testRun: TestRunSubset) => messageSizes.add(testRun.message_size)); + const messageSizesArrayUnique = Array.from(messageSizes); + + return messageSizesArrayUnique.length > 3 + ? `${messageSizesArrayUnique.slice(0, 3).sort((a, b) => a - b).join(', ')}...` + : messageSizesArrayUnique.sort((a, b) => a - b).join(', '); + }, + cell: cellInfo => {cellInfo.getValue() as ReactNode} + }, + ], []); + + return ( status === FetchDataStatus.Success && +
+
+

{LATEST_EXPERIMENTS_EN.TITLE}

+ + {LATEST_EXPERIMENTS_EN.ALL_EXPERIMENTS} + +
+ + {isInsightShowed && setIsInsightShowed(false)} + title={LATEST_EXPERIMENTS_EN.INSIGHT.TITLE} + description={LATEST_EXPERIMENTS_EN.INSIGHT.DESCRIPTION} + linkName={LATEST_EXPERIMENTS_EN.INSIGHT.LINK_TITLE} + linkUrl={LATEST_EXPERIMENTS_EN.INSIGHT.LINK_URL} + />} + + ); +} diff --git a/portal/src/app/components/home/components/latest-experiments/index.ts b/portal/src/app/components/home/components/latest-experiments/index.ts new file mode 100644 index 00000000..bc477125 --- /dev/null +++ b/portal/src/app/components/home/components/latest-experiments/index.ts @@ -0,0 +1 @@ +export * from './LatestExperiments'; diff --git a/portal/src/app/components/home/components/latest-experiments/translate/en.ts b/portal/src/app/components/home/components/latest-experiments/translate/en.ts new file mode 100644 index 00000000..adca05fa --- /dev/null +++ b/portal/src/app/components/home/components/latest-experiments/translate/en.ts @@ -0,0 +1,26 @@ +export const LATEST_EXPERIMENTS_EN = { + TITLE: 'Latest Experiments', + ALL_EXPERIMENTS: 'All Experiments', + TABLE_TITLES: { + EXPERIMENT_NAME: 'Experiment Name', + DATE: 'Date', + NUMBER_OF_ALGORITHMS: '#Algorithms', + ITERATIONS: 'Iterations', + MESSAGE_SIZES: 'Message Size (KB)' + }, + COLUMN_IDS: { + EXPERIMENT_NAME: 'experimentName', + DATE: 'date', + NUMBER_OF_ALGORITHMS: 'numberOfAlgorithms', + ITERATIONS: 'iterations', + MESSAGE_SIZES: 'messageSizes' + }, + INSIGHT: { + TITLE: 'Recommendation', + DESCRIPTION: 'Explore more about our product and his abilities, development process and how you can contribute.', + LINK_TITLE: 'Learn more', + LINK_URL: 'https://github.com/att/qujata' + }, + HOMEPAGE_LINK: '/qujata', + ALL_EXPERIMENTS_LINK: '/qujata/test_suites', +} diff --git a/portal/src/app/components/protocol-query/ProtocolQuery.tsx b/portal/src/app/components/protocol-query/ProtocolQuery.tsx index d7f040a5..38093c67 100644 --- a/portal/src/app/components/protocol-query/ProtocolQuery.tsx +++ b/portal/src/app/components/protocol-query/ProtocolQuery.tsx @@ -106,6 +106,7 @@ export const ProtocolQuery: React.FC = (props: ProtocolQuery value={experimentName} onChange={onExperimentNameChanged} placeholder='' + disabled={isFetching} required /> @@ -122,6 +123,7 @@ export const ProtocolQuery: React.FC = (props: ProtocolQuery isMulti hideSelectedOptions={false} closeMenuOnSelect={false} + disabled={isFetching} required customComponent={{ Option: AlgorithmsSelectorCustomOption as React.FC }} /> @@ -141,6 +143,7 @@ export const ProtocolQuery: React.FC = (props: ProtocolQuery closeMenuOnSelect={false} menuIsOpen={iterationsMenuIsOpen} setMenuIsOpen={setIterationsMenuIsOpen} + disabled={isFetching} required customComponent={{ Option: (props: any) => @@ -170,6 +173,7 @@ export const ProtocolQuery: React.FC = (props: ProtocolQuery closeMenuOnSelect={false} menuIsOpen={messageSizeMenuIsOpen} setMenuIsOpen={setMessageSizeMenuIsOpen} + disabled={isFetching} required customComponent={{ Option: (props: any) => @@ -192,6 +196,7 @@ export const ProtocolQuery: React.FC = (props: ProtocolQuery className={styles.form_item_text_area} onChange={onDescriptionChanged} placeholder='' + disabled={isFetching} />
@@ -206,10 +211,10 @@ export const ProtocolQuery: React.FC = (props: ProtocolQuery {PROTOCOL_QUERY_EN.ACTION_BUTTONS.RUN} {isFetching && -
- - {PROTOCOL_QUERY_EN.FETCH_DATA} -
} +
+ + {PROTOCOL_QUERY_EN.FETCH_DATA} +
}
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 6ef07f04..5120a2ca 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 @@ -19,7 +19,7 @@ exports[`ProtocolQuery should render ProtocolQuery 1`] = ` Note:

- For each experiment you can select or type in one or more algorithms and number of iterations + For each experiment you can select or type in one or more algorithms, number of iterations and message sizes

diff --git a/portal/src/app/components/protocol-query/translate/en.ts b/portal/src/app/components/protocol-query/translate/en.ts index 8883be9b..ba880793 100644 --- a/portal/src/app/components/protocol-query/translate/en.ts +++ b/portal/src/app/components/protocol-query/translate/en.ts @@ -3,7 +3,7 @@ export const PROTOCOL_QUERY_EN = { TITLE: 'Run new experiment', NOTE: { TITLE: 'Note:', - TEXT: 'For each experiment you can select or type in one or more algorithms and number of iterations', + TEXT: 'For each experiment you can select or type in one or more algorithms, number of iterations and message sizes', }, ACTION_BUTTONS: { RUN: 'Run', diff --git a/portal/src/app/components/protocol-query/utils/handleAlgorithmsChange.test.ts b/portal/src/app/components/protocol-query/utils/handleAlgorithmsSelection.test.ts similarity index 98% rename from portal/src/app/components/protocol-query/utils/handleAlgorithmsChange.test.ts rename to portal/src/app/components/protocol-query/utils/handleAlgorithmsSelection.test.ts index c960ac0c..dc53bfa3 100644 --- a/portal/src/app/components/protocol-query/utils/handleAlgorithmsChange.test.ts +++ b/portal/src/app/components/protocol-query/utils/handleAlgorithmsSelection.test.ts @@ -1,4 +1,4 @@ -import { handleAlgorithmsSelection } from './handleAlgorithmsChange'; +import { handleAlgorithmsSelection } from './handleAlgorithmsSelection'; import { AttSelectOption } from '../../../shared/components/att-select'; import { AlgosBySectionDict } from '../hooks'; import { SelectOptionType } from '../ProtocolQuery'; diff --git a/portal/src/app/components/protocol-query/utils/handleAlgorithmsChange.ts b/portal/src/app/components/protocol-query/utils/handleAlgorithmsSelection.ts similarity index 100% rename from portal/src/app/components/protocol-query/utils/handleAlgorithmsChange.ts rename to portal/src/app/components/protocol-query/utils/handleAlgorithmsSelection.ts diff --git a/portal/src/app/components/protocol-query/utils/index.ts b/portal/src/app/components/protocol-query/utils/index.ts index 49d978da..7a01efd6 100644 --- a/portal/src/app/components/protocol-query/utils/index.ts +++ b/portal/src/app/components/protocol-query/utils/index.ts @@ -1,2 +1,2 @@ -export * from './handleAlgorithmsChange'; +export * from './handleAlgorithmsSelection'; export * from './convertBytesToHumanReadable'; \ No newline at end of file diff --git a/portal/src/app/components/sub-header/SubHeader.tsx b/portal/src/app/components/sub-header/SubHeader.tsx index ff4ae731..12fd1bd4 100644 --- a/portal/src/app/components/sub-header/SubHeader.tsx +++ b/portal/src/app/components/sub-header/SubHeader.tsx @@ -16,7 +16,7 @@ export interface SubHeaderProps { handleCloseClick: () => void; } export const SubHeader: React.FC = (props: SubHeaderProps) => { - const [open, setOpen] = useState(true); + const [open, setOpen] = useState(false); const handleToggleClick: () => void = useCallback((): void => { setOpen((prev: boolean) => !prev); 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 c4ae92d0..dcf2b13c 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 @@ -2,20 +2,17 @@ exports[`SubHeader renders sub header 1`] = `
Button
-
- Button -
void; + imageUrl?: string; + title: string; + description: string; + linkName: string; + linkUrl: string; +} + +export const QujataInsight = ({ className, closeImageUrl, onInsightClose, imageUrl, title, description, linkName, linkUrl }: QujataInsightsProps) => { + return ( +
+ {CloseAriaLabel} + {imageUrl && {InsightAriaLabel}} +
+
+
+ {LogoAriaLabel} +

{title}

+
+
{description}
+ {linkName} +
+
+ ); +}; diff --git a/portal/src/app/shared/components/qujata-insight/index.ts b/portal/src/app/shared/components/qujata-insight/index.ts new file mode 100644 index 00000000..273d0c43 --- /dev/null +++ b/portal/src/app/shared/components/qujata-insight/index.ts @@ -0,0 +1 @@ +export * from './QujataInsight'; diff --git a/portal/src/app/shared/components/table/Table.tsx b/portal/src/app/shared/components/table/Table.tsx index 37386ed5..18d08945 100644 --- a/portal/src/app/shared/components/table/Table.tsx +++ b/portal/src/app/shared/components/table/Table.tsx @@ -32,9 +32,11 @@ export interface TableProps { className?: string; headers: TableColumn[]; data: T[]; + enableSorting?: boolean; + limit?: number; } -export const Table = ({ headers, data, className }: TableProps) => { +export const Table = ({ headers, data, className, enableSorting = true, limit }: TableProps) => { const [sorting, setSorting] = useState([]) const columns: ColumnDef[] = useMemo(() => { return headers.map(header => ({ @@ -46,12 +48,12 @@ export const Table = ({ headers, data, className }: TableProps }, [headers]); const table = useReactTable({ - data, + data: limit ? data.slice(0, limit) : data, columns, state: { sorting, }, - onSortingChange: setSorting, + onSortingChange: enableSorting ? setSorting : undefined, getCoreRowModel: getCoreRowModel(), getSortedRowModel: getSortedRowModel(), }); @@ -65,17 +67,17 @@ export const Table = ({ headers, data, className }: TableProps
))} diff --git a/portal/src/assets/images/qujata-logo-purple.svg b/portal/src/assets/images/qujata-logo-purple.svg new file mode 100644 index 00000000..6ecf692b --- /dev/null +++ b/portal/src/assets/images/qujata-logo-purple.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/portal/src/assets/images/qujata-logo.svg b/portal/src/assets/images/qujata-logo.svg index e94b23f9..3536d790 100644 --- a/portal/src/assets/images/qujata-logo.svg +++ b/portal/src/assets/images/qujata-logo.svg @@ -1,8 +1,44 @@ - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/portal/src/styles/colors.scss b/portal/src/styles/colors.scss index 2ef970d9..1aad8da6 100644 --- a/portal/src/styles/colors.scss +++ b/portal/src/styles/colors.scss @@ -4,6 +4,7 @@ #{$primaryWhite}: white; #{$textBlack}: #1D2329; #{$backgroundColorBlack}: black; + #{$dividerColorGray}: #d0d0d0cf; #{$backgroundColorGray}: #F2F2F2; #{$backgroundColorWhite}: white; #{$errorTextColor}: #c70032; diff --git a/portal/src/styles/variables-keys.scss b/portal/src/styles/variables-keys.scss index 2f1a90d9..9d2de25e 100644 --- a/portal/src/styles/variables-keys.scss +++ b/portal/src/styles/variables-keys.scss @@ -34,6 +34,7 @@ $errorTextColor: --att-error-text-red; $actionTextHoverColor: --att-hover-text-color; $subTextColor: --att-sub-text-color; $backgroundColorBlack: --att-background-color-black; +$dividerColorGray: --att-divider-color-gray; $backgroundColorGray: --att-background-color-gray; $backgroundColorWhite: --att-background-color-white; $attCobalt: --att-att-cobalt; diff --git a/portal/src/styles/variables.scss b/portal/src/styles/variables.scss index aec03549..43ad2fd1 100644 --- a/portal/src/styles/variables.scss +++ b/portal/src/styles/variables.scss @@ -1,7 +1,7 @@ @import "./variables-keys"; :root { - #{$headerSize}: 64px; + #{$headerSize}: 80px; #{$fontRegular}: ATTAleckSans; #{$fontMedium}: ATTAleckSansMed; #{$fontBold}: ATTAleckSansBold; diff --git a/run/kubernetes/charts/portal/values.yaml b/run/kubernetes/charts/portal/values.yaml index 158b1016..9cbe6a29 100644 --- a/run/kubernetes/charts/portal/values.yaml +++ b/run/kubernetes/charts/portal/values.yaml @@ -6,7 +6,7 @@ replicaCount: 1 image: repository: qujata/portal - tag: "1.1.0" + tag: "1.2.0" pullPolicy: Always
{ flexRender(header.column.columnDef.header, header.getContext()) } - { { + { enableSorting && ({ asc: , desc: , - }[header.column.getIsSorted() as string] ?? null } + }[header.column.getIsSorted() as string] ?? null) }