From 39485994ba2940ee12ca59c0328dc29b26da338f Mon Sep 17 00:00:00 2001 From: enudler Date: Thu, 23 Jul 2020 21:52:09 +0300 Subject: [PATCH] Benchmark report (#345) * feat(ui): support for deleting multiple reports * feat(ui): support for comparing runs to benchmark --- Dockerfile | 2 +- package-lock.json | 92 ++++++------ ui/config/webpack.config.js | 2 +- ui/src/App/common/env.js | 3 +- ui/src/features/components/DrawerE/index.js | 53 ++++++- ui/src/features/components/Report/Charts.js | 9 +- .../components/Report/compareReports.js | 134 ++++++++++++----- ui/src/features/components/Report/index.js | 76 +++++++--- ui/src/features/configurationColumn.js | 2 +- ui/src/features/get-last-reports.js | 92 ++++++++---- ui/src/features/get-test-reports.js | 80 +++++++--- ui/src/features/get-tests.js | 2 +- ui/src/features/redux/action.js | 11 +- .../features/redux/actions/reportsActions.js | 32 +++- ui/src/features/redux/apis/reportsApi.js | 14 ++ .../features/redux/reducers/reportsReducer.js | 16 +- ui/src/features/redux/saga/reportsSagas.js | 41 ++++- .../redux/selectors/reportsSelector.js | 141 ++++++++++++++---- ui/src/features/redux/types/reportsTypes.js | 11 +- 19 files changed, 613 insertions(+), 200 deletions(-) diff --git a/Dockerfile b/Dockerfile index c5f6459de..44f59e2b9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -24,7 +24,7 @@ ARG PREDATOR_DOCS_URL # Build UI from sources WORKDIR /usr/ui RUN npm ci --silent -RUN BUCKET_PATH=$BUCKET_PATH PREDATOR_DOCS_URL=$PREDATOR_DOCS_URL npm run build +RUN VERSION=$(node -p -e "require('/usr/package.json').version") && BUCKET_PATH=$BUCKET_PATH PREDATOR_DOCS_URL=$PREDATOR_DOCS_URL VERSION=$VERSION npm run build # Clean up RUN mv /usr/ui/dist /tmp/dist && rm -rf /usr/ui/* && mv /tmp/dist /usr/ui/dist diff --git a/package-lock.json b/package-lock.json index b3ba8da66..7a50d39ce 100644 --- a/package-lock.json +++ b/package-lock.json @@ -52,7 +52,7 @@ }, "@babel/generator": { "version": "7.9.4", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.9.4.tgz", + "resolved": "http://npm.zooz.co:8083/@babel%2fgenerator/-/generator-7.9.4.tgz", "integrity": "sha512-rjP8ahaDy/ouhrvCoU1E5mqaitWrxwuNGU+dy1EpaoK48jZay4MdkskKGIMHLZNewg8sAsqpGSREJwP0zH3YQA==", "dev": true, "requires": { @@ -101,7 +101,7 @@ }, "@babel/helper-validator-identifier": { "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.9.0.tgz", + "resolved": "http://npm.zooz.co:8083/@babel%2fhelper-validator-identifier/-/helper-validator-identifier-7.9.0.tgz", "integrity": "sha512-6G8bQKjOh+of4PV/ThDm/rRqlU7+IGoJuofpagU5GlEl29Vv0RGqqt86ZGRV8ZuSOY3o+8yXl5y782SMcG7SHw==", "dev": true }, @@ -149,7 +149,7 @@ }, "@babel/parser": { "version": "7.9.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.9.4.tgz", + "resolved": "http://npm.zooz.co:8083/@babel%2fparser/-/parser-7.9.4.tgz", "integrity": "sha512-bC49otXX6N0/VYhgOMh4gnP26E9xnDZK3TmbNpxYzzz9BQLBosQwfyOe9/cXUU3txYhTzLCbcqd5c8y/OmCjHA==", "dev": true }, @@ -172,7 +172,7 @@ }, "@babel/template": { "version": "7.8.6", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.6.tgz", + "resolved": "http://npm.zooz.co:8083/@babel%2ftemplate/-/template-7.8.6.tgz", "integrity": "sha512-zbMsPMy/v0PWFZEhQJ66bqjhH+z0JgMoBWuikXybgG3Gkd/3t5oQ1Rw2WQhnSrsOmsKXnZOx15tkC4qON/+JPg==", "dev": true, "requires": { @@ -183,7 +183,7 @@ }, "@babel/traverse": { "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.9.0.tgz", + "resolved": "http://npm.zooz.co:8083/@babel%2ftraverse/-/traverse-7.9.0.tgz", "integrity": "sha512-jAZQj0+kn4WTHO5dUZkZKhbFrqZE7K5LAQ5JysMnmvGij+wOdr+8lWqPeW0BcF4wFwrEXXtdGO7wcV6YPJcf3w==", "dev": true, "requires": { @@ -217,7 +217,7 @@ }, "@babel/types": { "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.9.0.tgz", + "resolved": "http://npm.zooz.co:8083/@babel%2ftypes/-/types-7.9.0.tgz", "integrity": "sha512-BS9JKfXkzzJl8RluW4JGknzpiUV7ZrvTayM6yfqLTVBEnFtyowVIOu6rqxRd5cVO6yGoWf4T8u8dgK9oB+GCng==", "dev": true, "requires": { @@ -682,7 +682,7 @@ }, "@types/color-name": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", + "resolved": "http://npm.zooz.co:8083/@types%2fcolor-name/-/color-name-1.1.1.tgz", "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==" }, "@types/long": { @@ -746,7 +746,7 @@ }, "acorn": { "version": "7.1.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.1.tgz", + "resolved": "http://npm.zooz.co:8083/acorn/-/acorn-7.1.1.tgz", "integrity": "sha512-add7dgA5ppRPxCFJoAGfMDi7PIBXq1RtGo7BhbLaxwrXPOmw8gq48Y9ozT01hUKy9byMjlR20EJhu5zlkErEkg==", "dev": true }, @@ -821,7 +821,7 @@ }, "ansi-colors": { "version": "3.2.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz", + "resolved": "http://npm.zooz.co:8083/ansi-colors/-/ansi-colors-3.2.3.tgz", "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==", "dev": true }, @@ -864,7 +864,7 @@ }, "anymatch": { "version": "3.1.1", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", + "resolved": "http://npm.zooz.co:8083/anymatch/-/anymatch-3.1.1.tgz", "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", "dev": true, "requires": { @@ -874,7 +874,7 @@ "dependencies": { "normalize-path": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "resolved": "http://npm.zooz.co:8083/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true } @@ -1363,7 +1363,7 @@ }, "binary-extensions": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.0.0.tgz", + "resolved": "http://npm.zooz.co:8083/binary-extensions/-/binary-extensions-2.0.0.tgz", "integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==", "dev": true }, @@ -1511,7 +1511,7 @@ }, "braces": { "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "resolved": "http://npm.zooz.co:8083/braces/-/braces-3.0.2.tgz", "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", "dev": true, "requires": { @@ -1574,7 +1574,7 @@ }, "bunyan": { "version": "1.8.12", - "resolved": "https://registry.npmjs.org/bunyan/-/bunyan-1.8.12.tgz", + "resolved": "http://npm.zooz.co:8083/bunyan/-/bunyan-1.8.12.tgz", "integrity": "sha1-8VDw9nSKvdcq6uhPBEA74u8RN5c=", "requires": { "dtrace-provider": "~0.8", @@ -1585,7 +1585,7 @@ }, "busboy": { "version": "0.3.1", - "resolved": "https://registry.npmjs.org/busboy/-/busboy-0.3.1.tgz", + "resolved": "http://npm.zooz.co:8083/busboy/-/busboy-0.3.1.tgz", "integrity": "sha512-y7tTxhGKXcyBxRKAni+awqx8uqaJKrSFSNFSeRG5CsWNdmy2BIK+6VGWEW7TZnIO/533mtMEA4rOevQV815YJw==", "requires": { "dicer": "0.3.0" @@ -1825,7 +1825,7 @@ }, "chokidar": { "version": "3.3.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.3.0.tgz", + "resolved": "http://npm.zooz.co:8083/chokidar/-/chokidar-3.3.0.tgz", "integrity": "sha512-dGmKLDdT3Gdl7fBUe8XK+gAtGmzy5Fn0XkkWQuYxGIgWVPPse2CxFA5mtrlD0TOHaHjEUqkWNyP1XdHoJES/4A==", "dev": true, "requires": { @@ -1841,13 +1841,13 @@ "dependencies": { "is-extglob": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "resolved": "http://npm.zooz.co:8083/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", "dev": true }, "is-glob": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "resolved": "http://npm.zooz.co:8083/is-glob/-/is-glob-4.0.1.tgz", "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", "dev": true, "requires": { @@ -1856,7 +1856,7 @@ }, "normalize-path": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "resolved": "http://npm.zooz.co:8083/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true } @@ -3871,7 +3871,7 @@ }, "dicer": { "version": "0.3.0", - "resolved": "https://registry.npmjs.org/dicer/-/dicer-0.3.0.tgz", + "resolved": "http://npm.zooz.co:8083/dicer/-/dicer-0.3.0.tgz", "integrity": "sha512-MdceRRWqltEG2dZqO769g27N/3PXfcKl04VhYnBlo2YhH7zPi88VebsjTKclaOyiuMaGU72hTfw3VkUitGcVCA==", "requires": { "streamsearch": "0.1.2" @@ -5026,7 +5026,7 @@ }, "fill-range": { "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "resolved": "http://npm.zooz.co:8083/fill-range/-/fill-range-7.0.1.tgz", "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", "dev": true, "requires": { @@ -5351,7 +5351,7 @@ }, "fsevents": { "version": "2.1.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.2.tgz", + "resolved": "http://npm.zooz.co:8083/fsevents/-/fsevents-2.1.2.tgz", "integrity": "sha512-R4wDiBwZ0KzpgOWetKDug1FZcYhqYnUYKtfZYt4mD5SBz76q0KR4Q9o7GIPamsVPGmW3EYPPJ0dOOjvx32ldZA==", "dev": true, "optional": true @@ -6114,7 +6114,7 @@ }, "he": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "resolved": "http://npm.zooz.co:8083/he/-/he-1.2.0.tgz", "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", "dev": true }, @@ -6573,7 +6573,7 @@ }, "is-binary-path": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "resolved": "http://npm.zooz.co:8083/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", "dev": true, "requires": { @@ -6666,7 +6666,7 @@ }, "is-finite": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.1.0.tgz", + "resolved": "http://npm.zooz.co:8083/is-finite/-/is-finite-1.1.0.tgz", "integrity": "sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w==", "dev": true }, @@ -6707,7 +6707,7 @@ }, "is-number": { "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "resolved": "http://npm.zooz.co:8083/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true }, @@ -7168,7 +7168,7 @@ }, "kind-of": { "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "resolved": "http://npm.zooz.co:8083/kind-of/-/kind-of-6.0.3.tgz", "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==" }, "latest-version": { @@ -7849,7 +7849,7 @@ }, "minimist": { "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "resolved": "http://npm.zooz.co:8083/minimist/-/minimist-1.2.5.tgz", "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" }, "minimist-options": { @@ -7931,7 +7931,7 @@ }, "mocha": { "version": "7.1.1", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-7.1.1.tgz", + "resolved": "http://npm.zooz.co:8083/mocha/-/mocha-7.1.1.tgz", "integrity": "sha512-3qQsu3ijNS3GkWcccT5Zw0hf/rWvu1fTN9sPvEd81hlwsr30GX2GcDSSoBxo24IR8FelmrAydGC6/1J5QQP4WA==", "dev": true, "requires": { @@ -7963,7 +7963,7 @@ "dependencies": { "ansi-styles": { "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "resolved": "http://npm.zooz.co:8083/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { @@ -7972,7 +7972,7 @@ }, "chalk": { "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "resolved": "http://npm.zooz.co:8083/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "dev": true, "requires": { @@ -7983,7 +7983,7 @@ "dependencies": { "supports-color": { "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "resolved": "http://npm.zooz.co:8083/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { @@ -7994,7 +7994,7 @@ }, "debug": { "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "resolved": "http://npm.zooz.co:8083/debug/-/debug-3.2.6.tgz", "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", "dev": true, "requires": { @@ -8003,7 +8003,7 @@ }, "glob": { "version": "7.1.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "resolved": "http://npm.zooz.co:8083/glob/-/glob-7.1.3.tgz", "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", "dev": true, "requires": { @@ -8017,7 +8017,7 @@ }, "log-symbols": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-3.0.0.tgz", + "resolved": "http://npm.zooz.co:8083/log-symbols/-/log-symbols-3.0.0.tgz", "integrity": "sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==", "dev": true, "requires": { @@ -8026,7 +8026,7 @@ }, "mkdirp": { "version": "0.5.3", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.3.tgz", + "resolved": "http://npm.zooz.co:8083/mkdirp/-/mkdirp-0.5.3.tgz", "integrity": "sha512-P+2gwrFqx8lhew375MQHHeTlY8AuOJSrGf0R5ddkEndUkmwpgUob/vQuBD1V22/Cw1/lJr4x+EjllSezBThzBg==", "dev": true, "requires": { @@ -8035,13 +8035,13 @@ }, "ms": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "resolved": "http://npm.zooz.co:8083/ms/-/ms-2.1.1.tgz", "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", "dev": true }, "supports-color": { "version": "6.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.0.0.tgz", + "resolved": "http://npm.zooz.co:8083/supports-color/-/supports-color-6.0.0.tgz", "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==", "dev": true, "requires": { @@ -8460,7 +8460,7 @@ }, "node-environment-flags": { "version": "1.0.6", - "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.6.tgz", + "resolved": "http://npm.zooz.co:8083/node-environment-flags/-/node-environment-flags-1.0.6.tgz", "integrity": "sha512-5Evy2epuL+6TM0lCQGpFIj6KwiEsGh1SrHUhTbNX+sLbBtjidPZFAnVK9y5yU1+h//RitLbRHTIMyxQPtxMdHw==", "dev": true, "requires": { @@ -8772,7 +8772,7 @@ }, "object.getownpropertydescriptors": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz", + "resolved": "http://npm.zooz.co:8083/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz", "integrity": "sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg==", "dev": true, "requires": { @@ -9172,7 +9172,7 @@ }, "picomatch": { "version": "2.2.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", + "resolved": "http://npm.zooz.co:8083/picomatch/-/picomatch-2.2.2.tgz", "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", "dev": true }, @@ -9572,7 +9572,7 @@ }, "readdirp": { "version": "3.2.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.2.0.tgz", + "resolved": "http://npm.zooz.co:8083/readdirp/-/readdirp-3.2.0.tgz", "integrity": "sha512-crk4Qu3pmXwgxdSgGhgA/eXiJAPQiX4GMOZZMXnqKxHX7TaoL+3gQVo/WeuAiogr07DpnfjIMpXXa+PAIvwPGQ==", "dev": true, "requires": { @@ -10000,7 +10000,7 @@ }, "rewire": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/rewire/-/rewire-5.0.0.tgz", + "resolved": "http://npm.zooz.co:8083/rewire/-/rewire-5.0.0.tgz", "integrity": "sha512-1zfitNyp9RH5UDyGGLe9/1N0bMlPQ0WrX0Tmg11kMHBpqwPJI4gfPpP7YngFyLbFmhXh19SToAG0sKKEFcOIJA==", "dev": true, "requires": { @@ -11067,7 +11067,7 @@ }, "streamsearch": { "version": "0.1.2", - "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz", + "resolved": "http://npm.zooz.co:8083/streamsearch/-/streamsearch-0.1.2.tgz", "integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=" }, "string-width": { @@ -11613,7 +11613,7 @@ }, "to-regex-range": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "resolved": "http://npm.zooz.co:8083/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, "requires": { @@ -12212,7 +12212,7 @@ }, "yargs-unparser": { "version": "1.6.0", - "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.6.0.tgz", + "resolved": "http://npm.zooz.co:8083/yargs-unparser/-/yargs-unparser-1.6.0.tgz", "integrity": "sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw==", "dev": true, "requires": { diff --git a/ui/config/webpack.config.js b/ui/config/webpack.config.js index eb0aa4471..584d07161 100755 --- a/ui/config/webpack.config.js +++ b/ui/config/webpack.config.js @@ -34,7 +34,7 @@ module.exports = { BUCKET_PATH: env.BUCKET_PATH || '/', }), new webpack.NamedModulesPlugin(), - new webpack.EnvironmentPlugin(['NODE_ENV', 'BUCKET_PATH', 'PREDATOR_URL', 'PREDATOR_DOCS_URL']), + new webpack.EnvironmentPlugin(['NODE_ENV', 'BUCKET_PATH', 'PREDATOR_URL', 'PREDATOR_DOCS_URL','VERSION']), new MonacoWebpackPlugin() ] }; diff --git a/ui/src/App/common/env.js b/ui/src/App/common/env.js index b43b9f449..124bce55f 100644 --- a/ui/src/App/common/env.js +++ b/ui/src/App/common/env.js @@ -1,7 +1,8 @@ module.exports = { PREDATOR_URL: generatePredatorUrl(), PREDATOR_DOCS_URL: generatePredatorDocsUrl(), - BUCKET_PATH: generateBucketPath() + BUCKET_PATH: generateBucketPath(), + VERSION: process.env.VERSION || 'dev-mode' }; function generatePredatorUrl() { diff --git a/ui/src/features/components/DrawerE/index.js b/ui/src/features/components/DrawerE/index.js index 70f73334b..60ebf74f5 100644 --- a/ui/src/features/components/DrawerE/index.js +++ b/ui/src/features/components/DrawerE/index.js @@ -5,7 +5,8 @@ import {connect} from 'react-redux' import history from '../../../store/history'; import logo from '../../../images/logo.png'; import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; - +import {VERSION} from '../../../App/common/env'; +import {faPoll} from "@fortawesome/free-solid-svg-icons"; const Logo = () => { @@ -56,7 +57,7 @@ class DrawerE extends Component { flex: 'initial' } - const appLogoInnerStyle={ + const appLogoInnerStyle = { display: 'flex', paddingLeft: '0px', paddingRight: '0', @@ -66,13 +67,22 @@ class DrawerE extends Component { return (
} titleStyle={style['appbar-logo']} titleStyle={appLogoTitleStyle} style={appLogoInnerStyle} className={style.appbar}/> -
+
{listItemData.map((listItem) => { @@ -94,7 +104,7 @@ class DrawerE extends Component { // className={url.includes(nestedItem.navigateTo) ? style['menu-selected'] : undefined} primaryText={nestedItem.primaryText} onClick={nestedItem.linkUrl ? () => window.open(nestedItem.linkUrl, '_blank') : () => this.apiClick(`/${nestedItem.navigateTo}`)} - iconStyle={{fontSize:'5px'}} + iconStyle={{fontSize: '5px'}} leftIcon={nestedItem.icon && } @@ -107,6 +117,15 @@ class DrawerE extends Component { })}
+
+ +
{'Mickey'}/Predator} @@ -123,10 +142,34 @@ class DrawerE extends Component { } } + +const Bottom = () => { + return ( +
+ window.open("https://docs.google.com/forms/d/15dozkkA2xBUV7T7ls5XMyBj-JDg5Tj-TXNMp9PkdFsM/viewform?edit_requested=true")}/> +
v{VERSION}
+
+ ) +} + + function mapStateToProps(state) { return { url: state.router.location.pathname } } + export default connect(mapStateToProps)(DrawerE); diff --git a/ui/src/features/components/Report/Charts.js b/ui/src/features/components/Report/Charts.js index 9f7ad3b10..ceb708c85 100644 --- a/ui/src/features/components/Report/Charts.js +++ b/ui/src/features/components/Report/Charts.js @@ -21,7 +21,7 @@ const COLORS = [{stroke: "#8884d8", fill: "#8884d8"}, {stroke: "#395B56", fill: "#395B56"}, {stroke: "#617A70", fill: "#617A70"}, {stroke: "#CCC39F", fill: "#CCC39F"}, - {stroke: "#FFFAD1", fill: "#FFFAD1"}, + {stroke: "#827e5b", fill: "#827e5b"}, ]; const COLOR_FAMILY = { p95: [{stroke: "#BBDEF0", fill: "#BBDEF0"}, {stroke: "#00A6A6", fill: "#00A6A6"}, { @@ -54,10 +54,10 @@ const getColor = (key, index) => { }; const filterKeysFromArrayOfObject = (data, graphType, filteredKeys) => { - const keysToFilter = Object.keys(_.pickBy(filteredKeys, (value) => value)); + const keysToFilter = Object.keys(_.pickBy(filteredKeys[graphType] || {}, (value) => value)); const filteredData = data.reduce((acc, cur) => { acc.push(_.omitBy(cur, (value, key) => { - return keysToFilter.includes(`${graphType}${key}`) + return keysToFilter.includes(`${key}`) })); return acc; }, []); @@ -100,7 +100,6 @@ export const BarChartPredator = ({data = [], keys=[], graphType, onSelectedGraph export const LineChartPredator = ({data = [], keys = [], labelY, graphType, onSelectedGraphPropertyFilter, filteredKeys}) => { const filteredData = filterKeysFromArrayOfObject(data, graphType, filteredKeys); - return ( { style={{margin: '5px', display: 'flex', flexDirection: 'row', alignItems: 'center'}}> { onSelectedGraphPropertyFilter(graphType, entry.value, value) diff --git a/ui/src/features/components/Report/compareReports.js b/ui/src/features/components/Report/compareReports.js index 53d552364..a9e9f31d1 100644 --- a/ui/src/features/components/Report/compareReports.js +++ b/ui/src/features/components/Report/compareReports.js @@ -13,19 +13,27 @@ import Snackbar from "material-ui/Snackbar"; import Checkbox from "../../../components/Checkbox/Checkbox"; import Button from "../../../components/Button"; - class CompareReports extends React.Component { constructor(props) { super(props); this.state = { reportsList: [], mergedReports: this.mergeGraphs([]), - filteredKeys: {} + filteredKeys: { + latency: {benchmark_p99: true, benchmark_p95: true, benchmark_median: true}, + rps: { + benchmark_mean: true + }, + status_codes_errors: { + benchmark_count: true + } + }, + enableBenchmark: false }; } componentDidUpdate(prevProps, prevState, snapshot) { - if (prevProps.aggregateReports !== this.props.aggregateReports) { + if (prevProps.aggregateReports !== this.props.aggregateReports || prevProps.benchmark !== this.props.benchmark) { const reportsList = this.props.aggregateReports.map((report) => ({ name: report.alias, startTime: report.startTime, @@ -38,16 +46,16 @@ class CompareReports extends React.Component { const keysToDefaultFilter = reportsList.flatMap((reportInfo) => [`${reportInfo.name}_p95`, `${reportInfo.name}_p99`]); this.onSelectedGraphPropertyFilter('latency', keysToDefaultFilter, false); this.setState({reportsList}); - this.setMergedReports(reportsList) + this.setMergedReports(reportsList, this.props.benchmark) } } - setMergedReports = (reportsList) => { + setMergedReports = (reportsList, benchmark) => { const reportsNames = reportsList.filter(cur => cur.show).map(cur => cur.name); const {aggregateReports} = this.props; const filteredData = aggregateReports.filter((report) => reportsNames.includes(report.alias)); - const mergedReports = this.mergeGraphs(filteredData); + const mergedReports = this.mergeGraphs(filteredData, benchmark); this.setState({mergedReports}); }; @@ -62,23 +70,25 @@ class CompareReports extends React.Component { this.setState({reportsList: [...reportsList]}); this.setMergedReports(reportsList); }; + onSelectedGraphPropertyFilter = (graphType, keys, value) => { - const {filteredKeys} = this.state; + const {filteredKeys = {}} = this.state; let newFilteredKeys = {...filteredKeys}; if (_.isArray(keys)) { newFilteredKeys = keys.reduce((acc, cur) => { - acc[`${graphType}${cur}`] = !value; + _.set(acc, `${graphType}.${cur}`, !value); return acc; }, filteredKeys) } else { - newFilteredKeys[`${graphType}${keys}`] = !value; + _.set(newFilteredKeys, `${graphType}.${keys}`, !value); } this.setState({filteredKeys: {...newFilteredKeys}}); }; + render() { - const {reportsList, mergedReports, filteredKeys} = this.state; - const {onClose} = this.props; + const {reportsList, mergedReports, filteredKeys, enableBenchmark} = this.state; + const {onClose, benchmark} = this.props; return (

Compare reports

+ { + benchmark && +
+
Enable benchmark
+ { + this.onSelectedGraphPropertyFilter('latency', ['benchmark_p99', 'benchmark_p95', 'benchmark_median'], value); + this.onSelectedGraphPropertyFilter('rps', ['benchmark_mean'], value); + this.onSelectedGraphPropertyFilter('status_codes_errors', ['benchmark_count'], value); + this.setState({enableBenchmark: value}); + }} + /> + +
+ }

Overall Latency

{ - const {getAggregateReports, selectedReports} = this.props; - const selectedReportsAsList = Object.entries(selectedReports) - .flatMap(selectedReport => { - const testId = selectedReport[0]; - const selectedList = Object.entries(selectedReport[1]) - .filter((isSelected) => isSelected[1]) - .map((pairs) => pairs[0]); - return selectedList.map((reportId) => { - return { - testId, - reportId - } - }) - }); - getAggregateReports(selectedReportsAsList); + const {getAggregateReports, selectedReportsAsArray, getBenchmark} = this.props; + const firstSelected = selectedReportsAsArray[0]; + const isAllSelectedReportsBelongToSameTest = _.every(selectedReportsAsArray, (data) => data.testId === firstSelected.testId); + getAggregateReports(selectedReportsAsArray); + if (isAllSelectedReportsBelongToSameTest) { + getBenchmark(firstSelected.testId); + } }; - mergeGraphs = (data) => { + mergeGraphs = (data, benchmark) => { const initial = { latencyGraph: [], latencyGraphKeys: [], @@ -166,7 +190,6 @@ class CompareReports extends React.Component { if (data.length === 0) { return initial; } - //merged sorted data by time const result = data.reduce((acc, cur) => { acc.latencyGraph = mergeSortedArraysByStartTime(acc.latencyGraph, cur.latencyGraph); @@ -178,15 +201,35 @@ class CompareReports extends React.Component { acc.rps = mergeSortedArraysByStartTime(acc.rps, cur.rps); acc.rpsKeys = acc.rpsKeys.concat(cur.rpsKeys); - acc.errorsBar = acc.errorsBar.concat(cur.errorsBar); + acc.errorsBar = mergeArrayOfObjectsPropsByParameter(acc.errorsBar, cur.errorsBar, 'name'); acc.errorsBarKeys = acc.errorsBarKeys.concat(cur.errorsBarKeys); acc.scenarios = acc.scenarios.concat(cur.scenarios); return acc; }, initial); + //assign benchmark; + if (benchmark) { + result.latencyGraph.forEach(function (data) { + Object.assign(data, benchmark.latency); + }); + result.rps.forEach(function (data) { + Object.assign(data, benchmark.rps); + }); + result.latencyGraphKeys.push(...benchmark.latencyKeys); + result.rpsKeys.push(...benchmark.rpsKeys); + + const benchmarkCodeAndErrors = Object.entries({...benchmark.codes, ...benchmark.errors}) + .map(function ([key, value]) { + return {name: key, benchmark_count: value} + }); + + result.errorsBarKeys.push('benchmark_count'); + result.errorsBar.push(...benchmarkCodeAndErrors); + result.errorsBar = mergeArrayOfObjectsPropsByParameter(result.errorsBar, benchmarkCodeAndErrors, 'name'); + } return result; - } + }; componentDidMount() { @@ -194,7 +237,8 @@ class CompareReports extends React.Component { } componentWillUnmount() { - this.props.getAggregateReportSuccess([]) + this.props.getAggregateReportSuccess([]); + this.props.clearAggregateReportAndBenchmark(); } }; @@ -203,14 +247,17 @@ function mapStateToProps(state) { return { aggregateReports: selectors.getAggregateReportsForCompare(state), createBenchmarkSucceed: selectors.createBenchmarkSuccess(state), + benchmark: selectors.benchmarkWithKeys(state), } } const mapDispatchToProps = { getAggregateReports: Actions.getAggregateReports, + getBenchmark: Actions.getBenchmark, createBenchmark: Actions.createBenchmark, createBenchmarkSuccess: Actions.createBenchmarkSuccess, getAggregateReportSuccess: Actions.getAggregateReportSuccess, + clearAggregateReportAndBenchmark: Actions.clearAggregateReportAndBenchmark, }; const Block = ({header, dataList, style = {}}) => { @@ -272,22 +319,22 @@ const ReportsList = ({list = [], onChange}) => { export default connect(mapStateToProps, mapDispatchToProps)(CompareReports); -function mergeSortedArraysByStartTime(arr1, arr2) { +function mergeSortedArraysByStartTime(arr1, arr2, assignProps = {}) { const arr3 = []; let i = 0, j = 0; while (i < arr1.length && j < arr2.length) { if (arr1[i].timeMills < arr2[j].timeMills) { - arr3.push(arr1[i]); + arr3.push(Object.assign({}, arr1[i], assignProps)); i++; } else if (arr1[i].timeMills === arr2[j].timeMills) { - const newData = {...arr1[i], ...arr2[j]}; + const newData = {...arr1[i], ...arr2[j], ...assignProps}; arr3.push(newData); i++; j++; } else { - arr3.push(arr2[j]); + arr3.push(Object.assign({}, arr2[j], assignProps)); j++; } @@ -303,3 +350,20 @@ function mergeSortedArraysByStartTime(arr1, arr2) { return arr3; } + +function mergeArrayOfObjectsPropsByParameter(arr1, arr2, propName) { + const resultAsObject = _.groupBy(arr1.concat(arr2), function (data) { + return data[propName]; + }); + + return Object.entries(resultAsObject).map(function ([key, value]) { + const props = value.reduce((acc, cur) => { + return Object.assign(acc, cur); + }, {}); + + return { + [propName]: key, + ...props + } + }) +} diff --git a/ui/src/features/components/Report/index.js b/ui/src/features/components/Report/index.js index 87e58fe7e..0cdec7344 100644 --- a/ui/src/features/components/Report/index.js +++ b/ui/src/features/components/Report/index.js @@ -12,12 +12,9 @@ import Button from '../../../components/Button'; import Snackbar from "material-ui/Snackbar"; import {BarChartPredator, LineChartPredator} from "./Charts"; import _ from "lodash"; +import Checkbox from "../../../components/Checkbox/Checkbox"; const REFRESH_DATA_INTERVAL = 30000; -const COLORS = [{stroke: "#8884d8", fill: "#8884d8"}, - {stroke: "#82ca9d", fill: "#82ca9d"}, - {stroke: "#ffc658", fill: "#ffc658"} -]; class Report extends React.Component { @@ -25,7 +22,16 @@ class Report extends React.Component { super(props); this.state = { disabledCreateBenchmark: false, - filteredKeys: {} + filteredKeys: { + latency: {benchmark_p99: true, benchmark_p95: true, benchmark_median: true}, + rps: { + benchmark_mean: true + }, + status_codes_errors: { + benchmark_count: true + } + }, + enableBenchmark: false } } @@ -34,34 +40,63 @@ class Report extends React.Component { this.props.createBenchmark(report.test_id, aggregateReport.benchMark); this.setState({disabledCreateBenchmark: true}) }; + + onSelectedGraphPropertyFilter = (graphType, keys, value) => { - const {filteredKeys} = this.state; + const {filteredKeys = {}} = this.state; let newFilteredKeys = {...filteredKeys}; if (_.isArray(keys)) { newFilteredKeys = keys.reduce((acc, cur) => { - acc[`${graphType}${cur}`] = !value; + _.set(acc, `${graphType}.${cur}`, !value); return acc; }, filteredKeys) } else { - newFilteredKeys[`${graphType}${keys}`] = !value; + _.set(newFilteredKeys, `${graphType}.${keys}`, !value); } this.setState({filteredKeys: {...newFilteredKeys}}); }; + + onExitReport = () => { + const {clearAggregateReportAndBenchmark, onClose} = this.props; + clearAggregateReportAndBenchmark(); + onClose(); + }; + render() { - const {report, onClose, aggregateReport} = this.props; - const {disabledCreateBenchmark,filteredKeys} = this.state; + const {report, aggregateReport} = this.props; + const {disabledCreateBenchmark, filteredKeys, enableBenchmark} = this.state; return ( - +
-

{report.test_name}

+

{report.test_name}

Started at {dateFormat(new Date(report.start_time), "dddd, mmmm dS, yyyy, h:MM:ss TT")} + { + aggregateReport.isBenchmarkExist &&
+
Enable benchmark
+ { + this.onSelectedGraphPropertyFilter('latency', ['benchmark_p99', 'benchmark_p95', 'benchmark_median'], value); + this.onSelectedGraphPropertyFilter('rps', ['benchmark_mean'], value); + this.onSelectedGraphPropertyFilter('status_codes_errors', ['benchmark_count'], value); + this.setState({enableBenchmark: value}); + }} + /> + +
+ }
Status Codes

RPS

@@ -103,7 +138,7 @@ class Report extends React.Component {
- +
{ - const {getAggregateReports, report} = this.props; - getAggregateReports([{testId:report.test_id, reportId:report.report_id}]); - - } + const {getAggregateReports, getBenchmark, report} = this.props; + getAggregateReports([{testId: report.test_id, reportId: report.report_id}]); + getBenchmark(report.test_id); + }; componentDidMount() { this.loadData(); @@ -132,6 +167,7 @@ class Report extends React.Component { componentWillUnmount() { clearInterval(this.refreshDataInterval); + this.props.clearAggregateReportAndBenchmark(); } }; @@ -148,12 +184,14 @@ const mapDispatchToProps = { getAggregateReports: Actions.getAggregateReports, createBenchmark: Actions.createBenchmark, createBenchmarkSuccess: Actions.createBenchmarkSuccess, + getBenchmark: Actions.getBenchmark, + clearAggregateReportAndBenchmark: Actions.clearAggregateReportAndBenchmark, }; const SummeryTable = ({report = {}}) => { return ( -
+
diff --git a/ui/src/features/configurationColumn.js b/ui/src/features/configurationColumn.js index e91c9e5f4..ca2a81a31 100644 --- a/ui/src/features/configurationColumn.js +++ b/ui/src/features/configurationColumn.js @@ -531,7 +531,7 @@ const Notes = ({data, onEditNote}) => { const [editMode, setEditMode] = useState(false); const [editValue, setEditValue] = useState(notes); const id = uuid(); - const cell = notes.split('\n').map((row) => (

{row}

)); + const cell = notes.split('\n').map((row,index) => (

{row}

)); function onKeyDown(e) { if (e.key === 'Enter') { diff --git a/ui/src/features/get-last-reports.js b/ui/src/features/get-last-reports.js index 24ccb7c9b..8f897cbe0 100644 --- a/ui/src/features/get-last-reports.js +++ b/ui/src/features/get-last-reports.js @@ -2,7 +2,6 @@ import React from 'react'; import {connect} from 'react-redux'; import * as selectors from './redux/selectors/reportsSelector'; import {createJobSuccess, errorOnStopRunningJob, stopRunningJobSuccess} from './redux/selectors/jobsSelector'; -import {tests} from './redux/selectors/testsSelector'; import Snackbar from 'material-ui/Snackbar'; import style from './style.scss'; import Dialog from './components/Dialog'; @@ -16,9 +15,9 @@ import {createJobRequest} from './requestBuilder'; import {ReactTableComponent} from './../components/ReactTable'; import {getColumns} from './configurationColumn' import ErrorDialog from "./components/ErrorDialog"; -import FormWrapper from "../components/FormWrapper"; import Button from "../components/Button"; import Loader from "./components/Loader"; +import DeleteDialog from "./components/DeleteDialog"; const REFRESH_DATA_INTERVAL = 30000; @@ -35,7 +34,8 @@ class getReports extends React.Component { sortedReports: [], sortHeader: '', rerunJob: null, - showCompareReports: false + showCompareReports: false, + showDeleteReportWarning: false }; } @@ -44,6 +44,11 @@ class getReports extends React.Component { if (prevProps.reports !== this.props.reports) { this.setState({sortedReports: [...this.props.reports]}) } + + if (prevProps.deleteReportSuccess === false && this.props.deleteReportSuccess) { + this.loadPageData(); + } + } @@ -89,14 +94,13 @@ class getReports extends React.Component { this.setState({showReport: null}) } loadPageData = () => { - this.props.getTests(); - this.props.getAllReports(); + this.props.getLastReports(); }; componentWillUnmount() { this.props.clearSelectedReport(); clearInterval(this.refreshDataInterval); - this.props.clearReportForCompare(); + this.props.clearSelectedReports(); } onSort = (field) => { @@ -135,10 +139,18 @@ class getReports extends React.Component { onReportSelected = (testId, reportId, value) => { this.props.addReportForCompare(testId, reportId, value); }; + loader() { return this.props.processingGetReports ? : 'There is no data' } + onDeleteSelectedReports = () => { + this.setState({showDeleteReportWarning: false}) + this.props.deleteReports(this.props.selectedReportsAsArray) + + }; + + render() { const {showReport, sortHeader, sortedReports, showCompareReports} = this.state; const { @@ -147,7 +159,9 @@ class getReports extends React.Component { errorOnStopRunningJob, errorCreateBenchmark, errorEditReport, - selectedReports + deleteReportFailure, + selectedReports, + selectedReportsAsArray } = this.props; const columns = getColumns({ columnsNames, @@ -159,25 +173,32 @@ class getReports extends React.Component { onRunTest: this.onRunTest, onEditNote: this.onEditNote, onReportSelected: this.onReportSelected, - selectedReports: this.props.selectedReports + selectedReports: selectedReports }); const feedbackMessage = this.generateFeedbackMessage(); - const error = errorOnGetReports || errorOnGetReport || errorOnStopRunningJob || errorCreateBenchmark || errorEditReport; - + const error = errorOnGetReports || errorOnGetReport || errorOnStopRunningJob || errorCreateBenchmark || errorEditReport || deleteReportFailure; return (
{showReport && } - +
+ + + +
: null} + + { + this.state.showDeleteReportWarning && { + this.setState({showDeleteReportWarning: false}) + }}/> + } { showCompareReports && - + } {feedbackMessage && { - this.props.getAllReports(); + this.props.getLastReports(); this.props.clearStopJobSuccess(); this.props.createJobSuccess(undefined); this.props.editNotesSuccess(false); + this.props.setDeleteReportSuccess(false); this.setState({ rerunJob: null }); @@ -229,13 +260,18 @@ class getReports extends React.Component { return `Job created successfully: ${this.props.jobSuccess.id}`; } if (this.props.noteSuccess) { - return `report notes edited successfully`; + return `Successfully updated note`; } + if (this.props.deleteReportSuccess) { + return `Successfully deleted ${this.props.deleteReportSuccess} reports`; + } + } } + function mapStateToProps(state) { return { reports: selectors.reports(state), @@ -245,32 +281,34 @@ function mapStateToProps(state) { errorOnGetReport: selectors.errorOnGetReport(state), errorOnStopRunningJob: errorOnStopRunningJob(state), stopRunningJobSuccess: stopRunningJobSuccess(state), - tests: tests(state), jobSuccess: createJobSuccess(state), noteSuccess: selectors.editNotesSuccess(state), errorEditReport: selectors.editReportFailure(state), errorCreateBenchmark: selectors.createBenchmarkFailure(state), selectedReports: selectors.selectedReports(state), + selectedReportsAsArray: selectors.selectedReportsAsArray(state), isAtLeastOneReportSelected: selectors.isAtLeastOneReportSelected(state), + deleteReportSuccess: selectors.deleteReportSuccess(state), + deleteReportFailure: selectors.deleteReportFailure(state) } } const mapDispatchToProps = { clearSelectedReport: Actions.clearSelectedReport, - getAllReports: Actions.getLastReports, + getLastReports: Actions.getLastReports, getReport: Actions.getReport, stopRunningJob: Actions.stopRunningJob, clearStopJobSuccess: Actions.clearStopJobSuccess, createJob: Actions.createJob, - getTests: Actions.getTests, createJobSuccess: Actions.createJobSuccess, editReport: Actions.editReport, editNotesSuccess: Actions.editReportSuccess, cleanAllReportsErrors: Actions.cleanAllReportsErrors, clearErrorOnStopJob: Actions.clearErrorOnStopJob, addReportForCompare: Actions.addReportForCompare, - clearReportForCompare: Actions.clearReportForCompare, - + clearSelectedReports: Actions.clearSelectedReports, + deleteReports: Actions.deleteReports, + setDeleteReportSuccess: Actions.deleteReportSuccess, }; export default connect(mapStateToProps, mapDispatchToProps)(getReports); diff --git a/ui/src/features/get-test-reports.js b/ui/src/features/get-test-reports.js index a37d34754..dc47b4caf 100644 --- a/ui/src/features/get-test-reports.js +++ b/ui/src/features/get-test-reports.js @@ -16,6 +16,7 @@ import {createJobSuccess} from "./redux/selectors/jobsSelector"; import Snackbar from 'material-ui/Snackbar'; import ErrorDialog from "./components/ErrorDialog"; import Button from "../components/Button"; +import DeleteDialog from "./components/DeleteDialog"; const noDataMsg = 'There is no data to display.'; const errorMsgGetReports = 'Error occurred while trying to get all reports for test.'; @@ -53,6 +54,9 @@ class getTests extends React.Component { if (prevProps.reports !== this.props.reports) { this.setState({sortedReports: [...this.props.reports]}) } + if (prevProps.deleteReportSuccess === false && this.props.deleteReportSuccess) { + this.props.getReports(this.testId); + } } onEditNote = (testId, reportId, notes) => { @@ -113,7 +117,7 @@ class getTests extends React.Component { this.props.clearErrorOnGetReports(); this.props.clearSelectedReport(); this.props.clearSelectedTest(); - this.props.clearReportForCompare(); + this.props.clearSelectedReports(); } loader() { @@ -130,9 +134,22 @@ class getTests extends React.Component { this.props.addReportForCompare(testId, reportId, value); }; + onDeleteSelectedReports = () => { + this.setState({showDeleteReportWarning: false}) + this.props.deleteReports(this.props.selectedReportsAsArray) + }; + render() { const noDataText = this.props.errorOnGetReports ? errorMsgGetReports : this.loader(); const {sortHeader, sortedReports} = this.state; + const { + errorCreateBenchmark, + errorEditReport, + selectedReports, + selectedReportsAsArray, + deleteReportFailure, + } = this.props; + const columns = getColumns({ columnsNames, sortHeader, @@ -143,29 +160,34 @@ class getTests extends React.Component { onRunTest: this.onRunTest, onEditNote: this.onEditNote, onReportSelected: this.onReportSelected, - selectedReports: this.props.selectedReports + selectedReports: selectedReports, }); const {showReport, showCompareReports} = this.state; - const { - errorCreateBenchmark, - errorEditReport, - selectedReports, - } = this.props; + const feedbackMessage = this.generateFeedbackMessage(); - const error = errorCreateBenchmark || errorEditReport; + const error = errorCreateBenchmark || errorEditReport || deleteReportFailure; return ( 0 && `${this.props.reports[0].test_name} Reports`} description={DESCRIPTION}> - +
+ + + +
+ } {this.state.openViewReport ? : null} + { + this.state.showDeleteReportWarning && { + this.setState({showDeleteReportWarning: false}) + }}/> + } { showCompareReports && - + } {this.state.openViewReport ? : null} @@ -199,6 +229,7 @@ class getTests extends React.Component { this.setState({ rerunJob: null }); + this.props.setDeleteReportSuccess(false); this.props.editNotesSuccess(false); }} />} @@ -214,7 +245,10 @@ class getTests extends React.Component { return `Job created successfully: ${this.props.jobSuccess.id}`; } if (this.props.noteSuccess) { - return `report notes edited successfully`; + return `Successfully updated note`; + } + if (this.props.deleteReportSuccess) { + return `Successfully deleted ${this.props.deleteReportSuccess} reports`; } } } @@ -231,8 +265,10 @@ function mapStateToProps(state) { errorEditReport: selectors.editReportFailure(state), errorCreateBenchmark: selectors.createBenchmarkFailure(state), selectedReports: selectors.selectedReports(state), + selectedReportsAsArray: selectors.selectedReportsAsArray(state), isAtLeastOneReportSelected: selectors.isAtLeastOneReportSelected(state), - + deleteReportSuccess: selectors.deleteReportSuccess(state), + deleteReportFailure: selectors.deleteReportFailure(state) } } @@ -249,7 +285,9 @@ const mapDispatchToProps = { cleanAllReportsErrors: Actions.cleanAllReportsErrors, clearErrorOnStopJob: Actions.clearErrorOnStopJob, addReportForCompare: Actions.addReportForCompare, - clearReportForCompare: Actions.clearReportForCompare, + clearSelectedReports: Actions.clearSelectedReports, + deleteReports: Actions.deleteReports, + setDeleteReportSuccess: Actions.deleteReportSuccess, }; export default connect(mapStateToProps, mapDispatchToProps)(getTests); diff --git a/ui/src/features/get-tests.js b/ui/src/features/get-tests.js index 3bb96d167..dd905f2f7 100644 --- a/ui/src/features/get-tests.js +++ b/ui/src/features/get-tests.js @@ -245,7 +245,7 @@ class getTests extends React.Component { diff --git a/ui/src/features/redux/action.js b/ui/src/features/redux/action.js index cceeff206..4cf919fc1 100644 --- a/ui/src/features/redux/action.js +++ b/ui/src/features/redux/action.js @@ -67,7 +67,10 @@ import { editReportSuccess, cleanAllReportsErrors, addReportForCompare, - clearReportForCompare, + clearSelectedReports, + deleteReports, + deleteReportSuccess, + deleteReportFailure, } from './actions/reportsActions'; import { updateConfig, @@ -156,8 +159,10 @@ export { editReportSuccess, cleanAllReportsErrors, addReportForCompare, - clearReportForCompare, - + clearSelectedReports, + deleteReports, + deleteReportSuccess, + deleteReportFailure, //config getConfigDataMapSuccess, getConfigSuccess, diff --git a/ui/src/features/redux/actions/reportsActions.js b/ui/src/features/redux/actions/reportsActions.js index 2731ed315..2a8c6db50 100644 --- a/ui/src/features/redux/actions/reportsActions.js +++ b/ui/src/features/redux/actions/reportsActions.js @@ -78,6 +78,18 @@ export const editReportFailure = (error) => ( {type: Types.EDIT_REPORT_FAILURE, error} ); +export const deleteReports = (selectedReports) => ( + {type: Types.DELETE_REPORT, selectedReports} +); + +export const deleteReportSuccess = (numberOfDeletedReports) => ( + {type: Types.DELETE_REPORT_SUCCESS, numberOfDeletedReports} +); + +export const deleteReportFailure = (error) => ( + {type: Types.EDIT_REPORT_FAILURE, error} +); + export const cleanAllReportsErrors = () => ( {type: Types.CLEAN_ALL_ERRORS} ); @@ -86,6 +98,22 @@ export const addReportForCompare = (testId, reportId, value) => ( {type: Types.ADD_REPORT_FOR_COMPARE, testId, reportId, value} ); -export const clearReportForCompare = () => ( - {type: Types.CLEAR_REPORT_FOR_COMPARE } +export const clearSelectedReports = () => ( + {type: Types.CLEAR_SELECTED_REPORTS } ); + + +export const getBenchmarkSuccess = (data) => ( + {type: Types.GET_BENCHMARK_SUCCESS,data } +); + + +export const getBenchmark = (testId) => ( + {type: Types.GET_BENCHMARK,testId } +); + + +export const clearAggregateReportAndBenchmark = () => ( + {type: Types.CLEAR_AGGREGATE_REPORT_AND_BENCHMARK } +); + diff --git a/ui/src/features/redux/apis/reportsApi.js b/ui/src/features/redux/apis/reportsApi.js index d20e72cad..24d8aefcf 100644 --- a/ui/src/features/redux/apis/reportsApi.js +++ b/ui/src/features/redux/apis/reportsApi.js @@ -36,9 +36,23 @@ export const createBenchmarkFromFramework = (testId, body) => { }); }; +export const getBenchmarkFromFramework = (testId) => { + const url = `${env.PREDATOR_URL}/tests/${testId}/benchmark`; + return axios.get(url, { + headers: getAuthorizationHeader() + }); +}; + export const editReportFromFramework = (testId, reportId, body) => { const url = `${env.PREDATOR_URL}/tests/${testId}/reports/${reportId}`; return axios.put(url, body, { headers: getAuthorizationHeader() }); }; + +export const deleteReportFromFramework = (testId, reportId) => { + const url = `${env.PREDATOR_URL}/tests/${testId}/reports/${reportId}`; + return axios.delete(url, { + headers: getAuthorizationHeader() + }); +}; diff --git a/ui/src/features/redux/reducers/reportsReducer.js b/ui/src/features/redux/reducers/reportsReducer.js index 8daaad7ee..aff701790 100644 --- a/ui/src/features/redux/reducers/reportsReducer.js +++ b/ui/src/features/redux/reducers/reportsReducer.js @@ -12,7 +12,9 @@ const initialState = Immutable.Map({ edit_report_failure: undefined, create_benchmark_failure: undefined, selected_reports: {}, - aggregate_reports: [] + aggregate_reports: [], + benchmark: undefined, + delete_report_success: false }); export default function reduce(state = initialState, action = {}) { @@ -41,6 +43,10 @@ export default function reduce(state = initialState, action = {}) { return state.set('edit_notes_success', action.value); case Types.EDIT_REPORT_FAILURE: return state.set('edit_report_failure', action.error); + case Types.DELETE_REPORT_SUCCESS: + return state.set('delete_report_success', action.numberOfDeletedReports); + case Types.DELETE_REPORT_FAILURE: + return state.set('delete_report_failure', action.error); case Types.CREATE_BENCHMARK_FAILURE: return state.set('create_benchmark_failure', action.error); case Types.ADD_REPORT_FOR_COMPARE: @@ -48,15 +54,21 @@ export default function reduce(state = initialState, action = {}) { currentSelectedReports[action.testId] = currentSelectedReports[action.testId] || {}; currentSelectedReports[action.testId][action.reportId] = action.value; return state.set('selected_reports', currentSelectedReports); - case Types.CLEAR_REPORT_FOR_COMPARE: + case Types.CLEAR_SELECTED_REPORTS: return state.set('selected_reports', {}); case Types.CLEAN_ALL_ERRORS: const newState = (state.set('error_get_reports', undefined) .set('error_get_reports', undefined) + .set('delete_report_failure', undefined) .set('edit_report_failure', undefined) .set('create_benchmark_failure', undefined)); return newState; + case Types.GET_BENCHMARK_SUCCESS: + return state.set('benchmark', action.data); + case Types.CLEAR_AGGREGATE_REPORT_AND_BENCHMARK: + return state.set('aggregate_reports', []) + .set('benchmark', undefined); default: return state; } diff --git a/ui/src/features/redux/saga/reportsSagas.js b/ui/src/features/redux/saga/reportsSagas.js index 2ecb55b7a..f5f0816d9 100644 --- a/ui/src/features/redux/saga/reportsSagas.js +++ b/ui/src/features/redux/saga/reportsSagas.js @@ -7,7 +7,9 @@ import { getLastReportsFromFramework, getAggregateFromFramework, createBenchmarkFromFramework, - editReportFromFramework + editReportFromFramework, + getBenchmarkFromFramework, + deleteReportFromFramework } from '../apis/reportsApi' export function* getReports({testId}) { @@ -83,6 +85,30 @@ export function* editReport({testId, reportId, body}) { } } +export function* deleteReports({selectedReports}) { + const failedDeletedReportsList = []; + try { + yield all(selectedReports.map(function* ({testId, reportId}) { + try { + yield call(deleteReportFromFramework, testId, reportId) + } catch (err) { + console.log('err', err); + failedDeletedReportsList.push(`(test id: ${testId}, report id: ${reportId})`); + } + + })); + if (failedDeletedReportsList.length > 0) { + throw new Error("Failed to delete the next reports: " + failedDeletedReportsList.join(',') + + ". Please note that it’s impossible to delete in-progress reports"); + } + + yield put(Actions.deleteReportSuccess(selectedReports.length)); + yield put(Actions.clearSelectedReports()); + } catch (e) { + yield put(Actions.deleteReportFailure(e)) + } +} + export function* getAggregateReports({reportsData}) { try { const results = yield all(reportsData.map(report => { @@ -98,6 +124,17 @@ export function* getAggregateReports({reportsData}) { } } + +export function* getBenchmark({testId}) { + try { + const result = yield call(getBenchmarkFromFramework, testId); + yield put(Actions.getBenchmarkSuccess(result.data)); + } catch (e) { + console.log('error', e); + } +} + + export function* reportsRegister() { yield takeLatest(Types.GET_REPORTS, getReports); yield takeLatest(Types.GET_REPORT, getReport); @@ -105,4 +142,6 @@ export function* reportsRegister() { yield takeLatest(Types.GET_AGGREGATE_REPORTS, getAggregateReports); yield takeLatest(Types.CREATE_BENCHMARK, createBenchmark); yield takeLatest(Types.EDIT_REPORT, editReport); + yield takeLatest(Types.GET_BENCHMARK, getBenchmark); + yield takeLatest(Types.DELETE_REPORT, deleteReports); } diff --git a/ui/src/features/redux/selectors/reportsSelector.js b/ui/src/features/redux/selectors/reportsSelector.js index 2666268e9..eeda42654 100644 --- a/ui/src/features/redux/selectors/reportsSelector.js +++ b/ui/src/features/redux/selectors/reportsSelector.js @@ -9,9 +9,51 @@ export const errorOnGetReport = (state) => state.ReportsReducer.get('error_get_r export const processingGetReports = (state) => state.ReportsReducer.get('processing_get_reports'); export const createBenchmarkSuccess = (state) => state.ReportsReducer.get('create_benchmark_success'); export const editNotesSuccess = (state) => state.ReportsReducer.get('edit_notes_success'); +export const deleteReportSuccess = (state) => state.ReportsReducer.get('delete_report_success'); +export const deleteReportFailure = (state) => state.ReportsReducer.get('delete_report_failure'); export const createBenchmarkFailure = (state) => state.ReportsReducer.get('create_benchmark_failure'); export const editReportFailure = (state) => state.ReportsReducer.get('edit_report_failure'); export const selectedReports = (state) => state.ReportsReducer.get('selected_reports'); +export const benchmark = (state) => state.ReportsReducer.get('benchmark'); + +export const benchmarkWithKeys = createSelector(benchmark, (benchmark) => { + if (benchmark) { + return { + rps: { + benchmark_mean: benchmark.rps.mean + }, + latency: { + benchmark_p99: benchmark.latency.p99, + benchmark_p95: benchmark.latency.p95, + benchmark_median: benchmark.latency.median, + }, + latencyKeys: ['benchmark_median', 'benchmark_p95', 'benchmark_p99'], + rpsKeys: ['benchmark_mean'], + errorsBarKeys: ['benchmark_count'], + codes: benchmark.codes, + errors: benchmark.errors, + } + } + +}) + +export const selectedReportsAsArray = createSelector(selectedReports, (selectedReports) => { + const selectedReportsAsList = Object.entries(selectedReports) + .flatMap(selectedReport => { + const testId = selectedReport[0]; + const selectedList = Object.entries(selectedReport[1]) + .filter((isSelected) => isSelected[1]) + .map((pairs) => pairs[0]); + return selectedList.map((reportId) => { + return { + testId, + reportId + } + }) + }); + return selectedReportsAsList; +}); + export const isAtLeastOneReportSelected = createSelector(selectedReports, (selectedReports) => { //find report with value true @@ -19,8 +61,8 @@ export const isAtLeastOneReportSelected = createSelector(selectedReports, (selec return !!result; }); -export const getAggregateReport = createSelector(aggregateReport, (reports) => { - return buildAggregateReportData(reports)[0] || {}; +export const getAggregateReport = createSelector(aggregateReport, benchmarkWithKeys, (reports, benchmarkWithKeys) => { + return buildAggregateReportData(reports, false, false, benchmarkWithKeys)[0] || {}; }); export const getAggregateReportsForCompare = createSelector(aggregateReport, (reports) => { @@ -28,7 +70,7 @@ export const getAggregateReportsForCompare = createSelector(aggregateReport, (re }); -function buildAggregateReportData(reports, withPrefix, startFromZeroTime) { +function buildAggregateReportData(reports, withPrefix, startFromZeroTime, lastBenchmark = {}) { let prefix = withPrefix ? 'A_' : ''; return reports.map((report) => { @@ -46,43 +88,50 @@ function buildAggregateReportData(reports, withPrefix, startFromZeroTime) { const offset = startFromZeroTime ? new Date(report.start_time).getTime() : 0; const startTime = new Date(report.start_time).getTime() - offset; + //intermediates report.intermediates.forEach((bucket, index) => { const latency = bucket.latency; const time = new Date(startTime + (bucket.bucket * 1000)); const timeMills = time.getTime(); - latencyGraph.push({ name: `${dateFormat(time, 'h:MM:ss')}`, [`${prefix}median`]: latency.median, [`${prefix}p95`]: latency.p95, [`${prefix}p99`]: latency.p99, + ...lastBenchmark.latency, timeMills }); - rps.push({name: `${dateFormat(time, 'h:MM:ss')}`, timeMills, [`${prefix}mean`]: bucket.rps.mean}); - - if (Object.keys(bucket.codes).length > 0) { - const errorsData = Object.entries({...bucket.codes, ...bucket.errors}).reduce((acc, cur) => { - acc[`${prefix}${cur[0]}`] = cur[1]; - return acc; - }, {}); - errorsCodeGraphKeysAsObjectAcc = Object.assign(errorsCodeGraphKeysAsObjectAcc, errorsData) - errorsCodeGraph.push({name: `${dateFormat(time, 'h:MM:ss')}`, timeMills, ...errorsData}); - Object.keys(bucket.codes).forEach((code) => { - errorCodes[`${prefix}${code}`] = true; - }); - Object.keys(bucket.errors).forEach((error) => { - errorCodes[`${prefix}${error}`] = true; - }) - } + rps.push({ + name: `${dateFormat(time, 'h:MM:ss')}`, + timeMills, + [`${prefix}mean`]: bucket.rps.mean, ...lastBenchmark.rps + }); + + + const errorsData = buildErrorDataObject(bucket, prefix); + errorsCodeGraphKeysAsObjectAcc = Object.assign(errorsCodeGraphKeysAsObjectAcc, errorsData); + errorsCodeGraph.push({ + name: `${dateFormat(time, 'h:MM:ss')}`, + timeMills, ...errorsData + }); + + const bucketErrorCodeData = lastBenchmark.codes ? {...bucket.codes, ...lastBenchmark.codes} : bucket; + + + Object.keys(bucketErrorCodeData).forEach((code) => { + errorCodes[`${prefix}${code}`] = true; + }); + Object.keys(bucketErrorCodeData).forEach((error) => { + errorCodes[`${prefix}${error}`] = true; + }) - }); - Object.keys(report.aggregate.codes).forEach((code) => { - errorsBar.push({name: code, [`${prefix}count`]: report.aggregate.codes[code]}) - }); - Object.keys(report.aggregate.errors).forEach((error) => { - errorsBar.push({name: error, [`${prefix}count`]: report.aggregate.errors[error]}) }); + + // aggregate data + buildErrorBars(errorsBar, report.aggregate, lastBenchmark, prefix); + + Object.keys(report.aggregate.scenarioCounts).forEach((key) => { scenarios.push({name: `${prefix}${key}`, value: report.aggregate.scenarioCounts[key]}) }); @@ -99,12 +148,18 @@ function buildAggregateReportData(reports, withPrefix, startFromZeroTime) { const rpsKeys = [`${prefix}mean`]; const errorsBarKeys = [`${prefix}count`]; - const errorsCodeGraphKeys = Object.keys(errorsCodeGraphKeysAsObjectAcc) if (withPrefix) { prefix = String.fromCharCode(prefix.charCodeAt(0) + 1) + '_'; } + if (Object.keys(lastBenchmark).length > 0) { + latencyGraphKeys.push(...lastBenchmark.latencyKeys); + rpsKeys.push(...lastBenchmark.rpsKeys); + errorsBarKeys.push('benchmark_count'); + } + + return { alias, latencyGraph, @@ -123,7 +178,37 @@ function buildAggregateReportData(reports, withPrefix, startFromZeroTime) { startTime: report.start_time, testName: report.test_name, duration: report.duration, - notes: report.notes + notes: report.notes, + isBenchmarkExist: Object.keys(lastBenchmark).length > 0 } }) } + + +function buildErrorDataObject(bucket, prefix) { + const errorsData = Object.entries({...bucket.codes, ...bucket.errors}).reduce((acc, cur) => { + acc[`${prefix}${cur[0]}`] = cur[1]; + return acc; + }, {}); + return errorsData; +} + +function buildErrorBars(errorsBar, data, benchmark, prefix) { + const allBenchmarkStatuses = Object.keys(benchmark).length > 0 ? {...benchmark.codes, ...benchmark.errors} : {}; + const reportStatuses = {...data.codes, ...data.errors}; + Object.keys(reportStatuses).forEach((status) => { + const data = {name: status, [`${prefix}count`]: reportStatuses[status]}; + if (allBenchmarkStatuses[status]) { + data.benchmark_count = allBenchmarkStatuses[status]; + delete allBenchmarkStatuses[status]; + } + errorsBar.push(data) + }); + const restOfBenchmarkStatuses = Object.keys(allBenchmarkStatuses); + + if (restOfBenchmarkStatuses.length > 0) { + restOfBenchmarkStatuses.forEach((status) => { + errorsBar.push({name: status, benchmark_count: restOfBenchmarkStatuses[status]}); + }) + } +} diff --git a/ui/src/features/redux/types/reportsTypes.js b/ui/src/features/redux/types/reportsTypes.js index 2ec2f319e..b6f3d9b59 100644 --- a/ui/src/features/redux/types/reportsTypes.js +++ b/ui/src/features/redux/types/reportsTypes.js @@ -26,11 +26,20 @@ export const EDIT_REPORT = 'EDIT_REPORT'; export const EDIT_REPORT_SUCCESS = 'EDIT_REPORT_SUCCESS'; export const EDIT_REPORT_FAILURE = 'EDIT_REPORT_FAILURE'; +export const DELETE_REPORT = 'DELETE_REPORT'; +export const DELETE_REPORT_SUCCESS = 'DELETE_REPORT_SUCCESS'; +export const DELETE_REPORT_FAILURE = 'DELETE_REPORT_FAILURE'; + export const CLEAN_ALL_ERRORS = 'CLEAN_ALL_ERRORS'; export const ADD_REPORT_FOR_COMPARE = 'ADD_REPORT_FOR_COMPARE'; -export const CLEAR_REPORT_FOR_COMPARE = 'CLEAR_REPORT_FOR_COMPARE'; +export const CLEAR_SELECTED_REPORTS = 'CLEAR_SELECTED_REPORTS'; + + +export const GET_BENCHMARK = 'GET_BENCHMARK'; +export const GET_BENCHMARK_SUCCESS = 'GET_BENCHMARK_SUCCESS'; +export const CLEAR_AGGREGATE_REPORT_AND_BENCHMARK = 'CLEAR_AGGREGATE_REPORT_AND_BENCHMARK';