From eeb1e909446e5be0cf9d77929c07df6e1a6cc1aa Mon Sep 17 00:00:00 2001 From: Stacey Chen Date: Sat, 17 Oct 2026 21:52:26 -0400 Subject: [PATCH 01/14] DINO-839: added MetricsChartSkeleton --- js/src/app/user/[userId]/UserProfile.page.tsx | 2 ++ .../UserProfile/MetricsChart/MetricsChart.tsx | 21 +++++++++++++++++++ .../MetricsChart/MetricsChartSkeleton.tsx | 12 +++++++++++ 3 files changed, 35 insertions(+) create mode 100644 js/src/app/user/[userId]/_components/UserProfile/MetricsChart/MetricsChart.tsx create mode 100644 js/src/app/user/[userId]/_components/UserProfile/MetricsChart/MetricsChartSkeleton.tsx diff --git a/js/src/app/user/[userId]/UserProfile.page.tsx b/js/src/app/user/[userId]/UserProfile.page.tsx index 9ffd5ba78..7d661ce60 100644 --- a/js/src/app/user/[userId]/UserProfile.page.tsx +++ b/js/src/app/user/[userId]/UserProfile.page.tsx @@ -1,6 +1,7 @@ import ProfilePicture from "@/app/user/[userId]/_components/UserProfile/ProfilePicture/ProfilePicture"; import UserProfileHeader from "@/app/user/[userId]/_components/UserProfile/UserProfileHeader"; import UserTags from "@/app/user/[userId]/_components/UserProfile/UserTags/UserTags"; +import MetricsChart from "@/app/user/[userId]/_components/UserProfile/MetricsChart/MetricsChart"; import MiniUserSubmissions from "@/app/user/[userId]/submissions/_components/UserSubmissions/MiniUserSubmissions"; import ToastWithRedirect from "@/components/ui/toast/ToastWithRedirect"; import { getUserSubmissionsUrl } from "@/lib/helper/leaderboardDateRange"; @@ -64,6 +65,7 @@ export default function UserProfilePage() { + ; + } + + if (status === "error") { + return ( + + ); + } + + if (!data.success) { + return ; + } +} diff --git a/js/src/app/user/[userId]/_components/UserProfile/MetricsChart/MetricsChartSkeleton.tsx b/js/src/app/user/[userId]/_components/UserProfile/MetricsChart/MetricsChartSkeleton.tsx new file mode 100644 index 000000000..90abf7aef --- /dev/null +++ b/js/src/app/user/[userId]/_components/UserProfile/MetricsChart/MetricsChartSkeleton.tsx @@ -0,0 +1,12 @@ +import { Flex, Skeleton, Divider } from "@mantine/core"; + +export default function MetricsChartSkeleton() { + return ( + <> + + + + + + ); +} From 443c69dfe713dc73673d7e2cf6e731ecbdf3e8d6 Mon Sep 17 00:00:00 2001 From: Stacey Chen Date: Thu, 22 Oct 2026 00:58:51 -0400 Subject: [PATCH 02/14] DINO-839: added metrics chart to user profile page --- js/package.json | 2 + js/pnpm-lock.yaml | 317 ++++++++++++++++++ js/src/app/user/[userId]/UserProfile.page.tsx | 4 +- .../UserProfile/MetricsChart/MetricsChart.tsx | 47 ++- .../MetricsChart/MetricsChartSkeleton.tsx | 2 +- js/src/lib/api/queries/user/index.ts | 98 ++++++ 6 files changed, 465 insertions(+), 5 deletions(-) diff --git a/js/package.json b/js/package.json index ac0d4846d..d3b0edcb6 100644 --- a/js/package.json +++ b/js/package.json @@ -22,6 +22,7 @@ "preview": "vite preview" }, "dependencies": { + "@mantine/charts": "^9.7.3", "@mantine/core": "^8.1.2", "@mantine/dates": "^8.1.2", "@mantine/form": "^8.1.2", @@ -42,6 +43,7 @@ "react-router": "^7.18.0", "react-router-dom": "^7.18.0", "react-shiki": "^0.9.1", + "recharts": "^3.10.1", "type-fest": "github:tahminator/type-fest#main", "use-debounce": "^10.0.4", "use-immer": "^0.11.0", diff --git a/js/pnpm-lock.yaml b/js/pnpm-lock.yaml index 03ea35cda..f792b49d2 100644 --- a/js/pnpm-lock.yaml +++ b/js/pnpm-lock.yaml @@ -31,6 +31,9 @@ importers: .: dependencies: + '@mantine/charts': + specifier: ^9.7.3 + version: 9.7.3(@mantine/core@8.1.2(@mantine/hooks@8.1.2(react@18.3.1))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@mantine/hooks@8.1.2(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(recharts@3.10.1(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react-is@17.0.2)(react@18.3.1)(redux@5.0.1)) '@mantine/core': specifier: ^8.1.2 version: 8.1.2(@mantine/hooks@8.1.2(react@18.3.1))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -91,6 +94,9 @@ importers: react-shiki: specifier: ^0.9.1 version: 0.9.1(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + recharts: + specifier: ^3.10.1 + version: 3.10.1(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react-is@17.0.2)(react@18.3.1)(redux@5.0.1) type-fest: specifier: github:tahminator/type-fest#main version: https://codeload.github.com/tahminator/type-fest/tar.gz/d0bbbbe83ef6c42d130cbf2fdbfd8a48bbc989bd @@ -721,6 +727,15 @@ packages: '@keyv/serialize@1.0.2': resolution: {integrity: sha512-+E/LyaAeuABniD/RvUezWVXKpeuvwLEA9//nE9952zBaOdBd2mQ3pPoM8cUe2X6IcMByfuSLzmYqnYshG60+HQ==} + '@mantine/charts@9.7.3': + resolution: {integrity: sha512-p8SMOVEmQATcHIszSYdKdU6MBq8Ozdm+QJeFz+Zrtpquv0+ZVHIb262emZV544a7UmwOGdMN5DxmhQ8lewrbvg==} + peerDependencies: + '@mantine/core': 9.7.3 + '@mantine/hooks': 9.7.3 + react: ^19.2.0 + react-dom: ^19.2.0 + recharts: '>=3.2.1' + '@mantine/core@8.1.2': resolution: {integrity: sha512-+maX0a1+fxh6Lvnzi0qb0AZsCnnHlIiTE/hFC+dd3eRfUW2PEKJ5/wTpmrX8IGyxa+NS+fXjZD/cU4Yt9xNjdg==} peerDependencies: @@ -893,6 +908,17 @@ packages: resolution: {integrity: sha512-3arRdUp1fNx55itnjKiUhO6t4Mf91TsrTIYINDNLAZPS0TPd5YpiXRctwjel0qqWoOOhjA34cZ3m4dksLDFUYg==} engines: {node: '>=18.17.0', npm: '>=9.5.0'} + '@reduxjs/toolkit@2.13.0': + resolution: {integrity: sha512-f6jwVRfwW30+tnllz4YcsHHpPYYOE7abcHkyUCovD+P7DyhdAI5prz2Fh/8TLhakjpN+7aIEion1s5L/tvlYlA==} + peerDependencies: + react: ^16.9.0 || ^17.0.0 || ^18 || ^19 + react-redux: ^7.2.1 || ^8.1.3 || ^9.0.0 + peerDependenciesMeta: + react: + optional: true + react-redux: + optional: true + '@rollup/rollup-android-arm-eabi@4.62.4': resolution: {integrity: sha512-RrPokAb7dmbxFoeO3TloqHyOjgye8RkBhSqmp4aJMIex4c9r46ZstPnleDQOq1t46VOVjwIuwNogIqbodV1Vvg==} cpu: [arm] @@ -1055,6 +1081,9 @@ packages: '@standard-schema/spec@1.1.0': resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + '@standard-schema/utils@0.3.0': + resolution: {integrity: sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==} + '@tabler/icons-react@3.30.0': resolution: {integrity: sha512-9KZ9D1UNAyjlLkkYp2HBPHdf6lAJ2aelDqh8YYAnnmLF3xwprWKxxW8+zw5jlI0IwdfN4XFFuzqePkaw+DpIOg==} peerDependencies: @@ -1219,6 +1248,33 @@ packages: '@types/chai@5.2.3': resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + '@types/d3-array@3.2.2': + resolution: {integrity: sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==} + + '@types/d3-color@3.1.3': + resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==} + + '@types/d3-ease@3.0.2': + resolution: {integrity: sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==} + + '@types/d3-interpolate@3.0.4': + resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==} + + '@types/d3-path@3.1.1': + resolution: {integrity: sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==} + + '@types/d3-scale@4.0.9': + resolution: {integrity: sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==} + + '@types/d3-shape@3.2.0': + resolution: {integrity: sha512-kVd74ta9eof3eJOvbNd1vGKS/XERRyQbT26Og63hIsvDO84cjD5gEOhsXf26w3FSoNlPVz84DOFcKv/oou+fMw==} + + '@types/d3-time@3.0.4': + resolution: {integrity: sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==} + + '@types/d3-timer@3.0.2': + resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==} + '@types/debug@4.1.12': resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} @@ -1275,6 +1331,9 @@ packages: '@types/unist@3.0.3': resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} + '@types/use-sync-external-store@0.0.6': + resolution: {integrity: sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==} + '@typescript-eslint/eslint-plugin@8.19.0': resolution: {integrity: sha512-NggSaEZCdSrFddbctrVjkVZvFC6KGfKfNK0CU7mNK/iKHGKbzT4Wmgm08dKpcZECBu9f5FypndoMyRHkdqfT1Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -1675,6 +1734,50 @@ packages: csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + d3-array@3.2.4: + resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==} + engines: {node: '>=12'} + + d3-color@3.1.0: + resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==} + engines: {node: '>=12'} + + d3-ease@3.0.1: + resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==} + engines: {node: '>=12'} + + d3-format@3.1.2: + resolution: {integrity: sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==} + engines: {node: '>=12'} + + d3-interpolate@3.0.1: + resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==} + engines: {node: '>=12'} + + d3-path@3.1.0: + resolution: {integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==} + engines: {node: '>=12'} + + d3-scale@4.0.2: + resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==} + engines: {node: '>=12'} + + d3-shape@3.2.0: + resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==} + engines: {node: '>=12'} + + d3-time-format@4.1.0: + resolution: {integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==} + engines: {node: '>=12'} + + d3-time@3.1.0: + resolution: {integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==} + engines: {node: '>=12'} + + d3-timer@3.0.1: + resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==} + engines: {node: '>=12'} + data-urls@6.0.1: resolution: {integrity: sha512-euIQENZg6x8mj3fO6o9+fOW8MimUI4PpD/fZBhJfeioZVy9TUpM4UY7KjQNVZFlqwJ0UdzRDzkycB997HEq1BQ==} engines: {node: '>=20'} @@ -1703,6 +1806,9 @@ packages: supports-color: optional: true + decimal.js-light@2.5.1: + resolution: {integrity: sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==} + decimal.js@10.6.0: resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} @@ -1816,6 +1922,9 @@ packages: resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} engines: {node: '>= 0.4'} + es-toolkit@1.52.0: + resolution: {integrity: sha512-XTNEJQh1tY1ZJVcf6ayP/2n4ZPyaHlW2FWs7xvw5ddPuhUVjLD3olQVQS7kf58JbAB48iL0uL/jerTrjtV3lDA==} + esbuild@0.27.2: resolution: {integrity: sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==} engines: {node: '>=18'} @@ -1904,6 +2013,9 @@ packages: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} + eventemitter3@5.0.4: + resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==} + expect-type@1.3.0: resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} engines: {node: '>=12.0.0'} @@ -2163,6 +2275,9 @@ packages: immer@10.1.3: resolution: {integrity: sha512-tmjF/k8QDKydUlm3mZU+tjM6zeq9/fFpPqH9SzWmBnVVKsPBg/V66qsMwb3/Bo90cgUN+ghdVBess+hPsxUyRw==} + immer@11.1.21: + resolution: {integrity: sha512-bZSadeCPGdaY5/qRLgr6mDSH1ra21rD3rqpiWk7+QTr4ayYPTE9qZYdLh/NdPhMe0wSrTvHD/Lk8q5EwhUs0aw==} + immutable@5.1.9: resolution: {integrity: sha512-m8nVez3rwrgmWxtLMt1ZYXB2Lv7OKYn/disyxAlSDYAlKSlFoPPfIAmAM/M5xqL4m4C/wAPw7S2/CNaUii1Hxg==} @@ -2192,6 +2307,10 @@ packages: resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} engines: {node: '>= 0.4'} + internmap@2.0.3: + resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} + engines: {node: '>=12'} + is-alphabetical@2.0.1: resolution: {integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==} @@ -3037,6 +3156,18 @@ packages: react: ^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-redux@9.3.0: + resolution: {integrity: sha512-KQopgqFo/p/fgmAs5qz6p5RWaNAzq40WAu7fJIXnQpYxFPbJYtsJPWvGeF2rOBaY/kEuV77AVsX8TsQzKm+A/g==} + peerDependencies: + '@types/react': ^18.2.25 || ^19 + react: ^18.0 || ^19 + redux: ^5.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + redux: + optional: true + react-refresh@0.14.2: resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==} engines: {node: '>=0.10.0'} @@ -3125,10 +3256,26 @@ packages: resolution: {integrity: sha512-h80JrZu/MHUZCyHu5ciuoI0+WxsCxzxJTILn6Fs8rxSnFPh+UVHYfeIxK1nVGugMqkfC4vJcBOYbkfkwYK0+gw==} engines: {node: '>= 14.18.0'} + recharts@3.10.1: + resolution: {integrity: sha512-QXFrvt6IVcw7eeZCoyXTwkIJAX3Dv1nyVhMicXJ47GsGDDpcN8z6o644DibE9XjpBTThtsomLKnTV6lc+cVFUA==} + engines: {node: '>=18'} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-is: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + redent@3.0.0: resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} engines: {node: '>=8'} + redux-thunk@3.1.0: + resolution: {integrity: sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==} + peerDependencies: + redux: ^5.0.0 + + redux@5.0.1: + resolution: {integrity: sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==} + reflect.getprototypeof@1.0.10: resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} engines: {node: '>= 0.4'} @@ -3154,6 +3301,9 @@ packages: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} + reselect@5.2.0: + resolution: {integrity: sha512-AgZ3UOZm3YndfrJ4OYjgrT7bmCm/1iqkjvEfH/oYjzh6PD2qw4QuT3jjnXIrpdt4MTpMXclMT3lXbmRY+XRakw==} + resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} @@ -3448,6 +3598,9 @@ packages: resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==} engines: {node: '>=6'} + tiny-invariant@1.3.3: + resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} + tinybench@2.9.0: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} @@ -3659,6 +3812,11 @@ packages: '@types/react': optional: true + use-sync-external-store@1.7.0: + resolution: {integrity: sha512-6L+EeigHMQhdaIPNIFUKwfWJSwWFQ8gJbJ2DLOs5sDIegTwR9fRxvnM3uciHKjIZhFz+KAv2emhWMRvDmMcY8A==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} @@ -3672,6 +3830,9 @@ packages: vfile@6.0.3: resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} + victory-vendor@37.3.6: + resolution: {integrity: sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==} + vite-tsconfig-paths@5.1.4: resolution: {integrity: sha512-cYj0LRuLV2c2sMqhqhGpaO3LretdtMn/BVX4cPLanIZuwwrkVl+lK84E/miEXkCHWXuq65rhNN4rXsBcOB3S4w==} peerDependencies: @@ -4300,6 +4461,14 @@ snapshots: dependencies: buffer: 6.0.3 + '@mantine/charts@9.7.3(@mantine/core@8.1.2(@mantine/hooks@8.1.2(react@18.3.1))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@mantine/hooks@8.1.2(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(recharts@3.10.1(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react-is@17.0.2)(react@18.3.1)(redux@5.0.1))': + dependencies: + '@mantine/core': 8.1.2(@mantine/hooks@8.1.2(react@18.3.1))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@mantine/hooks': 8.1.2(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + recharts: 3.10.1(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react-is@17.0.2)(react@18.3.1)(redux@5.0.1) + '@mantine/core@8.1.2(@mantine/hooks@8.1.2(react@18.3.1))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@floating-ui/react': 0.26.28(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -4465,6 +4634,18 @@ snapshots: transitivePeerDependencies: - supports-color + '@reduxjs/toolkit@2.13.0(react-redux@9.3.0(@types/react@18.3.18)(react@18.3.1)(redux@5.0.1))(react@18.3.1)': + dependencies: + '@standard-schema/spec': 1.1.0 + '@standard-schema/utils': 0.3.0 + immer: 11.1.21 + redux: 5.0.1 + redux-thunk: 3.1.0(redux@5.0.1) + reselect: 5.2.0 + optionalDependencies: + react: 18.3.1 + react-redux: 9.3.0(@types/react@18.3.18)(react@18.3.1)(redux@5.0.1) + '@rollup/rollup-android-arm-eabi@4.62.4': optional: true @@ -4575,6 +4756,8 @@ snapshots: '@standard-schema/spec@1.1.0': {} + '@standard-schema/utils@0.3.0': {} + '@tabler/icons-react@3.30.0(react@18.3.1)': dependencies: '@tabler/icons': 3.30.0 @@ -4728,6 +4911,30 @@ snapshots: '@types/deep-eql': 4.0.2 assertion-error: 2.0.1 + '@types/d3-array@3.2.2': {} + + '@types/d3-color@3.1.3': {} + + '@types/d3-ease@3.0.2': {} + + '@types/d3-interpolate@3.0.4': + dependencies: + '@types/d3-color': 3.1.3 + + '@types/d3-path@3.1.1': {} + + '@types/d3-scale@4.0.9': + dependencies: + '@types/d3-time': 3.0.4 + + '@types/d3-shape@3.2.0': + dependencies: + '@types/d3-path': 3.1.1 + + '@types/d3-time@3.0.4': {} + + '@types/d3-timer@3.0.2': {} + '@types/debug@4.1.12': dependencies: '@types/ms': 2.1.0 @@ -4782,6 +4989,8 @@ snapshots: '@types/unist@3.0.3': {} + '@types/use-sync-external-store@0.0.6': {} + '@typescript-eslint/eslint-plugin@8.19.0(@typescript-eslint/parser@8.19.0(eslint@9.17.0(jiti@2.6.1))(typescript@5.6.3))(eslint@9.17.0(jiti@2.6.1))(typescript@5.6.3)': dependencies: '@eslint-community/regexpp': 4.12.1 @@ -5267,6 +5476,44 @@ snapshots: csstype@3.1.3: {} + d3-array@3.2.4: + dependencies: + internmap: 2.0.3 + + d3-color@3.1.0: {} + + d3-ease@3.0.1: {} + + d3-format@3.1.2: {} + + d3-interpolate@3.0.1: + dependencies: + d3-color: 3.1.0 + + d3-path@3.1.0: {} + + d3-scale@4.0.2: + dependencies: + d3-array: 3.2.4 + d3-format: 3.1.2 + d3-interpolate: 3.0.1 + d3-time: 3.1.0 + d3-time-format: 4.1.0 + + d3-shape@3.2.0: + dependencies: + d3-path: 3.1.0 + + d3-time-format@4.1.0: + dependencies: + d3-time: 3.1.0 + + d3-time@3.1.0: + dependencies: + d3-array: 3.2.4 + + d3-timer@3.0.1: {} + data-urls@6.0.1: dependencies: whatwg-mimetype: 5.0.0 @@ -5308,6 +5555,8 @@ snapshots: optionalDependencies: supports-color: 5.5.0 + decimal.js-light@2.5.1: {} + decimal.js@10.6.0: {} decode-named-character-reference@1.2.0: @@ -5484,6 +5733,8 @@ snapshots: is-date-object: 1.1.0 is-symbol: 1.1.1 + es-toolkit@1.52.0: {} + esbuild@0.27.2: optionalDependencies: '@esbuild/aix-ppc64': 0.27.2 @@ -5633,6 +5884,8 @@ snapshots: esutils@2.0.3: {} + eventemitter3@5.0.4: {} + expect-type@1.3.0: {} fast-deep-equal@3.1.3: {} @@ -5915,6 +6168,8 @@ snapshots: immer@10.1.3: {} + immer@11.1.21: {} + immutable@5.1.9: {} import-fresh@3.3.0: @@ -5938,6 +6193,8 @@ snapshots: hasown: 2.0.2 side-channel: 1.1.0 + internmap@2.0.3: {} + is-alphabetical@2.0.1: {} is-alphanumerical@2.0.1: @@ -6869,6 +7126,15 @@ snapshots: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) + react-redux@9.3.0(@types/react@18.3.18)(react@18.3.1)(redux@5.0.1): + dependencies: + '@types/use-sync-external-store': 0.0.6 + react: 18.3.1 + use-sync-external-store: 1.7.0(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.18 + redux: 5.0.1 + react-refresh@0.14.2: {} react-remove-scroll-bar@2.3.8(@types/react@18.3.18)(react@18.3.1): @@ -6955,11 +7221,37 @@ snapshots: readdirp@4.1.1: {} + recharts@3.10.1(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react-is@17.0.2)(react@18.3.1)(redux@5.0.1): + dependencies: + '@reduxjs/toolkit': 2.13.0(react-redux@9.3.0(@types/react@18.3.18)(react@18.3.1)(redux@5.0.1))(react@18.3.1) + clsx: 2.1.1 + decimal.js-light: 2.5.1 + es-toolkit: 1.52.0 + eventemitter3: 5.0.4 + immer: 11.1.21 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-is: 17.0.2 + react-redux: 9.3.0(@types/react@18.3.18)(react@18.3.1)(redux@5.0.1) + reselect: 5.2.0 + tiny-invariant: 1.3.3 + use-sync-external-store: 1.7.0(react@18.3.1) + victory-vendor: 37.3.6 + transitivePeerDependencies: + - '@types/react' + - redux + redent@3.0.0: dependencies: indent-string: 4.0.0 strip-indent: 3.0.0 + redux-thunk@3.1.0(redux@5.0.1): + dependencies: + redux: 5.0.1 + + redux@5.0.1: {} + reflect.getprototypeof@1.0.10: dependencies: call-bind: 1.0.8 @@ -6994,6 +7286,8 @@ snapshots: require-from-string@2.0.2: {} + reselect@5.2.0: {} + resolve-from@4.0.0: {} resolve-from@5.0.0: {} @@ -7390,6 +7684,8 @@ snapshots: tapable@2.3.0: {} + tiny-invariant@1.3.3: {} + tinybench@2.9.0: {} tinyexec@1.0.2: {} @@ -7588,6 +7884,10 @@ snapshots: optionalDependencies: '@types/react': 18.3.18 + use-sync-external-store@1.7.0(react@18.3.1): + dependencies: + react: 18.3.1 + util-deprecate@1.0.2: {} uuid@13.0.1: {} @@ -7602,6 +7902,23 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.3 + victory-vendor@37.3.6: + dependencies: + '@types/d3-array': 3.2.2 + '@types/d3-ease': 3.0.2 + '@types/d3-interpolate': 3.0.4 + '@types/d3-scale': 4.0.9 + '@types/d3-shape': 3.2.0 + '@types/d3-time': 3.0.4 + '@types/d3-timer': 3.0.2 + d3-array: 3.2.4 + d3-ease: 3.0.1 + d3-interpolate: 3.0.1 + d3-scale: 4.0.2 + d3-shape: 3.2.0 + d3-time: 3.1.0 + d3-timer: 3.0.1 + vite-tsconfig-paths@5.1.4(typescript@5.6.3)(vite@7.3.6(@types/node@24.0.10)(jiti@2.6.1)(lightningcss@1.33.0)(sass@1.83.1)): dependencies: debug: 4.4.0 diff --git a/js/src/app/user/[userId]/UserProfile.page.tsx b/js/src/app/user/[userId]/UserProfile.page.tsx index 7d661ce60..a112b2f08 100644 --- a/js/src/app/user/[userId]/UserProfile.page.tsx +++ b/js/src/app/user/[userId]/UserProfile.page.tsx @@ -1,7 +1,7 @@ +import MetricsChart from "@/app/user/[userId]/_components/UserProfile/MetricsChart/MetricsChart"; import ProfilePicture from "@/app/user/[userId]/_components/UserProfile/ProfilePicture/ProfilePicture"; import UserProfileHeader from "@/app/user/[userId]/_components/UserProfile/UserProfileHeader"; import UserTags from "@/app/user/[userId]/_components/UserProfile/UserTags/UserTags"; -import MetricsChart from "@/app/user/[userId]/_components/UserProfile/MetricsChart/MetricsChart"; import MiniUserSubmissions from "@/app/user/[userId]/submissions/_components/UserSubmissions/MiniUserSubmissions"; import ToastWithRedirect from "@/components/ui/toast/ToastWithRedirect"; import { getUserSubmissionsUrl } from "@/lib/helper/leaderboardDateRange"; @@ -65,7 +65,7 @@ export default function UserProfilePage() { - + ; @@ -18,4 +26,39 @@ export default function MetricsChart({ userId }: { userId: string }) { if (!data.success) { return ; } + + const userMetrics = data.payload; + const pageData = userMetrics.items.map((item) => { + return { + ...item, + createdAt: d(item.createdAt).utc().format("MMM DD, YYYY"), + }; + }); + + return ( + <> + + + + + + + ); } diff --git a/js/src/app/user/[userId]/_components/UserProfile/MetricsChart/MetricsChartSkeleton.tsx b/js/src/app/user/[userId]/_components/UserProfile/MetricsChart/MetricsChartSkeleton.tsx index 90abf7aef..ad6a22f51 100644 --- a/js/src/app/user/[userId]/_components/UserProfile/MetricsChart/MetricsChartSkeleton.tsx +++ b/js/src/app/user/[userId]/_components/UserProfile/MetricsChart/MetricsChartSkeleton.tsx @@ -5,7 +5,7 @@ export default function MetricsChartSkeleton() { <> - + ); diff --git a/js/src/lib/api/queries/user/index.ts b/js/src/lib/api/queries/user/index.ts index 9f18fc482..1c2831a8f 100644 --- a/js/src/lib/api/queries/user/index.ts +++ b/js/src/lib/api/queries/user/index.ts @@ -304,6 +304,88 @@ export const useGetAllUsersInfiniteQuery = ({ }; }; +/** + * Fetches the user's metrics. + */ +export const useUserMetricsQuery = ({ + userId, + initialPage = 1, + tieToUrl = false, + pageSize = 5, +}: { + userId: string; + initialPage?: number; + tieToUrl?: boolean; + pageSize?: number; +}) => { + const { page, goBack, goForward, goTo } = usePagination({ + initialPage: initialPage, + tieToUrl: tieToUrl, + }); + + const { + startDate, + endDate, + setStartDate, + setEndDate, + debouncedStartDate, + debouncedEndDate, + } = useURLDateRange(tieToUrl); + + // if seconds are not included, use start of day and end of day + const processedStartDate = useMemo( + () => + debouncedStartDate ? + debouncedStartDate.includes(":") ? + d.utc(debouncedStartDate).toISOString() + : d(debouncedStartDate).startOf("day").utc().toISOString() + : undefined, + [debouncedStartDate], + ); + const processedEndDate = useMemo( + () => + debouncedEndDate ? + debouncedEndDate.includes(":") ? + d.utc(debouncedEndDate).toISOString() + : d(debouncedEndDate).endOf("day").utc().toISOString() + : undefined, + [debouncedEndDate], + ); + + const apiURL = ApiURL.create("/api/user/{userId}/metrics", { + method: "GET", + params: { + userId, + }, + queries: { + page, + pageSize, + startDate: processedStartDate, + endDate: processedEndDate, + }, + }); + + const { queryKey } = apiURL; + const query = useQuery({ + queryKey, + queryFn: () => fetchUserMetrics(apiURL), + placeholderData: keepPreviousData, + }); + + return { + ...query, + page, + goBack, + goForward, + goTo, + pageSize, + startDate, + endDate, + setStartDate, + setEndDate, + }; +}; + async function fetchUserProfile({ url, method, @@ -350,3 +432,19 @@ async function fetchAllUsers({ return json; } + +async function fetchUserMetrics({ + url, + method, + res, +}: ApiURL<"/api/user/{userId}/metrics", "get">) { + const response = await fetch(url, { + method, + }); + + await sleep(650); + + const json = res(await response.json()); + + return json; +} From 9eba0e52be4abdc6fe73243649bc1835b6d8a4f7 Mon Sep 17 00:00:00 2001 From: Stacey Chen Date: Fri, 23 Oct 2026 00:44:54 -0400 Subject: [PATCH 03/14] DINO-839: addressed sonarqube warnings and added 2 tests --- .../MetricsChart/MetricsChart.test.tsx | 33 +++++++++++++++++++ .../UserProfile/MetricsChart/MetricsChart.tsx | 3 +- .../MetricsChart/MetricsChartSkeleton.tsx | 15 +++++---- js/src/lib/api/queries/user/index.ts | 28 +++++++++------- 4 files changed, 60 insertions(+), 19 deletions(-) create mode 100644 js/src/app/user/[userId]/_components/UserProfile/MetricsChart/MetricsChart.test.tsx diff --git a/js/src/app/user/[userId]/_components/UserProfile/MetricsChart/MetricsChart.test.tsx b/js/src/app/user/[userId]/_components/UserProfile/MetricsChart/MetricsChart.test.tsx new file mode 100644 index 000000000..14d7ddc0f --- /dev/null +++ b/js/src/app/user/[userId]/_components/UserProfile/MetricsChart/MetricsChart.test.tsx @@ -0,0 +1,33 @@ +import MetricsChart from "@/app/user/[userId]/_components/UserProfile/MetricsChart/MetricsChart.tsx"; +import { TestUtils, TestUtilTypes } from "@/lib/test"; +import { screen, cleanup, waitFor } from "@testing-library/react"; +import { v4 as uuid } from "uuid"; + +describe("MetricsChart succeeded", () => { + afterEach(() => { + cleanup(); + }); + + let renderProviderFn: TestUtilTypes.RenderWithAllProvidersFn | null = null; + beforeEach(() => { + renderProviderFn = TestUtils.getRenderWithAllProvidersFn(); + }); + + it("should render skeleton metrics chart initially", () => { + const MOCK_USER_ID = uuid(); + renderProviderFn?.(); + const element = screen.getByTestId("user-metrics-chart-skeleton"); + expect(element).toBeInTheDocument(); + expect(element).toBeVisible(); + }); + + it("should render AreaChart eventually", async () => { + const MOCK_USER_ID = uuid(); + renderProviderFn?.(); + await waitFor(() => { + const element = screen.getByTestId("metrics-area-chart"); + expect(element).toBeInTheDocument(); + expect(element).toBeVisible(); + }); + }); +}); diff --git a/js/src/app/user/[userId]/_components/UserProfile/MetricsChart/MetricsChart.tsx b/js/src/app/user/[userId]/_components/UserProfile/MetricsChart/MetricsChart.tsx index 6c5a2b00c..f4e2c688a 100644 --- a/js/src/app/user/[userId]/_components/UserProfile/MetricsChart/MetricsChart.tsx +++ b/js/src/app/user/[userId]/_components/UserProfile/MetricsChart/MetricsChart.tsx @@ -8,7 +8,7 @@ import { Box, Divider } from "@mantine/core"; import "@mantine/charts/styles.css"; import d from "dayjs"; -export default function MetricsChart({ userId }: { userId: string }) { +export default function MetricsChart({ userId }: { readonly userId: string }) { const { data, status, page, goBack, goForward, goTo } = useUserMetricsQuery({ userId, }); @@ -43,6 +43,7 @@ export default function MetricsChart({ userId }: { userId: string }) { h={250} data={pageData} dataKey="createdAt" + data-testId = "metrics-area-chart" series={[ { name: "points", label: "Points", color: theme.colors.patina[4] }, ]} diff --git a/js/src/app/user/[userId]/_components/UserProfile/MetricsChart/MetricsChartSkeleton.tsx b/js/src/app/user/[userId]/_components/UserProfile/MetricsChart/MetricsChartSkeleton.tsx index ad6a22f51..1304d5380 100644 --- a/js/src/app/user/[userId]/_components/UserProfile/MetricsChart/MetricsChartSkeleton.tsx +++ b/js/src/app/user/[userId]/_components/UserProfile/MetricsChart/MetricsChartSkeleton.tsx @@ -2,11 +2,14 @@ import { Flex, Skeleton, Divider } from "@mantine/core"; export default function MetricsChartSkeleton() { return ( - <> - - - - - + + + + ); } diff --git a/js/src/lib/api/queries/user/index.ts b/js/src/lib/api/queries/user/index.ts index 1c2831a8f..5f86a4447 100644 --- a/js/src/lib/api/queries/user/index.ts +++ b/js/src/lib/api/queries/user/index.ts @@ -334,21 +334,25 @@ export const useUserMetricsQuery = ({ // if seconds are not included, use start of day and end of day const processedStartDate = useMemo( - () => - debouncedStartDate ? - debouncedStartDate.includes(":") ? - d.utc(debouncedStartDate).toISOString() - : d(debouncedStartDate).startOf("day").utc().toISOString() - : undefined, + () => { + if (debouncedStartDate){ + if (debouncedStartDate.includes(":")) + return d.utc(debouncedStartDate).toISOString(); + return d(debouncedStartDate).startOf("day").utc().toISOString(); + } + return undefined; + }, [debouncedStartDate], ); const processedEndDate = useMemo( - () => - debouncedEndDate ? - debouncedEndDate.includes(":") ? - d.utc(debouncedEndDate).toISOString() - : d(debouncedEndDate).endOf("day").utc().toISOString() - : undefined, + () => { + if (debouncedEndDate){ + if (debouncedEndDate.includes(":")) + return d.utc(debouncedEndDate).toISOString(); + return d(debouncedEndDate).endOf("day").utc().toISOString(); + } + return undefined; + }, [debouncedEndDate], ); From cfb378ed142dc93eacefc2cc5e68b402c3b5c748 Mon Sep 17 00:00:00 2001 From: Stacey Chen Date: Sat, 24 Oct 2026 00:17:27 -0400 Subject: [PATCH 04/14] DINO-839: added more tests --- .../MetricsChart/MetricsChart.test.tsx | 190 +++++++++++++++++- .../UserProfile/MetricsChart/MetricsChart.tsx | 6 +- 2 files changed, 191 insertions(+), 5 deletions(-) diff --git a/js/src/app/user/[userId]/_components/UserProfile/MetricsChart/MetricsChart.test.tsx b/js/src/app/user/[userId]/_components/UserProfile/MetricsChart/MetricsChart.test.tsx index 14d7ddc0f..db938502d 100644 --- a/js/src/app/user/[userId]/_components/UserProfile/MetricsChart/MetricsChart.test.tsx +++ b/js/src/app/user/[userId]/_components/UserProfile/MetricsChart/MetricsChart.test.tsx @@ -1,16 +1,90 @@ import MetricsChart from "@/app/user/[userId]/_components/UserProfile/MetricsChart/MetricsChart.tsx"; +import { ApiURL } from "@/lib/api/common/apiURL"; import { TestUtils, TestUtilTypes } from "@/lib/test"; -import { screen, cleanup, waitFor } from "@testing-library/react"; +import { cleanup, screen, waitFor } from "@testing-library/react"; +import { http, HttpResponse } from "msw"; +import { setupServer } from "msw/node"; import { v4 as uuid } from "uuid"; +const FIXED_USER_ID = "metrics-test-user"; + +const userMetricsUrl = ApiURL.create("/api/user/{userId}/metrics", { + method: "GET", + params: { userId: FIXED_USER_ID }, +}); + +/* +const MOCK_METRICS = [ + { + id: "metric-1", + userId: FIXED_USER_ID, + points: 500, + createdAt: new Date(2026, 9, 1, 0).toISOString(), + }, + { + id: "metric-2", + userId: FIXED_USER_ID, + points: 1500, + createdAt: new Date(2026, 9, 2, 0).toISOString(), + }, + { + id: "metric-3", + userId: FIXED_USER_ID, + points: 2000, + createdAt: new Date(2026, 9, 3, 0).toISOString(), + }, + { + id: "metric-4", + userId: FIXED_USER_ID, + points: 3000, + createdAt: new Date(2026, 9, 4, 0).toISOString(), + }, +]; + + +const userMetricsSuccessHandler = http.get(userMetricsUrl.url.toString(), () => + HttpResponse.json({ + success: true, + message: "Metrics loaded!", + payload: { + hasNextPage: false, + pages: 1, + items: MOCK_METRICS, + success: true, + }, + }), +); +*/ + +const userMetricsErrorHandler = http.get(userMetricsUrl.url.toString(), () => + HttpResponse.error(), +); + +const mockUseUserMetricsQuery = vi.fn(); + +vi.mock("@/lib/api/queries/user", () => ({ + useUserMetricsQuery: (...args: unknown[]) => mockUseUserMetricsQuery(...args), +})); + +const BASE_QUERY_RESULT = { + data: undefined, + status: "pending", + page: 1, + goBack: vi.fn(), + goForward: vi.fn(), + goTo: vi.fn(), +}; + describe("MetricsChart succeeded", () => { afterEach(() => { cleanup(); + vi.clearAllMocks(); }); let renderProviderFn: TestUtilTypes.RenderWithAllProvidersFn | null = null; beforeEach(() => { renderProviderFn = TestUtils.getRenderWithAllProvidersFn(); + mockUseUserMetricsQuery.mockReturnValue(BASE_QUERY_RESULT); }); it("should render skeleton metrics chart initially", () => { @@ -22,12 +96,120 @@ describe("MetricsChart succeeded", () => { }); it("should render AreaChart eventually", async () => { + mockUseUserMetricsQuery.mockReturnValue({ + ...BASE_QUERY_RESULT, + status: "success", + data: { + payload: { items: [], pages: 1, hasNextPage: false }, + success: true, + }, + }); + const MOCK_USER_ID = uuid(); + renderProviderFn?.(); + const element = await screen.findByTestId("metrics-area-chart"); + expect(element).toBeInTheDocument(); + expect(element).toBeVisible(); + }); + + it("should show data message toast and not render AreaChart when data is not successful", async () => { + const mockMessage = "Data not success."; + mockUseUserMetricsQuery.mockReturnValue({ + ...BASE_QUERY_RESULT, + status: "success", + data: { + payload: { items: [], pages: 1, hasNextPage: false }, + success: false, + message: mockMessage, + }, + }); const MOCK_USER_ID = uuid(); renderProviderFn?.(); + + await waitFor(() => { + expect(screen.getByText(mockMessage)).toBeInTheDocument(); + }); + + await waitFor(() => { + expect( + screen.queryByTestId("metrics-area-chart"), + ).not.toBeInTheDocument(); + }); + }); +}); + +/* +describe("UserMetricsChart with successful API", () => { + const server = setupServer(userMetricsSuccessHandler); + + beforeAll(() => server.listen()); + afterEach(() => { + server.resetHandlers(); + cleanup(); + vi.clearAllMocks(); + }); + afterAll(() => server.close()); + + let renderProviderFn: TestUtilTypes.RenderWithAllProvidersFn | null = null; + beforeEach(() => { + renderProviderFn = TestUtils.getRenderWithAllProvidersFn(); + + mockUseUserMetricsQuery.mockReturnValue({ + ...BASE_QUERY_RESULT, + status: "success", + data: { + payload: { items: MOCK_METRICS, pages: 1, hasNextPage: false }, + success: true, + }, + }); + }); + + it("should render formatted dates for chart after successful API call", async () => { + renderProviderFn?.(); + + await screen.findByTestId("metrics-area-chart"); + await waitFor(() => { + expect(screen.getByText("Oct 01, 2026")).toBeInTheDocument(); + }); + }); + + it("should have correct pages and hasNextPage for Paginator based on data payload", async () => { + renderProviderFn?.(); + + const element = await screen.findByTestId("metrics-paginator"); + expect(element.getAttribute("pages")).toBe(1); + expect(element.getAttribute("hasNextPage")).toBe(false); + }); +}); +*/ + +describe("UserMetricsChart error state", () => { + const server = setupServer(userMetricsErrorHandler); + + beforeAll(() => server.listen()); + afterEach(() => { + server.resetHandlers(); + cleanup(); + vi.clearAllMocks(); + }); + afterAll(() => server.close()); + + let renderProviderFn: TestUtilTypes.RenderWithAllProvidersFn | null = null; + beforeEach(() => { + renderProviderFn = TestUtils.getRenderWithAllProvidersFn(); + mockUseUserMetricsQuery.mockReturnValue({ + ...BASE_QUERY_RESULT, + status: "error", + data: undefined, + }); + }); + + it("should show error toast when API errors", async () => { + renderProviderFn?.(); + await waitFor(() => { - const element = screen.getByTestId("metrics-area-chart"); - expect(element).toBeInTheDocument(); - expect(element).toBeVisible(); + expect( + screen.getByText(/sorry, something went wrong/i), + ).toBeInTheDocument(); }); }); }); diff --git a/js/src/app/user/[userId]/_components/UserProfile/MetricsChart/MetricsChart.tsx b/js/src/app/user/[userId]/_components/UserProfile/MetricsChart/MetricsChart.tsx index f4e2c688a..9c0c712d6 100644 --- a/js/src/app/user/[userId]/_components/UserProfile/MetricsChart/MetricsChart.tsx +++ b/js/src/app/user/[userId]/_components/UserProfile/MetricsChart/MetricsChart.tsx @@ -7,6 +7,9 @@ import { AreaChart } from "@mantine/charts"; import { Box, Divider } from "@mantine/core"; import "@mantine/charts/styles.css"; import d from "dayjs"; +import utc from "dayjs/plugin/utc"; + +d.extend(utc); export default function MetricsChart({ userId }: { readonly userId: string }) { const { data, status, page, goBack, goForward, goTo } = useUserMetricsQuery({ @@ -43,7 +46,7 @@ export default function MetricsChart({ userId }: { readonly userId: string }) { h={250} data={pageData} dataKey="createdAt" - data-testId = "metrics-area-chart" + data-testId="metrics-area-chart" series={[ { name: "points", label: "Points", color: theme.colors.patina[4] }, ]} @@ -59,6 +62,7 @@ export default function MetricsChart({ userId }: { readonly userId: string }) { goBack={goBack} goForward={goForward} goTo={goTo} + data-testId="metrics-paginator" /> ); From 33a2336647001e893c21c0bee326bbf857db8dab Mon Sep 17 00:00:00 2001 From: Stacey Chen Date: Sat, 24 Oct 2026 11:09:27 -0400 Subject: [PATCH 05/14] DINO-839: prettier --- .../_components/UserProfile/MetricsChart/MetricsChart.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/src/app/user/[userId]/_components/UserProfile/MetricsChart/MetricsChart.test.tsx b/js/src/app/user/[userId]/_components/UserProfile/MetricsChart/MetricsChart.test.tsx index db938502d..3321d9e53 100644 --- a/js/src/app/user/[userId]/_components/UserProfile/MetricsChart/MetricsChart.test.tsx +++ b/js/src/app/user/[userId]/_components/UserProfile/MetricsChart/MetricsChart.test.tsx @@ -13,7 +13,7 @@ const userMetricsUrl = ApiURL.create("/api/user/{userId}/metrics", { params: { userId: FIXED_USER_ID }, }); -/* +/* const MOCK_METRICS = [ { id: "metric-1", From 6617c66bd97b816daf1f28e3560cf4b20ada8ad6 Mon Sep 17 00:00:00 2001 From: Stacey Chen Date: Sat, 24 Oct 2026 22:45:44 -0400 Subject: [PATCH 06/14] DINO-839: added tests and prettier --- .../MetricsChart/MetricsChart.test.tsx | 44 ++++++++++++------- .../UserProfile/MetricsChart/MetricsChart.tsx | 2 +- 2 files changed, 28 insertions(+), 18 deletions(-) diff --git a/js/src/app/user/[userId]/_components/UserProfile/MetricsChart/MetricsChart.test.tsx b/js/src/app/user/[userId]/_components/UserProfile/MetricsChart/MetricsChart.test.tsx index 3321d9e53..5d2fc31fe 100644 --- a/js/src/app/user/[userId]/_components/UserProfile/MetricsChart/MetricsChart.test.tsx +++ b/js/src/app/user/[userId]/_components/UserProfile/MetricsChart/MetricsChart.test.tsx @@ -13,7 +13,6 @@ const userMetricsUrl = ApiURL.create("/api/user/{userId}/metrics", { params: { userId: FIXED_USER_ID }, }); -/* const MOCK_METRICS = [ { id: "metric-1", @@ -39,9 +38,20 @@ const MOCK_METRICS = [ points: 3000, createdAt: new Date(2026, 9, 4, 0).toISOString(), }, + { + id: "metric-5", + userId: FIXED_USER_ID, + points: 3000, + createdAt: new Date(2026, 9, 5, 0).toISOString(), + }, + { + id: "metric-6", + userId: FIXED_USER_ID, + points: 3000, + createdAt: new Date(2026, 9, 6, 0).toISOString(), + }, ]; - const userMetricsSuccessHandler = http.get(userMetricsUrl.url.toString(), () => HttpResponse.json({ success: true, @@ -54,7 +64,6 @@ const userMetricsSuccessHandler = http.get(userMetricsUrl.url.toString(), () => }, }), ); -*/ const userMetricsErrorHandler = http.get(userMetricsUrl.url.toString(), () => HttpResponse.error(), @@ -106,7 +115,7 @@ describe("MetricsChart succeeded", () => { }); const MOCK_USER_ID = uuid(); renderProviderFn?.(); - const element = await screen.findByTestId("metrics-area-chart"); + const element = await screen.findByTestId(/metrics-area-chart.*$/); expect(element).toBeInTheDocument(); expect(element).toBeVisible(); }); @@ -131,13 +140,12 @@ describe("MetricsChart succeeded", () => { await waitFor(() => { expect( - screen.queryByTestId("metrics-area-chart"), + screen.queryByTestId(/metrics-area-chart.*$/), ).not.toBeInTheDocument(); }); }); }); -/* describe("UserMetricsChart with successful API", () => { const server = setupServer(userMetricsSuccessHandler); @@ -157,30 +165,32 @@ describe("UserMetricsChart with successful API", () => { ...BASE_QUERY_RESULT, status: "success", data: { - payload: { items: MOCK_METRICS, pages: 1, hasNextPage: false }, + payload: { items: MOCK_METRICS, pages: 2, hasNextPage: true }, success: true, }, }); }); - it("should render formatted dates for chart after successful API call", async () => { + it("should have formatted dates for chart after successful API call", async () => { renderProviderFn?.(); - await screen.findByTestId("metrics-area-chart"); - await waitFor(() => { - expect(screen.getByText("Oct 01, 2026")).toBeInTheDocument(); - }); + const element = await screen.findByTestId( + /metrics-area-chart Oct 01, 2026.*$/, + ); + expect(element).toBeInTheDocument(); + expect(element).toBeVisible(); }); - it("should have correct pages and hasNextPage for Paginator based on data payload", async () => { + it("should have correct userMetrics pages and hasNextPage based on data payload", async () => { renderProviderFn?.(); - const element = await screen.findByTestId("metrics-paginator"); - expect(element.getAttribute("pages")).toBe(1); - expect(element.getAttribute("hasNextPage")).toBe(false); + const element = await screen.findByTestId( + "metrics-area-chart Oct 01, 2026 pages2 hasNextPagetrue", + ); + expect(element).toBeInTheDocument(); + expect(element).toBeVisible(); }); }); -*/ describe("UserMetricsChart error state", () => { const server = setupServer(userMetricsErrorHandler); diff --git a/js/src/app/user/[userId]/_components/UserProfile/MetricsChart/MetricsChart.tsx b/js/src/app/user/[userId]/_components/UserProfile/MetricsChart/MetricsChart.tsx index 9c0c712d6..e84a088b0 100644 --- a/js/src/app/user/[userId]/_components/UserProfile/MetricsChart/MetricsChart.tsx +++ b/js/src/app/user/[userId]/_components/UserProfile/MetricsChart/MetricsChart.tsx @@ -46,7 +46,7 @@ export default function MetricsChart({ userId }: { readonly userId: string }) { h={250} data={pageData} dataKey="createdAt" - data-testId="metrics-area-chart" + data-testId={`metrics-area-chart ${pageData[0]?.createdAt} pages${userMetrics?.pages ?? 0} hasNextPage${userMetrics?.hasNextPage ?? false}`} series={[ { name: "points", label: "Points", color: theme.colors.patina[4] }, ]} From 450e5ab147e75c4007ba7e832d8649bcaa559e72 Mon Sep 17 00:00:00 2001 From: Stacey Chen Date: Sat, 24 Oct 2026 23:02:32 -0400 Subject: [PATCH 07/14] DINO-839: prettier --- js/src/lib/api/queries/user/index.ts | 38 ++++++++++++---------------- 1 file changed, 16 insertions(+), 22 deletions(-) diff --git a/js/src/lib/api/queries/user/index.ts b/js/src/lib/api/queries/user/index.ts index 5f86a4447..ac43bc0d0 100644 --- a/js/src/lib/api/queries/user/index.ts +++ b/js/src/lib/api/queries/user/index.ts @@ -333,28 +333,22 @@ export const useUserMetricsQuery = ({ } = useURLDateRange(tieToUrl); // if seconds are not included, use start of day and end of day - const processedStartDate = useMemo( - () => { - if (debouncedStartDate){ - if (debouncedStartDate.includes(":")) - return d.utc(debouncedStartDate).toISOString(); - return d(debouncedStartDate).startOf("day").utc().toISOString(); - } - return undefined; - }, - [debouncedStartDate], - ); - const processedEndDate = useMemo( - () => { - if (debouncedEndDate){ - if (debouncedEndDate.includes(":")) - return d.utc(debouncedEndDate).toISOString(); - return d(debouncedEndDate).endOf("day").utc().toISOString(); - } - return undefined; - }, - [debouncedEndDate], - ); + const processedStartDate = useMemo(() => { + if (debouncedStartDate) { + if (debouncedStartDate.includes(":")) + return d.utc(debouncedStartDate).toISOString(); + return d(debouncedStartDate).startOf("day").utc().toISOString(); + } + return undefined; + }, [debouncedStartDate]); + const processedEndDate = useMemo(() => { + if (debouncedEndDate) { + if (debouncedEndDate.includes(":")) + return d.utc(debouncedEndDate).toISOString(); + return d(debouncedEndDate).endOf("day").utc().toISOString(); + } + return undefined; + }, [debouncedEndDate]); const apiURL = ApiURL.create("/api/user/{userId}/metrics", { method: "GET", From c85ff74d30805128ff18e143c1c10278fcb7debc Mon Sep 17 00:00:00 2001 From: "coderabbitai[bot]" <136622811+coderabbitai[bot]@users.noreply.github.com> Date: Sun, 25 Oct 2026 17:21:12 +0000 Subject: [PATCH 08/14] DINO-839: Clear stale metrics when switching users, align Mantine charts with v8, and stabilize UTC test fixtures --- js/package.json | 2 +- js/pnpm-lock.yaml | 20 ++-- .../MetricsChart/MetricsChart.test.tsx | 12 +-- js/src/lib/api/queries/user/index.test.tsx | 92 +++++++++++++++++++ js/src/lib/api/queries/user/index.ts | 3 +- 5 files changed, 111 insertions(+), 18 deletions(-) create mode 100644 js/src/lib/api/queries/user/index.test.tsx diff --git a/js/package.json b/js/package.json index d3b0edcb6..d8b294413 100644 --- a/js/package.json +++ b/js/package.json @@ -22,7 +22,7 @@ "preview": "vite preview" }, "dependencies": { - "@mantine/charts": "^9.7.3", + "@mantine/charts": "^8.1.2", "@mantine/core": "^8.1.2", "@mantine/dates": "^8.1.2", "@mantine/form": "^8.1.2", diff --git a/js/pnpm-lock.yaml b/js/pnpm-lock.yaml index f792b49d2..2a335bcf7 100644 --- a/js/pnpm-lock.yaml +++ b/js/pnpm-lock.yaml @@ -32,8 +32,8 @@ importers: .: dependencies: '@mantine/charts': - specifier: ^9.7.3 - version: 9.7.3(@mantine/core@8.1.2(@mantine/hooks@8.1.2(react@18.3.1))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@mantine/hooks@8.1.2(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(recharts@3.10.1(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react-is@17.0.2)(react@18.3.1)(redux@5.0.1)) + specifier: ^8.1.2 + version: 8.1.2(@mantine/core@8.1.2(@mantine/hooks@8.1.2(react@18.3.1))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@mantine/hooks@8.1.2(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(recharts@3.10.1(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react-is@17.0.2)(react@18.3.1)(redux@5.0.1)) '@mantine/core': specifier: ^8.1.2 version: 8.1.2(@mantine/hooks@8.1.2(react@18.3.1))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -727,14 +727,14 @@ packages: '@keyv/serialize@1.0.2': resolution: {integrity: sha512-+E/LyaAeuABniD/RvUezWVXKpeuvwLEA9//nE9952zBaOdBd2mQ3pPoM8cUe2X6IcMByfuSLzmYqnYshG60+HQ==} - '@mantine/charts@9.7.3': - resolution: {integrity: sha512-p8SMOVEmQATcHIszSYdKdU6MBq8Ozdm+QJeFz+Zrtpquv0+ZVHIb262emZV544a7UmwOGdMN5DxmhQ8lewrbvg==} + '@mantine/charts@8.1.2': + resolution: {integrity: sha512-K1AfGQa+ABFLWNXT8d1mu+Zsqv6pG1LT+1FnIorpyxe3DOsWLXP3Tr4JHW9QSS0qFyvpOiFuKeCMdDUkdzyMfQ==} peerDependencies: - '@mantine/core': 9.7.3 - '@mantine/hooks': 9.7.3 - react: ^19.2.0 - react-dom: ^19.2.0 - recharts: '>=3.2.1' + '@mantine/core': 8.1.2 + '@mantine/hooks': 8.1.2 + react: ^18.x || ^19.x + react-dom: ^18.x || ^19.x + recharts: ^2.13.3 '@mantine/core@8.1.2': resolution: {integrity: sha512-+maX0a1+fxh6Lvnzi0qb0AZsCnnHlIiTE/hFC+dd3eRfUW2PEKJ5/wTpmrX8IGyxa+NS+fXjZD/cU4Yt9xNjdg==} @@ -4461,7 +4461,7 @@ snapshots: dependencies: buffer: 6.0.3 - '@mantine/charts@9.7.3(@mantine/core@8.1.2(@mantine/hooks@8.1.2(react@18.3.1))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@mantine/hooks@8.1.2(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(recharts@3.10.1(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react-is@17.0.2)(react@18.3.1)(redux@5.0.1))': + '@mantine/charts@8.1.2(@mantine/core@8.1.2(@mantine/hooks@8.1.2(react@18.3.1))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@mantine/hooks@8.1.2(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(recharts@3.10.1(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react-is@17.0.2)(react@18.3.1)(redux@5.0.1))': dependencies: '@mantine/core': 8.1.2(@mantine/hooks@8.1.2(react@18.3.1))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@mantine/hooks': 8.1.2(react@18.3.1) diff --git a/js/src/app/user/[userId]/_components/UserProfile/MetricsChart/MetricsChart.test.tsx b/js/src/app/user/[userId]/_components/UserProfile/MetricsChart/MetricsChart.test.tsx index 5d2fc31fe..4e09d8621 100644 --- a/js/src/app/user/[userId]/_components/UserProfile/MetricsChart/MetricsChart.test.tsx +++ b/js/src/app/user/[userId]/_components/UserProfile/MetricsChart/MetricsChart.test.tsx @@ -18,37 +18,37 @@ const MOCK_METRICS = [ id: "metric-1", userId: FIXED_USER_ID, points: 500, - createdAt: new Date(2026, 9, 1, 0).toISOString(), + createdAt: new Date(Date.UTC(2026, 9, 1)).toISOString(), }, { id: "metric-2", userId: FIXED_USER_ID, points: 1500, - createdAt: new Date(2026, 9, 2, 0).toISOString(), + createdAt: new Date(Date.UTC(2026, 9, 2)).toISOString(), }, { id: "metric-3", userId: FIXED_USER_ID, points: 2000, - createdAt: new Date(2026, 9, 3, 0).toISOString(), + createdAt: new Date(Date.UTC(2026, 9, 3)).toISOString(), }, { id: "metric-4", userId: FIXED_USER_ID, points: 3000, - createdAt: new Date(2026, 9, 4, 0).toISOString(), + createdAt: new Date(Date.UTC(2026, 9, 4)).toISOString(), }, { id: "metric-5", userId: FIXED_USER_ID, points: 3000, - createdAt: new Date(2026, 9, 5, 0).toISOString(), + createdAt: new Date(Date.UTC(2026, 9, 5)).toISOString(), }, { id: "metric-6", userId: FIXED_USER_ID, points: 3000, - createdAt: new Date(2026, 9, 6, 0).toISOString(), + createdAt: new Date(Date.UTC(2026, 9, 6)).toISOString(), }, ]; diff --git a/js/src/lib/api/queries/user/index.test.tsx b/js/src/lib/api/queries/user/index.test.tsx new file mode 100644 index 000000000..16b2b6dc6 --- /dev/null +++ b/js/src/lib/api/queries/user/index.test.tsx @@ -0,0 +1,92 @@ +import { useUserMetricsQuery } from "@/lib/api/queries/user"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { act, cleanup, renderHook, waitFor } from "@testing-library/react"; +import { ReactNode } from "react"; +import { MemoryRouter } from "react-router-dom"; + +const metrics = { + success: true, + message: "Metrics loaded!", + payload: { + items: [ + { + id: "metric-1", + userId: "user-1", + points: 500, + createdAt: "2026-10-01T00:00:00.000Z", + }, + ], + pages: 2, + hasNextPage: true, + }, +}; + +describe("useUserMetricsQuery placeholders", () => { + let queryClient: QueryClient; + + beforeEach(() => { + queryClient = new QueryClient({ + defaultOptions: { queries: { retry: false } }, + }); + vi.stubGlobal( + "fetch", + vi + .fn() + .mockResolvedValueOnce(Response.json(metrics)) + .mockImplementation(() => new Promise(() => {})), + ); + }); + + afterEach(() => { + cleanup(); + queryClient.clear(); + vi.unstubAllGlobals(); + }); + + function wrapper({ children }: { children: ReactNode }) { + return ( + + {children} + + ); + } + + it("retains metrics while fetching another page for the same user", async () => { + const { result } = renderHook( + () => useUserMetricsQuery({ userId: "user-1" }), + { wrapper }, + ); + + expect(result.current.data).toBeUndefined(); + await waitFor(() => expect(result.current.isSuccess).toBe(true)); + + act(() => result.current.goForward()); + + expect(result.current.page).toBe(2); + expect(result.current.isFetching).toBe(true); + expect(result.current.isPlaceholderData).toBe(true); + expect(result.current.data).toEqual(metrics); + }); + + it.each([false, true])( + "clears metrics when switching users (page request pending: %s)", + async (changePage) => { + const { result, rerender } = renderHook( + ({ userId }) => useUserMetricsQuery({ userId }), + { initialProps: { userId: "user-1" }, wrapper }, + ); + + await waitFor(() => expect(result.current.isSuccess).toBe(true)); + if (changePage) { + act(() => result.current.goForward()); + expect(result.current.isPlaceholderData).toBe(true); + } + + rerender({ userId: "user-2" }); + + expect(result.current.isPending).toBe(true); + expect(result.current.isPlaceholderData).toBe(false); + expect(result.current.data).toBeUndefined(); + }, + ); +}); diff --git a/js/src/lib/api/queries/user/index.ts b/js/src/lib/api/queries/user/index.ts index ac43bc0d0..a532e0944 100644 --- a/js/src/lib/api/queries/user/index.ts +++ b/js/src/lib/api/queries/user/index.ts @@ -367,7 +367,8 @@ export const useUserMetricsQuery = ({ const query = useQuery({ queryKey, queryFn: () => fetchUserMetrics(apiURL), - placeholderData: keepPreviousData, + placeholderData: (previousData, previousQuery) => + previousQuery?.queryKey[2] === queryKey[2] ? previousData : undefined, }); return { From 73829f64073bfd90b06938cd43203581b755dfd8 Mon Sep 17 00:00:00 2001 From: Stacey Chen Date: Sun, 25 Oct 2026 13:49:09 -0400 Subject: [PATCH 09/14] DINO-839: graphite and coderabbitai comments --- js/package.json | 2 +- js/src/app/user/[userId]/UserProfile.page.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/js/package.json b/js/package.json index d8b294413..388faf632 100644 --- a/js/package.json +++ b/js/package.json @@ -43,7 +43,7 @@ "react-router": "^7.18.0", "react-router-dom": "^7.18.0", "react-shiki": "^0.9.1", - "recharts": "^3.10.1", + "recharts": "^2.15.0", "type-fest": "github:tahminator/type-fest#main", "use-debounce": "^10.0.4", "use-immer": "^0.11.0", diff --git a/js/src/app/user/[userId]/UserProfile.page.tsx b/js/src/app/user/[userId]/UserProfile.page.tsx index a112b2f08..b02b4edd0 100644 --- a/js/src/app/user/[userId]/UserProfile.page.tsx +++ b/js/src/app/user/[userId]/UserProfile.page.tsx @@ -65,7 +65,7 @@ export default function UserProfilePage() { - + Date: Sun, 25 Oct 2026 14:03:21 -0400 Subject: [PATCH 10/14] DINO-839: downgraded recharts --- js/package.json | 2 +- js/pnpm-lock.yaml | 214 +++++++++++++--------------------------------- 2 files changed, 61 insertions(+), 155 deletions(-) diff --git a/js/package.json b/js/package.json index 388faf632..04ce7a18b 100644 --- a/js/package.json +++ b/js/package.json @@ -43,7 +43,7 @@ "react-router": "^7.18.0", "react-router-dom": "^7.18.0", "react-shiki": "^0.9.1", - "recharts": "^2.15.0", + "recharts": "^2.15.4", "type-fest": "github:tahminator/type-fest#main", "use-debounce": "^10.0.4", "use-immer": "^0.11.0", diff --git a/js/pnpm-lock.yaml b/js/pnpm-lock.yaml index 2a335bcf7..0a36d8fb9 100644 --- a/js/pnpm-lock.yaml +++ b/js/pnpm-lock.yaml @@ -33,7 +33,7 @@ importers: dependencies: '@mantine/charts': specifier: ^8.1.2 - version: 8.1.2(@mantine/core@8.1.2(@mantine/hooks@8.1.2(react@18.3.1))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@mantine/hooks@8.1.2(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(recharts@3.10.1(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react-is@17.0.2)(react@18.3.1)(redux@5.0.1)) + version: 8.1.2(@mantine/core@8.1.2(@mantine/hooks@8.1.2(react@18.3.1))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@mantine/hooks@8.1.2(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(recharts@2.15.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)) '@mantine/core': specifier: ^8.1.2 version: 8.1.2(@mantine/hooks@8.1.2(react@18.3.1))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -95,8 +95,8 @@ importers: specifier: ^0.9.1 version: 0.9.1(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) recharts: - specifier: ^3.10.1 - version: 3.10.1(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react-is@17.0.2)(react@18.3.1)(redux@5.0.1) + specifier: ^2.15.4 + version: 2.15.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) type-fest: specifier: github:tahminator/type-fest#main version: https://codeload.github.com/tahminator/type-fest/tar.gz/d0bbbbe83ef6c42d130cbf2fdbfd8a48bbc989bd @@ -787,7 +787,6 @@ packages: engines: {node: ^22.20 || ^24.12 || >=25} cpu: [x64] os: [linux] - libc: [glibc] '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} @@ -839,42 +838,36 @@ packages: engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] - libc: [glibc] '@parcel/watcher-linux-arm-musl@2.5.0': resolution: {integrity: sha512-6uHywSIzz8+vi2lAzFeltnYbdHsDm3iIB57d4g5oaB9vKwjb6N6dRIgZMujw4nm5r6v9/BQH0noq6DzHrqr2pA==} engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] - libc: [musl] '@parcel/watcher-linux-arm64-glibc@2.5.0': resolution: {integrity: sha512-BfNjXwZKxBy4WibDb/LDCriWSKLz+jJRL3cM/DllnHH5QUyoiUNEp3GmL80ZqxeumoADfCCP19+qiYiC8gUBjA==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] - libc: [glibc] '@parcel/watcher-linux-arm64-musl@2.5.0': resolution: {integrity: sha512-S1qARKOphxfiBEkwLUbHjCY9BWPdWnW9j7f7Hb2jPplu8UZ3nes7zpPOW9bkLbHRvWM0WDTsjdOTUgW0xLBN1Q==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] - libc: [musl] '@parcel/watcher-linux-x64-glibc@2.5.0': resolution: {integrity: sha512-d9AOkusyXARkFD66S6zlGXyzx5RvY+chTP9Jp0ypSTC9d4lzyRs9ovGf/80VCxjKddcUvnsGwCHWuF2EoPgWjw==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] - libc: [glibc] '@parcel/watcher-linux-x64-musl@2.5.0': resolution: {integrity: sha512-iqOC+GoTDoFyk/VYSFHwjHhYrk8bljW6zOhPuhi5t9ulqiYq1togGJB5e3PwYVFFfeVgc6pbz3JdQyDoBszVaA==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] - libc: [musl] '@parcel/watcher-win32-arm64@2.5.0': resolution: {integrity: sha512-twtft1d+JRNkM5YbmexfcH/N4znDtjgysFaV9zvZmmJezQsKpkfLYJ+JFV3uygugK6AtIM2oADPkB2AdhBrNig==} @@ -908,17 +901,6 @@ packages: resolution: {integrity: sha512-3arRdUp1fNx55itnjKiUhO6t4Mf91TsrTIYINDNLAZPS0TPd5YpiXRctwjel0qqWoOOhjA34cZ3m4dksLDFUYg==} engines: {node: '>=18.17.0', npm: '>=9.5.0'} - '@reduxjs/toolkit@2.13.0': - resolution: {integrity: sha512-f6jwVRfwW30+tnllz4YcsHHpPYYOE7abcHkyUCovD+P7DyhdAI5prz2Fh/8TLhakjpN+7aIEion1s5L/tvlYlA==} - peerDependencies: - react: ^16.9.0 || ^17.0.0 || ^18 || ^19 - react-redux: ^7.2.1 || ^8.1.3 || ^9.0.0 - peerDependenciesMeta: - react: - optional: true - react-redux: - optional: true - '@rollup/rollup-android-arm-eabi@4.62.4': resolution: {integrity: sha512-RrPokAb7dmbxFoeO3TloqHyOjgye8RkBhSqmp4aJMIex4c9r46ZstPnleDQOq1t46VOVjwIuwNogIqbodV1Vvg==} cpu: [arm] @@ -953,79 +935,66 @@ packages: resolution: {integrity: sha512-81wiiX3v7aqy+T+bT61TJ78yJjRquqFFTTbAPt08imfQQzkPIW8t6aJbkTagtCCrXMNc9D66+geqlK7ydLPNqA==} cpu: [arm] os: [linux] - libc: [glibc] '@rollup/rollup-linux-arm-musleabihf@4.62.4': resolution: {integrity: sha512-9kmDIvNZqdoHOBZgNtpTBeLWYO/LVipM3H/j62P8848/l/VPEQL6N3uxU9pvP1oZAsXyC2MEnFP3ovRjo7WYNQ==} cpu: [arm] os: [linux] - libc: [musl] '@rollup/rollup-linux-arm64-gnu@4.62.4': resolution: {integrity: sha512-CcnXHWnXg69g+DX5VWL3FHts3qMRN2uVEHX+BZvGLdd07/gXkn3ePjYtO1LDJvxkGKVHMclKBRa1QUTH+6toYQ==} cpu: [arm64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-arm64-musl@4.62.4': resolution: {integrity: sha512-iFOibiHnTRuhrWLlRsOQFdZJJIa7S8OwkneJr4ocALP16u5yk6lWLINFwhHaEqBFMsKDUZofLkGos7+CPzGB3g==} cpu: [arm64] os: [linux] - libc: [musl] '@rollup/rollup-linux-loong64-gnu@4.62.4': resolution: {integrity: sha512-XnWYMI7euHlb5a871xPja+Gm7DRCFU+FGRrtS2sMq9N8FvqtpagUy6gD4YOemC5MRk9xbh8+jYMEJbigFQwsgA==} cpu: [loong64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-loong64-musl@4.62.4': resolution: {integrity: sha512-qGDAlO0U8xedCcsdRm9oaoQY8DAx/QT7uIxJWhCdx0ceIWX783UC9QSYkdpzAe29wNiVfp24+bZdQmn49o45SQ==} cpu: [loong64] os: [linux] - libc: [musl] '@rollup/rollup-linux-ppc64-gnu@4.62.4': resolution: {integrity: sha512-ru4H6ezD7ysA5EiEK6qkkaEb4modH8CTej6kUy/gQi20u3kB3G7Zn8snXXkeJSCOFKG/rbPPtM/+9Wgas1961w==} cpu: [ppc64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-ppc64-musl@4.62.4': resolution: {integrity: sha512-2W4MO5WQVJnbJaZdvDb9rhBDuFU1nKIepPFpJUBsTh2k1YY2g+ODViaWuyOAjQ5cOP7NvrvLzt3wvHOoiAvc7w==} cpu: [ppc64] os: [linux] - libc: [musl] '@rollup/rollup-linux-riscv64-gnu@4.62.4': resolution: {integrity: sha512-+fxjfuoAmVMCYV5QyjoIpu0cp5DOiOTeqYFk1AVaxGr+/ravWLX89XfQmptsoWcaVy/TGf2hexzbUOrCQIL1CQ==} cpu: [riscv64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-riscv64-musl@4.62.4': resolution: {integrity: sha512-jTn8JfHGL4djjFxPuM06LmNUJDsst2jeVlsd9OmIH6zc5sC9K6rIuO4YajXatLUpBmBKl6b35ro1QZocLi+tcA==} cpu: [riscv64] os: [linux] - libc: [musl] '@rollup/rollup-linux-s390x-gnu@4.62.4': resolution: {integrity: sha512-oCJCJL4pXsoDcP2QZ+JVlPTIRc6266zsIaeJJsWImmF7HO0W8nb6HuSgZlMWxJwaPf8ehbSw8yo0EUw925hKsA==} cpu: [s390x] os: [linux] - libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.62.4': resolution: {integrity: sha512-W69hukhZ3KKNRCaMIEzKvcFye42hh0FE1+YoYaf5+Ikacuftoco6yO/xouz0hc5d5W/s3yBro5jRiuEE/Q5vUw==} cpu: [x64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-x64-musl@4.62.4': resolution: {integrity: sha512-qiXbGG2jkjXhzXpsFZSR2Xpb8DN/UaxYsbb/STbuR/6fpaDgRmmaq1B/LmtF2wQFOFOSsK2jdE0RZ3a0zHn4QA==} cpu: [x64] os: [linux] - libc: [musl] '@rollup/rollup-openbsd-x64@4.62.4': resolution: {integrity: sha512-nWeM//hxv8mIo6jD7Hu4o48DVmV9pbV6gsKaWU+4NFyqHoPKwrkRiZGLKUhOBk8qNmDmpwFtPKg80Bo/Tn4xiQ==} @@ -1081,9 +1050,6 @@ packages: '@standard-schema/spec@1.1.0': resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} - '@standard-schema/utils@0.3.0': - resolution: {integrity: sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==} - '@tabler/icons-react@3.30.0': resolution: {integrity: sha512-9KZ9D1UNAyjlLkkYp2HBPHdf6lAJ2aelDqh8YYAnnmLF3xwprWKxxW8+zw5jlI0IwdfN4XFFuzqePkaw+DpIOg==} peerDependencies: @@ -1130,28 +1096,24 @@ packages: engines: {node: '>= 20'} cpu: [arm64] os: [linux] - libc: [glibc] '@tailwindcss/oxide-linux-arm64-musl@4.2.1': resolution: {integrity: sha512-WZA0CHRL/SP1TRbA5mp9htsppSEkWuQ4KsSUumYQnyl8ZdT39ntwqmz4IUHGN6p4XdSlYfJwM4rRzZLShHsGAQ==} engines: {node: '>= 20'} cpu: [arm64] os: [linux] - libc: [musl] '@tailwindcss/oxide-linux-x64-gnu@4.2.1': resolution: {integrity: sha512-qMFzxI2YlBOLW5PhblzuSWlWfwLHaneBE0xHzLrBgNtqN6mWfs+qYbhryGSXQjFYB1Dzf5w+LN5qbUTPhW7Y5g==} engines: {node: '>= 20'} cpu: [x64] os: [linux] - libc: [glibc] '@tailwindcss/oxide-linux-x64-musl@4.2.1': resolution: {integrity: sha512-5r1X2FKnCMUPlXTWRYpHdPYUY6a1Ar/t7P24OuiEdEOmms5lyqjDRvVY1yy9Rmioh+AunQ0rWiOTPE8F9A3v5g==} engines: {node: '>= 20'} cpu: [x64] os: [linux] - libc: [musl] '@tailwindcss/oxide-wasm32-wasi@4.2.1': resolution: {integrity: sha512-MGFB5cVPvshR85MTJkEvqDUnuNoysrsRxd6vnk1Lf2tbiqNlXpHYZqkqOQalydienEWOHHFyyuTSYRsLfxFJ2Q==} @@ -1331,9 +1293,6 @@ packages: '@types/unist@3.0.3': resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} - '@types/use-sync-external-store@0.0.6': - resolution: {integrity: sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==} - '@typescript-eslint/eslint-plugin@8.19.0': resolution: {integrity: sha512-NggSaEZCdSrFddbctrVjkVZvFC6KGfKfNK0CU7mNK/iKHGKbzT4Wmgm08dKpcZECBu9f5FypndoMyRHkdqfT1Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -1922,9 +1881,6 @@ packages: resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} engines: {node: '>= 0.4'} - es-toolkit@1.52.0: - resolution: {integrity: sha512-XTNEJQh1tY1ZJVcf6ayP/2n4ZPyaHlW2FWs7xvw5ddPuhUVjLD3olQVQS7kf58JbAB48iL0uL/jerTrjtV3lDA==} - esbuild@0.27.2: resolution: {integrity: sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==} engines: {node: '>=18'} @@ -2013,8 +1969,8 @@ packages: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} - eventemitter3@5.0.4: - resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==} + eventemitter3@4.0.7: + resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} expect-type@1.3.0: resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} @@ -2023,6 +1979,10 @@ packages: fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + fast-equals@5.4.5: + resolution: {integrity: sha512-yKEpcFuRPu/pTQL2VB8YKE2yFldrLlIsoWtJeBnAdqEhvu21OgkFT0C8njf/xYc2LSZGEr3K9JGjthHJxnVLvQ==} + engines: {node: '>=6.0.0'} + fast-glob@3.3.2: resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} engines: {node: '>=8.6.0'} @@ -2275,9 +2235,6 @@ packages: immer@10.1.3: resolution: {integrity: sha512-tmjF/k8QDKydUlm3mZU+tjM6zeq9/fFpPqH9SzWmBnVVKsPBg/V66qsMwb3/Bo90cgUN+ghdVBess+hPsxUyRw==} - immer@11.1.21: - resolution: {integrity: sha512-bZSadeCPGdaY5/qRLgr6mDSH1ra21rD3rqpiWk7+QTr4ayYPTE9qZYdLh/NdPhMe0wSrTvHD/Lk8q5EwhUs0aw==} - immutable@5.1.9: resolution: {integrity: sha512-m8nVez3rwrgmWxtLMt1ZYXB2Lv7OKYn/disyxAlSDYAlKSlFoPPfIAmAM/M5xqL4m4C/wAPw7S2/CNaUii1Hxg==} @@ -2608,56 +2565,48 @@ packages: engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] - libc: [glibc] lightningcss-linux-arm64-gnu@1.33.0: resolution: {integrity: sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] - libc: [glibc] lightningcss-linux-arm64-musl@1.31.1: resolution: {integrity: sha512-mVZ7Pg2zIbe3XlNbZJdjs86YViQFoJSpc41CbVmKBPiGmC4YrfeOyz65ms2qpAobVd7WQsbW4PdsSJEMymyIMg==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] - libc: [musl] lightningcss-linux-arm64-musl@1.33.0: resolution: {integrity: sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] - libc: [musl] lightningcss-linux-x64-gnu@1.31.1: resolution: {integrity: sha512-xGlFWRMl+0KvUhgySdIaReQdB4FNudfUTARn7q0hh/V67PVGCs3ADFjw+6++kG1RNd0zdGRlEKa+T13/tQjPMA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] - libc: [glibc] lightningcss-linux-x64-gnu@1.33.0: resolution: {integrity: sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] - libc: [glibc] lightningcss-linux-x64-musl@1.31.1: resolution: {integrity: sha512-eowF8PrKHw9LpoZii5tdZwnBcYDxRw2rRCyvAXLi34iyeYfqCQNA9rmUM0ce62NlPhCvof1+9ivRaTY6pSKDaA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] - libc: [musl] lightningcss-linux-x64-musl@1.33.0: resolution: {integrity: sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] - libc: [musl] lightningcss-win32-arm64-msvc@1.31.1: resolution: {integrity: sha512-aJReEbSEQzx1uBlQizAOBSjcmr9dCdL3XuC/6HLXAxmtErsj2ICo5yYggg1qOODQMtnjNQv2UHb9NpOuFtYe4w==} @@ -2704,6 +2653,9 @@ packages: lodash.truncate@4.4.2: resolution: {integrity: sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==} + lodash@4.18.1: + resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==} + longest-streak@3.1.0: resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} @@ -3150,24 +3102,15 @@ packages: react-is@17.0.2: resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} + react-is@18.3.1: + resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} + react-number-format@5.4.4: resolution: {integrity: sha512-wOmoNZoOpvMminhifQYiYSTCLUDOiUbBunrMrMjA+dV52sY+vck1S4UhR6PkgnoCquvvMSeJjErXZ4qSaWCliA==} peerDependencies: react: ^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - react-redux@9.3.0: - resolution: {integrity: sha512-KQopgqFo/p/fgmAs5qz6p5RWaNAzq40WAu7fJIXnQpYxFPbJYtsJPWvGeF2rOBaY/kEuV77AVsX8TsQzKm+A/g==} - peerDependencies: - '@types/react': ^18.2.25 || ^19 - react: ^18.0 || ^19 - redux: ^5.0.0 - peerDependenciesMeta: - '@types/react': - optional: true - redux: - optional: true - react-refresh@0.14.2: resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==} engines: {node: '>=0.10.0'} @@ -3222,6 +3165,12 @@ packages: '@types/react-dom': optional: true + react-smooth@4.0.4: + resolution: {integrity: sha512-gnGKTpYwqL0Iii09gHobNolvX4Kiq4PKx6eWBCYYix+8cdw+cGo3do906l1NBPKkSWx1DghC1dlWG9L2uGd61Q==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-style-singleton@2.2.3: resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==} engines: {node: '>=10'} @@ -3256,26 +3205,21 @@ packages: resolution: {integrity: sha512-h80JrZu/MHUZCyHu5ciuoI0+WxsCxzxJTILn6Fs8rxSnFPh+UVHYfeIxK1nVGugMqkfC4vJcBOYbkfkwYK0+gw==} engines: {node: '>= 14.18.0'} - recharts@3.10.1: - resolution: {integrity: sha512-QXFrvt6IVcw7eeZCoyXTwkIJAX3Dv1nyVhMicXJ47GsGDDpcN8z6o644DibE9XjpBTThtsomLKnTV6lc+cVFUA==} - engines: {node: '>=18'} + recharts-scale@0.4.5: + resolution: {integrity: sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==} + + recharts@2.15.4: + resolution: {integrity: sha512-UT/q6fwS3c1dHbXv2uFgYJ9BMFHu3fwnd7AYZaEQhXuYQ4hgsxLvsUXzGdKeZrW5xopzDCvuA2N41WJ88I7zIw==} + engines: {node: '>=14'} + deprecated: 1.x and 2.x branches are no longer active. Bump to Recharts v3 to receive latest features and bugfixes. See https://github.com/recharts/recharts/wiki/3.0-migration-guide peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - react-is: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 redent@3.0.0: resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} engines: {node: '>=8'} - redux-thunk@3.1.0: - resolution: {integrity: sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==} - peerDependencies: - redux: ^5.0.0 - - redux@5.0.1: - resolution: {integrity: sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==} - reflect.getprototypeof@1.0.10: resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} engines: {node: '>= 0.4'} @@ -3301,9 +3245,6 @@ packages: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} - reselect@5.2.0: - resolution: {integrity: sha512-AgZ3UOZm3YndfrJ4OYjgrT7bmCm/1iqkjvEfH/oYjzh6PD2qw4QuT3jjnXIrpdt4MTpMXclMT3lXbmRY+XRakw==} - resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} @@ -3812,11 +3753,6 @@ packages: '@types/react': optional: true - use-sync-external-store@1.7.0: - resolution: {integrity: sha512-6L+EeigHMQhdaIPNIFUKwfWJSwWFQ8gJbJ2DLOs5sDIegTwR9fRxvnM3uciHKjIZhFz+KAv2emhWMRvDmMcY8A==} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} @@ -3830,8 +3766,8 @@ packages: vfile@6.0.3: resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} - victory-vendor@37.3.6: - resolution: {integrity: sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==} + victory-vendor@36.9.2: + resolution: {integrity: sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==} vite-tsconfig-paths@5.1.4: resolution: {integrity: sha512-cYj0LRuLV2c2sMqhqhGpaO3LretdtMn/BVX4cPLanIZuwwrkVl+lK84E/miEXkCHWXuq65rhNN4rXsBcOB3S4w==} @@ -4461,13 +4397,13 @@ snapshots: dependencies: buffer: 6.0.3 - '@mantine/charts@8.1.2(@mantine/core@8.1.2(@mantine/hooks@8.1.2(react@18.3.1))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@mantine/hooks@8.1.2(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(recharts@3.10.1(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react-is@17.0.2)(react@18.3.1)(redux@5.0.1))': + '@mantine/charts@8.1.2(@mantine/core@8.1.2(@mantine/hooks@8.1.2(react@18.3.1))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@mantine/hooks@8.1.2(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(recharts@2.15.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1))': dependencies: '@mantine/core': 8.1.2(@mantine/hooks@8.1.2(react@18.3.1))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@mantine/hooks': 8.1.2(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - recharts: 3.10.1(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react-is@17.0.2)(react@18.3.1)(redux@5.0.1) + recharts: 2.15.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@mantine/core@8.1.2(@mantine/hooks@8.1.2(react@18.3.1))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: @@ -4634,18 +4570,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@reduxjs/toolkit@2.13.0(react-redux@9.3.0(@types/react@18.3.18)(react@18.3.1)(redux@5.0.1))(react@18.3.1)': - dependencies: - '@standard-schema/spec': 1.1.0 - '@standard-schema/utils': 0.3.0 - immer: 11.1.21 - redux: 5.0.1 - redux-thunk: 3.1.0(redux@5.0.1) - reselect: 5.2.0 - optionalDependencies: - react: 18.3.1 - react-redux: 9.3.0(@types/react@18.3.18)(react@18.3.1)(redux@5.0.1) - '@rollup/rollup-android-arm-eabi@4.62.4': optional: true @@ -4756,8 +4680,6 @@ snapshots: '@standard-schema/spec@1.1.0': {} - '@standard-schema/utils@0.3.0': {} - '@tabler/icons-react@3.30.0(react@18.3.1)': dependencies: '@tabler/icons': 3.30.0 @@ -4989,8 +4911,6 @@ snapshots: '@types/unist@3.0.3': {} - '@types/use-sync-external-store@0.0.6': {} - '@typescript-eslint/eslint-plugin@8.19.0(@typescript-eslint/parser@8.19.0(eslint@9.17.0(jiti@2.6.1))(typescript@5.6.3))(eslint@9.17.0(jiti@2.6.1))(typescript@5.6.3)': dependencies: '@eslint-community/regexpp': 4.12.1 @@ -5733,8 +5653,6 @@ snapshots: is-date-object: 1.1.0 is-symbol: 1.1.1 - es-toolkit@1.52.0: {} - esbuild@0.27.2: optionalDependencies: '@esbuild/aix-ppc64': 0.27.2 @@ -5884,12 +5802,14 @@ snapshots: esutils@2.0.3: {} - eventemitter3@5.0.4: {} + eventemitter3@4.0.7: {} expect-type@1.3.0: {} fast-deep-equal@3.1.3: {} + fast-equals@5.4.5: {} + fast-glob@3.3.2: dependencies: '@nodelib/fs.stat': 2.0.5 @@ -6168,8 +6088,6 @@ snapshots: immer@10.1.3: {} - immer@11.1.21: {} - immutable@5.1.9: {} import-fresh@3.3.0: @@ -6542,6 +6460,8 @@ snapshots: lodash.truncate@4.4.2: {} + lodash@4.18.1: {} + longest-streak@3.1.0: {} loose-envify@1.4.0: @@ -7121,20 +7041,13 @@ snapshots: react-is@17.0.2: {} + react-is@18.3.1: {} + react-number-format@5.4.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - react-redux@9.3.0(@types/react@18.3.18)(react@18.3.1)(redux@5.0.1): - dependencies: - '@types/use-sync-external-store': 0.0.6 - react: 18.3.1 - use-sync-external-store: 1.7.0(react@18.3.1) - optionalDependencies: - '@types/react': 18.3.18 - redux: 5.0.1 - react-refresh@0.14.2: {} react-remove-scroll-bar@2.3.8(@types/react@18.3.18)(react@18.3.1): @@ -7185,6 +7098,14 @@ snapshots: transitivePeerDependencies: - supports-color + react-smooth@4.0.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + fast-equals: 5.4.5 + prop-types: 15.8.1 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-transition-group: 4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react-style-singleton@2.2.3(@types/react@18.3.18)(react@18.3.1): dependencies: get-nonce: 1.0.1 @@ -7221,37 +7142,28 @@ snapshots: readdirp@4.1.1: {} - recharts@3.10.1(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react-is@17.0.2)(react@18.3.1)(redux@5.0.1): + recharts-scale@0.4.5: dependencies: - '@reduxjs/toolkit': 2.13.0(react-redux@9.3.0(@types/react@18.3.18)(react@18.3.1)(redux@5.0.1))(react@18.3.1) - clsx: 2.1.1 decimal.js-light: 2.5.1 - es-toolkit: 1.52.0 - eventemitter3: 5.0.4 - immer: 11.1.21 + + recharts@2.15.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + clsx: 2.1.1 + eventemitter3: 4.0.7 + lodash: 4.18.1 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - react-is: 17.0.2 - react-redux: 9.3.0(@types/react@18.3.18)(react@18.3.1)(redux@5.0.1) - reselect: 5.2.0 + react-is: 18.3.1 + react-smooth: 4.0.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + recharts-scale: 0.4.5 tiny-invariant: 1.3.3 - use-sync-external-store: 1.7.0(react@18.3.1) - victory-vendor: 37.3.6 - transitivePeerDependencies: - - '@types/react' - - redux + victory-vendor: 36.9.2 redent@3.0.0: dependencies: indent-string: 4.0.0 strip-indent: 3.0.0 - redux-thunk@3.1.0(redux@5.0.1): - dependencies: - redux: 5.0.1 - - redux@5.0.1: {} - reflect.getprototypeof@1.0.10: dependencies: call-bind: 1.0.8 @@ -7286,8 +7198,6 @@ snapshots: require-from-string@2.0.2: {} - reselect@5.2.0: {} - resolve-from@4.0.0: {} resolve-from@5.0.0: {} @@ -7884,10 +7794,6 @@ snapshots: optionalDependencies: '@types/react': 18.3.18 - use-sync-external-store@1.7.0(react@18.3.1): - dependencies: - react: 18.3.1 - util-deprecate@1.0.2: {} uuid@13.0.1: {} @@ -7902,7 +7808,7 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.3 - victory-vendor@37.3.6: + victory-vendor@36.9.2: dependencies: '@types/d3-array': 3.2.2 '@types/d3-ease': 3.0.2 From b032af7ef1c9f3a0c93100dc89a8b92b00bc79e8 Mon Sep 17 00:00:00 2001 From: Stacey Chen Date: Wed, 28 Oct 2026 10:28:36 -0400 Subject: [PATCH 11/14] DINO-839: changed date range and removed extraneous index.test.ts file --- .../UserProfile/MetricsChart/MetricsChart.tsx | 1 - js/src/lib/api/queries/user/index.test.tsx | 92 ------------------- js/src/lib/api/queries/user/index.ts | 11 +-- 3 files changed, 5 insertions(+), 99 deletions(-) delete mode 100644 js/src/lib/api/queries/user/index.test.tsx diff --git a/js/src/app/user/[userId]/_components/UserProfile/MetricsChart/MetricsChart.tsx b/js/src/app/user/[userId]/_components/UserProfile/MetricsChart/MetricsChart.tsx index e84a088b0..77a7eca24 100644 --- a/js/src/app/user/[userId]/_components/UserProfile/MetricsChart/MetricsChart.tsx +++ b/js/src/app/user/[userId]/_components/UserProfile/MetricsChart/MetricsChart.tsx @@ -62,7 +62,6 @@ export default function MetricsChart({ userId }: { readonly userId: string }) { goBack={goBack} goForward={goForward} goTo={goTo} - data-testId="metrics-paginator" /> ); diff --git a/js/src/lib/api/queries/user/index.test.tsx b/js/src/lib/api/queries/user/index.test.tsx deleted file mode 100644 index 16b2b6dc6..000000000 --- a/js/src/lib/api/queries/user/index.test.tsx +++ /dev/null @@ -1,92 +0,0 @@ -import { useUserMetricsQuery } from "@/lib/api/queries/user"; -import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; -import { act, cleanup, renderHook, waitFor } from "@testing-library/react"; -import { ReactNode } from "react"; -import { MemoryRouter } from "react-router-dom"; - -const metrics = { - success: true, - message: "Metrics loaded!", - payload: { - items: [ - { - id: "metric-1", - userId: "user-1", - points: 500, - createdAt: "2026-10-01T00:00:00.000Z", - }, - ], - pages: 2, - hasNextPage: true, - }, -}; - -describe("useUserMetricsQuery placeholders", () => { - let queryClient: QueryClient; - - beforeEach(() => { - queryClient = new QueryClient({ - defaultOptions: { queries: { retry: false } }, - }); - vi.stubGlobal( - "fetch", - vi - .fn() - .mockResolvedValueOnce(Response.json(metrics)) - .mockImplementation(() => new Promise(() => {})), - ); - }); - - afterEach(() => { - cleanup(); - queryClient.clear(); - vi.unstubAllGlobals(); - }); - - function wrapper({ children }: { children: ReactNode }) { - return ( - - {children} - - ); - } - - it("retains metrics while fetching another page for the same user", async () => { - const { result } = renderHook( - () => useUserMetricsQuery({ userId: "user-1" }), - { wrapper }, - ); - - expect(result.current.data).toBeUndefined(); - await waitFor(() => expect(result.current.isSuccess).toBe(true)); - - act(() => result.current.goForward()); - - expect(result.current.page).toBe(2); - expect(result.current.isFetching).toBe(true); - expect(result.current.isPlaceholderData).toBe(true); - expect(result.current.data).toEqual(metrics); - }); - - it.each([false, true])( - "clears metrics when switching users (page request pending: %s)", - async (changePage) => { - const { result, rerender } = renderHook( - ({ userId }) => useUserMetricsQuery({ userId }), - { initialProps: { userId: "user-1" }, wrapper }, - ); - - await waitFor(() => expect(result.current.isSuccess).toBe(true)); - if (changePage) { - act(() => result.current.goForward()); - expect(result.current.isPlaceholderData).toBe(true); - } - - rerender({ userId: "user-2" }); - - expect(result.current.isPending).toBe(true); - expect(result.current.isPlaceholderData).toBe(false); - expect(result.current.data).toBeUndefined(); - }, - ); -}); diff --git a/js/src/lib/api/queries/user/index.ts b/js/src/lib/api/queries/user/index.ts index a532e0944..762add0dc 100644 --- a/js/src/lib/api/queries/user/index.ts +++ b/js/src/lib/api/queries/user/index.ts @@ -311,7 +311,7 @@ export const useUserMetricsQuery = ({ userId, initialPage = 1, tieToUrl = false, - pageSize = 5, + pageSize = 20, }: { userId: string; initialPage?: number; @@ -339,7 +339,7 @@ export const useUserMetricsQuery = ({ return d.utc(debouncedStartDate).toISOString(); return d(debouncedStartDate).startOf("day").utc().toISOString(); } - return undefined; + return d(0).utc().toISOString(); }, [debouncedStartDate]); const processedEndDate = useMemo(() => { if (debouncedEndDate) { @@ -347,7 +347,7 @@ export const useUserMetricsQuery = ({ return d.utc(debouncedEndDate).toISOString(); return d(debouncedEndDate).endOf("day").utc().toISOString(); } - return undefined; + return d().utc().toISOString(); }, [debouncedEndDate]); const apiURL = ApiURL.create("/api/user/{userId}/metrics", { @@ -356,7 +356,7 @@ export const useUserMetricsQuery = ({ userId, }, queries: { - page, + page: initialPage, pageSize, startDate: processedStartDate, endDate: processedEndDate, @@ -367,8 +367,7 @@ export const useUserMetricsQuery = ({ const query = useQuery({ queryKey, queryFn: () => fetchUserMetrics(apiURL), - placeholderData: (previousData, previousQuery) => - previousQuery?.queryKey[2] === queryKey[2] ? previousData : undefined, + placeholderData: keepPreviousData, }); return { From 1fd216b71bd693f7fbe2c40160495f76bc897c4a Mon Sep 17 00:00:00 2001 From: Stacey Chen Date: Wed, 28 Oct 2026 10:50:43 -0400 Subject: [PATCH 12/14] DINO-839: coderabbit comments --- js/src/lib/api/queries/user/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/src/lib/api/queries/user/index.ts b/js/src/lib/api/queries/user/index.ts index 762add0dc..19a06571f 100644 --- a/js/src/lib/api/queries/user/index.ts +++ b/js/src/lib/api/queries/user/index.ts @@ -347,7 +347,7 @@ export const useUserMetricsQuery = ({ return d.utc(debouncedEndDate).toISOString(); return d(debouncedEndDate).endOf("day").utc().toISOString(); } - return d().utc().toISOString(); + return undefined; }, [debouncedEndDate]); const apiURL = ApiURL.create("/api/user/{userId}/metrics", { @@ -356,7 +356,7 @@ export const useUserMetricsQuery = ({ userId, }, queries: { - page: initialPage, + page, pageSize, startDate: processedStartDate, endDate: processedEndDate, From db4bf9b1c39ede6b9552c521b564e43d1baa77ef Mon Sep 17 00:00:00 2001 From: Stacey Chen Date: Wed, 28 Oct 2026 19:50:59 -0400 Subject: [PATCH 13/14] DINO-839: coderabbit comments --- js/src/lib/api/queries/user/index.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/js/src/lib/api/queries/user/index.ts b/js/src/lib/api/queries/user/index.ts index 19a06571f..44c556964 100644 --- a/js/src/lib/api/queries/user/index.ts +++ b/js/src/lib/api/queries/user/index.ts @@ -367,7 +367,8 @@ export const useUserMetricsQuery = ({ const query = useQuery({ queryKey, queryFn: () => fetchUserMetrics(apiURL), - placeholderData: keepPreviousData, + placeholderData: (previousData, previousQuery) => + previousQuery?.queryKey[2] === queryKey[2] ? previousData : undefined, }); return { @@ -440,7 +441,7 @@ async function fetchUserMetrics({ method, }); - await sleep(650); + await sleep(500); const json = res(await response.json()); From 5fe7c0c5837e2bc867830ebe4569379ffab8a744 Mon Sep 17 00:00:00 2001 From: Stacey Chen Date: Wed, 28 Oct 2026 20:05:52 -0400 Subject: [PATCH 14/14] DINO-839: SonarCloud warning --- js/src/lib/api/queries/user/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/src/lib/api/queries/user/index.ts b/js/src/lib/api/queries/user/index.ts index 44c556964..29023a294 100644 --- a/js/src/lib/api/queries/user/index.ts +++ b/js/src/lib/api/queries/user/index.ts @@ -441,7 +441,7 @@ async function fetchUserMetrics({ method, }); - await sleep(500); + await sleep(600); const json = res(await response.json());