mirror of https://github.com/coder/coder.git
chore(site): remove xstate (#10659)
This commit is contained in:
parent
ef70165a8a
commit
90b6e86555
|
@ -101,10 +101,6 @@ updates:
|
|||
xterm:
|
||||
patterns:
|
||||
- "xterm*"
|
||||
xstate:
|
||||
patterns:
|
||||
- "xstate"
|
||||
- "@xstate*"
|
||||
mui:
|
||||
patterns:
|
||||
- "@mui*"
|
||||
|
|
|
@ -20,7 +20,6 @@ yarn-error.log
|
|||
|
||||
# Front-end ignore patterns.
|
||||
.next/
|
||||
site/**/*.typegen.ts
|
||||
site/build-storybook.log
|
||||
site/coverage/
|
||||
site/storybook-static/
|
||||
|
|
|
@ -23,7 +23,6 @@ yarn-error.log
|
|||
|
||||
# Front-end ignore patterns.
|
||||
.next/
|
||||
site/**/*.typegen.ts
|
||||
site/build-storybook.log
|
||||
site/coverage/
|
||||
site/storybook-static/
|
||||
|
|
|
@ -170,7 +170,6 @@
|
|||
"wsconncache",
|
||||
"wsjson",
|
||||
"xerrors",
|
||||
"xstate",
|
||||
"yamux"
|
||||
],
|
||||
"cSpell.ignorePaths": ["site/package.json", ".vscode/settings.json"],
|
||||
|
|
|
@ -34,7 +34,6 @@ important ones:
|
|||
- [react-router](https://reactrouter.com/en/main) for routing
|
||||
- [TanStack Query v4](https://tanstack.com/query/v4/docs/react/overview) for
|
||||
fetching data
|
||||
- [XState](https://xstate.js.org/docs/) for handling complex state flows
|
||||
- [axios](https://github.com/axios/axios) as fetching lib
|
||||
- [Playwright](https://playwright.dev/) for end-to-end (E2E) testing
|
||||
- [Jest](https://jestjs.io/) for integration testing
|
||||
|
@ -96,13 +95,7 @@ a `*.stories.ts` file.
|
|||
|
||||
We use
|
||||
[TanStack Query v4](https://tanstack.com/query/v4/docs/react/overview)(previously
|
||||
known as react-query) to fetch data from the API. We also use
|
||||
[XState](https://xstate.js.org/docs/) to handle complex flows with multiple
|
||||
states and transitions.
|
||||
|
||||
> ℹ️ We recently changed how we are going to fetch data from the server so you
|
||||
> will see a lot of fetches being made using XState machines but feel free to
|
||||
> refactor it if you are already touching those files.
|
||||
known as react-query) to fetch data from the API.
|
||||
|
||||
### Where to fetch data
|
||||
|
||||
|
|
|
@ -23,7 +23,6 @@ yarn-error.log
|
|||
|
||||
# Front-end ignore patterns.
|
||||
.next/
|
||||
**/*.typegen.ts
|
||||
build-storybook.log
|
||||
coverage/
|
||||
storybook-static/
|
||||
|
|
|
@ -23,7 +23,6 @@ yarn-error.log
|
|||
|
||||
# Front-end ignore patterns.
|
||||
.next/
|
||||
**/*.typegen.ts
|
||||
build-storybook.log
|
||||
coverage/
|
||||
storybook-static/
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
"private": true,
|
||||
"license": "AGPL-3.0",
|
||||
"scripts": {
|
||||
"postinstall": "pnpm typegen",
|
||||
"build": "NODE_ENV=production pnpm vite build",
|
||||
"check:all": "pnpm format:check && pnpm lint && pnpm test",
|
||||
"chromatic": "chromatic",
|
||||
|
@ -13,7 +12,7 @@
|
|||
"format:check": "prettier --cache --check '../**/*.{css,html,js,json,jsx,md,ts,tsx,yaml,yml}'",
|
||||
"format:write": "prettier --cache --write '../**/*.{css,html,js,json,jsx,md,ts,tsx,yaml,yml}'",
|
||||
"format:write:only": "prettier --cache --write",
|
||||
"lint": "pnpm typegen && pnpm run lint:types && pnpm exec jest --selectProjects lint",
|
||||
"lint": "pnpm run lint:types && pnpm exec jest --selectProjects lint",
|
||||
"lint:fix": "FIX=true pnpm lint",
|
||||
"lint:types": "tsc --noEmit",
|
||||
"playwright:install": "playwright install --with-deps chromium",
|
||||
|
@ -25,9 +24,8 @@
|
|||
"test:ci": "jest --selectProjects test --silent",
|
||||
"test:coverage": "jest --selectProjects test --collectCoverage",
|
||||
"test:watch": "jest --selectProjects test --watch",
|
||||
"typegen": "xstate typegen 'src/**/*.ts'",
|
||||
"stats": "STATS=true pnpm build && npx http-server ./stats -p 8081 -c-1",
|
||||
"deadcode": "ts-prune | grep -v \".stories\\|.typegen\\|.config\\|e2e\\|__mocks__\\|used in module\\|testHelpers\\|typesGenerated\" || echo \"No deadcode found.\""
|
||||
"deadcode": "ts-prune | grep -v \".stories\\|.config\\|e2e\\|__mocks__\\|used in module\\|testHelpers\\|typesGenerated\" || echo \"No deadcode found.\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@emoji-mart/data": "1.1.2",
|
||||
|
@ -46,8 +44,6 @@
|
|||
"@mui/system": "5.14.0",
|
||||
"@mui/utils": "5.14.11",
|
||||
"@vitejs/plugin-react": "4.1.0",
|
||||
"@xstate/inspect": "0.8.0",
|
||||
"@xstate/react": "3.2.1",
|
||||
"ansi-to-html": "0.7.2",
|
||||
"axios": "1.6.0",
|
||||
"canvas": "2.11.0",
|
||||
|
@ -94,7 +90,6 @@
|
|||
"unique-names-generator": "4.7.1",
|
||||
"uuid": "9.0.0",
|
||||
"vite": "4.5.0",
|
||||
"xstate": "4.38.1",
|
||||
"xterm": "5.2.0",
|
||||
"xterm-addon-canvas": "0.5.0",
|
||||
"xterm-addon-fit": "0.8.0",
|
||||
|
@ -138,7 +133,6 @@
|
|||
"@types/uuid": "9.0.2",
|
||||
"@typescript-eslint/eslint-plugin": "6.9.1",
|
||||
"@typescript-eslint/parser": "6.9.1",
|
||||
"@xstate/cli": "0.5.2",
|
||||
"chromatic": "7.6.0",
|
||||
"eslint": "8.52.0",
|
||||
"eslint-config-prettier": "9.0.0",
|
||||
|
|
|
@ -57,12 +57,6 @@ dependencies:
|
|||
'@vitejs/plugin-react':
|
||||
specifier: 4.1.0
|
||||
version: 4.1.0(vite@4.5.0)
|
||||
'@xstate/inspect':
|
||||
specifier: 0.8.0
|
||||
version: 0.8.0(ws@8.14.2)(xstate@4.38.1)
|
||||
'@xstate/react':
|
||||
specifier: 3.2.1
|
||||
version: 3.2.1(@types/react@18.2.6)(react@18.2.0)(xstate@4.38.1)
|
||||
ansi-to-html:
|
||||
specifier: 0.7.2
|
||||
version: 0.7.2
|
||||
|
@ -201,9 +195,6 @@ dependencies:
|
|||
vite:
|
||||
specifier: 4.5.0
|
||||
version: 4.5.0(@types/node@18.18.1)
|
||||
xstate:
|
||||
specifier: 4.38.1
|
||||
version: 4.38.1
|
||||
xterm:
|
||||
specifier: 5.2.0
|
||||
version: 5.2.0
|
||||
|
@ -329,9 +320,6 @@ devDependencies:
|
|||
'@typescript-eslint/parser':
|
||||
specifier: 6.9.1
|
||||
version: 6.9.1(eslint@8.52.0)(typescript@5.2.2)
|
||||
'@xstate/cli':
|
||||
specifier: 0.5.2
|
||||
version: 0.5.2
|
||||
chromatic:
|
||||
specifier: 7.6.0
|
||||
version: 7.6.0
|
||||
|
@ -474,29 +462,6 @@ packages:
|
|||
resolution: {integrity: sha512-0S9TQMmDHlqAZ2ITT95irXKfxN9bncq8ZCoJhun3nHL/lLUxd2NKBJYoNGWH7S0hz6fRQwWlAWn/ILM0C70KZQ==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
/@babel/core@7.22.9:
|
||||
resolution: {integrity: sha512-G2EgeufBcYw27U4hhoIwFcgc1XU7TlXJ3mv04oOv1WCuo900U/anZSPzEqNjwdjgffkk2Gs0AN0dW1CKVLcG7w==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
dependencies:
|
||||
'@ampproject/remapping': 2.2.1
|
||||
'@babel/code-frame': 7.22.13
|
||||
'@babel/generator': 7.23.0
|
||||
'@babel/helper-compilation-targets': 7.22.15
|
||||
'@babel/helper-module-transforms': 7.23.0(@babel/core@7.22.9)
|
||||
'@babel/helpers': 7.23.2
|
||||
'@babel/parser': 7.23.0
|
||||
'@babel/template': 7.22.15
|
||||
'@babel/traverse': 7.23.2
|
||||
'@babel/types': 7.23.0
|
||||
convert-source-map: 1.9.0
|
||||
debug: 4.3.4
|
||||
gensync: 1.0.0-beta.2
|
||||
json5: 2.2.3
|
||||
semver: 7.5.3
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
/@babel/core@7.23.0:
|
||||
resolution: {integrity: sha512-97z/ju/Jy1rZmDxybphrBuI+jtJjFVoz7Mr9yUQVVVi+DNZE333uFQeMOqcCIy1x3WYBIbWftUSLmbNXNT7qFQ==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
@ -668,20 +633,6 @@ packages:
|
|||
dependencies:
|
||||
'@babel/types': 7.23.0
|
||||
|
||||
/@babel/helper-module-transforms@7.23.0(@babel/core@7.22.9):
|
||||
resolution: {integrity: sha512-WhDWw1tdrlT0gMgUJSlX0IQvoO1eN279zrAUbVB+KpV2c3Tylz8+GnKOLllCS6Z/iZQEyVYxhZVUdPTqs2YYPw==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
peerDependencies:
|
||||
'@babel/core': ^7.0.0
|
||||
dependencies:
|
||||
'@babel/core': 7.22.9
|
||||
'@babel/helper-environment-visitor': 7.22.20
|
||||
'@babel/helper-module-imports': 7.22.15
|
||||
'@babel/helper-simple-access': 7.22.5
|
||||
'@babel/helper-split-export-declaration': 7.22.6
|
||||
'@babel/helper-validator-identifier': 7.22.20
|
||||
dev: true
|
||||
|
||||
/@babel/helper-module-transforms@7.23.0(@babel/core@7.23.0):
|
||||
resolution: {integrity: sha512-WhDWw1tdrlT0gMgUJSlX0IQvoO1eN279zrAUbVB+KpV2c3Tylz8+GnKOLllCS6Z/iZQEyVYxhZVUdPTqs2YYPw==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
@ -5799,81 +5750,6 @@ packages:
|
|||
resolution: {integrity: sha512-N8tkAACJx2ww8vFMneJmaAgmjAG1tnVBZJRLRcx061tmsLRZHSEZSLuGWnwPtunsSLvSqXQ2wfp7Mgqg1I+2dQ==}
|
||||
dev: false
|
||||
|
||||
/@xstate/cli@0.5.2:
|
||||
resolution: {integrity: sha512-KA0BJMd80Z3lp1MmVqlUpHkjLkKcq4Z09P19It4iJ6IX8Hzwo5lmRTZwX938UCiAjWWSA2jl6nfrJnfBR21riA==}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
'@babel/core': 7.22.9
|
||||
'@xstate/machine-extractor': 0.10.0(xstate@4.38.2)
|
||||
'@xstate/tools-shared': 3.0.1(xstate@4.38.2)
|
||||
chokidar: 3.5.3
|
||||
commander: 8.3.0
|
||||
prettier: 2.8.8
|
||||
xstate: 4.38.2
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
/@xstate/inspect@0.8.0(ws@8.14.2)(xstate@4.38.1):
|
||||
resolution: {integrity: sha512-wSkFeOnp+7dhn+zTThO0M4D2FEqZN9lGIWowJu5JLa2ojjtlzRwK8SkjcHZ4rLX8VnMev7kGjgQLrGs8kxy+hw==}
|
||||
peerDependencies:
|
||||
'@types/ws': ^8.0.0
|
||||
ws: ^8.0.0
|
||||
xstate: ^4.37.0
|
||||
peerDependenciesMeta:
|
||||
'@types/ws':
|
||||
optional: true
|
||||
dependencies:
|
||||
fast-safe-stringify: 2.1.1
|
||||
ws: 8.14.2
|
||||
xstate: 4.38.1
|
||||
dev: false
|
||||
|
||||
/@xstate/machine-extractor@0.10.0(xstate@4.38.2):
|
||||
resolution: {integrity: sha512-jsnYU9Y0DfFQCisY0IGxjmFrU6y3aqdBXBNohasxHiKHfuItkk4AUAQ+07MykJ+RiczXIYqYQNu5DvZAfCqKCA==}
|
||||
peerDependencies:
|
||||
xstate: ^4
|
||||
dependencies:
|
||||
'@babel/parser': 7.23.0
|
||||
'@babel/traverse': 7.23.2
|
||||
'@babel/types': 7.23.0
|
||||
recast: 0.23.4
|
||||
xstate: 4.38.2
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
/@xstate/react@3.2.1(@types/react@18.2.6)(react@18.2.0)(xstate@4.38.1):
|
||||
resolution: {integrity: sha512-L/mqYRxyBWVdIdSaXBHacfvS8NKn3sTKbPb31aRADbE9spsJ1p+tXil0GVQHPlzrmjGeozquLrxuYGiXsFNU7g==}
|
||||
peerDependencies:
|
||||
'@xstate/fsm': ^2.0.0
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
xstate: ^4.36.0
|
||||
peerDependenciesMeta:
|
||||
'@xstate/fsm':
|
||||
optional: true
|
||||
xstate:
|
||||
optional: true
|
||||
dependencies:
|
||||
react: 18.2.0
|
||||
use-isomorphic-layout-effect: 1.1.2(@types/react@18.2.6)(react@18.2.0)
|
||||
use-sync-external-store: 1.2.0(react@18.2.0)
|
||||
xstate: 4.38.1
|
||||
transitivePeerDependencies:
|
||||
- '@types/react'
|
||||
dev: false
|
||||
|
||||
/@xstate/tools-shared@3.0.1(xstate@4.38.2):
|
||||
resolution: {integrity: sha512-XW00KB72i4XiQPiB0e4P7Fsn9TvYBxqVR0HNGGEkmvQ7l8FZM2FpzBDAriVH67XRUgI1crfNyisxXmGlpB5WYg==}
|
||||
peerDependencies:
|
||||
xstate: ^4
|
||||
dependencies:
|
||||
'@xstate/machine-extractor': 0.10.0(xstate@4.38.2)
|
||||
xstate: 4.38.2
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
/@yarnpkg/esbuild-plugin-pnp@3.0.0-rc.15(esbuild@0.18.20):
|
||||
resolution: {integrity: sha512-kYzDJO5CA9sy+on/s2aIW0411AklfCi8Ck/4QDivOqsMKpStZA2SsR+X27VTggGwpStWaLrjJcDcdDMowtG8MA==}
|
||||
engines: {node: '>=14.15.0'}
|
||||
|
@ -8169,10 +8045,6 @@ packages:
|
|||
resolution: {integrity: sha512-8EZzEP0eKkEEVX+drtd9mtuQ+/QrlfW/5MlwcwK5Nds6EkZ/tRzEexkzUY2mIssnAyVLT+TKHuRXmFNNXYUd6g==}
|
||||
dev: false
|
||||
|
||||
/fast-safe-stringify@2.1.1:
|
||||
resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==}
|
||||
dev: false
|
||||
|
||||
/fast-shallow-equal@1.0.0:
|
||||
resolution: {integrity: sha512-HPtaa38cPgWvaCFmRNhlc6NG7pv6NUHqjPgVAkWGoB9mQMwYB27/K0CvOM5Czy+qpT3e8XJ6Q4aPAnzpNpzNaw==}
|
||||
dev: false
|
||||
|
@ -13477,19 +13349,6 @@ packages:
|
|||
tslib: 2.6.2
|
||||
dev: true
|
||||
|
||||
/use-isomorphic-layout-effect@1.1.2(@types/react@18.2.6)(react@18.2.0):
|
||||
resolution: {integrity: sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@types/react': 18.2.6
|
||||
react: 18.2.0
|
||||
dev: false
|
||||
|
||||
/use-resize-observer@9.1.0(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-R25VqO9Wb3asSD4eqtcxk8sJalvIOYBqS8MNZlpDSQ4l4xMQxC/J7Id9HoTqPq8FwULIn0PVW+OAqF2dyYbjow==}
|
||||
peerDependencies:
|
||||
|
@ -13924,14 +13783,6 @@ packages:
|
|||
resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==}
|
||||
dev: false
|
||||
|
||||
/xstate@4.38.1:
|
||||
resolution: {integrity: sha512-1gBUcFWBj/rv/pRcP2Bedl5sNRGX2d36CaOx9z7fE9uSiHaOEHIWzLg1B853q2xdUHUA9pEiWKjLZ3can4SJaQ==}
|
||||
dev: false
|
||||
|
||||
/xstate@4.38.2:
|
||||
resolution: {integrity: sha512-Fba/DwEPDLneHT3tbJ9F3zafbQXszOlyCJyQqqdzmtlY/cwE2th462KK48yaANf98jHlP6lJvxfNtN0LFKXPQg==}
|
||||
dev: true
|
||||
|
||||
/xtend@4.0.2:
|
||||
resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
|
||||
engines: {node: '>=0.4'}
|
||||
|
|
|
@ -525,7 +525,7 @@ export const postWorkspaceBuild = async (
|
|||
export const startWorkspace = (
|
||||
workspaceId: string,
|
||||
templateVersionId: string,
|
||||
logLevel?: TypesGen.CreateWorkspaceBuildRequest["log_level"],
|
||||
logLevel?: TypesGen.ProvisionerLogLevel,
|
||||
buildParameters?: TypesGen.WorkspaceBuildParameter[],
|
||||
) =>
|
||||
postWorkspaceBuild(workspaceId, {
|
||||
|
@ -536,16 +536,21 @@ export const startWorkspace = (
|
|||
});
|
||||
export const stopWorkspace = (
|
||||
workspaceId: string,
|
||||
logLevel?: TypesGen.CreateWorkspaceBuildRequest["log_level"],
|
||||
logLevel?: TypesGen.ProvisionerLogLevel,
|
||||
) =>
|
||||
postWorkspaceBuild(workspaceId, {
|
||||
transition: "stop",
|
||||
log_level: logLevel,
|
||||
});
|
||||
|
||||
export type DeleteWorkspaceOptions = Pick<
|
||||
TypesGen.CreateWorkspaceBuildRequest,
|
||||
"log_level" & "orphan"
|
||||
>;
|
||||
|
||||
export const deleteWorkspace = (
|
||||
workspaceId: string,
|
||||
options?: Pick<TypesGen.CreateWorkspaceBuildRequest, "log_level" & "orphan">,
|
||||
options?: DeleteWorkspaceOptions,
|
||||
) =>
|
||||
postWorkspaceBuild(workspaceId, {
|
||||
transition: "delete",
|
||||
|
@ -1207,10 +1212,15 @@ export const removeLicense = async (licenseId: number): Promise<void> => {
|
|||
|
||||
export class MissingBuildParameters extends Error {
|
||||
parameters: TypesGen.TemplateVersionParameter[] = [];
|
||||
versionId: string;
|
||||
|
||||
constructor(parameters: TypesGen.TemplateVersionParameter[]) {
|
||||
constructor(
|
||||
parameters: TypesGen.TemplateVersionParameter[],
|
||||
versionId: string,
|
||||
) {
|
||||
super("Missing build parameters.");
|
||||
this.parameters = parameters;
|
||||
this.versionId = versionId;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1239,7 +1249,7 @@ export const changeWorkspaceVersion = async (
|
|||
);
|
||||
|
||||
if (missingParameters.length > 0) {
|
||||
throw new MissingBuildParameters(missingParameters);
|
||||
throw new MissingBuildParameters(missingParameters, templateVersionId);
|
||||
}
|
||||
|
||||
return postWorkspaceBuild(workspace.id, {
|
||||
|
@ -1277,7 +1287,7 @@ export const updateWorkspace = async (
|
|||
);
|
||||
|
||||
if (missingParameters.length > 0) {
|
||||
throw new MissingBuildParameters(missingParameters);
|
||||
throw new MissingBuildParameters(missingParameters, activeVersionId);
|
||||
}
|
||||
|
||||
return postWorkspaceBuild(workspace.id, {
|
||||
|
|
|
@ -27,3 +27,10 @@ export const health = () => {
|
|||
queryFn: API.getHealth,
|
||||
};
|
||||
};
|
||||
|
||||
export const deploymentSSHConfig = () => {
|
||||
return {
|
||||
queryKey: ["deployment", "sshConfig"],
|
||||
queryFn: API.getDeploymentSSHConfig,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -29,6 +29,11 @@ export const workspaceBuildByNumber = (
|
|||
};
|
||||
};
|
||||
|
||||
export const workspaceBuildsKey = (workspaceId: string) => [
|
||||
"workspaceBuilds",
|
||||
workspaceId,
|
||||
];
|
||||
|
||||
export const infiniteWorkspaceBuilds = (
|
||||
workspaceId: string,
|
||||
req?: WorkspaceBuildsRequest,
|
||||
|
@ -36,7 +41,7 @@ export const infiniteWorkspaceBuilds = (
|
|||
const limit = req?.limit ?? 25;
|
||||
|
||||
return {
|
||||
queryKey: ["workspaceBuilds", workspaceId, req],
|
||||
queryKey: [...workspaceBuildsKey(workspaceId), req],
|
||||
getNextPageParam: (lastPage, pages) => {
|
||||
if (lastPage.length < limit) {
|
||||
return undefined;
|
||||
|
|
|
@ -9,7 +9,10 @@ import {
|
|||
type CreateWorkspaceRequest,
|
||||
type WorkspacesResponse,
|
||||
type WorkspacesRequest,
|
||||
WorkspaceBuild,
|
||||
ProvisionerLogLevel,
|
||||
} from "api/typesGenerated";
|
||||
import { workspaceBuildsKey } from "./workspaceBuilds";
|
||||
|
||||
export const workspaceByOwnerAndNameKey = (owner: string, name: string) => [
|
||||
"workspace",
|
||||
|
@ -18,13 +21,11 @@ export const workspaceByOwnerAndNameKey = (owner: string, name: string) => [
|
|||
"settings",
|
||||
];
|
||||
|
||||
export const workspaceByOwnerAndName = (
|
||||
owner: string,
|
||||
name: string,
|
||||
): QueryOptions<Workspace> => {
|
||||
export const workspaceByOwnerAndName = (owner: string, name: string) => {
|
||||
return {
|
||||
queryKey: workspaceByOwnerAndNameKey(owner, name),
|
||||
queryFn: () => API.getWorkspaceByOwnerAndName(owner, name),
|
||||
queryFn: () =>
|
||||
API.getWorkspaceByOwnerAndName(owner, name, { include_deleted: true }),
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -124,3 +125,135 @@ export const increaseDeadline = (workspace: Workspace) => {
|
|||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const changeVersion = (
|
||||
workspace: Workspace,
|
||||
queryClient: QueryClient,
|
||||
) => {
|
||||
return {
|
||||
mutationFn: ({
|
||||
versionId,
|
||||
buildParameters,
|
||||
}: {
|
||||
versionId: string;
|
||||
buildParameters?: WorkspaceBuildParameter[];
|
||||
}) => {
|
||||
return API.changeWorkspaceVersion(workspace, versionId, buildParameters);
|
||||
},
|
||||
onSuccess: async (build: WorkspaceBuild) => {
|
||||
await updateWorkspaceBuild(build, queryClient);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const updateWorkspace = (
|
||||
workspace: Workspace,
|
||||
queryClient: QueryClient,
|
||||
) => {
|
||||
return {
|
||||
mutationFn: (buildParameters?: WorkspaceBuildParameter[]) => {
|
||||
return API.updateWorkspace(workspace, buildParameters);
|
||||
},
|
||||
onSuccess: async (build: WorkspaceBuild) => {
|
||||
await updateWorkspaceBuild(build, queryClient);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const deleteWorkspace = (
|
||||
workspace: Workspace,
|
||||
queryClient: QueryClient,
|
||||
) => {
|
||||
return {
|
||||
mutationFn: (options: API.DeleteWorkspaceOptions) => {
|
||||
return API.deleteWorkspace(workspace.id, options);
|
||||
},
|
||||
onSuccess: async (build: WorkspaceBuild) => {
|
||||
await updateWorkspaceBuild(build, queryClient);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const stopWorkspace = (
|
||||
workspace: Workspace,
|
||||
queryClient: QueryClient,
|
||||
) => {
|
||||
return {
|
||||
mutationFn: ({ logLevel }: { logLevel?: ProvisionerLogLevel }) => {
|
||||
return API.stopWorkspace(workspace.id, logLevel);
|
||||
},
|
||||
onSuccess: async (build: WorkspaceBuild) => {
|
||||
await updateWorkspaceBuild(build, queryClient);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const startWorkspace = (
|
||||
workspace: Workspace,
|
||||
queryClient: QueryClient,
|
||||
) => {
|
||||
return {
|
||||
mutationFn: ({
|
||||
buildParameters,
|
||||
logLevel,
|
||||
}: {
|
||||
buildParameters?: WorkspaceBuildParameter[];
|
||||
logLevel?: ProvisionerLogLevel;
|
||||
}) => {
|
||||
return API.startWorkspace(
|
||||
workspace.id,
|
||||
workspace.latest_build.template_version_id,
|
||||
logLevel,
|
||||
buildParameters,
|
||||
);
|
||||
},
|
||||
onSuccess: async (build: WorkspaceBuild) => {
|
||||
await updateWorkspaceBuild(build, queryClient);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const cancelBuild = (workspace: Workspace, queryClient: QueryClient) => {
|
||||
return {
|
||||
mutationFn: () => {
|
||||
return API.cancelWorkspaceBuild(workspace.latest_build.id);
|
||||
},
|
||||
onSuccess: async () => {
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: workspaceBuildsKey(workspace.id),
|
||||
});
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const activate = (workspace: Workspace, queryClient: QueryClient) => {
|
||||
return {
|
||||
mutationFn: () => {
|
||||
return API.updateWorkspaceDormancy(workspace.id, false);
|
||||
},
|
||||
onSuccess: (updatedWorkspace: Workspace) => {
|
||||
queryClient.setQueryData(
|
||||
workspaceByOwnerAndNameKey(workspace.owner_name, workspace.name),
|
||||
updatedWorkspace,
|
||||
);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const updateWorkspaceBuild = async (
|
||||
build: WorkspaceBuild,
|
||||
queryClient: QueryClient,
|
||||
) => {
|
||||
const workspaceKey = workspaceByOwnerAndNameKey(
|
||||
build.workspace_owner_name,
|
||||
build.workspace_name,
|
||||
);
|
||||
const previousData = queryClient.getQueryData(workspaceKey) as Workspace;
|
||||
queryClient.setQueryData(workspaceKey, {
|
||||
...previousData,
|
||||
latest_build: build,
|
||||
});
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: workspaceBuildsKey(build.workspace_id),
|
||||
});
|
||||
};
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
import { TemplateVersionWarnings } from "./TemplateVersionWarnings";
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
|
||||
const meta: Meta<typeof TemplateVersionWarnings> = {
|
||||
title: "components/TemplateVersionWarnings",
|
||||
component: TemplateVersionWarnings,
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof TemplateVersionWarnings>;
|
||||
|
||||
export const UnsupportedWorkspaces: Story = {
|
||||
args: {
|
||||
warnings: ["UNSUPPORTED_WORKSPACES"],
|
||||
},
|
||||
};
|
|
@ -1,26 +0,0 @@
|
|||
import { FC } from "react";
|
||||
import * as TypesGen from "api/typesGenerated";
|
||||
import { Alert } from "components/Alert/Alert";
|
||||
|
||||
export interface TemplateVersionWarningsProps {
|
||||
warnings?: TypesGen.TemplateVersionWarning[];
|
||||
}
|
||||
|
||||
export const TemplateVersionWarnings: FC<
|
||||
React.PropsWithChildren<TemplateVersionWarningsProps>
|
||||
> = (props) => {
|
||||
const { warnings = [] } = props;
|
||||
|
||||
if (!warnings.includes("UNSUPPORTED_WORKSPACES")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div data-testid="error-unsupported-workspaces">
|
||||
<Alert severity="error">
|
||||
This template uses legacy parameters which are not supported anymore.
|
||||
Contact your administrator for assistance.
|
||||
</Alert>
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -1,23 +1,6 @@
|
|||
import { inspect } from "@xstate/inspect";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import { Interpreter } from "xstate";
|
||||
import { App } from "./App";
|
||||
|
||||
// if this is a development build and the developer wants to inspect
|
||||
// helpful to see realtime changes on the services
|
||||
if (
|
||||
process.env.NODE_ENV === "development" &&
|
||||
process.env.INSPECT_XSTATE === "true"
|
||||
) {
|
||||
// configure the XState inspector to open in a new tab
|
||||
inspect({
|
||||
url: "https://stately.ai/viz?inspect",
|
||||
iframe: false,
|
||||
});
|
||||
// configure all XServices to use the inspector
|
||||
Interpreter.defaultOptions.devTools = true;
|
||||
}
|
||||
|
||||
// This is the entry point for the app - where everything start.
|
||||
// In the future, we'll likely bring in more bootstrapping logic -
|
||||
// like: https://github.com/coder/m/blob/50898bd4803df7639bd181e484c74ac5d84da474/product/coder/site/pages/_app.tsx#L32
|
||||
|
|
|
@ -2,7 +2,6 @@ import { Meta, StoryObj } from "@storybook/react";
|
|||
import {
|
||||
MockTemplate,
|
||||
MockTemplateVersion,
|
||||
MockTemplateVersion3,
|
||||
MockWorkspaceResource,
|
||||
MockWorkspaceVolumeResource,
|
||||
} from "testHelpers/entities";
|
||||
|
@ -31,11 +30,3 @@ export const NoIcon: Story = {
|
|||
resources: [MockWorkspaceResource, MockWorkspaceVolumeResource],
|
||||
},
|
||||
};
|
||||
|
||||
export const WithDeprecatedParameters: Story = {
|
||||
args: {
|
||||
template: MockTemplate,
|
||||
activeVersion: MockTemplateVersion3,
|
||||
resources: [MockWorkspaceResource, MockWorkspaceVolumeResource],
|
||||
},
|
||||
};
|
||||
|
|
|
@ -9,7 +9,6 @@ import { Loader } from "components/Loader/Loader";
|
|||
import { Stack } from "components/Stack/Stack";
|
||||
import { TemplateResourcesTable } from "components/TemplateResourcesTable/TemplateResourcesTable";
|
||||
import { TemplateStats } from "./TemplateStats";
|
||||
import { TemplateVersionWarnings } from "components/TemplateVersionWarnings/TemplateVersionWarnings";
|
||||
|
||||
export interface TemplateSummaryPageViewProps {
|
||||
resources?: WorkspaceResource[];
|
||||
|
@ -45,7 +44,6 @@ export const TemplateSummaryPageView: FC<TemplateSummaryPageViewProps> = ({
|
|||
|
||||
return (
|
||||
<Stack spacing={4}>
|
||||
<TemplateVersionWarnings warnings={activeVersion.warnings} />
|
||||
<TemplateStats template={template} activeVersion={activeVersion} />
|
||||
<TemplateResourcesTable resources={getStartedResources(resources)} />
|
||||
</Stack>
|
||||
|
|
|
@ -77,6 +77,7 @@ describe("TerminalPage", () => {
|
|||
expect(API.getWorkspaceByOwnerAndName).toHaveBeenCalledWith(
|
||||
MockUser.username,
|
||||
MockWorkspace.name,
|
||||
{ include_deleted: true },
|
||||
);
|
||||
});
|
||||
spy.mockRestore();
|
||||
|
|
|
@ -747,10 +747,3 @@ function makeFailedBuildLogs(): ProvisionerJobLog[] {
|
|||
},
|
||||
];
|
||||
}
|
||||
|
||||
export const UnsupportedWorkspace: Story = {
|
||||
args: {
|
||||
...Running.args,
|
||||
templateWarnings: ["UNSUPPORTED_WORKSPACES"],
|
||||
},
|
||||
};
|
||||
|
|
|
@ -15,7 +15,6 @@ import {
|
|||
PageHeaderTitle,
|
||||
PageHeaderSubtitle,
|
||||
} from "components/PageHeader/FullWidthPageHeader";
|
||||
import { TemplateVersionWarnings } from "components/TemplateVersionWarnings/TemplateVersionWarnings";
|
||||
import { ErrorAlert } from "components/Alert/ErrorAlert";
|
||||
import { DormantWorkspaceBanner } from "components/WorkspaceDeletion";
|
||||
import { Avatar } from "components/Avatar/Avatar";
|
||||
|
@ -55,7 +54,6 @@ export interface WorkspaceProps {
|
|||
isRestarting: boolean;
|
||||
workspace: TypesGen.Workspace;
|
||||
resources?: TypesGen.WorkspaceResource[];
|
||||
templateWarnings?: TypesGen.TemplateVersionWarning[];
|
||||
canUpdateWorkspace: boolean;
|
||||
updateMessage?: string;
|
||||
canRetryDebugMode: boolean;
|
||||
|
@ -105,9 +103,7 @@ export const Workspace: FC<React.PropsWithChildren<WorkspaceProps>> = ({
|
|||
buildInfo,
|
||||
sshPrefix,
|
||||
template,
|
||||
quotaBudget,
|
||||
handleBuildRetry,
|
||||
templateWarnings,
|
||||
buildLogs,
|
||||
onLoadMoreBuilds,
|
||||
isLoadingMoreBuilds,
|
||||
|
@ -198,7 +194,6 @@ export const Workspace: FC<React.PropsWithChildren<WorkspaceProps>> = ({
|
|||
|
||||
<WorkspaceStats
|
||||
workspace={workspace}
|
||||
quotaBudget={quotaBudget}
|
||||
handleUpdate={handleUpdate}
|
||||
canUpdateWorkspace={canUpdateWorkspace}
|
||||
maxDeadlineDecrease={scheduleProps.maxDeadlineDecrease}
|
||||
|
@ -294,8 +289,6 @@ export const Workspace: FC<React.PropsWithChildren<WorkspaceProps>> = ({
|
|||
onDismiss={() => saveLocal("dismissedWorkspace", workspace.id)}
|
||||
/>
|
||||
|
||||
<TemplateVersionWarnings warnings={templateWarnings} />
|
||||
|
||||
{showAlertPendingInQueue && (
|
||||
<Alert severity="info">
|
||||
<AlertTitle>Workspace build is pending</AlertTitle>
|
||||
|
|
|
@ -12,7 +12,6 @@ import {
|
|||
MockTemplateVersionParameter1,
|
||||
MockTemplateVersionParameter2,
|
||||
MockBuilds,
|
||||
MockTemplateVersion3,
|
||||
MockUser,
|
||||
MockDeploymentConfig,
|
||||
MockWorkspaceBuildDelete,
|
||||
|
@ -259,10 +258,10 @@ describe("WorkspacePage", () => {
|
|||
const updateWorkspaceSpy = jest
|
||||
.spyOn(api, "updateWorkspace")
|
||||
.mockRejectedValueOnce(
|
||||
new api.MissingBuildParameters([
|
||||
MockTemplateVersionParameter1,
|
||||
MockTemplateVersionParameter2,
|
||||
]),
|
||||
new api.MissingBuildParameters(
|
||||
[MockTemplateVersionParameter1, MockTemplateVersionParameter2],
|
||||
MockOutdatedWorkspace.template_active_version_id,
|
||||
),
|
||||
);
|
||||
|
||||
// Render
|
||||
|
@ -324,20 +323,6 @@ describe("WorkspacePage", () => {
|
|||
});
|
||||
});
|
||||
|
||||
it("shows the template warning", async () => {
|
||||
server.use(
|
||||
rest.get(
|
||||
"/api/v2/templateversions/:templateVersionId",
|
||||
async (req, res, ctx) => {
|
||||
return res(ctx.status(200), ctx.json(MockTemplateVersion3));
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
await renderWorkspacePage();
|
||||
await screen.findByTestId("error-unsupported-workspaces");
|
||||
});
|
||||
|
||||
it("restart the workspace with one time parameters when having the confirmation dialog", async () => {
|
||||
window.localStorage.removeItem(`${MockUser.id}_ignoredWarnings`);
|
||||
jest.spyOn(api, "getWorkspaceParameters").mockResolvedValue({
|
||||
|
|
|
@ -1,22 +1,22 @@
|
|||
import { useMachine } from "@xstate/react";
|
||||
import { Loader } from "components/Loader/Loader";
|
||||
import { FC } from "react";
|
||||
import { FC, useEffect } from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { workspaceMachine } from "xServices/workspace/workspaceXService";
|
||||
import { WorkspaceReadyPage } from "./WorkspaceReadyPage";
|
||||
import { RequirePermission } from "components/RequirePermission/RequirePermission";
|
||||
import { ErrorAlert } from "components/Alert/ErrorAlert";
|
||||
import { useOrganizationId } from "hooks";
|
||||
import { isAxiosError } from "axios";
|
||||
import { Margins } from "components/Margins/Margins";
|
||||
import {
|
||||
workspaceQuota,
|
||||
workspaceResolveAutostart,
|
||||
} from "api/queries/workspaceQuota";
|
||||
import { useInfiniteQuery, useQuery } from "react-query";
|
||||
import { useInfiniteQuery, useQuery, useQueryClient } from "react-query";
|
||||
import { infiniteWorkspaceBuilds } from "api/queries/workspaceBuilds";
|
||||
import { templateByName } from "api/queries/templates";
|
||||
import { workspaceByOwnerAndName } from "api/queries/workspaces";
|
||||
import { checkAuthorization } from "api/queries/authCheck";
|
||||
import { WorkspacePermissions, workspaceChecks } from "./permissions";
|
||||
import { watchWorkspace } from "api/api";
|
||||
import { Workspace } from "api/typesGenerated";
|
||||
import { useEffectEvent } from "hooks/hookPolyfills";
|
||||
|
||||
export const WorkspacePage: FC = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const params = useParams() as {
|
||||
username: string;
|
||||
workspace: string;
|
||||
|
@ -24,31 +24,81 @@ export const WorkspacePage: FC = () => {
|
|||
const workspaceName = params.workspace;
|
||||
const username = params.username.replace("@", "");
|
||||
const orgId = useOrganizationId();
|
||||
const [workspaceState, workspaceSend] = useMachine(workspaceMachine, {
|
||||
context: {
|
||||
orgId,
|
||||
workspaceName,
|
||||
username,
|
||||
},
|
||||
actions: {
|
||||
refreshBuilds: async () => {
|
||||
await buildsQuery.refetch();
|
||||
},
|
||||
},
|
||||
|
||||
// Workspace
|
||||
const workspaceQueryOptions = workspaceByOwnerAndName(
|
||||
username,
|
||||
workspaceName,
|
||||
);
|
||||
const workspaceQuery = useQuery(workspaceQueryOptions);
|
||||
const workspace = workspaceQuery.data;
|
||||
|
||||
// Template
|
||||
const templateQuery = useQuery({
|
||||
...templateByName(orgId, workspace?.template_name ?? ""),
|
||||
enabled: workspace !== undefined,
|
||||
});
|
||||
const { workspace, error } = workspaceState.context;
|
||||
const quotaQuery = useQuery(workspaceQuota(username));
|
||||
const pageError = error ?? quotaQuery.error;
|
||||
const template = templateQuery.data;
|
||||
|
||||
// Permissions
|
||||
const checks =
|
||||
workspace && template ? workspaceChecks(workspace, template) : {};
|
||||
const permissionsQuery = useQuery({
|
||||
...checkAuthorization({ checks }),
|
||||
enabled: workspace !== undefined && template !== undefined,
|
||||
});
|
||||
const permissions = permissionsQuery.data as WorkspacePermissions | undefined;
|
||||
|
||||
// Builds
|
||||
const buildsQuery = useInfiniteQuery({
|
||||
...infiniteWorkspaceBuilds(workspace?.id ?? ""),
|
||||
enabled: Boolean(workspace),
|
||||
enabled: workspace !== undefined,
|
||||
});
|
||||
|
||||
const canAutostartResponse = useQuery(
|
||||
workspaceResolveAutostart(workspace?.id ?? ""),
|
||||
);
|
||||
// Watch workspace changes
|
||||
const updateWorkspaceData = useEffectEvent(
|
||||
async (newWorkspaceData: Workspace) => {
|
||||
queryClient.setQueryData(
|
||||
workspaceQueryOptions.queryKey,
|
||||
newWorkspaceData,
|
||||
);
|
||||
|
||||
const canAutostart = !canAutostartResponse.data?.parameter_mismatch ?? false;
|
||||
const hasNewBuild =
|
||||
newWorkspaceData.latest_build.id !== workspace!.latest_build.id;
|
||||
const lastBuildHasChanged =
|
||||
newWorkspaceData.latest_build.status !== workspace!.latest_build.status;
|
||||
|
||||
if (hasNewBuild || lastBuildHasChanged) {
|
||||
await buildsQuery.refetch();
|
||||
}
|
||||
},
|
||||
);
|
||||
const workspaceId = workspace?.id;
|
||||
useEffect(() => {
|
||||
if (!workspaceId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const eventSource = watchWorkspace(workspaceId);
|
||||
|
||||
eventSource.addEventListener("data", async (event) => {
|
||||
const newWorkspaceData = JSON.parse(event.data) as Workspace;
|
||||
await updateWorkspaceData(newWorkspaceData);
|
||||
});
|
||||
|
||||
eventSource.addEventListener("error", (event) => {
|
||||
console.error("Error on getting workspace changes.", event);
|
||||
});
|
||||
|
||||
return () => {
|
||||
eventSource.close();
|
||||
};
|
||||
}, [updateWorkspaceData, workspaceId]);
|
||||
|
||||
// Page statuses
|
||||
const pageError =
|
||||
workspaceQuery.error ?? templateQuery.error ?? permissionsQuery.error;
|
||||
const isLoading = !workspace || !template || !permissions;
|
||||
|
||||
if (pageError) {
|
||||
return (
|
||||
|
@ -58,30 +108,23 @@ export const WorkspacePage: FC = () => {
|
|||
);
|
||||
}
|
||||
|
||||
if (!workspace || !workspaceState.matches("ready") || !quotaQuery.isSuccess) {
|
||||
if (isLoading) {
|
||||
return <Loader />;
|
||||
}
|
||||
|
||||
return (
|
||||
<RequirePermission
|
||||
isFeatureVisible={
|
||||
!(isAxiosError(pageError) && pageError.response?.status === 404)
|
||||
}
|
||||
>
|
||||
<WorkspaceReadyPage
|
||||
workspaceState={workspaceState}
|
||||
quota={quotaQuery.data}
|
||||
workspaceSend={workspaceSend}
|
||||
builds={buildsQuery.data?.pages.flat()}
|
||||
buildsError={buildsQuery.error}
|
||||
isLoadingMoreBuilds={buildsQuery.isFetchingNextPage}
|
||||
onLoadMoreBuilds={async () => {
|
||||
await buildsQuery.fetchNextPage();
|
||||
}}
|
||||
hasMoreBuilds={Boolean(buildsQuery.hasNextPage)}
|
||||
canAutostart={canAutostart}
|
||||
/>
|
||||
</RequirePermission>
|
||||
<WorkspaceReadyPage
|
||||
workspace={workspace}
|
||||
template={template}
|
||||
permissions={permissions}
|
||||
builds={buildsQuery.data?.pages.flat()}
|
||||
buildsError={buildsQuery.error}
|
||||
isLoadingMoreBuilds={buildsQuery.isFetchingNextPage}
|
||||
onLoadMoreBuilds={async () => {
|
||||
await buildsQuery.fetchNextPage();
|
||||
}}
|
||||
hasMoreBuilds={Boolean(buildsQuery.hasNextPage)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -9,18 +9,13 @@ import {
|
|||
getMaxDeadlineChange,
|
||||
getMinDeadline,
|
||||
} from "utils/schedule";
|
||||
import { StateFrom } from "xstate";
|
||||
import { Workspace, WorkspaceErrors } from "./Workspace";
|
||||
import { pageTitle } from "utils/page";
|
||||
import { getFaviconByStatus, hasJobError } from "utils/workspace";
|
||||
import {
|
||||
WorkspaceEvent,
|
||||
workspaceMachine,
|
||||
} from "xServices/workspace/workspaceXService";
|
||||
import { hasJobError } from "utils/workspace";
|
||||
import { UpdateBuildParametersDialog } from "./UpdateBuildParametersDialog";
|
||||
import { ChangeVersionDialog } from "./ChangeVersionDialog";
|
||||
import { useMutation, useQuery } from "react-query";
|
||||
import { restartWorkspace } from "api/api";
|
||||
import { useMutation, useQuery, useQueryClient } from "react-query";
|
||||
import { MissingBuildParameters, restartWorkspace } from "api/api";
|
||||
import {
|
||||
ConfirmDialog,
|
||||
ConfirmDialogProps,
|
||||
|
@ -31,90 +26,76 @@ import { templateVersion, templateVersions } from "api/queries/templates";
|
|||
import { Alert } from "components/Alert/Alert";
|
||||
import { Stack } from "components/Stack/Stack";
|
||||
import { useWorkspaceBuildLogs } from "hooks/useWorkspaceBuildLogs";
|
||||
import { decreaseDeadline, increaseDeadline } from "api/queries/workspaces";
|
||||
import {
|
||||
activate,
|
||||
changeVersion,
|
||||
decreaseDeadline,
|
||||
deleteWorkspace,
|
||||
increaseDeadline,
|
||||
updateWorkspace,
|
||||
stopWorkspace,
|
||||
startWorkspace,
|
||||
cancelBuild,
|
||||
} from "api/queries/workspaces";
|
||||
import { getErrorMessage } from "api/errors";
|
||||
import { displaySuccess, displayError } from "components/GlobalSnackbar/utils";
|
||||
import { deploymentConfig, deploymentSSHConfig } from "api/queries/deployment";
|
||||
import { WorkspacePermissions } from "./permissions";
|
||||
import { workspaceResolveAutostart } from "api/queries/workspaceQuota";
|
||||
import { WorkspaceDeleteDialog } from "./WorkspaceDeleteDialog";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
interface WorkspaceReadyPageProps {
|
||||
workspaceState: StateFrom<typeof workspaceMachine>;
|
||||
workspaceSend: (event: WorkspaceEvent) => void;
|
||||
quota?: TypesGen.WorkspaceQuota;
|
||||
template: TypesGen.Template;
|
||||
workspace: TypesGen.Workspace;
|
||||
permissions: WorkspacePermissions;
|
||||
builds: TypesGen.WorkspaceBuild[] | undefined;
|
||||
buildsError: unknown;
|
||||
onLoadMoreBuilds: () => void;
|
||||
isLoadingMoreBuilds: boolean;
|
||||
hasMoreBuilds: boolean;
|
||||
canAutostart: boolean;
|
||||
}
|
||||
|
||||
export const WorkspaceReadyPage = ({
|
||||
workspaceState,
|
||||
workspaceSend,
|
||||
quota,
|
||||
workspace,
|
||||
template,
|
||||
permissions,
|
||||
builds,
|
||||
buildsError,
|
||||
onLoadMoreBuilds,
|
||||
isLoadingMoreBuilds,
|
||||
hasMoreBuilds,
|
||||
canAutostart,
|
||||
}: WorkspaceReadyPageProps): JSX.Element => {
|
||||
const navigate = useNavigate();
|
||||
const queryClient = useQueryClient();
|
||||
const { buildInfo } = useDashboard();
|
||||
const featureVisibility = useFeatureVisibility();
|
||||
const {
|
||||
workspace,
|
||||
template,
|
||||
templateVersion: currentVersion,
|
||||
deploymentValues,
|
||||
buildError,
|
||||
cancellationError,
|
||||
sshPrefix,
|
||||
permissions,
|
||||
missedParameters,
|
||||
} = workspaceState.context;
|
||||
if (workspace === undefined) {
|
||||
throw Error("Workspace is undefined");
|
||||
}
|
||||
const deadline = getDeadline(workspace);
|
||||
const canUpdateWorkspace = Boolean(permissions?.updateWorkspace);
|
||||
const canUpdateTemplate = Boolean(permissions?.updateTemplate);
|
||||
const canRetryDebugMode =
|
||||
Boolean(permissions?.viewDeploymentValues) &&
|
||||
Boolean(deploymentValues?.enable_terraform_debug_mode);
|
||||
const favicon = getFaviconByStatus(workspace.latest_build);
|
||||
const navigate = useNavigate();
|
||||
const [changeVersionDialogOpen, setChangeVersionDialogOpen] = useState(false);
|
||||
const [isConfirmingUpdate, setIsConfirmingUpdate] = useState(false);
|
||||
const [confirmingRestart, setConfirmingRestart] = useState<{
|
||||
open: boolean;
|
||||
buildParameters?: TypesGen.WorkspaceBuildParameter[];
|
||||
}>({ open: false });
|
||||
|
||||
const { data: allVersions } = useQuery({
|
||||
...templateVersions(workspace.template_id),
|
||||
enabled: changeVersionDialogOpen,
|
||||
// Debug mode
|
||||
const { data: deploymentValues } = useQuery({
|
||||
...deploymentConfig(),
|
||||
enabled: permissions?.viewDeploymentValues,
|
||||
});
|
||||
const { data: latestVersion } = useQuery({
|
||||
...templateVersion(workspace.template_active_version_id),
|
||||
enabled: workspace.outdated,
|
||||
});
|
||||
const [faviconTheme, setFaviconTheme] = useState<"light" | "dark">("dark");
|
||||
useEffect(() => {
|
||||
if (typeof window === "undefined" || !window.matchMedia) {
|
||||
return;
|
||||
}
|
||||
const canRetryDebugMode = Boolean(
|
||||
deploymentValues?.config.enable_terraform_debug_mode,
|
||||
);
|
||||
|
||||
const isDark = window.matchMedia("(prefers-color-scheme: dark)");
|
||||
// We want the favicon the opposite of the theme.
|
||||
setFaviconTheme(isDark.matches ? "light" : "dark");
|
||||
}, []);
|
||||
// Build logs
|
||||
const buildLogs = useWorkspaceBuildLogs(workspace.latest_build.id);
|
||||
const shouldDisplayBuildLogs =
|
||||
hasJobError(workspace) ||
|
||||
["canceling", "deleting", "pending", "starting", "stopping"].includes(
|
||||
workspace.latest_build.status,
|
||||
);
|
||||
|
||||
// Restart
|
||||
const [confirmingRestart, setConfirmingRestart] = useState<{
|
||||
open: boolean;
|
||||
buildParameters?: TypesGen.WorkspaceBuildParameter[];
|
||||
}>({ open: false });
|
||||
const {
|
||||
mutate: mutateRestartWorkspace,
|
||||
error: restartBuildError,
|
||||
|
@ -123,6 +104,8 @@ export const WorkspaceReadyPage = ({
|
|||
mutationFn: restartWorkspace,
|
||||
});
|
||||
|
||||
// Schedule controls
|
||||
const deadline = getDeadline(workspace);
|
||||
const onDeadlineChangeSuccess = () => {
|
||||
displaySuccess("Updated workspace shutdown time.");
|
||||
};
|
||||
|
@ -142,6 +125,77 @@ export const WorkspaceReadyPage = ({
|
|||
onError: onDeadlineChangeFails,
|
||||
});
|
||||
|
||||
// Auto start
|
||||
const canAutostartResponse = useQuery(
|
||||
workspaceResolveAutostart(workspace.id),
|
||||
);
|
||||
const canAutostart = !canAutostartResponse.data?.parameter_mismatch ?? false;
|
||||
|
||||
// SSH Prefix
|
||||
const sshPrefixQuery = useQuery(deploymentSSHConfig());
|
||||
|
||||
// Favicon
|
||||
const favicon = getFaviconByStatus(workspace.latest_build);
|
||||
const [faviconTheme, setFaviconTheme] = useState<"light" | "dark">("dark");
|
||||
useEffect(() => {
|
||||
if (typeof window === "undefined" || !window.matchMedia) {
|
||||
return;
|
||||
}
|
||||
|
||||
const isDark = window.matchMedia("(prefers-color-scheme: dark)");
|
||||
// We want the favicon the opposite of the theme.
|
||||
setFaviconTheme(isDark.matches ? "light" : "dark");
|
||||
}, []);
|
||||
|
||||
// Change version
|
||||
const canChangeVersions = Boolean(permissions?.updateTemplate);
|
||||
const [changeVersionDialogOpen, setChangeVersionDialogOpen] = useState(false);
|
||||
const changeVersionMutation = useMutation(
|
||||
changeVersion(workspace, queryClient),
|
||||
);
|
||||
|
||||
// Versions
|
||||
const { data: allVersions } = useQuery({
|
||||
...templateVersions(workspace.template_id),
|
||||
enabled: changeVersionDialogOpen,
|
||||
});
|
||||
const { data: latestVersion } = useQuery({
|
||||
...templateVersion(workspace.template_active_version_id),
|
||||
enabled: workspace.outdated,
|
||||
});
|
||||
|
||||
// Update workspace
|
||||
const canUpdateWorkspace = Boolean(permissions?.updateWorkspace);
|
||||
const [isConfirmingUpdate, setIsConfirmingUpdate] = useState(false);
|
||||
const updateWorkspaceMutation = useMutation(
|
||||
updateWorkspace(workspace, queryClient),
|
||||
);
|
||||
|
||||
// Delete workspace
|
||||
const canDeleteWorkspace = Boolean(permissions?.updateWorkspace);
|
||||
const [isConfirmingDelete, setIsConfirmingDelete] = useState(false);
|
||||
const deleteWorkspaceMutation = useMutation(
|
||||
deleteWorkspace(workspace, queryClient),
|
||||
);
|
||||
|
||||
// Activate workspace
|
||||
const activateWorkspaceMutation = useMutation(
|
||||
activate(workspace, queryClient),
|
||||
);
|
||||
|
||||
// Stop workspace
|
||||
const stopWorkspaceMutation = useMutation(
|
||||
stopWorkspace(workspace, queryClient),
|
||||
);
|
||||
|
||||
// Start workspace
|
||||
const startWorkspaceMutation = useMutation(
|
||||
startWorkspace(workspace, queryClient),
|
||||
);
|
||||
|
||||
// Cancel build
|
||||
const cancelBuildMutation = useMutation(cancelBuild(workspace, queryClient));
|
||||
|
||||
return (
|
||||
<>
|
||||
<Helmet>
|
||||
|
@ -168,27 +222,50 @@ export const WorkspaceReadyPage = ({
|
|||
deadline,
|
||||
),
|
||||
}}
|
||||
isUpdating={workspaceState.matches("ready.build.requestingUpdate")}
|
||||
isUpdating={updateWorkspaceMutation.isLoading}
|
||||
isRestarting={isRestarting}
|
||||
workspace={workspace}
|
||||
handleStart={(buildParameters) =>
|
||||
workspaceSend({ type: "START", buildParameters })
|
||||
}
|
||||
handleStop={() => workspaceSend({ type: "STOP" })}
|
||||
handleDelete={() => workspaceSend({ type: "ASK_DELETE" })}
|
||||
handleStart={(buildParameters) => {
|
||||
startWorkspaceMutation.mutate({ buildParameters });
|
||||
}}
|
||||
handleStop={() => {
|
||||
stopWorkspaceMutation.mutate({});
|
||||
}}
|
||||
handleDelete={() => {
|
||||
setIsConfirmingDelete(true);
|
||||
}}
|
||||
handleRestart={(buildParameters) => {
|
||||
setConfirmingRestart({ open: true, buildParameters });
|
||||
}}
|
||||
handleUpdate={() => {
|
||||
setIsConfirmingUpdate(true);
|
||||
}}
|
||||
handleCancel={() => workspaceSend({ type: "CANCEL" })}
|
||||
handleCancel={cancelBuildMutation.mutate}
|
||||
handleSettings={() => navigate("settings")}
|
||||
handleBuildRetry={() => workspaceSend({ type: "RETRY_BUILD" })}
|
||||
handleBuildRetry={() => {
|
||||
switch (workspace.latest_build.transition) {
|
||||
case "start":
|
||||
startWorkspaceMutation.mutate({ logLevel: "debug" });
|
||||
break;
|
||||
case "stop":
|
||||
stopWorkspaceMutation.mutate({ logLevel: "debug" });
|
||||
break;
|
||||
case "delete":
|
||||
deleteWorkspaceMutation.mutate({ logLevel: "debug" });
|
||||
break;
|
||||
}
|
||||
}}
|
||||
handleChangeVersion={() => {
|
||||
setChangeVersionDialogOpen(true);
|
||||
}}
|
||||
handleDormantActivate={() => workspaceSend({ type: "ACTIVATE" })}
|
||||
handleDormantActivate={async () => {
|
||||
try {
|
||||
await activateWorkspaceMutation.mutateAsync();
|
||||
} catch (e) {
|
||||
const message = getErrorMessage(e, "Error activate workspace.");
|
||||
displayError(message);
|
||||
}
|
||||
}}
|
||||
resources={workspace.latest_build.resources}
|
||||
builds={builds}
|
||||
onLoadMoreBuilds={onLoadMoreBuilds}
|
||||
|
@ -197,19 +274,22 @@ export const WorkspaceReadyPage = ({
|
|||
canUpdateWorkspace={canUpdateWorkspace}
|
||||
updateMessage={latestVersion?.message}
|
||||
canRetryDebugMode={canRetryDebugMode}
|
||||
canChangeVersions={canUpdateTemplate}
|
||||
canChangeVersions={canChangeVersions}
|
||||
hideSSHButton={featureVisibility["browser_only"]}
|
||||
hideVSCodeDesktopButton={featureVisibility["browser_only"]}
|
||||
workspaceErrors={{
|
||||
[WorkspaceErrors.GET_BUILDS_ERROR]: buildsError,
|
||||
[WorkspaceErrors.BUILD_ERROR]: buildError || restartBuildError,
|
||||
[WorkspaceErrors.CANCELLATION_ERROR]: cancellationError,
|
||||
[WorkspaceErrors.BUILD_ERROR]:
|
||||
restartBuildError ??
|
||||
startWorkspaceMutation.error ??
|
||||
stopWorkspaceMutation.error ??
|
||||
deleteWorkspaceMutation.error ??
|
||||
updateWorkspaceMutation.error,
|
||||
[WorkspaceErrors.CANCELLATION_ERROR]: cancelBuildMutation.error,
|
||||
}}
|
||||
buildInfo={buildInfo}
|
||||
sshPrefix={sshPrefix}
|
||||
sshPrefix={sshPrefixQuery.data?.hostname_prefix}
|
||||
template={template}
|
||||
quotaBudget={quota?.budget}
|
||||
templateWarnings={currentVersion?.warnings}
|
||||
buildLogs={
|
||||
shouldDisplayBuildLogs && (
|
||||
<WorkspaceBuildLogsSection logs={buildLogs} />
|
||||
|
@ -219,24 +299,50 @@ export const WorkspaceReadyPage = ({
|
|||
/>
|
||||
<WorkspaceDeleteDialog
|
||||
workspace={workspace}
|
||||
canUpdateTemplate={canUpdateTemplate}
|
||||
isOpen={workspaceState.matches({ ready: { build: "askingDelete" } })}
|
||||
onCancel={() => workspaceSend({ type: "CANCEL_DELETE" })}
|
||||
canUpdateTemplate={canDeleteWorkspace}
|
||||
isOpen={isConfirmingDelete}
|
||||
onCancel={() => {
|
||||
setIsConfirmingDelete(false);
|
||||
}}
|
||||
onConfirm={(orphan) => {
|
||||
workspaceSend({ type: "DELETE", orphan });
|
||||
deleteWorkspaceMutation.mutate({ orphan });
|
||||
setIsConfirmingDelete(false);
|
||||
}}
|
||||
workspaceBuildDateStr={dayjs(workspace.created_at).fromNow()}
|
||||
/>
|
||||
<UpdateBuildParametersDialog
|
||||
missedParameters={missedParameters ?? []}
|
||||
open={workspaceState.matches(
|
||||
"ready.build.askingForMissedBuildParameters",
|
||||
)}
|
||||
missedParameters={
|
||||
changeVersionMutation.error instanceof MissingBuildParameters
|
||||
? changeVersionMutation.error.parameters
|
||||
: []
|
||||
}
|
||||
open={changeVersionMutation.error instanceof MissingBuildParameters}
|
||||
onClose={() => {
|
||||
workspaceSend({ type: "CANCEL" });
|
||||
changeVersionMutation.reset();
|
||||
}}
|
||||
onUpdate={(buildParameters) => {
|
||||
workspaceSend({ type: "UPDATE", buildParameters });
|
||||
if (changeVersionMutation.error instanceof MissingBuildParameters) {
|
||||
changeVersionMutation.mutate({
|
||||
versionId: changeVersionMutation.error.versionId,
|
||||
buildParameters,
|
||||
});
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<UpdateBuildParametersDialog
|
||||
missedParameters={
|
||||
updateWorkspaceMutation.error instanceof MissingBuildParameters
|
||||
? updateWorkspaceMutation.error.parameters
|
||||
: []
|
||||
}
|
||||
open={updateWorkspaceMutation.error instanceof MissingBuildParameters}
|
||||
onClose={() => {
|
||||
updateWorkspaceMutation.reset();
|
||||
}}
|
||||
onUpdate={(buildParameters) => {
|
||||
if (updateWorkspaceMutation.error instanceof MissingBuildParameters) {
|
||||
updateWorkspaceMutation.mutate(buildParameters);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<ChangeVersionDialog
|
||||
|
@ -251,16 +357,13 @@ export const WorkspaceReadyPage = ({
|
|||
}}
|
||||
onConfirm={(templateVersion) => {
|
||||
setChangeVersionDialogOpen(false);
|
||||
workspaceSend({
|
||||
type: "CHANGE_VERSION",
|
||||
templateVersionId: templateVersion.id,
|
||||
});
|
||||
changeVersionMutation.mutate({ versionId: templateVersion.id });
|
||||
}}
|
||||
/>
|
||||
<WarningDialog
|
||||
open={isConfirmingUpdate}
|
||||
onConfirm={() => {
|
||||
workspaceSend({ type: "UPDATE" });
|
||||
updateWorkspaceMutation.mutate(undefined);
|
||||
setIsConfirmingUpdate(false);
|
||||
}}
|
||||
onClose={() => setIsConfirmingUpdate(false)}
|
||||
|
@ -310,3 +413,38 @@ const WarningDialog: FC<
|
|||
> = (props) => {
|
||||
return <ConfirmDialog type="info" hideCancel={false} {...props} />;
|
||||
};
|
||||
|
||||
// You can see the favicon designs here: https://www.figma.com/file/YIGBkXUcnRGz2ZKNmLaJQf/Coder-v2-Design?node-id=560%3A620
|
||||
type FaviconType =
|
||||
| "favicon"
|
||||
| "favicon-success"
|
||||
| "favicon-error"
|
||||
| "favicon-warning"
|
||||
| "favicon-running";
|
||||
|
||||
const getFaviconByStatus = (build: TypesGen.WorkspaceBuild): FaviconType => {
|
||||
switch (build.status) {
|
||||
case undefined:
|
||||
return "favicon";
|
||||
case "running":
|
||||
return "favicon-success";
|
||||
case "starting":
|
||||
return "favicon-running";
|
||||
case "stopping":
|
||||
return "favicon-running";
|
||||
case "stopped":
|
||||
return "favicon";
|
||||
case "deleting":
|
||||
return "favicon";
|
||||
case "deleted":
|
||||
return "favicon";
|
||||
case "canceling":
|
||||
return "favicon-warning";
|
||||
case "canceled":
|
||||
return "favicon";
|
||||
case "failed":
|
||||
return "favicon-error";
|
||||
case "pending":
|
||||
return "favicon";
|
||||
}
|
||||
};
|
||||
|
|
|
@ -24,6 +24,8 @@ import {
|
|||
PopoverTrigger,
|
||||
usePopover,
|
||||
} from "components/Popover/Popover";
|
||||
import { workspaceQuota } from "api/queries/workspaceQuota";
|
||||
import { useQuery } from "react-query";
|
||||
|
||||
const Language = {
|
||||
workspaceDetails: "Workspace Details",
|
||||
|
@ -37,7 +39,6 @@ export interface WorkspaceStatsProps {
|
|||
maxDeadlineIncrease: number;
|
||||
maxDeadlineDecrease: number;
|
||||
canUpdateWorkspace: boolean;
|
||||
quotaBudget?: number;
|
||||
onDeadlinePlus: (hours: number) => void;
|
||||
onDeadlineMinus: (hours: number) => void;
|
||||
handleUpdate: () => void;
|
||||
|
@ -45,7 +46,6 @@ export interface WorkspaceStatsProps {
|
|||
|
||||
export const WorkspaceStats: FC<WorkspaceStatsProps> = ({
|
||||
workspace,
|
||||
quotaBudget,
|
||||
maxDeadlineDecrease,
|
||||
maxDeadlineIncrease,
|
||||
canUpdateWorkspace,
|
||||
|
@ -56,6 +56,8 @@ export const WorkspaceStats: FC<WorkspaceStatsProps> = ({
|
|||
const displayTemplateName = getDisplayWorkspaceTemplateName(workspace);
|
||||
const deadlinePlusEnabled = maxDeadlineIncrease >= 1;
|
||||
const deadlineMinusEnabled = maxDeadlineDecrease >= 1;
|
||||
const quotaQuery = useQuery(workspaceQuota(workspace.owner_name));
|
||||
const quotaBudget = quotaQuery.data?.budget;
|
||||
|
||||
const paperStyles = css`
|
||||
padding: 24px;
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
import { Workspace, Template } from "api/typesGenerated";
|
||||
|
||||
export const workspaceChecks = (workspace: Workspace, template: Template) =>
|
||||
({
|
||||
readWorkspace: {
|
||||
object: {
|
||||
resource_type: "workspace",
|
||||
resource_id: workspace.id,
|
||||
owner_id: workspace.owner_id,
|
||||
},
|
||||
action: "read",
|
||||
},
|
||||
updateWorkspace: {
|
||||
object: {
|
||||
resource_type: "workspace",
|
||||
resource_id: workspace.id,
|
||||
owner_id: workspace.owner_id,
|
||||
},
|
||||
action: "update",
|
||||
},
|
||||
updateTemplate: {
|
||||
object: {
|
||||
resource_type: "template",
|
||||
resource_id: template.id,
|
||||
},
|
||||
action: "update",
|
||||
},
|
||||
viewDeploymentValues: {
|
||||
object: {
|
||||
resource_type: "deployment_config",
|
||||
},
|
||||
action: "read",
|
||||
},
|
||||
}) as const;
|
||||
|
||||
export type WorkspacePermissions = Record<
|
||||
keyof ReturnType<typeof workspaceChecks>,
|
||||
boolean
|
||||
>;
|
|
@ -400,20 +400,6 @@ You can add instructions here
|
|||
archived: false,
|
||||
};
|
||||
|
||||
export const MockTemplateVersion3: TypesGen.TemplateVersion = {
|
||||
id: "test-template-version-3",
|
||||
created_at: "2022-05-17T17:39:01.382927298Z",
|
||||
updated_at: "2022-05-17T17:39:01.382927298Z",
|
||||
template_id: "test-template",
|
||||
job: MockProvisionerJob,
|
||||
name: "test-version-3",
|
||||
message: "first version",
|
||||
readme: "README",
|
||||
created_by: MockUser,
|
||||
warnings: ["UNSUPPORTED_WORKSPACES"],
|
||||
archived: false,
|
||||
};
|
||||
|
||||
export const MockTemplate: TypesGen.Template = {
|
||||
id: "test-template",
|
||||
created_at: "2022-05-17T17:39:01.382927298Z",
|
||||
|
|
|
@ -147,44 +147,6 @@ export const defaultWorkspaceExtension = (
|
|||
};
|
||||
};
|
||||
|
||||
// You can see the favicon designs here: https://www.figma.com/file/YIGBkXUcnRGz2ZKNmLaJQf/Coder-v2-Design?node-id=560%3A620
|
||||
|
||||
type FaviconType =
|
||||
| "favicon"
|
||||
| "favicon-success"
|
||||
| "favicon-error"
|
||||
| "favicon-warning"
|
||||
| "favicon-running";
|
||||
|
||||
export const getFaviconByStatus = (
|
||||
build: TypesGen.WorkspaceBuild,
|
||||
): FaviconType => {
|
||||
switch (build.status) {
|
||||
case undefined:
|
||||
return "favicon";
|
||||
case "running":
|
||||
return "favicon-success";
|
||||
case "starting":
|
||||
return "favicon-running";
|
||||
case "stopping":
|
||||
return "favicon-running";
|
||||
case "stopped":
|
||||
return "favicon";
|
||||
case "deleting":
|
||||
return "favicon";
|
||||
case "deleted":
|
||||
return "favicon";
|
||||
case "canceling":
|
||||
return "favicon-warning";
|
||||
case "canceled":
|
||||
return "favicon";
|
||||
case "failed":
|
||||
return "favicon-error";
|
||||
case "pending":
|
||||
return "favicon";
|
||||
}
|
||||
};
|
||||
|
||||
export const getDisplayWorkspaceTemplateName = (
|
||||
workspace: TypesGen.Workspace,
|
||||
): string => {
|
||||
|
|
|
@ -1,714 +0,0 @@
|
|||
import { getErrorMessage } from "api/errors";
|
||||
import { assign, createMachine } from "xstate";
|
||||
import * as API from "api/api";
|
||||
import * as TypesGen from "api/typesGenerated";
|
||||
import { displayError, displaySuccess } from "components/GlobalSnackbar/utils";
|
||||
|
||||
type Permissions = Record<keyof ReturnType<typeof permissionsToCheck>, boolean>;
|
||||
|
||||
export interface WorkspaceContext {
|
||||
// Initial data
|
||||
orgId: string;
|
||||
username: string;
|
||||
workspaceName: string;
|
||||
|
||||
error?: unknown;
|
||||
// our server side events instance
|
||||
eventSource?: EventSource;
|
||||
workspace?: TypesGen.Workspace;
|
||||
template?: TypesGen.Template;
|
||||
permissions?: Permissions;
|
||||
templateVersion?: TypesGen.TemplateVersion;
|
||||
deploymentValues?: TypesGen.DeploymentValues;
|
||||
build?: TypesGen.WorkspaceBuild;
|
||||
// Builds
|
||||
builds?: TypesGen.WorkspaceBuild[];
|
||||
getBuildsError?: unknown;
|
||||
missedParameters?: TypesGen.TemplateVersionParameter[];
|
||||
// error creating a new WorkspaceBuild
|
||||
buildError?: unknown;
|
||||
cancellationMessage?: TypesGen.Response;
|
||||
cancellationError?: unknown;
|
||||
// debug
|
||||
createBuildLogLevel?: TypesGen.CreateWorkspaceBuildRequest["log_level"];
|
||||
// SSH Config
|
||||
sshPrefix?: string;
|
||||
// Change version
|
||||
templateVersionIdToChange?: TypesGen.TemplateVersion["id"];
|
||||
}
|
||||
|
||||
export type WorkspaceEvent =
|
||||
| { type: "REFRESH_WORKSPACE"; data: TypesGen.ServerSentEvent["data"] }
|
||||
| { type: "START"; buildParameters?: TypesGen.WorkspaceBuildParameter[] }
|
||||
| { type: "STOP" }
|
||||
| { type: "ASK_DELETE" }
|
||||
| {
|
||||
type: "DELETE";
|
||||
orphan: TypesGen.CreateWorkspaceBuildRequest["orphan"];
|
||||
}
|
||||
| { type: "CANCEL_DELETE" }
|
||||
| { type: "UPDATE"; buildParameters?: TypesGen.WorkspaceBuildParameter[] }
|
||||
| {
|
||||
type: "CHANGE_VERSION";
|
||||
templateVersionId: TypesGen.TemplateVersion["id"];
|
||||
buildParameters?: TypesGen.WorkspaceBuildParameter[];
|
||||
}
|
||||
| { type: "CANCEL" }
|
||||
| {
|
||||
type: "REFRESH_TIMELINE";
|
||||
}
|
||||
| { type: "EVENT_SOURCE_ERROR"; error: unknown }
|
||||
| { type: "INCREASE_DEADLINE"; hours: number }
|
||||
| { type: "DECREASE_DEADLINE"; hours: number }
|
||||
| {
|
||||
type: "RETRY_BUILD";
|
||||
orphan?: TypesGen.CreateWorkspaceBuildRequest["orphan"];
|
||||
}
|
||||
| { type: "ACTIVATE" };
|
||||
|
||||
export const checks = {
|
||||
readWorkspace: "readWorkspace",
|
||||
updateWorkspace: "updateWorkspace",
|
||||
updateTemplate: "updateTemplate",
|
||||
viewDeploymentValues: "viewDeploymentValues",
|
||||
} as const;
|
||||
|
||||
const permissionsToCheck = (
|
||||
workspace: TypesGen.Workspace,
|
||||
template: TypesGen.Template,
|
||||
) =>
|
||||
({
|
||||
[checks.readWorkspace]: {
|
||||
object: {
|
||||
resource_type: "workspace",
|
||||
resource_id: workspace.id,
|
||||
owner_id: workspace.owner_id,
|
||||
},
|
||||
action: "read",
|
||||
},
|
||||
[checks.updateWorkspace]: {
|
||||
object: {
|
||||
resource_type: "workspace",
|
||||
resource_id: workspace.id,
|
||||
owner_id: workspace.owner_id,
|
||||
},
|
||||
action: "update",
|
||||
},
|
||||
[checks.updateTemplate]: {
|
||||
object: {
|
||||
resource_type: "template",
|
||||
resource_id: template.id,
|
||||
},
|
||||
action: "update",
|
||||
},
|
||||
[checks.viewDeploymentValues]: {
|
||||
object: {
|
||||
resource_type: "deployment_config",
|
||||
},
|
||||
action: "read",
|
||||
},
|
||||
}) as const;
|
||||
|
||||
export const workspaceMachine = createMachine(
|
||||
{
|
||||
id: "workspaceState",
|
||||
predictableActionArguments: true,
|
||||
tsTypes: {} as import("./workspaceXService.typegen").Typegen0,
|
||||
schema: {
|
||||
context: {} as WorkspaceContext,
|
||||
events: {} as WorkspaceEvent,
|
||||
services: {} as {
|
||||
loadInitialWorkspaceData: {
|
||||
data: Awaited<ReturnType<typeof loadInitialWorkspaceData>>;
|
||||
};
|
||||
updateWorkspace: {
|
||||
data: TypesGen.WorkspaceBuild;
|
||||
};
|
||||
changeWorkspaceVersion: {
|
||||
data: TypesGen.WorkspaceBuild;
|
||||
};
|
||||
startWorkspace: {
|
||||
data: TypesGen.WorkspaceBuild;
|
||||
};
|
||||
stopWorkspace: {
|
||||
data: TypesGen.WorkspaceBuild;
|
||||
};
|
||||
deleteWorkspace: {
|
||||
data: TypesGen.WorkspaceBuild;
|
||||
};
|
||||
cancelWorkspace: {
|
||||
data: TypesGen.Response;
|
||||
};
|
||||
activateWorkspace: {
|
||||
data: TypesGen.Response;
|
||||
};
|
||||
listening: {
|
||||
data: TypesGen.ServerSentEvent;
|
||||
};
|
||||
getBuilds: {
|
||||
data: TypesGen.WorkspaceBuild[];
|
||||
};
|
||||
getSSHPrefix: {
|
||||
data: TypesGen.SSHConfigResponse;
|
||||
};
|
||||
},
|
||||
},
|
||||
initial: "loadInitialData",
|
||||
states: {
|
||||
loadInitialData: {
|
||||
entry: ["clearContext"],
|
||||
invoke: {
|
||||
src: "loadInitialWorkspaceData",
|
||||
id: "loadInitialWorkspaceData",
|
||||
onDone: [{ target: "ready", actions: ["assignInitialData"] }],
|
||||
onError: [
|
||||
{
|
||||
actions: "assignError",
|
||||
target: "error",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
ready: {
|
||||
type: "parallel",
|
||||
on: {
|
||||
REFRESH_TIMELINE: {
|
||||
actions: ["refreshBuilds"],
|
||||
},
|
||||
},
|
||||
states: {
|
||||
listening: {
|
||||
initial: "gettingEvents",
|
||||
states: {
|
||||
gettingEvents: {
|
||||
entry: ["initializeEventSource"],
|
||||
exit: "closeEventSource",
|
||||
invoke: {
|
||||
src: "listening",
|
||||
id: "listening",
|
||||
},
|
||||
on: {
|
||||
REFRESH_WORKSPACE: {
|
||||
actions: ["refreshWorkspace"],
|
||||
},
|
||||
EVENT_SOURCE_ERROR: {
|
||||
target: "error",
|
||||
},
|
||||
},
|
||||
},
|
||||
error: {
|
||||
entry: "logWatchWorkspaceWarning",
|
||||
after: {
|
||||
"2000": {
|
||||
target: "gettingEvents",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
build: {
|
||||
initial: "idle",
|
||||
states: {
|
||||
idle: {
|
||||
on: {
|
||||
START: "requestingStart",
|
||||
STOP: "requestingStop",
|
||||
ASK_DELETE: "askingDelete",
|
||||
UPDATE: "requestingUpdate",
|
||||
CHANGE_VERSION: {
|
||||
target: "requestingChangeVersion",
|
||||
actions: ["assignTemplateVersionIdToChange"],
|
||||
},
|
||||
CANCEL: "requestingCancel",
|
||||
RETRY_BUILD: [
|
||||
{
|
||||
target: "requestingStart",
|
||||
cond: "lastBuildWasStarting",
|
||||
actions: ["enableDebugMode"],
|
||||
},
|
||||
{
|
||||
target: "requestingStop",
|
||||
cond: "lastBuildWasStopping",
|
||||
actions: ["enableDebugMode"],
|
||||
},
|
||||
{
|
||||
target: "requestingDelete",
|
||||
cond: "lastBuildWasDeleting",
|
||||
actions: ["enableDebugMode"],
|
||||
},
|
||||
],
|
||||
ACTIVATE: "requestingActivate",
|
||||
},
|
||||
},
|
||||
askingDelete: {
|
||||
on: {
|
||||
DELETE: {
|
||||
target: "requestingDelete",
|
||||
},
|
||||
CANCEL_DELETE: {
|
||||
target: "idle",
|
||||
},
|
||||
},
|
||||
},
|
||||
requestingUpdate: {
|
||||
entry: ["clearBuildError"],
|
||||
invoke: {
|
||||
src: "updateWorkspace",
|
||||
onDone: {
|
||||
target: "idle",
|
||||
actions: ["assignBuild"],
|
||||
},
|
||||
onError: [
|
||||
{
|
||||
target: "askingForMissedBuildParameters",
|
||||
cond: "isMissingBuildParameterError",
|
||||
actions: ["assignMissedParameters"],
|
||||
},
|
||||
{
|
||||
target: "idle",
|
||||
actions: ["assignBuildError"],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
requestingChangeVersion: {
|
||||
entry: ["clearBuildError"],
|
||||
invoke: {
|
||||
src: "changeWorkspaceVersion",
|
||||
onDone: {
|
||||
target: "idle",
|
||||
actions: ["assignBuild", "clearTemplateVersionIdToChange"],
|
||||
},
|
||||
onError: [
|
||||
{
|
||||
target: "askingForMissedBuildParameters",
|
||||
cond: "isMissingBuildParameterError",
|
||||
actions: ["assignMissedParameters"],
|
||||
},
|
||||
{
|
||||
target: "idle",
|
||||
actions: ["assignBuildError"],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
askingForMissedBuildParameters: {
|
||||
on: {
|
||||
CANCEL: "idle",
|
||||
UPDATE: [
|
||||
{
|
||||
target: "requestingChangeVersion",
|
||||
cond: "isChangingVersion",
|
||||
},
|
||||
{ target: "requestingUpdate" },
|
||||
],
|
||||
},
|
||||
},
|
||||
requestingStart: {
|
||||
entry: ["clearBuildError"],
|
||||
invoke: {
|
||||
src: "startWorkspace",
|
||||
id: "startWorkspace",
|
||||
onDone: [
|
||||
{
|
||||
actions: ["assignBuild", "disableDebugMode"],
|
||||
target: "idle",
|
||||
},
|
||||
],
|
||||
onError: [
|
||||
{
|
||||
actions: "assignBuildError",
|
||||
target: "idle",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
requestingStop: {
|
||||
entry: ["clearBuildError"],
|
||||
invoke: {
|
||||
src: "stopWorkspace",
|
||||
id: "stopWorkspace",
|
||||
onDone: [
|
||||
{
|
||||
actions: ["assignBuild", "disableDebugMode"],
|
||||
target: "idle",
|
||||
},
|
||||
],
|
||||
onError: [
|
||||
{
|
||||
actions: "assignBuildError",
|
||||
target: "idle",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
requestingDelete: {
|
||||
entry: ["clearBuildError"],
|
||||
invoke: {
|
||||
src: "deleteWorkspace",
|
||||
id: "deleteWorkspace",
|
||||
onDone: [
|
||||
{
|
||||
actions: ["assignBuild", "disableDebugMode"],
|
||||
target: "idle",
|
||||
},
|
||||
],
|
||||
onError: [
|
||||
{
|
||||
actions: "assignBuildError",
|
||||
target: "idle",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
requestingCancel: {
|
||||
entry: ["clearCancellationMessage", "clearCancellationError"],
|
||||
invoke: {
|
||||
src: "cancelWorkspace",
|
||||
id: "cancelWorkspace",
|
||||
onDone: [
|
||||
{
|
||||
actions: [
|
||||
"assignCancellationMessage",
|
||||
"displayCancellationMessage",
|
||||
],
|
||||
target: "idle",
|
||||
},
|
||||
],
|
||||
onError: [
|
||||
{
|
||||
actions: "assignCancellationError",
|
||||
target: "idle",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
requestingActivate: {
|
||||
entry: ["clearBuildError"],
|
||||
invoke: {
|
||||
src: "activateWorkspace",
|
||||
id: "activateWorkspace",
|
||||
onDone: "idle",
|
||||
onError: {
|
||||
target: "idle",
|
||||
actions: ["displayActivateError"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
sshConfig: {
|
||||
initial: "gettingSshConfig",
|
||||
states: {
|
||||
gettingSshConfig: {
|
||||
invoke: {
|
||||
src: "getSSHPrefix",
|
||||
onDone: {
|
||||
target: "success",
|
||||
actions: ["assignSSHPrefix"],
|
||||
},
|
||||
onError: {
|
||||
target: "error",
|
||||
actions: ["displaySSHPrefixError"],
|
||||
},
|
||||
},
|
||||
},
|
||||
error: {
|
||||
type: "final",
|
||||
},
|
||||
success: {
|
||||
type: "final",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
error: {
|
||||
type: "final",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
actions: {
|
||||
// Clear data about an old workspace when looking at a new one
|
||||
clearContext: () =>
|
||||
assign({
|
||||
workspace: undefined,
|
||||
template: undefined,
|
||||
build: undefined,
|
||||
permissions: undefined,
|
||||
eventSource: undefined,
|
||||
}),
|
||||
assignInitialData: assign({
|
||||
workspace: (_, event) => event.data.workspace,
|
||||
template: (_, event) => event.data.template,
|
||||
templateVersion: (_, event) => event.data.templateVersion,
|
||||
permissions: (_, event) => event.data.permissions as Permissions,
|
||||
deploymentValues: (_, event) => event.data.deploymentValues,
|
||||
}),
|
||||
assignError: assign({
|
||||
error: (_, event) => event.data,
|
||||
}),
|
||||
assignBuild: assign({
|
||||
build: (_, event) => event.data,
|
||||
}),
|
||||
assignBuildError: assign({
|
||||
buildError: (_, event) => event.data,
|
||||
}),
|
||||
clearBuildError: assign({
|
||||
buildError: (_) => undefined,
|
||||
}),
|
||||
assignCancellationMessage: assign({
|
||||
cancellationMessage: (_, event) => event.data,
|
||||
}),
|
||||
clearCancellationMessage: assign({
|
||||
cancellationMessage: (_) => undefined,
|
||||
}),
|
||||
displayCancellationMessage: (context) => {
|
||||
if (context.cancellationMessage) {
|
||||
displaySuccess(context.cancellationMessage.message);
|
||||
}
|
||||
},
|
||||
assignCancellationError: assign({
|
||||
cancellationError: (_, event) => event.data,
|
||||
}),
|
||||
clearCancellationError: assign({
|
||||
cancellationError: (_) => undefined,
|
||||
}),
|
||||
// SSE related actions
|
||||
// open a new EventSource so we can stream SSE
|
||||
initializeEventSource: assign({
|
||||
eventSource: (context) =>
|
||||
context.workspace && API.watchWorkspace(context.workspace.id),
|
||||
}),
|
||||
closeEventSource: (context) =>
|
||||
context.eventSource && context.eventSource.close(),
|
||||
refreshWorkspace: assign({
|
||||
workspace: (_, event) => event.data,
|
||||
}),
|
||||
logWatchWorkspaceWarning: (_, event) => {
|
||||
console.error("Watch workspace error:", event);
|
||||
},
|
||||
// SSH
|
||||
assignSSHPrefix: assign({
|
||||
sshPrefix: (_, { data }) => data.hostname_prefix,
|
||||
}),
|
||||
displaySSHPrefixError: (_, { data }) => {
|
||||
const message = getErrorMessage(
|
||||
data,
|
||||
"Error getting the deployment ssh configuration.",
|
||||
);
|
||||
displayError(message);
|
||||
},
|
||||
displayActivateError: (_, { data }) => {
|
||||
const message = getErrorMessage(data, "Error activate workspace.");
|
||||
displayError(message);
|
||||
},
|
||||
assignMissedParameters: assign({
|
||||
missedParameters: (_, { data }) => {
|
||||
if (!(data instanceof API.MissingBuildParameters)) {
|
||||
throw new Error("data is not a MissingBuildParameters error");
|
||||
}
|
||||
return data.parameters;
|
||||
},
|
||||
}),
|
||||
// Debug mode when build fails
|
||||
enableDebugMode: assign({ createBuildLogLevel: (_) => "debug" as const }),
|
||||
disableDebugMode: assign({ createBuildLogLevel: (_) => undefined }),
|
||||
// Change version
|
||||
assignTemplateVersionIdToChange: assign({
|
||||
templateVersionIdToChange: (_, { templateVersionId }) =>
|
||||
templateVersionId,
|
||||
}),
|
||||
clearTemplateVersionIdToChange: assign({
|
||||
templateVersionIdToChange: (_) => undefined,
|
||||
}),
|
||||
},
|
||||
guards: {
|
||||
isMissingBuildParameterError: (_, { data }) => {
|
||||
return data instanceof API.MissingBuildParameters;
|
||||
},
|
||||
lastBuildWasStarting: ({ workspace }) => {
|
||||
return workspace?.latest_build.transition === "start";
|
||||
},
|
||||
lastBuildWasStopping: ({ workspace }) => {
|
||||
return workspace?.latest_build.transition === "stop";
|
||||
},
|
||||
lastBuildWasDeleting: ({ workspace }) => {
|
||||
return workspace?.latest_build.transition === "delete";
|
||||
},
|
||||
isChangingVersion: ({ templateVersionIdToChange }) =>
|
||||
Boolean(templateVersionIdToChange),
|
||||
},
|
||||
services: {
|
||||
loadInitialWorkspaceData,
|
||||
updateWorkspace:
|
||||
({ workspace }, { buildParameters }) =>
|
||||
async (send) => {
|
||||
if (!workspace) {
|
||||
throw new Error("Workspace is not set");
|
||||
}
|
||||
const build = await API.updateWorkspace(workspace, buildParameters);
|
||||
send({ type: "REFRESH_TIMELINE" });
|
||||
return build;
|
||||
},
|
||||
changeWorkspaceVersion:
|
||||
({ workspace, templateVersionIdToChange }, { buildParameters }) =>
|
||||
async (send) => {
|
||||
if (!workspace) {
|
||||
throw new Error("Workspace is not set");
|
||||
}
|
||||
if (!templateVersionIdToChange) {
|
||||
throw new Error("Template version id to change is not set");
|
||||
}
|
||||
const build = await API.changeWorkspaceVersion(
|
||||
workspace,
|
||||
templateVersionIdToChange,
|
||||
buildParameters,
|
||||
);
|
||||
send({ type: "REFRESH_TIMELINE" });
|
||||
return build;
|
||||
},
|
||||
startWorkspace: (context, data) => async (send) => {
|
||||
if (context.workspace) {
|
||||
const startWorkspacePromise = await API.startWorkspace(
|
||||
context.workspace.id,
|
||||
context.workspace.latest_build.template_version_id,
|
||||
context.createBuildLogLevel,
|
||||
"buildParameters" in data ? data.buildParameters : undefined,
|
||||
);
|
||||
send({ type: "REFRESH_TIMELINE" });
|
||||
return startWorkspacePromise;
|
||||
} else {
|
||||
throw Error("Cannot start workspace without workspace id");
|
||||
}
|
||||
},
|
||||
stopWorkspace: (context) => async (send) => {
|
||||
if (context.workspace) {
|
||||
const stopWorkspacePromise = await API.stopWorkspace(
|
||||
context.workspace.id,
|
||||
context.createBuildLogLevel,
|
||||
);
|
||||
send({ type: "REFRESH_TIMELINE" });
|
||||
return stopWorkspacePromise;
|
||||
} else {
|
||||
throw Error("Cannot stop workspace without workspace id");
|
||||
}
|
||||
},
|
||||
deleteWorkspace: (context, data) => async (send) => {
|
||||
if (context.workspace) {
|
||||
const deleteWorkspacePromise = await API.deleteWorkspace(
|
||||
context.workspace.id,
|
||||
{
|
||||
log_level: context.createBuildLogLevel,
|
||||
orphan: data.orphan,
|
||||
},
|
||||
);
|
||||
send({ type: "REFRESH_TIMELINE" });
|
||||
return deleteWorkspacePromise;
|
||||
} else {
|
||||
throw Error("Cannot delete workspace without workspace id");
|
||||
}
|
||||
},
|
||||
cancelWorkspace: (context) => async (send) => {
|
||||
if (context.workspace) {
|
||||
const cancelWorkspacePromise = await API.cancelWorkspaceBuild(
|
||||
context.workspace.latest_build.id,
|
||||
);
|
||||
send({ type: "REFRESH_TIMELINE" });
|
||||
return cancelWorkspacePromise;
|
||||
} else {
|
||||
throw Error("Cannot cancel workspace without build id");
|
||||
}
|
||||
},
|
||||
activateWorkspace: (context) => async (send) => {
|
||||
if (context.workspace) {
|
||||
const activateWorkspacePromise = await API.updateWorkspaceDormancy(
|
||||
context.workspace.id,
|
||||
false,
|
||||
);
|
||||
send({ type: "REFRESH_WORKSPACE", data: activateWorkspacePromise });
|
||||
return activateWorkspacePromise;
|
||||
} else {
|
||||
throw Error("Cannot activate workspace without workspace id");
|
||||
}
|
||||
},
|
||||
listening: (context) => (send) => {
|
||||
if (!context.eventSource) {
|
||||
send({ type: "EVENT_SOURCE_ERROR", error: "error initializing sse" });
|
||||
return;
|
||||
}
|
||||
|
||||
context.eventSource.addEventListener("data", (event) => {
|
||||
const newWorkspaceData = JSON.parse(event.data) as TypesGen.Workspace;
|
||||
// refresh our workspace with each SSE
|
||||
send({ type: "REFRESH_WORKSPACE", data: newWorkspaceData });
|
||||
|
||||
const currentWorkspace = context.workspace!;
|
||||
const hasNewBuild =
|
||||
newWorkspaceData.latest_build.id !==
|
||||
currentWorkspace.latest_build.id;
|
||||
const lastBuildHasChanged =
|
||||
newWorkspaceData.latest_build.status !==
|
||||
currentWorkspace.latest_build.status;
|
||||
|
||||
if (hasNewBuild || lastBuildHasChanged) {
|
||||
send({ type: "REFRESH_TIMELINE" });
|
||||
}
|
||||
});
|
||||
|
||||
// handle any error events returned by our sse
|
||||
context.eventSource.addEventListener("error", (event) => {
|
||||
send({ type: "EVENT_SOURCE_ERROR", error: event });
|
||||
});
|
||||
|
||||
// handle any sse implementation exceptions
|
||||
context.eventSource.onerror = () => {
|
||||
send({ type: "EVENT_SOURCE_ERROR", error: "sse error" });
|
||||
};
|
||||
|
||||
return () => {
|
||||
context.eventSource?.close();
|
||||
};
|
||||
},
|
||||
getSSHPrefix: async () => {
|
||||
return API.getDeploymentSSHConfig();
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
async function loadInitialWorkspaceData({
|
||||
orgId,
|
||||
username,
|
||||
workspaceName,
|
||||
}: WorkspaceContext) {
|
||||
const workspace = await API.getWorkspaceByOwnerAndName(
|
||||
username,
|
||||
workspaceName,
|
||||
{
|
||||
include_deleted: true,
|
||||
},
|
||||
);
|
||||
const template = await API.getTemplateByName(orgId, workspace.template_name);
|
||||
const [templateVersion, permissions] = await Promise.all([
|
||||
API.getTemplateVersion(template.active_version_id),
|
||||
API.checkAuthorization({
|
||||
checks: permissionsToCheck(workspace, template),
|
||||
}),
|
||||
]);
|
||||
|
||||
const canViewDeploymentValues = Boolean(
|
||||
(permissions as Permissions)?.viewDeploymentValues,
|
||||
);
|
||||
const deploymentValues = canViewDeploymentValues
|
||||
? (await API.getDeploymentConfig())?.config
|
||||
: undefined;
|
||||
return {
|
||||
workspace,
|
||||
template,
|
||||
templateVersion,
|
||||
permissions,
|
||||
deploymentValues,
|
||||
};
|
||||
}
|
Loading…
Reference in New Issue