From 1881ce6d378a5070d87641de9c11cd7a7df98150 Mon Sep 17 00:00:00 2001 From: Presley Pizzo Date: Fri, 26 Aug 2022 18:12:17 +0000 Subject: [PATCH 01/18] Add api call --- site/src/api/api.ts | 5 +++++ site/src/testHelpers/handlers.ts | 3 +++ 2 files changed, 8 insertions(+) diff --git a/site/src/api/api.ts b/site/src/api/api.ts index 20a5156e9d3f9..b8f84ce598a03 100644 --- a/site/src/api/api.ts +++ b/site/src/api/api.ts @@ -153,6 +153,11 @@ export const updateTemplateMeta = async ( return response.data } +export const deleteTemplate = async (templateId: string): Promise => { + const response = await axios.delete(`/api/v2/templates/${templateId}`) + return response.data +} + export const getWorkspace = async ( workspaceId: string, params?: TypesGen.WorkspaceOptions, diff --git a/site/src/testHelpers/handlers.ts b/site/src/testHelpers/handlers.ts index a12b46179a5be..8a9fd84365a8a 100644 --- a/site/src/testHelpers/handlers.ts +++ b/site/src/testHelpers/handlers.ts @@ -40,6 +40,9 @@ export const handlers = [ rest.get("/api/v2/templateversions/:templateVersionId/resources", async (req, res, ctx) => { return res(ctx.status(200), ctx.json([M.MockWorkspaceResource, M.MockWorkspaceResource2])) }), + rest.delete("/api/v2/templates/:templateId", async (req, res, ctx) => { + return res(ctx.status(200), ctx.json(M.MockTemplate)) + }), // users rest.get("/api/v2/users", async (req, res, ctx) => { From 066b7dcb0b90a04e881e25bd0eed77d1e6fe30b2 Mon Sep 17 00:00:00 2001 From: Presley Pizzo Date: Fri, 26 Aug 2022 22:12:57 +0000 Subject: [PATCH 02/18] Extract DropDownButton --- .../ActionCtas.tsx | 0 .../DropdownButton/DropdownButton.tsx | 108 ++++++++++++++++++ .../DropdownContent.stories.tsx | 19 +++ .../DropdownContent/DropdownContent.tsx | 11 +- .../DropdownContent.stories.tsx | 50 -------- .../WorkspaceActions.test.tsx | 2 +- .../WorkspaceActions/WorkspaceActions.tsx | 103 ++--------------- .../components/WorkspaceActions/constants.ts | 26 ++--- 8 files changed, 154 insertions(+), 165 deletions(-) rename site/src/components/{WorkspaceActions => DropdownButton}/ActionCtas.tsx (100%) create mode 100644 site/src/components/DropdownButton/DropdownButton.tsx create mode 100644 site/src/components/DropdownButton/DropdownContent/DropdownContent.stories.tsx rename site/src/components/{WorkspaceActions => DropdownButton}/DropdownContent/DropdownContent.tsx (70%) delete mode 100644 site/src/components/WorkspaceActions/DropdownContent/DropdownContent.stories.tsx diff --git a/site/src/components/WorkspaceActions/ActionCtas.tsx b/site/src/components/DropdownButton/ActionCtas.tsx similarity index 100% rename from site/src/components/WorkspaceActions/ActionCtas.tsx rename to site/src/components/DropdownButton/ActionCtas.tsx diff --git a/site/src/components/DropdownButton/DropdownButton.tsx b/site/src/components/DropdownButton/DropdownButton.tsx new file mode 100644 index 0000000000000..07f47593457fa --- /dev/null +++ b/site/src/components/DropdownButton/DropdownButton.tsx @@ -0,0 +1,108 @@ +import Button from "@material-ui/core/Button" +import Popover from "@material-ui/core/Popover" +import { makeStyles } from "@material-ui/core/styles" +import { CloseDropdown, OpenDropdown } from "components/DropdownArrows/DropdownArrows" +import { DropdownContent } from "components/DropdownButton/DropdownContent/DropdownContent" +import { FC, ReactNode, useRef, useState, useEffect } from "react" +import { CancelButton } from "./ActionCtas" + +export interface DropdownButtonProps { + primaryAction: ReactNode + secondaryActions: Array<{ action: string, button: ReactNode }> + canCancel: boolean + handleCancel?: () => void +} + +export const DropdownButton: FC = ({ primaryAction, secondaryActions, canCancel, handleCancel }) => { + const styles = useStyles() + const anchorRef = useRef(null) + const [isOpen, setIsOpen] = useState(false) + const id = isOpen ? "action-popover" : undefined + + /** + * Ensures we close the popover before calling any action handler + */ + useEffect(() => { + setIsOpen(false) + return () => { + setIsOpen(false) + } + }, []) + + return ( + + {/* primary workspace CTA */} + + {primaryAction} + + {canCancel && handleCancel ? ( + + ) : ( + <> + {/* popover toggle button */} + + setIsOpen(false)} + anchorOrigin={{ + vertical: "bottom", + horizontal: "right", + }} + transformOrigin={{ + vertical: "top", + horizontal: "right", + }} + > + {/* secondary workspace CTAs */} + + + + )} + + ) +} + +const useStyles = makeStyles((theme) => ({ + buttonContainer: { + border: `1px solid ${theme.palette.divider}`, + borderRadius: `${theme.shape.borderRadius}px`, + display: "inline-flex", + }, + dropdownButton: { + border: "none", + borderLeft: `1px solid ${theme.palette.divider}`, + borderRadius: `0px ${theme.shape.borderRadius}px ${theme.shape.borderRadius}px 0px`, + minWidth: "unset", + width: "63px", // matching cancel button so button grouping doesn't grow in size + "& .MuiButton-label": { + marginRight: "8px", + }, + }, + primaryCta: { + [theme.breakpoints.down("sm")]: { + width: "100%", + + "& > *": { + width: "100%", + }, + }, + }, + popoverPaper: { + padding: `${theme.spacing(1)}px ${theme.spacing(2)}px ${theme.spacing(1)}px`, + }, +})) diff --git a/site/src/components/DropdownButton/DropdownContent/DropdownContent.stories.tsx b/site/src/components/DropdownButton/DropdownContent/DropdownContent.stories.tsx new file mode 100644 index 0000000000000..d988886df8687 --- /dev/null +++ b/site/src/components/DropdownButton/DropdownContent/DropdownContent.stories.tsx @@ -0,0 +1,19 @@ +import { action } from "@storybook/addon-actions" +import { Story } from "@storybook/react" +import { DeleteButton, UpdateButton, } from "../ActionCtas" +import { DropdownContent, DropdownContentProps } from "./DropdownContent" + +export default { + title: "DropdownButton/DropdownContent", + component: DropdownContent, +} + +const Template: Story = (args) => + +export const Example = Template.bind({}) +Example.args = { + secondaryActions: [ + { action: "update", button: }, + { action: "delete", button: } + ] +} diff --git a/site/src/components/WorkspaceActions/DropdownContent/DropdownContent.tsx b/site/src/components/DropdownButton/DropdownContent/DropdownContent.tsx similarity index 70% rename from site/src/components/WorkspaceActions/DropdownContent/DropdownContent.tsx rename to site/src/components/DropdownButton/DropdownContent/DropdownContent.tsx index 3ce44a3e2e00a..95c9bcd185191 100644 --- a/site/src/components/WorkspaceActions/DropdownContent/DropdownContent.tsx +++ b/site/src/components/DropdownButton/DropdownContent/DropdownContent.tsx @@ -1,24 +1,21 @@ import { makeStyles } from "@material-ui/core/styles" -import { FC } from "react" -import { ButtonMapping, ButtonTypesEnum } from "../constants" +import { FC, ReactNode } from "react" export interface DropdownContentProps { - secondaryActions: ButtonTypesEnum[] - buttonMapping: Partial + secondaryActions: Array<{ action: string, button: ReactNode }> } /* secondary workspace CTAs */ export const DropdownContent: FC> = ({ secondaryActions, - buttonMapping, }) => { const styles = useStyles() return ( - {secondaryActions.map((action) => ( + {secondaryActions.map(({ action, button }) => (
- {buttonMapping[action]} + {button}
))}
diff --git a/site/src/components/WorkspaceActions/DropdownContent/DropdownContent.stories.tsx b/site/src/components/WorkspaceActions/DropdownContent/DropdownContent.stories.tsx deleted file mode 100644 index 257f31a01fb31..0000000000000 --- a/site/src/components/WorkspaceActions/DropdownContent/DropdownContent.stories.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { Story } from "@storybook/react" -import { WorkspaceStateEnum } from "util/workspace" -import { DeleteButton, StartButton, StopButton } from "../ActionCtas" -import { ButtonMapping, ButtonTypesEnum, WorkspaceStateActions } from "../constants" -import { DropdownContent, DropdownContentProps } from "./DropdownContent" - -// These are the stories for the secondary actions (housed in the dropdown) -// in WorkspaceActions.tsx - -export default { - title: "WorkspaceActionsDropdown", - component: DropdownContent, -} - -const Template: Story = (args) => - -const buttonMappingMock: Partial = { - [ButtonTypesEnum.delete]: jest.fn()} />, - [ButtonTypesEnum.start]: jest.fn()} />, - [ButtonTypesEnum.stop]: jest.fn()} />, - [ButtonTypesEnum.delete]: jest.fn()} />, -} - -const defaultArgs = { - buttonMapping: buttonMappingMock, -} - -export const Started = Template.bind({}) -Started.args = { - ...defaultArgs, - secondaryActions: WorkspaceStateActions[WorkspaceStateEnum.started].secondary, -} - -export const Stopped = Template.bind({}) -Stopped.args = { - ...defaultArgs, - secondaryActions: WorkspaceStateActions[WorkspaceStateEnum.stopped].secondary, -} - -export const Canceled = Template.bind({}) -Canceled.args = { - ...defaultArgs, - secondaryActions: WorkspaceStateActions[WorkspaceStateEnum.canceled].secondary, -} - -export const Errored = Template.bind({}) -Errored.args = { - ...defaultArgs, - secondaryActions: WorkspaceStateActions[WorkspaceStateEnum.error].secondary, -} diff --git a/site/src/components/WorkspaceActions/WorkspaceActions.test.tsx b/site/src/components/WorkspaceActions/WorkspaceActions.test.tsx index d744cc2971a5b..bb8df9087bb56 100644 --- a/site/src/components/WorkspaceActions/WorkspaceActions.test.tsx +++ b/site/src/components/WorkspaceActions/WorkspaceActions.test.tsx @@ -2,7 +2,7 @@ import { fireEvent, screen } from "@testing-library/react" import { WorkspaceStateEnum } from "util/workspace" import * as Mocks from "../../testHelpers/entities" import { render } from "../../testHelpers/renderHelpers" -import { Language } from "./ActionCtas" +import { Language } from "../DropdownButton/ActionCtas" import { WorkspaceActions, WorkspaceActionsProps } from "./WorkspaceActions" const renderComponent = async (props: Partial = {}) => { diff --git a/site/src/components/WorkspaceActions/WorkspaceActions.tsx b/site/src/components/WorkspaceActions/WorkspaceActions.tsx index 3564de055fd4c..2769ee063b93f 100644 --- a/site/src/components/WorkspaceActions/WorkspaceActions.tsx +++ b/site/src/components/WorkspaceActions/WorkspaceActions.tsx @@ -1,10 +1,7 @@ -import Button from "@material-ui/core/Button" -import Popover from "@material-ui/core/Popover" -import { makeStyles } from "@material-ui/core/styles" -import { FC, ReactNode, useEffect, useMemo, useRef, useState } from "react" +import { DropdownButton } from "components/DropdownButton/DropdownButton" +import { FC, ReactNode, useMemo } from "react" import { getWorkspaceStatus, WorkspaceStateEnum, WorkspaceStatus } from "util/workspace" import { Workspace } from "../../api/typesGenerated" -import { CloseDropdown, OpenDropdown } from "../DropdownArrows/DropdownArrows" import { ActionLoadingButton, CancelButton, @@ -14,9 +11,8 @@ import { StartButton, StopButton, UpdateButton, -} from "./ActionCtas" +} from "../DropdownButton/ActionCtas" import { ButtonMapping, ButtonTypesEnum, WorkspaceStateActions } from "./constants" -import { DropdownContent } from "./DropdownContent/DropdownContent" /** * Jobs submitted while another job is in progress will be discarded, @@ -43,10 +39,6 @@ export const WorkspaceActions: FC = ({ handleUpdate, handleCancel, }) => { - const styles = useStyles() - const anchorRef = useRef(null) - const [isOpen, setIsOpen] = useState(false) - const id = isOpen ? "action-popover" : undefined const workspaceStatus: keyof typeof WorkspaceStateEnum = getWorkspaceStatus( workspace.latest_build, @@ -70,16 +62,6 @@ export const WorkspaceActions: FC = ({ return updatedActions }, [canBeUpdated, workspaceState]) - /** - * Ensures we close the popover before calling any action handler - */ - useEffect(() => { - setIsOpen(false) - return () => { - setIsOpen(false) - } - }, [workspaceStatus]) - // A mapping of button type to the corresponding React component const buttonMapping: ButtonMapping = { [ButtonTypesEnum.update]: , @@ -98,80 +80,13 @@ export const WorkspaceActions: FC = ({ } return ( - - {/* primary workspace CTA */} - - {buttonMapping[actions.primary]} - - {actions.canCancel ? ( - // cancel CTA - <>{buttonMapping[ButtonTypesEnum.cancel]} - ) : ( - <> - {/* popover toggle button */} - - setIsOpen(false)} - anchorOrigin={{ - vertical: "bottom", - horizontal: "right", - }} - transformOrigin={{ - vertical: "top", - horizontal: "right", - }} - > - {/* secondary workspace CTAs */} - - - - )} - + ({ action, button: buttonMapping[action] }))} + /> ) } -const useStyles = makeStyles((theme) => ({ - buttonContainer: { - border: `1px solid ${theme.palette.divider}`, - borderRadius: `${theme.shape.borderRadius}px`, - display: "inline-flex", - }, - dropdownButton: { - border: "none", - borderLeft: `1px solid ${theme.palette.divider}`, - borderRadius: `0px ${theme.shape.borderRadius}px ${theme.shape.borderRadius}px 0px`, - minWidth: "unset", - width: "63px", // matching cancel button so button grouping doesn't grow in size - "& .MuiButton-label": { - marginRight: "8px", - }, - }, - primaryCta: { - [theme.breakpoints.down("sm")]: { - width: "100%", - "& > *": { - width: "100%", - }, - }, - }, - popoverPaper: { - padding: `${theme.spacing(1)}px ${theme.spacing(2)}px ${theme.spacing(1)}px`, - }, -})) diff --git a/site/src/components/WorkspaceActions/constants.ts b/site/src/components/WorkspaceActions/constants.ts index 383ca3630c8e3..335ca827208e1 100644 --- a/site/src/components/WorkspaceActions/constants.ts +++ b/site/src/components/WorkspaceActions/constants.ts @@ -3,20 +3,20 @@ import { WorkspaceStateEnum } from "util/workspace" // the button types we have export enum ButtonTypesEnum { - start, - starting, - stop, - stopping, - delete, - deleting, - update, - cancel, - error, + start = "start", + starting = "starting", + stop = "stop", + stopping = "stopping", + delete = "delete", + deleting = "deleting", + update = "update", + cancel = "cancel", + error = "error", // disabled buttons - canceling, - disabled, - queued, - loading, + canceling = "canceling", + disabled = "disabled", + queued = "queued", + loading = "loading", } export type ButtonMapping = { From e431721a05f0dd6f4f76c9b907bbc23fd57436b9 Mon Sep 17 00:00:00 2001 From: Presley Pizzo Date: Fri, 26 Aug 2022 22:16:58 +0000 Subject: [PATCH 03/18] Start adding DropdownButton to Template page --- site/src/pages/TemplatePage/TemplatePage.tsx | 6 +++++ .../pages/TemplatePage/TemplatePageView.tsx | 27 ++++++++++++++----- 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/site/src/pages/TemplatePage/TemplatePage.tsx b/site/src/pages/TemplatePage/TemplatePage.tsx index 1b72fa97e9e99..2dcfdb5db4ec1 100644 --- a/site/src/pages/TemplatePage/TemplatePage.tsx +++ b/site/src/pages/TemplatePage/TemplatePage.tsx @@ -31,6 +31,11 @@ export const TemplatePage: FC> = () => { templateState.context const isLoading = !template || !activeTemplateVersion || !templateResources + const handleDeleteTemplate = (templateId: string) => { + //TODO + console.log("implement me", templateId) + } + if (isLoading) { return } @@ -45,6 +50,7 @@ export const TemplatePage: FC> = () => { activeTemplateVersion={activeTemplateVersion} templateResources={templateResources} templateVersions={templateVersions} + handleDeleteTemplate={handleDeleteTemplate} /> ) diff --git a/site/src/pages/TemplatePage/TemplatePageView.tsx b/site/src/pages/TemplatePage/TemplatePageView.tsx index a2e25ef88fd4f..d5e4b8bcc3059 100644 --- a/site/src/pages/TemplatePage/TemplatePageView.tsx +++ b/site/src/pages/TemplatePage/TemplatePageView.tsx @@ -4,6 +4,8 @@ import Link from "@material-ui/core/Link" import { makeStyles } from "@material-ui/core/styles" import AddCircleOutline from "@material-ui/icons/AddCircleOutline" import SettingsOutlined from "@material-ui/icons/SettingsOutlined" +import { DeleteButton } from "components/DropdownButton/ActionCtas" +import { DropdownButton } from "components/DropdownButton/DropdownButton" import frontMatter from "front-matter" import { FC } from "react" import ReactMarkdown from "react-markdown" @@ -36,6 +38,7 @@ export interface TemplatePageViewProps { activeTemplateVersion: TemplateVersion templateResources: WorkspaceResource[] templateVersions?: TemplateVersion[] + handleDeleteTemplate: (templateId: string) => void } export const TemplatePageView: FC> = ({ @@ -43,6 +46,7 @@ export const TemplatePageView: FC activeTemplateVersion, templateResources, templateVersions, + handleDeleteTemplate }) => { const styles = useStyles() const readme = frontMatter(activeTemplateVersion.readme) @@ -66,13 +70,22 @@ export const TemplatePageView: FC {Language.settingsButton} - - - + + + + + } + secondaryActions={[ + { action: "delete", button: handleDeleteTemplate(template.id)} /> } + ]} + canCancel={false} + /> } > From a07a5c4f2a5c26a76a0282c400c2144c14944b11 Mon Sep 17 00:00:00 2001 From: Presley Pizzo Date: Fri, 26 Aug 2022 22:26:23 +0000 Subject: [PATCH 04/18] Move stories to dropdown button --- .../DropdownButton/DropdownButton.stories.tsx | 30 +++++++++++++++++++ .../DropdownContent.stories.tsx | 19 ------------ 2 files changed, 30 insertions(+), 19 deletions(-) create mode 100644 site/src/components/DropdownButton/DropdownButton.stories.tsx delete mode 100644 site/src/components/DropdownButton/DropdownContent/DropdownContent.stories.tsx diff --git a/site/src/components/DropdownButton/DropdownButton.stories.tsx b/site/src/components/DropdownButton/DropdownButton.stories.tsx new file mode 100644 index 0000000000000..dd0dbfbac4ee3 --- /dev/null +++ b/site/src/components/DropdownButton/DropdownButton.stories.tsx @@ -0,0 +1,30 @@ +import { action } from "@storybook/addon-actions" +import { Story } from "@storybook/react" +import { WorkspaceStateEnum } from "util/workspace" +import { DeleteButton, UpdateButton, StartButton, DisabledButton } from "./ActionCtas" +import { DropdownButton, DropdownButtonProps } from "./DropdownButton" + +export default { + title: "Components/DropdownButton", + component: DropdownButton, +} + +const Template: Story = (args) => + +export const WithDropdown = Template.bind({}) +WithDropdown.args = { + primaryAction: , + secondaryActions: [ + { action: "update", button: }, + { action: "delete", button: } + ], + canCancel: false +} + +export const WithCancel = Template.bind({}) +WithCancel.args = { + primaryAction: , + secondaryActions: [], + canCancel: true, + handleCancel: action("cancel") +} diff --git a/site/src/components/DropdownButton/DropdownContent/DropdownContent.stories.tsx b/site/src/components/DropdownButton/DropdownContent/DropdownContent.stories.tsx deleted file mode 100644 index d988886df8687..0000000000000 --- a/site/src/components/DropdownButton/DropdownContent/DropdownContent.stories.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { action } from "@storybook/addon-actions" -import { Story } from "@storybook/react" -import { DeleteButton, UpdateButton, } from "../ActionCtas" -import { DropdownContent, DropdownContentProps } from "./DropdownContent" - -export default { - title: "DropdownButton/DropdownContent", - component: DropdownContent, -} - -const Template: Story = (args) => - -export const Example = Template.bind({}) -Example.args = { - secondaryActions: [ - { action: "update", button: }, - { action: "delete", button: } - ] -} From 90db85d1a0394c938fc6aae4113e4960ff199e37 Mon Sep 17 00:00:00 2001 From: Presley Pizzo Date: Mon, 29 Aug 2022 17:01:57 +0000 Subject: [PATCH 05/18] Format --- .../DropdownButton/DropdownButton.stories.tsx | 8 +- .../DropdownButton/DropdownButton.tsx | 95 ++++++++++--------- .../DropdownContent/DropdownContent.tsx | 2 +- .../WorkspaceActions/WorkspaceActions.tsx | 8 +- .../pages/TemplatePage/TemplatePageView.tsx | 9 +- 5 files changed, 65 insertions(+), 57 deletions(-) diff --git a/site/src/components/DropdownButton/DropdownButton.stories.tsx b/site/src/components/DropdownButton/DropdownButton.stories.tsx index dd0dbfbac4ee3..6b9f70d88589f 100644 --- a/site/src/components/DropdownButton/DropdownButton.stories.tsx +++ b/site/src/components/DropdownButton/DropdownButton.stories.tsx @@ -1,7 +1,7 @@ import { action } from "@storybook/addon-actions" import { Story } from "@storybook/react" import { WorkspaceStateEnum } from "util/workspace" -import { DeleteButton, UpdateButton, StartButton, DisabledButton } from "./ActionCtas" +import { DeleteButton, DisabledButton, StartButton, UpdateButton } from "./ActionCtas" import { DropdownButton, DropdownButtonProps } from "./DropdownButton" export default { @@ -16,9 +16,9 @@ WithDropdown.args = { primaryAction: , secondaryActions: [ { action: "update", button: }, - { action: "delete", button: } + { action: "delete", button: }, ], - canCancel: false + canCancel: false, } export const WithCancel = Template.bind({}) @@ -26,5 +26,5 @@ WithCancel.args = { primaryAction: , secondaryActions: [], canCancel: true, - handleCancel: action("cancel") + handleCancel: action("cancel"), } diff --git a/site/src/components/DropdownButton/DropdownButton.tsx b/site/src/components/DropdownButton/DropdownButton.tsx index 07f47593457fa..d5d13ee737b8f 100644 --- a/site/src/components/DropdownButton/DropdownButton.tsx +++ b/site/src/components/DropdownButton/DropdownButton.tsx @@ -3,17 +3,22 @@ import Popover from "@material-ui/core/Popover" import { makeStyles } from "@material-ui/core/styles" import { CloseDropdown, OpenDropdown } from "components/DropdownArrows/DropdownArrows" import { DropdownContent } from "components/DropdownButton/DropdownContent/DropdownContent" -import { FC, ReactNode, useRef, useState, useEffect } from "react" +import { FC, ReactNode, useEffect, useRef, useState } from "react" import { CancelButton } from "./ActionCtas" export interface DropdownButtonProps { primaryAction: ReactNode - secondaryActions: Array<{ action: string, button: ReactNode }> + secondaryActions: Array<{ action: string; button: ReactNode }> canCancel: boolean handleCancel?: () => void } -export const DropdownButton: FC = ({ primaryAction, secondaryActions, canCancel, handleCancel }) => { +export const DropdownButton: FC = ({ + primaryAction, + secondaryActions, + canCancel, + handleCancel, +}) => { const styles = useStyles() const anchorRef = useRef(null) const [isOpen, setIsOpen] = useState(false) @@ -31,48 +36,48 @@ export const DropdownButton: FC = ({ primaryAction, seconda return ( - {/* primary workspace CTA */} - - {primaryAction} - - {canCancel && handleCancel ? ( - - ) : ( - <> - {/* popover toggle button */} - - setIsOpen(false)} - anchorOrigin={{ - vertical: "bottom", - horizontal: "right", - }} - transformOrigin={{ - vertical: "top", - horizontal: "right", - }} - > - {/* secondary workspace CTAs */} - - - - )} + {/* primary workspace CTA */} + + {primaryAction} + + {canCancel && handleCancel ? ( + + ) : ( + <> + {/* popover toggle button */} + + setIsOpen(false)} + anchorOrigin={{ + vertical: "bottom", + horizontal: "right", + }} + transformOrigin={{ + vertical: "top", + horizontal: "right", + }} + > + {/* secondary workspace CTAs */} + + + + )} ) } diff --git a/site/src/components/DropdownButton/DropdownContent/DropdownContent.tsx b/site/src/components/DropdownButton/DropdownContent/DropdownContent.tsx index 95c9bcd185191..9810c6abbad6f 100644 --- a/site/src/components/DropdownButton/DropdownContent/DropdownContent.tsx +++ b/site/src/components/DropdownButton/DropdownContent/DropdownContent.tsx @@ -2,7 +2,7 @@ import { makeStyles } from "@material-ui/core/styles" import { FC, ReactNode } from "react" export interface DropdownContentProps { - secondaryActions: Array<{ action: string, button: ReactNode }> + secondaryActions: Array<{ action: string; button: ReactNode }> } /* secondary workspace CTAs */ diff --git a/site/src/components/WorkspaceActions/WorkspaceActions.tsx b/site/src/components/WorkspaceActions/WorkspaceActions.tsx index 2769ee063b93f..c3e406d0b535a 100644 --- a/site/src/components/WorkspaceActions/WorkspaceActions.tsx +++ b/site/src/components/WorkspaceActions/WorkspaceActions.tsx @@ -39,7 +39,6 @@ export const WorkspaceActions: FC = ({ handleUpdate, handleCancel, }) => { - const workspaceStatus: keyof typeof WorkspaceStateEnum = getWorkspaceStatus( workspace.latest_build, ) @@ -84,9 +83,10 @@ export const WorkspaceActions: FC = ({ primaryAction={buttonMapping[actions.primary]} canCancel={actions.canCancel} handleCancel={handleCancel} - secondaryActions={actions.secondary.map((action) => ({ action, button: buttonMapping[action] }))} + secondaryActions={actions.secondary.map((action) => ({ + action, + button: buttonMapping[action], + }))} /> ) } - - diff --git a/site/src/pages/TemplatePage/TemplatePageView.tsx b/site/src/pages/TemplatePage/TemplatePageView.tsx index d5e4b8bcc3059..7b3d7c74bca1c 100644 --- a/site/src/pages/TemplatePage/TemplatePageView.tsx +++ b/site/src/pages/TemplatePage/TemplatePageView.tsx @@ -46,7 +46,7 @@ export const TemplatePageView: FC activeTemplateVersion, templateResources, templateVersions, - handleDeleteTemplate + handleDeleteTemplate, }) => { const styles = useStyles() const readme = frontMatter(activeTemplateVersion.readme) @@ -82,10 +82,13 @@ export const TemplatePageView: FC } secondaryActions={[ - { action: "delete", button: handleDeleteTemplate(template.id)} /> } + { + action: "delete", + button: handleDeleteTemplate(template.id)} />, + }, ]} canCancel={false} - /> + /> } > From 71f8a1de735e297b0f3285c7d941c562b7a79edd Mon Sep 17 00:00:00 2001 From: Presley Pizzo Date: Tue, 30 Aug 2022 15:06:41 +0000 Subject: [PATCH 06/18] Update xservice to delete --- .../xServices/template/templateXService.ts | 272 +++++++++++------- 1 file changed, 174 insertions(+), 98 deletions(-) diff --git a/site/src/xServices/template/templateXService.ts b/site/src/xServices/template/templateXService.ts index 1db743f0f8afa..405062f7915c4 100644 --- a/site/src/xServices/template/templateXService.ts +++ b/site/src/xServices/template/templateXService.ts @@ -1,5 +1,6 @@ import { assign, createMachine } from "xstate" import { + deleteTemplate, getTemplateByName, getTemplateVersion, getTemplateVersionResources, @@ -14,131 +15,206 @@ interface TemplateContext { activeTemplateVersion?: TemplateVersion templateResources?: WorkspaceResource[] templateVersions?: TemplateVersion[] + deleteTemplateError?: Error | unknown } -export const templateMachine = createMachine( - { - schema: { - context: {} as TemplateContext, - services: {} as { - getTemplate: { - data: Template - } - getActiveTemplateVersion: { - data: TemplateVersion - } - getTemplateResources: { - data: WorkspaceResource[] - } - getTemplateVersions: { - data: TemplateVersion[] - } - }, +type TemplateEvent = { type: "DELETE" } | { type: "CONFIRM_DELETE" } | { type: "CANCEL_DELETE" } + +export const templateMachine = + /** @xstate-layout N4IgpgJg5mDOIC5QAoC2BDAxgCwJYDswBKAOhgBdyCoAVMVABwBt1ywBiCAe0JIIDcuAazAk0WPIVIUq+WvWaswCAV0ytcPANoAGALqJQDLrFxUehkAA9EAJgDsOkgGZbO2wE5bANg8BGABZnHR0AgBoQAE9EPx0-Eg9EpIAOe28-D28ggF9siPEcAmI+fDNcdCYASXwAMy4SLCp+MDpGFjYANTAAJ1MeMjBKagBBTCaWhXawLt7NfE4eUVURMQxCqRKyiuq6hrHcZtbFTp6+-AGhuVHxo6mZs5V8QXVzfF0DJBBjU1fLGwQAgF4t4AKzeWzOTIhEEhZIRaIIZI6EEkEIhZyg2xuEF+XL5NaSYoELZVWr1NhtJQAJTgXAArt1MHALrJ5JS2DTYPTGXAFrxlqICoTSMSqNsySQKccwJzuUzYCzqLdqbSGfLHs8NNp9JZvmULJ9-kDkiRks4QR50SDkh5nH5nPCYjpXKi0SDnObbeaAniQEKiiLSmLSbspXdTnMFTIlZMlPdI3ylk9hIKCQHNsGduTYydZjwo4NWcrc2dYBq1Fq3jrPnrfobEAFQqbQt5kiCcf4go6ECD7Ca0XFMskAmksr7-RtReUQ1xEyRYOQlKsJOmp+K6rqTPr8H9ELaTclkg5wQEzRidB5u-Y0q6QgFbAEsuCMuO0xsmFx0BBIOwACIAUQAGX-Gh-03H45ksBFrycGE7zcW1bD8e0In+Pw3G8EggT8M0-Fbc8PGSV8Vw2TAeBqXBulQahfzAJhBg4ABhAB5AA5AAxSoqQAWQAfQA4DQPA7ddwQZCMVRUF7Bw3svXsEFuz8MFMNtC8bRtHQzWHYj1mKMjako6i5Fo+i2HYRjhlYxigP4oCQLAmstzrUA0OcaSSHsZwbSUjwAl83zwiiGJGw8LCwTtU8vGQnI8j9N9im-UzqDnAUSEShjizAYTnOsRB7BHEglLwoqskCWxFJ8ewPJ0bx8tsC1IQhZwdOFNK6MGZKem6LhuhIY46iotrTImdkssciCDRcvcbVRJrwr8fLXHsRTYRIOCau8WqYRxIjfXwLhv3gT4J2KaM5Ey7LIPrAFyqChBLVvEJPGk2w21PFrVyDacsz2G4c2mCN+jOqBrgOEbpXjSavicq6prEhar1tR7IXSaSfVik7AxJH7GjBzLIfOWA6UweUjqMGGof+TzbEKsEMiRdwYUhbtkhw5HnHvXx0g8D7Jy+9d6lxw5-oJy7Kb3B07vsJDkekjwcSyWxeaJfmZ0lf7ZTVZlgcyzWeTJ6GJp3a7kOWu7YjcR6wQCEFAQfbxlaxzMJTDFUuS1hUiZJuADdrWHcoQVtMJxdtWfvaL0hWm2rfcRXHxBR2M2+l2NdVfWxeNuHbW7Xz+xCWJkm8ZE3LbRO1zV12S0jRVzpFwH8F9inM4D03u2Ux6sWvQj2wTjH4qd5PQzrvMG-nYnSYz0TQRNG3gnUyE+zNNv-EevDrUya9mr7kiVexlPRoJxujdE7O7r8gIO+dXtUkyMvVazSfrt8bt7xpgcFttC1nXsROPy-SBH5w1iO6MKwRghAkbH2BSUtHBrTRPeC8rhxKJ30hRKiNF2psEAS3ZCNMkR4R8MOWqfloEIntCvDmnoRzyUIvLRO6VWTYP+F4TCNt0g22REhV6pCYgEJIPVDENsPBpA9GkehmCAHjREtdVmmFPCKy2u6RwcJzaQicMOb0wQ3ALVxNvXSRAmExBUWQvOA5baqXlrbXIuQgA */ + createMachine( + { + tsTypes: {} as import("./templateXService.typegen").Typegen0, + schema: { + context: {} as TemplateContext, + events: {} as TemplateEvent, + services: {} as { + getTemplate: { + data: Template + } + getActiveTemplateVersion: { + data: TemplateVersion + } + getTemplateResources: { + data: WorkspaceResource[] + } + getTemplateVersions: { + data: TemplateVersion[] + } + deleteTemplate: { + data: Template + } }, - tsTypes: {} as import("./templateXService.typegen").Typegen0, - initial: "gettingTemplate", - states: { - gettingTemplate: { - invoke: { - src: "getTemplate", - onDone: { - actions: ["assignTemplate"], + }, + id: "(machine)", + initial: "gettingTemplate", + states: { + gettingTemplate: { + invoke: { + src: "getTemplate", + onDone: [ + { + actions: "assignTemplate", target: "initialInfo", }, - }, + ], }, - initialInfo: { - type: "parallel", - onDone: "loaded", - states: { - activeTemplateVersion: { - initial: "gettingActiveTemplateVersion", - states: { - gettingActiveTemplateVersion: { - invoke: { - src: "getActiveTemplateVersion", - onDone: { - actions: ["assignActiveTemplateVersion"], + }, + initialInfo: { + type: "parallel", + states: { + activeTemplateVersion: { + initial: "gettingActiveTemplateVersion", + states: { + gettingActiveTemplateVersion: { + invoke: { + src: "getActiveTemplateVersion", + onDone: [ + { + actions: "assignActiveTemplateVersion", target: "success", }, - }, + ], }, - success: { type: "final" }, + }, + success: { + type: "final", }, }, - templateResources: { - initial: "gettingTemplateResources", - states: { - gettingTemplateResources: { - invoke: { - src: "getTemplateResources", - onDone: { - actions: ["assignTemplateResources"], + }, + templateResources: { + initial: "gettingTemplateResources", + states: { + gettingTemplateResources: { + invoke: { + src: "getTemplateResources", + onDone: [ + { + actions: "assignTemplateResources", target: "success", }, - }, + ], }, - success: { type: "final" }, + }, + success: { + type: "final", }, }, - templateVersions: { - initial: "gettingTemplateVersions", - states: { - gettingTemplateVersions: { - invoke: { - src: "getTemplateVersions", - onDone: { - actions: ["assignTemplateVersions"], + }, + templateVersions: { + initial: "gettingTemplateVersions", + states: { + gettingTemplateVersions: { + invoke: { + src: "getTemplateVersions", + onDone: [ + { + actions: "assignTemplateVersions", target: "success", }, - }, + ], }, - success: { type: "final" }, + }, + success: { + type: "final", }, }, }, }, - loaded: {}, + onDone: { + target: "loaded", + }, }, - }, - { - services: { - getTemplate: (ctx) => getTemplateByName(ctx.organizationId, ctx.templateName), - getActiveTemplateVersion: (ctx) => { - if (!ctx.template) { - throw new Error("Template not loaded") - } - - return getTemplateVersion(ctx.template.active_version_id) + loaded: { + on: { + DELETE: { + target: "confirmingDelete", + }, }, - getTemplateResources: (ctx) => { - if (!ctx.template) { - throw new Error("Template not loaded") - } - - return getTemplateVersionResources(ctx.template.active_version_id) + }, + confirmingDelete: { + on: { + CONFIRM_DELETE: { + target: "deleting", + }, + CANCEL_DELETE: { + target: "loaded", + }, }, - getTemplateVersions: (ctx) => { - if (!ctx.template) { - throw new Error("Template not loaded") - } - - return getTemplateVersions(ctx.template.id) + }, + deleting: { + entry: "clearDeleteTemplateError", + invoke: { + src: "deleteTemplate", + id: "deleteTemplate", + onDone: [ + { + target: "deleted", + }, + ], + onError: [ + { + actions: "assignDeleteTemplateError", + target: "loaded", + }, + ], }, }, - actions: { - assignTemplate: assign({ - template: (_, event) => event.data, - }), - assignActiveTemplateVersion: assign({ - activeTemplateVersion: (_, event) => event.data, - }), - assignTemplateResources: assign({ - templateResources: (_, event) => event.data, - }), - assignTemplateVersions: assign({ - templateVersions: (_, event) => event.data, - }), + deleted: { + type: "final", }, }, -) +}, + { + services: { + getTemplate: (ctx) => getTemplateByName(ctx.organizationId, ctx.templateName), + getActiveTemplateVersion: (ctx) => { + if (!ctx.template) { + throw new Error("Template not loaded") + } + + return getTemplateVersion(ctx.template.active_version_id) + }, + getTemplateResources: (ctx) => { + if (!ctx.template) { + throw new Error("Template not loaded") + } + + return getTemplateVersionResources(ctx.template.active_version_id) + }, + getTemplateVersions: (ctx) => { + if (!ctx.template) { + throw new Error("Template not loaded") + } + + return getTemplateVersions(ctx.template.id) + }, + deleteTemplate: (ctx) => { + if (!ctx.template) { + throw new Error("Template not loaded") + } + return deleteTemplate(ctx.template.id) + }, + }, + actions: { + assignTemplate: assign({ + template: (_, event) => event.data, + }), + assignActiveTemplateVersion: assign({ + activeTemplateVersion: (_, event) => event.data, + }), + assignTemplateResources: assign({ + templateResources: (_, event) => event.data, + }), + assignTemplateVersions: assign({ + templateVersions: (_, event) => event.data, + }), + assignDeleteTemplateError: assign({ + deleteTemplateError: (_, event) => event.data, + }), + clearDeleteTemplateError: assign({ + deleteTemplateError: (_) => undefined, + }), + }, + }, + ) From 259c54a4f8f0bff1f2d964a24b70f09d26fe3204 Mon Sep 17 00:00:00 2001 From: Presley Pizzo Date: Tue, 30 Aug 2022 17:28:57 +0000 Subject: [PATCH 07/18] Deletion flow --- site/src/i18n/en/index.ts | 2 + site/src/i18n/en/templatePage.json | 8 ++++ site/src/pages/TemplatePage/TemplatePage.tsx | 39 ++++++++++++++++--- .../pages/TemplatePage/TemplatePageView.tsx | 7 ++++ .../WorkspacePage/WorkspacePage.test.tsx | 2 +- .../xServices/template/templateXService.ts | 4 ++ 6 files changed, 55 insertions(+), 7 deletions(-) create mode 100644 site/src/i18n/en/templatePage.json diff --git a/site/src/i18n/en/index.ts b/site/src/i18n/en/index.ts index ffaa949384e91..1a210ea8a92b6 100644 --- a/site/src/i18n/en/index.ts +++ b/site/src/i18n/en/index.ts @@ -1,7 +1,9 @@ import common from "./common.json" import workspacePage from "./workspacePage.json" +import templatePage from "./templatePage.json" export const en = { common, workspacePage, + templatePage, } diff --git a/site/src/i18n/en/templatePage.json b/site/src/i18n/en/templatePage.json new file mode 100644 index 0000000000000..277e0fb87b4cf --- /dev/null +++ b/site/src/i18n/en/templatePage.json @@ -0,0 +1,8 @@ +{ + "deleteDialog": { + "title": "Delete template", + "message": "Are you sure you want to delete this template?", + "confirm": "Delete" + }, + "deleteSuccess": "Template successfully deleted." +} diff --git a/site/src/pages/TemplatePage/TemplatePage.tsx b/site/src/pages/TemplatePage/TemplatePage.tsx index 2dcfdb5db4ec1..3310c0f5c61d1 100644 --- a/site/src/pages/TemplatePage/TemplatePage.tsx +++ b/site/src/pages/TemplatePage/TemplatePage.tsx @@ -1,7 +1,9 @@ import { useMachine } from "@xstate/react" +import { ConfirmDialog } from "components/ConfirmDialog/ConfirmDialog" import { FC } from "react" import { Helmet } from "react-helmet-async" -import { useParams } from "react-router-dom" +import { useTranslation } from "react-i18next" +import { Navigate, useParams } from "react-router-dom" import { Loader } from "../../components/Loader/Loader" import { useOrganizationId } from "../../hooks/useOrganizationId" import { pageTitle } from "../../util/page" @@ -20,26 +22,30 @@ const useTemplateName = () => { export const TemplatePage: FC> = () => { const organizationId = useOrganizationId() + const { t } = useTranslation("templatePage") const templateName = useTemplateName() - const [templateState] = useMachine(templateMachine, { + const [templateState, templateSend] = useMachine(templateMachine, { context: { templateName, organizationId, }, }) - const { template, activeTemplateVersion, templateResources, templateVersions } = + const { template, activeTemplateVersion, templateResources, templateVersions, deleteTemplateError } = templateState.context const isLoading = !template || !activeTemplateVersion || !templateResources - const handleDeleteTemplate = (templateId: string) => { - //TODO - console.log("implement me", templateId) + const handleDeleteTemplate = () => { + templateSend("DELETE") } if (isLoading) { return } + if (templateState.matches("deleted")) { + return + } + return ( <> @@ -51,6 +57,27 @@ export const TemplatePage: FC> = () => { templateResources={templateResources} templateVersions={templateVersions} handleDeleteTemplate={handleDeleteTemplate} + deleteTemplateError={deleteTemplateError} + /> + + { + templateSend("CONFIRM_DELETE") + }} + onClose={() => { + templateSend("CANCEL_DELETE") + }} + description={ + <> + {t("deleteDialog.message")} + + } /> ) diff --git a/site/src/pages/TemplatePage/TemplatePageView.tsx b/site/src/pages/TemplatePage/TemplatePageView.tsx index 7b3d7c74bca1c..428faedb62f7e 100644 --- a/site/src/pages/TemplatePage/TemplatePageView.tsx +++ b/site/src/pages/TemplatePage/TemplatePageView.tsx @@ -6,6 +6,7 @@ import AddCircleOutline from "@material-ui/icons/AddCircleOutline" import SettingsOutlined from "@material-ui/icons/SettingsOutlined" import { DeleteButton } from "components/DropdownButton/ActionCtas" import { DropdownButton } from "components/DropdownButton/DropdownButton" +import { ErrorSummary } from "components/ErrorSummary/ErrorSummary" import frontMatter from "front-matter" import { FC } from "react" import ReactMarkdown from "react-markdown" @@ -39,6 +40,7 @@ export interface TemplatePageViewProps { templateResources: WorkspaceResource[] templateVersions?: TemplateVersion[] handleDeleteTemplate: (templateId: string) => void + deleteTemplateError: Error | unknown } export const TemplatePageView: FC> = ({ @@ -47,6 +49,7 @@ export const TemplatePageView: FC templateResources, templateVersions, handleDeleteTemplate, + deleteTemplateError }) => { const styles = useStyles() const readme = frontMatter(activeTemplateVersion.readme) @@ -111,6 +114,9 @@ export const TemplatePageView: FC + <> + {deleteTemplateError && } + + ) } diff --git a/site/src/pages/WorkspacePage/WorkspacePage.test.tsx b/site/src/pages/WorkspacePage/WorkspacePage.test.tsx index 326fa0205f247..bcf4a9e45696a 100644 --- a/site/src/pages/WorkspacePage/WorkspacePage.test.tsx +++ b/site/src/pages/WorkspacePage/WorkspacePage.test.tsx @@ -4,7 +4,7 @@ import i18next from "i18next" import { rest } from "msw" import * as api from "../../api/api" import { Workspace } from "../../api/typesGenerated" -import { Language } from "../../components/WorkspaceActions/ActionCtas" +import { Language } from "../../components/DropdownButton/ActionCtas" import { MockBuilds, MockCanceledWorkspace, diff --git a/site/src/xServices/template/templateXService.ts b/site/src/xServices/template/templateXService.ts index 405062f7915c4..522d4c3b583f7 100644 --- a/site/src/xServices/template/templateXService.ts +++ b/site/src/xServices/template/templateXService.ts @@ -1,3 +1,4 @@ +import { displaySuccess } from "components/GlobalSnackbar/utils" import { assign, createMachine } from "xstate" import { deleteTemplate, @@ -7,6 +8,7 @@ import { getTemplateVersions, } from "../../api/api" import { Template, TemplateVersion, WorkspaceResource } from "../../api/typesGenerated" +import { t } from "i18next" interface TemplateContext { organizationId: string @@ -150,6 +152,7 @@ export const templateMachine = onDone: [ { target: "deleted", + actions: "displayDeleteSuccess" }, ], onError: [ @@ -215,6 +218,7 @@ export const templateMachine = clearDeleteTemplateError: assign({ deleteTemplateError: (_) => undefined, }), + displayDeleteSuccess: () => displaySuccess(t("deleteSuccess", { ns: "templatePage" })) }, }, ) From 0d73b25b02b4221c72de581e83dac8b1338bd0b1 Mon Sep 17 00:00:00 2001 From: Presley Pizzo Date: Tue, 30 Aug 2022 17:31:27 +0000 Subject: [PATCH 08/18] Format --- site/src/i18n/en/index.ts | 2 +- site/src/pages/TemplatePage/TemplatePage.tsx | 15 +- .../pages/TemplatePage/TemplatePageView.tsx | 70 ++--- .../xServices/template/templateXService.ts | 246 +++++++++--------- 4 files changed, 167 insertions(+), 166 deletions(-) diff --git a/site/src/i18n/en/index.ts b/site/src/i18n/en/index.ts index 1a210ea8a92b6..7d453dd427903 100644 --- a/site/src/i18n/en/index.ts +++ b/site/src/i18n/en/index.ts @@ -1,6 +1,6 @@ import common from "./common.json" -import workspacePage from "./workspacePage.json" import templatePage from "./templatePage.json" +import workspacePage from "./workspacePage.json" export const en = { common, diff --git a/site/src/pages/TemplatePage/TemplatePage.tsx b/site/src/pages/TemplatePage/TemplatePage.tsx index 3310c0f5c61d1..b5313b8d9ff46 100644 --- a/site/src/pages/TemplatePage/TemplatePage.tsx +++ b/site/src/pages/TemplatePage/TemplatePage.tsx @@ -30,8 +30,13 @@ export const TemplatePage: FC> = () => { organizationId, }, }) - const { template, activeTemplateVersion, templateResources, templateVersions, deleteTemplateError } = - templateState.context + const { + template, + activeTemplateVersion, + templateResources, + templateVersions, + deleteTemplateError, + } = templateState.context const isLoading = !template || !activeTemplateVersion || !templateResources const handleDeleteTemplate = () => { @@ -73,11 +78,7 @@ export const TemplatePage: FC> = () => { onClose={() => { templateSend("CANCEL_DELETE") }} - description={ - <> - {t("deleteDialog.message")} - - } + description={<>{t("deleteDialog.message")}} /> ) diff --git a/site/src/pages/TemplatePage/TemplatePageView.tsx b/site/src/pages/TemplatePage/TemplatePageView.tsx index 428faedb62f7e..a45fd79d18854 100644 --- a/site/src/pages/TemplatePage/TemplatePageView.tsx +++ b/site/src/pages/TemplatePage/TemplatePageView.tsx @@ -49,7 +49,7 @@ export const TemplatePageView: FC templateResources, templateVersions, handleDeleteTemplate, - deleteTemplateError + deleteTemplateError, }) => { const styles = useStyles() const readme = frontMatter(activeTemplateVersion.readme) @@ -115,41 +115,41 @@ export const TemplatePageView: FC <> - {deleteTemplateError && } + {deleteTemplateError && } - - - - - - -
- ( - - {children} - - ), - }} - > - {readme.body} - -
-
- - - -
+ + + + + + +
+ ( + + {children} + + ), + }} + > + {readme.body} + +
+
+ + + +
) diff --git a/site/src/xServices/template/templateXService.ts b/site/src/xServices/template/templateXService.ts index 522d4c3b583f7..666b4e31c28f5 100644 --- a/site/src/xServices/template/templateXService.ts +++ b/site/src/xServices/template/templateXService.ts @@ -1,4 +1,5 @@ import { displaySuccess } from "components/GlobalSnackbar/utils" +import { t } from "i18next" import { assign, createMachine } from "xstate" import { deleteTemplate, @@ -8,7 +9,6 @@ import { getTemplateVersions, } from "../../api/api" import { Template, TemplateVersion, WorkspaceResource } from "../../api/typesGenerated" -import { t } from "i18next" interface TemplateContext { organizationId: string @@ -26,148 +26,148 @@ export const templateMachine = /** @xstate-layout N4IgpgJg5mDOIC5QAoC2BDAxgCwJYDswBKAOhgBdyCoAVMVABwBt1ywBiCAe0JIIDcuAazAk0WPIVIUq+WvWaswCAV0ytcPANoAGALqJQDLrFxUehkAA9EAJgDsOkgGZbO2wE5bANg8BGABZnHR0AgBoQAE9EPx0-Eg9EpIAOe28-D28ggF9siPEcAmI+fDNcdCYASXwAMy4SLCp+MDpGFjYANTAAJ1MeMjBKagBBTCaWhXawLt7NfE4eUVURMQxCqRKyiuq6hrHcZtbFTp6+-AGhuVHxo6mZs5V8QXVzfF0DJBBjU1fLGwQAgF4t4AKzeWzOTIhEEhZIRaIIZI6EEkEIhZyg2xuEF+XL5NaSYoELZVWr1NhtJQAJTgXAArt1MHALrJ5JS2DTYPTGXAFrxlqICoTSMSqNsySQKccwJzuUzYCzqLdqbSGfLHs8NNp9JZvmULJ9-kDkiRks4QR50SDkh5nH5nPCYjpXKi0SDnObbeaAniQEKiiLSmLSbspXdTnMFTIlZMlPdI3ylk9hIKCQHNsGduTYydZjwo4NWcrc2dYBq1Fq3jrPnrfobEAFQqbQt5kiCcf4go6ECD7Ca0XFMskAmksr7-RtReUQ1xEyRYOQlKsJOmp+K6rqTPr8H9ELaTclkg5wQEzRidB5u-Y0q6QgFbAEsuCMuO0xsmFx0BBIOwACIAUQAGX-Gh-03H45ksBFrycGE7zcW1bD8e0In+Pw3G8EggT8M0-Fbc8PGSV8Vw2TAeBqXBulQahfzAJhBg4ABhAB5AA5AAxSoqQAWQAfQA4DQPA7ddwQZCMVRUF7Bw3svXsEFuz8MFMNtC8bRtHQzWHYj1mKMjako6i5Fo+i2HYRjhlYxigP4oCQLAmstzrUA0OcaSSHsZwbSUjwAl83zwiiGJGw8LCwTtU8vGQnI8j9N9im-UzqDnAUSEShjizAYTnOsRB7BHEglLwoqskCWxFJ8ewPJ0bx8tsC1IQhZwdOFNK6MGZKem6LhuhIY46iotrTImdkssciCDRcvcbVRJrwr8fLXHsRTYRIOCau8WqYRxIjfXwLhv3gT4J2KaM5Ey7LIPrAFyqChBLVvEJPGk2w21PFrVyDacsz2G4c2mCN+jOqBrgOEbpXjSavicq6prEhar1tR7IXSaSfVik7AxJH7GjBzLIfOWA6UweUjqMGGof+TzbEKsEMiRdwYUhbtkhw5HnHvXx0g8D7Jy+9d6lxw5-oJy7Kb3B07vsJDkekjwcSyWxeaJfmZ0lf7ZTVZlgcyzWeTJ6GJp3a7kOWu7YjcR6wQCEFAQfbxlaxzMJTDFUuS1hUiZJuADdrWHcoQVtMJxdtWfvaL0hWm2rfcRXHxBR2M2+l2NdVfWxeNuHbW7Xz+xCWJkm8ZE3LbRO1zV12S0jRVzpFwH8F9inM4D03u2Ux6sWvQj2wTjH4qd5PQzrvMG-nYnSYz0TQRNG3gnUyE+zNNv-EevDrUya9mr7kiVexlPRoJxujdE7O7r8gIO+dXtUkyMvVazSfrt8bt7xpgcFttC1nXsROPy-SBH5w1iO6MKwRghAkbH2BSUtHBrTRPeC8rhxKJ30hRKiNF2psEAS3ZCNMkR4R8MOWqfloEIntCvDmnoRzyUIvLRO6VWTYP+F4TCNt0g22REhV6pCYgEJIPVDENsPBpA9GkehmCAHjREtdVmmFPCKy2u6RwcJzaQicMOb0wQ3ALVxNvXSRAmExBUWQvOA5baqXlrbXIuQgA */ createMachine( { - tsTypes: {} as import("./templateXService.typegen").Typegen0, - schema: { - context: {} as TemplateContext, - events: {} as TemplateEvent, - services: {} as { - getTemplate: { - data: Template - } - getActiveTemplateVersion: { - data: TemplateVersion - } - getTemplateResources: { - data: WorkspaceResource[] - } - getTemplateVersions: { - data: TemplateVersion[] - } - deleteTemplate: { - data: Template - } - }, - }, - id: "(machine)", - initial: "gettingTemplate", - states: { - gettingTemplate: { - invoke: { - src: "getTemplate", - onDone: [ - { - actions: "assignTemplate", - target: "initialInfo", - }, - ], + tsTypes: {} as import("./templateXService.typegen").Typegen0, + schema: { + context: {} as TemplateContext, + events: {} as TemplateEvent, + services: {} as { + getTemplate: { + data: Template + } + getActiveTemplateVersion: { + data: TemplateVersion + } + getTemplateResources: { + data: WorkspaceResource[] + } + getTemplateVersions: { + data: TemplateVersion[] + } + deleteTemplate: { + data: Template + } + }, }, - }, - initialInfo: { - type: "parallel", + id: "(machine)", + initial: "gettingTemplate", states: { - activeTemplateVersion: { - initial: "gettingActiveTemplateVersion", - states: { - gettingActiveTemplateVersion: { - invoke: { - src: "getActiveTemplateVersion", - onDone: [ - { - actions: "assignActiveTemplateVersion", - target: "success", - }, - ], + gettingTemplate: { + invoke: { + src: "getTemplate", + onDone: [ + { + actions: "assignTemplate", + target: "initialInfo", }, - }, - success: { - type: "final", - }, + ], }, }, - templateResources: { - initial: "gettingTemplateResources", + initialInfo: { + type: "parallel", states: { - gettingTemplateResources: { - invoke: { - src: "getTemplateResources", - onDone: [ - { - actions: "assignTemplateResources", - target: "success", + activeTemplateVersion: { + initial: "gettingActiveTemplateVersion", + states: { + gettingActiveTemplateVersion: { + invoke: { + src: "getActiveTemplateVersion", + onDone: [ + { + actions: "assignActiveTemplateVersion", + target: "success", + }, + ], }, - ], + }, + success: { + type: "final", + }, }, }, - success: { - type: "final", + templateResources: { + initial: "gettingTemplateResources", + states: { + gettingTemplateResources: { + invoke: { + src: "getTemplateResources", + onDone: [ + { + actions: "assignTemplateResources", + target: "success", + }, + ], + }, + }, + success: { + type: "final", + }, + }, }, - }, - }, - templateVersions: { - initial: "gettingTemplateVersions", - states: { - gettingTemplateVersions: { - invoke: { - src: "getTemplateVersions", - onDone: [ - { - actions: "assignTemplateVersions", - target: "success", + templateVersions: { + initial: "gettingTemplateVersions", + states: { + gettingTemplateVersions: { + invoke: { + src: "getTemplateVersions", + onDone: [ + { + actions: "assignTemplateVersions", + target: "success", + }, + ], }, - ], + }, + success: { + type: "final", + }, }, }, - success: { - type: "final", + }, + onDone: { + target: "loaded", + }, + }, + loaded: { + on: { + DELETE: { + target: "confirmingDelete", }, }, }, - }, - onDone: { - target: "loaded", - }, - }, - loaded: { - on: { - DELETE: { - target: "confirmingDelete", + confirmingDelete: { + on: { + CONFIRM_DELETE: { + target: "deleting", + }, + CANCEL_DELETE: { + target: "loaded", + }, + }, }, - }, - }, - confirmingDelete: { - on: { - CONFIRM_DELETE: { - target: "deleting", + deleting: { + entry: "clearDeleteTemplateError", + invoke: { + src: "deleteTemplate", + id: "deleteTemplate", + onDone: [ + { + target: "deleted", + actions: "displayDeleteSuccess", + }, + ], + onError: [ + { + actions: "assignDeleteTemplateError", + target: "loaded", + }, + ], + }, }, - CANCEL_DELETE: { - target: "loaded", + deleted: { + type: "final", }, }, }, - deleting: { - entry: "clearDeleteTemplateError", - invoke: { - src: "deleteTemplate", - id: "deleteTemplate", - onDone: [ - { - target: "deleted", - actions: "displayDeleteSuccess" - }, - ], - onError: [ - { - actions: "assignDeleteTemplateError", - target: "loaded", - }, - ], - }, - }, - deleted: { - type: "final", - }, - }, -}, { services: { getTemplate: (ctx) => getTemplateByName(ctx.organizationId, ctx.templateName), @@ -218,7 +218,7 @@ export const templateMachine = clearDeleteTemplateError: assign({ deleteTemplateError: (_) => undefined, }), - displayDeleteSuccess: () => displaySuccess(t("deleteSuccess", { ns: "templatePage" })) + displayDeleteSuccess: () => displaySuccess(t("deleteSuccess", { ns: "templatePage" })), }, }, ) From 4437f61bc0acc0249dd716494c34ad55a73d9509 Mon Sep 17 00:00:00 2001 From: Presley Pizzo Date: Tue, 30 Aug 2022 18:32:52 +0000 Subject: [PATCH 09/18] Move ErrorSummary for consistency --- .../pages/TemplatePage/TemplatePageView.tsx | 73 +++++++++---------- 1 file changed, 36 insertions(+), 37 deletions(-) diff --git a/site/src/pages/TemplatePage/TemplatePageView.tsx b/site/src/pages/TemplatePage/TemplatePageView.tsx index a45fd79d18854..226d3e89a98dd 100644 --- a/site/src/pages/TemplatePage/TemplatePageView.tsx +++ b/site/src/pages/TemplatePage/TemplatePageView.tsx @@ -61,6 +61,8 @@ export const TemplatePageView: FC return ( + <> + {deleteTemplateError && } @@ -114,43 +116,40 @@ export const TemplatePageView: FC - <> - {deleteTemplateError && } - - - - - - - -
- ( - - {children} - - ), - }} - > - {readme.body} - -
-
- - - -
- + + + + + + +
+ ( + + {children} + + ), + }} + > + {readme.body} + +
+
+ + + +
+
) } From 6719ed6be7a1dbcaf5119dd6550dcebaee73148c Mon Sep 17 00:00:00 2001 From: Presley Pizzo Date: Tue, 30 Aug 2022 19:29:45 +0000 Subject: [PATCH 10/18] RBAC (unfinished) and style tweak --- site/src/pages/TemplatePage/TemplatePage.tsx | 11 +++-- .../pages/TemplatePage/TemplatePageView.tsx | 47 ++++++++++++------- site/src/xServices/auth/authXService.ts | 7 +++ 3 files changed, 44 insertions(+), 21 deletions(-) diff --git a/site/src/pages/TemplatePage/TemplatePage.tsx b/site/src/pages/TemplatePage/TemplatePage.tsx index b5313b8d9ff46..0efa91bbf3da8 100644 --- a/site/src/pages/TemplatePage/TemplatePage.tsx +++ b/site/src/pages/TemplatePage/TemplatePage.tsx @@ -1,9 +1,11 @@ -import { useMachine } from "@xstate/react" +import { useMachine, useSelector } from "@xstate/react" import { ConfirmDialog } from "components/ConfirmDialog/ConfirmDialog" -import { FC } from "react" +import { FC, useContext } from "react" import { Helmet } from "react-helmet-async" import { useTranslation } from "react-i18next" import { Navigate, useParams } from "react-router-dom" +import { selectPermissions } from "xServices/auth/authSelectors" +import { XServiceContext } from "xServices/StateContext" import { Loader } from "../../components/Loader/Loader" import { useOrganizationId } from "../../hooks/useOrganizationId" import { pageTitle } from "../../util/page" @@ -37,7 +39,9 @@ export const TemplatePage: FC> = () => { templateVersions, deleteTemplateError, } = templateState.context - const isLoading = !template || !activeTemplateVersion || !templateResources + const xServices = useContext(XServiceContext) + const permissions = useSelector(xServices.authXService, selectPermissions) + const isLoading = !template || !activeTemplateVersion || !templateResources || !permissions const handleDeleteTemplate = () => { templateSend("DELETE") @@ -61,6 +65,7 @@ export const TemplatePage: FC> = () => { activeTemplateVersion={activeTemplateVersion} templateResources={templateResources} templateVersions={templateVersions} + canDeleteTemplate={permissions.deleteTemplates} handleDeleteTemplate={handleDeleteTemplate} deleteTemplateError={deleteTemplateError} /> diff --git a/site/src/pages/TemplatePage/TemplatePageView.tsx b/site/src/pages/TemplatePage/TemplatePageView.tsx index 226d3e89a98dd..6ff3f9eb84ffd 100644 --- a/site/src/pages/TemplatePage/TemplatePageView.tsx +++ b/site/src/pages/TemplatePage/TemplatePageView.tsx @@ -41,6 +41,7 @@ export interface TemplatePageViewProps { templateVersions?: TemplateVersion[] handleDeleteTemplate: (templateId: string) => void deleteTemplateError: Error | unknown + canDeleteTemplate: boolean } export const TemplatePageView: FC> = ({ @@ -50,6 +51,7 @@ export const TemplatePageView: FC templateVersions, handleDeleteTemplate, deleteTemplateError, + canDeleteTemplate }) => { const styles = useStyles() const readme = frontMatter(activeTemplateVersion.readme) @@ -59,6 +61,15 @@ export const TemplatePageView: FC return resources.filter((resource) => resource.workspace_transition === "start") } + const createWorkspaceButton = (className: string) => + + + + return ( <> @@ -76,24 +87,20 @@ export const TemplatePageView: FC - - - - } - secondaryActions={[ - { - action: "delete", - button: handleDeleteTemplate(template.id)} />, - }, - ]} - canCancel={false} - /> + {canDeleteTemplate ? + handleDeleteTemplate(template.id)} />, + }, + ]} + canCancel={false} + /> + : + createWorkspaceButton("") + } } > @@ -156,6 +163,10 @@ export const TemplatePageView: FC export const useStyles = makeStyles((theme) => { return { + actionButton: { + border: "none", + borderRadius: `${theme.shape.borderRadius}px 0px 0px ${theme.shape.borderRadius}px`, + }, readmeContents: { margin: 0, }, diff --git a/site/src/xServices/auth/authXService.ts b/site/src/xServices/auth/authXService.ts index 36e10e2347dc2..94a3c2bf7c974 100644 --- a/site/src/xServices/auth/authXService.ts +++ b/site/src/xServices/auth/authXService.ts @@ -14,6 +14,7 @@ export const checks = { updateUsers: "updateUsers", createUser: "createUser", createTemplates: "createTemplates", + deleteTemplates: "deleteTemplates", viewAuditLog: "viewAuditLog", } as const @@ -42,6 +43,12 @@ export const permissionsToCheck = { }, action: "update", }, + [checks.deleteTemplates]: { + object: { + resource_type: "template" + }, + action: "delete" + }, [checks.viewAuditLog]: { object: { resource_type: "audit_log", From e28639ec2aa0d7e94e250dab2dcbc8c71986f991 Mon Sep 17 00:00:00 2001 From: Presley Pizzo Date: Tue, 30 Aug 2022 19:30:16 +0000 Subject: [PATCH 11/18] Format --- .../pages/TemplatePage/TemplatePageView.tsx | 181 +++++++++--------- site/src/xServices/auth/authXService.ts | 4 +- 2 files changed, 93 insertions(+), 92 deletions(-) diff --git a/site/src/pages/TemplatePage/TemplatePageView.tsx b/site/src/pages/TemplatePage/TemplatePageView.tsx index 6ff3f9eb84ffd..4bac2b470b46a 100644 --- a/site/src/pages/TemplatePage/TemplatePageView.tsx +++ b/site/src/pages/TemplatePage/TemplatePageView.tsx @@ -51,7 +51,7 @@ export const TemplatePageView: FC templateVersions, handleDeleteTemplate, deleteTemplateError, - canDeleteTemplate + canDeleteTemplate, }) => { const styles = useStyles() const readme = frontMatter(activeTemplateVersion.readme) @@ -61,102 +61,103 @@ export const TemplatePageView: FC return resources.filter((resource) => resource.workspace_transition === "start") } - const createWorkspaceButton = (className: string) => - - - + const createWorkspaceButton = (className: string) => ( + + + + ) return ( <> - {deleteTemplateError && } - - - - + {deleteTemplateError && } + + + + - {canDeleteTemplate ? - handleDeleteTemplate(template.id)} />, - }, - ]} - canCancel={false} - /> - : - createWorkspaceButton("") - } + {canDeleteTemplate ? ( + handleDeleteTemplate(template.id)} /> + ), + }, + ]} + canCancel={false} + /> + ) : ( + createWorkspaceButton("") + )} + + } + > + +
+ {hasIcon ? ( +
+ +
+ ) : ( + {firstLetter(template.name)} + )} +
+
+ {template.name} + + {template.description === "" ? Language.noDescription : template.description} + +
- } - > - -
- {hasIcon ? ( -
- -
- ) : ( - {firstLetter(template.name)} - )} -
-
- {template.name} - - {template.description === "" ? Language.noDescription : template.description} - -
-
-
+
- - - - - - -
- ( - - {children} - - ), - }} - > - {readme.body} - -
-
- - - -
- + + + + + + +
+ ( + + {children} + + ), + }} + > + {readme.body} + +
+
+ + + +
+
) } diff --git a/site/src/xServices/auth/authXService.ts b/site/src/xServices/auth/authXService.ts index 94a3c2bf7c974..d3c0a27ab2bd2 100644 --- a/site/src/xServices/auth/authXService.ts +++ b/site/src/xServices/auth/authXService.ts @@ -45,9 +45,9 @@ export const permissionsToCheck = { }, [checks.deleteTemplates]: { object: { - resource_type: "template" + resource_type: "template", }, - action: "delete" + action: "delete", }, [checks.viewAuditLog]: { object: { From 065f55933ecd260c856fd61201fd5dde59a4afe7 Mon Sep 17 00:00:00 2001 From: Presley Pizzo Date: Tue, 30 Aug 2022 19:56:45 +0000 Subject: [PATCH 12/18] Test rbac --- .../DropdownArrows/DropdownArrows.tsx | 4 +-- .../pages/TemplatePage/TemplatePage.test.tsx | 30 ++++++++++++++++++- 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/site/src/components/DropdownArrows/DropdownArrows.tsx b/site/src/components/DropdownArrows/DropdownArrows.tsx index fa438157e87d0..4448fe9442fc0 100644 --- a/site/src/components/DropdownArrows/DropdownArrows.tsx +++ b/site/src/components/DropdownArrows/DropdownArrows.tsx @@ -21,10 +21,10 @@ interface ArrowProps { export const OpenDropdown: FC = ({ margin = true }) => { const styles = useStyles({ margin }) - return + return } export const CloseDropdown: FC = ({ margin = true }) => { const styles = useStyles({ margin }) - return + return } diff --git a/site/src/pages/TemplatePage/TemplatePage.test.tsx b/site/src/pages/TemplatePage/TemplatePage.test.tsx index 6497964053beb..2ca0f619f60d7 100644 --- a/site/src/pages/TemplatePage/TemplatePage.test.tsx +++ b/site/src/pages/TemplatePage/TemplatePage.test.tsx @@ -1,8 +1,12 @@ -import { screen } from "@testing-library/react" +import { fireEvent, screen } from "@testing-library/react" +import { rest } from "msw" +import { server } from "testHelpers/server" import * as CreateDayString from "util/createDayString" import { + MockMemberPermissions, MockTemplate, MockTemplateVersion, + MockUser, MockWorkspaceResource, renderWithAuth, } from "../../testHelpers/renderHelpers" @@ -23,4 +27,28 @@ describe("TemplatePage", () => { screen.getByText(MockWorkspaceResource.name) screen.queryAllByText(`${MockTemplateVersion.name}`).length }) + it("allows an admin to delete a template", async () => { + renderWithAuth(, { + route: `/templates/${MockTemplate.id}`, + path: "/templates/:template", + }) + const dropdownButton = await screen.findByLabelText("open-dropdown") + fireEvent.click(dropdownButton) + const deleteButton = await screen.findByText("Delete") + expect(deleteButton).toBeDefined() + }) + it("does not allow a member to delete a template", () => { + // get member-level permissions + server.use( + rest.post(`/api/v2/users/${MockUser.id}/authorization`, async (req, res, ctx) => { + return res(ctx.status(200), ctx.json(MockMemberPermissions)) + }) + ) + renderWithAuth(, { + route: `/templates/${MockTemplate.id}`, + path: "/templates/:template", + }) + const dropdownButton = screen.queryByLabelText("open-dropdown") + expect(dropdownButton).toBe(null) + }) }) From 577c034b41d442c606f216f30e0d11c7bd0c2d9d Mon Sep 17 00:00:00 2001 From: Presley Pizzo Date: Tue, 30 Aug 2022 19:57:09 +0000 Subject: [PATCH 13/18] Format --- site/src/components/DropdownArrows/DropdownArrows.tsx | 7 ++++++- site/src/pages/TemplatePage/TemplatePage.test.tsx | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/site/src/components/DropdownArrows/DropdownArrows.tsx b/site/src/components/DropdownArrows/DropdownArrows.tsx index 4448fe9442fc0..f8cea75582f8d 100644 --- a/site/src/components/DropdownArrows/DropdownArrows.tsx +++ b/site/src/components/DropdownArrows/DropdownArrows.tsx @@ -26,5 +26,10 @@ export const OpenDropdown: FC = ({ margin = true }) => { export const CloseDropdown: FC = ({ margin = true }) => { const styles = useStyles({ margin }) - return + return ( + + ) } diff --git a/site/src/pages/TemplatePage/TemplatePage.test.tsx b/site/src/pages/TemplatePage/TemplatePage.test.tsx index 2ca0f619f60d7..87099a927843a 100644 --- a/site/src/pages/TemplatePage/TemplatePage.test.tsx +++ b/site/src/pages/TemplatePage/TemplatePage.test.tsx @@ -42,7 +42,7 @@ describe("TemplatePage", () => { server.use( rest.post(`/api/v2/users/${MockUser.id}/authorization`, async (req, res, ctx) => { return res(ctx.status(200), ctx.json(MockMemberPermissions)) - }) + }), ) renderWithAuth(, { route: `/templates/${MockTemplate.id}`, From d201301cc13529aac425846857c22fe5f13c7cbf Mon Sep 17 00:00:00 2001 From: Presley Pizzo Date: Wed, 31 Aug 2022 15:53:05 +0000 Subject: [PATCH 14/18] Move ErrorSummary under PageHeader in workspace and template --- site/src/components/Workspace/Workspace.tsx | 75 +++++++++---------- .../pages/TemplatePage/TemplatePageView.tsx | 8 +- 2 files changed, 43 insertions(+), 40 deletions(-) diff --git a/site/src/components/Workspace/Workspace.tsx b/site/src/components/Workspace/Workspace.tsx index 0d74eca52dba8..661e2d30849da 100644 --- a/site/src/components/Workspace/Workspace.tsx +++ b/site/src/components/Workspace/Workspace.tsx @@ -66,20 +66,19 @@ export const Workspace: FC> = ({ const styles = useStyles() const navigate = useNavigate() + const buildError = workspaceErrors[WorkspaceErrors.BUILD_ERROR] ? ( + + ) : ( + <> + ) + const cancellationError = workspaceErrors[WorkspaceErrors.CANCELLATION_ERROR] ? ( + + ) : ( + <> + ) + return ( - - {workspaceErrors[WorkspaceErrors.BUILD_ERROR] ? ( - - ) : ( - <> - )} - {workspaceErrors[WorkspaceErrors.CANCELLATION_ERROR] ? ( - - ) : ( - <> - )} - @@ -107,38 +106,36 @@ export const Workspace: FC> = ({ {workspace.owner_name} - - - + + {buildError} + {cancellationError} + + - navigate(`/templates`)} /> + + + + {!!resources && !!resources.length && ( + navigate(`/templates`)} + canUpdateWorkspace={canUpdateWorkspace} /> + )} - - - {!!resources && !!resources.length && ( - + + {workspaceErrors[WorkspaceErrors.GET_BUILDS_ERROR] ? ( + + ) : ( + )} - - - {workspaceErrors[WorkspaceErrors.GET_BUILDS_ERROR] ? ( - - ) : ( - - )} - - + ) diff --git a/site/src/pages/TemplatePage/TemplatePageView.tsx b/site/src/pages/TemplatePage/TemplatePageView.tsx index 4bac2b470b46a..440c964afbbba 100644 --- a/site/src/pages/TemplatePage/TemplatePageView.tsx +++ b/site/src/pages/TemplatePage/TemplatePageView.tsx @@ -57,6 +57,12 @@ export const TemplatePageView: FC const readme = frontMatter(activeTemplateVersion.readme) const hasIcon = template.icon && template.icon !== "" + const deleteError = deleteTemplateError ? ( + + ) : ( + <> + ) + const getStartedResources = (resources: WorkspaceResource[]) => { return resources.filter((resource) => resource.workspace_transition === "start") } @@ -72,7 +78,6 @@ export const TemplatePageView: FC return ( <> - {deleteTemplateError && } @@ -125,6 +130,7 @@ export const TemplatePageView: FC + {deleteError} Date: Wed, 31 Aug 2022 18:49:11 +0000 Subject: [PATCH 15/18] Format --- site/src/components/Workspace/Workspace.tsx | 25 +++++++++------------ 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/site/src/components/Workspace/Workspace.tsx b/site/src/components/Workspace/Workspace.tsx index 9a374ec484ed1..647d031efad54 100644 --- a/site/src/components/Workspace/Workspace.tsx +++ b/site/src/components/Workspace/Workspace.tsx @@ -118,22 +118,19 @@ export const Workspace: FC> = ({ workspace={workspace} /> - navigate(`/templates`)} - /> + navigate(`/templates`)} /> - + - {!!resources && !!resources.length && ( - - )} + {!!resources && !!resources.length && ( + + )} {workspaceErrors[WorkspaceErrors.GET_BUILDS_ERROR] ? ( From 2744f0c6c07cc39234b5fe01c2c259de0e02066d Mon Sep 17 00:00:00 2001 From: Presley Pizzo Date: Thu, 1 Sep 2022 16:57:07 +0000 Subject: [PATCH 16/18] Replace hook with onBlur --- site/src/components/DropdownButton/DropdownButton.tsx | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/site/src/components/DropdownButton/DropdownButton.tsx b/site/src/components/DropdownButton/DropdownButton.tsx index d5d13ee737b8f..d5ac43a292dc8 100644 --- a/site/src/components/DropdownButton/DropdownButton.tsx +++ b/site/src/components/DropdownButton/DropdownButton.tsx @@ -24,16 +24,6 @@ export const DropdownButton: FC = ({ const [isOpen, setIsOpen] = useState(false) const id = isOpen ? "action-popover" : undefined - /** - * Ensures we close the popover before calling any action handler - */ - useEffect(() => { - setIsOpen(false) - return () => { - setIsOpen(false) - } - }, []) - return ( {/* primary workspace CTA */} @@ -64,6 +54,7 @@ export const DropdownButton: FC = ({ open={isOpen} anchorEl={anchorRef.current} onClose={() => setIsOpen(false)} + onBlur={() => setIsOpen(false)} anchorOrigin={{ vertical: "bottom", horizontal: "right", From e4730365b5e0aa1792fa1ed51321b799738427bf Mon Sep 17 00:00:00 2001 From: Presley Pizzo Date: Thu, 1 Sep 2022 16:57:16 +0000 Subject: [PATCH 17/18] Make style arg optional --- site/src/pages/TemplatePage/TemplatePageView.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/site/src/pages/TemplatePage/TemplatePageView.tsx b/site/src/pages/TemplatePage/TemplatePageView.tsx index 440c964afbbba..08cb829c27bb4 100644 --- a/site/src/pages/TemplatePage/TemplatePageView.tsx +++ b/site/src/pages/TemplatePage/TemplatePageView.tsx @@ -67,9 +67,9 @@ export const TemplatePageView: FC return resources.filter((resource) => resource.workspace_transition === "start") } - const createWorkspaceButton = (className: string) => ( + const createWorkspaceButton = (className?: string) => ( - @@ -105,7 +105,7 @@ export const TemplatePageView: FC canCancel={false} /> ) : ( - createWorkspaceButton("") + createWorkspaceButton() )} } From 3cc888f9710234f17337de073f846721fd8326a4 Mon Sep 17 00:00:00 2001 From: Presley Pizzo Date: Thu, 1 Sep 2022 16:57:54 +0000 Subject: [PATCH 18/18] Format --- site/src/components/DropdownButton/DropdownButton.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/src/components/DropdownButton/DropdownButton.tsx b/site/src/components/DropdownButton/DropdownButton.tsx index d5ac43a292dc8..67e129971b512 100644 --- a/site/src/components/DropdownButton/DropdownButton.tsx +++ b/site/src/components/DropdownButton/DropdownButton.tsx @@ -3,7 +3,7 @@ import Popover from "@material-ui/core/Popover" import { makeStyles } from "@material-ui/core/styles" import { CloseDropdown, OpenDropdown } from "components/DropdownArrows/DropdownArrows" import { DropdownContent } from "components/DropdownButton/DropdownContent/DropdownContent" -import { FC, ReactNode, useEffect, useRef, useState } from "react" +import { FC, ReactNode, useRef, useState } from "react" import { CancelButton } from "./ActionCtas" export interface DropdownButtonProps {