From 20e17cf898eba14730420fb22a4e143073e9e2c8 Mon Sep 17 00:00:00 2001 From: Kyrylo Shmidt Date: Wed, 17 May 2023 10:37:00 +0200 Subject: [PATCH 01/14] Add Live View to the Recent Activity Update styles --- .storybook/preview-head.html | 4 + package-lock.json | 661 ++++++++++- package.json | 2 + src/components/Assets/types.ts | 37 +- .../LiveView/LiveView.stories.tsx | 25 + .../RecentActivity/LiveView/index.tsx | 199 ++++ .../RecentActivity/LiveView/mockData.ts | 884 ++++++++++++++ .../RecentActivity/LiveView/styles.ts | 120 ++ .../RecentActivity/LiveView/types.ts | 24 + .../RecentActivity/RecentActivity.stories.tsx | 1037 +++++++++-------- .../RecentActivityTable/index.tsx | 4 +- .../RecentActivityTable/styles.ts | 37 +- src/components/RecentActivity/index.tsx | 81 +- src/components/RecentActivity/styles.ts | 46 +- src/components/RecentActivity/types.ts | 2 + .../common/icons/CheckmarkCircleIcon.tsx | 27 - .../common/icons/CrossCircleIcon.tsx | 30 - src/components/common/icons/CrossIcon.tsx | 24 + src/components/common/icons/CrosshairIcon.tsx | 2 +- src/components/common/icons/DataIcon.tsx | 34 - .../common/icons/DoubleCircleIcon.tsx | 31 + .../common/icons/WarningTriangleIcon.tsx | 26 - src/typeGuards/isNumber.ts | 1 + src/types.ts | 125 ++ src/utils/getInsightImportanceColor.ts | 4 +- 25 files changed, 2753 insertions(+), 714 deletions(-) create mode 100644 src/components/RecentActivity/LiveView/LiveView.stories.tsx create mode 100644 src/components/RecentActivity/LiveView/index.tsx create mode 100644 src/components/RecentActivity/LiveView/mockData.ts create mode 100644 src/components/RecentActivity/LiveView/styles.ts create mode 100644 src/components/RecentActivity/LiveView/types.ts delete mode 100644 src/components/common/icons/CheckmarkCircleIcon.tsx delete mode 100644 src/components/common/icons/CrossCircleIcon.tsx create mode 100644 src/components/common/icons/CrossIcon.tsx delete mode 100644 src/components/common/icons/DataIcon.tsx create mode 100644 src/components/common/icons/DoubleCircleIcon.tsx delete mode 100644 src/components/common/icons/WarningTriangleIcon.tsx create mode 100644 src/typeGuards/isNumber.ts diff --git a/.storybook/preview-head.html b/.storybook/preview-head.html index 7585a60e3..51c6b0baf 100644 --- a/.storybook/preview-head.html +++ b/.storybook/preview-head.html @@ -5,4 +5,8 @@ font-weight: 500; font-style: normal; } + + #storybook-root { + height: 100%; + } diff --git a/package-lock.json b/package-lock.json index 19a2cb70c..f244dc89e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,12 +11,14 @@ "dependencies": { "@floating-ui/react": "^0.21.0", "@tanstack/react-table": "^8.7.8", + "allotment": "^1.19.0", "copy-to-clipboard": "^3.3.3", "date-fns": "^2.29.3", "react": "^18.2.0", "react-cool-dimensions": "^3.0.1", "react-dom": "^18.2.0", "react-transition-group": "^4.4.5", + "recharts": "^2.6.2", "styled-components": "^5.3.6" }, "devDependencies": { @@ -2832,8 +2834,7 @@ "node_modules/@juggle/resize-observer": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/@juggle/resize-observer/-/resize-observer-3.4.0.tgz", - "integrity": "sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==", - "dev": true + "integrity": "sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==" }, "node_modules/@mdx-js/react": { "version": "2.3.0", @@ -4970,6 +4971,60 @@ "@types/node": "*" } }, + "node_modules/@types/d3-array": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.0.4.tgz", + "integrity": "sha512-nwvEkG9vYOc0Ic7G7kwgviY4AQlTfYGIZ0fqB7CQHXGyYM6nO7kJh5EguSNA3jfh4rq7Sb7eMVq8isuvg2/miQ==" + }, + "node_modules/@types/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-HKuicPHJuvPgCD+np6Se9MQvS6OCbJmOjGvylzMJRlDwUXjKTTXs6Pwgk79O09Vj/ho3u1ofXnhFOaEWWPrlwA==" + }, + "node_modules/@types/d3-ease": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.0.tgz", + "integrity": "sha512-aMo4eaAOijJjA6uU+GIeW018dvy9+oH5Y2VPPzjjfxevvGQ/oRDs+tfYC9b50Q4BygRR8yE2QCLsrT0WtAVseA==" + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-jx5leotSeac3jr0RePOH1KdR9rISG91QIE4Q2PYTu4OymLTZfA3SrnURSLzKH48HmXVUru50b8nje4E79oQSQw==", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.0.0.tgz", + "integrity": "sha512-0g/A+mZXgFkQxN3HniRDbXMN79K3CdTpLsevj+PXiTcb2hVyvkZUBg37StmgCQkaD84cUJ4uaDAWq7UJOQy2Tg==" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.3.tgz", + "integrity": "sha512-PATBiMCpvHJSMtZAMEhc2WyL+hnzarKzI6wAHYjhsonjWJYGq5BXTzQjv4l8m2jO183/4wZ90rKvSeT7o72xNQ==", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-shape": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.1.tgz", + "integrity": "sha512-6Uh86YFF7LGg4PQkuO2oG6EMBRLuW9cbavUW46zkIO5kuS2PfTqo2o9SkgtQzguBHbLgNnU90UNsITpsX1My+A==", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.0.tgz", + "integrity": "sha512-sZLCdHvBUcNby1cB6Fd3ZBrABbjz3v1Vm90nysCQ6Vt7vd6e/h9Lt7SiJUoEX0l4Dzc7P5llKyhqSi1ycSf1Hg==" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.0.tgz", + "integrity": "sha512-HNB/9GHqu7Fo8AQiugyJbv6ZxYz58wef0esl4Mv828w1ZKpAshw/uFWVDUcIB9KKFeFKoxS3cHY07FFgtTRZ1g==" + }, "node_modules/@types/detect-port": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/@types/detect-port/-/detect-port-1.3.2.tgz", @@ -5874,6 +5929,28 @@ "ajv": "^6.9.1" } }, + "node_modules/allotment": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/allotment/-/allotment-1.19.0.tgz", + "integrity": "sha512-qL/1faHUoCOvMstCFGkaGSK/nVoSAVN6NLFuIW4P6D5fvKdgxV8/KNfMFeuQUXReodILp4I6z+5a9zmuqK5t4g==", + "dependencies": { + "classnames": "^2.3.0", + "eventemitter3": "^5.0.0", + "lodash.clamp": "^4.0.0", + "lodash.debounce": "^4.0.0", + "lodash.isequal": "^4.5.0", + "use-resize-observer": "^9.0.0" + }, + "peerDependencies": { + "react": "^17.0.0 || ^18.0.0", + "react-dom": "^17.0.0 || ^18.0.0" + } + }, + "node_modules/allotment/node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==" + }, "node_modules/ansi-align": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", @@ -6904,6 +6981,11 @@ "node": ">=8" } }, + "node_modules/classnames": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.2.tgz", + "integrity": "sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==" + }, "node_modules/clean-css": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.2.tgz", @@ -7616,6 +7698,11 @@ "postcss-value-parser": "^4.0.2" } }, + "node_modules/css-unit-converter": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/css-unit-converter/-/css-unit-converter-1.1.2.tgz", + "integrity": "sha512-IiJwMC8rdZE0+xiEZHeru6YoONC4rfPMqGm2W85jMIbkFvv5nFTwJVFHam2eFrN6txmoUYFAFXiv8ICVeTO0MA==" + }, "node_modules/css-what": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", @@ -7645,6 +7732,116 @@ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz", "integrity": "sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==" }, + "node_modules/d3-array": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.3.tgz", + "integrity": "sha512-JRHwbQQ84XuAESWhvIPaUV4/1UYTBOLiOPGWqgFDHZS1D5QN9c57FbH3QpEnQMYiOXNzKUQyGTZf+EVO7RT5TQ==", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "engines": { + "node": ">=12" + } + }, "node_modules/date-fns": { "version": "2.29.3", "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.29.3.tgz", @@ -7707,6 +7904,11 @@ "node": ">=0.10.0" } }, + "node_modules/decimal.js-light": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz", + "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==" + }, "node_modules/dedent": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", @@ -8951,6 +9153,11 @@ "node": ">= 0.6" } }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" + }, "node_modules/events": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", @@ -9097,6 +9304,11 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true }, + "node_modules/fast-equals": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-4.0.3.tgz", + "integrity": "sha512-G3BSX9cfKttjr+2o1O22tYMLq0DPluZnYtq1rXumE1SpL/F/SLIfHx08WYQoWSIpeMYf8sRbJ8++71+v6Pnxfg==" + }, "node_modules/fast-glob": { "version": "3.2.12", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", @@ -10300,6 +10512,14 @@ "node": ">= 0.4" } }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "engines": { + "node": ">=12" + } + }, "node_modules/interpret": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", @@ -11552,11 +11772,20 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, + "node_modules/lodash.clamp": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/lodash.clamp/-/lodash.clamp-4.0.3.tgz", + "integrity": "sha512-HvzRFWjtcguTW7yd8NJBshuNaCa8aqNFtnswdT7f/cMd/1YKy5Zzoq4W/Oxvnx9l7aeY258uSdDfM793+eLsVg==" + }, "node_modules/lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", - "dev": true + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==" + }, + "node_modules/lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==" }, "node_modules/lodash.merge": { "version": "4.6.2", @@ -13323,6 +13552,11 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, + "node_modules/react-lifecycles-compat": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", + "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" + }, "node_modules/react-refresh": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz", @@ -13332,6 +13566,55 @@ "node": ">=0.10.0" } }, + "node_modules/react-resize-detector": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/react-resize-detector/-/react-resize-detector-8.1.0.tgz", + "integrity": "sha512-S7szxlaIuiy5UqLhLL1KY3aoyGHbZzsTpYal9eYMwCyKqoqoVLCmIgAgNyIM1FhnP2KyBygASJxdhejrzjMb+w==", + "dependencies": { + "lodash": "^4.17.21" + }, + "peerDependencies": { + "react": "^16.0.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/react-smooth": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-2.0.2.tgz", + "integrity": "sha512-pgqSp1q8rAGtF1bXQE0m3CHGLNfZZh5oA5o1tsPLXRHnKtkujMIJ8Ws5nO1mTySZf1c4vgwlEk+pHi3Ln6eYLw==", + "dependencies": { + "fast-equals": "^4.0.3", + "react-transition-group": "2.9.0" + }, + "peerDependencies": { + "prop-types": "^15.6.0", + "react": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/react-smooth/node_modules/dom-helpers": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-3.4.0.tgz", + "integrity": "sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA==", + "dependencies": { + "@babel/runtime": "^7.1.2" + } + }, + "node_modules/react-smooth/node_modules/react-transition-group": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.9.0.tgz", + "integrity": "sha512-+HzNTCHpeQyl4MJ/bdE0u6XRMe9+XG/+aL4mCxVN4DnPBQ0/5bfHWPDuOZUzYdMj94daZaZdCCc1Dzt9R/xSSg==", + "dependencies": { + "dom-helpers": "^3.4.0", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2", + "react-lifecycles-compat": "^3.0.4" + }, + "peerDependencies": { + "react": ">=15.0.0", + "react-dom": ">=15.0.0" + } + }, "node_modules/react-transition-group": { "version": "4.4.5", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", @@ -13478,6 +13761,38 @@ "node": ">=4" } }, + "node_modules/recharts": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.6.2.tgz", + "integrity": "sha512-dVhNfgI21LlF+4AesO3mj+i+9YdAAjoGaDWIctUgH/G2iy14YVtb/DSUeic77xr19rbKCiq+pQGfeg2kJQDHig==", + "dependencies": { + "classnames": "^2.2.5", + "eventemitter3": "^4.0.1", + "lodash": "^4.17.19", + "react-is": "^16.10.2", + "react-resize-detector": "^8.0.4", + "react-smooth": "^2.0.2", + "recharts-scale": "^0.4.4", + "reduce-css-calc": "^2.1.8", + "victory-vendor": "^36.6.8" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "prop-types": "^15.6.0", + "react": "^16.0.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/recharts-scale": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/recharts-scale/-/recharts-scale-0.4.5.tgz", + "integrity": "sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==", + "dependencies": { + "decimal.js-light": "^2.4.1" + } + }, "node_modules/rechoir": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", @@ -13503,6 +13818,20 @@ "node": ">=8" } }, + "node_modules/reduce-css-calc": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/reduce-css-calc/-/reduce-css-calc-2.1.8.tgz", + "integrity": "sha512-8liAVezDmUcH+tdzoEGrhfbGcP7nOV4NkGE3a74+qqvE7nt9i4sKLGBuZNOnpI4WiGksiNPklZxva80061QiPg==", + "dependencies": { + "css-unit-converter": "^1.1.1", + "postcss-value-parser": "^3.3.0" + } + }, + "node_modules/reduce-css-calc/node_modules/postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" + }, "node_modules/regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -15484,7 +15813,6 @@ "version": "9.1.0", "resolved": "https://registry.npmjs.org/use-resize-observer/-/use-resize-observer-9.1.0.tgz", "integrity": "sha512-R25VqO9Wb3asSD4eqtcxk8sJalvIOYBqS8MNZlpDSQ4l4xMQxC/J7Id9HoTqPq8FwULIn0PVW+OAqF2dyYbjow==", - "dev": true, "dependencies": { "@juggle/resize-observer": "^3.3.1" }, @@ -15578,6 +15906,27 @@ "node": ">= 0.8" } }, + "node_modules/victory-vendor": { + "version": "36.6.10", + "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.6.10.tgz", + "integrity": "sha512-7YqYGtsA4mByokBhCjk+ewwPhUfzhR1I3Da6/ZsZUv/31ceT77RKoaqrxRq5Ki+9we4uzf7+A+7aG2sfYhm7nA==", + "dependencies": { + "@types/d3-array": "^3.0.3", + "@types/d3-ease": "^3.0.0", + "@types/d3-interpolate": "^3.0.1", + "@types/d3-scale": "^4.0.2", + "@types/d3-shape": "^3.1.0", + "@types/d3-time": "^3.0.0", + "@types/d3-timer": "^3.0.0", + "d3-array": "^3.1.6", + "d3-ease": "^3.0.1", + "d3-interpolate": "^3.0.1", + "d3-scale": "^4.0.2", + "d3-shape": "^3.1.0", + "d3-time": "^3.0.0", + "d3-timer": "^3.0.1" + } + }, "node_modules/walker": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", @@ -17988,8 +18337,7 @@ "@juggle/resize-observer": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/@juggle/resize-observer/-/resize-observer-3.4.0.tgz", - "integrity": "sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==", - "dev": true + "integrity": "sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==" }, "@mdx-js/react": { "version": "2.3.0", @@ -19486,6 +19834,60 @@ "@types/node": "*" } }, + "@types/d3-array": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.0.4.tgz", + "integrity": "sha512-nwvEkG9vYOc0Ic7G7kwgviY4AQlTfYGIZ0fqB7CQHXGyYM6nO7kJh5EguSNA3jfh4rq7Sb7eMVq8isuvg2/miQ==" + }, + "@types/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-HKuicPHJuvPgCD+np6Se9MQvS6OCbJmOjGvylzMJRlDwUXjKTTXs6Pwgk79O09Vj/ho3u1ofXnhFOaEWWPrlwA==" + }, + "@types/d3-ease": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.0.tgz", + "integrity": "sha512-aMo4eaAOijJjA6uU+GIeW018dvy9+oH5Y2VPPzjjfxevvGQ/oRDs+tfYC9b50Q4BygRR8yE2QCLsrT0WtAVseA==" + }, + "@types/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-jx5leotSeac3jr0RePOH1KdR9rISG91QIE4Q2PYTu4OymLTZfA3SrnURSLzKH48HmXVUru50b8nje4E79oQSQw==", + "requires": { + "@types/d3-color": "*" + } + }, + "@types/d3-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.0.0.tgz", + "integrity": "sha512-0g/A+mZXgFkQxN3HniRDbXMN79K3CdTpLsevj+PXiTcb2hVyvkZUBg37StmgCQkaD84cUJ4uaDAWq7UJOQy2Tg==" + }, + "@types/d3-scale": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.3.tgz", + "integrity": "sha512-PATBiMCpvHJSMtZAMEhc2WyL+hnzarKzI6wAHYjhsonjWJYGq5BXTzQjv4l8m2jO183/4wZ90rKvSeT7o72xNQ==", + "requires": { + "@types/d3-time": "*" + } + }, + "@types/d3-shape": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.1.tgz", + "integrity": "sha512-6Uh86YFF7LGg4PQkuO2oG6EMBRLuW9cbavUW46zkIO5kuS2PfTqo2o9SkgtQzguBHbLgNnU90UNsITpsX1My+A==", + "requires": { + "@types/d3-path": "*" + } + }, + "@types/d3-time": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.0.tgz", + "integrity": "sha512-sZLCdHvBUcNby1cB6Fd3ZBrABbjz3v1Vm90nysCQ6Vt7vd6e/h9Lt7SiJUoEX0l4Dzc7P5llKyhqSi1ycSf1Hg==" + }, + "@types/d3-timer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.0.tgz", + "integrity": "sha512-HNB/9GHqu7Fo8AQiugyJbv6ZxYz58wef0esl4Mv828w1ZKpAshw/uFWVDUcIB9KKFeFKoxS3cHY07FFgtTRZ1g==" + }, "@types/detect-port": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/@types/detect-port/-/detect-port-1.3.2.tgz", @@ -20224,6 +20626,26 @@ "dev": true, "requires": {} }, + "allotment": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/allotment/-/allotment-1.19.0.tgz", + "integrity": "sha512-qL/1faHUoCOvMstCFGkaGSK/nVoSAVN6NLFuIW4P6D5fvKdgxV8/KNfMFeuQUXReodILp4I6z+5a9zmuqK5t4g==", + "requires": { + "classnames": "^2.3.0", + "eventemitter3": "^5.0.0", + "lodash.clamp": "^4.0.0", + "lodash.debounce": "^4.0.0", + "lodash.isequal": "^4.5.0", + "use-resize-observer": "^9.0.0" + }, + "dependencies": { + "eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==" + } + } + }, "ansi-align": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", @@ -20975,6 +21397,11 @@ "integrity": "sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==", "dev": true }, + "classnames": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.2.tgz", + "integrity": "sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==" + }, "clean-css": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.2.tgz", @@ -21518,6 +21945,11 @@ "postcss-value-parser": "^4.0.2" } }, + "css-unit-converter": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/css-unit-converter/-/css-unit-converter-1.1.2.tgz", + "integrity": "sha512-IiJwMC8rdZE0+xiEZHeru6YoONC4rfPMqGm2W85jMIbkFvv5nFTwJVFHam2eFrN6txmoUYFAFXiv8ICVeTO0MA==" + }, "css-what": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", @@ -21535,6 +21967,83 @@ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz", "integrity": "sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==" }, + "d3-array": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.3.tgz", + "integrity": "sha512-JRHwbQQ84XuAESWhvIPaUV4/1UYTBOLiOPGWqgFDHZS1D5QN9c57FbH3QpEnQMYiOXNzKUQyGTZf+EVO7RT5TQ==", + "requires": { + "internmap": "1 - 2" + } + }, + "d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==" + }, + "d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==" + }, + "d3-format": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==" + }, + "d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "requires": { + "d3-color": "1 - 3" + } + }, + "d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==" + }, + "d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "requires": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + } + }, + "d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "requires": { + "d3-path": "^3.1.0" + } + }, + "d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "requires": { + "d3-array": "2 - 3" + } + }, + "d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "requires": { + "d3-time": "1 - 3" + } + }, + "d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==" + }, "date-fns": { "version": "2.29.3", "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.29.3.tgz", @@ -21572,6 +22081,11 @@ } } }, + "decimal.js-light": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz", + "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==" + }, "dedent": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", @@ -22532,6 +23046,11 @@ "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", "dev": true }, + "eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" + }, "events": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", @@ -22661,6 +23180,11 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true }, + "fast-equals": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-4.0.3.tgz", + "integrity": "sha512-G3BSX9cfKttjr+2o1O22tYMLq0DPluZnYtq1rXumE1SpL/F/SLIfHx08WYQoWSIpeMYf8sRbJ8++71+v6Pnxfg==" + }, "fast-glob": { "version": "3.2.12", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", @@ -23560,6 +24084,11 @@ "side-channel": "^1.0.4" } }, + "internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==" + }, "interpret": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", @@ -24461,11 +24990,20 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, + "lodash.clamp": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/lodash.clamp/-/lodash.clamp-4.0.3.tgz", + "integrity": "sha512-HvzRFWjtcguTW7yd8NJBshuNaCa8aqNFtnswdT7f/cMd/1YKy5Zzoq4W/Oxvnx9l7aeY258uSdDfM793+eLsVg==" + }, "lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", - "dev": true + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==" + }, + "lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==" }, "lodash.merge": { "version": "4.6.2", @@ -25766,12 +26304,55 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, + "react-lifecycles-compat": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", + "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" + }, "react-refresh": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz", "integrity": "sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A==", "dev": true }, + "react-resize-detector": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/react-resize-detector/-/react-resize-detector-8.1.0.tgz", + "integrity": "sha512-S7szxlaIuiy5UqLhLL1KY3aoyGHbZzsTpYal9eYMwCyKqoqoVLCmIgAgNyIM1FhnP2KyBygASJxdhejrzjMb+w==", + "requires": { + "lodash": "^4.17.21" + } + }, + "react-smooth": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-2.0.2.tgz", + "integrity": "sha512-pgqSp1q8rAGtF1bXQE0m3CHGLNfZZh5oA5o1tsPLXRHnKtkujMIJ8Ws5nO1mTySZf1c4vgwlEk+pHi3Ln6eYLw==", + "requires": { + "fast-equals": "^4.0.3", + "react-transition-group": "2.9.0" + }, + "dependencies": { + "dom-helpers": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-3.4.0.tgz", + "integrity": "sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA==", + "requires": { + "@babel/runtime": "^7.1.2" + } + }, + "react-transition-group": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.9.0.tgz", + "integrity": "sha512-+HzNTCHpeQyl4MJ/bdE0u6XRMe9+XG/+aL4mCxVN4DnPBQ0/5bfHWPDuOZUzYdMj94daZaZdCCc1Dzt9R/xSSg==", + "requires": { + "dom-helpers": "^3.4.0", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2", + "react-lifecycles-compat": "^3.0.4" + } + } + } + }, "react-transition-group": { "version": "4.4.5", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", @@ -25890,6 +26471,30 @@ } } }, + "recharts": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.6.2.tgz", + "integrity": "sha512-dVhNfgI21LlF+4AesO3mj+i+9YdAAjoGaDWIctUgH/G2iy14YVtb/DSUeic77xr19rbKCiq+pQGfeg2kJQDHig==", + "requires": { + "classnames": "^2.2.5", + "eventemitter3": "^4.0.1", + "lodash": "^4.17.19", + "react-is": "^16.10.2", + "react-resize-detector": "^8.0.4", + "react-smooth": "^2.0.2", + "recharts-scale": "^0.4.4", + "reduce-css-calc": "^2.1.8", + "victory-vendor": "^36.6.8" + } + }, + "recharts-scale": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/recharts-scale/-/recharts-scale-0.4.5.tgz", + "integrity": "sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==", + "requires": { + "decimal.js-light": "^2.4.1" + } + }, "rechoir": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", @@ -25909,6 +26514,22 @@ "strip-indent": "^3.0.0" } }, + "reduce-css-calc": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/reduce-css-calc/-/reduce-css-calc-2.1.8.tgz", + "integrity": "sha512-8liAVezDmUcH+tdzoEGrhfbGcP7nOV4NkGE3a74+qqvE7nt9i4sKLGBuZNOnpI4WiGksiNPklZxva80061QiPg==", + "requires": { + "css-unit-converter": "^1.1.1", + "postcss-value-parser": "^3.3.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" + } + } + }, "regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -27396,7 +28017,6 @@ "version": "9.1.0", "resolved": "https://registry.npmjs.org/use-resize-observer/-/use-resize-observer-9.1.0.tgz", "integrity": "sha512-R25VqO9Wb3asSD4eqtcxk8sJalvIOYBqS8MNZlpDSQ4l4xMQxC/J7Id9HoTqPq8FwULIn0PVW+OAqF2dyYbjow==", - "dev": true, "requires": { "@juggle/resize-observer": "^3.3.1" } @@ -27477,6 +28097,27 @@ "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", "dev": true }, + "victory-vendor": { + "version": "36.6.10", + "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.6.10.tgz", + "integrity": "sha512-7YqYGtsA4mByokBhCjk+ewwPhUfzhR1I3Da6/ZsZUv/31ceT77RKoaqrxRq5Ki+9we4uzf7+A+7aG2sfYhm7nA==", + "requires": { + "@types/d3-array": "^3.0.3", + "@types/d3-ease": "^3.0.0", + "@types/d3-interpolate": "^3.0.1", + "@types/d3-scale": "^4.0.2", + "@types/d3-shape": "^3.1.0", + "@types/d3-time": "^3.0.0", + "@types/d3-timer": "^3.0.0", + "d3-array": "^3.1.6", + "d3-ease": "^3.0.1", + "d3-interpolate": "^3.0.1", + "d3-scale": "^4.0.2", + "d3-shape": "^3.1.0", + "d3-time": "^3.0.0", + "d3-timer": "^3.0.1" + } + }, "walker": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", diff --git a/package.json b/package.json index 1d5be982d..919fc4fdb 100644 --- a/package.json +++ b/package.json @@ -79,12 +79,14 @@ "dependencies": { "@floating-ui/react": "^0.21.0", "@tanstack/react-table": "^8.7.8", + "allotment": "^1.19.0", "copy-to-clipboard": "^3.3.3", "date-fns": "^2.29.3", "react": "^18.2.0", "react-cool-dimensions": "^3.0.1", "react-dom": "^18.2.0", "react-transition-group": "^4.4.5", + "recharts": "^2.6.2", "styled-components": "^5.3.6" } } diff --git a/src/components/Assets/types.ts b/src/components/Assets/types.ts index 5917075bc..fffa5a3f7 100644 --- a/src/components/Assets/types.ts +++ b/src/components/Assets/types.ts @@ -1,4 +1,8 @@ -import { Duration } from "../../globals"; +import { + DurationPercentileWithChange, + SpanInfo, + SpanInstanceInfo +} from "../../types"; export interface AssetsProps { data?: AssetsData; @@ -25,38 +29,19 @@ export interface Insight { }; } -export interface DurationPercentiles { - percentile: number; - currentDuration: Duration; - previousDuration: Duration | null; - changeTime: string | null; - changeVerified: boolean | null; - traceIds: string[]; +export interface AssetEntrySpanInfo extends SpanInfo { + classification: string; + role: string; } export interface AssetEntry { - span: { - classification: string; - role: string; - name: string; - displayName: string; - instrumentationLibrary: string; - methodCodeObjectId: string; - spanCodeObjectId: string; - kind: string; - codeObjectId: string; - }; + span: AssetEntrySpanInfo; assetType: string; serviceName: string; endpointCodeObjectId: string | null; - durationPercentiles: DurationPercentiles[]; + durationPercentiles: DurationPercentileWithChange[]; insights: Insight[]; - lastSpanInstanceInfo: { - traceId: string; - spanId: string; - startTime: string; - duration: Duration; - }; + lastSpanInstanceInfo: SpanInstanceInfo; firstDataSeenTime: string; } diff --git a/src/components/RecentActivity/LiveView/LiveView.stories.tsx b/src/components/RecentActivity/LiveView/LiveView.stories.tsx new file mode 100644 index 000000000..82b3aec3c --- /dev/null +++ b/src/components/RecentActivity/LiveView/LiveView.stories.tsx @@ -0,0 +1,25 @@ +import { Meta, StoryObj } from "@storybook/react"; +import { LiveView } from "."; +import { mockData } from "./mockData"; + +// More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction +const meta: Meta = { + title: "Recent Activity/LiveView", + component: LiveView, + parameters: { + // More on how to position stories at: https://storybook.js.org/docs/react/configure/story-layout + layout: "fullscreen" + } +}; + +export default meta; + +type Story = StoryObj; + +// More on writing stories with args: https://storybook.js.org/docs/react/writing-stories/args + +export const Default: Story = { + args: { + data: mockData + } +}; diff --git a/src/components/RecentActivity/LiveView/index.tsx b/src/components/RecentActivity/LiveView/index.tsx new file mode 100644 index 000000000..69dd843a2 --- /dev/null +++ b/src/components/RecentActivity/LiveView/index.tsx @@ -0,0 +1,199 @@ +import { format } from "date-fns"; +import { + Area, + CartesianGrid, + ComposedChart, + Line, + ResponsiveContainer, + XAxis, + YAxis +} from "recharts"; +import { DefaultTheme, useTheme } from "styled-components"; +import { isNumber } from "../../../typeGuards/isNumber"; +import { CrossIcon } from "../../common/icons/CrossIcon"; +import { DoubleCircleIcon } from "../../common/icons/DoubleCircleIcon"; +import { EndpointIcon } from "../../common/icons/EndpointIcon"; +import * as s from "./styles"; +import { LiveDataEntry, LiveViewProps, PercentileInfo } from "./types"; + +const PERCENTILES = [ + { label: "Median", percentile: 0.5 }, + { label: "Slowest 5%", percentile: 0.95 } +]; + +const getSpanIconColor = (theme: DefaultTheme) => { + switch (theme.mode) { + case "light": + return "#4d668a"; + case "dark": + case "dark-jetbrains": + return "#7891d0"; + } +}; + +const getLiveIconColor = (theme: DefaultTheme) => { + switch (theme.mode) { + case "light": + return "#426dda"; + case "dark": + case "dark-jetbrains": + return "#b9c2eb"; + } +}; + +const getCloseIconColor = (theme: DefaultTheme) => { + switch (theme.mode) { + case "light": + return "#6e6e6e"; + case "dark": + case "dark-jetbrains": + return "#afb1b3"; + } +}; + +const getLineColor = (theme: DefaultTheme) => { + switch (theme.mode) { + case "light": + return "#426dda"; + case "dark": + case "dark-jetbrains": + return "#b9c2eb"; + } +}; + +const getAxisColor = (theme: DefaultTheme) => { + switch (theme.mode) { + case "light": + return "#b9c0d4"; + case "dark": + case "dark-jetbrains": + return "#49494d"; + } +}; + +const getTickLabelColor = (theme: DefaultTheme) => { + switch (theme.mode) { + case "light": + return "#828797"; + case "dark": + case "dark-jetbrains": + return "#9b9b9b"; + } +}; + +const getAreaColor = (theme: DefaultTheme) => { + switch (theme.mode) { + case "light": + return "#7891d0"; + case "dark": + case "dark-jetbrains": + return "#5154ec"; + } +}; + +const formatDate = (datetime: string): string => + format(new Date(datetime), "HH:mm:ss.SSS"); + +export const LiveView = (props: LiveViewProps) => { + const theme = useTheme(); + const lineColor = getLineColor(theme); + const axisColor = getAxisColor(theme); + const areaColor = getAreaColor(theme); + const tickLabelColor = getTickLabelColor(theme); + + const percentiles = PERCENTILES.map((percentile) => ({ + ...percentile, + value: props.data.durationInsight.percentiles.find( + (x) => x.percentile === percentile.percentile + )?.currentDuration.raw + })).filter((x) => isNumber(x.value)) as PercentileInfo[]; + + const data: LiveDataEntry[] = [...props.data.liveDataRecords] + .reverse() + .map((x) => ({ + ...x, + percentiles + })); + + const handleCloseButtonClick = () => { + props.onClose(); + }; + + const spanName = props.data.durationInsight.spanInfo?.displayName; + + return ( + + + {spanName && ( + + + + + {spanName} + + )} + + + Live + + + + + + + + + + [ + x.percentiles[0].value, + x.percentiles[1].value + ]} + stroke={areaColor} + fill={areaColor} + fillOpacity={0.2} + /> + + x.value)} + tickFormatter={(x, i) => percentiles[i].label} + tick={{ + fill: tickLabelColor, + fontSize: 10, + width: 85 + }} + stroke={axisColor} + tickMargin={7} + /> + x.duration.raw} + stroke={lineColor} + dot={{ stroke: lineColor, fill: lineColor, r: 2 }} + /> + + + + + ); +}; diff --git a/src/components/RecentActivity/LiveView/mockData.ts b/src/components/RecentActivity/LiveView/mockData.ts new file mode 100644 index 000000000..a1d86304f --- /dev/null +++ b/src/components/RecentActivity/LiveView/mockData.ts @@ -0,0 +1,884 @@ +import { InsightCategory, InsightScope, InsightType } from "../../../types"; +import { LiveData } from "./types"; + +export const mockData: LiveData = { + liveDataRecords: [ + { + duration: { + value: 5.42, + unit: "ms", + raw: 5424000.0 + }, + dateTime: "2023-05-15T20:55:08.491486Z" + }, + { + duration: { + value: 6.18, + unit: "ms", + raw: 6181000.0 + }, + dateTime: "2023-05-15T20:55:08.484144Z" + }, + { + duration: { + value: 6.7, + unit: "ms", + raw: 6705000.0 + }, + dateTime: "2023-05-15T20:55:08.476678Z" + }, + { + duration: { + value: 6.23, + unit: "ms", + raw: 6232000.0 + }, + dateTime: "2023-05-15T20:55:08.469399Z" + }, + { + duration: { + value: 5.36, + unit: "ms", + raw: 5364000.0 + }, + dateTime: "2023-05-15T20:55:08.463486Z" + }, + { + duration: { + value: 6.25, + unit: "ms", + raw: 6252000.0 + }, + dateTime: "2023-05-15T20:55:08.45649Z" + }, + { + duration: { + value: 7.09, + unit: "ms", + raw: 7086000.0 + }, + dateTime: "2023-05-15T20:55:08.448348Z" + }, + { + duration: { + value: 5.51, + unit: "ms", + raw: 5514000.0 + }, + dateTime: "2023-05-15T20:55:08.441418Z" + }, + { + duration: { + value: 5.92, + unit: "ms", + raw: 5922000.0 + }, + dateTime: "2023-05-15T20:55:08.433369Z" + }, + { + duration: { + value: 5.66, + unit: "ms", + raw: 5655000.0 + }, + dateTime: "2023-05-15T20:55:08.427183Z" + }, + { + duration: { + value: 5.49, + unit: "ms", + raw: 5492000.0 + }, + dateTime: "2023-05-15T20:55:08.420245Z" + }, + { + duration: { + value: 5.58, + unit: "ms", + raw: 5578000.0 + }, + dateTime: "2023-05-15T20:55:08.414009Z" + }, + { + duration: { + value: 5.54, + unit: "ms", + raw: 5542000.0 + }, + dateTime: "2023-05-15T20:55:08.407718Z" + }, + { + duration: { + value: 6.06, + unit: "ms", + raw: 6062000.0 + }, + dateTime: "2023-05-15T20:55:08.400629Z" + }, + { + duration: { + value: 6.42, + unit: "ms", + raw: 6419000.0 + }, + dateTime: "2023-05-15T20:55:08.393341Z" + }, + { + duration: { + value: 5.77, + unit: "ms", + raw: 5766000.0 + }, + dateTime: "2023-05-15T20:55:08.386845Z" + }, + { + duration: { + value: 5.78, + unit: "ms", + raw: 5783000.0 + }, + dateTime: "2023-05-15T20:55:08.380328Z" + }, + { + duration: { + value: 5.7, + unit: "ms", + raw: 5703000.0 + }, + dateTime: "2023-05-15T20:55:08.373934Z" + }, + { + duration: { + value: 6.92, + unit: "ms", + raw: 6920000.0 + }, + dateTime: "2023-05-15T20:55:08.365194Z" + }, + { + duration: { + value: 5.52, + unit: "ms", + raw: 5515000.0 + }, + dateTime: "2023-05-15T20:55:08.358688Z" + }, + { + duration: { + value: 8.5, + unit: "ms", + raw: 8504000.0 + }, + dateTime: "2023-05-15T20:55:08.348849Z" + }, + { + duration: { + value: 6.11, + unit: "ms", + raw: 6108000.0 + }, + dateTime: "2023-05-15T20:55:08.341788Z" + }, + { + duration: { + value: 6.42, + unit: "ms", + raw: 6421000.0 + }, + dateTime: "2023-05-15T20:55:08.33408Z" + }, + { + duration: { + value: 6.78, + unit: "ms", + raw: 6785000.0 + }, + dateTime: "2023-05-15T20:55:08.326452Z" + }, + { + duration: { + value: 10.5, + unit: "ms", + raw: 10496000.0 + }, + dateTime: "2023-05-15T20:55:08.314516Z" + }, + { + duration: { + value: 7.64, + unit: "ms", + raw: 7643000.0 + }, + dateTime: "2023-05-15T20:55:08.303768Z" + }, + { + duration: { + value: 6.85, + unit: "ms", + raw: 6848000.0 + }, + dateTime: "2023-05-15T20:55:08.294392Z" + }, + { + duration: { + value: 5.7, + unit: "ms", + raw: 5705000.0 + }, + dateTime: "2023-05-15T20:55:08.287545Z" + }, + { + duration: { + value: 5.96, + unit: "ms", + raw: 5962000.0 + }, + dateTime: "2023-05-15T20:55:08.280037Z" + }, + { + duration: { + value: 7.12, + unit: "ms", + raw: 7123000.0 + }, + dateTime: "2023-05-15T20:55:08.265721Z" + }, + { + duration: { + value: 6.19, + unit: "ms", + raw: 6190000.0 + }, + dateTime: "2023-05-15T20:55:08.257448Z" + }, + { + duration: { + value: 7.33, + unit: "ms", + raw: 7334000.0 + }, + dateTime: "2023-05-15T20:55:08.248898Z" + }, + { + duration: { + value: 5.88, + unit: "ms", + raw: 5882000.0 + }, + dateTime: "2023-05-15T20:55:08.237502Z" + }, + { + duration: { + value: 6.0, + unit: "ms", + raw: 6003000.0 + }, + dateTime: "2023-05-15T20:55:08.230341Z" + }, + { + duration: { + value: 5.88, + unit: "ms", + raw: 5885000.0 + }, + dateTime: "2023-05-15T20:55:08.223674Z" + }, + { + duration: { + value: 5.47, + unit: "ms", + raw: 5469000.0 + }, + dateTime: "2023-05-15T20:55:08.214741Z" + }, + { + duration: { + value: 5.44, + unit: "ms", + raw: 5438000.0 + }, + dateTime: "2023-05-15T20:55:08.20848Z" + }, + { + duration: { + value: 6.69, + unit: "ms", + raw: 6687000.0 + }, + dateTime: "2023-05-15T20:55:08.200079Z" + }, + { + duration: { + value: 5.65, + unit: "ms", + raw: 5652000.0 + }, + dateTime: "2023-05-15T20:55:08.193541Z" + }, + { + duration: { + value: 5.86, + unit: "ms", + raw: 5857000.0 + }, + dateTime: "2023-05-15T20:55:08.186328Z" + }, + { + duration: { + value: 5.52, + unit: "ms", + raw: 5515000.0 + }, + dateTime: "2023-05-15T20:55:08.180081Z" + }, + { + duration: { + value: 5.56, + unit: "ms", + raw: 5555000.0 + }, + dateTime: "2023-05-15T20:55:08.173781Z" + }, + { + duration: { + value: 5.58, + unit: "ms", + raw: 5583000.0 + }, + dateTime: "2023-05-15T20:55:08.167073Z" + }, + { + duration: { + value: 5.5, + unit: "ms", + raw: 5497000.0 + }, + dateTime: "2023-05-15T20:55:08.160823Z" + }, + { + duration: { + value: 6.88, + unit: "ms", + raw: 6883000.0 + }, + dateTime: "2023-05-15T20:55:08.152939Z" + }, + { + duration: { + value: 5.49, + unit: "ms", + raw: 5490000.0 + }, + dateTime: "2023-05-15T20:55:08.145679Z" + }, + { + duration: { + value: 6.33, + unit: "ms", + raw: 6326000.0 + }, + dateTime: "2023-05-15T20:55:08.136397Z" + }, + { + duration: { + value: 6.12, + unit: "ms", + raw: 6116000.0 + }, + dateTime: "2023-05-15T20:55:08.128706Z" + }, + { + duration: { + value: 5.57, + unit: "ms", + raw: 5574000.0 + }, + dateTime: "2023-05-15T20:55:08.113816Z" + }, + { + duration: { + value: 16.52, + unit: "ms", + raw: 16517000.0 + }, + dateTime: "2023-05-15T20:55:08.093456Z" + }, + { + duration: { + value: 5.27, + unit: "ms", + raw: 5266000.0 + }, + dateTime: "2023-05-15T20:32:22.854431Z" + }, + { + duration: { + value: 5.42, + unit: "ms", + raw: 5418000.0 + }, + dateTime: "2023-05-15T20:32:22.848743Z" + }, + { + duration: { + value: 6.27, + unit: "ms", + raw: 6273000.0 + }, + dateTime: "2023-05-15T20:32:22.842156Z" + }, + { + duration: { + value: 5.52, + unit: "ms", + raw: 5524000.0 + }, + dateTime: "2023-05-15T20:32:22.836188Z" + }, + { + duration: { + value: 5.78, + unit: "ms", + raw: 5781000.0 + }, + dateTime: "2023-05-15T20:32:22.830112Z" + }, + { + duration: { + value: 5.78, + unit: "ms", + raw: 5778000.0 + }, + dateTime: "2023-05-15T20:32:22.824025Z" + }, + { + duration: { + value: 5.77, + unit: "ms", + raw: 5770000.0 + }, + dateTime: "2023-05-15T20:32:22.817972Z" + }, + { + duration: { + value: 6.52, + unit: "ms", + raw: 6520000.0 + }, + dateTime: "2023-05-15T20:32:22.811146Z" + }, + { + duration: { + value: 5.43, + unit: "ms", + raw: 5429000.0 + }, + dateTime: "2023-05-15T20:32:22.805435Z" + }, + { + duration: { + value: 5.62, + unit: "ms", + raw: 5625000.0 + }, + dateTime: "2023-05-15T20:32:22.79945Z" + }, + { + duration: { + value: 5.32, + unit: "ms", + raw: 5316000.0 + }, + dateTime: "2023-05-15T20:32:22.793818Z" + }, + { + duration: { + value: 5.43, + unit: "ms", + raw: 5426000.0 + }, + dateTime: "2023-05-15T20:32:22.787967Z" + }, + { + duration: { + value: 5.7, + unit: "ms", + raw: 5700000.0 + }, + dateTime: "2023-05-15T20:32:22.782014Z" + }, + { + duration: { + value: 5.83, + unit: "ms", + raw: 5827000.0 + }, + dateTime: "2023-05-15T20:32:22.775849Z" + }, + { + duration: { + value: 6.21, + unit: "ms", + raw: 6206000.0 + }, + dateTime: "2023-05-15T20:32:22.769162Z" + }, + { + duration: { + value: 5.59, + unit: "ms", + raw: 5594000.0 + }, + dateTime: "2023-05-15T20:32:22.763282Z" + }, + { + duration: { + value: 5.3, + unit: "ms", + raw: 5298000.0 + }, + dateTime: "2023-05-15T20:32:22.757595Z" + }, + { + duration: { + value: 5.93, + unit: "ms", + raw: 5930000.0 + }, + dateTime: "2023-05-15T20:32:22.751328Z" + }, + { + duration: { + value: 6.48, + unit: "ms", + raw: 6477000.0 + }, + dateTime: "2023-05-15T20:32:22.744535Z" + }, + { + duration: { + value: 5.22, + unit: "ms", + raw: 5219000.0 + }, + dateTime: "2023-05-15T20:32:22.739025Z" + }, + { + duration: { + value: 5.26, + unit: "ms", + raw: 5261000.0 + }, + dateTime: "2023-05-15T20:32:22.733472Z" + }, + { + duration: { + value: 5.7, + unit: "ms", + raw: 5696000.0 + }, + dateTime: "2023-05-15T20:32:22.727498Z" + }, + { + duration: { + value: 5.3, + unit: "ms", + raw: 5302000.0 + }, + dateTime: "2023-05-15T20:32:22.721696Z" + }, + { + duration: { + value: 5.28, + unit: "ms", + raw: 5282000.0 + }, + dateTime: "2023-05-15T20:32:22.716047Z" + }, + { + duration: { + value: 5.29, + unit: "ms", + raw: 5294000.0 + }, + dateTime: "2023-05-15T20:32:22.710412Z" + }, + { + duration: { + value: 5.33, + unit: "ms", + raw: 5326000.0 + }, + dateTime: "2023-05-15T20:32:22.704619Z" + }, + { + duration: { + value: 5.53, + unit: "ms", + raw: 5532000.0 + }, + dateTime: "2023-05-15T20:32:22.698742Z" + }, + { + duration: { + value: 5.24, + unit: "ms", + raw: 5241000.0 + }, + dateTime: "2023-05-15T20:32:22.693142Z" + }, + { + duration: { + value: 5.41, + unit: "ms", + raw: 5408000.0 + }, + dateTime: "2023-05-15T20:32:22.687328Z" + }, + { + duration: { + value: 6.38, + unit: "ms", + raw: 6377000.0 + }, + dateTime: "2023-05-15T20:32:22.680668Z" + }, + { + duration: { + value: 6.12, + unit: "ms", + raw: 6123000.0 + }, + dateTime: "2023-05-15T20:32:22.674225Z" + }, + { + duration: { + value: 6.09, + unit: "ms", + raw: 6091000.0 + }, + dateTime: "2023-05-15T20:32:22.667707Z" + }, + { + duration: { + value: 6.48, + unit: "ms", + raw: 6478000.0 + }, + dateTime: "2023-05-15T20:32:22.660967Z" + }, + { + duration: { + value: 5.36, + unit: "ms", + raw: 5361000.0 + }, + dateTime: "2023-05-15T20:32:22.655061Z" + }, + { + duration: { + value: 5.26, + unit: "ms", + raw: 5261000.0 + }, + dateTime: "2023-05-15T20:32:22.649463Z" + }, + { + duration: { + value: 5.56, + unit: "ms", + raw: 5558000.0 + }, + dateTime: "2023-05-15T20:32:22.643544Z" + }, + { + duration: { + value: 5.41, + unit: "ms", + raw: 5411000.0 + }, + dateTime: "2023-05-15T20:32:22.637717Z" + }, + { + duration: { + value: 5.27, + unit: "ms", + raw: 5267000.0 + }, + dateTime: "2023-05-15T20:32:22.63204Z" + }, + { + duration: { + value: 5.52, + unit: "ms", + raw: 5522000.0 + }, + dateTime: "2023-05-15T20:32:22.626062Z" + }, + { + duration: { + value: 6.46, + unit: "ms", + raw: 6456000.0 + }, + dateTime: "2023-05-15T20:32:22.618915Z" + }, + { + duration: { + value: 5.37, + unit: "ms", + raw: 5367000.0 + }, + dateTime: "2023-05-15T20:32:22.613257Z" + }, + { + duration: { + value: 5.56, + unit: "ms", + raw: 5561000.0 + }, + dateTime: "2023-05-15T20:32:22.607334Z" + }, + { + duration: { + value: 6.49, + unit: "ms", + raw: 6491000.0 + }, + dateTime: "2023-05-15T20:32:22.600531Z" + }, + { + duration: { + value: 5.37, + unit: "ms", + raw: 5367000.0 + }, + dateTime: "2023-05-15T20:32:22.594809Z" + }, + { + duration: { + value: 6.24, + unit: "ms", + raw: 6243000.0 + }, + dateTime: "2023-05-15T20:32:22.588216Z" + }, + { + duration: { + value: 5.16, + unit: "ms", + raw: 5158000.0 + }, + dateTime: "2023-05-15T20:32:22.582783Z" + }, + { + duration: { + value: 5.87, + unit: "ms", + raw: 5872000.0 + }, + dateTime: "2023-05-15T20:32:22.576547Z" + }, + { + duration: { + value: 5.46, + unit: "ms", + raw: 5464000.0 + }, + dateTime: "2023-05-15T20:32:22.570563Z" + }, + { + duration: { + value: 5.29, + unit: "ms", + raw: 5287000.0 + }, + dateTime: "2023-05-15T20:32:22.564778Z" + }, + { + duration: { + value: 8.29, + unit: "ms", + raw: 8287000.0 + }, + dateTime: "2023-05-15T20:32:22.556144Z" + } + ], + durationInsight: { + name: "Performance Stats", + type: InsightType.SpanDurations, + category: InsightCategory.Performance, + specifity: 4, + isRecalculateEnabled: true, + spanCodeObjectId: + "span:OpenTelemetry.Instrumentation.AspNetCore$_$HTTP GET SampleInsights/NormalUsage", + span: { + name: "HTTP GET SampleInsights/NormalUsage", + displayName: "HTTP GET SampleInsights/NormalUsage", + instrumentationLibrary: "OpenTelemetry.Instrumentation.AspNetCore", + spanCodeObjectId: + "span:OpenTelemetry.Instrumentation.AspNetCore$_$HTTP GET SampleInsights/NormalUsage", + methodCodeObjectId: + "method:Sample.MoneyTransfer.API.Controllers.SampleInsightsController$_$NormalUsage", + kind: "Server", + codeObjectId: + "Sample.MoneyTransfer.API.Controllers.SampleInsightsController$_$NormalUsage" + }, + periodicPercentiles: [], + percentiles: [ + { + percentile: 0.5, + currentDuration: { + value: 5.7, + unit: "ms", + raw: 5704000.0 + }, + previousDuration: null, + changeTime: null, + changeVerified: null, + traceIds: ["FF6A6EDDD99D70B50BD9DDA14447BDDB"] + }, + { + percentile: 0.95, + currentDuration: { + value: 7.49, + unit: "ms", + raw: 7488500.0 + }, + previousDuration: null, + changeTime: null, + changeVerified: null, + traceIds: ["54AEA66680710C8AEFD496DF65E291E3"] + } + ], + lastSpanInstanceInfo: null, + scope: InsightScope.Span, + spanInfo: { + name: "HTTP GET SampleInsights/NormalUsage", + displayName: "HTTP GET SampleInsights/NormalUsage", + instrumentationLibrary: "OpenTelemetry.Instrumentation.AspNetCore", + spanCodeObjectId: + "span:OpenTelemetry.Instrumentation.AspNetCore$_$HTTP GET SampleInsights/NormalUsage", + methodCodeObjectId: + "method:Sample.MoneyTransfer.API.Controllers.SampleInsightsController$_$NormalUsage", + kind: "Server", + codeObjectId: + "Sample.MoneyTransfer.API.Controllers.SampleInsightsController$_$NormalUsage" + }, + shortDisplayInfo: { + title: "", + targetDisplayName: "", + subtitle: "", + description: "" + }, + codeObjectId: + "method:Sample.MoneyTransfer.API.Controllers.SampleInsightsController$_$NormalUsage", + decorators: [], + environment: "KYRYLOS-MACBOOK-PRO.LOCAL[LOCAL]", + importance: 5, + severity: 0.0, + prefixedCodeObjectId: null, + customStartTime: null, + actualStartTime: null + } +}; diff --git a/src/components/RecentActivity/LiveView/styles.ts b/src/components/RecentActivity/LiveView/styles.ts new file mode 100644 index 000000000..980320bd0 --- /dev/null +++ b/src/components/RecentActivity/LiveView/styles.ts @@ -0,0 +1,120 @@ +import styled from "styled-components"; + +export const Container = styled.div` + display: flex; + flex-direction: column; + min-width: 150px; + height: 100%; + + border: 1px solid + ${({ theme }) => { + switch (theme.mode) { + case "light": + return "#d1d1d1"; + case "dark": + case "dark-jetbrains": + return "#323232"; + } + }}; +`; + +export const Header = styled.div` + display: flex; + align-items: center; + padding: 4px 8px; + gap: 12px; + font-weight: 500; + font-size: 10px; + line-height: 12px; + + border-bottom: ${({ theme }) => { + switch (theme.mode) { + case "light": + return "1px solid #d1d1d1"; + case "dark": + case "dark-jetbrains": + return "none"; + } + }}; + + background: ${({ theme }) => { + switch (theme.mode) { + case "light": + return "#e3e7ed"; + case "dark": + case "dark-jetbrains": + return "#3c3f41"; + } + }}; +`; + +export const Title = styled.span` + display: flex; + align-items: center; + gap: 4px; + overflow: hidden; +`; + +export const SpanIconContainer = styled.span` + display: flex; +`; + +export const SpanName = styled.span` + font-size: 12px; + line-height: 14px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + + color: ${({ theme }) => { + switch (theme.mode) { + case "light": + return "#4d668a"; + case "dark": + case "dark-jetbrains": + return "#7891d0"; + } + }}; +`; + +export const LiveBadge = styled.span` + display: flex; + align-items: center; + gap: 2px; + margin-left: auto; + padding: 2px 4px; + + color: ${({ theme }) => { + switch (theme.mode) { + case "light": + return "#426dda"; + case "dark": + case "dark-jetbrains": + return "#b9c2eb"; + } + }}; +`; + +export const CloseButton = styled.button` + display: flex; + padding: 0; + background: none; + border: none; + cursor: pointer; +`; + +export const ChartContainer = styled.div` + padding: 44px 12px 6px; + height: 100%; + overflow: auto; + + background: ${({ theme }) => { + switch (theme.mode) { + case "light": + return "#fbfdff"; + case "dark": + case "dark-jetbrains": + return "#383838"; + } + }}; +`; diff --git a/src/components/RecentActivity/LiveView/types.ts b/src/components/RecentActivity/LiveView/types.ts new file mode 100644 index 000000000..f3665d281 --- /dev/null +++ b/src/components/RecentActivity/LiveView/types.ts @@ -0,0 +1,24 @@ +import { Duration } from "../../../globals"; +import { SpanDurationsInsight } from "../../../types"; + +export interface LiveViewProps { + data: LiveData; + onClose: () => void; +} + +export interface LiveData { + liveDataRecords: { duration: Duration; dateTime: string }[]; + durationInsight: SpanDurationsInsight; +} + +export interface PercentileInfo { + value: number; + label: string; + percentile: number; +} + +export interface LiveDataEntry { + dateTime: string; + duration: Duration; + percentiles: PercentileInfo[]; +} diff --git a/src/components/RecentActivity/RecentActivity.stories.tsx b/src/components/RecentActivity/RecentActivity.stories.tsx index d4be31d60..1eb5b8029 100644 --- a/src/components/RecentActivity/RecentActivity.stories.tsx +++ b/src/components/RecentActivity/RecentActivity.stories.tsx @@ -1,5 +1,6 @@ import { Meta, StoryObj } from "@storybook/react"; import { RecentActivity } from "."; +import { mockData as liveData } from "./LiveView/mockData"; // More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction const meta: Meta = { @@ -18,534 +19,540 @@ type Story = StoryObj; // More on writing stories with args: https://storybook.js.org/docs/react/writing-stories/args export const Empty: Story = {}; -export const WithData: Story = { - args: { - data: { - environments: [ - "ENV_RENDER", - "UNSET_ENV", - "UNSET_ENV1", - "UNSET_ENV2", - "UNSET_ENV3", - "UNSET_ENV4", - "UNSET_ENV5", - "UNSET_ENV6", - "UNSET_ENV7", - "VERY-VERY-VERY-VERY-VERY-VERY-VERY-VERY-VERY-VERY-VERY-VERY-VERY-VERY-LONG-NAME" - ], - entries: [ - { - environment: "ENV_RENDER", - traceFlowDisplayName: "PetClinicWithAgent:HTTP GET /webjars/**", - firstEntrySpan: { - displayText: "PetClinicWithAgent:HTTP GET /webjars/**", - serviceName: "PetClinicWithAgent", - scopeId: "HTTP GET /webjars/**", - spanCodeObjectId: - "span:io.opentelemetry.tomcat-10.0$_$HTTP GET /webjars/**", - methodCodeObjectId: "" - }, - lastEntrySpan: null, - latestTraceId: "DB80F24773E2BBE574E97960F9CB0D64", - latestTraceTimestamp: "2023-02-07T12:59:21.794Z", - latestTraceDuration: { - value: 3.9, - unit: "ms", - raw: 3902134 - }, - slimAggregatedInsights: [] - }, - { - environment: "ENV_RENDER", - traceFlowDisplayName: "PetClinicWithAgent:HTTP GET /webjars/**", - firstEntrySpan: { - displayText: "PetClinicWithAgent:HTTP GET /webjars/**", - serviceName: "PetClinicWithAgent", - scopeId: "HTTP GET /webjars/**", - spanCodeObjectId: - "span:io.opentelemetry.tomcat-10.0$_$HTTP GET /webjars/**", - methodCodeObjectId: "" - }, - lastEntrySpan: null, - latestTraceId: "DB80F24773E2BBE574E97960F9CB0D64", - latestTraceTimestamp: "2023-02-07T12:59:21.794Z", - latestTraceDuration: { - value: 3.9, - unit: "ms", - raw: 3902134 - }, - slimAggregatedInsights: [] - }, - { - environment: "ENV_RENDER", - traceFlowDisplayName: "PetClinicWithAgent:HTTP GET /owners/{ownerId}", - firstEntrySpan: { - displayText: "PetClinicWithAgent:HTTP GET /owners/{ownerId}", - serviceName: "PetClinicWithAgent", - scopeId: "HTTP GET /owners/{ownerId}", - spanCodeObjectId: - "span:io.opentelemetry.tomcat-10.0$_$HTTP GET /owners/{ownerId}", - methodCodeObjectId: - "org.springframework.samples.petclinic.owner.OwnerController$_$showOwner" - }, - lastEntrySpan: null, - latestTraceId: "64CFA03CF78919C46ECBC96B9EC07E35", - latestTraceTimestamp: "2023-01-30T09:39:43.03647Z", - latestTraceDuration: { - value: 9.96, - unit: "ms", - raw: 9964194 - }, - slimAggregatedInsights: [ - { - type: "HotSpot", - codeObjectIds: [ - "org.springframework.samples.petclinic.owner.OwnerController$_$showOwner" - ], - importance: 2 - } - ] - }, - { - environment: "ENV_RENDER", - traceFlowDisplayName: "PetClinicWithAgent:HTTP GET /owners", - firstEntrySpan: { - displayText: "PetClinicWithAgent:HTTP GET /owners", - serviceName: "PetClinicWithAgent", - scopeId: "HTTP GET /owners", - spanCodeObjectId: - "span:io.opentelemetry.tomcat-10.0$_$HTTP GET /owners", - methodCodeObjectId: - "org.springframework.samples.petclinic.owner.OwnerController$_$processFindForm" - }, - lastEntrySpan: null, - latestTraceId: "BD9F4E2166D527AD168672E4A7DEB4FA", - latestTraceTimestamp: "2023-01-30T09:39:42.140086Z", - latestTraceDuration: { - value: 13, - unit: "ms", - raw: 12998642 - }, - slimAggregatedInsights: [ - { - type: "SlowestSpans", - codeObjectIds: [ - "org.springframework.samples.petclinic.owner.OwnerController$_$processFindForm" - ], - importance: 2 - }, - { - type: "SpanEndpointBottleneck", - codeObjectIds: [ - "org.springframework.samples.petclinic.owner.OwnerRepository$_$findByLastName" - ], - importance: 2 - } - ] - }, - { - environment: "ENV_RENDER", - traceFlowDisplayName: "PetClinicWithAgent:HTTP GET /owners/find", - firstEntrySpan: { - displayText: "PetClinicWithAgent:HTTP GET /owners/find", - serviceName: "PetClinicWithAgent", - scopeId: "HTTP GET /owners/find", - spanCodeObjectId: - "span:io.opentelemetry.tomcat-10.0$_$HTTP GET /owners/find", - methodCodeObjectId: - "org.springframework.samples.petclinic.owner.OwnerController$_$initFindForm" - }, - lastEntrySpan: null, - latestTraceId: "8A2E125F7165A13B08D62E0EDC0A62FF", - latestTraceTimestamp: "2023-01-30T09:39:40.214951Z", - latestTraceDuration: { - value: 4.45, - unit: "ms", - raw: 4446862 - }, - slimAggregatedInsights: [] - }, - { - environment: "ENV_RENDER", - traceFlowDisplayName: "PetClinicWithAgent:HTTP GET /", - firstEntrySpan: { - displayText: "PetClinicWithAgent:HTTP GET /", - serviceName: "PetClinicWithAgent", - scopeId: "HTTP GET /", - spanCodeObjectId: "span:io.opentelemetry.tomcat-10.0$_$HTTP GET /", - methodCodeObjectId: - "org.springframework.samples.petclinic.system.WelcomeController$_$welcome" - }, - lastEntrySpan: null, - latestTraceId: "4A819C438C6A18E669180D08729DC48F", - latestTraceTimestamp: "2023-01-30T09:39:39.669249Z", - latestTraceDuration: { - value: 4.55, - unit: "ms", - raw: 4549011 - }, - slimAggregatedInsights: [] - }, - { - environment: "ENV_RENDER", - traceFlowDisplayName: "PetClinicWithAgent:HTTP GET /vets.html", - firstEntrySpan: { - displayText: "PetClinicWithAgent:HTTP GET /vets.html", - serviceName: "PetClinicWithAgent", - scopeId: "HTTP GET /vets.html", - spanCodeObjectId: - "span:io.opentelemetry.tomcat-10.0$_$HTTP GET /vets.html", - methodCodeObjectId: - "org.springframework.samples.petclinic.vet.VetController$_$showVetList" - }, - lastEntrySpan: null, - latestTraceId: "66CE458E5FF9F54E930DE5DB48B07E65", - latestTraceTimestamp: "2023-01-30T09:39:37.650562Z", - latestTraceDuration: { - value: 58.49, - unit: "ms", - raw: 58492826 - }, - slimAggregatedInsights: [ - { - type: "SlowestSpans", - codeObjectIds: [ - "org.springframework.samples.petclinic.vet.VetController$_$showVetList" - ], - importance: 2 - }, - { - type: "SpanEndpointBottleneck", - codeObjectIds: [ - "io.opentelemetry.spring-webmvc-6.0$_$Render vets/vetList" - ], - importance: 2 - } - ] - }, - { - environment: "ENV_RENDER", - traceFlowDisplayName: - "PetClinicWithAgent:HTTP POST /owners/{ownerId}/pets/new", - firstEntrySpan: { - displayText: - "PetClinicWithAgent:HTTP POST /owners/{ownerId}/pets/new", - serviceName: "PetClinicWithAgent", - scopeId: "HTTP POST /owners/{ownerId}/pets/new", - spanCodeObjectId: - "span:io.opentelemetry.tomcat-10.0$_$HTTP POST /owners/{ownerId}/pets/new", - methodCodeObjectId: - "org.springframework.samples.petclinic.owner.PetController$_$processCreationForm" - }, - lastEntrySpan: null, - latestTraceId: "3E72B5F5AF009AE6141C616DAB4999DB", - latestTraceTimestamp: "2023-01-30T09:29:23.259494Z", - latestTraceDuration: { - value: 39.86, - unit: "ms", - raw: 39863411 - }, - slimAggregatedInsights: [ - { - type: "SpanEndpointBottleneck", - codeObjectIds: [ - "org.springframework.samples.petclinic.owner.OwnerRepository$_$save" - ], - importance: 2 - } - ] - }, - { - environment: "ENV_RENDER", - traceFlowDisplayName: - "PetClinicWithAgent:HTTP GET /owners/{ownerId}/pets/new", - firstEntrySpan: { - displayText: - "PetClinicWithAgent:HTTP GET /owners/{ownerId}/pets/new", - serviceName: "PetClinicWithAgent", - scopeId: "HTTP GET /owners/{ownerId}/pets/new", - spanCodeObjectId: - "span:io.opentelemetry.tomcat-10.0$_$HTTP GET /owners/{ownerId}/pets/new", - methodCodeObjectId: - "org.springframework.samples.petclinic.owner.PetController$_$initCreationForm" - }, - lastEntrySpan: null, - latestTraceId: "5D2B5A3CE1E127A5BFBFD953573DE985", - latestTraceTimestamp: "2023-01-30T09:29:16.681077Z", - latestTraceDuration: { - value: 37.3, - unit: "ms", - raw: 37296736 - }, - slimAggregatedInsights: [] - }, - { - environment: "ENV_RENDER", - traceFlowDisplayName: - "PetClinicWithAgent:HTTP POST /owners/{ownerId}/edit", - firstEntrySpan: { - displayText: "PetClinicWithAgent:HTTP POST /owners/{ownerId}/edit", - serviceName: "PetClinicWithAgent", - scopeId: "HTTP POST /owners/{ownerId}/edit", - spanCodeObjectId: - "span:io.opentelemetry.tomcat-10.0$_$HTTP POST /owners/{ownerId}/edit", - methodCodeObjectId: - "org.springframework.samples.petclinic.owner.OwnerController$_$processUpdateOwnerForm" - }, - lastEntrySpan: null, - latestTraceId: "C6C9545A395BAFB37D2FF42EB96183EB", - latestTraceTimestamp: "2023-01-30T09:25:57.35119Z", - latestTraceDuration: { - value: 124.8, - unit: "ms", - raw: 124800876 - }, - slimAggregatedInsights: [ - { - type: "SlowestSpans", - codeObjectIds: [ - "org.springframework.samples.petclinic.owner.OwnerController$_$processUpdateOwnerForm" - ], - importance: 2 - }, - { - type: "SpanEndpointBottleneck", - codeObjectIds: [ - "org.springframework.samples.petclinic.owner.OwnerRepository$_$save" - ], - importance: 2 - } - ] - }, - { - environment: "ENV_RENDER", - traceFlowDisplayName: - "PetClinicWithAgent:HTTP GET /owners/{ownerId}/edit", - firstEntrySpan: { - displayText: "PetClinicWithAgent:HTTP GET /owners/{ownerId}/edit", - serviceName: "PetClinicWithAgent", - scopeId: "HTTP GET /owners/{ownerId}/edit", - spanCodeObjectId: - "span:io.opentelemetry.tomcat-10.0$_$HTTP GET /owners/{ownerId}/edit", - methodCodeObjectId: - "org.springframework.samples.petclinic.owner.OwnerController$_$initUpdateOwnerForm" - }, - lastEntrySpan: null, - latestTraceId: "B6CFB46AE4F090D43B38CF3A51291A14", - latestTraceTimestamp: "2023-01-30T09:25:49.164936Z", - latestTraceDuration: { - value: 32.85, - unit: "ms", - raw: 32848772 - }, - slimAggregatedInsights: [] - }, +const data = { + environments: [ + "ENV_RENDER", + "UNSET_ENV", + "UNSET_ENV1", + "UNSET_ENV2", + "UNSET_ENV3", + "UNSET_ENV4", + "UNSET_ENV5", + "UNSET_ENV6", + "UNSET_ENV7", + "VERY-VERY-VERY-VERY-VERY-VERY-VERY-VERY-VERY-VERY-VERY-VERY-VERY-VERY-LONG-NAME" + ], + entries: [ + { + environment: "ENV_RENDER", + traceFlowDisplayName: "PetClinicWithAgent:HTTP GET /webjars/**", + firstEntrySpan: { + displayText: "PetClinicWithAgent:HTTP GET /webjars/**", + serviceName: "PetClinicWithAgent", + scopeId: "HTTP GET /webjars/**", + spanCodeObjectId: + "span:io.opentelemetry.tomcat-10.0$_$HTTP GET /webjars/**", + methodCodeObjectId: "" + }, + lastEntrySpan: null, + latestTraceId: "DB80F24773E2BBE574E97960F9CB0D64", + latestTraceTimestamp: "2023-02-07T12:59:21.794Z", + latestTraceDuration: { + value: 3.9, + unit: "ms", + raw: 3902134 + }, + slimAggregatedInsights: [] + }, + { + environment: "ENV_RENDER", + traceFlowDisplayName: "PetClinicWithAgent:HTTP GET /webjars/**", + firstEntrySpan: { + displayText: "PetClinicWithAgent:HTTP GET /webjars/**", + serviceName: "PetClinicWithAgent", + scopeId: "HTTP GET /webjars/**", + spanCodeObjectId: + "span:io.opentelemetry.tomcat-10.0$_$HTTP GET /webjars/**", + methodCodeObjectId: "" + }, + lastEntrySpan: null, + latestTraceId: "DB80F24773E2BBE574E97960F9CB0D64", + latestTraceTimestamp: "2023-02-07T12:59:21.794Z", + latestTraceDuration: { + value: 3.9, + unit: "ms", + raw: 3902134 + }, + slimAggregatedInsights: [] + }, + { + environment: "ENV_RENDER", + traceFlowDisplayName: "PetClinicWithAgent:HTTP GET /owners/{ownerId}", + firstEntrySpan: { + displayText: "PetClinicWithAgent:HTTP GET /owners/{ownerId}", + serviceName: "PetClinicWithAgent", + scopeId: "HTTP GET /owners/{ownerId}", + spanCodeObjectId: + "span:io.opentelemetry.tomcat-10.0$_$HTTP GET /owners/{ownerId}", + methodCodeObjectId: + "org.springframework.samples.petclinic.owner.OwnerController$_$showOwner" + }, + lastEntrySpan: null, + latestTraceId: "64CFA03CF78919C46ECBC96B9EC07E35", + latestTraceTimestamp: "2023-01-30T09:39:43.03647Z", + latestTraceDuration: { + value: 9.96, + unit: "ms", + raw: 9964194 + }, + slimAggregatedInsights: [ { - environment: "UNSET_ENV", - traceFlowDisplayName: "my-first-mn-app:HTTP GET /whiskey/get/{name}", - firstEntrySpan: { - displayText: "na:na", - serviceName: "na", - scopeId: "na", - spanCodeObjectId: "na", - methodCodeObjectId: null - }, - lastEntrySpan: null, - latestTraceId: "ED0C93D748A2035AD021377A6F3C2F86", - latestTraceTimestamp: "2023-01-29T13:09:13.341318Z", - latestTraceDuration: { - value: 1.09, - unit: "ms", - raw: 1087700 - }, - slimAggregatedInsights: [] - }, - { - environment: "UNSET_ENV", - traceFlowDisplayName: "my-first-mn-app:HTTP GET /book/get/{id}", - firstEntrySpan: { - displayText: "na:na", - serviceName: "na", - scopeId: "na", - spanCodeObjectId: "na", - methodCodeObjectId: null - }, - lastEntrySpan: null, - latestTraceId: "7A2EF4162A16FE7836841DD60C1C610A", - latestTraceTimestamp: "2023-01-29T12:53:17.361202Z", - latestTraceDuration: { - value: 38.24, - unit: "ms", - raw: 38239200 - }, - slimAggregatedInsights: [] - }, - { - environment: "UNSET_ENV", - traceFlowDisplayName: "my-first-mn-app:HTTP GET /book/id/{id}", - firstEntrySpan: { - displayText: "na:na", - serviceName: "na", - scopeId: "na", - spanCodeObjectId: "na", - methodCodeObjectId: null - }, - lastEntrySpan: null, - latestTraceId: "C0AAEAE7D9B8929CEA1F3D1464A80C99", - latestTraceTimestamp: "2023-01-29T12:53:02.290752Z", - latestTraceDuration: { - value: 7.62, - unit: "ms", - raw: 7619500 - }, - slimAggregatedInsights: [] - }, + type: "HotSpot", + codeObjectIds: [ + "org.springframework.samples.petclinic.owner.OwnerController$_$showOwner" + ], + importance: 2 + } + ] + }, + { + environment: "ENV_RENDER", + traceFlowDisplayName: "PetClinicWithAgent:HTTP GET /owners", + firstEntrySpan: { + displayText: "PetClinicWithAgent:HTTP GET /owners", + serviceName: "PetClinicWithAgent", + scopeId: "HTTP GET /owners", + spanCodeObjectId: + "span:io.opentelemetry.tomcat-10.0$_$HTTP GET /owners", + methodCodeObjectId: + "org.springframework.samples.petclinic.owner.OwnerController$_$processFindForm" + }, + lastEntrySpan: null, + latestTraceId: "BD9F4E2166D527AD168672E4A7DEB4FA", + latestTraceTimestamp: "2023-01-30T09:39:42.140086Z", + latestTraceDuration: { + value: 13, + unit: "ms", + raw: 12998642 + }, + slimAggregatedInsights: [ { - environment: "UNSET_ENV", - traceFlowDisplayName: "my-first-mn-app:HTTP GET GET - /", - firstEntrySpan: { - displayText: "na:na", - serviceName: "na", - scopeId: "na", - spanCodeObjectId: "na", - methodCodeObjectId: null - }, - lastEntrySpan: null, - latestTraceId: "D7EFB24B92C2EFEB6EB4E9F9181BD737", - latestTraceTimestamp: "2023-01-29T12:52:53.9949Z", - latestTraceDuration: { - value: 238.79, - unit: "ms", - raw: 238791600 - }, - slimAggregatedInsights: [] + type: "SlowestSpans", + codeObjectIds: [ + "org.springframework.samples.petclinic.owner.OwnerController$_$processFindForm" + ], + importance: 2 }, { - environment: "UNSET_ENV", - traceFlowDisplayName: - "my-first-mn-app:HTTP GET /users/get/{username}", - firstEntrySpan: { - displayText: "na:na", - serviceName: "na", - scopeId: "na", - spanCodeObjectId: "na", - methodCodeObjectId: null - }, - lastEntrySpan: null, - latestTraceId: "FB593F5978A19F9FD09B71ECD49C22B4", - latestTraceTimestamp: "2023-01-29T12:33:05.89872Z", - latestTraceDuration: { - value: 1.32, - unit: "ms", - raw: 1321000 - }, - slimAggregatedInsights: [] - }, + type: "SpanEndpointBottleneck", + codeObjectIds: [ + "org.springframework.samples.petclinic.owner.OwnerRepository$_$findByLastName" + ], + importance: 2 + } + ] + }, + { + environment: "ENV_RENDER", + traceFlowDisplayName: "PetClinicWithAgent:HTTP GET /owners/find", + firstEntrySpan: { + displayText: "PetClinicWithAgent:HTTP GET /owners/find", + serviceName: "PetClinicWithAgent", + scopeId: "HTTP GET /owners/find", + spanCodeObjectId: + "span:io.opentelemetry.tomcat-10.0$_$HTTP GET /owners/find", + methodCodeObjectId: + "org.springframework.samples.petclinic.owner.OwnerController$_$initFindForm" + }, + lastEntrySpan: null, + latestTraceId: "8A2E125F7165A13B08D62E0EDC0A62FF", + latestTraceTimestamp: "2023-01-30T09:39:40.214951Z", + latestTraceDuration: { + value: 4.45, + unit: "ms", + raw: 4446862 + }, + slimAggregatedInsights: [] + }, + { + environment: "ENV_RENDER", + traceFlowDisplayName: "PetClinicWithAgent:HTTP GET /", + firstEntrySpan: { + displayText: "PetClinicWithAgent:HTTP GET /", + serviceName: "PetClinicWithAgent", + scopeId: "HTTP GET /", + spanCodeObjectId: "span:io.opentelemetry.tomcat-10.0$_$HTTP GET /", + methodCodeObjectId: + "org.springframework.samples.petclinic.system.WelcomeController$_$welcome" + }, + lastEntrySpan: null, + latestTraceId: "4A819C438C6A18E669180D08729DC48F", + latestTraceTimestamp: "2023-01-30T09:39:39.669249Z", + latestTraceDuration: { + value: 4.55, + unit: "ms", + raw: 4549011 + }, + slimAggregatedInsights: [] + }, + { + environment: "ENV_RENDER", + traceFlowDisplayName: "PetClinicWithAgent:HTTP GET /vets.html", + firstEntrySpan: { + displayText: "PetClinicWithAgent:HTTP GET /vets.html", + serviceName: "PetClinicWithAgent", + scopeId: "HTTP GET /vets.html", + spanCodeObjectId: + "span:io.opentelemetry.tomcat-10.0$_$HTTP GET /vets.html", + methodCodeObjectId: + "org.springframework.samples.petclinic.vet.VetController$_$showVetList" + }, + lastEntrySpan: null, + latestTraceId: "66CE458E5FF9F54E930DE5DB48B07E65", + latestTraceTimestamp: "2023-01-30T09:39:37.650562Z", + latestTraceDuration: { + value: 58.49, + unit: "ms", + raw: 58492826 + }, + slimAggregatedInsights: [ { - environment: "UNSET_ENV", - traceFlowDisplayName: "my-first-mn-app:HTTP GET /book", - firstEntrySpan: { - displayText: "na:na", - serviceName: "na", - scopeId: "na", - spanCodeObjectId: "na", - methodCodeObjectId: null - }, - lastEntrySpan: null, - latestTraceId: "77284D368A3122C01B4ACCC18C83AEFF", - latestTraceTimestamp: "2023-01-29T12:32:35.333618Z", - latestTraceDuration: { - value: 1.11, - unit: "ms", - raw: 1113600 - }, - slimAggregatedInsights: [] + type: "SlowestSpans", + codeObjectIds: [ + "org.springframework.samples.petclinic.vet.VetController$_$showVetList" + ], + importance: 2 }, { - environment: "UNSET_ENV", - traceFlowDisplayName: - "PetClinicWithAgent:HTTP GET /SampleInsights/HighUsage", - firstEntrySpan: { - displayText: "na:na", - serviceName: "na", - scopeId: "na", - spanCodeObjectId: "na", - methodCodeObjectId: null - }, - lastEntrySpan: null, - latestTraceId: "5AA14D37EFC218878A45E341DF4ACE2E", - latestTraceTimestamp: "2023-01-26T15:27:03.009074Z", - latestTraceDuration: { - value: 6, - unit: "ms", - raw: 6004792 - }, - slimAggregatedInsights: [] - }, + type: "SpanEndpointBottleneck", + codeObjectIds: [ + "io.opentelemetry.spring-webmvc-6.0$_$Render vets/vetList" + ], + importance: 2 + } + ] + }, + { + environment: "ENV_RENDER", + traceFlowDisplayName: + "PetClinicWithAgent:HTTP POST /owners/{ownerId}/pets/new", + firstEntrySpan: { + displayText: "PetClinicWithAgent:HTTP POST /owners/{ownerId}/pets/new", + serviceName: "PetClinicWithAgent", + scopeId: "HTTP POST /owners/{ownerId}/pets/new", + spanCodeObjectId: + "span:io.opentelemetry.tomcat-10.0$_$HTTP POST /owners/{ownerId}/pets/new", + methodCodeObjectId: + "org.springframework.samples.petclinic.owner.PetController$_$processCreationForm" + }, + lastEntrySpan: null, + latestTraceId: "3E72B5F5AF009AE6141C616DAB4999DB", + latestTraceTimestamp: "2023-01-30T09:29:23.259494Z", + latestTraceDuration: { + value: 39.86, + unit: "ms", + raw: 39863411 + }, + slimAggregatedInsights: [ { - environment: "UNSET_ENV", - traceFlowDisplayName: - "PetClinicWithAgent:HTTP GET /SampleInsights/ErrorHotspot", - firstEntrySpan: { - displayText: "na:na", - serviceName: "na", - scopeId: "na", - spanCodeObjectId: "na", - methodCodeObjectId: null - }, - lastEntrySpan: null, - latestTraceId: "3B692DFE97379C119BB4960DDFA51268", - latestTraceTimestamp: "2023-01-26T15:27:01.035306Z", - latestTraceDuration: { - value: 1.98, - unit: "ms", - raw: 1979067 - }, - slimAggregatedInsights: [] - }, + type: "SpanEndpointBottleneck", + codeObjectIds: [ + "org.springframework.samples.petclinic.owner.OwnerRepository$_$save" + ], + importance: 2 + } + ] + }, + { + environment: "ENV_RENDER", + traceFlowDisplayName: + "PetClinicWithAgent:HTTP GET /owners/{ownerId}/pets/new", + firstEntrySpan: { + displayText: "PetClinicWithAgent:HTTP GET /owners/{ownerId}/pets/new", + serviceName: "PetClinicWithAgent", + scopeId: "HTTP GET /owners/{ownerId}/pets/new", + spanCodeObjectId: + "span:io.opentelemetry.tomcat-10.0$_$HTTP GET /owners/{ownerId}/pets/new", + methodCodeObjectId: + "org.springframework.samples.petclinic.owner.PetController$_$initCreationForm" + }, + lastEntrySpan: null, + latestTraceId: "5D2B5A3CE1E127A5BFBFD953573DE985", + latestTraceTimestamp: "2023-01-30T09:29:16.681077Z", + latestTraceDuration: { + value: 37.3, + unit: "ms", + raw: 37296736 + }, + slimAggregatedInsights: [] + }, + { + environment: "ENV_RENDER", + traceFlowDisplayName: + "PetClinicWithAgent:HTTP POST /owners/{ownerId}/edit", + firstEntrySpan: { + displayText: "PetClinicWithAgent:HTTP POST /owners/{ownerId}/edit", + serviceName: "PetClinicWithAgent", + scopeId: "HTTP POST /owners/{ownerId}/edit", + spanCodeObjectId: + "span:io.opentelemetry.tomcat-10.0$_$HTTP POST /owners/{ownerId}/edit", + methodCodeObjectId: + "org.springframework.samples.petclinic.owner.OwnerController$_$processUpdateOwnerForm" + }, + lastEntrySpan: null, + latestTraceId: "C6C9545A395BAFB37D2FF42EB96183EB", + latestTraceTimestamp: "2023-01-30T09:25:57.35119Z", + latestTraceDuration: { + value: 124.8, + unit: "ms", + raw: 124800876 + }, + slimAggregatedInsights: [ { - environment: "UNSET_ENV", - traceFlowDisplayName: - "PetClinicWithAgent:HTTP GET /SampleInsights/SpanBottleneck", - firstEntrySpan: { - displayText: "na:na", - serviceName: "na", - scopeId: "na", - spanCodeObjectId: "na", - methodCodeObjectId: null - }, - lastEntrySpan: null, - latestTraceId: "CEB1C28473439717AA7F212B0991B312", - latestTraceTimestamp: "2023-01-26T15:27:00.776231Z", - latestTraceDuration: { - value: 250.87, - unit: "ms", - raw: 250869118 - }, - slimAggregatedInsights: [] + type: "SlowestSpans", + codeObjectIds: [ + "org.springframework.samples.petclinic.owner.OwnerController$_$processUpdateOwnerForm" + ], + importance: 2 }, { - environment: "UNSET_ENV", - traceFlowDisplayName: - "PetClinicWithAgent:HTTP GET /SampleInsights/SlowEndpoint", - firstEntrySpan: { - displayText: "na:na", - serviceName: "na", - scopeId: "na", - spanCodeObjectId: "na", - methodCodeObjectId: null - }, - lastEntrySpan: null, - latestTraceId: "ABA784C0D7BA52F4FBF529FB17B91DBC", - latestTraceTimestamp: "2023-01-26T15:26:58.01489Z", - latestTraceDuration: { - value: 2.51, - unit: "sec", - raw: 2505131829 - }, - slimAggregatedInsights: [] + type: "SpanEndpointBottleneck", + codeObjectIds: [ + "org.springframework.samples.petclinic.owner.OwnerRepository$_$save" + ], + importance: 2 } ] + }, + { + environment: "ENV_RENDER", + traceFlowDisplayName: + "PetClinicWithAgent:HTTP GET /owners/{ownerId}/edit", + firstEntrySpan: { + displayText: "PetClinicWithAgent:HTTP GET /owners/{ownerId}/edit", + serviceName: "PetClinicWithAgent", + scopeId: "HTTP GET /owners/{ownerId}/edit", + spanCodeObjectId: + "span:io.opentelemetry.tomcat-10.0$_$HTTP GET /owners/{ownerId}/edit", + methodCodeObjectId: + "org.springframework.samples.petclinic.owner.OwnerController$_$initUpdateOwnerForm" + }, + lastEntrySpan: null, + latestTraceId: "B6CFB46AE4F090D43B38CF3A51291A14", + latestTraceTimestamp: "2023-01-30T09:25:49.164936Z", + latestTraceDuration: { + value: 32.85, + unit: "ms", + raw: 32848772 + }, + slimAggregatedInsights: [] + }, + { + environment: "UNSET_ENV", + traceFlowDisplayName: "my-first-mn-app:HTTP GET /whiskey/get/{name}", + firstEntrySpan: { + displayText: "na:na", + serviceName: "na", + scopeId: "na", + spanCodeObjectId: "na", + methodCodeObjectId: null + }, + lastEntrySpan: null, + latestTraceId: "ED0C93D748A2035AD021377A6F3C2F86", + latestTraceTimestamp: "2023-01-29T13:09:13.341318Z", + latestTraceDuration: { + value: 1.09, + unit: "ms", + raw: 1087700 + }, + slimAggregatedInsights: [] + }, + { + environment: "UNSET_ENV", + traceFlowDisplayName: "my-first-mn-app:HTTP GET /book/get/{id}", + firstEntrySpan: { + displayText: "na:na", + serviceName: "na", + scopeId: "na", + spanCodeObjectId: "na", + methodCodeObjectId: null + }, + lastEntrySpan: null, + latestTraceId: "7A2EF4162A16FE7836841DD60C1C610A", + latestTraceTimestamp: "2023-01-29T12:53:17.361202Z", + latestTraceDuration: { + value: 38.24, + unit: "ms", + raw: 38239200 + }, + slimAggregatedInsights: [] + }, + { + environment: "UNSET_ENV", + traceFlowDisplayName: "my-first-mn-app:HTTP GET /book/id/{id}", + firstEntrySpan: { + displayText: "na:na", + serviceName: "na", + scopeId: "na", + spanCodeObjectId: "na", + methodCodeObjectId: null + }, + lastEntrySpan: null, + latestTraceId: "C0AAEAE7D9B8929CEA1F3D1464A80C99", + latestTraceTimestamp: "2023-01-29T12:53:02.290752Z", + latestTraceDuration: { + value: 7.62, + unit: "ms", + raw: 7619500 + }, + slimAggregatedInsights: [] + }, + { + environment: "UNSET_ENV", + traceFlowDisplayName: "my-first-mn-app:HTTP GET GET - /", + firstEntrySpan: { + displayText: "na:na", + serviceName: "na", + scopeId: "na", + spanCodeObjectId: "na", + methodCodeObjectId: null + }, + lastEntrySpan: null, + latestTraceId: "D7EFB24B92C2EFEB6EB4E9F9181BD737", + latestTraceTimestamp: "2023-01-29T12:52:53.9949Z", + latestTraceDuration: { + value: 238.79, + unit: "ms", + raw: 238791600 + }, + slimAggregatedInsights: [] + }, + { + environment: "UNSET_ENV", + traceFlowDisplayName: "my-first-mn-app:HTTP GET /users/get/{username}", + firstEntrySpan: { + displayText: "na:na", + serviceName: "na", + scopeId: "na", + spanCodeObjectId: "na", + methodCodeObjectId: null + }, + lastEntrySpan: null, + latestTraceId: "FB593F5978A19F9FD09B71ECD49C22B4", + latestTraceTimestamp: "2023-01-29T12:33:05.89872Z", + latestTraceDuration: { + value: 1.32, + unit: "ms", + raw: 1321000 + }, + slimAggregatedInsights: [] + }, + { + environment: "UNSET_ENV", + traceFlowDisplayName: "my-first-mn-app:HTTP GET /book", + firstEntrySpan: { + displayText: "na:na", + serviceName: "na", + scopeId: "na", + spanCodeObjectId: "na", + methodCodeObjectId: null + }, + lastEntrySpan: null, + latestTraceId: "77284D368A3122C01B4ACCC18C83AEFF", + latestTraceTimestamp: "2023-01-29T12:32:35.333618Z", + latestTraceDuration: { + value: 1.11, + unit: "ms", + raw: 1113600 + }, + slimAggregatedInsights: [] + }, + { + environment: "UNSET_ENV", + traceFlowDisplayName: + "PetClinicWithAgent:HTTP GET /SampleInsights/HighUsage", + firstEntrySpan: { + displayText: "na:na", + serviceName: "na", + scopeId: "na", + spanCodeObjectId: "na", + methodCodeObjectId: null + }, + lastEntrySpan: null, + latestTraceId: "5AA14D37EFC218878A45E341DF4ACE2E", + latestTraceTimestamp: "2023-01-26T15:27:03.009074Z", + latestTraceDuration: { + value: 6, + unit: "ms", + raw: 6004792 + }, + slimAggregatedInsights: [] + }, + { + environment: "UNSET_ENV", + traceFlowDisplayName: + "PetClinicWithAgent:HTTP GET /SampleInsights/ErrorHotspot", + firstEntrySpan: { + displayText: "na:na", + serviceName: "na", + scopeId: "na", + spanCodeObjectId: "na", + methodCodeObjectId: null + }, + lastEntrySpan: null, + latestTraceId: "3B692DFE97379C119BB4960DDFA51268", + latestTraceTimestamp: "2023-01-26T15:27:01.035306Z", + latestTraceDuration: { + value: 1.98, + unit: "ms", + raw: 1979067 + }, + slimAggregatedInsights: [] + }, + { + environment: "UNSET_ENV", + traceFlowDisplayName: + "PetClinicWithAgent:HTTP GET /SampleInsights/SpanBottleneck", + firstEntrySpan: { + displayText: "na:na", + serviceName: "na", + scopeId: "na", + spanCodeObjectId: "na", + methodCodeObjectId: null + }, + lastEntrySpan: null, + latestTraceId: "CEB1C28473439717AA7F212B0991B312", + latestTraceTimestamp: "2023-01-26T15:27:00.776231Z", + latestTraceDuration: { + value: 250.87, + unit: "ms", + raw: 250869118 + }, + slimAggregatedInsights: [] + }, + { + environment: "UNSET_ENV", + traceFlowDisplayName: + "PetClinicWithAgent:HTTP GET /SampleInsights/SlowEndpoint", + firstEntrySpan: { + displayText: "na:na", + serviceName: "na", + scopeId: "na", + spanCodeObjectId: "na", + methodCodeObjectId: null + }, + lastEntrySpan: null, + latestTraceId: "ABA784C0D7BA52F4FBF529FB17B91DBC", + latestTraceTimestamp: "2023-01-26T15:26:58.01489Z", + latestTraceDuration: { + value: 2.51, + unit: "sec", + raw: 2505131829 + }, + slimAggregatedInsights: [] } + ] +}; + +export const WithData: Story = { + args: { + data + } +}; + +export const WithLiveData: Story = { + args: { + data, + liveData } }; diff --git a/src/components/RecentActivity/RecentActivityTable/index.tsx b/src/components/RecentActivity/RecentActivityTable/index.tsx index fbe83a364..abe9dc68a 100644 --- a/src/components/RecentActivity/RecentActivityTable/index.tsx +++ b/src/components/RecentActivity/RecentActivityTable/index.tsx @@ -211,7 +211,9 @@ export const RecentActivityTable = (props: RecentActivityTableProps) => { {renderTimeDistance(entry.latestTraceTimestamp, props.viewMode)} {renderSpanLinks(entry)} {renderDuration(entry.latestTraceDuration, props.viewMode)} - {renderInsights(entry.slimAggregatedInsights)} + + {renderInsights(entry.slimAggregatedInsights)} + {props.isTraceButtonVisible && renderTraceButton(entry)} ))} diff --git a/src/components/RecentActivity/RecentActivityTable/styles.ts b/src/components/RecentActivity/RecentActivityTable/styles.ts index 8466cff05..22a0317b4 100644 --- a/src/components/RecentActivity/RecentActivityTable/styles.ts +++ b/src/components/RecentActivity/RecentActivityTable/styles.ts @@ -4,9 +4,8 @@ import { Link } from "../../common/Link"; export const Table = styled.table` width: 100%; - border-spacing: 0 3px; - font-weight: 400; - font-size: 14px; + border-spacing: 0 4px; + font-size: 12px; color: ${({ theme }) => { switch (theme.mode) { @@ -22,7 +21,7 @@ export const Table = styled.table` export const TableHead = styled.thead` font-size: 10px; - height: 26px; + height: 28px; color: ${({ theme }) => { switch (theme.mode) { @@ -37,6 +36,7 @@ export const TableHead = styled.thead` `; export const TableHeaderRow = styled.th` + padding-top: 4px; font-weight: 400; text-align: start; @@ -76,7 +76,7 @@ export const TableBody = styled.tbody` export const TableBodyRow = styled.tr` position: relative; - height: 36px; + height: 42px; `; export const TableBodyCell = styled.td` @@ -98,11 +98,13 @@ export const BadgeContainer = styled.div` export const TimeDistanceContainer = styled.span` display: flex; align-items: center; + font-weight: 500; `; export const DurationContainer = styled.span` display: flex; align-items: center; + font-weight: 500; `; export const InsightsContainer = styled.span` @@ -114,8 +116,8 @@ export const InsightIconContainer = styled.span` display: flex; justify-content: center; align-items: center; - width: 20px; - height: 20px; + width: 24px; + height: 24px; flex-shrink: 0; border-radius: 4px; @@ -133,12 +135,12 @@ export const InsightIconContainer = styled.span` export const TraceButtonContainer = styled.div` display: flex; justify-content: flex-end; - margin-left: auto; `; export const Suffix = styled.span` margin-left: 2px; font-size: 10px; + line-height: 12px; color: ${({ theme }) => { switch (theme.mode) { @@ -158,10 +160,9 @@ export const ListContainer = styled.div` `; export const ListHeader = styled.div` - margin: 8px 0; + margin: 12px 0 8px; padding-left: 12px; line-height: 16px; - font-weight: 400; font-size: 10px; color: ${({ theme }) => { @@ -179,12 +180,10 @@ export const ListHeader = styled.div` export const List = styled.ul` border-radius: 12px; margin: 0; - padding: 9px 0 6px; + padding: 0; list-style-type: none; display: flex; flex-direction: column; - gap: 22px; - font-weight: 400; color: ${({ theme }) => { switch (theme.mode) { @@ -211,11 +210,13 @@ export const List = styled.ul` export const ListItem = styled.li` position: relative; font-size: 12px; - padding: 0 18px 0 12px; - height: 18px; + line-height: 14px; + padding: 0 12px; + height: 42px; display: flex; align-items: center; gap: 8px; + font-weight: 500; `; export const ListBadgeContainer = styled.div` @@ -228,8 +229,14 @@ export const ListBadgeContainer = styled.div` height: fit-content; `; +export const ListInsightsContainer = styled.div` + margin-left: auto; + margin-right: 16px; +`; + export const ListSuffix = styled(Suffix)` font-size: 12px; + line-height: 14px; `; // postcss-styled-components-disable-next-line diff --git a/src/components/RecentActivity/index.tsx b/src/components/RecentActivity/index.tsx index 7b0848d4d..d30a6cbf3 100644 --- a/src/components/RecentActivity/index.tsx +++ b/src/components/RecentActivity/index.tsx @@ -1,3 +1,5 @@ +import { Allotment } from "allotment"; +import "allotment/dist/style.css"; import { useEffect, useMemo, useState } from "react"; import { dispatcher } from "../../dispatcher"; import { usePrevious } from "../../hooks/usePrevious"; @@ -8,8 +10,11 @@ import { CursorFollower } from "../common/CursorFollower"; import { DigmaLogoFlatIcon } from "../common/icons/DigmaLogoFlatIcon"; import { EnvironmentPanel } from "./EnvironmentPanel"; import { ViewMode } from "./EnvironmentPanel/types"; +import { LiveView } from "./LiveView"; +import { LiveData } from "./LiveView/types"; import { RecentActivityTable, isRecent } from "./RecentActivityTable"; import * as s from "./styles"; + import { EntrySpan, RecentActivityData, @@ -27,6 +32,7 @@ const ACTION_PREFIX = "RECENT_ACTIVITY"; const actions = addPrefix(ACTION_PREFIX, { INITIALIZE: "INITIALIZE", SET_DATA: "SET_DATA", + SET_LIVE_DATA: "SET_LIVE_DATA", GO_TO_SPAN: "GO_TO_SPAN", GO_TO_TRACE: "GO_TO_TRACE" }); @@ -65,6 +71,7 @@ export const RecentActivity = (props: RecentActivityProps) => { const [isJaegerEnabled, setIsJaegerEnabled] = useState( window.isJaegerEnabled === true ); + const [liveData, setLiveData] = useState(); useEffect(() => { window.sendMessageToDigma({ @@ -79,11 +86,16 @@ export const RecentActivity = (props: RecentActivityProps) => { setIsJaegerEnabled((data as SetIsJaegerData).isJaegerEnabled); }; + const handleLiveData = (data: unknown) => { + setLiveData(data as LiveData); + }; + dispatcher.addActionListener(actions.SET_DATA, handleRecentActivityData); dispatcher.addActionListener( globalActions.SET_IS_JAEGER_ENABLED, handleSetIsJaegerEnabledData ); + dispatcher.addActionListener(actions.SET_LIVE_DATA, handleLiveData); return () => { dispatcher.removeActionListener( @@ -105,6 +117,14 @@ export const RecentActivity = (props: RecentActivityProps) => { setData(props.data); }, [props.data]); + useEffect(() => { + if (!props.liveData) { + return; + } + + setLiveData(props.liveData); + }, [props.liveData]); + useEffect(() => { if (!previousSelectedEnvironment && data && data.environments.length) { setSelectedEnvironment(data.environments[0]); @@ -139,6 +159,10 @@ export const RecentActivity = (props: RecentActivityProps) => { setViewMode(mode); }; + const handleCloseLiveView = () => { + setLiveData(undefined); + }; + const environmentActivities = useMemo( () => (data ? groupBy(data.entries, "environment") : {}), [data] @@ -159,29 +183,40 @@ export const RecentActivity = (props: RecentActivityProps) => { return ( - - {!selectedEnvironment || - !environmentActivities[selectedEnvironment] || - !environmentActivities[selectedEnvironment].length ? ( - <> - Recent Activity - {renderNoData()} - - ) : ( - - )} + + + + {!selectedEnvironment || + !environmentActivities[selectedEnvironment] || + !environmentActivities[selectedEnvironment].length ? ( + <> + Recent Activity + {renderNoData()} + + ) : ( + + )} + + + {liveData && ( + + + + )} + + ); }; diff --git a/src/components/RecentActivity/styles.ts b/src/components/RecentActivity/styles.ts index d1c11348f..9bc7fb130 100644 --- a/src/components/RecentActivity/styles.ts +++ b/src/components/RecentActivity/styles.ts @@ -2,9 +2,8 @@ import styled from "styled-components"; import { Link } from "../common/Link"; export const Container = styled.div` - min-height: 100vh; - box-sizing: border-box; - padding: 12px; + height: 100%; + background: ${({ theme }) => { switch (theme.mode) { case "light": @@ -15,10 +14,41 @@ export const Container = styled.div` return "#383838"; } }}; + + .sash { + --sash-size: 12px; + } + + .sash-hover { + --focus-border: none; + } + + &&&&& .split-view-view::before { + margin: 12px 0; + height: calc(100% - 24px); + + --separator-border: ${({ theme }) => { + switch (theme.mode) { + case "light": + return "#b9c0d4"; + case "dark": + case "dark-jetbrains": + return "#9b9b9b"; + } + }}; + } +`; + +export const RecentActivityContainer = styled.div` + padding: 12px; + height: 100%; + overflow: auto; + box-sizing: border-box; + padding-right: 24px; `; export const Header = styled.div` - margin: 8px 0; + margin: 12px 0 8px; padding-left: 12px; line-height: 16px; font-weight: 400; @@ -100,3 +130,11 @@ export const DocumentationLink = styled(Link)` } }}; `; + +export const LiveViewContainer = styled.div` + padding: 12px; + padding-left: 24px; + height: 100%; + overflow-y: auto; + box-sizing: border-box; +`; diff --git a/src/components/RecentActivity/types.ts b/src/components/RecentActivity/types.ts index 1c3fb1b88..857a545c7 100644 --- a/src/components/RecentActivity/types.ts +++ b/src/components/RecentActivity/types.ts @@ -1,4 +1,5 @@ import { Duration } from "../../globals"; +import { LiveData } from "./LiveView/types"; export interface EntrySpan { displayText: string; @@ -32,6 +33,7 @@ export interface RecentActivityData { export interface RecentActivityProps { data?: RecentActivityData; + liveData?: LiveData; } export interface SetIsJaegerData { diff --git a/src/components/common/icons/CheckmarkCircleIcon.tsx b/src/components/common/icons/CheckmarkCircleIcon.tsx deleted file mode 100644 index 7b25649ce..000000000 --- a/src/components/common/icons/CheckmarkCircleIcon.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import React from "react"; -import { useIconProps } from "./hooks"; -import { IconProps } from "./types"; - -const CheckmarkCircleIconComponent = (props: IconProps) => { - const { size, color } = useIconProps(props); - - return ( - - - - - ); -}; - -export const CheckmarkCircleIcon = React.memo(CheckmarkCircleIconComponent); diff --git a/src/components/common/icons/CrossCircleIcon.tsx b/src/components/common/icons/CrossCircleIcon.tsx deleted file mode 100644 index ab7f9d0c7..000000000 --- a/src/components/common/icons/CrossCircleIcon.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import React from "react"; -import { useIconProps } from "./hooks"; -import { IconProps } from "./types"; - -const CrossCircleIconComponent = (props: IconProps) => { - const { size, color } = useIconProps(props); - - return ( - - - - - ); -}; - -export const CrossCircleIcon = React.memo(CrossCircleIconComponent); diff --git a/src/components/common/icons/CrossIcon.tsx b/src/components/common/icons/CrossIcon.tsx new file mode 100644 index 000000000..a28e5740e --- /dev/null +++ b/src/components/common/icons/CrossIcon.tsx @@ -0,0 +1,24 @@ +import React from "react"; +import { useIconProps } from "./hooks"; +import { IconProps } from "./types"; + +const CrossIconComponent = (props: IconProps) => { + const { size, color } = useIconProps(props); + + return ( + + + + ); +}; + +export const CrossIcon = React.memo(CrossIconComponent); diff --git a/src/components/common/icons/CrosshairIcon.tsx b/src/components/common/icons/CrosshairIcon.tsx index 7c26d7a5d..ef88fbebf 100644 --- a/src/components/common/icons/CrosshairIcon.tsx +++ b/src/components/common/icons/CrosshairIcon.tsx @@ -17,7 +17,7 @@ const CrosshairIconComponent = (props: IconProps) => { stroke={color} strokeLinecap="round" strokeLinejoin="round" - d="M10 6H8.4M3.6 6H2m4-2.4V2m0 8V8.4M9.2 6a3.2 3.2 0 1 1-6.4 0 3.2 3.2 0 0 1 6.4 0Z" + d="M10 6a4 4 0 0 1-4 4m4-4a4 4 0 0 0-4-4m4 4H7.87M6 10a4 4 0 0 1-4-4m4 4V7.88M2 6a4 4 0 0 1 4-4M2 6h2.13M6 2v2.13" /> ); diff --git a/src/components/common/icons/DataIcon.tsx b/src/components/common/icons/DataIcon.tsx deleted file mode 100644 index 51b43221b..000000000 --- a/src/components/common/icons/DataIcon.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import React from "react"; -import { useIconProps } from "./hooks"; -import { IconProps } from "./types"; - -const DataIconComponent = (props: IconProps) => { - const { size, color } = useIconProps(props); - - return ( - - - - - - - - - - - ); -}; - -export const DataIcon = React.memo(DataIconComponent); diff --git a/src/components/common/icons/DoubleCircleIcon.tsx b/src/components/common/icons/DoubleCircleIcon.tsx new file mode 100644 index 000000000..5d853cdab --- /dev/null +++ b/src/components/common/icons/DoubleCircleIcon.tsx @@ -0,0 +1,31 @@ +import React from "react"; +import { useIconProps } from "./hooks"; +import { IconProps } from "./types"; + +const DoubleCircleIconComponent = (props: IconProps) => { + const { size, color } = useIconProps(props); + + return ( + + + + + + + + + + + ); +}; + +export const DoubleCircleIcon = React.memo(DoubleCircleIconComponent); diff --git a/src/components/common/icons/WarningTriangleIcon.tsx b/src/components/common/icons/WarningTriangleIcon.tsx deleted file mode 100644 index 3e005259b..000000000 --- a/src/components/common/icons/WarningTriangleIcon.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import React from "react"; -import { useIconProps } from "./hooks"; -import { IconProps } from "./types"; - -const WarningTriangleIconComponent = (props: IconProps) => { - const { size, color } = useIconProps(props); - - return ( - - - - ); -}; - -export const WarningTriangleIcon = React.memo(WarningTriangleIconComponent); diff --git a/src/typeGuards/isNumber.ts b/src/typeGuards/isNumber.ts new file mode 100644 index 000000000..14f80c301 --- /dev/null +++ b/src/typeGuards/isNumber.ts @@ -0,0 +1 @@ +export const isNumber = (x: unknown): x is number => typeof x === "number"; diff --git a/src/types.ts b/src/types.ts index d974f042c..76ffd444c 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,3 +1,5 @@ +import { Duration } from "./globals"; + export enum InsightType { TopErrorFlows = "TopErrorFlows", SpanDurationChange = "SpanDurationChange", @@ -18,3 +20,126 @@ export enum InsightType { SpanScalingRootCause = "SpanScalingRootCause", SpanDurationBreakdown = "SpanDurationBreakdown" } + +export enum InsightScope { + EntrySpan = "EntrySpan", + Span = "Span", + Function = "Function", + ChildSpan = "ChildSpan" +} + +export enum InsightCategory { + Performance = "Performance", + Errors = "Errors", + Usage = "Usage" +} + +export enum InsightSpecificity { + ChildInfo = 6, + Symptomatic = 5, + OwnInsight = 4, + TargetFound = 3, + TargetAndReasonFound = 2, + PinPoint = 1 +} + +export enum InsightImportance { + Spam = 9, + Clutter = 8, + NotInteresting = 7, + Info = 6, + Interesting = 5, + Important = 4, + HighlyImportant = 3, + Critical = 2, + ShowStopper = 1 +} + +export interface SpanInfo { + name: string; + displayName: string; + instrumentationLibrary: string; + spanCodeObjectId: string; + methodCodeObjectId: string | null; + kind: string; + + /** + * @deprecated + */ + codeObjectId: string | null; +} + +export interface Insight { + category: string; + type: InsightType; + specifity: InsightSpecificity; +} + +export interface CodeObjectInsight extends Insight { + shortDisplayInfo: { + title: string; + targetDisplayName: string; + subtitle: string; + description: string; + }; + name: string; + scope: InsightScope; + codeObjectId: string; + decorators: { + title: string; + description: string; + }[]; + environment: string; + importance: InsightImportance; + severity: number; + isRecalculateEnabled: boolean; + prefixedCodeObjectId: string | null; + customStartTime: string | null; + actualStartTime: string | null; +} + +export interface SpanInsight extends CodeObjectInsight { + spanInfo: SpanInfo | null; +} + +export interface DurationPercentileWithChange { + percentile: number; + currentDuration: Duration; + previousDuration: Duration | null; + changeTime: string | null; + changeVerified: boolean | null; + traceIds: string[]; +} + +export interface SpanInstanceInfo { + traceId: string; + spanId: string; + startTime: string; + duration: Duration; +} + +export interface SpanDurationsInsight extends SpanInsight { + name: "Performance Stats"; + type: InsightType.SpanDurations; + category: InsightCategory.Performance; + specifity: InsightSpecificity.OwnInsight; + isRecalculateEnabled: true; + periodicPercentiles: { + percentile: number; + currentDuration: Duration; + previousDuration: Duration | null; + sampleTraces: string[]; + period: string; + }[]; + percentiles: DurationPercentileWithChange[]; + lastSpanInstanceInfo: SpanInstanceInfo | null; + + /** + * @deprecated + */ + spanCodeObjectId: string; + /** + * @deprecated + */ + span: SpanInfo; +} diff --git a/src/utils/getInsightImportanceColor.ts b/src/utils/getInsightImportanceColor.ts index ad99f37fe..6fe762416 100644 --- a/src/utils/getInsightImportanceColor.ts +++ b/src/utils/getInsightImportanceColor.ts @@ -11,10 +11,10 @@ export const getInsightImportanceColor = ( if (importance < 3) { switch (theme.mode) { case "light": - return "#f93967"; + return "#e00036"; case "dark": case "dark-jetbrains": - return "#e00036"; + return "#f93967"; } } if (importance < 5) { From bda875f19f93fcc3624ffe54c8758661059cfece Mon Sep 17 00:00:00 2001 From: Kyrylo Shmidt Date: Wed, 17 May 2023 12:07:32 +0200 Subject: [PATCH 02/14] Set minimum width for live view --- src/components/RecentActivity/index.tsx | 2 +- src/components/RecentActivity/styles.ts | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/RecentActivity/index.tsx b/src/components/RecentActivity/index.tsx index d30a6cbf3..636beff54 100644 --- a/src/components/RecentActivity/index.tsx +++ b/src/components/RecentActivity/index.tsx @@ -209,7 +209,7 @@ export const RecentActivity = (props: RecentActivityProps) => { /> )} - + {liveData && ( diff --git a/src/components/RecentActivity/styles.ts b/src/components/RecentActivity/styles.ts index 9bc7fb130..7d64bd1f0 100644 --- a/src/components/RecentActivity/styles.ts +++ b/src/components/RecentActivity/styles.ts @@ -135,6 +135,5 @@ export const LiveViewContainer = styled.div` padding: 12px; padding-left: 24px; height: 100%; - overflow-y: auto; box-sizing: border-box; `; From 56751782783efa0ab81f2388f329bfa497439573 Mon Sep 17 00:00:00 2001 From: Kyrylo Shmidt Date: Wed, 17 May 2023 12:16:51 +0200 Subject: [PATCH 03/14] Hide chart scroll --- src/components/RecentActivity/LiveView/styles.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/RecentActivity/LiveView/styles.ts b/src/components/RecentActivity/LiveView/styles.ts index 980320bd0..0c38f9de1 100644 --- a/src/components/RecentActivity/LiveView/styles.ts +++ b/src/components/RecentActivity/LiveView/styles.ts @@ -106,7 +106,7 @@ export const CloseButton = styled.button` export const ChartContainer = styled.div` padding: 44px 12px 6px; height: 100%; - overflow: auto; + overflow: hidden; background: ${({ theme }) => { switch (theme.mode) { From 30a1910cb7d420098e37e0d717410ab9d42d4fd1 Mon Sep 17 00:00:00 2001 From: Kyrylo Shmidt Date: Wed, 17 May 2023 14:44:27 +0200 Subject: [PATCH 04/14] Add message for closing the Live view in Recent activity --- src/components/RecentActivity/LiveView/index.tsx | 2 +- src/components/RecentActivity/LiveView/types.ts | 2 +- src/components/RecentActivity/index.tsx | 11 +++++++++-- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/components/RecentActivity/LiveView/index.tsx b/src/components/RecentActivity/LiveView/index.tsx index 69dd843a2..622e5d6cc 100644 --- a/src/components/RecentActivity/LiveView/index.tsx +++ b/src/components/RecentActivity/LiveView/index.tsx @@ -116,7 +116,7 @@ export const LiveView = (props: LiveViewProps) => { })); const handleCloseButtonClick = () => { - props.onClose(); + props.onClose(props.data.durationInsight.codeObjectId); }; const spanName = props.data.durationInsight.spanInfo?.displayName; diff --git a/src/components/RecentActivity/LiveView/types.ts b/src/components/RecentActivity/LiveView/types.ts index f3665d281..2ef670994 100644 --- a/src/components/RecentActivity/LiveView/types.ts +++ b/src/components/RecentActivity/LiveView/types.ts @@ -3,7 +3,7 @@ import { SpanDurationsInsight } from "../../../types"; export interface LiveViewProps { data: LiveData; - onClose: () => void; + onClose: (codeObjectId: string) => void; } export interface LiveData { diff --git a/src/components/RecentActivity/index.tsx b/src/components/RecentActivity/index.tsx index 636beff54..f809460ba 100644 --- a/src/components/RecentActivity/index.tsx +++ b/src/components/RecentActivity/index.tsx @@ -34,7 +34,8 @@ const actions = addPrefix(ACTION_PREFIX, { SET_DATA: "SET_DATA", SET_LIVE_DATA: "SET_LIVE_DATA", GO_TO_SPAN: "GO_TO_SPAN", - GO_TO_TRACE: "GO_TO_TRACE" + GO_TO_TRACE: "GO_TO_TRACE", + CLOSE_LIVE_VIEW: "CLOSE_LIVE_VIEW" }); const renderNoData = () => { @@ -159,8 +160,14 @@ export const RecentActivity = (props: RecentActivityProps) => { setViewMode(mode); }; - const handleCloseLiveView = () => { + const handleCloseLiveView = (codeObjectId: string) => { setLiveData(undefined); + window.sendMessageToDigma({ + action: actions.CLOSE_LIVE_VIEW, + payload: { + codeObjectId + } + }); }; const environmentActivities = useMemo( From 24c6cce148c603f6f7ed2e37c16689b22e6e46a4 Mon Sep 17 00:00:00 2001 From: Kyrylo Shmidt Date: Wed, 17 May 2023 20:21:17 +0200 Subject: [PATCH 05/14] Add zoom buttons --- .../RecentActivity/LiveView/index.tsx | 45 +++++++++++++-- .../RecentActivity/LiveView/styles.ts | 56 +++++++++++++++++-- .../common/icons/ClockWithTicksIcon.tsx | 24 ++++++++ src/components/common/icons/MinusIcon.tsx | 24 ++++++++ src/components/common/icons/PlusIcon.tsx | 24 ++++++++ 5 files changed, 164 insertions(+), 9 deletions(-) create mode 100644 src/components/common/icons/ClockWithTicksIcon.tsx create mode 100644 src/components/common/icons/MinusIcon.tsx create mode 100644 src/components/common/icons/PlusIcon.tsx diff --git a/src/components/RecentActivity/LiveView/index.tsx b/src/components/RecentActivity/LiveView/index.tsx index 622e5d6cc..a2fe184c6 100644 --- a/src/components/RecentActivity/LiveView/index.tsx +++ b/src/components/RecentActivity/LiveView/index.tsx @@ -1,4 +1,5 @@ import { format } from "date-fns"; +import { useState } from "react"; import { Area, CartesianGrid, @@ -13,9 +14,14 @@ import { isNumber } from "../../../typeGuards/isNumber"; import { CrossIcon } from "../../common/icons/CrossIcon"; import { DoubleCircleIcon } from "../../common/icons/DoubleCircleIcon"; import { EndpointIcon } from "../../common/icons/EndpointIcon"; +import { MinusIcon } from "../../common/icons/MinusIcon"; +import { PlusIcon } from "../../common/icons/PlusIcon"; import * as s from "./styles"; import { LiveDataEntry, LiveViewProps, PercentileInfo } from "./types"; +const ZOOM_FACTOR = 1.2; +const DEFAULT_CHART_WIDTH = 388; + const PERCENTILES = [ { label: "Median", percentile: 0.5 }, { label: "Slowest 5%", percentile: 0.95 } @@ -91,6 +97,16 @@ const getAreaColor = (theme: DefaultTheme) => { } }; +const getZoomButtonIconColor = (theme: DefaultTheme) => { + switch (theme.mode) { + case "light": + return "#7891d0"; + case "dark": + case "dark-jetbrains": + return "#e2e7ff"; + } +}; + const formatDate = (datetime: string): string => format(new Date(datetime), "HH:mm:ss.SSS"); @@ -100,6 +116,8 @@ export const LiveView = (props: LiveViewProps) => { const axisColor = getAxisColor(theme); const areaColor = getAreaColor(theme); const tickLabelColor = getTickLabelColor(theme); + const zoomButtonIconColor = getZoomButtonIconColor(theme); + const [chartWidth, setChartWidth] = useState(DEFAULT_CHART_WIDTH); const percentiles = PERCENTILES.map((percentile) => ({ ...percentile, @@ -121,6 +139,18 @@ export const LiveView = (props: LiveViewProps) => { const spanName = props.data.durationInsight.spanInfo?.displayName; + const handleZoomOutButtonClick = () => { + const newWidth = Math.round(chartWidth / ZOOM_FACTOR); + if (newWidth >= DEFAULT_CHART_WIDTH) { + setChartWidth(newWidth); + } + }; + + const handleZoomInButtonClick = () => { + const newWidth = Math.round(chartWidth * ZOOM_FACTOR); + setChartWidth(newWidth); + }; + return ( @@ -140,14 +170,21 @@ export const LiveView = (props: LiveViewProps) => { + + + + + + + + - + { @@ -16,6 +16,16 @@ export const Container = styled.div` return "#323232"; } }}; + + background: ${({ theme }) => { + switch (theme.mode) { + case "light": + return "#fbfdff"; + case "dark": + case "dark-jetbrains": + return "#383838"; + } + }}; `; export const Header = styled.div` @@ -104,17 +114,53 @@ export const CloseButton = styled.button` `; export const ChartContainer = styled.div` - padding: 44px 12px 6px; + padding: 0 12px 12px; height: 100%; - overflow: hidden; + overflow: auto; +`; + +export const ZoomButtonsContainer = styled.div` + display: flex; + justify-content: flex-end; + gap: 4px; + padding: 0 12px; +`; + +export const ZoomButton = styled.button` + box-sizing: border-box; + display: flex; + padding: 4px; + border-radius: 4px; + cursor: pointer; background: ${({ theme }) => { switch (theme.mode) { case "light": - return "#fbfdff"; + return "#f1f5fa"; case "dark": case "dark-jetbrains": - return "#383838"; + return "#2e2e2e"; + } + }}; + + border: 1px solid + ${({ theme }) => { + switch (theme.mode) { + case "light": + return "#fbfdff"; + case "dark": + case "dark-jetbrains": + return "#383838"; + } + }}; + + box-shadow: ${({ theme }) => { + switch (theme.mode) { + case "light": + return "1px 1px 3px rgba(0, 0, 0, 0.15)"; + case "dark": + case "dark-jetbrains": + return "1px 1px 4px rgba(0, 0, 0, 0.25)"; } }}; `; diff --git a/src/components/common/icons/ClockWithTicksIcon.tsx b/src/components/common/icons/ClockWithTicksIcon.tsx new file mode 100644 index 000000000..7fb0215ef --- /dev/null +++ b/src/components/common/icons/ClockWithTicksIcon.tsx @@ -0,0 +1,24 @@ +import React from "react"; +import { useIconProps } from "./hooks"; +import { IconProps } from "./types"; + +const ClockWithTicksIconComponent = (props: IconProps) => { + const { size, color } = useIconProps(props); + + return ( + + + + ); +}; + +export const ClockWithTicksIcon = React.memo(ClockWithTicksIconComponent); diff --git a/src/components/common/icons/MinusIcon.tsx b/src/components/common/icons/MinusIcon.tsx new file mode 100644 index 000000000..b33fbe5cf --- /dev/null +++ b/src/components/common/icons/MinusIcon.tsx @@ -0,0 +1,24 @@ +import React from "react"; +import { useIconProps } from "./hooks"; +import { IconProps } from "./types"; + +const MinusIconComponent = (props: IconProps) => { + const { size, color } = useIconProps(props); + + return ( + + + + ); +}; + +export const MinusIcon = React.memo(MinusIconComponent); diff --git a/src/components/common/icons/PlusIcon.tsx b/src/components/common/icons/PlusIcon.tsx new file mode 100644 index 000000000..250409194 --- /dev/null +++ b/src/components/common/icons/PlusIcon.tsx @@ -0,0 +1,24 @@ +import React from "react"; +import { useIconProps } from "./hooks"; +import { IconProps } from "./types"; + +const PlusIconComponent = (props: IconProps) => { + const { size, color } = useIconProps(props); + + return ( + + + + ); +}; + +export const PlusIcon = React.memo(PlusIconComponent); From d7ad993cc381a3e51a2508e92dd04c39c747ff36 Mon Sep 17 00:00:00 2001 From: Kyrylo Shmidt Date: Thu, 18 May 2023 15:22:08 +0200 Subject: [PATCH 06/14] Improve zoom logic --- .../RecentActivity/LiveView/index.tsx | 54 +++++++++++++++---- .../RecentActivity/LiveView/styles.ts | 2 +- 2 files changed, 44 insertions(+), 12 deletions(-) diff --git a/src/components/RecentActivity/LiveView/index.tsx b/src/components/RecentActivity/LiveView/index.tsx index a2fe184c6..06b076cd6 100644 --- a/src/components/RecentActivity/LiveView/index.tsx +++ b/src/components/RecentActivity/LiveView/index.tsx @@ -1,5 +1,6 @@ import { format } from "date-fns"; -import { useState } from "react"; +import { useEffect, useState } from "react"; +import useDimensions from "react-cool-dimensions"; import { Area, CartesianGrid, @@ -10,6 +11,7 @@ import { YAxis } from "recharts"; import { DefaultTheme, useTheme } from "styled-components"; +import { usePrevious } from "../../../hooks/usePrevious"; import { isNumber } from "../../../typeGuards/isNumber"; import { CrossIcon } from "../../common/icons/CrossIcon"; import { DoubleCircleIcon } from "../../common/icons/DoubleCircleIcon"; @@ -20,7 +22,6 @@ import * as s from "./styles"; import { LiveDataEntry, LiveViewProps, PercentileInfo } from "./types"; const ZOOM_FACTOR = 1.2; -const DEFAULT_CHART_WIDTH = 388; const PERCENTILES = [ { label: "Median", percentile: 0.5 }, @@ -117,7 +118,21 @@ export const LiveView = (props: LiveViewProps) => { const areaColor = getAreaColor(theme); const tickLabelColor = getTickLabelColor(theme); const zoomButtonIconColor = getZoomButtonIconColor(theme); - const [chartWidth, setChartWidth] = useState(DEFAULT_CHART_WIDTH); + const { observe, width } = useDimensions(); + const previousWidth = usePrevious(width); + const [containerInitialWidth, setContainerInitialWidth] = + useState(width); + const [chartWidth, setChartWidth] = useState(containerInitialWidth); + const [isZoomed, setIsZoomed] = useState(false); + + useEffect(() => { + if (previousWidth !== width) { + setContainerInitialWidth(width); + if (width > chartWidth) { + setChartWidth(width); + } + } + }, [width, previousWidth, chartWidth]); const percentiles = PERCENTILES.map((percentile) => ({ ...percentile, @@ -140,15 +155,29 @@ export const LiveView = (props: LiveViewProps) => { const spanName = props.data.durationInsight.spanInfo?.displayName; const handleZoomOutButtonClick = () => { - const newWidth = Math.round(chartWidth / ZOOM_FACTOR); - if (newWidth >= DEFAULT_CHART_WIDTH) { - setChartWidth(newWidth); + if (chartWidth > containerInitialWidth) { + const newWidth = Math.round(chartWidth / ZOOM_FACTOR); + + if (newWidth > containerInitialWidth) { + setChartWidth(newWidth); + setIsZoomed(true); + } else { + setChartWidth(containerInitialWidth); + setIsZoomed(false); + } } }; const handleZoomInButtonClick = () => { const newWidth = Math.round(chartWidth * ZOOM_FACTOR); - setChartWidth(newWidth); + + if (newWidth <= containerInitialWidth) { + setChartWidth(containerInitialWidth); + setIsZoomed(false); + } else { + setChartWidth(newWidth); + setIsZoomed(true); + } }; return ( @@ -178,13 +207,16 @@ export const LiveView = (props: LiveViewProps) => { - - + + Date: Thu, 18 May 2023 17:25:48 +0200 Subject: [PATCH 07/14] Add dates to the tick labels --- .../RecentActivity/LiveView/index.tsx | 59 ++++++++++++------- .../RecentActivity/LiveView/mockData.ts | 2 +- .../RecentActivity/LiveView/types.ts | 11 ++-- 3 files changed, 47 insertions(+), 25 deletions(-) diff --git a/src/components/RecentActivity/LiveView/index.tsx b/src/components/RecentActivity/LiveView/index.tsx index 06b076cd6..4644297d4 100644 --- a/src/components/RecentActivity/LiveView/index.tsx +++ b/src/components/RecentActivity/LiveView/index.tsx @@ -19,7 +19,7 @@ import { EndpointIcon } from "../../common/icons/EndpointIcon"; import { MinusIcon } from "../../common/icons/MinusIcon"; import { PlusIcon } from "../../common/icons/PlusIcon"; import * as s from "./styles"; -import { LiveDataEntry, LiveViewProps, PercentileInfo } from "./types"; +import { ExtendedLiveDataRecord, LiveViewProps, PercentileInfo } from "./types"; const ZOOM_FACTOR = 1.2; @@ -109,7 +109,7 @@ const getZoomButtonIconColor = (theme: DefaultTheme) => { }; const formatDate = (datetime: string): string => - format(new Date(datetime), "HH:mm:ss.SSS"); + format(new Date(datetime), "HH:mm:ss.SSS MM/dd/yyyy"); export const LiveView = (props: LiveViewProps) => { const theme = useTheme(); @@ -141,12 +141,12 @@ export const LiveView = (props: LiveViewProps) => { )?.currentDuration.raw })).filter((x) => isNumber(x.value)) as PercentileInfo[]; - const data: LiveDataEntry[] = [...props.data.liveDataRecords] - .reverse() - .map((x) => ({ + const data: ExtendedLiveDataRecord[] = [...props.data.liveDataRecords].map( + (x) => ({ ...x, percentiles - })); + }) + ); const handleCloseButtonClick = () => { props.onClose(props.data.durationInsight.codeObjectId); @@ -180,6 +180,19 @@ export const LiveView = (props: LiveViewProps) => { } }; + const YAxisTicks = [...new Set(percentiles.map((x) => x.value))]; + + const getYAxisTickLabel = (value: number): string => { + const labels: string[] = []; + percentiles.forEach((percentile) => { + if (percentile.value === value) { + labels.push(percentile.label); + } + }); + + return labels.join(" / "); + }; + return ( @@ -216,7 +229,8 @@ export const LiveView = (props: LiveViewProps) => { data={data} margin={{ left: 8, - right: 0 + right: 0, + bottom: 4 }} > { stroke={axisColor} horizontal={false} /> - [ - x.percentiles[0].value, - x.percentiles[1].value - ]} - stroke={areaColor} - fill={areaColor} - fillOpacity={0.2} - /> + {data.length > 1 && ( + { + const p50 = x.percentiles.find((p) => p.percentile === 0.5); + const p95 = x.percentiles.find((p) => p.percentile === 0.95); + + return p50 && p95 ? [p50.value, p95.value] : []; + }} + stroke={areaColor} + fill={areaColor} + fillOpacity={0.2} + /> + )} x.value)} - tickFormatter={(x, i) => percentiles[i].label} + ticks={YAxisTicks} + tickFormatter={getYAxisTickLabel} tick={{ fill: tickLabelColor, fontSize: 10, @@ -256,7 +275,7 @@ export const LiveView = (props: LiveViewProps) => { tickMargin={7} /> x.duration.raw} + dataKey={(x: ExtendedLiveDataRecord): number => x.duration.raw} stroke={lineColor} dot={{ stroke: lineColor, fill: lineColor, r: 2 }} /> diff --git a/src/components/RecentActivity/LiveView/mockData.ts b/src/components/RecentActivity/LiveView/mockData.ts index a1d86304f..38c0e65f7 100644 --- a/src/components/RecentActivity/LiveView/mockData.ts +++ b/src/components/RecentActivity/LiveView/mockData.ts @@ -9,7 +9,7 @@ export const mockData: LiveData = { unit: "ms", raw: 5424000.0 }, - dateTime: "2023-05-15T20:55:08.491486Z" + dateTime: "2022-09-30T20:55:08.491486Z" }, { duration: { diff --git a/src/components/RecentActivity/LiveView/types.ts b/src/components/RecentActivity/LiveView/types.ts index 2ef670994..fe7e1e882 100644 --- a/src/components/RecentActivity/LiveView/types.ts +++ b/src/components/RecentActivity/LiveView/types.ts @@ -6,8 +6,13 @@ export interface LiveViewProps { onClose: (codeObjectId: string) => void; } +interface LiveDataRecord { + dateTime: string; + duration: Duration; +} + export interface LiveData { - liveDataRecords: { duration: Duration; dateTime: string }[]; + liveDataRecords: LiveDataRecord[]; durationInsight: SpanDurationsInsight; } @@ -17,8 +22,6 @@ export interface PercentileInfo { percentile: number; } -export interface LiveDataEntry { - dateTime: string; - duration: Duration; +export interface ExtendedLiveDataRecord extends LiveDataRecord { percentiles: PercentileInfo[]; } From bc6f22c79909f57be6c585fdf9b666a1c610af26 Mon Sep 17 00:00:00 2001 From: Kyrylo Shmidt Date: Mon, 22 May 2023 10:10:16 +0200 Subject: [PATCH 08/14] Add legend and sticky axis to the Live view --- src/components/Assets/index.tsx | 8 +- .../InstallationWizard/Step/index.tsx | 8 +- src/components/InstallationWizard/index.tsx | 4 +- .../LiveView/TooltipContent/index.tsx | 6 + .../LiveView/TooltipContent/styles.ts | 40 ++ .../LiveView/TooltipContent/types.ts | 5 + .../RecentActivity/LiveView/index.tsx | 397 +++++++++++++----- .../RecentActivity/LiveView/mockData.ts | 61 +-- .../RecentActivity/LiveView/styles.ts | 36 +- .../RecentActivity/LiveView/types.ts | 30 +- .../RecentActivityTable/index.tsx | 8 +- src/components/RecentActivity/index.tsx | 8 +- src/components/common/App/index.tsx | 2 +- src/platform.ts | 6 +- src/types.ts | 81 ---- src/utils/addPrefix.ts | 6 +- src/utils/getInsightTypeInfo.ts | 3 +- src/utils/roundTo.ts | 4 + 18 files changed, 438 insertions(+), 275 deletions(-) create mode 100644 src/components/RecentActivity/LiveView/TooltipContent/index.tsx create mode 100644 src/components/RecentActivity/LiveView/TooltipContent/styles.ts create mode 100644 src/components/RecentActivity/LiveView/TooltipContent/types.ts create mode 100644 src/utils/roundTo.ts diff --git a/src/components/Assets/index.tsx b/src/components/Assets/index.tsx index 1d2148997..585330ed0 100644 --- a/src/components/Assets/index.tsx +++ b/src/components/Assets/index.tsx @@ -1,5 +1,6 @@ import { useEffect, useMemo, useState } from "react"; import { dispatcher } from "../../dispatcher"; +import { isNumber } from "../../typeGuards/isNumber"; import { addPrefix } from "../../utils/addPrefix"; import { groupBy } from "../../utils/groupBy"; import { AssetList } from "./AssetList"; @@ -13,10 +14,9 @@ import { GroupedAssetEntries } from "./types"; -const REFRESH_INTERVAL = - typeof window.assetsRefreshInterval === "number" - ? window.assetsRefreshInterval - : 10 * 1000; // in milliseconds +const REFRESH_INTERVAL = isNumber(window.assetsRefreshInterval) + ? window.assetsRefreshInterval + : 10 * 1000; // in milliseconds const ACTION_PREFIX = "ASSETS"; diff --git a/src/components/InstallationWizard/Step/index.tsx b/src/components/InstallationWizard/Step/index.tsx index 32f0182a4..824b6a271 100644 --- a/src/components/InstallationWizard/Step/index.tsx +++ b/src/components/InstallationWizard/Step/index.tsx @@ -2,6 +2,7 @@ import { useRef } from "react"; import useDimensions from "react-cool-dimensions"; import { CSSTransition } from "react-transition-group"; import { useTheme } from "styled-components"; +import { isNumber } from "../../../typeGuards/isNumber"; import { CheckmarkCircleInvertedIcon } from "../../common/icons/CheckmarkCircleInvertedIcon"; import * as s from "./styles"; import { StepProps } from "./types"; @@ -13,10 +14,9 @@ const DEFAULT_TRANSITION_DURATION = 300; // in milliseconds export const Step = (props: StepProps) => { const theme = useTheme(); - const transitionDuration = - typeof props.transitionDuration === "number" - ? props.transitionDuration - : DEFAULT_TRANSITION_DURATION; + const transitionDuration = isNumber(props.transitionDuration) + ? props.transitionDuration + : DEFAULT_TRANSITION_DURATION; const containerRef = useRef(null); const numberRef = useRef(null); diff --git a/src/components/InstallationWizard/index.tsx b/src/components/InstallationWizard/index.tsx index f3f47fa61..5a29c6ee5 100644 --- a/src/components/InstallationWizard/index.tsx +++ b/src/components/InstallationWizard/index.tsx @@ -6,6 +6,7 @@ import { IDE } from "../../globals"; import { useDebounce } from "../../hooks/useDebounce"; import { usePrevious } from "../../hooks/usePrevious"; import { ide } from "../../platform"; +import { isString } from "../../typeGuards/isString"; import { addPrefix } from "../../utils/addPrefix"; import { actions as globalActions } from "../common/App"; import { getThemeKind } from "../common/App/styles"; @@ -77,8 +78,7 @@ const firstStep = window.wizardSkipInstallationStep === true ? 1 : 0; const preselectedIsObservabilityEnabled = window.isObservabilityEnabled === true; -const preselectedEmail = - typeof window.userEmail === "string" ? window.userEmail : ""; +const preselectedEmail = isString(window.userEmail) ? window.userEmail : ""; // TO DO: // add environment variable for presetting the correct installation type diff --git a/src/components/RecentActivity/LiveView/TooltipContent/index.tsx b/src/components/RecentActivity/LiveView/TooltipContent/index.tsx new file mode 100644 index 000000000..5fd7d738b --- /dev/null +++ b/src/components/RecentActivity/LiveView/TooltipContent/index.tsx @@ -0,0 +1,6 @@ +import * as s from "./styles"; +import { TooltipContentProps } from "./types"; + +export const TooltipContent = (props: TooltipContentProps) => ( + {props.children} +); diff --git a/src/components/RecentActivity/LiveView/TooltipContent/styles.ts b/src/components/RecentActivity/LiveView/TooltipContent/styles.ts new file mode 100644 index 000000000..5a6be4bc2 --- /dev/null +++ b/src/components/RecentActivity/LiveView/TooltipContent/styles.ts @@ -0,0 +1,40 @@ +import styled from "styled-components"; + +export const Container = styled.div` + display: flex; + flex-direction: column; + border-radius: 4px; + padding: 8px; + gap: 4px; + + color: ${({ theme }) => { + switch (theme.mode) { + case "light": + return "#4d668a"; + case "dark": + case "dark-jetbrains": + return "#7891d0"; + } + }}; + + border: 1px solid + ${({ theme }) => { + switch (theme.mode) { + case "light": + return "#d1d1d1"; + case "dark": + case "dark-jetbrains": + return "#323232"; + } + }}; + + background: ${({ theme }) => { + switch (theme.mode) { + case "light": + return "#fbfdff"; + case "dark": + case "dark-jetbrains": + return "#383838"; + } + }}; +`; diff --git a/src/components/RecentActivity/LiveView/TooltipContent/types.ts b/src/components/RecentActivity/LiveView/TooltipContent/types.ts new file mode 100644 index 000000000..de529b53b --- /dev/null +++ b/src/components/RecentActivity/LiveView/TooltipContent/types.ts @@ -0,0 +1,5 @@ +import { ReactNode } from "react"; + +export interface TooltipContentProps { + children: ReactNode; +} diff --git a/src/components/RecentActivity/LiveView/index.tsx b/src/components/RecentActivity/LiveView/index.tsx index 4644297d4..0c0ac6944 100644 --- a/src/components/RecentActivity/LiveView/index.tsx +++ b/src/components/RecentActivity/LiveView/index.tsx @@ -1,5 +1,5 @@ import { format } from "date-fns"; -import { useEffect, useState } from "react"; +import { useEffect, useRef, useState } from "react"; import useDimensions from "react-cool-dimensions"; import { Area, @@ -7,19 +7,28 @@ import { ComposedChart, Line, ResponsiveContainer, + Tooltip, XAxis, YAxis } from "recharts"; +import { ChartOffset } from "recharts/types/util/types"; import { DefaultTheme, useTheme } from "styled-components"; import { usePrevious } from "../../../hooks/usePrevious"; -import { isNumber } from "../../../typeGuards/isNumber"; +import { roundTo } from "../../../utils/roundTo"; import { CrossIcon } from "../../common/icons/CrossIcon"; import { DoubleCircleIcon } from "../../common/icons/DoubleCircleIcon"; import { EndpointIcon } from "../../common/icons/EndpointIcon"; import { MinusIcon } from "../../common/icons/MinusIcon"; import { PlusIcon } from "../../common/icons/PlusIcon"; +import { TooltipContent } from "./TooltipContent"; import * as s from "./styles"; -import { ExtendedLiveDataRecord, LiveViewProps, PercentileInfo } from "./types"; +import { + Coordinates, + DotTooltipProps, + ExtendedLiveDataRecord, + LiveViewProps, + PercentileInfo +} from "./types"; const ZOOM_FACTOR = 1.2; @@ -108,8 +117,31 @@ const getZoomButtonIconColor = (theme: DefaultTheme) => { } }; -const formatDate = (datetime: string): string => - format(new Date(datetime), "HH:mm:ss.SSS MM/dd/yyyy"); +const convertTo = (nanoseconds: number, unit: string) => { + const milliseconds = nanoseconds / 1000 / 1000; + + switch (unit) { + case "ms": + return milliseconds; + case "sec": + return milliseconds / 1000; + case "min": + default: + return milliseconds / 1000 / 60; + } +}; + +const getMaxDuration = ( + records: ExtendedLiveDataRecord[] +): ExtendedLiveDataRecord | undefined => + records.reduce( + (max: ExtendedLiveDataRecord | undefined, curr) => + max && max.duration.raw > curr.duration.raw ? max : curr, + undefined + ); + +const formatDate = (dateTime: number): string => + format(new Date(dateTime), "HH:mm MM/dd/yyyy"); export const LiveView = (props: LiveViewProps) => { const theme = useTheme(); @@ -122,8 +154,17 @@ export const LiveView = (props: LiveViewProps) => { const previousWidth = usePrevious(width); const [containerInitialWidth, setContainerInitialWidth] = useState(width); - const [chartWidth, setChartWidth] = useState(containerInitialWidth); + const containerRef = useRef(null); + const [chartWidth, setChartWidth] = useState(width); + const previousChartWidth = usePrevious(chartWidth); const [isZoomed, setIsZoomed] = useState(false); + const [areaTooltip, setAreaTooltip] = useState(); + const [dotToolTip, setDotTooltip] = useState(); + const [scrollPercentagePosition, setScrollPercentagePosition] = useState(1); + + useEffect(() => { + observe(containerRef.current); + }, [observe, containerRef.current]); useEffect(() => { if (previousWidth !== width) { @@ -134,27 +175,37 @@ export const LiveView = (props: LiveViewProps) => { } }, [width, previousWidth, chartWidth]); - const percentiles = PERCENTILES.map((percentile) => ({ - ...percentile, - value: props.data.durationInsight.percentiles.find( - (x) => x.percentile === percentile.percentile - )?.currentDuration.raw - })).filter((x) => isNumber(x.value)) as PercentileInfo[]; + useEffect(() => { + const containerEl = containerRef.current; + if (containerEl && previousChartWidth !== chartWidth) { + containerEl.scrollTo({ + left: + scrollPercentagePosition * containerEl.scrollWidth - + containerEl.clientWidth + }); + } + }, [previousChartWidth, chartWidth, scrollPercentagePosition]); - const data: ExtendedLiveDataRecord[] = [...props.data.liveDataRecords].map( - (x) => ({ - ...x, - percentiles - }) - ); + const persistScrollPosition = () => { + const el = containerRef.current; + if (el) { + if (el.clientWidth === el.scrollWidth) { + setScrollPercentagePosition(1); + } else { + setScrollPercentagePosition( + (el.scrollLeft + el.clientWidth) / el.scrollWidth + ); + } + } + }; const handleCloseButtonClick = () => { - props.onClose(props.data.durationInsight.codeObjectId); + props.onClose(props.data.durationData.codeObjectId); }; - const spanName = props.data.durationInsight.spanInfo?.displayName; - const handleZoomOutButtonClick = () => { + persistScrollPosition(); + if (chartWidth > containerInitialWidth) { const newWidth = Math.round(chartWidth / ZOOM_FACTOR); @@ -169,6 +220,8 @@ export const LiveView = (props: LiveViewProps) => { }; const handleZoomInButtonClick = () => { + persistScrollPosition(); + const newWidth = Math.round(chartWidth * ZOOM_FACTOR); if (newWidth <= containerInitialWidth) { @@ -180,30 +233,61 @@ export const LiveView = (props: LiveViewProps) => { } }; - const YAxisTicks = [...new Set(percentiles.map((x) => x.value))]; + const handleAreaMouseMove = (props: any, e: React.MouseEvent) => { + setAreaTooltip({ x: e.clientX, y: e.clientY }); + }; - const getYAxisTickLabel = (value: number): string => { - const labels: string[] = []; - percentiles.forEach((percentile) => { - if (percentile.value === value) { - labels.push(percentile.label); - } + const handleAreaMouseLeave = () => { + setAreaTooltip(undefined); + }; + + const handleDotMouseOver = ( + props: any, + e: React.MouseEvent + ) => { + setDotTooltip({ + coordinates: { x: e.clientX, y: e.clientY }, + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + data: props.payload as ExtendedLiveDataRecord }); + }; - return labels.join(" / "); + const handleDotMouseLeave = () => { + setDotTooltip(undefined); }; + const percentiles = PERCENTILES.map((percentile) => ({ + ...percentile, + duration: props.data.durationData.percentiles.find( + (x) => x.percentile === percentile.percentile + )?.currentDuration + })).filter(Boolean) as PercentileInfo[]; + + const p50 = percentiles.find((p) => p.percentile === 0.5); + const p95 = percentiles.find((p) => p.percentile === 0.95); + + const spanName = props.data.durationData.displayName; + + const data: ExtendedLiveDataRecord[] = [...props.data.liveDataRecords].map( + (x) => ({ + ...x, + dateTimeValue: new Date(x.dateTime).valueOf() + }) + ); + + const maxDuration = getMaxDuration(data)?.duration; + + const maxDurationUnit = maxDuration?.unit || "ms"; + return ( - {spanName && ( - - - - - {spanName} - - )} + + + + + {spanName} + Live @@ -212,76 +296,183 @@ export const LiveView = (props: LiveViewProps) => { - - - - - - - - - - - + + + + Duration + + + + P50 ~ P95 + + + + + + + + + + + + + + - - {data.length > 1 && ( - { - const p50 = x.percentiles.find((p) => p.percentile === 0.5); - const p95 = x.percentiles.find((p) => p.percentile === 0.95); - - return p50 && p95 ? [p50.value, p95.value] : []; + + { + if (!props.width || !props.offset.left) { + return []; + } + + let linesCount = 5; + + const lines = []; + const interval = Math.floor(props.width / linesCount); + let left = props.offset.left; + + while (linesCount) { + lines.push(left); + linesCount--; + left += interval; + } + + return lines; }} - stroke={areaColor} - fill={areaColor} - fillOpacity={0.2} /> - )} - - 1 && ( + + p50 && p95 ? [p50.duration.raw, p95.duration.raw] : [] + } + stroke={areaColor} + fill={areaColor} + fillOpacity={0.2} + isAnimationActive={false} + activeDot={false} + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + onMouseMove={handleAreaMouseMove} + onMouseLeave={handleAreaMouseLeave} + /> + )} + + x.duration.raw} + stroke={lineColor} + dot={{ + stroke: lineColor, + fill: lineColor, + r: 2, + onMouseOver: handleDotMouseOver, + onMouseLeave: handleDotMouseLeave + }} + isAnimationActive={false} + activeDot={false} + /> + {areaTooltip && ( + + {([p50, p95].filter(Boolean) as PercentileInfo[]).map( + (x) => ( + + {x.label}: {x.duration.value} {x.duration.unit} + + ) + )} + + } + cursor={false} + isAnimationActive={false} + /> + )} + {dotToolTip && ( + + + {dotToolTip.data.duration.value}{" "} + {dotToolTip.data.duration.unit} + + + {format( + new Date(dotToolTip.data.dateTime), + "HH:mm:ss.SSS MM/dd/yyyy" + )} + + + } + isAnimationActive={false} + cursor={false} + /> + )} + + + + + + - x.duration.raw} - stroke={lineColor} - dot={{ stroke: lineColor, fill: lineColor, r: 2 }} - /> - - - + > + x.duration.raw} + tickLine={false} + tickFormatter={(x: number) => + String(roundTo(convertTo(x, maxDurationUnit), 0)) + } + tick={{ + fill: tickLabelColor, + fontSize: 10, + width: 85 + }} + stroke={axisColor} + tickMargin={7} + orientation={"right"} + label={{ + value: `Duration, ${maxDurationUnit}`, + angle: 90, + position: "insideRight" + }} + /> + + + + ); }; diff --git a/src/components/RecentActivity/LiveView/mockData.ts b/src/components/RecentActivity/LiveView/mockData.ts index 38c0e65f7..df2b82a51 100644 --- a/src/components/RecentActivity/LiveView/mockData.ts +++ b/src/components/RecentActivity/LiveView/mockData.ts @@ -1,4 +1,3 @@ -import { InsightCategory, InsightScope, InsightType } from "../../../types"; import { LiveData } from "./types"; export const mockData: LiveData = { @@ -804,27 +803,7 @@ export const mockData: LiveData = { dateTime: "2023-05-15T20:32:22.556144Z" } ], - durationInsight: { - name: "Performance Stats", - type: InsightType.SpanDurations, - category: InsightCategory.Performance, - specifity: 4, - isRecalculateEnabled: true, - spanCodeObjectId: - "span:OpenTelemetry.Instrumentation.AspNetCore$_$HTTP GET SampleInsights/NormalUsage", - span: { - name: "HTTP GET SampleInsights/NormalUsage", - displayName: "HTTP GET SampleInsights/NormalUsage", - instrumentationLibrary: "OpenTelemetry.Instrumentation.AspNetCore", - spanCodeObjectId: - "span:OpenTelemetry.Instrumentation.AspNetCore$_$HTTP GET SampleInsights/NormalUsage", - methodCodeObjectId: - "method:Sample.MoneyTransfer.API.Controllers.SampleInsightsController$_$NormalUsage", - kind: "Server", - codeObjectId: - "Sample.MoneyTransfer.API.Controllers.SampleInsightsController$_$NormalUsage" - }, - periodicPercentiles: [], + durationData: { percentiles: [ { percentile: 0.5, @@ -834,9 +813,7 @@ export const mockData: LiveData = { raw: 5704000.0 }, previousDuration: null, - changeTime: null, - changeVerified: null, - traceIds: ["FF6A6EDDD99D70B50BD9DDA14447BDDB"] + changeVerified: null }, { percentile: 0.95, @@ -846,39 +823,11 @@ export const mockData: LiveData = { raw: 7488500.0 }, previousDuration: null, - changeTime: null, - changeVerified: null, - traceIds: ["54AEA66680710C8AEFD496DF65E291E3"] + changeVerified: null } ], - lastSpanInstanceInfo: null, - scope: InsightScope.Span, - spanInfo: { - name: "HTTP GET SampleInsights/NormalUsage", - displayName: "HTTP GET SampleInsights/NormalUsage", - instrumentationLibrary: "OpenTelemetry.Instrumentation.AspNetCore", - spanCodeObjectId: - "span:OpenTelemetry.Instrumentation.AspNetCore$_$HTTP GET SampleInsights/NormalUsage", - methodCodeObjectId: - "method:Sample.MoneyTransfer.API.Controllers.SampleInsightsController$_$NormalUsage", - kind: "Server", - codeObjectId: - "Sample.MoneyTransfer.API.Controllers.SampleInsightsController$_$NormalUsage" - }, - shortDisplayInfo: { - title: "", - targetDisplayName: "", - subtitle: "", - description: "" - }, + displayName: "HTTP GET SampleInsights/NormalUsage", codeObjectId: - "method:Sample.MoneyTransfer.API.Controllers.SampleInsightsController$_$NormalUsage", - decorators: [], - environment: "KYRYLOS-MACBOOK-PRO.LOCAL[LOCAL]", - importance: 5, - severity: 0.0, - prefixedCodeObjectId: null, - customStartTime: null, - actualStartTime: null + "method:Sample.MoneyTransfer.API.Controllers.SampleInsightsController$_$NormalUsage" } }; diff --git a/src/components/RecentActivity/LiveView/styles.ts b/src/components/RecentActivity/LiveView/styles.ts index e6133ad72..eba434fdc 100644 --- a/src/components/RecentActivity/LiveView/styles.ts +++ b/src/components/RecentActivity/LiveView/styles.ts @@ -113,17 +113,43 @@ export const CloseButton = styled.button` cursor: pointer; `; -export const ChartContainer = styled.div` - padding: 0 12px 4px; +export const ChartsContainer = styled.div` + display: flex; height: 100%; - overflow: auto; + padding: 0 12px 4px; +`; + +export const AxisChartContainer = styled.div` + width: 64px; +`; + +export const ChartContainer = styled.div` + flex-grow: 1; + overflow-x: auto; + overflow-y: hidden; +`; + +export const Subheader = styled.div` + display: flex; + padding: 0 12px; + justify-content: space-between; +`; + +export const LegendContainer = styled.div` + display: flex; + gap: 4px; + padding-left: 12px; +`; + +export const LegendRecord = styled.div<{ color: string }>` + display: flex; + gap: 4px; + color: ${({ color }) => color}; `; export const ZoomButtonsContainer = styled.div` display: flex; - justify-content: flex-end; gap: 4px; - padding: 0 12px; `; export const ZoomButton = styled.button` diff --git a/src/components/RecentActivity/LiveView/types.ts b/src/components/RecentActivity/LiveView/types.ts index fe7e1e882..72abae454 100644 --- a/src/components/RecentActivity/LiveView/types.ts +++ b/src/components/RecentActivity/LiveView/types.ts @@ -1,5 +1,4 @@ import { Duration } from "../../../globals"; -import { SpanDurationsInsight } from "../../../types"; export interface LiveViewProps { data: LiveData; @@ -11,17 +10,40 @@ interface LiveDataRecord { duration: Duration; } +interface LiveDataDurationPercentile { + percentile: number; + currentDuration: Duration; + previousDuration: Duration | null; + changeVerified: boolean | null; +} + +interface LiveDataDuration { + percentiles: LiveDataDurationPercentile[]; + codeObjectId: string; + displayName: string; +} + export interface LiveData { liveDataRecords: LiveDataRecord[]; - durationInsight: SpanDurationsInsight; + durationData: LiveDataDuration; } export interface PercentileInfo { - value: number; + duration: Duration; label: string; percentile: number; } export interface ExtendedLiveDataRecord extends LiveDataRecord { - percentiles: PercentileInfo[]; + dateTimeValue: number; +} + +export interface Coordinates { + x: number; + y: number; +} + +export interface DotTooltipProps { + coordinates: Coordinates; + data: ExtendedLiveDataRecord; } diff --git a/src/components/RecentActivity/RecentActivityTable/index.tsx b/src/components/RecentActivity/RecentActivityTable/index.tsx index 0b465bdfb..a2ffd1228 100644 --- a/src/components/RecentActivity/RecentActivityTable/index.tsx +++ b/src/components/RecentActivity/RecentActivityTable/index.tsx @@ -7,6 +7,7 @@ import { import { useMemo } from "react"; import { useTheme } from "styled-components"; import { Duration } from "../../../globals"; +import { isNumber } from "../../../typeGuards/isNumber"; import { getInsightImportanceColor } from "../../../utils/getInsightImportanceColor"; import { getInsightTypeInfo } from "../../../utils/getInsightTypeInfo"; import { timeAgo } from "../../../utils/timeAgo"; @@ -21,10 +22,9 @@ import { RecentActivityTableProps } from "./types"; const columnHelper = createColumnHelper(); export const isRecent = (entry: ActivityEntry): boolean => { - const MAX_DISTANCE = - typeof window.recentActivityExpirationLimit === "number" - ? window.recentActivityExpirationLimit - : 10 * 60 * 1000; // in milliseconds + const MAX_DISTANCE = isNumber(window.recentActivityExpirationLimit) + ? window.recentActivityExpirationLimit + : 10 * 60 * 1000; // in milliseconds const now = new Date(); return ( now.valueOf() - new Date(entry.latestTraceTimestamp).valueOf() <= diff --git a/src/components/RecentActivity/index.tsx b/src/components/RecentActivity/index.tsx index f809460ba..c074b9848 100644 --- a/src/components/RecentActivity/index.tsx +++ b/src/components/RecentActivity/index.tsx @@ -15,6 +15,7 @@ import { LiveData } from "./LiveView/types"; import { RecentActivityTable, isRecent } from "./RecentActivityTable"; import * as s from "./styles"; +import { isString } from "../../typeGuards/isString"; import { EntrySpan, RecentActivityData, @@ -22,10 +23,9 @@ import { SetIsJaegerData } from "./types"; -const documentationURL = - typeof window.recentActivityDocumentationURL === "string" - ? window.recentActivityDocumentationURL - : null; +const documentationURL = isString(window.recentActivityDocumentationURL) + ? window.recentActivityDocumentationURL + : null; const ACTION_PREFIX = "RECENT_ACTIVITY"; diff --git a/src/components/common/App/index.tsx b/src/components/common/App/index.tsx index e96585cdb..c381430fb 100644 --- a/src/components/common/App/index.tsx +++ b/src/components/common/App/index.tsx @@ -11,7 +11,7 @@ import { AppProps } from "./types"; export const THEMES = ["light", "dark", "dark-jetbrains"]; const isMode = (mode: unknown): mode is Mode => { - return typeof mode === "string" && THEMES.includes(mode); + return isString(mode) && THEMES.includes(mode); }; const getMode = (): Mode => { diff --git a/src/platform.ts b/src/platform.ts index 01773e85b..53dc57660 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -1,9 +1,10 @@ import { IDE, Platform } from "./globals"; +import { isString } from "./typeGuards/isString"; const PLATFORMS = ["JetBrains", "VS Code", "Other"]; const isPlatform = (platform: unknown): platform is Platform => - typeof platform === "string" && PLATFORMS.includes(platform); + isString(platform) && PLATFORMS.includes(platform); export const getPlatform = (platform: unknown): Platform => isPlatform(platform) ? platform : "Other"; @@ -12,8 +13,7 @@ export const platform = getPlatform(window.platform); const IDES = ["IDEA", "Rider", "PyCharm"]; -const isIDE = (ide: unknown): ide is IDE => - typeof ide === "string" && IDES.includes(ide); +const isIDE = (ide: unknown): ide is IDE => isString(ide) && IDES.includes(ide); export const getIDE = (ide: unknown): IDE | undefined => isIDE(ide) ? ide : undefined; diff --git a/src/types.ts b/src/types.ts index 76ffd444c..3109722c6 100644 --- a/src/types.ts +++ b/src/types.ts @@ -21,28 +21,6 @@ export enum InsightType { SpanDurationBreakdown = "SpanDurationBreakdown" } -export enum InsightScope { - EntrySpan = "EntrySpan", - Span = "Span", - Function = "Function", - ChildSpan = "ChildSpan" -} - -export enum InsightCategory { - Performance = "Performance", - Errors = "Errors", - Usage = "Usage" -} - -export enum InsightSpecificity { - ChildInfo = 6, - Symptomatic = 5, - OwnInsight = 4, - TargetFound = 3, - TargetAndReasonFound = 2, - PinPoint = 1 -} - export enum InsightImportance { Spam = 9, Clutter = 8, @@ -69,39 +47,6 @@ export interface SpanInfo { codeObjectId: string | null; } -export interface Insight { - category: string; - type: InsightType; - specifity: InsightSpecificity; -} - -export interface CodeObjectInsight extends Insight { - shortDisplayInfo: { - title: string; - targetDisplayName: string; - subtitle: string; - description: string; - }; - name: string; - scope: InsightScope; - codeObjectId: string; - decorators: { - title: string; - description: string; - }[]; - environment: string; - importance: InsightImportance; - severity: number; - isRecalculateEnabled: boolean; - prefixedCodeObjectId: string | null; - customStartTime: string | null; - actualStartTime: string | null; -} - -export interface SpanInsight extends CodeObjectInsight { - spanInfo: SpanInfo | null; -} - export interface DurationPercentileWithChange { percentile: number; currentDuration: Duration; @@ -117,29 +62,3 @@ export interface SpanInstanceInfo { startTime: string; duration: Duration; } - -export interface SpanDurationsInsight extends SpanInsight { - name: "Performance Stats"; - type: InsightType.SpanDurations; - category: InsightCategory.Performance; - specifity: InsightSpecificity.OwnInsight; - isRecalculateEnabled: true; - periodicPercentiles: { - percentile: number; - currentDuration: Duration; - previousDuration: Duration | null; - sampleTraces: string[]; - period: string; - }[]; - percentiles: DurationPercentileWithChange[]; - lastSpanInstanceInfo: SpanInstanceInfo | null; - - /** - * @deprecated - */ - spanCodeObjectId: string; - /** - * @deprecated - */ - span: SpanInfo; -} diff --git a/src/utils/addPrefix.ts b/src/utils/addPrefix.ts index 42da52a54..88a6bce31 100644 --- a/src/utils/addPrefix.ts +++ b/src/utils/addPrefix.ts @@ -1,3 +1,5 @@ +import { isString } from "../typeGuards/isString"; + type PrefixedMap = Record; export const addPrefix = >( @@ -10,9 +12,7 @@ export const addPrefix = >( for (const [key, value] of Object.entries(actions)) { const prop = key as keyof T; - res[prop] = `${prefix}${ - typeof separator === "string" ? separator : "/" - }${value}`; + res[prop] = `${prefix}${isString(separator) ? separator : "/"}${value}`; } return res; diff --git a/src/utils/getInsightTypeInfo.ts b/src/utils/getInsightTypeInfo.ts index 7e277100a..53ef06b6a 100644 --- a/src/utils/getInsightTypeInfo.ts +++ b/src/utils/getInsightTypeInfo.ts @@ -1,6 +1,7 @@ import { MemoExoticComponent } from "react"; import { AlarmClockIcon } from "../components/common/icons/AlarmClockIcon"; import { BottleneckIcon } from "../components/common/icons/BottleneckIcon"; +import { ClockWithTicksIcon } from "../components/common/icons/ClockWithTicksIcon"; import { MeterHighIcon } from "../components/common/icons/MeterHighIcon"; import { MeterLowIcon } from "../components/common/icons/MeterLowIcon"; import { SQLDatabaseIcon } from "../components/common/icons/SQLDatabaseIcon"; @@ -80,7 +81,7 @@ export const getInsightTypeInfo = ( label: "Duration" }, [InsightType.SpanDurationBreakdown]: { - icon: AlarmClockIcon, + icon: ClockWithTicksIcon, label: "Duration Breakdown" } }; diff --git a/src/utils/roundTo.ts b/src/utils/roundTo.ts new file mode 100644 index 000000000..6478f9cf9 --- /dev/null +++ b/src/utils/roundTo.ts @@ -0,0 +1,4 @@ +export const roundTo = (number: number, n: number): number => + Number.isInteger(n) && n >= 0 + ? Math.round((number + Number.EPSILON) * Math.pow(10, n)) / Math.pow(10, n) + : Math.round(number); From a769b9ec12faf57b1c6d6a2266b8ecc6f66cd245 Mon Sep 17 00:00:00 2001 From: Kyrylo Shmidt Date: Mon, 22 May 2023 13:42:21 +0200 Subject: [PATCH 09/14] Fix live view styles on resizing --- package-lock.json | 18 ++++++++++++++++++ package.json | 1 + .../RecentActivity/LiveView/index.tsx | 11 ++++++++++- .../RecentActivity/LiveView/styles.ts | 8 ++++++-- 4 files changed, 35 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index f244dc89e..9fe7e28ae 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "react": "^18.2.0", "react-cool-dimensions": "^3.0.1", "react-dom": "^18.2.0", + "react-scrollbar-size": "^5.0.0", "react-transition-group": "^4.4.5", "recharts": "^2.6.2", "styled-components": "^5.3.6" @@ -13578,6 +13579,17 @@ "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0" } }, + "node_modules/react-scrollbar-size": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/react-scrollbar-size/-/react-scrollbar-size-5.0.0.tgz", + "integrity": "sha512-Ly3OuRMz4yDFViTh+ANH6TrG8EqrgjC1uxxm2a/95+2Ijy3XT+bWtzm4QmgZUcUVg+8BCKzmPMM7z39ZtucDIQ==", + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.1 || ^18.0.0" + } + }, "node_modules/react-smooth": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-2.0.2.tgz", @@ -26323,6 +26335,12 @@ "lodash": "^4.17.21" } }, + "react-scrollbar-size": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/react-scrollbar-size/-/react-scrollbar-size-5.0.0.tgz", + "integrity": "sha512-Ly3OuRMz4yDFViTh+ANH6TrG8EqrgjC1uxxm2a/95+2Ijy3XT+bWtzm4QmgZUcUVg+8BCKzmPMM7z39ZtucDIQ==", + "requires": {} + }, "react-smooth": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-2.0.2.tgz", diff --git a/package.json b/package.json index 919fc4fdb..0d623602b 100644 --- a/package.json +++ b/package.json @@ -85,6 +85,7 @@ "react": "^18.2.0", "react-cool-dimensions": "^3.0.1", "react-dom": "^18.2.0", + "react-scrollbar-size": "^5.0.0", "react-transition-group": "^4.4.5", "recharts": "^2.6.2", "styled-components": "^5.3.6" diff --git a/src/components/RecentActivity/LiveView/index.tsx b/src/components/RecentActivity/LiveView/index.tsx index 0c0ac6944..92d5c664d 100644 --- a/src/components/RecentActivity/LiveView/index.tsx +++ b/src/components/RecentActivity/LiveView/index.tsx @@ -1,6 +1,8 @@ import { format } from "date-fns"; import { useEffect, useRef, useState } from "react"; import useDimensions from "react-cool-dimensions"; +import useScrollbarSize from "react-scrollbar-size"; + import { Area, CartesianGrid, @@ -161,6 +163,7 @@ export const LiveView = (props: LiveViewProps) => { const [areaTooltip, setAreaTooltip] = useState(); const [dotToolTip, setDotTooltip] = useState(); const [scrollPercentagePosition, setScrollPercentagePosition] = useState(1); + const scrollbar = useScrollbarSize(); useEffect(() => { observe(containerRef.current); @@ -279,6 +282,12 @@ export const LiveView = (props: LiveViewProps) => { const maxDurationUnit = maxDuration?.unit || "ms"; + const scrollbarOffset = + containerRef.current && + containerRef.current.scrollWidth > containerRef.current.clientWidth + ? scrollbar.width + : 0; + return ( @@ -440,7 +449,7 @@ export const LiveView = (props: LiveViewProps) => { - + ` width: 64px; + height: ${({ scrollbarOffset }) => + scrollbarOffset ? `calc(100% - ${scrollbarOffset}px)` : "100%"}; `; export const ChartContainer = styled.div` From cd6e5c7922ac73bb4c70e4e4a9b408af6f292d8f Mon Sep 17 00:00:00 2001 From: Kyrylo Shmidt Date: Mon, 22 May 2023 17:26:08 +0200 Subject: [PATCH 10/14] Update types --- src/components/RecentActivity/LiveView/styles.ts | 5 ++--- src/components/RecentActivity/LiveView/types.ts | 4 ++++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/components/RecentActivity/LiveView/styles.ts b/src/components/RecentActivity/LiveView/styles.ts index 849767c22..43d669184 100644 --- a/src/components/RecentActivity/LiveView/styles.ts +++ b/src/components/RecentActivity/LiveView/styles.ts @@ -1,4 +1,5 @@ import styled from "styled-components"; +import { AxisChartContainerProps } from "./types"; export const Container = styled.div` display: flex; @@ -119,9 +120,7 @@ export const ChartsContainer = styled.div` padding: 0 12px 4px; `; -export const AxisChartContainer = styled.div<{ - scrollbarOffset: number; -}>` +export const AxisChartContainer = styled.div` width: 64px; height: ${({ scrollbarOffset }) => scrollbarOffset ? `calc(100% - ${scrollbarOffset}px)` : "100%"}; diff --git a/src/components/RecentActivity/LiveView/types.ts b/src/components/RecentActivity/LiveView/types.ts index 72abae454..b7b648f74 100644 --- a/src/components/RecentActivity/LiveView/types.ts +++ b/src/components/RecentActivity/LiveView/types.ts @@ -47,3 +47,7 @@ export interface DotTooltipProps { coordinates: Coordinates; data: ExtendedLiveDataRecord; } + +export interface AxisChartContainerProps { + scrollbarOffset: number; +} From ccd0b8bb5f2ea4c4523ef89fe37b5155d7b1af29 Mon Sep 17 00:00:00 2001 From: Kyrylo Shmidt Date: Tue, 23 May 2023 11:37:23 +0200 Subject: [PATCH 11/14] Move Y axis to the left and legend to the bottom --- .../RecentActivity/LiveView/index.tsx | 102 ++++++++---------- .../RecentActivity/LiveView/styles.ts | 22 ++-- 2 files changed, 57 insertions(+), 67 deletions(-) diff --git a/src/components/RecentActivity/LiveView/index.tsx b/src/components/RecentActivity/LiveView/index.tsx index 92d5c664d..8517bf237 100644 --- a/src/components/RecentActivity/LiveView/index.tsx +++ b/src/components/RecentActivity/LiveView/index.tsx @@ -305,27 +305,42 @@ export const LiveView = (props: LiveViewProps) => { - - - - - Duration - - - - P50 ~ P95 - - - - - - - - - - - + + + + + + + + + + + + x.duration.raw} + tickLine={false} + tickFormatter={(x: number) => + String(roundTo(convertTo(x, maxDurationUnit), 0)) + } + tick={{ + fill: tickLabelColor, + fontSize: 10, + textAnchor: "start" + // width: 85 + }} + stroke={axisColor} + tickMargin={11} + width={28} + /> + + + { @@ -354,7 +368,7 @@ export const LiveView = (props: LiveViewProps) => { const lines = []; const interval = Math.floor(props.width / linesCount); - let left = props.offset.left; + let left = props.offset.left + interval; while (linesCount) { lines.push(left); @@ -367,7 +381,6 @@ export const LiveView = (props: LiveViewProps) => { /> {data.length > 1 && ( p50 && p95 ? [p50.duration.raw, p95.duration.raw] : [] } @@ -394,7 +407,6 @@ export const LiveView = (props: LiveViewProps) => { tickFormatter={formatDate} /> x.duration.raw} stroke={lineColor} dot={{ @@ -449,39 +461,17 @@ export const LiveView = (props: LiveViewProps) => { - - - - x.duration.raw} - tickLine={false} - tickFormatter={(x: number) => - String(roundTo(convertTo(x, maxDurationUnit), 0)) - } - tick={{ - fill: tickLabelColor, - fontSize: 10, - width: 85 - }} - stroke={axisColor} - tickMargin={7} - orientation={"right"} - label={{ - value: `Duration, ${maxDurationUnit}`, - angle: 90, - position: "insideRight" - }} - /> - - - + + + + Duration, {maxDurationUnit} + + + + Slowest 5% - Median + + ); }; diff --git a/src/components/RecentActivity/LiveView/styles.ts b/src/components/RecentActivity/LiveView/styles.ts index 43d669184..aca4f8a4b 100644 --- a/src/components/RecentActivity/LiveView/styles.ts +++ b/src/components/RecentActivity/LiveView/styles.ts @@ -116,43 +116,43 @@ export const CloseButton = styled.button` export const ChartsContainer = styled.div` display: flex; - height: calc(100% - 80px); - padding: 0 12px 4px; + height: calc(100% - 124px); + padding-right: 12px; `; export const AxisChartContainer = styled.div` - width: 64px; + width: 29px; height: ${({ scrollbarOffset }) => scrollbarOffset ? `calc(100% - ${scrollbarOffset}px)` : "100%"}; + flex-shrink: 0; `; export const ChartContainer = styled.div` - flex-grow: 1; + width: 100%; overflow-x: auto; overflow-y: hidden; `; -export const Subheader = styled.div` - display: flex; - padding: 0 12px; - justify-content: space-between; -`; - export const LegendContainer = styled.div` display: flex; gap: 4px; - padding-left: 12px; + padding: 4px 0 16px 12px; + font-size: 10px; + line-height: 12px; `; export const LegendRecord = styled.div<{ color: string }>` display: flex; + align-items: center; gap: 4px; color: ${({ color }) => color}; `; export const ZoomButtonsContainer = styled.div` display: flex; + justify-content: flex-end; gap: 4px; + padding: 0 12px; `; export const ZoomButton = styled.button` From 86d9bc1627bf16bfc0fa2d81d18cc42438ae9447 Mon Sep 17 00:00:00 2001 From: Kyrylo Shmidt Date: Tue, 23 May 2023 12:52:21 +0200 Subject: [PATCH 12/14] Restyle legend, add empty state --- .../LiveView/LiveView.stories.tsx | 11 +- .../LiveView/TooltipContent/styles.ts | 16 +- .../RecentActivity/LiveView/index.tsx | 347 +++++++++--------- .../RecentActivity/LiveView/styles.ts | 51 ++- .../RecentActivity/LiveView/types.ts | 4 + .../RecentActivity/RecentActivity.stories.tsx | 10 + src/components/common/icons/ChartIcon.tsx | 44 +++ 7 files changed, 297 insertions(+), 186 deletions(-) create mode 100644 src/components/common/icons/ChartIcon.tsx diff --git a/src/components/RecentActivity/LiveView/LiveView.stories.tsx b/src/components/RecentActivity/LiveView/LiveView.stories.tsx index 82b3aec3c..17e7542f1 100644 --- a/src/components/RecentActivity/LiveView/LiveView.stories.tsx +++ b/src/components/RecentActivity/LiveView/LiveView.stories.tsx @@ -18,7 +18,16 @@ type Story = StoryObj; // More on writing stories with args: https://storybook.js.org/docs/react/writing-stories/args -export const Default: Story = { +export const Empty: Story = { + args: { + data: { + ...mockData, + liveDataRecords: [] + } + } +}; + +export const WithData: Story = { args: { data: mockData } diff --git a/src/components/RecentActivity/LiveView/TooltipContent/styles.ts b/src/components/RecentActivity/LiveView/TooltipContent/styles.ts index 5a6be4bc2..07d472d38 100644 --- a/src/components/RecentActivity/LiveView/TooltipContent/styles.ts +++ b/src/components/RecentActivity/LiveView/TooltipContent/styles.ts @@ -6,6 +6,7 @@ export const Container = styled.div` border-radius: 4px; padding: 8px; gap: 4px; + box-shadow: 0px 0px 6px rgba(0, 0, 0, 0.15); color: ${({ theme }) => { switch (theme.mode) { @@ -13,28 +14,17 @@ export const Container = styled.div` return "#4d668a"; case "dark": case "dark-jetbrains": - return "#7891d0"; + return "#dadada"; } }}; - border: 1px solid - ${({ theme }) => { - switch (theme.mode) { - case "light": - return "#d1d1d1"; - case "dark": - case "dark-jetbrains": - return "#323232"; - } - }}; - background: ${({ theme }) => { switch (theme.mode) { case "light": return "#fbfdff"; case "dark": case "dark-jetbrains": - return "#383838"; + return "#2e2e2e"; } }}; `; diff --git a/src/components/RecentActivity/LiveView/index.tsx b/src/components/RecentActivity/LiveView/index.tsx index 8517bf237..16f378cff 100644 --- a/src/components/RecentActivity/LiveView/index.tsx +++ b/src/components/RecentActivity/LiveView/index.tsx @@ -17,6 +17,7 @@ import { ChartOffset } from "recharts/types/util/types"; import { DefaultTheme, useTheme } from "styled-components"; import { usePrevious } from "../../../hooks/usePrevious"; import { roundTo } from "../../../utils/roundTo"; +import { ChartIcon } from "../../common/icons/ChartIcon"; import { CrossIcon } from "../../common/icons/CrossIcon"; import { DoubleCircleIcon } from "../../common/icons/DoubleCircleIcon"; import { EndpointIcon } from "../../common/icons/EndpointIcon"; @@ -305,173 +306,187 @@ export const LiveView = (props: LiveViewProps) => { - - - - - - - - - - - - - x.duration.raw} - tickLine={false} - tickFormatter={(x: number) => - String(roundTo(convertTo(x, maxDurationUnit), 0)) - } - tick={{ - fill: tickLabelColor, - fontSize: 10, - textAnchor: "start" - // width: 85 - }} - stroke={axisColor} - tickMargin={11} - width={28} - /> - - - - - - - { - if (!props.width || !props.offset.left) { - return []; - } - - let linesCount = 5; - - const lines = []; - const interval = Math.floor(props.width / linesCount); - let left = props.offset.left + interval; - - while (linesCount) { - lines.push(left); - linesCount--; - left += interval; - } - - return lines; - }} - /> - {data.length > 1 && ( - - p50 && p95 ? [p50.duration.raw, p95.duration.raw] : [] - } - stroke={areaColor} - fill={areaColor} - fillOpacity={0.2} - isAnimationActive={false} - activeDot={false} - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - onMouseMove={handleAreaMouseMove} - onMouseLeave={handleAreaMouseLeave} - /> - )} - - x.duration.raw} - stroke={lineColor} - dot={{ - stroke: lineColor, - fill: lineColor, - r: 2, - onMouseOver: handleDotMouseOver, - onMouseLeave: handleDotMouseLeave - }} - isAnimationActive={false} - activeDot={false} - /> - {areaTooltip && ( - - {([p50, p95].filter(Boolean) as PercentileInfo[]).map( - (x) => ( - - {x.label}: {x.duration.value} {x.duration.unit} + {data.length > 0 ? ( + <> + + + + + + + + + + + + + x.duration.raw} + tickLine={false} + tickFormatter={(x: number) => + String(roundTo(convertTo(x, maxDurationUnit), 0)) + } + tick={{ + fill: tickLabelColor, + fontSize: 10, + textAnchor: "start" + }} + stroke={axisColor} + tickMargin={11} + width={28} + label={{ + value: maxDurationUnit, + position: "bottom", + fill: tickLabelColor, + fontSize: 10, + offset: 16 + }} + /> + + + + + + + { + if (!props.width || !props.offset.left) { + return []; + } + + let linesCount = 5; + + const lines = []; + const interval = Math.floor(props.width / linesCount); + let left = props.offset.left + interval; + + while (linesCount) { + lines.push(left); + linesCount--; + left += interval; + } + + return lines; + }} + /> + {data.length > 1 && ( + + p50 && p95 ? [p50.duration.raw, p95.duration.raw] : [] + } + stroke={areaColor} + fill={areaColor} + fillOpacity={0.2} + isAnimationActive={false} + activeDot={false} + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + onMouseMove={handleAreaMouseMove} + onMouseLeave={handleAreaMouseLeave} + /> + )} + + + x.duration.raw + } + stroke={lineColor} + dot={{ + stroke: lineColor, + fill: lineColor, + r: 2, + onMouseOver: handleDotMouseOver, + onMouseLeave: handleDotMouseLeave + }} + isAnimationActive={false} + activeDot={false} + /> + {areaTooltip && ( + + {([p50, p95].filter(Boolean) as PercentileInfo[]).map( + (x) => ( + + {x.label}: {x.duration.value} {x.duration.unit} + + ) + )} + + } + cursor={false} + isAnimationActive={false} + /> + )} + {dotToolTip && ( + + + {dotToolTip.data.duration.value}{" "} + {dotToolTip.data.duration.unit} - ) - )} - - } - cursor={false} - isAnimationActive={false} - /> - )} - {dotToolTip && ( - - - {dotToolTip.data.duration.value}{" "} - {dotToolTip.data.duration.unit} - - - {format( - new Date(dotToolTip.data.dateTime), - "HH:mm:ss.SSS MM/dd/yyyy" - )} - - - } - isAnimationActive={false} - cursor={false} - /> - )} - - - - - - - - Duration, {maxDurationUnit} - - - - Slowest 5% - Median - - + + {format( + new Date(dotToolTip.data.dateTime), + "HH:mm:ss.SSS MM/dd/yyyy" + )} + + + } + isAnimationActive={false} + cursor={false} + /> + )} + + + + + + + Slowest 5% - Median + + + ) : ( + + + No data yet + + Trigger some actions to follow the performance. + + + )} ); }; diff --git a/src/components/RecentActivity/LiveView/styles.ts b/src/components/RecentActivity/LiveView/styles.ts index aca4f8a4b..5d5c0f334 100644 --- a/src/components/RecentActivity/LiveView/styles.ts +++ b/src/components/RecentActivity/LiveView/styles.ts @@ -1,5 +1,5 @@ import styled from "styled-components"; -import { AxisChartContainerProps } from "./types"; +import { AreaLegendIllustrationProps, AxisChartContainerProps } from "./types"; export const Container = styled.div` display: flex; @@ -139,13 +139,26 @@ export const LegendContainer = styled.div` padding: 4px 0 16px 12px; font-size: 10px; line-height: 12px; + + color: ${({ theme }) => { + switch (theme.mode) { + case "light": + return "#828797"; + case "dark": + case "dark-jetbrains": + return "#9b9b9b"; + } + }}; `; -export const LegendRecord = styled.div<{ color: string }>` - display: flex; - align-items: center; - gap: 4px; - color: ${({ color }) => color}; +export const AreaLegendIllustration = styled.div` + background: ${({ color }) => color}33; // 20% opacity + border-color: ${({ color }) => color}; + border-style: solid none; + border-width: 1px; + width: 10px; + box-sizing: border-box; + height: 100%; `; export const ZoomButtonsContainer = styled.div` @@ -193,3 +206,29 @@ export const ZoomButton = styled.button` } }}; `; + +export const NoDataContainer = styled.div` + display: flex; + flex-direction: column; + flex-grow: 1; + align-items: center; + justify-content: center; + gap: 4px; + font-weight: 500; + font-size: 12px; + line-height: 14px; + text-align: center; + color: #7c7c94; +`; + +export const NoDataTitle = styled.span` + font-weight: 500; + font-size: 14px; + line-height: 17px; + text-transform: capitalize; + color: #dadada; +`; + +export const NoDataText = styled.span` + width: 208px; +`; diff --git a/src/components/RecentActivity/LiveView/types.ts b/src/components/RecentActivity/LiveView/types.ts index b7b648f74..24ae73f8c 100644 --- a/src/components/RecentActivity/LiveView/types.ts +++ b/src/components/RecentActivity/LiveView/types.ts @@ -51,3 +51,7 @@ export interface DotTooltipProps { export interface AxisChartContainerProps { scrollbarOffset: number; } + +export interface AreaLegendIllustrationProps { + color: string; +} diff --git a/src/components/RecentActivity/RecentActivity.stories.tsx b/src/components/RecentActivity/RecentActivity.stories.tsx index fc6e7a5d5..2d7f599d1 100644 --- a/src/components/RecentActivity/RecentActivity.stories.tsx +++ b/src/components/RecentActivity/RecentActivity.stories.tsx @@ -556,3 +556,13 @@ export const WithLiveData: Story = { liveData } }; + +export const WithEmptyLiveData: Story = { + args: { + data, + liveData: { + ...liveData, + liveDataRecords: [] + } + } +}; diff --git a/src/components/common/icons/ChartIcon.tsx b/src/components/common/icons/ChartIcon.tsx new file mode 100644 index 000000000..6b7b906a5 --- /dev/null +++ b/src/components/common/icons/ChartIcon.tsx @@ -0,0 +1,44 @@ +import React from "react"; +import { useIconProps } from "./hooks"; +import { IconProps } from "./types"; + +const ChartIconComponent = (props: IconProps) => { + const { size } = useIconProps(props); + + return ( + + + + + + + + + + + + + + ); +}; + +export const ChartIcon = React.memo(ChartIconComponent); From 8af9278de34a1cd08ab9b187b8fae6712aaf9880 Mon Sep 17 00:00:00 2001 From: Kyrylo Shmidt Date: Tue, 23 May 2023 13:43:48 +0200 Subject: [PATCH 13/14] Fix Y Axis labels --- src/components/RecentActivity/LiveView/index.tsx | 15 +++++++++++---- src/components/RecentActivity/LiveView/styles.ts | 2 +- src/components/RecentActivity/LiveView/types.ts | 1 + 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/components/RecentActivity/LiveView/index.tsx b/src/components/RecentActivity/LiveView/index.tsx index 16f378cff..8344233ca 100644 --- a/src/components/RecentActivity/LiveView/index.tsx +++ b/src/components/RecentActivity/LiveView/index.tsx @@ -280,8 +280,12 @@ export const LiveView = (props: LiveViewProps) => { ); const maxDuration = getMaxDuration(data)?.duration; - const maxDurationUnit = maxDuration?.unit || "ms"; + const maxDurationDigitCount = maxDuration + ? String(Math.ceil(maxDuration.value)).length + : 0; + const YAxisTickMargin = maxDurationDigitCount * 5.5; + const YAxisWidth = 12 + maxDurationDigitCount * 7.5; const scrollbarOffset = containerRef.current && @@ -317,7 +321,10 @@ export const LiveView = (props: LiveViewProps) => { - + { textAnchor: "start" }} stroke={axisColor} - tickMargin={11} - width={28} + tickMargin={YAxisTickMargin} + width={YAxisWidth} label={{ value: maxDurationUnit, position: "bottom", diff --git a/src/components/RecentActivity/LiveView/styles.ts b/src/components/RecentActivity/LiveView/styles.ts index 5d5c0f334..09eddd0b3 100644 --- a/src/components/RecentActivity/LiveView/styles.ts +++ b/src/components/RecentActivity/LiveView/styles.ts @@ -121,7 +121,7 @@ export const ChartsContainer = styled.div` `; export const AxisChartContainer = styled.div` - width: 29px; + width: ${({ width }) => width + 1}px; height: ${({ scrollbarOffset }) => scrollbarOffset ? `calc(100% - ${scrollbarOffset}px)` : "100%"}; flex-shrink: 0; diff --git a/src/components/RecentActivity/LiveView/types.ts b/src/components/RecentActivity/LiveView/types.ts index 24ae73f8c..af64ded60 100644 --- a/src/components/RecentActivity/LiveView/types.ts +++ b/src/components/RecentActivity/LiveView/types.ts @@ -50,6 +50,7 @@ export interface DotTooltipProps { export interface AxisChartContainerProps { scrollbarOffset: number; + width: number; } export interface AreaLegendIllustrationProps { From 17e77bd9c6dfec89bd272c007526ec3cc7ded675 Mon Sep 17 00:00:00 2001 From: Kyrylo Shmidt Date: Tue, 23 May 2023 14:16:54 +0200 Subject: [PATCH 14/14] Avoid subpixel dimensions --- src/components/RecentActivity/LiveView/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/RecentActivity/LiveView/index.tsx b/src/components/RecentActivity/LiveView/index.tsx index 8344233ca..3ac6a5817 100644 --- a/src/components/RecentActivity/LiveView/index.tsx +++ b/src/components/RecentActivity/LiveView/index.tsx @@ -284,8 +284,8 @@ export const LiveView = (props: LiveViewProps) => { const maxDurationDigitCount = maxDuration ? String(Math.ceil(maxDuration.value)).length : 0; - const YAxisTickMargin = maxDurationDigitCount * 5.5; - const YAxisWidth = 12 + maxDurationDigitCount * 7.5; + const YAxisTickMargin = Math.round(maxDurationDigitCount * 5.5); + const YAxisWidth = Math.round(12 + maxDurationDigitCount * 7.5); const scrollbarOffset = containerRef.current &&