mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat(site): Add change version for template admins (#6988)
This commit is contained in:
+37
-2
@@ -1,4 +1,4 @@
|
||||
import axios, { AxiosRequestHeaders } from "axios"
|
||||
import axios from "axios"
|
||||
import dayjs from "dayjs"
|
||||
import * as Types from "./types"
|
||||
import { DeploymentConfig } from "./types"
|
||||
@@ -65,7 +65,7 @@ if (token !== null && token.getAttribute("content") !== null) {
|
||||
}
|
||||
}
|
||||
|
||||
const CONTENT_TYPE_JSON: AxiosRequestHeaders = {
|
||||
const CONTENT_TYPE_JSON = {
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
|
||||
@@ -975,6 +975,41 @@ export class MissingBuildParameters extends Error {
|
||||
}
|
||||
}
|
||||
|
||||
/** Steps to change the workspace version
|
||||
* - Get the latest template to access the latest active version
|
||||
* - Get the current build parameters
|
||||
* - Get the template parameters
|
||||
* - Update the build parameters and check if there are missed parameters for the new version
|
||||
* - If there are missing parameters raise an error
|
||||
* - Create a build with the version and updated build parameters
|
||||
*/
|
||||
export const changeWorkspaceVersion = async (
|
||||
workspace: TypesGen.Workspace,
|
||||
templateVersionId: string,
|
||||
newBuildParameters: TypesGen.WorkspaceBuildParameter[] = [],
|
||||
): Promise<TypesGen.WorkspaceBuild> => {
|
||||
const [currentBuildParameters, templateParameters] = await Promise.all([
|
||||
getWorkspaceBuildParameters(workspace.latest_build.id),
|
||||
getTemplateVersionRichParameters(templateVersionId),
|
||||
])
|
||||
|
||||
const missingParameters = getMissingParameters(
|
||||
currentBuildParameters,
|
||||
newBuildParameters,
|
||||
templateParameters,
|
||||
)
|
||||
|
||||
if (missingParameters.length > 0) {
|
||||
throw new MissingBuildParameters(missingParameters)
|
||||
}
|
||||
|
||||
return postWorkspaceBuild(workspace.id, {
|
||||
transition: "start",
|
||||
template_version_id: templateVersionId,
|
||||
rich_parameter_values: newBuildParameters,
|
||||
})
|
||||
}
|
||||
|
||||
/** Steps to update the workspace
|
||||
* - Get the latest template to access the latest active version
|
||||
* - Get the current build parameters
|
||||
|
||||
@@ -31,7 +31,7 @@ export const AvatarData: FC<PropsWithChildren<AvatarDataProps>> = ({
|
||||
>
|
||||
{avatar}
|
||||
|
||||
<Stack spacing={0}>
|
||||
<Stack spacing={0} className={styles.info}>
|
||||
<span className={styles.title}>{title}</span>
|
||||
{subtitle && <span className={styles.subtitle}>{subtitle}</span>}
|
||||
</Stack>
|
||||
@@ -42,6 +42,11 @@ export const AvatarData: FC<PropsWithChildren<AvatarDataProps>> = ({
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
root: {
|
||||
minHeight: theme.spacing(5), // Make it predictable for the skeleton
|
||||
width: "100%",
|
||||
},
|
||||
|
||||
info: {
|
||||
width: "100%",
|
||||
},
|
||||
|
||||
title: {
|
||||
|
||||
@@ -4,6 +4,7 @@ import { makeStyles } from "@material-ui/core/styles"
|
||||
import BlockIcon from "@material-ui/icons/Block"
|
||||
import CloudQueueIcon from "@material-ui/icons/CloudQueue"
|
||||
import SettingsOutlined from "@material-ui/icons/SettingsOutlined"
|
||||
import HistoryOutlined from "@material-ui/icons/HistoryOutlined"
|
||||
import CropSquareIcon from "@material-ui/icons/CropSquare"
|
||||
import DeleteOutlineIcon from "@material-ui/icons/DeleteOutline"
|
||||
import PlayCircleOutlineIcon from "@material-ui/icons/PlayCircleOutline"
|
||||
@@ -53,6 +54,23 @@ export const SettingsButton: FC<React.PropsWithChildren<WorkspaceAction>> = ({
|
||||
)
|
||||
}
|
||||
|
||||
export const ChangeVersionButton: FC<
|
||||
React.PropsWithChildren<WorkspaceAction>
|
||||
> = ({ handleAction }) => {
|
||||
const styles = useStyles()
|
||||
|
||||
return (
|
||||
<Button
|
||||
variant="outlined"
|
||||
className={styles.actionButton}
|
||||
startIcon={<HistoryOutlined />}
|
||||
onClick={handleAction}
|
||||
>
|
||||
Change version
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
export const StartButton: FC<React.PropsWithChildren<WorkspaceAction>> = ({
|
||||
handleAction,
|
||||
}) => {
|
||||
|
||||
@@ -26,7 +26,6 @@ export const UserAutocomplete: FC<UserAutocompleteProps> = ({
|
||||
}) => {
|
||||
const styles = useStyles()
|
||||
const { t } = useTranslation("common")
|
||||
|
||||
const [isAutocompleteOpen, setIsAutocompleteOpen] = useState(false)
|
||||
const [searchState, sendSearch] = useMachine(searchUserMachine)
|
||||
const { searchResults } = searchState.context
|
||||
|
||||
@@ -47,12 +47,14 @@ export interface WorkspaceProps {
|
||||
handleUpdate: () => void
|
||||
handleCancel: () => void
|
||||
handleSettings: () => void
|
||||
handleChangeVersion: () => void
|
||||
isUpdating: boolean
|
||||
workspace: TypesGen.Workspace
|
||||
resources?: TypesGen.WorkspaceResource[]
|
||||
builds?: TypesGen.WorkspaceBuild[]
|
||||
canUpdateWorkspace: boolean
|
||||
canUpdateTemplate: boolean
|
||||
canChangeVersions: boolean
|
||||
hideSSHButton?: boolean
|
||||
hideVSCodeDesktopButton?: boolean
|
||||
workspaceErrors: Partial<Record<WorkspaceErrors, Error | unknown>>
|
||||
@@ -76,12 +78,14 @@ export const Workspace: FC<React.PropsWithChildren<WorkspaceProps>> = ({
|
||||
handleUpdate,
|
||||
handleCancel,
|
||||
handleSettings,
|
||||
handleChangeVersion,
|
||||
workspace,
|
||||
isUpdating,
|
||||
resources,
|
||||
builds,
|
||||
canUpdateWorkspace,
|
||||
canUpdateTemplate,
|
||||
canChangeVersions,
|
||||
workspaceErrors,
|
||||
hideSSHButton,
|
||||
hideVSCodeDesktopButton,
|
||||
@@ -142,6 +146,8 @@ export const Workspace: FC<React.PropsWithChildren<WorkspaceProps>> = ({
|
||||
handleUpdate={handleUpdate}
|
||||
handleCancel={handleCancel}
|
||||
handleSettings={handleSettings}
|
||||
handleChangeVersion={handleChangeVersion}
|
||||
canChangeVersions={canChangeVersions}
|
||||
isUpdating={isUpdating}
|
||||
/>
|
||||
</Stack>
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useTranslation } from "react-i18next"
|
||||
import { WorkspaceStatus } from "../../api/typesGenerated"
|
||||
import {
|
||||
ActionLoadingButton,
|
||||
ChangeVersionButton,
|
||||
DeleteButton,
|
||||
DisabledButton,
|
||||
SettingsButton,
|
||||
@@ -22,8 +23,10 @@ export interface WorkspaceActionsProps {
|
||||
handleUpdate: () => void
|
||||
handleCancel: () => void
|
||||
handleSettings: () => void
|
||||
handleChangeVersion: () => void
|
||||
isUpdating: boolean
|
||||
children?: ReactNode
|
||||
canChangeVersions: boolean
|
||||
}
|
||||
|
||||
export const WorkspaceActions: FC<WorkspaceActionsProps> = ({
|
||||
@@ -35,7 +38,9 @@ export const WorkspaceActions: FC<WorkspaceActionsProps> = ({
|
||||
handleUpdate,
|
||||
handleCancel,
|
||||
handleSettings,
|
||||
handleChangeVersion,
|
||||
isUpdating,
|
||||
canChangeVersions,
|
||||
}) => {
|
||||
const { t } = useTranslation("workspacePage")
|
||||
const { canCancel, canAcceptJobs, actions } = buttonAbilities(workspaceStatus)
|
||||
@@ -50,6 +55,11 @@ export const WorkspaceActions: FC<WorkspaceActionsProps> = ({
|
||||
[ButtonTypesEnum.settings]: (
|
||||
<SettingsButton handleAction={handleSettings} />
|
||||
),
|
||||
[ButtonTypesEnum.changeVersion]: canChangeVersions ? (
|
||||
<ChangeVersionButton handleAction={handleChangeVersion} />
|
||||
) : (
|
||||
<></>
|
||||
),
|
||||
[ButtonTypesEnum.start]: <StartButton handleAction={handleStart} />,
|
||||
[ButtonTypesEnum.starting]: (
|
||||
<ActionLoadingButton label={t("actionButton.starting")} />
|
||||
|
||||
@@ -12,6 +12,7 @@ export enum ButtonTypesEnum {
|
||||
update = "update",
|
||||
updating = "updating",
|
||||
settings = "settings",
|
||||
changeVersion = "changeVersion",
|
||||
// disabled buttons
|
||||
canceling = "canceling",
|
||||
deleted = "deleted",
|
||||
@@ -44,6 +45,7 @@ const statusToAbilities: Record<WorkspaceStatus, WorkspaceAbilities> = {
|
||||
actions: [
|
||||
ButtonTypesEnum.stop,
|
||||
ButtonTypesEnum.settings,
|
||||
ButtonTypesEnum.changeVersion,
|
||||
ButtonTypesEnum.delete,
|
||||
],
|
||||
canCancel: false,
|
||||
@@ -58,6 +60,7 @@ const statusToAbilities: Record<WorkspaceStatus, WorkspaceAbilities> = {
|
||||
actions: [
|
||||
ButtonTypesEnum.start,
|
||||
ButtonTypesEnum.settings,
|
||||
ButtonTypesEnum.changeVersion,
|
||||
ButtonTypesEnum.delete,
|
||||
],
|
||||
canCancel: false,
|
||||
@@ -68,6 +71,7 @@ const statusToAbilities: Record<WorkspaceStatus, WorkspaceAbilities> = {
|
||||
ButtonTypesEnum.start,
|
||||
ButtonTypesEnum.stop,
|
||||
ButtonTypesEnum.settings,
|
||||
ButtonTypesEnum.changeVersion,
|
||||
ButtonTypesEnum.delete,
|
||||
],
|
||||
canCancel: false,
|
||||
@@ -79,6 +83,7 @@ const statusToAbilities: Record<WorkspaceStatus, WorkspaceAbilities> = {
|
||||
ButtonTypesEnum.start,
|
||||
ButtonTypesEnum.stop,
|
||||
ButtonTypesEnum.settings,
|
||||
ButtonTypesEnum.changeVersion,
|
||||
ButtonTypesEnum.delete,
|
||||
],
|
||||
canCancel: false,
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
import { DialogProps } from "components/Dialogs/Dialog"
|
||||
import { FC, useRef, useState } from "react"
|
||||
import { FormFields } from "components/Form/Form"
|
||||
import TextField from "@material-ui/core/TextField"
|
||||
import { ConfirmDialog } from "components/Dialogs/ConfirmDialog/ConfirmDialog"
|
||||
import { Stack } from "components/Stack/Stack"
|
||||
import { Template, TemplateVersion } from "api/typesGenerated"
|
||||
import { Loader } from "components/Loader/Loader"
|
||||
import Autocomplete from "@material-ui/lab/Autocomplete"
|
||||
import { createDayString } from "util/createDayString"
|
||||
import { AvatarData } from "components/AvatarData/AvatarData"
|
||||
import { Pill } from "components/Pill/Pill"
|
||||
import { Avatar } from "components/Avatar/Avatar"
|
||||
import CircularProgress from "@material-ui/core/CircularProgress"
|
||||
|
||||
export type ChangeVersionDialogProps = DialogProps & {
|
||||
template: Template | undefined
|
||||
templateVersions: TemplateVersion[] | undefined
|
||||
defaultTemplateVersion: TemplateVersion | undefined
|
||||
onClose: () => void
|
||||
onConfirm: (templateVersion: TemplateVersion) => void
|
||||
}
|
||||
|
||||
export const ChangeVersionDialog: FC<ChangeVersionDialogProps> = ({
|
||||
onConfirm,
|
||||
onClose,
|
||||
template,
|
||||
templateVersions,
|
||||
defaultTemplateVersion,
|
||||
...dialogProps
|
||||
}) => {
|
||||
const [isAutocompleteOpen, setIsAutocompleteOpen] = useState(false)
|
||||
const selectedTemplateVersion = useRef<TemplateVersion | undefined>()
|
||||
|
||||
return (
|
||||
<ConfirmDialog
|
||||
{...dialogProps}
|
||||
onClose={onClose}
|
||||
onConfirm={() => {
|
||||
if (selectedTemplateVersion.current) {
|
||||
onConfirm(selectedTemplateVersion.current)
|
||||
}
|
||||
}}
|
||||
hideCancel={false}
|
||||
type="success"
|
||||
cancelText="Cancel"
|
||||
confirmText="Change"
|
||||
title="Change version"
|
||||
description={
|
||||
<Stack>
|
||||
<p>You are about to change the version of this workspace.</p>
|
||||
{templateVersions ? (
|
||||
<FormFields>
|
||||
<Autocomplete
|
||||
disableClearable
|
||||
options={templateVersions}
|
||||
defaultValue={defaultTemplateVersion}
|
||||
id="template-version-autocomplete"
|
||||
open={isAutocompleteOpen}
|
||||
onChange={(_, newTemplateVersion) => {
|
||||
selectedTemplateVersion.current =
|
||||
newTemplateVersion ?? undefined
|
||||
}}
|
||||
onOpen={() => {
|
||||
setIsAutocompleteOpen(true)
|
||||
}}
|
||||
onClose={() => {
|
||||
setIsAutocompleteOpen(false)
|
||||
}}
|
||||
getOptionSelected={(
|
||||
option: TemplateVersion,
|
||||
value: TemplateVersion,
|
||||
) => option.id === value.id}
|
||||
getOptionLabel={(option) => option.name}
|
||||
renderOption={(option: TemplateVersion) => (
|
||||
<AvatarData
|
||||
avatar={
|
||||
<Avatar src={option.created_by.avatar_url}>
|
||||
{option.name}
|
||||
</Avatar>
|
||||
}
|
||||
title={
|
||||
<Stack
|
||||
direction="row"
|
||||
justifyContent="space-between"
|
||||
style={{ width: "100%" }}
|
||||
>
|
||||
{option.name}
|
||||
{template?.active_version_id === option.id && (
|
||||
<Pill text="Active" type="success" />
|
||||
)}
|
||||
</Stack>
|
||||
}
|
||||
subtitle={createDayString(option.created_at)}
|
||||
/>
|
||||
)}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
{...params}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
placeholder="Template version name"
|
||||
InputProps={{
|
||||
...params.InputProps,
|
||||
endAdornment: (
|
||||
<>
|
||||
{!templateVersions ? (
|
||||
<CircularProgress size={16} />
|
||||
) : null}
|
||||
{params.InputProps.endAdornment}
|
||||
</>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</FormFields>
|
||||
) : (
|
||||
<Loader />
|
||||
)}
|
||||
</Stack>
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -3,7 +3,7 @@ import { ProvisionerJobLog } from "api/typesGenerated"
|
||||
import { useDashboard } from "components/Dashboard/DashboardProvider"
|
||||
import dayjs from "dayjs"
|
||||
import { useFeatureVisibility } from "hooks/useFeatureVisibility"
|
||||
import { useEffect } from "react"
|
||||
import { useEffect, useState } from "react"
|
||||
import { Helmet } from "react-helmet-async"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { useNavigate } from "react-router-dom"
|
||||
@@ -27,6 +27,9 @@ import {
|
||||
workspaceMachine,
|
||||
} from "../../xServices/workspace/workspaceXService"
|
||||
import { UpdateBuildParametersDialog } from "./UpdateBuildParametersDialog"
|
||||
import { ChangeVersionDialog } from "./ChangeVersionDialog"
|
||||
import { useQuery } from "@tanstack/react-query"
|
||||
import { getTemplateVersions } from "api/api"
|
||||
|
||||
interface WorkspaceReadyPageProps {
|
||||
workspaceState: StateFrom<typeof workspaceMachine>
|
||||
@@ -67,6 +70,13 @@ export const WorkspaceReadyPage = ({
|
||||
const { t } = useTranslation("workspacePage")
|
||||
const favicon = getFaviconByStatus(workspace.latest_build)
|
||||
const navigate = useNavigate()
|
||||
const [changeVersionDialogOpen, setChangeVersionDialogOpen] = useState(false)
|
||||
const { data: templateVersions } = useQuery({
|
||||
queryKey: ["template", "versions", workspace.template_id],
|
||||
queryFn: () => getTemplateVersions(workspace.template_id),
|
||||
enabled: changeVersionDialogOpen,
|
||||
})
|
||||
const dashboard = useDashboard()
|
||||
|
||||
// keep banner machine in sync with workspace
|
||||
useEffect(() => {
|
||||
@@ -119,10 +129,16 @@ export const WorkspaceReadyPage = ({
|
||||
handleCancel={() => workspaceSend({ type: "CANCEL" })}
|
||||
handleSettings={() => navigate("settings")}
|
||||
handleBuildRetry={() => workspaceSend({ type: "RETRY_BUILD" })}
|
||||
handleChangeVersion={() => {
|
||||
setChangeVersionDialogOpen(true)
|
||||
}}
|
||||
resources={workspace.latest_build.resources}
|
||||
builds={builds}
|
||||
canUpdateWorkspace={canUpdateWorkspace}
|
||||
canUpdateTemplate={canUpdateTemplate}
|
||||
canChangeVersions={
|
||||
canUpdateTemplate && dashboard.experiments.includes("template_editor")
|
||||
}
|
||||
hideSSHButton={featureVisibility["browser_only"]}
|
||||
hideVSCodeDesktopButton={featureVisibility["browser_only"]}
|
||||
workspaceErrors={{
|
||||
@@ -160,6 +176,24 @@ export const WorkspaceReadyPage = ({
|
||||
workspaceSend({ type: "UPDATE", buildParameters })
|
||||
}}
|
||||
/>
|
||||
<ChangeVersionDialog
|
||||
templateVersions={templateVersions?.reverse()}
|
||||
template={template}
|
||||
defaultTemplateVersion={templateVersions?.find(
|
||||
(v) => workspace.latest_build.template_version_id === v.id,
|
||||
)}
|
||||
open={changeVersionDialogOpen}
|
||||
onClose={() => {
|
||||
setChangeVersionDialogOpen(false)
|
||||
}}
|
||||
onConfirm={(templateVersion) => {
|
||||
setChangeVersionDialogOpen(false)
|
||||
workspaceSend({
|
||||
type: "CHANGE_VERSION",
|
||||
templateVersionId: templateVersion.id,
|
||||
})
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -80,6 +80,8 @@ export interface WorkspaceContext {
|
||||
createBuildLogLevel?: TypesGen.CreateWorkspaceBuildRequest["log_level"]
|
||||
// SSH Config
|
||||
sshPrefix?: string
|
||||
// Change version
|
||||
templateVersionIdToChange?: TypesGen.TemplateVersion["id"]
|
||||
}
|
||||
|
||||
export type WorkspaceEvent =
|
||||
@@ -90,6 +92,11 @@ export type WorkspaceEvent =
|
||||
| { type: "DELETE" }
|
||||
| { type: "CANCEL_DELETE" }
|
||||
| { type: "UPDATE"; buildParameters?: TypesGen.WorkspaceBuildParameter[] }
|
||||
| {
|
||||
type: "CHANGE_VERSION"
|
||||
templateVersionId: TypesGen.TemplateVersion["id"]
|
||||
buildParameters?: TypesGen.WorkspaceBuildParameter[]
|
||||
}
|
||||
| { type: "CANCEL" }
|
||||
| {
|
||||
type: "REFRESH_TIMELINE"
|
||||
@@ -157,6 +164,9 @@ export const workspaceMachine = createMachine(
|
||||
updateWorkspace: {
|
||||
data: TypesGen.WorkspaceBuild
|
||||
}
|
||||
changeWorkspaceVersion: {
|
||||
data: TypesGen.WorkspaceBuild
|
||||
}
|
||||
startWorkspace: {
|
||||
data: TypesGen.WorkspaceBuild
|
||||
}
|
||||
@@ -290,6 +300,10 @@ export const workspaceMachine = createMachine(
|
||||
STOP: "requestingStop",
|
||||
ASK_DELETE: "askingDelete",
|
||||
UPDATE: "requestingUpdate",
|
||||
CHANGE_VERSION: {
|
||||
target: "requestingChangeVersion",
|
||||
actions: ["assignTemplateVersionIdToChange"],
|
||||
},
|
||||
CANCEL: "requestingCancel",
|
||||
RETRY_BUILD: [
|
||||
{
|
||||
@@ -341,10 +355,37 @@ export const workspaceMachine = createMachine(
|
||||
],
|
||||
},
|
||||
},
|
||||
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: "requestingUpdate",
|
||||
UPDATE: [
|
||||
{
|
||||
target: "requestingChangeVersion",
|
||||
cond: "isChangingVersion",
|
||||
},
|
||||
{ target: "requestingUpdate" },
|
||||
],
|
||||
},
|
||||
},
|
||||
requestingStart: {
|
||||
@@ -669,6 +710,14 @@ export const workspaceMachine = createMachine(
|
||||
// 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: {
|
||||
moreBuildsAvailable,
|
||||
@@ -684,6 +733,8 @@ export const workspaceMachine = createMachine(
|
||||
lastBuildWasDeleting: ({ workspace }) => {
|
||||
return workspace?.latest_build.transition === "delete"
|
||||
},
|
||||
isChangingVersion: ({ templateVersionIdToChange }) =>
|
||||
Boolean(templateVersionIdToChange),
|
||||
},
|
||||
services: {
|
||||
getWorkspace: async ({ username, workspaceName }) => {
|
||||
@@ -708,6 +759,23 @@ export const workspaceMachine = createMachine(
|
||||
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) => async (send) => {
|
||||
if (context.workspace) {
|
||||
const startWorkspacePromise = await API.startWorkspace(
|
||||
|
||||
Reference in New Issue
Block a user