diff --git a/site/src/api/api.ts b/site/src/api/api.ts index c86748b2b64cd..39de3d7aff046 100644 --- a/site/src/api/api.ts +++ b/site/src/api/api.ts @@ -108,8 +108,11 @@ export const getTemplateVersionResources = async (versionId: string): Promise => { - const response = await axios.get(`/api/v2/workspaces/${workspaceId}`) +export const getWorkspace = async ( + workspaceId: string, + params?: TypesGen.WorkspaceOptions, +): Promise => { + const response = await axios.get(`/api/v2/workspaces/${workspaceId}`, { params }) return response.data } @@ -141,8 +144,11 @@ export const getWorkspaces = async (filter?: TypesGen.WorkspaceFilter): Promise< export const getWorkspaceByOwnerAndName = async ( username = "me", workspaceName: string, + params?: TypesGen.WorkspaceByOwnerAndNameParams, ): Promise => { - const response = await axios.get(`/api/v2/users/${username}/workspace/${workspaceName}`) + const response = await axios.get(`/api/v2/users/${username}/workspace/${workspaceName}`, { + params, + }) return response.data } diff --git a/site/src/components/Workspace/Workspace.tsx b/site/src/components/Workspace/Workspace.tsx index fa2b07c72253e..e607017341687 100644 --- a/site/src/components/Workspace/Workspace.tsx +++ b/site/src/components/Workspace/Workspace.tsx @@ -1,5 +1,6 @@ import { makeStyles } from "@material-ui/core/styles" import { FC } from "react" +import { useNavigate } from "react-router-dom" import * as TypesGen from "../../api/typesGenerated" import { BuildsTable } from "../BuildsTable/BuildsTable" import { Margins } from "../Margins/Margins" @@ -7,6 +8,7 @@ import { PageHeader, PageHeaderSubtitle, PageHeaderTitle } from "../PageHeader/P import { Resources } from "../Resources/Resources" import { Stack } from "../Stack/Stack" import { WorkspaceActions } from "../WorkspaceActions/WorkspaceActions" +import { WorkspaceDeletedBanner } from "../WorkspaceDeletedBanner/WorkspaceDeletedBanner" import { WorkspaceSchedule } from "../WorkspaceSchedule/WorkspaceSchedule" import { WorkspaceScheduleBanner } from "../WorkspaceScheduleBanner/WorkspaceScheduleBanner" import { WorkspaceSection } from "../WorkspaceSection/WorkspaceSection" @@ -44,6 +46,7 @@ export const Workspace: FC = ({ builds, }) => { const styles = useStyles() + const navigate = useNavigate() return ( @@ -72,9 +75,13 @@ export const Workspace: FC = ({ workspace={workspace} /> + navigate(`/workspaces/new`)} /> + - + {!!resources && !!resources.length && ( + + )} diff --git a/site/src/components/WorkspaceDeletedBanner/WorkspaceDeletedBanner.stories.tsx b/site/src/components/WorkspaceDeletedBanner/WorkspaceDeletedBanner.stories.tsx new file mode 100644 index 0000000000000..a53d234579332 --- /dev/null +++ b/site/src/components/WorkspaceDeletedBanner/WorkspaceDeletedBanner.stories.tsx @@ -0,0 +1,28 @@ +import { action } from "@storybook/addon-actions" +import { Story } from "@storybook/react" +import * as Mocks from "../../testHelpers/entities" +import { WorkspaceDeletedBanner, WorkspaceDeletedBannerProps } from "./WorkspaceDeletedBanner" + +export default { + title: "components/WorkspaceDeletedBanner", + component: WorkspaceDeletedBanner, +} + +const Template: Story = (args) => + +export const Example = Template.bind({}) +Example.args = { + handleClick: action("extend"), + workspace: { + ...Mocks.MockWorkspace, + + latest_build: { + ...Mocks.MockWorkspaceBuild, + job: { + ...Mocks.MockProvisionerJob, + status: "succeeded", + }, + transition: "delete", + }, + }, +} diff --git a/site/src/components/WorkspaceDeletedBanner/WorkspaceDeletedBanner.tsx b/site/src/components/WorkspaceDeletedBanner/WorkspaceDeletedBanner.tsx new file mode 100644 index 0000000000000..310a18fac95f1 --- /dev/null +++ b/site/src/components/WorkspaceDeletedBanner/WorkspaceDeletedBanner.tsx @@ -0,0 +1,50 @@ +import Button from "@material-ui/core/Button" +import { makeStyles } from "@material-ui/core/styles" +import Alert from "@material-ui/lab/Alert" +import AlertTitle from "@material-ui/lab/AlertTitle" +import { FC } from "react" +import * as TypesGen from "../../api/typesGenerated" +import { isWorkspaceDeleted } from "../../util/workspace" + +const Language = { + bannerTitle: "This workspace has been deleted and cannot be edited.", + createWorkspaceCta: "Create new workspace", +} + +export interface WorkspaceDeletedBannerProps { + workspace: TypesGen.Workspace + handleClick: () => void +} + +export const WorkspaceDeletedBanner: FC = ({ workspace, handleClick }) => { + const styles = useStyles() + + if (!isWorkspaceDeleted(workspace)) { + return null + } + + return ( + + {Language.createWorkspaceCta} + + } + severity="warning" + > + {Language.bannerTitle} + + ) +} + +export const useStyles = makeStyles(() => { + return { + root: { + alignItems: "center", + "& .MuiAlertTitle-root": { + marginBottom: "0px", + }, + }, + } +}) diff --git a/site/src/components/WorkspaceScheduleBanner/WorkspaceScheduleBanner.tsx b/site/src/components/WorkspaceScheduleBanner/WorkspaceScheduleBanner.tsx index 65c64fc75f37e..100bc502023a6 100644 --- a/site/src/components/WorkspaceScheduleBanner/WorkspaceScheduleBanner.tsx +++ b/site/src/components/WorkspaceScheduleBanner/WorkspaceScheduleBanner.tsx @@ -26,7 +26,7 @@ export const shouldDisplay = (workspace: TypesGen.Workspace): boolean => { if (!isWorkspaceOn(workspace)) { return false } else { - // a mannual shutdown has a deadline of '"0001-01-01T00:00:00Z"' + // a manual shutdown has a deadline of '"0001-01-01T00:00:00Z"' // SEE: #1834 const deadline = dayjs(workspace.latest_build.deadline).utc() const hasDeadline = deadline.year() > 1 diff --git a/site/src/pages/WorkspacePage/WorkspacePage.tsx b/site/src/pages/WorkspacePage/WorkspacePage.tsx index f33b1c31cf8ab..7584c8d193a4e 100644 --- a/site/src/pages/WorkspacePage/WorkspacePage.tsx +++ b/site/src/pages/WorkspacePage/WorkspacePage.tsx @@ -1,7 +1,7 @@ import { useMachine } from "@xstate/react" import React, { useEffect } from "react" import { Helmet } from "react-helmet" -import { useNavigate, useParams } from "react-router-dom" +import { useParams } from "react-router-dom" import { DeleteWorkspaceDialog } from "../../components/DeleteWorkspaceDialog/DeleteWorkspaceDialog" import { ErrorSummary } from "../../components/ErrorSummary/ErrorSummary" import { FullScreenLoader } from "../../components/Loader/FullScreenLoader" @@ -13,7 +13,6 @@ import { workspaceScheduleBannerMachine } from "../../xServices/workspaceSchedul export const WorkspacePage: React.FC = () => { const { username: usernameQueryParam, workspace: workspaceQueryParam } = useParams() - const navigate = useNavigate() const username = firstOrItem(usernameQueryParam, null) const workspaceName = firstOrItem(workspaceQueryParam, null) @@ -63,7 +62,6 @@ export const WorkspacePage: React.FC = () => { handleCancel={() => workspaceSend("CANCEL_DELETE")} handleConfirm={() => { workspaceSend("DELETE") - navigate("/workspaces") }} /> diff --git a/site/src/util/workspace.test.ts b/site/src/util/workspace.test.ts index 7c139b72d0ea1..a7789dfbf8285 100644 --- a/site/src/util/workspace.test.ts +++ b/site/src/util/workspace.test.ts @@ -1,7 +1,7 @@ import dayjs from "dayjs" import * as TypesGen from "../api/typesGenerated" import * as Mocks from "../testHelpers/entities" -import { defaultWorkspaceExtension, isWorkspaceOn, workspaceQueryToFilter } from "./workspace" +import { defaultWorkspaceExtension, isWorkspaceDeleted, isWorkspaceOn, workspaceQueryToFilter } from "./workspace" describe("util > workspace", () => { describe("isWorkspaceOn", () => { @@ -42,6 +42,44 @@ describe("util > workspace", () => { }) }) + describe("isWorkspaceDeleted", () => { + it.each<[TypesGen.WorkspaceTransition, TypesGen.ProvisionerJobStatus, boolean]>([ + ["delete", "canceled", false], + ["delete", "canceling", false], + ["delete", "failed", false], + ["delete", "pending", false], + ["delete", "running", false], + ["delete", "succeeded", true], + + ["stop", "canceled", false], + ["stop", "canceling", false], + ["stop", "failed", false], + ["stop", "pending", false], + ["stop", "running", false], + ["stop", "succeeded", false], + + ["start", "canceled", false], + ["start", "canceling", false], + ["start", "failed", false], + ["start", "pending", false], + ["start", "running", false], + ["start", "succeeded", false], + ])(`transition=%p, status=%p, isWorkspaceDeleted=%p`, (transition, status, isDeleted) => { + const workspace: TypesGen.Workspace = { + ...Mocks.MockWorkspace, + latest_build: { + ...Mocks.MockWorkspaceBuild, + job: { + ...Mocks.MockProvisionerJob, + status, + }, + transition, + }, + } + expect(isWorkspaceDeleted(workspace)).toBe(isDeleted) + }) + }) + describe("defaultWorkspaceExtension", () => { it.each<[string, TypesGen.PutExtendWorkspaceRequest]>([ [ diff --git a/site/src/util/workspace.ts b/site/src/util/workspace.ts index fd0684115bb88..94758aebd00d7 100644 --- a/site/src/util/workspace.ts +++ b/site/src/util/workspace.ts @@ -249,6 +249,10 @@ export const isWorkspaceOn = (workspace: TypesGen.Workspace): boolean => { return transition === "start" && status === "succeeded" } +export const isWorkspaceDeleted = (workspace: TypesGen.Workspace): boolean => { + return getWorkspaceStatus(workspace.latest_build) === succeededToStatus["delete"] +} + export const defaultWorkspaceExtension = (__startDate?: dayjs.Dayjs): TypesGen.PutExtendWorkspaceRequest => { const now = __startDate ? dayjs(__startDate) : dayjs() const fourHoursFromNow = now.add(4, "hours").utc() diff --git a/site/src/xServices/workspace/workspaceXService.ts b/site/src/xServices/workspace/workspaceXService.ts index d552e961a6b65..c24c5b968c8de 100644 --- a/site/src/xServices/workspace/workspaceXService.ts +++ b/site/src/xServices/workspace/workspaceXService.ts @@ -431,7 +431,7 @@ export const workspaceMachine = createMachine( }, services: { getWorkspace: async (_, event) => { - return await API.getWorkspaceByOwnerAndName(event.username, event.workspaceName) + return await API.getWorkspaceByOwnerAndName(event.username, event.workspaceName, { include_deleted: true }) }, getTemplate: async (context) => { if (context.workspace) { @@ -470,7 +470,9 @@ export const workspaceMachine = createMachine( }, refreshWorkspace: async (context) => { if (context.workspace) { - return await API.getWorkspaceByOwnerAndName(context.workspace.owner_name, context.workspace.name) + return await API.getWorkspaceByOwnerAndName(context.workspace.owner_name, context.workspace.name, { + include_deleted: true, + }) } else { throw Error("Cannot refresh workspace without id") } pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy