From 4d97a077e49dcbb993a6f1f325bd719389f3a44f Mon Sep 17 00:00:00 2001 From: Dan Barr Date: Fri, 31 Jan 2025 22:50:47 -0500 Subject: [PATCH 1/7] fix: update the cert install steps - Add some extra clarity about the uniqueness of the CA cert - List the simpler CLI method first for macOS - Clarify version differences for Keychain Access behavior - Correct the Linux steps (VS Code uses nssdb, not the system certs) --- src/routes/route-certificate-security.tsx | 2 +- src/routes/route-certificates.tsx | 35 +++++++++++------------ 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/src/routes/route-certificate-security.tsx b/src/routes/route-certificate-security.tsx index d2b2c169..784a2dff 100644 --- a/src/routes/route-certificate-security.tsx +++ b/src/routes/route-certificate-security.tsx @@ -76,7 +76,7 @@ export function RouteCertificateSecurity() {

- Robust Certificate Security + Robust certificate security

Security is a top priority for us. We have designed CodeGate's diff --git a/src/routes/route-certificates.tsx b/src/routes/route-certificates.tsx index 4203f19f..fb08c6eb 100644 --- a/src/routes/route-certificates.tsx +++ b/src/routes/route-certificates.tsx @@ -96,19 +96,19 @@ export function RouteCertificates() { const steps = { macos: { install: [ - "Open the downloaded certificate file; Keychain Access will open and prompt you to to add the certificates.", - "In the Add Certificates dialog, select the `login` keychain, and click Add.", - "In the Keychain Access dialog, select the `login` keychain from the Default Keychains list on the left.", + "CLI method: Open a terminal and run `security add-trusted-cert -d -r trustRoot -p ssl -p basic -k ~/Library/Keychains/login.keychain ~/Downloads/codegate.crt`", + "GUI method: Open the downloaded certificate file; Keychain Access will open.", + "Depending on your macOS version, you may see the Add Certificates dialog. If so, select the `login` keychain, and click Add.", + "In Keychain Access, select the `login` keychain from the Default Keychains list on the left.", 'Search for "CodeGate" (it may not appear until you search), then in the search results, double-click the "CodeGate CA" certificate.', 'Expand the Trust section and set the "Secure Sockets Layer" and "X.509 Basic Policy" options to "Always Trust".', - "Alternatively, run `security add-trusted-cert -r trustRoot -k ~/Library/Keychains/login.keychain ~/Downloads/codegate.crt` from a terminal.", ], remove: [ - "Launch the Keychain Access app.", + 'CLI method: Open a terminal and run `security delete-certificate -c "CodeGate CA" -t ~/Library/Keychains/login.keychain`', + "GUI method: Launch the Keychain Access app (Note: on newer macOS versions, Keychain Access is hidden from Launcher, but can be run from Spotlight Search).", 'Select the login keychain and search for "CodeGate".', 'Right-click the "CodeGate CA" certificate and Delete the certificate.', "Confirm the deletion when prompted.", - 'Alternatively, run `security delete-certificate -c "CodeGate CA" -t ~/Library/Keychains/login.keychain` from a terminal.', ], }, windows: { @@ -130,14 +130,13 @@ export function RouteCertificates() { }, linux: { install: [ - "Copy the certificate to `/usr/local/share/ca-certificates/codegate.crt` (Ubuntu/Debian) or `/etc/pki/ca-trust/source/anchors/codegate.pem` (RHEL/Fedora).", - "Run `sudo update-ca-certificates` (Ubuntu/Debian) or `sudo update-ca-trust` (RHEL/Fedora).", - "Restart your IDE.", + "Install the `certutil` tool: `sudo apt install libnss3-tools` (Ubuntu/Debian) or `sudo dnf install nss-tools` (RHEL/Fedora).", + 'Run `certutil -d sql:$HOME/.pki/nssdb -A -t "C,," -n CodeGate-CA -i ~/Downloads/codegate.crt` to install the certificate for your account.', + "Restart VS Code.", ], remove: [ - "Delete the certificate file from `/usr/local/share/ca-certificates/` (Ubuntu/Debian) or `/etc/pki/ca-trust/source/anchors/` (RHEL/Fedora).", - "Run `sudo update-ca-certificates --fresh` (Ubuntu/Debian) or `sudo update-ca-trust` (RHEL/Fedora).", - "Restart your IDE.", + "Run `certutil -d sql:$HOME/.pki/nssdb -D -n CodeGate-CA`", + "Restart VS Code.", ], }, }; @@ -166,7 +165,7 @@ export function RouteCertificates() {

This certificate allows CodeGate to act as a secure proxy for - integrations such as GitHub Copilot. + GitHub Copilot. This certificate is unique to your system.

@@ -191,9 +190,9 @@ export function RouteCertificates() {

- Secure certificate handling: this custom CA - is locally generated and managed. CodeGate developers have no - access to it. + Secure certificate handling: This custom CA + is locally generated and managed. It is unique to your + installation and CodeGate developers have no access to it.

@@ -202,7 +201,7 @@ export function RouteCertificates() { No external communications: CodeGate is designed with no capability to call home or communicate with external servers, outside of those requested by the IDE or - Agent. + agent.

@@ -218,7 +217,7 @@ export function RouteCertificates() {

- Certificate Management + Certificate management

{/* OS Selection Tabs */}
From 403d5de15bcd8e3f19f71080469956f868f2bd31 Mon Sep 17 00:00:00 2001 From: alex-mcgovern Date: Fri, 14 Feb 2025 11:41:20 +0000 Subject: [PATCH 2/7] fix: update the cert install steps - Add some extra clarity about the uniqueness of the CA cert - List the simpler CLI method first for macOS - Clarify version differences for Keychain Access behavior - Correct the Linux steps (VS Code uses nssdb, not the system certs) --- src/routes/route-certificate-security.tsx | 2 +- src/routes/route-certificates.tsx | 35 +++++++++++------------ 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/src/routes/route-certificate-security.tsx b/src/routes/route-certificate-security.tsx index d2b2c169..784a2dff 100644 --- a/src/routes/route-certificate-security.tsx +++ b/src/routes/route-certificate-security.tsx @@ -76,7 +76,7 @@ export function RouteCertificateSecurity() {

- Robust Certificate Security + Robust certificate security

Security is a top priority for us. We have designed CodeGate's diff --git a/src/routes/route-certificates.tsx b/src/routes/route-certificates.tsx index 4203f19f..fb08c6eb 100644 --- a/src/routes/route-certificates.tsx +++ b/src/routes/route-certificates.tsx @@ -96,19 +96,19 @@ export function RouteCertificates() { const steps = { macos: { install: [ - "Open the downloaded certificate file; Keychain Access will open and prompt you to to add the certificates.", - "In the Add Certificates dialog, select the `login` keychain, and click Add.", - "In the Keychain Access dialog, select the `login` keychain from the Default Keychains list on the left.", + "CLI method: Open a terminal and run `security add-trusted-cert -d -r trustRoot -p ssl -p basic -k ~/Library/Keychains/login.keychain ~/Downloads/codegate.crt`", + "GUI method: Open the downloaded certificate file; Keychain Access will open.", + "Depending on your macOS version, you may see the Add Certificates dialog. If so, select the `login` keychain, and click Add.", + "In Keychain Access, select the `login` keychain from the Default Keychains list on the left.", 'Search for "CodeGate" (it may not appear until you search), then in the search results, double-click the "CodeGate CA" certificate.', 'Expand the Trust section and set the "Secure Sockets Layer" and "X.509 Basic Policy" options to "Always Trust".', - "Alternatively, run `security add-trusted-cert -r trustRoot -k ~/Library/Keychains/login.keychain ~/Downloads/codegate.crt` from a terminal.", ], remove: [ - "Launch the Keychain Access app.", + 'CLI method: Open a terminal and run `security delete-certificate -c "CodeGate CA" -t ~/Library/Keychains/login.keychain`', + "GUI method: Launch the Keychain Access app (Note: on newer macOS versions, Keychain Access is hidden from Launcher, but can be run from Spotlight Search).", 'Select the login keychain and search for "CodeGate".', 'Right-click the "CodeGate CA" certificate and Delete the certificate.', "Confirm the deletion when prompted.", - 'Alternatively, run `security delete-certificate -c "CodeGate CA" -t ~/Library/Keychains/login.keychain` from a terminal.', ], }, windows: { @@ -130,14 +130,13 @@ export function RouteCertificates() { }, linux: { install: [ - "Copy the certificate to `/usr/local/share/ca-certificates/codegate.crt` (Ubuntu/Debian) or `/etc/pki/ca-trust/source/anchors/codegate.pem` (RHEL/Fedora).", - "Run `sudo update-ca-certificates` (Ubuntu/Debian) or `sudo update-ca-trust` (RHEL/Fedora).", - "Restart your IDE.", + "Install the `certutil` tool: `sudo apt install libnss3-tools` (Ubuntu/Debian) or `sudo dnf install nss-tools` (RHEL/Fedora).", + 'Run `certutil -d sql:$HOME/.pki/nssdb -A -t "C,," -n CodeGate-CA -i ~/Downloads/codegate.crt` to install the certificate for your account.', + "Restart VS Code.", ], remove: [ - "Delete the certificate file from `/usr/local/share/ca-certificates/` (Ubuntu/Debian) or `/etc/pki/ca-trust/source/anchors/` (RHEL/Fedora).", - "Run `sudo update-ca-certificates --fresh` (Ubuntu/Debian) or `sudo update-ca-trust` (RHEL/Fedora).", - "Restart your IDE.", + "Run `certutil -d sql:$HOME/.pki/nssdb -D -n CodeGate-CA`", + "Restart VS Code.", ], }, }; @@ -166,7 +165,7 @@ export function RouteCertificates() {

This certificate allows CodeGate to act as a secure proxy for - integrations such as GitHub Copilot. + GitHub Copilot. This certificate is unique to your system.

@@ -191,9 +190,9 @@ export function RouteCertificates() {

- Secure certificate handling: this custom CA - is locally generated and managed. CodeGate developers have no - access to it. + Secure certificate handling: This custom CA + is locally generated and managed. It is unique to your + installation and CodeGate developers have no access to it.

@@ -202,7 +201,7 @@ export function RouteCertificates() { No external communications: CodeGate is designed with no capability to call home or communicate with external servers, outside of those requested by the IDE or - Agent. + agent.

@@ -218,7 +217,7 @@ export function RouteCertificates() {

- Certificate Management + Certificate management

{/* OS Selection Tabs */}
From 7c440d2d7543b2befa5eb20d4fb53bb2066c80df Mon Sep 17 00:00:00 2001 From: alex-mcgovern Date: Fri, 14 Feb 2025 11:42:16 +0000 Subject: [PATCH 3/7] feat: use markdown for certificate instructions --- package-lock.json | 222 +++++++++++++++++++ package.json | 1 + src/markdown/certificates/linux-install.md | 3 + src/markdown/certificates/linux-remove.md | 2 + src/markdown/certificates/macos-install.md | 13 ++ src/markdown/certificates/macos-remove.md | 6 + src/markdown/certificates/windows-install.md | 6 + src/markdown/certificates/windows-remove.md | 5 + src/md.d.ts | 7 + src/routes/route-certificates.tsx | 93 ++------ src/routes/route-chat.tsx | 6 +- vite.config.ts | 10 +- 12 files changed, 298 insertions(+), 76 deletions(-) create mode 100644 src/markdown/certificates/linux-install.md create mode 100644 src/markdown/certificates/linux-remove.md create mode 100644 src/markdown/certificates/macos-install.md create mode 100644 src/markdown/certificates/macos-remove.md create mode 100644 src/markdown/certificates/windows-install.md create mode 100644 src/markdown/certificates/windows-remove.md create mode 100644 src/md.d.ts diff --git a/package-lock.json b/package-lock.json index 3f8f0be3..4167c3fb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -68,6 +68,7 @@ "typescript": "~5.7.2", "typescript-eslint": "^8.15.0", "vite": "^6.0.1", + "vite-plugin-markdown": "^2.2.0", "vite-tsconfig-paths": "^5.1.4", "vitest": "^3.0.4", "vitest-fail-on-console": "^0.7.1" @@ -6041,6 +6042,75 @@ "dev": true, "license": "MIT" }, + "node_modules/dom-serializer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", + "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", + "dev": true, + "license": "MIT", + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.2.0", + "entities": "^2.0.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/dom-serializer/node_modules/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "dev": true, + "license": "BSD-2-Clause", + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", + "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.2.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", + "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^1.0.1", + "domelementtype": "^2.2.0", + "domhandler": "^4.2.0" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, "node_modules/dotenv": { "version": "16.4.7", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", @@ -6353,6 +6423,20 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/esquery": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", @@ -6660,6 +6744,40 @@ "url": "https://github.com/sponsors/rawify" } }, + "node_modules/front-matter": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/front-matter/-/front-matter-4.0.2.tgz", + "integrity": "sha512-I8ZuJ/qG92NWX8i5x1Y8qyj3vizhXS31OxjKDu3LKP+7/qBgfIKValiZIEwoVoJKUHlhWtYrktkxV1XsX+pPlg==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-yaml": "^3.13.1" + } + }, + "node_modules/front-matter/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/front-matter/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, "node_modules/fs-minipass": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", @@ -7110,6 +7228,36 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/htmlparser2": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", + "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==", + "dev": true, + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.0.0", + "domutils": "^2.5.2", + "entities": "^2.0.0" + } + }, + "node_modules/htmlparser2/node_modules/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "dev": true, + "license": "BSD-2-Clause", + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/http-proxy-agent": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", @@ -7794,6 +7942,16 @@ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", "license": "MIT" }, + "node_modules/linkify-it": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz", + "integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "uc.micro": "^1.0.1" + } + }, "node_modules/lint-staged": { "version": "15.3.0", "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.3.0.tgz", @@ -8072,6 +8230,33 @@ "node": ">=10" } }, + "node_modules/markdown-it": { + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz", + "integrity": "sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1", + "entities": "~2.1.0", + "linkify-it": "^3.0.1", + "mdurl": "^1.0.1", + "uc.micro": "^1.0.5" + }, + "bin": { + "markdown-it": "bin/markdown-it.js" + } + }, + "node_modules/markdown-it/node_modules/entities": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", + "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==", + "dev": true, + "license": "BSD-2-Clause", + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/markdown-table": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz", @@ -8364,6 +8549,13 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/mdurl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", + "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==", + "dev": true, + "license": "MIT" + }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -10851,6 +11043,13 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true, + "license": "BSD-3-Clause" + }, "node_modules/stackback": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", @@ -11628,6 +11827,13 @@ "typescript": ">=4.8.4 <5.8.0" } }, + "node_modules/uc.micro": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", + "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==", + "dev": true, + "license": "MIT" + }, "node_modules/ufo": { "version": "1.5.4", "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.5.4.tgz", @@ -11993,6 +12199,22 @@ "dev": true, "license": "MIT" }, + "node_modules/vite-plugin-markdown": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/vite-plugin-markdown/-/vite-plugin-markdown-2.2.0.tgz", + "integrity": "sha512-eH2tXMZcx3EHb5okd+/0VIyoR8Gp9pGe24UXitOOcGkzObbJ1vl48aGOAbakoT88FBdzC8MXNkMfBIB9VK0Ndg==", + "dev": true, + "license": "MIT", + "dependencies": { + "domhandler": "^4.0.0", + "front-matter": "^4.0.0", + "htmlparser2": "^6.0.0", + "markdown-it": "^12.0.0" + }, + "peerDependencies": { + "vite": ">= 2.0.0" + } + }, "node_modules/vite-tsconfig-paths": { "version": "5.1.4", "resolved": "https://registry.npmjs.org/vite-tsconfig-paths/-/vite-tsconfig-paths-5.1.4.tgz", diff --git a/package.json b/package.json index 9fa218d0..c6e413ec 100644 --- a/package.json +++ b/package.json @@ -80,6 +80,7 @@ "typescript": "~5.7.2", "typescript-eslint": "^8.15.0", "vite": "^6.0.1", + "vite-plugin-markdown": "^2.2.0", "vite-tsconfig-paths": "^5.1.4", "vitest": "^3.0.4", "vitest-fail-on-console": "^0.7.1" diff --git a/src/markdown/certificates/linux-install.md b/src/markdown/certificates/linux-install.md new file mode 100644 index 00000000..04d8c129 --- /dev/null +++ b/src/markdown/certificates/linux-install.md @@ -0,0 +1,3 @@ +1. Install the `certutil` tool: `sudo apt install libnss3-tools` (Ubuntu/Debian) or `sudo dnf install nss-tools` (RHEL/Fedora). +2. Run `certutil -d sql:$HOME/.pki/nssdb -A -t "C,," -n CodeGate-CA -i ~ Downloads/codegate.crt` to install the certificate for your account. +3. Restart VS Code. diff --git a/src/markdown/certificates/linux-remove.md b/src/markdown/certificates/linux-remove.md new file mode 100644 index 00000000..f3abb1ab --- /dev/null +++ b/src/markdown/certificates/linux-remove.md @@ -0,0 +1,2 @@ +1. Run `certutil -d sql:$HOME/.pki/nssdb -D -n CodeGate-CA` +2. Restart VS Code. diff --git a/src/markdown/certificates/macos-install.md b/src/markdown/certificates/macos-install.md new file mode 100644 index 00000000..e7853364 --- /dev/null +++ b/src/markdown/certificates/macos-install.md @@ -0,0 +1,13 @@ +- **CLI method**: Open a terminal and run + +```shell +security add-trusted-cert -d -r trustRoot -p ssl -p basic -k ~/Library/Keychains/login.keychain ~/Downloads/codegate.crt +``` + +- **GUI method**: + 1. Open the downloaded certificate file; Keychain Access will open. + 2. Depending on your macOS version, you may see the Add Certificates dialog. If so, select the `login` keychain, and click Add. + 3. In Keychain Access, select the `login` keychain from the Default Keychains list on the left. + 4. Search for "CodeGate" (it may not appear until you search), then in the search results, double-click the "CodeGate CA" certificate. + 5. Expand the Trust section and set the "Secure Sockets Layer" and "X.509 + Basic Policy" options to "Always Trust". diff --git a/src/markdown/certificates/macos-remove.md b/src/markdown/certificates/macos-remove.md new file mode 100644 index 00000000..eee27b1f --- /dev/null +++ b/src/markdown/certificates/macos-remove.md @@ -0,0 +1,6 @@ +- **CLI method**: Open a terminal and run `security delete-certificate -c "CodeGate CA" -t ~/Library/Keychains/login.keychain` +- **GUI method**: + 1. Launch the Keychain Access app (Note: on newer macOS versions, Keychain Access is hidden from Launcher, but can be run from Spotlight Search). + 2. Select the login keychain and search for "CodeGate". + 3. Right-click the "CodeGate CA" certificate and Delete the certificate. + 4. Confirm the deletion when prompted. diff --git a/src/markdown/certificates/windows-install.md b/src/markdown/certificates/windows-install.md new file mode 100644 index 00000000..7456cb33 --- /dev/null +++ b/src/markdown/certificates/windows-install.md @@ -0,0 +1,6 @@ +1. Double-click the downloaded certificate file., +2. Click "Install Certificate"., +3. Select "Current User" and click Next., +4. Choose "Place all certificates in the following store"., +5. Click Browse and select "Trusted Root Certification Authorities"., +6. Click Next and Finish., diff --git a/src/markdown/certificates/windows-remove.md b/src/markdown/certificates/windows-remove.md new file mode 100644 index 00000000..3caa6ffe --- /dev/null +++ b/src/markdown/certificates/windows-remove.md @@ -0,0 +1,5 @@ +1. Open "Run" (Win + R) and enter `certmgr.msc`. +2. Navigate to "Trusted Root Certification Authorities" โ†’ "Certificates". +3. Find the "CodeGate CA" certificate. +4. Right-click and Delete the certificate. +5. Confirm the deletion when prompted. diff --git a/src/md.d.ts b/src/md.d.ts new file mode 100644 index 00000000..31817d8a --- /dev/null +++ b/src/md.d.ts @@ -0,0 +1,7 @@ +declare module "*.md" { + // When "Mode.MARKDOWN" is requested + const markdown: string; + + // Modify below per your usage + export { markdown }; +} diff --git a/src/routes/route-certificates.tsx b/src/routes/route-certificates.tsx index fb08c6eb..34d112c0 100644 --- a/src/routes/route-certificates.tsx +++ b/src/routes/route-certificates.tsx @@ -8,39 +8,18 @@ import { Breadcrumbs, Breadcrumb, } from "@stacklok/ui-kit"; -import { useState, ReactNode } from "react"; +import { useState } from "react"; +import { markdown as linuxInstall } from "../markdown/certificates/linux-install.md"; +import { markdown as linuxRemove } from "../markdown/certificates/linux-remove.md"; +import { markdown as windowsInstall } from "../markdown/certificates/windows-install.md"; +import { markdown as windowsRemove } from "../markdown/certificates/windows-remove.md"; +import { markdown as macosInstall } from "../markdown/certificates/macos-install.md"; +import { markdown as macosRemove } from "../markdown/certificates/macos-remove.md"; +import { Markdown } from "@/components/Markdown"; type OS = "macos" | "windows" | "linux"; type Action = "install" | "remove"; -function renderWithCode(text: string): ReactNode { - const parts = text.split(/(`[^`]+`)/); - return parts.map((part, index) => { - if (part.startsWith("`") && part.endsWith("`")) { - return ( - - {part.slice(1, -1)} - - ); - } - return part; - }); -} - -function InstructionStep({ number, text }: { number: number; text: string }) { - return ( -
-
- {number} -
-

{renderWithCode(text)}

-
- ); -} - const CheckIcon = () => ( + >; + const steps = { macos: { - install: [ - "CLI method: Open a terminal and run `security add-trusted-cert -d -r trustRoot -p ssl -p basic -k ~/Library/Keychains/login.keychain ~/Downloads/codegate.crt`", - "GUI method: Open the downloaded certificate file; Keychain Access will open.", - "Depending on your macOS version, you may see the Add Certificates dialog. If so, select the `login` keychain, and click Add.", - "In Keychain Access, select the `login` keychain from the Default Keychains list on the left.", - 'Search for "CodeGate" (it may not appear until you search), then in the search results, double-click the "CodeGate CA" certificate.', - 'Expand the Trust section and set the "Secure Sockets Layer" and "X.509 Basic Policy" options to "Always Trust".', - ], - remove: [ - 'CLI method: Open a terminal and run `security delete-certificate -c "CodeGate CA" -t ~/Library/Keychains/login.keychain`', - "GUI method: Launch the Keychain Access app (Note: on newer macOS versions, Keychain Access is hidden from Launcher, but can be run from Spotlight Search).", - 'Select the login keychain and search for "CodeGate".', - 'Right-click the "CodeGate CA" certificate and Delete the certificate.', - "Confirm the deletion when prompted.", - ], + install: macosInstall, + remove: macosRemove, }, windows: { - install: [ - "Double-click the downloaded certificate file.", - 'Click "Install Certificate".', - 'Select "Current User" and click Next.', - 'Choose "Place all certificates in the following store".', - 'Click Browse and select "Trusted Root Certification Authorities".', - "Click Next and Finish.", - ], - remove: [ - 'Open "Run" (Win + R) and enter `certmgr.msc`.', - 'Navigate to "Trusted Root Certification Authorities" โ†’ "Certificates".', - 'Find the "CodeGate CA" certificate.', - "Right-click and Delete the certificate.", - "Confirm the deletion when prompted.", - ], + install: windowsInstall, + remove: windowsRemove, }, linux: { - install: [ - "Install the `certutil` tool: `sudo apt install libnss3-tools` (Ubuntu/Debian) or `sudo dnf install nss-tools` (RHEL/Fedora).", - 'Run `certutil -d sql:$HOME/.pki/nssdb -A -t "C,," -n CodeGate-CA -i ~/Downloads/codegate.crt` to install the certificate for your account.', - "Restart VS Code.", - ], - remove: [ - "Run `certutil -d sql:$HOME/.pki/nssdb -D -n CodeGate-CA`", - "Restart VS Code.", - ], + install: linuxInstall, + remove: linuxRemove, }, - }; + } as const satisfies ListSchema; const currentSteps = steps[activeOS][activeAction]; @@ -277,9 +228,7 @@ export function RouteCertificates() {
- {currentSteps.map((step, index) => ( - - ))} +
diff --git a/src/routes/route-chat.tsx b/src/routes/route-chat.tsx index 605d137f..db20b55f 100644 --- a/src/routes/route-chat.tsx +++ b/src/routes/route-chat.tsx @@ -15,6 +15,7 @@ export function RouteChat() { const { id } = useParams(); const { data: prompts } = usePromptsData(); const chat = prompts?.find((prompt) => prompt.chat_id === id); + console.debug("๐Ÿ‘‰ chat:", chat); const title = chat === undefined || @@ -43,10 +44,11 @@ export function RouteChat() { - {sanitizeQuestionPrompt({ + {question.message} + {/* {sanitizeQuestionPrompt({ question: question?.message ?? "", answer: answer?.message ?? "", - })} + })} */} diff --git a/vite.config.ts b/vite.config.ts index 120ba0d7..be9185f1 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -2,10 +2,17 @@ import { defineConfig } from "vite"; import path from "path"; import react from "@vitejs/plugin-react-swc"; import tsconfigPaths from "vite-tsconfig-paths"; +import { Mode, plugin as mdPlugin } from "vite-plugin-markdown"; // https://vite.dev/config/ export default defineConfig({ - plugins: [tsconfigPaths(), react()], + plugins: [ + tsconfigPaths(), + mdPlugin({ + mode: [Mode.MARKDOWN], + }), + react(), + ], base: "/", build: { outDir: "dist", @@ -18,5 +25,4 @@ export default defineConfig({ "@": path.resolve(__dirname, "./src"), }, }, - assetsInclude: ["**/*.md"], }); From 0a90125593b9790f11e0e081eb5955483468eb84 Mon Sep 17 00:00:00 2001 From: alex-mcgovern Date: Fri, 14 Feb 2025 11:50:17 +0000 Subject: [PATCH 4/7] Merge branch 'main' of github.com:stacklok/codegate-ui into fix-cert-instructions --- .github/ISSUE_TEMPLATE/1-bug.yml | 62 +-- .github/ISSUE_TEMPLATE/2-task.yml | 8 +- .github/ISSUE_TEMPLATE/3-chore.yml | 10 +- .github/actions/setup/action.yaml | 10 +- .github/dependabot.yaml | 16 +- .github/workflows/_security-checks.yml | 14 +- .github/workflows/_static-checks.yml | 2 +- .prettierrc | 15 + eslint.config.js | 180 +++---- lint-staged.config.mjs | 10 +- openapi-ts.config.ts | 22 +- package-lock.json | 159 +++++- package.json | 7 +- postcss.config.js | 2 +- src/App.test.tsx | 144 +++--- src/App.tsx | 10 +- src/Page.test.tsx | 16 +- src/Page.tsx | 26 +- .../generated/@tanstack/react-query.gen.ts | 436 ++++++++-------- src/api/generated/index.ts | 4 +- src/api/generated/sdk.gen.ts | 222 ++++----- src/api/generated/types.gen.ts | 466 +++++++++--------- src/components/BreadcrumbHome.tsx | 4 +- src/components/CopyToClipboard.tsx | 30 +- src/components/EmptyState.tsx | 10 +- src/components/Error.tsx | 24 +- src/components/ErrorBoundary.tsx | 18 +- src/components/FormButtons.tsx | 24 +- src/components/HoverPopover.tsx | 20 +- src/components/Markdown.tsx | 114 ++--- .../__tests__/CopyToClipboard.test.tsx | 38 +- .../__tests__/ErrorBoundary.test.tsx | 30 +- src/components/empty-state.tsx | 35 +- src/components/heading.tsx | 14 +- src/components/icons/Continue.tsx | 6 +- src/components/icons/Copilot.tsx | 6 +- src/components/icons/Discord.tsx | 6 +- src/components/icons/Github.tsx | 6 +- src/components/icons/Youtube.tsx | 6 +- src/components/icons/index.ts | 10 +- src/components/page-container.tsx | 6 +- src/components/react-query-provider.tsx | 56 +-- src/components/ui/chat/chat-bubble.tsx | 119 ++--- src/components/ui/chat/chat-message-list.tsx | 18 +- src/components/ui/chat/message-loading.tsx | 2 +- src/constants/empty-state-strings.ts | 30 +- src/context/confirm-context.tsx | 62 +-- .../alerts-summary-malicious-pkg.test.tsx | 50 +- .../__tests__/alerts-summary-secrets.test.tsx | 50 +- ...rts-summary-workspace-token-usage.test.tsx | 70 +-- .../alerts-summary-malicious-pkg.tsx | 12 +- .../components/alerts-summary-secrets.tsx | 12 +- .../alerts-summary-workspace-token-usage.tsx | 14 +- .../components/alerts-summary.tsx | 28 +- ...uery-get-workspace-alerts-malicious-pkg.ts | 14 +- .../use-query-get-workspace-alerts-secrets.ts | 14 +- .../hooks/use-query-get-workspace-alerts.ts | 24 +- .../use-query-get-workspace-token-usage.ts | 18 +- .../__tests__/table-messages.alerts.test.tsx | 96 ++-- .../table-messages.empty-state.test.tsx | 242 +++++---- .../table-messages.pagination.test.tsx | 84 ++-- .../__tests__/table-messages.test.tsx | 22 +- .../table-messages.token-usage.test.tsx | 84 ++-- .../__tests__/tabs-messages.test.tsx | 92 ++-- .../conversation-secrets-detected.tsx | 20 +- .../components/conversation-summary.tsx | 80 +-- .../components/search-field-messages.tsx | 24 +- .../section-conversation-secrets.tsx | 22 +- .../section-conversation-transcript.tsx | 36 +- .../components/table-alert-token-usage.tsx | 24 +- .../components/table-messages-empty-state.tsx | 76 +-- .../components/table-messages.tsx | 122 ++--- .../components/tabs-conversation.tsx | 32 +- .../components/tabs-messages.tsx | 64 +-- .../components/token-usage-by-providers.tsx | 65 +-- .../components/token-usage-icon.tsx | 16 +- .../constants/table-messages-columns.ts | 34 +- .../hooks/use-conversation-by-id.tsx | 4 +- .../hooks/use-conversation-search-params.ts | 40 +- .../use-messages-filter-search-params.ts | 78 +-- .../use-query-get-workspace-messages-table.ts | 32 +- .../lib/__tests__/is-alert-malicious.test.ts | 16 +- .../lib/__tests__/is-alert-secret.test.ts | 16 +- .../lib/count-conversation-alerts.ts | 12 +- .../lib/filter-messages-by-substring.ts | 22 +- .../lib/get-conversation-title.ts | 14 +- .../lib/get-provider-string.ts | 8 +- .../__tests__/header-status-menu.test.tsx | 184 +++---- .../header-active-workspace-selector.tsx | 85 ++-- .../header/components/header-status-menu.tsx | 250 +++++----- src/features/header/components/header.tsx | 33 +- .../header/constants/help-menu-items.tsx | 70 ++- .../header/constants/settings-menu-items.tsx | 36 +- .../hooks/use-queries-codegate-status.ts | 30 +- src/features/header/types.ts | 10 +- .../components/provider-dialog-footer.tsx | 10 +- .../providers/components/provider-dialog.tsx | 14 +- .../providers/components/provider-form.tsx | 36 +- .../providers/components/table-providers.tsx | 70 +-- .../hooks/use-confirm-delete-provider.tsx | 26 +- .../hooks/use-invalidate-providers-queries.ts | 16 +- .../hooks/use-mutation-create-provider.ts | 20 +- .../hooks/use-mutation-delete-provider.ts | 14 +- .../hooks/use-mutation-update-provider.ts | 34 +- src/features/providers/hooks/use-provider.ts | 26 +- src/features/providers/hooks/use-providers.ts | 6 +- src/features/providers/lib/utils.ts | 38 +- .../__tests__/archive-workspace.test.tsx | 122 ++--- .../table-actions-workspaces.test.tsx | 336 +++++++------ .../__tests__/workspace-creation.test.tsx | 54 +- .../workspace-custom-instructions.test.tsx | 80 ++- .../__tests__/workspace-name.test.tsx | 92 ++-- .../workspace-preferred-model.test.tsx | 58 +-- .../components/archive-workspace.tsx | 74 +-- .../components/table-actions-workspaces.tsx | 80 +-- .../workspace/components/table-workspaces.tsx | 28 +- .../components/workspace-creation.tsx | 30 +- .../workspace-custom-instructions.tsx | 204 ++++---- .../workspace/components/workspace-name.tsx | 48 +- .../components/workspace-preferred-model.tsx | 66 +-- .../hooks/use-archive-workspace-button.tsx | 20 +- .../hooks/use-archived-workspaces.ts | 10 +- .../use-confirm-hard-delete-workspace.tsx | 26 +- .../hooks/use-invalidate-workspace-queries.ts | 16 +- .../hooks/use-mutation-archive-workspace.ts | 60 +-- .../hooks/use-mutation-create-workspace.ts | 12 +- .../use-mutation-hard-delete-workspace.ts | 10 +- .../use-mutation-preferred-model-workspace.ts | 12 +- .../hooks/use-mutation-restore-workspace.ts | 52 +- ...tion-set-workspace-custom-instructions.tsx | 18 +- .../hooks/use-preferred-preferred-model.ts | 42 +- ...query-get-workspace-custom-instructions.ts | 10 +- .../hooks/use-restore-workspace-button.tsx | 14 +- src/hooks/__tests__/useSee.test.ts | 86 ++-- src/hooks/use-confirm.tsx | 14 +- src/hooks/use-kbd-shortcuts.ts | 26 +- src/hooks/use-mutation-activate-workspace.ts | 12 +- src/hooks/use-query-active-workspace-name.ts | 8 +- src/hooks/use-query-get-workspace-messages.ts | 28 +- src/hooks/use-query-list-active-workspaces.ts | 10 +- ...query-list-all-models-for-all-providers.ts | 8 +- src/hooks/use-query-list-all-workspaces.ts | 28 +- src/hooks/use-query-list-workspaces.ts | 10 +- src/hooks/use-toast-mutation.ts | 44 +- src/hooks/useClientSidePagination.ts | 14 +- src/hooks/useFormState.ts | 52 +- src/hooks/useSse.ts | 32 +- src/lib/__tests__/currency.test.ts | 46 +- src/lib/currency.ts | 58 +-- src/lib/format-number.ts | 8 +- src/lib/format-time.ts | 22 +- src/lib/hrefs.ts | 14 +- src/lib/is-alert-critical.ts | 6 +- src/lib/is-alert-malicious.ts | 16 +- src/lib/is-alert-secret.ts | 12 +- src/lib/multi-filter.ts | 6 +- src/lib/react-query-utils.ts | 52 +- src/lib/test-utils.tsx | 38 +- src/lib/ui-kit-client-side-routing.tsx | 10 +- src/lib/utils.ts | 54 +- src/main.tsx | 36 +- src/md.d.ts | 6 +- src/mocks/msw/handlers.ts | 146 +++--- src/mocks/msw/mockers/alert.mock.ts | 52 +- src/mocks/msw/mockers/conversation.mock.ts | 32 +- src/mocks/msw/mockers/token-usage.mock.ts | 8 +- src/mocks/msw/node.ts | 6 +- .../route-certificate-security.test.tsx | 30 +- .../__tests__/route-certificates.test.tsx | 118 ++--- src/routes/__tests__/route-chat.test.tsx | 288 +++++------ src/routes/__tests__/route-dashboard.test.tsx | 222 ++++----- src/routes/__tests__/route-workspace.test.tsx | 192 ++++---- .../__tests__/route-workspaces.test.tsx | 76 +-- src/routes/route-certificate-security.tsx | 60 +-- src/routes/route-certificates.tsx | 128 ++--- src/routes/route-chat.tsx | 62 +-- src/routes/route-dashboard.tsx | 16 +- src/routes/route-not-found.tsx | 16 +- src/routes/route-provider-create.tsx | 38 +- src/routes/route-provider-update.tsx | 32 +- src/routes/route-providers.tsx | 20 +- src/routes/route-workspace-creation.tsx | 12 +- src/routes/route-workspace.tsx | 34 +- src/routes/route-workspaces.tsx | 26 +- src/test/msw-endpoint.ts | 8 +- src/types/openapi-ts.ts | 10 +- src/types/react-query.ts | 4 +- src/vite-env.d.ts | 4 +- tailwind.config.ts | 62 +-- vite.config.ts | 20 +- vitest.config.ts | 68 +-- vitest.setup.ts | 56 +-- 192 files changed, 4820 insertions(+), 4682 deletions(-) create mode 100644 .prettierrc diff --git a/.github/ISSUE_TEMPLATE/1-bug.yml b/.github/ISSUE_TEMPLATE/1-bug.yml index 040206fa..0911dfd6 100644 --- a/.github/ISSUE_TEMPLATE/1-bug.yml +++ b/.github/ISSUE_TEMPLATE/1-bug.yml @@ -1,9 +1,9 @@ -name: "Bug" -description: "Report a bug to help us improve the proxy system." -type: "Bug ๐Ÿž" -title: "-- Provide a general summary of the issue --" -labels: ["bug", "needs-triage"] -assignees: "-" +name: 'Bug' +description: 'Report a bug to help us improve the proxy system.' +type: 'Bug ๐Ÿž' +title: '-- Provide a general summary of the issue --' +labels: ['bug', 'needs-triage'] +assignees: '-' body: - type: markdown attributes: @@ -12,24 +12,24 @@ body: - type: textarea id: what-happened attributes: - label: "Describe the issue" - description: "A clear and concise description of what the bug is. If applicable, add screenshots to illustrate the problem." + label: 'Describe the issue' + description: 'A clear and concise description of what the bug is. If applicable, add screenshots to illustrate the problem.' validations: required: true - type: textarea id: reproduce-steps attributes: - label: "Steps to Reproduce" - description: "Describe the steps to reproduce the behavior." + label: 'Steps to Reproduce' + description: 'Describe the steps to reproduce the behavior.' validations: required: true - type: dropdown id: operating-system attributes: - label: "Operating System" - description: "Select the operating system where the issue occurred." + label: 'Operating System' + description: 'Select the operating system where the issue occurred.' options: - Microsoft Windows (Intel) - Microsoft Windows (Arm) @@ -44,26 +44,26 @@ body: - type: input id: ide-version attributes: - label: "IDE and Version" - description: "Enter the IDE name and version." - placeholder: "e.g. VS Code 1.78.0" + label: 'IDE and Version' + description: 'Enter the IDE name and version.' + placeholder: 'e.g. VS Code 1.78.0' validations: required: true - type: input id: extension-version attributes: - label: "Extension and Version" - description: "Enter the extension name and version." - placeholder: "e.g. Proxy Extension 0.5.1" + label: 'Extension and Version' + description: 'Enter the extension name and version.' + placeholder: 'e.g. Proxy Extension 0.5.1' validations: required: true - type: dropdown id: provider attributes: - label: "Provider" - description: "Select the provider used." + label: 'Provider' + description: 'Select the provider used.' options: - Anthropic - OpenAI @@ -77,32 +77,32 @@ body: - type: input id: model attributes: - label: "Model" - description: "Enter the model name used (e.g. GPT-4, Claude 3)." - placeholder: "e.g. GPT-4" + label: 'Model' + description: 'Enter the model name used (e.g. GPT-4, Claude 3).' + placeholder: 'e.g. GPT-4' validations: required: true - type: input id: codegate-version attributes: - label: "Codegate version" - description: "Enter the version of CodeGate (e.g. `v0.1.8`, `4845e00c039e`)." - placeholder: "e.g. v0.1.8" + label: 'Codegate version' + description: 'Enter the version of CodeGate (e.g. `v0.1.8`, `4845e00c039e`).' + placeholder: 'e.g. v0.1.8' validations: required: true - type: textarea id: logs attributes: - label: "Logs" - description: "If applicable, paste logs or error messages." - placeholder: "Paste log content here." + label: 'Logs' + description: 'If applicable, paste logs or error messages.' + placeholder: 'Paste log content here.' - type: textarea id: additional-context attributes: - label: "Additional Context" - description: "Add any other context or details about the problem here (e.g. link to Discussion, etc.)." + label: 'Additional Context' + description: 'Add any other context or details about the problem here (e.g. link to Discussion, etc.).' validations: required: false diff --git a/.github/ISSUE_TEMPLATE/2-task.yml b/.github/ISSUE_TEMPLATE/2-task.yml index e7a1691b..b8e570e1 100644 --- a/.github/ISSUE_TEMPLATE/2-task.yml +++ b/.github/ISSUE_TEMPLATE/2-task.yml @@ -1,7 +1,7 @@ name: Task description: Task request -type: "Task โœ…" -title: "[Task]: " +type: 'Task โœ…' +title: '[Task]: ' labels: [] body: - type: textarea @@ -16,7 +16,7 @@ body: - type: textarea id: additional-context attributes: - label: "Additional Context" - description: "Add any other context or details about the problem here (e.g. link to Discussion, etc.)." + label: 'Additional Context' + description: 'Add any other context or details about the problem here (e.g. link to Discussion, etc.).' validations: required: false diff --git a/.github/ISSUE_TEMPLATE/3-chore.yml b/.github/ISSUE_TEMPLATE/3-chore.yml index 6086468f..c84dba36 100644 --- a/.github/ISSUE_TEMPLATE/3-chore.yml +++ b/.github/ISSUE_TEMPLATE/3-chore.yml @@ -1,8 +1,8 @@ name: Chore description: Chore request -type: "Chore ๐Ÿงน" -title: "[Chore]: " -labels: ["chore", "needs-triage"] +type: 'Chore ๐Ÿงน' +title: '[Chore]: ' +labels: ['chore', 'needs-triage'] body: - type: textarea id: description @@ -16,7 +16,7 @@ body: - type: textarea id: additional-context attributes: - label: "Additional Context" - description: "Add any other context or details about the problem here (e.g. link to Discussion, etc.)." + label: 'Additional Context' + description: 'Add any other context or details about the problem here (e.g. link to Discussion, etc.).' validations: required: false diff --git a/.github/actions/setup/action.yaml b/.github/actions/setup/action.yaml index 6f841dd6..e6c2b968 100644 --- a/.github/actions/setup/action.yaml +++ b/.github/actions/setup/action.yaml @@ -1,7 +1,7 @@ -name: "Setup Action" -description: "Checkouts the repo, sets up node, and installs dependencies" +name: 'Setup Action' +description: 'Checkouts the repo, sets up node, and installs dependencies' runs: - using: "composite" + using: 'composite' steps: - name: Checkout Repository uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4 @@ -9,7 +9,7 @@ runs: - name: Set up Node.js uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v2 with: - node-version: "lts/*" + node-version: 'lts/*' - name: Cache dependencies id: cache @@ -20,5 +20,5 @@ runs: - name: Install dependencies if: steps.cache.outputs.cache-hit != 'true' - run: "npm ci" + run: 'npm ci' shell: bash diff --git a/.github/dependabot.yaml b/.github/dependabot.yaml index a7fab4c3..72ba74e5 100644 --- a/.github/dependabot.yaml +++ b/.github/dependabot.yaml @@ -5,14 +5,14 @@ version: 2 updates: - - package-ecosystem: "npm" # See documentation for possible values - directory: "/" # Location of package manifests + - package-ecosystem: 'npm' # See documentation for possible values + directory: '/' # Location of package manifests schedule: - interval: "weekly" - day: "saturday" + interval: 'weekly' + day: 'saturday' versioning-strategy: increase - - package-ecosystem: "github-actions" - directory: "/" + - package-ecosystem: 'github-actions' + directory: '/' schedule: - interval: "weekly" - day: "saturday" + interval: 'weekly' + day: 'saturday' diff --git a/.github/workflows/_security-checks.yml b/.github/workflows/_security-checks.yml index 07f70049..c2da6a2e 100644 --- a/.github/workflows/_security-checks.yml +++ b/.github/workflows/_security-checks.yml @@ -14,12 +14,12 @@ jobs: - name: Scan repo uses: aquasecurity/trivy-action@18f2510ee396bbf400402947b394f2dd8c87dbb0 # 0.29.0 with: - scan-type: "fs" - scan-ref: "." - scanners: "vuln,secret,config" - exit-code: "1" - ignore-unfixed: "true" - severity: "MEDIUM,HIGH,CRITICAL" + scan-type: 'fs' + scan-ref: '.' + scanners: 'vuln,secret,config' + exit-code: '1' + ignore-unfixed: 'true' + severity: 'MEDIUM,HIGH,CRITICAL' npm-audit: name: NPM Audit @@ -31,7 +31,7 @@ jobs: - name: Set up Node.js uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v2 with: - node-version: "lts/*" + node-version: 'lts/*' - name: Run npm audit run: npm audit --omit=dev --audit-level=moderate diff --git a/.github/workflows/_static-checks.yml b/.github/workflows/_static-checks.yml index 59d7573e..800eccd8 100644 --- a/.github/workflows/_static-checks.yml +++ b/.github/workflows/_static-checks.yml @@ -34,7 +34,7 @@ jobs: name: Build App Check runs-on: ubuntu-latest env: - NODE_OPTIONS: "--max_old_space_size=4096" + NODE_OPTIONS: '--max_old_space_size=4096' steps: - name: Checkout Repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 00000000..933a1b15 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,15 @@ +{ + "semi": false, + "trailingComma": "es5", + "singleQuote": true, + "tabWidth": 2, + "useTabs": false, + "plugins": [ + "prettier-plugin-tailwindcss", + "prettier-plugin-classnames", + "prettier-plugin-merge" + ], + "tailwindFunctions": ["tv", "twMerge", "composeTailwindRenderProps"], + "customFunctions": ["tv", "twMerge", "composeTailwindRenderProps"], + "tailwindConfig": "./tailwind.config.ts" +} diff --git a/eslint.config.js b/eslint.config.js index 0ac4e0d8..a031d0a6 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -1,14 +1,14 @@ -import js from "@eslint/js"; -import globals from "globals"; -import importPlugin from "eslint-plugin-import"; -import reactHooks from "eslint-plugin-react-hooks"; -import reactRefresh from "eslint-plugin-react-refresh"; -import tseslint from "typescript-eslint"; -import tailwindPlugin from "eslint-plugin-tailwindcss"; -import path from "path"; -import fs from "fs"; +import js from '@eslint/js' +import globals from 'globals' +import importPlugin from 'eslint-plugin-import' +import reactHooks from 'eslint-plugin-react-hooks' +import reactRefresh from 'eslint-plugin-react-refresh' +import tseslint from 'typescript-eslint' +import tailwindPlugin from 'eslint-plugin-tailwindcss' +import path from 'path' +import fs from 'fs' -const FEATURES_DIR = "./src/features"; +const FEATURES_DIR = './src/features' /** * Traverse the features directory and return an array of restricted paths for @@ -36,31 +36,31 @@ const FEATURES_DIR = "./src/features"; * ``` */ const getRestrictedPathsForFeatureDir = () => { - const featureDirPath = path.resolve(FEATURES_DIR); + const featureDirPath = path.resolve(FEATURES_DIR) /** * @type {Array<{except: `./${string}`[], from: './src/features', target: string}>} */ - const restrictedPaths = []; + const restrictedPaths = [] try { - const featureDirs = fs.readdirSync(featureDirPath); + const featureDirs = fs.readdirSync(featureDirPath) featureDirs.forEach((featureDir) => { - const subPath = path.join(featureDirPath, featureDir); + const subPath = path.join(featureDirPath, featureDir) if (fs.lstatSync(subPath).isDirectory()) { restrictedPaths.push({ except: [`./${featureDir}`], from: FEATURES_DIR, target: path.join(FEATURES_DIR, featureDir), - }); + }) } - }); + }) } catch (error) { - console.error("Error reading features directory:", error); + console.error('Error reading features directory:', error) } - return restrictedPaths; -}; + return restrictedPaths +} const restrictedSyntax = { reactQuery: { @@ -69,17 +69,17 @@ const restrictedSyntax = { useQueries: (v) => `CallExpression[callee.name='useQueries'] > ObjectExpression:first-child > Property[key.name='queries'] > ArrayExpression > ObjectExpression > Property[key.name='${v}']`, }, -}; +} export default tseslint.config( - { ignores: ["dist"] }, + { ignores: ['dist'] }, { extends: [ js.configs.recommended, ...tseslint.configs.recommended, - ...tailwindPlugin.configs["flat/recommended"], + ...tailwindPlugin.configs['flat/recommended'], ], - files: ["**/*.{ts,tsx}"], + files: ['**/*.{ts,tsx}'], languageOptions: { parserOptions: { projectService: true, @@ -94,87 +94,87 @@ export default tseslint.config( }, }, plugins: { - "react-hooks": reactHooks, - "react-refresh": reactRefresh, + 'react-hooks': reactHooks, + 'react-refresh': reactRefresh, import: importPlugin, }, settings: { - "import/resolver": { + 'import/resolver': { typescript: true, node: true, }, tailwindcss: { - callees: ["tv", "twMerge"], - config: "./tailwind.config.ts", + callees: ['tv', 'twMerge'], + config: './tailwind.config.ts', }, }, rules: { ...reactHooks.configs.recommended.rules, - "react-refresh/only-export-components": [ - "warn", + 'react-refresh/only-export-components': [ + 'warn', { allowConstantExport: true }, ], - "tailwindcss/enforces-negative-arbitrary-values": "error", - "tailwindcss/enforces-shorthand": "error", - "tailwindcss/classnames-order": "off", // handled by prettier - "tailwindcss/no-contradicting-classname": "error", - "tailwindcss/no-custom-classname": [ - "error", + 'tailwindcss/enforces-negative-arbitrary-values': 'error', + 'tailwindcss/enforces-shorthand': 'error', + 'tailwindcss/classnames-order': 'off', // handled by prettier + 'tailwindcss/no-contradicting-classname': 'error', + 'tailwindcss/no-custom-classname': [ + 'error', { - callees: ["tv", "twMerge"], + callees: ['tv', 'twMerge'], whitelist: [ - "theme\\-(minder|trusty)", - "light", - "dark", - "scrollbar-thin", - "font-default", - "font-title", - "font-code", - "subhead-bold", - "subhead-regular", + 'theme\\-(minder|trusty)', + 'light', + 'dark', + 'scrollbar-thin', + 'font-default', + 'font-title', + 'font-code', + 'subhead-bold', + 'subhead-regular', ], }, ], - "no-restricted-syntax": [ - "error", + 'no-restricted-syntax': [ + 'error', { selector: [ - restrictedSyntax.reactQuery.useQuery("staleTime"), - restrictedSyntax.reactQuery.useQuery("gcTime"), - restrictedSyntax.reactQuery.useQueries("staleTime"), - restrictedSyntax.reactQuery.useQueries("gcTime"), - ].join(", "), + restrictedSyntax.reactQuery.useQuery('staleTime'), + restrictedSyntax.reactQuery.useQuery('gcTime'), + restrictedSyntax.reactQuery.useQueries('staleTime'), + restrictedSyntax.reactQuery.useQueries('gcTime'), + ].join(', '), message: - "`staleTime` & `gcTime` should be managed via the `getQueryCacheConfig` util instead.", + '`staleTime` & `gcTime` should be managed via the `getQueryCacheConfig` util instead.', }, { selector: [ - restrictedSyntax.reactQuery.useQuery("queryKey"), - restrictedSyntax.reactQuery.useQuery("queryFn"), - restrictedSyntax.reactQuery.useQueries("queryKey"), - restrictedSyntax.reactQuery.useQueries("queryFn"), - ].join(", "), + restrictedSyntax.reactQuery.useQuery('queryKey'), + restrictedSyntax.reactQuery.useQuery('queryFn'), + restrictedSyntax.reactQuery.useQueries('queryKey'), + restrictedSyntax.reactQuery.useQueries('queryFn'), + ].join(', '), message: "'queryKey' & 'queryFn' should be managed by openapi-ts react-query integration instead. This allows standardized management of query keys & cache invalidation.", }, { selector: [ - restrictedSyntax.reactQuery.useQuery("refetchOnMount"), - restrictedSyntax.reactQuery.useQuery("refetchOnReconnect"), - restrictedSyntax.reactQuery.useQuery("refetchOnWindowFocus"), - restrictedSyntax.reactQuery.useQueries("refetchOnMount"), - restrictedSyntax.reactQuery.useQueries("refetchOnReconnect"), - restrictedSyntax.reactQuery.useQueries("refetchOnWindowFocus"), - ].join(", "), + restrictedSyntax.reactQuery.useQuery('refetchOnMount'), + restrictedSyntax.reactQuery.useQuery('refetchOnReconnect'), + restrictedSyntax.reactQuery.useQuery('refetchOnWindowFocus'), + restrictedSyntax.reactQuery.useQueries('refetchOnMount'), + restrictedSyntax.reactQuery.useQueries('refetchOnReconnect'), + restrictedSyntax.reactQuery.useQueries('refetchOnWindowFocus'), + ].join(', '), message: - "`refetchOnMount`, `refetchOnReconnect` & `refetchOnWindowFocus` should be managed centrally in the react-query provider", + '`refetchOnMount`, `refetchOnReconnect` & `refetchOnWindowFocus` should be managed centrally in the react-query provider', }, { selector: "CallExpression > MemberExpression[property.name='invalidateQueries']", message: - "Do not directly call `invalidateQueries`. Instead, use the `invalidateQueries` helper function.", + 'Do not directly call `invalidateQueries`. Instead, use the `invalidateQueries` helper function.', }, { selector: [ @@ -186,25 +186,25 @@ export default tseslint.config( "CallExpression[callee.object.name='http'][callee.property.name='delete'] > Literal:first-child", "CallExpression[callee.object.name='http'][callee.property.name='patch'] > Literal:first-child", "CallExpression[callee.object.name='http'][callee.property.name='options'] > Literal:first-child", - ].join(", "), + ].join(', '), message: "Do not pass a string as the first argument to methods on Mock Service Worker's `http`. Use the `mswEndpoint` helper function instead, which provides type-safe routes based on the OpenAPI spec and the API base URL.", }, ], - "no-restricted-imports": [ - "error", + 'no-restricted-imports': [ + 'error', { paths: [ { - importNames: ["useMutation"], - message: "Use the custom `useToastMutation` instead", - name: "@tanstack/react-query", + importNames: ['useMutation'], + message: 'Use the custom `useToastMutation` instead', + name: '@tanstack/react-query', }, ], }, ], - "import/no-restricted-paths": [ - "error", + 'import/no-restricted-paths': [ + 'error', { zones: [ // disables cross-feature imports: @@ -214,29 +214,29 @@ export default tseslint.config( // enforce unidirectional codebase: // e.g. src/routes can import from src/features but not the other way around { - from: "./src/routes", - target: "./src/features", + from: './src/routes', + target: './src/features', }, // enforce unidirectional codebase: // e.g src/features and src/routes can import from these shared modules but not the other way around { - from: ["./src/features", "./src/routes"], + from: ['./src/features', './src/routes'], target: [ - "./src/components", - "./src/constants", - "./src/hooks", - "./src/i18n", - "./src/lib", - "./src/mocks", - "./src/trusty-api", - "./src/types", - "./src/utils", + './src/components', + './src/constants', + './src/hooks', + './src/i18n', + './src/lib', + './src/mocks', + './src/trusty-api', + './src/types', + './src/utils', ], }, ], }, ], }, - }, -); + } +) diff --git a/lint-staged.config.mjs b/lint-staged.config.mjs index f0420ddd..65929b81 100644 --- a/lint-staged.config.mjs +++ b/lint-staged.config.mjs @@ -2,9 +2,9 @@ * @type {import("lint-staged").Config} */ export default { - "**/*.{js,jsx,ts,tsx,mjs,cjs}": [ - "npx prettier --write", - "npx eslint --fix", - "bash -c tsc -p ./tsconfig.app.json --noEmit", + '**/*.{js,jsx,ts,tsx,mjs,cjs}': [ + 'npx prettier --write', + 'npx eslint --fix', + 'bash -c tsc -p ./tsconfig.app.json --noEmit', ], -}; +} diff --git a/openapi-ts.config.ts b/openapi-ts.config.ts index af1af339..d5f6ee19 100644 --- a/openapi-ts.config.ts +++ b/openapi-ts.config.ts @@ -1,19 +1,19 @@ -import { defineConfig } from "@hey-api/openapi-ts"; +import { defineConfig } from '@hey-api/openapi-ts' export default defineConfig({ - client: "@hey-api/client-fetch", - input: "src/api/openapi.json", + client: '@hey-api/client-fetch', + input: 'src/api/openapi.json', output: { - format: "prettier", - path: "./src/api/generated", - lint: "eslint", + format: 'prettier', + path: './src/api/generated', + lint: 'eslint', }, plugins: [ - "@hey-api/sdk", - "@tanstack/react-query", + '@hey-api/sdk', + '@tanstack/react-query', { - enums: "typescript", - name: "@hey-api/typescript", + enums: 'typescript', + name: '@hey-api/typescript', }, ], -}); +}) diff --git a/package-lock.json b/package-lock.json index 41ad1ed9..89c17e43 100644 --- a/package-lock.json +++ b/package-lock.json @@ -36,7 +36,7 @@ "react-syntax-highlighter": "^15.6.1", "remark-gfm": "^4.0.0", "tailwind-merge": "^2.5.5", - "tailwind-variants": "^0.3.0", + "tailwind-variants": "^0.3.1", "tailwindcss-animate": "^1.0.7", "ts-pattern": "^5.6.2", "zod": "^3.24.1" @@ -50,7 +50,7 @@ "@testing-library/jest-dom": "^6.6.3", "@testing-library/react": "^16.2.0", "@testing-library/user-event": "^14.6.1", - "@types/node": "^22.10.1", + "@types/node": "^22.13.1", "@types/react": "^19.0.2", "@types/react-dom": "^19.0.2", "@typescript-eslint/parser": "^8.23.0", @@ -73,6 +73,9 @@ "msw": "^2.7.0", "postcss": "^8.4.49", "prettier": "3.4.2", + "prettier-plugin-classnames": "^0.7.6", + "prettier-plugin-merge": "^0.7.2", + "prettier-plugin-tailwindcss": "^0.6.11", "tailwindcss": "^3.4.15", "typescript": "~5.7.2", "typescript-eslint": "^8.15.0", @@ -4527,9 +4530,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "22.10.5", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.5.tgz", - "integrity": "sha512-F8Q+SeGimwOo86fiovQh8qiXfFEh2/ocYv7tU5pJ3EXMSSxk1Joj5wefpFK2fHTf/N6HKGSxIDBT9f3gCxXPkQ==", + "version": "22.13.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.1.tgz", + "integrity": "sha512-jK8uzQlrvXqEU91UxiK5J7pKHyzgnI1Qnl0QDHIgVGuolJhRb9EEl28Cj9b3rGR8B2lhFCtvIm5os8lFnO/1Ew==", "dev": true, "license": "MIT", "dependencies": { @@ -6508,6 +6511,16 @@ "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", "license": "Apache-2.0" }, + "node_modules/diff": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz", + "integrity": "sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/dlv": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", @@ -11529,6 +11542,124 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, + "node_modules/prettier-plugin-classnames": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/prettier-plugin-classnames/-/prettier-plugin-classnames-0.7.6.tgz", + "integrity": "sha512-sBBnS0VK/gXwtSbsgK5bB5kdtGRWn+hZVBoV/GqLKK6vJGlECmDck/ilqEUCmk7KTGZpRsxIFMW6DXn6OJHAng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "prettier": "^2 || ^3", + "prettier-plugin-astro": "*", + "prettier-plugin-svelte": "*" + }, + "peerDependenciesMeta": { + "prettier-plugin-astro": { + "optional": true + }, + "prettier-plugin-svelte": { + "optional": true + } + } + }, + "node_modules/prettier-plugin-merge": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/prettier-plugin-merge/-/prettier-plugin-merge-0.7.2.tgz", + "integrity": "sha512-o/twjBSVjKttfBBz7KWdJxyPjEAoSh1WwkEv02XKWoyifPXVPSifgB597nntEzTo5hN63XnoZQkAdDVnt6WRLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "diff": "5.1.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "prettier": "^2 || ^3" + } + }, + "node_modules/prettier-plugin-tailwindcss": { + "version": "0.6.11", + "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.6.11.tgz", + "integrity": "sha512-YxaYSIvZPAqhrrEpRtonnrXdghZg1irNg4qrjboCXrpybLWVs55cW2N3juhspVJiO0JBvYJT8SYsJpc8OQSnsA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.21.3" + }, + "peerDependencies": { + "@ianvs/prettier-plugin-sort-imports": "*", + "@prettier/plugin-pug": "*", + "@shopify/prettier-plugin-liquid": "*", + "@trivago/prettier-plugin-sort-imports": "*", + "@zackad/prettier-plugin-twig": "*", + "prettier": "^3.0", + "prettier-plugin-astro": "*", + "prettier-plugin-css-order": "*", + "prettier-plugin-import-sort": "*", + "prettier-plugin-jsdoc": "*", + "prettier-plugin-marko": "*", + "prettier-plugin-multiline-arrays": "*", + "prettier-plugin-organize-attributes": "*", + "prettier-plugin-organize-imports": "*", + "prettier-plugin-sort-imports": "*", + "prettier-plugin-style-order": "*", + "prettier-plugin-svelte": "*" + }, + "peerDependenciesMeta": { + "@ianvs/prettier-plugin-sort-imports": { + "optional": true + }, + "@prettier/plugin-pug": { + "optional": true + }, + "@shopify/prettier-plugin-liquid": { + "optional": true + }, + "@trivago/prettier-plugin-sort-imports": { + "optional": true + }, + "@zackad/prettier-plugin-twig": { + "optional": true + }, + "prettier-plugin-astro": { + "optional": true + }, + "prettier-plugin-css-order": { + "optional": true + }, + "prettier-plugin-import-sort": { + "optional": true + }, + "prettier-plugin-jsdoc": { + "optional": true + }, + "prettier-plugin-marko": { + "optional": true + }, + "prettier-plugin-multiline-arrays": { + "optional": true + }, + "prettier-plugin-organize-attributes": { + "optional": true + }, + "prettier-plugin-organize-imports": { + "optional": true + }, + "prettier-plugin-sort-imports": { + "optional": true + }, + "prettier-plugin-style-order": { + "optional": true + }, + "prettier-plugin-svelte": { + "optional": true + } + } + }, "node_modules/pretty-format": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", @@ -13185,12 +13316,12 @@ } }, "node_modules/tailwind-variants": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/tailwind-variants/-/tailwind-variants-0.3.0.tgz", - "integrity": "sha512-ho2k5kn+LB1fT5XdNS3Clb96zieWxbStE9wNLK7D0AV64kdZMaYzAKo0fWl6fXLPY99ffF9oBJnIj5escEl/8A==", + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/tailwind-variants/-/tailwind-variants-0.3.1.tgz", + "integrity": "sha512-krn67M3FpPwElg4FsZrOQd0U26o7UDH/QOkK8RNaiCCrr052f6YJPBUfNKnPo/s/xRzNPtv1Mldlxsg8Tb46BQ==", "license": "MIT", "dependencies": { - "tailwind-merge": "^2.5.4" + "tailwind-merge": "2.5.4" }, "engines": { "node": ">=16.x", @@ -13200,6 +13331,16 @@ "tailwindcss": "*" } }, + "node_modules/tailwind-variants/node_modules/tailwind-merge": { + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.5.4.tgz", + "integrity": "sha512-0q8cfZHMu9nuYP/b5Shb7Y7Sh1B7Nnl5GqNr1U+n2p6+mybvRtayrQ+0042Z5byvTA8ihjlP8Odo8/VnHbZu4Q==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, "node_modules/tailwindcss": { "version": "3.4.17", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz", diff --git a/package.json b/package.json index a3d152a5..522a9624 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,7 @@ "react-syntax-highlighter": "^15.6.1", "remark-gfm": "^4.0.0", "tailwind-merge": "^2.5.5", - "tailwind-variants": "^0.3.0", + "tailwind-variants": "^0.3.1", "tailwindcss-animate": "^1.0.7", "ts-pattern": "^5.6.2", "zod": "^3.24.1" @@ -62,7 +62,7 @@ "@testing-library/jest-dom": "^6.6.3", "@testing-library/react": "^16.2.0", "@testing-library/user-event": "^14.6.1", - "@types/node": "^22.10.1", + "@types/node": "^22.13.1", "@types/react": "^19.0.2", "@types/react-dom": "^19.0.2", "@typescript-eslint/parser": "^8.23.0", @@ -85,6 +85,9 @@ "msw": "^2.7.0", "postcss": "^8.4.49", "prettier": "3.4.2", + "prettier-plugin-classnames": "^0.7.6", + "prettier-plugin-merge": "^0.7.2", + "prettier-plugin-tailwindcss": "^0.6.11", "tailwindcss": "^3.4.15", "typescript": "~5.7.2", "typescript-eslint": "^8.15.0", diff --git a/postcss.config.js b/postcss.config.js index 2aa7205d..2e7af2b7 100644 --- a/postcss.config.js +++ b/postcss.config.js @@ -3,4 +3,4 @@ export default { tailwindcss: {}, autoprefixer: {}, }, -}; +} diff --git a/src/App.test.tsx b/src/App.test.tsx index 1cf341d5..1e5d55c0 100644 --- a/src/App.test.tsx +++ b/src/App.test.tsx @@ -1,109 +1,109 @@ -import { render } from "@/lib/test-utils"; -import { screen, waitFor } from "@testing-library/react"; -import { describe, expect, it } from "vitest"; -import App from "./App"; -import userEvent from "@testing-library/user-event"; - -describe("App", () => { - it("should render header", async () => { - render(); - expect(screen.getByText("Settings")).toBeVisible(); - expect(screen.getByText("Help")).toBeVisible(); - expect(screen.getByRole("banner", { name: "App header" })).toBeVisible(); - expect(screen.getByRole("heading", { name: /codeGate/i })).toBeVisible(); - - await userEvent.click(screen.getByText("Settings")); +import { render } from '@/lib/test-utils' +import { screen, waitFor } from '@testing-library/react' +import { describe, expect, it } from 'vitest' +import App from './App' +import userEvent from '@testing-library/user-event' + +describe('App', () => { + it('should render header', async () => { + render() + expect(screen.getByText('Settings')).toBeVisible() + expect(screen.getByText('Help')).toBeVisible() + expect(screen.getByRole('banner', { name: 'App header' })).toBeVisible() + expect(screen.getByRole('heading', { name: /codeGate/i })).toBeVisible() + + await userEvent.click(screen.getByText('Settings')) expect( - screen.getByRole("menuitem", { + screen.getByRole('menuitem', { name: /providers/i, - }), - ).toBeVisible(); + }) + ).toBeVisible() expect( - screen.getByRole("menuitem", { + screen.getByRole('menuitem', { name: /certificate security/i, - }), - ).toBeVisible(); + }) + ).toBeVisible() expect( - screen.getByRole("menuitem", { + screen.getByRole('menuitem', { name: /download/i, - }), - ).toBeVisible(); + }) + ).toBeVisible() - await userEvent.click(screen.getByText("Settings")); - await userEvent.click(screen.getByText("Help")); + await userEvent.click(screen.getByText('Settings')) + await userEvent.click(screen.getByText('Help')) expect( - screen.getByRole("menuitem", { + screen.getByRole('menuitem', { name: /use with continue/i, - }), - ).toBeVisible(); + }) + ).toBeVisible() expect( - screen.getByRole("menuitem", { + screen.getByRole('menuitem', { name: /use with copilot/i, - }), - ).toBeVisible(); + }) + ).toBeVisible() expect( - screen.getByRole("menuitem", { + screen.getByRole('menuitem', { name: /documentation/i, - }), - ).toBeVisible(); + }) + ).toBeVisible() - const discordMenuItem = screen.getByRole("menuitem", { + const discordMenuItem = screen.getByRole('menuitem', { name: /discord/i, - }); - expect(discordMenuItem).toBeVisible(); + }) + expect(discordMenuItem).toBeVisible() expect(discordMenuItem).toHaveAttribute( - "href", - "https://discord.gg/stacklok", - ); + 'href', + 'https://discord.gg/stacklok' + ) - const githubMenuItem = screen.getByRole("menuitem", { + const githubMenuItem = screen.getByRole('menuitem', { name: /github/i, - }); - expect(githubMenuItem).toBeVisible(); + }) + expect(githubMenuItem).toBeVisible() expect(githubMenuItem).toHaveAttribute( - "href", - "https://github.com/stacklok/codegate", - ); + 'href', + 'https://github.com/stacklok/codegate' + ) - const youtubeMenuItem = screen.getByRole("menuitem", { + const youtubeMenuItem = screen.getByRole('menuitem', { name: /youtube/i, - }); - expect(youtubeMenuItem).toBeVisible(); + }) + expect(youtubeMenuItem).toBeVisible() expect(youtubeMenuItem).toHaveAttribute( - "href", - "https://www.youtube.com/@Stacklok", - ); + 'href', + 'https://www.youtube.com/@Stacklok' + ) - await userEvent.click(screen.getByText("Help")); + await userEvent.click(screen.getByText('Help')) await waitFor(() => - expect(screen.getByRole("link", { name: /codeGate/i })).toBeVisible(), - ); - }); + expect(screen.getByRole('link', { name: /codeGate/i })).toBeVisible() + ) + }) - it("should render workspaces dropdown", async () => { - render(); + it('should render workspaces dropdown', async () => { + render() await waitFor(() => - expect(screen.getByRole("link", { name: "CodeGate" })).toBeVisible(), - ); + expect(screen.getByRole('link', { name: 'CodeGate' })).toBeVisible() + ) - const workspaceSelectionButton = screen.getByRole("button", { - name: "Active workspace default", - }); - await waitFor(() => expect(workspaceSelectionButton).toBeVisible()); + const workspaceSelectionButton = screen.getByRole('button', { + name: 'Active workspace default', + }) + await waitFor(() => expect(workspaceSelectionButton).toBeVisible()) - await userEvent.click(workspaceSelectionButton); + await userEvent.click(workspaceSelectionButton) await waitFor(() => expect( - screen.getByRole("option", { + screen.getByRole('option', { name: /anotherworkspae/i, - }), - ).toBeVisible(), - ); - }); -}); + }) + ).toBeVisible() + ) + }) +}) diff --git a/src/App.tsx b/src/App.tsx index 2badc373..e8404492 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,9 +1,9 @@ -import { Header } from "./features/header/components/header"; -import { useSse } from "./hooks/useSse"; -import Page from "./Page"; +import { Header } from './features/header/components/header' +import { useSse } from './hooks/useSse' +import Page from './Page' export default function App() { - useSse(); + useSse() return ( <> @@ -12,5 +12,5 @@ export default function App() { - ); + ) } diff --git a/src/Page.test.tsx b/src/Page.test.tsx index b01d95cb..9e95bd13 100644 --- a/src/Page.test.tsx +++ b/src/Page.test.tsx @@ -1,13 +1,13 @@ -import { render } from "./lib/test-utils"; -import Page from "./Page"; +import { render } from './lib/test-utils' +import Page from './Page' -test("render NotFound route", () => { +test('render NotFound route', () => { const { getByText, getByRole } = render(, { routeConfig: { - initialEntries: ["/fake-route"], + initialEntries: ['/fake-route'], }, - }); + }) - expect(getByText(/Oops! There's nothing here/i)).toBeVisible(); - expect(getByRole("button", { name: "Home" })).toBeVisible(); -}); + expect(getByText(/Oops! There's nothing here/i)).toBeVisible() + expect(getByRole('button', { name: 'Home' })).toBeVisible() +}) diff --git a/src/Page.tsx b/src/Page.tsx index 65d5c1ce..e2d4c30d 100644 --- a/src/Page.tsx +++ b/src/Page.tsx @@ -1,16 +1,16 @@ -import { Route, Routes } from "react-router-dom"; +import { Route, Routes } from 'react-router-dom' -import { RouteWorkspace } from "./routes/route-workspace"; -import { RouteWorkspaces } from "./routes/route-workspaces"; -import { RouteCertificates } from "./routes/route-certificates"; -import { RouteChat } from "./routes/route-chat"; -import { RouteDashboard } from "./routes/route-dashboard"; -import { RouteCertificateSecurity } from "./routes/route-certificate-security"; -import { RouteWorkspaceCreation } from "./routes/route-workspace-creation"; -import { RouteNotFound } from "./routes/route-not-found"; -import { RouteProvider } from "./routes/route-providers"; -import { RouteProviderCreate } from "./routes/route-provider-create"; -import { RouteProviderUpdate } from "./routes/route-provider-update"; +import { RouteWorkspace } from './routes/route-workspace' +import { RouteWorkspaces } from './routes/route-workspaces' +import { RouteCertificates } from './routes/route-certificates' +import { RouteChat } from './routes/route-chat' +import { RouteDashboard } from './routes/route-dashboard' +import { RouteCertificateSecurity } from './routes/route-certificate-security' +import { RouteWorkspaceCreation } from './routes/route-workspace-creation' +import { RouteNotFound } from './routes/route-not-found' +import { RouteProvider } from './routes/route-providers' +import { RouteProviderCreate } from './routes/route-provider-create' +import { RouteProviderUpdate } from './routes/route-provider-update' export default function Page() { return ( @@ -36,5 +36,5 @@ export default function Page() { } /> - ); + ) } diff --git a/src/api/generated/@tanstack/react-query.gen.ts b/src/api/generated/@tanstack/react-query.gen.ts index 28e8ffbb..102ed855 100644 --- a/src/api/generated/@tanstack/react-query.gen.ts +++ b/src/api/generated/@tanstack/react-query.gen.ts @@ -1,7 +1,7 @@ // This file is auto-generated by @hey-api/openapi-ts -import type { OptionsLegacyParser } from "@hey-api/client-fetch"; -import { queryOptions, type UseMutationOptions } from "@tanstack/react-query"; +import type { OptionsLegacyParser } from '@hey-api/client-fetch' +import { queryOptions, type UseMutationOptions } from '@tanstack/react-query' import { client, healthCheckHealthGet, @@ -31,7 +31,7 @@ import { v1StreamSse, v1VersionCheck, v1GetWorkspaceTokenUsage, -} from "../sdk.gen"; +} from '../sdk.gen' import type { V1ListProviderEndpointsData, V1AddProviderEndpointData, @@ -77,45 +77,45 @@ import type { V1SetWorkspaceMuxesError, V1SetWorkspaceMuxesResponse, V1GetWorkspaceTokenUsageData, -} from "../types.gen"; +} from '../types.gen' type QueryKey = [ - Pick & { - _id: string; - _infinite?: boolean; + Pick & { + _id: string + _infinite?: boolean }, -]; +] const createQueryKey = ( id: string, options?: TOptions, - infinite?: boolean, + infinite?: boolean ): QueryKey[0] => { const params: QueryKey[0] = { _id: id, baseUrl: (options?.client ?? client).getConfig().baseUrl, - } as QueryKey[0]; + } as QueryKey[0] if (infinite) { - params._infinite = infinite; + params._infinite = infinite } if (options?.body) { - params.body = options.body; + params.body = options.body } if (options?.headers) { - params.headers = options.headers; + params.headers = options.headers } if (options?.path) { - params.path = options.path; + params.path = options.path } if (options?.query) { - params.query = options.query; + params.query = options.query } - return params; -}; + return params +} export const healthCheckHealthGetQueryKey = (options?: OptionsLegacyParser) => [ - createQueryKey("healthCheckHealthGet", options), -]; + createQueryKey('healthCheckHealthGet', options), +] export const healthCheckHealthGetOptions = (options?: OptionsLegacyParser) => { return queryOptions({ @@ -125,19 +125,19 @@ export const healthCheckHealthGetOptions = (options?: OptionsLegacyParser) => { ...queryKey[0], signal, throwOnError: true, - }); - return data; + }) + return data }, queryKey: healthCheckHealthGetQueryKey(options), - }); -}; + }) +} export const v1ListProviderEndpointsQueryKey = ( - options?: OptionsLegacyParser, -) => [createQueryKey("v1ListProviderEndpoints", options)]; + options?: OptionsLegacyParser +) => [createQueryKey('v1ListProviderEndpoints', options)] export const v1ListProviderEndpointsOptions = ( - options?: OptionsLegacyParser, + options?: OptionsLegacyParser ) => { return queryOptions({ queryFn: async ({ queryKey, signal }) => { @@ -146,19 +146,19 @@ export const v1ListProviderEndpointsOptions = ( ...queryKey[0], signal, throwOnError: true, - }); - return data; + }) + return data }, queryKey: v1ListProviderEndpointsQueryKey(options), - }); -}; + }) +} export const v1AddProviderEndpointQueryKey = ( - options: OptionsLegacyParser, -) => [createQueryKey("v1AddProviderEndpoint", options)]; + options: OptionsLegacyParser +) => [createQueryKey('v1AddProviderEndpoint', options)] export const v1AddProviderEndpointOptions = ( - options: OptionsLegacyParser, + options: OptionsLegacyParser ) => { return queryOptions({ queryFn: async ({ queryKey, signal }) => { @@ -167,15 +167,15 @@ export const v1AddProviderEndpointOptions = ( ...queryKey[0], signal, throwOnError: true, - }); - return data; + }) + return data }, queryKey: v1AddProviderEndpointQueryKey(options), - }); -}; + }) +} export const v1AddProviderEndpointMutation = ( - options?: Partial>, + options?: Partial> ) => { const mutationOptions: UseMutationOptions< V1AddProviderEndpointResponse, @@ -187,19 +187,19 @@ export const v1AddProviderEndpointMutation = ( ...options, ...localOptions, throwOnError: true, - }); - return data; + }) + return data }, - }; - return mutationOptions; -}; + } + return mutationOptions +} export const v1ListAllModelsForAllProvidersQueryKey = ( - options?: OptionsLegacyParser, -) => [createQueryKey("v1ListAllModelsForAllProviders", options)]; + options?: OptionsLegacyParser +) => [createQueryKey('v1ListAllModelsForAllProviders', options)] export const v1ListAllModelsForAllProvidersOptions = ( - options?: OptionsLegacyParser, + options?: OptionsLegacyParser ) => { return queryOptions({ queryFn: async ({ queryKey, signal }) => { @@ -208,19 +208,19 @@ export const v1ListAllModelsForAllProvidersOptions = ( ...queryKey[0], signal, throwOnError: true, - }); - return data; + }) + return data }, queryKey: v1ListAllModelsForAllProvidersQueryKey(options), - }); -}; + }) +} export const v1ListModelsByProviderQueryKey = ( - options: OptionsLegacyParser, -) => [createQueryKey("v1ListModelsByProvider", options)]; + options: OptionsLegacyParser +) => [createQueryKey('v1ListModelsByProvider', options)] export const v1ListModelsByProviderOptions = ( - options: OptionsLegacyParser, + options: OptionsLegacyParser ) => { return queryOptions({ queryFn: async ({ queryKey, signal }) => { @@ -229,19 +229,19 @@ export const v1ListModelsByProviderOptions = ( ...queryKey[0], signal, throwOnError: true, - }); - return data; + }) + return data }, queryKey: v1ListModelsByProviderQueryKey(options), - }); -}; + }) +} export const v1GetProviderEndpointQueryKey = ( - options: OptionsLegacyParser, -) => [createQueryKey("v1GetProviderEndpoint", options)]; + options: OptionsLegacyParser +) => [createQueryKey('v1GetProviderEndpoint', options)] export const v1GetProviderEndpointOptions = ( - options: OptionsLegacyParser, + options: OptionsLegacyParser ) => { return queryOptions({ queryFn: async ({ queryKey, signal }) => { @@ -250,15 +250,15 @@ export const v1GetProviderEndpointOptions = ( ...queryKey[0], signal, throwOnError: true, - }); - return data; + }) + return data }, queryKey: v1GetProviderEndpointQueryKey(options), - }); -}; + }) +} export const v1UpdateProviderEndpointMutation = ( - options?: Partial>, + options?: Partial> ) => { const mutationOptions: UseMutationOptions< V1UpdateProviderEndpointResponse, @@ -270,15 +270,15 @@ export const v1UpdateProviderEndpointMutation = ( ...options, ...localOptions, throwOnError: true, - }); - return data; + }) + return data }, - }; - return mutationOptions; -}; + } + return mutationOptions +} export const v1DeleteProviderEndpointMutation = ( - options?: Partial>, + options?: Partial> ) => { const mutationOptions: UseMutationOptions< V1DeleteProviderEndpointResponse, @@ -290,15 +290,15 @@ export const v1DeleteProviderEndpointMutation = ( ...options, ...localOptions, throwOnError: true, - }); - return data; + }) + return data }, - }; - return mutationOptions; -}; + } + return mutationOptions +} export const v1ConfigureAuthMaterialMutation = ( - options?: Partial>, + options?: Partial> ) => { const mutationOptions: UseMutationOptions< V1ConfigureAuthMaterialResponse, @@ -310,16 +310,16 @@ export const v1ConfigureAuthMaterialMutation = ( ...options, ...localOptions, throwOnError: true, - }); - return data; + }) + return data }, - }; - return mutationOptions; -}; + } + return mutationOptions +} export const v1ListWorkspacesQueryKey = (options?: OptionsLegacyParser) => [ - createQueryKey("v1ListWorkspaces", options), -]; + createQueryKey('v1ListWorkspaces', options), +] export const v1ListWorkspacesOptions = (options?: OptionsLegacyParser) => { return queryOptions({ @@ -329,19 +329,19 @@ export const v1ListWorkspacesOptions = (options?: OptionsLegacyParser) => { ...queryKey[0], signal, throwOnError: true, - }); - return data; + }) + return data }, queryKey: v1ListWorkspacesQueryKey(options), - }); -}; + }) +} export const v1CreateWorkspaceQueryKey = ( - options: OptionsLegacyParser, -) => [createQueryKey("v1CreateWorkspace", options)]; + options: OptionsLegacyParser +) => [createQueryKey('v1CreateWorkspace', options)] export const v1CreateWorkspaceOptions = ( - options: OptionsLegacyParser, + options: OptionsLegacyParser ) => { return queryOptions({ queryFn: async ({ queryKey, signal }) => { @@ -350,15 +350,15 @@ export const v1CreateWorkspaceOptions = ( ...queryKey[0], signal, throwOnError: true, - }); - return data; + }) + return data }, queryKey: v1CreateWorkspaceQueryKey(options), - }); -}; + }) +} export const v1CreateWorkspaceMutation = ( - options?: Partial>, + options?: Partial> ) => { const mutationOptions: UseMutationOptions< V1CreateWorkspaceResponse, @@ -370,19 +370,19 @@ export const v1CreateWorkspaceMutation = ( ...options, ...localOptions, throwOnError: true, - }); - return data; + }) + return data }, - }; - return mutationOptions; -}; + } + return mutationOptions +} export const v1ListActiveWorkspacesQueryKey = ( - options?: OptionsLegacyParser, -) => [createQueryKey("v1ListActiveWorkspaces", options)]; + options?: OptionsLegacyParser +) => [createQueryKey('v1ListActiveWorkspaces', options)] export const v1ListActiveWorkspacesOptions = ( - options?: OptionsLegacyParser, + options?: OptionsLegacyParser ) => { return queryOptions({ queryFn: async ({ queryKey, signal }) => { @@ -391,19 +391,19 @@ export const v1ListActiveWorkspacesOptions = ( ...queryKey[0], signal, throwOnError: true, - }); - return data; + }) + return data }, queryKey: v1ListActiveWorkspacesQueryKey(options), - }); -}; + }) +} export const v1ActivateWorkspaceQueryKey = ( - options: OptionsLegacyParser, -) => [createQueryKey("v1ActivateWorkspace", options)]; + options: OptionsLegacyParser +) => [createQueryKey('v1ActivateWorkspace', options)] export const v1ActivateWorkspaceOptions = ( - options: OptionsLegacyParser, + options: OptionsLegacyParser ) => { return queryOptions({ queryFn: async ({ queryKey, signal }) => { @@ -412,15 +412,15 @@ export const v1ActivateWorkspaceOptions = ( ...queryKey[0], signal, throwOnError: true, - }); - return data; + }) + return data }, queryKey: v1ActivateWorkspaceQueryKey(options), - }); -}; + }) +} export const v1ActivateWorkspaceMutation = ( - options?: Partial>, + options?: Partial> ) => { const mutationOptions: UseMutationOptions< V1ActivateWorkspaceResponse, @@ -432,15 +432,15 @@ export const v1ActivateWorkspaceMutation = ( ...options, ...localOptions, throwOnError: true, - }); - return data; + }) + return data }, - }; - return mutationOptions; -}; + } + return mutationOptions +} export const v1DeleteWorkspaceMutation = ( - options?: Partial>, + options?: Partial> ) => { const mutationOptions: UseMutationOptions< V1DeleteWorkspaceResponse, @@ -452,19 +452,19 @@ export const v1DeleteWorkspaceMutation = ( ...options, ...localOptions, throwOnError: true, - }); - return data; + }) + return data }, - }; - return mutationOptions; -}; + } + return mutationOptions +} export const v1ListArchivedWorkspacesQueryKey = ( - options?: OptionsLegacyParser, -) => [createQueryKey("v1ListArchivedWorkspaces", options)]; + options?: OptionsLegacyParser +) => [createQueryKey('v1ListArchivedWorkspaces', options)] export const v1ListArchivedWorkspacesOptions = ( - options?: OptionsLegacyParser, + options?: OptionsLegacyParser ) => { return queryOptions({ queryFn: async ({ queryKey, signal }) => { @@ -473,19 +473,19 @@ export const v1ListArchivedWorkspacesOptions = ( ...queryKey[0], signal, throwOnError: true, - }); - return data; + }) + return data }, queryKey: v1ListArchivedWorkspacesQueryKey(options), - }); -}; + }) +} export const v1RecoverWorkspaceQueryKey = ( - options: OptionsLegacyParser, -) => [createQueryKey("v1RecoverWorkspace", options)]; + options: OptionsLegacyParser +) => [createQueryKey('v1RecoverWorkspace', options)] export const v1RecoverWorkspaceOptions = ( - options: OptionsLegacyParser, + options: OptionsLegacyParser ) => { return queryOptions({ queryFn: async ({ queryKey, signal }) => { @@ -494,15 +494,15 @@ export const v1RecoverWorkspaceOptions = ( ...queryKey[0], signal, throwOnError: true, - }); - return data; + }) + return data }, queryKey: v1RecoverWorkspaceQueryKey(options), - }); -}; + }) +} export const v1RecoverWorkspaceMutation = ( - options?: Partial>, + options?: Partial> ) => { const mutationOptions: UseMutationOptions< V1RecoverWorkspaceResponse, @@ -514,15 +514,15 @@ export const v1RecoverWorkspaceMutation = ( ...options, ...localOptions, throwOnError: true, - }); - return data; + }) + return data }, - }; - return mutationOptions; -}; + } + return mutationOptions +} export const v1HardDeleteWorkspaceMutation = ( - options?: Partial>, + options?: Partial> ) => { const mutationOptions: UseMutationOptions< V1HardDeleteWorkspaceResponse, @@ -534,19 +534,19 @@ export const v1HardDeleteWorkspaceMutation = ( ...options, ...localOptions, throwOnError: true, - }); - return data; + }) + return data }, - }; - return mutationOptions; -}; + } + return mutationOptions +} export const v1GetWorkspaceAlertsQueryKey = ( - options: OptionsLegacyParser, -) => [createQueryKey("v1GetWorkspaceAlerts", options)]; + options: OptionsLegacyParser +) => [createQueryKey('v1GetWorkspaceAlerts', options)] export const v1GetWorkspaceAlertsOptions = ( - options: OptionsLegacyParser, + options: OptionsLegacyParser ) => { return queryOptions({ queryFn: async ({ queryKey, signal }) => { @@ -555,19 +555,19 @@ export const v1GetWorkspaceAlertsOptions = ( ...queryKey[0], signal, throwOnError: true, - }); - return data; + }) + return data }, queryKey: v1GetWorkspaceAlertsQueryKey(options), - }); -}; + }) +} export const v1GetWorkspaceMessagesQueryKey = ( - options: OptionsLegacyParser, -) => [createQueryKey("v1GetWorkspaceMessages", options)]; + options: OptionsLegacyParser +) => [createQueryKey('v1GetWorkspaceMessages', options)] export const v1GetWorkspaceMessagesOptions = ( - options: OptionsLegacyParser, + options: OptionsLegacyParser ) => { return queryOptions({ queryFn: async ({ queryKey, signal }) => { @@ -576,19 +576,19 @@ export const v1GetWorkspaceMessagesOptions = ( ...queryKey[0], signal, throwOnError: true, - }); - return data; + }) + return data }, queryKey: v1GetWorkspaceMessagesQueryKey(options), - }); -}; + }) +} export const v1GetWorkspaceCustomInstructionsQueryKey = ( - options: OptionsLegacyParser, -) => [createQueryKey("v1GetWorkspaceCustomInstructions", options)]; + options: OptionsLegacyParser +) => [createQueryKey('v1GetWorkspaceCustomInstructions', options)] export const v1GetWorkspaceCustomInstructionsOptions = ( - options: OptionsLegacyParser, + options: OptionsLegacyParser ) => { return queryOptions({ queryFn: async ({ queryKey, signal }) => { @@ -597,15 +597,15 @@ export const v1GetWorkspaceCustomInstructionsOptions = ( ...queryKey[0], signal, throwOnError: true, - }); - return data; + }) + return data }, queryKey: v1GetWorkspaceCustomInstructionsQueryKey(options), - }); -}; + }) +} export const v1SetWorkspaceCustomInstructionsMutation = ( - options?: Partial>, + options?: Partial> ) => { const mutationOptions: UseMutationOptions< V1SetWorkspaceCustomInstructionsResponse, @@ -617,17 +617,17 @@ export const v1SetWorkspaceCustomInstructionsMutation = ( ...options, ...localOptions, throwOnError: true, - }); - return data; + }) + return data }, - }; - return mutationOptions; -}; + } + return mutationOptions +} export const v1DeleteWorkspaceCustomInstructionsMutation = ( options?: Partial< OptionsLegacyParser - >, + > ) => { const mutationOptions: UseMutationOptions< V1DeleteWorkspaceCustomInstructionsResponse, @@ -639,19 +639,19 @@ export const v1DeleteWorkspaceCustomInstructionsMutation = ( ...options, ...localOptions, throwOnError: true, - }); - return data; + }) + return data }, - }; - return mutationOptions; -}; + } + return mutationOptions +} export const v1GetWorkspaceMuxesQueryKey = ( - options: OptionsLegacyParser, -) => [createQueryKey("v1GetWorkspaceMuxes", options)]; + options: OptionsLegacyParser +) => [createQueryKey('v1GetWorkspaceMuxes', options)] export const v1GetWorkspaceMuxesOptions = ( - options: OptionsLegacyParser, + options: OptionsLegacyParser ) => { return queryOptions({ queryFn: async ({ queryKey, signal }) => { @@ -660,15 +660,15 @@ export const v1GetWorkspaceMuxesOptions = ( ...queryKey[0], signal, throwOnError: true, - }); - return data; + }) + return data }, queryKey: v1GetWorkspaceMuxesQueryKey(options), - }); -}; + }) +} export const v1SetWorkspaceMuxesMutation = ( - options?: Partial>, + options?: Partial> ) => { const mutationOptions: UseMutationOptions< V1SetWorkspaceMuxesResponse, @@ -680,16 +680,16 @@ export const v1SetWorkspaceMuxesMutation = ( ...options, ...localOptions, throwOnError: true, - }); - return data; + }) + return data }, - }; - return mutationOptions; -}; + } + return mutationOptions +} export const v1StreamSseQueryKey = (options?: OptionsLegacyParser) => [ - createQueryKey("v1StreamSse", options), -]; + createQueryKey('v1StreamSse', options), +] export const v1StreamSseOptions = (options?: OptionsLegacyParser) => { return queryOptions({ @@ -699,16 +699,16 @@ export const v1StreamSseOptions = (options?: OptionsLegacyParser) => { ...queryKey[0], signal, throwOnError: true, - }); - return data; + }) + return data }, queryKey: v1StreamSseQueryKey(options), - }); -}; + }) +} export const v1VersionCheckQueryKey = (options?: OptionsLegacyParser) => [ - createQueryKey("v1VersionCheck", options), -]; + createQueryKey('v1VersionCheck', options), +] export const v1VersionCheckOptions = (options?: OptionsLegacyParser) => { return queryOptions({ @@ -718,19 +718,19 @@ export const v1VersionCheckOptions = (options?: OptionsLegacyParser) => { ...queryKey[0], signal, throwOnError: true, - }); - return data; + }) + return data }, queryKey: v1VersionCheckQueryKey(options), - }); -}; + }) +} export const v1GetWorkspaceTokenUsageQueryKey = ( - options: OptionsLegacyParser, -) => [createQueryKey("v1GetWorkspaceTokenUsage", options)]; + options: OptionsLegacyParser +) => [createQueryKey('v1GetWorkspaceTokenUsage', options)] export const v1GetWorkspaceTokenUsageOptions = ( - options: OptionsLegacyParser, + options: OptionsLegacyParser ) => { return queryOptions({ queryFn: async ({ queryKey, signal }) => { @@ -739,9 +739,9 @@ export const v1GetWorkspaceTokenUsageOptions = ( ...queryKey[0], signal, throwOnError: true, - }); - return data; + }) + return data }, queryKey: v1GetWorkspaceTokenUsageQueryKey(options), - }); -}; + }) +} diff --git a/src/api/generated/index.ts b/src/api/generated/index.ts index eae885d0..544a87d4 100644 --- a/src/api/generated/index.ts +++ b/src/api/generated/index.ts @@ -1,3 +1,3 @@ // This file is auto-generated by @hey-api/openapi-ts -export * from "./sdk.gen"; -export * from "./types.gen"; +export * from './sdk.gen' +export * from './types.gen' diff --git a/src/api/generated/sdk.gen.ts b/src/api/generated/sdk.gen.ts index 8069e38d..9834d09b 100644 --- a/src/api/generated/sdk.gen.ts +++ b/src/api/generated/sdk.gen.ts @@ -4,7 +4,7 @@ import { createClient, createConfig, type OptionsLegacyParser, -} from "@hey-api/client-fetch"; +} from '@hey-api/client-fetch' import type { HealthCheckHealthGetError, HealthCheckHealthGetResponse, @@ -80,15 +80,15 @@ import type { V1GetWorkspaceTokenUsageData, V1GetWorkspaceTokenUsageError, V1GetWorkspaceTokenUsageResponse, -} from "./types.gen"; +} from './types.gen' -export const client = createClient(createConfig()); +export const client = createClient(createConfig()) /** * Health Check */ export const healthCheckHealthGet = ( - options?: OptionsLegacyParser, + options?: OptionsLegacyParser ) => { return (options?.client ?? client).get< HealthCheckHealthGetResponse, @@ -96,16 +96,16 @@ export const healthCheckHealthGet = ( ThrowOnError >({ ...options, - url: "/health", - }); -}; + url: '/health', + }) +} /** * List Provider Endpoints * List all provider endpoints. */ export const v1ListProviderEndpoints = ( - options?: OptionsLegacyParser, + options?: OptionsLegacyParser ) => { return (options?.client ?? client).get< V1ListProviderEndpointsResponse, @@ -113,16 +113,16 @@ export const v1ListProviderEndpoints = ( ThrowOnError >({ ...options, - url: "/api/v1/provider-endpoints", - }); -}; + url: '/api/v1/provider-endpoints', + }) +} /** * Add Provider Endpoint * Add a provider endpoint. */ export const v1AddProviderEndpoint = ( - options: OptionsLegacyParser, + options: OptionsLegacyParser ) => { return (options?.client ?? client).post< V1AddProviderEndpointResponse, @@ -130,9 +130,9 @@ export const v1AddProviderEndpoint = ( ThrowOnError >({ ...options, - url: "/api/v1/provider-endpoints", - }); -}; + url: '/api/v1/provider-endpoints', + }) +} /** * List All Models For All Providers @@ -141,7 +141,7 @@ export const v1AddProviderEndpoint = ( export const v1ListAllModelsForAllProviders = < ThrowOnError extends boolean = false, >( - options?: OptionsLegacyParser, + options?: OptionsLegacyParser ) => { return (options?.client ?? client).get< V1ListAllModelsForAllProvidersResponse, @@ -149,16 +149,16 @@ export const v1ListAllModelsForAllProviders = < ThrowOnError >({ ...options, - url: "/api/v1/provider-endpoints/models", - }); -}; + url: '/api/v1/provider-endpoints/models', + }) +} /** * List Models By Provider * List models by provider. */ export const v1ListModelsByProvider = ( - options: OptionsLegacyParser, + options: OptionsLegacyParser ) => { return (options?.client ?? client).get< V1ListModelsByProviderResponse, @@ -166,16 +166,16 @@ export const v1ListModelsByProvider = ( ThrowOnError >({ ...options, - url: "/api/v1/provider-endpoints/{provider_id}/models", - }); -}; + url: '/api/v1/provider-endpoints/{provider_id}/models', + }) +} /** * Get Provider Endpoint * Get a provider endpoint by ID. */ export const v1GetProviderEndpoint = ( - options: OptionsLegacyParser, + options: OptionsLegacyParser ) => { return (options?.client ?? client).get< V1GetProviderEndpointResponse, @@ -183,16 +183,16 @@ export const v1GetProviderEndpoint = ( ThrowOnError >({ ...options, - url: "/api/v1/provider-endpoints/{provider_id}", - }); -}; + url: '/api/v1/provider-endpoints/{provider_id}', + }) +} /** * Update Provider Endpoint * Update a provider endpoint by ID. */ export const v1UpdateProviderEndpoint = ( - options: OptionsLegacyParser, + options: OptionsLegacyParser ) => { return (options?.client ?? client).put< V1UpdateProviderEndpointResponse, @@ -200,16 +200,16 @@ export const v1UpdateProviderEndpoint = ( ThrowOnError >({ ...options, - url: "/api/v1/provider-endpoints/{provider_id}", - }); -}; + url: '/api/v1/provider-endpoints/{provider_id}', + }) +} /** * Delete Provider Endpoint * Delete a provider endpoint by id. */ export const v1DeleteProviderEndpoint = ( - options: OptionsLegacyParser, + options: OptionsLegacyParser ) => { return (options?.client ?? client).delete< V1DeleteProviderEndpointResponse, @@ -217,16 +217,16 @@ export const v1DeleteProviderEndpoint = ( ThrowOnError >({ ...options, - url: "/api/v1/provider-endpoints/{provider_id}", - }); -}; + url: '/api/v1/provider-endpoints/{provider_id}', + }) +} /** * Configure Auth Material * Configure auth material for a provider. */ export const v1ConfigureAuthMaterial = ( - options: OptionsLegacyParser, + options: OptionsLegacyParser ) => { return (options?.client ?? client).put< V1ConfigureAuthMaterialResponse, @@ -234,16 +234,16 @@ export const v1ConfigureAuthMaterial = ( ThrowOnError >({ ...options, - url: "/api/v1/provider-endpoints/{provider_id}/auth-material", - }); -}; + url: '/api/v1/provider-endpoints/{provider_id}/auth-material', + }) +} /** * List Workspaces * List all workspaces. */ export const v1ListWorkspaces = ( - options?: OptionsLegacyParser, + options?: OptionsLegacyParser ) => { return (options?.client ?? client).get< V1ListWorkspacesResponse, @@ -251,16 +251,16 @@ export const v1ListWorkspaces = ( ThrowOnError >({ ...options, - url: "/api/v1/workspaces", - }); -}; + url: '/api/v1/workspaces', + }) +} /** * Create Workspace * Create a new workspace. */ export const v1CreateWorkspace = ( - options: OptionsLegacyParser, + options: OptionsLegacyParser ) => { return (options?.client ?? client).post< V1CreateWorkspaceResponse, @@ -268,9 +268,9 @@ export const v1CreateWorkspace = ( ThrowOnError >({ ...options, - url: "/api/v1/workspaces", - }); -}; + url: '/api/v1/workspaces', + }) +} /** * List Active Workspaces @@ -280,7 +280,7 @@ export const v1CreateWorkspace = ( * the globally active workspace. */ export const v1ListActiveWorkspaces = ( - options?: OptionsLegacyParser, + options?: OptionsLegacyParser ) => { return (options?.client ?? client).get< V1ListActiveWorkspacesResponse, @@ -288,16 +288,16 @@ export const v1ListActiveWorkspaces = ( ThrowOnError >({ ...options, - url: "/api/v1/workspaces/active", - }); -}; + url: '/api/v1/workspaces/active', + }) +} /** * Activate Workspace * Activate a workspace by name. */ export const v1ActivateWorkspace = ( - options: OptionsLegacyParser, + options: OptionsLegacyParser ) => { return (options?.client ?? client).post< V1ActivateWorkspaceResponse, @@ -305,16 +305,16 @@ export const v1ActivateWorkspace = ( ThrowOnError >({ ...options, - url: "/api/v1/workspaces/active", - }); -}; + url: '/api/v1/workspaces/active', + }) +} /** * Delete Workspace * Delete a workspace by name. */ export const v1DeleteWorkspace = ( - options: OptionsLegacyParser, + options: OptionsLegacyParser ) => { return (options?.client ?? client).delete< V1DeleteWorkspaceResponse, @@ -322,16 +322,16 @@ export const v1DeleteWorkspace = ( ThrowOnError >({ ...options, - url: "/api/v1/workspaces/{workspace_name}", - }); -}; + url: '/api/v1/workspaces/{workspace_name}', + }) +} /** * List Archived Workspaces * List all archived workspaces. */ export const v1ListArchivedWorkspaces = ( - options?: OptionsLegacyParser, + options?: OptionsLegacyParser ) => { return (options?.client ?? client).get< V1ListArchivedWorkspacesResponse, @@ -339,16 +339,16 @@ export const v1ListArchivedWorkspaces = ( ThrowOnError >({ ...options, - url: "/api/v1/workspaces/archive", - }); -}; + url: '/api/v1/workspaces/archive', + }) +} /** * Recover Workspace * Recover an archived workspace by name. */ export const v1RecoverWorkspace = ( - options: OptionsLegacyParser, + options: OptionsLegacyParser ) => { return (options?.client ?? client).post< V1RecoverWorkspaceResponse, @@ -356,16 +356,16 @@ export const v1RecoverWorkspace = ( ThrowOnError >({ ...options, - url: "/api/v1/workspaces/archive/{workspace_name}/recover", - }); -}; + url: '/api/v1/workspaces/archive/{workspace_name}/recover', + }) +} /** * Hard Delete Workspace * Hard delete an archived workspace by name. */ export const v1HardDeleteWorkspace = ( - options: OptionsLegacyParser, + options: OptionsLegacyParser ) => { return (options?.client ?? client).delete< V1HardDeleteWorkspaceResponse, @@ -373,16 +373,16 @@ export const v1HardDeleteWorkspace = ( ThrowOnError >({ ...options, - url: "/api/v1/workspaces/archive/{workspace_name}", - }); -}; + url: '/api/v1/workspaces/archive/{workspace_name}', + }) +} /** * Get Workspace Alerts * Get alerts for a workspace. */ export const v1GetWorkspaceAlerts = ( - options: OptionsLegacyParser, + options: OptionsLegacyParser ) => { return (options?.client ?? client).get< V1GetWorkspaceAlertsResponse, @@ -390,16 +390,16 @@ export const v1GetWorkspaceAlerts = ( ThrowOnError >({ ...options, - url: "/api/v1/workspaces/{workspace_name}/alerts", - }); -}; + url: '/api/v1/workspaces/{workspace_name}/alerts', + }) +} /** * Get Workspace Messages * Get messages for a workspace. */ export const v1GetWorkspaceMessages = ( - options: OptionsLegacyParser, + options: OptionsLegacyParser ) => { return (options?.client ?? client).get< V1GetWorkspaceMessagesResponse, @@ -407,9 +407,9 @@ export const v1GetWorkspaceMessages = ( ThrowOnError >({ ...options, - url: "/api/v1/workspaces/{workspace_name}/messages", - }); -}; + url: '/api/v1/workspaces/{workspace_name}/messages', + }) +} /** * Get Workspace Custom Instructions @@ -421,7 +421,7 @@ export const v1GetWorkspaceCustomInstructions = < options: OptionsLegacyParser< V1GetWorkspaceCustomInstructionsData, ThrowOnError - >, + > ) => { return (options?.client ?? client).get< V1GetWorkspaceCustomInstructionsResponse, @@ -429,9 +429,9 @@ export const v1GetWorkspaceCustomInstructions = < ThrowOnError >({ ...options, - url: "/api/v1/workspaces/{workspace_name}/custom-instructions", - }); -}; + url: '/api/v1/workspaces/{workspace_name}/custom-instructions', + }) +} /** * Set Workspace Custom Instructions @@ -442,7 +442,7 @@ export const v1SetWorkspaceCustomInstructions = < options: OptionsLegacyParser< V1SetWorkspaceCustomInstructionsData, ThrowOnError - >, + > ) => { return (options?.client ?? client).put< V1SetWorkspaceCustomInstructionsResponse, @@ -450,9 +450,9 @@ export const v1SetWorkspaceCustomInstructions = < ThrowOnError >({ ...options, - url: "/api/v1/workspaces/{workspace_name}/custom-instructions", - }); -}; + url: '/api/v1/workspaces/{workspace_name}/custom-instructions', + }) +} /** * Delete Workspace Custom Instructions @@ -463,7 +463,7 @@ export const v1DeleteWorkspaceCustomInstructions = < options: OptionsLegacyParser< V1DeleteWorkspaceCustomInstructionsData, ThrowOnError - >, + > ) => { return (options?.client ?? client).delete< V1DeleteWorkspaceCustomInstructionsResponse, @@ -471,9 +471,9 @@ export const v1DeleteWorkspaceCustomInstructions = < ThrowOnError >({ ...options, - url: "/api/v1/workspaces/{workspace_name}/custom-instructions", - }); -}; + url: '/api/v1/workspaces/{workspace_name}/custom-instructions', + }) +} /** * Get Workspace Muxes @@ -483,7 +483,7 @@ export const v1DeleteWorkspaceCustomInstructions = < * has the highest priority. */ export const v1GetWorkspaceMuxes = ( - options: OptionsLegacyParser, + options: OptionsLegacyParser ) => { return (options?.client ?? client).get< V1GetWorkspaceMuxesResponse, @@ -491,16 +491,16 @@ export const v1GetWorkspaceMuxes = ( ThrowOnError >({ ...options, - url: "/api/v1/workspaces/{workspace_name}/muxes", - }); -}; + url: '/api/v1/workspaces/{workspace_name}/muxes', + }) +} /** * Set Workspace Muxes * Set the mux rules of a workspace. */ export const v1SetWorkspaceMuxes = ( - options: OptionsLegacyParser, + options: OptionsLegacyParser ) => { return (options?.client ?? client).put< V1SetWorkspaceMuxesResponse, @@ -508,16 +508,16 @@ export const v1SetWorkspaceMuxes = ( ThrowOnError >({ ...options, - url: "/api/v1/workspaces/{workspace_name}/muxes", - }); -}; + url: '/api/v1/workspaces/{workspace_name}/muxes', + }) +} /** * Stream Sse * Send alerts event */ export const v1StreamSse = ( - options?: OptionsLegacyParser, + options?: OptionsLegacyParser ) => { return (options?.client ?? client).get< V1StreamSseResponse, @@ -525,15 +525,15 @@ export const v1StreamSse = ( ThrowOnError >({ ...options, - url: "/api/v1/alerts_notification", - }); -}; + url: '/api/v1/alerts_notification', + }) +} /** * Version Check */ export const v1VersionCheck = ( - options?: OptionsLegacyParser, + options?: OptionsLegacyParser ) => { return (options?.client ?? client).get< V1VersionCheckResponse, @@ -541,16 +541,16 @@ export const v1VersionCheck = ( ThrowOnError >({ ...options, - url: "/api/v1/version", - }); -}; + url: '/api/v1/version', + }) +} /** * Get Workspace Token Usage * Get the token usage of a workspace. */ export const v1GetWorkspaceTokenUsage = ( - options: OptionsLegacyParser, + options: OptionsLegacyParser ) => { return (options?.client ?? client).get< V1GetWorkspaceTokenUsageResponse, @@ -558,6 +558,6 @@ export const v1GetWorkspaceTokenUsage = ( ThrowOnError >({ ...options, - url: "/api/v1/workspaces/{workspace_name}/token-usage", - }); -}; + url: '/api/v1/workspaces/{workspace_name}/token-usage', + }) +} diff --git a/src/api/generated/types.gen.ts b/src/api/generated/types.gen.ts index 388b8a8f..6334e21d 100644 --- a/src/api/generated/types.gen.ts +++ b/src/api/generated/types.gen.ts @@ -1,72 +1,72 @@ // This file is auto-generated by @hey-api/openapi-ts export type ActivateWorkspaceRequest = { - name: string; -}; + name: string +} export type ActiveWorkspace = { - name: string; - is_active: boolean; - last_updated: unknown; -}; + name: string + is_active: boolean + last_updated: unknown +} /** * Represents a request to add a provider endpoint. */ export type AddProviderEndpointRequest = { - id?: string | null; - name: string; - description?: string; - provider_type: ProviderType; - endpoint?: string; - auth_type?: ProviderAuthType; - api_key?: string | null; -}; + id?: string | null + name: string + description?: string + provider_type: ProviderType + endpoint?: string + auth_type?: ProviderAuthType + api_key?: string | null +} /** * Represents an alert. */ export type Alert = { - id: string; - prompt_id: string; - code_snippet: CodeSnippet | null; + id: string + prompt_id: string + code_snippet: CodeSnippet | null trigger_string: | string | { - [key: string]: unknown; + [key: string]: unknown } - | null; - trigger_type: string; - trigger_category: string | null; - timestamp: string; -}; + | null + trigger_type: string + trigger_category: string | null + timestamp: string +} /** * Represents an alert with it's respective conversation. */ export type AlertConversation = { - conversation: Conversation; - alert_id: string; - code_snippet: CodeSnippet | null; + conversation: Conversation + alert_id: string + code_snippet: CodeSnippet | null trigger_string: | string | { - [key: string]: unknown; + [key: string]: unknown } - | null; - trigger_type: string; - trigger_category: string | null; - timestamp: string; -}; + | null + trigger_type: string + trigger_category: string | null + timestamp: string +} /** * Represents a chat message. */ export type ChatMessage = { - message: string; - timestamp: string; - message_id: string; -}; + message: string + timestamp: string + message_id: string +} /** * Represents a code snippet with its programming language. @@ -76,54 +76,54 @@ export type ChatMessage = { * code: The actual code content */ export type CodeSnippet = { - code: string; - language: string | null; - filepath: string | null; - libraries?: Array; - file_extension?: string | null; -}; + code: string + language: string | null + filepath: string | null + libraries?: Array + file_extension?: string | null +} /** * Represents a request to configure auth material for a provider. */ export type ConfigureAuthMaterial = { - auth_type: ProviderAuthType; - api_key?: string | null; -}; + auth_type: ProviderAuthType + api_key?: string | null +} /** * Represents a conversation. */ export type Conversation = { - question_answers: Array; - provider: string | null; - type: QuestionType; - chat_id: string; - conversation_timestamp: string; - token_usage_agg: TokenUsageAggregate | null; - alerts?: Array; -}; + question_answers: Array + provider: string | null + type: QuestionType + chat_id: string + conversation_timestamp: string + token_usage_agg: TokenUsageAggregate | null + alerts?: Array +} export type CreateOrRenameWorkspaceRequest = { - name: string; - rename_to?: string | null; -}; + name: string + rename_to?: string | null +} export type CustomInstructions = { - prompt: string; -}; + prompt: string +} export type HTTPValidationError = { - detail?: Array; -}; + detail?: Array +} export type ListActiveWorkspacesResponse = { - workspaces: Array; -}; + workspaces: Array +} export type ListWorkspacesResponse = { - workspaces: Array; -}; + workspaces: Array +} /** * Represents a model supported by a provider. @@ -131,35 +131,35 @@ export type ListWorkspacesResponse = { * Note that these are auto-discovered by the provider. */ export type ModelByProvider = { - name: string; - provider_id: string; - provider_name: string; -}; + name: string + provider_id: string + provider_name: string +} /** * Represents the different types of matchers we support. */ export enum MuxMatcherType { - CATCH_ALL = "catch_all", + CATCH_ALL = 'catch_all', } /** * Represents a mux rule for a provider. */ export type MuxRule = { - provider_id: string; - model: string; - matcher_type: MuxMatcherType; - matcher?: string | null; -}; + provider_id: string + model: string + matcher_type: MuxMatcherType + matcher?: string | null +} /** * Represents the different types of auth we support for providers. */ export enum ProviderAuthType { - NONE = "none", - PASSTHROUGH = "passthrough", - API_KEY = "api_key", + NONE = 'none', + PASSTHROUGH = 'passthrough', + API_KEY = 'api_key', } /** @@ -168,38 +168,38 @@ export enum ProviderAuthType { * so we can use this for muxing messages. */ export type ProviderEndpoint = { - id?: string | null; - name: string; - description?: string; - provider_type: ProviderType; - endpoint?: string; - auth_type?: ProviderAuthType; -}; + id?: string | null + name: string + description?: string + provider_type: ProviderType + endpoint?: string + auth_type?: ProviderAuthType +} /** * Represents the different types of providers we support. */ export enum ProviderType { - OPENAI = "openai", - ANTHROPIC = "anthropic", - VLLM = "vllm", - OLLAMA = "ollama", - LM_STUDIO = "lm_studio", - LLAMACPP = "llamacpp", - OPENROUTER = "openrouter", + OPENAI = 'openai', + ANTHROPIC = 'anthropic', + VLLM = 'vllm', + OLLAMA = 'ollama', + LM_STUDIO = 'lm_studio', + LLAMACPP = 'llamacpp', + OPENROUTER = 'openrouter', } /** * Represents a question and answer pair. */ export type QuestionAnswer = { - question: ChatMessage; - answer: ChatMessage | null; -}; + question: ChatMessage + answer: ChatMessage | null +} export enum QuestionType { - CHAT = "chat", - FIM = "fim", + CHAT = 'chat', + FIM = 'fim', } /** @@ -207,11 +207,11 @@ export enum QuestionType { * The data is stored in the outputs table. */ export type TokenUsage = { - input_tokens?: number; - output_tokens?: number; - input_cost?: number; - output_cost?: number; -}; + input_tokens?: number + output_tokens?: number + input_cost?: number + output_cost?: number +} /** * Represents the tokens used. Includes the information of the tokens used by model. @@ -219,256 +219,256 @@ export type TokenUsage = { */ export type TokenUsageAggregate = { tokens_by_model: { - [key: string]: TokenUsageByModel; - }; - token_usage: TokenUsage; -}; + [key: string]: TokenUsageByModel + } + token_usage: TokenUsage +} /** * Represents the tokens used by a model. */ export type TokenUsageByModel = { - provider_type: ProviderType; - model: string; - token_usage: TokenUsage; -}; + provider_type: ProviderType + model: string + token_usage: TokenUsage +} export type ValidationError = { - loc: Array; - msg: string; - type: string; -}; + loc: Array + msg: string + type: string +} export type Workspace = { - name: string; - is_active: boolean; -}; + name: string + is_active: boolean +} -export type HealthCheckHealthGetResponse = unknown; +export type HealthCheckHealthGetResponse = unknown -export type HealthCheckHealthGetError = unknown; +export type HealthCheckHealthGetError = unknown export type V1ListProviderEndpointsData = { query?: { - name?: string | null; - }; -}; + name?: string | null + } +} -export type V1ListProviderEndpointsResponse = Array; +export type V1ListProviderEndpointsResponse = Array -export type V1ListProviderEndpointsError = HTTPValidationError; +export type V1ListProviderEndpointsError = HTTPValidationError export type V1AddProviderEndpointData = { - body: AddProviderEndpointRequest; -}; + body: AddProviderEndpointRequest +} -export type V1AddProviderEndpointResponse = ProviderEndpoint; +export type V1AddProviderEndpointResponse = ProviderEndpoint -export type V1AddProviderEndpointError = HTTPValidationError; +export type V1AddProviderEndpointError = HTTPValidationError -export type V1ListAllModelsForAllProvidersResponse = Array; +export type V1ListAllModelsForAllProvidersResponse = Array -export type V1ListAllModelsForAllProvidersError = unknown; +export type V1ListAllModelsForAllProvidersError = unknown export type V1ListModelsByProviderData = { path: { - provider_id: string; - }; -}; + provider_id: string + } +} -export type V1ListModelsByProviderResponse = Array; +export type V1ListModelsByProviderResponse = Array -export type V1ListModelsByProviderError = HTTPValidationError; +export type V1ListModelsByProviderError = HTTPValidationError export type V1GetProviderEndpointData = { path: { - provider_id: string; - }; -}; + provider_id: string + } +} -export type V1GetProviderEndpointResponse = ProviderEndpoint; +export type V1GetProviderEndpointResponse = ProviderEndpoint -export type V1GetProviderEndpointError = HTTPValidationError; +export type V1GetProviderEndpointError = HTTPValidationError export type V1UpdateProviderEndpointData = { - body: ProviderEndpoint; + body: ProviderEndpoint path: { - provider_id: string; - }; -}; + provider_id: string + } +} -export type V1UpdateProviderEndpointResponse = ProviderEndpoint; +export type V1UpdateProviderEndpointResponse = ProviderEndpoint -export type V1UpdateProviderEndpointError = HTTPValidationError; +export type V1UpdateProviderEndpointError = HTTPValidationError export type V1DeleteProviderEndpointData = { path: { - provider_id: string; - }; -}; + provider_id: string + } +} -export type V1DeleteProviderEndpointResponse = unknown; +export type V1DeleteProviderEndpointResponse = unknown -export type V1DeleteProviderEndpointError = HTTPValidationError; +export type V1DeleteProviderEndpointError = HTTPValidationError export type V1ConfigureAuthMaterialData = { - body: ConfigureAuthMaterial; + body: ConfigureAuthMaterial path: { - provider_id: string; - }; -}; + provider_id: string + } +} -export type V1ConfigureAuthMaterialResponse = void; +export type V1ConfigureAuthMaterialResponse = void -export type V1ConfigureAuthMaterialError = HTTPValidationError; +export type V1ConfigureAuthMaterialError = HTTPValidationError -export type V1ListWorkspacesResponse = ListWorkspacesResponse; +export type V1ListWorkspacesResponse = ListWorkspacesResponse -export type V1ListWorkspacesError = unknown; +export type V1ListWorkspacesError = unknown export type V1CreateWorkspaceData = { - body: CreateOrRenameWorkspaceRequest; -}; + body: CreateOrRenameWorkspaceRequest +} -export type V1CreateWorkspaceResponse = Workspace; +export type V1CreateWorkspaceResponse = Workspace -export type V1CreateWorkspaceError = HTTPValidationError; +export type V1CreateWorkspaceError = HTTPValidationError -export type V1ListActiveWorkspacesResponse = ListActiveWorkspacesResponse; +export type V1ListActiveWorkspacesResponse = ListActiveWorkspacesResponse -export type V1ListActiveWorkspacesError = unknown; +export type V1ListActiveWorkspacesError = unknown export type V1ActivateWorkspaceData = { - body: ActivateWorkspaceRequest; + body: ActivateWorkspaceRequest query?: { - status_code?: unknown; - }; -}; + status_code?: unknown + } +} -export type V1ActivateWorkspaceResponse = unknown; +export type V1ActivateWorkspaceResponse = unknown -export type V1ActivateWorkspaceError = HTTPValidationError; +export type V1ActivateWorkspaceError = HTTPValidationError export type V1DeleteWorkspaceData = { path: { - workspace_name: string; - }; -}; + workspace_name: string + } +} -export type V1DeleteWorkspaceResponse = unknown; +export type V1DeleteWorkspaceResponse = unknown -export type V1DeleteWorkspaceError = HTTPValidationError; +export type V1DeleteWorkspaceError = HTTPValidationError -export type V1ListArchivedWorkspacesResponse = ListWorkspacesResponse; +export type V1ListArchivedWorkspacesResponse = ListWorkspacesResponse -export type V1ListArchivedWorkspacesError = unknown; +export type V1ListArchivedWorkspacesError = unknown export type V1RecoverWorkspaceData = { path: { - workspace_name: string; - }; -}; + workspace_name: string + } +} -export type V1RecoverWorkspaceResponse = void; +export type V1RecoverWorkspaceResponse = void -export type V1RecoverWorkspaceError = HTTPValidationError; +export type V1RecoverWorkspaceError = HTTPValidationError export type V1HardDeleteWorkspaceData = { path: { - workspace_name: string; - }; -}; + workspace_name: string + } +} -export type V1HardDeleteWorkspaceResponse = unknown; +export type V1HardDeleteWorkspaceResponse = unknown -export type V1HardDeleteWorkspaceError = HTTPValidationError; +export type V1HardDeleteWorkspaceError = HTTPValidationError export type V1GetWorkspaceAlertsData = { path: { - workspace_name: string; - }; -}; + workspace_name: string + } +} -export type V1GetWorkspaceAlertsResponse = Array; +export type V1GetWorkspaceAlertsResponse = Array -export type V1GetWorkspaceAlertsError = HTTPValidationError; +export type V1GetWorkspaceAlertsError = HTTPValidationError export type V1GetWorkspaceMessagesData = { path: { - workspace_name: string; - }; -}; + workspace_name: string + } +} -export type V1GetWorkspaceMessagesResponse = Array; +export type V1GetWorkspaceMessagesResponse = Array -export type V1GetWorkspaceMessagesError = HTTPValidationError; +export type V1GetWorkspaceMessagesError = HTTPValidationError export type V1GetWorkspaceCustomInstructionsData = { path: { - workspace_name: string; - }; -}; + workspace_name: string + } +} -export type V1GetWorkspaceCustomInstructionsResponse = CustomInstructions; +export type V1GetWorkspaceCustomInstructionsResponse = CustomInstructions -export type V1GetWorkspaceCustomInstructionsError = HTTPValidationError; +export type V1GetWorkspaceCustomInstructionsError = HTTPValidationError export type V1SetWorkspaceCustomInstructionsData = { - body: CustomInstructions; + body: CustomInstructions path: { - workspace_name: string; - }; -}; + workspace_name: string + } +} -export type V1SetWorkspaceCustomInstructionsResponse = void; +export type V1SetWorkspaceCustomInstructionsResponse = void -export type V1SetWorkspaceCustomInstructionsError = HTTPValidationError; +export type V1SetWorkspaceCustomInstructionsError = HTTPValidationError export type V1DeleteWorkspaceCustomInstructionsData = { path: { - workspace_name: string; - }; -}; + workspace_name: string + } +} -export type V1DeleteWorkspaceCustomInstructionsResponse = void; +export type V1DeleteWorkspaceCustomInstructionsResponse = void -export type V1DeleteWorkspaceCustomInstructionsError = HTTPValidationError; +export type V1DeleteWorkspaceCustomInstructionsError = HTTPValidationError export type V1GetWorkspaceMuxesData = { path: { - workspace_name: string; - }; -}; + workspace_name: string + } +} -export type V1GetWorkspaceMuxesResponse = Array; +export type V1GetWorkspaceMuxesResponse = Array -export type V1GetWorkspaceMuxesError = HTTPValidationError; +export type V1GetWorkspaceMuxesError = HTTPValidationError export type V1SetWorkspaceMuxesData = { - body: Array; + body: Array path: { - workspace_name: string; - }; -}; + workspace_name: string + } +} -export type V1SetWorkspaceMuxesResponse = void; +export type V1SetWorkspaceMuxesResponse = void -export type V1SetWorkspaceMuxesError = HTTPValidationError; +export type V1SetWorkspaceMuxesError = HTTPValidationError -export type V1StreamSseResponse = unknown; +export type V1StreamSseResponse = unknown -export type V1StreamSseError = unknown; +export type V1StreamSseError = unknown -export type V1VersionCheckResponse = unknown; +export type V1VersionCheckResponse = unknown -export type V1VersionCheckError = unknown; +export type V1VersionCheckError = unknown export type V1GetWorkspaceTokenUsageData = { path: { - workspace_name: string; - }; -}; + workspace_name: string + } +} -export type V1GetWorkspaceTokenUsageResponse = TokenUsageAggregate; +export type V1GetWorkspaceTokenUsageResponse = TokenUsageAggregate -export type V1GetWorkspaceTokenUsageError = HTTPValidationError; +export type V1GetWorkspaceTokenUsageError = HTTPValidationError diff --git a/src/components/BreadcrumbHome.tsx b/src/components/BreadcrumbHome.tsx index 86d69b21..914fcd47 100644 --- a/src/components/BreadcrumbHome.tsx +++ b/src/components/BreadcrumbHome.tsx @@ -1,5 +1,5 @@ -import { Breadcrumb } from "@stacklok/ui-kit"; +import { Breadcrumb } from '@stacklok/ui-kit' export function BreadcrumbHome() { - return Dashboard; + return Dashboard } diff --git a/src/components/CopyToClipboard.tsx b/src/components/CopyToClipboard.tsx index 7a76ae35..53d3510a 100644 --- a/src/components/CopyToClipboard.tsx +++ b/src/components/CopyToClipboard.tsx @@ -1,34 +1,34 @@ -import { Button, Tooltip, TooltipTrigger } from "@stacklok/ui-kit"; -import { ClipboardCheck, Copy02 } from "@untitled-ui/icons-react"; -import { useEffect, useState } from "react"; -import { twMerge } from "tailwind-merge"; +import { Button, Tooltip, TooltipTrigger } from '@stacklok/ui-kit' +import { ClipboardCheck, Copy02 } from '@untitled-ui/icons-react' +import { useEffect, useState } from 'react' +import { twMerge } from 'tailwind-merge' export function CopyToClipboard({ text, className, }: { - className?: string; - text: string; + className?: string + text: string }) { - const [copied, setCopied] = useState(false); + const [copied, setCopied] = useState(false) useEffect(() => { - const id = setTimeout(() => setCopied(false), 2000); - return () => clearTimeout(id); - }, [copied]); + const id = setTimeout(() => setCopied(false), 2000) + return () => clearTimeout(id) + }, [copied]) return ( - ); + ) } diff --git a/src/components/HoverPopover.tsx b/src/components/HoverPopover.tsx index 18255ae9..580f9568 100644 --- a/src/components/HoverPopover.tsx +++ b/src/components/HoverPopover.tsx @@ -4,24 +4,24 @@ import { MenuTrigger, OptionsSchema, Popover, -} from "@stacklok/ui-kit"; -import { OverlayTriggerStateContext } from "react-aria-components"; -import { ReactNode, useContext } from "react"; -import { ChevronDown, ChevronUp } from "@untitled-ui/icons-react"; +} from '@stacklok/ui-kit' +import { OverlayTriggerStateContext } from 'react-aria-components' +import { ReactNode, useContext } from 'react' +import { ChevronDown, ChevronUp } from '@untitled-ui/icons-react' function PopoverIcon() { - const { isOpen = false } = useContext(OverlayTriggerStateContext) ?? {}; + const { isOpen = false } = useContext(OverlayTriggerStateContext) ?? {} - return isOpen ? : ; + return isOpen ? : } export function DropdownMenu({ items, title, }: { - title: ReactNode; - items: OptionsSchema<"menu">[]; - className?: string; + title: ReactNode + items: OptionsSchema<'menu'>[] + className?: string }) { return ( @@ -33,5 +33,5 @@ export function DropdownMenu({ - ); + ) } diff --git a/src/components/Markdown.tsx b/src/components/Markdown.tsx index 5e4f348c..9ff0537f 100644 --- a/src/components/Markdown.tsx +++ b/src/components/Markdown.tsx @@ -1,114 +1,118 @@ -import remarkGfm from "remark-gfm"; -import ReactMarkdown from "react-markdown"; -import { Prism as SyntaxHighlighter } from "react-syntax-highlighter"; -import { oneDark } from "react-syntax-highlighter/dist/esm/styles/prism"; -import { CopyToClipboard } from "./CopyToClipboard"; -import hljs from "highlight.js"; +import remarkGfm from 'remark-gfm' +import ReactMarkdown from 'react-markdown' +import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter' +import { oneDark } from 'react-syntax-highlighter/dist/esm/styles/prism' +import { CopyToClipboard } from './CopyToClipboard' +import hljs from 'highlight.js' const LANGUAGES_SUBSET_DETECTION = [ - "c", - "cpp", - "csharp", - "css", - "elixir", - "go", - "groovy", - "haskell", - "html", - "java", - "javascript", - "json", - "kotlin", - "markdown", - "php", - "python", - "ruby", - "rust", - "scala", - "sql", - "typescript", - "yaml", -]; + 'c', + 'cpp', + 'csharp', + 'css', + 'elixir', + 'go', + 'groovy', + 'haskell', + 'html', + 'java', + 'javascript', + 'json', + 'kotlin', + 'markdown', + 'php', + 'python', + 'ruby', + 'rust', + 'scala', + 'sql', + 'typescript', + 'yaml', +] interface Props { - children: string; - isInverted?: boolean; + children: string + isInverted?: boolean } const customStyle = { ...oneDark, 'code[class*="language-"]': { ...oneDark['code[class*="language-"]'], - background: "none", + background: 'none', }, 'pre[class*="language-"]': { ...oneDark['pre[class*="language-"]'], - background: "#1a1b26", - padding: "1.5rem", - borderRadius: "0.5rem", - width: "100%", - position: "relative", - boxSizing: "border-box", + background: '#1a1b26', + padding: '1.5rem', + borderRadius: '0.5rem', + width: '100%', + position: 'relative', + boxSizing: 'border-box', }, -}; +} export function Markdown({ children, isInverted = false }: Props) { return ( - {String(children).replace(/\n$/, "")} + {String(children).replace(/\n$/, '')} {language && ( )} - ); + ) }, pre({ children }) { - return children; + return children }, a({ children, ...props }) { return ( {children} - ); + ) }, img({ src, alt }) { - return {alt}; + return {alt} }, }} remarkPlugins={[remarkGfm]} > {children} - ); + ) } diff --git a/src/components/__tests__/CopyToClipboard.test.tsx b/src/components/__tests__/CopyToClipboard.test.tsx index 2d948ef0..2a853427 100644 --- a/src/components/__tests__/CopyToClipboard.test.tsx +++ b/src/components/__tests__/CopyToClipboard.test.tsx @@ -1,29 +1,29 @@ -import { render, screen } from "@testing-library/react"; -import userEvent from "@testing-library/user-event"; -import { describe, it, vi, expect } from "vitest"; -import { CopyToClipboard } from "../CopyToClipboard"; +import { render, screen } from '@testing-library/react' +import userEvent from '@testing-library/user-event' +import { describe, it, vi, expect } from 'vitest' +import { CopyToClipboard } from '../CopyToClipboard' -describe("CopyToClipboard", () => { - it("renders the button with the correct icon", () => { - render(); +describe('CopyToClipboard', () => { + it('renders the button with the correct icon', () => { + render() - expect(screen.getByRole("button")).toBeVisible(); - expect(screen.getByTestId("icon-clipboard-copy")).toBeVisible(); - }); + expect(screen.getByRole('button')).toBeVisible() + expect(screen.getByTestId('icon-clipboard-copy')).toBeVisible() + }) - it("copies text to clipboard when clicked", async () => { - const mockedText = vi.fn(); + it('copies text to clipboard when clicked', async () => { + const mockedText = vi.fn() Object.assign(navigator, { clipboard: { writeText: mockedText, }, - }); + }) - render(); + render() - await userEvent.click(screen.getByRole("button")); + await userEvent.click(screen.getByRole('button')) - expect(mockedText).toHaveBeenCalledWith("Fake text"); - expect(screen.getByTestId("icon-clipboard-check")).toBeVisible(); - }); -}); + expect(mockedText).toHaveBeenCalledWith('Fake text') + expect(screen.getByTestId('icon-clipboard-check')).toBeVisible() + }) +}) diff --git a/src/components/__tests__/ErrorBoundary.test.tsx b/src/components/__tests__/ErrorBoundary.test.tsx index ae78f176..2b513443 100644 --- a/src/components/__tests__/ErrorBoundary.test.tsx +++ b/src/components/__tests__/ErrorBoundary.test.tsx @@ -1,22 +1,22 @@ -import { screen } from "@testing-library/react"; -import { describe, expect, it, vi } from "vitest"; -import ErrorBoundary from "../ErrorBoundary"; -import { Error } from "../Error"; -import { render } from "@/lib/test-utils"; +import { screen } from '@testing-library/react' +import { describe, expect, it, vi } from 'vitest' +import ErrorBoundary from '../ErrorBoundary' +import { Error } from '../Error' +import { render } from '@/lib/test-utils' const ErrorComponent = () => { - throw Error(); -}; + throw Error() +} -describe("ErrorBoundary", () => { - it("renders fallback when a child throws an error", () => { - vi.spyOn(console, "error").mockImplementation(() => {}); +describe('ErrorBoundary', () => { + it('renders fallback when a child throws an error', () => { + vi.spyOn(console, 'error').mockImplementation(() => {}) render( }> - , - ); + + ) - expect(screen.getByText(/an error occurred/i)).toBeVisible(); - }); -}); + expect(screen.getByText(/an error occurred/i)).toBeVisible() + }) +}) diff --git a/src/components/empty-state.tsx b/src/components/empty-state.tsx index 10fab666..ed545851 100644 --- a/src/components/empty-state.tsx +++ b/src/components/empty-state.tsx @@ -1,21 +1,21 @@ -import { Heading } from "@stacklok/ui-kit"; -import { JSX, ReactNode, SVGProps } from "react"; -import { tv } from "tailwind-variants"; +import { Heading } from '@stacklok/ui-kit' +import { JSX, ReactNode, SVGProps } from 'react' +import { tv } from 'tailwind-variants' const actionsStyle = tv({ - base: "mx-auto mt-8", + base: 'mx-auto mt-8', variants: { actions: { - 1: "", - 2: "grid grid-cols-2 gap-2", + 1: '', + 2: 'grid grid-cols-2 gap-2', }, }, -}); +}) function Actions({ actions }: { actions: [ReactNode, ReactNode?] }) { return (
{actions}
- ); + ) } export function EmptyState({ @@ -24,19 +24,22 @@ export function EmptyState({ illustration: Illustration, title, }: { - illustration: (props: SVGProps) => JSX.Element; - title: string; - body: string; - actions: [ReactNode, ReactNode?] | null; + illustration: (props: SVGProps) => JSX.Element + title: string + body: string + actions: [ReactNode, ReactNode?] | null }) { return ( -
- - +
+ + {title}

{body}

{actions ? : null}
- ); + ) } diff --git a/src/components/heading.tsx b/src/components/heading.tsx index da655a68..8c9f16eb 100644 --- a/src/components/heading.tsx +++ b/src/components/heading.tsx @@ -1,22 +1,22 @@ -import { Heading as UIKitHeading } from "@stacklok/ui-kit"; -import React, { ComponentProps } from "react"; +import { Heading as UIKitHeading } from '@stacklok/ui-kit' +import React, { ComponentProps } from 'react' export function PageHeading({ title, children, level, }: { - level: ComponentProps["level"]; - title: React.ReactNode; - children?: React.ReactNode; + level: ComponentProps['level'] + title: React.ReactNode + children?: React.ReactNode }) { return ( {title} {children} - ); + ) } diff --git a/src/components/icons/Continue.tsx b/src/components/icons/Continue.tsx index cb9cd6fa..52799a35 100644 --- a/src/components/icons/Continue.tsx +++ b/src/components/icons/Continue.tsx @@ -1,4 +1,4 @@ -import type { SVGProps } from "react"; +import type { SVGProps } from 'react' const SvgContinue = (props: SVGProps) => ( ) => ( d="m16.114 2.483-1.081 1.815 2.733 4.58c.02.035.032.078.032.116a.24.24 0 0 1-.032.116l-2.733 4.584 1.081 1.815L20 8.994 16.114 2.48zm-1.5 1.58 1.081-1.815h-2.162l-1.081 1.815h2.166zm-2.166.47 2.525 4.23h2.162l-2.521-4.23zm2.166 8.93 2.521-4.233h-2.162l-2.525 4.232zm-2.166.47 1.08 1.808h2.163l-1.081-1.807h-2.166zm-7.33 2.256A.25.25 0 0 1 5 16.158a.23.23 0 0 1-.088-.085l-2.737-4.584H.012L3.898 18h7.768l-1.082-1.811H5.12m5.885-.236 1.082 1.812 1.08-1.816-1.08-1.815-1.082 1.815zm.663-2.05H6.623l-1.081 1.815h5.042zM6.2 13.67 3.674 9.438l-1.08 1.815 2.525 4.233zM.008 11.018H2.17l1.082-1.815H1.093zM4.899 1.93a.23.23 0 0 1 .088-.085c.036-.02.08-.03.12-.03h5.47L11.657 0H3.887L0 6.515h2.162l2.73-4.58zM3.252 8.797 2.17 6.982H.008l1.081 1.815zm1.859-6.28-2.522 4.23L3.67 8.562l2.522-4.229zm5.47-.235H5.53l1.08 1.815h5.052zm1.504 1.58 1.077-1.811L12.085.236l-1.082 1.81z" /> -); -export default SvgContinue; +) +export default SvgContinue diff --git a/src/components/icons/Copilot.tsx b/src/components/icons/Copilot.tsx index 9479811e..910dbfae 100644 --- a/src/components/icons/Copilot.tsx +++ b/src/components/icons/Copilot.tsx @@ -1,4 +1,4 @@ -import type { SVGProps } from "react"; +import type { SVGProps } from 'react' const SvgCopilot = (props: SVGProps) => ( ) => ( d="M18.52 5.871c1.19 1.256 1.69 2.97 1.9 5.373.56 0 1.08.124 1.432.604l.658.89c.189.257.29.563.29.882v2.42c0 .313-.155.618-.408.803-2.976 2.179-6.65 3.932-10.392 3.932-4.14 0-8.285-2.385-10.392-3.932a1.01 1.01 0 0 1-.408-.802v-2.42c0-.32.101-.627.29-.884l.657-.89c.352-.477.875-.603 1.433-.603.21-2.403.709-4.117 1.9-5.373 2.245-2.379 5.218-2.64 6.482-2.646H12c1.242 0 4.253.243 6.52 2.646m-6.518 3.997c-.257 0-.553.015-.867.046-.11.413-.274.786-.513 1.024-.945.945-2.085 1.09-2.695 1.09-.574 0-1.175-.12-1.666-.429-.464.153-.91.373-.94.92-.049 1.037-.053 2.072-.057 3.108q-.003.78-.013 1.56a.79.79 0 0 0 .46.707c2.234 1.018 4.346 1.531 6.29 1.531 1.942 0 4.054-.513 6.287-1.53a.79.79 0 0 0 .46-.708 72 72 0 0 0-.07-4.667h.002c-.029-.551-.477-.768-.942-.92-.491.307-1.091.428-1.665.428-.61 0-1.748-.145-2.694-1.09-.24-.238-.403-.61-.514-1.024a9 9 0 0 0-.864-.046m-2.274 3.707c.485 0 .879.393.879.878v1.619a.878.878 0 0 1-1.757 0v-1.619c0-.485.393-.878.878-.878m4.5 0c.485 0 .879.393.879.878v1.619a.878.878 0 0 1-1.757 0v-1.619c0-.485.393-.878.878-.878m-6.156-7.96c-.945.094-1.742.405-2.147.837-.877.958-.688 3.388-.189 3.901.365.365 1.053.608 1.796.608.567 0 1.647-.122 2.538-1.026.391-.378.634-1.323.607-2.282-.027-.77-.243-1.404-.567-1.674-.35-.31-1.147-.445-2.038-.364m5.818.364c-.324.27-.54.905-.567 1.674-.027.959.216 1.904.608 2.282.89.904 1.97 1.025 2.538 1.025.742 0 1.43-.242 1.795-.607.5-.513.689-2.943-.189-3.901-.405-.432-1.201-.743-2.146-.837-.891-.081-1.688.054-2.039.364M12 7.95c-.216 0-.472.014-.756.04.027.15.04.311.054.487 0 .121 0 .243-.013.378.27-.027.5-.027.715-.027.216 0 .446 0 .716.027-.014-.135-.014-.257-.014-.378.014-.176.027-.338.054-.486A8 8 0 0 0 12 7.95" /> -); -export default SvgCopilot; +) +export default SvgCopilot diff --git a/src/components/icons/Discord.tsx b/src/components/icons/Discord.tsx index d040b49f..55f459d6 100644 --- a/src/components/icons/Discord.tsx +++ b/src/components/icons/Discord.tsx @@ -1,4 +1,4 @@ -import type { SVGProps } from "react"; +import type { SVGProps } from 'react' const SvgDiscord = (props: SVGProps) => ( ) => ( d="M18.59 5.89c-1.23-.57-2.54-.99-3.92-1.23-.17.3-.37.71-.5 1.04-1.46-.22-2.91-.22-4.34 0-.14-.33-.34-.74-.51-1.04-1.38.24-2.69.66-3.92 1.23-2.48 3.74-3.15 7.39-2.82 10.98 1.65 1.23 3.24 1.97 4.81 2.46.39-.53.73-1.1 1.03-1.69-.57-.21-1.11-.48-1.62-.79.14-.1.27-.21.4-.31 3.13 1.46 6.52 1.46 9.61 0 .13.11.26.21.4.31-.51.31-1.06.57-1.62.79.3.59.64 1.16 1.03 1.69 1.57-.49 3.17-1.23 4.81-2.46.39-4.17-.67-7.78-2.82-10.98zm-9.75 8.78c-.94 0-1.71-.87-1.71-1.94s.75-1.94 1.71-1.94 1.72.87 1.71 1.94c0 1.06-.75 1.94-1.71 1.94m6.31 0c-.94 0-1.71-.87-1.71-1.94s.75-1.94 1.71-1.94 1.72.87 1.71 1.94c0 1.06-.75 1.94-1.71 1.94" /> -); -export default SvgDiscord; +) +export default SvgDiscord diff --git a/src/components/icons/Github.tsx b/src/components/icons/Github.tsx index da690ebd..09b924a2 100644 --- a/src/components/icons/Github.tsx +++ b/src/components/icons/Github.tsx @@ -1,4 +1,4 @@ -import type { SVGProps } from "react"; +import type { SVGProps } from 'react' const SvgGithub = (props: SVGProps) => ( ) => ( clipRule="evenodd" /> -); -export default SvgGithub; +) +export default SvgGithub diff --git a/src/components/icons/Youtube.tsx b/src/components/icons/Youtube.tsx index b474b895..d92299f3 100644 --- a/src/components/icons/Youtube.tsx +++ b/src/components/icons/Youtube.tsx @@ -1,4 +1,4 @@ -import type { SVGProps } from "react"; +import type { SVGProps } from 'react' const SvgYoutube = (props: SVGProps) => ( ) => ( clipRule="evenodd" /> -); -export default SvgYoutube; +) +export default SvgYoutube diff --git a/src/components/icons/index.ts b/src/components/icons/index.ts index b1107dfe..6c0e0fb2 100644 --- a/src/components/icons/index.ts +++ b/src/components/icons/index.ts @@ -1,5 +1,5 @@ -export { default as Continue } from "./Continue"; -export { default as Copilot } from "./Copilot"; -export { default as Discord } from "./Discord"; -export { default as Github } from "./Github"; -export { default as Youtube } from "./Youtube"; +export { default as Continue } from './Continue' +export { default as Copilot } from './Copilot' +export { default as Discord } from './Discord' +export { default as Github } from './Github' +export { default as Youtube } from './Youtube' diff --git a/src/components/page-container.tsx b/src/components/page-container.tsx index 18821485..89a7567f 100644 --- a/src/components/page-container.tsx +++ b/src/components/page-container.tsx @@ -1,9 +1,9 @@ -import { ReactNode } from "react"; +import { ReactNode } from 'react' export function PageContainer({ children }: { children: ReactNode }) { return ( -
+
{children}
- ); + ) } diff --git a/src/components/react-query-provider.tsx b/src/components/react-query-provider.tsx index da61b49d..567ce905 100644 --- a/src/components/react-query-provider.tsx +++ b/src/components/react-query-provider.tsx @@ -1,12 +1,12 @@ -import { V1ListActiveWorkspacesResponse } from "@/api/generated"; -import { v1ListActiveWorkspacesQueryKey } from "@/api/generated/@tanstack/react-query.gen"; -import { getQueryCacheConfig } from "@/lib/react-query-utils"; +import { V1ListActiveWorkspacesResponse } from '@/api/generated' +import { v1ListActiveWorkspacesQueryKey } from '@/api/generated/@tanstack/react-query.gen' +import { getQueryCacheConfig } from '@/lib/react-query-utils' import { QueryCacheNotifyEvent, QueryClient, QueryClientProvider as VendorQueryClientProvider, -} from "@tanstack/react-query"; -import { ReactNode, useState, useEffect } from "react"; +} from '@tanstack/react-query' +import { ReactNode, useState, useEffect } from 'react' /** * Responsible for determining whether a queryKey attached to a queryCache event @@ -16,7 +16,7 @@ function isActiveWorkspacesQueryKey(queryKey: unknown): boolean { return ( Array.isArray(queryKey) && queryKey[0]._id === v1ListActiveWorkspacesQueryKey()[0]?._id - ); + ) } /** @@ -24,69 +24,69 @@ function isActiveWorkspacesQueryKey(queryKey: unknown): boolean { * nested payload attached to a queryCache event. */ function getWorkspaceName(event: QueryCacheNotifyEvent): string | null { - if ("action" in event === false || "data" in event.action === false) - return null; + if ('action' in event === false || 'data' in event.action === false) + return null return ( (event.action.data as V1ListActiveWorkspacesResponse | undefined | null) ?.workspaces[0]?.name ?? null - ); + ) } export function QueryClientProvider({ children }: { children: ReactNode }) { const [activeWorkspaceName, setActiveWorkspaceName] = useState( - null, - ); + null + ) const [queryClient] = useState( () => new QueryClient({ defaultOptions: { queries: { - ...getQueryCacheConfig("no-cache"), + ...getQueryCacheConfig('no-cache'), refetchOnMount: false, // additional instances of a query shouldn't trigger background refetch refetchOnReconnect: true, refetchOnWindowFocus: true, }, }, - }), - ); + }) + ) useEffect(() => { - const queryCache = queryClient.getQueryCache(); + const queryCache = queryClient.getQueryCache() const unsubscribe = queryCache.subscribe((event) => { if ( - event.type === "updated" && - event.action.type === "success" && + event.type === 'updated' && + event.action.type === 'success' && isActiveWorkspacesQueryKey(event.query.options.queryKey) ) { - const newWorkspaceName: string | null = getWorkspaceName(event); + const newWorkspaceName: string | null = getWorkspaceName(event) if ( newWorkspaceName === activeWorkspaceName || newWorkspaceName === null ) - return; + return - setActiveWorkspaceName(newWorkspaceName); + setActiveWorkspaceName(newWorkspaceName) // eslint-disable-next-line no-restricted-syntax void queryClient.invalidateQueries({ - refetchType: "all", + refetchType: 'all', // Avoid a continuous loop predicate(query) { - return !isActiveWorkspacesQueryKey(query.queryKey); + return !isActiveWorkspacesQueryKey(query.queryKey) }, - }); + }) } - }); + }) return () => { - return unsubscribe(); - }; - }, [activeWorkspaceName, queryClient]); + return unsubscribe() + } + }, [activeWorkspaceName, queryClient]) return ( {children} - ); + ) } diff --git a/src/components/ui/chat/chat-bubble.tsx b/src/components/ui/chat/chat-bubble.tsx index 597da570..52f3b364 100644 --- a/src/components/ui/chat/chat-bubble.tsx +++ b/src/components/ui/chat/chat-bubble.tsx @@ -1,31 +1,31 @@ -import * as React from "react"; -import MessageLoading from "./message-loading"; -import { Avatar, Button } from "@stacklok/ui-kit"; -import { tv } from "tailwind-variants"; -import { twMerge } from "tailwind-merge"; +import * as React from 'react' +import MessageLoading from './message-loading' +import { Avatar, Button } from '@stacklok/ui-kit' +import { tv } from 'tailwind-variants' +import { twMerge } from 'tailwind-merge' // ChatBubble const chatBubbleVariant = tv({ - base: "flex gap-2 max-w-[60%] items-end relative group text-sm", + base: 'group relative flex max-w-[60%] items-end gap-2 text-sm', variants: { variant: { - received: "self-start", - sent: "self-end flex-row-reverse", + received: 'self-start', + sent: 'flex-row-reverse self-end', }, layout: { - default: "", - ai: "max-w-full w-full items-center", + default: '', + ai: 'w-full max-w-full items-center', }, }, defaultVariants: { - variant: "received", - layout: "default", + variant: 'received', + layout: 'default', }, -}); +}) interface ChatBubbleProps extends React.HTMLAttributes { - variant: "received" | "sent"; - layout?: "default" | "ai"; + variant: 'received' | 'sent' + layout?: 'default' | 'ai' } const ChatBubble = React.forwardRef( @@ -33,29 +33,29 @@ const ChatBubble = React.forwardRef(
{React.Children.map(children, (child) => - React.isValidElement(child) && typeof child.type !== "string" + React.isValidElement(child) && typeof child.type !== 'string' ? React.cloneElement(child, { variant, layout, } as React.ComponentProps) - : child, + : child )}
- ), -); -ChatBubble.displayName = "ChatBubble"; + ) +) +ChatBubble.displayName = 'ChatBubble' // ChatBubbleAvatar interface ChatBubbleAvatarProps extends React.ComponentProps { - src?: string; - fallback?: string; - className?: string; + src?: string + fallback?: string + className?: string } const ChatBubbleAvatar: React.FC = ({ @@ -67,34 +67,34 @@ const ChatBubbleAvatar: React.FC = ({ -); +) // ChatBubbleMessage const chatBubbleMessageVariants = tv({ - base: "p-4 bg-gray-100 text-primary", + base: 'bg-gray-100 p-4 text-primary', variants: { variant: { - received: "rounded-r-lg rounded-tl-lg", - sent: "rounded-l-lg rounded-tr-lg", + received: 'rounded-r-lg rounded-tl-lg', + sent: 'rounded-l-lg rounded-tr-lg', }, layout: { - default: "", - ai: "border-t w-full rounded-none bg-transparent", + default: '', + ai: 'w-full rounded-none border-t bg-transparent', }, }, defaultVariants: { - variant: "received", - layout: "default", + variant: 'received', + layout: 'default', }, -}); +}) interface ChatBubbleMessageProps extends React.HTMLAttributes { - variant: "received" | "sent"; - layout?: "default" | "ai"; - isLoading?: boolean; + variant: 'received' | 'sent' + layout?: 'default' | 'ai' + isLoading?: boolean } const ChatBubbleMessage = React.forwardRef< @@ -103,12 +103,12 @@ const ChatBubbleMessage = React.forwardRef< >( ( { className, variant, layout, isLoading = false, children, ...props }, - ref, + ref ) => (
- ), -); -ChatBubbleMessage.displayName = "ChatBubbleMessage"; + ) +) +ChatBubbleMessage.displayName = 'ChatBubbleMessage' // ChatBubbleTimestamp interface ChatBubbleTimestampProps extends React.HTMLAttributes { - timestamp: string; + timestamp: string } const ChatBubbleTimestamp: React.FC = ({ @@ -136,21 +136,21 @@ const ChatBubbleTimestamp: React.FC = ({ className, ...props }) => ( -
+
{timestamp}
-); +) // ChatBubbleAction type ChatBubbleActionProps = React.ComponentProps & { - icon: React.ReactNode; -}; + icon: React.ReactNode +} const ChatBubbleAction: React.FC = ({ icon, onPress, className, - variant = "tertiary", + variant = 'tertiary', isIcon = true, ...props }) => ( @@ -163,12 +163,12 @@ const ChatBubbleAction: React.FC = ({ > {icon} -); +) interface ChatBubbleActionWrapperProps extends React.HTMLAttributes { - variant?: "sent" | "received"; - className?: string; + variant?: 'sent' | 'received' + className?: string } const ChatBubbleActionWrapper = React.forwardRef< @@ -178,18 +178,19 @@ const ChatBubbleActionWrapper = React.forwardRef<
{children}
-)); -ChatBubbleActionWrapper.displayName = "ChatBubbleActionWrapper"; +)) +ChatBubbleActionWrapper.displayName = 'ChatBubbleActionWrapper' export { ChatBubble, @@ -200,4 +201,4 @@ export { chatBubbleMessageVariants, ChatBubbleAction, ChatBubbleActionWrapper, -}; +} diff --git a/src/components/ui/chat/chat-message-list.tsx b/src/components/ui/chat/chat-message-list.tsx index a469796c..33dc75d9 100644 --- a/src/components/ui/chat/chat-message-list.tsx +++ b/src/components/ui/chat/chat-message-list.tsx @@ -1,23 +1,23 @@ -import * as React from "react"; -import { twMerge } from "tailwind-merge"; +import * as React from 'react' +import { twMerge } from 'tailwind-merge' -type ChatMessageListProps = React.HTMLAttributes; +type ChatMessageListProps = React.HTMLAttributes const ChatMessageList = React.forwardRef( ({ className, children, ...props }, ref) => (
{children}
- ), -); + ) +) -ChatMessageList.displayName = "ChatMessageList"; +ChatMessageList.displayName = 'ChatMessageList' -export { ChatMessageList }; +export { ChatMessageList } diff --git a/src/components/ui/chat/message-loading.tsx b/src/components/ui/chat/message-loading.tsx index 6add0ee7..51755312 100644 --- a/src/components/ui/chat/message-loading.tsx +++ b/src/components/ui/chat/message-loading.tsx @@ -41,5 +41,5 @@ export default function MessageLoading() { /> - ); + ) } diff --git a/src/constants/empty-state-strings.ts b/src/constants/empty-state-strings.ts index dc329f51..58b54a56 100644 --- a/src/constants/empty-state-strings.ts +++ b/src/constants/empty-state-strings.ts @@ -1,28 +1,28 @@ export const emptyStateStrings = { title: { - loading: "Loading...", - getStarted: "Get started with CodeGate", - noMessages: "No messages found", + loading: 'Loading...', + getStarted: 'Get started with CodeGate', + noMessages: 'No messages found', noMessagesWorkspace: "This workspace hasn't recorded any messages yet.", - anErrorOccurred: "An error occurred", - noLeakedSecretsDetected: "No leaked secrets detected", - noMaliciousPackagesDetected: "No malicious packages detected", + anErrorOccurred: 'An error occurred', + noLeakedSecretsDetected: 'No leaked secrets detected', + noMaliciousPackagesDetected: 'No malicious packages detected', noSearchResultsFor: (x: string | undefined): string => - !x ? "No search results" : `No search results for "${x}"`, + !x ? 'No search results' : `No search results for "${x}"`, }, body: { - loading: "Checking for the latest messages.", + loading: 'Checking for the latest messages.', errorDesc: - "Please try refreshing the page. If this issue persists, please let us know on Discord, or open a a new Github Issue", - getStartedDesc: "Learn how to get started with CodeGate in your IDE.", - tryChangingSearch: "Try changing your search query or clearing the search.", + 'Please try refreshing the page. If this issue persists, please let us know on Discord, or open a a new Github Issue', + getStartedDesc: 'Learn how to get started with CodeGate in your IDE.', + tryChangingSearch: 'Try changing your search query or clearing the search.', messagesWillShowUpWhenWorkspace: - "Messages will show up here when they are detected for this workspace.", + 'Messages will show up here when they are detected for this workspace.', messagesDesc: - "Messages are issues that CodeGate has detected and mitigated in your interactions with the LLM.", + 'Messages are issues that CodeGate has detected and mitigated in your interactions with the LLM.', secretsDesc: - "CodeGate helps you protect sensitive information from being accidentally exposed to AI models and third-party AI provider systems by redacting detected secrets from your prompts using encryption.", + 'CodeGate helps you protect sensitive information from being accidentally exposed to AI models and third-party AI provider systems by redacting detected secrets from your prompts using encryption.', maliciousDesc: "CodeGate's dependency risk insight helps protect your codebase from malicious or vulnerable dependencies. It identifies potentially risky packages and suggests fixed versions or alternative packages to consider.", }, -} as const; +} as const diff --git a/src/context/confirm-context.tsx b/src/context/confirm-context.tsx index ba7fa19c..8509ce62 100644 --- a/src/context/confirm-context.tsx +++ b/src/context/confirm-context.tsx @@ -1,4 +1,4 @@ -"use client"; +'use client' import { Button, @@ -9,49 +9,49 @@ import { DialogModal, DialogModalOverlay, DialogTitle, -} from "@stacklok/ui-kit"; -import type { ReactNode } from "react"; -import { createContext, useState } from "react"; +} from '@stacklok/ui-kit' +import type { ReactNode } from 'react' +import { createContext, useState } from 'react' type Buttons = { - yes: ReactNode; - no: ReactNode; -}; + yes: ReactNode + no: ReactNode +} type Config = { - buttons: Buttons; - title?: ReactNode; - isDestructive?: boolean; -}; + buttons: Buttons + title?: ReactNode + isDestructive?: boolean +} type Question = { - message: ReactNode; - config: Config; - resolve: (value: boolean) => void; -}; + message: ReactNode + config: Config + resolve: (value: boolean) => void +} type ConfirmContextType = { - confirm: (message: ReactNode, config: Config) => Promise; -}; + confirm: (message: ReactNode, config: Config) => Promise +} -export const ConfirmContext = createContext(null); +export const ConfirmContext = createContext(null) export function ConfirmProvider({ children }: { children: ReactNode }) { - const [activeQuestion, setActiveQuestion] = useState(null); - const [isOpen, setIsOpen] = useState(false); + const [activeQuestion, setActiveQuestion] = useState(null) + const [isOpen, setIsOpen] = useState(false) const handleAnswer = (answer: boolean) => { - if (activeQuestion === null) return; - activeQuestion.resolve(answer); - setIsOpen(false); - }; + if (activeQuestion === null) return + activeQuestion.resolve(answer) + setIsOpen(false) + } const confirm = (message: ReactNode, config: Config) => { return new Promise((resolve) => { - setActiveQuestion({ message, config, resolve }); - setIsOpen(true); - }); - }; + setActiveQuestion({ message, config, resolve }) + setIsOpen(true) + }) + } return ( @@ -67,14 +67,14 @@ export function ConfirmProvider({ children }: { children: ReactNode }) {
@@ -82,5 +82,5 @@ export function ConfirmProvider({ children }: { children: ReactNode }) {
- ); + ) } diff --git a/src/features/dashboard-alerts/components/__tests__/alerts-summary-malicious-pkg.test.tsx b/src/features/dashboard-alerts/components/__tests__/alerts-summary-malicious-pkg.test.tsx index 44a4c7b1..bbb4f40f 100644 --- a/src/features/dashboard-alerts/components/__tests__/alerts-summary-malicious-pkg.test.tsx +++ b/src/features/dashboard-alerts/components/__tests__/alerts-summary-malicious-pkg.test.tsx @@ -1,36 +1,36 @@ -import { server } from "@/mocks/msw/node"; -import { test } from "vitest"; -import { http, HttpResponse } from "msw"; -import { render, waitFor } from "@/lib/test-utils"; -import { AlertsSummaryMaliciousPkg } from "../alerts-summary-malicious-pkg"; +import { server } from '@/mocks/msw/node' +import { test } from 'vitest' +import { http, HttpResponse } from 'msw' +import { render, waitFor } from '@/lib/test-utils' +import { AlertsSummaryMaliciousPkg } from '../alerts-summary-malicious-pkg' -import { mswEndpoint } from "@/test/msw-endpoint"; -import { mockAlert } from "@/mocks/msw/mockers/alert.mock"; +import { mswEndpoint } from '@/test/msw-endpoint' +import { mockAlert } from '@/mocks/msw/mockers/alert.mock' -test("shows correct count when there is a malicious alert", async () => { +test('shows correct count when there is a malicious alert', async () => { server.use( - http.get(mswEndpoint("/api/v1/workspaces/:workspace_name/alerts"), () => { - return HttpResponse.json([mockAlert({ type: "malicious" })]); - }), - ); + http.get(mswEndpoint('/api/v1/workspaces/:workspace_name/alerts'), () => { + return HttpResponse.json([mockAlert({ type: 'malicious' })]) + }) + ) - const { getByTestId } = render(); + const { getByTestId } = render() await waitFor(() => { - expect(getByTestId("malicious-count")).toHaveTextContent("1"); - }); -}); + expect(getByTestId('malicious-count')).toHaveTextContent('1') + }) +}) -test("shows correct count when there is no malicious alert", async () => { +test('shows correct count when there is no malicious alert', async () => { server.use( - http.get(mswEndpoint("/api/v1/workspaces/:workspace_name/alerts"), () => { - return HttpResponse.json([mockAlert({ type: "secret" })]); - }), - ); + http.get(mswEndpoint('/api/v1/workspaces/:workspace_name/alerts'), () => { + return HttpResponse.json([mockAlert({ type: 'secret' })]) + }) + ) - const { getByTestId } = render(); + const { getByTestId } = render() await waitFor(() => { - expect(getByTestId("malicious-count")).toHaveTextContent("0"); - }); -}); + expect(getByTestId('malicious-count')).toHaveTextContent('0') + }) +}) diff --git a/src/features/dashboard-alerts/components/__tests__/alerts-summary-secrets.test.tsx b/src/features/dashboard-alerts/components/__tests__/alerts-summary-secrets.test.tsx index 9e3c2484..ccc05bce 100644 --- a/src/features/dashboard-alerts/components/__tests__/alerts-summary-secrets.test.tsx +++ b/src/features/dashboard-alerts/components/__tests__/alerts-summary-secrets.test.tsx @@ -1,36 +1,36 @@ -import { server } from "@/mocks/msw/node"; -import { test } from "vitest"; -import { http, HttpResponse } from "msw"; -import { render, waitFor } from "@/lib/test-utils"; +import { server } from '@/mocks/msw/node' +import { test } from 'vitest' +import { http, HttpResponse } from 'msw' +import { render, waitFor } from '@/lib/test-utils' -import { AlertsSummaryMaliciousSecrets } from "../alerts-summary-secrets"; -import { mswEndpoint } from "@/test/msw-endpoint"; -import { mockAlert } from "@/mocks/msw/mockers/alert.mock"; +import { AlertsSummaryMaliciousSecrets } from '../alerts-summary-secrets' +import { mswEndpoint } from '@/test/msw-endpoint' +import { mockAlert } from '@/mocks/msw/mockers/alert.mock' -test("shows correct count when there is a secret alert", async () => { +test('shows correct count when there is a secret alert', async () => { server.use( - http.get(mswEndpoint("/api/v1/workspaces/:workspace_name/alerts"), () => { - return HttpResponse.json([mockAlert({ type: "secret" })]); - }), - ); + http.get(mswEndpoint('/api/v1/workspaces/:workspace_name/alerts'), () => { + return HttpResponse.json([mockAlert({ type: 'secret' })]) + }) + ) - const { getByTestId } = render(); + const { getByTestId } = render() await waitFor(() => { - expect(getByTestId("secrets-count")).toHaveTextContent("1"); - }); -}); + expect(getByTestId('secrets-count')).toHaveTextContent('1') + }) +}) -test("shows correct count when there is no malicious alert", async () => { +test('shows correct count when there is no malicious alert', async () => { server.use( - http.get(mswEndpoint("/api/v1/workspaces/:workspace_name/alerts"), () => { - return HttpResponse.json([mockAlert({ type: "malicious" })]); - }), - ); + http.get(mswEndpoint('/api/v1/workspaces/:workspace_name/alerts'), () => { + return HttpResponse.json([mockAlert({ type: 'malicious' })]) + }) + ) - const { getByTestId } = render(); + const { getByTestId } = render() await waitFor(() => { - expect(getByTestId("secrets-count")).toHaveTextContent("0"); - }); -}); + expect(getByTestId('secrets-count')).toHaveTextContent('0') + }) +}) diff --git a/src/features/dashboard-alerts/components/__tests__/alerts-summary-workspace-token-usage.test.tsx b/src/features/dashboard-alerts/components/__tests__/alerts-summary-workspace-token-usage.test.tsx index 02138190..df240a9f 100644 --- a/src/features/dashboard-alerts/components/__tests__/alerts-summary-workspace-token-usage.test.tsx +++ b/src/features/dashboard-alerts/components/__tests__/alerts-summary-workspace-token-usage.test.tsx @@ -1,50 +1,50 @@ -import { server } from "@/mocks/msw/node"; -import { test } from "vitest"; -import { http, HttpResponse } from "msw"; -import { render, waitFor } from "@/lib/test-utils"; +import { server } from '@/mocks/msw/node' +import { test } from 'vitest' +import { http, HttpResponse } from 'msw' +import { render, waitFor } from '@/lib/test-utils' -import { AlertsSummaryWorkspaceTokenUsage } from "../alerts-summary-workspace-token-usage"; +import { AlertsSummaryWorkspaceTokenUsage } from '../alerts-summary-workspace-token-usage' -import { formatNumberCompact } from "@/lib/format-number"; -import { mswEndpoint } from "@/test/msw-endpoint"; -import { TOKEN_USAGE_AGG } from "@/mocks/msw/mockers/token-usage.mock"; +import { formatNumberCompact } from '@/lib/format-number' +import { mswEndpoint } from '@/test/msw-endpoint' +import { TOKEN_USAGE_AGG } from '@/mocks/msw/mockers/token-usage.mock' -test("shows correct count when there is token usage", async () => { +test('shows correct count when there is token usage', async () => { server.use( http.get( - mswEndpoint("/api/v1/workspaces/:workspace_name/token-usage"), + mswEndpoint('/api/v1/workspaces/:workspace_name/token-usage'), () => { - return HttpResponse.json(TOKEN_USAGE_AGG); - }, - ), - ); + return HttpResponse.json(TOKEN_USAGE_AGG) + } + ) + ) - const { getByTestId } = render(); + const { getByTestId } = render() await waitFor(() => { - expect(getByTestId("usage-input-tokens")).toHaveTextContent( - formatNumberCompact(TOKEN_USAGE_AGG.token_usage.input_tokens), - ); - expect(getByTestId("usage-output-tokens")).toHaveTextContent( - formatNumberCompact(TOKEN_USAGE_AGG.token_usage.output_tokens), - ); - }); -}); - -test("shows correct count when there is no token usage", async () => { + expect(getByTestId('usage-input-tokens')).toHaveTextContent( + formatNumberCompact(TOKEN_USAGE_AGG.token_usage.input_tokens) + ) + expect(getByTestId('usage-output-tokens')).toHaveTextContent( + formatNumberCompact(TOKEN_USAGE_AGG.token_usage.output_tokens) + ) + }) +}) + +test('shows correct count when there is no token usage', async () => { server.use( http.get( - mswEndpoint("/api/v1/workspaces/:workspace_name/token-usage"), + mswEndpoint('/api/v1/workspaces/:workspace_name/token-usage'), () => { - return HttpResponse.json({}); - }, - ), - ); + return HttpResponse.json({}) + } + ) + ) - const { getByTestId } = render(); + const { getByTestId } = render() await waitFor(() => { - expect(getByTestId("usage-input-tokens")).toHaveTextContent("0"); - expect(getByTestId("usage-output-tokens")).toHaveTextContent("0"); - }); -}); + expect(getByTestId('usage-input-tokens')).toHaveTextContent('0') + expect(getByTestId('usage-output-tokens')).toHaveTextContent('0') + }) +}) diff --git a/src/features/dashboard-alerts/components/alerts-summary-malicious-pkg.tsx b/src/features/dashboard-alerts/components/alerts-summary-malicious-pkg.tsx index a1bd424b..95c29bff 100644 --- a/src/features/dashboard-alerts/components/alerts-summary-malicious-pkg.tsx +++ b/src/features/dashboard-alerts/components/alerts-summary-malicious-pkg.tsx @@ -1,18 +1,18 @@ -import { PackageX } from "@untitled-ui/icons-react"; +import { PackageX } from '@untitled-ui/icons-react' -import { useQueryGetWorkspaceAlertsMaliciousPkg } from "../hooks/use-query-get-workspace-alerts-malicious-pkg"; -import { AlertsSummary } from "./alerts-summary"; +import { useQueryGetWorkspaceAlertsMaliciousPkg } from '../hooks/use-query-get-workspace-alerts-malicious-pkg' +import { AlertsSummary } from './alerts-summary' export function AlertsSummaryMaliciousPkg() { - const { data = [], isPending } = useQueryGetWorkspaceAlertsMaliciousPkg(); + const { data = [], isPending } = useQueryGetWorkspaceAlertsMaliciousPkg() return ( - ); + ) } diff --git a/src/features/dashboard-alerts/components/alerts-summary-secrets.tsx b/src/features/dashboard-alerts/components/alerts-summary-secrets.tsx index ff51450a..207af1f9 100644 --- a/src/features/dashboard-alerts/components/alerts-summary-secrets.tsx +++ b/src/features/dashboard-alerts/components/alerts-summary-secrets.tsx @@ -1,15 +1,15 @@ -import { Key01 } from "@untitled-ui/icons-react"; -import { AlertsSummary } from "./alerts-summary"; -import { useQueryGetWorkspaceAlertSecrets } from "../hooks/use-query-get-workspace-alerts-secrets"; +import { Key01 } from '@untitled-ui/icons-react' +import { AlertsSummary } from './alerts-summary' +import { useQueryGetWorkspaceAlertSecrets } from '../hooks/use-query-get-workspace-alerts-secrets' export function AlertsSummaryMaliciousSecrets() { - const { data = [], isPending } = useQueryGetWorkspaceAlertSecrets(); + const { data = [], isPending } = useQueryGetWorkspaceAlertSecrets() return ( - ); + ) } diff --git a/src/features/dashboard-alerts/components/alerts-summary-workspace-token-usage.tsx b/src/features/dashboard-alerts/components/alerts-summary-workspace-token-usage.tsx index 18ee8515..e73453f1 100644 --- a/src/features/dashboard-alerts/components/alerts-summary-workspace-token-usage.tsx +++ b/src/features/dashboard-alerts/components/alerts-summary-workspace-token-usage.tsx @@ -1,11 +1,11 @@ -import { Download01, Upload01 } from "@untitled-ui/icons-react"; -import { AlertsSummary } from "./alerts-summary"; -import { useQueryGetWorkspaceTokenUsage } from "../hooks/use-query-get-workspace-token-usage"; +import { Download01, Upload01 } from '@untitled-ui/icons-react' +import { AlertsSummary } from './alerts-summary' +import { useQueryGetWorkspaceTokenUsage } from '../hooks/use-query-get-workspace-token-usage' export function AlertsSummaryWorkspaceTokenUsage() { const { data, isPending } = useQueryGetWorkspaceTokenUsage({ select: (data) => data.token_usage, - }); + }) return ( - ); + ) } diff --git a/src/features/dashboard-alerts/components/alerts-summary.tsx b/src/features/dashboard-alerts/components/alerts-summary.tsx index 6e5d953f..bfb16e57 100644 --- a/src/features/dashboard-alerts/components/alerts-summary.tsx +++ b/src/features/dashboard-alerts/components/alerts-summary.tsx @@ -1,22 +1,22 @@ -import { formatNumberCompact } from "@/lib/format-number"; -import { Card, CardBody, Heading, Skeleton } from "@stacklok/ui-kit"; -import { ComponentProps } from "react"; +import { formatNumberCompact } from '@/lib/format-number' +import { Card, CardBody, Heading, Skeleton } from '@stacklok/ui-kit' +import { ComponentProps } from 'react' function AlertsSummaryStatistic({ count, id, Icon: Icon, }: { - count: number; - id: string; - Icon: (props: React.SVGProps) => React.JSX.Element; + count: number + id: string + Icon: (props: React.SVGProps) => React.JSX.Element }) { return ( -
+
{formatNumberCompact(count)}
- ); + ) } export function AlertsSummary({ @@ -24,9 +24,9 @@ export function AlertsSummary({ statistics, isPending, }: { - title: string; - statistics: ComponentProps[]; - isPending: boolean; + title: string + statistics: ComponentProps[] + isPending: boolean }) { return ( @@ -35,9 +35,9 @@ export function AlertsSummary({ {title} {isPending ? ( -
+
- +
) : (
@@ -48,5 +48,5 @@ export function AlertsSummary({ )} - ); + ) } diff --git a/src/features/dashboard-alerts/hooks/use-query-get-workspace-alerts-malicious-pkg.ts b/src/features/dashboard-alerts/hooks/use-query-get-workspace-alerts-malicious-pkg.ts index 722cd42a..76a2df16 100644 --- a/src/features/dashboard-alerts/hooks/use-query-get-workspace-alerts-malicious-pkg.ts +++ b/src/features/dashboard-alerts/hooks/use-query-get-workspace-alerts-malicious-pkg.ts @@ -1,17 +1,17 @@ -import { V1GetWorkspaceAlertsResponse } from "@/api/generated"; -import { isAlertMalicious } from "../../../lib/is-alert-malicious"; -import { useQueryGetWorkspaceAlerts } from "./use-query-get-workspace-alerts"; -import { multiFilter } from "@/lib/multi-filter"; -import { isAlertCritical } from "../../../lib/is-alert-critical"; +import { V1GetWorkspaceAlertsResponse } from '@/api/generated' +import { isAlertMalicious } from '../../../lib/is-alert-malicious' +import { useQueryGetWorkspaceAlerts } from './use-query-get-workspace-alerts' +import { multiFilter } from '@/lib/multi-filter' +import { isAlertCritical } from '../../../lib/is-alert-critical' // NOTE: This needs to be a stable function reference to enable memo-isation of // the select operation on each React re-render. function select(data: V1GetWorkspaceAlertsResponse) { - return multiFilter(data, [isAlertCritical, isAlertMalicious]); + return multiFilter(data, [isAlertCritical, isAlertMalicious]) } export function useQueryGetWorkspaceAlertsMaliciousPkg() { return useQueryGetWorkspaceAlerts({ select, - }); + }) } diff --git a/src/features/dashboard-alerts/hooks/use-query-get-workspace-alerts-secrets.ts b/src/features/dashboard-alerts/hooks/use-query-get-workspace-alerts-secrets.ts index 9311bd8d..0570dcd9 100644 --- a/src/features/dashboard-alerts/hooks/use-query-get-workspace-alerts-secrets.ts +++ b/src/features/dashboard-alerts/hooks/use-query-get-workspace-alerts-secrets.ts @@ -1,17 +1,17 @@ -import { V1GetWorkspaceAlertsResponse } from "@/api/generated"; -import { isAlertSecret } from "../../../lib/is-alert-secret"; -import { useQueryGetWorkspaceAlerts } from "./use-query-get-workspace-alerts"; -import { multiFilter } from "@/lib/multi-filter"; -import { isAlertCritical } from "../../../lib/is-alert-critical"; +import { V1GetWorkspaceAlertsResponse } from '@/api/generated' +import { isAlertSecret } from '../../../lib/is-alert-secret' +import { useQueryGetWorkspaceAlerts } from './use-query-get-workspace-alerts' +import { multiFilter } from '@/lib/multi-filter' +import { isAlertCritical } from '../../../lib/is-alert-critical' // NOTE: This needs to be a stable function reference to enable memo-isation of // the select operation on each React re-render function select(data: V1GetWorkspaceAlertsResponse) { - return multiFilter(data, [isAlertCritical, isAlertSecret]); + return multiFilter(data, [isAlertCritical, isAlertSecret]) } export function useQueryGetWorkspaceAlertSecrets() { return useQueryGetWorkspaceAlerts({ select, - }); + }) } diff --git a/src/features/dashboard-alerts/hooks/use-query-get-workspace-alerts.ts b/src/features/dashboard-alerts/hooks/use-query-get-workspace-alerts.ts index fe980f6e..c734cf25 100644 --- a/src/features/dashboard-alerts/hooks/use-query-get-workspace-alerts.ts +++ b/src/features/dashboard-alerts/hooks/use-query-get-workspace-alerts.ts @@ -1,16 +1,16 @@ import { V1GetWorkspaceAlertsData, V1GetWorkspaceAlertsResponse, -} from "@/api/generated"; -import { v1GetWorkspaceAlertsOptions } from "@/api/generated/@tanstack/react-query.gen"; -import { useQueryActiveWorkspaceName } from "@/hooks/use-query-active-workspace-name"; -import { getQueryCacheConfig } from "@/lib/react-query-utils"; -import { useQuery } from "@tanstack/react-query"; +} from '@/api/generated' +import { v1GetWorkspaceAlertsOptions } from '@/api/generated/@tanstack/react-query.gen' +import { useQueryActiveWorkspaceName } from '@/hooks/use-query-active-workspace-name' +import { getQueryCacheConfig } from '@/lib/react-query-utils' +import { useQuery } from '@tanstack/react-query' export function useQueryGetWorkspaceAlerts({ select, }: { - select?: (data: V1GetWorkspaceAlertsResponse) => T; + select?: (data: V1GetWorkspaceAlertsResponse) => T } = {}) { const { data: activeWorkspaceName, @@ -18,13 +18,13 @@ export function useQueryGetWorkspaceAlerts({ isFetching: isWorkspaceFetching, isLoading: isWorkspaceLoading, isRefetching: isWorkspaceRefetching, - } = useQueryActiveWorkspaceName(); + } = useQueryActiveWorkspaceName() const options: V1GetWorkspaceAlertsData = { path: { - workspace_name: activeWorkspaceName ?? "default", + workspace_name: activeWorkspaceName ?? 'default', }, - }; + } const { isPending: isAlertsPending, @@ -34,9 +34,9 @@ export function useQueryGetWorkspaceAlerts({ ...rest } = useQuery({ ...v1GetWorkspaceAlertsOptions(options), - ...getQueryCacheConfig("5s"), + ...getQueryCacheConfig('5s'), select, - }); + }) return { ...rest, @@ -44,5 +44,5 @@ export function useQueryGetWorkspaceAlerts({ isFetching: isAlertsFetching || isWorkspaceFetching, isLoading: isAlertsLoading || isWorkspaceLoading, isRefetching: isAlertsRefetching || isWorkspaceRefetching, - }; + } } diff --git a/src/features/dashboard-alerts/hooks/use-query-get-workspace-token-usage.ts b/src/features/dashboard-alerts/hooks/use-query-get-workspace-token-usage.ts index 37384acb..973d2fda 100644 --- a/src/features/dashboard-alerts/hooks/use-query-get-workspace-token-usage.ts +++ b/src/features/dashboard-alerts/hooks/use-query-get-workspace-token-usage.ts @@ -1,28 +1,28 @@ import { V1GetWorkspaceTokenUsageData, V1GetWorkspaceTokenUsageResponse, -} from "@/api/generated"; -import { v1GetWorkspaceTokenUsageOptions } from "@/api/generated/@tanstack/react-query.gen"; -import { useQueryActiveWorkspaceName } from "@/hooks/use-query-active-workspace-name"; -import { useQuery } from "@tanstack/react-query"; +} from '@/api/generated' +import { v1GetWorkspaceTokenUsageOptions } from '@/api/generated/@tanstack/react-query.gen' +import { useQueryActiveWorkspaceName } from '@/hooks/use-query-active-workspace-name' +import { useQuery } from '@tanstack/react-query' export function useQueryGetWorkspaceTokenUsage< T = V1GetWorkspaceTokenUsageResponse, >({ select, }: { - select?: (data: V1GetWorkspaceTokenUsageResponse) => T; + select?: (data: V1GetWorkspaceTokenUsageResponse) => T } = {}) { - const { data: activeWorkspaceName } = useQueryActiveWorkspaceName(); + const { data: activeWorkspaceName } = useQueryActiveWorkspaceName() const options: V1GetWorkspaceTokenUsageData = { path: { - workspace_name: activeWorkspaceName ?? "default", + workspace_name: activeWorkspaceName ?? 'default', }, - }; + } return useQuery({ ...v1GetWorkspaceTokenUsageOptions(options), select, - }); + }) } diff --git a/src/features/dashboard-messages/components/__tests__/table-messages.alerts.test.tsx b/src/features/dashboard-messages/components/__tests__/table-messages.alerts.test.tsx index b1a5ef9a..1d12083a 100644 --- a/src/features/dashboard-messages/components/__tests__/table-messages.alerts.test.tsx +++ b/src/features/dashboard-messages/components/__tests__/table-messages.alerts.test.tsx @@ -1,82 +1,82 @@ -import {} from "vitest"; -import { TableMessages } from "../table-messages"; -import { render, screen, waitFor } from "@/lib/test-utils"; -import { server } from "@/mocks/msw/node"; -import { http, HttpResponse } from "msw"; +import {} from 'vitest' +import { TableMessages } from '../table-messages' +import { render, screen, waitFor } from '@/lib/test-utils' +import { server } from '@/mocks/msw/node' +import { http, HttpResponse } from 'msw' -import { mswEndpoint } from "@/test/msw-endpoint"; -import { mockConversation } from "@/mocks/msw/mockers/conversation.mock"; +import { mswEndpoint } from '@/test/msw-endpoint' +import { mockConversation } from '@/mocks/msw/mockers/conversation.mock' -it("shows zero in alerts counts when no alerts", async () => { +it('shows zero in alerts counts when no alerts', async () => { server.use( - http.get(mswEndpoint("/api/v1/workspaces/:workspace_name/messages"), () => + http.get(mswEndpoint('/api/v1/workspaces/:workspace_name/messages'), () => HttpResponse.json([ mockConversation({ alertsConfig: { numAlerts: 0 }, }), - ]), - ), - ); - render(); + ]) + ) + ) + render() await waitFor(() => { - expect(screen.queryByText(/loading.../i)).not.toBeInTheDocument(); - }); + expect(screen.queryByText(/loading.../i)).not.toBeInTheDocument() + }) expect( - screen.getByRole("button", { + screen.getByRole('button', { name: /malicious packages count/i, - }), - ).toHaveTextContent("0"); + }) + ).toHaveTextContent('0') expect( - screen.getByRole("button", { + screen.getByRole('button', { name: /secrets count/i, - }), - ).toHaveTextContent("0"); -}); + }) + ).toHaveTextContent('0') +}) -it("shows count of malicious alerts in row", async () => { +it('shows count of malicious alerts in row', async () => { server.use( - http.get(mswEndpoint("/api/v1/workspaces/:workspace_name/messages"), () => + http.get(mswEndpoint('/api/v1/workspaces/:workspace_name/messages'), () => HttpResponse.json([ mockConversation({ - alertsConfig: { numAlerts: 10, type: "malicious" }, + alertsConfig: { numAlerts: 10, type: 'malicious' }, }), - ]), - ), - ); - render(); + ]) + ) + ) + render() await waitFor(() => { - expect(screen.queryByText(/loading.../i)).not.toBeInTheDocument(); - }); + expect(screen.queryByText(/loading.../i)).not.toBeInTheDocument() + }) expect( - screen.getByRole("button", { + screen.getByRole('button', { name: /malicious packages count/i, - }), - ).toHaveTextContent("10"); -}); + }) + ).toHaveTextContent('10') +}) -it("shows count of secret alerts in row", async () => { +it('shows count of secret alerts in row', async () => { server.use( - http.get(mswEndpoint("/api/v1/workspaces/:workspace_name/messages"), () => + http.get(mswEndpoint('/api/v1/workspaces/:workspace_name/messages'), () => HttpResponse.json([ mockConversation({ - alertsConfig: { numAlerts: 10, type: "secret" }, + alertsConfig: { numAlerts: 10, type: 'secret' }, }), - ]), - ), - ); - render(); + ]) + ) + ) + render() await waitFor(() => { - expect(screen.queryByText(/loading.../i)).not.toBeInTheDocument(); - }); + expect(screen.queryByText(/loading.../i)).not.toBeInTheDocument() + }) expect( - screen.getByRole("button", { + screen.getByRole('button', { name: /secrets count/i, - }), - ).toHaveTextContent("10"); -}); + }) + ).toHaveTextContent('10') +}) diff --git a/src/features/dashboard-messages/components/__tests__/table-messages.empty-state.test.tsx b/src/features/dashboard-messages/components/__tests__/table-messages.empty-state.test.tsx index 5269e7e1..1d38805c 100644 --- a/src/features/dashboard-messages/components/__tests__/table-messages.empty-state.test.tsx +++ b/src/features/dashboard-messages/components/__tests__/table-messages.empty-state.test.tsx @@ -1,66 +1,62 @@ -import { test } from "vitest"; -import { render, waitFor } from "@/lib/test-utils"; -import { server } from "@/mocks/msw/node"; -import { emptyStateStrings } from "../../../../constants/empty-state-strings"; -import { useSearchParams } from "react-router-dom"; -import { delay, http, HttpHandler, HttpResponse } from "msw"; -import { mockAlert } from "../../../../mocks/msw/mockers/alert.mock"; -import { AlertsFilterView } from "../../hooks/use-messages-filter-search-params"; -import { hrefs } from "@/lib/hrefs"; -import { mswEndpoint } from "@/test/msw-endpoint"; -import { TableMessagesEmptyState } from "../table-messages-empty-state"; +import { test } from 'vitest' +import { render, waitFor } from '@/lib/test-utils' +import { server } from '@/mocks/msw/node' +import { emptyStateStrings } from '../../../../constants/empty-state-strings' +import { useSearchParams } from 'react-router-dom' +import { delay, http, HttpHandler, HttpResponse } from 'msw' +import { mockAlert } from '../../../../mocks/msw/mockers/alert.mock' +import { AlertsFilterView } from '../../hooks/use-messages-filter-search-params' +import { hrefs } from '@/lib/hrefs' +import { mswEndpoint } from '@/test/msw-endpoint' +import { TableMessagesEmptyState } from '../table-messages-empty-state' enum IllustrationTestId { - ALERT = "illustration-alert", - DONE = "illustration-done", - DRAG_AND_DROP = "illustration-drag-and-drop", - LOADER = "illustration-loader", - NO_SEARCH_RESULTS = "illustration-no-search-results", + ALERT = 'illustration-alert', + DONE = 'illustration-done', + DRAG_AND_DROP = 'illustration-drag-and-drop', + LOADER = 'illustration-loader', + NO_SEARCH_RESULTS = 'illustration-no-search-results', } type TestCaseAction = | { - role: "button"; - name: string; - href?: never; + role: 'button' + name: string + href?: never } | { - role: "link"; - name: string; - href: string; - }; + role: 'link' + name: string + href: string + } type TestCase = { - testDescription: string; - handlers: HttpHandler[]; + testDescription: string + handlers: HttpHandler[] searchParams: { - view: AlertsFilterView; - search: string | null; - }; + view: AlertsFilterView + search: string | null + } expected: { - title: string; - body: string; - illustrationTestId: IllustrationTestId; - actions: TestCaseAction[] | null; - }; -}; + title: string + body: string + illustrationTestId: IllustrationTestId + actions: TestCaseAction[] | null + } +} -vi.mock("react-router-dom", async () => { +vi.mock('react-router-dom', async () => { const original = - await vi.importActual( - "react-router-dom", - ); + await vi.importActual('react-router-dom') return { ...original, useSearchParams: vi.fn(() => [new URLSearchParams({}), () => {}]), - }; -}); + } +}) -vi.mock("@stacklok/ui-kit", async () => { +vi.mock('@stacklok/ui-kit', async () => { const original = - await vi.importActual( - "@stacklok/ui-kit", - ); + await vi.importActual('@stacklok/ui-kit') return { ...original, IllustrationDone: () =>
, @@ -72,15 +68,15 @@ vi.mock("@stacklok/ui-kit", async () => {
), Loader: () =>
, - }; -}); + } +}) const TEST_CASES: TestCase[] = [ { - testDescription: "Loading state", + testDescription: 'Loading state', handlers: [ - http.get(mswEndpoint("/api/v1/workspaces"), () => { - delay("infinite"); + http.get(mswEndpoint('/api/v1/workspaces'), () => { + delay('infinite') }), ], searchParams: { @@ -95,28 +91,28 @@ const TEST_CASES: TestCase[] = [ }, }, { - testDescription: "Only 1 workspace, no alerts", + testDescription: 'Only 1 workspace, no alerts', handlers: [ - http.get(mswEndpoint("/api/v1/workspaces"), () => { + http.get(mswEndpoint('/api/v1/workspaces'), () => { return HttpResponse.json({ workspaces: [ { - name: "default", + name: 'default', is_active: true, }, ], - }); + }) }), - http.get(mswEndpoint("/api/v1/workspaces/archive"), () => { + http.get(mswEndpoint('/api/v1/workspaces/archive'), () => { return HttpResponse.json({ workspaces: [], - }); + }) }), http.get( - mswEndpoint("/api/v1/workspaces/:workspace_name/messages"), + mswEndpoint('/api/v1/workspaces/:workspace_name/messages'), () => { - return HttpResponse.json([]); - }, + return HttpResponse.json([]) + } ), ], searchParams: { @@ -129,80 +125,80 @@ const TEST_CASES: TestCase[] = [ illustrationTestId: IllustrationTestId.DRAG_AND_DROP, actions: [ { - role: "link", - name: "CodeGate docs", + role: 'link', + name: 'CodeGate docs', href: hrefs.external.docs.home, }, ], }, }, { - testDescription: "No search results", + testDescription: 'No search results', handlers: [ - http.get(mswEndpoint("/api/v1/workspaces"), () => { + http.get(mswEndpoint('/api/v1/workspaces'), () => { return HttpResponse.json({ workspaces: [ { - name: "default", + name: 'default', is_active: true, }, ], - }); + }) }), - http.get(mswEndpoint("/api/v1/workspaces/archive"), () => { + http.get(mswEndpoint('/api/v1/workspaces/archive'), () => { return HttpResponse.json({ workspaces: [], - }); + }) }), http.get( - mswEndpoint("/api/v1/workspaces/:workspace_name/messages"), + mswEndpoint('/api/v1/workspaces/:workspace_name/messages'), () => { return HttpResponse.json( - Array.from({ length: 10 }, () => mockAlert({ type: "malicious" })), - ); - }, + Array.from({ length: 10 }, () => mockAlert({ type: 'malicious' })) + ) + } ), ], - searchParams: { search: "foo-bar", view: AlertsFilterView.ALL }, + searchParams: { search: 'foo-bar', view: AlertsFilterView.ALL }, expected: { - title: emptyStateStrings.title.noSearchResultsFor("foo-bar"), + title: emptyStateStrings.title.noSearchResultsFor('foo-bar'), body: emptyStateStrings.body.tryChangingSearch, illustrationTestId: IllustrationTestId.NO_SEARCH_RESULTS, actions: [ { - role: "button", - name: "Clear search", + role: 'button', + name: 'Clear search', }, ], }, }, { - testDescription: "No alerts, multiple workspaces", + testDescription: 'No alerts, multiple workspaces', handlers: [ - http.get(mswEndpoint("/api/v1/workspaces"), () => { + http.get(mswEndpoint('/api/v1/workspaces'), () => { return HttpResponse.json({ workspaces: [ { - name: "default", + name: 'default', is_active: true, }, { - name: "foo-bar", + name: 'foo-bar', is_active: false, }, ], - }); + }) }), - http.get(mswEndpoint("/api/v1/workspaces/archive"), () => { + http.get(mswEndpoint('/api/v1/workspaces/archive'), () => { return HttpResponse.json({ workspaces: [], - }); + }) }), http.get( - mswEndpoint("/api/v1/workspaces/:workspace_name/messages"), + mswEndpoint('/api/v1/workspaces/:workspace_name/messages'), () => { - return HttpResponse.json([]); - }, + return HttpResponse.json([]) + } ), ], searchParams: { @@ -215,8 +211,8 @@ const TEST_CASES: TestCase[] = [ illustrationTestId: IllustrationTestId.DONE, actions: [ { - role: "link", - name: "Learn about Workspaces", + role: 'link', + name: 'Learn about Workspaces', href: hrefs.external.docs.workspaces, }, ], @@ -225,32 +221,32 @@ const TEST_CASES: TestCase[] = [ { testDescription: 'Has alerts, view is "malicious"', handlers: [ - http.get(mswEndpoint("/api/v1/workspaces"), () => { + http.get(mswEndpoint('/api/v1/workspaces'), () => { return HttpResponse.json({ workspaces: [ { - name: "default", + name: 'default', is_active: true, }, { - name: "foo-bar", + name: 'foo-bar', is_active: false, }, ], - }); + }) }), - http.get(mswEndpoint("/api/v1/workspaces/archive"), () => { + http.get(mswEndpoint('/api/v1/workspaces/archive'), () => { return HttpResponse.json({ workspaces: [], - }); + }) }), http.get( - mswEndpoint("/api/v1/workspaces/:workspace_name/messages"), + mswEndpoint('/api/v1/workspaces/:workspace_name/messages'), () => { return HttpResponse.json( - Array.from({ length: 10 }).map(() => mockAlert({ type: "secret" })), - ); - }, + Array.from({ length: 10 }).map(() => mockAlert({ type: 'secret' })) + ) + } ), ], searchParams: { @@ -267,34 +263,34 @@ const TEST_CASES: TestCase[] = [ { testDescription: 'Has alerts, view is "secret"', handlers: [ - http.get(mswEndpoint("/api/v1/workspaces"), () => { + http.get(mswEndpoint('/api/v1/workspaces'), () => { return HttpResponse.json({ workspaces: [ { - name: "default", + name: 'default', is_active: true, }, { - name: "foo-bar", + name: 'foo-bar', is_active: false, }, ], - }); + }) }), - http.get(mswEndpoint("/api/v1/workspaces/archive"), () => { + http.get(mswEndpoint('/api/v1/workspaces/archive'), () => { return HttpResponse.json({ workspaces: [], - }); + }) }), http.get( - mswEndpoint("/api/v1/workspaces/:workspace_name/messages"), + mswEndpoint('/api/v1/workspaces/:workspace_name/messages'), () => { return HttpResponse.json( Array.from({ length: 10 }).map(() => - mockAlert({ type: "malicious" }), - ), - ); - }, + mockAlert({ type: 'malicious' }) + ) + ) + } ), ], searchParams: { @@ -308,38 +304,38 @@ const TEST_CASES: TestCase[] = [ actions: null, }, }, -]; +] -test.each(TEST_CASES)("$testDescription", async (testCase) => { - server.use(...testCase.handlers); +test.each(TEST_CASES)('$testDescription', async (testCase) => { + server.use(...testCase.handlers) vi.mocked(useSearchParams).mockReturnValue([ new URLSearchParams({ - search: testCase.searchParams.search ?? "", + search: testCase.searchParams.search ?? '', view: testCase.searchParams.view, }), () => {}, - ]); + ]) const { getByText, getByRole, getByTestId } = render( - , - ); + + ) await waitFor(() => { expect( - getByRole("heading", { level: 4, name: testCase.expected.title }), - ).toBeVisible(); - expect(getByText(testCase.expected.body)).toBeVisible(); - expect(getByTestId(testCase.expected.illustrationTestId)).toBeVisible(); + getByRole('heading', { level: 4, name: testCase.expected.title }) + ).toBeVisible() + expect(getByText(testCase.expected.body)).toBeVisible() + expect(getByTestId(testCase.expected.illustrationTestId)).toBeVisible() if (testCase.expected.actions) { for (const action of testCase.expected.actions) { - const actionButton = getByRole(action.role, { name: action.name }); - expect(actionButton).toBeVisible(); + const actionButton = getByRole(action.role, { name: action.name }) + expect(actionButton).toBeVisible() if (action.href) { - expect(actionButton).toHaveAttribute("href", action.href); + expect(actionButton).toHaveAttribute('href', action.href) } } } - }); -}); + }) +}) diff --git a/src/features/dashboard-messages/components/__tests__/table-messages.pagination.test.tsx b/src/features/dashboard-messages/components/__tests__/table-messages.pagination.test.tsx index 67d2f622..aee76c61 100644 --- a/src/features/dashboard-messages/components/__tests__/table-messages.pagination.test.tsx +++ b/src/features/dashboard-messages/components/__tests__/table-messages.pagination.test.tsx @@ -1,69 +1,69 @@ -import {} from "vitest"; -import { TableMessages } from "../table-messages"; -import { render, screen, waitFor, within } from "@/lib/test-utils"; -import { server } from "@/mocks/msw/node"; -import { http, HttpResponse } from "msw"; +import {} from 'vitest' +import { TableMessages } from '../table-messages' +import { render, screen, waitFor, within } from '@/lib/test-utils' +import { server } from '@/mocks/msw/node' +import { http, HttpResponse } from 'msw' -import { mswEndpoint } from "@/test/msw-endpoint"; -import { mockConversation } from "@/mocks/msw/mockers/conversation.mock"; -import userEvent from "@testing-library/user-event"; +import { mswEndpoint } from '@/test/msw-endpoint' +import { mockConversation } from '@/mocks/msw/mockers/conversation.mock' +import userEvent from '@testing-library/user-event' -it("only displays a limited number of items in the table", async () => { +it('only displays a limited number of items in the table', async () => { server.use( - http.get(mswEndpoint("/api/v1/workspaces/:workspace_name/messages"), () => { + http.get(mswEndpoint('/api/v1/workspaces/:workspace_name/messages'), () => { return HttpResponse.json( - Array.from({ length: 30 }).map(() => mockConversation()), - ); - }), - ); + Array.from({ length: 30 }).map(() => mockConversation()) + ) + }) + ) - render(); + render() await waitFor(() => { expect( - within(screen.getByTestId("messages-table")).getAllByRole("row"), - ).toHaveLength(16); - }); -}); + within(screen.getByTestId('messages-table')).getAllByRole('row') + ).toHaveLength(16) + }) +}) -it("allows pagination", async () => { +it('allows pagination', async () => { server.use( - http.get(mswEndpoint("/api/v1/workspaces/:workspace_name/messages"), () => { + http.get(mswEndpoint('/api/v1/workspaces/:workspace_name/messages'), () => { return HttpResponse.json( - Array.from({ length: 35 }).map(() => mockConversation()), - ); - }), - ); + Array.from({ length: 35 }).map(() => mockConversation()) + ) + }) + ) - render(); + render() await waitFor( async () => { - await userEvent.click(screen.getByRole("button", { name: /next/i })); + await userEvent.click(screen.getByRole('button', { name: /next/i })) expect( - within(screen.getByTestId("messages-table")).getAllByRole("row").length, - ).toBeLessThan(16); + within(screen.getByTestId('messages-table')).getAllByRole('row').length + ).toBeLessThan(16) }, - { timeout: 5000 }, - ); + { timeout: 5000 } + ) // on the last page, we cannot go further - expect(screen.getByRole("button", { name: /next/i })).toBeDisabled(); + expect(screen.getByRole('button', { name: /next/i })).toBeDisabled() - await userEvent.click(screen.getByRole("button", { name: /previous/i })); - expect(screen.getByRole("button", { name: /previous/i })).toBeEnabled(); - expect(screen.getByRole("button", { name: /next/i })).toBeEnabled(); + await userEvent.click(screen.getByRole('button', { name: /previous/i })) + expect(screen.getByRole('button', { name: /previous/i })).toBeEnabled() + expect(screen.getByRole('button', { name: /next/i })).toBeEnabled() await waitFor(async () => { - await userEvent.click(screen.getByRole("button", { name: /previous/i })); + await userEvent.click(screen.getByRole('button', { name: /previous/i })) // once we reach the first page, we cannot paginate backwards anymore - expect(screen.getByRole("button", { name: /previous/i })).toBeDisabled(); - expect(screen.getByRole("button", { name: /next/i })).toBeEnabled(); + expect(screen.getByRole('button', { name: /previous/i })).toBeDisabled() + expect(screen.getByRole('button', { name: /next/i })).toBeEnabled() expect( - within(screen.getByTestId("messages-table")).getAllByRole("row").length, - ).toEqual(16); - }); -}); + within(screen.getByTestId('messages-table')).getAllByRole('row').length + ).toEqual(16) + }) +}) diff --git a/src/features/dashboard-messages/components/__tests__/table-messages.test.tsx b/src/features/dashboard-messages/components/__tests__/table-messages.test.tsx index 47248cf0..818ad3c5 100644 --- a/src/features/dashboard-messages/components/__tests__/table-messages.test.tsx +++ b/src/features/dashboard-messages/components/__tests__/table-messages.test.tsx @@ -1,18 +1,18 @@ -import { it, expect } from "vitest"; +import { it, expect } from 'vitest' -import { render, screen, waitFor } from "@/lib/test-utils"; -import { TABLE_MESSAGES_COLUMNS } from "../../constants/table-messages-columns"; +import { render, screen, waitFor } from '@/lib/test-utils' +import { TABLE_MESSAGES_COLUMNS } from '../../constants/table-messages-columns' -import { TableMessages } from "../table-messages"; +import { TableMessages } from '../table-messages' -it.each(TABLE_MESSAGES_COLUMNS)("contains $children header", async (column) => { - render(); +it.each(TABLE_MESSAGES_COLUMNS)('contains $children header', async (column) => { + render() await waitFor(() => { expect( - screen.getByRole("columnheader", { + screen.getByRole('columnheader', { name: column.children as string, - }), - ).toBeVisible(); - }); -}); + }) + ).toBeVisible() + }) +}) diff --git a/src/features/dashboard-messages/components/__tests__/table-messages.token-usage.test.tsx b/src/features/dashboard-messages/components/__tests__/table-messages.token-usage.test.tsx index 2b949ba3..c7ea695a 100644 --- a/src/features/dashboard-messages/components/__tests__/table-messages.token-usage.test.tsx +++ b/src/features/dashboard-messages/components/__tests__/table-messages.token-usage.test.tsx @@ -1,67 +1,67 @@ -import {} from "vitest"; -import { TableMessages } from "../table-messages"; -import { render, waitFor } from "@/lib/test-utils"; -import { server } from "@/mocks/msw/node"; -import { http, HttpResponse } from "msw"; -import { TOKEN_USAGE_AGG } from "../../../../mocks/msw/mockers/token-usage.mock"; -import { formatNumberCompact } from "@/lib/format-number"; -import { mswEndpoint } from "@/test/msw-endpoint"; -import { mockConversation } from "@/mocks/msw/mockers/conversation.mock"; +import {} from 'vitest' +import { TableMessages } from '../table-messages' +import { render, waitFor } from '@/lib/test-utils' +import { server } from '@/mocks/msw/node' +import { http, HttpResponse } from 'msw' +import { TOKEN_USAGE_AGG } from '../../../../mocks/msw/mockers/token-usage.mock' +import { formatNumberCompact } from '@/lib/format-number' +import { mswEndpoint } from '@/test/msw-endpoint' +import { mockConversation } from '@/mocks/msw/mockers/conversation.mock' -vi.mock("@untitled-ui/icons-react", async () => { +vi.mock('@untitled-ui/icons-react', async () => { const original = await vi.importActual< - typeof import("@untitled-ui/icons-react") - >("@untitled-ui/icons-react"); + typeof import('@untitled-ui/icons-react') + >('@untitled-ui/icons-react') return { ...original, Download01: () =>
, Upload01: () =>
, - }; -}); + } +}) const INPUT_TOKENS = - TOKEN_USAGE_AGG.tokens_by_model["claude-3-5-sonnet-latest"].token_usage - .input_tokens; + TOKEN_USAGE_AGG.tokens_by_model['claude-3-5-sonnet-latest'].token_usage + .input_tokens const OUTPUT_TOKENS = - TOKEN_USAGE_AGG.tokens_by_model["claude-3-5-sonnet-latest"].token_usage - .output_tokens; + TOKEN_USAGE_AGG.tokens_by_model['claude-3-5-sonnet-latest'].token_usage + .output_tokens -test("renders token usage cell correctly", async () => { +test('renders token usage cell correctly', async () => { server.use( - http.get(mswEndpoint("/api/v1/workspaces/:workspace_name/messages"), () => { - return HttpResponse.json([mockConversation({ withTokenUsage: true })]); - }), - ); + http.get(mswEndpoint('/api/v1/workspaces/:workspace_name/messages'), () => { + return HttpResponse.json([mockConversation({ withTokenUsage: true })]) + }) + ) - const { getByRole, getByTestId, queryByText } = render(); + const { getByRole, getByTestId, queryByText } = render() await waitFor(() => { - expect(queryByText(/loading.../i)).not.toBeInTheDocument(); - }); + expect(queryByText(/loading.../i)).not.toBeInTheDocument() + }) - expect(getByTestId("icon-arrow-up")).toBeVisible(); - expect(getByTestId("icon-arrow-down")).toBeVisible(); + expect(getByTestId('icon-arrow-up')).toBeVisible() + expect(getByTestId('icon-arrow-down')).toBeVisible() expect( - getByRole("gridcell", { + getByRole('gridcell', { name: `${formatNumberCompact(INPUT_TOKENS)} ${formatNumberCompact(OUTPUT_TOKENS)}`, - }), - ).toBeVisible(); -}); + }) + ).toBeVisible() +}) -test("renders N/A when token usage is missing", async () => { +test('renders N/A when token usage is missing', async () => { server.use( - http.get(mswEndpoint("/api/v1/workspaces/:workspace_name/messages"), () => { - return HttpResponse.json([mockConversation({ withTokenUsage: false })]); - }), - ); + http.get(mswEndpoint('/api/v1/workspaces/:workspace_name/messages'), () => { + return HttpResponse.json([mockConversation({ withTokenUsage: false })]) + }) + ) - const { getByText, queryByText } = render(); + const { getByText, queryByText } = render() await waitFor(() => { - expect(queryByText(/loading.../i)).not.toBeInTheDocument(); - }); + expect(queryByText(/loading.../i)).not.toBeInTheDocument() + }) - expect(getByText("N/A")).toBeVisible(); -}); + expect(getByText('N/A')).toBeVisible() +}) diff --git a/src/features/dashboard-messages/components/__tests__/tabs-messages.test.tsx b/src/features/dashboard-messages/components/__tests__/tabs-messages.test.tsx index 5e795e17..4c83f9ad 100644 --- a/src/features/dashboard-messages/components/__tests__/tabs-messages.test.tsx +++ b/src/features/dashboard-messages/components/__tests__/tabs-messages.test.tsx @@ -1,95 +1,95 @@ -import { server } from "@/mocks/msw/node"; -import { http, HttpResponse } from "msw"; -import { render, waitFor } from "@/lib/test-utils"; -import { TabsMessages } from "../tabs-messages"; -import { mswEndpoint } from "@/test/msw-endpoint"; -import { mockConversation } from "@/mocks/msw/mockers/conversation.mock"; +import { server } from '@/mocks/msw/node' +import { http, HttpResponse } from 'msw' +import { render, waitFor } from '@/lib/test-utils' +import { TabsMessages } from '../tabs-messages' +import { mswEndpoint } from '@/test/msw-endpoint' +import { mockConversation } from '@/mocks/msw/mockers/conversation.mock' -test("shows correct count of all packages", async () => { +test('shows correct count of all packages', async () => { server.use( - http.get(mswEndpoint("/api/v1/workspaces/:workspace_name/messages"), () => { + http.get(mswEndpoint('/api/v1/workspaces/:workspace_name/messages'), () => { return HttpResponse.json([ ...Array.from({ length: 13 }).map(() => mockConversation({ alertsConfig: { - type: "secret", + type: 'secret', numAlerts: 1, }, - }), + }) ), ...Array.from({ length: 13 }).map(() => mockConversation({ alertsConfig: { - type: "malicious", + type: 'malicious', numAlerts: 1, }, - }), + }) ), - ]); - }), - ); + ]) + }) + ) const { getByRole } = render(
foo
-
, - ); + + ) await waitFor(() => { - expect(getByRole("tab", { name: /all/i })).toHaveTextContent("26"); - }); -}); + expect(getByRole('tab', { name: /all/i })).toHaveTextContent('26') + }) +}) -test("shows correct count of malicious packages", async () => { +test('shows correct count of malicious packages', async () => { server.use( - http.get(mswEndpoint("/api/v1/workspaces/:workspace_name/messages"), () => { + http.get(mswEndpoint('/api/v1/workspaces/:workspace_name/messages'), () => { return HttpResponse.json( Array.from({ length: 13 }).map(() => mockConversation({ alertsConfig: { - type: "malicious", + type: 'malicious', numAlerts: 1, }, - }), - ), - ); - }), - ); + }) + ) + ) + }) + ) const { getByRole } = render(
foo
-
, - ); + + ) await waitFor(() => { - expect(getByRole("tab", { name: /malicious/i })).toHaveTextContent("13"); - }); -}); + expect(getByRole('tab', { name: /malicious/i })).toHaveTextContent('13') + }) +}) -test("shows correct count of secret packages", async () => { +test('shows correct count of secret packages', async () => { server.use( - http.get(mswEndpoint("/api/v1/workspaces/:workspace_name/messages"), () => { + http.get(mswEndpoint('/api/v1/workspaces/:workspace_name/messages'), () => { return HttpResponse.json( Array.from({ length: 13 }).map(() => mockConversation({ alertsConfig: { - type: "secret", + type: 'secret', numAlerts: 1, }, - }), - ), - ); - }), - ); + }) + ) + ) + }) + ) const { getByRole } = render(
foo
-
, - ); + + ) await waitFor(() => { - expect(getByRole("tab", { name: /secrets/i })).toHaveTextContent("13"); - }); -}); + expect(getByRole('tab', { name: /secrets/i })).toHaveTextContent('13') + }) +}) diff --git a/src/features/dashboard-messages/components/conversation-secrets-detected.tsx b/src/features/dashboard-messages/components/conversation-secrets-detected.tsx index c82b11bf..6ef2e07f 100644 --- a/src/features/dashboard-messages/components/conversation-secrets-detected.tsx +++ b/src/features/dashboard-messages/components/conversation-secrets-detected.tsx @@ -1,21 +1,21 @@ -import { Alert } from "@/api/generated"; -import { ReactNode } from "react"; -import Markdown from "react-markdown"; +import { Alert } from '@/api/generated' +import { ReactNode } from 'react' +import Markdown from 'react-markdown' function ConversationSecretsList({ children }: { children: ReactNode }) { - return
    {children}
; + return
    {children}
} function ConversationSecretsListItem({ children }: { children: ReactNode }) { return ( -
  • +
  • {children}
  • - ); + ) } function formatTriggerString(string: string): string { - return string.replace(/REDACTED<[^>]*?>/g, "**REDACTED**"); + return string.replace(/REDACTED<[^>]*?>/g, '**REDACTED**') } // NOTE: The secrets detection backend code appears to be returning fairly @@ -25,14 +25,14 @@ export function ConversationSecretsDetected({ alerts }: { alerts: Alert[] }) { return ( {alerts.map((a) => { - if (typeof a.trigger_string !== "string") return null; + if (typeof a.trigger_string !== 'string') return null return ( {formatTriggerString(a.trigger_string)} - ); + ) })} - ); + ) } diff --git a/src/features/dashboard-messages/components/conversation-summary.tsx b/src/features/dashboard-messages/components/conversation-summary.tsx index 7189618d..549a9e03 100644 --- a/src/features/dashboard-messages/components/conversation-summary.tsx +++ b/src/features/dashboard-messages/components/conversation-summary.tsx @@ -1,69 +1,69 @@ -import { Conversation } from "@/api/generated"; -import { ReactNode } from "react"; -import { getProviderString } from "../lib/get-provider-string"; -import { formatTime } from "@/lib/format-time"; -import { countConversationAlerts } from "../lib/count-conversation-alerts"; +import { Conversation } from '@/api/generated' +import { ReactNode } from 'react' +import { getProviderString } from '../lib/get-provider-string' +import { formatTime } from '@/lib/format-time' +import { countConversationAlerts } from '../lib/count-conversation-alerts' -import { twMerge } from "tailwind-merge"; -import { TokenUsageIcon } from "./token-usage-icon"; -import { formatNumberCompact } from "@/lib/format-number"; +import { twMerge } from 'tailwind-merge' +import { TokenUsageIcon } from './token-usage-icon' +import { formatNumberCompact } from '@/lib/format-number' import { Clock, Hash01, Key01, PackageX, Server05, -} from "@untitled-ui/icons-react"; +} from '@untitled-ui/icons-react' function TokenUsage({ tokens, type, }: { - type: "input" | "output"; - tokens: number; + type: 'input' | 'output' + tokens: number }) { return (
    {formatNumberCompact(tokens)}
    - ); + ) } function TokenUsageRow({ input_tokens, output_tokens, }: { - input_tokens: number; - output_tokens: number; + input_tokens: number + output_tokens: number }) { return (
    - - + +
    - ); + ) } function AlertsSummaryCount({ count, type, }: { - count: number; + count: number type: { - singular: "malicious package" | "secret"; - plural: "malicious packages" | "secrets"; - }; + singular: 'malicious package' | 'secret' + plural: 'malicious packages' | 'secrets' + } }) { - const typeText = count === 1 ? type.singular : type.plural; + const typeText = count === 1 ? type.singular : type.plural - const text = `${count} ${typeText} detected`; + const text = `${count} ${typeText} detected` return ( - 0 ? "text-secondary" : "text-disabled")}> + 0 ? 'text-secondary' : 'text-disabled')}> {text} - ); + ) } function ConversationSummaryListItem({ @@ -71,35 +71,35 @@ function ConversationSummaryListItem({ value, icon: Icon, }: { - title: ReactNode; - value: ReactNode; - icon: (props: React.SVGProps) => React.JSX.Element; + title: ReactNode + value: ReactNode + icon: (props: React.SVGProps) => React.JSX.Element }) { return ( -
  • - +
  • + {title} {value}
  • - ); + ) } function ConversationSummaryList({ children }: { children: ReactNode }) { return (
      {children}
    - ); + ) } export function ConversationSummary({ conversation, }: { - conversation: Conversation; + conversation: Conversation }) { const { malicious, secrets } = conversation.alerts ? countConversationAlerts(conversation.alerts) - : { malicious: 0, secrets: 0 }; + : { malicious: 0, secrets: 0 } return (
    @@ -113,7 +113,7 @@ export function ConversationSummary({ icon={Clock} title="Timestamp" value={formatTime(new Date(conversation.conversation_timestamp), { - format: "absolute", + format: 'absolute', })} /> @@ -159,8 +159,8 @@ export function ConversationSummary({ value={ @@ -168,5 +168,5 @@ export function ConversationSummary({ />
    - ); + ) } diff --git a/src/features/dashboard-messages/components/search-field-messages.tsx b/src/features/dashboard-messages/components/search-field-messages.tsx index b311bc50..56636b0d 100644 --- a/src/features/dashboard-messages/components/search-field-messages.tsx +++ b/src/features/dashboard-messages/components/search-field-messages.tsx @@ -4,29 +4,29 @@ import { Kbd, SearchField, SearchFieldClearButton, -} from "@stacklok/ui-kit"; -import { useMessagesFilterSearchParams } from "../hooks/use-messages-filter-search-params"; -import { SearchMd } from "@untitled-ui/icons-react"; -import { useKbdShortcuts } from "@/hooks/use-kbd-shortcuts"; -import { useRef } from "react"; +} from '@stacklok/ui-kit' +import { useMessagesFilterSearchParams } from '../hooks/use-messages-filter-search-params' +import { SearchMd } from '@untitled-ui/icons-react' +import { useKbdShortcuts } from '@/hooks/use-kbd-shortcuts' +import { useRef } from 'react' export function SearchFieldMessages({ className }: { className?: string }) { - const { setSearch, state } = useMessagesFilterSearchParams(); - const ref = useRef(null); + const { setSearch, state } = useMessagesFilterSearchParams() + const ref = useRef(null) useKbdShortcuts([ [ - "/", + '/', () => { - ref.current?.focus(); + ref.current?.focus() }, ], - ]); + ]) return ( setSearch(value)} className={className} > @@ -42,5 +42,5 @@ export function SearchFieldMessages({ className }: { className?: string }) { / - ); + ) } diff --git a/src/features/dashboard-messages/components/section-conversation-secrets.tsx b/src/features/dashboard-messages/components/section-conversation-secrets.tsx index fd7e3149..52ac7837 100644 --- a/src/features/dashboard-messages/components/section-conversation-secrets.tsx +++ b/src/features/dashboard-messages/components/section-conversation-secrets.tsx @@ -1,16 +1,16 @@ -import { IllustrationDone } from "@stacklok/ui-kit"; -import { isAlertSecret } from "@/lib/is-alert-secret"; -import { ConversationSecretsDetected } from "./conversation-secrets-detected"; -import { EmptyState } from "@/components/empty-state"; -import { emptyStateStrings } from "../../../constants/empty-state-strings"; -import { Conversation } from "@/api/generated"; +import { IllustrationDone } from '@stacklok/ui-kit' +import { isAlertSecret } from '@/lib/is-alert-secret' +import { ConversationSecretsDetected } from './conversation-secrets-detected' +import { EmptyState } from '@/components/empty-state' +import { emptyStateStrings } from '../../../constants/empty-state-strings' +import { Conversation } from '@/api/generated' export function SectionConversationSecrets({ conversation, }: { - conversation: Conversation; + conversation: Conversation }) { - const secrets = conversation.alerts?.filter(isAlertSecret) ?? []; + const secrets = conversation.alerts?.filter(isAlertSecret) ?? [] if (secrets.length === 0) return ( @@ -20,10 +20,10 @@ export function SectionConversationSecrets({ illustration={IllustrationDone} actions={null} /> - ); + ) return ( -
    +

    CodeGate helps you protect sensitive information from being accidentally exposed to AI models and third-party AI provider systems by redacting @@ -36,5 +36,5 @@ export function SectionConversationSecrets({

    - ); + ) } diff --git a/src/features/dashboard-messages/components/section-conversation-transcript.tsx b/src/features/dashboard-messages/components/section-conversation-transcript.tsx index 8a56c41b..7b922aef 100644 --- a/src/features/dashboard-messages/components/section-conversation-transcript.tsx +++ b/src/features/dashboard-messages/components/section-conversation-transcript.tsx @@ -1,51 +1,51 @@ -import { Markdown } from "@/components/Markdown"; +import { Markdown } from '@/components/Markdown' import { ChatBubble, ChatBubbleAvatar, ChatBubbleMessage, -} from "@/components/ui/chat/chat-bubble"; -import { ChatMessageList } from "@/components/ui/chat/chat-message-list"; -import { sanitizeQuestionPrompt } from "@/lib/utils"; -import { Heading } from "@stacklok/ui-kit"; -import { ConversationSummary } from "./conversation-summary"; -import { Conversation } from "@/api/generated"; +} from '@/components/ui/chat/chat-bubble' +import { ChatMessageList } from '@/components/ui/chat/chat-message-list' +import { sanitizeQuestionPrompt } from '@/lib/utils' +import { Heading } from '@stacklok/ui-kit' +import { ConversationSummary } from './conversation-summary' +import { Conversation } from '@/api/generated' export function SectionConversationTranscript({ conversation, }: { - conversation: Conversation; + conversation: Conversation }) { return ( <>
    - + Conversation summary
    - + Conversation transcript {(conversation?.question_answers ?? []).map( ({ question, answer }, index) => ( -
    +
    {sanitizeQuestionPrompt({ - question: question?.message ?? "", - answer: answer?.message ?? "", + question: question?.message ?? '', + answer: answer?.message ?? '', })} @@ -53,14 +53,14 @@ export function SectionConversationTranscript({ - {answer?.message ?? ""} + {answer?.message ?? ''}
    - ), + ) )}
    - ); + ) } diff --git a/src/features/dashboard-messages/components/table-alert-token-usage.tsx b/src/features/dashboard-messages/components/table-alert-token-usage.tsx index b13ae3bb..a0094ed1 100644 --- a/src/features/dashboard-messages/components/table-alert-token-usage.tsx +++ b/src/features/dashboard-messages/components/table-alert-token-usage.tsx @@ -1,18 +1,18 @@ -import { TokenUsageAggregate } from "@/api/generated"; -import { TextLinkButton, Tooltip, TooltipTrigger } from "@stacklok/ui-kit"; -import { Download01, Upload01 } from "@untitled-ui/icons-react"; -import { TokenUsageByProviders } from "./token-usage-by-providers"; -import { formatNumberCompact } from "@/lib/format-number"; +import { TokenUsageAggregate } from '@/api/generated' +import { TextLinkButton, Tooltip, TooltipTrigger } from '@stacklok/ui-kit' +import { Download01, Upload01 } from '@untitled-ui/icons-react' +import { TokenUsageByProviders } from './token-usage-by-providers' +import { formatNumberCompact } from '@/lib/format-number' function Icons({ input_tokens = 0, output_tokens = 0, }: { - input_tokens: number | null; - output_tokens: number | null; + input_tokens: number | null + output_tokens: number | null }) { return ( -
    +
    {formatNumberCompact(input_tokens ?? 0)} @@ -22,15 +22,15 @@ function Icons({ {formatNumberCompact(output_tokens ?? 0)}
    - ); + ) } export function TableAlertTokenUsage({ usage, }: { - usage: TokenUsageAggregate | null; + usage: TokenUsageAggregate | null }) { - if (!usage) return "N/A"; + if (!usage) return 'N/A' return ( @@ -47,5 +47,5 @@ export function TableAlertTokenUsage({ /> - ); + ) } diff --git a/src/features/dashboard-messages/components/table-messages-empty-state.tsx b/src/features/dashboard-messages/components/table-messages-empty-state.tsx index 8e6e358a..cc378f72 100644 --- a/src/features/dashboard-messages/components/table-messages-empty-state.tsx +++ b/src/features/dashboard-messages/components/table-messages-empty-state.tsx @@ -6,21 +6,21 @@ import { IllustrationNoSearchResults, LinkButton, Loader, -} from "@stacklok/ui-kit"; -import { ReactNode } from "react"; +} from '@stacklok/ui-kit' +import { ReactNode } from 'react' -import { emptyStateStrings } from "../../../constants/empty-state-strings"; -import { EmptyState } from "@/components/empty-state"; -import { hrefs } from "@/lib/hrefs"; -import { LinkExternal02 } from "@untitled-ui/icons-react"; -import { useListAllWorkspaces } from "@/hooks/use-query-list-all-workspaces"; +import { emptyStateStrings } from '../../../constants/empty-state-strings' +import { EmptyState } from '@/components/empty-state' +import { hrefs } from '@/lib/hrefs' +import { LinkExternal02 } from '@untitled-ui/icons-react' +import { useListAllWorkspaces } from '@/hooks/use-query-list-all-workspaces' import { AlertsFilterView, useMessagesFilterSearchParams, -} from "../hooks/use-messages-filter-search-params"; -import { match, P } from "ts-pattern"; -import { useQueryGetWorkspaceMessages } from "@/hooks/use-query-get-workspace-messages"; -import { twMerge } from "tailwind-merge"; +} from '../hooks/use-messages-filter-search-params' +import { match, P } from 'ts-pattern' +import { useQueryGetWorkspaceMessages } from '@/hooks/use-query-get-workspace-messages' +import { twMerge } from 'tailwind-merge' function EmptyStateLoading() { return ( @@ -28,11 +28,11 @@ function EmptyStateLoading() { title={emptyStateStrings.title.loading} body={emptyStateStrings.body.loading} illustration={(props) => ( - + )} actions={null} /> - ); + ) } function EmptyStateGetStarted() { @@ -53,15 +53,15 @@ function EmptyStateGetStarted() { , ]} /> - ); + ) } function EmptyStateSearch({ search, setSearch, }: { - search: string; - setSearch: (v: string | null) => void; + search: string + setSearch: (v: string | null) => void }) { return ( , ]} /> - ); + ) } function EmptyStateNoMessagesInWorkspace() { @@ -95,7 +95,7 @@ function EmptyStateNoMessagesInWorkspace() { , ]} /> - ); + ) } function EmptyStateMalicious() { @@ -106,7 +106,7 @@ function EmptyStateMalicious() { illustration={IllustrationDone} actions={null} /> - ); + ) } function EmptyStateSecrets() { @@ -117,7 +117,7 @@ function EmptyStateSecrets() { illustration={IllustrationDone} actions={null} /> - ); + ) } export function EmptyStateError() { @@ -149,33 +149,33 @@ export function EmptyStateError() { , ]} /> - ); + ) } type MatchInput = { - isLoading: boolean; - hasWorkspaceMessages: boolean; - hasMultipleWorkspaces: boolean; - search: string | null; - view: AlertsFilterView | null; -}; + isLoading: boolean + hasWorkspaceMessages: boolean + hasMultipleWorkspaces: boolean + search: string | null + view: AlertsFilterView | null +} export function TableMessagesEmptyState() { - const { state, setSearch } = useMessagesFilterSearchParams(); + const { state, setSearch } = useMessagesFilterSearchParams() const { data: messages = [], isLoading: isMessagesLoading } = - useQueryGetWorkspaceMessages(); + useQueryGetWorkspaceMessages() const { data: workspaces = [], isLoading: isWorkspacesLoading } = - useListAllWorkspaces(); + useListAllWorkspaces() - const isLoading = isMessagesLoading || isWorkspacesLoading; + const isLoading = isMessagesLoading || isWorkspacesLoading return match({ isLoading, hasWorkspaceMessages: messages.length > 0, hasMultipleWorkspaces: - workspaces.filter((w) => w.name !== "default").length > 0, + workspaces.filter((w) => w.name !== 'default').length > 0, search: state.search || null, view: state.view, }) @@ -187,7 +187,7 @@ export function TableMessagesEmptyState() { view: P.any, isLoading: false, }, - () => , + () => ) .with( { @@ -197,7 +197,7 @@ export function TableMessagesEmptyState() { view: P.any, isLoading: false, }, - (search) => , + (search) => ) .with( { @@ -207,7 +207,7 @@ export function TableMessagesEmptyState() { view: P.any, isLoading: false, }, - () => , + () => ) .with( { @@ -217,7 +217,7 @@ export function TableMessagesEmptyState() { view: AlertsFilterView.MALICIOUS, isLoading: false, }, - () => , + () => ) .with( { @@ -226,7 +226,7 @@ export function TableMessagesEmptyState() { view: AlertsFilterView.SECRETS, isLoading: false, }, - () => , + () => ) - .otherwise(() => ); + .otherwise(() => ) } diff --git a/src/features/dashboard-messages/components/table-messages.tsx b/src/features/dashboard-messages/components/table-messages.tsx index 166238e0..d082251a 100644 --- a/src/features/dashboard-messages/components/table-messages.tsx +++ b/src/features/dashboard-messages/components/table-messages.tsx @@ -9,54 +9,54 @@ import { ResizableTableContainer, Tooltip, TooltipTrigger, -} from "@stacklok/ui-kit"; -import { Alert, Conversation, QuestionType } from "@/api/generated"; +} from '@stacklok/ui-kit' +import { Alert, Conversation, QuestionType } from '@/api/generated' -import { useClientSidePagination } from "@/hooks/useClientSidePagination"; -import { TableAlertTokenUsage } from "./table-alert-token-usage"; +import { useClientSidePagination } from '@/hooks/useClientSidePagination' +import { TableAlertTokenUsage } from './table-alert-token-usage' -import { useMessagesFilterSearchParams } from "../hooks/use-messages-filter-search-params"; -import { Key01, PackageX } from "@untitled-ui/icons-react"; +import { useMessagesFilterSearchParams } from '../hooks/use-messages-filter-search-params' +import { Key01, PackageX } from '@untitled-ui/icons-react' import { EmptyStateError, TableMessagesEmptyState, -} from "./table-messages-empty-state"; -import { hrefs } from "@/lib/hrefs"; -import { isAlertMalicious } from "../../../lib/is-alert-malicious"; -import { isAlertSecret } from "../../../lib/is-alert-secret"; -import { twMerge } from "tailwind-merge"; -import { useQueryGetWorkspaceMessagesTable } from "../hooks/use-query-get-workspace-messages-table"; +} from './table-messages-empty-state' +import { hrefs } from '@/lib/hrefs' +import { isAlertMalicious } from '../../../lib/is-alert-malicious' +import { isAlertSecret } from '../../../lib/is-alert-secret' +import { twMerge } from 'tailwind-merge' +import { useQueryGetWorkspaceMessagesTable } from '../hooks/use-query-get-workspace-messages-table' import { TABLE_MESSAGES_COLUMNS, TableMessagesColumn, -} from "../constants/table-messages-columns"; -import { formatTime } from "@/lib/format-time"; +} from '../constants/table-messages-columns' +import { formatTime } from '@/lib/format-time' const getPromptText = (conversation: Conversation) => { - return (conversation.question_answers[0]?.question?.message ?? "N/A") + return (conversation.question_answers[0]?.question?.message ?? 'N/A') .trim() - .slice(0, 200); // arbitrary slice to prevent long prompts -}; + .slice(0, 200) // arbitrary slice to prevent long prompts +} function getTypeText(type: QuestionType) { switch (type) { case QuestionType.CHAT: - return "Chat"; + return 'Chat' case QuestionType.FIM: - return "Fill in the middle (FIM)"; + return 'Fill in the middle (FIM)' default: - return "Unknown"; + return 'Unknown' } } function countAlerts(alerts: Alert[]): { - secrets: number; - malicious: number; + secrets: number + malicious: number } { return { secrets: alerts.filter(isAlertSecret).length, malicious: alerts.filter(isAlertMalicious).length, - }; + } } function AlertsSummaryCount({ @@ -64,14 +64,14 @@ function AlertsSummaryCount({ icon: Icon, strings, }: { - count: number; - icon: (props: React.SVGProps) => React.JSX.Element; + count: number + icon: (props: React.SVGProps) => React.JSX.Element strings: { - singular: string; - plural: string; - }; + singular: string + plural: string + } }) { - const tooltipText = `${count} ${count === 1 ? strings.singular : strings.plural} detected`; + const tooltipText = `${count} ${count === 1 ? strings.singular : strings.plural} detected` return ( @@ -80,8 +80,8 @@ function AlertsSummaryCount({ variant="tertiary" isIcon className={twMerge( - "flex gap-1 items-center", - count > 0 ? "text-secondary" : "text-disabled", + 'flex items-center gap-1', + count > 0 ? 'text-secondary' : 'text-disabled' )} > @@ -89,71 +89,71 @@ function AlertsSummaryCount({ {tooltipText} - ); + ) } function AlertsSummaryCellContent({ alerts }: { alerts: Alert[] }) { - const { malicious, secrets } = countAlerts(alerts); + const { malicious, secrets } = countAlerts(alerts) return ( -
    +
    - ); + ) } function CellRenderer({ column, row, }: { - column: TableMessagesColumn; - row: Conversation; + column: TableMessagesColumn + row: Conversation }) { switch (column.id) { - case "time": + case 'time': return ( {formatTime(new Date(row.conversation_timestamp))} - ); - case "type": - return getTypeText(row.type); - case "prompt": - return getPromptText(row); - case "alerts": - return ; - case "token_usage": - return ; + ) + case 'type': + return getTypeText(row.type) + case 'prompt': + return getPromptText(row) + case 'alerts': + return + case 'token_usage': + return default: - return column.id satisfies never; + return column.id satisfies never } } export function TableMessages() { - const { state, prevPage, nextPage } = useMessagesFilterSearchParams(); + const { state, prevPage, nextPage } = useMessagesFilterSearchParams() - const { data = [], isError } = useQueryGetWorkspaceMessagesTable(); + const { data = [], isError } = useQueryGetWorkspaceMessagesTable() const { dataView, hasNextPage, hasPreviousPage } = useClientSidePagination( data, state.page, - 15, - ); + 15 + ) return ( <> @@ -164,9 +164,9 @@ export function TableMessages() { { - if (isError) return ; + if (isError) return - return ; + return }} items={dataView} > @@ -179,7 +179,7 @@ export function TableMessages() { > {(column) => ( @@ -193,7 +193,7 @@ export function TableMessages() { {hasNextPage || hasPreviousPage ? ( -
    +
    @@ -78,7 +78,7 @@ export function HeaderActiveWorkspaceSelector() { items={filteredWorkspaces} selectedKeys={activeWorkspaceName ? [activeWorkspaceName] : []} onAction={(v) => { - handleWorkspaceClick(v?.toString()); + handleWorkspaceClick(v?.toString()) }} className="-mx-1 my-2 max-h-80 overflow-auto" renderEmptyState={() => ( @@ -92,11 +92,12 @@ export function HeaderActiveWorkspaceSelector() { textValue={item.name} data-is-selected={item.name === activeWorkspaceName} className={clsx( - "grid grid-cols-[auto_1.5rem] group/selector cursor-pointer py-2 m-1 text-base hover:bg-gray-200 rounded-sm", + `group/selector m-1 grid cursor-pointer grid-cols-[auto_1.5rem] rounded-sm py-2 + text-base hover:bg-gray-200`, { - "!bg-gray-900 hover:bg-gray-900 !text-gray-25 hover:!text-gray-25": + '!bg-gray-900 !text-gray-25 hover:bg-gray-900 hover:!text-gray-25': item.is_active, - }, + } )} > {item.name} @@ -107,15 +108,15 @@ export function HeaderActiveWorkspaceSelector() { isIcon variant="tertiary" className={twMerge( - "ml-auto size-6 group-hover/selector:opacity-100 opacity-0 transition-opacity", + 'ml-auto size-6 opacity-0 transition-opacity group-hover/selector:opacity-100', item.is_active - ? "hover:bg-gray-800 pressed:bg-gray-700" - : "hover:bg-gray-50 hover:text-primary", + ? 'hover:bg-gray-800 pressed:bg-gray-700' + : 'hover:bg-gray-50 hover:text-primary' )} > @@ -127,7 +128,7 @@ export function HeaderActiveWorkspaceSelector() { href="/workspaces" onPress={() => setIsOpen(false)} variant="tertiary" - className="text-secondary h-10 pl-2 gap-2 flex mt-2 justify-start" + className="mt-2 flex h-10 justify-start gap-2 pl-2 text-secondary" > Manage Workspaces @@ -135,5 +136,5 @@ export function HeaderActiveWorkspaceSelector() {
    - ); + ) } diff --git a/src/features/header/components/header-status-menu.tsx b/src/features/header/components/header-status-menu.tsx index 769019eb..98d47317 100644 --- a/src/features/header/components/header-status-menu.tsx +++ b/src/features/header/components/header-status-menu.tsx @@ -1,4 +1,4 @@ -import { useQueriesCodegateStatus } from "../hooks/use-queries-codegate-status"; +import { useQueriesCodegateStatus } from '../hooks/use-queries-codegate-status' import { Button, DialogTrigger, @@ -7,114 +7,114 @@ import { Popover, Tooltip, TooltipTrigger, -} from "@stacklok/ui-kit"; -import { Dialog } from "react-aria-components"; +} from '@stacklok/ui-kit' +import { Dialog } from 'react-aria-components' -import { ReactNode } from "react"; +import { ReactNode } from 'react' import { AlertCircle, AlertTriangle, CheckCircle, ShieldOff, ShieldTick, -} from "@untitled-ui/icons-react"; +} from '@untitled-ui/icons-react' type CodeGateStatus = - | "healthy" - | "update_available" - | "unhealthy" - | "loading" - | "error_checking_status"; + | 'healthy' + | 'update_available' + | 'unhealthy' + | 'loading' + | 'error_checking_status' type CodeGateVersionStatus = - | "up_to_date" - | "update_available" - | "loading" - | "error_checking_version"; + | 'up_to_date' + | 'update_available' + | 'loading' + | 'error_checking_version' type CodeGateHealthCheckStatus = - | "healthy" - | "unhealthy" - | "loading" - | "error_checking_health"; + | 'healthy' + | 'unhealthy' + | 'loading' + | 'error_checking_health' function deriveOverallStatus( - data: ReturnType["data"], + data: ReturnType['data'], isPending: boolean, - isError: boolean, + isError: boolean ): CodeGateStatus { - if (isPending) return "loading"; - if (isError) return "error_checking_status"; + if (isPending) return 'loading' + if (isError) return 'error_checking_status' if ( - data?.health?.status === "healthy" && + data?.health?.status === 'healthy' && data.version?.error === null && data.version?.is_latest === false ) - return "update_available"; + return 'update_available' - if (data?.health?.status === "healthy") return "healthy"; + if (data?.health?.status === 'healthy') return 'healthy' - return "unhealthy"; + return 'unhealthy' } function deriveVersionStatus( - data: ReturnType["data"], + data: ReturnType['data'], isPending: boolean, - isError: boolean, + isError: boolean ): CodeGateVersionStatus { - if (isPending) return "loading"; - if (isError || data?.version?.error) return "error_checking_version"; + if (isPending) return 'loading' + if (isError || data?.version?.error) return 'error_checking_version' - if (data?.version?.is_latest === false) return "update_available"; - return "up_to_date"; + if (data?.version?.is_latest === false) return 'update_available' + return 'up_to_date' } function deriveHealthCheckStatus( - data: ReturnType["data"], + data: ReturnType['data'], isPending: boolean, - isError: boolean, + isError: boolean ): CodeGateHealthCheckStatus { - if (isPending) return "loading"; - if (isError) return "error_checking_health"; + if (isPending) return 'loading' + if (isError) return 'error_checking_health' - if (data?.health?.status === "healthy") return "healthy"; - return "unhealthy"; + if (data?.health?.status === 'healthy') return 'healthy' + return 'unhealthy' } function getButtonText(status: CodeGateStatus): string { switch (status) { - case "error_checking_status": - return "Error"; - case "healthy": - return "Service healthy"; - case "loading": - return "Loading"; - case "unhealthy": - return "Service unhealthy"; - case "update_available": - return "Update available"; + case 'error_checking_status': + return 'Error' + case 'healthy': + return 'Service healthy' + case 'loading': + return 'Loading' + case 'unhealthy': + return 'Service unhealthy' + case 'update_available': + return 'Update available' default: - return status satisfies never; + return status satisfies never } } function getVersionText( status: CodeGateVersionStatus, - data: ReturnType["data"], + data: ReturnType['data'] ): ReactNode { switch (status) { - case "error_checking_version": - return "Error"; - case "loading": - return "Loading"; - case "up_to_date": - return "Up to date"; - case "update_available": + case 'error_checking_version': + return 'Error' + case 'loading': + return 'Loading' + case 'up_to_date': + return 'Up to date' + case 'update_available': return ( - ); + ) default: - return status satisfies never; + return status satisfies never } } function getHealthCheckText(status: CodeGateHealthCheckStatus): string { switch (status) { - case "healthy": - return "Healthy"; - case "loading": - return "Loading"; - case "error_checking_health": - return "Error"; - case "unhealthy": - return "Unhealthy"; + case 'healthy': + return 'Healthy' + case 'loading': + return 'Loading' + case 'error_checking_health': + return 'Error' + case 'unhealthy': + return 'Unhealthy' default: - return status satisfies never; + return status satisfies never } } @@ -156,22 +156,22 @@ function ButtonIcon({ status, className, }: { - status: CodeGateStatus; - className?: string; + status: CodeGateStatus + className?: string }) { switch (status) { - case "error_checking_status": - return ; - case "healthy": - return ; - case "loading": - return ; - case "unhealthy": - return ; - case "update_available": - return ; + case 'error_checking_status': + return + case 'healthy': + return + case 'loading': + return + case 'unhealthy': + return + case 'update_available': + return default: - return status satisfies never; + return status satisfies never } } @@ -179,19 +179,19 @@ function HealthCheckIcon({ healthCheckStatus, className, }: { - healthCheckStatus: CodeGateHealthCheckStatus; - className?: string; + healthCheckStatus: CodeGateHealthCheckStatus + className?: string }): ReactNode { switch (healthCheckStatus) { - case "error_checking_health": - case "unhealthy": - return ; - case "healthy": - return ; - case "loading": - return ; + case 'error_checking_health': + case 'unhealthy': + return + case 'healthy': + return + case 'loading': + return default: - return healthCheckStatus satisfies never; + return healthCheckStatus satisfies never } } @@ -199,20 +199,20 @@ function VersionIcon({ versionStatus: versionStatus, className, }: { - versionStatus: CodeGateVersionStatus; - className?: string; + versionStatus: CodeGateVersionStatus + className?: string }) { switch (versionStatus) { - case "error_checking_version": - return ; - case "update_available": - return ; - case "up_to_date": - return ; - case "loading": - return ; + case 'error_checking_version': + return + case 'update_available': + return + case 'up_to_date': + return + case 'loading': + return default: - return versionStatus satisfies never; + return versionStatus satisfies never } } @@ -220,19 +220,19 @@ function StatusMenuTrigger({ status, isPending, }: { - status: CodeGateStatus; - isPending: boolean; + status: CodeGateStatus + isPending: boolean }) { return ( - - ); + ) } function Row({ @@ -240,18 +240,18 @@ function Row({ value, icon, }: { - title: string; - value: ReactNode; - icon: ReactNode; + title: string + value: ReactNode + icon: ReactNode }) { return ( -
    +
    {title}
    -
    +
    {value} {icon}
    - ); + ) } function StatusPopover({ @@ -259,13 +259,13 @@ function StatusPopover({ healthCheckStatus, data, }: { - versionStatus: CodeGateVersionStatus; - healthCheckStatus: CodeGateHealthCheckStatus; - data: ReturnType["data"]; + versionStatus: CodeGateVersionStatus + healthCheckStatus: CodeGateHealthCheckStatus + data: ReturnType['data'] }) { return ( - - + + - ); + ) } export function HeaderStatusMenu() { - const { data, isPending, isError } = useQueriesCodegateStatus(); + const { data, isPending, isError } = useQueriesCodegateStatus() - const status = deriveOverallStatus(data, isPending, isError); - const versionStatus = deriveVersionStatus(data, isPending, isError); - const healthCheckStatus = deriveHealthCheckStatus(data, isPending, isError); + const status = deriveOverallStatus(data, isPending, isError) + const versionStatus = deriveVersionStatus(data, isPending, isError) + const healthCheckStatus = deriveHealthCheckStatus(data, isPending, isError) return ( @@ -304,5 +304,5 @@ export function HeaderStatusMenu() { data={data} /> - ); + ) } diff --git a/src/features/header/components/header.tsx b/src/features/header/components/header.tsx index 6ae077d1..2e5b56ad 100644 --- a/src/features/header/components/header.tsx +++ b/src/features/header/components/header.tsx @@ -1,42 +1,43 @@ -import { Link } from "react-router-dom"; +import { Link } from 'react-router-dom' -import { DropdownMenu } from "../../../components/HoverPopover"; -import { Separator, ButtonDarkMode } from "@stacklok/ui-kit"; -import { HeaderActiveWorkspaceSelector } from "@/features/header/components/header-active-workspace-selector"; -import { HELP_MENU_ITEMS } from "../constants/help-menu-items"; -import { HeaderStatusMenu } from "./header-status-menu"; -import { SETTINGS_MENU_ITEMS } from "../constants/settings-menu-items"; +import { DropdownMenu } from '../../../components/HoverPopover' +import { Separator, ButtonDarkMode } from '@stacklok/ui-kit' +import { HeaderActiveWorkspaceSelector } from '@/features/header/components/header-active-workspace-selector' +import { HELP_MENU_ITEMS } from '../constants/help-menu-items' +import { HeaderStatusMenu } from './header-status-menu' +import { SETTINGS_MENU_ITEMS } from '../constants/settings-menu-items' function HomeLink() { return ( -

    +

    CodeGate

    - ); + ) } export function Header() { return (
    -
    -
    - ); + ) } diff --git a/src/features/header/constants/help-menu-items.tsx b/src/features/header/constants/help-menu-items.tsx index 416f8ac4..b9ce06e3 100644 --- a/src/features/header/constants/help-menu-items.tsx +++ b/src/features/header/constants/help-menu-items.tsx @@ -1,66 +1,60 @@ -import { - Continue, - Copilot, - Discord, - Github, - Youtube, -} from "@/components/icons"; -import { OptionsSchema } from "@stacklok/ui-kit"; -import { BookOpen01 } from "@untitled-ui/icons-react"; +import { Continue, Copilot, Discord, Github, Youtube } from '@/components/icons' +import { OptionsSchema } from '@stacklok/ui-kit' +import { BookOpen01 } from '@untitled-ui/icons-react' export const HELP_MENU_ITEMS = [ { - textValue: "Getting started", - id: "setup", + textValue: 'Getting started', + id: 'setup', items: [ { icon: , - id: "continue-setup", - href: "https://docs.codegate.ai/how-to/use-with-continue", - textValue: "Use with Continue", - target: "_blank", + id: 'continue-setup', + href: 'https://docs.codegate.ai/how-to/use-with-continue', + textValue: 'Use with Continue', + target: '_blank', }, { icon: , - id: "copilot-setup", - href: "https://docs.codegate.ai/how-to/use-with-copilot", - textValue: "Use with Copilot", - target: "_blank", + id: 'copilot-setup', + href: 'https://docs.codegate.ai/how-to/use-with-copilot', + textValue: 'Use with Copilot', + target: '_blank', }, { icon: , - id: "documentation", - href: "https://docs.codegate.ai/", - textValue: "Documentation", - target: "_blank", + id: 'documentation', + href: 'https://docs.codegate.ai/', + textValue: 'Documentation', + target: '_blank', }, ], }, { - textValue: "Resources", - id: "resources", + textValue: 'Resources', + id: 'resources', items: [ { icon: , - id: "discord", - href: "https://discord.gg/stacklok", - textValue: "Discord", - target: "_blank", + id: 'discord', + href: 'https://discord.gg/stacklok', + textValue: 'Discord', + target: '_blank', }, { icon: , - id: "github", - href: "https://github.com/stacklok/codegate", - textValue: "GitHub", - target: "_blank", + id: 'github', + href: 'https://github.com/stacklok/codegate', + textValue: 'GitHub', + target: '_blank', }, { icon: , - id: "youtube", - href: "https://www.youtube.com/@Stacklok", - textValue: "YouTube", - target: "_blank", + id: 'youtube', + href: 'https://www.youtube.com/@Stacklok', + textValue: 'YouTube', + target: '_blank', }, ], }, -] as const satisfies OptionsSchema<"menu">[]; +] as const satisfies OptionsSchema<'menu'>[] diff --git a/src/features/header/constants/settings-menu-items.tsx b/src/features/header/constants/settings-menu-items.tsx index f85a70f2..40d64757 100644 --- a/src/features/header/constants/settings-menu-items.tsx +++ b/src/features/header/constants/settings-menu-items.tsx @@ -1,39 +1,35 @@ -import { OptionsSchema } from "@stacklok/ui-kit"; -import { - Download01, - LayersThree01, - ShieldTick, -} from "@untitled-ui/icons-react"; +import { OptionsSchema } from '@stacklok/ui-kit' +import { Download01, LayersThree01, ShieldTick } from '@untitled-ui/icons-react' export const SETTINGS_MENU_ITEMS = [ { - textValue: "Providers", - id: "providers", + textValue: 'Providers', + id: 'providers', items: [ { icon: , - id: "providers", - href: "/providers", - textValue: "Providers", + id: 'providers', + href: '/providers', + textValue: 'Providers', }, ], }, { - textValue: "Certificates", - id: "certificates", + textValue: 'Certificates', + id: 'certificates', items: [ { icon: , - id: "about-certificate-security", - href: "/certificates/security", - textValue: "About certificate security", + id: 'about-certificate-security', + href: '/certificates/security', + textValue: 'About certificate security', }, { icon: , - id: "download-certificates", - href: "/certificates", - textValue: "Download certificates", + id: 'download-certificates', + href: '/certificates', + textValue: 'Download certificates', }, ], }, -] as const satisfies OptionsSchema<"menu">[]; +] as const satisfies OptionsSchema<'menu'>[] diff --git a/src/features/header/hooks/use-queries-codegate-status.ts b/src/features/header/hooks/use-queries-codegate-status.ts index bffb4652..62317be7 100644 --- a/src/features/header/hooks/use-queries-codegate-status.ts +++ b/src/features/header/hooks/use-queries-codegate-status.ts @@ -1,28 +1,28 @@ -import { useQueries } from "@tanstack/react-query"; +import { useQueries } from '@tanstack/react-query' import { HealthCheckHealthGetResponse, V1VersionCheckResponse, -} from "@/api/generated"; -import { VersionResponse } from "../types"; -import { getQueryCacheConfig } from "@/lib/react-query-utils"; +} from '@/api/generated' +import { VersionResponse } from '../types' +import { getQueryCacheConfig } from '@/lib/react-query-utils' import { healthCheckHealthGetOptions, v1VersionCheckOptions, -} from "@/api/generated/@tanstack/react-query.gen"; -import { QueryResult } from "@/types/react-query"; +} from '@/api/generated/@tanstack/react-query.gen' +import { QueryResult } from '@/types/react-query' type UseQueryReturn = [ QueryResult, QueryResult, -]; +] const combine = (results: UseQueryReturn) => { - const [health, version] = results; + const [health, version] = results return { data: { - health: health.data as { status: "healthy" } | null, + health: health.data as { status: 'healthy' } | null, version: version.data as VersionResponse | null, }, isError: results.some((r) => r.isError), @@ -30,8 +30,8 @@ const combine = (results: UseQueryReturn) => { isFetching: results.some((r) => r.isFetching), isLoading: results.some((r) => r.isLoading), isRefetching: results.some((r) => r.isRefetching), - }; -}; + } +} export const useQueriesCodegateStatus = () => { return useQueries({ @@ -42,15 +42,15 @@ export const useQueriesCodegateStatus = () => { refetchInterval: 60_000, refetchIntervalInBackground: true, retry: false, - ...getQueryCacheConfig("indefinite"), + ...getQueryCacheConfig('indefinite'), }, { ...v1VersionCheckOptions(), refetchInterval: 60_000, refetchIntervalInBackground: true, retry: false, - ...getQueryCacheConfig("indefinite"), + ...getQueryCacheConfig('indefinite'), }, ], - }); -}; + }) +} diff --git a/src/features/header/types.ts b/src/features/header/types.ts index 89f7d0ea..9394c81f 100644 --- a/src/features/header/types.ts +++ b/src/features/header/types.ts @@ -1,6 +1,6 @@ export type VersionResponse = { - current_version: string; - latest_version: string; - is_latest: boolean | null; - error: string | null; -} | null; + current_version: string + latest_version: string + is_latest: boolean | null + error: string | null +} | null diff --git a/src/features/providers/components/provider-dialog-footer.tsx b/src/features/providers/components/provider-dialog-footer.tsx index 3297c601..3910bd73 100644 --- a/src/features/providers/components/provider-dialog-footer.tsx +++ b/src/features/providers/components/provider-dialog-footer.tsx @@ -1,17 +1,17 @@ -import { Button, DialogFooter } from "@stacklok/ui-kit"; -import { useNavigate } from "react-router-dom"; +import { Button, DialogFooter } from '@stacklok/ui-kit' +import { useNavigate } from 'react-router-dom' export function ProviderDialogFooter() { - const navigate = useNavigate(); + const navigate = useNavigate() return ( - - ); + ) } diff --git a/src/features/providers/components/provider-dialog.tsx b/src/features/providers/components/provider-dialog.tsx index 44ae7de9..e6977fa9 100644 --- a/src/features/providers/components/provider-dialog.tsx +++ b/src/features/providers/components/provider-dialog.tsx @@ -5,24 +5,24 @@ import { DialogHeader, DialogTitle, DialogCloseButton, -} from "@stacklok/ui-kit"; -import { useNavigate } from "react-router-dom"; +} from '@stacklok/ui-kit' +import { useNavigate } from 'react-router-dom' export function ProviderDialog({ title, children, }: { - title: string; - children: React.ReactNode; + title: string + children: React.ReactNode }) { - const navigate = useNavigate(); + const navigate = useNavigate() return ( { - navigate("/providers"); + navigate('/providers') }} > @@ -35,5 +35,5 @@ export function ProviderDialog({
    - ); + ) } diff --git a/src/features/providers/components/provider-form.tsx b/src/features/providers/components/provider-form.tsx index d3531d1a..84833d4c 100644 --- a/src/features/providers/components/provider-form.tsx +++ b/src/features/providers/components/provider-form.tsx @@ -1,11 +1,5 @@ -import { AddProviderEndpointRequest, ProviderAuthType } from "@/api/generated"; -import { - Label, - Select, - SelectButton, - Input, - TextField, -} from "@stacklok/ui-kit"; +import { AddProviderEndpointRequest, ProviderAuthType } from '@/api/generated' +import { Label, Select, SelectButton, Input, TextField } from '@stacklok/ui-kit' import { getAuthTypeOptions, getProviderAuthByType, @@ -13,26 +7,26 @@ import { getProviderType, isProviderAuthType, isProviderType, -} from "../lib/utils"; +} from '../lib/utils' interface Props { - provider: AddProviderEndpointRequest; - setProvider: (provider: AddProviderEndpointRequest) => void; + provider: AddProviderEndpointRequest + setProvider: (provider: AddProviderEndpointRequest) => void } export function ProviderForm({ provider, setProvider }: Props) { const providerAuthType = - provider.auth_type || getProviderAuthByType(provider.provider_type); + provider.auth_type || getProviderAuthByType(provider.provider_type) const providerEndpoint = - provider.endpoint || getProviderEndpointByAuthType(provider.provider_type); + provider.endpoint || getProviderEndpointByAuthType(provider.provider_type) const handleProviderType = (provider: AddProviderEndpointRequest) => { setProvider({ ...provider, auth_type: getProviderAuthByType(provider.provider_type), endpoint: getProviderEndpointByAuthType(provider.provider_type), - }); - }; + }) + } return (
    @@ -63,7 +57,7 @@ export function ProviderForm({ provider, setProvider }: Props) { handleProviderType({ ...provider, provider_type, - }); + }) } }} > @@ -108,7 +102,7 @@ export function ProviderForm({ provider, setProvider }: Props) { items={getAuthTypeOptions()} onSelectionChange={(auth_type) => { if (isProviderAuthType(auth_type)) { - setProvider({ ...provider, auth_type }); + setProvider({ ...provider, auth_type }) } }} > @@ -130,14 +124,14 @@ export function ProviderForm({ provider, setProvider }: Props) {
    )}
    - ); + ) } diff --git a/src/features/providers/components/table-providers.tsx b/src/features/providers/components/table-providers.tsx index 6f4de724..a24bea52 100644 --- a/src/features/providers/components/table-providers.tsx +++ b/src/features/providers/components/table-providers.tsx @@ -9,53 +9,53 @@ import { TableHeader, ResizableTableContainer, Button, -} from "@stacklok/ui-kit"; -import { Globe02, Tool01, Trash01 } from "@untitled-ui/icons-react"; -import { PROVIDER_AUTH_TYPE_MAP } from "../lib/utils"; -import { useProviders } from "../hooks/use-providers"; -import { match } from "ts-pattern"; -import { ComponentProps } from "react"; -import { ProviderEndpoint } from "@/api/generated"; -import { useConfirmDeleteProvider } from "../hooks/use-confirm-delete-provider"; +} from '@stacklok/ui-kit' +import { Globe02, Tool01, Trash01 } from '@untitled-ui/icons-react' +import { PROVIDER_AUTH_TYPE_MAP } from '../lib/utils' +import { useProviders } from '../hooks/use-providers' +import { match } from 'ts-pattern' +import { ComponentProps } from 'react' +import { ProviderEndpoint } from '@/api/generated' +import { useConfirmDeleteProvider } from '../hooks/use-confirm-delete-provider' const COLUMN_MAP = { - provider: "provider", - type: "type", - endpoint: "endpoint", - auth: "auth", - configuration: "configuration", -} as const; + provider: 'provider', + type: 'type', + endpoint: 'endpoint', + auth: 'auth', + configuration: 'configuration', +} as const -type ColumnId = keyof typeof COLUMN_MAP; -type Column = { id: ColumnId } & Omit, "id">; +type ColumnId = keyof typeof COLUMN_MAP +type Column = { id: ColumnId } & Omit, 'id'> const COLUMNS: Column[] = [ { - id: "provider", + id: 'provider', isRowHeader: true, - children: "Name & Description", + children: 'Name & Description', minWidth: 450, maxWidth: 520, }, { - id: "type", - children: "Provider", + id: 'type', + children: 'Provider', minWidth: 110, maxWidth: 130, - className: "capitalize", + className: 'capitalize', }, - { id: "endpoint", children: "Endpoint", minWidth: 250 }, - { id: "auth", children: "Authentication", minWidth: 140 }, - { id: "configuration", alignment: "end", minWidth: 40, children: "" }, -]; + { id: 'endpoint', children: 'Endpoint', minWidth: 250 }, + { id: 'auth', children: 'Authentication', minWidth: 140 }, + { id: 'configuration', alignment: 'end', minWidth: 40, children: '' }, +] function CellRenderer({ column, row, deleteProvider, }: { - column: Column; - row: ProviderEndpoint; - deleteProvider: () => void; + column: Column + row: ProviderEndpoint + deleteProvider: () => void }) { return match(column.id) .with(COLUMN_MAP.provider, () => ( @@ -78,11 +78,11 @@ function CellRenderer({ {PROVIDER_AUTH_TYPE_MAP[row.auth_type]} ) : ( - "N/A" + 'N/A' )} Manage @@ -94,12 +94,12 @@ function CellRenderer({ )) - .exhaustive(); + .exhaustive() } export function TableProviders() { - const { data: providers = [] } = useProviders(); - const deleteProvider = useConfirmDeleteProvider(); + const { data: providers = [] } = useProviders() + const deleteProvider = useConfirmDeleteProvider() return ( @@ -123,7 +123,7 @@ export function TableProviders() { deleteProvider={() => { deleteProvider({ path: { provider_id: row.id as string }, - }); + }) }} /> @@ -133,5 +133,5 @@ export function TableProviders() { - ); + ) } diff --git a/src/features/providers/hooks/use-confirm-delete-provider.tsx b/src/features/providers/hooks/use-confirm-delete-provider.tsx index 362afd9c..3cce4bd6 100644 --- a/src/features/providers/hooks/use-confirm-delete-provider.tsx +++ b/src/features/providers/hooks/use-confirm-delete-provider.tsx @@ -1,11 +1,11 @@ -import { useConfirm } from "@/hooks/use-confirm"; -import { useCallback } from "react"; -import { useMutationDeleteProvider } from "./use-mutation-delete-provider"; +import { useConfirm } from '@/hooks/use-confirm' +import { useCallback } from 'react' +import { useMutationDeleteProvider } from './use-mutation-delete-provider' export function useConfirmDeleteProvider() { - const { mutateAsync: deleteProvider } = useMutationDeleteProvider(); + const { mutateAsync: deleteProvider } = useMutationDeleteProvider() - const { confirm } = useConfirm(); + const { confirm } = useConfirm() return useCallback( async (...params: Parameters) => { @@ -17,17 +17,17 @@ export function useConfirmDeleteProvider() { , { buttons: { - yes: "Delete", - no: "Cancel", + yes: 'Delete', + no: 'Cancel', }, - title: "Permanently delete provider", + title: 'Permanently delete provider', isDestructive: true, - }, - ); + } + ) if (answer) { - return deleteProvider(...params); + return deleteProvider(...params) } }, - [confirm, deleteProvider], - ); + [confirm, deleteProvider] + ) } diff --git a/src/features/providers/hooks/use-invalidate-providers-queries.ts b/src/features/providers/hooks/use-invalidate-providers-queries.ts index 0b7ec2c1..2967ab4c 100644 --- a/src/features/providers/hooks/use-invalidate-providers-queries.ts +++ b/src/features/providers/hooks/use-invalidate-providers-queries.ts @@ -1,14 +1,14 @@ -import { v1ListProviderEndpointsQueryKey } from "@/api/generated/@tanstack/react-query.gen"; -import { useQueryClient } from "@tanstack/react-query"; -import { useCallback } from "react"; -import { invalidateQueries } from "../../../lib/react-query-utils"; +import { v1ListProviderEndpointsQueryKey } from '@/api/generated/@tanstack/react-query.gen' +import { useQueryClient } from '@tanstack/react-query' +import { useCallback } from 'react' +import { invalidateQueries } from '../../../lib/react-query-utils' export function useInvalidateProvidersQueries() { - const queryClient = useQueryClient(); + const queryClient = useQueryClient() const invalidate = useCallback(async () => { - invalidateQueries(queryClient, [v1ListProviderEndpointsQueryKey]); - }, [queryClient]); + invalidateQueries(queryClient, [v1ListProviderEndpointsQueryKey]) + }, [queryClient]) - return invalidate; + return invalidate } diff --git a/src/features/providers/hooks/use-mutation-create-provider.ts b/src/features/providers/hooks/use-mutation-create-provider.ts index 563af163..25299122 100644 --- a/src/features/providers/hooks/use-mutation-create-provider.ts +++ b/src/features/providers/hooks/use-mutation-create-provider.ts @@ -1,17 +1,17 @@ -import { v1AddProviderEndpointMutation } from "@/api/generated/@tanstack/react-query.gen"; -import { useToastMutation } from "@/hooks/use-toast-mutation"; -import { useInvalidateProvidersQueries } from "./use-invalidate-providers-queries"; -import { useNavigate } from "react-router-dom"; +import { v1AddProviderEndpointMutation } from '@/api/generated/@tanstack/react-query.gen' +import { useToastMutation } from '@/hooks/use-toast-mutation' +import { useInvalidateProvidersQueries } from './use-invalidate-providers-queries' +import { useNavigate } from 'react-router-dom' export function useMutationCreateProvider() { - const navigate = useNavigate(); - const invalidate = useInvalidateProvidersQueries(); + const navigate = useNavigate() + const invalidate = useInvalidateProvidersQueries() return useToastMutation({ ...v1AddProviderEndpointMutation(), - successMsg: "Successfully added provider", + successMsg: 'Successfully added provider', onSuccess: async () => { - await invalidate(); - navigate("/providers"); + await invalidate() + navigate('/providers') }, - }); + }) } diff --git a/src/features/providers/hooks/use-mutation-delete-provider.ts b/src/features/providers/hooks/use-mutation-delete-provider.ts index 6dacf200..d25dc328 100644 --- a/src/features/providers/hooks/use-mutation-delete-provider.ts +++ b/src/features/providers/hooks/use-mutation-delete-provider.ts @@ -1,13 +1,13 @@ -import { useToastMutation } from "@/hooks/use-toast-mutation"; -import { useInvalidateProvidersQueries } from "./use-invalidate-providers-queries"; -import { v1DeleteProviderEndpointMutation } from "@/api/generated/@tanstack/react-query.gen"; +import { useToastMutation } from '@/hooks/use-toast-mutation' +import { useInvalidateProvidersQueries } from './use-invalidate-providers-queries' +import { v1DeleteProviderEndpointMutation } from '@/api/generated/@tanstack/react-query.gen' export const useMutationDeleteProvider = () => { - const invalidate = useInvalidateProvidersQueries(); + const invalidate = useInvalidateProvidersQueries() return useToastMutation({ ...v1DeleteProviderEndpointMutation(), onSuccess: () => invalidate(), - successMsg: () => "Successfully deleted provider", - }); -}; + successMsg: () => 'Successfully deleted provider', + }) +} diff --git a/src/features/providers/hooks/use-mutation-update-provider.ts b/src/features/providers/hooks/use-mutation-update-provider.ts index 9ad43429..1aba8fbe 100644 --- a/src/features/providers/hooks/use-mutation-update-provider.ts +++ b/src/features/providers/hooks/use-mutation-update-provider.ts @@ -1,32 +1,32 @@ -import { useToastMutation } from "@/hooks/use-toast-mutation"; -import { useNavigate } from "react-router-dom"; -import { useInvalidateProvidersQueries } from "./use-invalidate-providers-queries"; +import { useToastMutation } from '@/hooks/use-toast-mutation' +import { useNavigate } from 'react-router-dom' +import { useInvalidateProvidersQueries } from './use-invalidate-providers-queries' import { AddProviderEndpointRequest, ProviderAuthType, v1ConfigureAuthMaterial, v1UpdateProviderEndpoint, -} from "@/api/generated"; +} from '@/api/generated' export function useMutationUpdateProvider() { - const navigate = useNavigate(); - const invalidate = useInvalidateProvidersQueries(); + const navigate = useNavigate() + const invalidate = useInvalidateProvidersQueries() const mutationFn = async ({ api_key, ...rest }: AddProviderEndpointRequest) => { - const provider_id = rest.id; - if (!provider_id) throw new Error("Provider is missing"); + const provider_id = rest.id + if (!provider_id) throw new Error('Provider is missing') const updateProviderPromise = v1UpdateProviderEndpoint({ path: { provider_id }, body: rest, - }); + }) // don't update the api key if it's not updated if (!api_key && rest.auth_type === ProviderAuthType.API_KEY) { - return updateProviderPromise; + return updateProviderPromise } const updateApiKey = v1ConfigureAuthMaterial({ @@ -36,17 +36,17 @@ export function useMutationUpdateProvider() { auth_type: rest.auth_type as ProviderAuthType, }, throwOnError: true, - }); + }) - return Promise.all([updateApiKey, updateProviderPromise]); - }; + return Promise.all([updateApiKey, updateProviderPromise]) + } return useToastMutation({ mutationFn, - successMsg: "Successfully updated provider", + successMsg: 'Successfully updated provider', onSuccess: async () => { - await invalidate(); - navigate("/providers"); + await invalidate() + navigate('/providers') }, - }); + }) } diff --git a/src/features/providers/hooks/use-provider.ts b/src/features/providers/hooks/use-provider.ts index 76fd2de2..86a8722f 100644 --- a/src/features/providers/hooks/use-provider.ts +++ b/src/features/providers/hooks/use-provider.ts @@ -1,27 +1,27 @@ -import { useQuery } from "@tanstack/react-query"; -import { v1GetProviderEndpointOptions } from "@/api/generated/@tanstack/react-query.gen"; -import { AddProviderEndpointRequest, ProviderType } from "@/api/generated"; -import { useEffect, useState } from "react"; +import { useQuery } from '@tanstack/react-query' +import { v1GetProviderEndpointOptions } from '@/api/generated/@tanstack/react-query.gen' +import { AddProviderEndpointRequest, ProviderType } from '@/api/generated' +import { useEffect, useState } from 'react' export function useProvider(providerId: string) { const [provider, setProvider] = useState({ - name: "", - description: "", + name: '', + description: '', auth_type: undefined, provider_type: ProviderType.OPENAI, - endpoint: "", - api_key: "", - }); + endpoint: '', + api_key: '', + }) const { data, isPending, isError } = useQuery({ ...v1GetProviderEndpointOptions({ path: { provider_id: providerId } }), - }); + }) useEffect(() => { if (data) { - setProvider(data); + setProvider(data) } - }, [data]); + }, [data]) - return { isPending, isError, provider, setProvider }; + return { isPending, isError, provider, setProvider } } diff --git a/src/features/providers/hooks/use-providers.ts b/src/features/providers/hooks/use-providers.ts index 8da5a386..cdd6b8b8 100644 --- a/src/features/providers/hooks/use-providers.ts +++ b/src/features/providers/hooks/use-providers.ts @@ -1,8 +1,8 @@ -import { useQuery } from "@tanstack/react-query"; -import { v1ListProviderEndpointsOptions } from "@/api/generated/@tanstack/react-query.gen"; +import { useQuery } from '@tanstack/react-query' +import { v1ListProviderEndpointsOptions } from '@/api/generated/@tanstack/react-query.gen' export function useProviders() { return useQuery({ ...v1ListProviderEndpointsOptions(), - }); + }) } diff --git a/src/features/providers/lib/utils.ts b/src/features/providers/lib/utils.ts index 47280549..210415c8 100644 --- a/src/features/providers/lib/utils.ts +++ b/src/features/providers/lib/utils.ts @@ -1,44 +1,44 @@ -import { ProviderAuthType, ProviderType } from "@/api/generated"; -import { match } from "ts-pattern"; +import { ProviderAuthType, ProviderType } from '@/api/generated' +import { match } from 'ts-pattern' export const PROVIDER_AUTH_TYPE_MAP = { - [ProviderAuthType.NONE]: "None", - [ProviderAuthType.PASSTHROUGH]: "Passthrough", - [ProviderAuthType.API_KEY]: "API Key", -}; + [ProviderAuthType.NONE]: 'None', + [ProviderAuthType.PASSTHROUGH]: 'Passthrough', + [ProviderAuthType.API_KEY]: 'API Key', +} export function getAuthTypeOptions() { return Object.entries(PROVIDER_AUTH_TYPE_MAP).map(([id, textValue]) => ({ id, textValue, - })); + })) } export function getProviderType() { return Object.values(ProviderType).map((textValue) => ({ id: textValue, textValue, - })); + })) } export function isProviderType(value: unknown): value is ProviderType { - return Object.values(ProviderType).includes(value as ProviderType); + return Object.values(ProviderType).includes(value as ProviderType) } export function isProviderAuthType(value: unknown): value is ProviderAuthType { - return Object.values(ProviderAuthType).includes(value as ProviderAuthType); + return Object.values(ProviderAuthType).includes(value as ProviderAuthType) } export function getProviderEndpointByAuthType(provider_type: ProviderType) { return match(provider_type) - .with(ProviderType.OPENAI, () => "https://api.openai.com") - .with(ProviderType.ANTHROPIC, () => "https://api.anthropic.com") - .with(ProviderType.OPENROUTER, () => "https://openrouter.ai/api") - .with(ProviderType.OLLAMA, () => "http://host.docker.internal:11434") - .with(ProviderType.LM_STUDIO, () => "http://host.docker.internal:1234") - .with(ProviderType.LLAMACPP, () => "http://host.docker.internal:8080") - .with(ProviderType.VLLM, () => "http://host.docker.internal:8000") - .exhaustive(); + .with(ProviderType.OPENAI, () => 'https://api.openai.com') + .with(ProviderType.ANTHROPIC, () => 'https://api.anthropic.com') + .with(ProviderType.OPENROUTER, () => 'https://openrouter.ai/api') + .with(ProviderType.OLLAMA, () => 'http://host.docker.internal:11434') + .with(ProviderType.LM_STUDIO, () => 'http://host.docker.internal:1234') + .with(ProviderType.LLAMACPP, () => 'http://host.docker.internal:8080') + .with(ProviderType.VLLM, () => 'http://host.docker.internal:8000') + .exhaustive() } export function getProviderAuthByType(provider_type: ProviderType) { @@ -46,5 +46,5 @@ export function getProviderAuthByType(provider_type: ProviderType) { .with(ProviderType.OPENAI, () => ProviderAuthType.API_KEY) .with(ProviderType.ANTHROPIC, () => ProviderAuthType.API_KEY) .with(ProviderType.OPENROUTER, () => ProviderAuthType.API_KEY) - .otherwise(() => ProviderAuthType.NONE); + .otherwise(() => ProviderAuthType.NONE) } diff --git a/src/features/workspace/components/__tests__/archive-workspace.test.tsx b/src/features/workspace/components/__tests__/archive-workspace.test.tsx index 9a6b32de..cf22e21a 100644 --- a/src/features/workspace/components/__tests__/archive-workspace.test.tsx +++ b/src/features/workspace/components/__tests__/archive-workspace.test.tsx @@ -1,101 +1,101 @@ -import { render } from "@/lib/test-utils"; -import { ArchiveWorkspace } from "../archive-workspace"; -import userEvent from "@testing-library/user-event"; -import { waitFor } from "@testing-library/react"; -import { server } from "@/mocks/msw/node"; -import { http, HttpResponse } from "msw"; -import { mswEndpoint } from "@/test/msw-endpoint"; - -test("has correct buttons when not archived", async () => { +import { render } from '@/lib/test-utils' +import { ArchiveWorkspace } from '../archive-workspace' +import userEvent from '@testing-library/user-event' +import { waitFor } from '@testing-library/react' +import { server } from '@/mocks/msw/node' +import { http, HttpResponse } from 'msw' +import { mswEndpoint } from '@/test/msw-endpoint' + +test('has correct buttons when not archived', async () => { const { getByRole, queryByRole } = render( - , - ); + + ) - expect(getByRole("button", { name: /archive/i })).toBeVisible(); - expect(queryByRole("button", { name: /contextual help/i })).toBe(null); -}); + expect(getByRole('button', { name: /archive/i })).toBeVisible() + expect(queryByRole('button', { name: /contextual help/i })).toBe(null) +}) -test("has correct buttons when archived", async () => { +test('has correct buttons when archived', async () => { const { getByRole } = render( - , - ); - expect(getByRole("button", { name: /restore/i })).toBeVisible(); - expect(getByRole("button", { name: /permanently delete/i })).toBeVisible(); -}); + + ) + expect(getByRole('button', { name: /restore/i })).toBeVisible() + expect(getByRole('button', { name: /permanently delete/i })).toBeVisible() +}) -test("can archive workspace", async () => { +test('can archive workspace', async () => { const { getByText, getByRole } = render( - , - ); + + ) - await userEvent.click(getByRole("button", { name: /archive/i })); + await userEvent.click(getByRole('button', { name: /archive/i })) await waitFor(() => { - expect(getByText(/archived "foo-bar" workspace/i)).toBeVisible(); - }); -}); + expect(getByText(/archived "foo-bar" workspace/i)).toBeVisible() + }) +}) -test("can restore archived workspace", async () => { +test('can restore archived workspace', async () => { const { getByText, getByRole } = render( - , - ); + + ) - await userEvent.click(getByRole("button", { name: /restore/i })); + await userEvent.click(getByRole('button', { name: /restore/i })) await waitFor(() => { - expect(getByText(/restored "foo-bar" workspace/i)).toBeVisible(); - }); -}); + expect(getByText(/restored "foo-bar" workspace/i)).toBeVisible() + }) +}) -test("can permanently delete archived workspace", async () => { +test('can permanently delete archived workspace', async () => { const { getByText, getByRole } = render( - , - ); + + ) - await userEvent.click(getByRole("button", { name: /permanently delete/i })); + await userEvent.click(getByRole('button', { name: /permanently delete/i })) await waitFor(() => { - expect(getByRole("dialog", { name: /permanently delete/i })).toBeVisible(); - }); + expect(getByRole('dialog', { name: /permanently delete/i })).toBeVisible() + }) - await userEvent.click(getByRole("button", { name: /delete/i })); + await userEvent.click(getByRole('button', { name: /delete/i })) await waitFor(() => { - expect(getByText(/permanently deleted "foo-bar" workspace/i)).toBeVisible(); - }); -}); + expect(getByText(/permanently deleted "foo-bar" workspace/i)).toBeVisible() + }) +}) test("can't archive active workspace", async () => { server.use( - http.get(mswEndpoint("/api/v1/workspaces/active"), () => + http.get(mswEndpoint('/api/v1/workspaces/active'), () => HttpResponse.json({ workspaces: [ { - name: "foo", + name: 'foo', is_active: true, last_updated: new Date(Date.now()).toISOString(), }, ], - }), - ), - ); + }) + ) + ) const { getByRole } = render( - , - ); + + ) await waitFor(() => { - expect(getByRole("button", { name: /archive/i })).toBeDisabled(); - expect(getByRole("button", { name: /contextual help/i })).toBeVisible(); - }); -}); + expect(getByRole('button', { name: /archive/i })).toBeDisabled() + expect(getByRole('button', { name: /contextual help/i })).toBeVisible() + }) +}) test("can't archive default workspace", async () => { const { getByRole } = render( - , - ); + + ) await waitFor(() => { - expect(getByRole("button", { name: /archive/i })).toBeDisabled(); - expect(getByRole("button", { name: /contextual help/i })).toBeVisible(); - }); -}); + expect(getByRole('button', { name: /archive/i })).toBeDisabled() + expect(getByRole('button', { name: /contextual help/i })).toBeVisible() + }) +}) diff --git a/src/features/workspace/components/__tests__/table-actions-workspaces.test.tsx b/src/features/workspace/components/__tests__/table-actions-workspaces.test.tsx index c5488fd9..941123f3 100644 --- a/src/features/workspace/components/__tests__/table-actions-workspaces.test.tsx +++ b/src/features/workspace/components/__tests__/table-actions-workspaces.test.tsx @@ -1,301 +1,299 @@ -import { hrefs } from "@/lib/hrefs"; +import { hrefs } from '@/lib/hrefs' -import { waitFor } from "@testing-library/dom"; -import userEvent from "@testing-library/user-event"; +import { waitFor } from '@testing-library/dom' +import userEvent from '@testing-library/user-event' -import { TableActionsWorkspaces } from "../table-actions-workspaces"; -import { render } from "@/lib/test-utils"; +import { TableActionsWorkspaces } from '../table-actions-workspaces' +import { render } from '@/lib/test-utils' -const mockNavigate = vi.fn(); -vi.mock("react-router-dom", async () => { +const mockNavigate = vi.fn() +vi.mock('react-router-dom', async () => { const original = - await vi.importActual( - "react-router-dom", - ); + await vi.importActual('react-router-dom') return { ...original, useNavigate: () => mockNavigate, - }; -}); + } +}) -it("has correct actions for default workspace when not active", async () => { +it('has correct actions for default workspace when not active', async () => { const { getByRole } = render( , - ); + /> + ) - await userEvent.click(getByRole("button", { name: /actions/i })); + await userEvent.click(getByRole('button', { name: /actions/i })) await waitFor(() => { - expect(getByRole("menu")).toBeVisible(); - }); + expect(getByRole('menu')).toBeVisible() + }) - const activate = getByRole("menuitem", { name: /activate/i }); - expect(activate).not.toHaveAttribute("aria-disabled", "true"); + const activate = getByRole('menuitem', { name: /activate/i }) + expect(activate).not.toHaveAttribute('aria-disabled', 'true') - const edit = getByRole("menuitem", { name: /edit/i }); - expect(edit).toHaveAttribute("href", hrefs.workspaces.edit("default")); + const edit = getByRole('menuitem', { name: /edit/i }) + expect(edit).toHaveAttribute('href', hrefs.workspaces.edit('default')) - const archive = getByRole("menuitem", { name: /archive/i }); - expect(archive).toHaveAttribute("aria-disabled", "true"); -}); + const archive = getByRole('menuitem', { name: /archive/i }) + expect(archive).toHaveAttribute('aria-disabled', 'true') +}) -it("has correct actions for default workspace when active", async () => { +it('has correct actions for default workspace when active', async () => { const { getByRole } = render( , - ); + /> + ) - await userEvent.click(getByRole("button", { name: /actions/i })); + await userEvent.click(getByRole('button', { name: /actions/i })) await waitFor(() => { - expect(getByRole("menu")).toBeVisible(); - }); + expect(getByRole('menu')).toBeVisible() + }) - const activate = getByRole("menuitem", { name: /activate/i }); - expect(activate).toHaveAttribute("aria-disabled", "true"); + const activate = getByRole('menuitem', { name: /activate/i }) + expect(activate).toHaveAttribute('aria-disabled', 'true') - const edit = getByRole("menuitem", { name: /edit/i }); - expect(edit).toHaveAttribute("href", hrefs.workspaces.edit("default")); + const edit = getByRole('menuitem', { name: /edit/i }) + expect(edit).toHaveAttribute('href', hrefs.workspaces.edit('default')) - const archive = getByRole("menuitem", { name: /archive/i }); - expect(archive).toHaveAttribute("aria-disabled", "true"); -}); + const archive = getByRole('menuitem', { name: /archive/i }) + expect(archive).toHaveAttribute('aria-disabled', 'true') +}) -it("has correct actions for normal workspace when not active", async () => { +it('has correct actions for normal workspace when not active', async () => { const { getByRole } = render( , - ); + /> + ) - await userEvent.click(getByRole("button", { name: /actions/i })); + await userEvent.click(getByRole('button', { name: /actions/i })) await waitFor(() => { - expect(getByRole("menu")).toBeVisible(); - }); + expect(getByRole('menu')).toBeVisible() + }) - const activate = getByRole("menuitem", { name: /activate/i }); - expect(activate).not.toHaveAttribute("aria-disabled", "true"); + const activate = getByRole('menuitem', { name: /activate/i }) + expect(activate).not.toHaveAttribute('aria-disabled', 'true') - const edit = getByRole("menuitem", { name: /edit/i }); - expect(edit).toHaveAttribute("href", hrefs.workspaces.edit("foo-bar")); + const edit = getByRole('menuitem', { name: /edit/i }) + expect(edit).toHaveAttribute('href', hrefs.workspaces.edit('foo-bar')) - const archive = getByRole("menuitem", { name: /archive/i }); - expect(archive).not.toHaveAttribute("aria-disabled", "true"); -}); + const archive = getByRole('menuitem', { name: /archive/i }) + expect(archive).not.toHaveAttribute('aria-disabled', 'true') +}) -it("has correct actions for normal workspace when active", async () => { +it('has correct actions for normal workspace when active', async () => { const { getByRole } = render( , - ); + /> + ) - await userEvent.click(getByRole("button", { name: /actions/i })); + await userEvent.click(getByRole('button', { name: /actions/i })) await waitFor(() => { - expect(getByRole("menu")).toBeVisible(); - }); + expect(getByRole('menu')).toBeVisible() + }) - const activate = getByRole("menuitem", { name: /activate/i }); - expect(activate).toHaveAttribute("aria-disabled", "true"); + const activate = getByRole('menuitem', { name: /activate/i }) + expect(activate).toHaveAttribute('aria-disabled', 'true') - const edit = getByRole("menuitem", { name: /edit/i }); - expect(edit).toHaveAttribute("href", hrefs.workspaces.edit("foo-bar")); + const edit = getByRole('menuitem', { name: /edit/i }) + expect(edit).toHaveAttribute('href', hrefs.workspaces.edit('foo-bar')) - const archive = getByRole("menuitem", { name: /archive/i }); - expect(archive).toHaveAttribute("aria-disabled", "true"); -}); + const archive = getByRole('menuitem', { name: /archive/i }) + expect(archive).toHaveAttribute('aria-disabled', 'true') +}) -it("has correct actions for archived workspace", async () => { +it('has correct actions for archived workspace', async () => { const { getByRole } = render( , - ); + /> + ) - await userEvent.click(getByRole("button", { name: /actions/i })); + await userEvent.click(getByRole('button', { name: /actions/i })) await waitFor(() => { - expect(getByRole("menu")).toBeVisible(); - }); + expect(getByRole('menu')).toBeVisible() + }) - const restore = getByRole("menuitem", { name: /restore/i }); - expect(restore).not.toHaveAttribute("aria-disabled", "true"); + const restore = getByRole('menuitem', { name: /restore/i }) + expect(restore).not.toHaveAttribute('aria-disabled', 'true') - const hardDelete = getByRole("menuitem", { + const hardDelete = getByRole('menuitem', { name: /permanently delete/i, - }); - expect(hardDelete).not.toHaveAttribute("aria-disabled", "true"); -}); + }) + expect(hardDelete).not.toHaveAttribute('aria-disabled', 'true') +}) -it("can activate default workspace", async () => { +it('can activate default workspace', async () => { const { getByRole, getByText } = render( , - ); + /> + ) - await userEvent.click(getByRole("button", { name: /actions/i })); + await userEvent.click(getByRole('button', { name: /actions/i })) await waitFor(() => { - expect(getByRole("menu")).toBeVisible(); - }); + expect(getByRole('menu')).toBeVisible() + }) - const activate = getByRole("menuitem", { name: /activate/i }); - await userEvent.click(activate); + const activate = getByRole('menuitem', { name: /activate/i }) + await userEvent.click(activate) await waitFor(() => { - expect(getByText(/activated "default" workspace/i)).toBeVisible(); - }); -}); + expect(getByText(/activated "default" workspace/i)).toBeVisible() + }) +}) -it("can edit default workspace", async () => { +it('can edit default workspace', async () => { const { getByRole } = render( , - ); + /> + ) - await userEvent.click(getByRole("button", { name: /actions/i })); + await userEvent.click(getByRole('button', { name: /actions/i })) await waitFor(() => { - expect(getByRole("menu")).toBeVisible(); - }); + expect(getByRole('menu')).toBeVisible() + }) - const edit = getByRole("menuitem", { name: /edit/i }); - await userEvent.click(edit); + const edit = getByRole('menuitem', { name: /edit/i }) + await userEvent.click(edit) await waitFor(() => { expect(mockNavigate).toHaveBeenCalledWith( - hrefs.workspaces.edit("default"), - undefined, - ); - }); -}); + hrefs.workspaces.edit('default'), + undefined + ) + }) +}) -it("can activate normal workspace", async () => { +it('can activate normal workspace', async () => { const { getByRole, getByText } = render( , - ); + /> + ) - await userEvent.click(getByRole("button", { name: /actions/i })); + await userEvent.click(getByRole('button', { name: /actions/i })) await waitFor(() => { - expect(getByRole("menu")).toBeVisible(); - }); + expect(getByRole('menu')).toBeVisible() + }) - const activate = getByRole("menuitem", { name: /activate/i }); - await userEvent.click(activate); + const activate = getByRole('menuitem', { name: /activate/i }) + await userEvent.click(activate) await waitFor(() => { - expect(getByText(/activated "foo-bar" workspace/i)).toBeVisible(); - }); -}); + expect(getByText(/activated "foo-bar" workspace/i)).toBeVisible() + }) +}) -it("can edit normal workspace", async () => { +it('can edit normal workspace', async () => { const { getByRole } = render( , - ); + /> + ) - await userEvent.click(getByRole("button", { name: /actions/i })); + await userEvent.click(getByRole('button', { name: /actions/i })) await waitFor(() => { - expect(getByRole("menu")).toBeVisible(); - }); + expect(getByRole('menu')).toBeVisible() + }) - const edit = getByRole("menuitem", { name: /edit/i }); - await userEvent.click(edit); + const edit = getByRole('menuitem', { name: /edit/i }) + await userEvent.click(edit) await waitFor(() => { expect(mockNavigate).toHaveBeenCalledWith( - hrefs.workspaces.edit("foo-bar"), - undefined, - ); - }); -}); + hrefs.workspaces.edit('foo-bar'), + undefined + ) + }) +}) -it("can archive normal workspace", async () => { +it('can archive normal workspace', async () => { const { getByRole, getByText } = render( , - ); + /> + ) - await userEvent.click(getByRole("button", { name: /actions/i })); + await userEvent.click(getByRole('button', { name: /actions/i })) await waitFor(() => { - expect(getByRole("menu")).toBeVisible(); - }); + expect(getByRole('menu')).toBeVisible() + }) - await userEvent.click(getByRole("menuitem", { name: /archive/i })); + await userEvent.click(getByRole('menuitem', { name: /archive/i })) await waitFor(() => { - expect(getByText(/archived "foo-bar" workspace/i)).toBeVisible(); - }); -}); + expect(getByText(/archived "foo-bar" workspace/i)).toBeVisible() + }) +}) -it("can restore archived workspace", async () => { +it('can restore archived workspace', async () => { const { getByRole, getByText } = render( , - ); + /> + ) - await userEvent.click(getByRole("button", { name: /actions/i })); + await userEvent.click(getByRole('button', { name: /actions/i })) await waitFor(() => { - expect(getByRole("menu")).toBeVisible(); - }); + expect(getByRole('menu')).toBeVisible() + }) - await userEvent.click(getByRole("menuitem", { name: /restore/i })); + await userEvent.click(getByRole('menuitem', { name: /restore/i })) await waitFor(() => { - expect(getByText(/restored "foo-bar" workspace/i)).toBeVisible(); - }); -}); + expect(getByText(/restored "foo-bar" workspace/i)).toBeVisible() + }) +}) -it("can permanently delete archived workspace", async () => { +it('can permanently delete archived workspace', async () => { const { getByRole, getByText } = render( , - ); + /> + ) - await userEvent.click(getByRole("button", { name: /actions/i })); + await userEvent.click(getByRole('button', { name: /actions/i })) await waitFor(() => { - expect(getByRole("menu")).toBeVisible(); - }); + expect(getByRole('menu')).toBeVisible() + }) - await userEvent.click(getByRole("menuitem", { name: /permanently/i })); + await userEvent.click(getByRole('menuitem', { name: /permanently/i })) await waitFor(() => { - expect(getByRole("dialog", { name: /permanently delete/i })).toBeVisible(); - }); + expect(getByRole('dialog', { name: /permanently delete/i })).toBeVisible() + }) - await userEvent.click(getByRole("button", { name: /delete/i })); + await userEvent.click(getByRole('button', { name: /delete/i })) await waitFor(() => { - expect(getByText(/permanently deleted "foo-bar" workspace/i)).toBeVisible(); - }); -}); + expect(getByText(/permanently deleted "foo-bar" workspace/i)).toBeVisible() + }) +}) diff --git a/src/features/workspace/components/__tests__/workspace-creation.test.tsx b/src/features/workspace/components/__tests__/workspace-creation.test.tsx index f9ea7d3f..43e173af 100644 --- a/src/features/workspace/components/__tests__/workspace-creation.test.tsx +++ b/src/features/workspace/components/__tests__/workspace-creation.test.tsx @@ -1,43 +1,41 @@ -import { WorkspaceCreation } from "../workspace-creation"; -import { render } from "@/lib/test-utils"; -import userEvent from "@testing-library/user-event"; -import { screen, waitFor } from "@testing-library/react"; +import { WorkspaceCreation } from '../workspace-creation' +import { render } from '@/lib/test-utils' +import userEvent from '@testing-library/user-event' +import { screen, waitFor } from '@testing-library/react' -const mockNavigate = vi.fn(); -vi.mock("react-router-dom", async () => { +const mockNavigate = vi.fn() +vi.mock('react-router-dom', async () => { const original = - await vi.importActual( - "react-router-dom", - ); + await vi.importActual('react-router-dom') return { ...original, useNavigate: () => mockNavigate, - }; -}); + } +}) -test("create workspace", async () => { - render(); +test('create workspace', async () => { + render() - expect(screen.getByText(/name/i)).toBeVisible(); + expect(screen.getByText(/name/i)).toBeVisible() - await userEvent.type(screen.getByRole("textbox"), "workspaceA"); - await userEvent.click(screen.getByRole("button", { name: /create/i })); - await waitFor(() => expect(mockNavigate).toBeCalled()); + await userEvent.type(screen.getByRole('textbox'), 'workspaceA') + await userEvent.click(screen.getByRole('button', { name: /create/i })) + await waitFor(() => expect(mockNavigate).toBeCalled()) await waitFor(() => { - expect(screen.getByText(/created "(.*)" workspace/i)).toBeVisible(); - }); -}); + expect(screen.getByText(/created "(.*)" workspace/i)).toBeVisible() + }) +}) -test("create workspace with enter button", async () => { - render(); +test('create workspace with enter button', async () => { + render() - expect(screen.getByText(/name/i)).toBeVisible(); + expect(screen.getByText(/name/i)).toBeVisible() - await userEvent.type(screen.getByRole("textbox"), "workspaceA{enter}"); - await waitFor(() => expect(mockNavigate).toBeCalled()); + await userEvent.type(screen.getByRole('textbox'), 'workspaceA{enter}') + await waitFor(() => expect(mockNavigate).toBeCalled()) await waitFor(() => { - expect(screen.getByText(/created "(.*)" workspace/i)).toBeVisible(); - }); -}); + expect(screen.getByText(/created "(.*)" workspace/i)).toBeVisible() + }) +}) diff --git a/src/features/workspace/components/__tests__/workspace-custom-instructions.test.tsx b/src/features/workspace/components/__tests__/workspace-custom-instructions.test.tsx index b9d83c1f..0b32eced 100644 --- a/src/features/workspace/components/__tests__/workspace-custom-instructions.test.tsx +++ b/src/features/workspace/components/__tests__/workspace-custom-instructions.test.tsx @@ -1,13 +1,13 @@ -import { render, waitFor } from "@/lib/test-utils"; -import { expect, test } from "vitest"; +import { render, waitFor } from '@/lib/test-utils' +import { expect, test } from 'vitest' -import userEvent from "@testing-library/user-event"; -import { server } from "@/mocks/msw/node"; -import { http, HttpResponse } from "msw"; -import { WorkspaceCustomInstructions } from "../workspace-custom-instructions"; -import { mswEndpoint } from "@/test/msw-endpoint"; +import userEvent from '@testing-library/user-event' +import { server } from '@/mocks/msw/node' +import { http, HttpResponse } from 'msw' +import { WorkspaceCustomInstructions } from '../workspace-custom-instructions' +import { mswEndpoint } from '@/test/msw-endpoint' -vi.mock("@monaco-editor/react", () => { +vi.mock('@monaco-editor/react', () => { const FakeEditor = vi.fn((props) => { return ( - ); - }); - return { default: FakeEditor }; -}); + ) + }) + return { default: FakeEditor } +}) const renderComponent = () => - render( - , - ); + render() -test("can update custom instructions", async () => { +test('can update custom instructions', async () => { server.use( http.get( - mswEndpoint("/api/v1/workspaces/:workspace_name/custom-instructions"), + mswEndpoint('/api/v1/workspaces/:workspace_name/custom-instructions'), () => { - return HttpResponse.json({ prompt: "initial prompt from server" }); - }, - ), - ); + return HttpResponse.json({ prompt: 'initial prompt from server' }) + } + ) + ) - const { getByRole, getByText } = renderComponent(); + const { getByRole, getByText } = renderComponent() await waitFor(() => { - expect(getByRole("textbox")).toBeVisible(); - }); + expect(getByRole('textbox')).toBeVisible() + }) - const input = getByRole("textbox"); - expect(input).toHaveTextContent("initial prompt from server"); + const input = getByRole('textbox') + expect(input).toHaveTextContent('initial prompt from server') - await userEvent.clear(input); - await userEvent.type(input, "new prompt from test"); - expect(input).toHaveTextContent("new prompt from test"); + await userEvent.clear(input) + await userEvent.type(input, 'new prompt from test') + expect(input).toHaveTextContent('new prompt from test') server.use( http.get( - mswEndpoint("/api/v1/workspaces/:workspace_name/custom-instructions"), + mswEndpoint('/api/v1/workspaces/:workspace_name/custom-instructions'), () => { - return HttpResponse.json({ prompt: "new prompt from test" }); - }, - ), - ); + return HttpResponse.json({ prompt: 'new prompt from test' }) + } + ) + ) - await userEvent.click(getByRole("button", { name: /Save/i })); + await userEvent.click(getByRole('button', { name: /Save/i })) await waitFor(() => { - expect( - getByText(/successfully updated custom instructions/i), - ).toBeVisible(); - }); + expect(getByText(/successfully updated custom instructions/i)).toBeVisible() + }) await waitFor(() => { - expect(input).toHaveTextContent("new prompt from test"); - }); -}); + expect(input).toHaveTextContent('new prompt from test') + }) +}) diff --git a/src/features/workspace/components/__tests__/workspace-name.test.tsx b/src/features/workspace/components/__tests__/workspace-name.test.tsx index 624cdaf8..0b4a44af 100644 --- a/src/features/workspace/components/__tests__/workspace-name.test.tsx +++ b/src/features/workspace/components/__tests__/workspace-name.test.tsx @@ -1,78 +1,78 @@ -import { test, expect } from "vitest"; -import { WorkspaceName } from "../workspace-name"; -import { render, waitFor } from "@/lib/test-utils"; -import userEvent from "@testing-library/user-event"; -import { server } from "@/mocks/msw/node"; -import { http, HttpResponse } from "msw"; -import { mswEndpoint } from "@/test/msw-endpoint"; +import { test, expect } from 'vitest' +import { WorkspaceName } from '../workspace-name' +import { render, waitFor } from '@/lib/test-utils' +import userEvent from '@testing-library/user-event' +import { server } from '@/mocks/msw/node' +import { http, HttpResponse } from 'msw' +import { mswEndpoint } from '@/test/msw-endpoint' -test("can rename workspace", async () => { +test('can rename workspace', async () => { const { getByRole, getByText } = render( - , - ); + + ) - const input = getByRole("textbox", { name: /workspace name/i }); - await userEvent.clear(input); + const input = getByRole('textbox', { name: /workspace name/i }) + await userEvent.clear(input) - await userEvent.type(input, "baz-qux"); - expect(input).toHaveValue("baz-qux"); + await userEvent.type(input, 'baz-qux') + expect(input).toHaveValue('baz-qux') - await userEvent.click(getByRole("button", { name: /save/i })); + await userEvent.click(getByRole('button', { name: /save/i })) await waitFor(() => { - expect(getByText(/renamed workspace to "baz-qux"/i)).toBeVisible(); - }); -}); + expect(getByText(/renamed workspace to "baz-qux"/i)).toBeVisible() + }) +}) test("can't rename archived workspace", async () => { const { getByRole } = render( - , - ); + + ) - expect(getByRole("textbox", { name: /workspace name/i })).toBeDisabled(); - expect(getByRole("button", { name: /save/i })).toBeDisabled(); -}); + expect(getByRole('textbox', { name: /workspace name/i })).toBeDisabled() + expect(getByRole('button', { name: /save/i })).toBeDisabled() +}) test("can't rename active workspace", async () => { server.use( - http.get(mswEndpoint("/api/v1/workspaces/active"), () => + http.get(mswEndpoint('/api/v1/workspaces/active'), () => HttpResponse.json({ workspaces: [ { - name: "foo", + name: 'foo', is_active: true, last_updated: new Date(Date.now()).toISOString(), }, ], - }), - ), - ); + }) + ) + ) const { getByRole } = render( - , - ); + + ) - expect(getByRole("textbox", { name: /workspace name/i })).toBeDisabled(); - expect(getByRole("button", { name: /save/i })).toBeDisabled(); -}); + expect(getByRole('textbox', { name: /workspace name/i })).toBeDisabled() + expect(getByRole('button', { name: /save/i })).toBeDisabled() +}) test("can't rename archived workspace", async () => { const { getByRole, queryByText } = render( - , - ); + + ) - expect(getByRole("textbox", { name: /workspace name/i })).toBeDisabled(); - expect(getByRole("button", { name: /save/i })).toBeDisabled(); + expect(getByRole('textbox', { name: /workspace name/i })).toBeDisabled() + expect(getByRole('button', { name: /save/i })).toBeDisabled() expect( - queryByText(/cannot rename the default workspace/i), - ).not.toBeInTheDocument(); -}); + queryByText(/cannot rename the default workspace/i) + ).not.toBeInTheDocument() +}) test("can't rename default workspace", async () => { const { getByRole, getByText } = render( - , - ); + + ) - expect(getByRole("textbox", { name: /workspace name/i })).toBeDisabled(); - expect(getByRole("button", { name: /save/i })).toBeDisabled(); - expect(getByText(/cannot rename the default workspace/i)).toBeVisible(); -}); + expect(getByRole('textbox', { name: /workspace name/i })).toBeDisabled() + expect(getByRole('button', { name: /save/i })).toBeDisabled() + expect(getByText(/cannot rename the default workspace/i)).toBeVisible() +}) diff --git a/src/features/workspace/components/__tests__/workspace-preferred-model.test.tsx b/src/features/workspace/components/__tests__/workspace-preferred-model.test.tsx index ad68667f..cd87b3c2 100644 --- a/src/features/workspace/components/__tests__/workspace-preferred-model.test.tsx +++ b/src/features/workspace/components/__tests__/workspace-preferred-model.test.tsx @@ -1,51 +1,51 @@ -import { render } from "@/lib/test-utils"; -import { screen, waitFor } from "@testing-library/react"; -import { WorkspacePreferredModel } from "../workspace-preferred-model"; -import userEvent from "@testing-library/user-event"; +import { render } from '@/lib/test-utils' +import { screen, waitFor } from '@testing-library/react' +import { WorkspacePreferredModel } from '../workspace-preferred-model' +import userEvent from '@testing-library/user-event' -test("render model overrides", async () => { +test('render model overrides', async () => { render( , - ); - expect(screen.getByText(/preferred model/i)).toBeVisible(); + /> + ) + expect(screen.getByText(/preferred model/i)).toBeVisible() expect( screen.getByText( - /select the model you would like to use in this workspace./i, - ), - ).toBeVisible(); + /select the model you would like to use in this workspace./i + ) + ).toBeVisible() expect( - screen.getByRole("button", { name: /select the model/i }), - ).toBeVisible(); + screen.getByRole('button', { name: /select the model/i }) + ).toBeVisible() await waitFor(() => { - expect(screen.getByRole("button", { name: /save/i })).toBeVisible(); - }); -}); + expect(screen.getByRole('button', { name: /save/i })).toBeVisible() + }) +}) -test("submit preferred model", async () => { +test('submit preferred model', async () => { render( , - ); + /> + ) await userEvent.click( - screen.getByRole("button", { name: /select the model/i }), - ); + screen.getByRole('button', { name: /select the model/i }) + ) await userEvent.click( - screen.getByRole("option", { - name: "anthropic/claude-3.5", - }), - ); + screen.getByRole('option', { + name: 'anthropic/claude-3.5', + }) + ) - await userEvent.click(screen.getByRole("button", { name: /save/i })); + await userEvent.click(screen.getByRole('button', { name: /save/i })) await waitFor(() => { - expect(screen.getByText(/preferred model for fake-workspace updated/i)); - }); -}); + expect(screen.getByText(/preferred model for fake-workspace updated/i)) + }) +}) diff --git a/src/features/workspace/components/archive-workspace.tsx b/src/features/workspace/components/archive-workspace.tsx index a3a2045a..88649fac 100644 --- a/src/features/workspace/components/archive-workspace.tsx +++ b/src/features/workspace/components/archive-workspace.tsx @@ -6,74 +6,74 @@ import { TooltipTrigger, Tooltip, TooltipInfoButton, -} from "@stacklok/ui-kit"; -import { twMerge } from "tailwind-merge"; -import { useRestoreWorkspaceButton } from "../hooks/use-restore-workspace-button"; -import { useArchiveWorkspaceButton } from "../hooks/use-archive-workspace-button"; -import { useConfirmHardDeleteWorkspace } from "../hooks/use-confirm-hard-delete-workspace"; -import { useNavigate } from "react-router-dom"; -import { hrefs } from "@/lib/hrefs"; -import { useQueryActiveWorkspaceName } from "../../../hooks/use-query-active-workspace-name"; +} from '@stacklok/ui-kit' +import { twMerge } from 'tailwind-merge' +import { useRestoreWorkspaceButton } from '../hooks/use-restore-workspace-button' +import { useArchiveWorkspaceButton } from '../hooks/use-archive-workspace-button' +import { useConfirmHardDeleteWorkspace } from '../hooks/use-confirm-hard-delete-workspace' +import { useNavigate } from 'react-router-dom' +import { hrefs } from '@/lib/hrefs' +import { useQueryActiveWorkspaceName } from '../../../hooks/use-query-active-workspace-name' function getContextualText({ activeWorkspaceName, workspaceName, }: { - workspaceName: string; - activeWorkspaceName: string; + workspaceName: string + activeWorkspaceName: string }) { if (workspaceName === activeWorkspaceName) { - return "Cannot archive the active workspace"; + return 'Cannot archive the active workspace' } - if (workspaceName === "default") { - return "Cannot archive the default workspace"; + if (workspaceName === 'default') { + return 'Cannot archive the default workspace' } - return null; + return null } // NOTE: You can't show a tooltip on a disabled button // React Aria's recommended approach is https://spectrum.adobe.com/page/contextual-help/ function ContextualHelp({ workspaceName }: { workspaceName: string }) { - const { data: activeWorkspaceName } = useQueryActiveWorkspaceName(); - if (!activeWorkspaceName) return null; + const { data: activeWorkspaceName } = useQueryActiveWorkspaceName() + if (!activeWorkspaceName) return null - const text = getContextualText({ activeWorkspaceName, workspaceName }); - if (!text) return null; + const text = getContextualText({ activeWorkspaceName, workspaceName }) + if (!text) return null return ( {text} - ); + ) } const ButtonsUnarchived = ({ workspaceName }: { workspaceName: string }) => { - const archiveButtonProps = useArchiveWorkspaceButton({ workspaceName }); + const archiveButtonProps = useArchiveWorkspaceButton({ workspaceName }) return ( -
    +
    - ); -}; + ) +} const ButtonsArchived = ({ workspaceName }: { workspaceName: string }) => { - const restoreButtonProps = useRestoreWorkspaceButton({ workspaceName }); - const hardDelete = useConfirmHardDeleteWorkspace(); + const restoreButtonProps = useRestoreWorkspaceButton({ workspaceName }) + const hardDelete = useConfirmHardDeleteWorkspace() - const navigate = useNavigate(); + const navigate = useNavigate() return ( -
    +
    - ); -}; + ) +} export function ArchiveWorkspace({ className, workspaceName, isArchived, }: { - workspaceName: string; - className?: string; - isArchived: boolean | undefined; + workspaceName: string + className?: string + isArchived: boolean | undefined }) { return ( - - + +
    Archive Workspace - + Archiving this workspace removes it from the main workspaces list, though it can be restored if needed. @@ -111,5 +111,5 @@ export function ArchiveWorkspace({ )} - ); + ) } diff --git a/src/features/workspace/components/table-actions-workspaces.tsx b/src/features/workspace/components/table-actions-workspaces.tsx index b651bd78..08cd2c08 100644 --- a/src/features/workspace/components/table-actions-workspaces.tsx +++ b/src/features/workspace/components/table-actions-workspaces.tsx @@ -1,25 +1,25 @@ -import { Workspace } from "@/api/generated"; +import { Workspace } from '@/api/generated' import { Button, Menu, MenuTrigger, OptionsSchema, Popover, -} from "@stacklok/ui-kit"; +} from '@stacklok/ui-kit' -import { useMutationArchiveWorkspace } from "@/features/workspace/hooks/use-mutation-archive-workspace"; -import { useMutationRestoreWorkspace } from "../hooks/use-mutation-restore-workspace"; -import { useMutationHardDeleteWorkspace } from "../hooks/use-mutation-hard-delete-workspace"; -import { useMutationActivateWorkspace } from "../../../hooks/use-mutation-activate-workspace"; -import { useConfirmHardDeleteWorkspace } from "../hooks/use-confirm-hard-delete-workspace"; -import { hrefs } from "@/lib/hrefs"; +import { useMutationArchiveWorkspace } from '@/features/workspace/hooks/use-mutation-archive-workspace' +import { useMutationRestoreWorkspace } from '../hooks/use-mutation-restore-workspace' +import { useMutationHardDeleteWorkspace } from '../hooks/use-mutation-hard-delete-workspace' +import { useMutationActivateWorkspace } from '../../../hooks/use-mutation-activate-workspace' +import { useConfirmHardDeleteWorkspace } from '../hooks/use-confirm-hard-delete-workspace' +import { hrefs } from '@/lib/hrefs' import { Check, DotsHorizontal, FlipBackward, Settings04, XClose, -} from "@untitled-ui/icons-react"; +} from '@untitled-ui/icons-react' const getWorkspaceActions = ({ archiveWorkspace, @@ -28,39 +28,39 @@ const getWorkspaceActions = ({ activeWorkspaceName, }: { workspace: Workspace & { - isArchived?: boolean; - }; + isArchived?: boolean + } archiveWorkspace: ReturnType< typeof useMutationArchiveWorkspace - >["mutateAsync"]; + >['mutateAsync'] activateWorkspace: ReturnType< typeof useMutationActivateWorkspace - >["mutateAsync"]; - activeWorkspaceName: string | null | undefined; -}): OptionsSchema<"menu">[] => [ + >['mutateAsync'] + activeWorkspaceName: string | null | undefined +}): OptionsSchema<'menu'>[] => [ { - textValue: "Activate", + textValue: 'Activate', icon: , - id: "activate", + id: 'activate', isDisabled: workspace.name === activeWorkspaceName, onAction: () => activateWorkspace({ body: { name: workspace.name } }), }, { - textValue: "Edit", + textValue: 'Edit', icon: , - id: "edit", + id: 'edit', href: hrefs.workspaces.edit(workspace.name), }, { - textValue: "Archive", + textValue: 'Archive', icon: , - id: "archive", + id: 'archive', isDisabled: - workspace.name === activeWorkspaceName || workspace.name === "default", + workspace.name === activeWorkspaceName || workspace.name === 'default', onAction: () => void archiveWorkspace({ path: { workspace_name: workspace.name } }), }, -]; +] const getArchivedWorkspaceActions = ({ workspace, @@ -68,43 +68,43 @@ const getArchivedWorkspaceActions = ({ hardDeleteWorkspace, }: { workspace: Workspace & { - isArchived?: boolean; - }; + isArchived?: boolean + } restoreWorkspace: ReturnType< typeof useMutationArchiveWorkspace - >["mutateAsync"]; + >['mutateAsync'] hardDeleteWorkspace: ReturnType< typeof useMutationHardDeleteWorkspace - >["mutateAsync"]; -}): OptionsSchema<"menu">[] => [ + >['mutateAsync'] +}): OptionsSchema<'menu'>[] => [ { - textValue: "Restore", + textValue: 'Restore', icon: , - id: "restore", + id: 'restore', onAction: () => restoreWorkspace({ path: { workspace_name: workspace.name } }), }, { - textValue: "Permanently delete", + textValue: 'Permanently delete', isDestructive: true, icon: , - id: "permanently-delete", + id: 'permanently-delete', onAction: () => hardDeleteWorkspace({ path: { workspace_name: workspace.name } }), }, -]; +] export function TableActionsWorkspaces({ workspace, activeWorkspaceName, }: { - activeWorkspaceName: string | null | undefined; - workspace: Workspace & { isArchived: boolean }; + activeWorkspaceName: string | null | undefined + workspace: Workspace & { isArchived: boolean } }) { - const { mutateAsync: archiveWorkspace } = useMutationArchiveWorkspace(); - const { mutateAsync: restoreWorkspace } = useMutationRestoreWorkspace(); - const { mutateAsync: activateWorkspace } = useMutationActivateWorkspace(); - const hardDeleteWorkspace = useConfirmHardDeleteWorkspace(); + const { mutateAsync: archiveWorkspace } = useMutationArchiveWorkspace() + const { mutateAsync: restoreWorkspace } = useMutationRestoreWorkspace() + const { mutateAsync: activateWorkspace } = useMutationActivateWorkspace() + const hardDeleteWorkspace = useConfirmHardDeleteWorkspace() return ( @@ -130,5 +130,5 @@ export function TableActionsWorkspaces({ /> - ); + ) } diff --git a/src/features/workspace/components/table-workspaces.tsx b/src/features/workspace/components/table-workspaces.tsx index c4a1fac2..5d61a66e 100644 --- a/src/features/workspace/components/table-workspaces.tsx +++ b/src/features/workspace/components/table-workspaces.tsx @@ -8,21 +8,21 @@ import { Table, TableBody, TableHeader, -} from "@stacklok/ui-kit"; +} from '@stacklok/ui-kit' -import { useListAllWorkspaces } from "../../../hooks/use-query-list-all-workspaces"; -import { useQueryActiveWorkspaceName } from "../../../hooks/use-query-active-workspace-name"; -import { TableActionsWorkspaces } from "./table-actions-workspaces"; -import { hrefs } from "@/lib/hrefs"; +import { useListAllWorkspaces } from '../../../hooks/use-query-list-all-workspaces' +import { useQueryActiveWorkspaceName } from '../../../hooks/use-query-active-workspace-name' +import { TableActionsWorkspaces } from './table-actions-workspaces' +import { hrefs } from '@/lib/hrefs' function CellName({ name, isArchived = false, isActive = false, }: { - name: string; - isArchived: boolean; - isActive: boolean; + name: string + isArchived: boolean + isActive: boolean }) { if (isArchived) return ( @@ -33,7 +33,7 @@ function CellName({ Archived - ); + ) if (isActive) return ( @@ -44,14 +44,14 @@ function CellName({ Active - ); + ) - return {name}; + return {name} } export function TableWorkspaces() { - const { data: workspaces } = useListAllWorkspaces(); - const { data: activeWorkspaceName } = useQueryActiveWorkspaceName(); + const { data: workspaces } = useListAllWorkspaces() + const { data: activeWorkspaceName } = useQueryActiveWorkspaceName() return ( @@ -88,5 +88,5 @@ export function TableWorkspaces() { - ); + ) } diff --git a/src/features/workspace/components/workspace-creation.tsx b/src/features/workspace/components/workspace-creation.tsx index 68571355..e6c3f207 100644 --- a/src/features/workspace/components/workspace-creation.tsx +++ b/src/features/workspace/components/workspace-creation.tsx @@ -1,4 +1,4 @@ -import { useMutationCreateWorkspace } from "@/features/workspace/hooks/use-mutation-create-workspace"; +import { useMutationCreateWorkspace } from '@/features/workspace/hooks/use-mutation-create-workspace' import { Button, Card, @@ -9,27 +9,27 @@ import { Label, LinkButton, TextField, -} from "@stacklok/ui-kit"; -import { FormEvent, useState } from "react"; -import { useNavigate } from "react-router-dom"; +} from '@stacklok/ui-kit' +import { FormEvent, useState } from 'react' +import { useNavigate } from 'react-router-dom' export function WorkspaceCreation() { - const navigate = useNavigate(); - const [workspaceName, setWorkspaceName] = useState(""); - const { mutateAsync, isPending, error } = useMutationCreateWorkspace(); - const errorMsg = error?.detail ? `${error?.detail}` : ""; + const navigate = useNavigate() + const [workspaceName, setWorkspaceName] = useState('') + const { mutateAsync, isPending, error } = useMutationCreateWorkspace() + const errorMsg = error?.detail ? `${error?.detail}` : '' const handleSubmit = (e: FormEvent) => { - e.preventDefault(); + e.preventDefault() mutateAsync( { body: { name: workspaceName }, }, { - onSuccess: () => navigate("/workspaces"), - }, - ); - }; + onSuccess: () => navigate('/workspaces'), + } + ) + } return (
    @@ -53,7 +53,7 @@ export function WorkspaceCreation() { Cancel
    - ); + ) })}
    - Prompt templates sourced from{" "} + Prompt templates sourced from{' '} - ); + ) } export function WorkspaceCustomInstructions({ @@ -265,37 +266,37 @@ export function WorkspaceCustomInstructions({ workspaceName, isArchived, }: { - className?: string; - workspaceName: string; - isArchived: boolean | undefined; + className?: string + workspaceName: string + isArchived: boolean | undefined }) { - const context = useContext(DarkModeContext); - const theme: Theme = inferDarkMode(context); + const context = useContext(DarkModeContext) + const theme: Theme = inferDarkMode(context) const options: V1GetWorkspaceCustomInstructionsData & - Omit = useMemo( + Omit = useMemo( () => ({ path: { workspace_name: workspaceName }, }), - [workspaceName], - ); + [workspaceName] + ) - const queryClient = useQueryClient(); + const queryClient = useQueryClient() const { data: customInstructionsResponse, isPending: isCustomInstructionsPending, - } = useQueryGetWorkspaceCustomInstructions(options); + } = useQueryGetWorkspaceCustomInstructions(options) const { mutateAsync, isPending: isMutationPending } = - useMutationSetWorkspaceCustomInstructions(options); + useMutationSetWorkspaceCustomInstructions(options) const formState = useCustomInstructionsValue({ - initialValue: customInstructionsResponse?.prompt ?? "", + initialValue: customInstructionsResponse?.prompt ?? '', options, queryClient, - }); + }) - const { values, updateFormValues } = formState; + const { values, updateFormValues } = formState const handleSubmit = useCallback( (value: string) => { @@ -306,42 +307,43 @@ export function WorkspaceCustomInstructions({ invalidateQueries(queryClient, [ v1GetWorkspaceCustomInstructionsQueryKey, ]), - }, - ); + } + ) }, - [mutateAsync, options, queryClient], - ); + [mutateAsync, options, queryClient] + ) return (
    { - e.preventDefault(); - handleSubmit(values.prompt); + e.preventDefault() + handleSubmit(values.prompt) }} validationBehavior="aria" > - + Custom instructions - + Pass custom instructions to your LLM to augment its behavior, and save time & tokens. -
    +
    {isCustomInstructionsPending ? ( ) : ( updateFormValues({ prompt: v ?? "" })} + onChange={(v) => updateFormValues({ prompt: v ?? '' })} height="20rem" defaultLanguage="Markdown" theme={theme} - className={twMerge("bg-base", isArchived ? "opacity-25" : "")} + className={twMerge('bg-base', isArchived ? 'opacity-25' : '')} /> )}
    @@ -361,11 +363,11 @@ export function WorkspaceCustomInstructions({ { - updateFormValues({ prompt }); + updateFormValues({ prompt }) }} /> @@ -376,5 +378,5 @@ export function WorkspaceCustomInstructions({ - ); + ) } diff --git a/src/features/workspace/components/workspace-name.tsx b/src/features/workspace/components/workspace-name.tsx index fc4206c4..8b9ac5a2 100644 --- a/src/features/workspace/components/workspace-name.tsx +++ b/src/features/workspace/components/workspace-name.tsx @@ -6,43 +6,43 @@ import { Input, Label, TextField, -} from "@stacklok/ui-kit"; -import { useMutationCreateWorkspace } from "../hooks/use-mutation-create-workspace"; -import { useNavigate } from "react-router-dom"; -import { twMerge } from "tailwind-merge"; -import { useFormState } from "@/hooks/useFormState"; -import { FormButtons } from "@/components/FormButtons"; -import { FormEvent } from "react"; +} from '@stacklok/ui-kit' +import { useMutationCreateWorkspace } from '../hooks/use-mutation-create-workspace' +import { useNavigate } from 'react-router-dom' +import { twMerge } from 'tailwind-merge' +import { useFormState } from '@/hooks/useFormState' +import { FormButtons } from '@/components/FormButtons' +import { FormEvent } from 'react' export function WorkspaceName({ className, workspaceName, isArchived, }: { - className?: string; - workspaceName: string; - isArchived: boolean | undefined; + className?: string + workspaceName: string + isArchived: boolean | undefined }) { - const navigate = useNavigate(); - const { mutateAsync, isPending, error } = useMutationCreateWorkspace(); - const errorMsg = error?.detail ? `${error?.detail}` : ""; + const navigate = useNavigate() + const { mutateAsync, isPending, error } = useMutationCreateWorkspace() + const errorMsg = error?.detail ? `${error?.detail}` : '' const formState = useFormState({ workspaceName, - }); - const { values, updateFormValues } = formState; - const isDefault = workspaceName === "default"; - const isUneditable = isArchived || isPending || isDefault; + }) + const { values, updateFormValues } = formState + const isDefault = workspaceName === 'default' + const isUneditable = isArchived || isPending || isDefault const handleSubmit = (event: FormEvent) => { - event.preventDefault(); + event.preventDefault() mutateAsync( { body: { name: workspaceName, rename_to: values.workspaceName } }, { onSuccess: () => navigate(`/workspace/${values.workspaceName}`), - }, - ); - }; + } + ) + } return (
    - + - ); + ) } diff --git a/src/features/workspace/components/workspace-preferred-model.tsx b/src/features/workspace/components/workspace-preferred-model.tsx index 1dc50bc2..b5fd73ae 100644 --- a/src/features/workspace/components/workspace-preferred-model.tsx +++ b/src/features/workspace/components/workspace-preferred-model.tsx @@ -7,18 +7,18 @@ import { Link, LinkButton, Text, -} from "@stacklok/ui-kit"; -import { twMerge } from "tailwind-merge"; -import { useMutationPreferredModelWorkspace } from "../hooks/use-mutation-preferred-model-workspace"; -import { MuxMatcherType } from "@/api/generated"; -import { FormEvent } from "react"; -import { usePreferredModelWorkspace } from "../hooks/use-preferred-preferred-model"; -import { Select, SelectButton } from "@stacklok/ui-kit"; -import { useQueryListAllModelsForAllProviders } from "@/hooks/use-query-list-all-models-for-all-providers"; -import { FormButtons } from "@/components/FormButtons"; -import { invalidateQueries } from "@/lib/react-query-utils"; -import { v1GetWorkspaceMuxesQueryKey } from "@/api/generated/@tanstack/react-query.gen"; -import { useQueryClient } from "@tanstack/react-query"; +} from '@stacklok/ui-kit' +import { twMerge } from 'tailwind-merge' +import { useMutationPreferredModelWorkspace } from '../hooks/use-mutation-preferred-model-workspace' +import { MuxMatcherType } from '@/api/generated' +import { FormEvent } from 'react' +import { usePreferredModelWorkspace } from '../hooks/use-preferred-preferred-model' +import { Select, SelectButton } from '@stacklok/ui-kit' +import { useQueryListAllModelsForAllProviders } from '@/hooks/use-query-list-all-models-for-all-providers' +import { FormButtons } from '@/components/FormButtons' +import { invalidateQueries } from '@/lib/react-query-utils' +import { v1GetWorkspaceMuxesQueryKey } from '@/api/generated/@tanstack/react-query.gen' +import { useQueryClient } from '@tanstack/react-query' function MissingProviderBanner() { return ( @@ -30,7 +30,7 @@ function MissingProviderBanner() { Add Provider - ); + ) } export function WorkspacePreferredModel({ @@ -38,24 +38,24 @@ export function WorkspacePreferredModel({ workspaceName, isArchived, }: { - className?: string; - workspaceName: string; - isArchived: boolean | undefined; + className?: string + workspaceName: string + isArchived: boolean | undefined }) { - const queryClient = useQueryClient(); - const { formState, isPending } = usePreferredModelWorkspace(workspaceName); - const { mutateAsync } = useMutationPreferredModelWorkspace(); - const { data: providerModels = [] } = useQueryListAllModelsForAllProviders(); - const isModelsEmpty = !isPending && providerModels.length === 0; + const queryClient = useQueryClient() + const { formState, isPending } = usePreferredModelWorkspace(workspaceName) + const { mutateAsync } = useMutationPreferredModelWorkspace() + const { data: providerModels = [] } = useQueryListAllModelsForAllProviders() + const isModelsEmpty = !isPending && providerModels.length === 0 const handleSubmit = (event: FormEvent) => { - event.preventDefault(); + event.preventDefault() mutateAsync( { path: { workspace_name: workspaceName }, body: [ { - matcher: "", + matcher: '', matcher_type: MuxMatcherType.CATCH_ALL, ...formState.values.preferredModel, }, @@ -64,9 +64,9 @@ export function WorkspacePreferredModel({ { onSuccess: () => invalidateQueries(queryClient, [v1GetWorkspaceMuxesQueryKey]), - }, - ); - }; + } + ) + } return (
    - +
    Preferred Model - + Select the model you would like to use in this workspace. This - section applies only if you are using the{" "} + section applies only if you are using the{' '} MUX endpoint. @@ -99,15 +99,15 @@ export function WorkspacePreferredModel({ placeholder="Select the model" onSelectionChange={(model) => { const preferredModelProvider = providerModels.find( - (item) => item.name === model, - ); + (item) => item.name === model + ) if (preferredModelProvider) { formState.updateFormValues({ preferredModel: { model: preferredModelProvider.name, provider_id: preferredModelProvider.provider_id, }, - }); + }) } }} items={providerModels.map((model) => ({ @@ -130,5 +130,5 @@ export function WorkspacePreferredModel({ - ); + ) } diff --git a/src/features/workspace/hooks/use-archive-workspace-button.tsx b/src/features/workspace/hooks/use-archive-workspace-button.tsx index 42085b3a..60eb4373 100644 --- a/src/features/workspace/hooks/use-archive-workspace-button.tsx +++ b/src/features/workspace/hooks/use-archive-workspace-button.tsx @@ -1,24 +1,24 @@ -import { Button } from "@stacklok/ui-kit"; -import { ComponentProps } from "react"; -import { useMutationArchiveWorkspace } from "@/features/workspace/hooks/use-mutation-archive-workspace"; -import { useQueryActiveWorkspaceName } from "../../../hooks/use-query-active-workspace-name"; +import { Button } from '@stacklok/ui-kit' +import { ComponentProps } from 'react' +import { useMutationArchiveWorkspace } from '@/features/workspace/hooks/use-mutation-archive-workspace' +import { useQueryActiveWorkspaceName } from '../../../hooks/use-query-active-workspace-name' export function useArchiveWorkspaceButton({ workspaceName, }: { - workspaceName: string; + workspaceName: string }): ComponentProps { - const { data: activeWorkspaceName } = useQueryActiveWorkspaceName(); - const { mutateAsync, isPending } = useMutationArchiveWorkspace(); + const { data: activeWorkspaceName } = useQueryActiveWorkspaceName() + const { mutateAsync, isPending } = useMutationArchiveWorkspace() return { isPending, isDisabled: isPending || workspaceName === activeWorkspaceName || - workspaceName === "default", + workspaceName === 'default', onPress: () => mutateAsync({ path: { workspace_name: workspaceName } }), isDestructive: true, - children: "Archive", - }; + children: 'Archive', + } } diff --git a/src/features/workspace/hooks/use-archived-workspaces.ts b/src/features/workspace/hooks/use-archived-workspaces.ts index cbd3b571..3f0203d1 100644 --- a/src/features/workspace/hooks/use-archived-workspaces.ts +++ b/src/features/workspace/hooks/use-archived-workspaces.ts @@ -1,11 +1,11 @@ -import { useQuery } from "@tanstack/react-query"; -import { v1ListArchivedWorkspacesOptions } from "@/api/generated/@tanstack/react-query.gen"; -import { V1ListArchivedWorkspacesResponse } from "@/api/generated"; +import { useQuery } from '@tanstack/react-query' +import { v1ListArchivedWorkspacesOptions } from '@/api/generated/@tanstack/react-query.gen' +import { V1ListArchivedWorkspacesResponse } from '@/api/generated' export function useArchivedWorkspaces({ select, }: { - select?: (data: V1ListArchivedWorkspacesResponse) => T; + select?: (data: V1ListArchivedWorkspacesResponse) => T } = {}) { return useQuery({ ...v1ListArchivedWorkspacesOptions(), @@ -13,5 +13,5 @@ export function useArchivedWorkspaces({ refetchIntervalInBackground: true, retry: false, select, - }); + }) } diff --git a/src/features/workspace/hooks/use-confirm-hard-delete-workspace.tsx b/src/features/workspace/hooks/use-confirm-hard-delete-workspace.tsx index 74e6350a..3ad8d1fc 100644 --- a/src/features/workspace/hooks/use-confirm-hard-delete-workspace.tsx +++ b/src/features/workspace/hooks/use-confirm-hard-delete-workspace.tsx @@ -1,11 +1,11 @@ -import { useConfirm } from "@/hooks/use-confirm"; -import { useCallback } from "react"; -import { useMutationHardDeleteWorkspace } from "./use-mutation-hard-delete-workspace"; +import { useConfirm } from '@/hooks/use-confirm' +import { useCallback } from 'react' +import { useMutationHardDeleteWorkspace } from './use-mutation-hard-delete-workspace' export function useConfirmHardDeleteWorkspace() { - const { mutateAsync: hardDeleteWorkspace } = useMutationHardDeleteWorkspace(); + const { mutateAsync: hardDeleteWorkspace } = useMutationHardDeleteWorkspace() - const { confirm } = useConfirm(); + const { confirm } = useConfirm() return useCallback( async (...params: Parameters) => { @@ -21,17 +21,17 @@ export function useConfirmHardDeleteWorkspace() { , { buttons: { - yes: "Delete", - no: "Cancel", + yes: 'Delete', + no: 'Cancel', }, - title: "Permanently delete workspace", + title: 'Permanently delete workspace', isDestructive: true, - }, - ); + } + ) if (answer) { - return hardDeleteWorkspace(...params); + return hardDeleteWorkspace(...params) } }, - [confirm, hardDeleteWorkspace], - ); + [confirm, hardDeleteWorkspace] + ) } diff --git a/src/features/workspace/hooks/use-invalidate-workspace-queries.ts b/src/features/workspace/hooks/use-invalidate-workspace-queries.ts index 9597bb82..277db5b4 100644 --- a/src/features/workspace/hooks/use-invalidate-workspace-queries.ts +++ b/src/features/workspace/hooks/use-invalidate-workspace-queries.ts @@ -1,20 +1,20 @@ import { v1ListArchivedWorkspacesQueryKey, v1ListWorkspacesQueryKey, -} from "@/api/generated/@tanstack/react-query.gen"; -import { invalidateQueries } from "@/lib/react-query-utils"; -import { useQueryClient } from "@tanstack/react-query"; -import { useCallback } from "react"; +} from '@/api/generated/@tanstack/react-query.gen' +import { invalidateQueries } from '@/lib/react-query-utils' +import { useQueryClient } from '@tanstack/react-query' +import { useCallback } from 'react' export function useInvalidateWorkspaceQueries() { - const queryClient = useQueryClient(); + const queryClient = useQueryClient() const invalidate = useCallback(async () => { invalidateQueries(queryClient, [ v1ListWorkspacesQueryKey, v1ListArchivedWorkspacesQueryKey, - ]); - }, [queryClient]); + ]) + }, [queryClient]) - return invalidate; + return invalidate } diff --git a/src/features/workspace/hooks/use-mutation-archive-workspace.ts b/src/features/workspace/hooks/use-mutation-archive-workspace.ts index 954211f6..abf9a221 100644 --- a/src/features/workspace/hooks/use-mutation-archive-workspace.ts +++ b/src/features/workspace/hooks/use-mutation-archive-workspace.ts @@ -2,46 +2,46 @@ import { v1DeleteWorkspaceMutation, v1ListArchivedWorkspacesQueryKey, v1ListWorkspacesQueryKey, -} from "@/api/generated/@tanstack/react-query.gen"; -import { useToastMutation } from "@/hooks/use-toast-mutation"; -import { useInvalidateWorkspaceQueries } from "./use-invalidate-workspace-queries"; -import { useQueryClient } from "@tanstack/react-query"; +} from '@/api/generated/@tanstack/react-query.gen' +import { useToastMutation } from '@/hooks/use-toast-mutation' +import { useInvalidateWorkspaceQueries } from './use-invalidate-workspace-queries' +import { useQueryClient } from '@tanstack/react-query' import { V1ListArchivedWorkspacesResponse, V1ListWorkspacesResponse, -} from "@/api/generated"; -import { useQueryActiveWorkspaceName } from "../../../hooks/use-query-active-workspace-name"; +} from '@/api/generated' +import { useQueryActiveWorkspaceName } from '../../../hooks/use-query-active-workspace-name' export function useMutationArchiveWorkspace() { - const queryClient = useQueryClient(); - const invalidate = useInvalidateWorkspaceQueries(); - const { data: activeWorkspaceName } = useQueryActiveWorkspaceName(); + const queryClient = useQueryClient() + const invalidate = useInvalidateWorkspaceQueries() + const { data: activeWorkspaceName } = useQueryActiveWorkspaceName() return useToastMutation({ ...v1DeleteWorkspaceMutation(), onMutate: async (variables) => { // These conditions would cause the archive operation to error - if (variables.path.workspace_name === "default") return; - if (variables.path.workspace_name === activeWorkspaceName) return; + if (variables.path.workspace_name === 'default') return + if (variables.path.workspace_name === activeWorkspaceName) return // Cancel any outgoing refetches // Prevents the refetch from overwriting the optimistic update await queryClient.cancelQueries({ queryKey: v1ListWorkspacesQueryKey(), - }); + }) await queryClient.cancelQueries({ queryKey: v1ListArchivedWorkspacesQueryKey(), - }); + }) // Snapshot the previous data const prevWorkspaces = queryClient.getQueryData( - v1ListWorkspacesQueryKey(), - ); + v1ListWorkspacesQueryKey() + ) const prevArchivedWorkspaces = queryClient.getQueryData( - v1ListArchivedWorkspacesQueryKey(), - ); + v1ListArchivedWorkspacesQueryKey() + ) - if (!prevWorkspaces || !prevArchivedWorkspaces) return; + if (!prevWorkspaces || !prevArchivedWorkspaces) return // Optimistically update values in cache await queryClient.setQueryData( @@ -49,40 +49,40 @@ export function useMutationArchiveWorkspace() { (old: V1ListWorkspacesResponse | null) => ({ workspaces: old ? [...old.workspaces].filter( - (o) => o.name !== variables.path.workspace_name, + (o) => o.name !== variables.path.workspace_name ) : [], - }), - ); + }) + ) await queryClient.setQueryData( v1ListArchivedWorkspacesQueryKey(), (old: V1ListArchivedWorkspacesResponse | null) => ({ workspaces: old ? [...old.workspaces, { name: variables.path.workspace_name }] : [], - }), - ); + }) + ) return { prevWorkspaces, prevArchivedWorkspaces, - }; + } }, onSettled: async () => { - await invalidate(); + await invalidate() }, // Rollback cache updates on error onError: async (_a, _b, context) => { queryClient.setQueryData( v1ListWorkspacesQueryKey(), - context?.prevWorkspaces, - ); + context?.prevWorkspaces + ) queryClient.setQueryData( v1ListArchivedWorkspacesQueryKey(), - context?.prevArchivedWorkspaces, - ); + context?.prevArchivedWorkspaces + ) }, successMsg: (variables) => `Archived "${variables.path.workspace_name}" workspace`, - }); + }) } diff --git a/src/features/workspace/hooks/use-mutation-create-workspace.ts b/src/features/workspace/hooks/use-mutation-create-workspace.ts index 92e35c43..d05ac0cc 100644 --- a/src/features/workspace/hooks/use-mutation-create-workspace.ts +++ b/src/features/workspace/hooks/use-mutation-create-workspace.ts @@ -1,18 +1,18 @@ -import { v1CreateWorkspaceMutation } from "@/api/generated/@tanstack/react-query.gen"; -import { useInvalidateWorkspaceQueries } from "./use-invalidate-workspace-queries"; -import { useToastMutation } from "@/hooks/use-toast-mutation"; +import { v1CreateWorkspaceMutation } from '@/api/generated/@tanstack/react-query.gen' +import { useInvalidateWorkspaceQueries } from './use-invalidate-workspace-queries' +import { useToastMutation } from '@/hooks/use-toast-mutation' export function useMutationCreateWorkspace() { - const invalidate = useInvalidateWorkspaceQueries(); + const invalidate = useInvalidateWorkspaceQueries() return useToastMutation({ ...v1CreateWorkspaceMutation(), onSuccess: async () => { - await invalidate(); + await invalidate() }, successMsg: (variables) => variables.body.rename_to ? `Renamed workspace to "${variables.body.rename_to}"` : `Created "${variables.body.name}" workspace`, - }); + }) } diff --git a/src/features/workspace/hooks/use-mutation-hard-delete-workspace.ts b/src/features/workspace/hooks/use-mutation-hard-delete-workspace.ts index 7ec91fea..d71c17d8 100644 --- a/src/features/workspace/hooks/use-mutation-hard-delete-workspace.ts +++ b/src/features/workspace/hooks/use-mutation-hard-delete-workspace.ts @@ -1,14 +1,14 @@ -import { v1HardDeleteWorkspaceMutation } from "@/api/generated/@tanstack/react-query.gen"; -import { useToastMutation } from "@/hooks/use-toast-mutation"; -import { useInvalidateWorkspaceQueries } from "./use-invalidate-workspace-queries"; +import { v1HardDeleteWorkspaceMutation } from '@/api/generated/@tanstack/react-query.gen' +import { useToastMutation } from '@/hooks/use-toast-mutation' +import { useInvalidateWorkspaceQueries } from './use-invalidate-workspace-queries' export function useMutationHardDeleteWorkspace() { - const invalidate = useInvalidateWorkspaceQueries(); + const invalidate = useInvalidateWorkspaceQueries() return useToastMutation({ ...v1HardDeleteWorkspaceMutation(), onSuccess: () => invalidate(), successMsg: (variables) => `Permanently deleted "${variables.path.workspace_name}" workspace`, - }); + }) } diff --git a/src/features/workspace/hooks/use-mutation-preferred-model-workspace.ts b/src/features/workspace/hooks/use-mutation-preferred-model-workspace.ts index 3558b37b..3ae1a267 100644 --- a/src/features/workspace/hooks/use-mutation-preferred-model-workspace.ts +++ b/src/features/workspace/hooks/use-mutation-preferred-model-workspace.ts @@ -1,15 +1,15 @@ -import { useToastMutation } from "@/hooks/use-toast-mutation"; -import { useInvalidateWorkspaceQueries } from "./use-invalidate-workspace-queries"; -import { v1SetWorkspaceMuxesMutation } from "@/api/generated/@tanstack/react-query.gen"; +import { useToastMutation } from '@/hooks/use-toast-mutation' +import { useInvalidateWorkspaceQueries } from './use-invalidate-workspace-queries' +import { v1SetWorkspaceMuxesMutation } from '@/api/generated/@tanstack/react-query.gen' export function useMutationPreferredModelWorkspace() { - const invalidate = useInvalidateWorkspaceQueries(); + const invalidate = useInvalidateWorkspaceQueries() return useToastMutation({ ...v1SetWorkspaceMuxesMutation(), onSuccess: async () => { - await invalidate(); + await invalidate() }, successMsg: (variables) => `Preferred model for ${variables.path.workspace_name} updated`, - }); + }) } diff --git a/src/features/workspace/hooks/use-mutation-restore-workspace.ts b/src/features/workspace/hooks/use-mutation-restore-workspace.ts index f984a238..b91a276d 100644 --- a/src/features/workspace/hooks/use-mutation-restore-workspace.ts +++ b/src/features/workspace/hooks/use-mutation-restore-workspace.ts @@ -2,18 +2,18 @@ import { v1ListArchivedWorkspacesQueryKey, v1ListWorkspacesQueryKey, v1RecoverWorkspaceMutation, -} from "@/api/generated/@tanstack/react-query.gen"; -import { useToastMutation } from "@/hooks/use-toast-mutation"; -import { useInvalidateWorkspaceQueries } from "./use-invalidate-workspace-queries"; +} from '@/api/generated/@tanstack/react-query.gen' +import { useToastMutation } from '@/hooks/use-toast-mutation' +import { useInvalidateWorkspaceQueries } from './use-invalidate-workspace-queries' import { V1ListWorkspacesResponse, V1ListArchivedWorkspacesResponse, -} from "@/api/generated"; -import { useQueryClient } from "@tanstack/react-query"; +} from '@/api/generated' +import { useQueryClient } from '@tanstack/react-query' export function useMutationRestoreWorkspace() { - const invalidate = useInvalidateWorkspaceQueries(); - const queryClient = useQueryClient(); + const invalidate = useInvalidateWorkspaceQueries() + const queryClient = useQueryClient() return useToastMutation({ ...v1RecoverWorkspaceMutation(), @@ -22,30 +22,30 @@ export function useMutationRestoreWorkspace() { // Prevents the refetch from overwriting the optimistic update await queryClient.cancelQueries({ queryKey: v1ListWorkspacesQueryKey(), - }); + }) await queryClient.cancelQueries({ queryKey: v1ListArchivedWorkspacesQueryKey(), - }); + }) // Snapshot the previous data const prevWorkspaces = queryClient.getQueryData( - v1ListWorkspacesQueryKey(), - ); + v1ListWorkspacesQueryKey() + ) const prevArchivedWorkspaces = queryClient.getQueryData( - v1ListArchivedWorkspacesQueryKey(), - ); + v1ListArchivedWorkspacesQueryKey() + ) - if (!prevWorkspaces || !prevArchivedWorkspaces) return; + if (!prevWorkspaces || !prevArchivedWorkspaces) return // Optimistically update values in cache queryClient.setQueryData( v1ListArchivedWorkspacesQueryKey(), (old: V1ListWorkspacesResponse) => ({ workspaces: [...old.workspaces].filter( - (o) => o.name !== variables.path.workspace_name, + (o) => o.name !== variables.path.workspace_name ), - }), - ); + }) + ) // Optimistically add the workspace to the non-archived list queryClient.setQueryData( v1ListWorkspacesQueryKey(), @@ -54,29 +54,29 @@ export function useMutationRestoreWorkspace() { ...old.workspaces, { name: variables.path.workspace_name }, ], - }), - ); + }) + ) return { prevWorkspaces, prevArchivedWorkspaces, - }; + } }, onSettled: async () => { - await invalidate(); + await invalidate() }, // Rollback cache updates on error onError: async (_a, _b, context) => { queryClient.setQueryData( v1ListWorkspacesQueryKey(), - context?.prevWorkspaces, - ); + context?.prevWorkspaces + ) queryClient.setQueryData( v1ListArchivedWorkspacesQueryKey(), - context?.prevArchivedWorkspaces, - ); + context?.prevArchivedWorkspaces + ) }, successMsg: (variables) => `Restored "${variables.path.workspace_name}" workspace`, - }); + }) } diff --git a/src/features/workspace/hooks/use-mutation-set-workspace-custom-instructions.tsx b/src/features/workspace/hooks/use-mutation-set-workspace-custom-instructions.tsx index 4bba5933..af5f86e9 100644 --- a/src/features/workspace/hooks/use-mutation-set-workspace-custom-instructions.tsx +++ b/src/features/workspace/hooks/use-mutation-set-workspace-custom-instructions.tsx @@ -1,16 +1,16 @@ import { v1GetWorkspaceCustomInstructionsQueryKey, v1SetWorkspaceCustomInstructionsMutation, -} from "@/api/generated/@tanstack/react-query.gen"; -import { V1GetWorkspaceCustomInstructionsData } from "@/api/generated"; -import { useToastMutation } from "@/hooks/use-toast-mutation"; -import { useQueryClient } from "@tanstack/react-query"; -import { invalidateQueries } from "@/lib/react-query-utils"; +} from '@/api/generated/@tanstack/react-query.gen' +import { V1GetWorkspaceCustomInstructionsData } from '@/api/generated' +import { useToastMutation } from '@/hooks/use-toast-mutation' +import { useQueryClient } from '@tanstack/react-query' +import { invalidateQueries } from '@/lib/react-query-utils' export function useMutationSetWorkspaceCustomInstructions( - options: V1GetWorkspaceCustomInstructionsData, + options: V1GetWorkspaceCustomInstructionsData ) { - const queryClient = useQueryClient(); + const queryClient = useQueryClient() return useToastMutation({ ...v1SetWorkspaceCustomInstructionsMutation(options), @@ -18,6 +18,6 @@ export function useMutationSetWorkspaceCustomInstructions( invalidateQueries(queryClient, [ v1GetWorkspaceCustomInstructionsQueryKey, ]), - successMsg: "Successfully updated custom instructions", - }); + successMsg: 'Successfully updated custom instructions', + }) } diff --git a/src/features/workspace/hooks/use-preferred-preferred-model.ts b/src/features/workspace/hooks/use-preferred-preferred-model.ts index 8ca4ad63..78ad4eab 100644 --- a/src/features/workspace/hooks/use-preferred-preferred-model.ts +++ b/src/features/workspace/hooks/use-preferred-preferred-model.ts @@ -1,39 +1,39 @@ -import { MuxRule, V1GetWorkspaceMuxesData } from "@/api/generated"; -import { v1GetWorkspaceMuxesOptions } from "@/api/generated/@tanstack/react-query.gen"; -import { useFormState } from "@/hooks/useFormState"; -import { useQuery } from "@tanstack/react-query"; -import { useMemo } from "react"; +import { MuxRule, V1GetWorkspaceMuxesData } from '@/api/generated' +import { v1GetWorkspaceMuxesOptions } from '@/api/generated/@tanstack/react-query.gen' +import { useFormState } from '@/hooks/useFormState' +import { useQuery } from '@tanstack/react-query' +import { useMemo } from 'react' -type ModelRule = Omit & {}; +type ModelRule = Omit & {} const DEFAULT_STATE = { - provider_id: "", - model: "", -} as const satisfies ModelRule; + provider_id: '', + model: '', +} as const satisfies ModelRule const usePreferredModel = (options: { path: { - workspace_name: string; - }; + workspace_name: string + } }) => { return useQuery({ ...v1GetWorkspaceMuxesOptions(options), - }); -}; + }) +} export const usePreferredModelWorkspace = (workspaceName: string) => { const options: V1GetWorkspaceMuxesData & - Omit = useMemo( + Omit = useMemo( () => ({ path: { workspace_name: workspaceName }, }), - [workspaceName], - ); - const { data, isPending } = usePreferredModel(options); - const providerModel = data?.[0]; + [workspaceName] + ) + const { data, isPending } = usePreferredModel(options) + const providerModel = data?.[0] const formState = useFormState<{ preferredModel: ModelRule }>({ preferredModel: providerModel ?? DEFAULT_STATE, - }); + }) - return { isPending, formState }; -}; + return { isPending, formState } +} diff --git a/src/features/workspace/hooks/use-query-get-workspace-custom-instructions.ts b/src/features/workspace/hooks/use-query-get-workspace-custom-instructions.ts index a39f01b2..61557160 100644 --- a/src/features/workspace/hooks/use-query-get-workspace-custom-instructions.ts +++ b/src/features/workspace/hooks/use-query-get-workspace-custom-instructions.ts @@ -1,12 +1,12 @@ -import { v1GetWorkspaceCustomInstructionsOptions } from "@/api/generated/@tanstack/react-query.gen"; -import { useQuery } from "@tanstack/react-query"; +import { v1GetWorkspaceCustomInstructionsOptions } from '@/api/generated/@tanstack/react-query.gen' +import { useQuery } from '@tanstack/react-query' export function useQueryGetWorkspaceCustomInstructions(options: { path: { - workspace_name: string; - }; + workspace_name: string + } }) { return useQuery({ ...v1GetWorkspaceCustomInstructionsOptions(options), - }); + }) } diff --git a/src/features/workspace/hooks/use-restore-workspace-button.tsx b/src/features/workspace/hooks/use-restore-workspace-button.tsx index 7ecfa2b4..bcb030f5 100644 --- a/src/features/workspace/hooks/use-restore-workspace-button.tsx +++ b/src/features/workspace/hooks/use-restore-workspace-button.tsx @@ -1,18 +1,18 @@ -import { Button } from "@stacklok/ui-kit"; -import { ComponentProps } from "react"; -import { useMutationRestoreWorkspace } from "./use-mutation-restore-workspace"; +import { Button } from '@stacklok/ui-kit' +import { ComponentProps } from 'react' +import { useMutationRestoreWorkspace } from './use-mutation-restore-workspace' export function useRestoreWorkspaceButton({ workspaceName, }: { - workspaceName: string; + workspaceName: string }): ComponentProps { - const { mutateAsync, isPending } = useMutationRestoreWorkspace(); + const { mutateAsync, isPending } = useMutationRestoreWorkspace() return { isPending, isDisabled: isPending, onPress: () => mutateAsync({ path: { workspace_name: workspaceName } }), - children: "Restore", - }; + children: 'Restore', + } } diff --git a/src/hooks/__tests__/useSee.test.ts b/src/hooks/__tests__/useSee.test.ts index 1fc34d03..0fe96938 100644 --- a/src/hooks/__tests__/useSee.test.ts +++ b/src/hooks/__tests__/useSee.test.ts @@ -1,40 +1,40 @@ -import { renderHook, act } from "@testing-library/react"; -import { vi } from "vitest"; -import { useSse } from "../useSse"; -import { TestQueryClientProvider } from "@/lib/test-utils"; +import { renderHook, act } from '@testing-library/react' +import { vi } from 'vitest' +import { useSse } from '../useSse' +import { TestQueryClientProvider } from '@/lib/test-utils' -vi.mock("react-router-dom", () => ({ - useLocation: vi.fn(() => ({ pathname: "/" })), -})); +vi.mock('react-router-dom', () => ({ + useLocation: vi.fn(() => ({ pathname: '/' })), +})) -const mockInvalidate = vi.fn(); +const mockInvalidate = vi.fn() -vi.mock("@tanstack/react-query", async () => { +vi.mock('@tanstack/react-query', async () => { const original = await vi.importActual< - typeof import("@tanstack/react-query") - >("@tanstack/react-query"); + typeof import('@tanstack/react-query') + >('@tanstack/react-query') return { ...original, useQueryClient: () => ({ invalidateQueries: mockInvalidate, }), - }; -}); + } +}) class MockEventSource { - static instances: MockEventSource[] = []; - private _onmessage: ((event: MessageEvent) => void) | null = null; + static instances: MockEventSource[] = [] + private _onmessage: ((event: MessageEvent) => void) | null = null constructor() { - MockEventSource.instances.push(this); + MockEventSource.instances.push(this) } get onmessage() { - return this._onmessage; + return this._onmessage } set onmessage(handler: ((event: MessageEvent) => void) | null) { - this._onmessage = handler; + this._onmessage = handler } close() {} @@ -42,47 +42,47 @@ class MockEventSource { static triggerMessage(data: string) { MockEventSource.instances.forEach((instance) => { if (instance.onmessage) { - instance.onmessage({ data } as MessageEvent); + instance.onmessage({ data } as MessageEvent) } - }); + }) } } -let originalEventSource: typeof EventSource; +let originalEventSource: typeof EventSource -describe("useSse", () => { +describe('useSse', () => { beforeAll(() => { - vi.useFakeTimers(); - originalEventSource = global.EventSource; - global.EventSource = MockEventSource as unknown as typeof EventSource; + vi.useFakeTimers() + originalEventSource = global.EventSource + global.EventSource = MockEventSource as unknown as typeof EventSource - Object.defineProperty(global, "location", { + Object.defineProperty(global, 'location', { value: { reload: vi.fn(), }, writable: true, - }); - }); + }) + }) afterAll(() => { - vi.clearAllMocks(); - vi.useRealTimers(); - global.EventSource = originalEventSource; - }); + vi.clearAllMocks() + vi.useRealTimers() + global.EventSource = originalEventSource + }) - it("should invalidate queries if new alert is detected", () => { - renderHook(() => useSse(), { wrapper: TestQueryClientProvider }); + it('should invalidate queries if new alert is detected', () => { + renderHook(() => useSse(), { wrapper: TestQueryClientProvider }) - expect(MockEventSource.instances.length).toBe(1); - const instance = MockEventSource.instances[0]; - expect(instance).toBeDefined(); + expect(MockEventSource.instances.length).toBe(1) + const instance = MockEventSource.instances[0] + expect(instance).toBeDefined() - expect(instance?.onmessage).toBeDefined(); + expect(instance?.onmessage).toBeDefined() act(() => { - MockEventSource.triggerMessage("new alert detected"); - }); + MockEventSource.triggerMessage('new alert detected') + }) - expect(mockInvalidate).toHaveBeenCalled(); - }); -}); + expect(mockInvalidate).toHaveBeenCalled() + }) +}) diff --git a/src/hooks/use-confirm.tsx b/src/hooks/use-confirm.tsx index 207637a0..6f50f29e 100644 --- a/src/hooks/use-confirm.tsx +++ b/src/hooks/use-confirm.tsx @@ -1,12 +1,12 @@ -"use client"; +'use client' -import { ConfirmContext } from "@/context/confirm-context"; -import { useContext } from "react"; +import { ConfirmContext } from '@/context/confirm-context' +import { useContext } from 'react' export const useConfirm = () => { - const context = useContext(ConfirmContext); + const context = useContext(ConfirmContext) if (!context) { - throw new Error("useConfirmContext must be used within a ConfirmProvider"); + throw new Error('useConfirmContext must be used within a ConfirmProvider') } - return context; -}; + return context +} diff --git a/src/hooks/use-kbd-shortcuts.ts b/src/hooks/use-kbd-shortcuts.ts index 02a65b3e..e0ce9979 100644 --- a/src/hooks/use-kbd-shortcuts.ts +++ b/src/hooks/use-kbd-shortcuts.ts @@ -1,30 +1,30 @@ -import { useEffect } from "react"; +import { useEffect } from 'react' export function useKbdShortcuts(map: [string, () => void][]) { return useEffect(() => { const documentListener = (e: KeyboardEvent) => { - const target = e.target as HTMLElement; + const target = e.target as HTMLElement if ( - target.tagName === "INPUT" || - target.tagName === "TEXTAREA" || + target.tagName === 'INPUT' || + target.tagName === 'TEXTAREA' || target.isContentEditable ) { - return; + return } for (const [key, callback] of map) { if (e.key.toLowerCase() === key.toLowerCase()) { - e.preventDefault(); - e.stopPropagation(); - callback(); + e.preventDefault() + e.stopPropagation() + callback() } } - }; + } - document.addEventListener("keydown", documentListener); + document.addEventListener('keydown', documentListener) return () => { - document.removeEventListener("keydown", documentListener); - }; - }, [map]); + document.removeEventListener('keydown', documentListener) + } + }, [map]) } diff --git a/src/hooks/use-mutation-activate-workspace.ts b/src/hooks/use-mutation-activate-workspace.ts index 0903e20f..67743ad3 100644 --- a/src/hooks/use-mutation-activate-workspace.ts +++ b/src/hooks/use-mutation-activate-workspace.ts @@ -1,14 +1,14 @@ -import { v1ActivateWorkspaceMutation } from "@/api/generated/@tanstack/react-query.gen"; -import { useToastMutation as useToastMutation } from "@/hooks/use-toast-mutation"; -import { useQueryClient } from "@tanstack/react-query"; +import { v1ActivateWorkspaceMutation } from '@/api/generated/@tanstack/react-query.gen' +import { useToastMutation as useToastMutation } from '@/hooks/use-toast-mutation' +import { useQueryClient } from '@tanstack/react-query' export function useMutationActivateWorkspace() { - const queryClient = useQueryClient(); + const queryClient = useQueryClient() return useToastMutation({ ...v1ActivateWorkspaceMutation(), // eslint-disable-next-line no-restricted-syntax - onSuccess: () => queryClient.invalidateQueries({ refetchType: "all" }), // Global setting, refetch **everything** + onSuccess: () => queryClient.invalidateQueries({ refetchType: 'all' }), // Global setting, refetch **everything** successMsg: (variables) => `Activated "${variables.body.name}" workspace`, - }); + }) } diff --git a/src/hooks/use-query-active-workspace-name.ts b/src/hooks/use-query-active-workspace-name.ts index 21e78885..4bed1439 100644 --- a/src/hooks/use-query-active-workspace-name.ts +++ b/src/hooks/use-query-active-workspace-name.ts @@ -1,14 +1,14 @@ -import { ListActiveWorkspacesResponse } from "@/api/generated"; -import { useQueryListActiveWorkspaces } from "./use-query-list-active-workspaces"; +import { ListActiveWorkspacesResponse } from '@/api/generated' +import { useQueryListActiveWorkspaces } from './use-query-list-active-workspaces' // NOTE: This needs to be a stable function reference to enable memo-isation of // the select operation on each React re-render. function select(data: ListActiveWorkspacesResponse | undefined): string | null { - return data?.workspaces?.[0]?.name ?? null; + return data?.workspaces?.[0]?.name ?? null } export function useQueryActiveWorkspaceName() { return useQueryListActiveWorkspaces({ select, - }); + }) } diff --git a/src/hooks/use-query-get-workspace-messages.ts b/src/hooks/use-query-get-workspace-messages.ts index 2e7de014..83573eec 100644 --- a/src/hooks/use-query-get-workspace-messages.ts +++ b/src/hooks/use-query-get-workspace-messages.ts @@ -1,34 +1,34 @@ -import { useQuery } from "@tanstack/react-query"; +import { useQuery } from '@tanstack/react-query' import { V1GetWorkspaceMessagesResponse, V1GetWorkspaceMessagesData, -} from "@/api/generated"; -import { v1GetWorkspaceMessagesOptions } from "@/api/generated/@tanstack/react-query.gen"; -import { useQueryActiveWorkspaceName } from "@/hooks/use-query-active-workspace-name"; -import { getQueryCacheConfig } from "@/lib/react-query-utils"; -import { useMemo } from "react"; +} from '@/api/generated' +import { v1GetWorkspaceMessagesOptions } from '@/api/generated/@tanstack/react-query.gen' +import { useQueryActiveWorkspaceName } from '@/hooks/use-query-active-workspace-name' +import { getQueryCacheConfig } from '@/lib/react-query-utils' +import { useMemo } from 'react' export const useQueryGetWorkspaceMessages = < T = V1GetWorkspaceMessagesResponse, >({ select, }: { - select?: (data: V1GetWorkspaceMessagesResponse) => T; + select?: (data: V1GetWorkspaceMessagesResponse) => T } = {}) => { - const { data: activeWorkspaceName } = useQueryActiveWorkspaceName(); + const { data: activeWorkspaceName } = useQueryActiveWorkspaceName() const options: V1GetWorkspaceMessagesData = useMemo( () => ({ path: { - workspace_name: activeWorkspaceName ?? "default", + workspace_name: activeWorkspaceName ?? 'default', }, }), - [activeWorkspaceName], - ); + [activeWorkspaceName] + ) return useQuery({ ...v1GetWorkspaceMessagesOptions(options), - ...getQueryCacheConfig("5s"), + ...getQueryCacheConfig('5s'), select: select, - }); -}; + }) +} diff --git a/src/hooks/use-query-list-active-workspaces.ts b/src/hooks/use-query-list-active-workspaces.ts index 4aa87c64..34fb1610 100644 --- a/src/hooks/use-query-list-active-workspaces.ts +++ b/src/hooks/use-query-list-active-workspaces.ts @@ -1,11 +1,11 @@ -import { ListActiveWorkspacesResponse } from "@/api/generated"; -import { v1ListActiveWorkspacesOptions } from "@/api/generated/@tanstack/react-query.gen"; -import { useQuery } from "@tanstack/react-query"; +import { ListActiveWorkspacesResponse } from '@/api/generated' +import { v1ListActiveWorkspacesOptions } from '@/api/generated/@tanstack/react-query.gen' +import { useQuery } from '@tanstack/react-query' export function useQueryListActiveWorkspaces({ select, }: { - select?: (data?: ListActiveWorkspacesResponse) => T; + select?: (data?: ListActiveWorkspacesResponse) => T } = {}) { return useQuery({ ...v1ListActiveWorkspacesOptions(), @@ -13,5 +13,5 @@ export function useQueryListActiveWorkspaces({ refetchIntervalInBackground: true, retry: false, select, - }); + }) } diff --git a/src/hooks/use-query-list-all-models-for-all-providers.ts b/src/hooks/use-query-list-all-models-for-all-providers.ts index 5b731708..dd3d5995 100644 --- a/src/hooks/use-query-list-all-models-for-all-providers.ts +++ b/src/hooks/use-query-list-all-models-for-all-providers.ts @@ -1,8 +1,8 @@ -import { useQuery } from "@tanstack/react-query"; -import { v1ListAllModelsForAllProvidersOptions } from "@/api/generated/@tanstack/react-query.gen"; +import { useQuery } from '@tanstack/react-query' +import { v1ListAllModelsForAllProvidersOptions } from '@/api/generated/@tanstack/react-query.gen' export const useQueryListAllModelsForAllProviders = () => { return useQuery({ ...v1ListAllModelsForAllProvidersOptions(), - }); -}; + }) +} diff --git a/src/hooks/use-query-list-all-workspaces.ts b/src/hooks/use-query-list-all-workspaces.ts index 97c0a455..dad2e0a6 100644 --- a/src/hooks/use-query-list-all-workspaces.ts +++ b/src/hooks/use-query-list-all-workspaces.ts @@ -1,35 +1,35 @@ -import { useQueries } from "@tanstack/react-query"; +import { useQueries } from '@tanstack/react-query' import { v1ListArchivedWorkspacesOptions, v1ListWorkspacesOptions, -} from "@/api/generated/@tanstack/react-query.gen"; +} from '@/api/generated/@tanstack/react-query.gen' import { V1ListArchivedWorkspacesResponse, V1ListWorkspacesResponse, -} from "@/api/generated"; -import { QueryResult } from "@/types/react-query"; +} from '@/api/generated' +import { QueryResult } from '@/types/react-query' type UseQueryReturn = [ QueryResult, QueryResult, -]; +] const combine = (results: UseQueryReturn) => { - const [workspaces, archivedWorkspaces] = results; + const [workspaces, archivedWorkspaces] = results const active = workspaces.data?.workspaces ? workspaces.data?.workspaces.map( (i) => ({ ...i, id: `workspace-${i.name}`, isArchived: false }), - [], + [] ) - : []; + : [] const archived = archivedWorkspaces.data?.workspaces ? archivedWorkspaces.data?.workspaces.map( (i) => ({ ...i, id: `archived-workspace-${i.name}`, isArchived: true }), - [], + [] ) - : []; + : [] return { data: [...active, ...archived], @@ -37,8 +37,8 @@ const combine = (results: UseQueryReturn) => { isFetching: results.some((r) => r.isFetching), isLoading: results.some((r) => r.isLoading), isRefetching: results.some((r) => r.isRefetching), - }; -}; + } +} export const useListAllWorkspaces = () => { return useQueries({ @@ -51,5 +51,5 @@ export const useListAllWorkspaces = () => { ...v1ListArchivedWorkspacesOptions(), }, ], - }); -}; + }) +} diff --git a/src/hooks/use-query-list-workspaces.ts b/src/hooks/use-query-list-workspaces.ts index 5c78c0b8..c16e20d0 100644 --- a/src/hooks/use-query-list-workspaces.ts +++ b/src/hooks/use-query-list-workspaces.ts @@ -1,11 +1,11 @@ -import { useQuery } from "@tanstack/react-query"; -import { v1ListWorkspacesOptions } from "@/api/generated/@tanstack/react-query.gen"; -import { V1ListWorkspacesResponse } from "@/api/generated"; +import { useQuery } from '@tanstack/react-query' +import { v1ListWorkspacesOptions } from '@/api/generated/@tanstack/react-query.gen' +import { V1ListWorkspacesResponse } from '@/api/generated' export function useQueryListWorkspaces({ select, }: { - select?: (data: V1ListWorkspacesResponse) => T; + select?: (data: V1ListWorkspacesResponse) => T } = {}) { return useQuery({ ...v1ListWorkspacesOptions(), @@ -13,5 +13,5 @@ export function useQueryListWorkspaces({ refetchIntervalInBackground: true, retry: false, select, - }); + }) } diff --git a/src/hooks/use-toast-mutation.ts b/src/hooks/use-toast-mutation.ts index e66894a8..88e5cbb2 100644 --- a/src/hooks/use-toast-mutation.ts +++ b/src/hooks/use-toast-mutation.ts @@ -1,12 +1,12 @@ -import { HTTPValidationError } from "@/api/generated"; -import { toast } from "@stacklok/ui-kit"; +import { HTTPValidationError } from '@/api/generated' +import { toast } from '@stacklok/ui-kit' import { DefaultError, // eslint-disable-next-line no-restricted-imports useMutation, UseMutationOptions, -} from "@tanstack/react-query"; -import { useCallback } from "react"; +} from '@tanstack/react-query' +import { useCallback } from 'react' export function useToastMutation< TData = unknown, @@ -19,9 +19,9 @@ export function useToastMutation< loadingMsg, ...options }: UseMutationOptions & { - successMsg?: ((variables: TVariables) => string) | string; - loadingMsg?: string; - errorMsg?: string; + successMsg?: ((variables: TVariables) => string) | string + loadingMsg?: string + errorMsg?: string }) { const { mutateAsync: originalMutateAsync, @@ -29,42 +29,42 @@ export function useToastMutation< // eslint-disable-next-line @typescript-eslint/no-unused-vars mutate: _, ...rest - } = useMutation(options); + } = useMutation(options) const mutateAsync = useCallback( async ( variables: Parameters[0], - options: Parameters[1] = {}, + options: Parameters[1] = {} ) => { - const promise = originalMutateAsync(variables, options); + const promise = originalMutateAsync(variables, options) toast.promise(promise, { success: - typeof successMsg === "function" ? successMsg(variables) : successMsg, - loading: loadingMsg ?? "Loading...", + typeof successMsg === 'function' ? successMsg(variables) : successMsg, + loading: loadingMsg ?? 'Loading...', duration: 5000, error: (e: TError) => { - if (errorMsg) return errorMsg; + if (errorMsg) return errorMsg - if (typeof e.detail == "string") { - return e.detail ?? "An error occurred"; + if (typeof e.detail == 'string') { + return e.detail ?? 'An error occurred' } if (Array.isArray(e.detail)) { const err = e.detail ?.map((item) => `${item.msg} - ${JSON.stringify(item.loc)}`) .filter(Boolean) - .join(", "); + .join(', ') - return err ?? "An error occurred"; + return err ?? 'An error occurred' } - return "An error occurred"; + return 'An error occurred' }, - }); + }) }, - [errorMsg, loadingMsg, originalMutateAsync, successMsg], - ); + [errorMsg, loadingMsg, originalMutateAsync, successMsg] + ) - return { mutateAsync, ...rest }; + return { mutateAsync, ...rest } } diff --git a/src/hooks/useClientSidePagination.ts b/src/hooks/useClientSidePagination.ts index 25290258..0bfea704 100644 --- a/src/hooks/useClientSidePagination.ts +++ b/src/hooks/useClientSidePagination.ts @@ -1,15 +1,15 @@ export function useClientSidePagination( data: T[], page: number, - pageSize: number, + pageSize: number ) { - const pageStart = page * pageSize; - const pageEnd = page * pageSize + pageSize; + const pageStart = page * pageSize + const pageEnd = page * pageSize + pageSize - const dataView = data.slice(pageStart, pageEnd); + const dataView = data.slice(pageStart, pageEnd) - const hasPreviousPage = page > 0; - const hasNextPage = pageEnd < data.length; + const hasPreviousPage = page > 0 + const hasNextPage = pageEnd < data.length - return { pageStart, pageEnd, dataView, hasPreviousPage, hasNextPage }; + return { pageStart, pageEnd, dataView, hasPreviousPage, hasNextPage } } diff --git a/src/hooks/useFormState.ts b/src/hooks/useFormState.ts index 2f859398..c668feda 100644 --- a/src/hooks/useFormState.ts +++ b/src/hooks/useFormState.ts @@ -1,57 +1,57 @@ -import { isEqual } from "lodash"; -import { useCallback, useEffect, useMemo, useRef, useState } from "react"; +import { isEqual } from 'lodash' +import { useCallback, useEffect, useMemo, useRef, useState } from 'react' export type FormState = { - values: T; - updateFormValues: (newState: Partial) => void; - resetForm: () => void; - isDirty: boolean; -}; + values: T + updateFormValues: (newState: Partial) => void + resetForm: () => void + isDirty: boolean +} function useDeepMemo(value: T): T { - const ref = useRef(value); + const ref = useRef(value) if (!isEqual(ref.current, value)) { - ref.current = value; + ref.current = value } - return ref.current; + return ref.current } export function useFormState>( - initialValues: Values, + initialValues: Values ): FormState { - const memoizedInitialValues = useDeepMemo(initialValues); + const memoizedInitialValues = useDeepMemo(initialValues) // this could be replaced with some form library later - const [values, setValues] = useState(memoizedInitialValues); - const [originalValues, setOriginalValues] = useState(values); + const [values, setValues] = useState(memoizedInitialValues) + const [originalValues, setOriginalValues] = useState(values) useEffect(() => { // this logic supports the use case when the initialValues change // due to an async request for instance - setOriginalValues(memoizedInitialValues); - setValues(memoizedInitialValues); - }, [memoizedInitialValues]); + setOriginalValues(memoizedInitialValues) + setValues(memoizedInitialValues) + }, [memoizedInitialValues]) const updateFormValues = useCallback((newState: Partial) => { setValues((prevState: Values) => ({ ...prevState, ...newState, - })); - }, []); + })) + }, []) const resetForm = useCallback(() => { - setValues(originalValues); - }, [originalValues]); + setValues(originalValues) + }, [originalValues]) const isDirty = useMemo( () => !isEqual(values, originalValues), - [values, originalValues], - ); + [values, originalValues] + ) const formState = useMemo( () => ({ values, updateFormValues, resetForm, isDirty }), - [values, updateFormValues, resetForm, isDirty], - ); + [values, updateFormValues, resetForm, isDirty] + ) - return formState; + return formState } diff --git a/src/hooks/useSse.ts b/src/hooks/useSse.ts index 67da4c04..586de07f 100644 --- a/src/hooks/useSse.ts +++ b/src/hooks/useSse.ts @@ -1,34 +1,34 @@ -import { useEffect } from "react"; -import { useLocation } from "react-router-dom"; -import { useQueryClient } from "@tanstack/react-query"; +import { useEffect } from 'react' +import { useLocation } from 'react-router-dom' +import { useQueryClient } from '@tanstack/react-query' import { v1GetWorkspaceAlertsQueryKey, v1GetWorkspaceMessagesQueryKey, -} from "@/api/generated/@tanstack/react-query.gen"; -import { invalidateQueries } from "@/lib/react-query-utils"; +} from '@/api/generated/@tanstack/react-query.gen' +import { invalidateQueries } from '@/lib/react-query-utils' -const BASE_URL = import.meta.env.VITE_BASE_API_URL; +const BASE_URL = import.meta.env.VITE_BASE_API_URL export function useSse() { - const location = useLocation(); - const queryClient = useQueryClient(); + const location = useLocation() + const queryClient = useQueryClient() useEffect(() => { const eventSource = new EventSource( - `${BASE_URL}/api/v1/alerts_notification`, - ); + `${BASE_URL}/api/v1/alerts_notification` + ) eventSource.onmessage = function (event) { - if (event.data.toLowerCase().includes("new alert detected")) { + if (event.data.toLowerCase().includes('new alert detected')) { invalidateQueries(queryClient, [ v1GetWorkspaceAlertsQueryKey, v1GetWorkspaceMessagesQueryKey, - ]); + ]) } - }; + } return () => { - eventSource.close(); - }; - }, [location.pathname, queryClient]); + eventSource.close() + } + }, [location.pathname, queryClient]) } diff --git a/src/lib/__tests__/currency.test.ts b/src/lib/__tests__/currency.test.ts index 1ff4e2a2..79ba3127 100644 --- a/src/lib/__tests__/currency.test.ts +++ b/src/lib/__tests__/currency.test.ts @@ -1,30 +1,30 @@ -import { convertCurrencyToMinor, convertCurrencyFromMinor } from "../currency"; +import { convertCurrencyToMinor, convertCurrencyFromMinor } from '../currency' -it("convertCurrencyToMinor", () => { - expect(convertCurrencyToMinor(1, "AED")).toBe(100); - expect(convertCurrencyToMinor(1, "JOD")).toBe(1000); +it('convertCurrencyToMinor', () => { + expect(convertCurrencyToMinor(1, 'AED')).toBe(100) + expect(convertCurrencyToMinor(1, 'JOD')).toBe(1000) - expect(convertCurrencyToMinor(1.0, "AED")).toBe(100); - expect(convertCurrencyToMinor(1.0, "JOD")).toBe(1000); + expect(convertCurrencyToMinor(1.0, 'AED')).toBe(100) + expect(convertCurrencyToMinor(1.0, 'JOD')).toBe(1000) - expect(convertCurrencyToMinor(1.11, "AED")).toBe(111); - expect(convertCurrencyToMinor(1.11, "JOD")).toBe(1110); + expect(convertCurrencyToMinor(1.11, 'AED')).toBe(111) + expect(convertCurrencyToMinor(1.11, 'JOD')).toBe(1110) - expect(convertCurrencyToMinor(1.1111, "AED")).toBe(111); - expect(convertCurrencyToMinor(1.1111, "JOD")).toBe(1111); + expect(convertCurrencyToMinor(1.1111, 'AED')).toBe(111) + expect(convertCurrencyToMinor(1.1111, 'JOD')).toBe(1111) - expect(convertCurrencyToMinor(10.0, "AED")).toBe(1000); - expect(convertCurrencyToMinor(1.0, "JOD")).toBe(1000); + expect(convertCurrencyToMinor(10.0, 'AED')).toBe(1000) + expect(convertCurrencyToMinor(1.0, 'JOD')).toBe(1000) - expect(convertCurrencyToMinor(420.69, "AED")).toBe(42069); - expect(convertCurrencyToMinor(42.069, "JOD")).toBe(42069); -}); + expect(convertCurrencyToMinor(420.69, 'AED')).toBe(42069) + expect(convertCurrencyToMinor(42.069, 'JOD')).toBe(42069) +}) -it("convertCurrencyFromMinor", () => { - expect(convertCurrencyFromMinor(100, "AED")).toBe(1); - expect(convertCurrencyFromMinor(100, "JOD")).toBe(0.1); - expect(convertCurrencyFromMinor(42069, "AED")).toBe(420.69); - expect(convertCurrencyFromMinor(42069, "JOD")).toBe(42.069); - expect(convertCurrencyFromMinor(42690, "AED")).toBe(426.9); - expect(convertCurrencyFromMinor(42690, "JOD")).toBe(42.69); -}); +it('convertCurrencyFromMinor', () => { + expect(convertCurrencyFromMinor(100, 'AED')).toBe(1) + expect(convertCurrencyFromMinor(100, 'JOD')).toBe(0.1) + expect(convertCurrencyFromMinor(42069, 'AED')).toBe(420.69) + expect(convertCurrencyFromMinor(42069, 'JOD')).toBe(42.069) + expect(convertCurrencyFromMinor(42690, 'AED')).toBe(426.9) + expect(convertCurrencyFromMinor(42690, 'JOD')).toBe(42.69) +}) diff --git a/src/lib/currency.ts b/src/lib/currency.ts index accacdb3..a37589d6 100644 --- a/src/lib/currency.ts +++ b/src/lib/currency.ts @@ -1,72 +1,72 @@ -type Currency = ("GBP" | "USD" | "EUR" | "AED" | "JOD") & string; +type Currency = ('GBP' | 'USD' | 'EUR' | 'AED' | 'JOD') & string type FormatCurrencyOptions = { - currency: Currency; - from_minor?: boolean; - region?: string | string[] | undefined; - to_minor?: boolean; -}; + currency: Currency + from_minor?: boolean + region?: string | string[] | undefined + to_minor?: boolean +} const getCurrencyFormatOptions = (currency: Currency) => { return new Intl.NumberFormat(undefined, { currency: currency, - currencyDisplay: "code", - style: "currency", - }).resolvedOptions(); -}; + currencyDisplay: 'code', + style: 'currency', + }).resolvedOptions() +} export function formatCurrency( number: number, { - currency = "GBP", + currency = 'GBP', from_minor, - region = "en-US", + region = 'en-US', to_minor, - }: FormatCurrencyOptions, + }: FormatCurrencyOptions ): string { if (from_minor) { return new Intl.NumberFormat( region, - getCurrencyFormatOptions(currency), - ).format(convertCurrencyFromMinor(number, currency)); + getCurrencyFormatOptions(currency) + ).format(convertCurrencyFromMinor(number, currency)) } if (to_minor) { return new Intl.NumberFormat( region, - getCurrencyFormatOptions(currency), - ).format(convertCurrencyToMinor(number, currency)); + getCurrencyFormatOptions(currency) + ).format(convertCurrencyToMinor(number, currency)) } return new Intl.NumberFormat( region, - getCurrencyFormatOptions(currency), - ).format(number); + getCurrencyFormatOptions(currency) + ).format(number) } const getDigits = (currency: Currency): number => { const digits = new Intl.NumberFormat(undefined, { currency, - style: "currency", - }).resolvedOptions().maximumFractionDigits; + style: 'currency', + }).resolvedOptions().maximumFractionDigits if (digits === undefined) throw Error( - `[currency/getDigits] Unable to get digits for currency ${currency}`, - ); + `[currency/getDigits] Unable to get digits for currency ${currency}` + ) - return digits; -}; + return digits +} export function convertCurrencyToMinor( amount: number, - currency: Currency, + currency: Currency ): number { - return Math.round(amount * 10 ** getDigits(currency)); + return Math.round(amount * 10 ** getDigits(currency)) } export function convertCurrencyFromMinor( amount: number, - currency: Currency, + currency: Currency ): number { - return amount / 10 ** getDigits(currency); + return amount / 10 ** getDigits(currency) } diff --git a/src/lib/format-number.ts b/src/lib/format-number.ts index 7f88ae50..0d43e0d0 100644 --- a/src/lib/format-number.ts +++ b/src/lib/format-number.ts @@ -1,5 +1,5 @@ export const formatNumberCompact = (value: number) => { - return Intl.NumberFormat("en-US", { - notation: "compact", - }).format(value); -}; + return Intl.NumberFormat('en-US', { + notation: 'compact', + }).format(value) +} diff --git a/src/lib/format-time.ts b/src/lib/format-time.ts index c6c3882d..ee67d8fa 100644 --- a/src/lib/format-time.ts +++ b/src/lib/format-time.ts @@ -1,23 +1,23 @@ -import { formatDistanceToNow } from "date-fns"; +import { formatDistanceToNow } from 'date-fns' -type Format = "relative" | "absolute"; +type Format = 'relative' | 'absolute' export const formatTime = ( date: Date, options: { - format: Format; + format: Format } = { - format: "relative", - }, + format: 'relative', + } ) => { switch (options.format) { - case "absolute": - return date.toLocaleString(); - case "relative": + case 'absolute': + return date.toLocaleString() + case 'relative': return formatDistanceToNow(date, { addSuffix: true, - }); + }) default: - return options.format satisfies never; + return options.format satisfies never } -}; +} diff --git a/src/lib/hrefs.ts b/src/lib/hrefs.ts index 53b2d7e6..b2dc0132 100644 --- a/src/lib/hrefs.ts +++ b/src/lib/hrefs.ts @@ -1,16 +1,16 @@ export const hrefs = { external: { - discord: "https://discord.gg/stacklok", - github: { newIssue: "https://github.com/stacklok/codegate/issues/new" }, + discord: 'https://discord.gg/stacklok', + github: { newIssue: 'https://github.com/stacklok/codegate/issues/new' }, docs: { - home: "https://docs.codegate.ai/", - workspaces: "https://docs.codegate.ai/features/workspaces", + home: 'https://docs.codegate.ai/', + workspaces: 'https://docs.codegate.ai/features/workspaces', }, }, prompt: (id: string) => `/prompt/${id}`, workspaces: { - all: "/workspaces", - create: "/workspace/create", + all: '/workspaces', + create: '/workspace/create', edit: (name: string) => `/workspace/${name}`, }, -}; +} diff --git a/src/lib/is-alert-critical.ts b/src/lib/is-alert-critical.ts index d4ea3e46..f6058240 100644 --- a/src/lib/is-alert-critical.ts +++ b/src/lib/is-alert-critical.ts @@ -1,10 +1,10 @@ import { AlertConversation, V1GetWorkspaceAlertsResponse, -} from "@/api/generated"; +} from '@/api/generated' export function isAlertCritical( - alert: V1GetWorkspaceAlertsResponse[number], + alert: V1GetWorkspaceAlertsResponse[number] ): alert is AlertConversation { - return alert !== null && alert.trigger_category === "critical"; + return alert !== null && alert.trigger_category === 'critical' } diff --git a/src/lib/is-alert-malicious.ts b/src/lib/is-alert-malicious.ts index 4a458dba..eab7b52d 100644 --- a/src/lib/is-alert-malicious.ts +++ b/src/lib/is-alert-malicious.ts @@ -1,17 +1,17 @@ -import { Alert, AlertConversation, Conversation } from "@/api/generated"; +import { Alert, AlertConversation, Conversation } from '@/api/generated' export function isConversationWithMaliciousAlerts( - conversation: Conversation | null, + conversation: Conversation | null ): boolean { - return conversation?.alerts?.some(isAlertMalicious) ?? false; + return conversation?.alerts?.some(isAlertMalicious) ?? false } export function isAlertMalicious(alert: Alert | AlertConversation | null) { return ( - alert?.trigger_category === "critical" && + alert?.trigger_category === 'critical' && alert.trigger_string !== null && - typeof alert.trigger_string === "object" && - "status" in alert.trigger_string && - alert.trigger_string.status === "malicious" - ); + typeof alert.trigger_string === 'object' && + 'status' in alert.trigger_string && + alert.trigger_string.status === 'malicious' + ) } diff --git a/src/lib/is-alert-secret.ts b/src/lib/is-alert-secret.ts index 36d825d6..1054af3c 100644 --- a/src/lib/is-alert-secret.ts +++ b/src/lib/is-alert-secret.ts @@ -1,14 +1,14 @@ -import { Alert, AlertConversation, Conversation } from "@/api/generated"; +import { Alert, AlertConversation, Conversation } from '@/api/generated' export function isConversationWithSecretAlerts( - conversation: Conversation | null, + conversation: Conversation | null ): boolean { - return conversation?.alerts?.some(isAlertSecret) ?? false; + return conversation?.alerts?.some(isAlertSecret) ?? false } export function isAlertSecret(alert: Alert | AlertConversation | null) { return ( - alert?.trigger_category === "critical" && - alert.trigger_type === "codegate-secrets" - ); + alert?.trigger_category === 'critical' && + alert.trigger_type === 'codegate-secrets' + ) } diff --git a/src/lib/multi-filter.ts b/src/lib/multi-filter.ts index 59a847bc..642a932f 100644 --- a/src/lib/multi-filter.ts +++ b/src/lib/multi-filter.ts @@ -12,9 +12,9 @@ */ export function multiFilter( array: T[] | undefined, - predicates: ((i: T) => boolean)[], + predicates: ((i: T) => boolean)[] ): T[] { - if (!array) return []; + if (!array) return [] - return array.filter((i) => predicates.every((p) => p(i) === true)); + return array.filter((i) => predicates.every((p) => p(i) === true)) } diff --git a/src/lib/react-query-utils.ts b/src/lib/react-query-utils.ts index 9bfb1818..f920ac4a 100644 --- a/src/lib/react-query-utils.ts +++ b/src/lib/react-query-utils.ts @@ -1,18 +1,14 @@ -import { OpenApiTsReactQueryKey } from "@/types/openapi-ts"; -import { OptionsLegacyParser } from "@hey-api/client-fetch"; -import { - Query, - QueryClient, - QueryObserverOptions, -} from "@tanstack/react-query"; +import { OpenApiTsReactQueryKey } from '@/types/openapi-ts' +import { OptionsLegacyParser } from '@hey-api/client-fetch' +import { Query, QueryClient, QueryObserverOptions } from '@tanstack/react-query' // NOTE: This is copy/pasted from @/api/generated/@tanstack/react-query.gen type QueryKey = [ - Pick & { - _id: string; - _infinite?: boolean; + Pick & { + _id: string + _infinite?: boolean }, -]; +] // A generic type that describes the possible permutations of openapi-ts // react-query queryKey functions. @@ -22,13 +18,13 @@ type QueryKey = [ // that it is still safe to use. // // eslint-disable-next-line @typescript-eslint/no-explicit-any -type QueryKeyFn = (options: any) => QueryKey[0][]; +type QueryKeyFn = (options: any) => QueryKey[0][] // NOTE: The type constraints on `queryKeyFn` are sufficiently strict that we // can use type assertion for the return type with relative safety. const getQueryKeyFnId = ( - queryKeyFn: QueryKeyFn, -) => queryKeyFn({} as T)[0]?._id as string; + queryKeyFn: QueryKeyFn +) => queryKeyFn({} as T)[0]?._id as string /** * Takes a queryClient, and an array of queryKeyFns, and invalidates all queries @@ -37,42 +33,42 @@ const getQueryKeyFnId = ( export function invalidateQueries( queryClient: QueryClient, queryKeyFns: QueryKeyFn[], - options: { stale: boolean } = { stale: true }, + options: { stale: boolean } = { stale: true } ) { // eslint-disable-next-line no-restricted-syntax return queryClient.invalidateQueries({ - refetchType: "all", + refetchType: 'all', stale: options.stale, predicate: ( - query: Query, + query: Query ) => { return queryKeyFns.some( - (fn) => query.queryKey[0]._id === getQueryKeyFnId(fn), - ); + (fn) => query.queryKey[0]._id === getQueryKeyFnId(fn) + ) }, - }); + }) } export function getQueryCacheConfig( - lifetime: "no-cache" | "5s" | "indefinite", + lifetime: 'no-cache' | '5s' | 'indefinite' ) { switch (lifetime) { - case "no-cache": + case 'no-cache': return { staleTime: 0, - } as const satisfies Pick; + } as const satisfies Pick - case "5s": + case '5s': return { staleTime: 5 * 1_000, - } as const satisfies Pick; + } as const satisfies Pick - case "indefinite": + case 'indefinite': return { staleTime: Infinity, - } as const satisfies Pick; + } as const satisfies Pick default: - return lifetime satisfies never; + return lifetime satisfies never } } diff --git a/src/lib/test-utils.tsx b/src/lib/test-utils.tsx index cf3e1394..c237514b 100644 --- a/src/lib/test-utils.tsx +++ b/src/lib/test-utils.tsx @@ -1,25 +1,25 @@ -import { ConfirmProvider } from "@/context/confirm-context"; -import { DarkModeProvider, Toaster } from "@stacklok/ui-kit"; -import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; -import { RenderOptions, render } from "@testing-library/react"; -import React, { ReactNode } from "react"; +import { ConfirmProvider } from '@/context/confirm-context' +import { DarkModeProvider, Toaster } from '@stacklok/ui-kit' +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' +import { RenderOptions, render } from '@testing-library/react' +import React, { ReactNode } from 'react' import { MemoryRouter, MemoryRouterProps, Route, Routes, -} from "react-router-dom"; -import { UiKitClientSideRoutingProvider } from "./ui-kit-client-side-routing"; +} from 'react-router-dom' +import { UiKitClientSideRoutingProvider } from './ui-kit-client-side-routing' type RoutConfig = { - routeConfig?: MemoryRouterProps; - pathConfig?: string; -}; + routeConfig?: MemoryRouterProps + pathConfig?: string +} export const TestQueryClientProvider = ({ children, }: { - children: ReactNode; + children: ReactNode }) => { return ( {children} - ); -}; + ) +} const renderWithProviders = ( children: React.ReactNode, - options?: Omit & RoutConfig, + options?: Omit & RoutConfig ) => render( @@ -54,15 +54,15 @@ const renderWithProviders = ( - + - , - ); + + ) -export * from "@testing-library/react"; +export * from '@testing-library/react' -export { renderWithProviders as render }; +export { renderWithProviders as render } diff --git a/src/lib/ui-kit-client-side-routing.tsx b/src/lib/ui-kit-client-side-routing.tsx index 6ad759cf..1522c2e0 100644 --- a/src/lib/ui-kit-client-side-routing.tsx +++ b/src/lib/ui-kit-client-side-routing.tsx @@ -1,12 +1,12 @@ -import { useNavigate } from "react-router-dom"; -import { RouterProvider } from "@stacklok/ui-kit"; +import { useNavigate } from 'react-router-dom' +import { RouterProvider } from '@stacklok/ui-kit' export function UiKitClientSideRoutingProvider({ children, }: { - children: React.ReactNode; + children: React.ReactNode }) { - const navigate = useNavigate(); + const navigate = useNavigate() - return {children}; + return {children} } diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 64df408c..7e2854aa 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -1,45 +1,45 @@ -import { format } from "date-fns"; +import { format } from 'date-fns' -const FILEPATH_REGEX = /(?:---FILEPATH|Path:|\/\/\s*filepath:)\s*([^\s]+)/g; -const COMPARE_CODE_REGEX = /Compare this snippet[^:]*:/g; +const FILEPATH_REGEX = /(?:---FILEPATH|Path:|\/\/\s*filepath:)\s*([^\s]+)/g +const COMPARE_CODE_REGEX = /Compare this snippet[^:]*:/g function parsingByKeys(text: string | undefined, timestamp: string) { - const fallback = `Prompt ${format(new Date(timestamp ?? ""), "y/MM/dd - hh:mm:ss a")}`; + const fallback = `Prompt ${format(new Date(timestamp ?? ''), 'y/MM/dd - hh:mm:ss a')}` try { - if (!text) return fallback; - const filePath = text.match(FILEPATH_REGEX); - const compareCode = text.match(COMPARE_CODE_REGEX); + if (!text) return fallback + const filePath = text.match(FILEPATH_REGEX) + const compareCode = text.match(COMPARE_CODE_REGEX) // there some edge cases in copilot where the prompts are not correctly parsed. In this case is better to show the filepath if (compareCode || filePath) { if (filePath) - return `Prompt on file${filePath[0]?.trim().toLocaleLowerCase()}`; + return `Prompt on file${filePath[0]?.trim().toLocaleLowerCase()}` if (compareCode) - return `Prompt from snippet ${compareCode[0]?.trim().toLocaleLowerCase()}`; + return `Prompt from snippet ${compareCode[0]?.trim().toLocaleLowerCase()}` } - return text.trim(); + return text.trim() } catch { - return fallback; + return fallback } } export function parsingPromptText(message: string, timestamp: string) { try { // checking malformed markdown code blocks - const regex = /^(.*)```[\s\S]*?```(.*)$/s; - const match = message.match(regex); + const regex = /^(.*)```[\s\S]*?```(.*)$/s + const match = message.match(regex) if (match !== null && match !== undefined) { - const beforeMarkdown = match[1]?.trim(); - const afterMarkdown = match[2]?.trim(); - const title = beforeMarkdown || afterMarkdown; - return parsingByKeys(title, timestamp); + const beforeMarkdown = match[1]?.trim() + const afterMarkdown = match[2]?.trim() + const title = beforeMarkdown || afterMarkdown + return parsingByKeys(title, timestamp) } - return parsingByKeys(message, timestamp); + return parsingByKeys(message, timestamp) } catch { - return message.trim(); + return message.trim() } } @@ -47,26 +47,26 @@ export function sanitizeQuestionPrompt({ question, answer, }: { - question: string; - answer: string; + question: string + answer: string }) { try { // it shouldn't be possible to receive the prompt answer without a question if (!answer) { - throw new Error("Missing AI answer"); + throw new Error('Missing AI answer') } // Check if 'answer' is truthy; if so, try to find and return the text after "Query:" - const index = question.indexOf("Query:"); + const index = question.indexOf('Query:') if (index !== -1) { // Return the substring starting right after the first occurrence of "Query:" // Adding the length of "Query:" to the index to start after it - return question.substring(index + "Query:".length).trim(); + return question.substring(index + 'Query:'.length).trim() } - return question; + return question } catch (error) { // Log the error and return the original question as a fallback - console.error("Error processing the question:", error); - return question; + console.error('Error processing the question:', error) + return question } } diff --git a/src/main.tsx b/src/main.tsx index fea4b6cd..81f1a200 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,25 +1,25 @@ -import { StrictMode } from "react"; -import { createRoot } from "react-dom/client"; -import "./index.css"; -import "@stacklok/ui-kit/style"; -import App from "./App.tsx"; +import { StrictMode } from 'react' +import { createRoot } from 'react-dom/client' +import './index.css' +import '@stacklok/ui-kit/style' +import App from './App.tsx' -import ErrorBoundary from "./components/ErrorBoundary.tsx"; -import { Error } from "./components/Error.tsx"; -import { DarkModeProvider, Toaster } from "@stacklok/ui-kit"; -import { client } from "./api/generated/index.ts"; -import { QueryClientProvider } from "./components/react-query-provider.tsx"; -import { BrowserRouter } from "react-router-dom"; -import { UiKitClientSideRoutingProvider } from "./lib/ui-kit-client-side-routing.tsx"; -import { ConfirmProvider } from "./context/confirm-context.tsx"; -import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; +import ErrorBoundary from './components/ErrorBoundary.tsx' +import { Error } from './components/Error.tsx' +import { DarkModeProvider, Toaster } from '@stacklok/ui-kit' +import { client } from './api/generated/index.ts' +import { QueryClientProvider } from './components/react-query-provider.tsx' +import { BrowserRouter } from 'react-router-dom' +import { UiKitClientSideRoutingProvider } from './lib/ui-kit-client-side-routing.tsx' +import { ConfirmProvider } from './context/confirm-context.tsx' +import { ReactQueryDevtools } from '@tanstack/react-query-devtools' // Initialize the API client client.setConfig({ baseUrl: import.meta.env.VITE_BASE_API_URL, -}); +}) -createRoot(document.getElementById("root")!).render( +createRoot(document.getElementById('root')!).render( @@ -37,5 +37,5 @@ createRoot(document.getElementById("root")!).render( - , -); + +) diff --git a/src/md.d.ts b/src/md.d.ts index 31817d8a..6bc02de3 100644 --- a/src/md.d.ts +++ b/src/md.d.ts @@ -1,7 +1,7 @@ -declare module "*.md" { +declare module '*.md' { // When "Mode.MARKDOWN" is requested - const markdown: string; + const markdown: string // Modify below per your usage - export { markdown }; + export { markdown } } diff --git a/src/mocks/msw/handlers.ts b/src/mocks/msw/handlers.ts index 4d6af386..8a931761 100644 --- a/src/mocks/msw/handlers.ts +++ b/src/mocks/msw/handlers.ts @@ -1,89 +1,89 @@ -import { http, HttpResponse } from "msw"; -import mockedAlerts from "@/mocks/msw/fixtures/GET_ALERTS.json"; -import mockedWorkspaces from "@/mocks/msw/fixtures/GET_WORKSPACES.json"; -import mockedProviders from "@/mocks/msw/fixtures/GET_PROVIDERS.json"; -import mockedProvidersModels from "@/mocks/msw/fixtures/GET_PROVIDERS_MODELS.json"; -import { ProviderType } from "@/api/generated"; -import { mockConversation } from "./mockers/conversation.mock"; -import { mswEndpoint } from "@/test/msw-endpoint"; +import { http, HttpResponse } from 'msw' +import mockedAlerts from '@/mocks/msw/fixtures/GET_ALERTS.json' +import mockedWorkspaces from '@/mocks/msw/fixtures/GET_WORKSPACES.json' +import mockedProviders from '@/mocks/msw/fixtures/GET_PROVIDERS.json' +import mockedProvidersModels from '@/mocks/msw/fixtures/GET_PROVIDERS_MODELS.json' +import { ProviderType } from '@/api/generated' +import { mockConversation } from './mockers/conversation.mock' +import { mswEndpoint } from '@/test/msw-endpoint' export const handlers = [ - http.get(mswEndpoint("/health"), () => + http.get(mswEndpoint('/health'), () => HttpResponse.json({ - current_version: "foo", - latest_version: "bar", + current_version: 'foo', + latest_version: 'bar', is_latest: false, error: null, - }), + }) ), - http.get(mswEndpoint("/api/v1/version"), () => - HttpResponse.json({ status: "healthy" }), + http.get(mswEndpoint('/api/v1/version'), () => + HttpResponse.json({ status: 'healthy' }) ), - http.get(mswEndpoint("/api/v1/workspaces/active"), () => + http.get(mswEndpoint('/api/v1/workspaces/active'), () => HttpResponse.json({ workspaces: [ { - name: "my-awesome-workspace", + name: 'my-awesome-workspace', is_active: true, last_updated: new Date(Date.now()).toISOString(), }, ], - }), + }) ), - http.get(mswEndpoint("/api/v1/workspaces/:workspace_name/messages"), () => { + http.get(mswEndpoint('/api/v1/workspaces/:workspace_name/messages'), () => { return HttpResponse.json( - Array.from({ length: 10 }).map(() => mockConversation()), - ); + Array.from({ length: 10 }).map(() => mockConversation()) + ) }), - http.get(mswEndpoint("/api/v1/workspaces/:workspace_name/alerts"), () => { - return HttpResponse.json(mockedAlerts); + http.get(mswEndpoint('/api/v1/workspaces/:workspace_name/alerts'), () => { + return HttpResponse.json(mockedAlerts) }), - http.get(mswEndpoint("/api/v1/workspaces"), () => { - return HttpResponse.json(mockedWorkspaces); + http.get(mswEndpoint('/api/v1/workspaces'), () => { + return HttpResponse.json(mockedWorkspaces) }), - http.get(mswEndpoint("/api/v1/workspaces/archive"), () => { + http.get(mswEndpoint('/api/v1/workspaces/archive'), () => { return HttpResponse.json({ workspaces: [ { - name: "archived_workspace", + name: 'archived_workspace', is_active: false, }, ], - }); + }) }), - http.post(mswEndpoint("/api/v1/workspaces"), () => { - return HttpResponse.json(mockedWorkspaces); + http.post(mswEndpoint('/api/v1/workspaces'), () => { + return HttpResponse.json(mockedWorkspaces) }), http.post( - mswEndpoint("/api/v1/workspaces/active"), - () => new HttpResponse(null, { status: 204 }), + mswEndpoint('/api/v1/workspaces/active'), + () => new HttpResponse(null, { status: 204 }) ), http.post( - mswEndpoint("/api/v1/workspaces/archive/:workspace_name/recover"), - () => new HttpResponse(null, { status: 204 }), + mswEndpoint('/api/v1/workspaces/archive/:workspace_name/recover'), + () => new HttpResponse(null, { status: 204 }) ), http.delete( - mswEndpoint("/api/v1/workspaces/:workspace_name"), - () => new HttpResponse(null, { status: 204 }), + mswEndpoint('/api/v1/workspaces/:workspace_name'), + () => new HttpResponse(null, { status: 204 }) ), http.delete( - mswEndpoint("/api/v1/workspaces/archive/:workspace_name"), - () => new HttpResponse(null, { status: 204 }), + mswEndpoint('/api/v1/workspaces/archive/:workspace_name'), + () => new HttpResponse(null, { status: 204 }) ), http.get( - mswEndpoint("/api/v1/workspaces/:workspace_name/custom-instructions"), + mswEndpoint('/api/v1/workspaces/:workspace_name/custom-instructions'), () => { - return HttpResponse.json({ prompt: "foo" }); - }, + return HttpResponse.json({ prompt: 'foo' }) + } ), http.get( - mswEndpoint("/api/v1/workspaces/:workspace_name/token-usage"), + mswEndpoint('/api/v1/workspaces/:workspace_name/token-usage'), () => { return HttpResponse.json({ tokens_by_model: { - "claude-3-5-sonnet-latest": { + 'claude-3-5-sonnet-latest': { provider_type: ProviderType.ANTHROPIC, - model: "claude-3-5-sonnet-latest", + model: 'claude-3-5-sonnet-latest', token_usage: { input_tokens: 1183, output_tokens: 433, @@ -98,54 +98,54 @@ export const handlers = [ input_cost: 0.003549, output_cost: 0.006495, }, - }); - }, + }) + } ), http.put( - mswEndpoint("/api/v1/workspaces/:workspace_name/custom-instructions"), - () => new HttpResponse(null, { status: 204 }), + mswEndpoint('/api/v1/workspaces/:workspace_name/custom-instructions'), + () => new HttpResponse(null, { status: 204 }) ), - http.get(mswEndpoint("/api/v1/workspaces/:workspace_name/muxes"), () => + http.get(mswEndpoint('/api/v1/workspaces/:workspace_name/muxes'), () => HttpResponse.json([ { - provider_id: "openai", - model: "gpt-3.5-turbo", - matcher_type: "file_regex", - matcher: ".*\\.txt", + provider_id: 'openai', + model: 'gpt-3.5-turbo', + matcher_type: 'file_regex', + matcher: '.*\\.txt', }, { - provider_id: "anthropic", - model: "davinci", - matcher_type: "catch_all", + provider_id: 'anthropic', + model: 'davinci', + matcher_type: 'catch_all', }, - ]), + ]) ), http.put( - mswEndpoint("/api/v1/workspaces/:workspace_name/muxes"), - () => new HttpResponse(null, { status: 204 }), + mswEndpoint('/api/v1/workspaces/:workspace_name/muxes'), + () => new HttpResponse(null, { status: 204 }) ), - http.get(mswEndpoint("/api/v1/provider-endpoints/:provider_id/models"), () => - HttpResponse.json(mockedProvidersModels), + http.get(mswEndpoint('/api/v1/provider-endpoints/:provider_id/models'), () => + HttpResponse.json(mockedProvidersModels) ), - http.get(mswEndpoint("/api/v1/provider-endpoints/models"), () => - HttpResponse.json(mockedProvidersModels), + http.get(mswEndpoint('/api/v1/provider-endpoints/models'), () => + HttpResponse.json(mockedProvidersModels) ), - http.get(mswEndpoint("/api/v1/provider-endpoints/:provider_id"), () => - HttpResponse.json(mockedProviders[0]), + http.get(mswEndpoint('/api/v1/provider-endpoints/:provider_id'), () => + HttpResponse.json(mockedProviders[0]) ), - http.get(mswEndpoint("/api/v1/provider-endpoints"), () => - HttpResponse.json(mockedProviders), + http.get(mswEndpoint('/api/v1/provider-endpoints'), () => + HttpResponse.json(mockedProviders) ), http.post( - mswEndpoint("/api/v1/provider-endpoints"), - () => new HttpResponse(null, { status: 204 }), + mswEndpoint('/api/v1/provider-endpoints'), + () => new HttpResponse(null, { status: 204 }) ), http.put( - mswEndpoint("/api/v1/provider-endpoints"), - () => new HttpResponse(null, { status: 204 }), + mswEndpoint('/api/v1/provider-endpoints'), + () => new HttpResponse(null, { status: 204 }) ), http.delete( - mswEndpoint("/api/v1/provider-endpoints"), - () => new HttpResponse(null, { status: 204 }), + mswEndpoint('/api/v1/provider-endpoints'), + () => new HttpResponse(null, { status: 204 }) ), -]; +] diff --git a/src/mocks/msw/mockers/alert.mock.ts b/src/mocks/msw/mockers/alert.mock.ts index 7c96ca03..7d77e3cc 100644 --- a/src/mocks/msw/mockers/alert.mock.ts +++ b/src/mocks/msw/mockers/alert.mock.ts @@ -1,60 +1,60 @@ -import { Alert } from "@/api/generated"; -import { faker } from "@faker-js/faker"; +import { Alert } from '@/api/generated' +import { faker } from '@faker-js/faker' const ALERT_SECRET_FIELDS = { - trigger_string: "foo", - trigger_type: "codegate-secrets", -} satisfies Pick; + trigger_string: 'foo', + trigger_type: 'codegate-secrets', +} satisfies Pick const ALERT_MALICIOUS_FIELDS = { trigger_string: { - name: "invokehttp", - type: "pypi", - status: "malicious", - description: "Python HTTP for Humans.", + name: 'invokehttp', + type: 'pypi', + status: 'malicious', + description: 'Python HTTP for Humans.', }, - trigger_type: "codegate-context-retriever", -} satisfies Pick; + trigger_type: 'codegate-context-retriever', +} satisfies Pick const getBaseAlert = ({ timestamp, }: { - timestamp: string; -}): Omit => ({ + timestamp: string +}): Omit => ({ id: faker.string.uuid(), prompt_id: faker.string.uuid(), code_snippet: null, - trigger_category: "critical", + trigger_category: 'critical', timestamp: timestamp, -}); +}) export const mockAlert = ({ type, }: { - type: "secret" | "malicious"; + type: 'secret' | 'malicious' }): Alert => { - const timestamp = faker.date.recent().toISOString(); + const timestamp = faker.date.recent().toISOString() - const base: Omit = getBaseAlert({ + const base: Omit = getBaseAlert({ timestamp, - }); + }) switch (type) { - case "malicious": { + case 'malicious': { const result: Alert = { ...base, ...ALERT_MALICIOUS_FIELDS, - }; + } - return result; + return result } - case "secret": { + case 'secret': { const result: Alert = { ...base, ...ALERT_SECRET_FIELDS, - }; + } - return result; + return result } } -}; +} diff --git a/src/mocks/msw/mockers/conversation.mock.ts b/src/mocks/msw/mockers/conversation.mock.ts index 9a0f16ca..11c10862 100644 --- a/src/mocks/msw/mockers/conversation.mock.ts +++ b/src/mocks/msw/mockers/conversation.mock.ts @@ -1,21 +1,21 @@ -import { Conversation, QuestionType } from "@/api/generated"; -import { faker } from "@faker-js/faker"; -import { TOKEN_USAGE_AGG } from "./token-usage.mock"; -import { mockAlert } from "./alert.mock"; +import { Conversation, QuestionType } from '@/api/generated' +import { faker } from '@faker-js/faker' +import { TOKEN_USAGE_AGG } from './token-usage.mock' +import { mockAlert } from './alert.mock' export function mockConversation({ type = QuestionType.CHAT, withTokenUsage = true, alertsConfig = {}, }: { - type?: QuestionType; - withTokenUsage?: boolean; + type?: QuestionType + withTokenUsage?: boolean alertsConfig?: { - numAlerts?: number; - type?: "secret" | "malicious" | "any"; - }; + numAlerts?: number + type?: 'secret' | 'malicious' | 'any' + } } = {}) { - const timestamp = faker.date.recent().toISOString(); + const timestamp = faker.date.recent().toISOString() return { question_answers: [ @@ -32,23 +32,23 @@ export function mockConversation({ }, }, ], - provider: "vllm", + provider: 'vllm', alerts: Array.from({ length: - typeof alertsConfig?.numAlerts === "number" + typeof alertsConfig?.numAlerts === 'number' ? alertsConfig?.numAlerts : faker.number.int({ min: 2, max: 5 }), }).map(() => mockAlert({ type: - alertsConfig?.type == null || alertsConfig.type === "any" - ? faker.helpers.arrayElement(["secret", "malicious"]) + alertsConfig?.type == null || alertsConfig.type === 'any' + ? faker.helpers.arrayElement(['secret', 'malicious']) : alertsConfig.type, - }), + }) ), token_usage_agg: withTokenUsage ? TOKEN_USAGE_AGG : null, type, chat_id: faker.string.uuid(), // NOTE: This isn't a UUID in the API conversation_timestamp: timestamp, - } as const satisfies Conversation; + } as const satisfies Conversation } diff --git a/src/mocks/msw/mockers/token-usage.mock.ts b/src/mocks/msw/mockers/token-usage.mock.ts index 52cc00cc..ad9bbbe1 100644 --- a/src/mocks/msw/mockers/token-usage.mock.ts +++ b/src/mocks/msw/mockers/token-usage.mock.ts @@ -1,10 +1,10 @@ -import { ProviderType, TokenUsageAggregate } from "@/api/generated"; +import { ProviderType, TokenUsageAggregate } from '@/api/generated' export const TOKEN_USAGE_AGG = { tokens_by_model: { - "claude-3-5-sonnet-latest": { + 'claude-3-5-sonnet-latest': { provider_type: ProviderType.ANTHROPIC, - model: "claude-3-5-sonnet-latest", + model: 'claude-3-5-sonnet-latest', token_usage: { input_tokens: 1183, output_tokens: 433, @@ -19,4 +19,4 @@ export const TOKEN_USAGE_AGG = { input_cost: 0.003549, output_cost: 0.006495, }, -} as const satisfies TokenUsageAggregate; +} as const satisfies TokenUsageAggregate diff --git a/src/mocks/msw/node.ts b/src/mocks/msw/node.ts index 7b37f2ac..86f7d615 100644 --- a/src/mocks/msw/node.ts +++ b/src/mocks/msw/node.ts @@ -1,4 +1,4 @@ -import { setupServer } from "msw/node"; -import { handlers } from "./handlers"; +import { setupServer } from 'msw/node' +import { handlers } from './handlers' -export const server = setupServer(...handlers); +export const server = setupServer(...handlers) diff --git a/src/routes/__tests__/route-certificate-security.test.tsx b/src/routes/__tests__/route-certificate-security.test.tsx index 1ef52b0d..d3adf19e 100644 --- a/src/routes/__tests__/route-certificate-security.test.tsx +++ b/src/routes/__tests__/route-certificate-security.test.tsx @@ -1,19 +1,17 @@ -import { render } from "@/lib/test-utils"; -import { screen, within } from "@testing-library/react"; -import { describe, expect, it } from "vitest"; -import { RouteCertificateSecurity } from "../route-certificate-security"; +import { render } from '@/lib/test-utils' +import { screen, within } from '@testing-library/react' +import { describe, expect, it } from 'vitest' +import { RouteCertificateSecurity } from '../route-certificate-security' -describe("Certificate security", () => { - it("has breadcrumbs", () => { - render(); +describe('Certificate security', () => { + it('has breadcrumbs', () => { + render() - const breadcrumbs = screen.getByRole("list", { name: "Breadcrumbs" }); - expect(breadcrumbs).toBeVisible(); + const breadcrumbs = screen.getByRole('list', { name: 'Breadcrumbs' }) + expect(breadcrumbs).toBeVisible() expect( - within(breadcrumbs).getByRole("link", { name: "Dashboard" }), - ).toHaveAttribute("href", "/"); - expect( - within(breadcrumbs).getByText(/certificate security/i), - ).toBeVisible(); - }); -}); + within(breadcrumbs).getByRole('link', { name: 'Dashboard' }) + ).toHaveAttribute('href', '/') + expect(within(breadcrumbs).getByText(/certificate security/i)).toBeVisible() + }) +}) diff --git a/src/routes/__tests__/route-certificates.test.tsx b/src/routes/__tests__/route-certificates.test.tsx index 9dbb65f2..411b76e8 100644 --- a/src/routes/__tests__/route-certificates.test.tsx +++ b/src/routes/__tests__/route-certificates.test.tsx @@ -1,85 +1,85 @@ -import { render } from "@/lib/test-utils"; -import { screen, within } from "@testing-library/react"; -import { describe, expect, it } from "vitest"; -import userEvent from "@testing-library/user-event"; -import { RouteCertificates } from "../route-certificates"; - -describe("Certificates", () => { - it("should render download certificate", () => { - render(); +import { render } from '@/lib/test-utils' +import { screen, within } from '@testing-library/react' +import { describe, expect, it } from 'vitest' +import userEvent from '@testing-library/user-event' +import { RouteCertificates } from '../route-certificates' + +describe('Certificates', () => { + it('should render download certificate', () => { + render() expect( - screen.getByRole("heading", { name: "CodeGate CA certificate" }), - ).toBeVisible(); + screen.getByRole('heading', { name: 'CodeGate CA certificate' }) + ).toBeVisible() expect( - screen.getByRole("button", { name: "Download certificate" }), - ).toBeVisible(); - expect(screen.getByRole("link", { name: "Learn more" })).toHaveAttribute( - "href", - "/certificates/security", - ); + screen.getByRole('button', { name: 'Download certificate' }) + ).toBeVisible() + expect(screen.getByRole('link', { name: 'Learn more' })).toHaveAttribute( + 'href', + '/certificates/security' + ) expect( - screen.getByRole("heading", { name: "Certificate Management" }), - ).toBeVisible(); + screen.getByRole('heading', { name: 'Certificate Management' }) + ).toBeVisible() - expect(screen.getByText("macOS")).toBeVisible(); - expect(screen.getByText("Windows")).toBeVisible(); - expect(screen.getByText("Linux")).toBeVisible(); - }); + expect(screen.getByText('macOS')).toBeVisible() + expect(screen.getByText('Windows')).toBeVisible() + expect(screen.getByText('Linux')).toBeVisible() + }) - it("has breadcrumbs", () => { - render(); + it('has breadcrumbs', () => { + render() - const breadcrumbs = screen.getByRole("list", { name: "Breadcrumbs" }); - expect(breadcrumbs).toBeVisible(); + const breadcrumbs = screen.getByRole('list', { name: 'Breadcrumbs' }) + expect(breadcrumbs).toBeVisible() expect( - within(breadcrumbs).getByRole("link", { name: "Dashboard" }), - ).toHaveAttribute("href", "/"); - expect(within(breadcrumbs).getByText(/certificates/i)).toBeVisible(); - }); + within(breadcrumbs).getByRole('link', { name: 'Dashboard' }) + ).toHaveAttribute('href', '/') + expect(within(breadcrumbs).getByText(/certificates/i)).toBeVisible() + }) - it("should render macOS certificate installation", async () => { - render(); + it('should render macOS certificate installation', async () => { + render() expect( screen.getByText( - "Open the downloaded certificate file; Keychain Access will open and prompt you to to add the certificates.", - ), - ).toBeVisible(); + 'Open the downloaded certificate file; Keychain Access will open and prompt you to to add the certificates.' + ) + ).toBeVisible() await userEvent.click( - screen.getByRole("button", { name: "Remove certificate" }), - ); - expect(screen.getByText("Launch the Keychain Access app.")).toBeVisible(); - }); + screen.getByRole('button', { name: 'Remove certificate' }) + ) + expect(screen.getByText('Launch the Keychain Access app.')).toBeVisible() + }) - it("should render Windows certificate installation", async () => { - render(); + it('should render Windows certificate installation', async () => { + render() - await userEvent.click(screen.getByText("Windows")); + await userEvent.click(screen.getByText('Windows')) expect( - screen.getByText("Double-click the downloaded certificate file."), - ).toBeVisible(); + screen.getByText('Double-click the downloaded certificate file.') + ).toBeVisible() await userEvent.click( - screen.getByRole("button", { name: "Remove certificate" }), - ); - expect(screen.getByText("certmgr.msc")).toBeVisible(); - }); + screen.getByRole('button', { name: 'Remove certificate' }) + ) + expect(screen.getByText('certmgr.msc')).toBeVisible() + }) - it("should render Linux certificate installation", async () => { - render(); + it('should render Linux certificate installation', async () => { + render() - await userEvent.click(screen.getByText("Linux")); + await userEvent.click(screen.getByText('Linux')) expect( - screen.getByText("/usr/local/share/ca-certificates/codegate.crt"), - ).toBeVisible(); + screen.getByText('/usr/local/share/ca-certificates/codegate.crt') + ).toBeVisible() await userEvent.click( - screen.getByRole("button", { name: "Remove certificate" }), - ); - expect(screen.getByText("/usr/local/share/ca-certificates/")).toBeVisible(); - }); -}); + screen.getByRole('button', { name: 'Remove certificate' }) + ) + expect(screen.getByText('/usr/local/share/ca-certificates/')).toBeVisible() + }) +}) diff --git a/src/routes/__tests__/route-chat.test.tsx b/src/routes/__tests__/route-chat.test.tsx index 9af830ef..04413655 100644 --- a/src/routes/__tests__/route-chat.test.tsx +++ b/src/routes/__tests__/route-chat.test.tsx @@ -1,190 +1,190 @@ -import { render } from "@/lib/test-utils"; -import { screen, waitFor, within } from "@testing-library/react"; -import { expect, it, vi } from "vitest"; -import { RouteChat } from "../route-chat"; -import { server } from "@/mocks/msw/node"; -import { http, HttpResponse } from "msw"; -import { mswEndpoint } from "@/test/msw-endpoint"; -import { mockConversation } from "@/mocks/msw/mockers/conversation.mock"; -import { getConversationTitle } from "@/features/dashboard-messages/lib/get-conversation-title"; -import { formatTime } from "@/lib/format-time"; -import userEvent from "@testing-library/user-event"; -import { getProviderString } from "@/features/dashboard-messages/lib/get-provider-string"; -import { isAlertMalicious } from "@/lib/is-alert-malicious"; -import { isAlertSecret } from "@/lib/is-alert-secret"; - -vi.mock("@stacklok/ui-kit", async (importOriginal) => { +import { render } from '@/lib/test-utils' +import { screen, waitFor, within } from '@testing-library/react' +import { expect, it, vi } from 'vitest' +import { RouteChat } from '../route-chat' +import { server } from '@/mocks/msw/node' +import { http, HttpResponse } from 'msw' +import { mswEndpoint } from '@/test/msw-endpoint' +import { mockConversation } from '@/mocks/msw/mockers/conversation.mock' +import { getConversationTitle } from '@/features/dashboard-messages/lib/get-conversation-title' +import { formatTime } from '@/lib/format-time' +import userEvent from '@testing-library/user-event' +import { getProviderString } from '@/features/dashboard-messages/lib/get-provider-string' +import { isAlertMalicious } from '@/lib/is-alert-malicious' +import { isAlertSecret } from '@/lib/is-alert-secret' + +vi.mock('@stacklok/ui-kit', async (importOriginal) => { return { - ...(await importOriginal()), - Avatar: ({ "data-testid": dataTestId }: { "data-testid": string }) => ( + ...(await importOriginal()), + Avatar: ({ 'data-testid': dataTestId }: { 'data-testid': string }) => (
    ), - }; -}); + } +}) -vi.mock("@/hooks/useCurrentPromptStore", () => ({ +vi.mock('@/hooks/useCurrentPromptStore', () => ({ useCurrentPromptStore: vi.fn(() => ({ - currentPromptId: "test-chat-id", + currentPromptId: 'test-chat-id', setCurrentPromptId: vi.fn(), })), -})); +})) -it("renders breadcrumbs", async () => { - const conversation = mockConversation(); +it('renders breadcrumbs', async () => { + const conversation = mockConversation() server.use( - http.get(mswEndpoint("/api/v1/workspaces/:workspace_name/messages"), () => - HttpResponse.json([conversation]), - ), - ); + http.get(mswEndpoint('/api/v1/workspaces/:workspace_name/messages'), () => + HttpResponse.json([conversation]) + ) + ) render(, { routeConfig: { initialEntries: [`/prompt/${conversation.chat_id}`], }, - pathConfig: "/prompt/:id", - }); + pathConfig: '/prompt/:id', + }) await waitFor(() => { - const breadcrumbs = screen.getByRole("list", { name: "Breadcrumbs" }); - expect(breadcrumbs).toBeVisible(); + const breadcrumbs = screen.getByRole('list', { name: 'Breadcrumbs' }) + expect(breadcrumbs).toBeVisible() expect( - within(breadcrumbs).getByRole("link", { name: "Dashboard" }), - ).toHaveAttribute("href", "/"); - }); -}); + within(breadcrumbs).getByRole('link', { name: 'Dashboard' }) + ).toHaveAttribute('href', '/') + }) +}) -it("renders title", async () => { - const conversation = mockConversation(); +it('renders title', async () => { + const conversation = mockConversation() server.use( - http.get(mswEndpoint("/api/v1/workspaces/:workspace_name/messages"), () => - HttpResponse.json([conversation]), - ), - ); + http.get(mswEndpoint('/api/v1/workspaces/:workspace_name/messages'), () => + HttpResponse.json([conversation]) + ) + ) render(, { routeConfig: { initialEntries: [`/prompt/${conversation.chat_id}`], }, - pathConfig: "/prompt/:id", - }); + pathConfig: '/prompt/:id', + }) await waitFor(() => { - const heading = screen.getByRole("heading", { + const heading = screen.getByRole('heading', { level: 1, - }); + }) - expect(heading).toHaveTextContent(getConversationTitle(conversation)); + expect(heading).toHaveTextContent(getConversationTitle(conversation)) expect(heading).toHaveTextContent( - formatTime(new Date(conversation.conversation_timestamp)), - ); - }); -}); + formatTime(new Date(conversation.conversation_timestamp)) + ) + }) +}) -it("renders conversation summary correctly", async () => { - const conversation = mockConversation({ alertsConfig: { numAlerts: 10 } }); +it('renders conversation summary correctly', async () => { + const conversation = mockConversation({ alertsConfig: { numAlerts: 10 } }) - const maliciousCount = conversation.alerts.filter(isAlertMalicious).length; - const secretsCount = conversation.alerts.filter(isAlertSecret).length; + const maliciousCount = conversation.alerts.filter(isAlertMalicious).length + const secretsCount = conversation.alerts.filter(isAlertSecret).length server.use( - http.get(mswEndpoint("/api/v1/workspaces/:workspace_name/messages"), () => - HttpResponse.json([conversation]), - ), - ); + http.get(mswEndpoint('/api/v1/workspaces/:workspace_name/messages'), () => + HttpResponse.json([conversation]) + ) + ) render(, { routeConfig: { initialEntries: [`/prompt/${conversation.chat_id}`], }, - pathConfig: "/prompt/:id", - }); + pathConfig: '/prompt/:id', + }) await waitFor(() => { - expect(screen.getByLabelText("Conversation summary")).toBeVisible(); - }); + expect(screen.getByLabelText('Conversation summary')).toBeVisible() + }) - const { getByText } = within(screen.getByLabelText("Conversation summary")); + const { getByText } = within(screen.getByLabelText('Conversation summary')) - expect(getByText(getProviderString(conversation.provider))).toBeVisible(); + expect(getByText(getProviderString(conversation.provider))).toBeVisible() expect( getByText( formatTime(new Date(conversation.conversation_timestamp), { - format: "absolute", - }), - ), - ).toBeVisible(); + format: 'absolute', + }) + ) + ).toBeVisible() - expect(getByText(conversation.chat_id)).toBeVisible(); + expect(getByText(conversation.chat_id)).toBeVisible() expect( - getByText(`${maliciousCount} malicious packages detected`), - ).toBeVisible(); + getByText(`${maliciousCount} malicious packages detected`) + ).toBeVisible() - expect(getByText(`${secretsCount} secrets detected`)).toBeVisible(); -}); + expect(getByText(`${secretsCount} secrets detected`)).toBeVisible() +}) -it("renders chat correctly", async () => { - const conversation = mockConversation(); +it('renders chat correctly', async () => { + const conversation = mockConversation() - const question = conversation.question_answers[0].question.message; - const answer = conversation.question_answers[0].answer.message; + const question = conversation.question_answers[0].question.message + const answer = conversation.question_answers[0].answer.message server.use( - http.get(mswEndpoint("/api/v1/workspaces/:workspace_name/messages"), () => - HttpResponse.json([conversation]), - ), - ); + http.get(mswEndpoint('/api/v1/workspaces/:workspace_name/messages'), () => + HttpResponse.json([conversation]) + ) + ) render(, { routeConfig: { initialEntries: [`/prompt/${conversation.chat_id}`], }, - pathConfig: "/prompt/:id", - }); + pathConfig: '/prompt/:id', + }) await waitFor(() => { const { getByText } = within( - screen.getByLabelText("Conversation transcript"), - ); - expect(getByText(question)).toBeVisible(); - expect(getByText(answer)).toBeVisible(); - }); -}); + screen.getByLabelText('Conversation transcript') + ) + expect(getByText(question)).toBeVisible() + expect(getByText(answer)).toBeVisible() + }) +}) -it("renders tabs", async () => { - const conversation = mockConversation(); +it('renders tabs', async () => { + const conversation = mockConversation() server.use( - http.get(mswEndpoint("/api/v1/workspaces/:workspace_name/messages"), () => - HttpResponse.json([conversation]), - ), - ); + http.get(mswEndpoint('/api/v1/workspaces/:workspace_name/messages'), () => + HttpResponse.json([conversation]) + ) + ) render(, { routeConfig: { initialEntries: [`/prompt/${conversation.chat_id}`], }, - pathConfig: "/prompt/:id", - }); + pathConfig: '/prompt/:id', + }) await waitFor(() => { - expect(screen.getByRole("tab", { name: /overview/i })).toBeVisible(); - expect(screen.getByRole("tab", { name: /secrets/i })).toBeVisible(); - }); -}); + expect(screen.getByRole('tab', { name: /overview/i })).toBeVisible() + expect(screen.getByRole('tab', { name: /secrets/i })).toBeVisible() + }) +}) -it("can navigate using tabs", async () => { - const conversation = mockConversation(); +it('can navigate using tabs', async () => { + const conversation = mockConversation() server.use( - http.get(mswEndpoint("/api/v1/workspaces/:workspace_name/messages"), () => - HttpResponse.json([conversation]), - ), - ); + http.get(mswEndpoint('/api/v1/workspaces/:workspace_name/messages'), () => + HttpResponse.json([conversation]) + ) + ) render(, { routeConfig: { @@ -194,46 +194,46 @@ it("can navigate using tabs", async () => { }, ], }, - pathConfig: "/prompt/:id", - }); + pathConfig: '/prompt/:id', + }) await waitFor(() => { - expect(screen.getByRole("tab", { name: /overview/i })).toBeVisible(); - expect(screen.getByRole("tab", { name: /secrets/i })).toBeVisible(); + expect(screen.getByRole('tab', { name: /overview/i })).toBeVisible() + expect(screen.getByRole('tab', { name: /secrets/i })).toBeVisible() - expect(screen.getByRole("tab", { name: /overview/i })).toHaveAttribute( - "data-selected", - "true", - ); - expect(screen.getByRole("tab", { name: /secrets/i })).not.toHaveAttribute( - "data-selected", - "true", - ); - }); + expect(screen.getByRole('tab', { name: /overview/i })).toHaveAttribute( + 'data-selected', + 'true' + ) + expect(screen.getByRole('tab', { name: /secrets/i })).not.toHaveAttribute( + 'data-selected', + 'true' + ) + }) - await userEvent.click(screen.getByRole("tab", { name: /secrets/i })); + await userEvent.click(screen.getByRole('tab', { name: /secrets/i })) await waitFor(() => { - expect(screen.getByRole("tab", { name: /overview/i })).not.toHaveAttribute( - "data-selected", - "true", - ); - expect(screen.getByRole("tab", { name: /secrets/i })).toHaveAttribute( - "data-selected", - "true", - ); - }); - - await userEvent.click(screen.getByRole("tab", { name: /overview/i })); + expect(screen.getByRole('tab', { name: /overview/i })).not.toHaveAttribute( + 'data-selected', + 'true' + ) + expect(screen.getByRole('tab', { name: /secrets/i })).toHaveAttribute( + 'data-selected', + 'true' + ) + }) + + await userEvent.click(screen.getByRole('tab', { name: /overview/i })) await waitFor(() => { - expect(screen.getByRole("tab", { name: /overview/i })).toHaveAttribute( - "data-selected", - "true", - ); - expect(screen.getByRole("tab", { name: /secrets/i })).not.toHaveAttribute( - "data-selected", - "true", - ); - }); -}); + expect(screen.getByRole('tab', { name: /overview/i })).toHaveAttribute( + 'data-selected', + 'true' + ) + expect(screen.getByRole('tab', { name: /secrets/i })).not.toHaveAttribute( + 'data-selected', + 'true' + ) + }) +}) diff --git a/src/routes/__tests__/route-dashboard.test.tsx b/src/routes/__tests__/route-dashboard.test.tsx index 965866e9..06b65527 100644 --- a/src/routes/__tests__/route-dashboard.test.tsx +++ b/src/routes/__tests__/route-dashboard.test.tsx @@ -1,193 +1,193 @@ -import { render } from "@/lib/test-utils"; -import { screen, waitFor, within } from "@testing-library/react"; -import { expect, it } from "vitest"; +import { render } from '@/lib/test-utils' +import { screen, waitFor, within } from '@testing-library/react' +import { expect, it } from 'vitest' -import { server } from "@/mocks/msw/node"; -import { HttpResponse, http } from "msw"; -import userEvent from "@testing-library/user-event"; -import { RouteDashboard } from "../route-dashboard"; -import { mswEndpoint } from "@/test/msw-endpoint"; +import { server } from '@/mocks/msw/node' +import { HttpResponse, http } from 'msw' +import userEvent from '@testing-library/user-event' +import { RouteDashboard } from '../route-dashboard' +import { mswEndpoint } from '@/test/msw-endpoint' -import { mockConversation } from "@/mocks/msw/mockers/conversation.mock"; -import { faker } from "@faker-js/faker"; +import { mockConversation } from '@/mocks/msw/mockers/conversation.mock' +import { faker } from '@faker-js/faker' -it("should mount alert summaries", async () => { - render(); +it('should mount alert summaries', async () => { + render() expect( - screen.getByRole("heading", { name: /workspace token usage/i }), - ).toBeVisible(); + screen.getByRole('heading', { name: /workspace token usage/i }) + ).toBeVisible() expect( - screen.getByRole("heading", { name: /secrets redacted/i }), - ).toBeVisible(); + screen.getByRole('heading', { name: /secrets redacted/i }) + ).toBeVisible() expect( - screen.getByRole("heading", { name: /malicious packages/i }), - ).toBeVisible(); -}); + screen.getByRole('heading', { name: /malicious packages/i }) + ).toBeVisible() +}) -it("should render messages table", async () => { - render(); +it('should render messages table', async () => { + render() expect( - screen.getByRole("grid", { + screen.getByRole('grid', { name: /alerts table/i, - }), - ).toBeVisible(); -}); + }) + ).toBeVisible() +}) -it("shows only conversations with secrets when you click on the secrets tab", async () => { +it('shows only conversations with secrets when you click on the secrets tab', async () => { server.use( - http.get(mswEndpoint("/api/v1/workspaces/:workspace_name/messages"), () => { + http.get(mswEndpoint('/api/v1/workspaces/:workspace_name/messages'), () => { return HttpResponse.json([ ...Array.from({ length: 10 }).map(() => mockConversation({ - alertsConfig: { numAlerts: 10, type: "malicious" }, - }), + alertsConfig: { numAlerts: 10, type: 'malicious' }, + }) ), ...Array.from({ length: 10 }).map(() => mockConversation({ - alertsConfig: { numAlerts: 10, type: "secret" }, - }), + alertsConfig: { numAlerts: 10, type: 'secret' }, + }) ), - ]); - }), - ); - render(); + ]) + }) + ) + render() await waitFor(() => { - expect(screen.queryByText(/loading.../i)).not.toBeInTheDocument(); - }); + expect(screen.queryByText(/loading.../i)).not.toBeInTheDocument() + }) - expect(screen.getByTestId(/tab-all-count/i)).toHaveTextContent("20"); - expect(screen.getByTestId(/tab-secrets-count/i)).toHaveTextContent("10"); + expect(screen.getByTestId(/tab-all-count/i)).toHaveTextContent('20') + expect(screen.getByTestId(/tab-secrets-count/i)).toHaveTextContent('10') await userEvent.click( - screen.getByRole("tab", { + screen.getByRole('tab', { name: /secrets/i, - }), - ); + }) + ) - const tbody = screen.getAllByRole("rowgroup")[1] as HTMLElement; + const tbody = screen.getAllByRole('rowgroup')[1] as HTMLElement await waitFor(() => { - const secretsCountButtons = within(tbody).getAllByRole("button", { + const secretsCountButtons = within(tbody).getAllByRole('button', { name: /secrets count/, - }) as HTMLElement[]; + }) as HTMLElement[] secretsCountButtons.forEach((e) => { - expect(e).toHaveTextContent("10"); - }); - }); -}); + expect(e).toHaveTextContent('10') + }) + }) +}) -it("shows only conversations with malicious when you click on the malicious tab", async () => { +it('shows only conversations with malicious when you click on the malicious tab', async () => { server.use( - http.get(mswEndpoint("/api/v1/workspaces/:workspace_name/messages"), () => { + http.get(mswEndpoint('/api/v1/workspaces/:workspace_name/messages'), () => { return HttpResponse.json([ ...Array.from({ length: 10 }).map(() => mockConversation({ - alertsConfig: { numAlerts: 10, type: "malicious" }, - }), + alertsConfig: { numAlerts: 10, type: 'malicious' }, + }) ), ...Array.from({ length: 10 }).map(() => mockConversation({ - alertsConfig: { numAlerts: 10, type: "secret" }, - }), + alertsConfig: { numAlerts: 10, type: 'secret' }, + }) ), - ]); - }), - ); - render(); + ]) + }) + ) + render() await waitFor(() => { - expect(screen.queryByText(/loading.../i)).not.toBeInTheDocument(); - }); + expect(screen.queryByText(/loading.../i)).not.toBeInTheDocument() + }) - expect(screen.getByTestId(/tab-all-count/i)).toHaveTextContent("20"); - expect(screen.getByTestId(/tab-malicious-count/i)).toHaveTextContent("10"); + expect(screen.getByTestId(/tab-all-count/i)).toHaveTextContent('20') + expect(screen.getByTestId(/tab-malicious-count/i)).toHaveTextContent('10') await userEvent.click( - screen.getByRole("tab", { + screen.getByRole('tab', { name: /malicious/i, - }), - ); + }) + ) - const tbody = screen.getAllByRole("rowgroup")[1] as HTMLElement; + const tbody = screen.getAllByRole('rowgroup')[1] as HTMLElement await waitFor(() => { - const secretsCountButtons = within(tbody).getAllByRole("button", { + const secretsCountButtons = within(tbody).getAllByRole('button', { name: /malicious packages count/, - }) as HTMLElement[]; + }) as HTMLElement[] secretsCountButtons.forEach((e) => { - expect(e).toHaveTextContent("10"); - }); - }); -}); + expect(e).toHaveTextContent('10') + }) + }) +}) -it("should render searchbox", async () => { - render(); +it('should render searchbox', async () => { + render() expect( - screen.getByRole("searchbox", { + screen.getByRole('searchbox', { name: /search messages/i, - }), - ).toBeVisible(); -}); + }) + ).toBeVisible() +}) -it("can filter using searchbox", async () => { - const STRING_TO_FILTER_BY = "foo-bar-my-awesome-string.com"; +it('can filter using searchbox', async () => { + const STRING_TO_FILTER_BY = 'foo-bar-my-awesome-string.com' // mock a conversation to filter to // - replace the message with our search string // - timestamp very far in the past, so it is sorted to end of list - const CONVERSATION_TO_FILTER_BY = mockConversation(); - (CONVERSATION_TO_FILTER_BY.question_answers[0].question.message as string) = - STRING_TO_FILTER_BY; - (CONVERSATION_TO_FILTER_BY.conversation_timestamp as string) = faker.date + const CONVERSATION_TO_FILTER_BY = mockConversation() + ;(CONVERSATION_TO_FILTER_BY.question_answers[0].question.message as string) = + STRING_TO_FILTER_BY + ;(CONVERSATION_TO_FILTER_BY.conversation_timestamp as string) = faker.date .past({ years: 1 }) - .toISOString(); + .toISOString() server.use( - http.get(mswEndpoint("/api/v1/workspaces/:workspace_name/messages"), () => { + http.get(mswEndpoint('/api/v1/workspaces/:workspace_name/messages'), () => { return HttpResponse.json([ ...Array.from({ length: 15 }).map(() => mockConversation()), // at least 1 page worth of data CONVERSATION_TO_FILTER_BY, - ]); - }), - ); - render(); + ]) + }) + ) + render() await waitFor(() => { - expect(screen.queryByText(/loading.../i)).not.toBeInTheDocument(); - }); + expect(screen.queryByText(/loading.../i)).not.toBeInTheDocument() + }) - expect(screen.queryByText(STRING_TO_FILTER_BY)).not.toBeInTheDocument(); + expect(screen.queryByText(STRING_TO_FILTER_BY)).not.toBeInTheDocument() await userEvent.type( - screen.getByRole("searchbox", { name: /search messages/i }), - STRING_TO_FILTER_BY, - ); + screen.getByRole('searchbox', { name: /search messages/i }), + STRING_TO_FILTER_BY + ) expect( - within(screen.getByRole("grid")).queryByText(STRING_TO_FILTER_BY), - ).toBeVisible(); -}); + within(screen.getByRole('grid')).queryByText(STRING_TO_FILTER_BY) + ).toBeVisible() +}) -it("should sort messages by date desc", async () => { - render(); +it('should sort messages by date desc', async () => { + render() await waitFor(() => { - expect(screen.queryByText(/loading.../i)).not.toBeInTheDocument(); - }); + expect(screen.queryByText(/loading.../i)).not.toBeInTheDocument() + }) - const tbody = screen.getAllByRole("rowgroup")[1] as HTMLElement; + const tbody = screen.getAllByRole('rowgroup')[1] as HTMLElement const newest = ( - within(tbody).getAllByRole("row")[1] as HTMLElement - ).getAttribute("data-timestamp") as string; + within(tbody).getAllByRole('row')[1] as HTMLElement + ).getAttribute('data-timestamp') as string const oldest = ( - within(tbody).getAllByRole("row")[2] as HTMLElement - ).getAttribute("data-timestamp") as string; + within(tbody).getAllByRole('row')[2] as HTMLElement + ).getAttribute('data-timestamp') as string - expect(oldest > newest).toBe(false); -}); + expect(oldest > newest).toBe(false) +}) diff --git a/src/routes/__tests__/route-workspace.test.tsx b/src/routes/__tests__/route-workspace.test.tsx index 5209a5f8..4ecf1b7a 100644 --- a/src/routes/__tests__/route-workspace.test.tsx +++ b/src/routes/__tests__/route-workspace.test.tsx @@ -1,152 +1,150 @@ -import { render, waitFor, within } from "@/lib/test-utils"; -import { test, expect, vi } from "vitest"; -import userEvent from "@testing-library/user-event"; -import { RouteWorkspace } from "../route-workspace"; -import { useParams } from "react-router-dom"; +import { render, waitFor, within } from '@/lib/test-utils' +import { test, expect, vi } from 'vitest' +import userEvent from '@testing-library/user-event' +import { RouteWorkspace } from '../route-workspace' +import { useParams } from 'react-router-dom' -const mockNavigate = vi.fn(); +const mockNavigate = vi.fn() const renderComponent = () => render(, { routeConfig: { - initialEntries: ["/workspace/foo"], + initialEntries: ['/workspace/foo'], }, - pathConfig: "/workspace/:name", - }); + pathConfig: '/workspace/:name', + }) -vi.mock("@monaco-editor/react", () => { +vi.mock('@monaco-editor/react', () => { const FakeEditor = vi.fn((props) => { return ( - ); - }); - return { default: FakeEditor }; -}); + ) + }) + return { default: FakeEditor } +}) -vi.mock("@/features/workspace/hooks/use-active-workspace-name", () => ({ - useActiveWorkspaceName: vi.fn(() => ({ data: "baz" })), -})); +vi.mock('@/features/workspace/hooks/use-active-workspace-name', () => ({ + useActiveWorkspaceName: vi.fn(() => ({ data: 'baz' })), +})) -vi.mock("react-router-dom", async () => { +vi.mock('react-router-dom', async () => { const original = - await vi.importActual( - "react-router-dom", - ); + await vi.importActual('react-router-dom') return { ...original, useNavigate: () => mockNavigate, - useParams: vi.fn(() => ({ name: "foo" })), - }; -}); + useParams: vi.fn(() => ({ name: 'foo' })), + } +}) -test("renders title", () => { - const { getByRole } = renderComponent(); +test('renders title', () => { + const { getByRole } = renderComponent() expect( - getByRole("heading", { name: "Workspace settings for foo", level: 1 }), - ).toBeVisible(); -}); + getByRole('heading', { name: 'Workspace settings for foo', level: 1 }) + ).toBeVisible() +}) -test("renders workspace name input", () => { - const { getByRole } = renderComponent(); +test('renders workspace name input', () => { + const { getByRole } = renderComponent() - expect(getByRole("textbox", { name: /workspace name/i })).toBeVisible(); -}); + expect(getByRole('textbox', { name: /workspace name/i })).toBeVisible() +}) -test("renders system prompt editor", async () => { - const { getByTestId } = renderComponent(); +test('renders system prompt editor', async () => { + const { getByTestId } = renderComponent() await waitFor(() => { - expect(getByTestId("system-prompt-editor")).toBeVisible(); - }); -}); + expect(getByTestId('system-prompt-editor')).toBeVisible() + }) +}) -test("has breadcrumbs", () => { - const { getByRole } = renderComponent(); +test('has breadcrumbs', () => { + const { getByRole } = renderComponent() - const breadcrumbs = getByRole("list", { name: "Breadcrumbs" }); - expect(breadcrumbs).toBeVisible(); + const breadcrumbs = getByRole('list', { name: 'Breadcrumbs' }) + expect(breadcrumbs).toBeVisible() expect( - within(breadcrumbs).getByRole("link", { name: "Dashboard" }), - ).toHaveAttribute("href", "/"); + within(breadcrumbs).getByRole('link', { name: 'Dashboard' }) + ).toHaveAttribute('href', '/') expect( - within(breadcrumbs).getByRole("link", { name: /manage workspaces/i }), - ).toHaveAttribute("href", "/workspaces"); - expect(within(breadcrumbs).getByText(/workspace settings/i)).toBeVisible(); -}); - -test("rename workspace", async () => { - (useParams as unknown as ReturnType).mockReturnValue({ - name: "foo", - }); - const { getByRole, getByTestId } = renderComponent(); - - const workspaceName = getByRole("textbox", { + within(breadcrumbs).getByRole('link', { name: /manage workspaces/i }) + ).toHaveAttribute('href', '/workspaces') + expect(within(breadcrumbs).getByText(/workspace settings/i)).toBeVisible() +}) + +test('rename workspace', async () => { + ;(useParams as unknown as ReturnType).mockReturnValue({ + name: 'foo', + }) + const { getByRole, getByTestId } = renderComponent() + + const workspaceName = getByRole('textbox', { name: /workspace name/i, - }); - await userEvent.type(workspaceName, "_renamed"); + }) + await userEvent.type(workspaceName, '_renamed') - const saveBtn = within(getByTestId("workspace-name")).getByRole("button", { + const saveBtn = within(getByTestId('workspace-name')).getByRole('button', { name: /save/i, - }); + }) await waitFor(() => { - expect(saveBtn).toBeEnabled(); - }); - await userEvent.click(saveBtn); - await waitFor(() => expect(mockNavigate).toHaveBeenCalledTimes(1)); - expect(mockNavigate).toHaveBeenCalledWith("/workspace/foo_renamed"); -}); - -test("revert changes button", async () => { - (useParams as unknown as ReturnType).mockReturnValue({ - name: "foo", - }); - const { getByRole, getByTestId } = renderComponent(); - - const workspaceName = getByRole("textbox", { + expect(saveBtn).toBeEnabled() + }) + await userEvent.click(saveBtn) + await waitFor(() => expect(mockNavigate).toHaveBeenCalledTimes(1)) + expect(mockNavigate).toHaveBeenCalledWith('/workspace/foo_renamed') +}) + +test('revert changes button', async () => { + ;(useParams as unknown as ReturnType).mockReturnValue({ + name: 'foo', + }) + const { getByRole, getByTestId } = renderComponent() + + const workspaceName = getByRole('textbox', { name: /workspace name/i, - }); - await userEvent.type(workspaceName, "_renamed"); + }) + await userEvent.type(workspaceName, '_renamed') await waitFor(() => { expect( - within(getByTestId("workspace-name")).getByRole("button", { + within(getByTestId('workspace-name')).getByRole('button', { name: /revert changes/i, - }), - ).toBeEnabled(); - }); + }) + ).toBeEnabled() + }) - const revertButton = within(getByTestId("workspace-name")).getByRole( - "button", + const revertButton = within(getByTestId('workspace-name')).getByRole( + 'button', { name: /.*revert changes.*/i, - }, - ); - await userEvent.click(revertButton); + } + ) + await userEvent.click(revertButton) await waitFor(() => { expect( - within(getByTestId("workspace-name")).getByRole("button", { + within(getByTestId('workspace-name')).getByRole('button', { name: /save/i, - }), - ).toBeDisabled(); - }); + }) + ).toBeDisabled() + }) expect( - within(getByTestId("workspace-name")).queryByRole("button", { + within(getByTestId('workspace-name')).queryByRole('button', { name: /.*revert changes.*/i, - }), - ).toBe(null); + }) + ).toBe(null) expect( - getByRole("textbox", { + getByRole('textbox', { name: /workspace name/i, - }), - ).toHaveValue("foo"); -}); + }) + ).toHaveValue('foo') +}) diff --git a/src/routes/__tests__/route-workspaces.test.tsx b/src/routes/__tests__/route-workspaces.test.tsx index 37b44cb6..9d7739c9 100644 --- a/src/routes/__tests__/route-workspaces.test.tsx +++ b/src/routes/__tests__/route-workspaces.test.tsx @@ -1,56 +1,56 @@ -import { render } from "@/lib/test-utils"; -import { screen, waitFor, within } from "@testing-library/react"; -import { describe, expect, it } from "vitest"; -import { RouteWorkspaces } from "../route-workspaces"; +import { render } from '@/lib/test-utils' +import { screen, waitFor, within } from '@testing-library/react' +import { describe, expect, it } from 'vitest' +import { RouteWorkspaces } from '../route-workspaces' -describe("Workspaces page", () => { +describe('Workspaces page', () => { beforeEach(() => { - render(); - }); + render() + }) - it("has breadcrumbs", () => { - const breadcrumbs = screen.getByRole("list", { name: "Breadcrumbs" }); - expect(breadcrumbs).toBeVisible(); + it('has breadcrumbs', () => { + const breadcrumbs = screen.getByRole('list', { name: 'Breadcrumbs' }) + expect(breadcrumbs).toBeVisible() expect( - within(breadcrumbs).getByRole("link", { name: "Dashboard" }), - ).toHaveAttribute("href", "/"); - expect(within(breadcrumbs).getByText(/manage workspaces/i)).toBeVisible(); - }); + within(breadcrumbs).getByRole('link', { name: 'Dashboard' }) + ).toHaveAttribute('href', '/') + expect(within(breadcrumbs).getByText(/manage workspaces/i)).toBeVisible() + }) - it("has a title", () => { + it('has a title', () => { expect( - screen.getByRole("heading", { name: /manage workspaces/i }), - ).toBeVisible(); - }); + screen.getByRole('heading', { name: /manage workspaces/i }) + ).toBeVisible() + }) - it("has a table with the correct columns", () => { - expect(screen.getByRole("columnheader", { name: /name/i })).toBeVisible(); - }); + it('has a table with the correct columns', () => { + expect(screen.getByRole('columnheader', { name: /name/i })).toBeVisible() + }) - it("has a row for each workspace", async () => { + it('has a row for each workspace', async () => { await waitFor(() => { - expect(screen.getAllByRole("row").length).toBeGreaterThan(1); - }); + expect(screen.getAllByRole('row').length).toBeGreaterThan(1) + }) expect( - screen.getByRole("rowheader", { name: /myworkspace/i }), - ).toBeVisible(); + screen.getByRole('rowheader', { name: /myworkspace/i }) + ).toBeVisible() expect( - screen.getByRole("rowheader", { name: /anotherworkspae/i }), - ).toBeVisible(); + screen.getByRole('rowheader', { name: /anotherworkspae/i }) + ).toBeVisible() - const firstRow = screen.getByRole("row", { name: /myworkspace/i }); + const firstRow = screen.getByRole('row', { name: /myworkspace/i }) - expect(firstRow).toHaveAttribute("data-href", "/workspace/myworkspace"); - }); + expect(firstRow).toHaveAttribute('data-href', '/workspace/myworkspace') + }) - it("has archived workspace", async () => { + it('has archived workspace', async () => { await waitFor(() => { - expect(screen.getAllByRole("row").length).toBeGreaterThan(1); - }); + expect(screen.getAllByRole('row').length).toBeGreaterThan(1) + }) expect( - screen.getByRole("rowheader", { name: /archived_workspace/i }), - ).toBeVisible(); - }); -}); + screen.getByRole('rowheader', { name: /archived_workspace/i }) + ).toBeVisible() + }) +}) diff --git a/src/routes/route-certificate-security.tsx b/src/routes/route-certificate-security.tsx index 784a2dff..6b0ad1b1 100644 --- a/src/routes/route-certificate-security.tsx +++ b/src/routes/route-certificate-security.tsx @@ -1,5 +1,5 @@ -import { BreadcrumbHome } from "@/components/BreadcrumbHome"; -import { Breadcrumb, Breadcrumbs, Card, CardBody } from "@stacklok/ui-kit"; +import { BreadcrumbHome } from '@/components/BreadcrumbHome' +import { Breadcrumb, Breadcrumbs, Card, CardBody } from '@stacklok/ui-kit' const SecurityShieldIcon = () => ( @@ -20,7 +20,7 @@ const SecurityShieldIcon = () => ( d="M9 12l2 2 4-4" /> -); +) const KeySecurityIcon = () => ( @@ -33,7 +33,7 @@ const KeySecurityIcon = () => ( d="M21 2l-2 2m-7.61 7.61a5.5 5.5 0 1 1-7.778 7.778 5.5 5.5 0 0 1 7.777-7.777zm0 0L15.5 7.5m0 0l3 3L22 7l-3-3m-3.5 3.5L19 4" /> -); +) const OpenSourceIcon = () => ( @@ -56,7 +56,7 @@ const OpenSourceIcon = () => ( d="M12 6v12m-6-6h12" /> -); +) export function RouteCertificateSecurity() { return ( @@ -66,19 +66,19 @@ export function RouteCertificateSecurity() { Certificate Security -
    -
    -

    Certificate Security

    +
    +
    +

    Certificate Security

    -
    +
    -

    - Robust certificate security +

    + Robust Certificate Security

    -

    +

    Security is a top priority for us. We have designed CodeGate's local certificate management with security in mind, balanced against ease of use. @@ -92,18 +92,18 @@ export function RouteCertificateSecurity() { -

    +
    -

    +

    Key security features

    -

    +

    Per-domain certificate generation

    -

    +

    Instead of using wildcard certificates, CodeGate generates a unique certificate for each domain. This approach minimizes security risks by limiting the impact of any single @@ -111,10 +111,10 @@ export function RouteCertificateSecurity() {

    -

    +

    High-strength encryption with 4096-bit RSA keys

    -

    +

    CodeGate utilizes 4096-bit RSA keys for certificate authority operations, providing enhanced security compared to standard 2048-bit keys. The increased key length @@ -124,10 +124,10 @@ export function RouteCertificateSecurity() {

    -

    +

    Secure SSL/TLS configuration

    -

    +

    CodeGate's SSL context is configured to enforce the latest security standards, including strong cipher suites and disabling outdated protocols. This ensures secure and @@ -135,10 +135,10 @@ export function RouteCertificateSecurity() {

    -

    +

    Certificate caching and management

    -

    +

    Certificates are cached efficiently to optimize performance without compromising security. Additionally, mechanisms are in place to manage certificate lifecycle and prevent @@ -151,10 +151,10 @@ export function RouteCertificateSecurity() { -

    +
    -

    +

    Open source and community engagement

    @@ -172,10 +172,10 @@ export function RouteCertificateSecurity() {

    If you discover a security vulnerability or have suggestions - for improvement, please reach out to us at{" "} + for improvement, please reach out to us at{' '} security@stacklok.com @@ -183,15 +183,15 @@ export function RouteCertificateSecurity() { standards.

    - Explore our codebase on{" "} + Explore our codebase on{' '} GitHub - {" "} + {' '} and join our community to help ensure CodeGate is secure and reliable for everyone.

    @@ -201,5 +201,5 @@ export function RouteCertificateSecurity() {
    - ); + ) } diff --git a/src/routes/route-certificates.tsx b/src/routes/route-certificates.tsx index 34d112c0..bafda697 100644 --- a/src/routes/route-certificates.tsx +++ b/src/routes/route-certificates.tsx @@ -1,5 +1,5 @@ // import { Card } from "./ui/card"; -import { BreadcrumbHome } from "@/components/BreadcrumbHome"; +import { BreadcrumbHome } from '@/components/BreadcrumbHome' import { Card, Button, @@ -7,21 +7,21 @@ import { CardBody, Breadcrumbs, Breadcrumb, -} from "@stacklok/ui-kit"; -import { useState } from "react"; -import { markdown as linuxInstall } from "../markdown/certificates/linux-install.md"; -import { markdown as linuxRemove } from "../markdown/certificates/linux-remove.md"; -import { markdown as windowsInstall } from "../markdown/certificates/windows-install.md"; -import { markdown as windowsRemove } from "../markdown/certificates/windows-remove.md"; -import { markdown as macosInstall } from "../markdown/certificates/macos-install.md"; -import { markdown as macosRemove } from "../markdown/certificates/macos-remove.md"; -import { Markdown } from "@/components/Markdown"; +} from '@stacklok/ui-kit' +import { useState } from 'react' +import { markdown as linuxInstall } from '../markdown/certificates/linux-install.md' +import { markdown as linuxRemove } from '../markdown/certificates/linux-remove.md' +import { markdown as windowsInstall } from '../markdown/certificates/windows-install.md' +import { markdown as windowsRemove } from '../markdown/certificates/windows-remove.md' +import { markdown as macosInstall } from '../markdown/certificates/macos-install.md' +import { markdown as macosRemove } from '../markdown/certificates/macos-remove.md' +import { Markdown } from '@/components/Markdown' -type OS = "macos" | "windows" | "linux"; -type Action = "install" | "remove"; +type OS = 'macos' | 'windows' | 'linux' +type Action = 'install' | 'remove' const CheckIcon = () => ( - + ( d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" /> -); +) const ShieldIcon = () => ( @@ -44,7 +44,7 @@ const ShieldIcon = () => ( d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" /> -); +) const ArrowIcon = () => ( @@ -57,25 +57,25 @@ const ArrowIcon = () => ( d="M9 5l7 7-7 7" /> -); +) export function RouteCertificates() { - const [activeOS, setActiveOS] = useState("macos"); - const [activeAction, setActiveAction] = useState("install"); + const [activeOS, setActiveOS] = useState('macos') + const [activeAction, setActiveAction] = useState('install') const handleDownload = () => { - const link = document.createElement("a"); - link.href = "/certificates/codegate_ca.crt"; - link.download = "codegate.crt"; - document.body.appendChild(link); - link.click(); - document.body.removeChild(link); - }; + const link = document.createElement('a') + link.href = '/certificates/codegate_ca.crt' + link.download = 'codegate.crt' + document.body.appendChild(link) + link.click() + document.body.removeChild(link) + } type ListSchema = Record< - "macos" | "windows" | "linux", + 'macos' | 'windows' | 'linux', Record - >; + > const steps = { macos: { @@ -90,9 +90,9 @@ export function RouteCertificates() { install: linuxInstall, remove: linuxRemove, }, - } as const satisfies ListSchema; + } as const satisfies ListSchema - const currentSteps = steps[activeOS][activeAction]; + const currentSteps = steps[activeOS][activeAction] return ( <> @@ -101,20 +101,20 @@ export function RouteCertificates() { Certificates -
    -

    Certificates

    +
    +

    Certificates

    -
    +
    -

    +

    CodeGate CA certificate

    -

    +

    This certificate allows CodeGate to act as a secure proxy for GitHub Copilot. This certificate is unique to your system.

    @@ -126,7 +126,7 @@ export function RouteCertificates() { -

    +

    Is this certificate safe to install on my machine?

    @@ -167,60 +167,60 @@ export function RouteCertificates() { -

    +

    Certificate management

    {/* OS Selection Tabs */}
    {/* Action Selection Tabs */} -
    +
    - ); + ) } diff --git a/src/routes/route-chat.tsx b/src/routes/route-chat.tsx index 01043d62..b6489af7 100644 --- a/src/routes/route-chat.tsx +++ b/src/routes/route-chat.tsx @@ -1,34 +1,34 @@ -import { useParams } from "react-router-dom"; -import { parsingPromptText, sanitizeQuestionPrompt } from "@/lib/utils"; -import { Breadcrumb, Breadcrumbs, Loader } from "@stacklok/ui-kit"; -import { BreadcrumbHome } from "@/components/BreadcrumbHome"; -import { PageContainer } from "@/components/page-container"; -import { PageHeading } from "@/components/heading"; +import { useParams } from 'react-router-dom' +import { parsingPromptText, sanitizeQuestionPrompt } from '@/lib/utils' +import { Breadcrumb, Breadcrumbs, Loader } from '@stacklok/ui-kit' +import { BreadcrumbHome } from '@/components/BreadcrumbHome' +import { PageContainer } from '@/components/page-container' +import { PageHeading } from '@/components/heading' import { ConversationView, useConversationSearchParams, -} from "@/features/dashboard-messages/hooks/use-conversation-search-params"; -import { TabsConversation } from "@/features/dashboard-messages/components/tabs-conversation"; -import { SectionConversationTranscript } from "@/features/dashboard-messages/components/section-conversation-transcript"; -import { SectionConversationSecrets } from "@/features/dashboard-messages/components/section-conversation-secrets"; -import { ErrorFallbackContent } from "@/components/Error"; -import { useConversationById } from "@/features/dashboard-messages/hooks/use-conversation-by-id"; -import { getConversationTitle } from "@/features/dashboard-messages/lib/get-conversation-title"; -import { formatTime } from "@/lib/format-time"; -import { Conversation } from "@/api/generated"; +} from '@/features/dashboard-messages/hooks/use-conversation-search-params' +import { TabsConversation } from '@/features/dashboard-messages/components/tabs-conversation' +import { SectionConversationTranscript } from '@/features/dashboard-messages/components/section-conversation-transcript' +import { SectionConversationSecrets } from '@/features/dashboard-messages/components/section-conversation-secrets' +import { ErrorFallbackContent } from '@/components/Error' +import { useConversationById } from '@/features/dashboard-messages/hooks/use-conversation-by-id' +import { getConversationTitle } from '@/features/dashboard-messages/lib/get-conversation-title' +import { formatTime } from '@/lib/format-time' +import { Conversation } from '@/api/generated' function ConversationContent({ view, conversation, }: { - view: ConversationView; - conversation: Conversation; + view: ConversationView + conversation: Conversation }) { switch (view) { case ConversationView.OVERVIEW: - return ; + return case ConversationView.SECRETS: - return ; + return } } @@ -40,14 +40,14 @@ function TitleContent({ conversation }: { conversation: Conversation }) { {formatTime(new Date(conversation.conversation_timestamp))}
    - ); + ) } export function RouteChat() { - const { id } = useParams<"id">(); - const { state } = useConversationSearchParams(); + const { id } = useParams<'id'>() + const { state } = useConversationSearchParams() - const { data: conversation, isLoading } = useConversationById(id ?? ""); + const { data: conversation, isLoading } = useConversationById(id ?? '') const title = conversation === undefined || @@ -56,24 +56,24 @@ export function RouteChat() { : parsingPromptText( sanitizeQuestionPrompt({ question: conversation.question_answers?.[0].question.message, - answer: conversation.question_answers?.[0]?.answer?.message ?? "", + answer: conversation.question_answers?.[0]?.answer?.message ?? '', }), - conversation.conversation_timestamp, - ); + conversation.conversation_timestamp + ) if (isLoading) return (
    - +
    - ); - if (!id || !conversation) return ; + ) + if (!id || !conversation) return return ( - {title} + {title} - ); + ) } diff --git a/src/routes/route-dashboard.tsx b/src/routes/route-dashboard.tsx index fb291c9c..26ef7a76 100644 --- a/src/routes/route-dashboard.tsx +++ b/src/routes/route-dashboard.tsx @@ -1,14 +1,14 @@ -import { TableMessages } from "@/features/dashboard-messages/components/table-messages"; -import { AlertsSummaryMaliciousPkg } from "@/features/dashboard-alerts/components/alerts-summary-malicious-pkg"; -import { AlertsSummaryWorkspaceTokenUsage } from "@/features/dashboard-alerts/components/alerts-summary-workspace-token-usage"; -import { AlertsSummaryMaliciousSecrets } from "@/features/dashboard-alerts/components/alerts-summary-secrets"; -import { TabsMessages } from "@/features/dashboard-messages/components/tabs-messages"; -import { PageContainer } from "@/components/page-container"; +import { TableMessages } from '@/features/dashboard-messages/components/table-messages' +import { AlertsSummaryMaliciousPkg } from '@/features/dashboard-alerts/components/alerts-summary-malicious-pkg' +import { AlertsSummaryWorkspaceTokenUsage } from '@/features/dashboard-alerts/components/alerts-summary-workspace-token-usage' +import { AlertsSummaryMaliciousSecrets } from '@/features/dashboard-alerts/components/alerts-summary-secrets' +import { TabsMessages } from '@/features/dashboard-messages/components/tabs-messages' +import { PageContainer } from '@/components/page-container' export function RouteDashboard() { return ( -
    +
    @@ -18,5 +18,5 @@ export function RouteDashboard() { - ); + ) } diff --git a/src/routes/route-not-found.tsx b/src/routes/route-not-found.tsx index 8207343e..185bfa9a 100644 --- a/src/routes/route-not-found.tsx +++ b/src/routes/route-not-found.tsx @@ -1,22 +1,22 @@ -import { EmptyState } from "@/components/EmptyState"; -import { IllustrationError, Button } from "@stacklok/ui-kit"; -import { useNavigate } from "react-router-dom"; -import { FlipBackward } from "@untitled-ui/icons-react"; +import { EmptyState } from '@/components/EmptyState' +import { IllustrationError, Button } from '@stacklok/ui-kit' +import { useNavigate } from 'react-router-dom' +import { FlipBackward } from '@untitled-ui/icons-react' export function RouteNotFound() { - const navigate = useNavigate(); + const navigate = useNavigate() return ( -
    +
    } > -
    - ); + ) } diff --git a/src/routes/route-provider-create.tsx b/src/routes/route-provider-create.tsx index 020ce4ab..bb711b31 100644 --- a/src/routes/route-provider-create.tsx +++ b/src/routes/route-provider-create.tsx @@ -2,35 +2,35 @@ import { AddProviderEndpointRequest, ProviderAuthType, ProviderType, -} from "@/api/generated"; -import { ProviderDialog } from "@/features/providers/components/provider-dialog"; -import { ProviderDialogFooter } from "@/features/providers/components/provider-dialog-footer"; -import { ProviderForm } from "@/features/providers/components/provider-form"; -import { useMutationCreateProvider } from "@/features/providers/hooks/use-mutation-create-provider"; -import { DialogContent, Form } from "@stacklok/ui-kit"; -import { useState } from "react"; +} from '@/api/generated' +import { ProviderDialog } from '@/features/providers/components/provider-dialog' +import { ProviderDialogFooter } from '@/features/providers/components/provider-dialog-footer' +import { ProviderForm } from '@/features/providers/components/provider-form' +import { useMutationCreateProvider } from '@/features/providers/hooks/use-mutation-create-provider' +import { DialogContent, Form } from '@stacklok/ui-kit' +import { useState } from 'react' const DEFAULT_PROVIDER_STATE = { - name: "", - description: "", + name: '', + description: '', auth_type: ProviderAuthType.API_KEY, provider_type: ProviderType.OPENAI, - endpoint: "", - api_key: "", -}; + endpoint: '', + api_key: '', +} export function RouteProviderCreate() { const [provider, setProvider] = useState( - DEFAULT_PROVIDER_STATE, - ); - const { mutateAsync } = useMutationCreateProvider(); + DEFAULT_PROVIDER_STATE + ) + const { mutateAsync } = useMutationCreateProvider() const handleSubmit = (event: React.FormEvent) => { - event.preventDefault(); + event.preventDefault() mutateAsync({ body: provider, - }); - }; + }) + } return ( @@ -45,5 +45,5 @@ export function RouteProviderCreate() { - ); + ) } diff --git a/src/routes/route-provider-update.tsx b/src/routes/route-provider-update.tsx index e39918e8..c286e0f5 100644 --- a/src/routes/route-provider-update.tsx +++ b/src/routes/route-provider-update.tsx @@ -1,26 +1,26 @@ -import { ProviderDialog } from "@/features/providers/components/provider-dialog"; -import { ProviderDialogFooter } from "@/features/providers/components/provider-dialog-footer"; -import { ProviderForm } from "@/features/providers/components/provider-form"; -import { useMutationUpdateProvider } from "@/features/providers/hooks/use-mutation-update-provider"; -import { useProvider } from "@/features/providers/hooks/use-provider"; -import { DialogContent, Form } from "@stacklok/ui-kit"; -import { useParams } from "react-router-dom"; +import { ProviderDialog } from '@/features/providers/components/provider-dialog' +import { ProviderDialogFooter } from '@/features/providers/components/provider-dialog-footer' +import { ProviderForm } from '@/features/providers/components/provider-form' +import { useMutationUpdateProvider } from '@/features/providers/hooks/use-mutation-update-provider' +import { useProvider } from '@/features/providers/hooks/use-provider' +import { DialogContent, Form } from '@stacklok/ui-kit' +import { useParams } from 'react-router-dom' export function RouteProviderUpdate() { - const { id } = useParams(); + const { id } = useParams() if (id === undefined) { - throw new Error("Provider id is required"); + throw new Error('Provider id is required') } - const { setProvider, provider } = useProvider(id); - const { mutateAsync } = useMutationUpdateProvider(); + const { setProvider, provider } = useProvider(id) + const { mutateAsync } = useMutationUpdateProvider() const handleSubmit = (event: React.FormEvent) => { - event.preventDefault(); - mutateAsync(provider); - }; + event.preventDefault() + mutateAsync(provider) + } // TODO add empty state and loading in a next step - if (provider === undefined) return; + if (provider === undefined) return return ( @@ -35,5 +35,5 @@ export function RouteProviderUpdate() { - ); + ) } diff --git a/src/routes/route-providers.tsx b/src/routes/route-providers.tsx index 3f0459cd..d74f9cf4 100644 --- a/src/routes/route-providers.tsx +++ b/src/routes/route-providers.tsx @@ -1,17 +1,17 @@ -import { BreadcrumbHome } from "@/components/BreadcrumbHome"; +import { BreadcrumbHome } from '@/components/BreadcrumbHome' import { Breadcrumbs, Breadcrumb, Card, LinkButton, CardBody, -} from "@stacklok/ui-kit"; -import { twMerge } from "tailwind-merge"; -import { PlusSquare } from "@untitled-ui/icons-react"; -import { TableProviders } from "@/features/providers/components/table-providers"; -import { Outlet } from "react-router-dom"; -import { PageContainer } from "@/components/page-container"; -import { PageHeading } from "@/components/heading"; +} from '@stacklok/ui-kit' +import { twMerge } from 'tailwind-merge' +import { PlusSquare } from '@untitled-ui/icons-react' +import { TableProviders } from '@/features/providers/components/table-providers' +import { Outlet } from 'react-router-dom' +import { PageContainer } from '@/components/page-container' +import { PageHeading } from '@/components/heading' export function RouteProvider({ className }: { className?: string }) { return ( @@ -25,7 +25,7 @@ export function RouteProvider({ className }: { className?: string }) { Add Provider - + @@ -33,5 +33,5 @@ export function RouteProvider({ className }: { className?: string }) { - ); + ) } diff --git a/src/routes/route-workspace-creation.tsx b/src/routes/route-workspace-creation.tsx index ad897e5b..27b211d9 100644 --- a/src/routes/route-workspace-creation.tsx +++ b/src/routes/route-workspace-creation.tsx @@ -1,8 +1,8 @@ -import { BreadcrumbHome } from "@/components/BreadcrumbHome"; -import { PageContainer } from "@/components/page-container"; -import { WorkspaceCreation } from "@/features/workspace/components/workspace-creation"; -import { PageHeading } from "@/components/heading"; -import { Breadcrumbs, Breadcrumb } from "@stacklok/ui-kit"; +import { BreadcrumbHome } from '@/components/BreadcrumbHome' +import { PageContainer } from '@/components/page-container' +import { WorkspaceCreation } from '@/features/workspace/components/workspace-creation' +import { PageHeading } from '@/components/heading' +import { Breadcrumbs, Breadcrumb } from '@stacklok/ui-kit' export function RouteWorkspaceCreation() { return ( @@ -16,5 +16,5 @@ export function RouteWorkspaceCreation() { - ); + ) } diff --git a/src/routes/route-workspace.tsx b/src/routes/route-workspace.tsx index fea687e0..9cea7aa1 100644 --- a/src/routes/route-workspace.tsx +++ b/src/routes/route-workspace.tsx @@ -1,18 +1,18 @@ -import { BreadcrumbHome } from "@/components/BreadcrumbHome"; -import { ArchiveWorkspace } from "@/features/workspace/components/archive-workspace"; +import { BreadcrumbHome } from '@/components/BreadcrumbHome' +import { ArchiveWorkspace } from '@/features/workspace/components/archive-workspace' -import { PageHeading } from "@/components/heading"; -import { WorkspaceName } from "@/features/workspace/components/workspace-name"; -import { Alert, Breadcrumb, Breadcrumbs } from "@stacklok/ui-kit"; -import { useParams } from "react-router-dom"; -import { useArchivedWorkspaces } from "@/features/workspace/hooks/use-archived-workspaces"; -import { useRestoreWorkspaceButton } from "@/features/workspace/hooks/use-restore-workspace-button"; -import { WorkspaceCustomInstructions } from "@/features/workspace/components/workspace-custom-instructions"; -import { WorkspacePreferredModel } from "@/features/workspace/components/workspace-preferred-model"; -import { PageContainer } from "@/components/page-container"; +import { PageHeading } from '@/components/heading' +import { WorkspaceName } from '@/features/workspace/components/workspace-name' +import { Alert, Breadcrumb, Breadcrumbs } from '@stacklok/ui-kit' +import { useParams } from 'react-router-dom' +import { useArchivedWorkspaces } from '@/features/workspace/hooks/use-archived-workspaces' +import { useRestoreWorkspaceButton } from '@/features/workspace/hooks/use-restore-workspace-button' +import { WorkspaceCustomInstructions } from '@/features/workspace/components/workspace-custom-instructions' +import { WorkspacePreferredModel } from '@/features/workspace/components/workspace-preferred-model' +import { PageContainer } from '@/components/page-container' function WorkspaceArchivedBanner({ name }: { name: string }) { - const restoreButtonProps = useRestoreWorkspaceButton({ workspaceName: name }); + const restoreButtonProps = useRestoreWorkspaceButton({ workspaceName: name }) return ( - ); + ) } export function RouteWorkspace() { - const { name } = useParams(); + const { name } = useParams() - if (!name) throw Error("Workspace name is required"); + if (!name) throw Error('Workspace name is required') const { data: isArchived } = useArchivedWorkspaces({ select: (data) => data?.workspaces.find((w) => w.name === name) !== undefined, - }); + }) return ( @@ -66,5 +66,5 @@ export function RouteWorkspace() { /> - ); + ) } diff --git a/src/routes/route-workspaces.tsx b/src/routes/route-workspaces.tsx index 57f77bca..b4f21b7d 100644 --- a/src/routes/route-workspaces.tsx +++ b/src/routes/route-workspaces.tsx @@ -1,5 +1,5 @@ -import { PageHeading } from "@/components/heading"; -import { BreadcrumbHome } from "@/components/BreadcrumbHome"; +import { PageHeading } from '@/components/heading' +import { BreadcrumbHome } from '@/components/BreadcrumbHome' import { Breadcrumb, Breadcrumbs, @@ -7,18 +7,18 @@ import { LinkButton, Tooltip, TooltipTrigger, -} from "@stacklok/ui-kit"; -import { TableWorkspaces } from "@/features/workspace/components/table-workspaces"; -import { useKbdShortcuts } from "@/hooks/use-kbd-shortcuts"; -import { useNavigate } from "react-router-dom"; -import { hrefs } from "@/lib/hrefs"; -import { PlusSquare } from "@untitled-ui/icons-react"; -import { PageContainer } from "@/components/page-container"; +} from '@stacklok/ui-kit' +import { TableWorkspaces } from '@/features/workspace/components/table-workspaces' +import { useKbdShortcuts } from '@/hooks/use-kbd-shortcuts' +import { useNavigate } from 'react-router-dom' +import { hrefs } from '@/lib/hrefs' +import { PlusSquare } from '@untitled-ui/icons-react' +import { PageContainer } from '@/components/page-container' export function RouteWorkspaces() { - const navigate = useNavigate(); + const navigate = useNavigate() - useKbdShortcuts([["c", () => navigate(hrefs.workspaces.create)]]); + useKbdShortcuts([['c', () => navigate(hrefs.workspaces.create)]]) return ( @@ -32,7 +32,7 @@ export function RouteWorkspaces() { Create - + Create a new workspace C @@ -41,5 +41,5 @@ export function RouteWorkspaces() { - ); + ) } diff --git a/src/test/msw-endpoint.ts b/src/test/msw-endpoint.ts index 4f6e94a4..cca64037 100644 --- a/src/test/msw-endpoint.ts +++ b/src/test/msw-endpoint.ts @@ -1,4 +1,4 @@ -import type json from "../api/openapi.json"; +import type json from '../api/openapi.json' /** * OpenAPI spec uses curly braces to denote path parameters @@ -16,10 +16,10 @@ import type json from "../api/openapi.json"; type ReplacePathParams = T extends `${infer Start}{${infer Param}}${infer End}` ? `${Start}:${Param}${ReplacePathParams}` - : T; + : T -type Endpoint = ReplacePathParams; +type Endpoint = ReplacePathParams export function mswEndpoint(endpoint: Endpoint) { - return new URL(endpoint, import.meta.env.VITE_BASE_API_URL).toString(); + return new URL(endpoint, import.meta.env.VITE_BASE_API_URL).toString() } diff --git a/src/types/openapi-ts.ts b/src/types/openapi-ts.ts index 1f95fc74..5bc9936c 100644 --- a/src/types/openapi-ts.ts +++ b/src/types/openapi-ts.ts @@ -1,11 +1,11 @@ -import { OptionsLegacyParser } from "@hey-api/client-fetch"; +import { OptionsLegacyParser } from '@hey-api/client-fetch' export type OpenApiTsReactQueryKey = [ Pick< OptionsLegacyParser, - "baseUrl" | "body" | "headers" | "path" | "query" + 'baseUrl' | 'body' | 'headers' | 'path' | 'query' > & { - _id: string; - _infinite?: boolean; + _id: string + _infinite?: boolean }, -]; +] diff --git a/src/types/react-query.ts b/src/types/react-query.ts index 1c98b479..cc643196 100644 --- a/src/types/react-query.ts +++ b/src/types/react-query.ts @@ -4,11 +4,11 @@ import { QueryObserverLoadingResult, QueryObserverPendingResult, QueryObserverRefetchErrorResult, -} from "@tanstack/react-query"; +} from '@tanstack/react-query' export type QueryResult = | DefinedUseQueryResult | QueryObserverLoadingErrorResult | QueryObserverLoadingResult | QueryObserverPendingResult - | QueryObserverRefetchErrorResult; + | QueryObserverRefetchErrorResult diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts index 2aa07e67..e8dd3c9e 100644 --- a/src/vite-env.d.ts +++ b/src/vite-env.d.ts @@ -1,5 +1,5 @@ /// interface ImportBaseApiEnv { - readonly BASE_API_URL: string; - readonly VITE_BASE_API_URL: string; + readonly BASE_API_URL: string + readonly VITE_BASE_API_URL: string } diff --git a/tailwind.config.ts b/tailwind.config.ts index d2d469f2..dbe94b7a 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -1,50 +1,50 @@ -import { stacklokTailwindPreset } from "@stacklok/ui-kit"; -import type { Config } from "tailwindcss"; -import typographyPlugin from "@tailwindcss/typography"; +import { stacklokTailwindPreset } from '@stacklok/ui-kit' +import type { Config } from 'tailwindcss' +import typographyPlugin from '@tailwindcss/typography' export default { content: [ - "./src/**/*.{js,ts,jsx,tsx,mdx}", - "./node_modules/@stacklok/ui-kit/dist/**/*.js", + './src/**/*.{js,ts,jsx,tsx,mdx}', + './node_modules/@stacklok/ui-kit/dist/**/*.js', ], presets: [stacklokTailwindPreset], theme: { ...stacklokTailwindPreset.theme, extend: { animation: { - "spin-once": "spin-once 0.5s ease-in-out", + 'spin-once': 'spin-once 0.5s ease-in-out', }, keyframes: { - "spin-once": { - "0%": { - transform: "rotate(0deg)", + 'spin-once': { + '0%': { + transform: 'rotate(0deg)', }, - "100%": { - transform: "rotate(180deg)", + '100%': { + transform: 'rotate(180deg)', }, }, }, typography: () => ({ DEFAULT: { css: { - "--tw-prose-body": "var(--gray-950)", - "--tw-prose-headings": "var(--gray-950)", - "--tw-prose-lead": "var(--gray-600)", - "--tw-prose-links": "var(--gray-950)", - "--tw-prose-bold": "var(--gray-950)", - "--tw-prose-counters": "var(--gray-500)", - "--tw-prose-bullets": "var(--gray-300)", - "--tw-prose-hr": "var(--gray-200)", - "--tw-prose-quotes": "var(--gray-950)", - "--tw-prose-quote-borders": "var(--gray-200)", - "--tw-prose-captions": "var(--gray-500)", - "--tw-prose-code": "var(--gray-950)", - "--tw-prose-pre-code": "var(--gray-200)", - "--tw-prose-pre-bg": "var(--gray-800)", - "--tw-prose-th-borders": "var(--gray-300)", - "--tw-prose-td-borders": "var(--gray-200)", - "h1, h2, h3, h4, h5, h6": { - fontFamily: "var(--font-title)", - fontWeight: "600", + '--tw-prose-body': 'var(--gray-950)', + '--tw-prose-headings': 'var(--gray-950)', + '--tw-prose-lead': 'var(--gray-600)', + '--tw-prose-links': 'var(--gray-950)', + '--tw-prose-bold': 'var(--gray-950)', + '--tw-prose-counters': 'var(--gray-500)', + '--tw-prose-bullets': 'var(--gray-300)', + '--tw-prose-hr': 'var(--gray-200)', + '--tw-prose-quotes': 'var(--gray-950)', + '--tw-prose-quote-borders': 'var(--gray-200)', + '--tw-prose-captions': 'var(--gray-500)', + '--tw-prose-code': 'var(--gray-950)', + '--tw-prose-pre-code': 'var(--gray-200)', + '--tw-prose-pre-bg': 'var(--gray-800)', + '--tw-prose-th-borders': 'var(--gray-300)', + '--tw-prose-td-borders': 'var(--gray-200)', + 'h1, h2, h3, h4, h5, h6': { + fontFamily: 'var(--font-title)', + fontWeight: '600', }, }, }, @@ -52,4 +52,4 @@ export default { }, }, plugins: [typographyPlugin], -} satisfies Config; +} satisfies Config diff --git a/vite.config.ts b/vite.config.ts index be9185f1..58d1398c 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,8 +1,8 @@ -import { defineConfig } from "vite"; -import path from "path"; -import react from "@vitejs/plugin-react-swc"; -import tsconfigPaths from "vite-tsconfig-paths"; -import { Mode, plugin as mdPlugin } from "vite-plugin-markdown"; +import { defineConfig } from 'vite' +import path from 'path' +import react from '@vitejs/plugin-react-swc' +import tsconfigPaths from 'vite-tsconfig-paths' +import { Mode, plugin as mdPlugin } from 'vite-plugin-markdown' // https://vite.dev/config/ export default defineConfig({ @@ -13,16 +13,16 @@ export default defineConfig({ }), react(), ], - base: "/", + base: '/', build: { - outDir: "dist", + outDir: 'dist', rollupOptions: { - input: "./index.html", + input: './index.html', }, }, resolve: { alias: { - "@": path.resolve(__dirname, "./src"), + '@': path.resolve(__dirname, './src'), }, }, -}); +}) diff --git a/vitest.config.ts b/vitest.config.ts index 0a764c97..9dde4ced 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -1,55 +1,55 @@ /// -import { defineConfig } from "vitest/config"; -import react from "@vitejs/plugin-react-swc"; -import tsconfigPaths from "vite-tsconfig-paths"; -import path from "path"; +import { defineConfig } from 'vitest/config' +import react from '@vitejs/plugin-react-swc' +import tsconfigPaths from 'vite-tsconfig-paths' +import path from 'path' export default defineConfig({ plugins: [tsconfigPaths(), react()], resolve: { alias: { - "@": path.resolve(__dirname, "./src"), + '@': path.resolve(__dirname, './src'), }, }, test: { globals: true, - environment: "jsdom", - setupFiles: "./vitest.setup.ts", + environment: 'jsdom', + setupFiles: './vitest.setup.ts', env: { - VITE_BASE_API_URL: "https://mock.codegate.ai", + VITE_BASE_API_URL: 'https://mock.codegate.ai', }, exclude: [ - "**/node_modules/**", - "**/dist/**", - "**/.{idea,git,cache,output,temp}/**", - "**/{karma,rollup,webpack,vite,vitest,jest,ava,babel,nyc,cypress,tsup,build}.config.*", + '**/node_modules/**', + '**/dist/**', + '**/.{idea,git,cache,output,temp}/**', + '**/{karma,rollup,webpack,vite,vitest,jest,ava,babel,nyc,cypress,tsup,build}.config.*', ], css: false, coverage: { - include: ["src/**/*.{js,jsx}", "src/**/*.{ts,tsx}"], + include: ['src/**/*.{js,jsx}', 'src/**/*.{ts,tsx}'], exclude: [ - "coverage/**", - "dist/**", - "**/[.]**", - "**/*.d.ts", - "**/virtual:*", - "**/__x00__*", - "**/\x00*", - "public/**", - "test?(s)/**", - "test?(-*).?(c|m)[jt]s?(x)", - "**/*{.,-}{test,spec}?(-d).?(c|m)[jt]s?(x)", - "**/__tests__/**", - "**/test-utils.tsx", - "**/{karma,rollup,webpack,vite,vitest,jest,ava,babel,nyc,cypress,tsup,build}.config.*", - "**/vitest.{workspace,projects}.[jt]s?(on)", - "**/.{eslint,mocha,prettier}rc.{?(c|m)js,yml}", - "src/**/*stories.tsx", - "src/types/**/*.{ts,tsx}", + 'coverage/**', + 'dist/**', + '**/[.]**', + '**/*.d.ts', + '**/virtual:*', + '**/__x00__*', + '**/\x00*', + 'public/**', + 'test?(s)/**', + 'test?(-*).?(c|m)[jt]s?(x)', + '**/*{.,-}{test,spec}?(-d).?(c|m)[jt]s?(x)', + '**/__tests__/**', + '**/test-utils.tsx', + '**/{karma,rollup,webpack,vite,vitest,jest,ava,babel,nyc,cypress,tsup,build}.config.*', + '**/vitest.{workspace,projects}.[jt]s?(on)', + '**/.{eslint,mocha,prettier}rc.{?(c|m)js,yml}', + 'src/**/*stories.tsx', + 'src/types/**/*.{ts,tsx}', ], enabled: false, - provider: "istanbul", - reporter: ["text", "lcov"], + provider: 'istanbul', + reporter: ['text', 'lcov'], }, }, -}); +}) diff --git a/vitest.setup.ts b/vitest.setup.ts index e2d97f8d..a809a6f7 100644 --- a/vitest.setup.ts +++ b/vitest.setup.ts @@ -1,13 +1,13 @@ -import { server } from "./src/mocks/msw/node"; -import * as testingLibraryMatchers from "@testing-library/jest-dom/matchers"; -import "@testing-library/jest-dom/vitest"; -import { cleanup } from "@testing-library/react"; -import { afterEach, expect, beforeAll, afterAll, vi } from "vitest"; -import failOnConsole from "vitest-fail-on-console"; -import { client } from "./src/api/generated/sdk.gen"; +import { server } from './src/mocks/msw/node' +import * as testingLibraryMatchers from '@testing-library/jest-dom/matchers' +import '@testing-library/jest-dom/vitest' +import { cleanup } from '@testing-library/react' +import { afterEach, expect, beforeAll, afterAll, vi } from 'vitest' +import failOnConsole from 'vitest-fail-on-console' +import { client } from './src/api/generated/sdk.gen' class MockEventSource { - onmessage: ((event: MessageEvent) => void) | null = null; + onmessage: ((event: MessageEvent) => void) | null = null constructor() {} @@ -15,26 +15,26 @@ class MockEventSource { triggerMessage(data: string) { if (this.onmessage) { - this.onmessage({ data } as MessageEvent); + this.onmessage({ data } as MessageEvent) } } } -expect.extend(testingLibraryMatchers); +expect.extend(testingLibraryMatchers) afterEach(() => { - cleanup(); -}); + cleanup() +}) beforeAll(() => { server.listen({ - onUnhandledRequest: "error", - }); + onUnhandledRequest: 'error', + }) client.setConfig({ - baseUrl: "https://mock.codegate.ai", + baseUrl: 'https://mock.codegate.ai', fetch, - }); + }) global.window.matchMedia = vi.fn().mockImplementation((query) => ({ matches: false, @@ -43,9 +43,9 @@ beforeAll(() => { addEventListener: vi.fn(), removeEventListener: vi.fn(), dispatchEvent: vi.fn(), - })); + })) - global.EventSource = MockEventSource as unknown as typeof EventSource; + global.EventSource = MockEventSource as unknown as typeof EventSource global.ResizeObserver = class ResizeObserver { disconnect() { @@ -57,17 +57,17 @@ beforeAll(() => { unobserve() { // do nothing } - }; -}); + } +}) afterEach(() => { - server.resetHandlers(); - vi.clearAllMocks(); -}); -afterAll(() => server.close()); + server.resetHandlers() + vi.clearAllMocks() +}) +afterAll(() => server.close()) const SILENCED_MESSAGES = [ - "Not implemented: navigation (except hash changes)", // JSDom issue โ€” can safely be ignored -]; + 'Not implemented: navigation (except hash changes)', // JSDom issue โ€” can safely be ignored +] failOnConsole({ shouldFailOnDebug: false, @@ -76,6 +76,6 @@ failOnConsole({ shouldFailOnLog: false, shouldFailOnWarn: true, silenceMessage: (message: string) => { - return SILENCED_MESSAGES.some((m) => message.includes(m)); + return SILENCED_MESSAGES.some((m) => message.includes(m)) }, -}); +}) From 9552fee253593a1211da3008fc8836db1653fd3c Mon Sep 17 00:00:00 2001 From: alex-mcgovern Date: Fri, 14 Feb 2025 14:48:26 +0000 Subject: [PATCH 5/7] tidy up certificate documentation markdown --- package-lock.json | 89 ++++++++++++++++++++++ src/components/Markdown.tsx | 2 +- src/markdown/certificates/linux-install.md | 13 +++- src/markdown/certificates/macos-install.md | 26 ++++--- src/markdown/certificates/macos-remove.md | 22 ++++-- vite.config.ts | 9 ++- 6 files changed, 142 insertions(+), 19 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5e36d04a..ee49c82c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14553,6 +14553,95 @@ "vite": ">= 2.0.0" } }, + "node_modules/vite-plugin-markdown/node_modules/dom-serializer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", + "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", + "dev": true, + "license": "MIT", + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.2.0", + "entities": "^2.0.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/vite-plugin-markdown/node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/vite-plugin-markdown/node_modules/domhandler": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", + "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.2.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/vite-plugin-markdown/node_modules/domutils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", + "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^1.0.1", + "domelementtype": "^2.2.0", + "domhandler": "^4.2.0" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/vite-plugin-markdown/node_modules/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "dev": true, + "license": "BSD-2-Clause", + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/vite-plugin-markdown/node_modules/htmlparser2": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", + "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==", + "dev": true, + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.0.0", + "domutils": "^2.5.2", + "entities": "^2.0.0" + } + }, "node_modules/vite-tsconfig-paths": { "version": "5.1.4", "resolved": "https://registry.npmjs.org/vite-tsconfig-paths/-/vite-tsconfig-paths-5.1.4.tgz", diff --git a/src/components/Markdown.tsx b/src/components/Markdown.tsx index dd3b7511..3e5e8fa5 100644 --- a/src/components/Markdown.tsx +++ b/src/components/Markdown.tsx @@ -128,7 +128,7 @@ const markdownStyles = tv({ 'prose-h4:mb-2 prose-h4:text-lg prose-h4:font-semibold', 'prose-h5:mb-2 prose-h5:text-lg prose-h5:font-semibold', 'prose-h6:mb-2 prose-h6:text-lg prose-h6:font-semibold', - 'prose-p:text-base', + 'prose-sm', 'prose max-w-none prose-p:leading-relaxed', '[--tw-prose-pre-code:theme(textColor.secondary)]', '[--tw-prose-pre-bg:theme(colors.gray.200)]', diff --git a/src/markdown/certificates/linux-install.md b/src/markdown/certificates/linux-install.md index 04d8c129..7d0e89f8 100644 --- a/src/markdown/certificates/linux-install.md +++ b/src/markdown/certificates/linux-install.md @@ -1,3 +1,12 @@ -1. Install the `certutil` tool: `sudo apt install libnss3-tools` (Ubuntu/Debian) or `sudo dnf install nss-tools` (RHEL/Fedora). -2. Run `certutil -d sql:$HOME/.pki/nssdb -A -t "C,," -n CodeGate-CA -i ~ Downloads/codegate.crt` to install the certificate for your account. +1. Install the `certutil` tool using the appropriate command for your distribution: + - **Ubuntu/Debian**: `sudo apt install libnss3-tools` + - **RHEL/Fedora**: `sudo dnf install nss-tools` +2. Run the following command to install the certificate for your account: + ```shell + certutil -d sql:$HOME/.pki/nssdb \ + -A \ + -t "C,," \ + -n CodeGate-CA \ + -i ~/Downloads/codegate.crt + ``` 3. Restart VS Code. diff --git a/src/markdown/certificates/macos-install.md b/src/markdown/certificates/macos-install.md index e7853364..0945e006 100644 --- a/src/markdown/certificates/macos-install.md +++ b/src/markdown/certificates/macos-install.md @@ -1,13 +1,21 @@ -- **CLI method**: Open a terminal and run +### CLI method + +After downloading the certificate, open a terminal and run the following command: ```shell -security add-trusted-cert -d -r trustRoot -p ssl -p basic -k ~/Library/Keychains/login.keychain ~/Downloads/codegate.crt +security add-trusted-cert \ +-d \ +-r trustRoot \ +-p ssl \ +-p basic \ +-k ~/Library/Keychains/login.keychain \ +~/Downloads/codegate.crt ``` -- **GUI method**: - 1. Open the downloaded certificate file; Keychain Access will open. - 2. Depending on your macOS version, you may see the Add Certificates dialog. If so, select the `login` keychain, and click Add. - 3. In Keychain Access, select the `login` keychain from the Default Keychains list on the left. - 4. Search for "CodeGate" (it may not appear until you search), then in the search results, double-click the "CodeGate CA" certificate. - 5. Expand the Trust section and set the "Secure Sockets Layer" and "X.509 - Basic Policy" options to "Always Trust". +### GUI method + +1. Open the downloaded certificate file; Keychain Access will open. +2. Depending on your macOS version, you may see the Add Certificates dialog. If so, select the `login` keychain, and click Add. +3. In Keychain Access, select the `login` keychain from the Default Keychains list on the left. +4. Search for "CodeGate" (it may not appear until you search), then in the search results, double-click the "CodeGate CA" certificate. +5. Expand the Trust section and set the "Secure Sockets Layer" and "X.509 Basic Policy" options to "Always Trust". diff --git a/src/markdown/certificates/macos-remove.md b/src/markdown/certificates/macos-remove.md index eee27b1f..8b9e3662 100644 --- a/src/markdown/certificates/macos-remove.md +++ b/src/markdown/certificates/macos-remove.md @@ -1,6 +1,16 @@ -- **CLI method**: Open a terminal and run `security delete-certificate -c "CodeGate CA" -t ~/Library/Keychains/login.keychain` -- **GUI method**: - 1. Launch the Keychain Access app (Note: on newer macOS versions, Keychain Access is hidden from Launcher, but can be run from Spotlight Search). - 2. Select the login keychain and search for "CodeGate". - 3. Right-click the "CodeGate CA" certificate and Delete the certificate. - 4. Confirm the deletion when prompted. +### CLI method + +Open a terminal and run the following command: + +```shell +security delete-certificate \ +-c "CodeGate CA" \ +-t ~/Library/Keychains/login.keychain +``` + +### GUI method + +1. Launch the Keychain Access app (Note: on newer macOS versions, Keychain Access is hidden from Launcher, but can be run from Spotlight Search). +2. Select the login keychain and search for "CodeGate". +3. Right-click the "CodeGate CA" certificate and Delete the certificate. +4. Confirm the deletion when prompted. diff --git a/vite.config.ts b/vite.config.ts index e69b8346..58d1398c 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -2,10 +2,17 @@ import { defineConfig } from 'vite' import path from 'path' import react from '@vitejs/plugin-react-swc' import tsconfigPaths from 'vite-tsconfig-paths' +import { Mode, plugin as mdPlugin } from 'vite-plugin-markdown' // https://vite.dev/config/ export default defineConfig({ - plugins: [tsconfigPaths(), react()], + plugins: [ + tsconfigPaths(), + mdPlugin({ + mode: [Mode.MARKDOWN], + }), + react(), + ], base: '/', build: { outDir: 'dist', From a71604663532b7647acb1550acec269ecd00ea6c Mon Sep 17 00:00:00 2001 From: alex-mcgovern Date: Fri, 14 Feb 2025 15:45:11 +0000 Subject: [PATCH 6/7] chore: tidy ups --- src/components/page-container.tsx | 13 +- src/markdown/certificates/windows-install.md | 12 +- .../__tests__/route-certificates.test.tsx | 15 +- src/routes/route-certificate-security.tsx | 259 +++++++++--------- src/routes/route-certificates.tsx | 255 +++++++++-------- vitest.config.ts | 9 +- 6 files changed, 287 insertions(+), 276 deletions(-) diff --git a/src/components/page-container.tsx b/src/components/page-container.tsx index 89a7567f..7d6eaeb4 100644 --- a/src/components/page-container.tsx +++ b/src/components/page-container.tsx @@ -1,8 +1,17 @@ import { ReactNode } from 'react' +import { twMerge } from 'tailwind-merge' -export function PageContainer({ children }: { children: ReactNode }) { +export function PageContainer({ + children, + className, +}: { + children: ReactNode + className?: string +}) { return ( -
    +
    {children}
    ) diff --git a/src/markdown/certificates/windows-install.md b/src/markdown/certificates/windows-install.md index 7456cb33..ad8852fc 100644 --- a/src/markdown/certificates/windows-install.md +++ b/src/markdown/certificates/windows-install.md @@ -1,6 +1,6 @@ -1. Double-click the downloaded certificate file., -2. Click "Install Certificate"., -3. Select "Current User" and click Next., -4. Choose "Place all certificates in the following store"., -5. Click Browse and select "Trusted Root Certification Authorities"., -6. Click Next and Finish., +1. Double-click the downloaded certificate file. +2. Click "Install Certificate". +3. Select "Current User" and click Next. +4. Choose "Place all certificates in the following store". +5. Click Browse and select "Trusted Root Certification Authorities". +6. Click Next and Finish. diff --git a/src/routes/__tests__/route-certificates.test.tsx b/src/routes/__tests__/route-certificates.test.tsx index 411b76e8..5d4cdde8 100644 --- a/src/routes/__tests__/route-certificates.test.tsx +++ b/src/routes/__tests__/route-certificates.test.tsx @@ -41,16 +41,19 @@ describe('Certificates', () => { it('should render macOS certificate installation', async () => { render() - expect( - screen.getByText( - 'Open the downloaded certificate file; Keychain Access will open and prompt you to to add the certificates.' - ) - ).toBeVisible() + expect(screen.getByRole('heading', { name: 'CLI method' })).toBeVisible() + expect(screen.getByText('After downloading the certificate')).toBeVisible() + expect(screen.getByRole('heading', { name: 'GUI method' })).toBeVisible() + expect(screen.getByText('Open the downloaded certificate')).toBeVisible() await userEvent.click( screen.getByRole('button', { name: 'Remove certificate' }) ) - expect(screen.getByText('Launch the Keychain Access app.')).toBeVisible() + + expect(screen.getByRole('heading', { name: 'CLI method' })).toBeVisible() + expect(screen.getByText('Open a terminal and run')).toBeVisible() + expect(screen.getByRole('heading', { name: 'GUI method' })).toBeVisible() + expect(screen.getByText('Launch the Keychain Access app')).toBeVisible() }) it('should render Windows certificate installation', async () => { diff --git a/src/routes/route-certificate-security.tsx b/src/routes/route-certificate-security.tsx index 6b0ad1b1..56589626 100644 --- a/src/routes/route-certificate-security.tsx +++ b/src/routes/route-certificate-security.tsx @@ -1,4 +1,6 @@ import { BreadcrumbHome } from '@/components/BreadcrumbHome' +import { PageHeading } from '@/components/heading' +import { PageContainer } from '@/components/page-container' import { Breadcrumb, Breadcrumbs, Card, CardBody } from '@stacklok/ui-kit' const SecurityShieldIcon = () => ( @@ -60,146 +62,139 @@ const OpenSourceIcon = () => ( export function RouteCertificateSecurity() { return ( - <> + Certificate Security -
    -
    -

    Certificate Security

    + - - -
    - -
    -

    - Robust Certificate Security -

    -

    - Security is a top priority for us. We have designed CodeGate's - local certificate management with security in mind, balanced - against ease of use. + + +

    + +
    +

    + Robust Certificate Security +

    +

    + Security is a top priority for us. We have designed CodeGate's local + certificate management with security in mind, balanced against ease + of use. +

    +

    + We always seek to improve and balance security, privacy, and + usability. +

    +
    +
    + + + +
    + +
    +

    Key security features

    +
    +
    +

    + Per-domain certificate generation +

    +

    + Instead of using wildcard certificates, CodeGate generates a + unique certificate for each domain. This approach minimizes + security risks by limiting the impact of any single certificate + compromise.

    -

    - We always seek to improve and balance security, privacy, and - usability. +

    +
    +

    + High-strength encryption with 4096-bit RSA keys +

    +

    + CodeGate utilizes 4096-bit RSA keys for certificate authority + operations, providing enhanced security compared to standard + 2048-bit keys. The increased key length significantly reduces + the risk of brute-force attacks, ensuring long-term protection + for your data. To balance performance, 2048-bit keys are used + for server certificates.

    - - - - - -
    - -
    -

    - Key security features -

    -
    -
    -

    - Per-domain certificate generation -

    -

    - Instead of using wildcard certificates, CodeGate generates a - unique certificate for each domain. This approach minimizes - security risks by limiting the impact of any single - certificate compromise. -

    -
    -
    -

    - High-strength encryption with 4096-bit RSA keys -

    -

    - CodeGate utilizes 4096-bit RSA keys for certificate - authority operations, providing enhanced security compared - to standard 2048-bit keys. The increased key length - significantly reduces the risk of brute-force attacks, - ensuring long-term protection for your data. To balance - performance, 2048-bit keys are used for server certificates. -

    -
    -
    -

    - Secure SSL/TLS configuration -

    -

    - CodeGate's SSL context is configured to enforce the latest - security standards, including strong cipher suites and - disabling outdated protocols. This ensures secure and - efficient encrypted communications. -

    -
    -
    -

    - Certificate caching and management -

    -

    - Certificates are cached efficiently to optimize performance - without compromising security. Additionally, mechanisms are - in place to manage certificate lifecycle and prevent - resource exhaustion. -

    -
    -
    -
    -
    +
    +
    +

    + Secure SSL/TLS configuration +

    +

    + CodeGate's SSL context is configured to enforce the latest + security standards, including strong cipher suites and disabling + outdated protocols. This ensures secure and efficient encrypted + communications. +

    +
    +
    +

    + Certificate caching and management +

    +

    + Certificates are cached efficiently to optimize performance + without compromising security. Additionally, mechanisms are in + place to manage certificate lifecycle and prevent resource + exhaustion. +

    +
    +
    +
    +
    - - -
    - -
    -

    - Open source and community engagement -

    -
    -

    - Security has been a fundamental consideration throughout the - development of CodeGate. Our comprehensive approach ensures - that your development environment remains secure without - sacrificing functionality or performance. -

    -

    - We believe in transparency and continuous improvement. By - making our code open source, we invite the global security - community to review, audit, and contribute to enhancing our - security measures. -

    -

    - If you discover a security vulnerability or have suggestions - for improvement, please reach out to us at{' '} - - security@stacklok.com - - . Your contributions help us maintain the highest security - standards. -

    -

    - Explore our codebase on{' '} - - GitHub - {' '} - and join our community to help ensure CodeGate is secure and - reliable for everyone. -

    -
    -
    -
    -
    -
    - + + +
    + +
    +

    + Open source and community engagement +

    +
    +

    + Security has been a fundamental consideration throughout the + development of CodeGate. Our comprehensive approach ensures that + your development environment remains secure without sacrificing + functionality or performance. +

    +

    + We believe in transparency and continuous improvement. By making + our code open source, we invite the global security community to + review, audit, and contribute to enhancing our security measures. +

    +

    + If you discover a security vulnerability or have suggestions for + improvement, please reach out to us at{' '} + + security@stacklok.com + + . Your contributions help us maintain the highest security + standards. +

    +

    + Explore our codebase on{' '} + + GitHub + {' '} + and join our community to help ensure CodeGate is secure and + reliable for everyone. +

    +
    +
    +
    +
    ) } diff --git a/src/routes/route-certificates.tsx b/src/routes/route-certificates.tsx index bafda697..d720f9e0 100644 --- a/src/routes/route-certificates.tsx +++ b/src/routes/route-certificates.tsx @@ -16,6 +16,8 @@ import { markdown as windowsRemove } from '../markdown/certificates/windows-remo import { markdown as macosInstall } from '../markdown/certificates/macos-install.md' import { markdown as macosRemove } from '../markdown/certificates/macos-remove.md' import { Markdown } from '@/components/Markdown' +import { PageContainer } from '@/components/page-container' +import { PageHeading } from '@/components/heading' type OS = 'macos' | 'windows' | 'linux' type Action = 'install' | 'remove' @@ -95,145 +97,140 @@ export function RouteCertificates() { const currentSteps = steps[activeOS][activeAction] return ( - <> + Certificates + -
    -

    Certificates

    - - - -
    -
    - -
    -
    -

    - CodeGate CA certificate -

    -

    - This certificate allows CodeGate to act as a secure proxy for - GitHub Copilot. This certificate is unique to your system. -

    - -
    -
    -
    -
    - - - -

    - Is this certificate safe to install on my machine? -

    -
    -
    - -

    - Local-only: CodeGate runs entirely on your - machine within an isolated container, ensuring all data - processing stays local without any external transmissions. -

    -
    -
    - -

    - Secure certificate handling: This custom CA - is locally generated and managed. It is unique to your - installation and CodeGate developers have no access to it. -

    -
    -
    - -

    - No external communications: CodeGate is - designed with no capability to call home or communicate with - external servers, outside of those requested by the IDE or - agent. -

    -
    + + +
    +
    +
    -
    - - Learn more - - +
    +

    + CodeGate CA certificate +

    +

    + This certificate allows CodeGate to act as a secure proxy for + GitHub Copilot. This certificate is unique to your system. +

    +
    - - +
    + + - - -

    - Certificate management -

    - {/* OS Selection Tabs */} -
    - - - + + +

    + Is this certificate safe to install on my machine? +

    +
    +
    + +

    + Local-only: CodeGate runs entirely on your + machine within an isolated container, ensuring all data + processing stays local without any external transmissions. +

    - {/* Action Selection Tabs */} -
    - - +
    + +

    + Secure certificate handling: This custom CA is + locally generated and managed. It is unique to your installation + and CodeGate developers have no access to it. +

    -
    -
    - -
    +
    + +

    + No external communications: CodeGate is + designed with no capability to call home or communicate with + external servers, outside of those requested by the IDE or + agent. +

    +
    +
    +
    + + Learn more + + +
    + + + + + +

    Certificate management

    + {/* OS Selection Tabs */} +
    + + + +
    + {/* Action Selection Tabs */} +
    + + +
    +
    +
    +
    - - -
    - +
    + + + ) } diff --git a/vitest.config.ts b/vitest.config.ts index 9dde4ced..a71200c8 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -3,9 +3,16 @@ import { defineConfig } from 'vitest/config' import react from '@vitejs/plugin-react-swc' import tsconfigPaths from 'vite-tsconfig-paths' import path from 'path' +import { Mode, plugin as mdPlugin } from 'vite-plugin-markdown' export default defineConfig({ - plugins: [tsconfigPaths(), react()], + plugins: [ + tsconfigPaths(), + mdPlugin({ + mode: [Mode.MARKDOWN], + }), + react(), + ], resolve: { alias: { '@': path.resolve(__dirname, './src'), From f00e9d1e20d74cafb1bf352ab2aa6cff930c80a4 Mon Sep 17 00:00:00 2001 From: alex-mcgovern Date: Fri, 14 Feb 2025 16:16:19 +0000 Subject: [PATCH 7/7] fix tests --- src/components/Markdown.tsx | 2 +- src/markdown/certificates/linux-remove.md | 5 +++- .../__tests__/route-certificates.test.tsx | 28 ++++++++++++++----- 3 files changed, 26 insertions(+), 9 deletions(-) diff --git a/src/components/Markdown.tsx b/src/components/Markdown.tsx index 3e5e8fa5..e183a01e 100644 --- a/src/components/Markdown.tsx +++ b/src/components/Markdown.tsx @@ -122,13 +122,13 @@ function Code({ const markdownStyles = tv({ base: [ 'prose', + 'prose-sm prose-code:text-sm', 'prose-h1:mb-2 prose-h1:text-lg prose-h1:font-semibold', 'prose-h2:mb-2 prose-h2:text-lg prose-h2:font-semibold', 'prose-h3:mb-2 prose-h3:text-lg prose-h3:font-semibold', 'prose-h4:mb-2 prose-h4:text-lg prose-h4:font-semibold', 'prose-h5:mb-2 prose-h5:text-lg prose-h5:font-semibold', 'prose-h6:mb-2 prose-h6:text-lg prose-h6:font-semibold', - 'prose-sm', 'prose max-w-none prose-p:leading-relaxed', '[--tw-prose-pre-code:theme(textColor.secondary)]', '[--tw-prose-pre-bg:theme(colors.gray.200)]', diff --git a/src/markdown/certificates/linux-remove.md b/src/markdown/certificates/linux-remove.md index f3abb1ab..c40167bb 100644 --- a/src/markdown/certificates/linux-remove.md +++ b/src/markdown/certificates/linux-remove.md @@ -1,2 +1,5 @@ -1. Run `certutil -d sql:$HOME/.pki/nssdb -D -n CodeGate-CA` +1. Run the following command to uninstall the certificate from your account: + ```shell + certutil -d sql:$HOME/.pki/nssdb -D -n CodeGate-CA + ``` 2. Restart VS Code. diff --git a/src/routes/__tests__/route-certificates.test.tsx b/src/routes/__tests__/route-certificates.test.tsx index 5d4cdde8..78902cd7 100644 --- a/src/routes/__tests__/route-certificates.test.tsx +++ b/src/routes/__tests__/route-certificates.test.tsx @@ -19,7 +19,7 @@ describe('Certificates', () => { ) expect( - screen.getByRole('heading', { name: 'Certificate Management' }) + screen.getByRole('heading', { name: 'Certificate management' }) ).toBeVisible() expect(screen.getByText('macOS')).toBeVisible() @@ -42,18 +42,26 @@ describe('Certificates', () => { render() expect(screen.getByRole('heading', { name: 'CLI method' })).toBeVisible() - expect(screen.getByText('After downloading the certificate')).toBeVisible() + expect( + screen.getByText('After downloading the certificate', { exact: false }) + ).toBeVisible() expect(screen.getByRole('heading', { name: 'GUI method' })).toBeVisible() - expect(screen.getByText('Open the downloaded certificate')).toBeVisible() + expect( + screen.getByText('Open the downloaded certificate', { exact: false }) + ).toBeVisible() await userEvent.click( screen.getByRole('button', { name: 'Remove certificate' }) ) expect(screen.getByRole('heading', { name: 'CLI method' })).toBeVisible() - expect(screen.getByText('Open a terminal and run')).toBeVisible() + expect( + screen.getByText('Open a terminal and run', { exact: false }) + ).toBeVisible() expect(screen.getByRole('heading', { name: 'GUI method' })).toBeVisible() - expect(screen.getByText('Launch the Keychain Access app')).toBeVisible() + expect( + screen.getByText('Launch the Keychain Access app', { exact: false }) + ).toBeVisible() }) it('should render Windows certificate installation', async () => { @@ -77,12 +85,18 @@ describe('Certificates', () => { await userEvent.click(screen.getByText('Linux')) expect( - screen.getByText('/usr/local/share/ca-certificates/codegate.crt') + screen.getByText( + 'Run the following command to install the certificate for your account:' + ) ).toBeVisible() await userEvent.click( screen.getByRole('button', { name: 'Remove certificate' }) ) - expect(screen.getByText('/usr/local/share/ca-certificates/')).toBeVisible() + expect( + screen.getByText( + 'Run the following command to uninstall the certificate from your account:' + ) + ).toBeVisible() }) })