From c035344bf4e0fc09917a8c689acb7b709db59c2c Mon Sep 17 00:00:00 2001
From: cheungrowan <138825305+cheungrowan@users.noreply.github.com>
Date: Tue, 8 Aug 2023 17:00:53 -0400
Subject: [PATCH 1/3] feat/local file system UI (#22)
* client interface and local client migration
* local client fixes
* add remote client when api env variable is set
* update local client for compatibility
* docs and type cleanup
* add initial summary walkthrough
* update walkthrough
* docs update
* remove generated files from docs
* update docs per feedback
* update tests
* clean up
* update model fields
* update data models and add paginated local file system responses
* add filter and sort for test suites
* update server with frontend
* update unit tests and type hints
* repopulate examples and remove unused html templates
* add javascript source
* update git ignore
* update requirements
* fix reqs
---
.gitignore | 4 +-
MANIFEST.in | 2 +-
arthur_bench/client/bench_client.py | 86 +
arthur_bench/client/local/client.py | 397 ++
arthur_bench/client/rest/admin/models.py | 2 +-
arthur_bench/client/rest/bench/client.py | 106 +-
arthur_bench/models/models.py | 202 +-
arthur_bench/run/testrun.py | 18 +-
arthur_bench/run/testsuite.py | 57 +-
arthur_bench/run/utils.py | 47 +-
arthur_bench/scoring/qa_quality.py | 2 +-
arthur_bench/scoring/scoring_method.py | 1 -
arthur_bench/server/html/assets/styles.css | 166 -
.../server/html/test_run_overview.html | 88 -
arthur_bench/server/html/test_run_table.html | 53 -
.../server/html/test_suite_overview.html | 81 -
arthur_bench/server/js/index.tsx | 17 +
arthur_bench/server/js/package.json | 55 +
.../js/packages/api/comparators.types.ts | 35 +
.../server/js/packages/api/package.json | 14 +
.../js/packages/api/useOnClickOutside.ts | 27 +
.../server/js/packages/arthur-axios/index.ts | 14 +
.../js/packages/arthur-axios/package.json | 14 +
.../config/configureStore.prod.ts | 22 +
.../arthur-redux/config/configureStore.ts | 14 +
.../arthur-redux/config/rootReducer.ts | 8 +
.../arthur-redux/config/state.type.ts | 6 +
.../server/js/packages/arthur-redux/index.ts | 6 +
.../js/packages/arthur-redux/package.json | 22 +
.../slices/skin/skin.constants.ts | 3 +
.../arthur-redux/slices/skin/skin.reducers.ts | 41 +
.../arthur-redux/slices/skin/skin.type.ts | 3 +
.../arthur-redux/slices/testSuites/actions.ts | 35 +
.../slices/testSuites/constants.ts | 20 +
.../slices/testSuites/reducers.ts | 78 +
.../arthur-redux/slices/testSuites/types.ts | 110 +
.../components/compound/Chip/Chip.tsx | 79 +
.../components/compound/Chip/index.ts | 6 +
.../components/compound/Chip/styles.ts | 47 +
.../components/compound/Chip/types.ts | 26 +
.../compound/FilterButton/FilterButton.tsx | 35 +
.../components/compound/FilterButton/index.ts | 3 +
.../compound/FilterButton/styles.ts | 23 +
.../FilterComponent/FilterComponent.styles.ts | 51 +
.../FilterComponent/FilterComponent.tsx | 45 +
.../FilterComponentProps.types.ts | 18 +
.../compound/FilterComponent/index.tsx | 3 +
.../compound/FloatingHelp/FloatingHelp.tsx | 299 ++
.../compound/FloatingHelp/index.tsx | 3 +
.../compound/FloatingHelp/styles.ts | 76 +
.../InputsOutputsTable/InputsOutputsTable.tsx | 102 +
.../compound/InsightCard/InsightCard.tsx | 93 +
.../components/compound/InsightCard/styles.ts | 59 +
.../components/compound/InsightCard/types.ts | 28 +
.../components/compound/InsightCard/utils.ts | 19 +
.../InsightHeadline/InsightHeadline.tsx | 89 +
.../compound/InsightHeadline/index.ts | 3 +
.../compound/InsightHeadline/styles.ts | 39 +
.../components/compound/Loading/index.tsx | 36 +
.../MetricDropdownSelection.tsx | 223 +
.../MetricsDropdownSelection/index.ts | 3 +
.../MetricsDropdownSelection/styles.ts | 97 +
.../MetricsDropdownSelection/typings.d.ts | 14 +
.../compound/ModeSelector/ModeSelector.tsx | 117 +
.../components/compound/ModeSelector/index.ts | 3 +
.../compound/ModeSelector/styles.ts | 33 +
.../MultipleSelect/MultipleSelect.tsx | 246 ++
.../compound/MultipleSelect/styles.ts | 117 +
.../compound/SortDropdown/SortDropdown.tsx | 76 +
.../compound/SortDropdown/styles.ts | 38 +
.../SummaryVisualizations/AverageScores.tsx | 83 +
.../RunDistributions.tsx | 43 +
.../SummaryVisualizations.tsx | 57 +
.../compound/SummaryVisualizations/styles.ts | 36 +
.../compound/TestRunDeepDive/Header.tsx | 30 +
.../TestRunDeepDive/TestRunDeepDive.tsx | 97 +
.../compound/TestRunDeepDive/styles.ts | 26 +
.../compound/TestRunTable/TestRunHeader.tsx | 60 +
.../compound/TestRunTable/TestRunRow.tsx | 60 +
.../compound/TestRunTable/TestRunTable.tsx | 124 +
.../compound/TestRunTable/styles.ts | 60 +
.../compound/TestSuiteCard/MethodTag.tsx | 43 +
.../compound/TestSuiteCard/TestSuiteCard.tsx | 78 +
.../compound/TestSuiteCard/styles.ts | 49 +
.../TestSuiteDetailsModal.tsx | 83 +
.../compound/TestSuiteDetailsModal/styles.ts | 64 +
.../TestSuitesHeader/TestSuitesHeader.tsx | 47 +
.../compound/TestSuitesHeader/styles.ts | 18 +
.../compound/WelcomeModal/WelcomeModal.tsx | 90 +
.../compound/WelcomeModal/styles.ts | 52 +
.../core/Breadcrumbs/Breadcrumbs.tsx | 56 +
.../components/core/Breadcrumbs/index.ts | 3 +
.../components/core/Breadcrumbs/styles.ts | 41 +
.../components/core/Button/Button.tsx | 114 +
.../components/core/Button/constants.ts | 24 +
.../packages/components/core/Button/index.ts | 4 +
.../packages/components/core/Button/styles.ts | 150 +
.../components/core/Button/typings.d.ts | 18 +
.../components/core/Button/typings.ts | 43 +
.../packages/components/core/Button/utils.ts | 49 +
.../core/Charts/BarChart/BarChart.stories.tsx | 111 +
.../core/Charts/BarChart/BarChart.tsx | 366 ++
.../components/core/Charts/BarChart/index.ts | 3 +
.../components/core/Charts/Legend/Legend.tsx | 52 +
.../components/core/Charts/Legend/styles.ts | 83 +
.../Charts/LineChart/LineChart.stories.tsx | 107 +
.../core/Charts/LineChart/LineChart.tsx | 340 ++
.../LineChart/components/MultiLineTooltip.tsx | 48 +
.../Charts/LineChart/components/styles.ts | 25 +
.../components/core/Charts/LineChart/index.ts | 4 +
.../core/Charts/LineChart/typings.d.ts | 58 +
.../components/core/Charts/constants.ts | 106 +
.../core/Checkbox/Checkbox.styles.ts | 57 +
.../components/core/Checkbox/Checkbox.tsx | 31 +
.../components/core/Checkbox/index.ts | 3 +
.../components/core/Checkbox/typings.d.ts | 15 +
.../core/Collapsible/Collapsible.tsx | 56 +
.../components/core/Collapsible/index.ts | 3 +
.../components/core/Collapsible/styles.ts | 5 +
.../components/core/Collapsible/typings.d.ts | 6 +
.../components/core/Dropdown/Dropdown.tsx | 112 +
.../components/core/Dropdown/index.ts | 3 +
.../components/core/Dropdown/styles.ts | 35 +
.../components/core/Field/Field.styles.ts | 26 +
.../packages/components/core/Field/Field.tsx | 19 +
.../packages/components/core/Field/index.ts | 3 +
.../packages/components/core/Field/types.ts | 4 +
.../components/core/HelpTile/HelpTile.tsx | 74 +
.../components/core/HelpTile/index.ts | 5 +
.../components/core/HelpTile/styles.ts | 62 +
.../components/core/HelpTile/typings.d.ts | 13 +
.../js/packages/components/core/Icon/Icon.tsx | 39 +
.../components/core/Icon/constants.ts | 1 +
.../js/packages/components/core/Icon/index.ts | 4 +
.../packages/components/core/Icon/readme.md | 18 +
.../js/packages/components/core/Icon/types.ts | 147 +
.../packages/components/core/Modal/Modal.tsx | 52 +
.../packages/components/core/Modal/styles.ts | 16 +
.../components/core/Paginator/Paginator.tsx | 303 ++
.../components/core/Paginator/index.ts | 3 +
.../components/core/Paginator/styles.ts | 101 +
.../components/core/Paginator/typings.d.ts | 21 +
.../packages/components/core/PopUp/PopUp.tsx | 80 +
.../core/PopUp/PopUpHeader/PopUpHeader.tsx | 27 +
.../core/PopUp/PopUpHeader/styles.tsx | 18 +
.../packages/components/core/PopUp/styles.ts | 23 +
.../components/core/Search/Search.styles.ts | 69 +
.../components/core/Search/Search.tsx | 60 +
.../packages/components/core/Search/index.ts | 3 +
.../packages/components/core/Search/types.ts | 18 +
.../core/StyledSelect/StyledSelect.tsx | 159 +
.../components/core/StyledSelect/index.ts | 3 +
.../components/core/StyledSelect/styles.ts | 85 +
.../packages/components/core/Table/Table.tsx | 22 +
.../Table/components/ExpandableTableCell.tsx | 100 +
.../core/Table/components/TableBody.tsx | 19 +
.../core/Table/components/TableCell.tsx | 45 +
.../core/Table/components/TableFooter.tsx | 19 +
.../core/Table/components/TableHeader.tsx | 19 +
.../core/Table/components/TableRow.tsx | 30 +
.../packages/components/core/Table/index.ts | 8 +
.../packages/components/core/Table/styles.ts | 33 +
.../components/core/Table/typings.d.ts | 33 +
.../js/packages/components/core/Tabs/Tab.tsx | 45 +
.../js/packages/components/core/Tabs/Tabs.tsx | 44 +
.../js/packages/components/core/Tabs/index.ts | 3 +
.../packages/components/core/Tabs/styles.ts | 68 +
.../components/core/Toggle/Toggle.styles.ts | 70 +
.../components/core/Toggle/Toggle.tsx | 41 +
.../components/core/Toggle/Toggle.types.ts | 8 +
.../packages/components/core/Toggle/index.ts | 3 +
.../components/core/Tooltip/Tooltip.styles.ts | 101 +
.../components/core/Tooltip/Tooltip.tsx | 59 +
.../js/packages/components/helpers/compose.ts | 4 +
.../js/packages/components/package.json | 59 +
.../js/packages/components/tsconfig.json | 10 +
.../js/packages/components/typings.d.ts | 2 +
.../resources/colors/Arthur/graphs.ts | 47 +
.../resources/colors/Arthur/primary.ts | 11 +
.../resources/colors/Arthur/secondary.ts | 45 +
.../resources/fonts/Graphik/Graphik-Black.otf | Bin 0 -> 140268 bytes
.../fonts/Graphik/Graphik-BlackItalic.otf | Bin 0 -> 142948 bytes
.../fonts/Graphik/Graphik-BlackItalic.woff | Bin 0 -> 52128 bytes
.../fonts/Graphik/Graphik-BlackItalic.woff2 | Bin 0 -> 38572 bytes
.../resources/fonts/Graphik/Graphik-Bold.otf | Bin 0 -> 139708 bytes
.../resources/fonts/Graphik/Graphik-Bold.woff | Bin 0 -> 50076 bytes
.../fonts/Graphik/Graphik-Bold.woff2 | Bin 0 -> 36932 bytes
.../fonts/Graphik/Graphik-BoldItalic.otf | Bin 0 -> 142756 bytes
.../fonts/Graphik/Graphik-BoldItalic.woff | Bin 0 -> 52616 bytes
.../fonts/Graphik/Graphik-BoldItalic.woff2 | Bin 0 -> 38936 bytes
.../fonts/Graphik/Graphik-Extralight.otf | Bin 0 -> 135248 bytes
.../fonts/Graphik/Graphik-Extralight.woff | Bin 0 -> 49532 bytes
.../fonts/Graphik/Graphik-Extralight.woff2 | Bin 0 -> 36448 bytes
.../Graphik/Graphik-ExtralightItalic.otf | Bin 0 -> 139472 bytes
.../Graphik/Graphik-ExtralightItalic.woff | Bin 0 -> 52396 bytes
.../Graphik/Graphik-ExtralightItalic.woff2 | Bin 0 -> 38292 bytes
.../resources/fonts/Graphik/Graphik-Light.otf | Bin 0 -> 134752 bytes
.../fonts/Graphik/Graphik-Light.woff | Bin 0 -> 49312 bytes
.../fonts/Graphik/Graphik-Light.woff2 | Bin 0 -> 36124 bytes
.../fonts/Graphik/Graphik-LightItalic.otf | Bin 0 -> 139012 bytes
.../fonts/Graphik/Graphik-LightItalic.woff | Bin 0 -> 52140 bytes
.../fonts/Graphik/Graphik-LightItalic.woff2 | Bin 0 -> 38200 bytes
.../fonts/Graphik/Graphik-Medium.otf | Bin 0 -> 137664 bytes
.../fonts/Graphik/Graphik-Medium.woff | Bin 0 -> 49964 bytes
.../fonts/Graphik/Graphik-Medium.woff2 | Bin 0 -> 36908 bytes
.../fonts/Graphik/Graphik-MediumItalic.otf | Bin 0 -> 140808 bytes
.../fonts/Graphik/Graphik-MediumItalic.woff | Bin 0 -> 52608 bytes
.../fonts/Graphik/Graphik-MediumItalic.woff2 | Bin 0 -> 38796 bytes
.../fonts/Graphik/Graphik-Regular.otf | Bin 0 -> 131204 bytes
.../fonts/Graphik/Graphik-Regular.woff | Bin 0 -> 46928 bytes
.../fonts/Graphik/Graphik-Regular.woff2 | Bin 0 -> 34352 bytes
.../fonts/Graphik/Graphik-RegularItalic.otf | Bin 0 -> 134832 bytes
.../fonts/Graphik/Graphik-Semibold.otf | Bin 0 -> 138448 bytes
.../fonts/Graphik/Graphik-Semibold.woff | Bin 0 -> 50004 bytes
.../fonts/Graphik/Graphik-Semibold.woff2 | Bin 0 -> 37064 bytes
.../fonts/Graphik/Graphik-SemiboldItalic.otf | Bin 0 -> 142280 bytes
.../fonts/Graphik/Graphik-SemiboldItalic.woff | Bin 0 -> 52772 bytes
.../Graphik/Graphik-SemiboldItalic.woff2 | Bin 0 -> 39000 bytes
.../resources/fonts/Graphik/Graphik-Super.otf | Bin 0 -> 138840 bytes
.../fonts/Graphik/Graphik-Super.woff | Bin 0 -> 48796 bytes
.../fonts/Graphik/Graphik-Super.woff2 | Bin 0 -> 35924 bytes
.../fonts/Graphik/Graphik-SuperItalic.otf | Bin 0 -> 139892 bytes
.../fonts/Graphik/Graphik-SuperItalic.woff | Bin 0 -> 50272 bytes
.../fonts/Graphik/Graphik-SuperItalic.woff2 | Bin 0 -> 36912 bytes
.../resources/fonts/Graphik/Graphik-Thin.otf | Bin 0 -> 131292 bytes
.../resources/fonts/Graphik/Graphik-Thin.woff | Bin 0 -> 47616 bytes
.../fonts/Graphik/Graphik-Thin.woff2 | Bin 0 -> 34792 bytes
.../fonts/Graphik/Graphik-ThinItalic.otf | Bin 0 -> 133272 bytes
.../fonts/Graphik/Graphik-ThinItalic.woff | Bin 0 -> 49768 bytes
.../fonts/Graphik/Graphik-ThinItalic.woff2 | Bin 0 -> 35832 bytes
.../fonts/IBM_Plex_Mono/IBMPlexMono-Bold.ttf | Bin 0 -> 111944 bytes
.../IBM_Plex_Mono/IBMPlexMono-BoldItalic.ttf | Bin 0 -> 118540 bytes
.../IBM_Plex_Mono/IBMPlexMono-ExtraLight.ttf | Bin 0 -> 110244 bytes
.../IBMPlexMono-ExtraLightItalic.ttf | Bin 0 -> 118700 bytes
.../IBM_Plex_Mono/IBMPlexMono-Italic.ttf | Bin 0 -> 117952 bytes
.../fonts/IBM_Plex_Mono/IBMPlexMono-Light.ttf | Bin 0 -> 109436 bytes
.../IBM_Plex_Mono/IBMPlexMono-LightItalic.ttf | Bin 0 -> 117416 bytes
.../IBM_Plex_Mono/IBMPlexMono-Medium.ttf | Bin 0 -> 110924 bytes
.../IBMPlexMono-MediumItalic.ttf | Bin 0 -> 118048 bytes
.../IBM_Plex_Mono/IBMPlexMono-Regular.ttf | Bin 0 -> 109780 bytes
.../IBM_Plex_Mono/IBMPlexMono-SemiBold.ttf | Bin 0 -> 114392 bytes
.../IBMPlexMono-SemiBoldItalic.ttf | Bin 0 -> 121360 bytes
.../fonts/IBM_Plex_Mono/IBMPlexMono-Thin.ttf | Bin 0 -> 112100 bytes
.../IBM_Plex_Mono/IBMPlexMono-ThinItalic.ttf | Bin 0 -> 120160 bytes
.../resources/fonts/IBM_Plex_Mono/OFL.txt | 93 +
.../resources/fonts/Lato/Lato-Black.ttf | Bin 0 -> 69500 bytes
.../resources/fonts/Lato/Lato-BlackItalic.ttf | Bin 0 -> 72000 bytes
.../resources/fonts/Lato/Lato-Bold.ttf | Bin 0 -> 73332 bytes
.../resources/fonts/Lato/Lato-BoldItalic.ttf | Bin 0 -> 77732 bytes
.../resources/fonts/Lato/Lato-Italic.ttf | Bin 0 -> 75792 bytes
.../resources/fonts/Lato/Lato-Light.ttf | Bin 0 -> 77208 bytes
.../resources/fonts/Lato/Lato-LightItalic.ttf | Bin 0 -> 49080 bytes
.../resources/fonts/Lato/Lato-Regular.ttf | Bin 0 -> 75152 bytes
.../resources/fonts/Lato/Lato-Thin.ttf | Bin 0 -> 69976 bytes
.../resources/fonts/Lato/Lato-ThinItalic.ttf | Bin 0 -> 48864 bytes
.../js/packages/resources/fonts/Lato/OFL.txt | 93 +
.../js/packages/resources/fonts/fonts.css | 326 ++
.../js/packages/resources/fonts/index.ts | 19 +
.../js/packages/resources/icons/constants.ts | 11 +
.../js/packages/resources/icons/selection.js | 3616 +++++++++++++++++
.../packages/resources/icons/selection.json | 3616 +++++++++++++++++
.../resources/images/Arthur_Logo_PBW.svg | 27 +
.../images/Arthur_Logo_Symbol_low_margin.svg | 3 +
.../js/packages/resources/images/welcome.svg | 14 +
.../server/js/packages/resources/package.json | 11 +
.../resources/style-constants/index.ts | 7 +
.../packages/resources/theme/button/index.ts | 216 +
.../js/packages/resources/theme/light.ts | 24 +
.../resources/theme/tagSelector/index.ts | 5 +
.../js/packages/resources/theme/types.ts | 46 +
.../js/packages/translations/constants.ts | 21 +
.../js/packages/translations/en/chat.json | 1 +
.../js/packages/translations/en/common.json | 735 ++++
.../js/packages/translations/en/element.json | 71 +
.../js/packages/translations/en/overview.json | 18 +
.../packages/translations/en/roundtable.json | 54 +
.../js/packages/translations/en/shield.json | 10 +
.../js/packages/translations/es/chatEs.json | 1 +
.../js/packages/translations/es/commonEs.json | 55 +
.../packages/translations/es/elementEs.json | 41 +
.../translations/es/roundtableEs.json | 23 +
.../js/packages/translations/es/shieldEs.json | 10 +
.../server/js/packages/translations/hooks.ts | 20 +
.../js/packages/translations/jp/chatJp.json | 1 +
.../js/packages/translations/jp/common.json | 73 +
.../js/packages/translations/jp/commonJp.json | 55 +
.../packages/translations/jp/elementJp.json | 41 +
.../translations/jp/roundtableJp.json | 23 +
.../js/packages/translations/jp/shieldJp.json | 10 +
.../server/js/packages/translations/types.ts | 16 +
.../server/js/packages/tsconfig/README.md | 3 +
.../server/js/packages/tsconfig/base.json | 22 +
.../server/js/packages/tsconfig/nextjs.json | 22 +
.../server/js/packages/tsconfig/package.json | 11 +
.../js/packages/tsconfig/react-library.json | 11 +
.../server/js/packages/utils/.eslintrc.js | 9 +
.../server/js/packages/utils/README.md | 1 +
.../server/js/packages/utils/babel.config.js | 7 +
.../capitalizeFirstLetter.test.js | 15 +
.../utils/capitalize-first-letter/index.ts | 15 +
.../server/js/packages/utils/chunk/index.ts | 9 +
.../abbreviateNumber/abbreviateNumber.test.js | 41 +
.../utils/format/abbreviateNumber/index.ts | 20 +
.../format/date-label-formatter/index.ts | 26 +
.../server/js/packages/utils/helpers/index.ts | 15 +
.../server/js/packages/utils/is-nil/index.ts | 6 +
.../js/packages/utils/is-nil/isNil.test.js | 27 +
.../server/js/packages/utils/jest.config.ts | 197 +
.../js/packages/utils/keypress-enter/index.ts | 19 +
.../js/packages/utils/node_modules/.bin/jest | 1 +
.../packages/utils/node_modules/.bin/ts-jest | 1 +
.../server/js/packages/utils/package.json | 31 +
.../js/packages/utils/parse-int/index.ts | 22 +
.../packages/utils/parse-int/parseInt.test.js | 23 +
.../server/js/packages/utils/range/index.ts | 7 +
.../js/packages/utils/range/range.test.ts | 7 +
.../server/js/packages/utils/tsconfig.json | 8 +
.../js/public/arthur-triangle-favicon.png | Bin 0 -> 1699 bytes
arthur_bench/server/js/public/index.html | 13 +
arthur_bench/server/js/src/App.tsx | 29 +
.../server/js/src/Bench/InputsOutputs.tsx | 20 +
arthur_bench/server/js/src/Bench/TestRun.tsx | 88 +
arthur_bench/server/js/src/Bench/TestRuns.tsx | 23 +
.../server/js/src/Bench/TestSuiteHeader.tsx | 105 +
.../server/js/src/Bench/TestSuites.tsx | 118 +
arthur_bench/server/js/src/Bench/index.tsx | 30 +
arthur_bench/server/js/src/Bench/styles.ts | 84 +
.../server/js/src/Bench/useTestSuites.ts | 136 +
arthur_bench/server/js/src/Skin/skin.tsx | 37 +
arthur_bench/server/js/src/copy/initCopy.ts | 39 +
arthur_bench/server/js/src/index.html | 13 +
arthur_bench/server/js/src/renderer.ts | 3 +
arthur_bench/server/js/src/routes/index.tsx | 40 +
arthur_bench/server/js/tsconfig.json | 16 +
.../server/js/webpack-config/config.js | 72 +
arthur_bench/server/run_server.py | 120 +-
...thur_bench.scoring.bertscore.BERTScore.rst | 23 -
...coring.qa_quality.QAQualityCorrectness.rst | 23 -
...h.scoring.scoring_method.ScoringMethod.rst | 23 -
...scoring.summary_quality.SummaryQuality.rst | 23 -
docs/source/guides.rst | 3 +-
docs/source/quickstart.md | 21 +
docs/source/walkthrough.md | 96 +
.../bench/clothing_qa/chatgpt-run/run.json | 1 -
.../bench/clothing_qa/claude-run/run.json | 1 -
.../bench/clothing_qa/cohere-run/run.json | 1 -
.../bench/clothing_qa/minilm-run/run.json | 1 -
.../bench/clothing_qa/roberta-run/run.json | 1 -
examples/bench/clothing_qa/suite.json | 1 -
.../clothing_qa/tinyroberta-run/run.json | 1 -
examples/bench/news_summary/corrupt/run.json | 2 +-
.../bench/news_summary/longt5books/run.json | 2 +-
examples/bench/news_summary/rephrase/run.json | 2 +-
.../bench/news_summary/run_id_to_name.json | 1 +
examples/bench/news_summary/suite.json | 2 +-
.../stack_dist_to_golden/prompt1/run.json | 1 +
.../stack_dist_to_golden/prompt39842/run.json | 1 +
.../stack_dist_to_golden/run_id_to_name.json | 1 +
.../bench/stack_dist_to_golden/suite.json | 2 +-
examples/bench/suite_id_to_name.json | 1 +
examples/demo_question_answering.ipynb | 2024 ---------
pyproject.toml | 7 +-
requirements.txt | 75 +-
test/conftest.py | 2 +-
test/fixtures/mock_data.py | 31 +
test/fixtures/mock_requests.py | 45 +
test/fixtures/mock_responses.py | 35 +
test/helpers.py | 35 +-
test/test_testrun.py | 5 +-
test/test_testsuite.py | 173 +-
370 files changed, 20440 insertions(+), 2934 deletions(-)
create mode 100644 arthur_bench/client/bench_client.py
create mode 100644 arthur_bench/client/local/client.py
delete mode 100644 arthur_bench/server/html/assets/styles.css
delete mode 100644 arthur_bench/server/html/test_run_overview.html
delete mode 100644 arthur_bench/server/html/test_run_table.html
delete mode 100644 arthur_bench/server/html/test_suite_overview.html
create mode 100644 arthur_bench/server/js/index.tsx
create mode 100644 arthur_bench/server/js/package.json
create mode 100644 arthur_bench/server/js/packages/api/comparators.types.ts
create mode 100644 arthur_bench/server/js/packages/api/package.json
create mode 100644 arthur_bench/server/js/packages/api/useOnClickOutside.ts
create mode 100644 arthur_bench/server/js/packages/arthur-axios/index.ts
create mode 100644 arthur_bench/server/js/packages/arthur-axios/package.json
create mode 100644 arthur_bench/server/js/packages/arthur-redux/config/configureStore.prod.ts
create mode 100644 arthur_bench/server/js/packages/arthur-redux/config/configureStore.ts
create mode 100644 arthur_bench/server/js/packages/arthur-redux/config/rootReducer.ts
create mode 100644 arthur_bench/server/js/packages/arthur-redux/config/state.type.ts
create mode 100644 arthur_bench/server/js/packages/arthur-redux/index.ts
create mode 100644 arthur_bench/server/js/packages/arthur-redux/package.json
create mode 100644 arthur_bench/server/js/packages/arthur-redux/slices/skin/skin.constants.ts
create mode 100644 arthur_bench/server/js/packages/arthur-redux/slices/skin/skin.reducers.ts
create mode 100644 arthur_bench/server/js/packages/arthur-redux/slices/skin/skin.type.ts
create mode 100644 arthur_bench/server/js/packages/arthur-redux/slices/testSuites/actions.ts
create mode 100644 arthur_bench/server/js/packages/arthur-redux/slices/testSuites/constants.ts
create mode 100644 arthur_bench/server/js/packages/arthur-redux/slices/testSuites/reducers.ts
create mode 100644 arthur_bench/server/js/packages/arthur-redux/slices/testSuites/types.ts
create mode 100644 arthur_bench/server/js/packages/components/compound/Chip/Chip.tsx
create mode 100644 arthur_bench/server/js/packages/components/compound/Chip/index.ts
create mode 100644 arthur_bench/server/js/packages/components/compound/Chip/styles.ts
create mode 100644 arthur_bench/server/js/packages/components/compound/Chip/types.ts
create mode 100644 arthur_bench/server/js/packages/components/compound/FilterButton/FilterButton.tsx
create mode 100644 arthur_bench/server/js/packages/components/compound/FilterButton/index.ts
create mode 100644 arthur_bench/server/js/packages/components/compound/FilterButton/styles.ts
create mode 100644 arthur_bench/server/js/packages/components/compound/FilterComponent/FilterComponent.styles.ts
create mode 100644 arthur_bench/server/js/packages/components/compound/FilterComponent/FilterComponent.tsx
create mode 100644 arthur_bench/server/js/packages/components/compound/FilterComponent/FilterComponentProps.types.ts
create mode 100644 arthur_bench/server/js/packages/components/compound/FilterComponent/index.tsx
create mode 100644 arthur_bench/server/js/packages/components/compound/FloatingHelp/FloatingHelp.tsx
create mode 100644 arthur_bench/server/js/packages/components/compound/FloatingHelp/index.tsx
create mode 100644 arthur_bench/server/js/packages/components/compound/FloatingHelp/styles.ts
create mode 100644 arthur_bench/server/js/packages/components/compound/InputsOutputsTable/InputsOutputsTable.tsx
create mode 100644 arthur_bench/server/js/packages/components/compound/InsightCard/InsightCard.tsx
create mode 100644 arthur_bench/server/js/packages/components/compound/InsightCard/styles.ts
create mode 100644 arthur_bench/server/js/packages/components/compound/InsightCard/types.ts
create mode 100644 arthur_bench/server/js/packages/components/compound/InsightCard/utils.ts
create mode 100644 arthur_bench/server/js/packages/components/compound/InsightHeadline/InsightHeadline.tsx
create mode 100644 arthur_bench/server/js/packages/components/compound/InsightHeadline/index.ts
create mode 100644 arthur_bench/server/js/packages/components/compound/InsightHeadline/styles.ts
create mode 100644 arthur_bench/server/js/packages/components/compound/Loading/index.tsx
create mode 100644 arthur_bench/server/js/packages/components/compound/MetricsDropdownSelection/MetricDropdownSelection.tsx
create mode 100644 arthur_bench/server/js/packages/components/compound/MetricsDropdownSelection/index.ts
create mode 100644 arthur_bench/server/js/packages/components/compound/MetricsDropdownSelection/styles.ts
create mode 100644 arthur_bench/server/js/packages/components/compound/MetricsDropdownSelection/typings.d.ts
create mode 100644 arthur_bench/server/js/packages/components/compound/ModeSelector/ModeSelector.tsx
create mode 100644 arthur_bench/server/js/packages/components/compound/ModeSelector/index.ts
create mode 100644 arthur_bench/server/js/packages/components/compound/ModeSelector/styles.ts
create mode 100644 arthur_bench/server/js/packages/components/compound/MultipleSelect/MultipleSelect.tsx
create mode 100644 arthur_bench/server/js/packages/components/compound/MultipleSelect/styles.ts
create mode 100644 arthur_bench/server/js/packages/components/compound/SortDropdown/SortDropdown.tsx
create mode 100644 arthur_bench/server/js/packages/components/compound/SortDropdown/styles.ts
create mode 100644 arthur_bench/server/js/packages/components/compound/SummaryVisualizations/AverageScores.tsx
create mode 100644 arthur_bench/server/js/packages/components/compound/SummaryVisualizations/RunDistributions.tsx
create mode 100644 arthur_bench/server/js/packages/components/compound/SummaryVisualizations/SummaryVisualizations.tsx
create mode 100644 arthur_bench/server/js/packages/components/compound/SummaryVisualizations/styles.ts
create mode 100644 arthur_bench/server/js/packages/components/compound/TestRunDeepDive/Header.tsx
create mode 100644 arthur_bench/server/js/packages/components/compound/TestRunDeepDive/TestRunDeepDive.tsx
create mode 100644 arthur_bench/server/js/packages/components/compound/TestRunDeepDive/styles.ts
create mode 100644 arthur_bench/server/js/packages/components/compound/TestRunTable/TestRunHeader.tsx
create mode 100644 arthur_bench/server/js/packages/components/compound/TestRunTable/TestRunRow.tsx
create mode 100644 arthur_bench/server/js/packages/components/compound/TestRunTable/TestRunTable.tsx
create mode 100644 arthur_bench/server/js/packages/components/compound/TestRunTable/styles.ts
create mode 100644 arthur_bench/server/js/packages/components/compound/TestSuiteCard/MethodTag.tsx
create mode 100644 arthur_bench/server/js/packages/components/compound/TestSuiteCard/TestSuiteCard.tsx
create mode 100644 arthur_bench/server/js/packages/components/compound/TestSuiteCard/styles.ts
create mode 100644 arthur_bench/server/js/packages/components/compound/TestSuiteDetailsModal/TestSuiteDetailsModal.tsx
create mode 100644 arthur_bench/server/js/packages/components/compound/TestSuiteDetailsModal/styles.ts
create mode 100644 arthur_bench/server/js/packages/components/compound/TestSuitesHeader/TestSuitesHeader.tsx
create mode 100644 arthur_bench/server/js/packages/components/compound/TestSuitesHeader/styles.ts
create mode 100644 arthur_bench/server/js/packages/components/compound/WelcomeModal/WelcomeModal.tsx
create mode 100644 arthur_bench/server/js/packages/components/compound/WelcomeModal/styles.ts
create mode 100644 arthur_bench/server/js/packages/components/core/Breadcrumbs/Breadcrumbs.tsx
create mode 100644 arthur_bench/server/js/packages/components/core/Breadcrumbs/index.ts
create mode 100644 arthur_bench/server/js/packages/components/core/Breadcrumbs/styles.ts
create mode 100644 arthur_bench/server/js/packages/components/core/Button/Button.tsx
create mode 100644 arthur_bench/server/js/packages/components/core/Button/constants.ts
create mode 100644 arthur_bench/server/js/packages/components/core/Button/index.ts
create mode 100644 arthur_bench/server/js/packages/components/core/Button/styles.ts
create mode 100644 arthur_bench/server/js/packages/components/core/Button/typings.d.ts
create mode 100644 arthur_bench/server/js/packages/components/core/Button/typings.ts
create mode 100644 arthur_bench/server/js/packages/components/core/Button/utils.ts
create mode 100644 arthur_bench/server/js/packages/components/core/Charts/BarChart/BarChart.stories.tsx
create mode 100644 arthur_bench/server/js/packages/components/core/Charts/BarChart/BarChart.tsx
create mode 100644 arthur_bench/server/js/packages/components/core/Charts/BarChart/index.ts
create mode 100644 arthur_bench/server/js/packages/components/core/Charts/Legend/Legend.tsx
create mode 100644 arthur_bench/server/js/packages/components/core/Charts/Legend/styles.ts
create mode 100644 arthur_bench/server/js/packages/components/core/Charts/LineChart/LineChart.stories.tsx
create mode 100644 arthur_bench/server/js/packages/components/core/Charts/LineChart/LineChart.tsx
create mode 100644 arthur_bench/server/js/packages/components/core/Charts/LineChart/components/MultiLineTooltip.tsx
create mode 100644 arthur_bench/server/js/packages/components/core/Charts/LineChart/components/styles.ts
create mode 100644 arthur_bench/server/js/packages/components/core/Charts/LineChart/index.ts
create mode 100644 arthur_bench/server/js/packages/components/core/Charts/LineChart/typings.d.ts
create mode 100644 arthur_bench/server/js/packages/components/core/Charts/constants.ts
create mode 100644 arthur_bench/server/js/packages/components/core/Checkbox/Checkbox.styles.ts
create mode 100644 arthur_bench/server/js/packages/components/core/Checkbox/Checkbox.tsx
create mode 100644 arthur_bench/server/js/packages/components/core/Checkbox/index.ts
create mode 100644 arthur_bench/server/js/packages/components/core/Checkbox/typings.d.ts
create mode 100644 arthur_bench/server/js/packages/components/core/Collapsible/Collapsible.tsx
create mode 100644 arthur_bench/server/js/packages/components/core/Collapsible/index.ts
create mode 100644 arthur_bench/server/js/packages/components/core/Collapsible/styles.ts
create mode 100644 arthur_bench/server/js/packages/components/core/Collapsible/typings.d.ts
create mode 100644 arthur_bench/server/js/packages/components/core/Dropdown/Dropdown.tsx
create mode 100644 arthur_bench/server/js/packages/components/core/Dropdown/index.ts
create mode 100644 arthur_bench/server/js/packages/components/core/Dropdown/styles.ts
create mode 100644 arthur_bench/server/js/packages/components/core/Field/Field.styles.ts
create mode 100644 arthur_bench/server/js/packages/components/core/Field/Field.tsx
create mode 100644 arthur_bench/server/js/packages/components/core/Field/index.ts
create mode 100644 arthur_bench/server/js/packages/components/core/Field/types.ts
create mode 100644 arthur_bench/server/js/packages/components/core/HelpTile/HelpTile.tsx
create mode 100644 arthur_bench/server/js/packages/components/core/HelpTile/index.ts
create mode 100644 arthur_bench/server/js/packages/components/core/HelpTile/styles.ts
create mode 100644 arthur_bench/server/js/packages/components/core/HelpTile/typings.d.ts
create mode 100644 arthur_bench/server/js/packages/components/core/Icon/Icon.tsx
create mode 100644 arthur_bench/server/js/packages/components/core/Icon/constants.ts
create mode 100644 arthur_bench/server/js/packages/components/core/Icon/index.ts
create mode 100644 arthur_bench/server/js/packages/components/core/Icon/readme.md
create mode 100644 arthur_bench/server/js/packages/components/core/Icon/types.ts
create mode 100644 arthur_bench/server/js/packages/components/core/Modal/Modal.tsx
create mode 100644 arthur_bench/server/js/packages/components/core/Modal/styles.ts
create mode 100644 arthur_bench/server/js/packages/components/core/Paginator/Paginator.tsx
create mode 100644 arthur_bench/server/js/packages/components/core/Paginator/index.ts
create mode 100644 arthur_bench/server/js/packages/components/core/Paginator/styles.ts
create mode 100644 arthur_bench/server/js/packages/components/core/Paginator/typings.d.ts
create mode 100644 arthur_bench/server/js/packages/components/core/PopUp/PopUp.tsx
create mode 100644 arthur_bench/server/js/packages/components/core/PopUp/PopUpHeader/PopUpHeader.tsx
create mode 100644 arthur_bench/server/js/packages/components/core/PopUp/PopUpHeader/styles.tsx
create mode 100644 arthur_bench/server/js/packages/components/core/PopUp/styles.ts
create mode 100644 arthur_bench/server/js/packages/components/core/Search/Search.styles.ts
create mode 100644 arthur_bench/server/js/packages/components/core/Search/Search.tsx
create mode 100644 arthur_bench/server/js/packages/components/core/Search/index.ts
create mode 100644 arthur_bench/server/js/packages/components/core/Search/types.ts
create mode 100644 arthur_bench/server/js/packages/components/core/StyledSelect/StyledSelect.tsx
create mode 100644 arthur_bench/server/js/packages/components/core/StyledSelect/index.ts
create mode 100644 arthur_bench/server/js/packages/components/core/StyledSelect/styles.ts
create mode 100644 arthur_bench/server/js/packages/components/core/Table/Table.tsx
create mode 100644 arthur_bench/server/js/packages/components/core/Table/components/ExpandableTableCell.tsx
create mode 100644 arthur_bench/server/js/packages/components/core/Table/components/TableBody.tsx
create mode 100644 arthur_bench/server/js/packages/components/core/Table/components/TableCell.tsx
create mode 100644 arthur_bench/server/js/packages/components/core/Table/components/TableFooter.tsx
create mode 100644 arthur_bench/server/js/packages/components/core/Table/components/TableHeader.tsx
create mode 100644 arthur_bench/server/js/packages/components/core/Table/components/TableRow.tsx
create mode 100644 arthur_bench/server/js/packages/components/core/Table/index.ts
create mode 100644 arthur_bench/server/js/packages/components/core/Table/styles.ts
create mode 100644 arthur_bench/server/js/packages/components/core/Table/typings.d.ts
create mode 100644 arthur_bench/server/js/packages/components/core/Tabs/Tab.tsx
create mode 100644 arthur_bench/server/js/packages/components/core/Tabs/Tabs.tsx
create mode 100644 arthur_bench/server/js/packages/components/core/Tabs/index.ts
create mode 100644 arthur_bench/server/js/packages/components/core/Tabs/styles.ts
create mode 100644 arthur_bench/server/js/packages/components/core/Toggle/Toggle.styles.ts
create mode 100644 arthur_bench/server/js/packages/components/core/Toggle/Toggle.tsx
create mode 100644 arthur_bench/server/js/packages/components/core/Toggle/Toggle.types.ts
create mode 100644 arthur_bench/server/js/packages/components/core/Toggle/index.ts
create mode 100644 arthur_bench/server/js/packages/components/core/Tooltip/Tooltip.styles.ts
create mode 100644 arthur_bench/server/js/packages/components/core/Tooltip/Tooltip.tsx
create mode 100644 arthur_bench/server/js/packages/components/helpers/compose.ts
create mode 100644 arthur_bench/server/js/packages/components/package.json
create mode 100644 arthur_bench/server/js/packages/components/tsconfig.json
create mode 100644 arthur_bench/server/js/packages/components/typings.d.ts
create mode 100644 arthur_bench/server/js/packages/resources/colors/Arthur/graphs.ts
create mode 100644 arthur_bench/server/js/packages/resources/colors/Arthur/primary.ts
create mode 100644 arthur_bench/server/js/packages/resources/colors/Arthur/secondary.ts
create mode 100644 arthur_bench/server/js/packages/resources/fonts/Graphik/Graphik-Black.otf
create mode 100644 arthur_bench/server/js/packages/resources/fonts/Graphik/Graphik-BlackItalic.otf
create mode 100644 arthur_bench/server/js/packages/resources/fonts/Graphik/Graphik-BlackItalic.woff
create mode 100644 arthur_bench/server/js/packages/resources/fonts/Graphik/Graphik-BlackItalic.woff2
create mode 100644 arthur_bench/server/js/packages/resources/fonts/Graphik/Graphik-Bold.otf
create mode 100644 arthur_bench/server/js/packages/resources/fonts/Graphik/Graphik-Bold.woff
create mode 100644 arthur_bench/server/js/packages/resources/fonts/Graphik/Graphik-Bold.woff2
create mode 100644 arthur_bench/server/js/packages/resources/fonts/Graphik/Graphik-BoldItalic.otf
create mode 100644 arthur_bench/server/js/packages/resources/fonts/Graphik/Graphik-BoldItalic.woff
create mode 100644 arthur_bench/server/js/packages/resources/fonts/Graphik/Graphik-BoldItalic.woff2
create mode 100644 arthur_bench/server/js/packages/resources/fonts/Graphik/Graphik-Extralight.otf
create mode 100644 arthur_bench/server/js/packages/resources/fonts/Graphik/Graphik-Extralight.woff
create mode 100644 arthur_bench/server/js/packages/resources/fonts/Graphik/Graphik-Extralight.woff2
create mode 100644 arthur_bench/server/js/packages/resources/fonts/Graphik/Graphik-ExtralightItalic.otf
create mode 100644 arthur_bench/server/js/packages/resources/fonts/Graphik/Graphik-ExtralightItalic.woff
create mode 100644 arthur_bench/server/js/packages/resources/fonts/Graphik/Graphik-ExtralightItalic.woff2
create mode 100644 arthur_bench/server/js/packages/resources/fonts/Graphik/Graphik-Light.otf
create mode 100644 arthur_bench/server/js/packages/resources/fonts/Graphik/Graphik-Light.woff
create mode 100644 arthur_bench/server/js/packages/resources/fonts/Graphik/Graphik-Light.woff2
create mode 100644 arthur_bench/server/js/packages/resources/fonts/Graphik/Graphik-LightItalic.otf
create mode 100644 arthur_bench/server/js/packages/resources/fonts/Graphik/Graphik-LightItalic.woff
create mode 100644 arthur_bench/server/js/packages/resources/fonts/Graphik/Graphik-LightItalic.woff2
create mode 100644 arthur_bench/server/js/packages/resources/fonts/Graphik/Graphik-Medium.otf
create mode 100644 arthur_bench/server/js/packages/resources/fonts/Graphik/Graphik-Medium.woff
create mode 100644 arthur_bench/server/js/packages/resources/fonts/Graphik/Graphik-Medium.woff2
create mode 100644 arthur_bench/server/js/packages/resources/fonts/Graphik/Graphik-MediumItalic.otf
create mode 100644 arthur_bench/server/js/packages/resources/fonts/Graphik/Graphik-MediumItalic.woff
create mode 100644 arthur_bench/server/js/packages/resources/fonts/Graphik/Graphik-MediumItalic.woff2
create mode 100644 arthur_bench/server/js/packages/resources/fonts/Graphik/Graphik-Regular.otf
create mode 100644 arthur_bench/server/js/packages/resources/fonts/Graphik/Graphik-Regular.woff
create mode 100644 arthur_bench/server/js/packages/resources/fonts/Graphik/Graphik-Regular.woff2
create mode 100644 arthur_bench/server/js/packages/resources/fonts/Graphik/Graphik-RegularItalic.otf
create mode 100644 arthur_bench/server/js/packages/resources/fonts/Graphik/Graphik-Semibold.otf
create mode 100644 arthur_bench/server/js/packages/resources/fonts/Graphik/Graphik-Semibold.woff
create mode 100644 arthur_bench/server/js/packages/resources/fonts/Graphik/Graphik-Semibold.woff2
create mode 100644 arthur_bench/server/js/packages/resources/fonts/Graphik/Graphik-SemiboldItalic.otf
create mode 100644 arthur_bench/server/js/packages/resources/fonts/Graphik/Graphik-SemiboldItalic.woff
create mode 100644 arthur_bench/server/js/packages/resources/fonts/Graphik/Graphik-SemiboldItalic.woff2
create mode 100644 arthur_bench/server/js/packages/resources/fonts/Graphik/Graphik-Super.otf
create mode 100644 arthur_bench/server/js/packages/resources/fonts/Graphik/Graphik-Super.woff
create mode 100644 arthur_bench/server/js/packages/resources/fonts/Graphik/Graphik-Super.woff2
create mode 100644 arthur_bench/server/js/packages/resources/fonts/Graphik/Graphik-SuperItalic.otf
create mode 100644 arthur_bench/server/js/packages/resources/fonts/Graphik/Graphik-SuperItalic.woff
create mode 100644 arthur_bench/server/js/packages/resources/fonts/Graphik/Graphik-SuperItalic.woff2
create mode 100644 arthur_bench/server/js/packages/resources/fonts/Graphik/Graphik-Thin.otf
create mode 100644 arthur_bench/server/js/packages/resources/fonts/Graphik/Graphik-Thin.woff
create mode 100644 arthur_bench/server/js/packages/resources/fonts/Graphik/Graphik-Thin.woff2
create mode 100644 arthur_bench/server/js/packages/resources/fonts/Graphik/Graphik-ThinItalic.otf
create mode 100644 arthur_bench/server/js/packages/resources/fonts/Graphik/Graphik-ThinItalic.woff
create mode 100644 arthur_bench/server/js/packages/resources/fonts/Graphik/Graphik-ThinItalic.woff2
create mode 100644 arthur_bench/server/js/packages/resources/fonts/IBM_Plex_Mono/IBMPlexMono-Bold.ttf
create mode 100644 arthur_bench/server/js/packages/resources/fonts/IBM_Plex_Mono/IBMPlexMono-BoldItalic.ttf
create mode 100644 arthur_bench/server/js/packages/resources/fonts/IBM_Plex_Mono/IBMPlexMono-ExtraLight.ttf
create mode 100644 arthur_bench/server/js/packages/resources/fonts/IBM_Plex_Mono/IBMPlexMono-ExtraLightItalic.ttf
create mode 100644 arthur_bench/server/js/packages/resources/fonts/IBM_Plex_Mono/IBMPlexMono-Italic.ttf
create mode 100644 arthur_bench/server/js/packages/resources/fonts/IBM_Plex_Mono/IBMPlexMono-Light.ttf
create mode 100644 arthur_bench/server/js/packages/resources/fonts/IBM_Plex_Mono/IBMPlexMono-LightItalic.ttf
create mode 100644 arthur_bench/server/js/packages/resources/fonts/IBM_Plex_Mono/IBMPlexMono-Medium.ttf
create mode 100644 arthur_bench/server/js/packages/resources/fonts/IBM_Plex_Mono/IBMPlexMono-MediumItalic.ttf
create mode 100644 arthur_bench/server/js/packages/resources/fonts/IBM_Plex_Mono/IBMPlexMono-Regular.ttf
create mode 100644 arthur_bench/server/js/packages/resources/fonts/IBM_Plex_Mono/IBMPlexMono-SemiBold.ttf
create mode 100644 arthur_bench/server/js/packages/resources/fonts/IBM_Plex_Mono/IBMPlexMono-SemiBoldItalic.ttf
create mode 100644 arthur_bench/server/js/packages/resources/fonts/IBM_Plex_Mono/IBMPlexMono-Thin.ttf
create mode 100644 arthur_bench/server/js/packages/resources/fonts/IBM_Plex_Mono/IBMPlexMono-ThinItalic.ttf
create mode 100644 arthur_bench/server/js/packages/resources/fonts/IBM_Plex_Mono/OFL.txt
create mode 100644 arthur_bench/server/js/packages/resources/fonts/Lato/Lato-Black.ttf
create mode 100644 arthur_bench/server/js/packages/resources/fonts/Lato/Lato-BlackItalic.ttf
create mode 100644 arthur_bench/server/js/packages/resources/fonts/Lato/Lato-Bold.ttf
create mode 100644 arthur_bench/server/js/packages/resources/fonts/Lato/Lato-BoldItalic.ttf
create mode 100644 arthur_bench/server/js/packages/resources/fonts/Lato/Lato-Italic.ttf
create mode 100644 arthur_bench/server/js/packages/resources/fonts/Lato/Lato-Light.ttf
create mode 100644 arthur_bench/server/js/packages/resources/fonts/Lato/Lato-LightItalic.ttf
create mode 100644 arthur_bench/server/js/packages/resources/fonts/Lato/Lato-Regular.ttf
create mode 100644 arthur_bench/server/js/packages/resources/fonts/Lato/Lato-Thin.ttf
create mode 100644 arthur_bench/server/js/packages/resources/fonts/Lato/Lato-ThinItalic.ttf
create mode 100644 arthur_bench/server/js/packages/resources/fonts/Lato/OFL.txt
create mode 100644 arthur_bench/server/js/packages/resources/fonts/fonts.css
create mode 100644 arthur_bench/server/js/packages/resources/fonts/index.ts
create mode 100644 arthur_bench/server/js/packages/resources/icons/constants.ts
create mode 100644 arthur_bench/server/js/packages/resources/icons/selection.js
create mode 100644 arthur_bench/server/js/packages/resources/icons/selection.json
create mode 100644 arthur_bench/server/js/packages/resources/images/Arthur_Logo_PBW.svg
create mode 100644 arthur_bench/server/js/packages/resources/images/Arthur_Logo_Symbol_low_margin.svg
create mode 100644 arthur_bench/server/js/packages/resources/images/welcome.svg
create mode 100644 arthur_bench/server/js/packages/resources/package.json
create mode 100644 arthur_bench/server/js/packages/resources/style-constants/index.ts
create mode 100644 arthur_bench/server/js/packages/resources/theme/button/index.ts
create mode 100644 arthur_bench/server/js/packages/resources/theme/light.ts
create mode 100644 arthur_bench/server/js/packages/resources/theme/tagSelector/index.ts
create mode 100644 arthur_bench/server/js/packages/resources/theme/types.ts
create mode 100644 arthur_bench/server/js/packages/translations/constants.ts
create mode 100644 arthur_bench/server/js/packages/translations/en/chat.json
create mode 100644 arthur_bench/server/js/packages/translations/en/common.json
create mode 100644 arthur_bench/server/js/packages/translations/en/element.json
create mode 100644 arthur_bench/server/js/packages/translations/en/overview.json
create mode 100644 arthur_bench/server/js/packages/translations/en/roundtable.json
create mode 100644 arthur_bench/server/js/packages/translations/en/shield.json
create mode 100644 arthur_bench/server/js/packages/translations/es/chatEs.json
create mode 100644 arthur_bench/server/js/packages/translations/es/commonEs.json
create mode 100644 arthur_bench/server/js/packages/translations/es/elementEs.json
create mode 100644 arthur_bench/server/js/packages/translations/es/roundtableEs.json
create mode 100644 arthur_bench/server/js/packages/translations/es/shieldEs.json
create mode 100644 arthur_bench/server/js/packages/translations/hooks.ts
create mode 100644 arthur_bench/server/js/packages/translations/jp/chatJp.json
create mode 100644 arthur_bench/server/js/packages/translations/jp/common.json
create mode 100644 arthur_bench/server/js/packages/translations/jp/commonJp.json
create mode 100644 arthur_bench/server/js/packages/translations/jp/elementJp.json
create mode 100644 arthur_bench/server/js/packages/translations/jp/roundtableJp.json
create mode 100644 arthur_bench/server/js/packages/translations/jp/shieldJp.json
create mode 100644 arthur_bench/server/js/packages/translations/types.ts
create mode 100644 arthur_bench/server/js/packages/tsconfig/README.md
create mode 100644 arthur_bench/server/js/packages/tsconfig/base.json
create mode 100644 arthur_bench/server/js/packages/tsconfig/nextjs.json
create mode 100644 arthur_bench/server/js/packages/tsconfig/package.json
create mode 100644 arthur_bench/server/js/packages/tsconfig/react-library.json
create mode 100644 arthur_bench/server/js/packages/utils/.eslintrc.js
create mode 100644 arthur_bench/server/js/packages/utils/README.md
create mode 100644 arthur_bench/server/js/packages/utils/babel.config.js
create mode 100644 arthur_bench/server/js/packages/utils/capitalize-first-letter/capitalizeFirstLetter.test.js
create mode 100644 arthur_bench/server/js/packages/utils/capitalize-first-letter/index.ts
create mode 100644 arthur_bench/server/js/packages/utils/chunk/index.ts
create mode 100644 arthur_bench/server/js/packages/utils/format/abbreviateNumber/abbreviateNumber.test.js
create mode 100644 arthur_bench/server/js/packages/utils/format/abbreviateNumber/index.ts
create mode 100644 arthur_bench/server/js/packages/utils/format/date-label-formatter/index.ts
create mode 100644 arthur_bench/server/js/packages/utils/helpers/index.ts
create mode 100644 arthur_bench/server/js/packages/utils/is-nil/index.ts
create mode 100644 arthur_bench/server/js/packages/utils/is-nil/isNil.test.js
create mode 100644 arthur_bench/server/js/packages/utils/jest.config.ts
create mode 100644 arthur_bench/server/js/packages/utils/keypress-enter/index.ts
create mode 120000 arthur_bench/server/js/packages/utils/node_modules/.bin/jest
create mode 120000 arthur_bench/server/js/packages/utils/node_modules/.bin/ts-jest
create mode 100644 arthur_bench/server/js/packages/utils/package.json
create mode 100644 arthur_bench/server/js/packages/utils/parse-int/index.ts
create mode 100644 arthur_bench/server/js/packages/utils/parse-int/parseInt.test.js
create mode 100644 arthur_bench/server/js/packages/utils/range/index.ts
create mode 100644 arthur_bench/server/js/packages/utils/range/range.test.ts
create mode 100644 arthur_bench/server/js/packages/utils/tsconfig.json
create mode 100644 arthur_bench/server/js/public/arthur-triangle-favicon.png
create mode 100644 arthur_bench/server/js/public/index.html
create mode 100644 arthur_bench/server/js/src/App.tsx
create mode 100644 arthur_bench/server/js/src/Bench/InputsOutputs.tsx
create mode 100644 arthur_bench/server/js/src/Bench/TestRun.tsx
create mode 100644 arthur_bench/server/js/src/Bench/TestRuns.tsx
create mode 100644 arthur_bench/server/js/src/Bench/TestSuiteHeader.tsx
create mode 100644 arthur_bench/server/js/src/Bench/TestSuites.tsx
create mode 100644 arthur_bench/server/js/src/Bench/index.tsx
create mode 100644 arthur_bench/server/js/src/Bench/styles.ts
create mode 100644 arthur_bench/server/js/src/Bench/useTestSuites.ts
create mode 100644 arthur_bench/server/js/src/Skin/skin.tsx
create mode 100644 arthur_bench/server/js/src/copy/initCopy.ts
create mode 100644 arthur_bench/server/js/src/index.html
create mode 100644 arthur_bench/server/js/src/renderer.ts
create mode 100644 arthur_bench/server/js/src/routes/index.tsx
create mode 100644 arthur_bench/server/js/tsconfig.json
create mode 100644 arthur_bench/server/js/webpack-config/config.js
delete mode 100644 docs/source/generated/arthur_bench.scoring.bertscore.BERTScore.rst
delete mode 100644 docs/source/generated/arthur_bench.scoring.qa_quality.QAQualityCorrectness.rst
delete mode 100644 docs/source/generated/arthur_bench.scoring.scoring_method.ScoringMethod.rst
delete mode 100644 docs/source/generated/arthur_bench.scoring.summary_quality.SummaryQuality.rst
create mode 100644 docs/source/walkthrough.md
delete mode 100644 examples/bench/clothing_qa/chatgpt-run/run.json
delete mode 100644 examples/bench/clothing_qa/claude-run/run.json
delete mode 100644 examples/bench/clothing_qa/cohere-run/run.json
delete mode 100644 examples/bench/clothing_qa/minilm-run/run.json
delete mode 100644 examples/bench/clothing_qa/roberta-run/run.json
delete mode 100644 examples/bench/clothing_qa/suite.json
delete mode 100644 examples/bench/clothing_qa/tinyroberta-run/run.json
create mode 100644 examples/bench/news_summary/run_id_to_name.json
create mode 100644 examples/bench/stack_dist_to_golden/prompt1/run.json
create mode 100644 examples/bench/stack_dist_to_golden/prompt39842/run.json
create mode 100644 examples/bench/stack_dist_to_golden/run_id_to_name.json
create mode 100644 examples/bench/suite_id_to_name.json
delete mode 100644 examples/demo_question_answering.ipynb
create mode 100644 test/fixtures/mock_data.py
create mode 100644 test/fixtures/mock_requests.py
create mode 100644 test/fixtures/mock_responses.py
diff --git a/.gitignore b/.gitignore
index 2119f5c..a04c8eb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -26,4 +26,6 @@ include/
*.egg-info/
.installed.cfg
*.egg
-
+arthur_bench/server/js/dist/*
+arthur_bench/server/js/node_modules/*
+arthur_bench/server/js/packages/*/node_modules/
diff --git a/MANIFEST.in b/MANIFEST.in
index f368d0d..4d0556c 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1 +1 @@
-recursive-include arthur_bench/server/html/*
+recursive-include arthur_bench/server/js/dist/*
diff --git a/arthur_bench/client/bench_client.py b/arthur_bench/client/bench_client.py
new file mode 100644
index 0000000..2dc7d93
--- /dev/null
+++ b/arthur_bench/client/bench_client.py
@@ -0,0 +1,86 @@
+from typing import Optional, TypeVar
+from abc import ABC, abstractmethod
+
+from arthur_bench.models.models import (
+ PaginatedTestSuites,
+ CreateRunResponse,
+ CreateRunRequest,
+ PaginatedRuns,
+ PaginatedRun,
+ TestSuiteRequest,
+ PaginatedTestSuite,
+ TestSuiteSummary,
+)
+
+TBenchClient = TypeVar("TBenchClient", bound="BenchClient")
+
+
+class BenchClient(ABC):
+
+ @abstractmethod
+ def get_test_suites(
+ self,
+ name: Optional[str] = None,
+ sort: Optional[str] = None,
+ scoring_method: Optional[str] = None,
+ page: int = 1,
+ page_size: int = 5
+ ) -> PaginatedTestSuites:
+ raise NotImplementedError
+
+ @abstractmethod
+ def create_test_suite(self, json_body: TestSuiteRequest) -> PaginatedTestSuite:
+ raise NotImplementedError
+
+ @abstractmethod
+ def get_test_suite(
+ self,
+ test_suite_id: str,
+ page: int = 1,
+ page_size: int = 5) -> PaginatedTestSuite:
+ raise NotImplementedError
+
+ @abstractmethod
+ def get_runs_for_test_suite(
+ self,
+ test_suite_id: str,
+ sort: Optional[str] = None,
+ page: int = 1,
+ page_size: int = 5
+ ) -> PaginatedRuns:
+ raise NotImplementedError
+
+ @abstractmethod
+ def create_new_test_run(
+ self, test_suite_id: str, json_body: CreateRunRequest
+ ) -> CreateRunResponse:
+ raise NotImplementedError
+
+ @abstractmethod
+ def get_test_run(
+ self,
+ test_suite_id: str,
+ test_run_id: str,
+ page: int = 1,
+ page_size: int = 5,
+ sort: Optional[bool] = None,
+ ) -> PaginatedRun:
+ raise NotImplementedError
+
+ @abstractmethod
+ def get_summary_statistics(
+ self,
+ test_suite_id: str,
+ run_id: Optional[str] = None,
+ page: int = 1,
+ page_size: int = 5
+ ) -> TestSuiteSummary:
+ raise NotImplementedError
+
+ @abstractmethod
+ def delete_test_suite(self, test_suite_id: str):
+ raise NotImplementedError
+
+ @abstractmethod
+ def delete_test_run(self, test_suite_id: str, test_run_id: str):
+ raise NotImplementedError
diff --git a/arthur_bench/client/local/client.py b/arthur_bench/client/local/client.py
new file mode 100644
index 0000000..c9b8419
--- /dev/null
+++ b/arthur_bench/client/local/client.py
@@ -0,0 +1,397 @@
+import os
+import duckdb
+import numpy as np
+import glob
+import json
+from datetime import datetime
+from math import ceil
+from typing import Optional, Union, List
+from dataclasses import dataclass
+import uuid
+from pathlib import Path
+from arthur_bench.client.bench_client import BenchClient
+from arthur_bench.client.exceptions import NotFoundError, ArthurError, UserValueError
+from arthur_bench.models.models import CreateRunRequest, CreateRunResponse, PaginatedRun, PaginatedRuns, \
+ PaginatedTestSuite, PaginatedTestSuites, TestSuiteRequest, TestSuiteSummary, TestSuiteMetadata, TestRunMetadata, \
+ SummaryItem, HistogramItem, TestCaseRequest, TestCaseResponse, RunResult
+from arthur_bench.run.utils import load_suite_from_json, get_file_extension
+
+BENCH_FILE_DIR_KEY = 'BENCH_FILE_DIR'
+DEFAULT_BENCH_FILE_DIR = Path(os.getcwd()) / "bench"
+
+SUITE_INDEX_FILE = 'suite_id_to_name.json'
+RUN_INDEX_FILE = 'run_id_to_name.json'
+
+TIMESTAMP_FORMAT = '%Y-%m-%dT%H:%M:%S.%f'
+DEFAULT_PAGE_SIZE = 5
+
+SORT_QUERY_TO_FUNC = {
+ 'last_run_time': lambda x: x.last_run_time if x.last_run_time is not None else x.created_at,
+ 'name': lambda x: x.name,
+ 'created_at': lambda x: x.created_at,
+ 'avg_score': lambda x: x.avg_score,
+ '-last_run_time': lambda x: x.last_run_time if x.last_run_time is not None else x.created_at,
+ '-name': lambda x: x.name,
+ '-created_at': lambda x: x.created_at,
+ '-avg_score': lambda x: x.avg_score,
+ 'score': lambda x: x.score,
+ 'id': lambda x: x.id
+}
+
+
+def _write_suite_index(root_dir: Path):
+ suite_index_path = root_dir / SUITE_INDEX_FILE
+ if suite_index_path.is_file():
+ return
+ json.dump({}, open(suite_index_path, "w"))
+ return None
+
+
+def _write_run_index(root_dir: Path, test_suite: str):
+ run_index_path = root_dir / test_suite / RUN_INDEX_FILE
+ if run_index_path.is_file():
+ return
+ json.dump({}, open(run_index_path, "w"))
+ return None
+
+
+def _bench_root_dir() -> Path:
+ return Path(os.environ.get(BENCH_FILE_DIR_KEY, DEFAULT_BENCH_FILE_DIR))
+
+
+def _test_suite_dir(test_suite_name: str) -> Path:
+ return Path(_bench_root_dir()) / test_suite_name
+
+
+def _create_test_suite_dir(test_suite_name: str) -> Path:
+ if not os.path.exists(_bench_root_dir()):
+ os.mkdir(_bench_root_dir())
+ test_suite_dir = _test_suite_dir(test_suite_name)
+ if test_suite_dir.is_dir():
+ raise UserValueError(f"test_suite {test_suite_name} already exists")
+ os.mkdir(test_suite_dir)
+ return test_suite_dir
+
+
+def _create_run_dir(test_suite_name: str, run_name: str) -> Path:
+ run_dir = _test_suite_dir(test_suite_name) / run_name
+ if os.path.exists(run_dir):
+ raise UserValueError(f"run {run_name} already exists")
+ os.mkdir(run_dir)
+ return run_dir
+
+
+def _load_suite_with_optional_id(filepath: Union[str, os.PathLike]) -> Optional[PaginatedTestSuite]:
+ if get_file_extension(filepath) != '.json':
+ raise UserValueError("filepath must be json file")
+ suite = json.load(open(filepath))
+ if 'id' in suite:
+ return PaginatedTestSuite.parse_obj(suite)
+ return None
+
+
+def _summarize_run(run: PaginatedRun) -> SummaryItem:
+ scores = [o.score for o in run.test_cases]
+ avg_score = np.mean(scores).item()
+ hist, bin_edges = np.histogram(scores, bins=20, range=(0, max(1, np.max(scores))))
+ histogram = []
+ for i in range(len(hist)):
+ hist_item = HistogramItem(
+ count=hist[i],
+ low=bin_edges[i],
+ high=bin_edges[i+1]
+ )
+ histogram.append(hist_item)
+ return SummaryItem(id=run.id, name=run.name, avg_score=avg_score, histogram=histogram)
+
+
+@dataclass
+class PageInfo:
+ start: int
+ end: int
+ page: int
+ page_size: int
+ total_pages: int
+ total_count: int
+
+
+def _paginate(objs: List, page: int, page_size: int, sort_key: str) -> PageInfo:
+ """Paginate sorted files and return iteration indices and page info"""
+ desc = sort_key[0] == "-"
+ objs.sort(key=SORT_QUERY_TO_FUNC[sort_key], reverse=desc)
+ offset = (page - 1) * page_size
+ return PageInfo(
+ start=offset,
+ end=min(offset + page_size, len(objs)),
+ page=page,
+ page_size=page_size,
+ total_count=len(objs),
+ total_pages=ceil(len(objs) / page_size)
+ )
+
+
+class LocalBenchClient(BenchClient):
+ """
+ Client for managing local file system test suites and runs
+ """
+ def __init__(self, root_dir: Optional[Union[str, Path]] = None):
+ if root_dir is None:
+ root_dir = _bench_root_dir()
+ self.root_dir = Path(root_dir)
+ _write_suite_index(self.root_dir)
+
+ def _get_suite_name_from_id(self, id: str) -> Optional[str]:
+ suite_index = json.load(open(self.root_dir / SUITE_INDEX_FILE))
+ if id not in suite_index:
+ return None
+ return suite_index[id]
+
+ def _get_run_name_from_id(self, test_suite_name: str, id: str) -> Optional[str]:
+ run_index = json.load(open(self.root_dir / test_suite_name / RUN_INDEX_FILE))
+ if id not in run_index:
+ return None
+ return run_index[id]
+
+ def _create_test_case_with_id(self, test_case: TestCaseRequest) -> TestCaseResponse:
+ return TestCaseResponse(id=uuid.uuid4(),
+ input=test_case.input,
+ reference_output=test_case.reference_output)
+
+ def _update_suite_run_time(self, test_suite_name: str, runtime: datetime):
+ suite_file = self.root_dir / test_suite_name / "suite.json"
+ suite = PaginatedTestSuite.parse_file(suite_file)
+ suite.last_run_time = runtime
+ suite_file.write_text(suite.json())
+
+ @staticmethod
+ def _update_index(filepath: Path, id: uuid.UUID, name: str):
+ suite_index = json.load(open(filepath))
+ suite_index[str(id)] = name
+ json.dump(suite_index, open(filepath, "w"))
+
+ def _update_suite_index(self, id: uuid.UUID, name: str):
+ suite_index_path = self.root_dir / SUITE_INDEX_FILE
+ LocalBenchClient._update_index(suite_index_path, id, name)
+
+ def _update_run_index(self, test_suite_name: str, id: uuid.UUID, name: str):
+ run_index_path = self.root_dir / test_suite_name / RUN_INDEX_FILE
+ LocalBenchClient._update_index(run_index_path, id, name)
+
+ def get_test_suite(self, test_suite_id: str, page: int = 1, page_size: int = DEFAULT_PAGE_SIZE) -> PaginatedTestSuite:
+ suite_index = json.load(open(self.root_dir / SUITE_INDEX_FILE))
+ if test_suite_id not in suite_index:
+ raise NotFoundError()
+ else:
+ suite_file = self.root_dir / suite_index[test_suite_id] / "suite.json"
+ suite = PaginatedTestSuite.parse_file(suite_file)
+ pagination = _paginate(suite.test_cases, page, page_size, sort_key='id')
+ return PaginatedTestSuite(id=uuid.UUID(test_suite_id),
+ name=suite.name,
+ scoring_method=suite.scoring_method,
+ test_cases=suite.test_cases[pagination.start:pagination.end],
+ created_at=suite.created_at,
+ updated_at=suite.updated_at,
+ page=pagination.page,
+ page_size=pagination.page_size,
+ total_count=pagination.total_count,
+ total_pages=pagination.total_pages)
+
+ def get_test_suites(
+ self,
+ name: Optional[str] = None,
+ sort: Optional[str] = None,
+ scoring_method: Optional[str] = None,
+ page: int = 1,
+ page_size: int = DEFAULT_PAGE_SIZE) -> PaginatedTestSuites:
+
+ # name uniqueness
+ if name is not None:
+ test_suite_dir = self.root_dir / name
+ if test_suite_dir.is_dir():
+ suite = _load_suite_with_optional_id(test_suite_dir / "suite.json")
+ if suite is None:
+ suite = self.get_test_suite_by_name(name)
+ return PaginatedTestSuites(test_suites=[
+ TestSuiteMetadata(
+ id=suite.id,
+ name=suite.name,
+ scoring_method=suite.scoring_method,
+ description=suite.description,
+ created_at=suite.created_at
+ )
+ ], page=1, page_size=1, total_pages=1, total_count=1)
+ else:
+ return PaginatedTestSuites(test_suites=[], page=1, page_size=0, total_pages=1, total_count=0)
+
+ suites = []
+ suite_files = glob.glob(f'{self.root_dir}/*/suite.json')
+ for f in suite_files:
+ suite = _load_suite_with_optional_id(f)
+ if suite is None:
+ suite = self.get_test_suite_by_name(f.split('/')[-2])
+ if scoring_method is None or suite.scoring_method.name == scoring_method:
+ suites.append(TestSuiteMetadata(
+ id=suite.id,
+ name=suite.name,
+ scoring_method=suite.scoring_method,
+ description=suite.description,
+ created_at=suite.created_at
+ ))
+
+ # default sort by last run time
+ if sort is None:
+ sort = 'last_run_time'
+ paginate = _paginate(suites, page=page, page_size=page_size, sort_key=sort)
+ return PaginatedTestSuites(test_suites=suites[paginate.start:paginate.end],
+ page=paginate.page,
+ page_size=paginate.page_size,
+ total_pages=paginate.total_pages,
+ total_count=paginate.total_count)
+
+ def create_test_suite(self, json_body: TestSuiteRequest) -> PaginatedTestSuite:
+ test_suite_dir = _create_test_suite_dir(json_body.name)
+ suite_file = test_suite_dir / "suite.json"
+
+ test_suite_id = uuid.uuid4()
+ self._update_suite_index(test_suite_id, json_body.name)
+ _write_run_index(self.root_dir, json_body.name)
+
+ resp = PaginatedTestSuite(id=test_suite_id,
+ name=json_body.name,
+ test_cases=[self._create_test_case_with_id(test_case) for test_case in json_body.test_cases],
+ scoring_method=json_body.scoring_method,
+ created_at=json_body.created_at,
+ updated_at=json_body.created_at)
+
+ suite_file.write_text(resp.json())
+ return resp
+
+ def create_new_test_run(self, test_suite_id: str, json_body: CreateRunRequest) -> CreateRunResponse:
+ test_suite_name = self._get_suite_name_from_id(test_suite_id)
+ if test_suite_name is None:
+ raise NotFoundError()
+
+ run_id = uuid.uuid4()
+ resp = PaginatedRun(id=run_id,
+ test_suite_id=uuid.UUID(test_suite_id),
+ updated_at=json_body.created_at,
+ **json_body.dict())
+
+ run_dir = _create_run_dir(test_suite_name, json_body.name)
+ run_file = run_dir / 'run.json'
+ run_file.write_text(resp.json())
+ self._update_run_index(test_suite_name, run_id, json_body.name)
+ self._update_suite_run_time(test_suite_name=test_suite_name, runtime=resp.created_at)
+ return CreateRunResponse(id=resp.id)
+
+ def get_runs_for_test_suite(self, test_suite_id: str, sort: Optional[str] = None, page: int = 1, page_size: int = DEFAULT_PAGE_SIZE) -> PaginatedRuns:
+ test_suite_name = self._get_suite_name_from_id(test_suite_id)
+ if test_suite_name is None:
+ raise NotFoundError()
+
+ runs = []
+ run_files = glob.glob(f'{self.root_dir}/{test_suite_name}/*/run.json')
+ for f in run_files:
+ run_obj = PaginatedRun.parse_file(f)
+ avg_score = np.mean([o.score for o in run_obj.test_cases])
+ run_resp = TestRunMetadata(**run_obj.dict(), avg_score=avg_score) # type: ignore
+ runs.append(run_resp)
+
+ if sort is None:
+ sort = 'created_at'
+
+ pagination = _paginate(runs, page, page_size, sort_key=sort)
+ return PaginatedRuns(test_suite_id=uuid.UUID(test_suite_id),
+ test_runs=runs,
+ page_size=pagination.page_size,
+ page=pagination.page,
+ total_pages=pagination.total_pages,
+ total_count=pagination.total_count)
+
+ def get_summary_statistics(self, test_suite_id: str, run_id: Optional[str] = None, page: int = 1, page_size: int = DEFAULT_PAGE_SIZE) -> TestSuiteSummary:
+ print(test_suite_id)
+ test_suite_name = self._get_suite_name_from_id(test_suite_id)
+ if test_suite_name is None:
+ raise NotFoundError()
+
+ runs = []
+ run_id_found = False
+ run_files = glob.glob(f'{self.root_dir}/{test_suite_name}/*/run.json')
+ for f in run_files:
+ run_obj = PaginatedRun.parse_file(f)
+ runs.append(_summarize_run(run=run_obj))
+ if str(run_obj.id) == run_id:
+ run_id_found = True
+
+ pagination = _paginate(runs, page, page_size, sort_key='avg_score')
+ paginated_summary = TestSuiteSummary(summary=runs,
+ num_test_cases=len(run_obj.test_cases),
+ page_size=pagination.page_size,
+ page=pagination.page,
+ total_pages=pagination.total_pages,
+ total_count=pagination.total_count)
+
+ if run_id is not None and not run_id_found:
+ run_name = self._get_run_name_from_id(test_suite_name, run_id)
+ if run_name is None:
+ raise NotFoundError()
+ additional_run = PaginatedRun.parse_file(self.root_dir / test_suite_name / run_name / "run.json")
+ paginated_summary.summary.append(_summarize_run(additional_run))
+ return paginated_summary
+
+ def get_test_run(self, test_suite_id: str, test_run_id: str, page: int = 1, page_size: int = DEFAULT_PAGE_SIZE, sort: Optional[bool] = True) -> PaginatedRun:
+ test_suite_name = self._get_suite_name_from_id(test_suite_id)
+ if test_suite_name is None:
+ raise NotFoundError()
+
+ run_name = self._get_run_name_from_id(test_suite_name, test_run_id)
+ if run_name is None:
+ raise NotFoundError()
+
+ created_at = PaginatedRun.parse_file(self.root_dir / test_suite_name / run_name / "run.json").created_at
+ try:
+ cases = duckdb.sql(f"SELECT * FROM ("
+ f"SELECT test_cases.id, test_cases.input, test_cases.reference_output FROM ("
+ f"SELECT unnest(test_cases) as test_cases from read_json_auto('{self.root_dir}/{test_suite_name}/suite.json', timestampformat='{TIMESTAMP_FORMAT}'))) "
+ f"POSITIONAL JOIN (SELECT test_cases.output, test_cases.score FROM ("
+ f"SELECT unnest(test_cases) as test_cases from read_json_auto('{self.root_dir}/{test_suite_name}/{run_name}/run.json',timestampformat='{TIMESTAMP_FORMAT}')))").df().to_dict('records')
+ except duckdb.IOException:
+ cases = []
+
+ pagination = _paginate([RunResult.parse_obj(r) for r in cases], page, page_size, sort_key='score')
+ return PaginatedRun(
+ id=uuid.UUID(test_run_id),
+ name=run_name,
+ created_at=created_at,
+ updated_at=created_at,
+ test_case_runs=cases[pagination.start:pagination.end],
+ test_suite_id=uuid.UUID(test_suite_id),
+ page=pagination.page,
+ page_size=pagination.page_size,
+ total_pages=pagination.total_pages,
+ total_count=pagination.total_count)
+
+ def delete_test_suite(self, test_suite_id: str):
+ # TODO
+ return ArthurError("delete test suite is not supported in local mode yet")
+
+ def delete_test_run(self, test_suite_id: str, test_run_id: str):
+ # TODO:
+ return ArthurError("delete test run is not supported in local mode yet")
+
+ def get_test_suite_by_name(self, test_suite_name: str) -> PaginatedTestSuite:
+ """
+ Additional getter to maintain backwards compatibility with non-identified local files
+ """
+ suite_file = self.root_dir / test_suite_name / "suite.json"
+ suite = load_suite_from_json(suite_file)
+
+ # override file with index
+ id_ = uuid.uuid4()
+ resp = PaginatedTestSuite(id=id_,
+ **suite.dict())
+ suite_file.write_text(resp.json())
+ self._update_suite_index(id_, test_suite_name)
+ _write_run_index(self.root_dir, test_suite_name)
+
+ return resp
\ No newline at end of file
diff --git a/arthur_bench/client/rest/admin/models.py b/arthur_bench/client/rest/admin/models.py
index 1d40e72..8eb76ef 100644
--- a/arthur_bench/client/rest/admin/models.py
+++ b/arthur_bench/client/rest/admin/models.py
@@ -148,7 +148,7 @@ class UserResponse(BaseModel):
"""
The username the user can use to login
"""
- roles: List[str]
+ roles: Optional[List[str]]
"""
The user's roles
"""
diff --git a/arthur_bench/client/rest/bench/client.py b/arthur_bench/client/rest/bench/client.py
index 2735dc5..98c1b96 100644
--- a/arthur_bench/client/rest/bench/client.py
+++ b/arthur_bench/client/rest/bench/client.py
@@ -4,24 +4,24 @@
# import http client
from arthur_bench.client.http.requests import HTTPClient
+from arthur_bench.client.bench_client import BenchClient
from arthur_bench.models.models import (
- PaginatedGetTestSuitesResponse,
- CreateRunResponse,
+ PaginatedTestSuites,
+ PaginatedRun,
CreateRunRequest,
- PaginatedGetRunsForTestSuiteResponse,
- PaginatedGetRunResponse,
- TestSuiteResponse,
+ PaginatedRuns,
TestSuiteRequest,
- PaginatedGetTestSuiteResponse,
- TestSuiteSummaryResponse,
+ PaginatedTestSuite,
+ TestSuiteSummary,
+ CreateRunResponse
)
PATH_PREFIX = "/api/v3"
-class ArthurBenchClient:
+class ArthurBenchClient(BenchClient):
"""
A Python client to interact with the Arthur Bench API
"""
@@ -40,7 +40,9 @@ def get_test_suites(
name: Optional[str] = None,
sort: Optional[str] = None,
scoring_method: Optional[str] = None,
- ) -> PaginatedGetTestSuitesResponse:
+ page: Optional[int] = 1,
+ page_size: Optional[int] = None
+ ) -> PaginatedTestSuites:
"""
Gets test suites
@@ -59,13 +61,17 @@ def get_test_suites(
params["sort"] = sort
if scoring_method is not None:
params["scoring_method"] = scoring_method
+ if page is not None:
+ params["page"] = page # type: ignore
+ if page_size is not None:
+ params["page_size"] = page_size # type: ignore
parsed_resp = cast(Dict, self.http_client.get(
f"/bench/test_suites", params=params, validation_response_code=HTTPStatus.OK
))
- return PaginatedGetTestSuitesResponse(**parsed_resp)
+ return PaginatedTestSuites(**parsed_resp)
- def create_test_suite(self, json_body: TestSuiteRequest) -> TestSuiteResponse:
+ def create_test_suite(self, json_body: TestSuiteRequest) -> PaginatedTestSuite:
"""
Creates a new test suite from reference data using specified scoring_method for scoring
@@ -77,20 +83,30 @@ def create_test_suite(self, json_body: TestSuiteRequest) -> TestSuiteResponse:
json=json_body.dict(exclude={'created_at', 'created_by', 'bench_version'}),
validation_response_code=HTTPStatus.CREATED,
))
- return TestSuiteResponse(**parsed_resp)
+ return PaginatedTestSuite(**parsed_resp)
- def get_test_suite(self, test_suite_id: str) -> PaginatedGetTestSuiteResponse:
+ def get_test_suite(
+ self,
+ test_suite_id: str,
+ page: Optional[int] = 1,
+ page_size: Optional[int] = None) -> PaginatedTestSuite:
"""
Get reference data for an existing test suite
:param test_suite_id:
"""
+ params = {}
+ if page is not None:
+ params["page"] = page # type: ignore
+ if page_size is not None:
+ params["page_size"] = page_size # type: ignore
parsed_resp = cast(Dict, self.http_client.get(
f"/bench/test_suites/{test_suite_id}",
+ params=params,
validation_response_code=HTTPStatus.OK,
))
- return PaginatedGetTestSuiteResponse(**parsed_resp)
+ return PaginatedTestSuite(**parsed_resp)
def get_summary_statistics(
self,
@@ -98,9 +114,9 @@ def get_summary_statistics(
run_id: Optional[str] = None,
page: Optional[int] = 1,
page_size: Optional[int] = None,
- ) -> TestSuiteSummaryResponse:
+ ) -> TestSuiteSummary:
"""
- Get pagianted summary statistics of a test suite
+ Get paginated summary statistics of a test suite
Defaults to page size of 5.
@@ -123,11 +139,15 @@ def get_summary_statistics(
params=params,
validation_response_code=HTTPStatus.OK,
))
- return TestSuiteSummaryResponse(**parsed_resp)
+ return TestSuiteSummary(**parsed_resp)
def get_runs_for_test_suite(
- self, test_suite_id: str, sort: Optional[str] = None
- ) -> PaginatedGetRunsForTestSuiteResponse:
+ self,
+ test_suite_id: str,
+ sort: Optional[str] = None,
+ page: Optional[int] = 1,
+ page_size: Optional[int] = None,
+ ) -> PaginatedRuns:
"""
Get runs for a particular test suite (identified by test_suite_id)
@@ -138,13 +158,17 @@ def get_runs_for_test_suite(
params = {}
if sort is not None:
params["sort"] = sort
+ if page is not None:
+ params["page"] = page # type: ignore
+ if page_size is not None:
+ params["page_size"] = page_size # type: ignore
parsed_resp = cast(Dict, self.http_client.get(
f"/bench/test_suites/{test_suite_id}/runs",
params=params,
validation_response_code=HTTPStatus.OK,
))
- return PaginatedGetRunsForTestSuiteResponse(**parsed_resp)
+ return PaginatedRuns(**parsed_resp)
def create_new_test_run(
self, test_suite_id: str, json_body: CreateRunRequest
@@ -171,7 +195,7 @@ def get_test_run(
page: Optional[int] = 1,
page_size: Optional[int] = None,
sort: Optional[bool] = None,
- ) -> PaginatedGetRunResponse:
+ ) -> PaginatedRun:
"""
Get a test run with input, output, and reference data
@@ -184,9 +208,9 @@ def get_test_run(
params = {}
if page is not None:
- params["page"] = page
+ params["page"] = page # type: ignore
if page_size is not None:
- params["page_size"] = page_size
+ params["page_size"] = page_size # type: ignore
if sort is not None:
params["sort"] = sort
@@ -195,4 +219,38 @@ def get_test_run(
params=params,
validation_response_code=HTTPStatus.OK,
))
- return PaginatedGetRunResponse(**parsed_resp)
+ return PaginatedRun(**parsed_resp)
+
+ def delete_test_suite(self, test_suite_id: str):
+ """
+ Deletes test suite
+
+ Is idempotent.
+
+ :param test_suite_id:
+ """
+
+ raw_resp = self.http_client.delete(
+ f"/bench/test_suites/{test_suite_id}",
+ validation_response_code=HTTPStatus.NO_CONTENT,
+ return_raw_response=True,
+ )
+ return raw_resp
+
+ def delete_test_run(self, test_suite_id: str, test_run_id: str):
+ """
+ Deletes a test run
+
+ Is idempotent.
+
+ :param test_suite_id:
+ :param test_run_id:
+ """
+
+ raw_resp = self.http_client.delete(
+ f"/bench/test_suites/{test_suite_id}/runs/{test_run_id}",
+ validation_response_code=HTTPStatus.NO_CONTENT,
+ return_raw_response=True,
+ )
+ return raw_resp
+
diff --git a/arthur_bench/models/models.py b/arthur_bench/models/models.py
index 0e67b6d..f429c56 100644
--- a/arthur_bench/models/models.py
+++ b/arthur_bench/models/models.py
@@ -1,7 +1,3 @@
-# generated by datamodel-codegen:
-# filename: bench.yaml
-# timestamp: 2023-06-21T19:01:59+00:00
-
from __future__ import annotations
from datetime import datetime
@@ -10,21 +6,28 @@
from uuid import UUID
from arthur_bench.client.exceptions import UserValueError
-from arthur_bench.models.client import Page, PageSize, TotalCount, TotalPages
from pydantic import BaseModel, Field, validator
+## COMMON
class ScoringMethodType(str, Enum):
- BuiltIn = 'built_in'
+ BuiltIn = 'built_in'
Custom = 'custom'
+class ScoringMethod(BaseModel):
+ name: str
+ type: ScoringMethodType
+
+
+## REQUESTS
+
+
class TestCaseRequest(BaseModel):
"""
An input, reference output pair.
"""
-
input: str
"""
Input to the test case. Does not include the prompt template.
@@ -35,10 +38,6 @@ class TestCaseRequest(BaseModel):
"""
-class ScoringMethod(BaseModel):
- name: str
- type: ScoringMethodType
-
class TestSuiteRequest(BaseModel):
"""
Test case data and metadata for the test suite.
@@ -76,47 +75,36 @@ def null_reference_outputs_all_or_none(cls, v):
last_ref_output_null = False
return v
-
+
@validator('scoring_method', pre=True)
def scoring_method_backwards_compatible(cls, v):
if isinstance(v, str):
return ScoringMethod(name=v, type=ScoringMethodType.BuiltIn)
return v
-
-class TestSuite(BaseModel):
- id: UUID
- name: str
- scoring_method: str
- last_run_time: Optional[datetime] = None
- description: Optional[str] = None
- created_at: Optional[datetime] = None
- updated_at: Optional[datetime] = None
-
-
+
class TestCaseOutput(BaseModel):
"""
A generated output, score pair
"""
- id: Optional[UUID] = None
+ id: UUID
"""
Optional unique identifier for this test case of the suite and run
"""
- output: Optional[str] = None
+ output: str
"""
Generated output for test case
"""
- score: Optional[float] = None
+ score: float
"""
Score assigned to output
"""
-
-
+
class CreateRunRequest(BaseModel):
name: str
"""
Name identifier of the run
"""
- test_case_outputs: List[TestCaseOutput]
+ test_cases: List[TestCaseOutput]
"""
List of outputs and scores for all cases in the test suite
"""
@@ -145,27 +133,28 @@ class CreateRunRequest(BaseModel):
"""
-class TestRun(BaseModel):
- id: Optional[UUID] = None
- name: Optional[str] = None
+## RESPONSES
+
+class TestSuiteMetadata(BaseModel):
+ id: UUID
+ name: str
+ scoring_method: ScoringMethod
+ last_run_time: Optional[datetime] = None
+ description: Optional[str] = None
created_at: Optional[datetime] = None
updated_at: Optional[datetime] = None
- model_version: Optional[str] = None
- prompt_template: Optional[str] = None
- avg_score: Optional[float] = None
-class PaginatedGetTestSuitesResponse(BaseModel):
- test_suites: Optional[List[TestSuite]] = None
- page: Optional[Page] = None
- page_size: Optional[PageSize] = None
- total_pages: Optional[TotalPages] = None
- total_count: Optional[TotalCount] = None
+class PaginatedTestSuites(BaseModel):
+ test_suites: List[TestSuiteMetadata]
+ page: int
+ page_size: int
+ total_pages: int
+ total_count: int
-
-class TestCaseResponseItem(BaseModel):
- id: Optional[UUID] = None
- input: Optional[str] = None
+class TestCaseResponse(BaseModel):
+ id: UUID
+ input: str
"""
Input to the test case. Does not include the prompt template.
"""
@@ -175,85 +164,92 @@ class TestCaseResponseItem(BaseModel):
"""
-class HistogramItem(BaseModel):
- count: Optional[int] = None
- low: Optional[float] = None
- high: Optional[float] = None
+class PaginatedTestSuite(BaseModel):
+ id: UUID
+ name: str
+ scoring_method: ScoringMethod
+ test_cases: List[TestCaseResponse]
+ created_at: datetime
+ updated_at: datetime
+ description: Optional[str] = None
+ last_run_time: Optional[datetime] = None
+ num_runs: Optional[int] = 0
+ page: Optional[int] = None
+ page_size: Optional[int] = None
+ total_pages: Optional[int] = None
+ total_count: Optional[int] = None
-class SummaryItem(BaseModel):
- id: Optional[UUID] = None
+class TestRunMetadata(BaseModel):
+ id: UUID
+ name: str
+ created_at: datetime
+ updated_at: datetime
avg_score: Optional[float] = None
- histogram: Optional[List[HistogramItem]] = None
+ model_version: Optional[str] = None
+ prompt_template: Optional[str] = None
-class TestSuiteSummaryResponse(BaseModel):
- summary: Optional[List[SummaryItem]] = None
- page: Optional[Page] = None
- page_size: Optional[PageSize] = None
- total_pages: Optional[TotalPages] = None
- total_count: Optional[TotalCount] = None
+class PaginatedRuns(BaseModel):
+ """
+ Paginated list of runs for a test suite.
+ """
+ test_suite_id: UUID
+ test_runs: List[TestRunMetadata]
+ page: int
+ page_size: int
+ total_pages: int
+ total_count: int
-class TestSuiteCase(BaseModel):
- id: Optional[UUID] = None
- input: Optional[str] = None
- reference_output: Optional[str] = None
+class HistogramItem(BaseModel):
+ count: int
+ low: float
+ high: float
+
+
+class SummaryItem(BaseModel):
+ id: UUID
+ name: str
+ avg_score: float
+ histogram: List[HistogramItem]
-class PaginatedGetTestSuiteResponse(BaseModel):
- id: Optional[UUID] = None
- name: Optional[str] = None
- scoring_method: Optional[str] = None
- test_cases: Optional[List[TestSuiteCase]] = None
+class TestSuiteSummary(BaseModel):
+ summary: List[SummaryItem]
+ page: int
+ page_size: int
+ total_pages: int
+ total_count: int
+ num_test_cases: int
class CreateRunResponse(BaseModel):
- id: Optional[UUID] = None
+ id: UUID
class RunResult(BaseModel):
- id: Optional[str] = None
+ id: UUID
+ output: str
+ score: float
input: Optional[str] = None
reference_output: Optional[str] = None
- output: Optional[str] = None
- score: Optional[float] = None
-class PaginatedGetRunsForTestSuiteResponse(BaseModel):
- """
- Paginated list of runs for a test suite.
- """
-
- test_runs: List[TestRun]
- page: Optional[Page] = None
- page_size: Optional[PageSize] = None
- total_pages: Optional[TotalPages] = None
- total_count: Optional[TotalCount] = None
-
-
-class PaginatedGetRunResponse(BaseModel):
+class PaginatedRun(BaseModel):
"""
Paginated list of prompts, reference outputs, and model outputs for a particular run.
"""
-
- test_case_runs: Optional[List[RunResult]] = None
- test_suite_id: Optional[UUID] = None
- page: Page
- page_size: PageSize
- total_pages: Optional[TotalPages] = None
- total_count: Optional[TotalCount] = None
-
-
-class TestSuiteResponse(BaseModel):
id: UUID
name: str
- test_cases: List[TestCaseResponseItem] = Field(..., min_items=1)
- scoring_method: ScoringMethod
- description: Optional[str] = None
- organization_id: Optional[UUID] = None
- created_at: Optional[datetime] = None
- updated_at: Optional[datetime] = None
- """
- JSON object containing test case data for the test suite.
- """
+ test_suite_id: UUID
+ test_cases: List[RunResult] = Field(alias='test_case_runs')
+ updated_at: datetime
+ created_at: datetime
+ page: Optional[int] = None
+ page_size: Optional[int] = None
+ total_pages: Optional[int] = None
+ total_count: Optional[int] = None
+
+ class Config:
+ allow_population_by_field_name = True
diff --git a/arthur_bench/run/testrun.py b/arthur_bench/run/testrun.py
index a4cb249..2e1945d 100644
--- a/arthur_bench/run/testrun.py
+++ b/arthur_bench/run/testrun.py
@@ -1,17 +1,17 @@
import uuid
-from pathlib import Path
-from typing import Optional
from arthur_bench.models.models import CreateRunRequest
+from arthur_bench.client.bench_client import BenchClient
class TestRun(CreateRunRequest):
- test_suite_id: Optional[uuid.UUID] = None
- run_dir: Optional[Path] = None
+ test_suite_id: uuid.UUID
+ client: BenchClient # type: ignore
+
+ class Config:
+ arbitrary_types_allowed = True
+
def save(self):
"""Save a test run to local file system."""
- if self.run_dir is not None:
- run_file = self.run_dir / 'run.json'
- run_file.write_text(self.json(exclude={'test_suite_id', 'run_dir'}))
-
- # TODO: in client MR, update for if run dir is None
+ return self.client.create_new_test_run(test_suite_id=str(self.test_suite_id),
+ json_body=CreateRunRequest(**self.dict()))
\ No newline at end of file
diff --git a/arthur_bench/run/testsuite.py b/arthur_bench/run/testsuite.py
index 2853617..e822a67 100644
--- a/arthur_bench/run/testsuite.py
+++ b/arthur_bench/run/testsuite.py
@@ -1,16 +1,16 @@
+import os
import logging
import pandas as pd
from typing import List, Optional, Union
-from pathlib import Path
-
from arthur_bench.scoring import ScoringMethod, scoring_method_class_from_string
-from arthur_bench.models.models import TestSuiteRequest, TestCaseOutput, ScoringMethod as ScoringMethodMetadata, \
+from arthur_bench.models.models import TestSuiteRequest, PaginatedTestSuite, TestCaseOutput, CreateRunRequest, ScoringMethod as ScoringMethodMetadata, \
ScoringMethodType
-from arthur_bench.client.exceptions import UserValueError, ArthurInternalError
+from arthur_bench.client.exceptions import UserValueError, ArthurInternalError, MissingParameterError
+from arthur_bench.client.bench_client import BenchClient
+from arthur_bench.client.local.client import LocalBenchClient
+from arthur_bench.client.rest.client import ArthurClient
from arthur_bench.run.testrun import TestRun
-from arthur_bench.run.utils import _create_test_suite_dir, _initialize_metadata, _test_suite_dir, \
- _create_run_dir, _clean_up_run, _load_suite_from_args, _load_run_data_from_args, _get_suite_if_exists
-from arthur_bench.scoring import scoring_method_class_from_string
+from arthur_bench.run.utils import _initialize_metadata, _load_suite_from_args, _load_run_data_from_args, _get_suite_if_exists
from arthur_bench.scoring.scoring_method import SINGLE_ITEM_BATCH_DEFAULT
@@ -42,10 +42,20 @@ def __init__(
input_column: str = "input",
reference_column: str = "reference_output",
input_text_list: Optional[List[str]] = None,
- reference_output_list: Optional[List[str]] = None
+ reference_output_list: Optional[List[str]] = None,
+ client: Optional[type[BenchClient]] = None
):
- self.id = None
- self.suite: TestSuiteRequest = _get_suite_if_exists(name) # type: ignore
+ url = os.getenv('ARTHUR_API_URL')
+ if client is None:
+ if url: # if remote url is specified use remote client
+ api_key = os.getenv('ARTHUR_API_KEY')
+ if api_key is None:
+ raise MissingParameterError("You must provide an api key when using remote url")
+ client = ArthurClient(url=url, api_key=api_key).bench # type: ignore
+ else:
+ client = LocalBenchClient() # type: ignore
+ self.client: BenchClient = client # type: ignore
+ self.suite: PaginatedTestSuite = _get_suite_if_exists(self.client, name) # type: ignore
# get a scoringMethod class
if isinstance(scoring_method, str):
@@ -62,18 +72,18 @@ def __init__(
requires_reference=scoring_method.requires_reference()
)
method_meta = ScoringMethodMetadata(name=scoring_method.name(), type=scoring_method.type())
- self.suite = TestSuiteRequest(
+ new_suite = TestSuiteRequest(
name=name,
scoring_method=method_meta,
description=description,
test_cases=cases,
**_initialize_metadata()
)
- self._test_suite_dir: Path = _create_test_suite_dir(name)
+ self.suite = self.client.create_test_suite(new_suite)
self.scorer: ScoringMethod = scoring_method()
+
else:
logger.info(f"Found existing test suite with name {name}. Using existing suite")
- self._test_suite_dir = _test_suite_dir(name)
if self.suite.scoring_method.type == ScoringMethodType.Custom:
if scoring_method.name() != self.suite.scoring_method.name:
@@ -133,11 +143,8 @@ def run(
raise UserValueError(
f"candidate data has {len(candidate_output_list)} tests but expected {len(self.suite.test_cases)} tests")
- run_dir = None
- if save:
- run_dir = _create_run_dir(self.suite.name, run_name)
-
inputs = [case.input for case in self.suite.test_cases]
+ ids = [case.id for case in self.suite.test_cases]
# ref outputs should be None if any items are None (we validate nullness must be all-or-none)
ref_outputs: Optional[List[str]] = []
if ref_outputs is not None:
@@ -152,27 +159,25 @@ def run(
batch_size=batch_size)
except Exception as e:
logger.error(f"failed to create run: {e}")
- if run_dir:
- _clean_up_run(run_dir=run_dir)
raise ArthurInternalError(f"failed to create run {run_name}") from e
-
- test_case_outputs = [TestCaseOutput(output=output, score=score) for output, score in zip(candidate_output_list, all_scores)]
-
+
+ test_case_outputs = [TestCaseOutput(id=id_, output=output, score=score) for id_, output, score in zip(ids, candidate_output_list, all_scores)]
+
run = TestRun(
name=run_name,
- test_case_outputs=test_case_outputs,
+ test_cases=test_case_outputs,
model_name=model_name,
model_version=model_version,
foundation_model=foundation_model,
prompt_template=prompt_template,
- run_dir=run_dir,
+ test_suite_id=self.suite.id,
+ client=self.client,
**_initialize_metadata()
)
if save:
- self.save()
run.save()
-
+
return run
def save(self):
diff --git a/arthur_bench/run/utils.py b/arthur_bench/run/utils.py
index 8bddf99..beb2259 100644
--- a/arthur_bench/run/utils.py
+++ b/arthur_bench/run/utils.py
@@ -7,12 +7,9 @@
from datetime import datetime
from arthur_bench import __version__
-from arthur_bench.models.models import TestCaseRequest, TestSuiteRequest, ScoringMethod as ScoringMethodEnum
+from arthur_bench.models.models import TestCaseRequest, TestSuiteRequest, PaginatedTestSuite
from arthur_bench.client.exceptions import UserValueError
-from arthur_bench.scoring import BERTScore, QAQualityCorrectness, SummaryQuality, ScoringMethod
-
-BENCH_FILE_DIR_KEY = 'BENCH_FILE_DIR'
-DEFAULT_BENCH_FILE_DIR = str(Path(os.getcwd()) / "bench")
+from arthur_bench.client.bench_client import BenchClient
def get_file_extension(filepath: Union[str, os.PathLike]) -> str:
@@ -20,24 +17,6 @@ def get_file_extension(filepath: Union[str, os.PathLike]) -> str:
return ext
-def _bench_root_dir() -> str:
- return os.environ.get(BENCH_FILE_DIR_KEY, DEFAULT_BENCH_FILE_DIR)
-
-
-def _test_suite_dir(test_suite_name: str) -> Path:
- return Path(_bench_root_dir()) / test_suite_name
-
-
-def _create_test_suite_dir(test_suite_name: str) -> Path:
- if not os.path.exists(_bench_root_dir()):
- os.mkdir(_bench_root_dir())
- test_suite_dir = _test_suite_dir(test_suite_name)
- if test_suite_dir.is_dir():
- raise UserValueError(f"test_suite {test_suite_name} already exists")
- os.mkdir(test_suite_dir)
- return test_suite_dir
-
-
def _initialize_metadata() -> Dict[str, Any]:
return {
"created_at": datetime.now().isoformat(),
@@ -46,18 +25,6 @@ def _initialize_metadata() -> Dict[str, Any]:
}
-def _create_run_dir(test_suite_name: str, run_name: str) -> Path:
- run_dir = _test_suite_dir(test_suite_name) / run_name
- if os.path.exists(run_dir):
- raise UserValueError(f"run {run_name} already exists")
- os.mkdir(run_dir)
- return run_dir
-
-
-def _clean_up_run(run_dir: Path):
- run_dir.rmdir()
-
-
def _validate_dataframe(data: pd.DataFrame, column: str):
if column not in data.columns:
raise UserValueError(f"column {column} not found in dataset")
@@ -178,11 +145,13 @@ def _load_run_data_from_args(
"candidate_data_path csv, or candidate_output_list strings")
-def _get_suite_if_exists(name: str) -> Optional[TestSuiteRequest]:
+def _get_suite_if_exists(client: BenchClient, name: str) -> Optional[PaginatedTestSuite]:
"""
TODO: add version validation
"""
- test_suite_dir = _test_suite_dir(name)
- if test_suite_dir.is_dir():
- return load_suite_from_json(Path(test_suite_dir / "suite.json"))
+ test_suite_resp = client.get_test_suites(name=name)
+ if len(test_suite_resp.test_suites) > 0:
+ # we enforce name validation, so there should ever only be one
+ suite = client.get_test_suite(str(test_suite_resp.test_suites[0].id), page_size=100) # TODO: can we enforce test suite length
+ return PaginatedTestSuite(**suite.dict())
return None
diff --git a/arthur_bench/scoring/qa_quality.py b/arthur_bench/scoring/qa_quality.py
index 562874f..c42b9a8 100644
--- a/arthur_bench/scoring/qa_quality.py
+++ b/arthur_bench/scoring/qa_quality.py
@@ -3,7 +3,7 @@
from langchain.chains import LLMChain
from langchain.chat_models import ChatOpenAI
from langchain.prompts.chat import ChatPromptTemplate, SystemMessagePromptTemplate, AIMessagePromptTemplate, \
- HumanMessagePromptTemplate, BasePromptTemplate
+ HumanMessagePromptTemplate
from arthur_bench.scoring import ScoringMethod
from arthur_bench.client.exceptions import UserValueError
diff --git a/arthur_bench/scoring/scoring_method.py b/arthur_bench/scoring/scoring_method.py
index 2e817e1..9db0948 100644
--- a/arthur_bench/scoring/scoring_method.py
+++ b/arthur_bench/scoring/scoring_method.py
@@ -1,6 +1,5 @@
from abc import abstractmethod, ABC
import sys
-from os import PathLike
from typing import List, Optional, TypeVar
from tqdm import tqdm
diff --git a/arthur_bench/server/html/assets/styles.css b/arthur_bench/server/html/assets/styles.css
deleted file mode 100644
index 50ae5e9..0000000
--- a/arthur_bench/server/html/assets/styles.css
+++ /dev/null
@@ -1,166 +0,0 @@
-/* ALL PAGES */
-
-body {
- font-family: 'Helvetica Neue', Helvetica, Arial,
- 'Lucida Grande', sans-serif;
-}
-
-a.logo {
- box-sizing: border-box;
- text-decoration: underline;
-}
-img.logo {
- -ms-interpolation-mode: bicubic;
- margin-left: 1.6%;
- margin-top: 30px;
-}
-
-a.back-home {
- text-align: center;
- float: right;
- margin-right: 20px;
- margin-top: 40px;
- font-weight: 500;
- font-size: 18px;
- /*text-decoration: none;*/
- color: black;
-}
-
-h2 {
- font-weight: 400;
- line-height: 56px;
- margin: 0;
- margin-bottom: 30px;
- font-size: 36px;
- text-transform: capitalize;
- text-align: left;
- color: #1A0016 !important;
- margin-top: 30px;
- margin-left: 1.7%;
-}
-
-h3 {
- font-size: 18px;
- color: #1A0016;
- margin-top: 0;
-}
-
-/* text */
-.detail {
- font-size: 12px;
- color: #1A0016;
-}
-.detailNormal {
- font-size: 12px;
- color: #1A0016;
- font-weight: normal
-}
-
-/* OVERVIEW PAGES */
-
-/* a box holding details of a test suite, run, etc. */
-table.itemCard {
- box-sizing: border-box;
- mso-table-lspace: 0pt;
- mso-table-rspace: 0pt;
- width: 100%;
- border-collapse: separate !important;
- border: 1px solid #e9e9e9;
- padding: 16px;
- margin-bottom: 12px;
- max-width: 97%;
- margin-left: auto;
- margin-right: auto;
-}
-
-table.itemCard td {
- box-sizing: border-box;
- font-size: 14px;
- vertical-align: top;
-}
-
-table.itemCardHeader {
- box-sizing: border-box;
- mso-table-lspace: 0pt;
- mso-table-rspace: 0pt;
- width: 100%;
- border-collapse: separate !important;
-}
-
-/* title of run/suite to left of timestamp */
-table.itemCardHeader tr td:first-child {
- width: 70%;
-}
-
-/* table cell, inner table, and button links in view details section in bottom half of item card*/
-td.viewDetails {
- box-sizing: border-box;
- font-size: 14px;
- vertical-align: top;
- padding-bottom: 15px;
-}
-table.viewDetails {
- box-sizing: border-box;
- mso-table-lspace: 0pt;
- mso-table-rspace: 0pt;
- width: auto;
- border-collapse: separate !important;
-}
-/* button link e.g. "View Test Runs", "View Test Run Results" */
-a.buttonLink {
- box-sizing: border-box;
- border-color: #B536FB;
- text-decoration: none;
- background-color: #B536FB;
- border: solid 1px #B536FB;
- border-radius: 5px;
- cursor: pointer;
- color: #ffffff;
- font-size: 14px;
- font-weight: bold;
- margin: 0;
- padding: 12px 25px;
- text-transform: capitalize;
- display: inline-block;
- margin-top:20px;
-}
-
-
-/* RUN DETAIL PAGE */
-
-table.runTable {
- box-sizing: border-box;
- mso-table-lspace: 0;
- mso-table-rspace: 0;
- width: 95%;
- border: 1px solid black;
- border-collapse: collapse;
- padding: 4px;
- margin-bottom: 30px;
- margin-left: auto;
- margin-right: auto;
-}
-
-table.runTable td,th {
- border: 1px solid black;
- border-collapse: collapse;
- padding: 4px;
-}
-table.runTable th {
- text-align: left;
- font-size: 14px;
- font-weight: bold;
-}
-table.runTable th.score {
- min-width: 5%;
-}
-table.runTable th.output {
- min-width: 20%;
-}
-
-table.runTable tbody {
- box-sizing: border-box;
- font-size: 14px;
- vertical-align: top;
-}
-
diff --git a/arthur_bench/server/html/test_run_overview.html b/arthur_bench/server/html/test_run_overview.html
deleted file mode 100644
index 33e0295..0000000
--- a/arthur_bench/server/html/test_run_overview.html
+++ /dev/null
@@ -1,88 +0,0 @@
-
-
-
-
- Test Runs
-
-
-
-
-
-
- Test Runs for {{test_suite_name}} Suite
-
-{% for run in runs %}
-
-
-
-
-
- Description:
-
-
- {% if run.description is none or run.description | trim == "" %}(no description)
- {% else %}{{run.description}}{% endif %}
-
- |
-
-
-
-
- |
-
-
-{% endfor %}
-
-
\ No newline at end of file
diff --git a/arthur_bench/server/html/test_run_table.html b/arthur_bench/server/html/test_run_table.html
deleted file mode 100644
index 2b36a18..0000000
--- a/arthur_bench/server/html/test_run_table.html
+++ /dev/null
@@ -1,53 +0,0 @@
-
-
-
-
- Test Run
-
-
-
-
-
-
- Test Run: {{run_name}} for Test Suite: {{test_suite_name}}
-
-
-
-
-
- Input |
- Reference Output |
- Candidate Output |
- Score |
-
-
-
-{% for case in cases %}
-
- {{case.input}} |
- {{case.reference_output}} |
- {{case.output}} |
- {{case.score}} |
-
-
-{% endfor %}
-
-
-
-
-
\ No newline at end of file
diff --git a/arthur_bench/server/html/test_suite_overview.html b/arthur_bench/server/html/test_suite_overview.html
deleted file mode 100644
index cee6d01..0000000
--- a/arthur_bench/server/html/test_suite_overview.html
+++ /dev/null
@@ -1,81 +0,0 @@
-
-
-
-
- Test Suites
-
-
-
-
-
-
-
- All Test Suites
-
-{% for suite in suites %}
-
-
-
-
-
- Description:
-
-
- {% if suite.description is none or suite.description | trim == "" %}(no description)
- {% else %}{{suite.description}}{% endif %}
-
- |
-
-
-
-
- |
-
-
-{% endfor %}
-
-
diff --git a/arthur_bench/server/js/index.tsx b/arthur_bench/server/js/index.tsx
new file mode 100644
index 0000000..92dc6c2
--- /dev/null
+++ b/arthur_bench/server/js/index.tsx
@@ -0,0 +1,17 @@
+import React from 'react';
+import { createRoot } from 'react-dom/client'
+import { Provider as StateProvider } from 'react-redux';
+import { store } from 'arthur-redux';
+import 'resources/icons/selection';
+import 'resources/fonts/fonts.css';
+import Skin from './src/Skin/skin';
+
+
+const element = document.getElementById('root') as HTMLElement;
+const root = createRoot(element);
+
+root.render(
+
+
+
+);
diff --git a/arthur_bench/server/js/package.json b/arthur_bench/server/js/package.json
new file mode 100644
index 0000000..b59d17a
--- /dev/null
+++ b/arthur_bench/server/js/package.json
@@ -0,0 +1,55 @@
+{
+ "name": "arthur-bench",
+ "version": "0.1.0",
+ "private": true,
+ "dependencies": {
+ "@reduxjs/toolkit": "^1.8.3",
+ "@storybook/addon-actions": "^6.5.9",
+ "@types/luxon": "^3.0.0",
+ "@types/react": "^18.0.17",
+ "api": "file:packages/api",
+ "arthur-axios": "file:packages/arthur-axios",
+ "arthur-redux": "file:packages/arthur-redux",
+ "axios": "^0.27.2",
+ "components": "*",
+ "echarts": "^5.3.3",
+ "echarts-for-react": "^3.0.2",
+ "fela": "^12.1.2",
+ "i18next": "^21.8.13",
+ "icomoon-react": "3.0.0",
+ "luxon": "^3.0.1",
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0",
+ "react-fela": "^12.1.2",
+ "react-i18next": "^11.18.0",
+ "react-redux": "^8.0.2",
+ "react-router-dom": "^6.3.0",
+ "react-scripts": "5.0.1",
+ "redux": "^4.2.0",
+ "redux-api-middleware": "^3.2.1",
+ "redux-logger": "^3.0.6",
+ "resources": "file:packages/resources",
+ "ts-loader": "^9.3.0",
+ "tsconfig": "file:packages/tsconfig",
+ "ui": "file:packages/components",
+ "utils": "file:packages/utils",
+ "webpack": "^5.73.0"
+ },
+ "scripts": {
+ "build": "NODE_ENV=production webpack --config=webpack-config/config.js"
+ },
+ "eslintConfig": {
+ "extends": [
+ "react-app",
+ "react-app/jest"
+ ]
+ },
+ "devDependencies": {
+ "@types/jest": "^29.5.3",
+ "@types/lodash.merge": "^4.6.7",
+ "@types/react-dom": "^18.0.6",
+ "@types/redux-api-middleware": "^3.2.3",
+ "@types/uuid": "^9.0.2",
+ "webpack-cli": "^5.1.4"
+ }
+}
diff --git a/arthur_bench/server/js/packages/api/comparators.types.ts b/arthur_bench/server/js/packages/api/comparators.types.ts
new file mode 100644
index 0000000..88dae03
--- /dev/null
+++ b/arthur_bench/server/js/packages/api/comparators.types.ts
@@ -0,0 +1,35 @@
+export enum ComparatorType {
+ GREATER_THAN_EQUAL = 'gte',
+ GREATER_THAN = 'gt',
+ LESS_THAN = 'lt',
+ LESS_THAN_EQUAL = 'lte',
+ EQUAL = 'eq',
+ NOT_EQUAL = 'ne',
+ LIKE = 'like',
+ IN = 'in',
+ NOT_NULL = 'NotNull',
+}
+
+export const comparatorLangMap: Record = {
+ gt: 'greater than',
+ gte: 'greater than or equal to',
+ lt: 'less than',
+ lte: 'less than or equal to',
+ eq: 'equal to',
+ ne: 'not equal to',
+ like: 'like',
+ in: 'in',
+ NotNull: 'not null',
+};
+
+export const ComparatorTypeMap = {
+ gt: ComparatorType.GREATER_THAN,
+ gte: ComparatorType.GREATER_THAN_EQUAL,
+ lt: ComparatorType.LESS_THAN,
+ lte: ComparatorType.LESS_THAN_EQUAL,
+ eq: ComparatorType.EQUAL,
+ ne: ComparatorType.NOT_EQUAL,
+ like: ComparatorType.LIKE,
+ in: ComparatorType.IN,
+ NotNull: ComparatorType.NOT_NULL,
+};
diff --git a/arthur_bench/server/js/packages/api/package.json b/arthur_bench/server/js/packages/api/package.json
new file mode 100644
index 0000000..a6d1bfe
--- /dev/null
+++ b/arthur_bench/server/js/packages/api/package.json
@@ -0,0 +1,14 @@
+{
+ "name": "api",
+ "version": "1.0.0",
+ "description": "Arthur axios instance",
+ "main": "index.ts",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "react": "18.2.0"
+ }
+}
diff --git a/arthur_bench/server/js/packages/api/useOnClickOutside.ts b/arthur_bench/server/js/packages/api/useOnClickOutside.ts
new file mode 100644
index 0000000..5ec09d3
--- /dev/null
+++ b/arthur_bench/server/js/packages/api/useOnClickOutside.ts
@@ -0,0 +1,27 @@
+import React, { useEffect } from 'react';
+
+type TOnClickOutsideHandler = (event: Event) => void;
+
+const useOnClickOutside = (
+ ref: React.RefObject,
+ handler: TOnClickOutsideHandler
+) => {
+ useEffect(() => {
+ const listener: EventListener = (event: Event) => {
+ if (!ref.current || ref.current.contains(event.target)) {
+ return;
+ }
+ handler(event);
+ };
+
+ document.addEventListener('mousedown', listener);
+ document.addEventListener('touchstart', listener);
+
+ return () => {
+ document.removeEventListener('mousedown', listener);
+ document.removeEventListener('touchstart', listener);
+ };
+ }, [ref.current]);
+};
+
+export default useOnClickOutside;
diff --git a/arthur_bench/server/js/packages/arthur-axios/index.ts b/arthur_bench/server/js/packages/arthur-axios/index.ts
new file mode 100644
index 0000000..165c5df
--- /dev/null
+++ b/arthur_bench/server/js/packages/arthur-axios/index.ts
@@ -0,0 +1,14 @@
+import axios from 'axios';
+
+/**
+ * The default
+ * @type {AxiosInstance}
+ */
+const arthurAxios = axios.create({
+ baseURL: 'http://localhost:8000/',
+ headers: { 'Content-Type': 'application/json' },
+ timeout: 1000 * 60, //60 seconds
+});
+
+
+export default arthurAxios;
diff --git a/arthur_bench/server/js/packages/arthur-axios/package.json b/arthur_bench/server/js/packages/arthur-axios/package.json
new file mode 100644
index 0000000..9e12ee4
--- /dev/null
+++ b/arthur_bench/server/js/packages/arthur-axios/package.json
@@ -0,0 +1,14 @@
+{
+ "name": "arthur-axios",
+ "version": "1.0.0",
+ "description": "Arthur axios instance",
+ "main": "index.ts",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "axios": "^0.27.2"
+ }
+}
diff --git a/arthur_bench/server/js/packages/arthur-redux/config/configureStore.prod.ts b/arthur_bench/server/js/packages/arthur-redux/config/configureStore.prod.ts
new file mode 100644
index 0000000..e60e6c1
--- /dev/null
+++ b/arthur_bench/server/js/packages/arthur-redux/config/configureStore.prod.ts
@@ -0,0 +1,22 @@
+import {
+ applyMiddleware,
+ compose,
+ legacy_createStore as createStore,
+} from 'redux';
+import { apiMiddleware } from 'redux-api-middleware';
+import rootReducer from './rootReducer';
+
+export default function configureStore(preloadedState: any) {
+ const store = createStore(
+ rootReducer,
+ preloadedState,
+ compose(applyMiddleware(apiMiddleware))
+ );
+
+ // expose store when run in Cypress
+ if ((window as any).Cypress) {
+ (window as any).store = store;
+ }
+
+ return store;
+}
diff --git a/arthur_bench/server/js/packages/arthur-redux/config/configureStore.ts b/arthur_bench/server/js/packages/arthur-redux/config/configureStore.ts
new file mode 100644
index 0000000..2c668f0
--- /dev/null
+++ b/arthur_bench/server/js/packages/arthur-redux/config/configureStore.ts
@@ -0,0 +1,14 @@
+import configureProductionStore from './configureStore.prod';
+const currentEnvironment = process.env.NODE_ENV;
+
+const environmentConfigs: Record = {
+ production: configureProductionStore,
+};
+
+if (currentEnvironment && !(currentEnvironment in environmentConfigs)) {
+ throw new Error('Unrecognized node environment');
+}
+
+export default currentEnvironment
+ ? environmentConfigs[currentEnvironment]
+ : null;
diff --git a/arthur_bench/server/js/packages/arthur-redux/config/rootReducer.ts b/arthur_bench/server/js/packages/arthur-redux/config/rootReducer.ts
new file mode 100644
index 0000000..9814e73
--- /dev/null
+++ b/arthur_bench/server/js/packages/arthur-redux/config/rootReducer.ts
@@ -0,0 +1,8 @@
+import { combineReducers } from 'redux';
+import Skin from '../slices/skin/skin.reducers';
+import { testSuitesReducer } from '../slices/testSuites/reducers';
+
+export default combineReducers({
+ testSuites: testSuitesReducer,
+ skin: Skin,
+});
diff --git a/arthur_bench/server/js/packages/arthur-redux/config/state.type.ts b/arthur_bench/server/js/packages/arthur-redux/config/state.type.ts
new file mode 100644
index 0000000..92446e3
--- /dev/null
+++ b/arthur_bench/server/js/packages/arthur-redux/config/state.type.ts
@@ -0,0 +1,6 @@
+
+import {TTestSuitesState} from "../slices/testSuites/types";
+
+export type State = {
+ testSuites: TTestSuitesState;
+};
diff --git a/arthur_bench/server/js/packages/arthur-redux/index.ts b/arthur_bench/server/js/packages/arthur-redux/index.ts
new file mode 100644
index 0000000..fde3cc5
--- /dev/null
+++ b/arthur_bench/server/js/packages/arthur-redux/index.ts
@@ -0,0 +1,6 @@
+import configureStore from './config/configureStore'
+import type { State } from './config/state.type';
+
+const store = configureStore();
+
+export { State, store };
diff --git a/arthur_bench/server/js/packages/arthur-redux/package.json b/arthur_bench/server/js/packages/arthur-redux/package.json
new file mode 100644
index 0000000..1213948
--- /dev/null
+++ b/arthur_bench/server/js/packages/arthur-redux/package.json
@@ -0,0 +1,22 @@
+{
+ "name": "arthur-redux",
+ "version": "1.0.0",
+ "description": "Shared Redux store for Arthur Applications",
+ "main": "index.ts",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "@reduxjs/toolkit": "^1.8.3",
+ "luxon": "^3.0.1",
+ "redux": "^4.2.0",
+ "redux-api-middleware": "^3.2.1",
+ "redux-logger": "^3.0.6"
+ },
+ "devDependencies": {
+ "@types/redux-api-middleware": "^3.2.3",
+ "@types/redux-logger": "^3.0.9"
+ }
+}
diff --git a/arthur_bench/server/js/packages/arthur-redux/slices/skin/skin.constants.ts b/arthur_bench/server/js/packages/arthur-redux/slices/skin/skin.constants.ts
new file mode 100644
index 0000000..9a8c095
--- /dev/null
+++ b/arthur_bench/server/js/packages/arthur-redux/slices/skin/skin.constants.ts
@@ -0,0 +1,3 @@
+export const FETCH_SKIN_REQUEST = 'FETCH_SKIN_REQUEST';
+export const FETCH_SKIN_RECEIVE = 'FETCH_SKIN_RECEIVE';
+export const FETCH_SKIN_ERROR = 'FETCH_SKIN_ERROR';
diff --git a/arthur_bench/server/js/packages/arthur-redux/slices/skin/skin.reducers.ts b/arthur_bench/server/js/packages/arthur-redux/slices/skin/skin.reducers.ts
new file mode 100644
index 0000000..93cf026
--- /dev/null
+++ b/arthur_bench/server/js/packages/arthur-redux/slices/skin/skin.reducers.ts
@@ -0,0 +1,41 @@
+import { SkinState } from './skin.type';
+import {
+ FETCH_SKIN_REQUEST,
+ FETCH_SKIN_RECEIVE,
+ FETCH_SKIN_ERROR,
+} from './skin.constants';
+
+const defaultState: SkinState = {
+ skin: {},
+};
+
+type Action = {
+ type: string;
+ payload?: any;
+ error?: any;
+ redirectUrl?: string;
+};
+
+const Skin = (state: SkinState = defaultState, action: Action): SkinState => {
+ switch (action.type) {
+ case FETCH_SKIN_REQUEST:
+ return {
+ ...state,
+ };
+ case FETCH_SKIN_RECEIVE:
+ return {
+ ...state,
+ skin: action.payload,
+ };
+
+ case FETCH_SKIN_ERROR:
+ return {
+ ...state,
+ };
+
+ default:
+ return state;
+ }
+};
+
+export default Skin;
diff --git a/arthur_bench/server/js/packages/arthur-redux/slices/skin/skin.type.ts b/arthur_bench/server/js/packages/arthur-redux/slices/skin/skin.type.ts
new file mode 100644
index 0000000..bcb93c0
--- /dev/null
+++ b/arthur_bench/server/js/packages/arthur-redux/slices/skin/skin.type.ts
@@ -0,0 +1,3 @@
+export type SkinState = {
+ skin: any;
+};
diff --git a/arthur_bench/server/js/packages/arthur-redux/slices/testSuites/actions.ts b/arthur_bench/server/js/packages/arthur-redux/slices/testSuites/actions.ts
new file mode 100644
index 0000000..c718442
--- /dev/null
+++ b/arthur_bench/server/js/packages/arthur-redux/slices/testSuites/actions.ts
@@ -0,0 +1,35 @@
+import {createAction} from "@reduxjs/toolkit";
+import * as constants from "../testSuites/constants";
+
+
+
+export const fetchTestSuitesRequest = createAction(
+ constants.FETCH_TEST_SUITES_REQUEST
+);
+export const fetchTestSuitesReceive = createAction(
+ constants.FETCH_TEST_SUITES_RECEIVE
+);
+export const fetchTestRunsRequest = createAction(
+ constants.FETCH_TEST_RUNS_REQUEST
+);
+export const fetchTestRunsReceive = createAction(
+ constants.FETCH_TEST_RUNS_RECEIVE
+);
+export const fetchTestSuiteDataRequest = createAction(
+ constants.FETCH_TEST_SUITE_DATA_REQUEST
+);
+export const fetchTestSuiteDataReceive = createAction(
+ constants.FETCH_TEST_SUITE_DATA_RECEIVE
+);
+export const fetchTestRunSummaryRequest = createAction(
+ constants.FETCH_TEST_RUN_SUMMARY_REQUEST
+);
+export const fetchTestRunSummaryReceive = createAction(
+ constants.FETCH_TEST_RUN_SUMMARY_RECEIVE
+);
+export const fetchTestRunDetailsRequest = createAction(
+ constants.FETCH_TEST_RUN_DETAILS_REQUEST
+);
+export const fetchTestRunDetailsReceive = createAction(
+ constants.FETCH_TEST_RUN_DETAILS_RECEIVE
+);
diff --git a/arthur_bench/server/js/packages/arthur-redux/slices/testSuites/constants.ts b/arthur_bench/server/js/packages/arthur-redux/slices/testSuites/constants.ts
new file mode 100644
index 0000000..559b42e
--- /dev/null
+++ b/arthur_bench/server/js/packages/arthur-redux/slices/testSuites/constants.ts
@@ -0,0 +1,20 @@
+export const FETCH_TEST_SUITES_REQUEST =
+ 'FETCH_TEST_SUITES_REQUEST';
+export const FETCH_TEST_SUITES_RECEIVE =
+ 'FETCH_TEST_SUITES_RECEIVE';
+export const FETCH_TEST_RUNS_REQUEST =
+ 'FETCH_TEST_RUNS_REQUEST';
+export const FETCH_TEST_RUNS_RECEIVE =
+ 'FETCH_TEST_RUNS_RECEIVE';
+export const FETCH_TEST_SUITE_DATA_REQUEST =
+ 'FETCH_TEST_SUITE_DATA_REQUEST';
+export const FETCH_TEST_SUITE_DATA_RECEIVE =
+ 'FETCH_TEST_SUITE_DATA_RECEIVE';
+export const FETCH_TEST_RUN_SUMMARY_REQUEST =
+ 'FETCH_TEST_RUN_SUMMARY_REQUEST'
+export const FETCH_TEST_RUN_SUMMARY_RECEIVE =
+ 'FETCH_TEST_RUN_SUMMARY_RECEIVE'
+export const FETCH_TEST_RUN_DETAILS_REQUEST =
+ 'FETCH_TEST_RUN_DETAILS_RECEIVE'
+export const FETCH_TEST_RUN_DETAILS_RECEIVE =
+ 'FETCH_TEST_RUN_DETAILS_RECEIVE'
diff --git a/arthur_bench/server/js/packages/arthur-redux/slices/testSuites/reducers.ts b/arthur_bench/server/js/packages/arthur-redux/slices/testSuites/reducers.ts
new file mode 100644
index 0000000..7edd179
--- /dev/null
+++ b/arthur_bench/server/js/packages/arthur-redux/slices/testSuites/reducers.ts
@@ -0,0 +1,78 @@
+import {
+ ActionReducerMapBuilder,
+ createReducer,
+ PayloadAction,
+} from '@reduxjs/toolkit';
+import {TestSuiteData, TRunData, TSummary, TTestRun, TTestSuiteData, TTestSuitesState} from './types';
+import * as actions from './actions';
+
+const defaultState = {
+ currentTestSuite: {
+ runs: {
+ runs: null,
+ pagination: { total_count: 0, page: 1, page_size: 0, total_pages: 0 }
+ },
+ summaries: {
+ summaries: null,
+ num_test_cases: 0
+ },
+ data: {
+ data: null,
+ pagination: { total_count: 0, page: 1, page_size: 0, total_pages: 0 }},
+
+ },
+ currentTestRun: {
+ data: null,
+ pagination: { total_count: 0, page: 1, page_size: 0, total_pages: 0 }
+ },
+ data: null,
+ pagination: { total_count: 0, page: 1, page_size: 0, total_pages: 0 },
+};
+
+export const testSuitesReducer = createReducer(
+ defaultState,
+ (builder: ActionReducerMapBuilder) => {
+ builder
+ .addCase(
+ actions.fetchTestSuitesReceive,
+ (state: TTestSuitesState, action: PayloadAction) => {
+ state.data = action.payload.data;
+ state.pagination = action.payload.pagination;
+ }
+ )
+ .addCase(
+ actions.fetchTestRunsReceive,
+ (state: TTestSuitesState, action: PayloadAction) => {
+ if (state.currentTestSuite && state.currentTestSuite.runs) {
+ state.currentTestSuite.runs.runs = action.payload.runs;
+ state.currentTestSuite.runs.pagination = action.payload.pagination;
+ }
+ }
+ )
+ .addCase(
+ actions.fetchTestSuiteDataReceive,
+ (state: TTestSuitesState, action: PayloadAction) => {
+ if (state.currentTestSuite && state.currentTestSuite.data) {
+ state.currentTestSuite.data.data = action.payload.data;
+ state.currentTestSuite.data.pagination = action.payload.pagination;
+ }
+ }
+ )
+ .addCase(
+ actions.fetchTestRunSummaryReceive,
+ (state: TTestSuitesState, action: PayloadAction) => {
+ if (state.currentTestSuite) {
+ state.currentTestSuite.summaries.summaries = action.payload.summaries;
+ state.currentTestSuite.summaries.num_test_cases = action.payload.num_test_cases;
+ }
+ }
+ )
+ .addCase(
+ actions.fetchTestRunDetailsReceive,
+ (state: TTestSuitesState, action: PayloadAction) => {
+ state.currentTestRun.data = action.payload.data;
+ state.currentTestRun.pagination = action.payload.pagination;
+ }
+ );
+ }
+);
diff --git a/arthur_bench/server/js/packages/arthur-redux/slices/testSuites/types.ts b/arthur_bench/server/js/packages/arthur-redux/slices/testSuites/types.ts
new file mode 100644
index 0000000..db63133
--- /dev/null
+++ b/arthur_bench/server/js/packages/arthur-redux/slices/testSuites/types.ts
@@ -0,0 +1,110 @@
+export type TTestSuite = {
+ id: string;
+ name: string;
+ scoring_method: EMethodType;
+ last_run_time: string;
+ updated_at: string;
+ created_at: string;
+};
+
+export type TestSuiteCase = {
+ id: string;
+ input: string;
+ reference_output: string;
+};
+
+export type TRunData = {
+ runs: Run[] | null;
+ pagination: TPagination
+}
+
+export type TTestSuiteData = {
+ data: DetailedTestSuite | null;
+ pagination: TPagination;
+}
+
+export type Run = {
+ id: string;
+ name: string;
+ timestamp: string;
+ model_name: string;
+ model_version: string;
+ foundation_model: string;
+ prompt_template: string;
+ avg_score: number;
+ updated_at: string
+};
+
+export type DetailedTestSuite = {
+ id: string;
+ description: string;
+ name: string;
+ scoring_method: EMethodType;
+ last_run_time: string;
+ num_runs: number;
+ test_cases: TestSuiteCase[];
+};
+
+export type TDistribution = {
+ count: number;
+ low: number;
+ high: number;
+}
+
+export type TestRunSummary = {
+ avg_score: number;
+ name: string;
+ histogram: TDistribution[];
+ id: string
+}
+export type TSummary = {
+ summaries: TestRunSummary[] | null;
+ num_test_cases: number;
+}
+
+export type TPagination = {
+ page: number;
+ page_size: number;
+ total_count: number;
+ total_pages: number;
+}
+
+export type TestSuiteData = {
+ data: TTestSuiteData | null;
+ runs: TRunData | null;
+ summaries: TSummary;
+};
+
+export type TestRunCase = {
+ id: string;
+ input: string;
+ output: string;
+ reference_output: string;
+ score: number;
+};
+
+export type TTestRun = {
+ data: TTestRunData | null;
+ pagination: TPagination;
+}
+
+export type TTestRunData = {
+ test_suite_id: string;
+ test_case_runs: TestRunCase[];
+ name: string;
+ created_at: string,
+ id: string
+};
+
+export type TTestSuitesState = {
+ data: TTestSuite[] | null;
+ currentTestSuite: TestSuiteData;
+ currentTestRun: TTestRun;
+ pagination: TPagination;
+};
+
+export enum EMethodType {
+ BERT = 'bertscore',
+ SUMMARY = 'summary_quality',
+ QA = 'qa_correctness',
+}
diff --git a/arthur_bench/server/js/packages/components/compound/Chip/Chip.tsx b/arthur_bench/server/js/packages/components/compound/Chip/Chip.tsx
new file mode 100644
index 0000000..0c89b8a
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/compound/Chip/Chip.tsx
@@ -0,0 +1,79 @@
+import React from 'react';
+import { useFela } from 'react-fela';
+
+import Icon from '@core/Icon';
+import { Button, EButtonSize } from '@core/Button';
+
+import styles from './styles';
+import { EChipTheme, TChipProps } from './types';
+
+const Chip: React.FC = (
+ props: TChipProps
+): React.ReactElement => {
+ const {
+ chip,
+ theme = EChipTheme.DEFAULT,
+ chipName,
+ minimal,
+ iconEnd,
+ iconStart,
+ onIconClick,
+ overrideStyles = {},
+ iconStartColor
+ } = props;
+
+ const className = styles(theme, minimal);
+ const { css } = useFela();
+
+ const iconClickHandler = () => {
+ if (onIconClick && chip) {
+ onIconClick(chip);
+ }
+ };
+
+ const renderStartIcon = () => {
+ if (iconStart) {
+ return (
+
+ );
+ }
+ };
+
+ const renderEndIcon = () => {
+ if (iconEnd && onIconClick) {
+ return (
+
+ );
+ } else if (iconEnd) {
+ return (
+
+ );
+ }
+
+ return null;
+ };
+
+ return (
+
+ {renderStartIcon()}
+ {chip ? chip.name : ''}
+ {chipName ? chipName : ''}
+ {renderEndIcon()}
+
+ );
+};
+
+export default Chip;
diff --git a/arthur_bench/server/js/packages/components/compound/Chip/index.ts b/arthur_bench/server/js/packages/components/compound/Chip/index.ts
new file mode 100644
index 0000000..907a448
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/compound/Chip/index.ts
@@ -0,0 +1,6 @@
+import Chip from './Chip';
+import { TChip, TChipProps, EChipTheme } from './types';
+
+export type { TChip, TChipProps };
+export { EChipTheme };
+export default Chip;
diff --git a/arthur_bench/server/js/packages/components/compound/Chip/styles.ts b/arthur_bench/server/js/packages/components/compound/Chip/styles.ts
new file mode 100644
index 0000000..4cb6f49
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/compound/Chip/styles.ts
@@ -0,0 +1,47 @@
+import { GRAPHIK } from 'resources/fonts';
+import primary from 'resources/colors/Arthur/primary';
+
+import { EChipTheme } from './types';
+
+const chipThemeColors: Record = {
+ [EChipTheme.ARTHUR]: primary.purple,
+}
+
+const styles = (chipTheme: EChipTheme , isMinimal?: boolean) => ({
+ root: {
+ whiteSpace: 'nowrap',
+ fontFamily: GRAPHIK,
+ fontSize: '12px',
+ backgroundColor: chipTheme !== EChipTheme.DEFAULT ? 'transparent' : primary.ashGrey,
+ ...(chipTheme !== EChipTheme.DEFAULT && {
+ border: `1px solid ${primary.ashGrey}`,
+ }),
+ borderRadius: '2px',
+ display: 'inline-flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ padding: isMinimal ? '0 4px' : '4px 8px',
+ color: chipThemeColors[chipTheme] || primary.eggplant,
+ },
+ button: {
+ width: 'auto',
+ padding: '0 !important',
+ },
+ icon: {
+ marginLeft: '4px',
+
+ '& path': {
+ fill: chipThemeColors[chipTheme] || 'inherit !important',
+ },
+ },
+ name: {
+ overflow: 'hidden',
+ textOverflow: 'ellipsis',
+ },
+ iconStart: {
+ marginLeft: '0px',
+ marginRight: '4px',
+ },
+});
+
+export default styles;
diff --git a/arthur_bench/server/js/packages/components/compound/Chip/types.ts b/arthur_bench/server/js/packages/components/compound/Chip/types.ts
new file mode 100644
index 0000000..ede2823
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/compound/Chip/types.ts
@@ -0,0 +1,26 @@
+import { EIconType } from '../../core/Icon';
+import { FelaStyle } from 'react-fela';
+
+export enum EChipTheme {
+ ARTHUR = 'arthur',
+ DEFAULT = 'default',
+ OUTLINED = 'outlined',
+ PRODUCTION = 'production'
+}
+
+export type TChip = {
+ id: string;
+ name: string;
+};
+
+export type TChipProps = {
+ onIconClick?: (tag: TChip) => void;
+ chip?: TChip;
+ chipName?: string | number;
+ minimal?: boolean;
+ iconStart?: EIconType;
+ iconEnd?: EIconType;
+ theme?: EChipTheme
+ overrideStyles?: FelaStyle<{}>;
+ iconStartColor?: string
+};
diff --git a/arthur_bench/server/js/packages/components/compound/FilterButton/FilterButton.tsx b/arthur_bench/server/js/packages/components/compound/FilterButton/FilterButton.tsx
new file mode 100644
index 0000000..da901e4
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/compound/FilterButton/FilterButton.tsx
@@ -0,0 +1,35 @@
+import React, { ForwardedRef } from 'react';
+import { Button, EButtonSize } from '@core/Button';
+import { useFela } from 'react-fela';
+import styles from './styles';
+import { EIconType } from '@core/Icon/types';
+
+export type FilterButtonProps = {
+ text: string;
+ icon: EIconType;
+ isActive?: boolean;
+ onClick: () => void;
+};
+
+const FilterButton = React.forwardRef(
+ (props: FilterButtonProps, ref: ForwardedRef) => {
+ const { css } = useFela();
+ const classNames = styles(props.isActive);
+
+ return (
+
+ );
+ }
+);
+
+FilterButton.displayName = 'ArthurFilterButton';
+
+export default FilterButton;
diff --git a/arthur_bench/server/js/packages/components/compound/FilterButton/index.ts b/arthur_bench/server/js/packages/components/compound/FilterButton/index.ts
new file mode 100644
index 0000000..3db6eb2
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/compound/FilterButton/index.ts
@@ -0,0 +1,3 @@
+import FilterButton from './FilterButton';
+
+export default FilterButton;
diff --git a/arthur_bench/server/js/packages/components/compound/FilterButton/styles.ts b/arthur_bench/server/js/packages/components/compound/FilterButton/styles.ts
new file mode 100644
index 0000000..8ceefc9
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/compound/FilterButton/styles.ts
@@ -0,0 +1,23 @@
+import primary from 'resources/colors/Arthur/primary';
+
+const activeStyles = {
+ textDecoration: 'none !important',
+ backgroundColor: 'white !important',
+};
+
+const styles = (isActive: boolean | undefined) => ({
+ root: {
+ color: `${primary.eggplant} !important`,
+ borderRadius: '4px',
+ transition: '0.5s',
+ padding: '8px !important',
+ ...(isActive && activeStyles),
+ '&:hover': activeStyles,
+ '& path': {
+ transition: '0.5s',
+ fill: `${primary.eggplant} !important`,
+ },
+ },
+});
+
+export default styles;
diff --git a/arthur_bench/server/js/packages/components/compound/FilterComponent/FilterComponent.styles.ts b/arthur_bench/server/js/packages/components/compound/FilterComponent/FilterComponent.styles.ts
new file mode 100644
index 0000000..e2d9fa4
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/compound/FilterComponent/FilterComponent.styles.ts
@@ -0,0 +1,51 @@
+import primary from 'resources/colors/Arthur/primary';
+import secondary from 'resources/colors/Arthur/secondary';
+
+const styles = ({ theme, type, isActive, color, bkgColor }: any) => ({
+ wrap: {
+ overflow: 'hidden',
+ display:
+ type === 'horizontal' || type === 'selection' || type === 'square'
+ ? 'flex'
+ : 'block',
+ alignItems: 'center',
+ ...(color && isActive && { border: `1px solid ${color}` }),
+ ...(!color &&
+ isActive && {
+ border: `1px solid ${
+ isActive ? secondary.blue : primary.ashGrey
+ }`,
+ }),
+ borderRadius: '4px',
+ cursor: 'pointer',
+ padding:
+ type === 'selection' || type === 'text' ? '4px 5px' : '10px 15px',
+ ...(type === 'square' && {
+ textAlign: 'center',
+ width: '80px',
+ height: '80px',
+ flexDirection: 'column',
+ justifyContent: 'center',
+ padding: 0,
+ }),
+ ...(isActive && {
+ background: bkgColor ? bkgColor : secondary.lightBlue,
+ }),
+
+ ':hover': {
+ background: bkgColor ? bkgColor : secondary.lightBlue,
+ },
+ },
+ icon: {
+ ...(type === 'square' && {
+ marginBottom: '10px',
+ }),
+ fill: color ? color : secondary.blue,
+ },
+ label: {
+ marginLeft:
+ type === 'square' ? '0' : type === 'selection' ? '5px' : '12px',
+ },
+});
+
+export default styles;
diff --git a/arthur_bench/server/js/packages/components/compound/FilterComponent/FilterComponent.tsx b/arthur_bench/server/js/packages/components/compound/FilterComponent/FilterComponent.tsx
new file mode 100644
index 0000000..fc40eaa
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/compound/FilterComponent/FilterComponent.tsx
@@ -0,0 +1,45 @@
+import React from 'react';
+import { connect as connectStyles } from 'react-fela';
+import { compose } from 'ui/helpers/compose';
+import { useFela } from 'react-fela';
+import Checkbox from '../../core/Checkbox';
+import Field from '../../core/Field';
+import Icon from '../../core/Icon';
+import styles from './FilterComponent.styles';
+import { FilterComponentProps } from './FilterComponentProps.types';
+
+const FilterComponent = ({
+ icon,
+ label,
+ type = 'horizontal',
+ isActive = false,
+ clickHandler = () => {},
+ styles = {},
+ color,
+}: FilterComponentProps) => {
+ const { theme }: any = useFela();
+
+ return (
+
+ {icon && (
+
+
+
+ )}
+ {type === 'selection' && (
+
+
+
+ )}
+
+
+
+
+ );
+};
+
+export default compose(connectStyles(styles))(FilterComponent);
diff --git a/arthur_bench/server/js/packages/components/compound/FilterComponent/FilterComponentProps.types.ts b/arthur_bench/server/js/packages/components/compound/FilterComponent/FilterComponentProps.types.ts
new file mode 100644
index 0000000..3c421ff
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/compound/FilterComponent/FilterComponentProps.types.ts
@@ -0,0 +1,18 @@
+import { EIconType } from '../../core/Icon';
+
+export type FilterComponentTypes =
+ | 'square'
+ | 'horizontal'
+ | 'selection'
+ | 'text';
+
+export type FilterComponentProps = {
+ icon?: EIconType;
+ label: string;
+ type: FilterComponentTypes;
+ isActive?: boolean;
+ clickHandler?: () => void;
+ styles?: Record;
+ color?: string;
+ bkgColor?: string;
+};
diff --git a/arthur_bench/server/js/packages/components/compound/FilterComponent/index.tsx b/arthur_bench/server/js/packages/components/compound/FilterComponent/index.tsx
new file mode 100644
index 0000000..2e16808
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/compound/FilterComponent/index.tsx
@@ -0,0 +1,3 @@
+import FilterComponent from './FilterComponent';
+
+export default FilterComponent;
diff --git a/arthur_bench/server/js/packages/components/compound/FloatingHelp/FloatingHelp.tsx b/arthur_bench/server/js/packages/components/compound/FloatingHelp/FloatingHelp.tsx
new file mode 100644
index 0000000..efc43ac
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/compound/FloatingHelp/FloatingHelp.tsx
@@ -0,0 +1,299 @@
+import React, { useEffect, useState } from 'react';
+import { useFela } from 'react-fela';
+import { useTranslation } from 'react-i18next';
+
+import primary from 'resources/colors/Arthur/primary';
+import checkIfEnter from 'utils/keypress-enter';
+
+import Icon, { EIconType } from '../../core/Icon';
+import HelpTile, { HelpTileProps } from '../../core/HelpTile';
+import * as styles from './styles';
+import { Button } from '../../core/Button';
+import secondary from 'resources/colors/Arthur/secondary';
+
+const INDEPTH_LINK = 'https://github.com/arthur-ai/arthur-sandbox';
+const DOCS_LINK = 'https://docs.arthur.ai/index.html';
+const EMAIL_LINK = 'mailto:support@arthur.ai';
+const ELEMENT_EMAIL_LINK = 'mailto:feedback@arthur.ai';
+const QUICKSTART_LINK =
+ 'https://docs.arthur.ai/user-guide/arthur_quickstart.html';
+const BUTTON_LINK =
+ 'https://docs.arthur.ai/user-guide/walkthroughs/model-onboarding/index.html';
+const PYTHON_LINK = 'https://github.com/arthur-ai/bench';
+const BENCH_DOCS_LINK = 'https://docs.arthur.ai/bench/index.html';
+
+interface Props {
+ paidUser?: boolean;
+}
+
+function FloatingHelp({ paidUser }: Props) {
+ const { t } = useTranslation(['common']);
+ const { css } = useFela();
+ const [open, setOpen] = useState(false);
+ const [isBench, setIsBench] = useState(false);
+
+ const togglePanel = () => setOpen(!open);
+ const closePanel = () => setOpen(false);
+ const handleKeypress = (e: any) => {
+ checkIfEnter(e, togglePanel);
+ };
+ const clickHandler = () => {
+ window.open(BUTTON_LINK);
+ };
+ useEffect(() => {
+ window.location.href.includes('bench')
+ ? setIsBench(true)
+ : setIsBench(false);
+ }, [window.location.href]);
+
+ const leftOptions: Array = [
+ {
+ title: t('onboarding.quickstartExample'),
+ description: t('onboarding.tryArthur'),
+ link: QUICKSTART_LINK,
+ icon: EIconType.EXTERNAL_LINK,
+ effort: t('onboarding.uploadEffort', { minutes: '5' }),
+ },
+ {
+ title: t('onboarding.inDepthExample'),
+ description: t('onboarding.practiceWithOne'),
+ link: INDEPTH_LINK,
+ icon: EIconType.EXTERNAL_LINK,
+ effort: t('onboarding.uploadEffort', { minutes: '15-20' }),
+ },
+ {
+ title: t('onboarding.uploadOwnModel'),
+ description: (
+
+
{t('onboarding.startMonitor')}
+
+
+
+ ),
+ link: '',
+ icon: undefined,
+ effort: '',
+ },
+ ];
+
+ const rightOptions: Array = [
+ {
+ title: t('onboarding.productTour'),
+ titleIcon: (
+
+
+
+ ),
+ description: t('onboarding.productText'),
+ link: '',
+ icon: EIconType.ARROW_RIGHT,
+ effort: t('onboarding.totalEffort', { minutes: '15-20' }),
+ disabled: true,
+ },
+ {
+ title: t('onboarding.arthurDocs'),
+ titleIcon: (
+
+
+
+ ),
+ description: t('onboarding.learnDetails'),
+ link: DOCS_LINK,
+ icon: EIconType.EXTERNAL_LINK,
+ effort: '',
+ },
+ {
+ title: paidUser
+ ? t('onboarding.support')
+ : t('onboarding.feedback'),
+ titleIcon: (
+
+
+
+ ),
+ description: paidUser
+ ? t('onboarding.emailUs')
+ : t('onboarding.hearFromYou'),
+ link: paidUser ? EMAIL_LINK : ELEMENT_EMAIL_LINK,
+ icon: EIconType.EXTERNAL_LINK,
+ effort: '',
+ disabled: paidUser,
+ },
+ ];
+
+ const benchOptions: HelpTileProps[] = [
+ {
+ title: t('benchResources.python'),
+ titleIcon: (
+
+
+
+ ),
+ description: t('benchResources.pythonText'),
+ link: PYTHON_LINK,
+ icon: EIconType.EXTERNAL_LINK,
+ },
+ {
+ title: t('benchResources.api'),
+ titleIcon: (
+
+
+
+ ),
+ description: t('benchResources.apiText'),
+ link: BENCH_DOCS_LINK,
+ icon: EIconType.EXTERNAL_LINK,
+ },
+ {
+ title: t('benchResources.support'),
+ titleIcon: (
+
+
+
+ ),
+ description: t('benchResources.supportText'),
+ link: EMAIL_LINK,
+ icon: EIconType.EXTERNAL_LINK,
+ },
+ ];
+
+ return (
+
+
+
+
{t('onboarding.arthurResources')}
+
+
+
+
+
+ {isBench ? (
+
+ {benchOptions.map((o) => (
+
+
+
+ ))}
+
+ ) : (
+ <>
+
+
{t('onboarding.getStarted')}
+ {leftOptions.map((o) => (
+
+
+
+ ))}
+
+
+
{t('onboarding.addHelp')}
+ {rightOptions.map((o) => (
+
+
+
+ ))}
+
+ >
+ )}
+
+
+
+
+
+
+
+
+ );
+}
+
+export default FloatingHelp;
diff --git a/arthur_bench/server/js/packages/components/compound/FloatingHelp/index.tsx b/arthur_bench/server/js/packages/components/compound/FloatingHelp/index.tsx
new file mode 100644
index 0000000..052fd69
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/compound/FloatingHelp/index.tsx
@@ -0,0 +1,3 @@
+import FloatingHelp from './FloatingHelp';
+
+export default FloatingHelp;
diff --git a/arthur_bench/server/js/packages/components/compound/FloatingHelp/styles.ts b/arthur_bench/server/js/packages/components/compound/FloatingHelp/styles.ts
new file mode 100644
index 0000000..b93147f
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/compound/FloatingHelp/styles.ts
@@ -0,0 +1,76 @@
+import { FelaStyle } from 'react-fela';
+import primary from 'resources/colors/Arthur/primary';
+import { GRAPHIK, MONO } from 'resources/fonts';
+
+export const iconStyle: FelaStyle = {
+ color: 'white',
+ cursor: 'pointer',
+ position: 'fixed',
+ right: '5px',
+ bottom: '5px',
+ zIndex: 11,
+};
+
+export const panelStyle = (open: boolean): FelaStyle => ({
+ display: open ? 'block' : 'none',
+ height: 'auto', //'917px',
+ maxWidth: '919px',
+ width: '919px',
+ backgroundColor: '#FFFFFF',
+ boxShadow: '0px 4px 12px rgba(26, 0, 22, 0.1)',
+ borderRadius: '2px',
+ position: 'fixed',
+ right: '3%',
+ bottom: '8%',
+ zIndex: 11,
+});
+
+export const headerStyle: FelaStyle = {
+ width: '100%',
+ height: '48px',
+ borderRadius: '2px 2px 0px 0px',
+ boxShadow: 'inset 0px -1px 0px rgba(26, 0, 22, 0.1)',
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'space-between',
+ padding: '12px',
+ boxSizing: 'border-box',
+ '> div': {
+ fontFamily: MONO,
+ fontWeight: 500,
+ fontSize: '14px',
+ lineHeight: '40px',
+ letterSpacing: '5%',
+ color: primary.eggplant,
+ },
+};
+
+export const iconBox = {
+ height: '40px',
+ width: '40px',
+ display: 'flex',
+ justifyContent: 'center',
+ alignItems: 'center',
+ marginRight: '10px',
+};
+
+export const contentStyle: FelaStyle = {
+ display: 'grid',
+ gridAutoFlow: 'column',
+ gridAutoColumns: '1fr',
+ alignItems: 'start',
+};
+
+export const columnStyle: FelaStyle = {
+ padding: '16px',
+ display: 'grid',
+ rowGap: '16px',
+ '> h4': {
+ fontFamily: GRAPHIK,
+ fontWeight: 400,
+ fontSize: '18px',
+ color: primary.eggplant,
+ lineHeight: '32px',
+ margin: 0,
+ },
+};
diff --git a/arthur_bench/server/js/packages/components/compound/InputsOutputsTable/InputsOutputsTable.tsx b/arthur_bench/server/js/packages/components/compound/InputsOutputsTable/InputsOutputsTable.tsx
new file mode 100644
index 0000000..3bba4e8
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/compound/InputsOutputsTable/InputsOutputsTable.tsx
@@ -0,0 +1,102 @@
+import React, {useCallback, useEffect, useState} from 'react';
+import { Table, TableHeader, TableRow, TableCell } from '../../core/Table';
+import { useParams } from 'react-router-dom';
+import { useSelector } from 'react-redux';
+import { useTestSuites } from '../../../../src/Bench/useTestSuites';
+import { State } from 'arthur-redux';
+import { TestSuiteCase } from 'arthur-redux/slices/testSuites/types';
+import Loading from '../Loading';
+import styles, { cellStyles, headerCell } from '../TestRunTable/styles';
+import { useFela } from 'react-fela';
+import { Paginator } from '../../core/Paginator';
+import ExpandableTableCell from '../../core/Table/components/ExpandableTableCell';
+import primary from 'resources/colors/Arthur/primary';
+
+type RowProps = {
+ testCase: TestSuiteCase;
+};
+
+const cellStyle = {
+ width: '450px',
+ textAlign: 'left' as 'left',
+ border: `1px solid ${primary.ashGrey}`,
+ padding: '25px',
+};
+const Row = ({ testCase }: RowProps) => {
+ const { css } = useFela();
+ return (
+
+
+
+
+ );
+};
+const InputsOutputsTable = () => {
+ const { testSuiteId } = useParams();
+ const { fetchTestSuiteData } = useTestSuites();
+ const { css } = useFela();
+ const [page, setPage] = useState(1);
+
+ useEffect(() => {
+ testSuiteId && fetchTestSuiteData(testSuiteId, page, 10);
+ }, [testSuiteId, page]);
+
+
+ const setNewPage = useCallback(
+ (propsPage: number) => {
+ const newPage = propsPage + 1;
+ if (newPage === page || !newPage || !page) {
+ return;
+ }
+ setPage(newPage);
+ },
+ [page]
+ );
+
+ const { data, pagination } = useSelector((state: State) => ({
+ data: state.testSuites?.currentTestSuite?.data?.data,
+ pagination: state.testSuites?.currentTestSuite?.data?.pagination,
+ }));
+
+ return (
+ <>
+
+
+
+
+ INPUT PROMPTS
+
+
+ REFERENCE OUTPUT
+
+
+
+ {data ? (
+ data.test_cases.map((testCase) => (
+
+ ))
+ ) : (
+
+ )}
+
+ {pagination && (
+
+ )}
+ >
+ );
+};
+
+export default InputsOutputsTable;
diff --git a/arthur_bench/server/js/packages/components/compound/InsightCard/InsightCard.tsx b/arthur_bench/server/js/packages/components/compound/InsightCard/InsightCard.tsx
new file mode 100644
index 0000000..f2ecae4
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/compound/InsightCard/InsightCard.tsx
@@ -0,0 +1,93 @@
+import React, { useMemo, useState } from 'react';
+import { useFela } from 'react-fela';
+import { useTranslation } from 'react-i18next';
+import arthurAxios from 'arthur-axios';
+
+import TooltipComponent from '../../core/Tooltip/Tooltip';
+
+import { InsightProps } from './types';
+import StyledSelect from '../../core/StyledSelect/StyledSelect';
+import { Button, EButtonVariation } from '../../core/Button';
+import { buildPhrasing } from './utils';
+import styles, { statusDropdownStyles } from './styles';
+
+const InsightCard = ({ insight, openInferences }: InsightProps) => {
+ const { metric_type, metric_value, region } = insight;
+ const { css } = useFela();
+ const { t } = useTranslation(['common']);
+ const [status, setStatus] = useState(insight.status);
+
+ const statusList = useMemo(
+ () => [
+ { id: 'new', name: t('insightCard.new') },
+ { id: 'acknowledged', name: t('insightCard.acknowledged') },
+ ],
+ []
+ );
+
+ const handleSetStatus = (option: any) => {
+ arthurAxios
+ .patch(
+ `/api/v3/models/${insight.model_id}/insights/${insight.id}`,
+ {
+ status: option.id,
+ }
+ )
+ .then(() => setStatus(option.id));
+ };
+
+ const phrasing = buildPhrasing(region);
+ const phraseList = (start: number, stop?: number) => {
+ return phrasing
+ .slice(start, stop)
+ .map((item) => (
+ {item}
+ ));
+ };
+
+ return (
+
+
+
+ {metric_type + ': ' + metric_value.toFixed(4)}
+
+ {/* to do: get actual inference count*/}
+
+ Inferences Count: {insight.inference_count}
+
+
+
+ {phraseList(0, 4)}
+ {phrasing.length > 4 && (
+
+
+ {`+ ${phrasing.length - 4} more`}
+
+
+ )}
+
+
+
+ x.id === status)}
+ filled={true}
+ customStyles={statusDropdownStyles}
+ large={true}
+ >
+
+
+ );
+};
+
+export default InsightCard;
diff --git a/arthur_bench/server/js/packages/components/compound/InsightCard/styles.ts b/arthur_bench/server/js/packages/components/compound/InsightCard/styles.ts
new file mode 100644
index 0000000..301c4c1
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/compound/InsightCard/styles.ts
@@ -0,0 +1,59 @@
+import primary from 'resources/colors/Arthur/primary';
+import secondary from 'resources/colors/Arthur/secondary';
+import { GRAPHIK, MONO } from 'resources/fonts';
+
+export const statusDropdownStyles = {
+ backgroundColor: secondary.blue,
+ color: primary.white,
+ fontFamily: MONO,
+ letterSpacing: '0.8px',
+ fill: primary.white,
+};
+const styles = {
+ container: {
+ backgroundColor: 'white',
+ height: '265px',
+ width: '470px',
+ margin: '18px',
+ border: `1px solid ${primary.ashGrey}`,
+ borderLeft: 'solid 5px #ffbf02',
+ borderRadius: '2px',
+ color: primary.eggplant,
+ },
+ box: {
+ backgroundColor: '#f0f1f5',
+ padding: '6px',
+ fontFamily: MONO,
+ display: 'inline-block',
+ margin: '15px 0px',
+ fontSize: '12px',
+ },
+ topRow: {
+ display: 'flex',
+ alignItems: 'flex-start',
+ gap: '10px',
+ margin: '0px 20px',
+ },
+ bottomRow: {
+ display: 'flex',
+ justifyContent: 'flex-end',
+ margin: '20px',
+ gap: '15px',
+ },
+ attributeLogic: {
+ margin: '10px 20px',
+ fontFamily: GRAPHIK,
+ fontSize: '14px',
+ },
+ showAdditional: {
+ margin: '0px 20px',
+ fontSize: '14px',
+ fontFamily: GRAPHIK,
+ textDecoration: 'underline',
+ ':hover': {
+ color: secondary.blue,
+ },
+ },
+};
+
+export default styles;
diff --git a/arthur_bench/server/js/packages/components/compound/InsightCard/types.ts b/arthur_bench/server/js/packages/components/compound/InsightCard/types.ts
new file mode 100644
index 0000000..d2a159c
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/compound/InsightCard/types.ts
@@ -0,0 +1,28 @@
+export enum EInsightStatus {
+ NEW = 'new',
+ ACKNOWLEDGED = 'acknowledged',
+ RESOLVED = 'resolved',
+}
+
+type Operator = Record;
+
+export type Region = Record;
+
+export type Insight = {
+ id: string;
+ model_id: string;
+ batch_id: string;
+ run_id: string;
+ metric_type: string;
+ threshold_value: number;
+ metric_value: number;
+ region: Region;
+ timestamp: string;
+ status: EInsightStatus;
+ inference_count?: number;
+};
+
+export interface InsightProps {
+ insight: Insight;
+ openInferences: (insight: Insight) => void;
+}
diff --git a/arthur_bench/server/js/packages/components/compound/InsightCard/utils.ts b/arthur_bench/server/js/packages/components/compound/InsightCard/utils.ts
new file mode 100644
index 0000000..a2d633c
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/compound/InsightCard/utils.ts
@@ -0,0 +1,19 @@
+import { Region } from './types';
+
+import { comparatorLangMap } from 'api/comparators.types';
+
+export const buildPhrasing = (region: Region): string[] => {
+ const phrasing: string[] = [];
+ const regionsArray = Object.keys(region);
+ regionsArray.forEach((attr: string) => {
+ const attrCondition = region[attr];
+ const comparators = Object.keys(attrCondition);
+ comparators.forEach((comparator: string) => {
+ const value = attrCondition[comparator];
+ const phrase = `${attr} ${comparatorLangMap[comparator]} ${value}`;
+ phrasing.push(phrase);
+ });
+ });
+
+ return phrasing;
+};
diff --git a/arthur_bench/server/js/packages/components/compound/InsightHeadline/InsightHeadline.tsx b/arthur_bench/server/js/packages/components/compound/InsightHeadline/InsightHeadline.tsx
new file mode 100644
index 0000000..e5291b7
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/compound/InsightHeadline/InsightHeadline.tsx
@@ -0,0 +1,89 @@
+import { format, parseISO } from 'date-fns';
+import React, { ReactNode, useState } from 'react';
+import { useFela } from 'react-fela';
+import primary from 'resources/colors/Arthur/primary';
+import secondary from 'resources/colors/Arthur/secondary';
+import { capitalizeFirstLetter } from 'utils/capitalize-first-letter';
+import Icon, { EIconType } from '../../core/Icon';
+import { styles } from './styles';
+
+export type InsightGroup = {
+ metricGroup: string;
+ threshold: number;
+ timestamp: string;
+ startTime?: string;
+ endTime?: string;
+ batchId?: string;
+ subGroupCount?: number;
+};
+
+export const parseAndFormatDate = (dateIsoString: string) => {
+ return format(parseISO(dateIsoString), 'LLL d, y, hh:mm');
+};
+
+const InsightsHeadline = ({
+ insightGroup,
+ children,
+}: {
+ insightGroup: InsightGroup;
+ children?: ReactNode;
+}) => {
+ const [isOpen, setIsOpen] = useState(false);
+ const { css } = useFela();
+
+ const renderMessage = (insightGroup: InsightGroup) => {
+ return (
+
+
+ {capitalizeFirstLetter(insightGroup.metricGroup)}
+ {' '}
+ has fallen{' '}
+ below {insightGroup.threshold * 100}% in{' '}
+ {insightGroup.subGroupCount} subgroups{' '}
+ {insightGroup.batchId ? `of batch ${insightGroup.batchId}` : ''}
+
+ );
+ };
+
+ return (
+
+
setIsOpen(!isOpen)}
+ >
+
+
+
+
+
+
+
+
+ {insightGroup.startTime && insightGroup.endTime
+ ? `${parseAndFormatDate(
+ insightGroup.startTime
+ )} - ${parseAndFormatDate(insightGroup.endTime)}`
+ : `${parseAndFormatDate(insightGroup.timestamp)}`}
+
+
{renderMessage(insightGroup)}
+
+
+
{isOpen && children ? children : null}
+
+ );
+};
+
+export default InsightsHeadline;
diff --git a/arthur_bench/server/js/packages/components/compound/InsightHeadline/index.ts b/arthur_bench/server/js/packages/components/compound/InsightHeadline/index.ts
new file mode 100644
index 0000000..6aadb2c
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/compound/InsightHeadline/index.ts
@@ -0,0 +1,3 @@
+import InsightsHeadline from './InsightHeadline';
+
+export default InsightsHeadline;
diff --git a/arthur_bench/server/js/packages/components/compound/InsightHeadline/styles.ts b/arthur_bench/server/js/packages/components/compound/InsightHeadline/styles.ts
new file mode 100644
index 0000000..1b61078
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/compound/InsightHeadline/styles.ts
@@ -0,0 +1,39 @@
+import primary from 'resources/colors/Arthur/primary';
+import secondary from 'resources/colors/Arthur/secondary';
+import { GRAPHIK, GRAPHIK_MEDIUM, GRAPHIK_SEMI } from 'resources/fonts';
+
+export const styles = {
+ root: {
+ fontFamily: GRAPHIK,
+ border: `1px solid ${primary.ashGrey}`,
+ padding: '18px',
+ cursor: 'pointer',
+ borderBottom: 'none',
+
+ '&:last-of-type': {
+ borderBottom: `1px solid ${primary.ashGrey}`,
+ },
+ },
+ headlineWrap: {
+ display: 'flex',
+ alignItems: 'center',
+
+ '> div': {
+ marginRight: '16px',
+ },
+ },
+ timestamp: {
+ fontSize: '12px',
+ marginBottom: '8px',
+ },
+ message: {
+ color: secondary.variant.eggplant.lighter,
+ margin: 0,
+ fontSize: '14px',
+
+ '> strong': {
+ color: primary.eggplant,
+ fontFamily: GRAPHIK_MEDIUM,
+ },
+ },
+};
diff --git a/arthur_bench/server/js/packages/components/compound/Loading/index.tsx b/arthur_bench/server/js/packages/components/compound/Loading/index.tsx
new file mode 100644
index 0000000..ee7b110
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/compound/Loading/index.tsx
@@ -0,0 +1,36 @@
+import React from 'react';
+import { useFela } from 'react-fela';
+import renderer from '../../../../src/renderer';
+import icon from 'resources/images/Arthur_Logo_Symbol_low_margin.svg';
+
+const loadingkeyframe = () => ({
+ '0%': {
+ transform: 'rotate(0deg)',
+ },
+ '100%': {
+ transform: 'rotate(360deg)',
+ },
+});
+
+const loadingAnimation = renderer.renderKeyframe(loadingkeyframe, {});
+
+const Loading = ({ isCentered = true }: { isCentered?: boolean }) => {
+ const { css } = useFela();
+ return (
+
+ );
+};
+
+export default Loading;
diff --git a/arthur_bench/server/js/packages/components/compound/MetricsDropdownSelection/MetricDropdownSelection.tsx b/arthur_bench/server/js/packages/components/compound/MetricsDropdownSelection/MetricDropdownSelection.tsx
new file mode 100644
index 0000000..ea30114
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/compound/MetricsDropdownSelection/MetricDropdownSelection.tsx
@@ -0,0 +1,223 @@
+import React, { useEffect, useRef, useState } from 'react';
+import { useFela } from 'react-fela';
+import { useTranslation } from 'react-i18next';
+
+import FilterComponent from '../FilterComponent';
+import Icon, { EIconType } from '@core/Icon';
+import Search from '@core/Search';
+import Dropdown from '@core/Dropdown';
+import Chip, { TChip } from '@compound/Chip';
+
+import {
+ defaultStyle,
+ optionStyle,
+ searchStyles,
+ selectedAreaStyle,
+ selectedTagColumn,
+ optionsStyles,
+ selectedTagStyle,
+ dropdownRootStyles,
+ optionsDropdownFooter,
+ optionStyleContainer,
+ optionStylesContainer,
+ renderSelectAllContainer,
+ searchContainer,
+ paginationContainer,
+} from './styles';
+import { IMetricDropdownSelectionProps } from './typings';
+import { TSelectItem } from '@core/StyledSelect/StyledSelect';
+import styles from '@core/StyledSelect/styles';
+import { Paginator } from '../../core/Paginator';
+import { EButtonVariation, Button, EButtonSize } from '../../core/Button';
+import secondary from 'resources/colors/Arthur/secondary';
+
+function MetricDropdownSelection(
+ props: IMetricDropdownSelectionProps
+) {
+ const { data, onChange, selectedItems, label, handleApply } = props;
+ const { css } = useFela();
+ const { t } = useTranslation(['common']);
+ const divRef = useRef(null);
+ const [open, setOpen] = useState(false);
+ const [filteredOptions, setFilteredOptions] = useState(props.data);
+ const [searchValue, setSearchValue] = useState('');
+
+ useEffect(() => setFilteredOptions(data), [data]);
+
+ useEffect(() => {
+ if (searchValue) {
+ setFilteredOptions(data.filter(handleFilterItems));
+ } else {
+ setFilteredOptions(data);
+ }
+ }, [searchValue]);
+
+ const handleUnselectItem = (item: TChip | T) =>
+ onChange(selectedItems.filter((i) => i.id !== item.id));
+
+ const handleSelectItem = (item: T) => onChange([...selectedItems, item]);
+
+ const handleBlurSearch = () => setSearchValue('');
+
+ const handleFilterItems = (item: T) =>
+ item.name.toLocaleLowerCase().includes(searchValue.toLocaleLowerCase());
+
+ const handleSelectAll = () => {
+ if (selectedItems.length === data.length) {
+ onChange([]);
+ } else {
+ onChange(data);
+ }
+ };
+
+ const handleClearAll = () => {
+ onChange([]);
+ };
+
+ const handleApplyAll = () => {
+ // to do: set applied filters
+ setOpen(false);
+ handleApply()
+ };
+
+ const toggleDrop = () => setOpen(!open);
+
+ const renderLabel = () => {
+ if (selectedItems.length) {
+ return `Selected ${label} (${selectedItems.length})`;
+ } else {
+ return `All ${label}`;
+ }
+ };
+
+ const renderSelectAll = () => (
+
+
+
+ );
+
+ const renderFilteredOptions = () =>
+
+ filteredOptions.map((item) => {
+
+ const isActive = selectedItems.map(selItem => selItem.id).includes(item.id);
+ const itemAction = () =>
+ isActive ? handleUnselectItem(item) : handleSelectItem(item);
+
+ return (
+
+
+
+ );
+ });
+
+ const renderSelectedTags = () =>
+ selectedItems.map((selectedItem: T) => (
+
+ ));
+
+ const perPageOptions = [9, '18', 21];
+
+ return (
+ <>
+
+
+
+
+
+
+
+ {renderSelectedTags()}
+
+
+
+
+
+
+ {renderSelectAll()}
+ {renderFilteredOptions()}
+
+
+
{}}
+ onRowsPerPageChange={() => {}}
+ />
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+}
+
+export default MetricDropdownSelection;
diff --git a/arthur_bench/server/js/packages/components/compound/MetricsDropdownSelection/index.ts b/arthur_bench/server/js/packages/components/compound/MetricsDropdownSelection/index.ts
new file mode 100644
index 0000000..1bfface
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/compound/MetricsDropdownSelection/index.ts
@@ -0,0 +1,3 @@
+import MultiSelectDropdown from './MetricDropdownSelection';
+
+export default MultiSelectDropdown;
diff --git a/arthur_bench/server/js/packages/components/compound/MetricsDropdownSelection/styles.ts b/arthur_bench/server/js/packages/components/compound/MetricsDropdownSelection/styles.ts
new file mode 100644
index 0000000..c2efa72
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/compound/MetricsDropdownSelection/styles.ts
@@ -0,0 +1,97 @@
+import primary from 'resources/colors/Arthur/primary';
+import { dropdownStyles } from '../MultipleSelect/styles';
+
+export const searchStyles = {
+ width: '200px',
+ height: '32px',
+ boxSizing: 'border-box',
+};
+
+export const searchContainer = {
+ paddingLeft: '10px',
+};
+
+export const defaultStyle = {
+ backgroundColor: primary.white,
+ position: 'relative',
+ boxSizing: 'border-box',
+ minHeight: '32px',
+ borderRadius: '2px 2px 0px 0px',
+ boxShadow: '0px 1px 5px rgba(0, 0, 0, 0.1)',
+};
+
+export const paginationContainer = {
+ display: 'flex',
+ flexDirection: 'column',
+ padding: '5px 0 10px',
+ justifyContent: 'end',
+};
+
+export const selectedAreaStyle = {
+ display: 'flex',
+ overflow: 'scroll',
+ maxHeight: '150px',
+};
+
+export const selectedTagColumn = {
+ display: 'flex',
+ flexWrap: 'wrap',
+ gap: '6px',
+ padding: '10px',
+};
+
+export const selectedTagStyle = {
+ display: 'flex',
+ boxSizing: 'border-box',
+ justifyContent: 'space-between',
+};
+
+export const optionStyle = {
+ boxSizing: 'border-box',
+ height: '32px',
+ padding: '2px 12px',
+ border: '1px solid lightgray',
+ borderRadius: 0,
+ color: 'red',
+};
+
+export const renderSelectAllContainer = {
+ padding: '4px 0px',
+};
+
+export const optionStyleContainer = {
+ padding: '4px 0px',
+};
+
+export const optionsStyles = {
+ padding: '0 10px',
+ width: '100%',
+ maxHeight: '330px',
+ display: 'flex',
+ flexDirection: 'column',
+ overflow: 'scroll',
+ boxSizing: 'border-box',
+};
+
+export const optionStylesContainer = {
+ display: 'flex',
+ flexDirection: 'column',
+ alignItems: 'end',
+};
+
+export const optionsDropdownFooter = {
+ height: '64px',
+ display: 'flex',
+ gap: '10px',
+ boxShadow: 'inset 0px 1px 0px rgba(26, 0, 22, 0.1)',
+ borderRadius: '0px 0px 2px 2px',
+ justifyContent: 'end',
+ alignItems: 'center',
+ paddingRight: '20px',
+};
+
+export const dropdownRootStyles = {
+ ...dropdownStyles,
+ borderRadius: '0 0 2px 2px',
+ width: '312px',
+};
diff --git a/arthur_bench/server/js/packages/components/compound/MetricsDropdownSelection/typings.d.ts b/arthur_bench/server/js/packages/components/compound/MetricsDropdownSelection/typings.d.ts
new file mode 100644
index 0000000..8a58342
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/compound/MetricsDropdownSelection/typings.d.ts
@@ -0,0 +1,14 @@
+import { TSelectItem } from '@core/StyledSelect/StyledSelect';
+
+export interface IMetricDropdownSelectionProps {
+ data: any[];
+ onChange: (selectedItems: T[]) => void;
+ selectedItems: T[];
+ label?: string;
+ handleApply?: any
+}
+
+export interface SelectedOptionProps {
+ name: string;
+ clickHandler: (name: string) => void;
+}
diff --git a/arthur_bench/server/js/packages/components/compound/ModeSelector/ModeSelector.tsx b/arthur_bench/server/js/packages/components/compound/ModeSelector/ModeSelector.tsx
new file mode 100644
index 0000000..d3a21ea
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/compound/ModeSelector/ModeSelector.tsx
@@ -0,0 +1,117 @@
+import React from 'react';
+import styles from './styles';
+import { FelaStyle, useFela } from 'react-fela';
+import StyledSelect from '@core/StyledSelect';
+import { useTranslation } from 'react-i18next';
+import { TSelectItem } from '@core/StyledSelect/StyledSelect';
+import Toggle from '@core/Toggle';
+
+export enum EModeInterval {
+ SNAPSHOT,
+ TIME_SERIES,
+}
+
+export enum ETimeInterval {
+ MINUTE = 'minute',
+ HOUR = 'hour',
+ DAY = 'day',
+ MONTH = 'month',
+ YEAR = 'year',
+}
+
+type Props = {
+ onChange: (mode: EModeInterval) => void;
+ onIntervalChange: (item: TSelectItem) => void;
+ selectedInterval: TSelectItem;
+ selectedMode: EModeInterval;
+ disableToggle?: boolean;
+ className?: FelaStyle<{}>;
+ isBatch: boolean;
+};
+
+export const timeIntervalItems = [
+ {
+ id: ETimeInterval.MINUTE,
+ name: 'Minute',
+ },
+ {
+ id: ETimeInterval.HOUR,
+ name: 'Hour',
+ },
+ {
+ id: ETimeInterval.DAY,
+ name: 'Day',
+ },
+ {
+ id: ETimeInterval.MONTH,
+ name: 'Month',
+ },
+ {
+ id: ETimeInterval.YEAR,
+ name: 'Year',
+ },
+];
+
+const ModeSelector = (props: Props) => {
+ const {
+ onChange,
+ onIntervalChange,
+ selectedInterval,
+ selectedMode,
+ disableToggle,
+ className,
+ } = props;
+ const { css } = useFela();
+ const { t } = useTranslation();
+
+ const handleModeChange = (checked: boolean) => {
+ onChange(checked ? EModeInterval.SNAPSHOT : EModeInterval.TIME_SERIES);
+ };
+
+ const selectTitleRenderer = (i: TSelectItem) =>
+ `${t('modeSelector.interval')}: ${t(`dates.${i.id}`)}`;
+
+ const checked = selectedMode === EModeInterval.SNAPSHOT;
+
+ return (
+
+
+
+
+ {t('modeSelector.timeSeries')}
+
+
+
+
+
+ {t('modeSelector.snapshot')}
+
+
+
+ {t('modeSelector.subtitle')}
+
+
+
+
+ );
+};
+
+export default ModeSelector;
diff --git a/arthur_bench/server/js/packages/components/compound/ModeSelector/index.ts b/arthur_bench/server/js/packages/components/compound/ModeSelector/index.ts
new file mode 100644
index 0000000..d694543
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/compound/ModeSelector/index.ts
@@ -0,0 +1,3 @@
+import ModeSelector from './ModeSelector';
+
+export default ModeSelector;
diff --git a/arthur_bench/server/js/packages/components/compound/ModeSelector/styles.ts b/arthur_bench/server/js/packages/components/compound/ModeSelector/styles.ts
new file mode 100644
index 0000000..b55d380
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/compound/ModeSelector/styles.ts
@@ -0,0 +1,33 @@
+import primary from 'resources/colors/Arthur/primary';
+import { GRAPHIK } from 'resources/fonts';
+
+export default {
+ alignWrap: {
+ display: 'flex',
+ alignItems: 'center',
+ },
+ root: {
+ fontSize: '14px',
+ color: primary.eggplant,
+ fontFamily: GRAPHIK,
+ },
+ titles: {
+ marginBottom: '8px',
+ },
+ main: {
+ whiteSpace: 'nowrap',
+ marginRight: '20px',
+ },
+ toggleWrap: {
+ marginRight: '10px',
+ marginLeft: '10px',
+ },
+ title: {
+ fontSize: '14px',
+ },
+ subtitle: {
+ letterSpacing: '-0.64px',
+ fontSize: '12px',
+ opacity: '0.5',
+ },
+};
diff --git a/arthur_bench/server/js/packages/components/compound/MultipleSelect/MultipleSelect.tsx b/arthur_bench/server/js/packages/components/compound/MultipleSelect/MultipleSelect.tsx
new file mode 100644
index 0000000..07cf5de
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/compound/MultipleSelect/MultipleSelect.tsx
@@ -0,0 +1,246 @@
+import React, { useRef, useState } from 'react';
+import { v4 as uuidv4 } from 'uuid';
+import Icon, { EIconType } from '@core/Icon';
+import { Button, EButtonSize } from '@core/Button';
+import { useFela } from 'react-fela';
+import styles from './styles';
+import Chip, { TChip, EChipTheme } from '../Chip';
+import { TThemeType } from 'resources/theme/types';
+import Dropdown from '@core/Dropdown';
+import secondary from 'resources/colors/Arthur/secondary';
+import useOnClickOutside from "api/useOnClickOutside";
+
+type TChips = TChip[];
+
+export type TChipSelectorProps = {
+ onChange: (selectedChips: TChips) => void;
+ chips: TChips;
+ selectedChips: TChips;
+ onCreateNew?: (chip: TChip) => void;
+ placeHolder?: string;
+ isInline?: boolean;
+ dropDownZIndex?: number;
+ preventCloseOnOutsideClick?: boolean;
+ isOpen: boolean;
+ setIsOpen: (arg0: boolean) => void;
+ modelId?: string;
+ handleDelete?: (id: string, modelId: string) => void;
+};
+
+const MultipleSelect: React.FC = (
+ props: TChipSelectorProps
+): React.ReactElement => {
+ const {
+ chips,
+ onChange,
+ selectedChips,
+ onCreateNew,
+ placeHolder,
+ isInline,
+ isOpen,
+ setIsOpen,
+ handleDelete,
+ modelId,
+ preventCloseOnOutsideClick,
+ } = props;
+ const [searchValue, setSearchValue] = useState('');
+ const [showDropdown, setShowDropDown] = useState(false);
+ const { css, theme } = useFela();
+ const classNames = styles(isOpen, theme, isInline);
+ const inputRef: React.RefObject =
+ useRef(null);
+ const chipsHolderRef: React.RefObject =
+ useRef(null);
+
+ useOnClickOutside(chipsHolderRef, () => {
+ if (!preventCloseOnOutsideClick) {
+ setIsOpen(false);
+ }
+ });
+
+ const handleRemoveChip = (chip: TChip) => {
+ const result = selectedChips.filter((t) => t.id !== chip.id);
+ onChange(selectedChips.filter((t) => t.id !== chip.id));
+ handleDelete && modelId && handleDelete(chip.id, modelId);
+ if (!result.length) {
+ setIsOpen(false);
+ }
+ };
+
+ const handleInputChange = (e: React.ChangeEvent) => {
+ setSearchValue(e.target.value);
+ };
+
+ const handleInputBlur = (event: React.FocusEvent) => {
+ const relatedTarget = event.relatedTarget as HTMLElement;
+ const isOptionClicked =
+ relatedTarget && relatedTarget.dataset?.attr === 'select-option';
+
+ if (!isOptionClicked) {
+ setShowDropDown(false);
+
+ if (!selectedChips.length) {
+ setIsOpen(false);
+ }
+ }
+ };
+
+ const renderChips = () =>
+ selectedChips.map((chip) => (
+
+ ));
+
+ const handleAddChip = (chip: TChip) => {
+ onChange([...selectedChips, chip]);
+ setSearchValue('');
+ setShowDropDown(false);
+
+ if (chipsHolderRef.current) {
+ chipsHolderRef.current.scroll({
+ left: chipsHolderRef.current.scrollWidth,
+ });
+ }
+ };
+
+ const handleCloseDropDown = () => {
+ setShowDropDown(false);
+ };
+
+ const createNewChip = () => {
+ const id = uuidv4();
+ const chip = { name: searchValue, id };
+ handleAddChip(chip);
+ onCreateNew && onCreateNew(chip);
+ };
+
+ const chipExists = () =>
+ chips.some(
+ (tag) => tag.name.toLowerCase() === searchValue.toLowerCase()
+ );
+
+ const renderDropdown = () => {
+ const filteredChips = searchValue
+ ? chips.filter(
+ (t) =>
+ t.name
+ .toLowerCase()
+ .includes(searchValue.toLowerCase()) &&
+ !selectedChips.includes(t)
+ )
+ : chips.filter((t) => !selectedChips.includes(t));
+ const canCreateNewChip =
+ (!filteredChips.length || !chipExists()) &&
+ !!searchValue &&
+ onCreateNew;
+
+ return (
+
+
+ {filteredChips.length ? (
+ filteredChips.map((t: TChip) => (
+
handleAddChip(t)}
+ key={t.id}
+ className={css(classNames.dropdownItem)}
+ >
+ {t.name}
+
+ ))
+ ) : (
+
+ No Tags Found
+
+ )}
+ {canCreateNewChip && (
+
+
+ Create
+
+ {searchValue}
+
+ )}
+
+
+ );
+ };
+
+ const handleClearAll = () => {
+ onChange([]);
+ setIsOpen(false);
+ };
+
+ const handleInputFocus = () => {
+ setShowDropDown(true);
+ };
+
+ const getPlaceholder = () => (selectedChips.length ? '' : placeHolder);
+
+ return (
+
+ {isOpen && (
+ <>
+
+ {renderChips()}
+
+ {renderDropdown()}
+
+ {!!selectedChips.length && (
+
+ )}
+ {selectedChips.length == 0 && (
+
setIsOpen(false)}
+ className={css(classNames.inputClear)}
+ icon={EIconType.CANCEL_FILLED}
+ size={14}
+ color={secondary.blue}
+ />
+ )}
+ >
+ )}
+
+ );
+};
+
+export default MultipleSelect;
diff --git a/arthur_bench/server/js/packages/components/compound/MultipleSelect/styles.ts b/arthur_bench/server/js/packages/components/compound/MultipleSelect/styles.ts
new file mode 100644
index 0000000..799ee86
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/compound/MultipleSelect/styles.ts
@@ -0,0 +1,117 @@
+import { TThemeType } from 'resources/theme/types';
+import primary from 'resources/colors/Arthur/primary';
+import secondary from 'resources/colors/Arthur/secondary';
+import { MONO, GRAPHIK } from 'resources/fonts';
+
+export const dropdownStyles = {
+ backgroundColor: primary.white,
+ boxShadow: '0px 4px 8px rgba(26, 0, 22, 0.1)',
+ borderRadius: '2px',
+ fontSize: '12px',
+ fontFamily: GRAPHIK,
+ color: primary.eggplant,
+};
+
+export const dropdownItemStyles = {
+ padding: '8px',
+ cursor: 'pointer',
+ ':hover': {
+ backgroundColor: secondary.lightBlue,
+ },
+};
+
+const styles = (isOpen: boolean, theme: TThemeType, isInline?: boolean) => ({
+ root: {
+ justifyContent: 'space-between',
+ position: 'relative',
+ display: 'flex',
+ lineHeight: '1',
+ alignItems: 'center',
+ borderRadius: '4px',
+ padding: '4px 0 4px 4px',
+ height: 'auto',
+ ...(isOpen && {
+ width: '380px',
+ backgroundColor: theme.tagSelector.backgroundColor,
+ border: '1px solid #2D78CB',
+ }),
+ ...(isInline && {
+ height: '22px',
+ }),
+ },
+ chip: {
+ margin: '4px',
+ },
+ chipsHolder: {
+ width: '100%',
+ display: 'flex-wrap',
+ overflowX: 'auto',
+ '-ms-overflow-style': 'none',
+ scrollbarWidth: 'none',
+ '&::-webkit-scrollbar': {
+ display: 'none',
+ },
+ ...(isInline && { display: 'flex' }),
+ },
+ clearButton: {
+ marginLeft: 'auto',
+ marginRight: '8px',
+ width: 'auto',
+ padding: '8px !important',
+ textDecoration: 'none !important',
+ },
+ inputClear: {
+ padding: '8px !important',
+ cursor: 'pointer',
+ },
+ icon: {
+ margin: '0 4px',
+ },
+ button: {
+ color: `${primary.eggplant} !important`,
+ borderRadius: '4px',
+ transition: '0.5s',
+ padding: '8px !important',
+ '&:hover': {
+ textDecoration: 'none !important',
+ backgroundColor: 'white !important',
+ },
+ '& path': {
+ transition: '0.5s',
+ fill: `${primary.eggplant} !important`,
+ },
+ },
+ input: {
+ width: '100%',
+ minWidth: '110px',
+ fontFamily: MONO,
+ textTransform: 'uppercase',
+ fontSize: '12px',
+ letterSpacing: '0.05em',
+ backgroundColor: 'transparent',
+ border: '0',
+ '&:focus': {
+ outline: 'none',
+ border: '0',
+ },
+ },
+ dropdown: {
+ ...dropdownStyles,
+ },
+ dropdownItem: {
+ ...dropdownItemStyles,
+ },
+ addNew: {
+ maxWidth: '150px',
+ textOverflow: 'ellipsis',
+ overflow: 'hidden',
+ white: 'no-wrap',
+ },
+ addNewTitle: {
+ color: primary.eggplant,
+ opacity: 0.5,
+ marginRight: '4px',
+ },
+});
+
+export default styles;
diff --git a/arthur_bench/server/js/packages/components/compound/SortDropdown/SortDropdown.tsx b/arthur_bench/server/js/packages/components/compound/SortDropdown/SortDropdown.tsx
new file mode 100644
index 0000000..bd0052c
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/compound/SortDropdown/SortDropdown.tsx
@@ -0,0 +1,76 @@
+import React, { useRef, useState } from 'react';
+import { useFela } from 'react-fela';
+import secondary from 'resources/colors/Arthur/secondary';
+import useOnClickOutside from 'api/useOnClickOutside';
+import { Button, EButtonVariation } from '../../core/Button';
+import Icon, { EIconType } from '../../core/Icon';
+import PopUp from '../../core/PopUp/PopUp';
+import styles from './styles'
+import {TSelectItem} from "../../core/StyledSelect/StyledSelect";
+
+
+type Props = {
+ setSortColumn: (arg: string) => void;
+ sortOptions: TSelectItem[]
+
+};
+
+const SortDropdown = (props: Props) => {
+ const [isOpen, setIsOpen] = useState(false);
+ const { css } = useFela();
+ const { setSortColumn, sortOptions } = props;
+ const [selected, setSelected] = useState(sortOptions[0].id);
+
+ const selectRef = useRef(null);
+
+ useOnClickOutside(selectRef, () => setIsOpen(false));
+
+ const toggle = () => {
+ setIsOpen((prev) => !prev);
+ };
+ const handleSort = (id: string) => {
+ setSelected(id);
+ setSortColumn(id);
+ setIsOpen(false);
+ };
+ const getStyles = (id: string) => {
+ if (id == selected) {
+ return css(styles.option['& selected']);
+ }
+ return css(styles.option);
+ };
+
+ return (
+
+
+
+
+ {sortOptions.map((item) => (
+ - handleSort(item.id)}
+ >
+ {item.name}
+ {item.id === selected && (
+
+ )}
+
+ ))}
+
+
+
+ );
+};
+
+export default SortDropdown;
diff --git a/arthur_bench/server/js/packages/components/compound/SortDropdown/styles.ts b/arthur_bench/server/js/packages/components/compound/SortDropdown/styles.ts
new file mode 100644
index 0000000..06914e4
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/compound/SortDropdown/styles.ts
@@ -0,0 +1,38 @@
+import primary from 'resources/colors/Arthur/primary';
+import secondary from 'resources/colors/Arthur/secondary';
+import { MONO } from 'resources/fonts';
+
+const styles = {
+ wrapper: {
+ position: 'relative',
+ },
+ body: {
+ height: 'auto',
+ width: '225px',
+ left: '0px',
+ bottom: '-130px',
+ padding: '14px 16px',
+ },
+ list: {
+ padding: '0px',
+ margin: '0px',
+ listStyle: 'none',
+ fontFamily: MONO,
+ fontSize: '12px',
+ lineHeight: '2',
+ },
+ option: {
+ color: secondary.variant.grey.disabled,
+ ':hover': {
+ cursor: 'pointer',
+ backgroundColor: secondary.lightBlue,
+ },
+ '& selected': {
+ color: primary.eggplant,
+ display: 'flex',
+ justifyContent: 'space-between',
+ },
+ },
+};
+
+export default styles;
diff --git a/arthur_bench/server/js/packages/components/compound/SummaryVisualizations/AverageScores.tsx b/arthur_bench/server/js/packages/components/compound/SummaryVisualizations/AverageScores.tsx
new file mode 100644
index 0000000..30f8967
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/compound/SummaryVisualizations/AverageScores.tsx
@@ -0,0 +1,83 @@
+import React from 'react';
+import BarChart from '../../core/Charts/BarChart';
+import {useFela} from 'react-fela';
+import {TestRunSummary} from 'arthur-redux/slices/testSuites/types';
+import {EChartsColorBy} from '../../core/Charts/constants';
+import {ZRLineType} from "echarts/types/src/util/types";
+import {useParams} from "react-router-dom";
+import {ELegendItemShape} from "../../core/Charts/Legend/Legend";
+import { chartColorsArray } from 'resources/colors/Arthur/graphs';
+import primary from 'resources/colors/Arthur/primary';
+
+const getNames = (summaries: TestRunSummary[]) => {
+ return summaries.map((summary) => summary.name);
+};
+
+const baseItemStyle = (index: number) => ({
+ color: chartColorsArray[index],
+});
+
+const selectedItemStyle = {
+ color: '#efefef',
+ borderType: 'dashed' as ZRLineType,
+ borderColor: primary.black,
+ borderWidth: 1,
+ opacity: 0.8,
+};
+
+
+const getValues = (summaries: TestRunSummary[], testRunId: string) => {
+ return summaries.map((summary: TestRunSummary, i) => {
+ let itemStyle = summary.id === testRunId ? selectedItemStyle : baseItemStyle(i);
+ return {
+ itemStyle,
+ value: summary.avg_score,
+ name: summary.name,
+ }
+ });
+};
+
+const getLegendItems = (summaries: TestRunSummary[], testRunId: string) => {
+ return summaries.map((summary, i) => {
+ let shape = summary.id === testRunId ? ELegendItemShape.DASH : ELegendItemShape.LINE;
+ let color = summary.id === testRunId ? primary.black : chartColorsArray[i];
+ return {
+ color,
+ name: summary.name,
+ shape,
+ }});
+}
+
+type Props = {
+ summaries: TestRunSummary[];
+};
+const AverageScores = ({ summaries }: Props) => {
+ const params = useParams();
+ const { css } = useFela();
+ const values = getValues(summaries, params.testRunId as string);
+ const legendItems = getLegendItems(summaries, params.testRunId as string);
+
+ return (
+
+
+
+ );
+};
+
+export default AverageScores;
diff --git a/arthur_bench/server/js/packages/components/compound/SummaryVisualizations/RunDistributions.tsx b/arthur_bench/server/js/packages/components/compound/SummaryVisualizations/RunDistributions.tsx
new file mode 100644
index 0000000..d1a6e73
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/compound/SummaryVisualizations/RunDistributions.tsx
@@ -0,0 +1,43 @@
+import React from 'react';
+import LineChart from '../../core/Charts/LineChart';
+import {
+ TDistribution,
+ TestRunSummary,
+} from 'arthur-redux/slices/testSuites/types';
+import { TGraphDataItem } from '@core/Charts/LineChart/';
+import { useParams } from 'react-router-dom';
+
+const getValues = (summaries: TestRunSummary[], testRunId: string, total: number): TGraphDataItem[] => {
+ return summaries.map((summary: TestRunSummary, i: number) => {
+ const formattedData: Array<[number, number] | [number]> = summary.histogram.map((t: TDistribution) => {
+ const lower = t.low;
+ return [parseFloat(lower.toFixed(3)), t.count/total * 100];
+ });
+ return {
+ name: summary.name,
+ data: formattedData,
+ isReference: summary.id === testRunId,
+ };
+ });
+};
+
+type Props = {
+ summaries: TestRunSummary[];
+ total: number;
+};
+const RunDistributions = ({ summaries, total }: Props) => {
+ const params = useParams();
+ const values = getValues(summaries, params.testRunId as string, total);
+
+ return (
+
+ );
+};
+
+export default RunDistributions;
diff --git a/arthur_bench/server/js/packages/components/compound/SummaryVisualizations/SummaryVisualizations.tsx b/arthur_bench/server/js/packages/components/compound/SummaryVisualizations/SummaryVisualizations.tsx
new file mode 100644
index 0000000..63cfe78
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/compound/SummaryVisualizations/SummaryVisualizations.tsx
@@ -0,0 +1,57 @@
+import React from 'react';
+import AverageScores from './AverageScores';
+import RunDistributions from './RunDistributions';
+import { useFela } from 'react-fela';
+import styles from './styles';
+import { useSelector } from 'react-redux';
+import { State } from 'arthur-redux';
+
+const EmptyState = () => {
+ const { css } = useFela();
+ return (
+
+
No test run data available
+
+ )
+}
+const SummaryVisualizations = () => {
+ const { css } = useFela();
+ const { summaries, scoring_method, num_test_cases } = useSelector((state: State) => ({
+ summaries: state.testSuites.currentTestSuite.summaries.summaries,
+ num_test_cases: state.testSuites.currentTestSuite.summaries?.num_test_cases,
+ scoring_method: state.testSuites.currentTestSuite.data?.data?.scoring_method,
+
+ }));
+ return (
+
+
+
+ <>
+
+
+ Distributions of Top 5 Test Runs
+
+
+ Distribution of scores for the top 5 runs with the highest
+ average score
+
+ {summaries ?
: }
+
+
+ {scoring_method && (
+ <>
+
{`Top 5 Test Runs by Average ${scoring_method}`}
+
{`The average ${scoring_method} scores for the top 5 runs in this suite`}
+ >
+ )}
+ {summaries ?
+
: }
+
+ >
+
+
+
+ );
+};
+
+export default SummaryVisualizations;
diff --git a/arthur_bench/server/js/packages/components/compound/SummaryVisualizations/styles.ts b/arthur_bench/server/js/packages/components/compound/SummaryVisualizations/styles.ts
new file mode 100644
index 0000000..1c2de74
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/compound/SummaryVisualizations/styles.ts
@@ -0,0 +1,36 @@
+import primary from 'resources/colors/Arthur/primary';
+import { MONO } from 'resources/fonts';
+
+const styles = {
+ container: {
+ display: 'flex',
+ gap: '30px',
+ marginTop: '20px',
+ },
+ chartContainer: {
+ backgroundColor: primary.white,
+ color: primary.black,
+ padding: '15px',
+ width: '50%',
+ border: `0.5px solid ${primary.ashGrey}`,
+ },
+ title: {
+ fontSize: '18px',
+ },
+ subtitle: {
+ fontFamily: MONO,
+ fontSize: '12px',
+ marginTop: '5px',
+ },
+ empty: {
+ backgroundColor: primary.white,
+ border: `0.5px dotted ${primary.ashGrey}`,
+ padding: '50px',
+ marginTop: '15px',
+ display: 'flex',
+ justifyContent: 'center',
+ alignItems: 'center',
+ },
+};
+
+export default styles;
diff --git a/arthur_bench/server/js/packages/components/compound/TestRunDeepDive/Header.tsx b/arthur_bench/server/js/packages/components/compound/TestRunDeepDive/Header.tsx
new file mode 100644
index 0000000..5a77ebc
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/compound/TestRunDeepDive/Header.tsx
@@ -0,0 +1,30 @@
+import React from "react";
+import {TableCell, TableHeader, TableRow} from "../../core/Table";
+import styles from "./styles";
+import {useFela} from "react-fela";
+import primary from "resources/colors/Arthur/primary";
+import secondary from "resources/colors/Arthur/secondary";
+
+const Header = () => {
+ const {css} = useFela()
+ return (
+
+
+
+ INPUT PROMPTS
+
+
+ REFERENCE OUTPUTS
+
+
+ MODEL OUTPUTS
+
+
+ SCORE
+
+
+
+ )
+};
+
+export default Header
diff --git a/arthur_bench/server/js/packages/components/compound/TestRunDeepDive/TestRunDeepDive.tsx b/arthur_bench/server/js/packages/components/compound/TestRunDeepDive/TestRunDeepDive.tsx
new file mode 100644
index 0000000..46cdb49
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/compound/TestRunDeepDive/TestRunDeepDive.tsx
@@ -0,0 +1,97 @@
+import { TestRunCase } from 'arthur-redux/slices/testSuites/types';
+import React, {useCallback, useEffect, useState} from 'react';
+import { Table, TableCell, TableRow } from '../../core/Table';
+import Header from './Header';
+import styles from './styles';
+import { useFela } from 'react-fela';
+import ExpandableTableCell from '../../core/Table/components/ExpandableTableCell';
+import primary from 'resources/colors/Arthur/primary';
+import {Paginator} from "../../core/Paginator";
+import {useSelector} from "react-redux";
+import { State } from 'arthur-redux';
+import {useParams} from "react-router-dom";
+import { useTestSuites } from '../../../../src/Bench/useTestSuites';
+
+
+type RowProps = {
+ runCase: TestRunCase;
+};
+
+const cellStyle = {
+ width: '450px',
+ textAlign: 'left' as 'left',
+ border: `1px solid ${primary.ashGrey}`,
+ padding: '25px',
+};
+const Row = ({ runCase }: RowProps) => {
+ const { css } = useFela();
+
+ return (
+
+
+
+
+
+ {runCase.score.toFixed(3)}
+
+
+ );
+};
+
+const TestRunDeepDive = () => {
+ const { css } = useFela();
+ const [page, setPage] = useState(1);
+ const { testSuiteId, testRunId } = useParams();
+ const { fetchTestRunDetail } = useTestSuites();
+ const { pagination, data } = useSelector((state: State) => ({
+ pagination: state.testSuites.currentTestRun?.pagination,
+ data: state.testSuites.currentTestRun?.data,
+ }));
+ useEffect(() => {
+ testSuiteId && testRunId && fetchTestRunDetail(testSuiteId, testRunId, page, 10);
+ }, [page]);
+
+ const setNewPage = useCallback(
+ (propsPage: number) => {
+ const newPage = propsPage + 1;
+ if (newPage === page || !newPage || !page) {
+ return;
+ }
+ setPage(newPage);
+ },
+ [page]
+ );
+
+ return (
+ <>
+
+
+ {data && data.test_case_runs.map((runCase: TestRunCase) => (
+
+ ))}
+
+ {pagination &&
+ }
+ >
+ );
+};
+
+export default TestRunDeepDive;
diff --git a/arthur_bench/server/js/packages/components/compound/TestRunDeepDive/styles.ts b/arthur_bench/server/js/packages/components/compound/TestRunDeepDive/styles.ts
new file mode 100644
index 0000000..3f36469
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/compound/TestRunDeepDive/styles.ts
@@ -0,0 +1,26 @@
+import primary from 'resources/colors/Arthur/primary';
+import secondary from 'resources/colors/Arthur/secondary';
+import {GRAPHIK_LIGHT, MONO, MONO_MEDIUM} from 'resources/fonts';
+
+const styles = {
+ headerCell: (color: string) => ({
+ backgroundColor: color,
+ fontFamily: MONO_MEDIUM,
+ border: `1px solid ${primary.ashGrey}`,
+ textAlign: 'left',
+ }),
+ table: {
+ borderCollapse: 'collapse',
+ border: `1px solid ${primary.ashGrey}`,
+ },
+ cell: (width?: string) => ({
+ border: `1px solid ${primary.ashGrey}`,
+ fontSize: '16px',
+ FontFamily: GRAPHIK_LIGHT,
+ textAlign: 'left',
+ padding: '16px',
+ width: width ?? 'auto'
+ }),
+};
+
+export default styles;
diff --git a/arthur_bench/server/js/packages/components/compound/TestRunTable/TestRunHeader.tsx b/arthur_bench/server/js/packages/components/compound/TestRunTable/TestRunHeader.tsx
new file mode 100644
index 0000000..812ecb2
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/compound/TestRunTable/TestRunHeader.tsx
@@ -0,0 +1,60 @@
+import React, {useEffect, useState} from 'react';
+import {TableCell} from '../../core/Table';
+import {headerCell} from './styles';
+import {useFela} from 'react-fela';
+import Icon, {EIconType} from "../../core/Icon";
+import {TColumn} from "./TestRunTable";
+
+const HeaderCell = ({
+ column,
+ sort,
+ setSort,
+ selectedSort,
+ setSelectedSort,
+}: {
+ column: TColumn;
+ sort: string;
+ setSort: (name: string) => void;
+ selectedSort: TColumn;
+ setSelectedSort: (column: TColumn) => void;
+
+}) => {
+ const { css } = useFela();
+ const [icon, setIcon] = useState(EIconType.SORT_DEFAULT);
+
+ useEffect(() => {
+ if (selectedSort !== column) {
+ setIcon(EIconType.SORT_DEFAULT);
+ }
+ }, [selectedSort, column]);
+ const handleSort = () => {
+ if (sort === column.asc) {
+ setSort(column.desc);
+ setIcon(EIconType.SORT_DESC)
+ } else {
+ setSort(column.asc);
+ setIcon(EIconType.SORT_ASC)
+ }
+
+ }
+ const handleClick = () => {
+ handleSort();
+ setSelectedSort(column);
+ }
+
+ return (
+
+
+ {column.name}
+
+
+
+ );
+};
+
+
+export default HeaderCell;
diff --git a/arthur_bench/server/js/packages/components/compound/TestRunTable/TestRunRow.tsx b/arthur_bench/server/js/packages/components/compound/TestRunTable/TestRunRow.tsx
new file mode 100644
index 0000000..56b38f6
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/compound/TestRunTable/TestRunRow.tsx
@@ -0,0 +1,60 @@
+import React, { useState } from 'react';
+import {TableCell, TableRow} from '../../core/Table';
+import styles, {cellStyles} from './styles';
+import {useFela} from 'react-fela';
+import {Run} from 'arthur-redux/slices/testSuites/types';
+import {parseAndFormatDate} from '../InsightHeadline/InsightHeadline';
+import {Button} from '../../core/Button';
+import {EIconType} from "../../core/Icon";
+import {useNavigate} from "react-router-dom";
+
+type TRowProps = {
+ testRun: Run;
+ testSuiteId: string
+};
+
+
+const TestRunRow = ({ testRun, testSuiteId }: TRowProps) => {
+ const [showActions, setShowActions] = useState(false);
+ const { css } = useFela();
+ const navigate = useNavigate();
+
+ return (
+ setShowActions(true)}
+ onMouseLeave={() => setShowActions(false)}
+ >
+ {/* hide checkbox until we implement compare functionality
+
+
+ */}
+
+ {parseAndFormatDate(testRun.updated_at)}
+
+
+
+
{testRun.name}
+ {showActions && (
+
+
+
+
+ {testRun.avg_score.toFixed(2)}
+
+
+ );
+};
+
+export default TestRunRow;
diff --git a/arthur_bench/server/js/packages/components/compound/TestRunTable/TestRunTable.tsx b/arthur_bench/server/js/packages/components/compound/TestRunTable/TestRunTable.tsx
new file mode 100644
index 0000000..2e8a2f3
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/compound/TestRunTable/TestRunTable.tsx
@@ -0,0 +1,124 @@
+import React, { useCallback, useEffect, useState } from 'react';
+import { Table, TableCell, TableHeader } from '../../core/Table';
+import HeaderCell from './TestRunHeader';
+import TestRunRow from './TestRunRow';
+import { useFela } from 'react-fela';
+import styles from './styles';
+import { Paginator } from '../../core/Paginator';
+import { useTestSuites } from '../../../../src/Bench/useTestSuites';
+import { useSelector } from 'react-redux';
+import { State } from 'arthur-redux';
+import { Run } from 'arthur-redux/slices/testSuites/types';
+import { useParams } from 'react-router-dom';
+import { Button } from '../../core/Button';
+
+type TTableProps = {
+ testSuiteId?: string;
+ testSuiteName?: string;
+};
+
+export type TColumn = {
+ asc: string;
+ desc: string;
+ name: string;
+};
+
+const columns: TColumn[] = [
+ { asc: 'updated_at', desc: '-updated_at', name: 'TIMESTAMP' },
+ { asc: 'name', desc: '-name', name: 'TEST RUN NAME' },
+ { asc: 'avg_score', desc: '-avg_score', name: 'AVG SCORE' },
+];
+
+const EmptyState = () => {
+ const { css } = useFela();
+ return (
+
+
No test runs created yet
+
Upload your first run through the SDK
+
+
+ );
+};
+
+const TestRunTable = (props: TTableProps) => {
+ const { testSuiteId: testId } = useParams();
+ const testSuiteId = props.testSuiteId ?? testId;
+ const { fetchTestRuns } = useTestSuites();
+ const [sort, setSort] = useState('');
+ const [selectedSort, setSelectedSort] = useState({
+ asc: '',
+ desc: '',
+ name: '',
+ });
+ const [page, setPage] = useState(1);
+ const { css } = useFela();
+ const { runs, pagination } = useSelector((state: State) => ({
+ runs: state.testSuites?.currentTestSuite?.runs?.runs,
+ pagination: state.testSuites?.currentTestSuite?.runs?.pagination,
+ }));
+
+ useEffect(() => {
+ testSuiteId && fetchTestRuns(testSuiteId, page, 5, sort);
+ }, [testSuiteId, page, sort]);
+
+ const setNewPage = useCallback(
+ (propsPage: number) => {
+ const newPage = propsPage + 1;
+ if (newPage === page || !newPage || !page) {
+ return;
+ }
+ setPage(newPage);
+ },
+ [page]
+ );
+
+ return (
+
+ {runs && testSuiteId && pagination ? (
+ <>
+
+
+ {columns.map((column) => (
+
+ ))}
+
+ {runs.map((run: Run) => (
+
+ ))}
+
+
+ >
+ ) : (
+ <>
+
+ {pagination &&
+
}
+ >
+ )}
+
+ );
+};
+
+export default TestRunTable;
diff --git a/arthur_bench/server/js/packages/components/compound/TestRunTable/styles.ts b/arthur_bench/server/js/packages/components/compound/TestRunTable/styles.ts
new file mode 100644
index 0000000..d110fbf
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/compound/TestRunTable/styles.ts
@@ -0,0 +1,60 @@
+import primary from "resources/colors/Arthur/primary"
+import secondary from "resources/colors/Arthur/secondary"
+import {GRAPHIK, GRAPHIK_LIGHT, MONO, MONO_MEDIUM} from "resources/fonts"
+
+const styles = {
+ table: {
+ borderCollapse: 'collapse',
+ border: `1px solid ${primary.ashGrey}`,
+ margin: '20px'
+ },
+ runName: {
+ display: 'flex',
+ flexDirection: 'column',
+ gap: '10px'
+ },
+ row: {
+ height: '60px',
+ ':hover': {
+ backgroundColor: secondary.lightBlue,
+ }
+
+ },
+ nameCell: {
+ display: 'flex',
+ justifyContent: 'space-between',
+ alignItems: 'center',
+ },
+ empty: {
+ backgroundColor: primary.white,
+ height: '360px',
+ width: '680px',
+ border: `1px dashed ${primary.ashGrey}`,
+ color: primary.black,
+ display: 'flex',
+ flexDirection: 'column',
+ justifyContent: 'center',
+ alignItems: 'center',
+ gap: '30px'
+ }
+
+}
+
+export const cellStyles = (width?: string) => ({
+ width: width ?? 'auto',
+ border: `1px solid ${primary.ashGrey}`,
+ fontSize: '16px',
+ FontFamily: GRAPHIK_LIGHT,
+ textAlign: 'left',
+ padding: '16px',
+})
+
+export const headerCell = (color?: string) => ({
+ backgroundColor: color ?? primary.white,
+ fontFamily: MONO_MEDIUM,
+ border: `1px solid ${primary.ashGrey}`,
+ textAlign: 'left',
+ fontSize: '16px'
+})
+
+export default styles
diff --git a/arthur_bench/server/js/packages/components/compound/TestSuiteCard/MethodTag.tsx b/arthur_bench/server/js/packages/components/compound/TestSuiteCard/MethodTag.tsx
new file mode 100644
index 0000000..adf7034
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/compound/TestSuiteCard/MethodTag.tsx
@@ -0,0 +1,43 @@
+import { EMethodType } from 'arthur-redux/slices/testSuites/types';
+import React from 'react';
+import primary from 'resources/colors/Arthur/primary';
+import secondary from 'resources/colors/Arthur/secondary';
+import styles from './styles';
+
+type Props = {
+ name: EMethodType;
+};
+const MethodTag = ({ name }: Props) => {
+ const methods = [
+ {
+ name: EMethodType.BERT,
+ color: primary.purple,
+ },
+ {
+ name: EMethodType.SUMMARY,
+ color: secondary.orange,
+ },
+ {
+ name: EMethodType.QA,
+ color: secondary.yellow,
+ },
+ ];
+ const renderTag = (name: string) => {
+ const method = methods.find((method) => method.name === name);
+ if (method) {
+ return (
+
+ {method.name}
+
+ );
+ }
+ };
+
+ return (
+ <>
+ {renderTag(name)}
+ >
+ )
+};
+
+export default MethodTag;
diff --git a/arthur_bench/server/js/packages/components/compound/TestSuiteCard/TestSuiteCard.tsx b/arthur_bench/server/js/packages/components/compound/TestSuiteCard/TestSuiteCard.tsx
new file mode 100644
index 0000000..685056a
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/compound/TestSuiteCard/TestSuiteCard.tsx
@@ -0,0 +1,78 @@
+import React from 'react';
+import { useFela } from 'react-fela';
+import styles from './styles';
+import { Button } from '../../core/Button';
+import TestRunTable from '../TestRunTable/TestRunTable';
+import { EIconType } from '../../core/Icon';
+import { useNavigate } from 'react-router-dom';
+import { TTestSuite } from 'arthur-redux/slices/testSuites/types';
+import { useTestSuites } from '../../../../src/Bench/useTestSuites';
+import { parseAndFormatDate } from '../InsightHeadline/InsightHeadline';
+import MethodTag from './MethodTag';
+
+type cardProps = {
+ suite: TTestSuite;
+ expandedIndex: number;
+ setExpandedIndex: (arg: number) => void;
+ index: number;
+ isExpanded: boolean;
+};
+
+const TestSuiteCard = ({
+ suite,
+ expandedIndex,
+ setExpandedIndex,
+ isExpanded,
+ index,
+}: cardProps) => {
+ const { css } = useFela();
+ const navigate = useNavigate();
+ const { fetchTestRuns } = useTestSuites();
+ const toggleExpandedIndex = (index: number) => {
+ if (index === expandedIndex) {
+ setExpandedIndex(-1);
+ } else {
+ setExpandedIndex(index);
+ }
+ };
+ const handleSelectSuite = (id: string) => {
+ navigate(`/bench/${id}/runs`);
+ fetchTestRuns(id, 1, 5);
+ };
+
+ return (
+
+
+ handleSelectSuite(suite.id)}
+ >
+ {suite.name}
+
+
+
+ Latest Run:{' '}
+ {suite.last_run_time
+ ? parseAndFormatDate(suite.last_run_time)
+ : 'N/A'}
+
+
+
+
+
+
+ );
+};
+
+export default TestSuiteCard;
diff --git a/arthur_bench/server/js/packages/components/compound/TestSuiteCard/styles.ts b/arthur_bench/server/js/packages/components/compound/TestSuiteCard/styles.ts
new file mode 100644
index 0000000..c90a85e
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/compound/TestSuiteCard/styles.ts
@@ -0,0 +1,49 @@
+import primary from "resources/colors/Arthur/primary";
+import secondary from "resources/colors/Arthur/secondary";
+import {GRAPHIK, GRAPHIK_LIGHT, MONO} from "resources/fonts";
+
+const styles = {
+ container: {
+ backgroundColor: primary.white,
+ width: '90%',
+ border: `1px solid ${primary.ashGrey}`,
+ marginBottom: '20px',
+ color: primary.black,
+ fontFamily: MONO,
+ padding: '10px 20px',
+ fontSize: '14px'
+ },
+ date: {
+ textAlign: 'right',
+ },
+ name: {
+ color: secondary.blue,
+ fontFamily: GRAPHIK_LIGHT,
+ fontSize: '24px',
+ ':hover': {
+ cursor: 'pointer',
+ textDecoration: 'underline'
+ }
+ },
+ row: {
+ display: 'grid',
+ gridTemplateColumns: '1fr 150px 1fr',
+ justifyContent: 'space-between',
+ alignItems: 'flex-start',
+ margin: '10px',
+ textAlign: 'left',
+ padding: '10px'
+ },
+ hr: {
+ border:`1px solid ${primary.ashGrey}`,
+ margin: '15px 0px'
+ },
+ tag: (color: string) => ({
+ border: `2px solid ${color}`,
+ borderRadius: '5px',
+ padding: '5px',
+ width: 'fit-content',
+ })
+};
+
+export default styles;
diff --git a/arthur_bench/server/js/packages/components/compound/TestSuiteDetailsModal/TestSuiteDetailsModal.tsx b/arthur_bench/server/js/packages/components/compound/TestSuiteDetailsModal/TestSuiteDetailsModal.tsx
new file mode 100644
index 0000000..30dce2f
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/compound/TestSuiteDetailsModal/TestSuiteDetailsModal.tsx
@@ -0,0 +1,83 @@
+import React from 'react';
+import Modal from '@core/Modal/Modal';
+import Icon, { EIconType } from '@core/Icon';
+import styles from './styles';
+import { useFela } from 'react-fela';
+import { useTranslation } from 'react-i18next';
+import { DetailedTestSuite } from 'arthur-redux/slices/testSuites/types';
+import { parseAndFormatDate } from '../InsightHeadline/InsightHeadline';
+import MethodTag from "../TestSuiteCard/MethodTag";
+
+type props = {
+ testSuite: DetailedTestSuite;
+ showModal: boolean;
+ setShowModal: (arg: boolean) => void;
+};
+const TestSuiteDetailsModal = ({
+ testSuite,
+ showModal,
+ setShowModal,
+}: props) => {
+ const { t } = useTranslation(['common']);
+ const { css } = useFela();
+ const modelDetail = (
+
+
+
{testSuite.name}
+
setShowModal(!showModal)}
+ />
+
+
+
+
+ {t('testSuite.generalInformation')}
+
+
+
+ {t('testSuite.testSuiteId')}
+
+
{testSuite.id}
+
+
+
+ {t('testSuite.description')}
+
+
{testSuite.description}
+
+
+
+
+ {t('testSuite.testRunInformation')}
+
+
+
+ {t('testSuite.scoringMethod')}
+
+
+
+
+
+ {t('testSuite.lastRun')}
+
+
{testSuite.last_run_time ? parseAndFormatDate(testSuite.last_run_time) : 'N/A'}
+
+
+
+ {t('testSuite.number')}
+
+
{testSuite.num_runs}
+
+
+
+
+ );
+
+ return ;
+};
+
+export default TestSuiteDetailsModal;
diff --git a/arthur_bench/server/js/packages/components/compound/TestSuiteDetailsModal/styles.ts b/arthur_bench/server/js/packages/components/compound/TestSuiteDetailsModal/styles.ts
new file mode 100644
index 0000000..ef7e0db
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/compound/TestSuiteDetailsModal/styles.ts
@@ -0,0 +1,64 @@
+import primary from "resources/colors/Arthur/primary"
+import { MONO } from "resources/fonts"
+
+const styles = {
+ container: {
+ background: primary.white,
+ width: '968px',
+ minHeight: '575px',
+ padding: '32px',
+ boxSizing: 'border-box',
+ display: 'flex',
+ flexDirection: 'column',
+ justifyContent: 'space-between',
+ fontFamily: MONO,
+ filter: 'drop-shadow(0px 2px 12px rgba(26, 0, 22, 0.2))',
+ color: primary.black
+ },
+ header: {
+ display: 'flex',
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ },
+ body: {
+ display: 'flex',
+ justifyContent: 'space-between',
+ width: '904px',
+ minHeight: '427px',
+ },
+ column: {
+ width: '435px',
+ },
+ columnHeader: {
+ background: primary.mint,
+ height: '24px',
+ display: 'flex',
+ justifyContent: 'center',
+ fontSize: '12px',
+ alignItems: 'center',
+ },
+ dataChunk: {
+ fontSize: '14px',
+ minHeight: '45px',
+ padding: '7px',
+ display: 'flex',
+ flexDirection: 'column',
+ overflowWrap: 'anywhere',
+ justifyContent: 'space-between',
+ margin: '8px 0',
+ gap: '8px',
+ },
+ dataChunkLabel: {
+ fontSize: '12px',
+ },
+ title: {
+ fontSize: '24px',
+ fontWeight: 400,
+ fontStyle: 'normal',
+ lineHeight: '38px',
+ display: 'flex',
+ width: '100%',
+ }
+}
+
+export default styles
diff --git a/arthur_bench/server/js/packages/components/compound/TestSuitesHeader/TestSuitesHeader.tsx b/arthur_bench/server/js/packages/components/compound/TestSuitesHeader/TestSuitesHeader.tsx
new file mode 100644
index 0000000..22511e4
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/compound/TestSuitesHeader/TestSuitesHeader.tsx
@@ -0,0 +1,47 @@
+import React from 'react';
+import { useFela } from 'react-fela';
+import styles from './styles';
+import MetricDropdownSelection from '../MetricsDropdownSelection';
+import { TSelectItem } from '../../core/StyledSelect/StyledSelect';
+import SortDropdown from '../SortDropdown/SortDropdown';
+
+const metrics = [
+ { id: '1', name: 'BERTScore' },
+ { id: '2', name: 'summary_quality' },
+ { id: '3', name: 'qa_correctness' },
+];
+const sortOptions = [
+ { id: 'last_run_time', name: 'OLDEST FIRST' },
+ { id: '-last_run_time', name: 'NEWEST FIRST' },
+ { id: 'name', name: 'TEST SUITE NAME: A-Z' },
+ { id: '-name', name: 'TEST SUITE NAME: Z-A' }
+
+];
+type Props = {
+ setFilters: (arg: TSelectItem[]) => void;
+ filters: TSelectItem[];
+ setSortColumn: (arg: string) => void;
+};
+const TestSuitesHeader = (props: Props) => {
+ const { css } = useFela();
+ const { setFilters, filters, setSortColumn } = props;
+ return (
+
+ );
+};
+
+export default TestSuitesHeader;
diff --git a/arthur_bench/server/js/packages/components/compound/TestSuitesHeader/styles.ts b/arthur_bench/server/js/packages/components/compound/TestSuitesHeader/styles.ts
new file mode 100644
index 0000000..980f0fc
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/compound/TestSuitesHeader/styles.ts
@@ -0,0 +1,18 @@
+import primary from 'resources/colors/Arthur/primary';
+import { GRAPHIK_LIGHT } from 'resources/fonts';
+
+const styles = {
+ container: {
+ color: primary.black,
+ padding: '20px 50px',
+ fontFamily: GRAPHIK_LIGHT,
+ },
+ toolbar: {
+ display: 'flex',
+ gap: '20px',
+ alignItems: 'center',
+ marginTop: '30px',
+ },
+};
+
+export default styles;
diff --git a/arthur_bench/server/js/packages/components/compound/WelcomeModal/WelcomeModal.tsx b/arthur_bench/server/js/packages/components/compound/WelcomeModal/WelcomeModal.tsx
new file mode 100644
index 0000000..ecb1c7b
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/compound/WelcomeModal/WelcomeModal.tsx
@@ -0,0 +1,90 @@
+import React, { useState } from 'react';
+import Modal from '../../core/Modal/Modal';
+import HelpTile from '../../core/HelpTile';
+import { useTranslation } from 'react-i18next';
+import { iconBox } from '../FloatingHelp/styles';
+import Icon, { EIconType } from '../../core/Icon';
+import secondary from 'resources/colors/Arthur/secondary';
+import { useFela } from 'react-fela';
+import styles from './styles';
+import Welcome from 'resources/images/welcome.svg';
+import Logo from 'resources/images/Arthur_Logo_PBW.svg';
+import primary from 'resources/colors/Arthur/primary';
+
+const PYTHON_LINK = 'https://github.com/arthur-ai/bench';
+const BENCH_DOCS_LINK = 'https://docs.arthur.ai/bench/index.html';
+const WelcomeModal = () => {
+ const [showModal, setShowModal] = useState(true);
+ const { t } = useTranslation();
+ const { css } = useFela();
+ return (
+
+
+
+
+
Welcome to
{' '}
+
+
+
+ Here are some helpful resources for you to get started
+
+
+
+
+
+
+ }
+ icon={EIconType.EXTERNAL_LINK}
+ />
+
+
+
+ }
+ icon={EIconType.EXTERNAL_LINK}
+ />
+
+
+
+
+
setShowModal(false)}
+ />
+
+
+
+ );
+};
+
+export default WelcomeModal;
diff --git a/arthur_bench/server/js/packages/components/compound/WelcomeModal/styles.ts b/arthur_bench/server/js/packages/components/compound/WelcomeModal/styles.ts
new file mode 100644
index 0000000..44b6c80
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/compound/WelcomeModal/styles.ts
@@ -0,0 +1,52 @@
+import primary from 'resources/colors/Arthur/primary';
+
+const styles = {
+ modal: {
+ backgroundColor: primary.ashGrey,
+ color: primary.black,
+ display: 'flex',
+ },
+ imageContainer: {
+ backgroundColor: primary.raisin,
+ position: 'relative',
+ height: '600px',
+ '> img': {
+ height: '100%',
+ },
+ },
+ closeButton: {
+ color: primary.white,
+ position: 'absolute',
+ top: '10px',
+ right: '10px',
+ cursor: 'pointer',
+ },
+ left: {
+ width: '550px',
+ display: 'flex',
+ flexDirection: 'column',
+ alignItems: 'center',
+ paddingTop: '15px',
+ },
+ helpTiles: {
+ height: '200px',
+ width: '450px',
+ display: 'flex',
+ flexDirection: 'column',
+ gap: '20px',
+ padding: '50px',
+ },
+ title: {
+ display: 'flex',
+ fontSize: '22px',
+ justifyContent: 'center',
+ alignItems: 'center',
+ width: '100%',
+ '> img': {
+ width: '200px',
+ marginLeft: '-20px',
+ },
+ },
+};
+
+export default styles;
diff --git a/arthur_bench/server/js/packages/components/core/Breadcrumbs/Breadcrumbs.tsx b/arthur_bench/server/js/packages/components/core/Breadcrumbs/Breadcrumbs.tsx
new file mode 100644
index 0000000..dcaa289
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/core/Breadcrumbs/Breadcrumbs.tsx
@@ -0,0 +1,56 @@
+import React from 'react';
+import styles from './styles';
+import { Link } from 'react-router-dom';
+import { useFela } from 'react-fela';
+
+type TBreadcrumbItem = {
+ link?: string;
+ label: string;
+};
+
+type TBreadcrumbItems = Array;
+
+type Props = {
+ items: TBreadcrumbItems;
+ testId?: string;
+};
+
+const Breadcrumbs = (props: Props) => {
+ const { items, testId = 'Breadcrumbs--Root' } = props;
+ const { css } = useFela();
+
+ const isLastItem = (index: number) => items.length === index + 1;
+
+ const renderLink = (item: TBreadcrumbItem) => (
+
+ {item.label}
+
+ );
+
+ const renderItem = (item: TBreadcrumbItem, index: number) => {
+ const isLast = isLastItem(index);
+
+ return (
+
+ {item.link && !isLast ? (
+ renderLink(item)
+ ) : (
+
+ {item.label}
+
+ )}
+ {isLast ? null : /}
+
+ );
+ };
+
+ const renderItems = () => (
+
+ {items.map(renderItem)}
+
+ );
+
+ return items.length ? renderItems() : null;
+};
+
+export default Breadcrumbs;
diff --git a/arthur_bench/server/js/packages/components/core/Breadcrumbs/index.ts b/arthur_bench/server/js/packages/components/core/Breadcrumbs/index.ts
new file mode 100644
index 0000000..1690c4e
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/core/Breadcrumbs/index.ts
@@ -0,0 +1,3 @@
+import Breadcrumbs from './Breadcrumbs';
+
+export default Breadcrumbs;
diff --git a/arthur_bench/server/js/packages/components/core/Breadcrumbs/styles.ts b/arthur_bench/server/js/packages/components/core/Breadcrumbs/styles.ts
new file mode 100644
index 0000000..3af779f
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/core/Breadcrumbs/styles.ts
@@ -0,0 +1,41 @@
+import primary from 'resources/colors/Arthur/primary';
+import { MONO } from 'resources/fonts';
+
+export default {
+ root: {
+ fontFamily: MONO,
+ display: 'flex',
+ flexWrap: 'wrap',
+ alignItems: 'center',
+ fontSize: '12px',
+ fontStyle: 'normal',
+ fontWeight: 400,
+ lineHeight: '24px',
+ },
+ item: {
+ display: 'flex',
+ alignItems: 'center',
+ },
+ divider: {
+ color: primary.raisin,
+ marginLeft: '5px',
+ marginRight: '5px',
+ fontWeight: 500,
+ fontSize: '14px',
+ lineHeight: '14px',
+ },
+ link: {
+ color: primary.eggplant,
+ textDecoration: 'none',
+ opacity: 0.7,
+ whiteSpace: 'nowrap',
+ '&:hover': {
+ opacity: 1,
+ },
+ },
+ label: (isLast: boolean) => ({
+ color: primary.eggplant,
+ whiteSpace: 'nowrap',
+ opacity: isLast ? 1 : 0.7,
+ }),
+};
diff --git a/arthur_bench/server/js/packages/components/core/Button/Button.tsx b/arthur_bench/server/js/packages/components/core/Button/Button.tsx
new file mode 100644
index 0000000..af02d38
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/core/Button/Button.tsx
@@ -0,0 +1,114 @@
+import React, { ForwardedRef } from 'react';
+import { useFela } from 'react-fela';
+import { ButtonProps, EButtonSize, EButtonVariation } from './typings';
+import styles from './styles';
+import Icon from '../Icon/Icon';
+import { isNil } from 'utils/is-nil';
+import { getProgressBarWidth } from './utils';
+import { TThemeType } from 'resources/theme/types';
+
+const Button = React.forwardRef(
+ (props: ButtonProps, ref: ForwardedRef) => {
+ const { css, theme } = useFela();
+
+ const {
+ variation = EButtonVariation.PRIMARY,
+ size = EButtonSize.NORMAL,
+ iconStart,
+ iconEnd,
+ iconText,
+ isLoading,
+ loadingProgress = 0,
+ iconSize,
+ iconClass,
+ isLink = false,
+ text,
+ disabled,
+ style,
+ clickHandler,
+ noBorder = false,
+ customWidth = 0,
+ customHeight = 0,
+ } = props;
+
+ const classNames = styles(
+ variation,
+ size,
+ isLink,
+ theme,
+ !!iconStart,
+ !!iconEnd,
+ !isNil(text),
+ noBorder,
+ customWidth,
+ customHeight
+ );
+
+ const renderProgressBar = () => {
+ if (isLoading && !isLink) {
+ return (
+
+ );
+ }
+
+ return null;
+ };
+
+ return (
+
+ );
+ }
+);
+
+Button.displayName = 'ArthurButton';
+
+export default Button;
diff --git a/arthur_bench/server/js/packages/components/core/Button/constants.ts b/arthur_bench/server/js/packages/components/core/Button/constants.ts
new file mode 100644
index 0000000..92a254f
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/core/Button/constants.ts
@@ -0,0 +1,24 @@
+import { EButtonSize } from './typings';
+
+export const sizeParams = {
+ [EButtonSize.SMALL]: {
+ text: 12,
+ iconTextMargin: 5,
+ icon: 13,
+ },
+ [EButtonSize.NORMAL]: {
+ text: 14,
+ iconTextMargin: 10,
+ icon: 20,
+ },
+ [EButtonSize.LARGE]: {
+ text: 18,
+ iconTextMargin: 10.5,
+ icon: 27,
+ },
+ [EButtonSize.FULL_WIDTH]: {
+ text: 14,
+ iconTextMargin: 10,
+ icon: 20,
+ },
+};
diff --git a/arthur_bench/server/js/packages/components/core/Button/index.ts b/arthur_bench/server/js/packages/components/core/Button/index.ts
new file mode 100644
index 0000000..850ab19
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/core/Button/index.ts
@@ -0,0 +1,4 @@
+import Button from './Button';
+export * from './typings';
+
+export { Button };
diff --git a/arthur_bench/server/js/packages/components/core/Button/styles.ts b/arthur_bench/server/js/packages/components/core/Button/styles.ts
new file mode 100644
index 0000000..d8d4cb3
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/core/Button/styles.ts
@@ -0,0 +1,150 @@
+import { getBorder, getPaddings } from './utils';
+import { EButtonSize, EButtonVariation } from './typings';
+import {
+ ACTIVE,
+ DISABLED,
+ FOCUS,
+ HOVER,
+ NONE,
+ UNDERLINE,
+ TRANSPARENT,
+} from 'resources/style-constants';
+import { sizeParams } from './constants';
+import { TThemeType } from 'resources/theme/types';
+import { useFela } from 'react-fela';
+
+const styles = (
+ variation: EButtonVariation,
+ size: EButtonSize,
+ isLink: boolean,
+ buttonTheme: TThemeType,
+ iconStart: boolean,
+ iconEnd: boolean,
+ text: boolean,
+ noBorder: boolean,
+ customWidth?: string | number,
+ customHeight?: string | number
+) => {
+ const colorPalette = buttonTheme.button[variation];
+ const sizeParamValue = sizeParams[size];
+ const { theme }: any = useFela();
+
+ return {
+ root: {
+ ...(customWidth && { width: `${customWidth}px` }),
+ ...(customHeight && { height: `${customHeight}px` }),
+ ...(size === EButtonSize.FULL_WIDTH && { width: '100%' }),
+ position: 'relative',
+ display: 'inline-flex',
+ whiteSpace: 'nowrap',
+ alignItems: 'center',
+ fontSize: `${sizeParamValue.text}px`,
+ padding: getPaddings(size, iconStart, iconEnd, text),
+ lineHeight: 1,
+ ...(!noBorder && {
+ boxShadow: getBorder(colorPalette.borderColor, isLink),
+ }),
+ border: NONE,
+ cursor: 'pointer',
+ fontFamily: theme.font_2,
+ letterSpacing: '0.8px',
+ textTransform: 'uppercase',
+ backgroundColor: isLink
+ ? TRANSPARENT
+ : colorPalette.backgroundColor,
+ color: isLink ? colorPalette.linkColor : colorPalette.textColor,
+ '& path': {
+ fill: isLink ? colorPalette.linkColor : colorPalette.textColor,
+ },
+ [HOVER]: {
+ backgroundColor: isLink
+ ? TRANSPARENT
+ : colorPalette.hoverBackgroundColor,
+ ...(!noBorder && {
+ boxShadow: getBorder(colorPalette.borderColor, isLink),
+ }),
+ color: isLink
+ ? colorPalette.linkColor
+ : colorPalette.hoverTextColor,
+ '& path': {
+ fill: isLink
+ ? colorPalette.linkColor
+ : colorPalette.hoverTextColor,
+ },
+ textDecoration: isLink ? UNDERLINE : NONE,
+ },
+ [FOCUS]: {
+ backgroundColor: isLink
+ ? TRANSPARENT
+ : colorPalette.focusBackgroundColor,
+ ...(!noBorder && {
+ boxShadow: getBorder(colorPalette.borderColor, isLink),
+ }),
+ color: isLink
+ ? colorPalette.linkFocusColor
+ : colorPalette.focusTextColor,
+ textDecoration: isLink ? UNDERLINE : NONE,
+ '& path': {
+ fill: isLink
+ ? colorPalette.linkFocusColor
+ : colorPalette.focusTextColor,
+ },
+ },
+ [ACTIVE]: {
+ backgroundColor: isLink
+ ? TRANSPARENT
+ : colorPalette.backgroundColor,
+ ...(!noBorder && {
+ boxShadow: getBorder(colorPalette.borderColor, isLink),
+ }),
+ color: isLink ? colorPalette.linkColor : colorPalette.textColor,
+ '& path': {
+ fill: isLink
+ ? colorPalette.linkColor
+ : colorPalette.textColor,
+ },
+ },
+ [DISABLED]: {
+ pointerEvents: NONE,
+ userSelect: NONE,
+ },
+ },
+ disabled: {
+ backgroundColor: isLink
+ ? TRANSPARENT
+ : colorPalette.disabledBackgroundColor,
+ color: isLink
+ ? colorPalette.linkDisabledColor
+ : colorPalette.disabledTextColor,
+ ...(!noBorder && {
+ boxShadow: getBorder(colorPalette.borderColor, isLink),
+ }),
+ '& path': {
+ fill: isLink
+ ? colorPalette.linkDisabledColor
+ : colorPalette.disabledTextColor,
+ },
+ },
+ text: {
+ width: '100%',
+ position: 'relative',
+ zIndex: 2,
+ display: 'inline-block',
+ marginLeft: iconStart ? `${sizeParamValue.iconTextMargin}px` : 0,
+ marginRight: iconEnd ? `${sizeParamValue.iconTextMargin}px` : 0,
+ },
+ icon: {
+ size: sizeParamValue.icon,
+ },
+ progressBar: {
+ backgroundColor: colorPalette.progressBarBackgroundColor,
+ position: 'absolute',
+ zIndex: colorPalette.progressBarIndex,
+ left: '0px',
+ top: '0px',
+ bottom: '0px',
+ },
+ };
+};
+
+export default styles;
diff --git a/arthur_bench/server/js/packages/components/core/Button/typings.d.ts b/arthur_bench/server/js/packages/components/core/Button/typings.d.ts
new file mode 100644
index 0000000..0f9d2e8
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/core/Button/typings.d.ts
@@ -0,0 +1,18 @@
+import React, { ReactComponentElement } from 'react';
+
+export interface ButtonProps
+ extends React.ButtonHTMLAttributes {
+ text: string | number;
+ onClick: () => void;
+ disabled?: boolean;
+ className?: string;
+ style?: Record;
+ testid?: string;
+ ariarole?: string;
+ icon?: ReactComponentElement;
+ iconAlign?: 'left' | 'right';
+}
+
+export interface PrimaryButtonProps extends ButtonProps {
+ color?: string;
+}
diff --git a/arthur_bench/server/js/packages/components/core/Button/typings.ts b/arthur_bench/server/js/packages/components/core/Button/typings.ts
new file mode 100644
index 0000000..644ebe3
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/core/Button/typings.ts
@@ -0,0 +1,43 @@
+import React from 'react';
+import { EIconType } from '../Icon';
+
+export enum EButtonVariation {
+ PRIMARY = 'primary',
+ SECONDARY = 'secondary',
+ SUBTLE = 'subtle',
+ DESTRUCTIVE = 'destructive',
+ ARTHUR = 'arthur',
+ ARTHUR_SECONDARY = 'arthurSecondary',
+}
+
+export enum EButtonSize {
+ LARGE = 'large',
+ NORMAL = 'normal',
+ SMALL = 'small',
+ FULL_WIDTH = 'full_width',
+}
+
+export interface ButtonProps
+ extends React.ButtonHTMLAttributes {
+ variation?: EButtonVariation;
+ size?: EButtonSize;
+ text?: string | number;
+ isLink?: boolean;
+ disabled?: boolean;
+ className?: string;
+ iconSize?: number;
+ iconClass?: string;
+ iconStart?: EIconType;
+ iconText?: EIconType;
+ iconEnd?: EIconType;
+ isLoading?: boolean;
+ loadingProgress?: number;
+ style?: Record;
+ testId?: string;
+ ariaRole?: string;
+ clickHandler?: () => any;
+ isHighlighted?: boolean;
+ noBorder?: boolean;
+ customWidth?: string | number;
+ customHeight?: string | number;
+}
diff --git a/arthur_bench/server/js/packages/components/core/Button/utils.ts b/arthur_bench/server/js/packages/components/core/Button/utils.ts
new file mode 100644
index 0000000..6d64844
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/core/Button/utils.ts
@@ -0,0 +1,49 @@
+import { EButtonSize } from './typings';
+
+export const getProgressBarWidth = (progress: number): number => {
+ if (progress < 0) {
+ return 0;
+ } else if (progress > 100) {
+ return 100;
+ } else {
+ return progress;
+ }
+};
+
+export const getBorder = (color: string | undefined, isLink: boolean) =>
+ color && !isLink ? `0px 0px 0px 2px ${color} inset` : 'none';
+
+export const getPaddings = (
+ size: EButtonSize,
+ iconStart: boolean,
+ iconEnd: boolean,
+ text: boolean
+): string => {
+ const anyIconApplied = iconEnd || iconStart;
+ const isSingleIcon =
+ ((!iconEnd && iconStart) || (iconEnd && !iconStart)) && !text;
+
+ if (isSingleIcon) {
+ switch (size) {
+ case EButtonSize.LARGE:
+ return '14.5px 18.5px';
+ default:
+ return '10px';
+ }
+ } else {
+ switch (size) {
+ case EButtonSize.SMALL:
+ return `${anyIconApplied ? 9 : 10}px ${iconEnd ? 8 : 16}px ${
+ anyIconApplied ? 9 : 10
+ }px ${iconStart ? 8 : 16}px`;
+ case EButtonSize.LARGE:
+ return `${anyIconApplied ? 14.5 : 19}px ${
+ iconEnd ? 19 : 24
+ }px ${anyIconApplied ? 14.5 : 19}px ${iconStart ? 19 : 24}px`;
+ default:
+ return `${anyIconApplied ? 10 : 13}px ${iconEnd ? 9.5 : 16}px ${
+ anyIconApplied ? 10 : 13
+ }px ${iconStart ? 9.5 : 16}px`;
+ }
+ }
+};
diff --git a/arthur_bench/server/js/packages/components/core/Charts/BarChart/BarChart.stories.tsx b/arthur_bench/server/js/packages/components/core/Charts/BarChart/BarChart.stories.tsx
new file mode 100644
index 0000000..51892cb
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/core/Charts/BarChart/BarChart.stories.tsx
@@ -0,0 +1,111 @@
+import React from 'react';
+import BarChart from './BarChart';
+import { EAxis, EChartsColorBy, TMarkAreaData } from '../constants';
+import { ELegendItemShape } from '../Legend/Legend';
+import primary from 'resources/colors/Arthur/primary';
+import secondary from 'resources/colors/Arthur/secondary';
+
+export default {
+ title: 'Arthur/Core/Chart/Bar',
+ component: BarChart,
+};
+
+const data0 = { name: '1', data: { Third: 18, Fourth: 4 } };
+const data1 = {
+ name: '1',
+ data: { First: 8, Second: 20, Third: 18, Fourth: 4 },
+};
+const data2 = {
+ name: '2',
+ data: { First: 4, Second: 16, Third: 9, Fourth: 8 },
+};
+
+const markArea = {
+ axis: EAxis.Y,
+ data: [-15, 15],
+} as TMarkAreaData;
+
+const legendItem1 = {
+ name: 'Young (Baseline)',
+ color: primary.eggplant,
+ shape: ELegendItemShape.DASH,
+};
+
+const legendItem2 = {
+ name: 'Within Threshold',
+ color: '#FFBF00',
+ shape: ELegendItemShape.SQUARE,
+};
+
+const legendItem3 = {
+ name: 'Over Threshold',
+ color: '#2D78CB',
+ shape: ELegendItemShape.SQUARE,
+};
+
+const markLine = { yAxis: 10 };
+const markLineOrange = { yAxis: 10, lineStyle: { color: primary.purple } };
+const markLinePurple = { yAxis: 6, lineStyle: { color: secondary.orange } };
+
+export const Default = () => (
+
+);
+
+export const MultipleSeriesAndLine = () => (
+
+);
+
+export const MultipleSeriesAndLineStacked = () => (
+
+);
+
+export const MultipleLinesAndArea = () => (
+
+);
+
+export const WithNegativeValue = () => (
+
+);
diff --git a/arthur_bench/server/js/packages/components/core/Charts/BarChart/BarChart.tsx b/arthur_bench/server/js/packages/components/core/Charts/BarChart/BarChart.tsx
new file mode 100644
index 0000000..b796e7c
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/core/Charts/BarChart/BarChart.tsx
@@ -0,0 +1,366 @@
+import React, { useEffect, useMemo, useState } from 'react';
+import merge from 'lodash.merge';
+import { EChartsOption, EChartsType } from 'echarts';
+import EChartsReact from 'echarts-for-react';
+import primary from 'resources/colors/Arthur/primary';
+
+import {
+ xAxisDefaults,
+ yAxisDefaults,
+ EChartsColorBy,
+ EChartType,
+ LOADING_OPTIONS,
+ TMarkAreaData,
+ emptyMessageStyles,
+ rootStyles,
+ secondaryTitle,
+ legendTopContainer,
+} from '../constants';
+import graphs, { chartColorsArray } from 'resources/colors/Arthur/graphs';
+import Legend, { ELegendItemShape, LegendItems } from '../Legend/Legend';
+import { MarkLine1DDataItemOption } from 'echarts/types/src/component/marker/MarkLineModel';
+import { CallbackDataParams } from 'echarts/types/dist/shared';
+import { useTranslation } from 'react-i18next';
+import { useFela } from 'react-fela';
+
+export type TBarChartData = Record;
+
+export type TBarChartDataItem = {
+ name: string;
+ data: TBarChartData;
+ color?: string;
+ subtitle?: string;
+ opacity?: number;
+};
+
+export type TBarChartDataItems = Array;
+
+type TBarChartProps = {
+ id?: string;
+ dataTestId?: string;
+ isLoading?: boolean;
+ xAxisTitle?: string;
+ emptyMessage?: any;
+ xAxisLabelFormatter?: string | ((value: string, index: number) => string);
+ yAxisLabelFormatter?: string | ((value: string, index: number) => string);
+ yAxisTitle?: string;
+ xSecondaryAxisTitle?: string;
+ height?: string;
+ tooltip?: any;
+ barGap?: number;
+ barWidth?: string | number;
+ colorBy?: EChartsColorBy;
+ onChartClick?: (event: CallbackDataParams, chart: EChartsType) => void;
+ options?: EChartsOption;
+ markArea?: TMarkAreaData;
+ markLine?: MarkLine1DDataItemOption[];
+ graphData?: TBarChartDataItems; //setting this as optional for now until there is time to refactor the bench bar chart
+ hideVerticalLines?: boolean;
+ legendItems?: LegendItems;
+ showLegend?: boolean;
+ showLegendTop?: boolean;
+ useCustomLegendItems?: boolean;
+ addMarkLineLegendItems?: boolean;
+ yAxisMax?: number;
+ stacked?: boolean;
+ groupByName?: boolean;
+ splitLegendOnChar?: string;
+ hasToolbox?: boolean;
+ openChartSelections?: () => void | null;
+ additionalToolboxItems?: Record>
+};
+
+const BarChart = (props: TBarChartProps) => {
+ const { t } = useTranslation();
+ const { css } = useFela();
+ const {
+ id,
+ dataTestId,
+ isLoading,
+ height = '400px',
+ options,
+ barWidth = '15%',
+ barGap,
+ markArea,
+ markLine,
+ colorBy = EChartsColorBy.DATA,
+ onChartClick,
+ graphData,
+ hideVerticalLines,
+ legendItems = [],
+ tooltip,
+ xAxisLabelFormatter,
+ yAxisLabelFormatter,
+ emptyMessage,
+ showLegend,
+ showLegendTop,
+ xSecondaryAxisTitle,
+ useCustomLegendItems = false,
+ addMarkLineLegendItems = false,
+ yAxisMax,
+ stacked = false,
+ groupByName = false,
+ hasToolbox = false,
+ additionalToolboxItems = {}
+ } = props;
+
+ const [stateLegendItems, setLegendItems] = useState(legendItems);
+
+ useEffect(() => {
+ handleAddLegendItems();
+ }, [graphData, markLine])
+
+ const renderEmptyMessage = () => {
+ if (graphData) {
+ const emptyGraphData = !graphData.length;
+ const emptyDataPoint = graphData.every(
+ (d) => !Object.keys(d.data).length
+ );
+ const zeroesData = graphData.every((d) =>
+ Object.values(d.data).every((val) => !val)
+ );
+
+ if (
+ (emptyGraphData || emptyDataPoint || zeroesData) &&
+ !isLoading
+ ) {
+ return (
+
+ {emptyMessage || t('charts.emptyMessage')}
+
+ );
+ }
+ }
+
+ return null;
+ };
+
+ const handleAddLegendItems = () => {
+ if (!showLegend || useCustomLegendItems || !graphData) {
+ return;
+ }
+
+ const allGraphDataKeys: string[] = [];
+ graphData.forEach((dataItem: TBarChartDataItem) => {
+ allGraphDataKeys.push(...Object.keys(dataItem.data))
+ })
+ const uniqueDataKeys = Array.from(new Set(allGraphDataKeys));
+ const moreLegendItems: LegendItems = [];
+ uniqueDataKeys.map((name: string, index: number) => {
+ moreLegendItems.push({
+ color: chartColorsArray[index],
+ name,
+ shape: ELegendItemShape.SQUARE,
+ });
+ })
+
+ if (addMarkLineLegendItems) {
+ markLine?.map((dp: any) => {
+ moreLegendItems.push({
+ color: dp.lineStyle.color,
+ name: `${dp.name} Ref Data`,
+ shape: ELegendItemShape.DASH,
+ });
+ });
+ }
+
+ setLegendItems([...moreLegendItems, ...legendItems]);
+ };
+
+ const tooltipFormatter = (
+ event: { value: number; axisValueLabel: string }[]
+ ) => {
+ return `${event[0].axisValueLabel}
+ ${event[0].value} `;
+ };
+
+ const handleChartClick = (
+ event: CallbackDataParams,
+ chart: EChartsType
+ ) => {
+ if (onChartClick) {
+ onChartClick(event, chart);
+ }
+ };
+
+ const handleXAxisData = (data: TBarChartDataItems) => {
+ if (data?.length) {
+ if (groupByName) {
+ return data.map((dp: TBarChartDataItem) => dp.name);
+ } else {
+ return Object.keys(data[0].data);
+ }
+ } else {
+ return [];
+ }
+ };
+
+ const defaultSeriesOptions = (rw: any, index: number) => ({
+ ...(stacked && { stack: 'stackbar' }),
+ barMinHeight: 1,
+ silent: !onChartClick,
+ barCategoryGap: '10px',
+ barWidth,
+ barGap,
+ barMaxWidth: 120,
+ colorBy,
+ type: EChartType.BAR,
+ symbolSize: 14,
+ itemStyle: {
+ color: rw.color,
+ barBorderRadius: [2, 2, 0, 0],
+ opacity: rw.opacity,
+ },
+ emphasis: {
+ itemStyle: {
+ borderWidth: 2,
+ shadowColor: 'rgba(0, 0, 0, 0.5)',
+ shadowBlur: 2,
+ },
+ focus: 'series',
+ },
+ showSymbol: false,
+ ...(!index && {
+ ...(markLine && {
+ markLine: {
+ silent: true,
+ symbol: 'none',
+ lineStyle: { color: primary.raisin, width: 2 },
+ label: { show: false },
+ data: markLine,
+ },
+ }),
+ ...(markArea && {
+ markArea: {
+ emphasis: { disabled: true },
+ itemStyle: {
+ color: graphs.backgrounds.ashGrey,
+ opacity: 0.5,
+ },
+ data: [
+ [
+ { [markArea.axis]: markArea.data[0] },
+ { [markArea.axis]: markArea.data[1] },
+ ],
+ ],
+ },
+ }),
+ }),
+ });
+ const defaultOptions = useMemo(
+ () => ({
+ ...(hasToolbox ? {
+ toolbox: {
+ right: 10,
+ feature: {
+ dataZoom: {
+ yAxisIndex: 'none'
+ },
+ ...additionalToolboxItems,
+ }
+ }
+ } : {}),
+ color: chartColorsArray,
+ legend: { show: false },
+ tooltip: {
+ trigger: 'axis',
+ formatter: tooltip || tooltipFormatter,
+ extraCssText:
+ 'font-family: "Mono-Regular"; border-radius: 6px; padding: 4px 12px;',
+ borderColor: 'transparent',
+ },
+ xAxis: {
+ name: props.xAxisTitle,
+ ...xAxisDefaults(xAxisLabelFormatter),
+ type: 'category',
+ axisTick: { show: !hideVerticalLines },
+ splitLine: {
+ show: !hideVerticalLines,
+ lineStyle: { color: graphs.backgrounds.ashGrey },
+ },
+ data: graphData && handleXAxisData(graphData),
+ },
+ ...(yAxisMax
+ ? {
+ yAxis: {
+ max: yAxisMax,
+ name: props.yAxisTitle,
+ ...yAxisDefaults(yAxisLabelFormatter),
+ axisTick: { show: false },
+ splitLine: {
+ lineStyle: { color: graphs.backgrounds.ashGrey },
+ },
+ },
+ }
+ : {
+ yAxis: {
+ name: props.yAxisTitle,
+ ...yAxisDefaults(yAxisLabelFormatter),
+ axisTick: { show: false },
+ splitLine: {
+ lineStyle: { color: graphs.backgrounds.ashGrey },
+ },
+ },
+ }),
+ series:
+ graphData &&
+ (groupByName
+ ? graphData.reduce((final: any, rw: any, index: number) => {
+ Object.keys(rw.data).forEach((key) => {
+ const mappedSeriesData = final.findIndex(
+ (dp: any) => dp.name === key
+ );
+ if (mappedSeriesData >= 0) {
+ final[mappedSeriesData].data = [
+ ...final[mappedSeriesData].data,
+ rw.data[key],
+ ];
+ } else {
+ final.push({
+ ...defaultSeriesOptions(rw, index),
+ data: [rw.data[key]],
+ name: key,
+ });
+ }
+ });
+
+ return final;
+ }, [])
+ : graphData.map((rw: TBarChartDataItem, index: number) => ({
+ ...defaultSeriesOptions(rw, index),
+ name: rw.name,
+ data: Object.values(rw.data),
+ }))),
+ }),
+ [graphData]
+ );
+
+ return (
+
+ {renderEmptyMessage()}
+ {showLegendTop && !!stateLegendItems?.length && (
+
+
+
+ )}
+
+ {xSecondaryAxisTitle && (
+
{xSecondaryAxisTitle}
+ )}
+ {showLegend && !!stateLegendItems?.length && (
+
+ )}
+
+ );
+};
+
+export default BarChart;
diff --git a/arthur_bench/server/js/packages/components/core/Charts/BarChart/index.ts b/arthur_bench/server/js/packages/components/core/Charts/BarChart/index.ts
new file mode 100644
index 0000000..95124c2
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/core/Charts/BarChart/index.ts
@@ -0,0 +1,3 @@
+import BarChart from './BarChart';
+
+export default BarChart;
diff --git a/arthur_bench/server/js/packages/components/core/Charts/Legend/Legend.tsx b/arthur_bench/server/js/packages/components/core/Charts/Legend/Legend.tsx
new file mode 100644
index 0000000..65bb08b
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/core/Charts/Legend/Legend.tsx
@@ -0,0 +1,52 @@
+import React from 'react';
+import { useFela } from 'react-fela';
+import { styles } from './styles';
+
+export enum ELegendItemShape {
+ CIRCLE = 'circle',
+ SQUARE = 'square',
+ LINE = 'line',
+ DASH = 'dash',
+}
+
+export type LegendItem = {
+ color: string;
+ name: string;
+ subtitle?: string;
+ shape: ELegendItemShape;
+};
+
+export type LegendItems = Array;
+
+type LegendProps = {
+ items: LegendItems;
+};
+
+const Legend = (props: LegendProps) => {
+ const { css } = useFela();
+
+ return (
+
+ {props.items.map((item: LegendItem) => (
+
+
+
+ {item.name}
+ {item.subtitle && (
+
+ {item.subtitle}
+
+ )}
+
+
+ ))}
+
+ );
+};
+
+export default Legend;
diff --git a/arthur_bench/server/js/packages/components/core/Charts/Legend/styles.ts b/arthur_bench/server/js/packages/components/core/Charts/Legend/styles.ts
new file mode 100644
index 0000000..6189f35
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/core/Charts/Legend/styles.ts
@@ -0,0 +1,83 @@
+import primary from 'resources/colors/Arthur/primary';
+import { MONO } from 'resources/fonts';
+
+export const styles = {
+ root: {
+ display: 'flex',
+ marginTop: '10px',
+ alignItems: 'start',
+ justifyContent: 'center',
+ flexWrap: 'wrap',
+ fontFamily: MONO,
+ fontSize: '12px',
+ },
+ itemRoot: {
+ display: 'flex',
+ marginRight: '30px',
+ ':last-child': {
+ marginRight: 0,
+ },
+ },
+ itemBase: (color: string) => ({
+ display: 'inline-block',
+ marginRight: '8px',
+ backgroundColor: color,
+ borderColor: color,
+ }),
+ itemHolder: {
+ display: 'flex',
+ flexDirection: 'column',
+ },
+ subtitle: {
+ marginTop: '4px',
+ color: primary.raisin,
+ fontSize: '10px',
+ },
+ item: {
+ line: {
+ width: '12px',
+ height: '2.5px',
+ marginTop: '7px',
+ },
+ circle: {
+ borderRadius: '10px',
+ width: '7px',
+ height: '7px',
+ borderWidth: '3px',
+ marginTop: '2px',
+ borderStyle: 'solid',
+ backgroundColor: 'transparent',
+ },
+ square: {
+ width: '12px',
+ height: '12px',
+ marginTop: '2px',
+ },
+ dash: {
+ width: '12px',
+ height: '2.5px',
+ marginLeft: '17px',
+ marginRight: '25px',
+ marginTop: '7px',
+ position: 'relative',
+ ':after': {
+ content: '""',
+ position: 'absolute',
+ left: '-16px',
+ display: 'inline-block',
+ width: '12px',
+ height: '2.5px',
+ backgroundColor: 'inherit',
+ },
+ ':before': {
+ content: '""',
+ position: 'absolute',
+ left: '16px',
+ display: 'inline-block',
+ width: '12px',
+ height: '2.5px',
+ backgroundColor: 'inherit',
+ },
+ },
+ },
+};
diff --git a/arthur_bench/server/js/packages/components/core/Charts/LineChart/LineChart.stories.tsx b/arthur_bench/server/js/packages/components/core/Charts/LineChart/LineChart.stories.tsx
new file mode 100644
index 0000000..7cf2a69
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/core/Charts/LineChart/LineChart.stories.tsx
@@ -0,0 +1,107 @@
+import React from 'react';
+import LineChart from './LineChart';
+import { EAxis, TMarkAreaData } from '../constants';
+import { ELegendItemShape } from '../Legend/Legend';
+import primary from 'resources/colors/Arthur/primary';
+import { MarkerStatisticType } from 'echarts/types/src/component/marker/MarkerModel';
+import secondary from 'resources/colors/Arthur/secondary';
+
+export default {
+ title: 'Arthur/Core/Chart/Line',
+ component: LineChart,
+};
+
+const graphData = [
+ {
+ name: 'Line 1',
+ subTitle: 'Reference Group',
+ data: [
+ [0, 150],
+ [1, 230],
+ [3, 224],
+ [4, 218],
+ [5, 135],
+ ] as Array<[number, number]>,
+ },
+];
+
+const markArea = {
+ axis: EAxis.Y,
+ data: [100, 200],
+} as TMarkAreaData;
+
+const legendItem = {
+ name: 'Female',
+ color: primary.purple,
+ shape: ELegendItemShape.LINE,
+ subtitle: 'Reference Group',
+};
+
+const legendItem2 = {
+ name: 'Max',
+ color: primary.raisin,
+ shape: ELegendItemShape.DASH,
+};
+
+const legendItem3 = {
+ name: 'Highlighted area',
+ color: primary.ashGrey,
+ shape: ELegendItemShape.SQUARE,
+};
+
+const legendItem4 = {
+ name: 'Max Point',
+ color: primary.purple,
+ shape: ELegendItemShape.CIRCLE,
+ subtitle: 'Reference Group',
+};
+
+const legendItem5 = {
+ name: 'Average',
+ color: 'orange',
+ shape: ELegendItemShape.DASH,
+};
+
+const markLineData = [
+ { type: 'average' as MarkerStatisticType, lineStyle: { color: 'orange' } },
+ { type: 'max' as MarkerStatisticType },
+];
+
+export const Default = () => (
+
+);
+
+export const NoData = () => (
+
+);
+
+export const PointOnDist = () => (
+
+);
diff --git a/arthur_bench/server/js/packages/components/core/Charts/LineChart/LineChart.tsx b/arthur_bench/server/js/packages/components/core/Charts/LineChart/LineChart.tsx
new file mode 100644
index 0000000..3a1e44c
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/core/Charts/LineChart/LineChart.tsx
@@ -0,0 +1,340 @@
+import React, {useEffect, useState} from 'react';
+import merge from 'lodash.merge';
+import EChartsReact from 'echarts-for-react';
+import {useTranslation} from 'react-i18next';
+import {useFela} from 'react-fela';
+
+import graphs, {chartColorsArray} from 'resources/colors/Arthur/graphs';
+import primary from 'resources/colors/Arthur/primary';
+import secondary from 'resources/colors/Arthur/secondary';
+
+import Legend, {ELegendItemShape, LegendItems} from '../Legend/Legend';
+import {TGraphDataItem, TLineChartProps} from './typings';
+import {
+ EChartType,
+ emptyMessageStyles,
+ LOADING_OPTIONS,
+ rootStyles,
+ secondaryTitle,
+ xAxisDefaults,
+ yAxisDefaults,
+} from '../constants';
+import Loading from '../../../compound/Loading';
+
+const LineChart = (props: TLineChartProps) => {
+ const { t } = useTranslation();
+ const { css } = useFela();
+ const {
+ id,
+ dataTestId,
+ isLoading,
+ height = '400px',
+ options,
+ markArea,
+ markLine,
+ graphData,
+ xAxisData,
+ hideVerticalLines,
+ tooltipFormatter,
+ xAxisLabelFormatter,
+ yAxisLabelFormatter,
+ legendItems = [],
+ showLegend,
+ showMaxPoint,
+ notMerge,
+ emptyMessage,
+ xSecondaryAxisTitle,
+ xAxisType = 'value',
+ xAxisMax,
+ xAxisMin,
+ yAxisMax,
+ addMarkLineLegendItems = false,
+ markLineShape = ELegendItemShape.DASH,
+ markPoint,
+ markLineColor,
+ disableTooltip = false,
+ xAxisScale = false,
+ showMarkLineLabel = false,
+ hideHoverEffects = false,
+ smoothData = false,
+ hasToolbox = false,
+
+ additionalToolboxItems = {},
+ } = props;
+ const [stateLegendItems, setLegendItems] =
+ useState(legendItems);
+ const [graphOptions, setGraphOptions] = useState | null>(null);
+
+ const defaultTooltipFormatter = (
+ event: { data: [number | string, number | string] | string | number }[]
+ ) => {
+ let yValue = '';
+ let xValue = '';
+ const eventDataItem = event[0]?.data;
+
+ if (!Array.isArray(eventDataItem)) {
+ xValue = `${eventDataItem || 'no data'}`;
+ } else {
+ yValue = `${eventDataItem[0] || ''}`;
+ xValue = `${eventDataItem[1] || 'no data'}`;
+ }
+
+ return `${yValue}
+ ${xValue} `;
+ };
+
+ const handleAddLegendItems = () => {
+ if (!showLegend || !graphData) {
+ return;
+ }
+
+ const moreLegendItems: LegendItems = [];
+
+ graphData.map((line: TGraphDataItem, index: number) => {
+ moreLegendItems.push({
+ color: line.name.toLowerCase().includes('baseline') || line.isReference
+ ? graphs.backgrounds.raisin
+ : chartColorsArray[index],
+ name: line.name,
+ subtitle: graphData[index]?.subtitle || '',
+ shape: line.isReference ? ELegendItemShape.DASH : ELegendItemShape.LINE,
+ });
+ });
+
+ if (addMarkLineLegendItems) {
+ markLine?.map((dp: any) => {
+ moreLegendItems.push({
+ color: dp.lineStyle.color,
+ name: `${dp.name} Ref Data`,
+ shape: markLineShape,
+ });
+ });
+ }
+
+ setLegendItems([...moreLegendItems, ...legendItems]);
+ };
+
+ useEffect(() => {
+ setGraphOptions(null);
+ const defaultOptions: Record = {
+ ...(hasToolbox ? {
+ toolbox: {
+ right: 10,
+ feature: {
+ dataZoom: {
+ yAxisIndex: 'none'
+ },
+ ...additionalToolboxItems,
+ }
+ }
+ } : {}),
+ useUTC: true,
+ color: chartColorsArray,
+ legend: { show: false },
+ ...(!disableTooltip && {
+ tooltip: {
+ trigger: 'point',
+ formatter: tooltipFormatter || defaultTooltipFormatter,
+ extraCssText:
+ 'font-family: "Mono-Regular"; border-radius: 6px; padding: 4px 12px;',
+ borderColor: 'transparent',
+ },
+ }),
+ xAxis: {
+ axisPointer: {
+ show: true,
+ snap: true,
+ label: {
+ show: false,
+ },
+ },
+ name: props.xAxisTitle,
+ ...xAxisDefaults(xAxisLabelFormatter),
+ ...(xAxisData && { data: xAxisData }),
+ ...(xAxisMax && { max: xAxisMax }),
+ ...(xAxisMin && { min: xAxisMin }),
+ scale: xAxisScale,
+ type: xAxisType,
+ axisTick: { show: !hideVerticalLines },
+ splitLine: {
+ show: !hideVerticalLines,
+ lineStyle: { color: graphs.backgrounds.ashGrey },
+ },
+ },
+ ...(yAxisMax
+ ? {
+ yAxis: {
+ max: yAxisMax,
+ name: props.yAxisTitle,
+ ...yAxisDefaults(yAxisLabelFormatter),
+ axisTick: { show: false },
+ splitLine: {
+ lineStyle: { color: graphs.backgrounds.ashGrey },
+ },
+ },
+ }
+ : {
+ yAxis: {
+ name: props.yAxisTitle,
+ ...yAxisDefaults(yAxisLabelFormatter),
+ axisTick: { show: false },
+ splitLine: {
+ lineStyle: { color: graphs.backgrounds.ashGrey },
+ },
+ },
+ }),
+
+ series: graphData?.map((line: TGraphDataItem, index: number) => ({
+ data: line.data,
+ name: line.name,
+ type: EChartType.LINE,
+ symbolSize: 14,
+ smooth: smoothData,
+ color: chartColorsArray[index],
+ emphasis: {
+ itemStyle: {
+ color: 'white',
+ borderColor: chartColorsArray[index] || 'black',
+ borderWidth: 2,
+ shadowColor: 'rgba(0, 0, 0, 0.5)',
+ shadowBlur: 2,
+ },
+ },
+ symbol: line.data.length === 1 ? 'circle' : 'emptyCircle',
+ showSymbol: line.data.length === 1,
+ ...(line.isReference
+ ? {
+ lineStyle: {
+ normal: {
+ color: primary.raisin,
+ type: 'dashed',
+ },
+ },
+ }
+ : {}),
+ ...(showMaxPoint &&
+ line.hasMaxValue && {
+ markPoint: {
+ symbol: 'circle',
+ symbolSize: 8,
+ label: { show: false },
+ data: [
+ {
+ type: line.isMaxValuePositive
+ ? 'max'
+ : 'min',
+ },
+ ],
+ itemStyle: {
+ color: 'red',
+ },
+ },
+ }),
+ ...(markPoint ||
+ (hideHoverEffects && {
+ symbolSize: 0,
+ color: [secondary.variant.grey.disabled],
+ emphasis: {
+ itemStyle: {
+ color: secondary.variant.grey.disabled,
+ borderWidth: 0,
+ },
+ },
+ markPoint: {
+ symbol: 'circle',
+ symbolSize: 10,
+ label: { show: false },
+ data: markPoint,
+ itemStyle: {
+ color: primary.white,
+ borderWidth: 2,
+ borderColor: primary.purple,
+ },
+ },
+ })),
+ ...(!index && {
+ ...(markLine && {
+ markLine: {
+ silent: true,
+ symbol: 'none',
+ lineStyle: {
+ color: markLineColor || primary.raisin,
+ width: 2,
+ type:
+ markLineShape === ELegendItemShape.LINE
+ ? 'solid'
+ : 'dashed',
+ },
+ label: { show: showMarkLineLabel },
+ data: markLine,
+ },
+ }),
+ ...(markArea && {
+ markArea: {
+ emphasis: { disabled: true },
+ itemStyle: {
+ color: graphs.accents.purple,
+ opacity: 0.1,
+ },
+ data: [
+ [
+ { [markArea.axis]: markArea.data[0] },
+ { [markArea.axis]: markArea.data[1] },
+ ],
+ ],
+ },
+ }),
+ }),
+ })),
+ };
+ setGraphOptions(defaultOptions);
+ handleAddLegendItems();
+ }, [graphData, markLine]);
+
+ const renderEmptyMessage = () => {
+ const graphEmptyLineData =
+ !graphData?.length ||
+ graphData.every((line: TGraphDataItem) => !line.data.length);
+
+ if (graphEmptyLineData && !isLoading) {
+ return (
+
+ {emptyMessage || t('charts.emptyMessage')}
+
+ );
+ }
+
+ return null;
+ };
+
+ if (!graphOptions) {
+ return ;
+ }
+
+ return (
+
+ {renderEmptyMessage()}
+
+
+ {xSecondaryAxisTitle && (
+
{xSecondaryAxisTitle}
+ )}
+ {showLegend && !!stateLegendItems.length && (
+
+ )}
+
+ );
+};
+
+export default LineChart;
diff --git a/arthur_bench/server/js/packages/components/core/Charts/LineChart/components/MultiLineTooltip.tsx b/arthur_bench/server/js/packages/components/core/Charts/LineChart/components/MultiLineTooltip.tsx
new file mode 100644
index 0000000..7c6b62f
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/core/Charts/LineChart/components/MultiLineTooltip.tsx
@@ -0,0 +1,48 @@
+import React from 'react';
+import { CssFunction } from 'react-fela';
+import { dateLabelFormatter } from 'utils/format/date-label-formatter';
+import { ETimeInterval } from '../../../../compound/ModeSelector/ModeSelector';
+import { styles } from './styles';
+
+export type TooltipDataPoint = {
+ data: Array;
+ color: string;
+ seriesName: string;
+ axisValue: string;
+};
+
+type Props = {
+ event: TooltipDataPoint[];
+ css: CssFunction<{}, {}>;
+ data: any;
+ timeInterval: ETimeInterval;
+};
+
+const MultiLineTooltip = ({ event, css, data, timeInterval }: Props) => {
+ const timeStamp = event[0]?.axisValue;
+ const dateDisplay = dateLabelFormatter(timeStamp, timeInterval);
+ return (
+
+ {timeStamp &&
{dateDisplay}
}
+ {event.map((dataPoint: TooltipDataPoint, i: number) => {
+ return (
+
+
+
+ {data[i].name}:
+ {' '}
+ {parseFloat(dataPoint.data[1]).toFixed(7)}
+
+ );
+ })}
+
+ );
+};
+
+export default MultiLineTooltip;
diff --git a/arthur_bench/server/js/packages/components/core/Charts/LineChart/components/styles.ts b/arthur_bench/server/js/packages/components/core/Charts/LineChart/components/styles.ts
new file mode 100644
index 0000000..30a0566
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/core/Charts/LineChart/components/styles.ts
@@ -0,0 +1,25 @@
+import primary from 'resources/colors/Arthur/primary';
+import secondary from 'resources/colors/Arthur/secondary';
+import { GRAPHIK } from 'resources/fonts';
+
+export const styles = {
+ tooltipDate: {
+ marginBottom: '10px',
+ fontSize: '12px',
+ },
+ tooltipDatapoint: {
+ fontSize: '14px',
+ color: primary.eggplant,
+ fontFamily: GRAPHIK,
+ },
+ tooltipDatapointLabel: {
+ color: secondary.variant.eggplant.light,
+ },
+ tooltipDataColor: {
+ display: 'inline-block',
+ marginRight: '8px',
+ width: '8px',
+ height: '8px',
+ borderRadius: '8px',
+ },
+};
diff --git a/arthur_bench/server/js/packages/components/core/Charts/LineChart/index.ts b/arthur_bench/server/js/packages/components/core/Charts/LineChart/index.ts
new file mode 100644
index 0000000..4b39c4f
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/core/Charts/LineChart/index.ts
@@ -0,0 +1,4 @@
+import LineChart from './LineChart';
+export type { TGraphDataItem } from './typings';
+
+export default LineChart;
diff --git a/arthur_bench/server/js/packages/components/core/Charts/LineChart/typings.d.ts b/arthur_bench/server/js/packages/components/core/Charts/LineChart/typings.d.ts
new file mode 100644
index 0000000..8ddf987
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/core/Charts/LineChart/typings.d.ts
@@ -0,0 +1,58 @@
+import { TMarkAreaData } from '../constants';
+import { ELegendItemShape } from '../Legend/Legend';
+
+export type LineSeriesModel = {
+ name: string;
+ seriesIndex: number;
+ option: any;
+};
+
+export type TGraphDataItem = {
+ name: string;
+ data: Array<[number, number] | [number]>;
+ subtitle?: string;
+ hasMaxValue?: boolean;
+ isMaxValuePositive?: boolean;
+ isReference?: boolean;
+ batchId?: string;
+};
+
+export type TLineChartProps = {
+ isLoading?: boolean;
+ showLegend?: boolean;
+ showMaxPoint?: boolean;
+ xAxisTitle?: string;
+ yAxisTitle?: string;
+ height?: string;
+ options?: EChartsOption;
+ tooltipFormatter?: any;
+ xAxisLabelFormatter?: string | ((value: string, index: number) => string);
+ yAxisLabelFormatter?: string | ((value: string, index: number) => string);
+ markArea?: TMarkAreaData;
+ markLine?: MarkLine1DDataItemOption[];
+ xAxisData?: string[];
+ graphData: Array;
+ hideVerticalLines?: boolean;
+ legendItems?: LegendItems;
+ xAxisType?: string;
+ xSecondaryAxisTitle?: string;
+ emptyMessage?: string;
+ notMerge?: boolean;
+ xAxisLabelOptions?: Record;
+ xAxisMax?: number | string | Function;
+ xAxisMin?: number | string | Function;
+ yAxisMax?: number;
+ addMarkLineLegendItems?: boolean;
+ markLineShape?: ELegendItemShape;
+ markPoint?: any;
+ markLineColor?: string;
+ disableTooltip?: boolean;
+ xAxisScale?: boolean;
+ showMarkLineLabel?: boolean;
+ hideHoverEffects?: boolean;
+ smoothData?: boolean;
+ hasToolbox?: boolean;
+ additionalToolboxItems?: any;
+ id?: string;
+ dataTestId?: string;
+};
diff --git a/arthur_bench/server/js/packages/components/core/Charts/constants.ts b/arthur_bench/server/js/packages/components/core/Charts/constants.ts
new file mode 100644
index 0000000..6f39b02
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/core/Charts/constants.ts
@@ -0,0 +1,106 @@
+import primary from 'resources/colors/Arthur/primary';
+import secondary from 'resources/colors/Arthur/secondary';
+import graphs from 'resources/colors/Arthur/graphs';
+import { GRAPHIK, ROBOTO } from 'resources/fonts';
+
+export const LOADING_OPTIONS = {
+ text: '',
+ color: primary.purple,
+ textColor: primary.white,
+ maskColor: 'transparent',
+};
+
+export const rootStyles = {
+ position: 'relative',
+};
+
+export const emptyMessageStyles = {
+ position: 'absolute',
+ margin: '40px 150px',
+ textAlign: 'center',
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ fontFamily: ROBOTO,
+ color: primary.raisin,
+ fontSize: '14px',
+ zIndex: '2',
+};
+
+export const secondaryTitle = {
+ fontSize: '12px',
+ color: primary.raisin,
+ fontFamily: GRAPHIK,
+ marginTop: '4px',
+ textAlign: 'center',
+};
+
+export const legendTopContainer = {
+ width: '100%',
+ display: 'flex',
+ justifyContent: 'end',
+ paddingRight: '20px',
+ boxSizing: 'border-box',
+};
+
+export enum EChartsColorBy {
+ SERIES = 'series',
+ DATA = 'data',
+}
+
+export enum EAxis {
+ X = 'xAxis',
+ Y = 'yAxis',
+}
+
+export enum EChartType {
+ LINE = 'line',
+ BAR = 'bar',
+ SCATTER = 'scatter',
+}
+
+export const TEXT_COLOR = secondary.variant.eggplant.darker;
+
+export const xAxisDefaults = (formatter: any) => ({
+ nameLocation: 'center',
+ nameGap: 50,
+ type: 'value',
+ axisLine: { show: false, lineStyle: { color: graphs.backgrounds.ashGrey } },
+ nameTextStyle: {
+ color: graphs.backgrounds.raisin,
+ fontFamily: GRAPHIK,
+ },
+ axisLabel: {
+ ...(formatter && { formatter }),
+ hideOverlap: true,
+ color: TEXT_COLOR,
+ fontSize: 12,
+ fontFamily: ROBOTO,
+ },
+});
+
+export const yAxisDefaults = (formatter: any) => ({
+ nameLocation: 'center',
+ nameGap: 40,
+ type: 'value',
+ axisLine: { show: false, lineStyle: { color: graphs.backgrounds.ashGrey } },
+ nameTextStyle: {
+ color: graphs.backgrounds.raisin,
+ fontFamily: GRAPHIK,
+ },
+ axisLabel: {
+ ...(formatter && { formatter }),
+ color: TEXT_COLOR,
+ fontSize: 12,
+ fontFamily: ROBOTO,
+ },
+});
+
+export type TMarkAreaData = {
+ axis: EAxis;
+ data: [number | string, number | string];
+};
diff --git a/arthur_bench/server/js/packages/components/core/Checkbox/Checkbox.styles.ts b/arthur_bench/server/js/packages/components/core/Checkbox/Checkbox.styles.ts
new file mode 100644
index 0000000..4b88b3c
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/core/Checkbox/Checkbox.styles.ts
@@ -0,0 +1,57 @@
+import secondary from 'resources/colors/Arthur/secondary';
+import { MONO } from 'resources/fonts';
+
+const styles = {
+ root: {
+ display: 'flex',
+ alignItems: 'center',
+ overflow: 'hidden',
+ },
+ checkboxWrap: {
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ position: 'relative',
+ },
+ checkbox: (theme: any, color: string) => ({
+ width: '18px',
+ height: '18px',
+ border: '2px solid #E4E0E4',
+ appearance: 'none',
+ display: 'inline-block',
+ boxSizing: 'border-box',
+ margin: 0,
+ padding: 0,
+ borderRadius: '2px',
+ cursor: 'pointer',
+ ':hover': {
+ borderColor: color ? color : theme.color_1,
+ },
+ ':checked': {
+ borderColor: color ? color : theme.color_1,
+ },
+ }),
+ label: {
+ cursor: 'pointer',
+ marginLeft: '10px',
+ fontSize: '14px',
+ fontFamily: MONO,
+ overflow: 'hidden',
+ whiteSpace: 'nowrap',
+ textOverflow: 'ellipsis',
+ },
+ icon: (theme: any, color: string) => ({
+ display: 'block',
+ transform: 'rotate(-45deg)',
+ position: 'absolute',
+ top: '4.5px',
+ width: '8px',
+ height: '4px',
+ border: `2px solid ${color ? color : theme.color_1}`,
+ borderRight: 'none',
+ borderTop: 'none',
+ pointerEvents: 'none',
+ }),
+};
+
+export default styles;
diff --git a/arthur_bench/server/js/packages/components/core/Checkbox/Checkbox.tsx b/arthur_bench/server/js/packages/components/core/Checkbox/Checkbox.tsx
new file mode 100644
index 0000000..5ab336c
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/core/Checkbox/Checkbox.tsx
@@ -0,0 +1,31 @@
+import React from 'react';
+import { useFela } from 'react-fela';
+import styles from './Checkbox.styles';
+import { CheckboxProps } from './typings';
+
+const Checkbox = (props: CheckboxProps) => {
+ const { clickHandler, checked, label, color } = props;
+ const { css, theme } = useFela();
+
+ const handleChange = (e: React.ChangeEvent) => {
+ clickHandler && clickHandler(e.target.checked);
+ };
+
+ return (
+
+ );
+};
+
+export default Checkbox;
diff --git a/arthur_bench/server/js/packages/components/core/Checkbox/index.ts b/arthur_bench/server/js/packages/components/core/Checkbox/index.ts
new file mode 100644
index 0000000..36fa16d
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/core/Checkbox/index.ts
@@ -0,0 +1,3 @@
+import Checkbox from './Checkbox';
+
+export default Checkbox;
diff --git a/arthur_bench/server/js/packages/components/core/Checkbox/typings.d.ts b/arthur_bench/server/js/packages/components/core/Checkbox/typings.d.ts
new file mode 100644
index 0000000..7650a60
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/core/Checkbox/typings.d.ts
@@ -0,0 +1,15 @@
+import { FelaStyle } from 'react-fela';
+
+export interface CheckboxProps {
+ checked?: boolean;
+ clickHandler?: (checked: boolean) => void;
+ testId?: string;
+ label?: string;
+ checkboxStyle?: FelaStyle;
+ iconStyle?: FelaStyle;
+ labelStyle?: FelaStyle;
+ styles?: any;
+ color?: any;
+ labelFontSize?: string;
+ customDefaultColor?: string;
+}
diff --git a/arthur_bench/server/js/packages/components/core/Collapsible/Collapsible.tsx b/arthur_bench/server/js/packages/components/core/Collapsible/Collapsible.tsx
new file mode 100644
index 0000000..d3f5f81
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/core/Collapsible/Collapsible.tsx
@@ -0,0 +1,56 @@
+import React, { useEffect, useRef, useState } from 'react';
+import { useFela } from 'react-fela';
+
+import { CollapsibleProps } from './typings';
+import { defaultStyle } from './styles';
+
+type TCollapsibleContext = {
+ updateSize: (() => void) | null;
+};
+
+export const CollapsibleContext = React.createContext({
+ updateSize: null,
+});
+
+function Collapsible(props: CollapsibleProps) {
+ const { css } = useFela();
+ const [height, setHeight] = useState(0);
+ const contentRef = useRef(null);
+ const { open, testId = 'collapsible-toggle' } = props;
+
+ const combinedStyle = {
+ ...defaultStyle(open),
+ ...(props.style ? props.style : {}),
+ };
+
+ useEffect(() => {
+ if (open) {
+ setHeight(contentRef?.current?.scrollHeight);
+ } else {
+ setHeight(0);
+ }
+ }, [open, props.children]);
+
+ const updateSize = () => {
+ setTimeout(() => {
+ setHeight(
+ (contentRef?.current?.firstChild as HTMLElement)?.scrollHeight
+ );
+ }, 100);
+ };
+
+ return (
+
+
+ {props.children}
+
+
+ );
+}
+
+export default Collapsible;
diff --git a/arthur_bench/server/js/packages/components/core/Collapsible/index.ts b/arthur_bench/server/js/packages/components/core/Collapsible/index.ts
new file mode 100644
index 0000000..bd95822
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/core/Collapsible/index.ts
@@ -0,0 +1,3 @@
+import Collapsible from './Collapsible';
+
+export default Collapsible;
diff --git a/arthur_bench/server/js/packages/components/core/Collapsible/styles.ts b/arthur_bench/server/js/packages/components/core/Collapsible/styles.ts
new file mode 100644
index 0000000..c1e578e
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/core/Collapsible/styles.ts
@@ -0,0 +1,5 @@
+export const defaultStyle = (open?: boolean) => ({
+ width: '100%',
+ overflow: open ? 'visible' : 'hidden',
+ transition: 'height ease 0.2s',
+});
diff --git a/arthur_bench/server/js/packages/components/core/Collapsible/typings.d.ts b/arthur_bench/server/js/packages/components/core/Collapsible/typings.d.ts
new file mode 100644
index 0000000..ff35f7c
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/core/Collapsible/typings.d.ts
@@ -0,0 +1,6 @@
+import React, { FunctionComponent, HTMLAttributes } from 'react';
+
+export interface CollapsibleProps extends HTMLAttributes {
+ testId?: string;
+ open: boolean;
+}
diff --git a/arthur_bench/server/js/packages/components/core/Dropdown/Dropdown.tsx b/arthur_bench/server/js/packages/components/core/Dropdown/Dropdown.tsx
new file mode 100644
index 0000000..6d2cec4
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/core/Dropdown/Dropdown.tsx
@@ -0,0 +1,112 @@
+import React, { useEffect, useState, useRef } from 'react';
+import ReactDOM from 'react-dom';
+import { useFela } from 'react-fela';
+import styles from './styles';
+import { TThemeType } from 'resources/theme/types';
+import Icon, { EIconType } from '../Icon';
+import primary from 'resources/colors/Arthur/primary';
+import useOnClickOutside from "api/useOnClickOutside";
+
+type TDropdownProps = {
+ children: any;
+ isOpen?: boolean;
+ className?: string;
+ handleClose: () => void;
+ actionRef: React.RefObject;
+ hasHeader?: boolean;
+ preventClickOutside?: boolean;
+ headerTitle?: string;
+ alignRight?: boolean;
+ inlineStyles?: Object;
+};
+
+export type TPosition = {
+ x: number;
+ y: number;
+};
+
+const Dropdown = (
+ props: TDropdownProps
+): React.ReactElement | null => {
+ const [position, setPosition] = useState({ x: 0, y: 0 });
+ const { css } = useFela();
+ const {
+ isOpen,
+ children,
+ actionRef,
+ className = '',
+ hasHeader,
+ headerTitle,
+ preventClickOutside,
+ handleClose,
+ alignRight
+ } = props;
+
+ const containerRef = useRef(null);
+
+ useOnClickOutside(containerRef, () => {
+ if (!preventClickOutside) {
+ handleClose();
+ }
+ });
+
+ const classNames = styles(position);
+
+ const handleResizeEffect = () => {
+ if (actionRef.current && containerRef.current) {
+ const pos = actionRef.current.getBoundingClientRect();
+ const y = pos.top + actionRef.current.offsetHeight + window.scrollY;
+ const x = alignRight
+ ? pos.left -
+ (containerRef.current?.offsetWidth -
+ actionRef.current.offsetWidth)
+ : pos.left + window.scrollX;
+ setPosition({ x, y });
+ }
+ };
+
+ useEffect(() => handleResizeEffect(), [children]);
+
+ useEffect(() => {
+ window.addEventListener('resize', handleResizeEffect);
+ return () => window.removeEventListener('resize', handleResizeEffect);
+ }, []);
+
+ const root = document.querySelector('#root');
+
+ const renderContent = () => (
+
+ {hasHeader && (
+
+ {headerTitle && (
+
+ {headerTitle}
+
+ )}
+
handleClose()}
+ className={css(classNames.closeIcon)}
+ >
+
+
+
+ )}
+ {children}
+
+ );
+
+ if (!isOpen) return null;
+
+ return ReactDOM.createPortal(renderContent(), root!);
+};
+
+export default Dropdown;
diff --git a/arthur_bench/server/js/packages/components/core/Dropdown/index.ts b/arthur_bench/server/js/packages/components/core/Dropdown/index.ts
new file mode 100644
index 0000000..3cb5574
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/core/Dropdown/index.ts
@@ -0,0 +1,3 @@
+import Dropdown from './Dropdown';
+
+export default Dropdown;
diff --git a/arthur_bench/server/js/packages/components/core/Dropdown/styles.ts b/arthur_bench/server/js/packages/components/core/Dropdown/styles.ts
new file mode 100644
index 0000000..d5abee4
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/core/Dropdown/styles.ts
@@ -0,0 +1,35 @@
+import primary from 'resources/colors/Arthur/primary';
+import secondary from 'resources/colors/Arthur/secondary';
+import { MONO_MEDIUM } from 'resources/fonts';
+import { TPosition } from './Dropdown';
+
+const styles = ({ x, y }: TPosition) => {
+ return {
+ root: {
+ zIndex: 101,
+ position: 'absolute',
+ left: `${x}px`,
+ top: `${y}px`,
+ filter: 'drop-shadow(0px 4px 8px rgba(26, 0, 22, 0.1))',
+ background: primary.white,
+ },
+ header: {
+ display: 'flex',
+ justifyContent: 'space-between',
+ padding: '12px 16px 12px 24px',
+ background: primary.white,
+ },
+ title: {
+ fontFamily: MONO_MEDIUM,
+ fontSize: '12px',
+ textTransform: 'uppercase',
+ letterSpacing: '0.05em',
+ color: secondary.black,
+ },
+ closeIcon: {
+ cursor: 'pointer',
+ },
+ };
+};
+
+export default styles;
diff --git a/arthur_bench/server/js/packages/components/core/Field/Field.styles.ts b/arthur_bench/server/js/packages/components/core/Field/Field.styles.ts
new file mode 100644
index 0000000..68ed869
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/core/Field/Field.styles.ts
@@ -0,0 +1,26 @@
+import primary from 'resources/colors/Arthur/primary';
+import { GRAPHIK } from 'resources/fonts';
+
+const styles = {
+ field: {
+ fontFamily: GRAPHIK,
+ color: primary.eggplant,
+ display: 'block',
+ },
+ fieldLabel: {
+ fontSize: '14px',
+ margin: 0,
+ padding: 0,
+ },
+ fieldSublabel: {
+ fontSize: '12px',
+ opacity: 0.8,
+ margin: 0,
+ marginTop: '4px',
+ padding: 0,
+ whiteSpace: 'wrap',
+ overflowX: 'hidden',
+ },
+};
+
+export default styles;
diff --git a/arthur_bench/server/js/packages/components/core/Field/Field.tsx b/arthur_bench/server/js/packages/components/core/Field/Field.tsx
new file mode 100644
index 0000000..b34ad64
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/core/Field/Field.tsx
@@ -0,0 +1,19 @@
+import React from 'react';
+import { useFela } from 'react-fela';
+import styles from './Field.styles';
+import { FieldProps } from './types';
+
+const Field = ({ label, sublabel }: FieldProps) => {
+ const { css } = useFela();
+
+ return (
+
+
{label}
+ {Boolean(sublabel) && (
+
{sublabel}
+ )}
+
+ );
+};
+
+export default Field;
diff --git a/arthur_bench/server/js/packages/components/core/Field/index.ts b/arthur_bench/server/js/packages/components/core/Field/index.ts
new file mode 100644
index 0000000..2c2414a
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/core/Field/index.ts
@@ -0,0 +1,3 @@
+import Field from './Field';
+
+export default Field;
diff --git a/arthur_bench/server/js/packages/components/core/Field/types.ts b/arthur_bench/server/js/packages/components/core/Field/types.ts
new file mode 100644
index 0000000..53a7988
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/core/Field/types.ts
@@ -0,0 +1,4 @@
+export type FieldProps = {
+ label: string;
+ sublabel?: any;
+};
diff --git a/arthur_bench/server/js/packages/components/core/HelpTile/HelpTile.tsx b/arthur_bench/server/js/packages/components/core/HelpTile/HelpTile.tsx
new file mode 100644
index 0000000..5694ef8
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/core/HelpTile/HelpTile.tsx
@@ -0,0 +1,74 @@
+import React, { useState } from 'react';
+import { useFela } from 'react-fela';
+
+import primary from 'resources/colors/Arthur/primary';
+import secondary from 'resources/colors/Arthur/secondary';
+import Icon from '../Icon';
+import checkIfEnter from 'utils/keypress-enter';
+
+import { HelpTileProps } from './typings';
+import { helpTileStyle } from './styles';
+
+function HelpTile(props: HelpTileProps) {
+ const { css } = useFela();
+ const [iconColor, setColor] = useState(primary.ashGrey);
+ const { disabled = false } = props;
+
+ const mouseOver = () => {
+ if (!disabled) {
+ setColor(secondary.blue);
+ }
+ };
+
+ const mouseLeave = () => {
+ setColor(primary.ashGrey);
+ };
+
+ const clickHandler = () => {
+ if (props.link && !disabled) {
+ window.open(props.link);
+ }
+ };
+
+ const handleKeypress = (e: any) => {
+ checkIfEnter(e, clickHandler);
+ };
+
+ const notButton = !props.link || disabled;
+
+ return (
+
+
+
+
+ {props.titleIcon ? props.titleIcon : null}
+ {props.title}
+
+
+ {props.description}
+
+
+ {props.icon ? (
+
+ ) : null}
+
+ {props.effort ? (
+
+ {props.effort}
+
+ ) : null}
+
+ );
+}
+
+export default HelpTile;
diff --git a/arthur_bench/server/js/packages/components/core/HelpTile/index.ts b/arthur_bench/server/js/packages/components/core/HelpTile/index.ts
new file mode 100644
index 0000000..573222a
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/core/HelpTile/index.ts
@@ -0,0 +1,5 @@
+import HelpTile from './HelpTile';
+import { HelpTileProps } from './typings';
+
+export type { HelpTileProps };
+export default HelpTile;
diff --git a/arthur_bench/server/js/packages/components/core/HelpTile/styles.ts b/arthur_bench/server/js/packages/components/core/HelpTile/styles.ts
new file mode 100644
index 0000000..59821a1
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/core/HelpTile/styles.ts
@@ -0,0 +1,62 @@
+import { FelaStyle } from 'react-fela';
+import primary from 'resources/colors/Arthur/primary';
+import { GRAPHIK } from 'resources/fonts';
+
+const helpTopDiv = (notButton: boolean): FelaStyle => ({
+ width: '100%',
+ height: '100%',
+ boxSizing: 'border-box',
+ backgroundColor: 'white',
+ padding: '24px 32px',
+ border: '1px solid #E4E0E4',
+ borderRadius: '4px',
+ display: 'flex',
+ flexDirection: 'column',
+ justifyContent: 'space-between',
+ cursor: notButton ? 'default' : 'pointer',
+ ':hover': {
+ boxShadow: notButton ? 'none' : '0px 4px 12px rgba(26, 0, 22, 0.1)',
+ },
+});
+
+const titleDiv: FelaStyle = {
+ display: 'flex',
+ justifyContent: 'space-between',
+ width: '100%',
+};
+
+const titleText = (disabled: boolean): FelaStyle => ({
+ fontFamily: GRAPHIK,
+ fontSize: '24px',
+ fontWeight: 400,
+ lineHeight: '38.4px',
+ color: disabled ? primary.ashGrey : primary.eggplant,
+ letterSpacing: '-0.64px',
+ margin: 0,
+ display: 'flex',
+ alignItems: 'center',
+});
+
+const subtitleText = (disabled: boolean): FelaStyle => ({
+ fontFamily: GRAPHIK,
+ fontSize: '14px',
+ fontWeight: 400,
+ lineHeight: '160%',
+ color: disabled ? primary.ashGrey : primary.eggplant,
+});
+
+const effortStyle = (disabled: boolean): FelaStyle => ({
+ display: 'flex',
+ alignItems: 'flex-end',
+ fontSize: '12px',
+ color: disabled ? primary.ashGrey : primary.eggplant,
+ opacity: 0.8,
+});
+
+export const helpTileStyle = {
+ helpTopDiv,
+ titleDiv,
+ titleText,
+ subtitleText,
+ effortStyle,
+};
diff --git a/arthur_bench/server/js/packages/components/core/HelpTile/typings.d.ts b/arthur_bench/server/js/packages/components/core/HelpTile/typings.d.ts
new file mode 100644
index 0000000..f6afba5
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/core/HelpTile/typings.d.ts
@@ -0,0 +1,13 @@
+import { ReactNode } from 'react';
+import { FelaStyle } from 'react-fela';
+import { EIconType } from '../Icon';
+
+export interface HelpTileProps {
+ title: string;
+ titleIcon?: ReactNode;
+ description: string | ReactNode;
+ link?: string;
+ icon?: EIconType;
+ effort?: string;
+ disabled?: boolean;
+}
diff --git a/arthur_bench/server/js/packages/components/core/Icon/Icon.tsx b/arthur_bench/server/js/packages/components/core/Icon/Icon.tsx
new file mode 100644
index 0000000..aed4b17
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/core/Icon/Icon.tsx
@@ -0,0 +1,39 @@
+import React from 'react';
+import IcomoonReact from 'icomoon-react';
+import { TIconProps } from './types';
+
+const Icon = (props: TIconProps): React.ReactElement => {
+ const {
+ color,
+ size,
+ icon,
+ className = '',
+ style,
+ clickHandler,
+ testId = 'arthurIcon',
+ } = props;
+ const { iconSet } = window;
+ return (
+ {}}
+ onClick={clickHandler}
+ data-testid={testId}
+ >
+
+
+ );
+};
+
+export default Icon;
diff --git a/arthur_bench/server/js/packages/components/core/Icon/constants.ts b/arthur_bench/server/js/packages/components/core/Icon/constants.ts
new file mode 100644
index 0000000..d65160d
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/core/Icon/constants.ts
@@ -0,0 +1 @@
+export const EYE_ICON = 'path://M7.99984 3C8.53027 3 9.03898 3.21071 9.41405 3.58579C9.78912 3.96086 9.99984 4.46957 9.99984 5C9.99984 5.53043 9.78912 6.03914 9.41405 6.41421C9.03898 6.78929 8.53027 7 7.99984 7C7.4694 7 6.9607 6.78929 6.58562 6.41421C6.21055 6.03914 5.99984 5.53043 5.99984 5C5.99984 4.46957 6.21055 3.96086 6.58562 3.58579C6.9607 3.21071 7.4694 3 7.99984 3M7.99984 0C11.2398 0 14.1465 2 15.3332 5C13.7398 9.05333 9.1665 11.04 5.11317 9.44667C3.07984 8.64667 1.4665 7.04 0.666504 5C1.85317 2 4.75984 0 7.99984 0ZM2.11984 5C3.7065 8.24667 7.63317 9.59333 10.8798 8C12.1865 7.36 13.2398 6.30667 13.8798 5C12.2932 1.75333 8.3665 0.406667 5.11984 2C3.81317 2.64 2.75984 3.69333 2.11984 5ZM5.99984 11.6667H4.6665V13H5.99984V11.6667ZM8.6665 11.6667H7.33317V13H8.6665V11.6667ZM11.3332 11.6667H9.99984V13H11.3332V11.6667Z';
\ No newline at end of file
diff --git a/arthur_bench/server/js/packages/components/core/Icon/index.ts b/arthur_bench/server/js/packages/components/core/Icon/index.ts
new file mode 100644
index 0000000..c68d203
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/core/Icon/index.ts
@@ -0,0 +1,4 @@
+import Icon from './Icon';
+export { EIconType } from './types';
+
+export default Icon;
diff --git a/arthur_bench/server/js/packages/components/core/Icon/readme.md b/arthur_bench/server/js/packages/components/core/Icon/readme.md
new file mode 100644
index 0000000..3906152
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/core/Icon/readme.md
@@ -0,0 +1,18 @@
+# Icon Component
+
+Usage
+
+1. Import Icon component whenever you need it to render
+2. Pass the right icon `type`, the complete list of available icon types can be found in file `types.ts`
+
+How to add new icon type
+
+1. go to https://icomoon.io/
+2. register account if you don't have one and authorize
+3. got to set redactor page https://icomoon.io/app/#/select
+4. drop there your new icons svg
+5. upload existing `selection.json` file from `packages/resources/icons/` folder
+6. reset colors for new icons
+7. click generate font
+8. upload `selection.json` from the generated archive to the `packages/resources/icons` folder
+9. add new icon types to the existing `Icon` type definitions `packages/ui/Icon/types.ts`
diff --git a/arthur_bench/server/js/packages/components/core/Icon/types.ts b/arthur_bench/server/js/packages/components/core/Icon/types.ts
new file mode 100644
index 0000000..f0e021c
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/core/Icon/types.ts
@@ -0,0 +1,147 @@
+/* eslint-disable no-unused-vars */
+import React, { MouseEventHandler } from 'react';
+declare global {
+ interface Window {
+ iconSet: string;
+ }
+}
+
+export enum EIconType {
+ ADD = 'add',
+ ADD_CIRCLE = 'add-circle',
+ ADD_TAG = 'new-label',
+ ALERT = 'alert',
+ // API_KEY = 'e92b': See 'Generate'
+ ARROW_BACK = 'arrow-back',
+ ARROW_DOWN = 'arrow-down',
+ ARROW_DOWNWARD = 'arrow-downward',
+ ARROW_RIGHT = 'arrow-right',
+ ARROW_UPWARD = 'arrow-upward',
+ BELL = 'bell',
+ BELL_ALERT_OUTLINE = 'bell-alert-outline',
+ BULLSEYE = 'bullseye',
+ C_SUITE = 'CSuiteMembers',
+ CALENDAR = 'calendar',
+ CANCEL = 'cancel',
+ CANCEL_FILLED = 'cancel-filled',
+ // CHART = 'chart',
+ CHART_OPTION_2 = 'insights',
+ CHART_VIEW_SETTINGS = 'tune',
+ CHAT = 'chat',
+ CHAT_FILLED = 'chat-filled',
+ CHEVRON_DOWN = 'expand-more-black-24dp',
+ CHEVRON_LEFT = 'chevron-left',
+ CHEVRON_RIGHT = 'chevron-right',
+ CHEVRON_UP = 'expand-less-black-24dp',
+ CIRCLE_LEFT = 'circle-left',
+ CIRCLE_RIGHT = 'circle-right',
+ CLOSE_CIRCLE = 'cancel',
+ CODE_BRIEFCASE = 'integration-instructions',
+ CODE_BRIEFCASE_FILLED = 'integration-instructions-filled',
+ COMMUNITY = 'forum',
+ COPY = 'copy',
+ CRITICAL = 'report',
+ DATA_SCIENTIST = 'DataScientist',
+ DATA_SECURITY = 'DataSecurity',
+ DELETE = 'delete',
+ // DELETE_ITEM = 'delete_item',
+ DELETE_OUTLINED = 'delete-item',
+ DEM_ML = 'DemocratizeMLperformance',
+ DOCUMENTATION = 'article',
+ DONE = 'done',
+ // DUPLICATED = 'duplicated',
+ // DROP_DOWN = 'arrow_drop_down',
+ // EDIT = 'edit': See 'PENCIL'
+ ERROR = 'error',
+ EXPLAINABILITY = 'Explainability',
+ EXPORT = 'export',
+ EXTERNAL_LINK = 'launch',
+ FAIRNESS = 'Fairness',
+ FILTER = 'filter',
+ FINANCE = 'finance',
+ FINANCIAL_SERVICES = 'FinancialServices',
+ FLAG = 'flag',
+ FOLDER_SETTINGS_OUTLINE = 'folder-settings-outline',
+ GROWTH = 'Growth',
+ HAND = 'hand',
+ HEALTH_INSURANCE = 'HealthInsurance',
+ HELP = 'help',
+ HIDE = 'hide',
+ HOME = 'home',
+ // HOVER = 'hover',
+ I18N = 'localization',
+ INCREASE = 'IncreaseIcon',
+ INFO = 'info',
+ INFORMATION = 'information',
+ INSIGHTS = 'ActionableInsights',
+ LIGHTBULB = 'lightbulb-on',
+ // LOCALIZATION = 'localization': See 'I18N'
+ LOGOUT = 'logout',
+ MAIL = 'email',
+ ML_GOVERNANCE = 'StrengthenMLGovernance',
+ // MORE = 'more-vert': See 'OPTIONS'
+ NONE = 'none',
+ NOTEBOOK = 'sticky_note_2',
+ NOTE_EDIT_OUTLINE = 'note-edit-outline',
+ NOTE_WRITE = 'note-write',
+ OPTIONS = 'options',
+ PENCIL = 'pencil',
+ PENCIL_BOX = 'pencil-box-multiple-outline',
+ PENCIL_BOX_MULTIPLE_OUTLINE = 'pencil-box-multiple-outline1',
+ PERFORMANCE = 'Performance',
+ PLAY = 'play-circle-filled',
+ PRODUCT_MANAGERS = 'ProductManagers',
+ RD = 'RD',
+ REMOVE = 'remove',
+ REPLAY = 'replay',
+ RETAIL = 'Retail',
+ RISK_AND_COMPLIANCE = 'RiskandCmplianceOfficers',
+ SCALABILITY = 'Scalability',
+ SEARCH = 'search',
+ SEGMENTATION = 'segmentation',
+ SEND = 'send',
+ SHAPE = 'shape',
+ // SHOW = 'show': See 'DETAILS'
+ SORT_ASC = 'sort-asc',
+ SORT_DEFAULT = 'sort-default',
+ SORT_DESC = 'sort-desc',
+ SUCCESS = 'success',
+ TAG = 'tag',
+ // TAGS = 'tags',
+ TECH = 'Tech',
+ TOUR = 'rocket-launch',
+ UPDATE = 'update',
+ ZOOM = 'zoom-in',
+ ZOOM_OUT = 'zoom-out',
+ ZOOM_RESET = 'Vector',
+ //Input Type Icons
+ COMPUTER_VISION = 'computer-vision',
+ NLP_DATA = 'nlp',
+ TABULAR_DATA = 'tabular-data',
+ //Output Type Icons
+ CLASSIFICATION = 'classification',
+ DETAILS = 'details',
+ GENERATE = 'generate',
+ GLOBAL_FILTER = 'global-filter',
+ MULTILABEL = 'multilabel',
+ NLP = 'NaturalLanguageProcessing1',
+ OBJECT_DETECTION = 'object-detection',
+ REGRESSION = 'regression',
+ SCHEMA = 'schema',
+ SHOW_CHART = 'show-chart',
+ COLLAPSE = 'collapse',
+ EXPAND = 'expand',
+ POINTER = 'pointer',
+ WORKSPACE ='workspace'
+}
+
+export type TIconProps = {
+ color?: string;
+ size: string | number;
+ icon: EIconType;
+ style?: React.CSSProperties;
+ className?: string;
+ clickHandler?: MouseEventHandler;
+ ref?: React.RefObject;
+ testId?: string;
+};
diff --git a/arthur_bench/server/js/packages/components/core/Modal/Modal.tsx b/arthur_bench/server/js/packages/components/core/Modal/Modal.tsx
new file mode 100644
index 0000000..40b07bc
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/core/Modal/Modal.tsx
@@ -0,0 +1,52 @@
+import React, { ReactNode, useEffect, useRef } from 'react';
+import defaultStyles from './styles';
+import { useFela } from 'react-fela';
+
+type Props = {
+ children: ReactNode | string;
+ styles?: any;
+ showModal: boolean;
+ setShowModal: (arg: boolean) => void;
+};
+
+const Modal = ({
+ children,
+ styles,
+ showModal = false,
+ setShowModal,
+}: Props) => {
+ if (!showModal) return null;
+ const { css } = useFela();
+ const baseStyle = defaultStyles().modalWrapper;
+ const modalRef = useRef(null);
+
+ useEffect(() => {
+ const handleClickOutside = (event: MouseEvent) => {
+ if (
+ modalRef.current &&
+ modalRef.current.isEqualNode(event.target as Node)
+ ) {
+ showModal && setShowModal(false);
+ }
+ };
+ if (modalRef.current) {
+ modalRef.current.addEventListener('mousedown', handleClickOutside);
+ }
+ return () => {
+ if (modalRef.current) {
+ modalRef.current.removeEventListener(
+ 'mousedown',
+ handleClickOutside
+ );
+ }
+ };
+ }, [modalRef, setShowModal]);
+
+ return (
+
+ );
+};
+
+export default Modal;
diff --git a/arthur_bench/server/js/packages/components/core/Modal/styles.ts b/arthur_bench/server/js/packages/components/core/Modal/styles.ts
new file mode 100644
index 0000000..0694625
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/core/Modal/styles.ts
@@ -0,0 +1,16 @@
+const styles = () => ({
+ modalWrapper: {
+ background: 'rgba(99, 89, 89, 0.5)',
+ position: 'fixed',
+ width: '100%',
+ height: '100%',
+ top: '0',
+ left: '0',
+ display: 'flex',
+ justifyContent: 'center',
+ alignItems: 'center',
+ zIndex: 100,
+ },
+});
+
+export default styles;
diff --git a/arthur_bench/server/js/packages/components/core/Paginator/Paginator.tsx b/arthur_bench/server/js/packages/components/core/Paginator/Paginator.tsx
new file mode 100644
index 0000000..f616dda
--- /dev/null
+++ b/arthur_bench/server/js/packages/components/core/Paginator/Paginator.tsx
@@ -0,0 +1,303 @@
+import React, { useCallback, useEffect, useRef, useState } from 'react';
+import { useFela } from 'react-fela';
+import { useTranslation } from 'react-i18next';
+import secondary from 'resources/colors/Arthur/secondary';
+
+import { parseInt as parseIntCustom } from 'utils/parse-int';
+
+import { Button } from '../Button';
+import Dropdown from '../Dropdown';
+import { EIconType } from '../Icon';
+import { EButtonVariation } from '../Button/typings';
+import { PaginatorProps, PaginatorState } from './typings';
+import {
+ defaultStyle,
+ textAlign,
+ pageButtonStyle,
+ dropdownToggleStyle,
+ dropdownStyle,
+ dropdownButtonStyle,
+ pageInputWrap,
+ pageInput,
+ currentPageStyles,
+ rowsPerPageStyles,
+ morePage,
+ pages,
+} from './styles';
+import range from 'utils/range';
+
+const DEFAULT_PER_PAGE_OPTIONS = [10, 15, 25];
+const DEFAULT_PER_PAGE = 10;
+const START_PAGE = 0;
+
+function Paginator(props: PaginatorProps) {
+ const [pageNumber, setPageNumber] = useState('');
+ const { t } = useTranslation(['common']);
+ const { css } = useFela();
+ const buttonRef = useRef(null);
+ const [state, setState] = useState({
+ openDrop: false,
+ page: 0,
+ perPage: props.rowsPerPage || 0,
+ perPageOpts: [],
+ });
+
+ const toggleDrop = () => {
+ setState({ ...state, openDrop: !state.openDrop });
+ };
+
+ const setPerPage = (value: number) => {
+ setState({
+ ...state,
+ page: START_PAGE,
+ perPage: value,
+ openDrop: false,
+ });
+ };
+
+ const changePage = (forward: boolean) => {
+ if (forward) {
+ setState({ ...state, page: state.page + 1 });
+ } else {
+ setState({ ...state, page: state.page - 1 });
+ }
+ };
+
+ const setPage = (page: number) => {
+ setState({ ...state, page });
+ };
+
+ const { rowsPerPageOptions, rowsPerPage, page: pageProp } = props;
+
+ useEffect(() => {
+ let perPageOpts = DEFAULT_PER_PAGE_OPTIONS;
+ let perPage = DEFAULT_PER_PAGE;
+ let page = START_PAGE;
+
+ if (rowsPerPageOptions?.length) {
+ perPageOpts = rowsPerPageOptions.map((opt: number | string) =>
+ parseIntCustom(opt),
+ );
+ }
+
+ if (rowsPerPage) {
+ perPage = parseIntCustom(rowsPerPage) as number;
+ } else {
+ perPage = perPageOpts[0];
+ }
+
+ if (typeof pageProp === 'number') {
+ page = parseIntCustom(pageProp) as number;
+ }
+
+ setState({ perPageOpts, perPage, page, openDrop: false });
+ }, [rowsPerPageOptions, rowsPerPage, pageProp]);
+
+ const { page, perPage, perPageOpts, openDrop } = state;
+ const { onPageChange, onRowsPerPageChange, allowPageInput } = props;
+
+ useEffect(() => {
+ onPageChange(page);
+ }, [onPageChange, page]);
+
+ useEffect(() => {
+ if (onRowsPerPageChange) {
+ onRowsPerPageChange(perPage);
+ }
+ }, [onRowsPerPageChange, perPage]);
+
+ const totalPages = Math.floor(props.total / (state.perPage || 1)) + 1;
+
+ const handleInputPageNumber = (e: React.KeyboardEvent) => {
+ const newPage = +pageNumber;
+ if (e.key === 'Enter') {
+ if (!Number.isNaN(newPage) && newPage - 1 < totalPages && newPage) {
+ setState({ page: newPage - 1, perPageOpts, perPage, openDrop });
+ }
+ setPageNumber('');
+ }
+ };
+
+ const validateNumber = (e: React.KeyboardEvent) => {
+ const numericRegex = /[0-9]|Backspace|Delete|Enter/;
+ if (!numericRegex.test(e.key)) {
+ e.preventDefault();
+ e.stopPropagation();
+ return false;
+ }
+ };
+
+ const renderPageInput = () => {
+ if (!allowPageInput || totalPages <= 1) {
+ return null;
+ }
+
+ return (
+
+ {t('pagination.goToPage')}
+ setPageNumber(e.target.value)}
+ onKeyDown={validateNumber}
+ onKeyUp={handleInputPageNumber}
+ />
+
+ );
+ };
+
+ const renderBackButton = () => (
+