From 65b324b72e183ae62f2b7bd9e94e5d1ed362f5d7 Mon Sep 17 00:00:00 2001 From: Charro Gruver Date: Mon, 9 Oct 2023 19:48:10 +0000 Subject: [PATCH 01/42] keycloak init --- app/api/auth/[...nextauth]/options.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/api/auth/[...nextauth]/options.js b/app/api/auth/[...nextauth]/options.js index 6c4c61b..263bd5b 100644 --- a/app/api/auth/[...nextauth]/options.js +++ b/app/api/auth/[...nextauth]/options.js @@ -1,9 +1,15 @@ import NextAuth from "next-auth" import GithubProvider from "next-auth/providers/github" import GoogleProvider from "next-auth/providers/google" +import KeycloakProvider from "next-auth/providers/keycloak"; export const options = { providers: [ + KeycloakProvider({ + clientId: process.env.KEYCLOAK_ID, + clientSecret: process.env.KEYCLOAK_SECRET, + issuer: process.env.KEYCLOAK_ISSUER, + }), GithubProvider({ clientId: process.env.GITHUB_ID, clientSecret: process.env.GITHUB_SECRET, From 9a1ee615918bd6e836070190cd8e9339e633ef23 Mon Sep 17 00:00:00 2001 From: Charro Gruver Date: Fri, 13 Oct 2023 14:14:42 +0000 Subject: [PATCH 02/42] update with KeyCloak --- app/page.js | 2 + components/button-signin-keycloak.jsx | 31 + dto/application.ts | 3 + dto/deployment.ts | 6 + package-lock.json | 3142 +++++++++++++++++++++---- package.json | 5 + public/logos/keycloak_icon_512px.svg | 259 ++ tsconfig.json | 35 + 8 files changed, 3006 insertions(+), 477 deletions(-) create mode 100644 components/button-signin-keycloak.jsx create mode 100644 dto/application.ts create mode 100644 dto/deployment.ts create mode 100644 public/logos/keycloak_icon_512px.svg create mode 100644 tsconfig.json diff --git a/app/page.js b/app/page.js index c5093f2..4f2df56 100644 --- a/app/page.js +++ b/app/page.js @@ -1,6 +1,7 @@ import Link from 'next/link' import ButtonSignInGithub from '@/components/button-signin-github' import ButtonSignInGoogle from '@/components/button-signin-google' +import ButtonSignInKeyCloak from '@/components/button-signin-keycloak' import SignInPhoto from '@/public/images/image--sign-in.jpg' import BlurImage from '@/components/blur-image' import { AppLogo } from '@/components/app-logo' @@ -68,6 +69,7 @@ export default async function AuthenticationPage() { +

By signing in, you agree to our terms of service and privacy policy.

)} diff --git a/components/button-signin-keycloak.jsx b/components/button-signin-keycloak.jsx new file mode 100644 index 0000000..a457ebe --- /dev/null +++ b/components/button-signin-keycloak.jsx @@ -0,0 +1,31 @@ +'use client' + +import { useState } from 'react' +import { signIn } from 'next-auth/react' +import { Button } from '@/components/ui/button' +import LogoKeyCloak from '@/public/logos/keycloak_icon_512px.svg' +import { Loader2 } from "lucide-react" + +export default function ButtonSignInKeyCloak() { + + const [loading, setLoading] = useState(false) + + function handleClick() { + setLoading(loading => !loading) + signIn('keycloak', { callbackUrl: '/dashboard' }) + } + + return ( + + ) +} diff --git a/dto/application.ts b/dto/application.ts new file mode 100644 index 0000000..af90766 --- /dev/null +++ b/dto/application.ts @@ -0,0 +1,3 @@ +interface Application { + app: string; +} diff --git a/dto/deployment.ts b/dto/deployment.ts new file mode 100644 index 0000000..8f7d42c --- /dev/null +++ b/dto/deployment.ts @@ -0,0 +1,6 @@ +interface Deployment { + name: string; + commit: string; + date: string; + status: string; +} diff --git a/package-lock.json b/package-lock.json index 6504043..bd77732 100644 --- a/package-lock.json +++ b/package-lock.json @@ -41,12 +41,16 @@ "react": "^18.2.0", "react-day-picker": "^8.8.0", "react-dom": "^18.2.0", - "react-gauge-chart": "^0.4.1", "recharts": "^2.7.2", "sass": "^1.64.1", "tailwind-merge": "^1.14.0", "tailwindcss": "3.3.3", "tailwindcss-animate": "^1.0.6" + }, + "devDependencies": { + "@types/node": "20.8.5", + "@types/react": "18.2.28", + "live-server": "^1.2.2" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -3487,6 +3491,38 @@ "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==" }, + "node_modules/@types/node": { + "version": "20.8.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.5.tgz", + "integrity": "sha512-SPlobFgbidfIeOYlzXiEjSYeIJiOCthv+9tSQVpvk4PAdIIc+2SmjNVzWXk9t0Y7dl73Zdf+OgXKHX9XtkqUpw==", + "dev": true, + "dependencies": { + "undici-types": "~5.25.1" + } + }, + "node_modules/@types/prop-types": { + "version": "15.7.8", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.8.tgz", + "integrity": "sha512-kMpQpfZKSCBqltAJwskgePRaYRFukDkm1oItcAbC3gNELR20XIBcN9VRgg4+m8DKsTfkWeA4m4Imp4DDuWy7FQ==", + "devOptional": true + }, + "node_modules/@types/react": { + "version": "18.2.28", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.28.tgz", + "integrity": "sha512-ad4aa/RaaJS3hyGz0BGegdnSRXQBkd1CCYDCdNjBPg90UUpLgo+WlJqb9fMYUxtehmzF3PJaTWqRZjko6BRzBg==", + "devOptional": true, + "dependencies": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/scheduler": { + "version": "0.16.4", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.4.tgz", + "integrity": "sha512-2L9ifAGl7wmXwP4v3pN4p2FLhD0O1qsJpvKmNin5VA8+UvNVb447UDaAEV6UdrkA+m/Xs58U1RFps44x6TFsVQ==", + "devOptional": true + }, "node_modules/@typescript-eslint/parser": { "version": "5.62.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", @@ -3613,6 +3649,19 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/acorn": { "version": "8.10.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", @@ -3683,6 +3732,27 @@ "node": ">= 8" } }, + "node_modules/apache-crypt": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/apache-crypt/-/apache-crypt-1.2.6.tgz", + "integrity": "sha512-072WetlM4blL8PREJVeY+WHiUh1R5VNt2HfceGS8aKqttPHcmqE5pkKuXPz/ULmJOFkc8Hw3kfKl6vy7Qka6DA==", + "dev": true, + "dependencies": { + "unix-crypt-td-js": "^1.1.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/apache-md5": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/apache-md5/-/apache-md5-1.1.8.tgz", + "integrity": "sha512-FCAJojipPn0bXjuEpjOOOMN8FZDkxfWWp4JGN9mifU2IhxvKyXZYqpzPHdnTSUpmPDy+tsslB6Z1g+Vg6nVbYA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/arg": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", @@ -3712,6 +3782,33 @@ "dequal": "^2.0.3" } }, + "node_modules/arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/array-buffer-byte-length": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", @@ -3750,6 +3847,15 @@ "node": ">=8" } }, + "node_modules/array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/array.prototype.findlastindex": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.2.tgz", @@ -3833,11 +3939,44 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/ast-types-flow": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz", "integrity": "sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag==" }, + "node_modules/async-each": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.6.tgz", + "integrity": "sha512-c646jH1avxr+aVpndVMeAfYw7wAa6idufrlN3LPA4PmKS0QEGp6PIC9nwz0WQkkvBGAMEki3pFdtxaF39J9vvg==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ] + }, + "node_modules/atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "dev": true, + "bin": { + "atob": "bin/atob.js" + }, + "engines": { + "node": ">= 4.5.0" + } + }, "node_modules/autoprefixer": { "version": "10.4.14", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.14.tgz", @@ -3938,6 +4077,107 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, + "node_modules/base": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "dev": true, + "dependencies": { + "cache-base": "^1.0.1", + "class-utils": "^0.3.5", + "component-emitter": "^1.2.1", + "define-property": "^1.0.0", + "isobject": "^3.0.1", + "mixin-deep": "^1.2.0", + "pascalcase": "^0.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/base/node_modules/define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", + "dev": true, + "dependencies": { + "is-descriptor": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/base/node_modules/is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/base/node_modules/is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/base/node_modules/is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/base/node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "dev": true, + "dependencies": { + "safe-buffer": "5.1.2" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/batch": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", + "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==", + "dev": true + }, + "node_modules/bcryptjs": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz", + "integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==", + "dev": true + }, "node_modules/big-integer": { "version": "1.6.51", "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz", @@ -3954,6 +4194,16 @@ "node": ">=8" } }, + "node_modules/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "dev": true, + "optional": true, + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, "node_modules/boolbase": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", @@ -4046,6 +4296,26 @@ "node": ">=10.16.0" } }, + "node_modules/cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "dev": true, + "dependencies": { + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/call-bind": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", @@ -4154,6 +4424,21 @@ "node": ">= 6" } }, + "node_modules/class-utils": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", + "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", + "dev": true, + "dependencies": { + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/class-variance-authority": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.0.tgz", @@ -4429,6 +4714,19 @@ } } }, + "node_modules/collection-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", + "integrity": "sha512-lNkKvzEeMBBjUGHZ+q6z9pSJla0KWAQPvtzhEV9+iGyQYG+pBpl7xKDhxoNSOZH2hhv0v5k0y2yAM4o4SjoSkw==", + "dev": true, + "dependencies": { + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -4442,6 +4740,15 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" }, + "node_modules/colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", + "dev": true, + "engines": { + "node": ">=0.1.90" + } + }, "node_modules/command-score": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/command-score/-/command-score-0.1.2.tgz", @@ -4455,11 +4762,47 @@ "node": ">= 10" } }, + "node_modules/component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", + "dev": true + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, + "node_modules/connect": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", + "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "finalhandler": "1.1.2", + "parseurl": "~1.3.3", + "utils-merge": "1.0.1" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/connect/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/connect/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, "node_modules/convert-source-map": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", @@ -4473,6 +4816,15 @@ "node": ">= 0.6" } }, + "node_modules/copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/core-js-compat": { "version": "3.32.0", "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.32.0.tgz", @@ -4485,6 +4837,25 @@ "url": "https://opencollective.com/core-js" } }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dev": true, + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/cosmiconfig": { "version": "8.2.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.2.0.tgz", @@ -4599,45 +4970,11 @@ "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz", "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==" }, - "node_modules/d3": { - "version": "7.8.5", - "resolved": "https://registry.npmjs.org/d3/-/d3-7.8.5.tgz", - "integrity": "sha512-JgoahDG51ncUfJu6wX/1vWQEqOflgXyl4MaHqlcSruTez7yhaRKR9i8VjjcQGeS2en/jnFivXuaIMnseMMt0XA==", - "dependencies": { - "d3-array": "3", - "d3-axis": "3", - "d3-brush": "3", - "d3-chord": "3", - "d3-color": "3", - "d3-contour": "4", - "d3-delaunay": "6", - "d3-dispatch": "3", - "d3-drag": "3", - "d3-dsv": "3", - "d3-ease": "3", - "d3-fetch": "3", - "d3-force": "3", - "d3-format": "3", - "d3-geo": "3", - "d3-hierarchy": "3", - "d3-interpolate": "3", - "d3-path": "3", - "d3-polygon": "3", - "d3-quadtree": "3", - "d3-random": "3", - "d3-scale": "4", - "d3-scale-chromatic": "3", - "d3-selection": "3", - "d3-shape": "3", - "d3-time": "3", - "d3-time-format": "4", - "d3-timer": "3", - "d3-transition": "3", - "d3-zoom": "3" - }, - "engines": { - "node": ">=12" - } + "node_modules/csstype": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", + "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==", + "devOptional": true }, "node_modules/d3-array": { "version": "3.2.4", @@ -4650,344 +4987,124 @@ "node": ">=12" } }, - "node_modules/d3-axis": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", - "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==", + "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-brush": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", - "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", - "dependencies": { - "d3-dispatch": "1 - 3", - "d3-drag": "2 - 3", - "d3-interpolate": "1 - 3", - "d3-selection": "3", - "d3-transition": "3" - }, + "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-chord": { + "node_modules/d3-interpolate": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", - "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", "dependencies": { - "d3-path": "1 - 3" + "d3-color": "1 - 3" }, "engines": { "node": ">=12" } }, - "node_modules/d3-color": { + "node_modules/d3-path": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", - "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "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-contour": { + "node_modules/d3-scale": { "version": "4.0.2", - "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz", - "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", "dependencies": { - "d3-array": "^3.2.0" + "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-delaunay": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz", - "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", + "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": { - "delaunator": "5" + "d3-path": "^3.1.0" }, "engines": { "node": ">=12" } }, - "node_modules/d3-dispatch": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", - "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-drag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", - "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "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-dispatch": "1 - 3", - "d3-selection": "3" + "d3-array": "2 - 3" }, "engines": { "node": ">=12" } }, - "node_modules/d3-dsv": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", - "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", + "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": { - "commander": "7", - "iconv-lite": "0.6", - "rw": "1" - }, - "bin": { - "csv2json": "bin/dsv2json.js", - "csv2tsv": "bin/dsv2dsv.js", - "dsv2dsv": "bin/dsv2dsv.js", - "dsv2json": "bin/dsv2json.js", - "json2csv": "bin/json2dsv.js", - "json2dsv": "bin/json2dsv.js", - "json2tsv": "bin/json2dsv.js", - "tsv2csv": "bin/dsv2dsv.js", - "tsv2json": "bin/dsv2json.js" + "d3-time": "1 - 3" }, "engines": { "node": ">=12" } }, - "node_modules/d3-ease": { + "node_modules/d3-timer": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", - "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", "engines": { "node": ">=12" } }, - "node_modules/d3-fetch": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", - "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", - "dependencies": { - "d3-dsv": "1 - 3" - }, + "node_modules/daemon": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/daemon/-/daemon-1.1.0.tgz", + "integrity": "sha512-1vX9YVcP21gt12nSD3SQRC/uPU7fyA6M8qyClTBIFuiRWoylFn57PwXhjBAqRl085bZAje7sILhZU48qcS9SWw==", "engines": { - "node": ">=12" + "node": ">= 0.8.0" } }, - "node_modules/d3-force": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", - "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", + "node_modules/damerau-levenshtein": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", + "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==" + }, + "node_modules/date-fns": { + "version": "2.30.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", + "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", "dependencies": { - "d3-dispatch": "1 - 3", - "d3-quadtree": "1 - 3", - "d3-timer": "1 - 3" - }, - "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-geo": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.0.tgz", - "integrity": "sha512-JEo5HxXDdDYXCaWdwLRt79y7giK8SbhZJbFWXqbRTolCHFI5jRqteLzCsq51NKbUoX0PjBVSohxrx+NoOUujYA==", - "dependencies": { - "d3-array": "2.5.0 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-hierarchy": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", - "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", - "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-polygon": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz", - "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-quadtree": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", - "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-random": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", - "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", - "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-scale-chromatic": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.0.0.tgz", - "integrity": "sha512-Lx9thtxAKrO2Pq6OO2Ua474opeziKr279P/TKZsMAhYyNDD3EnCffdbgeSYN5O7m2ByQsxtuP2CSDczNUIZ22g==", - "dependencies": { - "d3-color": "1 - 3", - "d3-interpolate": "1 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-selection": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", - "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", - "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/d3-transition": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", - "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", - "dependencies": { - "d3-color": "1 - 3", - "d3-dispatch": "1 - 3", - "d3-ease": "1 - 3", - "d3-interpolate": "1 - 3", - "d3-timer": "1 - 3" - }, - "engines": { - "node": ">=12" - }, - "peerDependencies": { - "d3-selection": "2 - 3" - } - }, - "node_modules/d3-zoom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", - "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", - "dependencies": { - "d3-dispatch": "1 - 3", - "d3-drag": "2 - 3", - "d3-interpolate": "1 - 3", - "d3-selection": "2 - 3", - "d3-transition": "2 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/daemon": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/daemon/-/daemon-1.1.0.tgz", - "integrity": "sha512-1vX9YVcP21gt12nSD3SQRC/uPU7fyA6M8qyClTBIFuiRWoylFn57PwXhjBAqRl085bZAje7sILhZU48qcS9SWw==", - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/damerau-levenshtein": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", - "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==" - }, - "node_modules/date-fns": { - "version": "2.30.0", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", - "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", - "dependencies": { - "@babel/runtime": "^7.21.0" + "@babel/runtime": "^7.21.0" }, "engines": { "node": ">=0.11" @@ -5018,6 +5135,15 @@ "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz", "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==" }, + "node_modules/decode-uri-component": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", + "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==", + "dev": true, + "engines": { + "node": ">=0.10" + } + }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -5089,12 +5215,25 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/delaunator": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.0.tgz", - "integrity": "sha512-AyLvtyJdbv/U1GkiS6gUUzclRoAY4Gs75qkMygJJhU75LW4DNuSF2RMzpxs9jw9Oz1BobHjTdkG3zdP55VxAqw==", + "node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", + "dev": true, "dependencies": { - "robust-predicates": "^3.0.0" + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true, + "engines": { + "node": ">= 0.8" } }, "node_modules/dequal": { @@ -5105,6 +5244,16 @@ "node": ">=6" } }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "dev": true, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, "node_modules/detect-node-es": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", @@ -5210,6 +5359,18 @@ "tslib": "^2.0.3" } }, + "node_modules/duplexer": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", + "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", + "dev": true + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "dev": true + }, "node_modules/electron-to-chromium": { "version": "1.4.484", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.484.tgz", @@ -5220,6 +5381,15 @@ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/enhanced-resolve": { "version": "5.15.0", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz", @@ -5348,6 +5518,12 @@ "node": ">=6" } }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "dev": true + }, "node_modules/escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", @@ -5860,6 +6036,30 @@ "node": ">=0.10.0" } }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/event-stream": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", + "integrity": "sha512-QHpkERcGsR0T7Qm3HNJSyXKEEj8AHNxkY3PK8TS2KJvQ7NiSHe3DDpwVKKtoYprL/AreyzFBeIkBIWChAqn60g==", + "dev": true, + "dependencies": { + "duplexer": "~0.1.1", + "from": "~0", + "map-stream": "~0.1.0", + "pause-stream": "0.0.11", + "split": "0.3", + "stream-combiner": "~0.0.4", + "through": "~2.3.1" + } + }, "node_modules/eventemitter3": { "version": "4.0.7", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", @@ -5887,6 +6087,129 @@ "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, + "node_modules/expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha512-w/ozOKR9Obk3qoWeY/WDi6MFta9AoMR+zud60mdnbniMcBxRuFJyDt2LdX/14A1UABeqk+Uk+LDfUpvoGKppZA==", + "dev": true, + "dependencies": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/expand-brackets/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "dev": true, + "dependencies": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extglob/node_modules/define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", + "dev": true, + "dependencies": { + "is-descriptor": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extglob/node_modules/is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extglob/node_modules/is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extglob/node_modules/is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extglob/node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -5944,10 +6267,22 @@ "reusify": "^1.0.4" } }, - "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "node_modules/faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "dev": true, + "dependencies": { + "websocket-driver": ">=0.5.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", "dependencies": { "flat-cache": "^3.0.4" }, @@ -5955,6 +6290,13 @@ "node": "^10.12.0 || >=12.0.0" } }, + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "dev": true, + "optional": true + }, "node_modules/fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -5966,6 +6308,39 @@ "node": ">=8" } }, + "node_modules/finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -6006,6 +6381,15 @@ "is-callable": "^1.1.3" } }, + "node_modules/for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/fraction.js": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz", @@ -6018,6 +6402,18 @@ "url": "https://www.patreon.com/infusion" } }, + "node_modules/fragment-cache": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", + "integrity": "sha512-GMBAbW9antB8iZRHLoGw0b3HANt57diZYFO/HL1JGIC1MjKrdmhxvrJbupnVvpys0zsz7yBApXdQyfepKly2kA==", + "dev": true, + "dependencies": { + "map-cache": "^0.2.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/framer-motion": { "version": "10.15.0", "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-10.15.0.tgz", @@ -6041,6 +6437,21 @@ } } }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/from": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", + "integrity": "sha512-twe20eF1OxVxp/ML/kq2p1uc6KvFK/+vs8WjEbeKmV2He22MKm7YF2ANIt+EOqhJ5L3K/SuuPhk0hWQDjOM23g==", + "dev": true + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -6156,6 +6567,15 @@ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, + "node_modules/get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/glob": { "version": "7.1.7", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", @@ -6327,25 +6747,121 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/human-signals": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-4.3.1.tgz", - "integrity": "sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==", + "node_modules/has-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", + "integrity": "sha512-IBXk4GTsLYdQ7Rvt+GRBrFSVEkmuOUy4re0Xjd9kJSUQpnTrWR4/y9RpfexN9vkAPMFuQoeWKwqzPozRTlasGw==", + "dev": true, + "dependencies": { + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" + }, "engines": { - "node": ">=14.18.0" + "node": ">=0.10.0" } }, - "node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "node_modules/has-values": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", + "integrity": "sha512-ODYZC64uqzmtfGMEAX/FvZiRyWLpAC3vYnNunURUnkGVTS+mI0smVsWaPydRBsE3g+ok7h960jChO8mFcWlHaQ==", + "dev": true, + "dependencies": { + "is-number": "^3.0.0", + "kind-of": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-values/node_modules/is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-values/node_modules/is-number/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" + "is-buffer": "^1.1.5" }, "engines": { "node": ">=0.10.0" } }, + "node_modules/http-auth": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/http-auth/-/http-auth-3.1.3.tgz", + "integrity": "sha512-Jbx0+ejo2IOx+cRUYAGS1z6RGc6JfYUNkysZM4u4Sfk1uLlGv814F7/PIjQQAuThLdAWxb74JMGd5J8zex1VQg==", + "dev": true, + "dependencies": { + "apache-crypt": "^1.1.2", + "apache-md5": "^1.0.6", + "bcryptjs": "^2.3.0", + "uuid": "^3.0.0" + }, + "engines": { + "node": ">=4.6.1" + } + }, + "node_modules/http-auth/node_modules/uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "dev": true, + "bin": { + "uuid": "bin/uuid" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dev": true, + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-errors/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-parser-js": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", + "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==", + "dev": true + }, + "node_modules/human-signals": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-4.3.1.tgz", + "integrity": "sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==", + "engines": { + "node": ">=14.18.0" + } + }, "node_modules/ignore": { "version": "5.2.4", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", @@ -6436,6 +6952,30 @@ "loose-envify": "^1.0.0" } }, + "node_modules/is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-accessor-descriptor/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-array-buffer": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", @@ -6491,6 +7031,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, "node_modules/is-callable": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", @@ -6513,6 +7059,30 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-data-descriptor/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-date-object": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", @@ -6527,6 +7097,29 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-descriptor/node_modules/kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-docker": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", @@ -6541,6 +7134,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -6618,6 +7220,18 @@ "node": ">=8" } }, + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-regex": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", @@ -6708,6 +7322,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-wsl": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", @@ -6743,6 +7366,15 @@ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/jiti": { "version": "1.19.1", "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.19.1.tgz", @@ -6826,6 +7458,18 @@ "node": ">=4.0" } }, + "node_modules/kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha512-24XsCxmEbRwEDbz/qz3stgin8TTzZ1ESR56OMCN0ujYg+vRutNSiOj9bHH9u85DKgXguraugV5sFuvbD4FW/hw==", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/language-subtag-registry": { "version": "0.3.22", "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.22.tgz", @@ -6864,71 +7508,455 @@ "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dependencies": { - "p-locate": "^5.0.0" + "node_modules/live-server": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/live-server/-/live-server-1.2.2.tgz", + "integrity": "sha512-t28HXLjITRGoMSrCOv4eZ88viHaBVIjKjdI5PO92Vxlu+twbk6aE0t7dVIaz6ZWkjPilYFV6OSdMYl9ybN2B4w==", + "dev": true, + "dependencies": { + "chokidar": "^2.0.4", + "colors": "1.4.0", + "connect": "^3.6.6", + "cors": "latest", + "event-stream": "3.3.4", + "faye-websocket": "0.11.x", + "http-auth": "3.1.x", + "morgan": "^1.9.1", + "object-assign": "latest", + "opn": "latest", + "proxy-middleware": "latest", + "send": "latest", + "serve-index": "^1.9.1" }, - "engines": { - "node": ">=10" + "bin": { + "live-server": "live-server.js" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">=0.10.0" } }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + "node_modules/live-server/node_modules/anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "dev": true, + "dependencies": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + } }, - "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==" + "node_modules/live-server/node_modules/anymatch/node_modules/normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==", + "dev": true, + "dependencies": { + "remove-trailing-separator": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" + "node_modules/live-server/node_modules/binary-extensions": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", + "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } }, - "node_modules/loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" + "node_modules/live-server/node_modules/braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "dependencies": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" }, - "bin": { - "loose-envify": "cli.js" + "engines": { + "node": ">=0.10.0" } }, - "node_modules/lower-case": { + "node_modules/live-server/node_modules/chokidar": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", + "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", + "deprecated": "Chokidar 2 does not receive security updates since 2019. Upgrade to chokidar 3 with 15x fewer dependencies", + "dev": true, + "dependencies": { + "anymatch": "^2.0.0", + "async-each": "^1.0.1", + "braces": "^2.3.2", + "glob-parent": "^3.1.0", + "inherits": "^2.0.3", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "normalize-path": "^3.0.0", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.2.1", + "upath": "^1.1.1" + }, + "optionalDependencies": { + "fsevents": "^1.2.7" + } + }, + "node_modules/live-server/node_modules/define-property": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", - "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "dev": true, "dependencies": { - "tslib": "^2.0.3" + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" } }, - "node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "node_modules/live-server/node_modules/fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ==", + "dev": true, "dependencies": { - "yallist": "^3.0.2" + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "engines": { + "node": ">=0.10.0" } }, - "node_modules/lucide-react": { - "version": "0.263.1", - "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.263.1.tgz", - "integrity": "sha512-keqxAx97PlaEN89PXZ6ki1N8nRjGWtDa4021GFYLNj0RgruM5odbpl8GHTExj0hhPq3sF6Up0gnxt6TSHu+ovw==", - "peerDependencies": { - "react": "^16.5.1 || ^17.0.0 || ^18.0.0" + "node_modules/live-server/node_modules/fsevents": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", + "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", + "deprecated": "The v1 package contains DANGEROUS / INSECURE binaries. Upgrade to safe fsevents v2", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "dependencies": { + "bindings": "^1.5.0", + "nan": "^2.12.1" + }, + "engines": { + "node": ">= 4.0" } }, - "node_modules/mdn-data": { + "node_modules/live-server/node_modules/glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha512-E8Ak/2+dZY6fnzlR7+ueWvhsH1SjHr4jjss4YS/h4py44jY9MhK/VFdaZJAWDz6BbL21KeteKxFSFpq8OS5gVA==", + "dev": true, + "dependencies": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + } + }, + "node_modules/live-server/node_modules/glob-parent/node_modules/is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha512-UFpDDrPgM6qpnFNI+rh/p3bUaq9hKLZN8bMUWzxmcnZVS3omf4IPK+BrewlnWjO1WmUsMYuSjKh4UJuV4+Lqmw==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/live-server/node_modules/is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/live-server/node_modules/is-accessor-descriptor/node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/live-server/node_modules/is-binary-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", + "integrity": "sha512-9fRVlXc0uCxEDj1nQzaWONSpbTfx0FmJfzHF7pwlI8DkWGoHBBea4Pg5Ky0ojwwxQmnSifgbKkI06Qv0Ljgj+Q==", + "dev": true, + "dependencies": { + "binary-extensions": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/live-server/node_modules/is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/live-server/node_modules/is-data-descriptor/node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/live-server/node_modules/is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/live-server/node_modules/is-descriptor/node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/live-server/node_modules/is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "dependencies": { + "is-plain-object": "^2.0.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/live-server/node_modules/is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/live-server/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/live-server/node_modules/micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "dependencies": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/live-server/node_modules/micromatch/node_modules/extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", + "dev": true, + "dependencies": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/live-server/node_modules/micromatch/node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/live-server/node_modules/readdirp": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", + "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.11", + "micromatch": "^3.1.10", + "readable-stream": "^2.0.2" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/live-server/node_modules/to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg==", + "dev": true, + "dependencies": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "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==" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/lucide-react": { + "version": "0.263.1", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.263.1.tgz", + "integrity": "sha512-keqxAx97PlaEN89PXZ6ki1N8nRjGWtDa4021GFYLNj0RgruM5odbpl8GHTExj0hhPq3sF6Up0gnxt6TSHu+ovw==", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/map-stream": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz", + "integrity": "sha512-CkYQrPYZfWnu/DAmVCpTSX/xHpKZ80eKh2lAkyA6AJTef6bW+6JpbQZN5rofum7da+SyN1bi5ctTm+lTfcCW3g==", + "dev": true + }, + "node_modules/map-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", + "integrity": "sha512-4y7uGv8bd2WdM9vpQsiQNo41Ln1NvhvDRuVt0k2JZQ+ezN2uaQes7lZeZ+QQUHOLQAtDaBJ+7wCbi+ab/KFs+w==", + "dev": true, + "dependencies": { + "object-visit": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/mdn-data": { "version": "2.0.30", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==" @@ -6958,6 +7986,39 @@ "node": ">=8.6" } }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/mimic-fn": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", @@ -6988,6 +8049,62 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/mixin-deep": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", + "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", + "dev": true, + "dependencies": { + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/mixin-deep/node_modules/is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "dependencies": { + "is-plain-object": "^2.0.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/morgan": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", + "integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==", + "dev": true, + "dependencies": { + "basic-auth": "~2.0.1", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-finished": "~2.3.0", + "on-headers": "~1.0.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/morgan/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/morgan/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -7003,6 +8120,13 @@ "thenify-all": "^1.0.0" } }, + "node_modules/nan": { + "version": "2.18.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.18.0.tgz", + "integrity": "sha512-W7tfG7vMOGtD30sHoZSSc/JVYiyDPEyQVso/Zz+/uQd0B0L46gtC+pHha5FFMRpil6fm/AoEcRWyOVi4+E/f8w==", + "dev": true, + "optional": true + }, "node_modules/nanoid": { "version": "3.3.6", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", @@ -7020,11 +8144,127 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/nanomatch": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", + "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", + "dev": true, + "dependencies": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "fragment-cache": "^0.2.1", + "is-windows": "^1.0.2", + "kind-of": "^6.0.2", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nanomatch/node_modules/define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "dev": true, + "dependencies": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nanomatch/node_modules/extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", + "dev": true, + "dependencies": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nanomatch/node_modules/is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nanomatch/node_modules/is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nanomatch/node_modules/is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nanomatch/node_modules/is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "dependencies": { + "is-plain-object": "^2.0.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nanomatch/node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==" }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/next": { "version": "13.4.12", "resolved": "https://registry.npmjs.org/next/-/next-13.4.12.tgz", @@ -7214,6 +8454,32 @@ "node": ">=0.10.0" } }, + "node_modules/object-copy": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", + "integrity": "sha512-79LYn6VAb63zgtmAteVOWo9Vdj71ZVBy3Pbse+VqxDpEP83XuujMrGqHIwAXJ5I/aM0zU7dIyIAhifVTPrNItQ==", + "dev": true, + "dependencies": { + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/object-hash": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz", @@ -7238,6 +8504,18 @@ "node": ">= 0.4" } }, + "node_modules/object-visit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", + "integrity": "sha512-GBaMwwAVK9qbQN3Scdo0OyvgPW7l3lnaVMj84uTOZlswkX0KpF6fyDBJhtTthf7pymztoN36/KEr1DyhF96zEA==", + "dev": true, + "dependencies": { + "isobject": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/object.assign": { "version": "4.1.4", "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", @@ -7307,6 +8585,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ==", + "dev": true, + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/object.values": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.6.tgz", @@ -7331,6 +8621,27 @@ "node": "^10.13.0 || >=12.0.0" } }, + "node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "dev": true, + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -7400,6 +8711,28 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, + "node_modules/opn": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/opn/-/opn-6.0.0.tgz", + "integrity": "sha512-I9PKfIZC+e4RXZ/qr1RhgyCnGgYX0UEIlXgWnCOVACIvFgaC9rz6Won7xbdhoHrd8IIhV7YEpHjreNUNkqCGkQ==", + "deprecated": "The package has been renamed to `open`", + "dev": true, + "dependencies": { + "is-wsl": "^1.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/opn/node_modules/is-wsl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", + "integrity": "sha512-gfygJYZ2gLTDlmbWMI0CE2MwnFzSN/2SZfkMlItC4K/JBlsWVDB0bO6XhqcY13YXE7iMcAJnzTCJjPiTeJJ0Mw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/optionator": { "version": "0.9.3", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", @@ -7472,6 +8805,30 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/pascalcase": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", + "integrity": "sha512-XHXfu/yOQRy9vYOtUDVMN60OEJjW013GoObG1o+xwQTpB9eYJX/BjXMsdW13ZDPruFhYYn0AG22w0xgQMwl3Nw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-dirname": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", + "integrity": "sha512-ALzNPpyNq9AqXMBjeymIjFDAkAFH06mHJH/cSBHAgU0s4vfpBn6b2nf8tiRLvagKD8RbTpq2FKTBg7cl9l3c7Q==", + "dev": true + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -7509,6 +8866,15 @@ "node": ">=8" } }, + "node_modules/pause-stream": { + "version": "0.0.11", + "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", + "integrity": "sha512-e3FBlXLmN/D1S+zHzanP4E/4Z60oFAa3O051qt1pxa7DEJWKAyil6upYVXCWadEnuoqa4Pkc9oUx9zsxYeRv8A==", + "dev": true, + "dependencies": { + "through": "~2.3" + } + }, "node_modules/picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", @@ -7541,6 +8907,15 @@ "node": ">= 6" } }, + "node_modules/posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha512-xTgYBc3fuo7Yt7JbiuFxSYGToMoz8fLoE6TC9Wx1P/u+LfeThMOAqmuyECnlBaaJb+u1m9hHiXUEtwW4OzfUJg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/postcss": { "version": "8.4.27", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.27.tgz", @@ -7698,6 +9073,12 @@ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-3.8.0.tgz", "integrity": "sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==" }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, "node_modules/prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", @@ -7708,6 +9089,15 @@ "react-is": "^16.13.1" } }, + "node_modules/proxy-middleware": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/proxy-middleware/-/proxy-middleware-0.15.0.tgz", + "integrity": "sha512-EGCG8SeoIRVMhsqHQUdDigB2i7qU7fCsWASwn54+nPutYO8n4q6EiwMzyfWlC+dzRFExP+kvcnDFdBDHoZBU7Q==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/punycode": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", @@ -7735,6 +9125,15 @@ } ] }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/react": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", @@ -7771,18 +9170,6 @@ "react": "^18.2.0" } }, - "node_modules/react-gauge-chart": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/react-gauge-chart/-/react-gauge-chart-0.4.1.tgz", - "integrity": "sha512-xc1pQ6tF3shRpjyzIGA5EeQcRNfjy5og6Ory5fUe9cWChw8ZadaONMnUIpGHdfUefF+Ffzh6HAZuOX5+AMzOzw==", - "dependencies": { - "d3": "^7.6.1" - }, - "peerDependencies": { - "react": "^16.8.2 || ^17.0 || ^18.x", - "react-dom": "^16.8.2 || ^17.0 || ^18.x" - } - }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", @@ -7909,6 +9296,27 @@ "pify": "^2.3.0" } }, + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/readable-stream/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true + }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -7995,6 +9403,44 @@ "@babel/runtime": "^7.8.4" } }, + "node_modules/regex-not": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "dev": true, + "dependencies": { + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/regex-not/node_modules/extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", + "dev": true, + "dependencies": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/regex-not/node_modules/is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "dependencies": { + "is-plain-object": "^2.0.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/regexp.prototype.flags": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz", @@ -8046,6 +9492,30 @@ "jsesc": "bin/jsesc" } }, + "node_modules/remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha512-/hS+Y0u3aOfIETiaiirUFwDBDzmXPvO+jAfKTitUngIPzdKc6Z0LoFjM/CK5PL4C+eKwHohlHAb6H0VFfmmUsw==", + "dev": true + }, + "node_modules/repeat-element": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.4.tgz", + "integrity": "sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", + "dev": true, + "engines": { + "node": ">=0.10" + } + }, "node_modules/resolve": { "version": "1.22.2", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", @@ -8078,6 +9548,22 @@ "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" } }, + "node_modules/resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg==", + "deprecated": "https://github.com/lydell/resolve-url#deprecated", + "dev": true + }, + "node_modules/ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", + "dev": true, + "engines": { + "node": ">=0.12" + } + }, "node_modules/reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", @@ -8101,11 +9587,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/robust-predicates": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz", - "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==" - }, "node_modules/run-applescript": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-5.0.0.tgz", @@ -8224,11 +9705,6 @@ "queue-microtask": "^1.2.2" } }, - "node_modules/rw": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", - "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==" - }, "node_modules/safe-array-concat": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.0.0.tgz", @@ -8246,6 +9722,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/safe-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha512-aJXcif4xnaNUzvUuC5gcb46oTS7zvg4jpMTnuqtrEPlR3vFr4pxtdTwaF1Qs3Enjn9HK+ZlwQui+a7z0SywIzg==", + "dev": true, + "dependencies": { + "ret": "~0.1.10" + } + }, "node_modules/safe-regex-test": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", @@ -8259,105 +9750,497 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + "node_modules/sass": { + "version": "1.64.2", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.64.2.tgz", + "integrity": "sha512-TnDlfc+CRnUAgLO9D8cQLFu/GIjJIzJCGkE7o4ekIGQOH7T3GetiRR/PsTWJUHhkzcSPrARkPI+gNWn5alCzDg==", + "dependencies": { + "chokidar": ">=3.0.0 <4.0.0", + "immutable": "^4.0.0", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/scheduler": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", + "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/send/node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/send/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==", + "dev": true, + "dependencies": { + "accepts": "~1.3.4", + "batch": "0.6.1", + "debug": "2.6.9", + "escape-html": "~1.0.3", + "http-errors": "~1.6.2", + "mime-types": "~2.1.17", + "parseurl": "~1.3.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/serve-index/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/serve-index/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", + "dev": true, + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", + "dev": true + }, + "node_modules/serve-index/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/serve-index/node_modules/setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "dev": true + }, + "node_modules/set-value": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", + "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", + "dev": true, + "dependencies": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "engines": { + "node": ">=8" + } + }, + "node_modules/snake-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz", + "integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==", + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/snapdragon": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", + "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "dev": true, + "dependencies": { + "base": "^0.11.1", + "debug": "^2.2.0", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "map-cache": "^0.2.2", + "source-map": "^0.5.6", + "source-map-resolve": "^0.5.0", + "use": "^3.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", + "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "dev": true, + "dependencies": { + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-node/node_modules/define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", + "dev": true, + "dependencies": { + "is-descriptor": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-node/node_modules/is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-node/node_modules/is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-node/node_modules/is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-node/node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } }, - "node_modules/sass": { - "version": "1.64.2", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.64.2.tgz", - "integrity": "sha512-TnDlfc+CRnUAgLO9D8cQLFu/GIjJIzJCGkE7o4ekIGQOH7T3GetiRR/PsTWJUHhkzcSPrARkPI+gNWn5alCzDg==", + "node_modules/snapdragon-util": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", + "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "dev": true, "dependencies": { - "chokidar": ">=3.0.0 <4.0.0", - "immutable": "^4.0.0", - "source-map-js": ">=0.6.2 <2.0.0" - }, - "bin": { - "sass": "sass.js" + "kind-of": "^3.2.0" }, "engines": { - "node": ">=14.0.0" + "node": ">=0.10.0" } }, - "node_modules/scheduler": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", - "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", + "node_modules/snapdragon-util/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, "dependencies": { - "loose-envify": "^1.1.0" + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" } }, - "node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "bin": { - "semver": "bin/semver.js" + "node_modules/snapdragon/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" } }, - "node_modules/shebang-command": { + "node_modules/snapdragon/node_modules/ms": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dependencies": { - "shebang-regex": "^3.0.0" - }, + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "dev": true, "engines": { - "node": ">=8" + "node": ">=0.10.0" } }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "node_modules/source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", "engines": { - "node": ">=8" + "node": ">=0.10.0" } }, - "node_modules/side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "node_modules/source-map-resolve": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", + "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", + "deprecated": "See https://github.com/lydell/source-map-resolve#deprecated", + "dev": true, "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" + "atob": "^2.1.2", + "decode-uri-component": "^0.2.0", + "resolve-url": "^0.2.1", + "source-map-url": "^0.4.0", + "urix": "^0.1.0" + } + }, + "node_modules/source-map-url": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz", + "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==", + "deprecated": "See https://github.com/lydell/source-map-url#deprecated", + "dev": true + }, + "node_modules/split": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/split/-/split-0.3.3.tgz", + "integrity": "sha512-wD2AeVmxXRBoX44wAycgjVpMhvbwdI2aZjCkvfNcH1YqHQvJVa1duWc73OyVGJUc05fhFaTZeQ/PYsrmyH0JVA==", + "dev": true, + "dependencies": { + "through": "2" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": "*" } }, - "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" + "node_modules/split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "dev": true, + "dependencies": { + "extend-shallow": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "node_modules/split-string/node_modules/extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", + "dev": true, + "dependencies": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, "engines": { - "node": ">=8" + "node": ">=0.10.0" } }, - "node_modules/snake-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz", - "integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==", + "node_modules/split-string/node_modules/is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, "dependencies": { - "dot-case": "^3.0.4", - "tslib": "^2.0.3" + "is-plain-object": "^2.0.4" + }, + "engines": { + "node": ">=0.10.0" } }, - "node_modules/source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "node_modules/static-extend": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", + "integrity": "sha512-72E9+uLc27Mt718pMHt9VMNiAL4LMsmDbBva8mxWUCkT07fSzEGMYUCk0XWY6lp0j6RBAG4cJ3mWuZv2OE3s0g==", + "dev": true, + "dependencies": { + "define-property": "^0.2.5", + "object-copy": "^0.1.0" + }, "engines": { "node": ">=0.10.0" } }, + "node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/stream-combiner": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz", + "integrity": "sha512-rT00SPnTVyRsaSz5zgSPma/aHSOic5U1prhYdRy5HS2kTZviFpmDgzilbtsJsxiroqACmayynDN/9VzIbX5DOw==", + "dev": true, + "dependencies": { + "duplexer": "~0.1.1" + } + }, "node_modules/streamsearch": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", @@ -8366,6 +10249,15 @@ "node": ">=10.0.0" } }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/string.prototype.matchall": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.8.tgz", @@ -8695,6 +10587,12 @@ "node": ">=0.8" } }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "dev": true + }, "node_modules/titleize": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/titleize/-/titleize-3.0.0.tgz", @@ -8714,6 +10612,45 @@ "node": ">=4" } }, + "node_modules/to-object-path": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", + "integrity": "sha512-9mWHdnGRuh3onocaHzukyvCZhzvr6tiflAy/JRFXcJX0TjgfWA9pk9t8CMbzmBE4Jfw58pXbkngtBtqYxzNEyg==", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-object-path/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-regex": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", + "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "dev": true, + "dependencies": { + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -8725,6 +10662,100 @@ "node": ">=8.0" } }, + "node_modules/to-regex/node_modules/define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "dev": true, + "dependencies": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-regex/node_modules/extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", + "dev": true, + "dependencies": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-regex/node_modules/is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-regex/node_modules/is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-regex/node_modules/is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-regex/node_modules/is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "dependencies": { + "is-plain-object": "^2.0.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-regex/node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "dev": true, + "engines": { + "node": ">=0.6" + } + }, "node_modules/ts-interface-checker": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", @@ -8886,6 +10917,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/undici-types": { + "version": "5.25.3", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.25.3.tgz", + "integrity": "sha512-Ga1jfYwRn7+cP9v8auvEXN1rX3sWqlayd4HP7OKk4mZWylEmu3KzXDUGrQUN6Ol7qo1gPvB2e5gX6udnyEPgdA==", + "dev": true + }, "node_modules/unicode-canonical-property-names-ecmascript": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", @@ -8922,6 +10959,90 @@ "node": ">=4" } }, + "node_modules/union-value": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", + "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", + "dev": true, + "dependencies": { + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^2.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unix-crypt-td-js": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/unix-crypt-td-js/-/unix-crypt-td-js-1.1.4.tgz", + "integrity": "sha512-8rMeVYWSIyccIJscb9NdCfZKSRBKYTeVnwmiRYT2ulE3qd1RaDQ0xQDP+rI3ccIWbhu/zuo5cgN8z73belNZgw==", + "dev": true + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/unset-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", + "integrity": "sha512-PcA2tsuGSF9cnySLHTLSh2qrQiJ70mn+r+Glzxv2TWZblxsxCC52BDlZoPCsz7STd9pN7EZetkWZBAvk4cgZdQ==", + "dev": true, + "dependencies": { + "has-value": "^0.3.1", + "isobject": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unset-value/node_modules/has-value": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", + "integrity": "sha512-gpG936j8/MzaeID5Yif+577c17TxaDmhuyVgSwtnL/q8UUTySg8Mecb+8Cf1otgLoD7DDH75axp86ER7LFsf3Q==", + "dev": true, + "dependencies": { + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unset-value/node_modules/has-value/node_modules/isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA==", + "dev": true, + "dependencies": { + "isarray": "1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unset-value/node_modules/has-values": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", + "integrity": "sha512-J8S0cEdWuQbqD9//tlZxiMuMNmxB8PlEwvYwuxsTmR1G5RXUePEX/SJn7aD0GMLieuZYSwNH0cQuJGwnYunXRQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unset-value/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true + }, "node_modules/untildify": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", @@ -8930,6 +11051,16 @@ "node": ">=8" } }, + "node_modules/upath": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", + "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", + "dev": true, + "engines": { + "node": ">=4", + "yarn": "*" + } + }, "node_modules/update-browserslist-db": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz", @@ -8967,6 +11098,22 @@ "punycode": "^2.1.0" } }, + "node_modules/urix": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", + "integrity": "sha512-Am1ousAhSLBeB9cG/7k7r2R0zj50uDRlZHPGbazid5s9rlF1F/QKYObEKSIunSjIOkJZqwRRLpvewjEkM7pSqg==", + "deprecated": "Please see https://github.com/lydell/urix#deprecated", + "dev": true + }, + "node_modules/use": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/use-callback-ref": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.0.tgz", @@ -9013,6 +11160,15 @@ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "dev": true, + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/uuid": { "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", @@ -9021,6 +11177,15 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/victory-vendor": { "version": "36.6.11", "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.6.11.tgz", @@ -9054,6 +11219,29 @@ "node": ">=10.13.0" } }, + "node_modules/websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "dev": true, + "dependencies": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/package.json b/package.json index 2545c45..e01d457 100644 --- a/package.json +++ b/package.json @@ -47,5 +47,10 @@ "tailwind-merge": "^1.14.0", "tailwindcss": "3.3.3", "tailwindcss-animate": "^1.0.6" + }, + "devDependencies": { + "@types/node": "20.8.5", + "@types/react": "18.2.28", + "live-server": "^1.2.2" } } diff --git a/public/logos/keycloak_icon_512px.svg b/public/logos/keycloak_icon_512px.svg new file mode 100644 index 0000000..345e238 --- /dev/null +++ b/public/logos/keycloak_icon_512px.svg @@ -0,0 +1,259 @@ + + + + + + + + + + + + + keycloak_deliverables + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..b2c146a --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,35 @@ +{ + "compilerOptions": { + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], + "allowJs": true, + "skipLibCheck": true, + "strict": false, + "forceConsistentCasingInFileNames": true, + "noEmit": true, + "incremental": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "plugins": [ + { + "name": "next" + } + ] + }, + "include": [ + "next-env.d.ts", + ".next/types/**/*.ts", + "**/*.ts", + "**/*.tsx" + ], + "exclude": [ + "node_modules" + ] +} From eea834f07e4681d3a00dcd6438e8a48c3ab87aa9 Mon Sep 17 00:00:00 2001 From: Charro Gruver Date: Mon, 16 Oct 2023 20:08:25 +0000 Subject: [PATCH 03/42] wip add appList --- app/api/pelorus-api.jsx | 28 + app/dashboard/page.js | 14 +- components/application-selector.jsx | 10 +- components/dashboard-alternate.jsx | 20 +- .../{dashboard.jsx => dashboard.jsx.not-used} | 0 dto/application.ts | 3 - dto/deployment.ts | 6 - package-lock.json | 11330 ---------------- tsconfig.json | 35 - 9 files changed, 46 insertions(+), 11400 deletions(-) create mode 100644 app/api/pelorus-api.jsx rename components/{dashboard.jsx => dashboard.jsx.not-used} (100%) delete mode 100644 dto/application.ts delete mode 100644 dto/deployment.ts delete mode 100644 package-lock.json delete mode 100644 tsconfig.json diff --git a/app/api/pelorus-api.jsx b/app/api/pelorus-api.jsx new file mode 100644 index 0000000..fe6f902 --- /dev/null +++ b/app/api/pelorus-api.jsx @@ -0,0 +1,28 @@ +export async function getApps() { + const response = await fetch(process.env.PELORUS_API_URL + "/sdp/apps?range=1w") + if (!response.ok) { + throw new Error('Failed to fetch list of Apps from Pelorus') + } + + return response.json() +} + +export async function getLTFC( {appName} ) { + const response = await fetch(process.env.PELORUS_API_URL + "/sdp/lead_time_for_change/" + appName + "?range=1w") + + if (!response.ok) { + throw new Error("Failed to fetch Lead Time For Change data for: " + appName) + } + + return response.json() +} + +export async function getData() { + const response = await fetch('https://api.npoint.io/ee46484033b5d4d4658e') + + if (!response.ok) { + throw new Error('Failed to fetch data') + } + + return response.json() + } diff --git a/app/dashboard/page.js b/app/dashboard/page.js index c1943b9..fd1b0da 100644 --- a/app/dashboard/page.js +++ b/app/dashboard/page.js @@ -9,25 +9,17 @@ import { AppIcon } from '@/components/app-icon' import { Dashboard } from '@/components/dashboard-alternate' import { Cog, FileText, LayoutGrid, Users, Search, Bell, GitBranch } from 'lucide-react' import { Button } from '@/components/ui/button' +import { getApps, getData } from '../api/pelorus-api' export const metadata = { title: 'Dashboard - Red Hat Developer Intelligence' } -async function getData() { - const response = await fetch('https://api.npoint.io/ee46484033b5d4d4658e') - - if (!response.ok) { - throw new Error('Failed to fetch data') - } - - return response.json() -} - export default async function Page() { const session = await getServerSession(options) const data = await getData() + const appList = await getApps() if (!session) { redirect('/') @@ -62,7 +54,7 @@ export default async function Page() {
- +
) diff --git a/components/application-selector.jsx b/components/application-selector.jsx index 8d81841..a047ecb 100644 --- a/components/application-selector.jsx +++ b/components/application-selector.jsx @@ -17,7 +17,7 @@ export function ApplicationSelector({ applications, activeApplication, changeAct + + + + + No application found. + + {applications.map((application) => ( + { + changeActiveApplication(currentValue) + setOpen(false) + }}> +
+ + {application.app} +
+ +
+ ))} +
+
+
+ +
+
+ + ) +} diff --git a/components/application-selector.jsx b/components/application-selector.jsx index a047ecb..e1954d9 100644 --- a/components/application-selector.jsx +++ b/components/application-selector.jsx @@ -17,7 +17,7 @@ export function ApplicationSelector({ applications, activeApplication, changeAct @@ -28,16 +28,16 @@ export function AppSelector({ applications, activeApplication, changeActiveAppli No application found. - {applications.map((application) => ( + {appList.map((application) => ( { - changeActiveApplication(currentValue) + changeActiveApp(currentValue) setOpen(false) }}>
- - {application.app} + + {application.app}
- +
))}
diff --git a/components/change-failure-rate/tab-trigger.jsx b/components/change-failure-rate/tab-trigger.jsx index 60a0f2e..1da04bd 100644 --- a/components/change-failure-rate/tab-trigger.jsx +++ b/components/change-failure-rate/tab-trigger.jsx @@ -4,8 +4,9 @@ import { XCircle, ArrowDown } from 'lucide-react' import { Badge } from '@/components/ui/badge' import { InfoTooltip } from '@/components/info-tooltip' import { ChangeFailureRateRating } from './rating' +import { getCFR } from '@/app/api/pelorus-api' -export function ChangeFailureRateTabTrigger({ data }) { +export function ChangeFailureRateTabTrigger({ data, appName }) { // Calculate the mean const calculateMean = data => { @@ -19,7 +20,8 @@ export function ChangeFailureRateTabTrigger({ data }) { return element.rollingAverage }) - const chartMean = calculateMean(averages) + const response = getCFR( { appName } ) + const chartMean = response.cfr * 100 // Anomaly detection const showAnomalyWarning = data.some((day) => { diff --git a/components/dashboard.jsx b/components/dashboard.jsx index d57ffa8..db922bf 100644 --- a/components/dashboard.jsx +++ b/components/dashboard.jsx @@ -69,7 +69,7 @@ export function Dashboard({ data, appList }) {
- +
@@ -102,13 +102,13 @@ export function Dashboard({ data, appList }) { - + - + - + diff --git a/components/deployment-frequency/tab-trigger.jsx b/components/deployment-frequency/tab-trigger.jsx index 0d7dd88..2bcb9ec 100644 --- a/components/deployment-frequency/tab-trigger.jsx +++ b/components/deployment-frequency/tab-trigger.jsx @@ -4,8 +4,9 @@ import { Rocket, ArrowDown } from 'lucide-react' import { Badge } from '@/components/ui/badge' import { InfoTooltip } from '@/components/info-tooltip' import { DeploymentFrequencyRating } from '@/components/deployment-frequency/rating' +import { getDF } from '@/app/api/pelorus-api' -export function DeploymentFrequencyTabTrigger({ data }) { +export function DeploymentFrequencyTabTrigger({ data, appName }) { // Calculate the mean const calculateMean = data => { @@ -19,7 +20,8 @@ export function DeploymentFrequencyTabTrigger({ data }) { return element.rollingAverage }) - const chartMean = calculateMean(averages) + const response = getDF( { appName } ) + const chartMean = response.df // Anomaly detection const showAnomalyWarning = data.some((day) => { diff --git a/components/lead-time-for-change/tab-trigger.jsx b/components/lead-time-for-change/tab-trigger.jsx index 8cacba1..b904c52 100644 --- a/components/lead-time-for-change/tab-trigger.jsx +++ b/components/lead-time-for-change/tab-trigger.jsx @@ -20,7 +20,8 @@ export function LeadTimeForChangeTabTrigger({ data, appName }) { return element.rollingAverage }) - const chartMean = getLTFC( { appName } ).ltfc + const response = getLTFC( { appName } ) + const chartMean = response.ltfc / 86400 // Anomaly detection const showAnomalyWarning = data.some((day) => { diff --git a/components/mean-time-to-recovery/tab-trigger.jsx b/components/mean-time-to-recovery/tab-trigger.jsx index 2b5b61f..3754698 100644 --- a/components/mean-time-to-recovery/tab-trigger.jsx +++ b/components/mean-time-to-recovery/tab-trigger.jsx @@ -4,8 +4,9 @@ import { PackageCheck, ArrowDown } from 'lucide-react' import { Badge } from '@/components/ui/badge' import { InfoTooltip } from '@/components/info-tooltip' import { MeanTimeToRecoveryRating } from './rating' +import { getMTTR } from '@/app/api/pelorus-api' -export function MeanTimeToRecoveryTabTrigger({ data }) { +export function MeanTimeToRecoveryTabTrigger({ data, appName }) { // Calculate the mean const calculateMean = data => { @@ -19,7 +20,8 @@ export function MeanTimeToRecoveryTabTrigger({ data }) { return element.rollingAverage }) - const chartMean = calculateMean(averages) + const response = getMTTR( { appName } ) + const chartMean = response.mttr / 86400 // Anomaly detection const showAnomalyWarning = data.some((day) => { From 33bab6e4bc968647487f7ec56513ab63ad30d0a1 Mon Sep 17 00:00:00 2001 From: Charro Gruver Date: Wed, 18 Oct 2023 20:29:18 +0000 Subject: [PATCH 06/42] wip not working --- app/api/old-api.jsx | 2 +- app/api/pelorus-api.jsx | 62 +++++++++++++------ .../change-failure-rate/tab-trigger.jsx | 7 ++- components/dashboard.jsx | 22 +++++-- .../deployment-frequency/tab-trigger.jsx | 5 +- .../lead-time-for-change/tab-trigger.jsx | 5 +- .../mean-time-to-recovery/tab-trigger.jsx | 5 +- 7 files changed, 70 insertions(+), 38 deletions(-) diff --git a/app/api/old-api.jsx b/app/api/old-api.jsx index e586880..2e613be 100644 --- a/app/api/old-api.jsx +++ b/app/api/old-api.jsx @@ -6,7 +6,7 @@ export async function getData() { return response.json() } - export async function getApps() { +export async function getApps() { const response = await fetch(`${process.env.NEXT_PUBLIC_PELORUS_API_URL}/sdp/apps?range=1w`) if (!response.ok) { throw new Error('Failed to fetch list of Apps from Pelorus') diff --git a/app/api/pelorus-api.jsx b/app/api/pelorus-api.jsx index 78a0d74..16c7143 100644 --- a/app/api/pelorus-api.jsx +++ b/app/api/pelorus-api.jsx @@ -1,3 +1,4 @@ +'use client' import { useState, useEffect } from "react" export async function getApps() { @@ -8,36 +9,57 @@ export async function getApps() { return response.json() } -export function getLTFC( { appName } ) { +export function getLTFC(appName) { - const [response, setResponse] = useState('') - useEffect(() => { - fetch(`${process.env.NEXT_PUBLIC_PELORUS_API_URL}/sdp/lead_time_for_change/${appName}?range=1w`).then((test) => test.json()).then((data) => {setResponse(data)}).catch((error) => { console.log(error) }) - }, []); + // const [response, setResponse] = useState('') + // useEffect(() => { + // fetch(`${process.env.NEXT_PUBLIC_PELORUS_API_URL}/sdp/lead_time_for_change/${appName}?range=1w`).then((test) => test.json()).then((data) => {setResponse(data)}).catch((error) => { console.log(error) }) + // }, []); + // return response + const response = fetch(`${process.env.NEXT_PUBLIC_PELORUS_API_URL}/sdp/lead_time_for_change/${appName}?range=1w`).then((test) => test.json()).catch((error) => { console.log(error) }) return response } -export function getDF( { appName } ) { +export function getDF(appName) { - const [response, setResponse] = useState('') - useEffect(() => { - fetch(`${process.env.NEXT_PUBLIC_PELORUS_API_URL}/sdp/deployment_frequency/${appName}?range=1w`).then((test) => test.json()).then((data) => {setResponse(data)}).catch((error) => { console.log(error) }) - }, []); + // const [response, setResponse] = useState('') + // useEffect(() => { + // fetch(`${process.env.NEXT_PUBLIC_PELORUS_API_URL}/sdp/deployment_frequency/${appName}?range=1w`).then((test) => test.json()).then((data) => {setResponse(data)}).catch((error) => { console.log(error) }) + // }, []); + // return response + const response = fetch(`${process.env.NEXT_PUBLIC_PELORUS_API_URL}/sdp/deployment_frequency/${appName}?range=1w`).then((test) => test.json()).catch((error) => { console.log(error) }) return response } -export function getMTTR( { appName } ) { - const [response, setResponse] = useState('') - useEffect(() => { - fetch(`${process.env.NEXT_PUBLIC_PELORUS_API_URL}/sdp/mean_time_to_restore/${appName}?range=1w`).then((test) => test.json()).then((data) => {setResponse(data)}).catch((error) => { console.log(error) }) - }, []); +export function getMTTR(appName) { + // const [response, setResponse] = useState('') + // useEffect(() => { + // fetch(`${process.env.NEXT_PUBLIC_PELORUS_API_URL}/sdp/mean_time_to_restore/${appName}?range=1w`).then((test) => test.json()).then((data) => {setResponse(data)}).catch((error) => { console.log(error) }) + // }, []); + const response = fetch(`${process.env.NEXT_PUBLIC_PELORUS_API_URL}/sdp/mean_time_to_restore/${appName}?range=1w`).then((test) => test.json()).catch((error) => { console.log(error) }) return response } -export function getCFR( { appName } ) { - const [response, setResponse] = useState('') - useEffect(() => { - fetch(`${process.env.NEXT_PUBLIC_PELORUS_API_URL}/sdp/change_failure_rate/${appName}?range=1w`).then((test) => test.json()).then((data) => {setResponse(data)}).catch((error) => { console.log(error) }) - }, []); +export function getCFR(appName) { + // const [response, setResponse] = useState('') + // useEffect(() => { + // fetch(`${process.env.NEXT_PUBLIC_PELORUS_API_URL}/sdp/change_failure_rate/${appName}?range=1w`).then((test) => test.json()).then((data) => {setResponse(data)}).catch((error) => { console.log(error) }) + // }, []); + + const response = fetch(`${process.env.NEXT_PUBLIC_PELORUS_API_URL}/sdp/change_failure_rate/${appName}?range=1w`).then((test) => test.json()).catch((error) => { console.log(error) }) return response } + +export function getDORA(appName) { + + const dora = {mttr: 0, cfr: 0, df: 0, ltfc: 0} + const cfr = getCFR(appName) + dora.cfr = cfr.cfr + const df = getDF(appName) + dora.df = df.df + const mttr = getMTTR(appName) + dora.mttr = mttr.mttr + const ltfc = getLTFC(appName) + dora.ltfc = ltfc.ltfc + return dora +} \ No newline at end of file diff --git a/components/change-failure-rate/tab-trigger.jsx b/components/change-failure-rate/tab-trigger.jsx index 1da04bd..c1d6780 100644 --- a/components/change-failure-rate/tab-trigger.jsx +++ b/components/change-failure-rate/tab-trigger.jsx @@ -4,7 +4,7 @@ import { XCircle, ArrowDown } from 'lucide-react' import { Badge } from '@/components/ui/badge' import { InfoTooltip } from '@/components/info-tooltip' import { ChangeFailureRateRating } from './rating' -import { getCFR } from '@/app/api/pelorus-api' +import { useState, useEffect } from "react" export function ChangeFailureRateTabTrigger({ data, appName }) { @@ -20,7 +20,10 @@ export function ChangeFailureRateTabTrigger({ data, appName }) { return element.rollingAverage }) - const response = getCFR( { appName } ) + const [response, setResponse] = useState('') + useEffect(() => { + fetch(`${process.env.NEXT_PUBLIC_PELORUS_API_URL}/sdp/change_failure_rate/${appName}?range=1w`).then((test) => test.json()).then((data) => {setResponse(data)}).catch((error) => { console.log(error) }) + }, []); const chartMean = response.cfr * 100 // Anomaly detection diff --git a/components/dashboard.jsx b/components/dashboard.jsx index db922bf..aa27807 100644 --- a/components/dashboard.jsx +++ b/components/dashboard.jsx @@ -1,6 +1,6 @@ 'use client' -import { useState } from 'react' +import { useState, useEffect } from 'react' import { ApplicationSelector } from '@/components/application-selector' import { AppSelector } from '@/components/app-selector' import { DateRangeSelector } from '@/components/date-range-selector' @@ -32,23 +32,33 @@ import { ScorecardScore } from './scorecard/score' import { ScorecardAnalysis } from './scorecard/analysis' import { ScorecardTrend } from './scorecard/trend' import { MeanTimeToRecoveryTabTrigger } from './mean-time-to-recovery/tab-trigger' -import { getApps, getLTFC, getDF, getData } from '../app/api/pelorus-api' +import { getCFR, getDF, getLTFC, getMTTR } from '@/app/api/pelorus-api' export function Dashboard({ data, appList }) { + const [dora, setDora] = useState({mttr: 0, cfr: 0, df: 0, ltfc: 0}) const applicationsList = data.applications const [activeApp, setActiveApp] = useState( appList[0].app ) const [activeApplication, setActiveApplication] = useState(applicationsList[0].id) const [activeDateRange, setActiveDateRange] = useState('') - const [dataScorecard, setDataScorecard] = useState(applicationsList[0].scorecard[0]) const [dataDeploymentFrequency, setDataDeploymentFrequency] = useState(applicationsList[0].metrics[1].data) const [dataLeadTimeForChange, setDataLeadTimeForChange] = useState(applicationsList[0].metrics[2].data) const [dataChangeFailureRate, setDataChangeFailureRate] = useState(applicationsList[0].metrics[0].data) const [dataMeanTimeToRecovery, setDataMeanTimeToRecovery] = useState(applicationsList[0].metrics[3].data) + const [leadTimeForChange, setLeadTimeForChange] = useState( dora.ltfc ) + + // useEffect(() => {getCFR(activeApp).then((data) => {dora.cfr = data.cfr})}, []) function changeActiveApp(app) { + // const new_dora = {mttr: 0, cfr: 0, df: 0, ltfc: 0} + console.log(app) setActiveApp(app) + // getCFR(app).then((data) => {dora.cfr = data.cfr}) + // getDF(app).then((data) => {dora.df = data.df}) + // getMTTR(app).then((data) => {dora.mttr = data.mttr}) + // getLTFC(app).then((data) => {dora.ltfc = data.ltfc}) + // setDora(dora) } function changeActiveApplication(id) { @@ -99,13 +109,13 @@ export function Dashboard({ data, appList }) { - + - + - + diff --git a/components/deployment-frequency/tab-trigger.jsx b/components/deployment-frequency/tab-trigger.jsx index 2bcb9ec..360bee6 100644 --- a/components/deployment-frequency/tab-trigger.jsx +++ b/components/deployment-frequency/tab-trigger.jsx @@ -6,7 +6,7 @@ import { InfoTooltip } from '@/components/info-tooltip' import { DeploymentFrequencyRating } from '@/components/deployment-frequency/rating' import { getDF } from '@/app/api/pelorus-api' -export function DeploymentFrequencyTabTrigger({ data, appName }) { +export function DeploymentFrequencyTabTrigger({ data, dora }) { // Calculate the mean const calculateMean = data => { @@ -20,8 +20,7 @@ export function DeploymentFrequencyTabTrigger({ data, appName }) { return element.rollingAverage }) - const response = getDF( { appName } ) - const chartMean = response.df + const chartMean = dora.df // Anomaly detection const showAnomalyWarning = data.some((day) => { diff --git a/components/lead-time-for-change/tab-trigger.jsx b/components/lead-time-for-change/tab-trigger.jsx index b904c52..9eec1bf 100644 --- a/components/lead-time-for-change/tab-trigger.jsx +++ b/components/lead-time-for-change/tab-trigger.jsx @@ -6,7 +6,7 @@ import { InfoTooltip } from '@/components/info-tooltip' import { LeadTimeForChangeRating } from './rating' import { getLTFC } from '@/app/api/pelorus-api' -export function LeadTimeForChangeTabTrigger({ data, appName }) { +export function LeadTimeForChangeTabTrigger({ data, dora }) { // Calculate the mean const calculateMean = data => { @@ -20,8 +20,7 @@ export function LeadTimeForChangeTabTrigger({ data, appName }) { return element.rollingAverage }) - const response = getLTFC( { appName } ) - const chartMean = response.ltfc / 86400 + const chartMean = dora.ltfc / 86400 // Anomaly detection const showAnomalyWarning = data.some((day) => { diff --git a/components/mean-time-to-recovery/tab-trigger.jsx b/components/mean-time-to-recovery/tab-trigger.jsx index 3754698..d56cac6 100644 --- a/components/mean-time-to-recovery/tab-trigger.jsx +++ b/components/mean-time-to-recovery/tab-trigger.jsx @@ -6,7 +6,7 @@ import { InfoTooltip } from '@/components/info-tooltip' import { MeanTimeToRecoveryRating } from './rating' import { getMTTR } from '@/app/api/pelorus-api' -export function MeanTimeToRecoveryTabTrigger({ data, appName }) { +export function MeanTimeToRecoveryTabTrigger({ data, dora }) { // Calculate the mean const calculateMean = data => { @@ -20,8 +20,7 @@ export function MeanTimeToRecoveryTabTrigger({ data, appName }) { return element.rollingAverage }) - const response = getMTTR( { appName } ) - const chartMean = response.mttr / 86400 + const chartMean = dora.mttr / 86400 // Anomaly detection const showAnomalyWarning = data.some((day) => { From e39842fca6de66f7526b152f5a16bba77bb6b33a Mon Sep 17 00:00:00 2001 From: Charro Gruver Date: Wed, 18 Oct 2023 21:04:17 +0000 Subject: [PATCH 07/42] Working --- components/change-failure-rate/tab-trigger.jsx | 4 +--- components/dashboard.jsx | 18 +++--------------- .../deployment-frequency/tab-trigger.jsx | 8 +++++--- .../lead-time-for-change/tab-trigger.jsx | 8 +++++--- .../mean-time-to-recovery/tab-trigger.jsx | 8 +++++--- 5 files changed, 19 insertions(+), 27 deletions(-) diff --git a/components/change-failure-rate/tab-trigger.jsx b/components/change-failure-rate/tab-trigger.jsx index c1d6780..8121669 100644 --- a/components/change-failure-rate/tab-trigger.jsx +++ b/components/change-failure-rate/tab-trigger.jsx @@ -4,7 +4,7 @@ import { XCircle, ArrowDown } from 'lucide-react' import { Badge } from '@/components/ui/badge' import { InfoTooltip } from '@/components/info-tooltip' import { ChangeFailureRateRating } from './rating' -import { useState, useEffect } from "react" +import { useState } from "react" export function ChangeFailureRateTabTrigger({ data, appName }) { @@ -21,9 +21,7 @@ export function ChangeFailureRateTabTrigger({ data, appName }) { }) const [response, setResponse] = useState('') - useEffect(() => { fetch(`${process.env.NEXT_PUBLIC_PELORUS_API_URL}/sdp/change_failure_rate/${appName}?range=1w`).then((test) => test.json()).then((data) => {setResponse(data)}).catch((error) => { console.log(error) }) - }, []); const chartMean = response.cfr * 100 // Anomaly detection diff --git a/components/dashboard.jsx b/components/dashboard.jsx index aa27807..83ab610 100644 --- a/components/dashboard.jsx +++ b/components/dashboard.jsx @@ -32,11 +32,9 @@ import { ScorecardScore } from './scorecard/score' import { ScorecardAnalysis } from './scorecard/analysis' import { ScorecardTrend } from './scorecard/trend' import { MeanTimeToRecoveryTabTrigger } from './mean-time-to-recovery/tab-trigger' -import { getCFR, getDF, getLTFC, getMTTR } from '@/app/api/pelorus-api' export function Dashboard({ data, appList }) { - const [dora, setDora] = useState({mttr: 0, cfr: 0, df: 0, ltfc: 0}) const applicationsList = data.applications const [activeApp, setActiveApp] = useState( appList[0].app ) const [activeApplication, setActiveApplication] = useState(applicationsList[0].id) @@ -46,19 +44,9 @@ export function Dashboard({ data, appList }) { const [dataLeadTimeForChange, setDataLeadTimeForChange] = useState(applicationsList[0].metrics[2].data) const [dataChangeFailureRate, setDataChangeFailureRate] = useState(applicationsList[0].metrics[0].data) const [dataMeanTimeToRecovery, setDataMeanTimeToRecovery] = useState(applicationsList[0].metrics[3].data) - const [leadTimeForChange, setLeadTimeForChange] = useState( dora.ltfc ) - - // useEffect(() => {getCFR(activeApp).then((data) => {dora.cfr = data.cfr})}, []) function changeActiveApp(app) { - // const new_dora = {mttr: 0, cfr: 0, df: 0, ltfc: 0} - console.log(app) setActiveApp(app) - // getCFR(app).then((data) => {dora.cfr = data.cfr}) - // getDF(app).then((data) => {dora.df = data.df}) - // getMTTR(app).then((data) => {dora.mttr = data.mttr}) - // getLTFC(app).then((data) => {dora.ltfc = data.ltfc}) - // setDora(dora) } function changeActiveApplication(id) { @@ -109,13 +97,13 @@ export function Dashboard({ data, appList }) { - + - + - + diff --git a/components/deployment-frequency/tab-trigger.jsx b/components/deployment-frequency/tab-trigger.jsx index 360bee6..5f73637 100644 --- a/components/deployment-frequency/tab-trigger.jsx +++ b/components/deployment-frequency/tab-trigger.jsx @@ -4,9 +4,9 @@ import { Rocket, ArrowDown } from 'lucide-react' import { Badge } from '@/components/ui/badge' import { InfoTooltip } from '@/components/info-tooltip' import { DeploymentFrequencyRating } from '@/components/deployment-frequency/rating' -import { getDF } from '@/app/api/pelorus-api' +import { useState } from "react" -export function DeploymentFrequencyTabTrigger({ data, dora }) { +export function DeploymentFrequencyTabTrigger({ data, appName }) { // Calculate the mean const calculateMean = data => { @@ -20,7 +20,9 @@ export function DeploymentFrequencyTabTrigger({ data, dora }) { return element.rollingAverage }) - const chartMean = dora.df + const [response, setResponse] = useState('') + fetch(`${process.env.NEXT_PUBLIC_PELORUS_API_URL}/sdp/deployment_frequency/${appName}?range=1w`).then((test) => test.json()).then((data) => {setResponse(data)}).catch((error) => { console.log(error) }) + const chartMean = response.df // Anomaly detection const showAnomalyWarning = data.some((day) => { diff --git a/components/lead-time-for-change/tab-trigger.jsx b/components/lead-time-for-change/tab-trigger.jsx index 9eec1bf..ba979a0 100644 --- a/components/lead-time-for-change/tab-trigger.jsx +++ b/components/lead-time-for-change/tab-trigger.jsx @@ -4,9 +4,9 @@ import { Clock4, ArrowDown } from 'lucide-react' import { Badge } from '@/components/ui/badge' import { InfoTooltip } from '@/components/info-tooltip' import { LeadTimeForChangeRating } from './rating' -import { getLTFC } from '@/app/api/pelorus-api' +import { useState } from "react" -export function LeadTimeForChangeTabTrigger({ data, dora }) { +export function LeadTimeForChangeTabTrigger({ data, appName }) { // Calculate the mean const calculateMean = data => { @@ -20,7 +20,9 @@ export function LeadTimeForChangeTabTrigger({ data, dora }) { return element.rollingAverage }) - const chartMean = dora.ltfc / 86400 + const [response, setResponse] = useState('') + fetch(`${process.env.NEXT_PUBLIC_PELORUS_API_URL}/sdp/lead_time_for_change/${appName}?range=1w`).then((test) => test.json()).then((data) => {setResponse(data)}).catch((error) => { console.log(error) }) + const chartMean = response.ltfc / 86400 // Anomaly detection const showAnomalyWarning = data.some((day) => { diff --git a/components/mean-time-to-recovery/tab-trigger.jsx b/components/mean-time-to-recovery/tab-trigger.jsx index d56cac6..2d6e5ce 100644 --- a/components/mean-time-to-recovery/tab-trigger.jsx +++ b/components/mean-time-to-recovery/tab-trigger.jsx @@ -4,9 +4,9 @@ import { PackageCheck, ArrowDown } from 'lucide-react' import { Badge } from '@/components/ui/badge' import { InfoTooltip } from '@/components/info-tooltip' import { MeanTimeToRecoveryRating } from './rating' -import { getMTTR } from '@/app/api/pelorus-api' +import { useState } from "react" -export function MeanTimeToRecoveryTabTrigger({ data, dora }) { +export function MeanTimeToRecoveryTabTrigger({ data, appName }) { // Calculate the mean const calculateMean = data => { @@ -20,7 +20,9 @@ export function MeanTimeToRecoveryTabTrigger({ data, dora }) { return element.rollingAverage }) - const chartMean = dora.mttr / 86400 + const [response, setResponse] = useState('') + fetch(`${process.env.NEXT_PUBLIC_PELORUS_API_URL}/sdp/mean_time_to_restore/${appName}?range=1w`).then((test) => test.json()).then((data) => {setResponse(data)}).catch((error) => { console.log(error) }) + const chartMean = response.mttr / 86400 // Anomaly detection const showAnomalyWarning = data.some((day) => { From c1b71e727adce5708bf7f572fe2133b04a3a5c6f Mon Sep 17 00:00:00 2001 From: Eric Sauer Date: Thu, 19 Oct 2023 15:06:17 +0000 Subject: [PATCH 08/42] Refactor to have useEffect retrigger dashboard components --- .gitignore | 1 + components/app-selector.jsx | 4 ++-- .../change-failure-rate/tab-trigger.jsx | 17 +++++++++++++++-- components/dashboard.jsx | 8 ++------ .../deployment-frequency/tab-trigger.jsx | 17 +++++++++++++++-- .../lead-time-for-change/tab-trigger.jsx | 19 ++++++++++++++++--- .../mean-time-to-recovery/tab-trigger.jsx | 16 ++++++++++++++-- 7 files changed, 65 insertions(+), 17 deletions(-) diff --git a/.gitignore b/.gitignore index 8f322f0..b9a8b64 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ /node_modules /.pnp .pnp.js +package-lock.json # testing /coverage diff --git a/components/app-selector.jsx b/components/app-selector.jsx index 89d64ae..93ce460 100644 --- a/components/app-selector.jsx +++ b/components/app-selector.jsx @@ -7,7 +7,7 @@ import { Button } from "@/components/ui/button" import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, } from "@/components/ui/command" import { Popover, PopoverContent, PopoverTrigger, } from "@/components/ui/popover" -export function AppSelector({ appList, activeApp, changeActiveApp }) { +export function AppSelector({ appList, activeApp, setActiveApp }) { const [open, setOpen] = useState(false) @@ -30,7 +30,7 @@ export function AppSelector({ appList, activeApp, changeActiveApp }) { {appList.map((application) => ( { - changeActiveApp(currentValue) + setActiveApp(currentValue) setOpen(false) }}>
diff --git a/components/change-failure-rate/tab-trigger.jsx b/components/change-failure-rate/tab-trigger.jsx index 8121669..4ab5fdf 100644 --- a/components/change-failure-rate/tab-trigger.jsx +++ b/components/change-failure-rate/tab-trigger.jsx @@ -4,7 +4,7 @@ import { XCircle, ArrowDown } from 'lucide-react' import { Badge } from '@/components/ui/badge' import { InfoTooltip } from '@/components/info-tooltip' import { ChangeFailureRateRating } from './rating' -import { useState } from "react" +import { useState, useEffect } from "react" export function ChangeFailureRateTabTrigger({ data, appName }) { @@ -21,7 +21,20 @@ export function ChangeFailureRateTabTrigger({ data, appName }) { }) const [response, setResponse] = useState('') - fetch(`${process.env.NEXT_PUBLIC_PELORUS_API_URL}/sdp/change_failure_rate/${appName}?range=1w`).then((test) => test.json()).then((data) => {setResponse(data)}).catch((error) => { console.log(error) }) + const [isLoading, setLoading] = useState(true) + + useEffect(() => { + fetch(`${process.env.NEXT_PUBLIC_PELORUS_API_URL}/sdp/change_failure_rate/${appName}?range=1w`) + .then((test) => test.json()) + .then((response) => { + setResponse(response) + setLoading(false) + }) + }, [appName]); + + if (isLoading) return

Loading...

+ if (!response) return

No cfr data!

+ const chartMean = response.cfr * 100 // Anomaly detection diff --git a/components/dashboard.jsx b/components/dashboard.jsx index 83ab610..526f2ba 100644 --- a/components/dashboard.jsx +++ b/components/dashboard.jsx @@ -45,10 +45,6 @@ export function Dashboard({ data, appList }) { const [dataChangeFailureRate, setDataChangeFailureRate] = useState(applicationsList[0].metrics[0].data) const [dataMeanTimeToRecovery, setDataMeanTimeToRecovery] = useState(applicationsList[0].metrics[3].data) - function changeActiveApp(app) { - setActiveApp(app) - } - function changeActiveApplication(id) { setActiveApplication(id) setDataScorecard(applicationsList.find((application) => application.id === id)?.scorecard[0]) @@ -63,11 +59,11 @@ export function Dashboard({ data, appList }) {
-

Dashboard

+

{activeApp} Dashboard

- +
diff --git a/components/deployment-frequency/tab-trigger.jsx b/components/deployment-frequency/tab-trigger.jsx index 5f73637..f50e160 100644 --- a/components/deployment-frequency/tab-trigger.jsx +++ b/components/deployment-frequency/tab-trigger.jsx @@ -4,7 +4,7 @@ import { Rocket, ArrowDown } from 'lucide-react' import { Badge } from '@/components/ui/badge' import { InfoTooltip } from '@/components/info-tooltip' import { DeploymentFrequencyRating } from '@/components/deployment-frequency/rating' -import { useState } from "react" +import { useState, useEffect } from "react" export function DeploymentFrequencyTabTrigger({ data, appName }) { @@ -21,7 +21,20 @@ export function DeploymentFrequencyTabTrigger({ data, appName }) { }) const [response, setResponse] = useState('') - fetch(`${process.env.NEXT_PUBLIC_PELORUS_API_URL}/sdp/deployment_frequency/${appName}?range=1w`).then((test) => test.json()).then((data) => {setResponse(data)}).catch((error) => { console.log(error) }) + const [isLoading, setLoading] = useState(true) + + useEffect(() => { + fetch(`${process.env.NEXT_PUBLIC_PELORUS_API_URL}/sdp/deployment_frequency/${appName}?range=1w`) + .then((test) => test.json()) + .then((response) => { + setResponse(response) + setLoading(false) + }) + }, [appName]); + + if (isLoading) return

Loading...

+ if (!response) return

No cfr data!

+ const chartMean = response.df // Anomaly detection diff --git a/components/lead-time-for-change/tab-trigger.jsx b/components/lead-time-for-change/tab-trigger.jsx index ba979a0..9d98d3f 100644 --- a/components/lead-time-for-change/tab-trigger.jsx +++ b/components/lead-time-for-change/tab-trigger.jsx @@ -4,7 +4,7 @@ import { Clock4, ArrowDown } from 'lucide-react' import { Badge } from '@/components/ui/badge' import { InfoTooltip } from '@/components/info-tooltip' import { LeadTimeForChangeRating } from './rating' -import { useState } from "react" +import { useState, useEffect } from "react" export function LeadTimeForChangeTabTrigger({ data, appName }) { @@ -21,7 +21,20 @@ export function LeadTimeForChangeTabTrigger({ data, appName }) { }) const [response, setResponse] = useState('') - fetch(`${process.env.NEXT_PUBLIC_PELORUS_API_URL}/sdp/lead_time_for_change/${appName}?range=1w`).then((test) => test.json()).then((data) => {setResponse(data)}).catch((error) => { console.log(error) }) + const [isLoading, setLoading] = useState(true) + + useEffect(() => { + fetch(`${process.env.NEXT_PUBLIC_PELORUS_API_URL}/sdp/lead_time_for_change/${appName}?range=1w`) + .then((test) => test.json()) + .then((response) => { + setResponse(response) + setLoading(false) + }) + }, [appName]); + + if (isLoading) return

Loading...

+ if (!response) return

No cfr data!

+ const chartMean = response.ltfc / 86400 // Anomaly detection @@ -37,7 +50,7 @@ export function LeadTimeForChangeTabTrigger({ data, appName }) { {showAnomalyWarning && Anomaly detected}

- Lead time for change + Lead time for change ({appName})

diff --git a/components/mean-time-to-recovery/tab-trigger.jsx b/components/mean-time-to-recovery/tab-trigger.jsx index 2d6e5ce..50466e5 100644 --- a/components/mean-time-to-recovery/tab-trigger.jsx +++ b/components/mean-time-to-recovery/tab-trigger.jsx @@ -4,7 +4,7 @@ import { PackageCheck, ArrowDown } from 'lucide-react' import { Badge } from '@/components/ui/badge' import { InfoTooltip } from '@/components/info-tooltip' import { MeanTimeToRecoveryRating } from './rating' -import { useState } from "react" +import { useState, useEffect } from "react" export function MeanTimeToRecoveryTabTrigger({ data, appName }) { @@ -21,7 +21,19 @@ export function MeanTimeToRecoveryTabTrigger({ data, appName }) { }) const [response, setResponse] = useState('') - fetch(`${process.env.NEXT_PUBLIC_PELORUS_API_URL}/sdp/mean_time_to_restore/${appName}?range=1w`).then((test) => test.json()).then((data) => {setResponse(data)}).catch((error) => { console.log(error) }) + const [isLoading, setLoading] = useState(true) + + useEffect(() => { + fetch(`${process.env.NEXT_PUBLIC_PELORUS_API_URL}/sdp/mean_time_to_restore/${appName}?range=1w`) + .then((test) => test.json()) + .then((response) => { + setResponse(response) + setLoading(false) + }) + }, [appName]); + + if (isLoading) return

Loading...

+ if (!response) return

No cfr data!

const chartMean = response.mttr / 86400 // Anomaly detection From 196b614174c339437388f62a0985e03ab86f7eef Mon Sep 17 00:00:00 2001 From: Charro Gruver Date: Thu, 19 Oct 2023 16:55:48 +0000 Subject: [PATCH 09/42] chart for LTFC --- components/dashboard.jsx | 4 +- components/dashboard.jsx.not-used | 45 --------- components/lead-time-for-change/chart.jsx | 97 ++++++------------- components/lead-time-for-change/iso-chart.jsx | 92 ------------------ components/lead-time-for-change/tooltip.jsx | 14 ++- 5 files changed, 41 insertions(+), 211 deletions(-) delete mode 100644 components/dashboard.jsx.not-used delete mode 100644 components/lead-time-for-change/iso-chart.jsx diff --git a/components/dashboard.jsx b/components/dashboard.jsx index 526f2ba..6fc15ea 100644 --- a/components/dashboard.jsx +++ b/components/dashboard.jsx @@ -15,7 +15,7 @@ import { } from "@/components/ui/card" import { IsolatedDeploymentFrequencyChart } from '@/components/deployment-frequency/iso-chart' -import { IsolatedLeadTimeForChangeChart } from '@/components/lead-time-for-change/iso-chart' +import { LeadTimeForChangeChart } from '@/components/lead-time-for-change/chart' import { IsolatedChangeFailureRateChart } from '@/components/change-failure-rate/iso-chart' import { IsolatedMeanTimeToRecoveryChart } from '@/components/mean-time-to-recovery/iso-chart' @@ -119,7 +119,7 @@ export function Dashboard({ data, appList }) {
- +

diff --git a/components/dashboard.jsx.not-used b/components/dashboard.jsx.not-used deleted file mode 100644 index 09a4b0a..0000000 --- a/components/dashboard.jsx.not-used +++ /dev/null @@ -1,45 +0,0 @@ -'use client' - -import { useState } from 'react' -import { ApplicationSelector } from '@/components/application-selector' -import { DateRangeSelector } from '@/components/date-range-selector' -import { LeadTimeForChangeChart } from '@/components/lead-time-for-change/chart' -import { DeploymentFrequencyChart } from '@/components/deployment-frequency/chart' -import { ChangeFailureRateChart } from '@/components/change-failure-rate/chart' -import { MeanTimeToRecoveryChart } from '@/components/mean-time-to-recovery/chart' - -export function Dashboard({ data }) { - - const applicationsList = data.applications - const [activeApplication, setActiveApplication] = useState(applicationsList[0].id) - const [activeDateRange, setActiveDateRange] = useState('') - - const [dataDeploymentFrequency, setDataDeploymentFrequency] = useState(applicationsList[0].metrics[1].data) - const [dataLeadTimeForChange, setDataLeadTimeForChange] = useState(applicationsList[0].metrics[2].data) - const [dataChangeFailureRate, setDataChangeFailureRate] = useState(applicationsList[0].metrics[0].data) - const [dataMeanTimeToRecovery, setDataMeanTimeToRecovery] = useState(applicationsList[0].metrics[3].data) - - function changeActiveApplication(id) { - setActiveApplication(id) - setDataDeploymentFrequency(applicationsList.find((application) => application.id === id)?.metrics[1].data) - setDataLeadTimeForChange(applicationsList.find((application) => application.id === id)?.metrics[2].data) - setDataChangeFailureRate(applicationsList.find((application) => application.id === id)?.metrics[0].data) - setDataMeanTimeToRecovery(applicationsList.find((application) => application.id === id)?.metrics[3].data) - } - - return ( -
-
- - -
- -
- - - - -
-
- ) -} \ No newline at end of file diff --git a/components/lead-time-for-change/chart.jsx b/components/lead-time-for-change/chart.jsx index 06e3d18..2b67d31 100644 --- a/components/lead-time-for-change/chart.jsx +++ b/components/lead-time-for-change/chart.jsx @@ -1,30 +1,23 @@ 'use client' -import { useState } from 'react' +import { useState, useEffect } from 'react' import { useTheme } from "next-themes" import { format } from 'date-fns' -import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, } from '@/components/ui/card' -import { Badge } from '@/components/ui/badge' -import { XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, ComposedChart, Legend, Area, Line, Label, ReferenceLine } from 'recharts' -import { MoveRight, TrendingUp, TrendingDown, Clock4 } from 'lucide-react' -import { InfoTooltip } from '@/components/info-tooltip' +import { XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, ComposedChart, Area, Line, ReferenceLine } from 'recharts' import { LeadTimeForChangeTooltip } from './tooltip' -import { LeadTimeForChangeRating } from './rating' -import { LeadTimeForChangeOptions } from './options' -import { LeadTimeForChangeReport } from "./report" -const dateFormatter = date => { + +const dateFormatter = epoch => { + const date = new Date(epoch * 1000) return format(new Date(date), "MMM d") } -const calculateMean = data => { - if (data.length < 1) { - return - } - return data.reduce((prev, current) => prev + current) / data.length +const dayFormatter = seconds => { + const days = parseFloat(seconds / 86400).toFixed(2) + return days + "d" } -export function LeadTimeForChangeChart({ data }) { +export function LeadTimeForChangeChart( { appName } ) { const { resolvedTheme } = useTheme() const animationDuration = 1000 @@ -43,12 +36,14 @@ export function LeadTimeForChangeChart({ data }) { const strokeRollingAverage = '#8b5cf6' // Violet 500 const strokeGoal = '#f59e0b' // Amber 500 - // Calculate the mean - const averages = data.map(element => { - return element.rollingAverage - }) - - const chartMean = calculateMean(averages) + const [ltfcData, setLtfcData] = useState('') + useEffect(() => { + fetch(`${process.env.NEXT_PUBLIC_PELORUS_API_URL}/sdp/lead_time_for_change/${appName}/data?range=1w`) + .then((test) => test.json()) + .then((response) => { + setLtfcData(response) + }) + }, [appName]) // Reports const [reportLeadTimeForChangeData, setReportLeadTimeForChangeData] = useState(null) @@ -59,12 +54,6 @@ export function LeadTimeForChangeChart({ data }) { setShowReportLeadTimeForChangeData(true) } - // Anomaly detection - const showAnomalyWarning = data.some((day) => { - if (day.rollingAverage < day.expectedRange[0] || day.rollingAverage > day.expectedRange[1]) { return true } - return false - }) - const customAnomalyLabel = props => { return ( @@ -76,48 +65,16 @@ export function LeadTimeForChangeChart({ data }) { return ( <> - - -
- -
- - Lead time for change - - - - {parseFloat(chartMean).toFixed(2)} days - 16% - {showAnomalyWarning && Anomaly detected} - -
-
-
- - -
-
- - - - - - `${tick}d`} /> - } cursor={{ stroke: strokeCursor }} /> - - - - - {data.map((day, index) => ( - day.rollingAverage < day.expectedRange[0] || day.rollingAverage > day.expectedRange[1] && ( - - ) - ))} - - - -
- + + + + + + } cursor={{ stroke: strokeCursor }} /> + + + + {/* */} ) } \ No newline at end of file diff --git a/components/lead-time-for-change/iso-chart.jsx b/components/lead-time-for-change/iso-chart.jsx deleted file mode 100644 index 8580309..0000000 --- a/components/lead-time-for-change/iso-chart.jsx +++ /dev/null @@ -1,92 +0,0 @@ -'use client' - -import { useState } from 'react' -import { useTheme } from "next-themes" -import { format } from 'date-fns' -import { XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, ComposedChart, Area, Line, ReferenceLine } from 'recharts' -import { LeadTimeForChangeTooltip } from './tooltip' - -const dateFormatter = date => { - return format(new Date(date), "MMM d") -} - -const calculateMean = data => { - if (data.length < 1) { - return - } - return data.reduce((prev, current) => prev + current) / data.length -} - -export function IsolatedLeadTimeForChangeChart({ data }) { - - const { resolvedTheme } = useTheme() - const animationDuration = 1000 - - // Chart colors - const strokeGrid = '#d4d4d4' // Neutral 300 - const strokeGridDark = '#404040' // Neutral 700 - const strokeCursor = '#8b5cf6' // Violet 500 - const strokeActiveDot = '#ffffff' // White - const strokeActiveDotDark = '#171717' // Neutral 900 - const strokeAnomaly = '#f97316' // Orange 500 - - const fillRange = '#ddd6fe' // Violet 200 - const fillRangeDark = '#4c1d95' // Violet 900 - const strokeRange = '#8b5cf6' // Violet 500 - const strokeRollingAverage = '#8b5cf6' // Violet 500 - const strokeGoal = '#f59e0b' // Amber 500 - - // Calculate the mean - const averages = data.map(element => { - return element.rollingAverage - }) - - const chartMean = calculateMean(averages) - - // Reports - const [reportLeadTimeForChangeData, setReportLeadTimeForChangeData] = useState(null) - const [showReportLeadTimeForChangeData, setShowReportLeadTimeForChangeData] = useState(false) - - function handleChartClick(event) { - setReportLeadTimeForChangeData(event) - setShowReportLeadTimeForChangeData(true) - } - - // Anomaly detection - const showAnomalyWarning = data.some((day) => { - if (day.rollingAverage < day.expectedRange[0] || day.rollingAverage > day.expectedRange[1]) { return true } - return false - }) - - const customAnomalyLabel = props => { - return ( - - - - - ) - } - - return ( - <> - - - - - `${tick}d`} /> - } cursor={{ stroke: strokeCursor }} /> - - - - - {data.map((day, index) => ( - day.rollingAverage < day.expectedRange[0] || day.rollingAverage > day.expectedRange[1] && ( - - ) - ))} - - - {/* */} - - ) -} \ No newline at end of file diff --git a/components/lead-time-for-change/tooltip.jsx b/components/lead-time-for-change/tooltip.jsx index 03cf348..ed54d80 100644 --- a/components/lead-time-for-change/tooltip.jsx +++ b/components/lead-time-for-change/tooltip.jsx @@ -18,17 +18,27 @@ function toSentence(str) { }).join("") } +function dateFormatter(epoch) { + const date = new Date(epoch * 1000) + return format(new Date(date), "MMM d, y") +} + +function dayFormatter (seconds) { + const days = parseFloat(seconds / 86400).toFixed(2) + return days +} + export const LeadTimeForChangeTooltip = ({ active, payload, label }) => { if (active && payload && payload.length) { return (
-
{format(new Date(label), "MMM d, y")}
+
{dateFormatter(label)}
{payload.map((data, index) => (
{toSentence(data.name)}
-
{data.value instanceof Array ? data.value.join('-') : data.value} days
+
{dayFormatter(data.value)} days
))}
From 9ecd599d54bb7a59754e6b911ac7850a1fb74596 Mon Sep 17 00:00:00 2001 From: Charro Gruver Date: Thu, 19 Oct 2023 17:32:05 +0000 Subject: [PATCH 10/42] sort dates in LTFC --- components/lead-time-for-change/chart.jsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/components/lead-time-for-change/chart.jsx b/components/lead-time-for-change/chart.jsx index 2b67d31..3f539e9 100644 --- a/components/lead-time-for-change/chart.jsx +++ b/components/lead-time-for-change/chart.jsx @@ -39,9 +39,9 @@ export function LeadTimeForChangeChart( { appName } ) { const [ltfcData, setLtfcData] = useState('') useEffect(() => { fetch(`${process.env.NEXT_PUBLIC_PELORUS_API_URL}/sdp/lead_time_for_change/${appName}/data?range=1w`) - .then((test) => test.json()) - .then((response) => { - setLtfcData(response) + .then((response) => response.json()).then((data) => data.sort((d1, d2) => (d1.timestamp > d2.timestamp) ? 1 : (d1.timestamp < d2.timestamp) ? -1 : 0 )) + .then((sortedData) => { + setLtfcData(sortedData) }) }, [appName]) From 6912369b0f22c6f6db0c6181c6dadeb389bd8337 Mon Sep 17 00:00:00 2001 From: Charro Gruver Date: Thu, 19 Oct 2023 18:45:16 +0000 Subject: [PATCH 11/42] wip for ltfc table --- components/dashboard.jsx | 2 +- components/lead-time-for-change/chart.jsx | 1 - components/lead-time-for-change/table.jsx | 307 +++------------------- 3 files changed, 34 insertions(+), 276 deletions(-) diff --git a/components/dashboard.jsx b/components/dashboard.jsx index 6fc15ea..225e1d4 100644 --- a/components/dashboard.jsx +++ b/components/dashboard.jsx @@ -126,7 +126,7 @@ export function Dashboard({ data, appList }) { Pull requests

- +
diff --git a/components/lead-time-for-change/chart.jsx b/components/lead-time-for-change/chart.jsx index 3f539e9..50f9049 100644 --- a/components/lead-time-for-change/chart.jsx +++ b/components/lead-time-for-change/chart.jsx @@ -74,7 +74,6 @@ export function LeadTimeForChangeChart( { appName } ) { - {/* */} ) } \ No newline at end of file diff --git a/components/lead-time-for-change/table.jsx b/components/lead-time-for-change/table.jsx index 7190e91..af9969b 100644 --- a/components/lead-time-for-change/table.jsx +++ b/components/lead-time-for-change/table.jsx @@ -1,5 +1,6 @@ 'use client' +import { useState, useEffect } from 'react' import { Table, TableBody, @@ -11,288 +12,46 @@ import { } from "@/components/ui/table" import { Calendar, GitBranch, GitCommit, Globe, Timer } from "lucide-react" -export function LeadTimeForChangeTable({ data }) { +export function LeadTimeForChangeTable({ appName }) { + + const [ltfcData, setLtfcData] = useState('') + useEffect(() => { + fetch(`${process.env.NEXT_PUBLIC_PELORUS_API_URL}/sdp/lead_time_for_change/${appName}/data?range=1w`) + .then((response) => response.json()).then((data) => data.sort((d1, d2) => (d1.timestamp > d2.timestamp) ? 1 : (d1.timestamp < d2.timestamp) ? -1 : 0 )) + .then((sortedData) => { + setLtfcData(sortedData) + }) + }, [appName]) + return ( - First commit - Deployment + Commit SHA + Commit Date Lead time - Status - - -
- Feature/authentication -
-
- 0577e50 Commit name description -
-
Jul 19, 2023
-
- -
- Main -
-
- project-crimson-prototype-q6yb8whv -
-
Jul 26, 2023
-
- -
- - 8 days -
-
-
Ready
-
- - -
- Feature/authentication -
-
- 0577e50 Commit name description -
-
Jul 19, 2023
-
- -
- Main -
-
- project-crimson-prototype-q6yb8whv -
-
Jul 26, 2023
-
- -
- - 8 days -
-
-
Ready
-
- - -
- Feature/authentication -
-
- 0577e50 Commit name description -
-
Jul 19, 2023
-
- -
- Main -
-
- project-crimson-prototype-q6yb8whv -
-
Jul 26, 2023
-
- -
- - 8 days -
-
-
Ready
-
- - -
- Feature/authentication -
-
- 0577e50 Commit name description -
-
Jul 19, 2023
-
- -
- Main -
-
- project-crimson-prototype-q6yb8whv -
-
Jul 26, 2023
-
- -
- - 8 days -
-
-
Ready
-
- - -
- Feature/authentication -
-
- 0577e50 Commit name description -
-
Jul 19, 2023
-
- -
- Main -
-
- project-crimson-prototype-q6yb8whv -
-
Jul 26, 2023
-
- -
- - 8 days -
-
-
Ready
-
- - -
- Feature/authentication -
-
- 0577e50 Commit name description -
-
Jul 19, 2023
-
- -
- Main -
-
- project-crimson-prototype-q6yb8whv -
-
Jul 26, 2023
-
- -
- - 8 days -
-
-
Ready
-
- - -
- Feature/authentication -
-
- 0577e50 Commit name description -
-
Jul 19, 2023
-
- -
- Main -
-
- project-crimson-prototype-q6yb8whv -
-
Jul 26, 2023
-
- -
- - 8 days -
-
-
Ready
-
- - -
- Feature/authentication -
-
- 0577e50 Commit name description -
-
Jul 19, 2023
-
- -
- Main -
-
- project-crimson-prototype-q6yb8whv -
-
Jul 26, 2023
-
- -
- - 8 days -
-
-
Ready
-
- - -
- Feature/authentication -
-
- 0577e50 Commit name description -
-
Jul 19, 2023
-
- -
- Main -
-
- project-crimson-prototype-q6yb8whv -
-
Jul 26, 2023
-
- -
- - 8 days -
-
-
Ready
-
- - -
- Feature/authentication -
-
- 0577e50 Commit name description -
-
Jul 19, 2023
-
- -
- Main -
-
- project-crimson-prototype-q6yb8whv -
-
Jul 26, 2023
-
- -
- - 8 days -
-
-
Ready
-
+ {/* {ltfcData.map((ltfc) => ( + + +
+ {ltfc.commit} +
+
+ +
+ {ltfc.timestamp} +
+
+ +
+ {ltfc.lead_time} +
+
+
+ ))} */}
) From e1d7b4593823235480fa0683a8ec9055937e2387 Mon Sep 17 00:00:00 2001 From: Charro Gruver Date: Fri, 20 Oct 2023 12:55:30 +0000 Subject: [PATCH 12/42] add table to LTFC --- components/lead-time-for-change/chart.jsx | 2 +- components/lead-time-for-change/table.jsx | 23 +++++++++++++++++------ 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/components/lead-time-for-change/chart.jsx b/components/lead-time-for-change/chart.jsx index 50f9049..e1407bc 100644 --- a/components/lead-time-for-change/chart.jsx +++ b/components/lead-time-for-change/chart.jsx @@ -36,7 +36,7 @@ export function LeadTimeForChangeChart( { appName } ) { const strokeRollingAverage = '#8b5cf6' // Violet 500 const strokeGoal = '#f59e0b' // Amber 500 - const [ltfcData, setLtfcData] = useState('') + const [ltfcData, setLtfcData] = useState([]) useEffect(() => { fetch(`${process.env.NEXT_PUBLIC_PELORUS_API_URL}/sdp/lead_time_for_change/${appName}/data?range=1w`) .then((response) => response.json()).then((data) => data.sort((d1, d2) => (d1.timestamp > d2.timestamp) ? 1 : (d1.timestamp < d2.timestamp) ? -1 : 0 )) diff --git a/components/lead-time-for-change/table.jsx b/components/lead-time-for-change/table.jsx index af9969b..5a26d7d 100644 --- a/components/lead-time-for-change/table.jsx +++ b/components/lead-time-for-change/table.jsx @@ -11,10 +11,21 @@ import { TableRow, } from "@/components/ui/table" import { Calendar, GitBranch, GitCommit, Globe, Timer } from "lucide-react" +import { format } from 'date-fns' + +const dateFormatter = epoch => { + const date = new Date(epoch * 1000) + return format(new Date(date), "MMM d") +} + +const dayFormatter = seconds => { + const days = parseFloat(seconds / 86400).toFixed(2) + return days + "d" +} export function LeadTimeForChangeTable({ appName }) { - const [ltfcData, setLtfcData] = useState('') + const [ltfcData, setLtfcData] = useState( []) useEffect(() => { fetch(`${process.env.NEXT_PUBLIC_PELORUS_API_URL}/sdp/lead_time_for_change/${appName}/data?range=1w`) .then((response) => response.json()).then((data) => data.sort((d1, d2) => (d1.timestamp > d2.timestamp) ? 1 : (d1.timestamp < d2.timestamp) ? -1 : 0 )) @@ -33,25 +44,25 @@ export function LeadTimeForChangeTable({ appName }) { - {/* {ltfcData.map((ltfc) => ( + {ltfcData.map (({ commit, timestamp, lead_time }) => (
- {ltfc.commit} + {commit}
- {ltfc.timestamp} + { dateFormatter(timestamp) }
- {ltfc.lead_time} + {dayFormatter(lead_time)} days
- ))} */} + ))}
) From 13dcf456508ffbac0277ec9a9d82043692e84ca2 Mon Sep 17 00:00:00 2001 From: Eric Sauer Date: Fri, 20 Oct 2023 13:19:04 +0000 Subject: [PATCH 13/42] Add percent change badges for ltfc and df --- components/deployment-frequency/tab-trigger.jsx | 5 +++-- components/lead-time-for-change/tab-trigger.jsx | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/components/deployment-frequency/tab-trigger.jsx b/components/deployment-frequency/tab-trigger.jsx index f50e160..f43c0d2 100644 --- a/components/deployment-frequency/tab-trigger.jsx +++ b/components/deployment-frequency/tab-trigger.jsx @@ -1,6 +1,6 @@ 'use client' -import { Rocket, ArrowDown } from 'lucide-react' +import { Rocket, ArrowDown, ArrowUp } from 'lucide-react' import { Badge } from '@/components/ui/badge' import { InfoTooltip } from '@/components/info-tooltip' import { DeploymentFrequencyRating } from '@/components/deployment-frequency/rating' @@ -36,6 +36,7 @@ export function DeploymentFrequencyTabTrigger({ data, appName }) { if (!response) return

No cfr data!

const chartMean = response.df + const percentChange = Math.round((response.df / response.last) * 100) // Anomaly detection const showAnomalyWarning = data.some((day) => { @@ -56,7 +57,7 @@ export function DeploymentFrequencyTabTrigger({ data, appName }) {
{parseFloat(chartMean).toFixed(2)} per day - 16% + {percentChange}%
diff --git a/components/lead-time-for-change/tab-trigger.jsx b/components/lead-time-for-change/tab-trigger.jsx index 9d98d3f..84394c4 100644 --- a/components/lead-time-for-change/tab-trigger.jsx +++ b/components/lead-time-for-change/tab-trigger.jsx @@ -36,6 +36,7 @@ export function LeadTimeForChangeTabTrigger({ data, appName }) { if (!response) return

No cfr data!

const chartMean = response.ltfc / 86400 + const percentChange = Math.round((1 - (response.ltfc / response.last)) * 100) // Anomaly detection const showAnomalyWarning = data.some((day) => { @@ -56,7 +57,7 @@ export function LeadTimeForChangeTabTrigger({ data, appName }) {
{parseFloat(chartMean).toFixed(2)} days - 16% + {percentChange}%
From 1cfae5e34988d20f556bd87741ca29128af9c8a6 Mon Sep 17 00:00:00 2001 From: Charro Gruver Date: Fri, 20 Oct 2023 14:32:34 +0000 Subject: [PATCH 14/42] fix useState inits, and fix missing key in table --- components/change-failure-rate/tab-trigger.jsx | 2 +- components/deployment-frequency/tab-trigger.jsx | 2 +- components/lead-time-for-change/tab-trigger.jsx | 2 +- components/lead-time-for-change/table.jsx | 4 ++-- components/mean-time-to-recovery/tab-trigger.jsx | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/components/change-failure-rate/tab-trigger.jsx b/components/change-failure-rate/tab-trigger.jsx index 4ab5fdf..1a851e3 100644 --- a/components/change-failure-rate/tab-trigger.jsx +++ b/components/change-failure-rate/tab-trigger.jsx @@ -20,7 +20,7 @@ export function ChangeFailureRateTabTrigger({ data, appName }) { return element.rollingAverage }) - const [response, setResponse] = useState('') + const [response, setResponse] = useState([]) const [isLoading, setLoading] = useState(true) useEffect(() => { diff --git a/components/deployment-frequency/tab-trigger.jsx b/components/deployment-frequency/tab-trigger.jsx index f43c0d2..664f361 100644 --- a/components/deployment-frequency/tab-trigger.jsx +++ b/components/deployment-frequency/tab-trigger.jsx @@ -20,7 +20,7 @@ export function DeploymentFrequencyTabTrigger({ data, appName }) { return element.rollingAverage }) - const [response, setResponse] = useState('') + const [response, setResponse] = useState([]) const [isLoading, setLoading] = useState(true) useEffect(() => { diff --git a/components/lead-time-for-change/tab-trigger.jsx b/components/lead-time-for-change/tab-trigger.jsx index 84394c4..bbc6e7e 100644 --- a/components/lead-time-for-change/tab-trigger.jsx +++ b/components/lead-time-for-change/tab-trigger.jsx @@ -20,7 +20,7 @@ export function LeadTimeForChangeTabTrigger({ data, appName }) { return element.rollingAverage }) - const [response, setResponse] = useState('') + const [response, setResponse] = useState([]) const [isLoading, setLoading] = useState(true) useEffect(() => { diff --git a/components/lead-time-for-change/table.jsx b/components/lead-time-for-change/table.jsx index 5a26d7d..5f11d68 100644 --- a/components/lead-time-for-change/table.jsx +++ b/components/lead-time-for-change/table.jsx @@ -25,7 +25,7 @@ const dayFormatter = seconds => { export function LeadTimeForChangeTable({ appName }) { - const [ltfcData, setLtfcData] = useState( []) + const [ltfcData, setLtfcData] = useState([]) useEffect(() => { fetch(`${process.env.NEXT_PUBLIC_PELORUS_API_URL}/sdp/lead_time_for_change/${appName}/data?range=1w`) .then((response) => response.json()).then((data) => data.sort((d1, d2) => (d1.timestamp > d2.timestamp) ? 1 : (d1.timestamp < d2.timestamp) ? -1 : 0 )) @@ -45,7 +45,7 @@ export function LeadTimeForChangeTable({ appName }) { {ltfcData.map (({ commit, timestamp, lead_time }) => ( - +
{commit} diff --git a/components/mean-time-to-recovery/tab-trigger.jsx b/components/mean-time-to-recovery/tab-trigger.jsx index 50466e5..4dee7cd 100644 --- a/components/mean-time-to-recovery/tab-trigger.jsx +++ b/components/mean-time-to-recovery/tab-trigger.jsx @@ -20,7 +20,7 @@ export function MeanTimeToRecoveryTabTrigger({ data, appName }) { return element.rollingAverage }) - const [response, setResponse] = useState('') + const [response, setResponse] = useState([]) const [isLoading, setLoading] = useState(true) useEffect(() => { From 1852c188d738ea9c6fd5dbe201ddc4f3cb7fc1b0 Mon Sep 17 00:00:00 2001 From: Eric Sauer Date: Fri, 20 Oct 2023 14:52:46 +0000 Subject: [PATCH 15/42] Add a github action to build the app into a NodeJS Image --- .github/workflows/build-image.yaml | 47 ++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 .github/workflows/build-image.yaml diff --git a/.github/workflows/build-image.yaml b/.github/workflows/build-image.yaml new file mode 100644 index 0000000..d61ed96 --- /dev/null +++ b/.github/workflows/build-image.yaml @@ -0,0 +1,47 @@ +--- + +name: CD + +on: + push: + + +jobs: + + build_image: + + # The type of runner that the job will run on + runs-on: ubuntu-latest + +# This workflow builds a container image of a java +# application using the source to image build strategy, +# and pushes the image to quay.io. + env: + IMAGE_NAME: my-java-app + TAGS: v1 ${{ github.sha }} + + steps: + + - name: Checkout + uses: actions/checkout@v2 + + # Setup S2i and Build container image + - name: Setup and Build + id: build_image + uses: redhat-actions/s2i-build@v2 + with: + path_context: '.' + # Builder image for a java project + builder_image: 'registry.access.redhat.com/ubi9/nodejs-18:1' + image: ${{ env.IMAGE_NAME }} + tags: ${{ env.TAGS }} + + # Push Image to Quay registry + - name: Push To Quay Action + uses: redhat-actions/push-to-registry@v2 + with: + image: ${{ steps.build_image.outputs.image }} + tags: ${{ steps.build_image.outputs.tags }} + registry: quay.io/${{ secrets.QUAY_USERNAME }} + username: ${{ secrets.QUAY_USERNAME }} + password: ${{ secrets.QUAY_PASSWORD }} \ No newline at end of file From 717cd3120231bd60e348b52e711fe456bfc08ae3 Mon Sep 17 00:00:00 2001 From: Eric Sauer Date: Fri, 20 Oct 2023 15:01:10 +0000 Subject: [PATCH 16/42] Update trigger --- .github/workflows/build-image.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/build-image.yaml b/.github/workflows/build-image.yaml index d61ed96..e30f731 100644 --- a/.github/workflows/build-image.yaml +++ b/.github/workflows/build-image.yaml @@ -2,8 +2,7 @@ name: CD -on: - push: +on: push jobs: From 4b47c803ca5a3de881432ed49fe245f9ea32c28a Mon Sep 17 00:00:00 2001 From: Eric Sauer Date: Fri, 20 Oct 2023 15:04:57 +0000 Subject: [PATCH 17/42] trigger on pr --- .github/workflows/build-image.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-image.yaml b/.github/workflows/build-image.yaml index e30f731..6de0187 100644 --- a/.github/workflows/build-image.yaml +++ b/.github/workflows/build-image.yaml @@ -2,7 +2,7 @@ name: CD -on: push +on: [push, pull_request] jobs: From eec8339ae7715d5cc0766f1576778efd89816e74 Mon Sep 17 00:00:00 2001 From: Eric Sauer Date: Fri, 20 Oct 2023 15:12:28 +0000 Subject: [PATCH 18/42] Change namespace --- .github/workflows/build-image.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-image.yaml b/.github/workflows/build-image.yaml index 6de0187..962abc1 100644 --- a/.github/workflows/build-image.yaml +++ b/.github/workflows/build-image.yaml @@ -16,8 +16,8 @@ jobs: # application using the source to image build strategy, # and pushes the image to quay.io. env: - IMAGE_NAME: my-java-app - TAGS: v1 ${{ github.sha }} + IMAGE_NAME: pelorus-dashboard + TAGS: ${{ github.sha }} steps: @@ -41,6 +41,6 @@ jobs: with: image: ${{ steps.build_image.outputs.image }} tags: ${{ steps.build_image.outputs.tags }} - registry: quay.io/${{ secrets.QUAY_USERNAME }} + registry: quay.io/${{ env.QUAY_NAMESPACE }} username: ${{ secrets.QUAY_USERNAME }} password: ${{ secrets.QUAY_PASSWORD }} \ No newline at end of file From 4b2d0814931454bb799210bf7d7a9758c2a4f82b Mon Sep 17 00:00:00 2001 From: Eric Sauer Date: Fri, 20 Oct 2023 15:24:56 +0000 Subject: [PATCH 19/42] Try vars instead of env --- .github/workflows/build-image.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-image.yaml b/.github/workflows/build-image.yaml index 962abc1..fab2078 100644 --- a/.github/workflows/build-image.yaml +++ b/.github/workflows/build-image.yaml @@ -41,6 +41,6 @@ jobs: with: image: ${{ steps.build_image.outputs.image }} tags: ${{ steps.build_image.outputs.tags }} - registry: quay.io/${{ env.QUAY_NAMESPACE }} + registry: quay.io/${{ vars.QUAY_NAMESPACE }} username: ${{ secrets.QUAY_USERNAME }} password: ${{ secrets.QUAY_PASSWORD }} \ No newline at end of file From 17c9f097fe32cbfb12cd795aaff899b6fcb1586f Mon Sep 17 00:00:00 2001 From: Eric Sauer Date: Fri, 20 Oct 2023 15:32:02 +0000 Subject: [PATCH 20/42] Add dev tag --- .github/workflows/build-image.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-image.yaml b/.github/workflows/build-image.yaml index fab2078..7111640 100644 --- a/.github/workflows/build-image.yaml +++ b/.github/workflows/build-image.yaml @@ -17,7 +17,7 @@ jobs: # and pushes the image to quay.io. env: IMAGE_NAME: pelorus-dashboard - TAGS: ${{ github.sha }} + TAGS: dev ${{ github.sha }} steps: From d7b9c0ccc65218dca5251d6deb0e3397ff112ef6 Mon Sep 17 00:00:00 2001 From: Charro Gruver Date: Fri, 20 Oct 2023 19:52:56 +0000 Subject: [PATCH 21/42] wip add openshift oauth --- app/api/auth/[...nextauth]/options.js | 8 ++ app/dashboard-old/page.js | 52 --------- app/page.js | 2 + components/button-signin-openshift.jsx | 31 ++++++ components/change-failure-rate/chart.jsx | 123 --------------------- components/deployment-frequency/chart.jsx | 123 --------------------- components/mean-time-to-recovery/chart.jsx | 123 --------------------- public/logos/openshift-logo.svg | 1 + 8 files changed, 42 insertions(+), 421 deletions(-) delete mode 100644 app/dashboard-old/page.js create mode 100644 components/button-signin-openshift.jsx delete mode 100644 components/change-failure-rate/chart.jsx delete mode 100644 components/deployment-frequency/chart.jsx delete mode 100644 components/mean-time-to-recovery/chart.jsx create mode 100644 public/logos/openshift-logo.svg diff --git a/app/api/auth/[...nextauth]/options.js b/app/api/auth/[...nextauth]/options.js index 263bd5b..4325fb2 100644 --- a/app/api/auth/[...nextauth]/options.js +++ b/app/api/auth/[...nextauth]/options.js @@ -5,6 +5,14 @@ import KeycloakProvider from "next-auth/providers/keycloak"; export const options = { providers: [ + { + id: "openshift", + name: "OpenShift", + type: "oauth", + wellKnown: "https://oauth-openshift.openshift-authentication.svc.cluster.local/.well-known/openid-configuration", + clientId: process.env.OPENSHIFT_ID, + clientSecret: process.env.OPENSHIFT_SECRET + }, KeycloakProvider({ clientId: process.env.KEYCLOAK_ID, clientSecret: process.env.KEYCLOAK_SECRET, diff --git a/app/dashboard-old/page.js b/app/dashboard-old/page.js deleted file mode 100644 index 5cbf0ac..0000000 --- a/app/dashboard-old/page.js +++ /dev/null @@ -1,52 +0,0 @@ -import { UserNav } from '@/components/user-nav' -import { NotificationsToggle } from '@/components/notifications-toggle' -import { ThemeToggle } from '@/components/theme-toggle' -import { SearchToggle } from '@/components/search-toggle' -import { getServerSession } from 'next-auth' -import { options } from '../api/auth/[...nextauth]/options' -import { redirect } from 'next/navigation' -import { AppLogo } from '@/components/app-logo' -import { Dashboard } from '@/components/dashboard' - -export const metadata = { - title: 'Dashboard - Red Hat Developer Intelligence' -} - -async function getData() { - const response = await fetch('https://api.npoint.io/ee46484033b5d4d4658e') - - if (!response.ok) { - throw new Error('Failed to fetch data') - } - - return response.json() -} - -export default async function Page() { - - const session = await getServerSession(options) - const data = await getData() - - if (!session) { - redirect('/') - } - - return ( -
-
- -

Red Hat Developer Intelligence

-
-
- - - -
- -
-
- - -
- ) -} \ No newline at end of file diff --git a/app/page.js b/app/page.js index 4f2df56..a40832b 100644 --- a/app/page.js +++ b/app/page.js @@ -2,6 +2,7 @@ import Link from 'next/link' import ButtonSignInGithub from '@/components/button-signin-github' import ButtonSignInGoogle from '@/components/button-signin-google' import ButtonSignInKeyCloak from '@/components/button-signin-keycloak' +import ButtonSignInOpenShift from '@/components/button-signin-openshift' import SignInPhoto from '@/public/images/image--sign-in.jpg' import BlurImage from '@/components/blur-image' import { AppLogo } from '@/components/app-logo' @@ -70,6 +71,7 @@ export default async function AuthenticationPage() {
+

By signing in, you agree to our terms of service and privacy policy.

)} diff --git a/components/button-signin-openshift.jsx b/components/button-signin-openshift.jsx new file mode 100644 index 0000000..dd6ab6c --- /dev/null +++ b/components/button-signin-openshift.jsx @@ -0,0 +1,31 @@ +'use client' + +import { useState } from 'react' +import { signIn } from 'next-auth/react' +import { Button } from '@/components/ui/button' +import LogoOpenShift from '@/public/logos/openshift-logo.svg' +import { Loader2 } from "lucide-react" + +export default function ButtonSignInOpenShift() { + + const [loading, setLoading] = useState(false) + + function handleClick() { + setLoading(loading => !loading) + signIn('openshift', { callbackUrl: '/dashboard' }) + } + + return ( + + ) +} diff --git a/components/change-failure-rate/chart.jsx b/components/change-failure-rate/chart.jsx deleted file mode 100644 index af8b5f4..0000000 --- a/components/change-failure-rate/chart.jsx +++ /dev/null @@ -1,123 +0,0 @@ -'use client' - -import { useState } from 'react' -import { format } from 'date-fns' -import { useTheme } from "next-themes" -import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, } from '@/components/ui/card' -import { Badge } from '@/components/ui/badge' -import { XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, ComposedChart, Legend, Area, Line, Label, ReferenceLine } from 'recharts' -import { MoveRight, TrendingUp, TrendingDown, XCircle, AlertTriangle } from 'lucide-react' -import { InfoTooltip } from '@/components/info-tooltip' -import { ChangeFailureRateTooltip } from './tooltip' -import { ChangeFailureRateRating } from './rating' -import { ChangeFailureRateOptions } from './options' -import { ChangeFailureRateReport } from './report' - -const dateFormatter = date => { - return format(new Date(date), "MMM d") -} - -const calculateMean = data => { - if (data.length < 1) { - return - } - return data.reduce((prev, current) => prev + current) / data.length -} - -export function ChangeFailureRateChart({ data }) { - - const { resolvedTheme } = useTheme() - const animationDuration = 1000 - - // Chart colors - const strokeGrid = '#d4d4d4' // Neutral 300 - const strokeGridDark = '#404040' // Neutral 700 - const strokeCursor = '#f43f5e' // Rose 500 - const strokeActiveDot = '#ffffff' // White - const strokeActiveDotDark = '#171717' // Neutral 900 - const strokeAnomaly = '#f97316' // Orange 500 - - const fillRange = '#fecdd3' // Rose 200 - const fillRangeDark = '#881337' // Rose 900 - const strokeRange = '#f43f5e' // Rose 500 - const strokeRollingAverage = '#f43f5e' // Rose 500 - const strokeGoal = '#f59e0b' // Amber 500 - - // Calculate the mean - const averages = data.map(element => { - return element.rollingAverage - }) - - const chartMean = calculateMean(averages) - - // Reports - const [reportChangeFailureRateData, setReportChangeFailureRateData] = useState(null) - const [showReportChangeFailureRateData, setShowReportChangeFailureRateData] = useState(false) - - function handleChartClick(event) { - setReportChangeFailureRateData(event) - setShowReportChangeFailureRateData(true) - } - - // Anomaly detection - const showAnomalyWarning = data.some((day) => { - if (day.rollingAverage < day.expectedRange[0] || day.rollingAverage > day.expectedRange[1]) { return true } - return false - }) - - const customAnomalyLabel = props => { - return ( - - - - - ) - } - - return ( - <> - - -
- -
- - Change failure rate - - - - {parseFloat(chartMean).toFixed(2)}% - 0% - {showAnomalyWarning && Anomaly detected} - -
-
-
- - -
-
- - - - - - `${tick}%`} /> - } cursor={{ stroke: strokeCursor }} /> - - - - - {data.map((day, index) => ( - day.rollingAverage < day.expectedRange[0] || day.rollingAverage > day.expectedRange[1] && ( - - ) - ))} - - - -
- - - ) -} \ No newline at end of file diff --git a/components/deployment-frequency/chart.jsx b/components/deployment-frequency/chart.jsx deleted file mode 100644 index 682d1d0..0000000 --- a/components/deployment-frequency/chart.jsx +++ /dev/null @@ -1,123 +0,0 @@ -'use client' - -import { useState } from 'react' -import { useTheme } from "next-themes" -import { format } from 'date-fns' -import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, } from '@/components/ui/card' -import { Badge } from '@/components/ui/badge' -import { XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, ComposedChart, Legend, Area, Line, Label, ReferenceLine } from 'recharts' -import { MoveRight, TrendingUp, TrendingDown, Rocket } from 'lucide-react' -import { InfoTooltip } from '@/components/info-tooltip' -import { DeploymentFrequencyTooltip } from './tooltip' -import { DeploymentFrequencyRating } from './rating' -import { DeploymentFrequencyOptions } from './options' -import { DeploymentFrequencyReport } from "./report" - -const dateFormatter = date => { - return format(new Date(date), "MMM d") -} - -const calculateMean = data => { - if (data.length < 1) { - return - } - return data.reduce((prev, current) => prev + current) / data.length -} - -export function DeploymentFrequencyChart({ data }) { - - const { resolvedTheme } = useTheme() - const animationDuration = 1000 - - // Chart colors - const strokeGrid = '#d4d4d4' // Neutral 300 - const strokeGridDark = '#404040' // Neutral 700 - const strokeCursor = '#3b82f6' // Blue 500 - const strokeActiveDot = '#ffffff' // White - const strokeActiveDotDark = '#171717' // Neutral 900 - const strokeAnomaly = '#f97316' // Orange 500 - - const fillRange = '#bfdbfe' // Blue 200 - const fillRangeDark = '#1e3a8a' // Blue 900 - const strokeRange = '#3b82f6' // Blue 500 - const strokeRollingAverage = '#3b82f6' // Blue 500 - const strokeGoal = '#f59e0b' // Amber 500 - - // Calculate the mean - const averages = data.map(element => { - return element.rollingAverage - }) - - const chartMean = calculateMean(averages) - - // Reports - const [reportDeploymentFrequencyData, setReportDeploymentFrequencyData] = useState(null) - const [showReportDeploymentFrequencyData, setShowReportDeploymentFrequencyData] = useState(false) - - function handleChartClick(event) { - setReportDeploymentFrequencyData(event) - setShowReportDeploymentFrequencyData(true) - } - - // Anomaly detection - const showAnomalyWarning = data.some((day) => { - if (day.rollingAverage < day.expectedRange[0] || day.rollingAverage > day.expectedRange[1]) { return true } - return false - }) - - const customAnomalyLabel = props => { - return ( - - - - - ) - } - - return ( - <> - - -
- -
- - Deployment frequency - - - - {parseFloat(chartMean).toFixed(2)} per day - 16% - {showAnomalyWarning && Anomaly detected} - -
-
-
- - -
-
- - - - - - - } cursor={{ stroke: strokeCursor }} /> - - - - - {data.map((day, index) => ( - day.rollingAverage < day.expectedRange[0] || day.rollingAverage > day.expectedRange[1] && ( - - ) - ))} - - - -
- - - ) -} \ No newline at end of file diff --git a/components/mean-time-to-recovery/chart.jsx b/components/mean-time-to-recovery/chart.jsx deleted file mode 100644 index e20b19a..0000000 --- a/components/mean-time-to-recovery/chart.jsx +++ /dev/null @@ -1,123 +0,0 @@ -'use client' - -import { useState } from 'react' -import { useTheme } from "next-themes" -import { format } from 'date-fns' -import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, } from '@/components/ui/card' -import { Badge } from '@/components/ui/badge' -import { XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, ComposedChart, Legend, Area, Line, Label, ReferenceLine } from 'recharts' -import { MoveRight, TrendingUp, TrendingDown, PackageCheck } from 'lucide-react' -import { InfoTooltip } from '@/components/info-tooltip' -import { MeanTimeToRecoveryTooltip } from './tooltip' -import { MeanTimeToRecoveryRating } from './rating' -import { MeanTimeToRecoveryOptions } from './options' -import { MeanTimeToRecoveryReport } from "./report" - -const dateFormatter = date => { - return format(new Date(date), "MMM d") -} - -const calculateMean = data => { - if (data.length < 1) { - return - } - return data.reduce((prev, current) => prev + current) / data.length -} - -export function MeanTimeToRecoveryChart({ data }) { - - const { resolvedTheme } = useTheme() - const animationDuration = 1000 - - // Chart colors - const strokeGrid = '#d4d4d4' // Neutral 300 - const strokeGridDark = '#404040' // Neutral 700 - const strokeCursor = '#10b981' // Emerald 500 - const strokeActiveDot = '#ffffff' // White - const strokeActiveDotDark = '#171717' // Neutral 900 - const strokeAnomaly = '#f97316' // Orange 500 - - const fillRange = '#a7f3d0' // Emerald 200 - const fillRangeDark = '#064e3b' // Emerald 900 - const strokeRange = '#10b981' // Emerald 500 - const strokeRollingAverage = '#10b981' // Emerald 500 - const strokeGoal = '#f59e0b' // Amber 500 - - // Calculate the mean - const averages = data.map(element => { - return element.rollingAverage - }) - - const chartMean = calculateMean(averages) - - // Reports - const [reportMeanTimeToRecoveryData, setReportMeanTimeToRecoveryData] = useState(null) - const [showReportMeanTimeToRecoveryData, setShowReportMeanTimeToRecoveryData] = useState(false) - - function handleChartClick(event) { - setReportMeanTimeToRecoveryData(event) - setShowReportMeanTimeToRecoveryData(true) - } - - // Anomaly detection - const showAnomalyWarning = data.some((day) => { - if (day.rollingAverage < day.expectedRange[0] || day.rollingAverage > day.expectedRange[1]) { return true } - return false - }) - - const customAnomalyLabel = props => { - return ( - - - - - ) - } - - return ( - <> - - -
- -
- - Mean time to recovery - - - - {parseFloat(chartMean).toFixed(2)} days - 16% - {showAnomalyWarning && Anomaly detected} - -
-
-
- - -
-
- - - - - - `${tick}d`} /> - } cursor={{ stroke: strokeCursor }} /> - - - - - {data.map((day, index) => ( - day.rollingAverage < day.expectedRange[0] || day.rollingAverage > day.expectedRange[1] && ( - - ) - ))} - - - -
- - - ) -} \ No newline at end of file diff --git a/public/logos/openshift-logo.svg b/public/logos/openshift-logo.svg new file mode 100644 index 0000000..6a71806 --- /dev/null +++ b/public/logos/openshift-logo.svg @@ -0,0 +1 @@ + \ No newline at end of file From 535b55632636721ff0ac7eb8c68e49e79991d9fc Mon Sep 17 00:00:00 2001 From: Charro Gruver Date: Mon, 23 Oct 2023 15:04:41 +0000 Subject: [PATCH 22/42] wip OpenShift OAuth - hardcoded URLs --- app/api/auth/[...nextauth]/options.js | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/app/api/auth/[...nextauth]/options.js b/app/api/auth/[...nextauth]/options.js index 4325fb2..ab4702d 100644 --- a/app/api/auth/[...nextauth]/options.js +++ b/app/api/auth/[...nextauth]/options.js @@ -9,7 +9,18 @@ export const options = { id: "openshift", name: "OpenShift", type: "oauth", - wellKnown: "https://oauth-openshift.openshift-authentication.svc.cluster.local/.well-known/openid-configuration", + issuer: "https://oauth-openshift.apps.sno-2.clg.lab", + authorization: { + url: "https://oauth-openshift.apps.sno-2.clg.lab/oauth/authorize", + params: { scope: "user:full"} + }, + token: "https://oauth-openshift.apps.sno-2.clg.lab/oauth/token", + userinfo: "https://api.sno-2.clg.lab:6443/apis/user.openshift.io/v1/users/~", + async profile(profile) { + return { + id: profile.metadata.name, + name: profile.metadata.name + }}, clientId: process.env.OPENSHIFT_ID, clientSecret: process.env.OPENSHIFT_SECRET }, From a7c25f135471b3707bfdc43371d959addf69acc4 Mon Sep 17 00:00:00 2001 From: Charro Gruver Date: Mon, 23 Oct 2023 17:28:07 +0000 Subject: [PATCH 23/42] externalize OpenShift OAuth Config --- app/api/auth/[...nextauth]/options.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/app/api/auth/[...nextauth]/options.js b/app/api/auth/[...nextauth]/options.js index ab4702d..a423d04 100644 --- a/app/api/auth/[...nextauth]/options.js +++ b/app/api/auth/[...nextauth]/options.js @@ -9,13 +9,17 @@ export const options = { id: "openshift", name: "OpenShift", type: "oauth", - issuer: "https://oauth-openshift.apps.sno-2.clg.lab", + // issuer: "https://oauth-openshift.apps.sno-2.clg.lab", + issuer: process.env.OPENSHIFT_OAUTH_ISSUER_URL, authorization: { - url: "https://oauth-openshift.apps.sno-2.clg.lab/oauth/authorize", + // url: "https://oauth-openshift.apps.sno-2.clg.lab/oauth/authorize", + url: process.env.OPENSHIFT_OAUTH_AUTH_URL, params: { scope: "user:full"} }, - token: "https://oauth-openshift.apps.sno-2.clg.lab/oauth/token", - userinfo: "https://api.sno-2.clg.lab:6443/apis/user.openshift.io/v1/users/~", + // token: "https://oauth-openshift.apps.sno-2.clg.lab/oauth/token", + token: process.env.OPENSHIFT_OAUTH_TOKEN_URL, + // userinfo: "https://api.sno-2.clg.lab:6443/apis/user.openshift.io/v1/users/~", + userinfo: process.env.OPENSHIFT_OAUTH_USERINFO_URL, async profile(profile) { return { id: profile.metadata.name, From 8228fb39585e20b63f3f23a988932d4e705d6068 Mon Sep 17 00:00:00 2001 From: Charro Gruver Date: Mon, 23 Oct 2023 18:24:04 +0000 Subject: [PATCH 24/42] remove comments from options.js --- app/api/auth/[...nextauth]/options.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/app/api/auth/[...nextauth]/options.js b/app/api/auth/[...nextauth]/options.js index a423d04..c4a9a26 100644 --- a/app/api/auth/[...nextauth]/options.js +++ b/app/api/auth/[...nextauth]/options.js @@ -9,16 +9,12 @@ export const options = { id: "openshift", name: "OpenShift", type: "oauth", - // issuer: "https://oauth-openshift.apps.sno-2.clg.lab", issuer: process.env.OPENSHIFT_OAUTH_ISSUER_URL, authorization: { - // url: "https://oauth-openshift.apps.sno-2.clg.lab/oauth/authorize", url: process.env.OPENSHIFT_OAUTH_AUTH_URL, params: { scope: "user:full"} }, - // token: "https://oauth-openshift.apps.sno-2.clg.lab/oauth/token", token: process.env.OPENSHIFT_OAUTH_TOKEN_URL, - // userinfo: "https://api.sno-2.clg.lab:6443/apis/user.openshift.io/v1/users/~", userinfo: process.env.OPENSHIFT_OAUTH_USERINFO_URL, async profile(profile) { return { From 54fbf3e3857e4ea6e47a28ab1d1f8307c2391c8f Mon Sep 17 00:00:00 2001 From: Charro Gruver Date: Tue, 24 Oct 2023 18:20:15 +0000 Subject: [PATCH 25/42] update scope to user:info --- app/api/auth/[...nextauth]/options.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/api/auth/[...nextauth]/options.js b/app/api/auth/[...nextauth]/options.js index c4a9a26..ce36537 100644 --- a/app/api/auth/[...nextauth]/options.js +++ b/app/api/auth/[...nextauth]/options.js @@ -12,7 +12,7 @@ export const options = { issuer: process.env.OPENSHIFT_OAUTH_ISSUER_URL, authorization: { url: process.env.OPENSHIFT_OAUTH_AUTH_URL, - params: { scope: "user:full"} + params: { scope: "user:info"} }, token: process.env.OPENSHIFT_OAUTH_TOKEN_URL, userinfo: process.env.OPENSHIFT_OAUTH_USERINFO_URL, From 08441111a8077c708d57f14b82f6171babfd688c Mon Sep 17 00:00:00 2001 From: Eric Sauer Date: Wed, 25 Oct 2023 20:59:33 +0000 Subject: [PATCH 26/42] Updated measure tabs to recalculate on date selection change --- .../change-failure-rate/tab-trigger.jsx | 7 +- components/dashboard.jsx | 16 +++-- components/date-range-selector.jsx | 67 ++++++++++++------- .../deployment-frequency/tab-trigger.jsx | 7 +- .../lead-time-for-change/tab-trigger.jsx | 7 +- .../mean-time-to-recovery/tab-trigger.jsx | 7 +- 6 files changed, 66 insertions(+), 45 deletions(-) diff --git a/components/change-failure-rate/tab-trigger.jsx b/components/change-failure-rate/tab-trigger.jsx index 1a851e3..0758c16 100644 --- a/components/change-failure-rate/tab-trigger.jsx +++ b/components/change-failure-rate/tab-trigger.jsx @@ -5,8 +5,9 @@ import { Badge } from '@/components/ui/badge' import { InfoTooltip } from '@/components/info-tooltip' import { ChangeFailureRateRating } from './rating' import { useState, useEffect } from "react" +import { getDaysBetweenDates } from '@/components/date-range-selector' -export function ChangeFailureRateTabTrigger({ data, appName }) { +export function ChangeFailureRateTabTrigger({ dateRange, data, appName }) { // Calculate the mean const calculateMean = data => { @@ -24,13 +25,13 @@ export function ChangeFailureRateTabTrigger({ data, appName }) { const [isLoading, setLoading] = useState(true) useEffect(() => { - fetch(`${process.env.NEXT_PUBLIC_PELORUS_API_URL}/sdp/change_failure_rate/${appName}?range=1w`) + fetch(`${process.env.NEXT_PUBLIC_PELORUS_API_URL}/sdp/change_failure_rate/${appName}?range=${getDaysBetweenDates(dateRange)}d`) .then((test) => test.json()) .then((response) => { setResponse(response) setLoading(false) }) - }, [appName]); + }, [dateRange, appName]); if (isLoading) return

Loading...

if (!response) return

No cfr data!

diff --git a/components/dashboard.jsx b/components/dashboard.jsx index 225e1d4..715db51 100644 --- a/components/dashboard.jsx +++ b/components/dashboard.jsx @@ -1,6 +1,7 @@ 'use client' import { useState, useEffect } from 'react' +import { subDays } from "date-fns" import { ApplicationSelector } from '@/components/application-selector' import { AppSelector } from '@/components/app-selector' import { DateRangeSelector } from '@/components/date-range-selector' @@ -35,10 +36,11 @@ import { MeanTimeToRecoveryTabTrigger } from './mean-time-to-recovery/tab-trigge export function Dashboard({ data, appList }) { - const applicationsList = data.applications + const applicationsList = data.applications; + const defaultDaysAgo = 7; const [activeApp, setActiveApp] = useState( appList[0].app ) const [activeApplication, setActiveApplication] = useState(applicationsList[0].id) - const [activeDateRange, setActiveDateRange] = useState('') + const [activeDateRange, setActiveDateRange] = useState({ from: subDays(new Date(), defaultDaysAgo), to: new Date()}) const [dataScorecard, setDataScorecard] = useState(applicationsList[0].scorecard[0]) const [dataDeploymentFrequency, setDataDeploymentFrequency] = useState(applicationsList[0].metrics[1].data) const [dataLeadTimeForChange, setDataLeadTimeForChange] = useState(applicationsList[0].metrics[2].data) @@ -65,7 +67,7 @@ export function Dashboard({ data, appList }) {
- +
@@ -93,16 +95,16 @@ export function Dashboard({ data, appList }) { - + - + - + - + diff --git a/components/date-range-selector.jsx b/components/date-range-selector.jsx index 5ec3f85..70539e1 100644 --- a/components/date-range-selector.jsx +++ b/components/date-range-selector.jsx @@ -3,7 +3,8 @@ import { useState } from 'react' import { addDays, subDays, format } from "date-fns" import { Calendar as CalendarIcon } from "lucide-react" -import { DateRange } from "react-day-picker" +import { DateRange, DayPicker } from "react-day-picker" +import 'react-day-picker/dist/style.css'; import { cn } from "@/lib/utils" import { Button } from "@/components/ui/button" @@ -11,35 +12,49 @@ import { Badge } from "@/components/ui/badge" import { Calendar } from "@/components/ui/calendar" import { Popover, PopoverContent, PopoverTrigger, } from "@/components/ui/popover" -export function DateRangeSelector() { +export function getDaysBetweenDates(dateRange) { + let differenceInMs = dateRange?.from ? ( + dateRange.to ? ( + dateRange.to.getTime() - dateRange.from.getTime() + ) : ( + 0 + ) + ) : ( + 0 + ) + + return convertMsToDays(differenceInMs + 1) +} - const [date, setDate] = useState({ - from: new Date("June 29, 2023"), - to: new Date("July 26, 2023"), - }) - - const convertMsToDays = ms => { - const msInOneSecond = 1000 - const secondsInOneMinute = 60 - const minutesInOneHour = 60 - const hoursInOneDay = 24 +function convertMsToDays(ms) { + const msInOneSecond = 1000 + const secondsInOneMinute = 60 + const minutesInOneHour = 60 + const hoursInOneDay = 24 - const minutesInOneDay = hoursInOneDay * minutesInOneHour - const secondsInOneDay = secondsInOneMinute * minutesInOneDay - const msInOneDay = msInOneSecond * secondsInOneDay + const minutesInOneDay = hoursInOneDay * minutesInOneHour + const secondsInOneDay = secondsInOneMinute * minutesInOneDay + const msInOneDay = msInOneSecond * secondsInOneDay + + return Math.ceil(ms / msInOneDay) +} - return Math.ceil(ms / msInOneDay) - } - const getDaysBetweenDates = (dateOne, dateTwo) => { - let differenceInMs = dateTwo.getTime() - dateOne.getTime() +export function DateRangeSelector({date, setDate}) { - if (differenceInMs < 0) { - differenceInMs = dateOne.getTime() - dateTwo.getTime() - } + // const [date, setDate] = useState({ from: new Date(), to: subDays(new Date(), defaultDaysAgo)}) - return convertMsToDays(differenceInMs + 1) - } + // To calculate the time difference of two dates + //var Difference_In_Time = date.to.getTime() - date.from.getTime(); + + // To calculate the no. of days between two dates + //var Difference_In_Days = Difference_In_Time / (1000 * 3600 * 24); + + //To display the final no. of days (result) + // console.log("Total number of days between dates
" + // + date.from + "
and
" + // + date.to + " is:
" + // + Difference_In_Days); return (
@@ -77,10 +92,10 @@ export function DateRangeSelector() {
- +
-
Range: 28 days
+
Range: {getDaysBetweenDates(date)} days
diff --git a/components/deployment-frequency/tab-trigger.jsx b/components/deployment-frequency/tab-trigger.jsx index 664f361..362b9f0 100644 --- a/components/deployment-frequency/tab-trigger.jsx +++ b/components/deployment-frequency/tab-trigger.jsx @@ -5,8 +5,9 @@ import { Badge } from '@/components/ui/badge' import { InfoTooltip } from '@/components/info-tooltip' import { DeploymentFrequencyRating } from '@/components/deployment-frequency/rating' import { useState, useEffect } from "react" +import { getDaysBetweenDates } from '@/components/date-range-selector' -export function DeploymentFrequencyTabTrigger({ data, appName }) { +export function DeploymentFrequencyTabTrigger({ dateRange, data, appName }) { // Calculate the mean const calculateMean = data => { @@ -24,13 +25,13 @@ export function DeploymentFrequencyTabTrigger({ data, appName }) { const [isLoading, setLoading] = useState(true) useEffect(() => { - fetch(`${process.env.NEXT_PUBLIC_PELORUS_API_URL}/sdp/deployment_frequency/${appName}?range=1w`) + fetch(`${process.env.NEXT_PUBLIC_PELORUS_API_URL}/sdp/deployment_frequency/${appName}?range=${getDaysBetweenDates(dateRange)}d`) .then((test) => test.json()) .then((response) => { setResponse(response) setLoading(false) }) - }, [appName]); + }, [dateRange, appName]); if (isLoading) return

Loading...

if (!response) return

No cfr data!

diff --git a/components/lead-time-for-change/tab-trigger.jsx b/components/lead-time-for-change/tab-trigger.jsx index bbc6e7e..0eeb0d5 100644 --- a/components/lead-time-for-change/tab-trigger.jsx +++ b/components/lead-time-for-change/tab-trigger.jsx @@ -5,8 +5,9 @@ import { Badge } from '@/components/ui/badge' import { InfoTooltip } from '@/components/info-tooltip' import { LeadTimeForChangeRating } from './rating' import { useState, useEffect } from "react" +import { getDaysBetweenDates } from '@/components/date-range-selector' -export function LeadTimeForChangeTabTrigger({ data, appName }) { +export function LeadTimeForChangeTabTrigger({ dateRange, data, appName }) { // Calculate the mean const calculateMean = data => { @@ -24,13 +25,13 @@ export function LeadTimeForChangeTabTrigger({ data, appName }) { const [isLoading, setLoading] = useState(true) useEffect(() => { - fetch(`${process.env.NEXT_PUBLIC_PELORUS_API_URL}/sdp/lead_time_for_change/${appName}?range=1w`) + fetch(`${process.env.NEXT_PUBLIC_PELORUS_API_URL}/sdp/lead_time_for_change/${appName}?range=${getDaysBetweenDates(dateRange)}d`) .then((test) => test.json()) .then((response) => { setResponse(response) setLoading(false) }) - }, [appName]); + }, [dateRange, appName]); if (isLoading) return

Loading...

if (!response) return

No cfr data!

diff --git a/components/mean-time-to-recovery/tab-trigger.jsx b/components/mean-time-to-recovery/tab-trigger.jsx index 4dee7cd..c164eea 100644 --- a/components/mean-time-to-recovery/tab-trigger.jsx +++ b/components/mean-time-to-recovery/tab-trigger.jsx @@ -5,8 +5,9 @@ import { Badge } from '@/components/ui/badge' import { InfoTooltip } from '@/components/info-tooltip' import { MeanTimeToRecoveryRating } from './rating' import { useState, useEffect } from "react" +import { getDaysBetweenDates } from '@/components/date-range-selector' -export function MeanTimeToRecoveryTabTrigger({ data, appName }) { +export function MeanTimeToRecoveryTabTrigger({ dateRange, data, appName }) { // Calculate the mean const calculateMean = data => { @@ -24,13 +25,13 @@ export function MeanTimeToRecoveryTabTrigger({ data, appName }) { const [isLoading, setLoading] = useState(true) useEffect(() => { - fetch(`${process.env.NEXT_PUBLIC_PELORUS_API_URL}/sdp/mean_time_to_restore/${appName}?range=1w`) + fetch(`${process.env.NEXT_PUBLIC_PELORUS_API_URL}/sdp/mean_time_to_restore/${appName}?range=${getDaysBetweenDates(dateRange)}d`) .then((test) => test.json()) .then((response) => { setResponse(response) setLoading(false) }) - }, [appName]); + }, [dateRange, appName]); if (isLoading) return

Loading...

if (!response) return

No cfr data!

From cb64dc95659cbcd32ed93fd7e4d5fee5ce782c53 Mon Sep 17 00:00:00 2001 From: Eric Sauer Date: Wed, 7 Feb 2024 19:24:14 +0000 Subject: [PATCH 27/42] For now, go to 5 decimal places --- components/mean-time-to-recovery/tab-trigger.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/mean-time-to-recovery/tab-trigger.jsx b/components/mean-time-to-recovery/tab-trigger.jsx index c164eea..42d19f6 100644 --- a/components/mean-time-to-recovery/tab-trigger.jsx +++ b/components/mean-time-to-recovery/tab-trigger.jsx @@ -55,7 +55,7 @@ export function MeanTimeToRecoveryTabTrigger({ dateRange, data, appName }) {
- {parseFloat(chartMean).toFixed(2)} days + {parseFloat(chartMean).toFixed(5)} days 16%
From 16fe8760c8e02fe8e0fbaf6df475242b50762c27 Mon Sep 17 00:00:00 2001 From: Eric Sauer Date: Wed, 7 Feb 2024 23:47:58 +0000 Subject: [PATCH 28/42] Update to pass a timestamp along with a date range to the api call --- components/mean-time-to-recovery/tab-trigger.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/mean-time-to-recovery/tab-trigger.jsx b/components/mean-time-to-recovery/tab-trigger.jsx index 42d19f6..ff7cd30 100644 --- a/components/mean-time-to-recovery/tab-trigger.jsx +++ b/components/mean-time-to-recovery/tab-trigger.jsx @@ -25,7 +25,7 @@ export function MeanTimeToRecoveryTabTrigger({ dateRange, data, appName }) { const [isLoading, setLoading] = useState(true) useEffect(() => { - fetch(`${process.env.NEXT_PUBLIC_PELORUS_API_URL}/sdp/mean_time_to_restore/${appName}?range=${getDaysBetweenDates(dateRange)}d`) + fetch(`${process.env.NEXT_PUBLIC_PELORUS_API_URL}/sdp/mean_time_to_restore/${appName}?range=${getDaysBetweenDates(dateRange)}d&start=${dateRange.to.getTime() / 1000}`) .then((test) => test.json()) .then((response) => { setResponse(response) @@ -62,4 +62,4 @@ export function MeanTimeToRecoveryTabTrigger({ dateRange, data, appName }) {
) -} \ No newline at end of file +} From 3b7fdf959b084fec525b6d047d040fcb56436965 Mon Sep 17 00:00:00 2001 From: Eric Sauer Date: Thu, 8 Feb 2024 21:11:09 +0000 Subject: [PATCH 29/42] updated to pass query param per new api updates. Date picker now works --- components/change-failure-rate/tab-trigger.jsx | 2 +- components/deployment-frequency/tab-trigger.jsx | 2 +- components/lead-time-for-change/tab-trigger.jsx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/components/change-failure-rate/tab-trigger.jsx b/components/change-failure-rate/tab-trigger.jsx index 0758c16..6488d12 100644 --- a/components/change-failure-rate/tab-trigger.jsx +++ b/components/change-failure-rate/tab-trigger.jsx @@ -25,7 +25,7 @@ export function ChangeFailureRateTabTrigger({ dateRange, data, appName }) { const [isLoading, setLoading] = useState(true) useEffect(() => { - fetch(`${process.env.NEXT_PUBLIC_PELORUS_API_URL}/sdp/change_failure_rate/${appName}?range=${getDaysBetweenDates(dateRange)}d`) + fetch(`${process.env.NEXT_PUBLIC_PELORUS_API_URL}/sdp/change_failure_rate/${appName}?range=${getDaysBetweenDates(dateRange)}d&start=${dateRange.to.getTime() / 1000}`) .then((test) => test.json()) .then((response) => { setResponse(response) diff --git a/components/deployment-frequency/tab-trigger.jsx b/components/deployment-frequency/tab-trigger.jsx index 362b9f0..ceb73eb 100644 --- a/components/deployment-frequency/tab-trigger.jsx +++ b/components/deployment-frequency/tab-trigger.jsx @@ -25,7 +25,7 @@ export function DeploymentFrequencyTabTrigger({ dateRange, data, appName }) { const [isLoading, setLoading] = useState(true) useEffect(() => { - fetch(`${process.env.NEXT_PUBLIC_PELORUS_API_URL}/sdp/deployment_frequency/${appName}?range=${getDaysBetweenDates(dateRange)}d`) + fetch(`${process.env.NEXT_PUBLIC_PELORUS_API_URL}/sdp/deployment_frequency/${appName}?range=${getDaysBetweenDates(dateRange)}d&start=${dateRange.to.getTime() / 1000}`) .then((test) => test.json()) .then((response) => { setResponse(response) diff --git a/components/lead-time-for-change/tab-trigger.jsx b/components/lead-time-for-change/tab-trigger.jsx index 0eeb0d5..7f6053d 100644 --- a/components/lead-time-for-change/tab-trigger.jsx +++ b/components/lead-time-for-change/tab-trigger.jsx @@ -25,7 +25,7 @@ export function LeadTimeForChangeTabTrigger({ dateRange, data, appName }) { const [isLoading, setLoading] = useState(true) useEffect(() => { - fetch(`${process.env.NEXT_PUBLIC_PELORUS_API_URL}/sdp/lead_time_for_change/${appName}?range=${getDaysBetweenDates(dateRange)}d`) + fetch(`${process.env.NEXT_PUBLIC_PELORUS_API_URL}/sdp/lead_time_for_change/${appName}?range=${getDaysBetweenDates(dateRange)}d&start=${dateRange.to.getTime() / 1000}`) .then((test) => test.json()) .then((response) => { setResponse(response) From dd1eab8b2a64854c03387a770aebff3806422393 Mon Sep 17 00:00:00 2001 From: Eric Sauer Date: Tue, 5 Mar 2024 17:02:57 +0000 Subject: [PATCH 30/42] Finally got the bug fixed, using a custom event handler --- components/dashboard.jsx | 6 +- components/date-range-selector.jsx | 58 ++++++++++++------- components/lead-time-for-change/chart.jsx | 7 ++- .../lead-time-for-change/tab-trigger.jsx | 3 +- components/lead-time-for-change/table.jsx | 7 ++- package.json | 1 + 6 files changed, 49 insertions(+), 33 deletions(-) diff --git a/components/dashboard.jsx b/components/dashboard.jsx index 715db51..b94a286 100644 --- a/components/dashboard.jsx +++ b/components/dashboard.jsx @@ -1,6 +1,6 @@ 'use client' -import { useState, useEffect } from 'react' +import { useState } from 'react' import { subDays } from "date-fns" import { ApplicationSelector } from '@/components/application-selector' import { AppSelector } from '@/components/app-selector' @@ -121,14 +121,14 @@ export function Dashboard({ data, appList }) {
- +

Pull requests

- +
diff --git a/components/date-range-selector.jsx b/components/date-range-selector.jsx index 70539e1..71d1839 100644 --- a/components/date-range-selector.jsx +++ b/components/date-range-selector.jsx @@ -1,14 +1,11 @@ "use client" -import { useState } from 'react' -import { addDays, subDays, format } from "date-fns" +import { format } from "date-fns" import { Calendar as CalendarIcon } from "lucide-react" -import { DateRange, DayPicker } from "react-day-picker" import 'react-day-picker/dist/style.css'; import { cn } from "@/lib/utils" import { Button } from "@/components/ui/button" -import { Badge } from "@/components/ui/badge" import { Calendar } from "@/components/ui/calendar" import { Popover, PopoverContent, PopoverTrigger, } from "@/components/ui/popover" @@ -39,22 +36,38 @@ function convertMsToDays(ms) { return Math.ceil(ms / msInOneDay) } +function isComplete(date) { + if (date?.from) { + if (date.to) { + console.log("Date is complete") + return true + } + } + console.log("Date is not complete") + return false +} export function DateRangeSelector({date, setDate}) { - // const [date, setDate] = useState({ from: new Date(), to: subDays(new Date(), defaultDaysAgo)}) + let footer =

Please pick the first day.

; + if (date?.from) { + if (!date.to) { + footer =

{format(date.from, 'PPP')}

; + } else if (date.to) { + footer = ( +

+ {format(date.from, 'PPP')}–{format(date.to, 'PPP')} +

+ ); + } + } - // To calculate the time difference of two dates - //var Difference_In_Time = date.to.getTime() - date.from.getTime(); - - // To calculate the no. of days between two dates - //var Difference_In_Days = Difference_In_Time / (1000 * 3600 * 24); - - //To display the final no. of days (result) - // console.log("Total number of days between dates
" - // + date.from + "
and
" - // + date.to + " is:
" - // + Difference_In_Days); + // This handler will prevent errors when only 'to' or 'from' are selected + const handleDateSelect = function (date) { + if (isComplete(date)) { + setDate(date) + } + }; return (
@@ -63,7 +76,8 @@ export function DateRangeSelector({date, setDate}) {
-
+ {/*
@@ -91,14 +105,14 @@ export function DateRangeSelector({date, setDate}) { -
- +
*/} +
Range: {getDaysBetweenDates(date)} days
- +
diff --git a/components/lead-time-for-change/chart.jsx b/components/lead-time-for-change/chart.jsx index e1407bc..98d2dfc 100644 --- a/components/lead-time-for-change/chart.jsx +++ b/components/lead-time-for-change/chart.jsx @@ -5,6 +5,7 @@ import { useTheme } from "next-themes" import { format } from 'date-fns' import { XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, ComposedChart, Area, Line, ReferenceLine } from 'recharts' import { LeadTimeForChangeTooltip } from './tooltip' +import { getDaysBetweenDates } from '@/components/date-range-selector' const dateFormatter = epoch => { @@ -17,7 +18,7 @@ const dayFormatter = seconds => { return days + "d" } -export function LeadTimeForChangeChart( { appName } ) { +export function LeadTimeForChangeChart( { dateRange, appName } ) { const { resolvedTheme } = useTheme() const animationDuration = 1000 @@ -38,12 +39,12 @@ export function LeadTimeForChangeChart( { appName } ) { const [ltfcData, setLtfcData] = useState([]) useEffect(() => { - fetch(`${process.env.NEXT_PUBLIC_PELORUS_API_URL}/sdp/lead_time_for_change/${appName}/data?range=1w`) + fetch(`${process.env.NEXT_PUBLIC_PELORUS_API_URL}/sdp/lead_time_for_change/${appName}/data?range=${getDaysBetweenDates(dateRange)}d&start=${dateRange.to.getTime() / 1000}`) .then((response) => response.json()).then((data) => data.sort((d1, d2) => (d1.timestamp > d2.timestamp) ? 1 : (d1.timestamp < d2.timestamp) ? -1 : 0 )) .then((sortedData) => { setLtfcData(sortedData) }) - }, [appName]) + }, [dateRange, appName]) // Reports const [reportLeadTimeForChangeData, setReportLeadTimeForChangeData] = useState(null) diff --git a/components/lead-time-for-change/tab-trigger.jsx b/components/lead-time-for-change/tab-trigger.jsx index 7f6053d..aa7221f 100644 --- a/components/lead-time-for-change/tab-trigger.jsx +++ b/components/lead-time-for-change/tab-trigger.jsx @@ -23,7 +23,6 @@ export function LeadTimeForChangeTabTrigger({ dateRange, data, appName }) { const [response, setResponse] = useState([]) const [isLoading, setLoading] = useState(true) - useEffect(() => { fetch(`${process.env.NEXT_PUBLIC_PELORUS_API_URL}/sdp/lead_time_for_change/${appName}?range=${getDaysBetweenDates(dateRange)}d&start=${dateRange.to.getTime() / 1000}`) .then((test) => test.json()) @@ -31,7 +30,7 @@ export function LeadTimeForChangeTabTrigger({ dateRange, data, appName }) { setResponse(response) setLoading(false) }) - }, [dateRange, appName]); + }, [dateRange, appName]); if (isLoading) return

Loading...

if (!response) return

No cfr data!

diff --git a/components/lead-time-for-change/table.jsx b/components/lead-time-for-change/table.jsx index 5f11d68..ee1603c 100644 --- a/components/lead-time-for-change/table.jsx +++ b/components/lead-time-for-change/table.jsx @@ -12,6 +12,7 @@ import { } from "@/components/ui/table" import { Calendar, GitBranch, GitCommit, Globe, Timer } from "lucide-react" import { format } from 'date-fns' +import { getDaysBetweenDates } from '@/components/date-range-selector' const dateFormatter = epoch => { const date = new Date(epoch * 1000) @@ -23,16 +24,16 @@ const dayFormatter = seconds => { return days + "d" } -export function LeadTimeForChangeTable({ appName }) { +export function LeadTimeForChangeTable({ dateRange, appName }) { const [ltfcData, setLtfcData] = useState([]) useEffect(() => { - fetch(`${process.env.NEXT_PUBLIC_PELORUS_API_URL}/sdp/lead_time_for_change/${appName}/data?range=1w`) + fetch(`${process.env.NEXT_PUBLIC_PELORUS_API_URL}/sdp/lead_time_for_change/${appName}/data?range=${getDaysBetweenDates(dateRange)}d&start=${dateRange.to.getTime() / 1000}`) .then((response) => response.json()).then((data) => data.sort((d1, d2) => (d1.timestamp > d2.timestamp) ? 1 : (d1.timestamp < d2.timestamp) ? -1 : 0 )) .then((sortedData) => { setLtfcData(sortedData) }) - }, [appName]) + }, [dateRange, appName]) return ( diff --git a/package.json b/package.json index e01d457..d886a9d 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "react": "^18.2.0", "react-day-picker": "^8.8.0", "react-dom": "^18.2.0", + "react-error-boundary": "^4.0.13", "recharts": "^2.7.2", "sass": "^1.64.1", "tailwind-merge": "^1.14.0", From 4a5ae75ed82b3ac0f9786900144420d583a9ad90 Mon Sep 17 00:00:00 2001 From: Eric Sauer Date: Tue, 5 Mar 2024 18:33:40 +0000 Subject: [PATCH 31/42] OK, date picker minimally works now --- components/dashboard.jsx | 2 +- components/date-range-selector.jsx | 44 ++++++++++++++++-------------- 2 files changed, 25 insertions(+), 21 deletions(-) diff --git a/components/dashboard.jsx b/components/dashboard.jsx index b94a286..c6a4a6a 100644 --- a/components/dashboard.jsx +++ b/components/dashboard.jsx @@ -67,7 +67,7 @@ export function Dashboard({ data, appList }) {
- +
diff --git a/components/date-range-selector.jsx b/components/date-range-selector.jsx index 71d1839..f2de269 100644 --- a/components/date-range-selector.jsx +++ b/components/date-range-selector.jsx @@ -1,5 +1,6 @@ "use client" +import { useState } from 'react' import { format } from "date-fns" import { Calendar as CalendarIcon } from "lucide-react" import 'react-day-picker/dist/style.css'; @@ -47,25 +48,28 @@ function isComplete(date) { return false } -export function DateRangeSelector({date, setDate}) { +export function DateRangeSelector({dashDate, setDashDate}) { - let footer =

Please pick the first day.

; - if (date?.from) { - if (!date.to) { - footer =

{format(date.from, 'PPP')}

; - } else if (date.to) { - footer = ( -

- {format(date.from, 'PPP')}–{format(date.to, 'PPP')} -

- ); - } - } + const [date, setDate] = useState(dashDate); + + + // let footer =

Please pick the first day.

; + // if (date?.from) { + // if (!date.to) { + // footer =

{format(date.from, 'PPP')}

; + // } else if (date.to) { + // footer = ( + //

+ // {format(date.from, 'PPP')}–{format(date.to, 'PPP')} + //

+ // ); + // } + // } // This handler will prevent errors when only 'to' or 'from' are selected - const handleDateSelect = function (date) { + const handleApplyClick = function () { if (isComplete(date)) { - setDate(date) + setDashDate(date); } }; @@ -76,8 +80,8 @@ export function DateRangeSelector({date, setDate}) { @@ -106,13 +110,13 @@ export function DateRangeSelector({date, setDate}) { */} - +
Range: {getDaysBetweenDates(date)} days
- +
From 0477e3ca35ddd2573ccc531741c1bca45538c9f9 Mon Sep 17 00:00:00 2001 From: Eric Sauer Date: Fri, 8 Mar 2024 15:22:48 +0000 Subject: [PATCH 32/42] Remove unused code --- components/date-range-selector.jsx | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/components/date-range-selector.jsx b/components/date-range-selector.jsx index f2de269..ce67329 100644 --- a/components/date-range-selector.jsx +++ b/components/date-range-selector.jsx @@ -52,20 +52,6 @@ export function DateRangeSelector({dashDate, setDashDate}) { const [date, setDate] = useState(dashDate); - - // let footer =

Please pick the first day.

; - // if (date?.from) { - // if (!date.to) { - // footer =

{format(date.from, 'PPP')}

; - // } else if (date.to) { - // footer = ( - //

- // {format(date.from, 'PPP')}–{format(date.to, 'PPP')} - //

- // ); - // } - // } - // This handler will prevent errors when only 'to' or 'from' are selected const handleApplyClick = function () { if (isComplete(date)) { From 16a18abdb3ced8dc150d8aca6fd0afe2bad34609 Mon Sep 17 00:00:00 2001 From: Eric Sauer Date: Fri, 8 Mar 2024 17:41:31 +0000 Subject: [PATCH 33/42] Refactor to store the selected date in the browser cache so it survives a refresh --- components/dashboard.jsx | 28 +++++++- components/date-range-selector.jsx | 65 ++++++++++++------- .../lead-time-for-change/tab-trigger.jsx | 1 + 3 files changed, 68 insertions(+), 26 deletions(-) diff --git a/components/dashboard.jsx b/components/dashboard.jsx index c6a4a6a..0e000a2 100644 --- a/components/dashboard.jsx +++ b/components/dashboard.jsx @@ -37,10 +37,32 @@ import { MeanTimeToRecoveryTabTrigger } from './mean-time-to-recovery/tab-trigge export function Dashboard({ data, appList }) { const applicationsList = data.applications; - const defaultDaysAgo = 7; + const [activeDateRange, setActiveDateRange] = useState(() => { + console.log('Initializing activeDateRange'); + const storedDateRange = localStorage.getItem('dateRange'); + if (storedDateRange) { + console.log('Using stored date range'); + // Parse the stored dateRange and convert dates back to Date objects + const parsedDateRange = JSON.parse(storedDateRange, (key, value) => { + if (key === 'from' || key === 'to') { + return new Date(value); + } + return value; + }); + return parsedDateRange; + } else { + console.log('No stored date range found, using default'); + const defaultDaysAgo = 7; + return { + from: subDays(new Date(), defaultDaysAgo), + to: new Date() + }; + } + }); + console.log('Date Range after initialization:', activeDateRange); + const [activeApp, setActiveApp] = useState( appList[0].app ) const [activeApplication, setActiveApplication] = useState(applicationsList[0].id) - const [activeDateRange, setActiveDateRange] = useState({ from: subDays(new Date(), defaultDaysAgo), to: new Date()}) const [dataScorecard, setDataScorecard] = useState(applicationsList[0].scorecard[0]) const [dataDeploymentFrequency, setDataDeploymentFrequency] = useState(applicationsList[0].metrics[1].data) const [dataLeadTimeForChange, setDataLeadTimeForChange] = useState(applicationsList[0].metrics[2].data) @@ -67,7 +89,7 @@ export function Dashboard({ data, appList }) {
- +
diff --git a/components/date-range-selector.jsx b/components/date-range-selector.jsx index ce67329..33cd911 100644 --- a/components/date-range-selector.jsx +++ b/components/date-range-selector.jsx @@ -1,6 +1,6 @@ "use client" -import { useState } from 'react' +import { useState, useEffect } from 'react' import { format } from "date-fns" import { Calendar as CalendarIcon } from "lucide-react" import 'react-day-picker/dist/style.css'; @@ -11,17 +11,16 @@ import { Calendar } from "@/components/ui/calendar" import { Popover, PopoverContent, PopoverTrigger, } from "@/components/ui/popover" export function getDaysBetweenDates(dateRange) { - let differenceInMs = dateRange?.from ? ( - dateRange.to ? ( - dateRange.to.getTime() - dateRange.from.getTime() - ) : ( - 0 - ) - ) : ( - 0 - ) + console.log('DateRange value in getDaysBetweenDates', dateRange) + let differenceInMs = 0; - return convertMsToDays(differenceInMs + 1) + if (dateRange?.from) { + if (dateRange.to instanceof Date) { + differenceInMs = dateRange.to.getTime() - dateRange.from.getTime(); + } + } + + return convertMsToDays(differenceInMs + 1); } function convertMsToDays(ms) { @@ -48,15 +47,35 @@ function isComplete(date) { return false } -export function DateRangeSelector({dashDate, setDashDate}) { +export function DateRangeSelector({activeDateRange, setActiveDateRange}) { - const [date, setDate] = useState(dashDate); + const [dateRange, setDateRange] = useState(() => activeDateRange || null); + + useEffect(() => { + if (dateRange && typeof window !== 'undefined') { + localStorage.setItem('dateRange', JSON.stringify(dateRange)); + } + }, [dateRange]); // Removed setDateRange from the dependency array + + useEffect(() => { + console.log('Date range value after update:', dateRange); + }, [dateRange]); // This handler will prevent errors when only 'to' or 'from' are selected const handleApplyClick = function () { - if (isComplete(date)) { - setDashDate(date); + if (isComplete(dateRange) && isValidDateRange(dateRange)) { // Add isValidDateRange check + setActiveDateRange(dateRange); + } + }; + + // Function to check if the date range is valid + const isValidDateRange = (dateRange) => { + if (dateRange?.from && dateRange?.to) { + const fromDate = new Date(dateRange.from); + const toDate = new Date(dateRange.to); + return fromDate < toDate; } + return false; }; return ( @@ -64,17 +83,17 @@ export function DateRangeSelector({dashDate, setDashDate}) { {/* Custom */} - */} - +
-
Range: {getDaysBetweenDates(date)} days
+
Range: {getDaysBetweenDates(dateRange)} days
diff --git a/components/lead-time-for-change/tab-trigger.jsx b/components/lead-time-for-change/tab-trigger.jsx index aa7221f..597fa3a 100644 --- a/components/lead-time-for-change/tab-trigger.jsx +++ b/components/lead-time-for-change/tab-trigger.jsx @@ -24,6 +24,7 @@ export function LeadTimeForChangeTabTrigger({ dateRange, data, appName }) { const [response, setResponse] = useState([]) const [isLoading, setLoading] = useState(true) useEffect(() => { + console.log('LTFC date range: ', dateRange) fetch(`${process.env.NEXT_PUBLIC_PELORUS_API_URL}/sdp/lead_time_for_change/${appName}?range=${getDaysBetweenDates(dateRange)}d&start=${dateRange.to.getTime() / 1000}`) .then((test) => test.json()) .then((response) => { From 6f65e68e55b8147b6ffa0b2f777ed6ff6d8c262e Mon Sep 17 00:00:00 2001 From: Eric Sauer Date: Fri, 8 Mar 2024 17:50:29 +0000 Subject: [PATCH 34/42] Add a few helpful comments --- components/dashboard.jsx | 3 +++ components/date-range-selector.jsx | 1 + 2 files changed, 4 insertions(+) diff --git a/components/dashboard.jsx b/components/dashboard.jsx index 0e000a2..26d03b9 100644 --- a/components/dashboard.jsx +++ b/components/dashboard.jsx @@ -37,6 +37,9 @@ import { MeanTimeToRecoveryTabTrigger } from './mean-time-to-recovery/tab-trigge export function Dashboard({ data, appList }) { const applicationsList = data.applications; + // This code initializes the date range that the dashboard will show data from. + // We start by checking if a previously selected date is cached in the browser. + // If not, we default to the past 7 days const [activeDateRange, setActiveDateRange] = useState(() => { console.log('Initializing activeDateRange'); const storedDateRange = localStorage.getItem('dateRange'); diff --git a/components/date-range-selector.jsx b/components/date-range-selector.jsx index 33cd911..8ab7d06 100644 --- a/components/date-range-selector.jsx +++ b/components/date-range-selector.jsx @@ -51,6 +51,7 @@ export function DateRangeSelector({activeDateRange, setActiveDateRange}) { const [dateRange, setDateRange] = useState(() => activeDateRange || null); + // when dateRange is updated, cache it in the browser so that the selected range survives a restart useEffect(() => { if (dateRange && typeof window !== 'undefined') { localStorage.setItem('dateRange', JSON.stringify(dateRange)); From c7a1db7dfaa10444feecacf288cbf5bd95298183 Mon Sep 17 00:00:00 2001 From: Eric Sauer Date: Thu, 7 Mar 2024 22:05:21 +0000 Subject: [PATCH 35/42] Get chart and table populating --- components/dashboard.jsx | 6 +- .../{iso-chart.jsx => chart.jsx} | 82 ++++-- .../deploymentFrequency.js | 28 ++ .../deployment-frequency/tab-trigger.jsx | 2 +- components/deployment-frequency/table.jsx | 271 +++--------------- components/lead-time-for-change/table.jsx | 12 +- lib/date-funcs.js | 11 + 7 files changed, 145 insertions(+), 267 deletions(-) rename components/deployment-frequency/{iso-chart.jsx => chart.jsx} (54%) create mode 100644 components/deployment-frequency/deploymentFrequency.js create mode 100644 lib/date-funcs.js diff --git a/components/dashboard.jsx b/components/dashboard.jsx index 26d03b9..ce1862e 100644 --- a/components/dashboard.jsx +++ b/components/dashboard.jsx @@ -15,7 +15,7 @@ import { CardTitle, } from "@/components/ui/card" -import { IsolatedDeploymentFrequencyChart } from '@/components/deployment-frequency/iso-chart' +import { DeploymentFrequencyChart } from '@/components/deployment-frequency/chart' import { LeadTimeForChangeChart } from '@/components/lead-time-for-change/chart' import { IsolatedChangeFailureRateChart } from '@/components/change-failure-rate/iso-chart' import { IsolatedMeanTimeToRecoveryChart } from '@/components/mean-time-to-recovery/iso-chart' @@ -134,14 +134,14 @@ export function Dashboard({ data, appList }) {
- +

Deployments

- +
diff --git a/components/deployment-frequency/iso-chart.jsx b/components/deployment-frequency/chart.jsx similarity index 54% rename from components/deployment-frequency/iso-chart.jsx rename to components/deployment-frequency/chart.jsx index ba38151..b1699c1 100644 --- a/components/deployment-frequency/iso-chart.jsx +++ b/components/deployment-frequency/chart.jsx @@ -1,15 +1,22 @@ 'use client' -import { useState } from 'react' +import { useState, useEffect } from 'react' import { useTheme } from "next-themes" import { format } from 'date-fns' import { XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, ComposedChart, Area, Line, ReferenceLine } from 'recharts' import { DeploymentFrequencyTooltip } from './tooltip' +import { getDaysBetweenDates } from '@/components/date-range-selector' -const dateFormatter = date => { +const dateFormatter = epoch => { + const date = new Date(epoch * 1000) return format(new Date(date), "MMM d") } +const dayFormatter = seconds => { + const days = parseFloat(seconds / 86400).toFixed(2) + return days + "d" +} + const calculateMean = data => { if (data.length < 1) { return @@ -17,7 +24,38 @@ const calculateMean = data => { return data.reduce((prev, current) => prev + current) / data.length } -export function IsolatedDeploymentFrequencyChart({ data }) { +function deploymentsPerDay(data) { + // Object to store the count of deployments per day + const deploymentsCountPerDay = {}; + + // Loop through each item in the data array + data.forEach(item => { + // Convert timestamp to Date object and extract the date + const date = new Date(item.timestamp * 1000); + // Format the date to YYYY-MM-DD + const formattedDate = date.toISOString().split('T')[0]; + + // Increment the count of deployments for the corresponding date + if (deploymentsCountPerDay[formattedDate]) { + deploymentsCountPerDay[formattedDate]++; + } else { + deploymentsCountPerDay[formattedDate] = 1; + } + }); + + // Convert the deployments count per day object into an array of objects + const result = Object.keys(deploymentsCountPerDay).map(date => ({ + // Convert date back to epoch format and assign to day_epoch property + day_epoch: new Date(date).getTime() / 1000, + // Assign the count of deployments to the count property + count: deploymentsCountPerDay[date] + })); + + // Return the result + return result; +} + +export function DeploymentFrequencyChart({ dateRange, appName }) { const { resolvedTheme } = useTheme() const animationDuration = 1000 @@ -36,9 +74,21 @@ export function IsolatedDeploymentFrequencyChart({ data }) { const strokeRollingAverage = '#3b82f6' // Blue 500 const strokeGoal = '#f59e0b' // Amber 500 + const [dfData, setDfData] = useState([]) + console.log(`${process.env.NEXT_PUBLIC_PELORUS_API_URL}/sdp/deployment_frequency/${appName}/data?range=${getDaysBetweenDates(dateRange)}d&start=${dateRange.to.getTime() / 1000}`) + useEffect(() => { + fetch(`${process.env.NEXT_PUBLIC_PELORUS_API_URL}/sdp/deployment_frequency/${appName}/data?range=${getDaysBetweenDates(dateRange)}d&start=${dateRange.to.getTime() / 1000}`) + .then((response) => response.json()).then((data) => data.sort((d1, d2) => (d1.timestamp > d2.timestamp) ? 1 : (d1.timestamp < d2.timestamp) ? -1 : 0 )) + .then((sortedData) => { + setDfData(sortedData) + }) + }, [dateRange, appName]) + + var countPerDay = deploymentsPerDay(dfData); + // Calculate the mean - const averages = data.map(element => { - return element.rollingAverage + const averages = dfData.map(element => { + return element.count }) const chartMean = calculateMean(averages) @@ -52,11 +102,11 @@ export function IsolatedDeploymentFrequencyChart({ data }) { setShowReportDeploymentFrequencyData(true) } - // Anomaly detection - const showAnomalyWarning = data.some((day) => { - if (day.rollingAverage < day.expectedRange[0] || day.rollingAverage > day.expectedRange[1]) { return true } - return false - }) + // // Anomaly detection + // const showAnomalyWarning = dfData.some((day) => { + // if (day.rollingAverage < day.expectedRange[0] || day.rollingAverage > day.expectedRange[1]) { return true } + // return false + // }) const customAnomalyLabel = props => { return ( @@ -70,20 +120,20 @@ export function IsolatedDeploymentFrequencyChart({ data }) { return ( <> - + - - + + } cursor={{ stroke: strokeCursor }} /> - + - {data.map((day, index) => ( + {/* {dfData.map((day, index) => ( day.rollingAverage < day.expectedRange[0] || day.rollingAverage > day.expectedRange[1] && ( ) - ))} + ))} */} {/* */} diff --git a/components/deployment-frequency/deploymentFrequency.js b/components/deployment-frequency/deploymentFrequency.js new file mode 100644 index 0000000..d391e0e --- /dev/null +++ b/components/deployment-frequency/deploymentFrequency.js @@ -0,0 +1,28 @@ +// components/deploymentFrequency.js +import { useState, useEffect } from 'react'; +import { getDaysBetweenDates } from '@/components/date-range-selector' + +export default function useDeploymentFrequencyData(appName, dateRange) { + const [dfData, setDfData] = useState([]); + const [loading, setLoading] = useState(true); // Add loading state + + useEffect(() => { + const fetchData = async () => { + try { + const req = `${process.env.NEXT_PUBLIC_PELORUS_API_URL}/sdp/deployment_frequency/${appName}/data?range=${getDaysBetweenDates(dateRange)}d&start=${dateRange.to.getTime() / 1000}`; + const response = await fetch(req); + const data = await response.json(); + const sortedData = data.sort((d1, d2) => (d1.timestamp > d2.timestamp) ? 1 : (d1.timestamp < d2.timestamp) ? -1 : 0); + setDfData(sortedData); + } catch (error) { + console.error('Error fetching deployment frequency data:', error); + } finally { + setLoading(false); // Set loading to false regardless of success or failure + } + }; + + fetchData(); + }, [appName, dateRange]); + + return { dfData, loading }; // Return loading state along with dfData +} diff --git a/components/deployment-frequency/tab-trigger.jsx b/components/deployment-frequency/tab-trigger.jsx index ceb73eb..af80399 100644 --- a/components/deployment-frequency/tab-trigger.jsx +++ b/components/deployment-frequency/tab-trigger.jsx @@ -36,7 +36,7 @@ export function DeploymentFrequencyTabTrigger({ dateRange, data, appName }) { if (isLoading) return

Loading...

if (!response) return

No cfr data!

- const chartMean = response.df + const chartMean = response.df / getDaysBetweenDates(dateRange) const percentChange = Math.round((response.df / response.last) * 100) // Anomaly detection diff --git a/components/deployment-frequency/table.jsx b/components/deployment-frequency/table.jsx index c8e7173..c2e010b 100644 --- a/components/deployment-frequency/table.jsx +++ b/components/deployment-frequency/table.jsx @@ -1,5 +1,6 @@ 'use client' +import { useState, useEffect } from 'react'; import { Table, TableBody, @@ -9,251 +10,49 @@ import { TableHeader, TableRow, } from "@/components/ui/table" -import { Calendar, GitBranch, GitCommit, Globe } from "lucide-react" +import { Orbit, FileCode, Clock } from "lucide-react" +import useDeploymentFrequencyData from './deploymentFrequency'; +import {dateFormatter, dayFormatter} from '@/lib/date-funcs'; + +export function DeploymentFrequencyTable({ dateRange, appName }) { + + const { dfData, loading } = useDeploymentFrequencyData(appName, dateRange); + console.log(dfData) + + if (loading) { + return
Loading...
; // Render loading state while data is being fetched + } -export function DeploymentFrequencyTable({ data }) { return (
- Deployment name - Service + Deployment Name + Image Sha Date - Status - - -
- - project-crimson-prototype-q6yb8whv -
-
- -
- Main -
-
- 0577e50 Commit name description -
-
- -
- - Jul 26, 2023 -
-
-
Ready
-
- - -
- - project-crimson-prototype-q6yb8whv -
-
- -
- Main -
-
- 0577e50 Commit name description -
-
- -
- - Jul 26, 2023 -
-
-
Ready
-
- - -
- - project-crimson-prototype-q6yb8whv -
-
- -
- Main -
-
- 0577e50 Commit name description -
-
- -
- - Jul 26, 2023 -
-
-
Ready
-
- - -
- - project-crimson-prototype-q6yb8whv -
-
- -
- Main -
-
- 0577e50 Commit name description -
-
- -
- - Jul 26, 2023 -
-
-
Ready
-
- - -
- - project-crimson-prototype-q6yb8whv -
-
- -
- Main -
-
- 0577e50 Commit name description -
-
- -
- - Jul 26, 2023 -
-
-
Ready
-
- - -
- - project-crimson-prototype-q6yb8whv -
-
- -
- Main -
-
- 0577e50 Commit name description -
-
- -
- - Jul 26, 2023 -
-
-
Ready
-
- - -
- - project-crimson-prototype-q6yb8whv -
-
- -
- Main -
-
- 0577e50 Commit name description -
-
- -
- - Jul 26, 2023 -
-
-
Ready
-
- - -
- - project-crimson-prototype-q6yb8whv -
-
- -
- Main -
-
- 0577e50 Commit name description -
-
- -
- - Jul 26, 2023 -
-
-
Ready
-
- - -
- - project-crimson-prototype-q6yb8whv -
-
- -
- Main -
-
- 0577e50 Commit name description -
-
- -
- - Jul 26, 2023 -
-
-
Ready
-
- - -
- - project-crimson-prototype-q6yb8whv -
-
- -
- Main -
-
- 0577e50 Commit name description -
-
- -
- - Jul 26, 2023 -
-
-
Ready
-
-
+ {dfData.map (({ image, timestamp }) => ( + + +
+ {appName} +
+
+ +
+ {image} +
+
+ +
+ { dateFormatter(timestamp) } +
+
+
+ ))} +
) } \ No newline at end of file diff --git a/components/lead-time-for-change/table.jsx b/components/lead-time-for-change/table.jsx index ee1603c..92280aa 100644 --- a/components/lead-time-for-change/table.jsx +++ b/components/lead-time-for-change/table.jsx @@ -11,18 +11,8 @@ import { TableRow, } from "@/components/ui/table" import { Calendar, GitBranch, GitCommit, Globe, Timer } from "lucide-react" -import { format } from 'date-fns' import { getDaysBetweenDates } from '@/components/date-range-selector' - -const dateFormatter = epoch => { - const date = new Date(epoch * 1000) - return format(new Date(date), "MMM d") -} - -const dayFormatter = seconds => { - const days = parseFloat(seconds / 86400).toFixed(2) - return days + "d" -} +import {dateFormatter, dayFormatter} from '@/lib/date-funcs'; export function LeadTimeForChangeTable({ dateRange, appName }) { diff --git a/lib/date-funcs.js b/lib/date-funcs.js new file mode 100644 index 0000000..8c45500 --- /dev/null +++ b/lib/date-funcs.js @@ -0,0 +1,11 @@ +import { format } from 'date-fns' + +export const dateFormatter = epoch => { + const date = new Date(epoch * 1000) + return format(new Date(date), "MMM d") + } + +export const dayFormatter = seconds => { + const days = parseFloat(seconds / 86400).toFixed(2) + return days + "d" +} \ No newline at end of file From a7b2d2a211d0fe83f4bcad3aa458a2cfae6ae24b Mon Sep 17 00:00:00 2001 From: Eric Sauer Date: Fri, 8 Mar 2024 15:07:32 +0000 Subject: [PATCH 36/42] Further refactors --- components/deployment-frequency/chart.jsx | 43 +++++++++-------------- components/deployment-frequency/table.jsx | 2 +- 2 files changed, 17 insertions(+), 28 deletions(-) diff --git a/components/deployment-frequency/chart.jsx b/components/deployment-frequency/chart.jsx index b1699c1..32158f2 100644 --- a/components/deployment-frequency/chart.jsx +++ b/components/deployment-frequency/chart.jsx @@ -6,16 +6,8 @@ import { format } from 'date-fns' import { XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, ComposedChart, Area, Line, ReferenceLine } from 'recharts' import { DeploymentFrequencyTooltip } from './tooltip' import { getDaysBetweenDates } from '@/components/date-range-selector' - -const dateFormatter = epoch => { - const date = new Date(epoch * 1000) - return format(new Date(date), "MMM d") -} - -const dayFormatter = seconds => { - const days = parseFloat(seconds / 86400).toFixed(2) - return days + "d" -} +import useDeploymentFrequencyData from './deploymentFrequency'; +import { dateFormatter, dayFormatter } from '@/lib/date-funcs'; const calculateMean = data => { if (data.length < 1) { @@ -74,15 +66,12 @@ export function DeploymentFrequencyChart({ dateRange, appName }) { const strokeRollingAverage = '#3b82f6' // Blue 500 const strokeGoal = '#f59e0b' // Amber 500 - const [dfData, setDfData] = useState([]) - console.log(`${process.env.NEXT_PUBLIC_PELORUS_API_URL}/sdp/deployment_frequency/${appName}/data?range=${getDaysBetweenDates(dateRange)}d&start=${dateRange.to.getTime() / 1000}`) - useEffect(() => { - fetch(`${process.env.NEXT_PUBLIC_PELORUS_API_URL}/sdp/deployment_frequency/${appName}/data?range=${getDaysBetweenDates(dateRange)}d&start=${dateRange.to.getTime() / 1000}`) - .then((response) => response.json()).then((data) => data.sort((d1, d2) => (d1.timestamp > d2.timestamp) ? 1 : (d1.timestamp < d2.timestamp) ? -1 : 0 )) - .then((sortedData) => { - setDfData(sortedData) - }) - }, [dateRange, appName]) + const { dfData, loading } = useDeploymentFrequencyData(appName, dateRange); + console.log(dfData) + + if (loading) { + return
Loading...
; // Render loading state while data is being fetched + } var countPerDay = deploymentsPerDay(dfData); @@ -93,14 +82,14 @@ export function DeploymentFrequencyChart({ dateRange, appName }) { const chartMean = calculateMean(averages) - // Reports - const [reportDeploymentFrequencyData, setReportDeploymentFrequencyData] = useState(null) - const [showReportDeploymentFrequencyData, setShowReportDeploymentFrequencyData] = useState(false) + // // Reports + // const [reportDeploymentFrequencyData, setReportDeploymentFrequencyData] = useState(null) + // const [showReportDeploymentFrequencyData, setShowReportDeploymentFrequencyData] = useState(false) - function handleChartClick(event) { - setReportDeploymentFrequencyData(event) - setShowReportDeploymentFrequencyData(true) - } + // function handleChartClick(event) { + // setReportDeploymentFrequencyData(event) + // setShowReportDeploymentFrequencyData(true) + // } // // Anomaly detection // const showAnomalyWarning = dfData.some((day) => { @@ -120,7 +109,7 @@ export function DeploymentFrequencyChart({ dateRange, appName }) { return ( <> - + diff --git a/components/deployment-frequency/table.jsx b/components/deployment-frequency/table.jsx index c2e010b..4f3aea7 100644 --- a/components/deployment-frequency/table.jsx +++ b/components/deployment-frequency/table.jsx @@ -12,7 +12,7 @@ import { } from "@/components/ui/table" import { Orbit, FileCode, Clock } from "lucide-react" import useDeploymentFrequencyData from './deploymentFrequency'; -import {dateFormatter, dayFormatter} from '@/lib/date-funcs'; +import { dateFormatter } from '@/lib/date-funcs'; export function DeploymentFrequencyTable({ dateRange, appName }) { From c01797a6a827a408d87c7ea0e681cc3060153a3e Mon Sep 17 00:00:00 2001 From: Eric Sauer Date: Fri, 8 Mar 2024 21:13:27 +0000 Subject: [PATCH 37/42] fix issue with locality in the chart rendering --- components/deployment-frequency/chart.jsx | 10 ++++------ components/deployment-frequency/table.jsx | 2 +- components/deployment-frequency/tooltip.jsx | 2 +- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/components/deployment-frequency/chart.jsx b/components/deployment-frequency/chart.jsx index 32158f2..0ae9009 100644 --- a/components/deployment-frequency/chart.jsx +++ b/components/deployment-frequency/chart.jsx @@ -1,11 +1,8 @@ 'use client' -import { useState, useEffect } from 'react' import { useTheme } from "next-themes" -import { format } from 'date-fns' import { XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, ComposedChart, Area, Line, ReferenceLine } from 'recharts' import { DeploymentFrequencyTooltip } from './tooltip' -import { getDaysBetweenDates } from '@/components/date-range-selector' import useDeploymentFrequencyData from './deploymentFrequency'; import { dateFormatter, dayFormatter } from '@/lib/date-funcs'; @@ -25,7 +22,7 @@ function deploymentsPerDay(data) { // Convert timestamp to Date object and extract the date const date = new Date(item.timestamp * 1000); // Format the date to YYYY-MM-DD - const formattedDate = date.toISOString().split('T')[0]; + const formattedDate = date.toLocaleDateString().split('T')[0]; // Increment the count of deployments for the corresponding date if (deploymentsCountPerDay[formattedDate]) { @@ -67,13 +64,14 @@ export function DeploymentFrequencyChart({ dateRange, appName }) { const strokeGoal = '#f59e0b' // Amber 500 const { dfData, loading } = useDeploymentFrequencyData(appName, dateRange); - console.log(dfData) + console.log('Chart dfData: ', dfData) if (loading) { return
Loading...
; // Render loading state while data is being fetched } var countPerDay = deploymentsPerDay(dfData); + console.log('Count per day', countPerDay) // Calculate the mean const averages = dfData.map(element => { @@ -112,7 +110,7 @@ export function DeploymentFrequencyChart({ dateRange, appName }) { - + } cursor={{ stroke: strokeCursor }} /> diff --git a/components/deployment-frequency/table.jsx b/components/deployment-frequency/table.jsx index 4f3aea7..2f6f1dd 100644 --- a/components/deployment-frequency/table.jsx +++ b/components/deployment-frequency/table.jsx @@ -17,7 +17,7 @@ import { dateFormatter } from '@/lib/date-funcs'; export function DeploymentFrequencyTable({ dateRange, appName }) { const { dfData, loading } = useDeploymentFrequencyData(appName, dateRange); - console.log(dfData) + console.log('Table dfData: ', dfData) if (loading) { return
Loading...
; // Render loading state while data is being fetched diff --git a/components/deployment-frequency/tooltip.jsx b/components/deployment-frequency/tooltip.jsx index b68aa76..f5611d8 100644 --- a/components/deployment-frequency/tooltip.jsx +++ b/components/deployment-frequency/tooltip.jsx @@ -23,7 +23,7 @@ export const DeploymentFrequencyTooltip = ({ active, payload, label }) => { if (active && payload && payload.length) { return (
-
{format(new Date(label), "MMM d, y")}
+
{format(new Date(label * 1000), "MMM d, y")}
{payload.map((data, index) => (
From 990c036b7a1cca3fa0be44840890ded4a8008468 Mon Sep 17 00:00:00 2001 From: Eric Sauer Date: Wed, 13 Mar 2024 20:56:12 +0000 Subject: [PATCH 38/42] Code changes for enabling the MTTR chart and table, plus icon improvements --- components/dashboard.jsx | 6 +- .../deployment-frequency/tab-trigger.jsx | 2 +- components/deployment-frequency/table.jsx | 8 +- components/lead-time-for-change/table.jsx | 6 +- .../{iso-chart.jsx => chart.jsx} | 53 +-- .../meantimetorestore.js | 29 ++ components/mean-time-to-recovery/table.jsx | 316 +++--------------- components/mean-time-to-recovery/tooltip.jsx | 2 +- package.json | 2 +- 9 files changed, 111 insertions(+), 313 deletions(-) rename components/mean-time-to-recovery/{iso-chart.jsx => chart.jsx} (67%) create mode 100644 components/mean-time-to-recovery/meantimetorestore.js diff --git a/components/dashboard.jsx b/components/dashboard.jsx index ce1862e..a76ed83 100644 --- a/components/dashboard.jsx +++ b/components/dashboard.jsx @@ -18,7 +18,7 @@ import { import { DeploymentFrequencyChart } from '@/components/deployment-frequency/chart' import { LeadTimeForChangeChart } from '@/components/lead-time-for-change/chart' import { IsolatedChangeFailureRateChart } from '@/components/change-failure-rate/iso-chart' -import { IsolatedMeanTimeToRecoveryChart } from '@/components/mean-time-to-recovery/iso-chart' +import { MeanTimeToRecoveryChart } from '@/components/mean-time-to-recovery/chart' import { DeploymentFrequencyTabTrigger } from './deployment-frequency/tab-trigger' import { ChangeFailureRateTabTrigger } from './change-failure-rate/tab-trigger' @@ -170,14 +170,14 @@ export function Dashboard({ data, appList }) {
- +

Pull requests

- +
diff --git a/components/deployment-frequency/tab-trigger.jsx b/components/deployment-frequency/tab-trigger.jsx index af80399..263fcaa 100644 --- a/components/deployment-frequency/tab-trigger.jsx +++ b/components/deployment-frequency/tab-trigger.jsx @@ -1,6 +1,6 @@ 'use client' -import { Rocket, ArrowDown, ArrowUp } from 'lucide-react' +import { Rocket, ArrowUp } from 'lucide-react' import { Badge } from '@/components/ui/badge' import { InfoTooltip } from '@/components/info-tooltip' import { DeploymentFrequencyRating } from '@/components/deployment-frequency/rating' diff --git a/components/deployment-frequency/table.jsx b/components/deployment-frequency/table.jsx index 2f6f1dd..462d536 100644 --- a/components/deployment-frequency/table.jsx +++ b/components/deployment-frequency/table.jsx @@ -10,7 +10,7 @@ import { TableHeader, TableRow, } from "@/components/ui/table" -import { Orbit, FileCode, Clock } from "lucide-react" +import { Orbit, FileCode, CalendarCheck, Blocks, Fingerprint } from "lucide-react" import useDeploymentFrequencyData from './deploymentFrequency'; import { dateFormatter } from '@/lib/date-funcs'; @@ -37,17 +37,17 @@ export function DeploymentFrequencyTable({ dateRange, appName }) {
- {appName} + {appName}
- {image} + {image}
- { dateFormatter(timestamp) } + { dateFormatter(timestamp) }
diff --git a/components/lead-time-for-change/table.jsx b/components/lead-time-for-change/table.jsx index 92280aa..70e34fe 100644 --- a/components/lead-time-for-change/table.jsx +++ b/components/lead-time-for-change/table.jsx @@ -10,7 +10,7 @@ import { TableHeader, TableRow, } from "@/components/ui/table" -import { Calendar, GitBranch, GitCommit, Globe, Timer } from "lucide-react" +import { Calendar, GitCommit, GitGraph, Timer } from "lucide-react" import { getDaysBetweenDates } from '@/components/date-range-selector' import {dateFormatter, dayFormatter} from '@/lib/date-funcs'; @@ -39,12 +39,12 @@ export function LeadTimeForChangeTable({ dateRange, appName }) {
- {commit} + {commit}
- { dateFormatter(timestamp) } + { dateFormatter(timestamp) }
diff --git a/components/mean-time-to-recovery/iso-chart.jsx b/components/mean-time-to-recovery/chart.jsx similarity index 67% rename from components/mean-time-to-recovery/iso-chart.jsx rename to components/mean-time-to-recovery/chart.jsx index 93f8c30..0e228aa 100644 --- a/components/mean-time-to-recovery/iso-chart.jsx +++ b/components/mean-time-to-recovery/chart.jsx @@ -5,10 +5,8 @@ import { useTheme } from "next-themes" import { format } from 'date-fns' import { XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, ComposedChart, Area, Line, ReferenceLine } from 'recharts' import { MeanTimeToRecoveryTooltip } from './tooltip' - -const dateFormatter = date => { - return format(new Date(date), "MMM d") -} +import useMeanTimeToRestore from './meantimetorestore' +import { dateFormatter, dayFormatter } from '@/lib/date-funcs'; const calculateMean = data => { if (data.length < 1) { @@ -17,7 +15,7 @@ const calculateMean = data => { return data.reduce((prev, current) => prev + current) / data.length } -export function IsolatedMeanTimeToRecoveryChart({ data }) { +export function MeanTimeToRecoveryChart({ appName, dateRange }) { const { resolvedTheme } = useTheme() const animationDuration = 1000 @@ -36,27 +34,34 @@ export function IsolatedMeanTimeToRecoveryChart({ data }) { const strokeRollingAverage = '#10b981' // Emerald 500 const strokeGoal = '#f59e0b' // Amber 500 + const { mttrData, loading } = useMeanTimeToRestore(appName, dateRange); + console.log('Chart mttrData: ', mttrData) + + if (loading) { + return
Loading...
; // Render loading state while data is being fetched + } + // Calculate the mean - const averages = data.map(element => { + const averages = mttrData.map(element => { return element.rollingAverage }) const chartMean = calculateMean(averages) - // Reports - const [reportMeanTimeToRecoveryData, setReportMeanTimeToRecoveryData] = useState(null) - const [showReportMeanTimeToRecoveryData, setShowReportMeanTimeToRecoveryData] = useState(false) + // // Reports + // const [reportMeanTimeToRecoveryData, setReportMeanTimeToRecoveryData] = useState(null) + // const [showReportMeanTimeToRecoveryData, setShowReportMeanTimeToRecoveryData] = useState(false) - function handleChartClick(event) { - setReportMeanTimeToRecoveryData(event) - setShowReportMeanTimeToRecoveryData(true) - } + // function handleChartClick(event) { + // setReportMeanTimeToRecoveryData(event) + // setShowReportMeanTimeToRecoveryData(true) + // } - // Anomaly detection - const showAnomalyWarning = data.some((day) => { - if (day.rollingAverage < day.expectedRange[0] || day.rollingAverage > day.expectedRange[1]) { return true } - return false - }) + // // Anomaly detection + // const showAnomalyWarning = data.some((day) => { + // if (day.rollingAverage < day.expectedRange[0] || day.rollingAverage > day.expectedRange[1]) { return true } + // return false + // }) const customAnomalyLabel = props => { return ( @@ -70,20 +75,20 @@ export function IsolatedMeanTimeToRecoveryChart({ data }) { return ( <> - + - - `${tick}d`} /> + + } cursor={{ stroke: strokeCursor }} /> - + - {data.map((day, index) => ( + {/* {data.map((day, index) => ( day.rollingAverage < day.expectedRange[0] || day.rollingAverage > day.expectedRange[1] && ( ) - ))} + ))} */} {/* */} diff --git a/components/mean-time-to-recovery/meantimetorestore.js b/components/mean-time-to-recovery/meantimetorestore.js new file mode 100644 index 0000000..7d9af01 --- /dev/null +++ b/components/mean-time-to-recovery/meantimetorestore.js @@ -0,0 +1,29 @@ +// components/deploymentFrequency.js +import { useState, useEffect } from 'react'; +import { getDaysBetweenDates } from '@/components/date-range-selector' + +export default function useMeanTimeToRestore(appName, dateRange) { + const [mttrData, setMttrData] = useState([]); + const [loading, setLoading] = useState(true); // Add loading state + + useEffect(() => { + const fetchData = async () => { + try { + const req = `${process.env.NEXT_PUBLIC_PELORUS_API_URL}/sdp/mean_time_to_restore/${appName}/data?range=${getDaysBetweenDates(dateRange)}d&start=${dateRange.to.getTime() / 1000}`; + console.log(req) + const response = await fetch(req); + const data = await response.json(); + const sortedData = data.sort((d1, d2) => (d1.timestamp > d2.timestamp) ? 1 : (d1.timestamp < d2.timestamp) ? -1 : 0); + setMttrData(sortedData); + } catch (error) { + console.error('Error fetching deployment frequency data:', error); + } finally { + setLoading(false); // Set loading to false regardless of success or failure + } + }; + + fetchData(); + }, [appName, dateRange]); + + return { mttrData, loading }; // Return loading state along with dfData +} \ No newline at end of file diff --git a/components/mean-time-to-recovery/table.jsx b/components/mean-time-to-recovery/table.jsx index 17266c4..b98fbe3 100644 --- a/components/mean-time-to-recovery/table.jsx +++ b/components/mean-time-to-recovery/table.jsx @@ -9,290 +9,54 @@ import { TableHeader, TableRow, } from "@/components/ui/table" -import { Calendar, GitBranch, GitCommit, Globe, Timer } from "lucide-react" +import { Orbit, FileCode, CalendarCheck, Ticket, Timer } from "lucide-react" +import useMeanTimeToRestore from "./meantimetorestore"; +import { dateFormatter, dayFormatter } from '@/lib/date-funcs'; + +export function MeanTimeToRecoveryTable({ dateRange, appName }) { + + const { mttrData, loading } = useMeanTimeToRestore(appName, dateRange); + console.log('Table mttrData: ', mttrData) + + if (loading) { + return
Loading...
; // Render loading state while data is being fetched + } -export function MeanTimeToRecoveryTable({ data }) { return ( - Failed deployment - Pull request - Mean time - Status + Ticket Reference + App Name + Time to Resolve + Date Resolved - - -
- Main -
-
- project-crimson-prototype-q6yb8whv -
-
Jul 19, 2023
-
- -
- Feature/authentication -
-
- 0577e50 Commit name description -
-
Jul 26, 2023
-
- -
- - 8 days -
-
-
Ready
-
- - -
- Main -
-
- project-crimson-prototype-q6yb8whv -
-
Jul 19, 2023
-
- -
- Feature/authentication -
-
- 0577e50 Commit name description -
-
Jul 26, 2023
-
- -
- - 8 days -
-
-
Ready
-
- - -
- Main -
-
- project-crimson-prototype-q6yb8whv -
-
Jul 19, 2023
-
- -
- Feature/authentication -
-
- 0577e50 Commit name description -
-
Jul 26, 2023
-
- -
- - 8 days -
-
-
Ready
-
- - -
- Main -
-
- project-crimson-prototype-q6yb8whv -
-
Jul 19, 2023
-
- -
- Feature/authentication -
-
- 0577e50 Commit name description -
-
Jul 26, 2023
-
- -
- - 8 days -
-
-
Ready
-
- - -
- Main -
-
- project-crimson-prototype-q6yb8whv -
-
Jul 19, 2023
-
- -
- Feature/authentication -
-
- 0577e50 Commit name description -
-
Jul 26, 2023
-
- -
- - 8 days -
-
-
Ready
-
- - -
- Main -
-
- project-crimson-prototype-q6yb8whv -
-
Jul 19, 2023
-
- -
- Feature/authentication -
-
- 0577e50 Commit name description -
-
Jul 26, 2023
-
- -
- - 8 days -
-
-
Ready
-
- - -
- Main -
-
- project-crimson-prototype-q6yb8whv -
-
Jul 19, 2023
-
- -
- Feature/authentication -
-
- 0577e50 Commit name description -
-
Jul 26, 2023
-
- -
- - 8 days -
-
-
Ready
-
- - -
- Main -
-
- project-crimson-prototype-q6yb8whv -
-
Jul 19, 2023
-
- -
- Feature/authentication -
-
- 0577e50 Commit name description -
-
Jul 26, 2023
-
- -
- - 8 days -
-
-
Ready
-
- - -
- Main -
-
- project-crimson-prototype-q6yb8whv -
-
Jul 19, 2023
-
- -
- Feature/authentication -
-
- 0577e50 Commit name description -
-
Jul 26, 2023
-
- -
- - 8 days -
-
-
Ready
-
- - -
- Main -
-
- project-crimson-prototype-q6yb8whv -
-
Jul 19, 2023
-
- -
- Feature/authentication -
-
- 0577e50 Commit name description -
-
Jul 26, 2023
-
- -
- - 8 days -
-
-
Ready
-
+ {mttrData.map (({ issue_id, time_to_resolve, timestamp }) => ( + + +
+ {issue_id} +
+
+ +
+ {appName} +
+
+ +
+ {dayFormatter(time_to_resolve)} +
+
+ +
+ { dateFormatter(timestamp) } +
+
+
+ ))}
) diff --git a/components/mean-time-to-recovery/tooltip.jsx b/components/mean-time-to-recovery/tooltip.jsx index a21aa30..81da4a2 100644 --- a/components/mean-time-to-recovery/tooltip.jsx +++ b/components/mean-time-to-recovery/tooltip.jsx @@ -23,7 +23,7 @@ export const MeanTimeToRecoveryTooltip = ({ active, payload, label }) => { if (active && payload && payload.length) { return (
-
{format(new Date(label), "MMM d, y")}
+
{format(new Date(label * 1000), "MMM d, y")}
{payload.map((data, index) => (
diff --git a/package.json b/package.json index d886a9d..aae0817 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "eslint-config-next": "13.4.12", "framer-motion": "^10.13.1", "init": "^0.1.2", - "lucide-react": "^0.263.1", + "lucide-react": "^0.315.0", "next": "^13.4.12", "next-auth": "^4.22.3", "next-themes": "^0.2.1", From 82fe01cbe52864a4cdb5603aebf0c232a1155842 Mon Sep 17 00:00:00 2001 From: Eric Sauer Date: Thu, 14 Mar 2024 14:31:10 +0000 Subject: [PATCH 39/42] Enable chart and table for change failure rate --- .../change-failure-rate/changeFailureRate.js | 28 ++ .../{iso-chart.jsx => chart.jsx} | 88 ++++-- components/change-failure-rate/table.jsx | 270 +++--------------- components/change-failure-rate/tooltip.jsx | 4 +- components/dashboard.jsx | 7 +- 5 files changed, 131 insertions(+), 266 deletions(-) create mode 100644 components/change-failure-rate/changeFailureRate.js rename components/change-failure-rate/{iso-chart.jsx => chart.jsx} (55%) diff --git a/components/change-failure-rate/changeFailureRate.js b/components/change-failure-rate/changeFailureRate.js new file mode 100644 index 0000000..c60947e --- /dev/null +++ b/components/change-failure-rate/changeFailureRate.js @@ -0,0 +1,28 @@ +// components/deploymentFrequency.js +import { useState, useEffect } from 'react'; +import { getDaysBetweenDates } from '@/components/date-range-selector' + +export default function useChangeFailureRate(appName, dateRange) { + const [cfrData, setCfrData] = useState([]); + const [loading, setLoading] = useState(true); // Add loading state + + useEffect(() => { + const fetchData = async () => { + try { + const req = `${process.env.NEXT_PUBLIC_PELORUS_API_URL}/sdp/change_failure_rate/${appName}/data?range=${getDaysBetweenDates(dateRange)}d&start=${dateRange.to.getTime() / 1000}`; + const response = await fetch(req); + const data = await response.json(); + const sortedData = data.sort((d1, d2) => (d1.timestamp > d2.timestamp) ? 1 : (d1.timestamp < d2.timestamp) ? -1 : 0); + setCfrData(sortedData); + } catch (error) { + console.error('Error fetching deployment frequency data:', error); + } finally { + setLoading(false); // Set loading to false regardless of success or failure + } + }; + + fetchData(); + }, [appName, dateRange]); + + return { cfrData, loading }; // Return loading state along with cfrData +} diff --git a/components/change-failure-rate/iso-chart.jsx b/components/change-failure-rate/chart.jsx similarity index 55% rename from components/change-failure-rate/iso-chart.jsx rename to components/change-failure-rate/chart.jsx index 3fd9c9d..3f186c6 100644 --- a/components/change-failure-rate/iso-chart.jsx +++ b/components/change-failure-rate/chart.jsx @@ -5,10 +5,8 @@ import { format } from 'date-fns' import { useTheme } from "next-themes" import { XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, ComposedChart, Area, Line, ReferenceLine } from 'recharts' import { ChangeFailureRateTooltip } from './tooltip' - -const dateFormatter = date => { - return format(new Date(date), "MMM d") -} +import { dateFormatter, dayFormatter } from '@/lib/date-funcs'; +import useChangeFailureRate from './changeFailureRate' const calculateMean = data => { if (data.length < 1) { @@ -17,7 +15,39 @@ const calculateMean = data => { return data.reduce((prev, current) => prev + current) / data.length } -export function IsolatedChangeFailureRateChart({ data }) { +function eventsPerDay(data) { + // Object to store the count of deployments per day + const countPerDay = {}; + + // Loop through each item in the data array + data.forEach(item => { + // Convert timestamp to Date object and extract the date + const date = new Date(item.timestamp * 1000); + // Format the date to YYYY-MM-DD + const formattedDate = date.toLocaleDateString().split('T')[0]; + + // Increment the count of deployments for the corresponding date + if (countPerDay[formattedDate]) { + countPerDay[formattedDate]++; + } else { + countPerDay[formattedDate] = 1; + } + }); + + // Convert the deployments count per day object into an array of objects + const result = Object.keys(countPerDay).map(date => ({ + // Convert date back to epoch format and assign to day_epoch property + day_epoch: new Date(date).getTime() / 1000, + // Assign the count of deployments to the count property + count: countPerDay[date] + })); + + // Return the result + return result; +} + + +export function ChangeFailureRateChart({ dateRange, appName }) { const { resolvedTheme } = useTheme() const animationDuration = 1000 @@ -36,27 +66,37 @@ export function IsolatedChangeFailureRateChart({ data }) { const strokeRollingAverage = '#f43f5e' // Rose 500 const strokeGoal = '#f59e0b' // Amber 500 + const { cfrData, loading } = useChangeFailureRate(appName, dateRange); + console.log('Chart cfrData: ', cfrData) + + if (loading) { + return
Loading...
; // Render loading state while data is being fetched + } + + var countPerDay = eventsPerDay(cfrData); + console.log('Count per day', countPerDay) + // Calculate the mean - const averages = data.map(element => { + const averages = cfrData.map(element => { return element.rollingAverage }) const chartMean = calculateMean(averages) - // Reports - const [reportChangeFailureRateData, setReportChangeFailureRateData] = useState(null) - const [showReportChangeFailureRateData, setShowReportChangeFailureRateData] = useState(false) + // // Reports + // const [reportChangeFailureRateData, setReportChangeFailureRateData] = useState(null) + // const [showReportChangeFailureRateData, setShowReportChangeFailureRateData] = useState(false) - function handleChartClick(event) { - setReportChangeFailureRateData(event) - setShowReportChangeFailureRateData(true) - } + // function handleChartClick(event) { + // setReportChangeFailureRateData(event) + // setShowReportChangeFailureRateData(true) + // } - // Anomaly detection - const showAnomalyWarning = data.some((day) => { - if (day.rollingAverage < day.expectedRange[0] || day.rollingAverage > day.expectedRange[1]) { return true } - return false - }) + // // Anomaly detection + // const showAnomalyWarning = data.some((day) => { + // if (day.rollingAverage < day.expectedRange[0] || day.rollingAverage > day.expectedRange[1]) { return true } + // return false + // }) const customAnomalyLabel = props => { return ( @@ -70,20 +110,20 @@ export function IsolatedChangeFailureRateChart({ data }) { return ( <> - + - - `${tick}%`} /> + + } cursor={{ stroke: strokeCursor }} /> - + - {data.map((day, index) => ( + {/* {data.map((day, index) => ( day.rollingAverage < day.expectedRange[0] || day.rollingAverage > day.expectedRange[1] && ( ) - ))} + ))} */} {/* */} diff --git a/components/change-failure-rate/table.jsx b/components/change-failure-rate/table.jsx index 143914e..af409e3 100644 --- a/components/change-failure-rate/table.jsx +++ b/components/change-failure-rate/table.jsx @@ -9,250 +9,48 @@ import { TableHeader, TableRow, } from "@/components/ui/table" -import { Calendar, GitBranch, GitCommit, Globe } from "lucide-react" +import { Orbit, CalendarCheck, Ticket } from "lucide-react" +import useChangeFailureRate from "./changeFailureRate"; +import { dateFormatter, dayFormatter } from '@/lib/date-funcs'; + +export function ChangeFailureRateTable({ dateRange, appName }) { + + const { cfrData, loading } = useChangeFailureRate(appName, dateRange); + console.log('Table cfrData: ', cfrData) + + if (loading) { + return
Loading...
; // Render loading state while data is being fetched + } -export function ChangeFailureRateTable({ data }) { return ( - Deployment name - Service - Date - Status + Ticket Reference + App Name + Date Created - - -
- - project-crimson-prototype-q6yb8whv -
-
- -
- Main -
-
- 0577e50 Commit name description -
-
- -
- - Jul 26, 2023 -
-
-
Error
-
- - -
- - project-crimson-prototype-q6yb8whv -
-
- -
- Main -
-
- 0577e50 Commit name description -
-
- -
- - Jul 26, 2023 -
-
-
Error
-
- - -
- - project-crimson-prototype-q6yb8whv -
-
- -
- Main -
-
- 0577e50 Commit name description -
-
- -
- - Jul 26, 2023 -
-
-
Error
-
- - -
- - project-crimson-prototype-q6yb8whv -
-
- -
- Main -
-
- 0577e50 Commit name description -
-
- -
- - Jul 26, 2023 -
-
-
Error
-
- - -
- - project-crimson-prototype-q6yb8whv -
-
- -
- Main -
-
- 0577e50 Commit name description -
-
- -
- - Jul 26, 2023 -
-
-
Error
-
- - -
- - project-crimson-prototype-q6yb8whv -
-
- -
- Main -
-
- 0577e50 Commit name description -
-
- -
- - Jul 26, 2023 -
-
-
Error
-
- - -
- - project-crimson-prototype-q6yb8whv -
-
- -
- Main -
-
- 0577e50 Commit name description -
-
- -
- - Jul 26, 2023 -
-
-
Error
-
- - -
- - project-crimson-prototype-q6yb8whv -
-
- -
- Main -
-
- 0577e50 Commit name description -
-
- -
- - Jul 26, 2023 -
-
-
Error
-
- - -
- - project-crimson-prototype-q6yb8whv -
-
- -
- Main -
-
- 0577e50 Commit name description -
-
- -
- - Jul 26, 2023 -
-
-
Error
-
- - -
- - project-crimson-prototype-q6yb8whv -
-
- -
- Main -
-
- 0577e50 Commit name description -
-
- -
- - Jul 26, 2023 -
-
-
Error
-
+ {cfrData.map (({ issue_id, timestamp }) => ( + + +
+ {issue_id} +
+
+ +
+ {appName} +
+
+ +
+ { dateFormatter(timestamp) } +
+
+
+ ))}
) diff --git a/components/change-failure-rate/tooltip.jsx b/components/change-failure-rate/tooltip.jsx index ed3b971..162bd1e 100644 --- a/components/change-failure-rate/tooltip.jsx +++ b/components/change-failure-rate/tooltip.jsx @@ -23,12 +23,12 @@ export const ChangeFailureRateTooltip = ({ active, payload, label }) => { if (active && payload && payload.length) { return (
-
{format(new Date(label), "MMM d, y")}
+
{format(new Date(label * 1000), "MMM d, y")}
{payload.map((data, index) => (
{toSentence(data.name)}
-
{data.value instanceof Array ? data.value.join('-') : data.value}%
+
{data.value instanceof Array ? data.value.join('-') : data.value} issues
))}
diff --git a/components/dashboard.jsx b/components/dashboard.jsx index a76ed83..a112358 100644 --- a/components/dashboard.jsx +++ b/components/dashboard.jsx @@ -17,7 +17,7 @@ import { import { DeploymentFrequencyChart } from '@/components/deployment-frequency/chart' import { LeadTimeForChangeChart } from '@/components/lead-time-for-change/chart' -import { IsolatedChangeFailureRateChart } from '@/components/change-failure-rate/iso-chart' +import { ChangeFailureRateChart } from '@/components/change-failure-rate/chart' import { MeanTimeToRecoveryChart } from '@/components/mean-time-to-recovery/chart' import { DeploymentFrequencyTabTrigger } from './deployment-frequency/tab-trigger' @@ -91,7 +91,6 @@ export function Dashboard({ data, appList }) {
-
@@ -158,14 +157,14 @@ export function Dashboard({ data, appList }) {
- +

Failed deployments

- +
From 2989a1fb99bfae4f1d16805ed4d8ed93cf38c73a Mon Sep 17 00:00:00 2001 From: Eric Sauer Date: Thu, 14 Mar 2024 15:55:27 +0000 Subject: [PATCH 40/42] Get change sine list badges working for mttr and cfr --- components/change-failure-rate/tab-trigger.jsx | 3 ++- components/mean-time-to-recovery/tab-trigger.jsx | 8 ++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/components/change-failure-rate/tab-trigger.jsx b/components/change-failure-rate/tab-trigger.jsx index 6488d12..edd48f6 100644 --- a/components/change-failure-rate/tab-trigger.jsx +++ b/components/change-failure-rate/tab-trigger.jsx @@ -37,6 +37,7 @@ export function ChangeFailureRateTabTrigger({ dateRange, data, appName }) { if (!response) return

No cfr data!

const chartMean = response.cfr * 100 + const percentChange = Math.round((1 - (response.cfr / response.last)) * 100) // Anomaly detection const showAnomalyWarning = data.some((day) => { @@ -57,7 +58,7 @@ export function ChangeFailureRateTabTrigger({ dateRange, data, appName }) {
{parseFloat(chartMean).toFixed(2)}% - 16% + {percentChange}%
diff --git a/components/mean-time-to-recovery/tab-trigger.jsx b/components/mean-time-to-recovery/tab-trigger.jsx index ff7cd30..25c7b54 100644 --- a/components/mean-time-to-recovery/tab-trigger.jsx +++ b/components/mean-time-to-recovery/tab-trigger.jsx @@ -24,8 +24,10 @@ export function MeanTimeToRecoveryTabTrigger({ dateRange, data, appName }) { const [response, setResponse] = useState([]) const [isLoading, setLoading] = useState(true) + var req = `${process.env.NEXT_PUBLIC_PELORUS_API_URL}/sdp/mean_time_to_restore/${appName}?range=${getDaysBetweenDates(dateRange)}d&start=${dateRange.to.getTime() / 1000}` + console.log(req) useEffect(() => { - fetch(`${process.env.NEXT_PUBLIC_PELORUS_API_URL}/sdp/mean_time_to_restore/${appName}?range=${getDaysBetweenDates(dateRange)}d&start=${dateRange.to.getTime() / 1000}`) + fetch(req) .then((test) => test.json()) .then((response) => { setResponse(response) @@ -35,7 +37,9 @@ export function MeanTimeToRecoveryTabTrigger({ dateRange, data, appName }) { if (isLoading) return

Loading...

if (!response) return

No cfr data!

+ const chartMean = response.mttr / 86400 + const percentChange = Math.round((1 - (response.mttr / response.last)) * 100) // Anomaly detection const showAnomalyWarning = data.some((day) => { @@ -56,7 +60,7 @@ export function MeanTimeToRecoveryTabTrigger({ dateRange, data, appName }) {
{parseFloat(chartMean).toFixed(5)} days - 16% + {percentChange}%
From c31c918a0692f74050c4d2ffd00613b0bdcff625 Mon Sep 17 00:00:00 2001 From: Eric Sauer Date: Fri, 15 Mar 2024 08:06:51 -0500 Subject: [PATCH 41/42] Modify the 'to' date to the end of the day (i.e. 23:59:59) instead of the start so we get data for that day (#8) --- components/date-range-selector.jsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/components/date-range-selector.jsx b/components/date-range-selector.jsx index 8ab7d06..da2632e 100644 --- a/components/date-range-selector.jsx +++ b/components/date-range-selector.jsx @@ -65,6 +65,9 @@ export function DateRangeSelector({activeDateRange, setActiveDateRange}) { // This handler will prevent errors when only 'to' or 'from' are selected const handleApplyClick = function () { if (isComplete(dateRange) && isValidDateRange(dateRange)) { // Add isValidDateRange check + console.log("Handle Apply Click", dateRange) + // Set "to" date to end of day (23:59:59,999) instead of start + dateRange.to.setHours(23, 59, 59, 999) setActiveDateRange(dateRange); } }; From 0755cf8f9ce94e6ea72bd31c5b4b3e056dab9519 Mon Sep 17 00:00:00 2001 From: Eric Sauer Date: Fri, 15 Mar 2024 08:41:18 -0500 Subject: [PATCH 42/42] Refactor to gather all the API calls into one place (#9) * Refactor to gather all the API calls into one place * Fix spacing --- .../change-failure-rate/changeFailureRate.js | 26 +++++++++- .../change-failure-rate/tab-trigger.jsx | 21 +++----- components/deployment-frequency/chart.jsx | 2 +- .../deploymentFrequency.js | 26 +++++++++- .../deployment-frequency/tab-trigger.jsx | 21 +++----- components/lead-time-for-change/chart.jsx | 33 ++++++------ .../lead-time-for-change/leadTimeForChange.js | 52 +++++++++++++++++++ .../lead-time-for-change/tab-trigger.jsx | 21 +++----- components/lead-time-for-change/table.jsx | 17 +++--- components/mean-time-to-recovery/chart.jsx | 2 +- .../meantimetorestore.js | 28 +++++++++- .../mean-time-to-recovery/tab-trigger.jsx | 23 +++----- components/mean-time-to-recovery/table.jsx | 2 +- 13 files changed, 181 insertions(+), 93 deletions(-) create mode 100644 components/lead-time-for-change/leadTimeForChange.js diff --git a/components/change-failure-rate/changeFailureRate.js b/components/change-failure-rate/changeFailureRate.js index c60947e..8b5fd93 100644 --- a/components/change-failure-rate/changeFailureRate.js +++ b/components/change-failure-rate/changeFailureRate.js @@ -2,7 +2,7 @@ import { useState, useEffect } from 'react'; import { getDaysBetweenDates } from '@/components/date-range-selector' -export default function useChangeFailureRate(appName, dateRange) { +export default function fetchChangeFailureRateData(appName, dateRange) { const [cfrData, setCfrData] = useState([]); const [loading, setLoading] = useState(true); // Add loading state @@ -26,3 +26,27 @@ export default function useChangeFailureRate(appName, dateRange) { return { cfrData, loading }; // Return loading state along with cfrData } + +export function fetchChangeFailureRate(appName, dateRange) { + const [response, setResponse] = useState([]) + const [loading, setLoading] = useState(true) + + useEffect(() => { + const fetchData = async () => { + try { + const req = `${process.env.NEXT_PUBLIC_PELORUS_API_URL}/sdp/change_failure_rate/${appName}?range=${getDaysBetweenDates(dateRange)}d&start=${dateRange.to.getTime() / 1000}`; + const response = await fetch(req); + const data = await response.json() + setResponse(data) + } catch(error) { + console.error('Error fetching deployment frequency data:', error); + } finally { + setLoading(false) + } + }; + + fetchData(); + }, [dateRange, appName]); + + return { response, loading }; // Return loading state along with cfrData +} diff --git a/components/change-failure-rate/tab-trigger.jsx b/components/change-failure-rate/tab-trigger.jsx index edd48f6..dbfafaf 100644 --- a/components/change-failure-rate/tab-trigger.jsx +++ b/components/change-failure-rate/tab-trigger.jsx @@ -6,6 +6,7 @@ import { InfoTooltip } from '@/components/info-tooltip' import { ChangeFailureRateRating } from './rating' import { useState, useEffect } from "react" import { getDaysBetweenDates } from '@/components/date-range-selector' +import { fetchChangeFailureRate } from './changeFailureRate' export function ChangeFailureRateTabTrigger({ dateRange, data, appName }) { @@ -21,20 +22,12 @@ export function ChangeFailureRateTabTrigger({ dateRange, data, appName }) { return element.rollingAverage }) - const [response, setResponse] = useState([]) - const [isLoading, setLoading] = useState(true) + const { response, loading } = fetchChangeFailureRate(appName, dateRange); + console.log('Chart cfr: ', response) - useEffect(() => { - fetch(`${process.env.NEXT_PUBLIC_PELORUS_API_URL}/sdp/change_failure_rate/${appName}?range=${getDaysBetweenDates(dateRange)}d&start=${dateRange.to.getTime() / 1000}`) - .then((test) => test.json()) - .then((response) => { - setResponse(response) - setLoading(false) - }) - }, [dateRange, appName]); - - if (isLoading) return

Loading...

- if (!response) return

No cfr data!

+ if (loading) { + return
Loading...
; // Render loading state while data is being fetched + } const chartMean = response.cfr * 100 const percentChange = Math.round((1 - (response.cfr / response.last)) * 100) @@ -60,7 +53,7 @@ export function ChangeFailureRateTabTrigger({ dateRange, data, appName }) { {parseFloat(chartMean).toFixed(2)}% {percentChange}%
- + {/* */}
) diff --git a/components/deployment-frequency/chart.jsx b/components/deployment-frequency/chart.jsx index 0ae9009..bf7b148 100644 --- a/components/deployment-frequency/chart.jsx +++ b/components/deployment-frequency/chart.jsx @@ -110,7 +110,7 @@ export function DeploymentFrequencyChart({ dateRange, appName }) { - + } cursor={{ stroke: strokeCursor }} /> diff --git a/components/deployment-frequency/deploymentFrequency.js b/components/deployment-frequency/deploymentFrequency.js index d391e0e..d559404 100644 --- a/components/deployment-frequency/deploymentFrequency.js +++ b/components/deployment-frequency/deploymentFrequency.js @@ -2,7 +2,7 @@ import { useState, useEffect } from 'react'; import { getDaysBetweenDates } from '@/components/date-range-selector' -export default function useDeploymentFrequencyData(appName, dateRange) { +export default function fetchDeploymentFrequencyData(appName, dateRange) { const [dfData, setDfData] = useState([]); const [loading, setLoading] = useState(true); // Add loading state @@ -26,3 +26,27 @@ export default function useDeploymentFrequencyData(appName, dateRange) { return { dfData, loading }; // Return loading state along with dfData } + +export function fetchDeploymentFrequency(appName, dateRange) { + const [response, setResponse] = useState([]) + const [loading, setLoading] = useState(true) + + useEffect(() => { + const fetchData = async () => { + try { + const req = `${process.env.NEXT_PUBLIC_PELORUS_API_URL}/sdp/deployment_frequency/${appName}?range=${getDaysBetweenDates(dateRange)}d&start=${dateRange.to.getTime() / 1000}`; + const response = await fetch(req); + const data = await response.json() + setResponse(data) + } catch(error) { + console.error('Error fetching deployment frequency data:', error); + } finally { + setLoading(false) + } + }; + + fetchData(); + }, [dateRange, appName]); + + return { response, loading }; // Return loading state along with cfrData +} diff --git a/components/deployment-frequency/tab-trigger.jsx b/components/deployment-frequency/tab-trigger.jsx index 263fcaa..69ce7b1 100644 --- a/components/deployment-frequency/tab-trigger.jsx +++ b/components/deployment-frequency/tab-trigger.jsx @@ -6,6 +6,7 @@ import { InfoTooltip } from '@/components/info-tooltip' import { DeploymentFrequencyRating } from '@/components/deployment-frequency/rating' import { useState, useEffect } from "react" import { getDaysBetweenDates } from '@/components/date-range-selector' +import { fetchDeploymentFrequency } from './deploymentFrequency' export function DeploymentFrequencyTabTrigger({ dateRange, data, appName }) { @@ -21,20 +22,12 @@ export function DeploymentFrequencyTabTrigger({ dateRange, data, appName }) { return element.rollingAverage }) - const [response, setResponse] = useState([]) - const [isLoading, setLoading] = useState(true) + const { response, loading } = fetchDeploymentFrequency(appName, dateRange); + console.log('DF Tab: ', response) - useEffect(() => { - fetch(`${process.env.NEXT_PUBLIC_PELORUS_API_URL}/sdp/deployment_frequency/${appName}?range=${getDaysBetweenDates(dateRange)}d&start=${dateRange.to.getTime() / 1000}`) - .then((test) => test.json()) - .then((response) => { - setResponse(response) - setLoading(false) - }) - }, [dateRange, appName]); - - if (isLoading) return

Loading...

- if (!response) return

No cfr data!

+ if (loading) { + return
Loading...
; // Render loading state while data is being fetched + } const chartMean = response.df / getDaysBetweenDates(dateRange) const percentChange = Math.round((response.df / response.last) * 100) @@ -60,7 +53,7 @@ export function DeploymentFrequencyTabTrigger({ dateRange, data, appName }) { {parseFloat(chartMean).toFixed(2)} per day {percentChange}%
- + {/* */}
) diff --git a/components/lead-time-for-change/chart.jsx b/components/lead-time-for-change/chart.jsx index 98d2dfc..1ebd77f 100644 --- a/components/lead-time-for-change/chart.jsx +++ b/components/lead-time-for-change/chart.jsx @@ -1,11 +1,10 @@ 'use client' -import { useState, useEffect } from 'react' import { useTheme } from "next-themes" import { format } from 'date-fns' import { XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, ComposedChart, Area, Line, ReferenceLine } from 'recharts' import { LeadTimeForChangeTooltip } from './tooltip' -import { getDaysBetweenDates } from '@/components/date-range-selector' +import fetchLeadTimeForChangeData from './leadTimeForChange' const dateFormatter = epoch => { @@ -37,24 +36,22 @@ export function LeadTimeForChangeChart( { dateRange, appName } ) { const strokeRollingAverage = '#8b5cf6' // Violet 500 const strokeGoal = '#f59e0b' // Amber 500 - const [ltfcData, setLtfcData] = useState([]) - useEffect(() => { - fetch(`${process.env.NEXT_PUBLIC_PELORUS_API_URL}/sdp/lead_time_for_change/${appName}/data?range=${getDaysBetweenDates(dateRange)}d&start=${dateRange.to.getTime() / 1000}`) - .then((response) => response.json()).then((data) => data.sort((d1, d2) => (d1.timestamp > d2.timestamp) ? 1 : (d1.timestamp < d2.timestamp) ? -1 : 0 )) - .then((sortedData) => { - setLtfcData(sortedData) - }) - }, [dateRange, appName]) + const { ltfcData, loading } = fetchLeadTimeForChangeData(appName, dateRange); + console.log('Chart ltfcData: ', ltfcData) - // Reports - const [reportLeadTimeForChangeData, setReportLeadTimeForChangeData] = useState(null) - const [showReportLeadTimeForChangeData, setShowReportLeadTimeForChangeData] = useState(false) - - function handleChartClick(event) { - setReportLeadTimeForChangeData(event) - setShowReportLeadTimeForChangeData(true) + if (loading) { + return
Loading...
; // Render loading state while data is being fetched } + // // Reports + // const [reportLeadTimeForChangeData, setReportLeadTimeForChangeData] = useState(null) + // const [showReportLeadTimeForChangeData, setShowReportLeadTimeForChangeData] = useState(false) + + // function handleChartClick(event) { + // setReportLeadTimeForChangeData(event) + // setShowReportLeadTimeForChangeData(true) + // } + const customAnomalyLabel = props => { return ( @@ -67,7 +64,7 @@ export function LeadTimeForChangeChart( { dateRange, appName } ) { return ( <> - + diff --git a/components/lead-time-for-change/leadTimeForChange.js b/components/lead-time-for-change/leadTimeForChange.js new file mode 100644 index 0000000..092f06c --- /dev/null +++ b/components/lead-time-for-change/leadTimeForChange.js @@ -0,0 +1,52 @@ +// components/deploymentFrequency.js +import { useState, useEffect } from 'react'; +import { getDaysBetweenDates } from '@/components/date-range-selector' + +export default function fetchLeadTimeForChangeData(appName, dateRange) { + const [ltfcData, setLtfcData] = useState([]); + const [loading, setLoading] = useState(true); // Add loading state + + useEffect(() => { + const fetchData = async () => { + try { + const req = `${process.env.NEXT_PUBLIC_PELORUS_API_URL}/sdp/lead_time_for_change/${appName}/data?range=${getDaysBetweenDates(dateRange)}d&start=${dateRange.to.getTime() / 1000}`; + const response = await fetch(req); + const data = await response.json(); + const sortedData = data.sort((d1, d2) => (d1.timestamp > d2.timestamp) ? 1 : (d1.timestamp < d2.timestamp) ? -1 : 0); + setLtfcData(sortedData); + } catch (error) { + console.error('Error fetching deployment frequency data:', error); + } finally { + setLoading(false); // Set loading to false regardless of success or failure + } + }; + + fetchData(); + }, [appName, dateRange]); + + return { ltfcData, loading }; // Return loading state along with dfData +} + +export function fetchLeadTimeForChange(appName, dateRange) { + const [response, setResponse] = useState([]) + const [loading, setLoading] = useState(true) + + useEffect(() => { + const fetchData = async () => { + try { + const req = `${process.env.NEXT_PUBLIC_PELORUS_API_URL}/sdp/lead_time_for_change/${appName}?range=${getDaysBetweenDates(dateRange)}d&start=${dateRange.to.getTime() / 1000}`; + const response = await fetch(req); + const data = await response.json() + setResponse(data) + } catch(error) { + console.error('Error fetching lead time for change:', error); + } finally { + setLoading(false) + } + }; + + fetchData(); + }, [dateRange, appName]); + + return { response, loading }; // Return loading state along with cfrData +} diff --git a/components/lead-time-for-change/tab-trigger.jsx b/components/lead-time-for-change/tab-trigger.jsx index 597fa3a..04bded0 100644 --- a/components/lead-time-for-change/tab-trigger.jsx +++ b/components/lead-time-for-change/tab-trigger.jsx @@ -6,6 +6,7 @@ import { InfoTooltip } from '@/components/info-tooltip' import { LeadTimeForChangeRating } from './rating' import { useState, useEffect } from "react" import { getDaysBetweenDates } from '@/components/date-range-selector' +import { fetchLeadTimeForChange } from './leadTimeForChange' export function LeadTimeForChangeTabTrigger({ dateRange, data, appName }) { @@ -21,20 +22,12 @@ export function LeadTimeForChangeTabTrigger({ dateRange, data, appName }) { return element.rollingAverage }) - const [response, setResponse] = useState([]) - const [isLoading, setLoading] = useState(true) - useEffect(() => { - console.log('LTFC date range: ', dateRange) - fetch(`${process.env.NEXT_PUBLIC_PELORUS_API_URL}/sdp/lead_time_for_change/${appName}?range=${getDaysBetweenDates(dateRange)}d&start=${dateRange.to.getTime() / 1000}`) - .then((test) => test.json()) - .then((response) => { - setResponse(response) - setLoading(false) - }) - }, [dateRange, appName]); + const { response, loading } = fetchLeadTimeForChange(appName, dateRange); + console.log('LTFC Tab: ', response) - if (isLoading) return

Loading...

- if (!response) return

No cfr data!

+ if (loading) { + return
Loading...
; // Render loading state while data is being fetched + } const chartMean = response.ltfc / 86400 const percentChange = Math.round((1 - (response.ltfc / response.last)) * 100) @@ -60,7 +53,7 @@ export function LeadTimeForChangeTabTrigger({ dateRange, data, appName }) { {parseFloat(chartMean).toFixed(2)} days {percentChange}%
- + {/* */}
) diff --git a/components/lead-time-for-change/table.jsx b/components/lead-time-for-change/table.jsx index 70e34fe..46cb5ea 100644 --- a/components/lead-time-for-change/table.jsx +++ b/components/lead-time-for-change/table.jsx @@ -1,6 +1,5 @@ 'use client' -import { useState, useEffect } from 'react' import { Table, TableBody, @@ -11,19 +10,17 @@ import { TableRow, } from "@/components/ui/table" import { Calendar, GitCommit, GitGraph, Timer } from "lucide-react" -import { getDaysBetweenDates } from '@/components/date-range-selector' import {dateFormatter, dayFormatter} from '@/lib/date-funcs'; +import fetchLeadTimeForChangeData from './leadTimeForChange' export function LeadTimeForChangeTable({ dateRange, appName }) { - const [ltfcData, setLtfcData] = useState([]) - useEffect(() => { - fetch(`${process.env.NEXT_PUBLIC_PELORUS_API_URL}/sdp/lead_time_for_change/${appName}/data?range=${getDaysBetweenDates(dateRange)}d&start=${dateRange.to.getTime() / 1000}`) - .then((response) => response.json()).then((data) => data.sort((d1, d2) => (d1.timestamp > d2.timestamp) ? 1 : (d1.timestamp < d2.timestamp) ? -1 : 0 )) - .then((sortedData) => { - setLtfcData(sortedData) - }) - }, [dateRange, appName]) + const { ltfcData, loading } = fetchLeadTimeForChangeData(appName, dateRange); + console.log('Chart ltfcData: ', ltfcData) + + if (loading) { + return
Loading...
; // Render loading state while data is being fetched + } return ( diff --git a/components/mean-time-to-recovery/chart.jsx b/components/mean-time-to-recovery/chart.jsx index 0e228aa..2d905f9 100644 --- a/components/mean-time-to-recovery/chart.jsx +++ b/components/mean-time-to-recovery/chart.jsx @@ -78,7 +78,7 @@ export function MeanTimeToRecoveryChart({ appName, dateRange }) { - + } cursor={{ stroke: strokeCursor }} /> diff --git a/components/mean-time-to-recovery/meantimetorestore.js b/components/mean-time-to-recovery/meantimetorestore.js index 7d9af01..da60fa4 100644 --- a/components/mean-time-to-recovery/meantimetorestore.js +++ b/components/mean-time-to-recovery/meantimetorestore.js @@ -2,7 +2,7 @@ import { useState, useEffect } from 'react'; import { getDaysBetweenDates } from '@/components/date-range-selector' -export default function useMeanTimeToRestore(appName, dateRange) { +export default function fetchMeanTimeToRestoreData(appName, dateRange) { const [mttrData, setMttrData] = useState([]); const [loading, setLoading] = useState(true); // Add loading state @@ -26,4 +26,28 @@ export default function useMeanTimeToRestore(appName, dateRange) { }, [appName, dateRange]); return { mttrData, loading }; // Return loading state along with dfData -} \ No newline at end of file +} + +export function fetchMeanTimeToRestore(appName, dateRange) { + const [response, setResponse] = useState([]) + const [loading, setLoading] = useState(true) + + useEffect(() => { + const fetchData = async () => { + try { + const req = `${process.env.NEXT_PUBLIC_PELORUS_API_URL}/sdp/mean_time_to_restore/${appName}?range=${getDaysBetweenDates(dateRange)}d&start=${dateRange.to.getTime() / 1000}`; + const response = await fetch(req); + const data = await response.json() + setResponse(data) + } catch(error) { + console.error('Error fetching deployment frequency data:', error); + } finally { + setLoading(false) + } + }; + + fetchData(); + }, [dateRange, appName]); + + return { response, loading }; // Return loading state along with cfrData +} diff --git a/components/mean-time-to-recovery/tab-trigger.jsx b/components/mean-time-to-recovery/tab-trigger.jsx index 25c7b54..7ffc3d7 100644 --- a/components/mean-time-to-recovery/tab-trigger.jsx +++ b/components/mean-time-to-recovery/tab-trigger.jsx @@ -6,6 +6,7 @@ import { InfoTooltip } from '@/components/info-tooltip' import { MeanTimeToRecoveryRating } from './rating' import { useState, useEffect } from "react" import { getDaysBetweenDates } from '@/components/date-range-selector' +import { fetchMeanTimeToRestore } from './meantimetorestore' export function MeanTimeToRecoveryTabTrigger({ dateRange, data, appName }) { @@ -21,22 +22,12 @@ export function MeanTimeToRecoveryTabTrigger({ dateRange, data, appName }) { return element.rollingAverage }) - const [response, setResponse] = useState([]) - const [isLoading, setLoading] = useState(true) + const { response, loading } = fetchMeanTimeToRestore(appName, dateRange); + console.log('MTTR Tab: ', response) - var req = `${process.env.NEXT_PUBLIC_PELORUS_API_URL}/sdp/mean_time_to_restore/${appName}?range=${getDaysBetweenDates(dateRange)}d&start=${dateRange.to.getTime() / 1000}` - console.log(req) - useEffect(() => { - fetch(req) - .then((test) => test.json()) - .then((response) => { - setResponse(response) - setLoading(false) - }) - }, [dateRange, appName]); - - if (isLoading) return

Loading...

- if (!response) return

No cfr data!

+ if (loading) { + return
Loading...
; // Render loading state while data is being fetched + } const chartMean = response.mttr / 86400 const percentChange = Math.round((1 - (response.mttr / response.last)) * 100) @@ -62,7 +53,7 @@ export function MeanTimeToRecoveryTabTrigger({ dateRange, data, appName }) { {parseFloat(chartMean).toFixed(5)} days {percentChange}% - + {/* */} ) diff --git a/components/mean-time-to-recovery/table.jsx b/components/mean-time-to-recovery/table.jsx index b98fbe3..ce98d06 100644 --- a/components/mean-time-to-recovery/table.jsx +++ b/components/mean-time-to-recovery/table.jsx @@ -33,7 +33,7 @@ export function MeanTimeToRecoveryTable({ dateRange, appName }) { - {mttrData.map (({ issue_id, time_to_resolve, timestamp }) => ( + {mttrData.map (({ issue_id, time_to_resolve, timestamp }) => (