chore: upgrade tanstack/react-query to 5.77.0 (#18039)

This commit is contained in:
Bruno Quaresma
2025-05-26 16:16:55 -03:00
committed by GitHub
parent 2a15aa8a6f
commit f3311400d1
98 changed files with 523 additions and 577 deletions
+2 -15
View File
@@ -28,7 +28,7 @@ import { DecoratorHelpers } from "@storybook/addon-themes";
import isChromatic from "chromatic/isChromatic"; import isChromatic from "chromatic/isChromatic";
import { StrictMode } from "react"; import { StrictMode } from "react";
import { HelmetProvider } from "react-helmet-async"; import { HelmetProvider } from "react-helmet-async";
import { QueryClient, QueryClientProvider, parseQueryArgs } from "react-query"; import { QueryClient, QueryClientProvider } from "react-query";
import { withRouter } from "storybook-addon-remix-react-router"; import { withRouter } from "storybook-addon-remix-react-router";
import "theme/globalFonts"; import "theme/globalFonts";
import themes from "../src/theme"; import themes from "../src/theme";
@@ -114,20 +114,7 @@ function withQuery(Story, { parameters }) {
if (parameters.queries) { if (parameters.queries) {
for (const query of parameters.queries) { for (const query of parameters.queries) {
if (query.isError) { queryClient.setQueryData(query.key, query.data);
// Based on `setQueryData`, but modified to set the result as an error.
const cache = queryClient.getQueryCache();
const parsedOptions = parseQueryArgs(query.key);
const defaultedOptions = queryClient.defaultQueryOptions(parsedOptions);
// Adds an uninitialized response to the cache, which we can now mutate.
const cachedQuery = cache.build(queryClient, defaultedOptions);
// Setting `manual` prevents retries.
cachedQuery.setData(undefined, { manual: true });
// Set the `error` value and the appropriate status.
cachedQuery.setState({ error: query.data, status: "error" });
} else {
queryClient.setQueryData(query.key, query.data);
}
} }
} }
+2 -2
View File
@@ -69,7 +69,7 @@
"@radix-ui/react-slot": "1.1.1", "@radix-ui/react-slot": "1.1.1",
"@radix-ui/react-switch": "1.1.1", "@radix-ui/react-switch": "1.1.1",
"@radix-ui/react-tooltip": "1.1.7", "@radix-ui/react-tooltip": "1.1.7",
"@tanstack/react-query-devtools": "4.35.3", "@tanstack/react-query-devtools": "5.77.0",
"@xterm/addon-canvas": "0.7.0", "@xterm/addon-canvas": "0.7.0",
"@xterm/addon-fit": "0.10.0", "@xterm/addon-fit": "0.10.0",
"@xterm/addon-unicode11": "0.8.0", "@xterm/addon-unicode11": "0.8.0",
@@ -103,7 +103,7 @@
"react-dom": "18.3.1", "react-dom": "18.3.1",
"react-helmet-async": "2.0.5", "react-helmet-async": "2.0.5",
"react-markdown": "9.0.3", "react-markdown": "9.0.3",
"react-query": "npm:@tanstack/react-query@4.35.3", "react-query": "npm:@tanstack/react-query@5.77.0",
"react-router-dom": "6.26.2", "react-router-dom": "6.26.2",
"react-syntax-highlighter": "15.6.1", "react-syntax-highlighter": "15.6.1",
"react-virtualized-auto-sizer": "1.0.24", "react-virtualized-auto-sizer": "1.0.24",
+23 -75
View File
@@ -119,8 +119,8 @@ importers:
specifier: 1.1.7 specifier: 1.1.7
version: 1.1.7(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) version: 1.1.7(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@tanstack/react-query-devtools': '@tanstack/react-query-devtools':
specifier: 4.35.3 specifier: 5.77.0
version: 4.35.3(@tanstack/react-query@4.35.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) version: 5.77.0(@tanstack/react-query@5.77.0(react@18.3.1))(react@18.3.1)
'@xterm/addon-canvas': '@xterm/addon-canvas':
specifier: 0.7.0 specifier: 0.7.0
version: 0.7.0(@xterm/xterm@5.5.0) version: 0.7.0(@xterm/xterm@5.5.0)
@@ -221,8 +221,8 @@ importers:
specifier: 9.0.3 specifier: 9.0.3
version: 9.0.3(@types/react@18.3.12)(react@18.3.1) version: 9.0.3(@types/react@18.3.12)(react@18.3.1)
react-query: react-query:
specifier: npm:@tanstack/react-query@4.35.3 specifier: npm:@tanstack/react-query@5.77.0
version: '@tanstack/react-query@4.35.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)' version: '@tanstack/react-query@5.77.0(react@18.3.1)'
react-router-dom: react-router-dom:
specifier: 6.26.2 specifier: 6.26.2
version: 6.26.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) version: 6.26.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@@ -2398,31 +2398,22 @@ packages:
peerDependencies: peerDependencies:
tailwindcss: '>=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1' tailwindcss: '>=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1'
'@tanstack/match-sorter-utils@8.8.4': '@tanstack/query-core@5.77.0':
resolution: {integrity: sha512-rKH8LjZiszWEvmi01NR72QWZ8m4xmXre0OOwlRGnjU01Eqz/QnN+cqpty2PJ0efHblq09+KilvyR7lsbzmXVEw==, tarball: https://registry.npmjs.org/@tanstack/match-sorter-utils/-/match-sorter-utils-8.8.4.tgz} resolution: {integrity: sha512-PFeWjgMQjOsnxBwnW/TJoO0pCja2dzuMQoZ3Diho7dPz7FnTUwTrjNmdf08evrhSE5nvPIKeqV6R0fvQfmhGeg==, tarball: https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.77.0.tgz}
engines: {node: '>=12'}
'@tanstack/query-core@4.35.3': '@tanstack/query-devtools@5.76.0':
resolution: {integrity: sha512-PS+WEjd9wzKTyNjjQymvcOe1yg8f3wYc6mD+vb6CKyZAKvu4sIJwryfqfBULITKCla7P9C4l5e9RXePHvZOZeQ==, tarball: https://registry.npmjs.org/@tanstack/query-core/-/query-core-4.35.3.tgz} resolution: {integrity: sha512-1p92nqOBPYVqVDU0Ua5nzHenC6EGZNrLnB2OZphYw8CNA1exuvI97FVgIKON7Uug3uQqvH/QY8suUKpQo8qHNQ==, tarball: https://registry.npmjs.org/@tanstack/query-devtools/-/query-devtools-5.76.0.tgz}
'@tanstack/react-query-devtools@4.35.3': '@tanstack/react-query-devtools@5.77.0':
resolution: {integrity: sha512-UvLT7qPzCuCZ3NfjwsOqDUVN84JvSOuW6ukrjZmSqgjPqVxD6ra/HUp1CEOatQY2TRvKCp8y1lTVu+trXM30fg==, tarball: https://registry.npmjs.org/@tanstack/react-query-devtools/-/react-query-devtools-4.35.3.tgz} resolution: {integrity: sha512-Dwvs+ksXiK1tW4YnTtHwYPO5+d8IUk1l8QQJ4aGEIqKz6uTLu/67NIo7EnUF0G/Edv+UOn9P1V3tYWuVfvhbmg==, tarball: https://registry.npmjs.org/@tanstack/react-query-devtools/-/react-query-devtools-5.77.0.tgz}
peerDependencies: peerDependencies:
'@tanstack/react-query': ^4.35.3 '@tanstack/react-query': ^5.77.0
react: ^16.8.0 || ^17.0.0 || ^18.0.0 react: ^18 || ^19
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
'@tanstack/react-query@4.35.3': '@tanstack/react-query@5.77.0':
resolution: {integrity: sha512-UgTPioip/rGG3EQilXfA2j4BJkhEQsR+KAbF+KIuvQ7j4MkgnTCJF01SfRpIRNtQTlEfz/+IL7+jP8WA8bFbsw==, tarball: https://registry.npmjs.org/@tanstack/react-query/-/react-query-4.35.3.tgz} resolution: {integrity: sha512-jX52ot8WxWzWnAknpRSEWj6PTR/7nkULOfoiaVPk6nKu0otwt30UMBC9PTg/m1x0uhz1g71/imwjViTm/oYHxA==, tarball: https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.77.0.tgz}
peerDependencies: peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0 react: ^18 || ^19
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
react-native: '*'
peerDependenciesMeta:
react-dom:
optional: true
react-native:
optional: true
'@testing-library/dom@10.4.0': '@testing-library/dom@10.4.0':
resolution: {integrity: sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==, tarball: https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz} resolution: {integrity: sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==, tarball: https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz}
@@ -3258,10 +3249,6 @@ packages:
resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==, tarball: https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz} resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==, tarball: https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz}
engines: {node: '>= 0.6'} engines: {node: '>= 0.6'}
copy-anything@3.0.5:
resolution: {integrity: sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==, tarball: https://registry.npmjs.org/copy-anything/-/copy-anything-3.0.5.tgz}
engines: {node: '>=12.13'}
core-util-is@1.0.3: core-util-is@1.0.3:
resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==, tarball: https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz} resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==, tarball: https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz}
@@ -3635,6 +3622,7 @@ packages:
eslint@8.52.0: eslint@8.52.0:
resolution: {integrity: sha512-zh/JHnaixqHZsolRB/w9/02akBk9EPrOs9JwcTP2ek7yL5bVvXuRariiaAjjoJ5DvuwQ1WAE/HsMz+w17YgBCg==, tarball: https://registry.npmjs.org/eslint/-/eslint-8.52.0.tgz} resolution: {integrity: sha512-zh/JHnaixqHZsolRB/w9/02akBk9EPrOs9JwcTP2ek7yL5bVvXuRariiaAjjoJ5DvuwQ1WAE/HsMz+w17YgBCg==, tarball: https://registry.npmjs.org/eslint/-/eslint-8.52.0.tgz}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options.
hasBin: true hasBin: true
espree@9.6.1: espree@9.6.1:
@@ -4219,10 +4207,6 @@ packages:
is-weakset@2.0.2: is-weakset@2.0.2:
resolution: {integrity: sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==, tarball: https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.2.tgz} resolution: {integrity: sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==, tarball: https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.2.tgz}
is-what@4.1.16:
resolution: {integrity: sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==, tarball: https://registry.npmjs.org/is-what/-/is-what-4.1.16.tgz}
engines: {node: '>=12.13'}
is-wsl@2.2.0: is-wsl@2.2.0:
resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==, tarball: https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz} resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==, tarball: https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz}
engines: {node: '>=8'} engines: {node: '>=8'}
@@ -5516,9 +5500,6 @@ packages:
remark-stringify@11.0.0: remark-stringify@11.0.0:
resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==, tarball: https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz} resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==, tarball: https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz}
remove-accents@0.4.2:
resolution: {integrity: sha512-7pXIJqJOq5tFgG1A2Zxti3Ht8jJF337m4sowbuHsW30ZnkQFnDzy9qBNhgzX8ZLW4+UBcXiiR7SwR6pokHsxiA==, tarball: https://registry.npmjs.org/remove-accents/-/remove-accents-0.4.2.tgz}
require-directory@2.1.1: require-directory@2.1.1:
resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==, tarball: https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz} resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==, tarball: https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
@@ -5835,10 +5816,6 @@ packages:
engines: {node: '>=16 || 14 >=14.17'} engines: {node: '>=16 || 14 >=14.17'}
hasBin: true hasBin: true
superjson@1.13.3:
resolution: {integrity: sha512-mJiVjfd2vokfDxsQPOwJ/PtanO87LhpYY88ubI5dUB1Ab58Txbyje3+jpm+/83R/fevaq/107NNhtYBLuoTrFg==, tarball: https://registry.npmjs.org/superjson/-/superjson-1.13.3.tgz}
engines: {node: '>=10'}
supports-color@5.5.0: supports-color@5.5.0:
resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==, tarball: https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz} resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==, tarball: https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz}
engines: {node: '>=4'} engines: {node: '>=4'}
@@ -6152,11 +6129,6 @@ packages:
'@types/react': '@types/react':
optional: true optional: true
use-sync-external-store@1.2.0:
resolution: {integrity: sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==, tarball: https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz}
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0
use-sync-external-store@1.4.0: use-sync-external-store@1.4.0:
resolution: {integrity: sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw==, tarball: https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.4.0.tgz} resolution: {integrity: sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw==, tarball: https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.4.0.tgz}
peerDependencies: peerDependencies:
@@ -8523,28 +8495,20 @@ snapshots:
postcss-selector-parser: 6.0.10 postcss-selector-parser: 6.0.10
tailwindcss: 3.4.17(ts-node@10.9.2(@swc/core@1.3.38)(@types/node@20.17.16)(typescript@5.6.3)) tailwindcss: 3.4.17(ts-node@10.9.2(@swc/core@1.3.38)(@types/node@20.17.16)(typescript@5.6.3))
'@tanstack/match-sorter-utils@8.8.4': '@tanstack/query-core@5.77.0': {}
dependencies:
remove-accents: 0.4.2
'@tanstack/query-core@4.35.3': {} '@tanstack/query-devtools@5.76.0': {}
'@tanstack/react-query-devtools@4.35.3(@tanstack/react-query@4.35.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': '@tanstack/react-query-devtools@5.77.0(@tanstack/react-query@5.77.0(react@18.3.1))(react@18.3.1)':
dependencies: dependencies:
'@tanstack/match-sorter-utils': 8.8.4 '@tanstack/query-devtools': 5.76.0
'@tanstack/react-query': 4.35.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@tanstack/react-query': 5.77.0(react@18.3.1)
react: 18.3.1 react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
superjson: 1.13.3
use-sync-external-store: 1.2.0(react@18.3.1)
'@tanstack/react-query@4.35.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': '@tanstack/react-query@5.77.0(react@18.3.1)':
dependencies: dependencies:
'@tanstack/query-core': 4.35.3 '@tanstack/query-core': 5.77.0
react: 18.3.1 react: 18.3.1
use-sync-external-store: 1.2.0(react@18.3.1)
optionalDependencies:
react-dom: 18.3.1(react@18.3.1)
'@testing-library/dom@10.4.0': '@testing-library/dom@10.4.0':
dependencies: dependencies:
@@ -9447,10 +9411,6 @@ snapshots:
cookie@0.7.2: {} cookie@0.7.2: {}
copy-anything@3.0.5:
dependencies:
is-what: 4.1.16
core-util-is@1.0.3: {} core-util-is@1.0.3: {}
cosmiconfig@7.1.0: cosmiconfig@7.1.0:
@@ -10542,8 +10502,6 @@ snapshots:
call-bind: 1.0.8 call-bind: 1.0.8
get-intrinsic: 1.3.0 get-intrinsic: 1.3.0
is-what@4.1.16: {}
is-wsl@2.2.0: is-wsl@2.2.0:
dependencies: dependencies:
is-docker: 2.2.1 is-docker: 2.2.1
@@ -12425,8 +12383,6 @@ snapshots:
mdast-util-to-markdown: 2.1.0 mdast-util-to-markdown: 2.1.0
unified: 11.0.5 unified: 11.0.5
remove-accents@0.4.2: {}
require-directory@2.1.1: {} require-directory@2.1.1: {}
requires-port@1.0.0: {} requires-port@1.0.0: {}
@@ -12767,10 +12723,6 @@ snapshots:
pirates: 4.0.6 pirates: 4.0.6
ts-interface-checker: 0.1.13 ts-interface-checker: 0.1.13
superjson@1.13.3:
dependencies:
copy-anything: 3.0.5
supports-color@5.5.0: supports-color@5.5.0:
dependencies: dependencies:
has-flag: 3.0.0 has-flag: 3.0.0
@@ -13108,10 +13060,6 @@ snapshots:
optionalDependencies: optionalDependencies:
'@types/react': 18.3.12 '@types/react': 18.3.12
use-sync-external-store@1.2.0(react@18.3.1):
dependencies:
react: 18.3.1
use-sync-external-store@1.4.0(react@18.3.1): use-sync-external-store@1.4.0(react@18.3.1):
dependencies: dependencies:
react: 18.3.1 react: 18.3.1
+1 -1
View File
@@ -5,7 +5,7 @@ export const createChat = (queryClient: QueryClient) => {
return { return {
mutationFn: API.createChat, mutationFn: API.createChat,
onSuccess: async () => { onSuccess: async () => {
await queryClient.invalidateQueries(["chats"]); await queryClient.invalidateQueries({ queryKey: ["chats"] });
}, },
}; };
}; };
+2 -2
View File
@@ -13,7 +13,7 @@ export const health = () => ({
export const refreshHealth = (queryClient: QueryClient) => { export const refreshHealth = (queryClient: QueryClient) => {
return { return {
mutationFn: async () => { mutationFn: async () => {
await queryClient.cancelQueries(HEALTH_QUERY_KEY); await queryClient.cancelQueries({ queryKey: HEALTH_QUERY_KEY });
const newHealthData = await API.getHealth(true); const newHealthData = await API.getHealth(true);
queryClient.setQueryData(HEALTH_QUERY_KEY, newHealthData); queryClient.setQueryData(HEALTH_QUERY_KEY, newHealthData);
}, },
@@ -38,7 +38,7 @@ export const updateHealthSettings = (
return { return {
mutationFn: API.updateHealthSettings, mutationFn: API.updateHealthSettings,
onSuccess: async (_, newSettings) => { onSuccess: async (_, newSettings) => {
await queryClient.invalidateQueries(HEALTH_QUERY_KEY); await queryClient.invalidateQueries({ queryKey: HEALTH_QUERY_KEY });
queryClient.setQueryData(HEALTH_QUERY_SETTINGS_KEY, newSettings); queryClient.setQueryData(HEALTH_QUERY_SETTINGS_KEY, newSettings);
}, },
}; };
+6 -2
View File
@@ -37,7 +37,9 @@ export const exchangeExternalAuthDevice = (
queryKey: ["external-auth", providerId, "device", deviceCode], queryKey: ["external-auth", providerId, "device", deviceCode],
onSuccess: async () => { onSuccess: async () => {
// Force a refresh of the Git auth status. // Force a refresh of the Git auth status.
await queryClient.invalidateQueries(["external-auth", providerId]); await queryClient.invalidateQueries({
queryKey: ["external-auth", providerId],
});
}, },
}; };
}; };
@@ -57,7 +59,9 @@ export const unlinkExternalAuths = (queryClient: QueryClient) => {
return { return {
mutationFn: API.unlinkExternalAuthProvider, mutationFn: API.unlinkExternalAuthProvider,
onSuccess: async () => { onSuccess: async () => {
await queryClient.invalidateQueries(["external-auth"]); await queryClient.invalidateQueries({
queryKey: ["external-auth"],
});
}, },
}; };
}; };
+13 -9
View File
@@ -117,10 +117,12 @@ export const createGroup = (queryClient: QueryClient, organization: string) => {
mutationFn: (request: CreateGroupRequest) => mutationFn: (request: CreateGroupRequest) =>
API.createGroup(organization, request), API.createGroup(organization, request),
onSuccess: async () => { onSuccess: async () => {
await queryClient.invalidateQueries(groupsQueryKey); await queryClient.invalidateQueries({
await queryClient.invalidateQueries( queryKey: groupsQueryKey,
getGroupsByOrganizationQueryKey(organization), });
); await queryClient.invalidateQueries({
queryKey: getGroupsByOrganizationQueryKey(organization),
});
}, },
}; };
}; };
@@ -169,11 +171,13 @@ const invalidateGroup = (
groupId: string, groupId: string,
) => ) =>
Promise.all([ Promise.all([
queryClient.invalidateQueries(groupsQueryKey), queryClient.invalidateQueries({ queryKey: groupsQueryKey }),
queryClient.invalidateQueries( queryClient.invalidateQueries({
getGroupsByOrganizationQueryKey(organization), queryKey: getGroupsByOrganizationQueryKey(organization),
), }),
queryClient.invalidateQueries(getGroupQueryKey(organization, groupId)), queryClient.invalidateQueries({
queryKey: getGroupQueryKey(organization, groupId),
}),
]); ]);
function sortGroupsByName<T extends Group>( function sortGroupsByName<T extends Group>(
+3 -1
View File
@@ -9,7 +9,9 @@ export const patchOrganizationSyncSettings = (queryClient: QueryClient) => {
mutationFn: (request: OrganizationSyncSettings) => mutationFn: (request: OrganizationSyncSettings) =>
API.patchOrganizationIdpSyncSettings(request), API.patchOrganizationIdpSyncSettings(request),
onSuccess: async () => onSuccess: async () =>
await queryClient.invalidateQueries(getOrganizationIdpSyncSettingsKey()), await queryClient.invalidateQueries({
queryKey: getOrganizationIdpSyncSettingsKey(),
}),
}; };
}; };
+33 -29
View File
@@ -22,7 +22,7 @@ import {
type WorkspacePermissions, type WorkspacePermissions,
workspacePermissionChecks, workspacePermissionChecks,
} from "modules/permissions/workspaces"; } from "modules/permissions/workspaces";
import type { QueryClient } from "react-query"; import type { QueryClient, UseQueryOptions } from "react-query";
import { meKey } from "./users"; import { meKey } from "./users";
export const createOrganization = (queryClient: QueryClient) => { export const createOrganization = (queryClient: QueryClient) => {
@@ -31,8 +31,8 @@ export const createOrganization = (queryClient: QueryClient) => {
API.createOrganization(params), API.createOrganization(params),
onSuccess: async () => { onSuccess: async () => {
await queryClient.invalidateQueries(meKey); await queryClient.invalidateQueries({ queryKey: meKey });
await queryClient.invalidateQueries(organizationsKey); await queryClient.invalidateQueries({ queryKey: organizationsKey });
}, },
}; };
}; };
@@ -48,7 +48,7 @@ export const updateOrganization = (queryClient: QueryClient) => {
API.updateOrganization(variables.organizationId, variables.req), API.updateOrganization(variables.organizationId, variables.req),
onSuccess: async () => { onSuccess: async () => {
await queryClient.invalidateQueries(organizationsKey); await queryClient.invalidateQueries({ queryKey: organizationsKey });
}, },
}; };
}; };
@@ -59,8 +59,8 @@ export const deleteOrganization = (queryClient: QueryClient) => {
API.deleteOrganization(organizationId), API.deleteOrganization(organizationId),
onSuccess: async () => { onSuccess: async () => {
await queryClient.invalidateQueries(meKey); await queryClient.invalidateQueries({ queryKey: meKey });
await queryClient.invalidateQueries(organizationsKey); await queryClient.invalidateQueries({ queryKey: organizationsKey });
}, },
}; };
}; };
@@ -117,7 +117,9 @@ export const addOrganizationMember = (queryClient: QueryClient, id: string) => {
}, },
onSuccess: async () => { onSuccess: async () => {
await queryClient.invalidateQueries(["organization", id, "members"]); await queryClient.invalidateQueries({
queryKey: ["organization", id, "members"],
});
}, },
}; };
}; };
@@ -132,7 +134,9 @@ export const removeOrganizationMember = (
}, },
onSuccess: async () => { onSuccess: async () => {
await queryClient.invalidateQueries(["organization", id, "members"]); await queryClient.invalidateQueries({
queryKey: ["organization", id, "members"],
});
}, },
}; };
}; };
@@ -147,11 +151,9 @@ export const updateOrganizationMemberRoles = (
}, },
onSuccess: async () => { onSuccess: async () => {
await queryClient.invalidateQueries([ await queryClient.invalidateQueries({
"organization", queryKey: ["organization", organizationId, "members"],
organizationId, });
"members",
]);
}, },
}; };
}; };
@@ -240,9 +242,9 @@ export const patchRoleSyncSettings = (
mutationFn: (request: RoleSyncSettings) => mutationFn: (request: RoleSyncSettings) =>
API.patchRoleIdpSyncSettings(request, organization), API.patchRoleIdpSyncSettings(request, organization),
onSuccess: async () => onSuccess: async () =>
await queryClient.invalidateQueries( await queryClient.invalidateQueries({
getRoleIdpSyncSettingsKey(organization), queryKey: getRoleIdpSyncSettingsKey(organization),
), }),
}; };
}; };
@@ -269,12 +271,13 @@ export const provisionerJobs = (
export const organizationsPermissions = ( export const organizationsPermissions = (
organizationIds: string[] | undefined, organizationIds: string[] | undefined,
) => { ) => {
if (!organizationIds) {
return { enabled: false };
}
return { return {
queryKey: ["organizations", [...organizationIds.sort()], "permissions"], enabled: !!organizationIds,
queryKey: [
"organizations",
[...(organizationIds ?? []).sort()],
"permissions",
],
queryFn: async () => { queryFn: async () => {
// Only request what we need for the sidebar, which is one edit permission // Only request what we need for the sidebar, which is one edit permission
// per sub-link (settings, groups, roles, and members pages) that tells us // per sub-link (settings, groups, roles, and members pages) that tells us
@@ -283,7 +286,7 @@ export const organizationsPermissions = (
// The endpoint takes a flat array, so to avoid collisions prepend each // The endpoint takes a flat array, so to avoid collisions prepend each
// check with the org ID (the key can be anything we want). // check with the org ID (the key can be anything we want).
const prefixedChecks = organizationIds.flatMap((orgId) => const prefixedChecks = (organizationIds ?? []).flatMap((orgId) =>
Object.entries(organizationPermissionChecks(orgId)).map( Object.entries(organizationPermissionChecks(orgId)).map(
([key, val]) => [`${orgId}.${key}`, val], ([key, val]) => [`${orgId}.${key}`, val],
), ),
@@ -315,14 +318,15 @@ export const workspacePermissionsByOrganization = (
organizationIds: string[] | undefined, organizationIds: string[] | undefined,
userId: string, userId: string,
) => { ) => {
if (!organizationIds) {
return { enabled: false };
}
return { return {
queryKey: ["workspaces", [...organizationIds.sort()], "permissions"], enabled: !!organizationIds,
queryKey: [
"workspaces",
[...(organizationIds ?? []).sort()],
"permissions",
],
queryFn: async () => { queryFn: async () => {
const prefixedChecks = organizationIds.flatMap((orgId) => const prefixedChecks = (organizationIds ?? []).flatMap((orgId) =>
Object.entries(workspacePermissionChecks(orgId, userId)).map( Object.entries(workspacePermissionChecks(orgId, userId)).map(
([key, val]) => [`${orgId}.${key}`, val], ([key, val]) => [`${orgId}.${key}`, val],
), ),
@@ -346,7 +350,7 @@ export const workspacePermissionsByOrganization = (
{} as Record<string, Partial<WorkspacePermissions>>, {} as Record<string, Partial<WorkspacePermissions>>,
) as Record<string, WorkspacePermissions>; ) as Record<string, WorkspacePermissions>;
}, },
}; } satisfies UseQueryOptions<Record<string, WorkspacePermissions>>;
}; };
const getOrganizationIdpSyncClaimFieldValuesKey = ( const getOrganizationIdpSyncClaimFieldValuesKey = (
+9 -9
View File
@@ -33,9 +33,9 @@ export const createOrganizationRole = (
mutationFn: (request: Role) => mutationFn: (request: Role) =>
API.createOrganizationRole(organization, request), API.createOrganizationRole(organization, request),
onSuccess: async (updatedRole: Role) => onSuccess: async (updatedRole: Role) =>
await queryClient.invalidateQueries( await queryClient.invalidateQueries({
getRoleQueryKey(organization, updatedRole.name), queryKey: getRoleQueryKey(organization, updatedRole.name),
), }),
}; };
}; };
@@ -47,9 +47,9 @@ export const updateOrganizationRole = (
mutationFn: (request: Role) => mutationFn: (request: Role) =>
API.updateOrganizationRole(organization, request), API.updateOrganizationRole(organization, request),
onSuccess: async (updatedRole: Role) => onSuccess: async (updatedRole: Role) =>
await queryClient.invalidateQueries( await queryClient.invalidateQueries({
getRoleQueryKey(organization, updatedRole.name), queryKey: getRoleQueryKey(organization, updatedRole.name),
), }),
}; };
}; };
@@ -61,8 +61,8 @@ export const deleteOrganizationRole = (
mutationFn: (roleName: string) => mutationFn: (roleName: string) =>
API.deleteOrganizationRole(organization, roleName), API.deleteOrganizationRole(organization, roleName),
onSuccess: async (_: unknown, roleName: string) => onSuccess: async (_: unknown, roleName: string) =>
await queryClient.invalidateQueries( await queryClient.invalidateQueries({
getRoleQueryKey(organization, roleName), queryKey: getRoleQueryKey(organization, roleName),
), }),
}; };
}; };
+5 -5
View File
@@ -11,13 +11,11 @@ const userQuietHoursScheduleKey = (userId: string) => [
"quietHours", "quietHours",
]; ];
export const userQuietHoursSchedule = ( export const userQuietHoursSchedule = (userId: string) => {
userId: string,
): QueryOptions<UserQuietHoursScheduleResponse> => {
return { return {
queryKey: userQuietHoursScheduleKey(userId), queryKey: userQuietHoursScheduleKey(userId),
queryFn: () => API.getUserQuietHoursSchedule(userId), queryFn: () => API.getUserQuietHoursSchedule(userId),
}; } satisfies QueryOptions<UserQuietHoursScheduleResponse>;
}; };
export const updateUserQuietHoursSchedule = ( export const updateUserQuietHoursSchedule = (
@@ -28,7 +26,9 @@ export const updateUserQuietHoursSchedule = (
mutationFn: (request: UpdateUserQuietHoursScheduleRequest) => mutationFn: (request: UpdateUserQuietHoursScheduleRequest) =>
API.updateUserQuietHoursSchedule(userId, request), API.updateUserQuietHoursSchedule(userId, request),
onSuccess: async () => { onSuccess: async () => {
await queryClient.invalidateQueries(userQuietHoursScheduleKey(userId)); await queryClient.invalidateQueries({
queryKey: userQuietHoursScheduleKey(userId),
});
}, },
}; };
}; };
+13 -12
View File
@@ -15,11 +15,11 @@ import { getTemplateVersionFiles } from "utils/templateVersion";
const templateKey = (templateId: string) => ["template", templateId]; const templateKey = (templateId: string) => ["template", templateId];
export const template = (templateId: string): QueryOptions<Template> => { export const template = (templateId: string) => {
return { return {
queryKey: templateKey(templateId), queryKey: templateKey(templateId),
queryFn: async () => API.getTemplate(templateId), queryFn: async () => API.getTemplate(templateId),
}; } satisfies QueryOptions<Template>;
}; };
export const templateByNameKey = (organization: string, name: string) => [ export const templateByNameKey = (organization: string, name: string) => [
@@ -28,14 +28,11 @@ export const templateByNameKey = (organization: string, name: string) => [
name, name,
]; ];
export const templateByName = ( export const templateByName = (organization: string, name: string) => {
organization: string,
name: string,
): QueryOptions<Template> => {
return { return {
queryKey: templateByNameKey(organization, name), queryKey: templateByNameKey(organization, name),
queryFn: async () => API.getTemplateByName(organization, name), queryFn: async () => API.getTemplateByName(organization, name),
}; } satisfies QueryOptions<Template>;
}; };
const getTemplatesQueryKey = ( const getTemplatesQueryKey = (
@@ -88,7 +85,9 @@ export const setUserRole = (
}, },
}), }),
onSuccess: async (_res, { templateId }) => { onSuccess: async (_res, { templateId }) => {
await queryClient.invalidateQueries(["templateAcl", templateId]); await queryClient.invalidateQueries({
queryKey: ["templateAcl", templateId],
});
}, },
}; };
}; };
@@ -108,7 +107,9 @@ export const setGroupRole = (
}, },
}), }),
onSuccess: async (_res, { templateId }) => { onSuccess: async (_res, { templateId }) => {
await queryClient.invalidateQueries(["templateAcl", templateId]); await queryClient.invalidateQueries({
queryKey: ["templateAcl", templateId],
});
}, },
}; };
}; };
@@ -197,9 +198,9 @@ export const updateActiveTemplateVersion = (
}), }),
onSuccess: async () => { onSuccess: async () => {
// invalidated because of `active_version_id` // invalidated because of `active_version_id`
await queryClient.invalidateQueries( await queryClient.invalidateQueries({
templateByNameKey(template.organization_id, template.name), queryKey: templateByNameKey(template.organization_id, template.name),
); });
}, },
}; };
}; };
+9 -7
View File
@@ -51,7 +51,7 @@ export const users = (req: UsersRequest): UseQueryOptions<GetUsersResponse> => {
return { return {
queryKey: usersKey(req), queryKey: usersKey(req),
queryFn: ({ signal }) => API.getUsers(req, signal), queryFn: ({ signal }) => API.getUsers(req, signal),
cacheTime: 5 * 1000 * 60, gcTime: 5 * 1000 * 60,
}; };
}; };
@@ -69,7 +69,7 @@ export const createUser = (queryClient: QueryClient) => {
return { return {
mutationFn: API.createUser, mutationFn: API.createUser,
onSuccess: async () => { onSuccess: async () => {
await queryClient.invalidateQueries(["users"]); await queryClient.invalidateQueries({ queryKey: ["users"] });
}, },
}; };
}; };
@@ -84,7 +84,7 @@ export const suspendUser = (queryClient: QueryClient) => {
return { return {
mutationFn: API.suspendUser, mutationFn: API.suspendUser,
onSuccess: async () => { onSuccess: async () => {
await queryClient.invalidateQueries(["users"]); await queryClient.invalidateQueries({ queryKey: ["users"] });
}, },
}; };
}; };
@@ -93,7 +93,7 @@ export const activateUser = (queryClient: QueryClient) => {
return { return {
mutationFn: API.activateUser, mutationFn: API.activateUser,
onSuccess: async () => { onSuccess: async () => {
await queryClient.invalidateQueries(["users"]); await queryClient.invalidateQueries({ queryKey: ["users"] });
}, },
}; };
}; };
@@ -102,7 +102,7 @@ export const deleteUser = (queryClient: QueryClient) => {
return { return {
mutationFn: API.deleteUser, mutationFn: API.deleteUser,
onSuccess: async () => { onSuccess: async () => {
await queryClient.invalidateQueries(["users"]); await queryClient.invalidateQueries({ queryKey: ["users"] });
}, },
}; };
}; };
@@ -112,7 +112,7 @@ export const updateRoles = (queryClient: QueryClient) => {
mutationFn: ({ userId, roles }: { userId: string; roles: string[] }) => mutationFn: ({ userId, roles }: { userId: string; roles: string[] }) =>
API.updateUserRoles(roles, userId), API.updateUserRoles(roles, userId),
onSuccess: async () => { onSuccess: async () => {
await queryClient.invalidateQueries(["users"]); await queryClient.invalidateQueries({ queryKey: ["users"] });
}, },
}; };
}; };
@@ -257,7 +257,9 @@ export const updateAppearanceSettings = (
onSuccess: async () => onSuccess: async () =>
// Could technically invalidate more, but we only ever care about the // Could technically invalidate more, but we only ever care about the
// `theme_preference` for the `me` query. // `theme_preference` for the `me` query.
await queryClient.invalidateQueries(myAppearanceKey), await queryClient.invalidateQueries({
queryKey: myAppearanceKey,
}),
}; };
}; };
+2 -2
View File
@@ -2,12 +2,12 @@ import type { MetadataState, MetadataValue } from "hooks/useEmbeddedMetadata";
import type { QueryKey, UseQueryOptions } from "react-query"; import type { QueryKey, UseQueryOptions } from "react-query";
export const disabledRefetchOptions = { export const disabledRefetchOptions = {
cacheTime: Number.POSITIVE_INFINITY, gcTime: Number.POSITIVE_INFINITY,
staleTime: Number.POSITIVE_INFINITY, staleTime: Number.POSITIVE_INFINITY,
refetchOnMount: false, refetchOnMount: false,
refetchOnReconnect: false, refetchOnReconnect: false,
refetchOnWindowFocus: false, refetchOnWindowFocus: false,
} as const satisfies UseQueryOptions; } as const satisfies Partial<UseQueryOptions>;
type UseQueryOptionsWithMetadata< type UseQueryOptionsWithMetadata<
TMetadata extends MetadataValue = MetadataValue, TMetadata extends MetadataValue = MetadataValue,
+7 -3
View File
@@ -37,7 +37,7 @@ export const workspaceBuildsKey = (workspaceId: string) => [
export const infiniteWorkspaceBuilds = ( export const infiniteWorkspaceBuilds = (
workspaceId: string, workspaceId: string,
req?: WorkspaceBuildsRequest, req?: WorkspaceBuildsRequest,
): UseInfiniteQueryOptions<WorkspaceBuild[]> => { ) => {
const limit = req?.limit ?? 25; const limit = req?.limit ?? 25;
return { return {
@@ -48,13 +48,17 @@ export const infiniteWorkspaceBuilds = (
} }
return pages.length + 1; return pages.length + 1;
}, },
queryFn: ({ pageParam = 0 }) => { initialPageParam: 0,
queryFn: ({ pageParam }) => {
if (typeof pageParam !== "number") {
throw new Error("pageParam must be a number");
}
return API.getWorkspaceBuilds(workspaceId, { return API.getWorkspaceBuilds(workspaceId, {
limit, limit,
offset: pageParam <= 0 ? 0 : (pageParam - 1) * limit, offset: pageParam <= 0 ? 0 : (pageParam - 1) * limit,
}); });
}, },
}; } satisfies UseInfiniteQueryOptions<WorkspaceBuild[]>;
}; };
// We use readyAgentsCount to invalidate the query when an agent connects // We use readyAgentsCount to invalidate the query when an agent connects
+2 -2
View File
@@ -55,7 +55,7 @@ export const createWorkspace = (queryClient: QueryClient) => {
return API.createWorkspace(userId, req); return API.createWorkspace(userId, req);
}, },
onSuccess: async () => { onSuccess: async () => {
await queryClient.invalidateQueries(["workspaces"]); await queryClient.invalidateQueries({ queryKey: ["workspaces"] });
}, },
}; };
}; };
@@ -114,7 +114,7 @@ export const autoCreateWorkspace = (queryClient: QueryClient) => {
}); });
}, },
onSuccess: async () => { onSuccess: async () => {
await queryClient.invalidateQueries(["workspaces"]); await queryClient.invalidateQueries({ queryKey: ["workspaces"] });
}, },
}; };
}; };
+2 -2
View File
@@ -1,6 +1,6 @@
import type { SelectFilterOption } from "components/Filter/SelectFilter"; import type { SelectFilterOption } from "components/Filter/SelectFilter";
import { useMemo, useRef, useState } from "react"; import { useMemo, useRef, useState } from "react";
import { useQuery } from "react-query"; import { keepPreviousData, useQuery } from "react-query";
export type UseFilterMenuOptions = { export type UseFilterMenuOptions = {
id: string; id: string;
@@ -40,7 +40,7 @@ export const useFilterMenu = ({
return getSelectedOption(); return getSelectedOption();
}, },
enabled, enabled,
keepPreviousData: true, placeholderData: keepPreviousData,
}); });
const selectedOption = selectedOptionQuery.data; const selectedOption = selectedOptionQuery.data;
const searchOptionsQuery = useQuery({ const searchOptionsQuery = useQuery({
@@ -29,24 +29,26 @@ export const OrganizationAutocomplete: FC<OrganizationAutocompleteProps> = ({
}) => { }) => {
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const [selected, setSelected] = useState<Organization | null>(null); const [selected, setSelected] = useState<Organization | null>(null);
const organizationsQuery = useQuery(organizations()); const organizationsQuery = useQuery(organizations());
const checks =
check &&
organizationsQuery.data &&
Object.fromEntries(
organizationsQuery.data.map((org) => [
org.id,
{
...check,
object: { ...check.object, organization_id: org.id },
},
]),
);
const permissionsQuery = useQuery( const permissionsQuery = useQuery({
check && organizationsQuery.data ...checkAuthorization({
? checkAuthorization({ checks: checks ?? {},
checks: Object.fromEntries( }),
organizationsQuery.data.map((org) => [ enabled: Boolean(check && organizationsQuery.data),
org.id, });
{
...check,
object: { ...check.object, organization_id: org.id },
},
]),
),
})
: { enabled: false },
);
// If an authorization check was provided, filter the organizations based on // If an authorization check was provided, filter the organizations based on
// the results of that check. // the results of that check.
@@ -9,7 +9,7 @@ import type { PaginationResult } from "./PaginationContainer";
type ResultBase = Omit< type ResultBase = Omit<
PaginationResult, PaginationResult,
"isPreviousData" | "currentOffsetStart" | "totalRecords" | "totalPages" "isPlaceholderData" | "currentOffsetStart" | "totalRecords" | "totalPages"
>; >;
export const mockPaginationResultBase: ResultBase = { export const mockPaginationResultBase: ResultBase = {
@@ -27,7 +27,7 @@ export const mockPaginationResultBase: ResultBase = {
export const mockInitialRenderResult: PaginationResult = { export const mockInitialRenderResult: PaginationResult = {
...mockPaginationResultBase, ...mockPaginationResultBase,
isSuccess: false, isSuccess: false,
isPreviousData: false, isPlaceholderData: false,
currentOffsetStart: undefined, currentOffsetStart: undefined,
hasNextPage: false, hasNextPage: false,
hasPreviousPage: false, hasPreviousPage: false,
@@ -38,7 +38,7 @@ export const mockInitialRenderResult: PaginationResult = {
export const mockSuccessResult: PaginationResult = { export const mockSuccessResult: PaginationResult = {
...mockPaginationResultBase, ...mockPaginationResultBase,
isSuccess: true, isSuccess: true,
isPreviousData: false, isPlaceholderData: false,
currentOffsetStart: 1, currentOffsetStart: 1,
totalPages: 1, totalPages: 1,
totalRecords: 4, totalRecords: 4,
@@ -49,7 +49,7 @@ export const FirstPageWithData: Story = {
totalPages: 4, totalPages: 4,
hasPreviousPage: false, hasPreviousPage: false,
hasNextPage: true, hasNextPage: true,
isPreviousData: false, isPlaceholderData: false,
}, },
}, },
}; };
@@ -65,7 +65,7 @@ export const FirstPageWithLittleData: Story = {
totalPages: 1, totalPages: 1,
hasPreviousPage: false, hasPreviousPage: false,
hasNextPage: false, hasNextPage: false,
isPreviousData: false, isPlaceholderData: false,
}, },
}, },
}; };
@@ -81,7 +81,7 @@ export const FirstPageWithNoData: Story = {
totalPages: 0, totalPages: 0,
hasPreviousPage: false, hasPreviousPage: false,
hasNextPage: false, hasNextPage: false,
isPreviousData: false, isPlaceholderData: false,
}, },
}, },
}; };
@@ -97,7 +97,7 @@ export const TransitionFromFirstToSecondPage: Story = {
totalPages: 4, totalPages: 4,
hasPreviousPage: false, hasPreviousPage: false,
hasNextPage: false, hasNextPage: false,
isPreviousData: true, isPlaceholderData: true,
}, },
children: <div>Previous data from page 1</div>, children: <div>Previous data from page 1</div>,
}, },
@@ -114,7 +114,7 @@ export const SecondPageWithData: Story = {
totalPages: 4, totalPages: 4,
hasPreviousPage: true, hasPreviousPage: true,
hasNextPage: true, hasNextPage: true,
isPreviousData: false, isPlaceholderData: false,
}, },
children: <div>New data for page 2</div>, children: <div>New data for page 2</div>,
}, },
@@ -4,7 +4,7 @@ import { PaginationHeader } from "./PaginationHeader";
import { PaginationWidgetBase } from "./PaginationWidgetBase"; import { PaginationWidgetBase } from "./PaginationWidgetBase";
export type PaginationResult = PaginationResultInfo & { export type PaginationResult = PaginationResultInfo & {
isPreviousData: boolean; isPlaceholderData: boolean;
}; };
type PaginationProps = HTMLAttributes<HTMLDivElement> & { type PaginationProps = HTMLAttributes<HTMLDivElement> & {
@@ -2,7 +2,7 @@ import TextField, { type TextFieldProps } from "@mui/material/TextField";
import { API } from "api/api"; import { API } from "api/api";
import { useDebouncedValue } from "hooks/debounce"; import { useDebouncedValue } from "hooks/debounce";
import type { FC } from "react"; import type { FC } from "react";
import { useQuery } from "react-query"; import { keepPreviousData, useQuery } from "react-query";
// TODO: @BrunoQuaresma: Unable to integrate Yup + Formik for validation. The // TODO: @BrunoQuaresma: Unable to integrate Yup + Formik for validation. The
// validation was triggering on the onChange event, but the form.errors were not // validation was triggering on the onChange event, but the form.errors were not
@@ -19,7 +19,7 @@ export const PasswordField: FC<TextFieldProps> = (props) => {
const validatePasswordQuery = useQuery({ const validatePasswordQuery = useQuery({
queryKey: ["validatePassword", debouncedValue], queryKey: ["validatePassword", debouncedValue],
queryFn: () => API.validateUserPassword(debouncedValue), queryFn: () => API.validateUserPassword(debouncedValue),
keepPreviousData: true, placeholderData: keepPreviousData,
enabled: debouncedValue.length > 0, enabled: debouncedValue.length > 0,
}); });
const valid = validatePasswordQuery.data?.valid ?? true; const valid = validatePasswordQuery.data?.valid ?? true;
@@ -15,7 +15,7 @@ import {
type FC, type FC,
useState, useState,
} from "react"; } from "react";
import { useQuery } from "react-query"; import { keepPreviousData, useQuery } from "react-query";
import { prepareQuery } from "utils/filters"; import { prepareQuery } from "utils/filters";
// The common properties between users and org members that we need. // The common properties between users and org members that we need.
@@ -45,7 +45,7 @@ export const UserAutocomplete: FC<UserAutocompleteProps> = (props) => {
limit: 25, limit: 25,
}), }),
enabled: filter !== undefined, enabled: filter !== undefined,
keepPreviousData: true, placeholderData: keepPreviousData,
}); });
return ( return (
<InnerAutocomplete<User> <InnerAutocomplete<User>
@@ -72,7 +72,7 @@ export const MemberAutocomplete: FC<MemberAutocompleteProps> = ({
const membersQuery = useQuery({ const membersQuery = useQuery({
...organizationMembers(organizationId), ...organizationMembers(organizationId),
enabled: filter !== undefined, enabled: filter !== undefined,
keepPreviousData: true, placeholderData: keepPreviousData,
}); });
return ( return (
<InnerAutocomplete<OrganizationMemberWithUserData> <InnerAutocomplete<OrganizationMemberWithUserData>
+3 -3
View File
@@ -72,7 +72,7 @@ export const AuthProvider: FC<PropsWithChildren> = ({ children }) => {
userQuery.isError && userQuery.isError &&
isApiError(userQuery.error) && isApiError(userQuery.error) &&
userQuery.error.response.status === 401; userQuery.error.response.status === 401;
const isSigningOut = logoutMutation.isLoading; const isSigningOut = logoutMutation.isPending;
const isLoading = const isLoading =
userQuery.isLoading || userQuery.isLoading ||
hasFirstUserQuery.isLoading || hasFirstUserQuery.isLoading ||
@@ -80,8 +80,8 @@ export const AuthProvider: FC<PropsWithChildren> = ({ children }) => {
const isConfiguringTheFirstUser = const isConfiguringTheFirstUser =
!hasFirstUserQuery.isLoading && !hasFirstUserQuery.data; !hasFirstUserQuery.isLoading && !hasFirstUserQuery.data;
const isSignedIn = userQuery.isSuccess && userQuery.data !== undefined; const isSignedIn = userQuery.isSuccess && userQuery.data !== undefined;
const isSigningIn = loginMutation.isLoading; const isSigningIn = loginMutation.isPending;
const isUpdatingProfile = updateProfileMutation.isLoading; const isUpdatingProfile = updateProfileMutation.isPending;
const signOut = useCallback(() => { const signOut = useCallback(() => {
logoutMutation.mutate(); logoutMutation.mutate();
+2 -1
View File
@@ -5,6 +5,7 @@ import {
type QueryKey, type QueryKey,
type UseQueryOptions, type UseQueryOptions,
type UseQueryResult, type UseQueryResult,
keepPreviousData,
useQuery, useQuery,
useQueryClient, useQueryClient,
} from "react-query"; } from "react-query";
@@ -140,7 +141,7 @@ export function usePaginatedQuery<
const query = useQuery<TQueryFnData, TError, TData, TQueryKey>({ const query = useQuery<TQueryFnData, TError, TData, TQueryKey>({
...extraOptions, ...extraOptions,
...getQueryOptionsFromPage(currentPage), ...getQueryOptionsFromPage(currentPage),
keepPreviousData: true, placeholderData: keepPreviousData,
}); });
const totalRecords = query.data?.count; const totalRecords = query.data?.count;
@@ -47,7 +47,7 @@ export const NotificationsInbox: FC<NotificationsInboxProps> = ({
res: ListInboxNotificationsResponse, res: ListInboxNotificationsResponse,
) => ListInboxNotificationsResponse, ) => ListInboxNotificationsResponse,
) => { ) => {
await queryClient.cancelQueries(NOTIFICATIONS_QUERY_KEY); await queryClient.cancelQueries({ queryKey: NOTIFICATIONS_QUERY_KEY });
queryClient.setQueryData<ListInboxNotificationsResponse>( queryClient.setQueryData<ListInboxNotificationsResponse>(
NOTIFICATIONS_QUERY_KEY, NOTIFICATIONS_QUERY_KEY,
(prev) => { (prev) => {
@@ -90,7 +90,7 @@ export const NotificationsInbox: FC<NotificationsInboxProps> = ({
const { const {
mutate: loadMoreNotifications, mutate: loadMoreNotifications,
isLoading: isLoadingMoreNotifications, isPending: isLoadingMoreNotifications,
} = useMutation({ } = useMutation({
mutationFn: async () => { mutationFn: async () => {
if (!inboxRes || inboxRes.notifications.length === 0) { if (!inboxRes || inboxRes.notifications.length === 0) {
+2 -2
View File
@@ -157,8 +157,8 @@ export const AgentRow: FC<AgentRowProps> = ({
select: (res) => res.containers.filter((c) => c.status === "running"), select: (res) => res.containers.filter((c) => c.status === "running"),
// TODO: Implement a websocket connection to get updates on containers // TODO: Implement a websocket connection to get updates on containers
// without having to poll. // without having to poll.
refetchInterval: (_, query) => { refetchInterval: ({ state }) => {
const { error } = query.state; const { error } = state;
return isAxiosError(error) && error.response?.status === 403 return isAxiosError(error) && error.response?.status === 403
? false ? false
: 10_000; : 10_000;
@@ -152,7 +152,7 @@ export const PortForwardPopoverView: FC<PortForwardPopoverViewProps> = ({
// share port form // share port form
const { const {
mutateAsync: upsertWorkspacePortShareForm, mutateAsync: upsertWorkspacePortShareForm,
isLoading: isSubmitting, isPending: isSubmitting,
error: submitError, error: submitError,
} = useMutation(upsertWorkspacePortShare(workspaceID)); } = useMutation(upsertWorkspacePortShare(workspaceID));
const validationSchema = getValidationSchema(); const validationSchema = getValidationSchema();
@@ -34,11 +34,10 @@ function getDuplicationUrlParams(
export function useWorkspaceDuplication(workspace?: Workspace) { export function useWorkspaceDuplication(workspace?: Workspace) {
const navigate = useNavigate(); const navigate = useNavigate();
const getLink = useLinks(); const getLink = useLinks();
const buildParametersQuery = useQuery( const buildParametersQuery = useQuery({
workspace !== undefined ...workspaceBuildParameters(workspace?.latest_build.id ?? ""),
? workspaceBuildParameters(workspace.latest_build.id) enabled: !!workspace,
: { enabled: false }, });
);
// Not using useEffectEvent for this, because useEffect isn't really an // Not using useEffectEvent for this, because useEffect isn't really an
// intended use case for this custom hook // intended use case for this custom hook
@@ -58,7 +58,7 @@ export const useWorkspaceUpdate = ({
return { return {
update, update,
isUpdating: updateWorkspaceMutation.isLoading, isUpdating: updateWorkspaceMutation.isPending,
dialogs: { dialogs: {
updateConfirmation: { updateConfirmation: {
open: isConfirmingUpdate, open: isConfirmingUpdate,
+1 -1
View File
@@ -170,7 +170,7 @@ export const ChatLayout: FC = () => {
variant="outline" variant="outline"
size="sm" size="sm"
onClick={handleNewChat} onClick={handleNewChat}
disabled={createChatMutation.isLoading} disabled={createChatMutation.isPending}
> >
<PlusIcon /> <PlusIcon />
New Chat New Chat
@@ -221,14 +221,10 @@ export const CreateTemplateForm: FC<CreateTemplateFormProps> = (props) => {
}); });
const getFieldHelpers = getFormHelpers<CreateTemplateFormData>(form, error); const getFieldHelpers = getFormHelpers<CreateTemplateFormData>(form, error);
const { data: provisioners } = useQuery( const { data: provisioners } = useQuery({
selectedOrg ...provisionerDaemons(selectedOrg?.id ?? ""),
? { enabled: showOrganizationPicker && !!selectedOrg,
...provisionerDaemons(selectedOrg.id), });
enabled: showOrganizationPicker,
}
: { enabled: false },
);
// TODO: Ideally, we would have a backend endpoint that could notify the // TODO: Ideally, we would have a backend endpoint that could notify the
// frontend that a provisioner has been connected, so that we could hide // frontend that a provisioner has been connected, so that we could hide
@@ -40,7 +40,7 @@ const CreateTemplatePage: FC = () => {
}, },
onOpenBuildLogsDrawer: () => setIsBuildLogsOpen(true), onOpenBuildLogsDrawer: () => setIsBuildLogsOpen(true),
error: createTemplateMutation.error, error: createTemplateMutation.error,
isCreating: createTemplateMutation.isLoading, isCreating: createTemplateMutation.isPending,
variablesSectionRef, variablesSectionRef,
}; };
@@ -5,6 +5,7 @@ import {
templateVersionLogs, templateVersionLogs,
templateVersionVariables, templateVersionVariables,
} from "api/queries/templates"; } from "api/queries/templates";
import type { Template, TemplateVersion } from "api/typesGenerated";
import { ErrorAlert } from "components/Alert/ErrorAlert"; import { ErrorAlert } from "components/Alert/ErrorAlert";
import { Loader } from "components/Loader/Loader"; import { Loader } from "components/Loader/Loader";
import { useDashboard } from "modules/dashboard/useDashboard"; import { useDashboard } from "modules/dashboard/useDashboard";
@@ -25,7 +26,9 @@ export const DuplicateTemplateView: FC<CreateTemplatePageViewProps> = ({
const navigate = useNavigate(); const navigate = useNavigate();
const { entitlements } = useDashboard(); const { entitlements } = useDashboard();
const [searchParams] = useSearchParams(); const [searchParams] = useSearchParams();
const templateQuery = useQuery(template(searchParams.get("fromTemplate")!)); const templateQuery = useQuery(
template(searchParams.get("fromTemplate") as string),
);
const activeVersionId = templateQuery.data?.active_version_id ?? ""; const activeVersionId = templateQuery.data?.active_version_id ?? "";
const templateVersionQuery = useQuery({ const templateVersionQuery = useQuery({
...templateVersion(activeVersionId), ...templateVersion(activeVersionId),
@@ -65,7 +68,7 @@ export const DuplicateTemplateView: FC<CreateTemplatePageViewProps> = ({
{...formPermissions} {...formPermissions}
variablesSectionRef={variablesSectionRef} variablesSectionRef={variablesSectionRef}
onOpenBuildLogsDrawer={onOpenBuildLogsDrawer} onOpenBuildLogsDrawer={onOpenBuildLogsDrawer}
copiedTemplate={templateQuery.data!} copiedTemplate={templateQuery.data as Template}
error={error} error={error}
isSubmitting={isCreating} isSubmitting={isCreating}
variables={templateVersionVariablesQuery.data} variables={templateVersionVariablesQuery.data}
@@ -74,9 +77,9 @@ export const DuplicateTemplateView: FC<CreateTemplatePageViewProps> = ({
logs={templateVersionLogsQuery.data} logs={templateVersionLogsQuery.data}
onSubmit={async (formData) => { onSubmit={async (formData) => {
await onCreateTemplate({ await onCreateTemplate({
organization: templateQuery.data!.organization_name, organization: (templateQuery.data as Template).organization_name,
version: firstVersionFromFile( version: firstVersionFromFile(
templateVersionQuery.data!.job.file_id, (templateVersionQuery.data as TemplateVersion).job.file_id,
formData.user_variable_values, formData.user_variable_values,
formData.provisioner_type, formData.provisioner_type,
formData.tags, formData.tags,
@@ -8,7 +8,7 @@ import { ErrorAlert } from "components/Alert/ErrorAlert";
import { Loader } from "components/Loader/Loader"; import { Loader } from "components/Loader/Loader";
import { useDashboard } from "modules/dashboard/useDashboard"; import { useDashboard } from "modules/dashboard/useDashboard";
import type { FC } from "react"; import type { FC } from "react";
import { useQuery } from "react-query"; import { keepPreviousData, useQuery } from "react-query";
import { useNavigate, useSearchParams } from "react-router-dom"; import { useNavigate, useSearchParams } from "react-router-dom";
import { CreateTemplateForm } from "./CreateTemplateForm"; import { CreateTemplateForm } from "./CreateTemplateForm";
import type { CreateTemplatePageViewProps } from "./types"; import type { CreateTemplatePageViewProps } from "./types";
@@ -46,7 +46,7 @@ export const ImportStarterTemplateView: FC<CreateTemplatePageViewProps> = ({
const missedVariables = useQuery({ const missedVariables = useQuery({
...templateVersionVariables(isJobError ? error.version.id : ""), ...templateVersionVariables(isJobError ? error.version.id : ""),
keepPreviousData: true, placeholderData: keepPreviousData,
enabled: enabled:
isJobError && error.job.error_code === "REQUIRED_TEMPLATE_VARIABLES", isJobError && error.job.error_code === "REQUIRED_TEMPLATE_VARIABLES",
}); });
@@ -60,7 +60,7 @@ export const UploadTemplateView: FC<CreateTemplatePageViewProps> = ({
uploadFileMutation.reset(); uploadFileMutation.reset();
} }
}, },
isUploading: uploadFileMutation.isLoading, isUploading: uploadFileMutation.isPending,
onRemove: uploadFileMutation.reset, onRemove: uploadFileMutation.reset,
file: uploadFileMutation.variables, file: uploadFileMutation.variables,
}} }}
@@ -24,11 +24,11 @@ const CreateTokenPage: FC = () => {
const { const {
mutate: saveToken, mutate: saveToken,
isLoading: isCreating, isPending: isCreating,
isError: creationFailed, isError: creationFailed,
isSuccess: creationSuccessful, isSuccess: creationSuccessful,
data: newToken, data: newToken,
} = useMutation(API.createToken); } = useMutation({ mutationFn: API.createToken });
const { const {
data: tokenConfig, data: tokenConfig,
isLoading: fetchingTokenConfig, isLoading: fetchingTokenConfig,
@@ -28,7 +28,7 @@ const CreateUserPage: FC = () => {
<CreateUserForm <CreateUserForm
error={createUserMutation.error} error={createUserMutation.error}
isLoading={createUserMutation.isLoading} isLoading={createUserMutation.isPending}
onSubmit={async (user) => { onSubmit={async (user) => {
await createUserMutation.mutateAsync({ await createUserMutation.mutateAsync({
username: user.username, username: user.username,
@@ -15,44 +15,33 @@ const CreateWorkspaceExperimentRouter: FC = () => {
const { organization: organizationName = "default", template: templateName } = const { organization: organizationName = "default", template: templateName } =
useParams() as { organization?: string; template: string }; useParams() as { organization?: string; template: string };
const templateQuery = useQuery( const templateQuery = useQuery({
dynamicParametersEnabled ...templateByName(organizationName, templateName),
? templateByName(organizationName, templateName) enabled: dynamicParametersEnabled,
: { enabled: false }, });
);
const optOutQuery = useQuery( const optOutQuery = useQuery({
templateQuery.data enabled: !!templateQuery.data,
? { queryKey: [organizationName, "template", templateQuery.data?.id, "optOut"],
queryKey: [ queryFn: () => {
organizationName, const templateId = templateQuery.data?.id;
"template", const localStorageKey = optOutKey(templateId ?? "");
templateQuery.data.id, const storedOptOutString = localStorage.getItem(localStorageKey);
"optOut",
],
queryFn: () => {
const templateId = templateQuery.data.id;
const localStorageKey = optOutKey(templateId);
const storedOptOutString = localStorage.getItem(localStorageKey);
let optOutResult: boolean; let optOutResult: boolean;
if (storedOptOutString !== null) { if (storedOptOutString !== null) {
optOutResult = storedOptOutString === "true"; optOutResult = storedOptOutString === "true";
} else { } else {
optOutResult = Boolean( optOutResult = !!templateQuery.data?.use_classic_parameter_flow;
templateQuery.data.use_classic_parameter_flow, }
);
}
return { return {
templateId: templateId, templateId: templateId,
optedOut: optOutResult, optedOut: optOutResult,
}; };
}, },
} });
: { enabled: false },
);
if (dynamicParametersEnabled) { if (dynamicParametersEnabled) {
if (optOutQuery.isLoading) { if (optOutQuery.isLoading) {
@@ -63,7 +52,7 @@ const CreateWorkspaceExperimentRouter: FC = () => {
} }
const toggleOptedOut = () => { const toggleOptedOut = () => {
const key = optOutKey(optOutQuery.data.templateId); const key = optOutKey(optOutQuery.data?.templateId ?? "");
const storedValue = localStorage.getItem(key); const storedValue = localStorage.getItem(key);
const current = storedValue const current = storedValue
@@ -9,6 +9,7 @@ import {
} from "api/queries/templates"; } from "api/queries/templates";
import { autoCreateWorkspace, createWorkspace } from "api/queries/workspaces"; import { autoCreateWorkspace, createWorkspace } from "api/queries/workspaces";
import type { import type {
Template,
TemplateVersionParameter, TemplateVersionParameter,
UserParameter, UserParameter,
Workspace, Workspace,
@@ -62,15 +63,14 @@ const CreateWorkspacePage: FC = () => {
); );
const templateVersionPresetsQuery = useQuery({ const templateVersionPresetsQuery = useQuery({
...templateVersionPresets(templateQuery.data?.active_version_id ?? ""), ...templateVersionPresets(templateQuery.data?.active_version_id ?? ""),
enabled: templateQuery.data !== undefined, enabled: !!templateQuery.data,
});
const permissionsQuery = useQuery({
...checkAuthorization({
checks: createWorkspaceChecks(templateQuery.data?.organization_id ?? ""),
}),
enabled: !!templateQuery.data,
}); });
const permissionsQuery = useQuery(
templateQuery.data
? checkAuthorization({
checks: createWorkspaceChecks(templateQuery.data.organization_id),
})
: { enabled: false },
);
const realizedVersionId = const realizedVersionId =
customVersionId ?? templateQuery.data?.active_version_id; customVersionId ?? templateQuery.data?.active_version_id;
const organizationId = templateQuery.data?.organization_id; const organizationId = templateQuery.data?.organization_id;
@@ -96,7 +96,7 @@ const CreateWorkspacePage: FC = () => {
const loadFormDataError = const loadFormDataError =
templateQuery.error ?? permissionsQuery.error ?? richParametersQuery.error; templateQuery.error ?? permissionsQuery.error ?? richParametersQuery.error;
const title = autoCreateWorkspaceMutation.isLoading const title = autoCreateWorkspaceMutation.isPending
? "Creating workspace..." ? "Creating workspace..."
: "Create workspace"; : "Create workspace";
@@ -111,7 +111,7 @@ const CreateWorkspacePage: FC = () => {
const autofillEnabled = experiments.includes("auto-fill-parameters"); const autofillEnabled = experiments.includes("auto-fill-parameters");
const userParametersQuery = useQuery({ const userParametersQuery = useQuery({
queryKey: ["userParameters"], queryKey: ["userParameters"],
queryFn: () => API.getUserParameters(templateQuery.data!.id), queryFn: () => API.getUserParameters(templateQuery.data?.id ?? ""),
enabled: autofillEnabled && templateQuery.isSuccess, enabled: autofillEnabled && templateQuery.isSuccess,
}); });
const autofillParameters = getAutofillParameters( const autofillParameters = getAutofillParameters(
@@ -203,7 +203,7 @@ const CreateWorkspacePage: FC = () => {
autoCreateWorkspaceMutation.error autoCreateWorkspaceMutation.error
} }
resetMutation={createWorkspaceMutation.reset} resetMutation={createWorkspaceMutation.reset}
template={templateQuery.data!} template={templateQuery.data as Template}
versionId={realizedVersionId} versionId={realizedVersionId}
externalAuth={externalAuth ?? []} externalAuth={externalAuth ?? []}
externalAuthPollingState={externalAuthPollingState} externalAuthPollingState={externalAuthPollingState}
@@ -212,7 +212,7 @@ const CreateWorkspacePage: FC = () => {
permissions={permissionsQuery.data as CreateWorkspacePermissions} permissions={permissionsQuery.data as CreateWorkspacePermissions}
parameters={realizedParameters as TemplateVersionParameter[]} parameters={realizedParameters as TemplateVersionParameter[]}
presets={templateVersionPresetsQuery.data ?? []} presets={templateVersionPresetsQuery.data ?? []}
creatingWorkspace={createWorkspaceMutation.isLoading} creatingWorkspace={createWorkspaceMutation.isPending}
onCancel={() => { onCancel={() => {
navigate(-1); navigate(-1);
}} }}
@@ -245,15 +245,11 @@ const useExternalAuth = (versionId: string | undefined) => {
setExternalAuthPollingState("polling"); setExternalAuthPollingState("polling");
}, []); }, []);
const { data: externalAuth, isLoading: isLoadingExternalAuth } = useQuery( const { data: externalAuth, isPending: isLoadingExternalAuth } = useQuery({
versionId ...templateVersionExternalAuth(versionId ?? ""),
? { enabled: !!versionId,
...templateVersionExternalAuth(versionId), refetchInterval: externalAuthPollingState === "polling" ? 1000 : false,
refetchInterval: });
externalAuthPollingState === "polling" ? 1000 : false,
}
: { enabled: false },
);
const allSignedIn = externalAuth?.every((it) => it.authenticated); const allSignedIn = externalAuth?.every((it) => it.authenticated);
@@ -73,18 +73,16 @@ const CreateWorkspacePageExperimental: FC = () => {
const templateQuery = useQuery( const templateQuery = useQuery(
templateByName(organizationName, templateName), templateByName(organizationName, templateName),
); );
const templateVersionPresetsQuery = useQuery( const templateVersionPresetsQuery = useQuery({
templateQuery.data ...templateVersionPresets(templateQuery.data?.active_version_id ?? ""),
? templateVersionPresets(templateQuery.data.active_version_id) enabled: !!templateQuery.data,
: { enabled: false }, });
); const permissionsQuery = useQuery({
const permissionsQuery = useQuery( ...checkAuthorization({
templateQuery.data checks: createWorkspaceChecks(templateQuery.data?.organization_id ?? ""),
? checkAuthorization({ }),
checks: createWorkspaceChecks(templateQuery.data.organization_id), enabled: !!templateQuery.data,
}) });
: { enabled: false },
);
const realizedVersionId = const realizedVersionId =
customVersionId ?? templateQuery.data?.active_version_id; customVersionId ?? templateQuery.data?.active_version_id;
@@ -192,7 +190,7 @@ const CreateWorkspacePageExperimental: FC = () => {
permissionsQuery.isLoading; permissionsQuery.isLoading;
const loadFormDataError = templateQuery.error ?? permissionsQuery.error; const loadFormDataError = templateQuery.error ?? permissionsQuery.error;
const title = autoCreateWorkspaceMutation.isLoading const title = autoCreateWorkspaceMutation.isPending
? "Creating workspace..." ? "Creating workspace..."
: "Create workspace"; : "Create workspace";
@@ -308,7 +306,7 @@ const CreateWorkspacePageExperimental: FC = () => {
permissions={permissionsQuery.data as CreateWorkspacePermissions} permissions={permissionsQuery.data as CreateWorkspacePermissions}
parameters={sortedParams} parameters={sortedParams}
presets={templateVersionPresetsQuery.data ?? []} presets={templateVersionPresetsQuery.data ?? []}
creatingWorkspace={createWorkspaceMutation.isLoading} creatingWorkspace={createWorkspaceMutation.isPending}
sendMessage={sendMessage} sendMessage={sendMessage}
onCancel={() => { onCancel={() => {
navigate(-1); navigate(-1);
@@ -344,15 +342,11 @@ const useExternalAuth = (versionId: string | undefined) => {
setExternalAuthPollingState("polling"); setExternalAuthPollingState("polling");
}, []); }, []);
const { data: externalAuth, isLoading: isLoadingExternalAuth } = useQuery( const { data: externalAuth, isLoading: isLoadingExternalAuth } = useQuery({
versionId ...templateVersionExternalAuth(versionId ?? ""),
? { enabled: !!versionId,
...templateVersionExternalAuth(versionId), refetchInterval: externalAuthPollingState === "polling" ? 1000 : false,
refetchInterval: });
externalAuthPollingState === "polling" ? 1000 : false,
}
: { enabled: false },
);
const allSignedIn = externalAuth?.every((it) => it.authenticated); const allSignedIn = externalAuth?.every((it) => it.authenticated);
@@ -27,7 +27,7 @@ const AppearanceSettingsPage: FC = () => {
try { try {
await updateAppearanceMutation.mutateAsync(newAppearance); await updateAppearanceMutation.mutateAsync(newAppearance);
await queryClient.invalidateQueries(appearanceConfigKey); await queryClient.invalidateQueries({ queryKey: appearanceConfigKey });
displaySuccess("Successfully updated appearance settings!"); displaySuccess("Successfully updated appearance settings!");
} catch (error) { } catch (error) {
displayError( displayError(
@@ -36,9 +36,10 @@ const IdpOrgSyncPage: FC = () => {
setField(settingsQuery.data.field); setField(settingsQuery.data.field);
}, [settingsQuery.data]); }, [settingsQuery.data]);
const fieldValuesQuery = useQuery( const fieldValuesQuery = useQuery({
field ? deploymentIdpSyncFieldValues(field) : { enabled: false }, ...deploymentIdpSyncFieldValues(field),
); enabled: !!field,
});
const patchOrganizationSyncSettingsMutation = useMutation( const patchOrganizationSyncSettingsMutation = useMutation(
patchOrganizationSyncSettings(queryClient), patchOrganizationSyncSettings(queryClient),
@@ -12,9 +12,10 @@ const AddNewLicensePage: FC = () => {
const { const {
mutate: saveLicenseKeyApi, mutate: saveLicenseKeyApi,
isLoading: isCreating, isPending: isCreating,
error: savingLicenseError, error: savingLicenseError,
} = useMutation(API.createLicense, { } = useMutation({
mutationFn: API.createLicense,
onSuccess: () => { onSuccess: () => {
displaySuccess("You have successfully added a license"); displaySuccess("You have successfully added a license");
navigate("/deployment/licenses?success=true"); navigate("/deployment/licenses?success=true");
@@ -37,11 +37,12 @@ const LicensesSettingsPage: FC = () => {
} }
}, [entitlementsQuery.error]); }, [entitlementsQuery.error]);
const { mutate: removeLicenseApi, isLoading: isRemovingLicense } = const { mutate: removeLicenseApi, isPending: isRemovingLicense } =
useMutation(API.removeLicense, { useMutation({
mutationFn: API.removeLicense,
onSuccess: () => { onSuccess: () => {
displaySuccess("Successfully removed license"); displaySuccess("Successfully removed license");
void queryClient.invalidateQueries(["licenses"]); void queryClient.invalidateQueries({ queryKey: ["licenses"] });
}, },
onError: () => { onError: () => {
displayError("Failed to remove license"); displayError("Failed to remove license");
@@ -77,7 +78,7 @@ const LicensesSettingsPage: FC = () => {
<LicensesSettingsPageView <LicensesSettingsPageView
showConfetti={confettiOn} showConfetti={confettiOn}
isLoading={isLoading} isLoading={isLoading}
isRefreshing={refreshEntitlementsMutation.isLoading} isRefreshing={refreshEntitlementsMutation.isPending}
userLimitActual={entitlementsQuery.data?.features.user_limit.actual} userLimitActual={entitlementsQuery.data?.features.user_limit.actual}
userLimitLimit={entitlementsQuery.data?.features.user_limit.limit} userLimitLimit={entitlementsQuery.data?.features.user_limit.limit}
licenses={licenses} licenses={licenses}
@@ -7,13 +7,11 @@ import type { FC } from "react";
import { useMutation } from "react-query"; import { useMutation } from "react-query";
export const Troubleshooting: FC = () => { export const Troubleshooting: FC = () => {
const { mutate: sendTestNotificationApi, isLoading } = useMutation( const { mutate: sendTestNotificationApi, isPending } = useMutation({
API.postTestNotification, mutationFn: API.postTestNotification,
{ onSuccess: () => displaySuccess("Test notification sent"),
onSuccess: () => displaySuccess("Test notification sent"), onError: () => displayError("Failed to send test notification"),
onError: () => displayError("Failed to send test notification"), });
},
);
const theme = useTheme(); const theme = useTheme();
return ( return (
@@ -33,12 +31,12 @@ export const Troubleshooting: FC = () => {
<Button <Button
variant="outline" variant="outline"
size="sm" size="sm"
disabled={isLoading} disabled={isPending}
onClick={() => { onClick={() => {
sendTestNotificationApi(); sendTestNotificationApi();
}} }}
> >
<Spinner loading={isLoading} /> <Spinner loading={isPending} />
Send notification Send notification
</Button> </Button>
</span> </span>
@@ -19,7 +19,7 @@ const CreateOAuth2AppPage: FC = () => {
</Helmet> </Helmet>
<CreateOAuth2AppPageView <CreateOAuth2AppPageView
isUpdating={postAppMutation.isLoading} isUpdating={postAppMutation.isPending}
error={postAppMutation.error} error={postAppMutation.error}
createApp={async (req) => { createApp={async (req) => {
try { try {
@@ -39,10 +39,10 @@ const EditOAuth2AppPage: FC = () => {
isLoadingApp={appQuery.isLoading} isLoadingApp={appQuery.isLoading}
isLoadingSecrets={secretsQuery.isLoading} isLoadingSecrets={secretsQuery.isLoading}
mutatingResource={{ mutatingResource={{
updateApp: putAppMutation.isLoading, updateApp: putAppMutation.isPending,
deleteApp: deleteAppMutation.isLoading, deleteApp: deleteAppMutation.isPending,
createSecret: postSecretMutation.isLoading, createSecret: postSecretMutation.isPending,
deleteSecret: deleteSecretMutation.isLoading, deleteSecret: deleteSecretMutation.isPending,
}} }}
fullNewSecret={fullNewSecret} fullNewSecret={fullNewSecret}
ackFullNewSecret={() => setFullNewSecret(undefined)} ackFullNewSecret={() => setFullNewSecret(undefined)}
@@ -29,7 +29,7 @@ const CreateGroupPage: FC = () => {
); );
}} }}
error={createGroupMutation.error} error={createGroupMutation.error}
isLoading={createGroupMutation.isLoading} isLoading={createGroupMutation.isPending}
/> />
</> </>
); );
+17 -14
View File
@@ -1,5 +1,6 @@
import type { Meta, StoryObj } from "@storybook/react"; import type { Meta, StoryObj } from "@storybook/react";
import { userEvent, within } from "@storybook/test"; import { spyOn, userEvent, within } from "@storybook/test";
import { API } from "api/api";
import { getGroupQueryKey, groupPermissionsKey } from "api/queries/groups"; import { getGroupQueryKey, groupPermissionsKey } from "api/queries/groups";
import { organizationMembersKey } from "api/queries/organizations"; import { organizationMembersKey } from "api/queries/organizations";
import { reactRouterParameters } from "storybook-addon-remix-react-router"; import { reactRouterParameters } from "storybook-addon-remix-react-router";
@@ -52,11 +53,9 @@ export const LoadingGroup: Story = {
}; };
export const GroupError: Story = { export const GroupError: Story = {
parameters: { beforeEach: () => {
queries: [ spyOn(API, "getGroup").mockRejectedValue(new Error("test group error"));
{ ...groupQuery(new Error("test group error")), isError: true }, spyOn(API, "checkAuthorization").mockResolvedValue({});
permissionsQuery({}),
],
}, },
}; };
@@ -89,16 +88,19 @@ export const EveryoneGroup: Story = {
}; };
export const MembersError: Story = { export const MembersError: Story = {
parameters: { beforeEach() {
queries: [ spyOn(API, "getGroup").mockResolvedValue(MockGroup);
groupQuery(MockGroup), spyOn(API, "checkAuthorization").mockResolvedValue({
permissionsQuery({ canUpdateGroup: true }), canUpdateGroup: true,
{ ...membersQuery(new Error("test members error")), isError: true }, });
], spyOn(API, "getOrganizationPaginatedMembers").mockRejectedValue(
new Error("test members error"),
);
}, },
play: async ({ canvasElement }) => { play: async ({ canvasElement }) => {
const canvas = within(canvasElement); const canvas = within(canvasElement);
await userEvent.click(canvas.getByRole("button", { name: "Open" })); const combobox = await canvas.findByRole("combobox");
await userEvent.click(combobox);
}, },
}; };
@@ -115,7 +117,8 @@ export const NoMembers: Story = {
}, },
play: async ({ canvasElement }) => { play: async ({ canvasElement }) => {
const canvas = within(canvasElement); const canvas = within(canvasElement);
await userEvent.click(canvas.getByRole("button", { name: "Open" })); const combobox = await canvas.findByRole("combobox");
await userEvent.click(combobox);
}, },
}; };
+6 -5
View File
@@ -67,9 +67,10 @@ const GroupPage: FC = () => {
const navigate = useNavigate(); const navigate = useNavigate();
const groupQuery = useQuery(group(organization, groupName)); const groupQuery = useQuery(group(organization, groupName));
const groupData = groupQuery.data; const groupData = groupQuery.data;
const { data: permissions } = useQuery( const { data: permissions } = useQuery({
groupData ? groupPermissions(groupData.id) : { enabled: false }, ...groupPermissions(groupData?.id ?? ""),
); enabled: !!groupData,
});
const addMemberMutation = useMutation(addMember(queryClient)); const addMemberMutation = useMutation(addMember(queryClient));
const removeMemberMutation = useMutation(removeMember(queryClient)); const removeMemberMutation = useMutation(removeMember(queryClient));
const deleteGroupMutation = useMutation(deleteGroup(queryClient)); const deleteGroupMutation = useMutation(deleteGroup(queryClient));
@@ -145,7 +146,7 @@ const GroupPage: FC = () => {
<Stack spacing={1}> <Stack spacing={1}>
{canUpdateGroup && groupData && !isEveryoneGroup(groupData) && ( {canUpdateGroup && groupData && !isEveryoneGroup(groupData) && (
<AddGroupMember <AddGroupMember
isLoading={addMemberMutation.isLoading} isLoading={addMemberMutation.isPending}
organizationId={groupData.organization_id} organizationId={groupData.organization_id}
onSubmit={async (member, reset) => { onSubmit={async (member, reset) => {
try { try {
@@ -220,7 +221,7 @@ const GroupPage: FC = () => {
{groupQuery.data && ( {groupQuery.data && (
<DeleteDialog <DeleteDialog
isOpen={isDeletingGroup} isOpen={isDeletingGroup}
confirmLoading={deleteGroupMutation.isLoading} confirmLoading={deleteGroupMutation.isPending}
name={groupQuery.data.name} name={groupQuery.data.name}
entity="group" entity="group"
onConfirm={async () => { onConfirm={async () => {
@@ -66,7 +66,7 @@ const GroupSettingsPage: FC = () => {
group={groupQuery.data} group={groupQuery.data}
formErrors={groupQuery.error} formErrors={groupQuery.error}
isLoading={groupQuery.isLoading} isLoading={groupQuery.isLoading}
isUpdating={patchGroupMutation.isLoading} isUpdating={patchGroupMutation.isPending}
/> />
</> </>
); );
+8 -8
View File
@@ -25,14 +25,14 @@ import { GroupsPageView } from "./GroupsPageView";
const GroupsPage: FC = () => { const GroupsPage: FC = () => {
const { template_rbac: groupsEnabled } = useFeatureVisibility(); const { template_rbac: groupsEnabled } = useFeatureVisibility();
const { organization, showOrganizations } = useGroupsSettings(); const { organization, showOrganizations } = useGroupsSettings();
const groupsQuery = useQuery( const groupsQuery = useQuery({
organization ? groupsByOrganization(organization.name) : { enabled: false }, ...groupsByOrganization(organization?.name ?? ""),
); enabled: !!organization,
const permissionsQuery = useQuery( });
organization const permissionsQuery = useQuery({
? organizationsPermissions([organization.id]) ...organizationsPermissions([organization?.id ?? ""]),
: { enabled: false }, enabled: !!organization,
); });
useEffect(() => { useEffect(() => {
if (groupsQuery.error) { if (groupsQuery.error) {
@@ -35,7 +35,7 @@ export const DismissWarningButton = (props: { healthcheck: HealthSection }) => {
if (isDismissed) { if (isDismissed) {
return ( return (
<Button <Button
disabled={healthSettingsQuery.isLoading || enableMutation.isLoading} disabled={healthSettingsQuery.isLoading || enableMutation.isPending}
variant="outline" variant="outline"
onClick={async () => { onClick={async () => {
const updatedSettings = dismissed_healthchecks.filter( const updatedSettings = dismissed_healthchecks.filter(
@@ -48,7 +48,7 @@ export const DismissWarningButton = (props: { healthcheck: HealthSection }) => {
displaySuccess("Warnings enabled successfully!"); displaySuccess("Warnings enabled successfully!");
}} }}
> >
<Spinner loading={enableMutation.isLoading}> <Spinner loading={enableMutation.isPending}>
<NotificationsOffOutlined /> <NotificationsOffOutlined />
</Spinner> </Spinner>
Enable warnings Enable warnings
@@ -58,7 +58,7 @@ export const DismissWarningButton = (props: { healthcheck: HealthSection }) => {
return ( return (
<Button <Button
disabled={healthSettingsQuery.isLoading || dismissMutation.isLoading} disabled={healthSettingsQuery.isLoading || dismissMutation.isPending}
variant="outline" variant="outline"
onClick={async () => { onClick={async () => {
const updatedSettings = [...dismissed_healthchecks, props.healthcheck]; const updatedSettings = [...dismissed_healthchecks, props.healthcheck];
@@ -68,7 +68,7 @@ export const DismissWarningButton = (props: { healthcheck: HealthSection }) => {
displaySuccess("Warnings dismissed successfully!"); displaySuccess("Warnings dismissed successfully!");
}} }}
> >
<Spinner loading={dismissMutation.isLoading}> <Spinner loading={dismissMutation.isPending}>
<NotificationOutlined /> <NotificationOutlined />
</Spinner> </Spinner>
Dismiss warnings Dismiss warnings
+1 -1
View File
@@ -31,7 +31,7 @@ export const HealthLayout: FC = () => {
...health(), ...health(),
refetchInterval: 30_000, refetchInterval: 30_000,
}); });
const { mutate: forceRefresh, isLoading: isRefreshing } = useMutation( const { mutate: forceRefresh, isPending: isRefreshing } = useMutation(
refreshHealth(queryClient), refreshHealth(queryClient),
); );
const sections = { const sections = {
@@ -84,8 +84,8 @@ const CreateEditRolePage: FC = () => {
} }
isLoading={ isLoading={
role role
? updateOrganizationRoleMutation.isLoading ? updateOrganizationRoleMutation.isPending
: createOrganizationRoleMutation.isLoading : createOrganizationRoleMutation.isPending
} }
organizationName={organizationName} organizationName={organizationName}
/> />
@@ -96,7 +96,7 @@ const CustomRolesPage: FC = () => {
<DeleteDialog <DeleteDialog
key={roleToDelete?.name} key={roleToDelete?.name}
isOpen={roleToDelete !== undefined} isOpen={roleToDelete !== undefined}
confirmLoading={deleteRoleMutation.isLoading} confirmLoading={deleteRoleMutation.isPending}
name={roleToDelete?.name ?? ""} name={roleToDelete?.name ?? ""}
entity="role" entity="role"
onCancel={() => setRoleToDelete(undefined)} onCancel={() => setRoleToDelete(undefined)}
@@ -70,11 +70,10 @@ const IdpSyncPage: FC = () => {
const tab = searchParams.get("tab") || "groups"; const tab = searchParams.get("tab") || "groups";
const field = tab === "groups" ? groupField : roleField; const field = tab === "groups" ? groupField : roleField;
const fieldValuesQuery = useQuery( const fieldValuesQuery = useQuery({
field ...organizationIdpSyncClaimFieldValues(organizationName, field),
? organizationIdpSyncClaimFieldValues(organizationName, field) enabled: !!field,
: { enabled: false }, });
);
if (!organization) { if (!organization) {
return <EmptyState message="Organization not found" />; return <EmptyState message="Organization not found" />;
@@ -98,8 +98,8 @@ const OrganizationMembersPage: FC = () => {
removeMemberMutation.error ?? removeMemberMutation.error ??
updateMemberRolesMutation.error updateMemberRolesMutation.error
} }
isAddingMember={addMemberMutation.isLoading} isAddingMember={addMemberMutation.isPending}
isUpdatingMemberRoles={updateMemberRolesMutation.isLoading} isUpdatingMemberRoles={updateMemberRolesMutation.isPending}
me={me} me={me}
members={members} members={members}
membersQuery={membersQuery} membersQuery={membersQuery}
@@ -51,7 +51,7 @@ interface OrganizationMembersPageViewProps {
me: User; me: User;
members: Array<OrganizationMemberTableEntry> | undefined; members: Array<OrganizationMemberTableEntry> | undefined;
membersQuery: PaginationResultInfo & { membersQuery: PaginationResultInfo & {
isPreviousData: boolean; isPlaceholderData: boolean;
}; };
addMember: (user: User) => Promise<void>; addMember: (user: User) => Promise<void>;
removeMember: (member: OrganizationMemberWithUserData) => void; removeMember: (member: OrganizationMemberWithUserData) => void;
@@ -27,12 +27,12 @@ export const CancelJobConfirmationDialog: FC<
const cancelMutation = useMutation({ const cancelMutation = useMutation({
mutationFn: cancelProvisionerJob, mutationFn: cancelProvisionerJob,
onSuccess: () => { onSuccess: () => {
queryClient.invalidateQueries( queryClient.invalidateQueries({
provisionerJobsQueryKey(job.organization_id), queryKey: provisionerJobsQueryKey(job.organization_id),
); });
queryClient.invalidateQueries( queryClient.invalidateQueries({
getProvisionerDaemonsKey(job.organization_id, job.tags), queryKey: getProvisionerDaemonsKey(job.organization_id, job.tags),
); });
}, },
}); });
@@ -44,7 +44,7 @@ export const CancelJobConfirmationDialog: FC<
description={`Are you sure you want to cancel the provisioner job "${job.id}"? This operation will result in the associated workspaces not getting created.`} description={`Are you sure you want to cancel the provisioner job "${job.id}"? This operation will result in the associated workspaces not getting created.`}
confirmText="Confirm" confirmText="Confirm"
cancelText="Discard" cancelText="Discard"
confirmLoading={cancelMutation.isLoading} confirmLoading={cancelMutation.isPending}
onConfirm={async () => { onConfirm={async () => {
try { try {
await cancelMutation.mutateAsync(job); await cancelMutation.mutateAsync(job);
@@ -31,7 +31,7 @@ const RequestOTPPage: FC = () => {
) : ( ) : (
<RequestOTP <RequestOTP
error={requestOTPMutation.error} error={requestOTPMutation.error}
isRequesting={requestOTPMutation.isLoading} isRequesting={requestOTPMutation.isPending}
onRequest={(email) => { onRequest={(email) => {
requestOTPMutation.mutate({ email }); requestOTPMutation.mutate({ email });
}} }}
+1 -1
View File
@@ -56,7 +56,7 @@ export const SetupPage: FC = () => {
</Helmet> </Helmet>
<SetupPageView <SetupPageView
authMethods={authMethodsQuery.data} authMethods={authMethodsQuery.data}
isLoading={isSigningIn || createFirstUserMutation.isLoading} isLoading={isSigningIn || createFirstUserMutation.isPending}
error={createFirstUserMutation.error} error={createFirstUserMutation.error}
onSubmit={async (firstUser) => { onSubmit={async (firstUser) => {
await createFirstUserMutation.mutateAsync(firstUser); await createFirstUserMutation.mutateAsync(firstUser);
+14 -11
View File
@@ -84,20 +84,23 @@ export const TemplateLayout: FC<PropsWithChildren> = ({
queryKey: ["template", templateName], queryKey: ["template", templateName],
queryFn: () => fetchTemplate(organizationName, templateName), queryFn: () => fetchTemplate(organizationName, templateName),
}); });
const workspacePermissionsQuery = useQuery( const workspacePermissionsQuery = useQuery({
data ...checkAuthorization({
? checkAuthorization({ checks: workspacePermissionChecks(
checks: workspacePermissionChecks( data?.template.organization_id ?? "",
data.template.organization_id, me.id,
me.id, ),
), }),
}) enabled: !!data,
: { enabled: false }, });
);
const location = useLocation(); const location = useLocation();
const paths = location.pathname.split("/"); const paths = location.pathname.split("/");
const activeTab = paths.at(-1) === templateName ? "summary" : paths.at(-1)!; const templateNamePath = paths.at(-1);
const activeTab =
templateNamePath === templateName
? "summary"
: (templateNamePath as string);
// Auditors should also be able to view insights, but do not automatically // Auditors should also be able to view insights, but do not automatically
// have permission to update templates. Need both checks. // have permission to update templates. Need both checks.
const shouldShowInsights = const shouldShowInsights =
@@ -19,7 +19,7 @@ const TemplateVersionsPage = () => {
const [latestActiveVersion, setLatestActiveVersion] = useState( const [latestActiveVersion, setLatestActiveVersion] = useState(
template.active_version_id, template.active_version_id,
); );
const { mutate: promoteVersion, isLoading: isPromoting } = useMutation({ const { mutate: promoteVersion, isPending: isPromoting } = useMutation({
mutationFn: (templateVersionId: string) => { mutationFn: (templateVersionId: string) => {
return API.updateActiveTemplateVersion(template.id, { return API.updateActiveTemplateVersion(template.id, {
id: templateVersionId, id: templateVersionId,
@@ -35,7 +35,7 @@ const TemplateVersionsPage = () => {
}, },
}); });
const { mutate: archiveVersion, isLoading: isArchiving } = useMutation({ const { mutate: archiveVersion, isPending: isArchiving } = useMutation({
mutationFn: (templateVersionId: string) => { mutationFn: (templateVersionId: string) => {
return API.archiveTemplateVersion(templateVersionId); return API.archiveTemplateVersion(templateVersionId);
}, },
@@ -28,35 +28,33 @@ const TemplateSettingsPage: FC = () => {
const { const {
mutate: updateTemplate, mutate: updateTemplate,
isLoading: isSubmitting, isPending: isSubmitting,
error: submitError, error: submitError,
} = useMutation( } = useMutation({
(data: UpdateTemplateMeta) => { mutationFn: (data: UpdateTemplateMeta) => {
return API.updateTemplateMeta(template.id, data); return API.updateTemplateMeta(template.id, data);
}, },
{ onSuccess: async (data) => {
onSuccess: async (data) => { // This update has a chance to return a 304 which means nothing was updated.
// This update has a chance to return a 304 which means nothing was updated. // In this case, the return payload will be empty and we should use the
// In this case, the return payload will be empty and we should use the // original template data.
// original template data. if (!data) {
if (!data) { data = template;
data = template; } else {
} else { // Only invalid the query if data is returned, indicating at least one field was updated.
// Only invalid the query if data is returned, indicating at least one field was updated. //
// // we use data.name because an admin may have updated templateName to something new
// we use data.name because an admin may have updated templateName to something new await queryClient.invalidateQueries({
await queryClient.invalidateQueries( queryKey: templateByNameKey(template.organization_name, data.name),
templateByNameKey(template.organization_name, data.name), });
); }
} displaySuccess("Template updated successfully");
displaySuccess("Template updated successfully"); navigate(getLink(linkToTemplate(data.organization_name, data.name)));
navigate(getLink(linkToTemplate(data.organization_name, data.name)));
},
onError: (error) => {
displayError(getErrorMessage(error, "Failed to update template"));
},
}, },
); onError: (error) => {
displayError(getErrorMessage(error, "Failed to update template"));
},
});
return ( return (
<> <>
@@ -48,7 +48,7 @@ const TemplatePermissionsPage: FC = () => {
}); });
reset(); reset();
}} }}
isAddingUser={addUserMutation.isLoading} isAddingUser={addUserMutation.isPending}
onUpdateUser={async (user, role) => { onUpdateUser={async (user, role) => {
await updateUserMutation.mutateAsync({ await updateUserMutation.mutateAsync({
templateId: template.id, templateId: template.id,
@@ -58,7 +58,7 @@ const TemplatePermissionsPage: FC = () => {
displaySuccess("User role updated successfully!"); displaySuccess("User role updated successfully!");
}} }}
updatingUserId={ updatingUserId={
updateUserMutation.isLoading updateUserMutation.isPending
? updateUserMutation.variables?.userId ? updateUserMutation.variables?.userId
: undefined : undefined
} }
@@ -78,7 +78,7 @@ const TemplatePermissionsPage: FC = () => {
}); });
reset(); reset();
}} }}
isAddingGroup={addGroupMutation.isLoading} isAddingGroup={addGroupMutation.isPending}
onUpdateGroup={async (group, role) => { onUpdateGroup={async (group, role) => {
await updateGroupMutation.mutateAsync({ await updateGroupMutation.mutateAsync({
templateId: template.id, templateId: template.id,
@@ -88,7 +88,7 @@ const TemplatePermissionsPage: FC = () => {
displaySuccess("Group role updated successfully!"); displaySuccess("Group role updated successfully!");
}} }}
updatingGroupId={ updatingGroupId={
updateGroupMutation.isLoading updateGroupMutation.isPending
? updateGroupMutation.variables?.groupId ? updateGroupMutation.variables?.groupId
: undefined : undefined
} }
@@ -7,7 +7,7 @@ import type { Group, ReducedUser } from "api/typesGenerated";
import { AvatarData } from "components/Avatar/AvatarData"; import { AvatarData } from "components/Avatar/AvatarData";
import { useDebouncedFunction } from "hooks/debounce"; import { useDebouncedFunction } from "hooks/debounce";
import { type ChangeEvent, type FC, useState } from "react"; import { type ChangeEvent, type FC, useState } from "react";
import { useQuery } from "react-query"; import { keepPreviousData, useQuery } from "react-query";
import { prepareQuery } from "utils/filters"; import { prepareQuery } from "utils/filters";
import { getGroupSubtitle } from "utils/groups"; import { getGroupSubtitle } from "utils/groups";
@@ -36,7 +36,7 @@ export const UserOrGroupAutocomplete: FC<UserOrGroupAutocompleteProps> = ({
limit: 25, limit: 25,
}), }),
enabled: autoComplete.open, enabled: autoComplete.open,
keepPreviousData: true, placeholderData: keepPreviousData,
}); });
const options = aclAvailableQuery.data const options = aclAvailableQuery.data
? [ ? [
@@ -25,22 +25,21 @@ const TemplateSchedulePage: FC = () => {
const { const {
mutate: updateTemplate, mutate: updateTemplate,
isLoading: isSubmitting, isPending: isSubmitting,
error: submitError, error: submitError,
} = useMutation( } = useMutation({
(data: UpdateTemplateMeta) => API.updateTemplateMeta(template.id, data), mutationFn: (data: UpdateTemplateMeta) =>
{ API.updateTemplateMeta(template.id, data),
onSuccess: async () => { onSuccess: async () => {
await queryClient.invalidateQueries( await queryClient.invalidateQueries({
templateByNameKey(organizationName, templateName), queryKey: templateByNameKey(organizationName, templateName),
); });
displaySuccess("Template updated successfully"); displaySuccess("Template updated successfully");
// clear browser storage of workspaces impending deletion // clear browser storage of workspaces impending deletion
localStorage.removeItem("dismissedWorkspaceList"); // workspaces page localStorage.removeItem("dismissedWorkspaceList"); // workspaces page
localStorage.removeItem("dismissedWorkspace"); // workspace page localStorage.removeItem("dismissedWorkspace"); // workspace page
},
}, },
); });
return ( return (
<> <>
@@ -8,7 +8,7 @@ const queryClient = new QueryClient({
defaultOptions: { defaultOptions: {
queries: { queries: {
retry: false, retry: false,
cacheTime: 0, gcTime: 0,
refetchOnWindowFocus: false, refetchOnWindowFocus: false,
networkMode: "offlineFirst", networkMode: "offlineFirst",
}, },
@@ -46,7 +46,7 @@ export const TemplateSettingsLayout: FC = () => {
enabled: templateQuery.isSuccess, enabled: templateQuery.isSuccess,
}); });
if (templateQuery.isLoading || permissionsQuery.isLoading) { if (!(templateQuery.data && permissionsQuery.data)) {
return <Loader />; return <Loader />;
} }
@@ -15,7 +15,12 @@ import { Loader } from "components/Loader/Loader";
import { linkToTemplate, useLinks } from "modules/navigation"; import { linkToTemplate, useLinks } from "modules/navigation";
import { type FC, useCallback } from "react"; import { type FC, useCallback } from "react";
import { Helmet } from "react-helmet-async"; import { Helmet } from "react-helmet-async";
import { useMutation, useQuery, useQueryClient } from "react-query"; import {
keepPreviousData,
useMutation,
useQuery,
useQueryClient,
} from "react-query";
import { useNavigate, useParams } from "react-router-dom"; import { useNavigate, useParams } from "react-router-dom";
import { pageTitle } from "utils/page"; import { pageTitle } from "utils/page";
import { useTemplateSettings } from "../TemplateSettingsLayout"; import { useTemplateSettings } from "../TemplateSettingsLayout";
@@ -36,25 +41,28 @@ const TemplateVariablesPage: FC = () => {
data: version, data: version,
error: versionError, error: versionError,
isLoading: isVersionLoading, isLoading: isVersionLoading,
} = useQuery({ ...templateVersion(versionId), keepPreviousData: true }); } = useQuery({
...templateVersion(versionId),
placeholderData: keepPreviousData,
});
const { const {
data: variables, data: variables,
error: variablesError, error: variablesError,
isLoading: isVariablesLoading, isLoading: isVariablesLoading,
} = useQuery({ } = useQuery({
...templateVersionVariables(versionId), ...templateVersionVariables(versionId),
keepPreviousData: true, placeholderData: keepPreviousData,
}); });
const { const {
mutateAsync: sendCreateAndBuildTemplateVersion, mutateAsync: sendCreateAndBuildTemplateVersion,
error: buildError, error: buildError,
isLoading: isBuilding, isPending: isBuilding,
} = useMutation(createAndBuildTemplateVersion(organization)); } = useMutation(createAndBuildTemplateVersion(organization));
const { const {
mutateAsync: sendUpdateActiveTemplateVersion, mutateAsync: sendUpdateActiveTemplateVersion,
error: publishError, error: publishError,
isLoading: isPublishing, isPending: isPublishing,
} = useMutation(updateActiveTemplateVersion(template, queryClient)); } = useMutation(updateActiveTemplateVersion(template, queryClient));
const publishVersion = useCallback( const publishVersion = useCallback(
@@ -18,7 +18,12 @@ import { linkToTemplate, useLinks } from "modules/navigation";
import { useWatchVersionLogs } from "modules/templates/useWatchVersionLogs"; import { useWatchVersionLogs } from "modules/templates/useWatchVersionLogs";
import { type FC, useEffect, useState } from "react"; import { type FC, useEffect, useState } from "react";
import { Helmet } from "react-helmet-async"; import { Helmet } from "react-helmet-async";
import { useMutation, useQuery, useQueryClient } from "react-query"; import {
keepPreviousData,
useMutation,
useQuery,
useQueryClient,
} from "react-query";
import { useNavigate, useParams, useSearchParams } from "react-router-dom"; import { useNavigate, useParams, useSearchParams } from "react-router-dom";
import { type FileTree, existsFile, traverse } from "utils/filetree"; import { type FileTree, existsFile, traverse } from "utils/filetree";
import { pageTitle } from "utils/page"; import { pageTitle } from "utils/page";
@@ -50,9 +55,9 @@ const TemplateVersionEditorPage: FC = () => {
); );
const activeTemplateVersionQuery = useQuery({ const activeTemplateVersionQuery = useQuery({
...templateVersionOptions, ...templateVersionOptions,
keepPreviousData: true, placeholderData: keepPreviousData,
refetchInterval(data) { refetchInterval({ state }) {
return data?.job.status === "pending" ? 1_000 : false; return state.data?.job.status === "pending" ? 1_000 : false;
}, },
}); });
const { data: activeTemplateVersion } = activeTemplateVersionQuery; const { data: activeTemplateVersion } = activeTemplateVersionQuery;
@@ -79,9 +84,9 @@ const TemplateVersionEditorPage: FC = () => {
const publishVersionMutation = useMutation({ const publishVersionMutation = useMutation({
mutationFn: publishVersion, mutationFn: publishVersion,
onSuccess: async () => { onSuccess: async () => {
await queryClient.invalidateQueries( await queryClient.invalidateQueries({
templateByNameKey(organizationName, templateName), queryKey: templateByNameKey(organizationName, templateName),
); });
}, },
}); });
const [lastSuccessfulPublishedVersion, setLastSuccessfulPublishedVersion] = const [lastSuccessfulPublishedVersion, setLastSuccessfulPublishedVersion] =
@@ -183,7 +188,7 @@ const TemplateVersionEditorPage: FC = () => {
navigateToVersion(publishedVersion); navigateToVersion(publishedVersion);
}} }}
isAskingPublishParameters={isPublishingDialogOpen} isAskingPublishParameters={isPublishingDialogOpen}
isPublishing={publishVersionMutation.isLoading} isPublishing={publishVersionMutation.isPending}
publishingError={publishVersionMutation.error} publishingError={publishVersionMutation.error}
publishedVersion={lastSuccessfulPublishedVersion} publishedVersion={lastSuccessfulPublishedVersion}
onCreateWorkspace={() => { onCreateWorkspace={() => {
@@ -199,8 +204,8 @@ const TemplateVersionEditorPage: FC = () => {
); );
}} }}
isBuilding={ isBuilding={
createTemplateVersionMutation.isLoading || createTemplateVersionMutation.isPending ||
uploadFileMutation.isLoading || uploadFileMutation.isPending ||
activeTemplateVersion.job.status === "running" || activeTemplateVersion.job.status === "running" ||
activeTemplateVersion.job.status === "pending" activeTemplateVersion.job.status === "pending"
} }
@@ -345,9 +350,9 @@ const publishVersion = async (options: {
publishActions.push(API.patchTemplateVersion(version.id, data)); publishActions.push(API.patchTemplateVersion(version.id, data));
} }
if (isActiveVersion) { if (version.template_id && isActiveVersion) {
publishActions.push( publishActions.push(
API.updateActiveTemplateVersion(version.template_id!, { API.updateActiveTemplateVersion(version.template_id, {
id: version.id, id: version.id,
}), }),
); );
@@ -29,7 +29,7 @@ const AppearancePage: FC = () => {
return ( return (
<> <>
<AppearanceForm <AppearanceForm
isUpdating={updateAppearanceSettingsMutation.isLoading} isUpdating={updateAppearanceSettingsMutation.isPending}
error={updateAppearanceSettingsMutation.error} error={updateAppearanceSettingsMutation.error}
initialValues={{ initialValues={{
theme_preference: appearanceSettingsQuery.data.theme_preference, theme_preference: appearanceSettingsQuery.data.theme_preference,
@@ -58,7 +58,7 @@ const ExternalAuthPage: FC = () => {
do so on the oauth2 provider's side." do so on the oauth2 provider's side."
label="Name of the application to unlink" label="Name of the application to unlink"
isOpen={appToUnlink !== undefined} isOpen={appToUnlink !== undefined}
confirmLoading={unlinkAppMutation.isLoading} confirmLoading={unlinkAppMutation.isPending}
name={appToUnlink ?? ""} name={appToUnlink ?? ""}
entity="application" entity="application"
onCancel={() => setAppToUnlink(undefined)} onCancel={() => setAppToUnlink(undefined)}
@@ -35,7 +35,7 @@ const OAuth2ProviderPage: FC = () => {
info={`This will invalidate any tokens created by the OAuth2 application "${appToRevoke.name}".`} info={`This will invalidate any tokens created by the OAuth2 application "${appToRevoke.name}".`}
label="Name of the application to revoke" label="Name of the application to revoke"
isOpen isOpen
confirmLoading={revokeAppMutation.isLoading} confirmLoading={revokeAppMutation.isPending}
name={appToRevoke.name} name={appToRevoke.name}
entity="application" entity="application"
onCancel={() => setAppIdToRevoke(undefined)} onCancel={() => setAppIdToRevoke(undefined)}
@@ -43,7 +43,7 @@ const SSHKeysPage: FC = () => {
type="delete" type="delete"
hideCancel={false} hideCancel={false}
open={isConfirmingRegeneration} open={isConfirmingRegeneration}
confirmLoading={regenerateSSHKeyMutation.isLoading} confirmLoading={regenerateSSHKeyMutation.isPending}
title={Language.regenerateDialogTitle} title={Language.regenerateDialogTitle}
description={Language.regenerateDialogMessage} description={Language.regenerateDialogMessage}
confirmText={Language.confirmLabel} confirmText={Language.confirmLabel}
@@ -2,6 +2,7 @@ import {
updateUserQuietHoursSchedule, updateUserQuietHoursSchedule,
userQuietHoursSchedule, userQuietHoursSchedule,
} from "api/queries/settings"; } from "api/queries/settings";
import type { UserQuietHoursScheduleResponse } from "api/typesGenerated";
import { ErrorAlert } from "components/Alert/ErrorAlert"; import { ErrorAlert } from "components/Alert/ErrorAlert";
import { displaySuccess } from "components/GlobalSnackbar/utils"; import { displaySuccess } from "components/GlobalSnackbar/utils";
import { Loader } from "components/Loader/Loader"; import { Loader } from "components/Loader/Loader";
@@ -25,7 +26,7 @@ const SchedulePage: FC = () => {
const { const {
mutate: onSubmit, mutate: onSubmit,
error: submitError, error: submitError,
isLoading: mutationLoading, isPending: mutationLoading,
} = useMutation(updateUserQuietHoursSchedule(me.id, queryClient)); } = useMutation(updateUserQuietHoursSchedule(me.id, queryClient));
if (isLoading) { if (isLoading) {
@@ -44,7 +45,7 @@ const SchedulePage: FC = () => {
> >
<ScheduleForm <ScheduleForm
isLoading={mutationLoading} isLoading={mutationLoading}
initialValues={quietHoursSchedule} initialValues={quietHoursSchedule as UserQuietHoursScheduleResponse}
submitError={submitError} submitError={submitError}
onSubmit={(values) => { onSubmit={(values) => {
onSubmit(values, { onSubmit(values, {
@@ -33,7 +33,7 @@ const SecurityPage: FC = () => {
form: { form: {
disabled: userLoginType.login_type !== "password", disabled: userLoginType.login_type !== "password",
error: updatePasswordMutation.error, error: updatePasswordMutation.error,
isLoading: updatePasswordMutation.isLoading, isLoading: updatePasswordMutation.isPending,
onSubmit: async (data) => { onSubmit: async (data) => {
await updatePasswordMutation.mutateAsync({ await updatePasswordMutation.mutateAsync({
userId: me.id, userId: me.id,
@@ -25,7 +25,7 @@ const defaultArgs: ComponentProps<typeof SecurityPageView> = {
authMethods: MockAuthMethodsPasswordOnly, authMethods: MockAuthMethodsPasswordOnly,
closeConfirmation: action("closeConfirmation"), closeConfirmation: action("closeConfirmation"),
confirm: action("confirm"), confirm: action("confirm"),
error: undefined, error: null,
isConfirming: false, isConfirming: false,
isUpdating: false, isUpdating: false,
openConfirmation: action("openConfirmation"), openConfirmation: action("openConfirmation"),
@@ -52,7 +52,8 @@ export const useSingleSignOnSection = () => {
const [loginTypeConfirmation, setLoginTypeConfirmation] = const [loginTypeConfirmation, setLoginTypeConfirmation] =
useState<LoginTypeConfirmation>({ open: false, selectedType: undefined }); useState<LoginTypeConfirmation>({ open: false, selectedType: undefined });
const mutation = useMutation(API.convertToOAUTH, { const mutation = useMutation({
mutationFn: API.convertToOAUTH,
onSuccess: (data) => { onSuccess: (data) => {
const loginTypeMsg = const loginTypeMsg =
data.to_type === "github" ? "Github" : "OpenID Connect"; data.to_type === "github" ? "Github" : "OpenID Connect";
@@ -93,7 +94,7 @@ export const useSingleSignOnSection = () => {
confirm, confirm,
// We still want to show it loading when it is success so the modal does not // We still want to show it loading when it is success so the modal does not
// change until the redirect // change until the redirect
isUpdating: mutation.isLoading || mutation.isSuccess, isUpdating: mutation.isPending || mutation.isSuccess,
isConfirming: loginTypeConfirmation.open, isConfirming: loginTypeConfirmation.open,
error: mutation.error, error: mutation.error,
}; };
@@ -7,7 +7,7 @@ const queryClient = new QueryClient({
defaultOptions: { defaultOptions: {
queries: { queries: {
retry: false, retry: false,
cacheTime: 0, gcTime: 0,
refetchOnWindowFocus: false, refetchOnWindowFocus: false,
}, },
}, },
@@ -18,7 +18,7 @@ export const ConfirmDeleteDialog: FC<ConfirmDeleteDialogProps> = ({
}) => { }) => {
const tokenName = token?.token_name; const tokenName = token?.token_name;
const { mutate: deleteToken, isLoading: isDeleting } = const { mutate: deleteToken, isPending: isDeleting } =
useDeleteToken(queryKey); useDeleteToken(queryKey);
const onDeleteSuccess = () => { const onDeleteSuccess = () => {
@@ -29,7 +29,9 @@ export const useDeleteToken = (queryKey: QueryKey) => {
mutationFn: API.deleteToken, mutationFn: API.deleteToken,
onSuccess: () => { onSuccess: () => {
// Invalidate and refetch // Invalidate and refetch
void queryClient.invalidateQueries(queryKey); void queryClient.invalidateQueries({
queryKey,
});
}, },
}); });
}; };
+5 -5
View File
@@ -143,7 +143,7 @@ const UsersPage: FC<UserPageProps> = ({ defaultNewPassword }) => {
); );
} }
}} }}
isUpdatingUserRoles={updateRolesMutation.isLoading} isUpdatingUserRoles={updateRolesMutation.isPending}
isLoading={isLoading} isLoading={isLoading}
canEditUsers={canEditUsers} canEditUsers={canEditUsers}
canViewActivity={entitlements.features.audit_log.enabled} canViewActivity={entitlements.features.audit_log.enabled}
@@ -161,7 +161,7 @@ const UsersPage: FC<UserPageProps> = ({ defaultNewPassword }) => {
<DeleteDialog <DeleteDialog
key={userToDelete?.username} key={userToDelete?.username}
isOpen={userToDelete !== undefined} isOpen={userToDelete !== undefined}
confirmLoading={deleteUserMutation.isLoading} confirmLoading={deleteUserMutation.isPending}
name={userToDelete?.username ?? ""} name={userToDelete?.username ?? ""}
entity="user" entity="user"
onCancel={() => setUserToDelete(undefined)} onCancel={() => setUserToDelete(undefined)}
@@ -183,7 +183,7 @@ const UsersPage: FC<UserPageProps> = ({ defaultNewPassword }) => {
type="delete" type="delete"
hideCancel={false} hideCancel={false}
open={userToSuspend !== undefined} open={userToSuspend !== undefined}
confirmLoading={suspendUserMutation.isLoading} confirmLoading={suspendUserMutation.isPending}
title="Suspend user" title="Suspend user"
confirmText="Suspend" confirmText="Suspend"
onClose={() => setUserToSuspend(undefined)} onClose={() => setUserToSuspend(undefined)}
@@ -211,7 +211,7 @@ const UsersPage: FC<UserPageProps> = ({ defaultNewPassword }) => {
type="success" type="success"
hideCancel={false} hideCancel={false}
open={userToActivate !== undefined} open={userToActivate !== undefined}
confirmLoading={activateUserMutation.isLoading} confirmLoading={activateUserMutation.isPending}
title="Activate user" title="Activate user"
confirmText="Activate" confirmText="Activate"
onClose={() => setUserToActivate(undefined)} onClose={() => setUserToActivate(undefined)}
@@ -238,7 +238,7 @@ const UsersPage: FC<UserPageProps> = ({ defaultNewPassword }) => {
<ResetPasswordDialog <ResetPasswordDialog
key={confirmResetPassword?.user.username} key={confirmResetPassword?.user.username}
open={confirmResetPassword !== undefined} open={confirmResetPassword !== undefined}
loading={updatePasswordMutation.isLoading} loading={updatePasswordMutation.isPending}
user={confirmResetPassword?.user} user={confirmResetPassword?.user}
newPassword={confirmResetPassword?.newPassword} newPassword={confirmResetPassword?.newPassword}
onClose={() => { onClose={() => {
@@ -4,7 +4,7 @@ import dayjs from "dayjs";
import { useWorkspaceBuildLogs } from "hooks/useWorkspaceBuildLogs"; import { useWorkspaceBuildLogs } from "hooks/useWorkspaceBuildLogs";
import type { FC } from "react"; import type { FC } from "react";
import { Helmet } from "react-helmet-async"; import { Helmet } from "react-helmet-async";
import { useQuery } from "react-query"; import { keepPreviousData, useQuery } from "react-query";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import { pageTitle } from "utils/page"; import { pageTitle } from "utils/page";
import { WorkspaceBuildPageView } from "./WorkspaceBuildPageView"; import { WorkspaceBuildPageView } from "./WorkspaceBuildPageView";
@@ -20,7 +20,7 @@ const WorkspaceBuildPage: FC = () => {
const username = params.username.replace("@", ""); const username = params.username.replace("@", "");
const wsBuildQuery = useQuery({ const wsBuildQuery = useQuery({
...workspaceBuildByNumber(username, workspaceName, buildNumber), ...workspaceBuildByNumber(username, workspaceName, buildNumber),
keepPreviousData: true, placeholderData: keepPreviousData,
}); });
const build = wsBuildQuery.data; const build = wsBuildQuery.data;
const buildsQuery = useQuery({ const buildsQuery = useQuery({
@@ -36,11 +36,10 @@ const WorkspacePage: FC = () => {
const workspace = workspaceQuery.data; const workspace = workspaceQuery.data;
// Template // Template
const templateQuery = useQuery( const templateQuery = useQuery({
workspace ...templateQueryOptions(workspace?.template_id ?? ""),
? templateQueryOptions(workspace.template_id) enabled: !!workspace,
: { enabled: false }, });
);
const template = templateQuery.data; const template = templateQuery.data;
// Permissions // Permissions
@@ -67,9 +66,9 @@ const WorkspacePage: FC = () => {
newWorkspaceData.latest_build.status !== workspace.latest_build.status; newWorkspaceData.latest_build.status !== workspace.latest_build.status;
if (hasNewBuild || lastBuildHasChanged) { if (hasNewBuild || lastBuildHasChanged) {
await queryClient.invalidateQueries( await queryClient.invalidateQueries({
workspaceBuildsKey(newWorkspaceData.id), queryKey: workspaceBuildsKey(newWorkspaceData.id),
); });
} }
}, },
); );
@@ -60,7 +60,7 @@ export const WorkspaceReadyPage: FC<WorkspaceReadyPageProps> = ({
open: boolean; open: boolean;
buildParameters?: TypesGen.WorkspaceBuildParameter[]; buildParameters?: TypesGen.WorkspaceBuildParameter[];
}>({ open: false }); }>({ open: false });
const { mutate: mutateRestartWorkspace, isLoading: isRestarting } = const { mutate: mutateRestartWorkspace, isPending: isRestarting } =
useMutation({ useMutation({
mutationFn: API.restartWorkspace, mutationFn: API.restartWorkspace,
}); });
@@ -133,7 +133,8 @@ export const WorkspaceReadyPage: FC<WorkspaceReadyPageProps> = ({
// timings. To refetch the timings, I found the best way was to compare the // timings. To refetch the timings, I found the best way was to compare the
// expected amount of script timings that run on start, with the current // expected amount of script timings that run on start, with the current
// amount of script timings returned in the response. // amount of script timings returned in the response.
refetchInterval: (data) => { refetchInterval: ({ state }) => {
const { data } = state;
const expectedScriptTimingsCount = workspace.latest_build.resources const expectedScriptTimingsCount = workspace.latest_build.resources
.flatMap((r) => r.agents) .flatMap((r) => r.agents)
.flatMap((a) => a?.scripts ?? []) .flatMap((a) => a?.scripts ?? [])
@@ -13,41 +13,36 @@ const WorkspaceParametersExperimentRouter: FC = () => {
const workspace = useWorkspaceSettings(); const workspace = useWorkspaceSettings();
const dynamicParametersEnabled = experiments.includes("dynamic-parameters"); const dynamicParametersEnabled = experiments.includes("dynamic-parameters");
const optOutQuery = useQuery( const optOutQuery = useQuery({
dynamicParametersEnabled enabled: dynamicParametersEnabled,
? { queryKey: [
queryKey: [ "workspace",
"workspace", workspace.id,
workspace.id, "template_id",
"template_id", workspace.template_id,
workspace.template_id, "optOut",
"optOut", ],
], queryFn: () => {
queryFn: () => { const templateId = workspace.template_id;
const templateId = workspace.template_id; const workspaceId = workspace.id;
const workspaceId = workspace.id; const localStorageKey = optOutKey(templateId);
const localStorageKey = optOutKey(templateId); const storedOptOutString = localStorage.getItem(localStorageKey);
const storedOptOutString = localStorage.getItem(localStorageKey);
let optOutResult: boolean; let optOutResult: boolean;
if (storedOptOutString !== null) { if (storedOptOutString !== null) {
optOutResult = storedOptOutString === "true"; optOutResult = storedOptOutString === "true";
} else { } else {
optOutResult = Boolean( optOutResult = Boolean(workspace.template_use_classic_parameter_flow);
workspace.template_use_classic_parameter_flow, }
);
}
return { return {
templateId, templateId,
workspaceId, workspaceId,
optedOut: optOutResult, optedOut: optOutResult,
}; };
}, },
} });
: { enabled: false },
);
if (dynamicParametersEnabled) { if (dynamicParametersEnabled) {
if (optOutQuery.isLoading) { if (optOutQuery.isLoading) {
@@ -63,7 +63,7 @@ const WorkspaceParametersPage: FC = () => {
canChangeVersions={canChangeVersions} canChangeVersions={canChangeVersions}
data={parameters.data} data={parameters.data}
submitError={updateParameters.error} submitError={updateParameters.error}
isSubmitting={updateParameters.isLoading} isSubmitting={updateParameters.isPending}
onSubmit={(values) => { onSubmit={(values) => {
if (!parameters.data) { if (!parameters.data) {
return; return;
@@ -222,7 +222,7 @@ const WorkspaceParametersPageExperimental: FC = () => {
canChangeVersions={canChangeVersions} canChangeVersions={canChangeVersions}
parameters={sortedParams} parameters={sortedParams}
diagnostics={latestResponse.diagnostics} diagnostics={latestResponse.diagnostics}
isSubmitting={updateParameters.isLoading} isSubmitting={updateParameters.isPending}
onSubmit={handleSubmit} onSubmit={handleSubmit}
onCancel={() => onCancel={() =>
navigate(`/@${workspace.owner_name}/${workspace.name}`) navigate(`/@${workspace.owner_name}/${workspace.name}`)
@@ -55,12 +55,12 @@ const WorkspaceSchedulePage: FC = () => {
const submitScheduleMutation = useMutation({ const submitScheduleMutation = useMutation({
mutationFn: submitSchedule, mutationFn: submitSchedule,
onSuccess: async () => { onSuccess: async () => {
await queryClient.invalidateQueries( await queryClient.invalidateQueries({
workspaceByOwnerAndNameKey( queryKey: workspaceByOwnerAndNameKey(
params.username.replace(/^@/, ""), params.username.replace(/^@/, ""),
params.workspace, params.workspace,
), ),
); });
displaySuccess("Workspace schedule updated"); displaySuccess("Workspace schedule updated");
}, },
onError: () => displayError("Failed to update workspace schedule"), onError: () => displayError("Failed to update workspace schedule"),
@@ -102,7 +102,7 @@ const WorkspaceSchedulePage: FC = () => {
...getAutostart(workspace), ...getAutostart(workspace),
...getAutostop(workspace), ...getAutostop(workspace),
}} }}
isLoading={submitScheduleMutation.isLoading} isLoading={submitScheduleMutation.isPending}
defaultTTL={dayjs.duration(template.default_ttl_ms, "ms").asHours()} defaultTTL={dayjs.duration(template.default_ttl_ms, "ms").asHours()}
onCancel={() => { onCancel={() => {
navigate(`/@${username}/${workspaceName}`); navigate(`/@${username}/${workspaceName}`);
@@ -53,14 +53,16 @@ export const WorkspaceSettingsLayout: FC = () => {
{isError ? ( {isError ? (
<ErrorAlert error={error} /> <ErrorAlert error={error} />
) : ( ) : (
<WorkspaceSettings.Provider value={workspace}> workspace && (
<Sidebar workspace={workspace} username={username} /> <WorkspaceSettings.Provider value={workspace}>
<Suspense fallback={<Loader />}> <Sidebar workspace={workspace} username={username} />
<main css={{ width: "100%" }}> <Suspense fallback={<Loader />}>
<Outlet /> <main css={{ width: "100%" }}>
</main> <Outlet />
</Suspense> </main>
</WorkspaceSettings.Provider> </Suspense>
</WorkspaceSettings.Provider>
)
)} )}
</Stack> </Stack>
</Margins> </Margins>
@@ -84,7 +84,7 @@ export const WorkspacesButton: FC<WorkspacesButtonProps> = ({
paddingBottom: "8px", paddingBottom: "8px",
}} }}
> >
{templatesFetchStatus === "loading" ? ( {templatesFetchStatus === "pending" ? (
<Loader size="sm" /> <Loader size="sm" />
) : ( ) : (
<> <>
@@ -77,8 +77,8 @@ const WorkspacesPage: FC = () => {
}); });
const { data, error, refetch } = useQuery({ const { data, error, refetch } = useQuery({
...workspacesQueryOptions, ...workspacesQueryOptions,
refetchInterval: (_, query) => { refetchInterval: ({ state }) => {
return query.state.error ? false : 5_000; return state.error ? false : 5_000;
}, },
}); });
@@ -489,9 +489,9 @@ const WorkspaceActionsCell: FC<WorkspaceActionsCellProps> = ({
}); });
const isRetrying = const isRetrying =
startWorkspaceMutation.isLoading || startWorkspaceMutation.isPending ||
stopWorkspaceMutation.isLoading || stopWorkspaceMutation.isPending ||
deleteWorkspaceMutation.isLoading; deleteWorkspaceMutation.isPending;
const retry = () => { const retry = () => {
switch (workspace.latest_build.transition) { switch (workspace.latest_build.transition) {
@@ -525,7 +525,7 @@ const WorkspaceActionsCell: FC<WorkspaceActionsCellProps> = ({
{abilities.actions.includes("start") && ( {abilities.actions.includes("start") && (
<PrimaryAction <PrimaryAction
onClick={() => startWorkspaceMutation.mutate({})} onClick={() => startWorkspaceMutation.mutate({})}
isLoading={startWorkspaceMutation.isLoading} isLoading={startWorkspaceMutation.isPending}
label="Start workspace" label="Start workspace"
> >
<PlayIcon /> <PlayIcon />
@@ -548,7 +548,7 @@ const WorkspaceActionsCell: FC<WorkspaceActionsCellProps> = ({
{abilities.canCancel && ( {abilities.canCancel && (
<PrimaryAction <PrimaryAction
onClick={cancelBuildMutation.mutate} onClick={cancelBuildMutation.mutate}
isLoading={cancelBuildMutation.isLoading} isLoading={cancelBuildMutation.isPending}
label="Cancel build" label="Cancel build"
> >
<BanIcon /> <BanIcon />
@@ -94,10 +94,10 @@ export function useBatchActions(options: UseBatchActionsProps) {
deleteAll: deleteAllMutation.mutateAsync, deleteAll: deleteAllMutation.mutateAsync,
updateAll: updateAllMutation.mutateAsync, updateAll: updateAllMutation.mutateAsync,
isLoading: isLoading:
favoriteAllMutation.isLoading || favoriteAllMutation.isPending ||
unfavoriteAllMutation.isLoading || unfavoriteAllMutation.isPending ||
startAllMutation.isLoading || startAllMutation.isPending ||
stopAllMutation.isLoading || stopAllMutation.isPending ||
deleteAllMutation.isLoading, deleteAllMutation.isPending,
}; };
} }
+3 -11
View File
@@ -22,22 +22,14 @@ import {
import themes, { DEFAULT_THEME } from "theme"; import themes, { DEFAULT_THEME } from "theme";
import { MockUserOwner } from "./entities"; import { MockUserOwner } from "./entities";
// Creates one query client for each test case, to make sure that tests are
// isolated and can't affect each other
export function createTestQueryClient() { export function createTestQueryClient() {
// Helps create one query client for each test case, to make sure that tests
// are isolated and can't affect each other
return new QueryClient({ return new QueryClient({
logger: {
...console,
// Some tests are designed to throw errors as part of their functionality.
// To avoid unnecessary noise from these expected errors, the code is
// structured to suppress them. If this suppression becomes problematic,
// the code can be refactored to handle query errors on a per-test basis.
error: () => {},
},
defaultOptions: { defaultOptions: {
queries: { queries: {
retry: false, retry: false,
cacheTime: 0, gcTime: 0,
refetchOnWindowFocus: false, refetchOnWindowFocus: false,
networkMode: "offlineFirst", networkMode: "offlineFirst",
}, },