From 58532a55ae94a6b52b63324b661419fc8b93b1f7 Mon Sep 17 00:00:00 2001 From: Garrett Delfosse Date: Fri, 12 Jan 2024 19:44:28 +0000 Subject: [PATCH 01/11] feat: manage provisioner tags in template editor --- .../HealthPage/ProvisionerDaemonsPage.tsx | 22 ++--- .../TemplateVersionEditor.tsx | 95 ++++++++++++++++--- site/src/utils/provisionertags.ts | 11 +++ 3 files changed, 105 insertions(+), 23 deletions(-) create mode 100644 site/src/utils/provisionertags.ts diff --git a/site/src/pages/HealthPage/ProvisionerDaemonsPage.tsx b/site/src/pages/HealthPage/ProvisionerDaemonsPage.tsx index 050450be9b360..26e8d4b9cf377 100644 --- a/site/src/pages/HealthPage/ProvisionerDaemonsPage.tsx +++ b/site/src/pages/HealthPage/ProvisionerDaemonsPage.tsx @@ -21,6 +21,8 @@ import Person from "@mui/icons-material/Person"; import SwapHoriz from "@mui/icons-material/SwapHoriz"; import Tooltip from "@mui/material/Tooltip"; import Sell from "@mui/icons-material/Sell"; +import { FC } from "react"; +import { additionalTags } from "utils/provisionertags"; export const ProvisionerDaemonsPage = () => { const healthStatus = useOutletContext(); @@ -58,15 +60,7 @@ export const ProvisionerDaemonsPage = () => { const daemonScope = daemon.tags["scope"] || "organization"; const iconScope = daemonScope === "organization" ? : ; - const extraTags = Object.keys(daemon.tags) - .filter((key) => key !== "scope" && key !== "owner") - .reduce( - (acc, key) => { - acc[key] = daemon.tags[key]; - return acc; - }, - {} as Record, - ); + const extraTags = additionalTags(daemon.tags) const isWarning = warnings.length > 0; return (
{ {Object.keys(extraTags).map((k) => - renderTag(k, extraTags[k]), + )}
@@ -188,7 +182,13 @@ const parseBool = (s: string): { valid: boolean; value: boolean } => { } }; -const renderTag = (k: string, v: string) => { +interface ProvisionerTagProps { + k: string; + v: string; + onDelete?: () => void; +} + +export const ProvisionerTag : FC = ({ k, v }) => { const { valid, value: boolValue } = parseBool(v); const kv = `${k}: ${v}`; if (valid) { diff --git a/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditor.tsx b/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditor.tsx index 11b93f1188f4e..4147a1ef46e26 100644 --- a/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditor.tsx +++ b/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditor.tsx @@ -53,6 +53,18 @@ import { TopbarIconButton, } from "components/FullPageLayout/Topbar"; import { Sidebar } from "components/FullPageLayout/Sidebar"; +import ButtonGroup from "@mui/material/ButtonGroup"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "components/Popover/Popover"; +import { HelpTooltipTitle, HelpTooltipText, HelpTooltipLinksGroup, HelpTooltipLink } from "components/HelpTooltip/HelpTooltip"; +import { docs } from "utils/docs"; +import ExpandMoreOutlined from "@mui/icons-material/ExpandMoreOutlined"; +import { ProvisionerTag } from "pages/HealthPage/ProvisionerDaemonsPage"; +import { Stack } from "components/Stack/Stack"; +import { additionalTags } from "utils/provisionertags"; type Tab = "logs" | "resources" | undefined; // Undefined is to hide the tab @@ -181,6 +193,16 @@ export const TemplateVersionEditor: FC = ({ } }, [buildLogs]); + const disabled = false; + const extraTags = additionalTags(templateVersion.job.tags); + // const extraTags = { + // "key1": "value1", + // "1": "2", + // "3": "true", + // "5": "6", + // "seven": "0", + // } as Record; + return ( <>
@@ -236,20 +258,69 @@ export const TemplateVersionEditor: FC = ({ )} - - } - title="Build template (Ctrl + Enter)" - disabled={disablePreview} - onClick={() => { - triggerPreview(); + button:hover + button": { + borderLeft: "1px solid #FFF", + }, }} + disabled={disabled} > - Build - + + } + title="Build template (Ctrl + Enter)" + disabled={disablePreview} + onClick={() => { + triggerPreview(); + }} + > + Build + + + + + + + + +
+ Provisioner Tags + + {Object.keys(extraTags).length > 0 ? ( + + {Object.keys(extraTags).map((k) => + + )} + + ) : ( + "No tags" + )} + + +
+
+
+ ) => { + return Object.keys(records) + .filter((key) => key !== "scope" && key !== "owner") + .reduce( + (acc, key) => { + acc[key] = records[key]; + return acc; + }, + {} as Record, + ); +} From 77d27cd4f7571fc3d44ea2de16de4d8a35c54e33 Mon Sep 17 00:00:00 2001 From: Garrett Delfosse Date: Tue, 16 Jan 2024 21:22:18 +0000 Subject: [PATCH 02/11] before form --- site/src/pages/HealthPage/Content.tsx | 6 +- .../HealthPage/ProvisionerDaemonsPage.tsx | 25 ++++++- .../TemplateVersionEditor.tsx | 70 +++++++++++++++---- 3 files changed, 81 insertions(+), 20 deletions(-) diff --git a/site/src/pages/HealthPage/Content.tsx b/site/src/pages/HealthPage/Content.tsx index 33548e5011909..345e9501fe9d5 100644 --- a/site/src/pages/HealthPage/Content.tsx +++ b/site/src/pages/HealthPage/Content.tsx @@ -170,7 +170,7 @@ export const Pill = forwardRef((props, ref) => { border: `1px solid ${theme.palette.divider}`, fontSize: 12, fontWeight: 500, - padding: "8px 16px 8px 8px", + padding: "8px 8px 8px 8px", gap: 8, cursor: "default", }} @@ -183,10 +183,8 @@ export const Pill = forwardRef((props, ref) => { }); type BooleanPillProps = Omit< - ComponentProps, - "children" | "icon" | "value" + ComponentProps, "icon" | "value" > & { - children: string; value: boolean; }; diff --git a/site/src/pages/HealthPage/ProvisionerDaemonsPage.tsx b/site/src/pages/HealthPage/ProvisionerDaemonsPage.tsx index 26e8d4b9cf377..2e429fc7fe8c1 100644 --- a/site/src/pages/HealthPage/ProvisionerDaemonsPage.tsx +++ b/site/src/pages/HealthPage/ProvisionerDaemonsPage.tsx @@ -23,6 +23,8 @@ import Tooltip from "@mui/material/Tooltip"; import Sell from "@mui/icons-material/Sell"; import { FC } from "react"; import { additionalTags } from "utils/provisionertags"; +import CloseIcon from '@mui/icons-material/Close'; +import IconButton from "@mui/material/IconButton"; export const ProvisionerDaemonsPage = () => { const healthStatus = useOutletContext(); @@ -188,13 +190,30 @@ interface ProvisionerTagProps { onDelete?: () => void; } -export const ProvisionerTag : FC = ({ k, v }) => { +export const ProvisionerTag : FC = ({ k, v, onDelete}) => { const { valid, value: boolValue } = parseBool(v); const kv = `${k}: ${v}`; + const content = ( + <> + {onDelete ? ( + <> + {kv} + + + + + ) : ( + <>{kv} + )} + + ) if (valid) { - return {kv}; + return {content}; } - return }>{kv}; + return }>{content}; }; export default ProvisionerDaemonsPage; diff --git a/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditor.tsx b/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditor.tsx index 4147a1ef46e26..0ae570dffc4cd 100644 --- a/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditor.tsx +++ b/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditor.tsx @@ -59,12 +59,13 @@ import { PopoverContent, PopoverTrigger, } from "components/Popover/Popover"; -import { HelpTooltipTitle, HelpTooltipText, HelpTooltipLinksGroup, HelpTooltipLink } from "components/HelpTooltip/HelpTooltip"; -import { docs } from "utils/docs"; +import { HelpTooltipTitle, HelpTooltipText, } from "components/HelpTooltip/HelpTooltip"; import ExpandMoreOutlined from "@mui/icons-material/ExpandMoreOutlined"; import { ProvisionerTag } from "pages/HealthPage/ProvisionerDaemonsPage"; import { Stack } from "components/Stack/Stack"; import { additionalTags } from "utils/provisionertags"; +import TextField from "@mui/material/TextField"; +import AddIcon from '@mui/icons-material/Add'; type Tab = "logs" | "resources" | undefined; // Undefined is to hide the tab @@ -194,8 +195,10 @@ export const TemplateVersionEditor: FC = ({ }, [buildLogs]); const disabled = false; - const extraTags = additionalTags(templateVersion.job.tags); - // const extraTags = { + const [extraTags, setExtraTags] = useState(additionalTags(templateVersion.job.tags)); + const [keyInput, setKeyInput] = useState(""); + const [valueInput, setValueInput] = useState(""); + // extraTags = { // "key1": "value1", // "1": "2", // "3": "true", @@ -306,16 +309,57 @@ export const TemplateVersionEditor: FC = ({ > Provisioner Tags - {Object.keys(extraTags).length > 0 ? ( - - {Object.keys(extraTags).map((k) => - - )} + + {Object.keys(extraTags).length > 0 ? ( + + {Object.keys(extraTags).map((k) => + { + return + }}/> + )} + + ) : ("No tags")} + + + { + setKeyInput(event.target.value); + }} + label="Key" + /> + { + setValueInput(event.target.value); + }} + label="Value" + /> + + - ) : ( - "No tags" - )} -
From 9de069f21d851973fe4b8a71775408d680e75cb2 Mon Sep 17 00:00:00 2001 From: Garrett Delfosse Date: Wed, 17 Jan 2024 18:26:19 +0000 Subject: [PATCH 03/11] get mvp working --- site/src/components/Form/Form.tsx | 6 +- .../HealthPage/ProvisionerDaemonsPage.tsx | 6 +- .../ProvisionerTagsPopover.tsx | 130 ++++++++++++++++++ .../TemplateVersionEditor.tsx | 124 +++-------------- .../TemplateVersionEditorPage.tsx | 14 +- 5 files changed, 171 insertions(+), 109 deletions(-) create mode 100644 site/src/pages/TemplateVersionEditorPage/ProvisionerTagsPopover.tsx diff --git a/site/src/components/Form/Form.tsx b/site/src/components/Form/Form.tsx index a7e902a3deb59..8eccfe7f126f5 100644 --- a/site/src/components/Form/Form.tsx +++ b/site/src/components/Form/Form.tsx @@ -70,7 +70,7 @@ export const VerticalForm: FC> = ({ export const FormSection: FC< PropsWithChildren & { title: string | JSX.Element; - description: string | JSX.Element; + description: string | JSX.Element | undefined; classes?: { root?: string; sectionInfo?: string; @@ -125,7 +125,9 @@ export const FormSection: FC< {alpha && } {deprecated && } -
{description}
+ {description && ( +
{description}
+ )} {children} diff --git a/site/src/pages/HealthPage/ProvisionerDaemonsPage.tsx b/site/src/pages/HealthPage/ProvisionerDaemonsPage.tsx index 2e429fc7fe8c1..73e21aa905f06 100644 --- a/site/src/pages/HealthPage/ProvisionerDaemonsPage.tsx +++ b/site/src/pages/HealthPage/ProvisionerDaemonsPage.tsx @@ -187,7 +187,7 @@ const parseBool = (s: string): { valid: boolean; value: boolean } => { interface ProvisionerTagProps { k: string; v: string; - onDelete?: () => void; + onDelete?: (key: string) => void; } export const ProvisionerTag : FC = ({ k, v, onDelete}) => { @@ -198,7 +198,9 @@ export const ProvisionerTag : FC = ({ k, v, onDelete}) => { {onDelete ? ( <> {kv} - + { + onDelete(k) + }}> { + if (key === "scope") { + return schema.oneOf(["organization", "scope"], "Scope value must be 'organization' or 'user'"); + } + + return schema; + }) +}); + +interface ProviderTagsPopoverProps { + tags: Record ; + onSubmit: (values: typeof initialValues) => void; + onDelete: (key: string) => void; +} + +export const ProviderTagsPopover: FC = ({ tags, onSubmit, onDelete }) => { + const theme = useTheme(); + + const form = useFormik({ + initialValues, + validationSchema, + onSubmit: (values) => { + onSubmit(values); + form.resetForm(); + }, + }); + const getFieldHelpers = getFormHelpers(form); + + return ( + + + + + + + +
+ + + + + {Object.keys(tags).length > 0 && ( + + {Object.keys(tags).filter((key) => { + // filter out owner since you cannot override it + return key !== "owner" + }).map((k) => + <> + {k === "scope" ? ( + + ) : ( + + )} + + )} + + )} + + + + + + + + + + + +
+
+
+ ); +}; diff --git a/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditor.tsx b/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditor.tsx index 0ae570dffc4cd..c700f5b1d71ea 100644 --- a/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditor.tsx +++ b/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditor.tsx @@ -54,18 +54,7 @@ import { } from "components/FullPageLayout/Topbar"; import { Sidebar } from "components/FullPageLayout/Sidebar"; import ButtonGroup from "@mui/material/ButtonGroup"; -import { - Popover, - PopoverContent, - PopoverTrigger, -} from "components/Popover/Popover"; -import { HelpTooltipTitle, HelpTooltipText, } from "components/HelpTooltip/HelpTooltip"; -import ExpandMoreOutlined from "@mui/icons-material/ExpandMoreOutlined"; -import { ProvisionerTag } from "pages/HealthPage/ProvisionerDaemonsPage"; -import { Stack } from "components/Stack/Stack"; -import { additionalTags } from "utils/provisionertags"; -import TextField from "@mui/material/TextField"; -import AddIcon from '@mui/icons-material/Add'; +import { ProviderTagsPopover } from "./ProvisionerTagsPopover"; type Tab = "logs" | "resources" | undefined; // Undefined is to hide the tab @@ -91,6 +80,8 @@ export interface TemplateVersionEditorProps { onSubmitMissingVariableValues: (values: VariableValue[]) => void; onCancelSubmitMissingVariableValues: () => void; defaultTab?: Tab; + provisionerTags: Record; + onUpdateProvisionerTags: (tags: Record) => void; } const findInitialFile = (fileTree: FileTree): string | undefined => { @@ -127,6 +118,8 @@ export const TemplateVersionEditor: FC = ({ onSubmitMissingVariableValues, onCancelSubmitMissingVariableValues, defaultTab, + provisionerTags, + onUpdateProvisionerTags, }) => { const theme = useTheme(); const [selectedTab, setSelectedTab] = useState(defaultTab); @@ -194,18 +187,6 @@ export const TemplateVersionEditor: FC = ({ } }, [buildLogs]); - const disabled = false; - const [extraTags, setExtraTags] = useState(additionalTags(templateVersion.job.tags)); - const [keyInput, setKeyInput] = useState(""); - const [valueInput, setValueInput] = useState(""); - // extraTags = { - // "key1": "value1", - // "1": "2", - // "3": "true", - // "5": "6", - // "seven": "0", - // } as Record; - return ( <>
@@ -269,7 +250,7 @@ export const TemplateVersionEditor: FC = ({ borderLeft: "1px solid #FFF", }, }} - disabled={disabled} + disabled={disablePreview} > = ({ > Build - - - - - - - -
- Provisioner Tags - - - {Object.keys(extraTags).length > 0 ? ( - - {Object.keys(extraTags).map((k) => - { - return - }}/> - )} - - ) : ("No tags")} - - - { - setKeyInput(event.target.value); - }} - label="Key" - /> - { - setValueInput(event.target.value); - }} - label="Value" - /> - - - - -
-
-
+ { + onUpdateProvisionerTags({ + ...provisionerTags, + [key]: value, + }); + }} + onDelete={(key) => { + const newTags = { ...provisionerTags }; + delete newTags[key]; + onUpdateProvisionerTags(newTags); + }} + /> { queryClient.setQueryData(templateVersionOptions.queryKey, newVersion); }; + // Provisioner Tags + const [provisionerTags, setProvisionerTags] = useState>({}); + useEffect(() => { + if (templateVersionQuery.data?.job.tags) { + setProvisionerTags(templateVersionQuery.data.job.tags); + } + }, [templateVersionQuery.data?.job.tags]); + return ( <> @@ -127,7 +135,7 @@ export const TemplateVersionEditorPage: FC = () => { const newVersion = await createTemplateVersionMutation.mutateAsync({ provisioner: "terraform", storage_method: "file", - tags: templateVersionQuery.data.job.tags, + tags: provisionerTags, template_id: templateQuery.data.id, file_id: serverFile.hash, }); @@ -210,6 +218,10 @@ export const TemplateVersionEditorPage: FC = () => { onCancelSubmitMissingVariableValues={() => { setIsMissingVariablesDialogOpen(false); }} + provisionerTags={provisionerTags} + onUpdateProvisionerTags={(tags) => { + setProvisionerTags(tags); + }} /> ) : ( From 07b2241edd9f8105fa08bd53cd4b0685f6b87e94 Mon Sep 17 00:00:00 2001 From: Garrett Delfosse Date: Wed, 17 Jan 2024 19:31:05 +0000 Subject: [PATCH 04/11] pr comments --- site/src/components/Form/Form.tsx | 2 +- site/src/pages/HealthPage/Content.tsx | 2 +- .../HealthPage/ProvisionerDaemonsPage.tsx | 49 ++++++++++++------- .../ProvisionerTagsPopover.tsx | 4 +- 4 files changed, 33 insertions(+), 24 deletions(-) diff --git a/site/src/components/Form/Form.tsx b/site/src/components/Form/Form.tsx index 8eccfe7f126f5..d368a39e8c7a4 100644 --- a/site/src/components/Form/Form.tsx +++ b/site/src/components/Form/Form.tsx @@ -70,7 +70,7 @@ export const VerticalForm: FC> = ({ export const FormSection: FC< PropsWithChildren & { title: string | JSX.Element; - description: string | JSX.Element | undefined; + description: string | JSX.Element; classes?: { root?: string; sectionInfo?: string; diff --git a/site/src/pages/HealthPage/Content.tsx b/site/src/pages/HealthPage/Content.tsx index 345e9501fe9d5..b95e9dd99d60d 100644 --- a/site/src/pages/HealthPage/Content.tsx +++ b/site/src/pages/HealthPage/Content.tsx @@ -170,7 +170,7 @@ export const Pill = forwardRef((props, ref) => { border: `1px solid ${theme.palette.divider}`, fontSize: 12, fontWeight: 500, - padding: "8px 8px 8px 8px", + padding: 8, gap: 8, cursor: "default", }} diff --git a/site/src/pages/HealthPage/ProvisionerDaemonsPage.tsx b/site/src/pages/HealthPage/ProvisionerDaemonsPage.tsx index 73e21aa905f06..9bdd7bae7adb9 100644 --- a/site/src/pages/HealthPage/ProvisionerDaemonsPage.tsx +++ b/site/src/pages/HealthPage/ProvisionerDaemonsPage.tsx @@ -22,7 +22,6 @@ import SwapHoriz from "@mui/icons-material/SwapHoriz"; import Tooltip from "@mui/material/Tooltip"; import Sell from "@mui/icons-material/Sell"; import { FC } from "react"; -import { additionalTags } from "utils/provisionertags"; import CloseIcon from '@mui/icons-material/Close'; import IconButton from "@mui/material/IconButton"; @@ -62,7 +61,15 @@ export const ProvisionerDaemonsPage = () => { const daemonScope = daemon.tags["scope"] || "organization"; const iconScope = daemonScope === "organization" ? : ; - const extraTags = additionalTags(daemon.tags) + const extraTags = Object.keys(daemon.tags) + .filter((key) => key !== "scope" && key !== "owner") + .reduce( + (acc, key) => { + acc[key] = daemon.tags[key]; + return acc; + }, + {} as Record, + ); const isWarning = warnings.length > 0; return (
= ({ k, v, onDelete}) => { const { valid, value: boolValue } = parseBool(v); const kv = `${k}: ${v}`; - const content = ( + const content = onDelete ? ( <> - {onDelete ? ( - <> - {kv} - { - onDelete(k) - }}> - - - - ) : ( - <>{kv} - )} + {kv} + { + onDelete(k); + }} + > + + - ) + ) : ( + kv + ); if (valid) { return {content}; } diff --git a/site/src/pages/TemplateVersionEditorPage/ProvisionerTagsPopover.tsx b/site/src/pages/TemplateVersionEditorPage/ProvisionerTagsPopover.tsx index 89e9d263e315b..e9ed12b58deda 100644 --- a/site/src/pages/TemplateVersionEditorPage/ProvisionerTagsPopover.tsx +++ b/site/src/pages/TemplateVersionEditorPage/ProvisionerTagsPopover.tsx @@ -54,10 +54,9 @@ export const ProviderTagsPopover: FC = ({ tags, onSubm const getFieldHelpers = getFormHelpers(form); return ( - + @@ -76,7 +75,6 @@ export const ProviderTagsPopover: FC = ({ tags, onSubm }} > - {Object.keys(tags).length > 0 && ( From 94fecc5eb696418708791790ee42117a4dce5e21 Mon Sep 17 00:00:00 2001 From: Garrett Delfosse Date: Wed, 17 Jan 2024 19:31:47 +0000 Subject: [PATCH 05/11] fmt --- site/src/pages/HealthPage/Content.tsx | 4 +- .../HealthPage/ProvisionerDaemonsPage.tsx | 24 +-- .../ProvisionerTagsPopover.tsx | 197 ++++++++++-------- .../TemplateVersionEditorPage.tsx | 4 +- site/src/utils/provisionertags.ts | 11 - 5 files changed, 124 insertions(+), 116 deletions(-) delete mode 100644 site/src/utils/provisionertags.ts diff --git a/site/src/pages/HealthPage/Content.tsx b/site/src/pages/HealthPage/Content.tsx index b95e9dd99d60d..a304205c58fe6 100644 --- a/site/src/pages/HealthPage/Content.tsx +++ b/site/src/pages/HealthPage/Content.tsx @@ -182,9 +182,7 @@ export const Pill = forwardRef((props, ref) => { ); }); -type BooleanPillProps = Omit< - ComponentProps, "icon" | "value" -> & { +type BooleanPillProps = Omit, "icon" | "value"> & { value: boolean; }; diff --git a/site/src/pages/HealthPage/ProvisionerDaemonsPage.tsx b/site/src/pages/HealthPage/ProvisionerDaemonsPage.tsx index 9bdd7bae7adb9..ae41ed63a46ba 100644 --- a/site/src/pages/HealthPage/ProvisionerDaemonsPage.tsx +++ b/site/src/pages/HealthPage/ProvisionerDaemonsPage.tsx @@ -22,7 +22,7 @@ import SwapHoriz from "@mui/icons-material/SwapHoriz"; import Tooltip from "@mui/material/Tooltip"; import Sell from "@mui/icons-material/Sell"; import { FC } from "react"; -import CloseIcon from '@mui/icons-material/Close'; +import CloseIcon from "@mui/icons-material/Close"; import IconButton from "@mui/material/IconButton"; export const ProvisionerDaemonsPage = () => { @@ -62,14 +62,14 @@ export const ProvisionerDaemonsPage = () => { const iconScope = daemonScope === "organization" ? : ; const extraTags = Object.keys(daemon.tags) - .filter((key) => key !== "scope" && key !== "owner") - .reduce( - (acc, key) => { - acc[key] = daemon.tags[key]; - return acc; - }, - {} as Record, - ); + .filter((key) => key !== "scope" && key !== "owner") + .reduce( + (acc, key) => { + acc[key] = daemon.tags[key]; + return acc; + }, + {} as Record, + ); const isWarning = warnings.length > 0; return (
{ - {Object.keys(extraTags).map((k) => + {Object.keys(extraTags).map((k) => ( - )} + ))}
@@ -197,7 +197,7 @@ interface ProvisionerTagProps { onDelete?: (key: string) => void; } -export const ProvisionerTag : FC = ({ k, v, onDelete}) => { +export const ProvisionerTag: FC = ({ k, v, onDelete }) => { const { valid, value: boolValue } = parseBool(v); const kv = `${k}: ${v}`; const content = onDelete ? ( diff --git a/site/src/pages/TemplateVersionEditorPage/ProvisionerTagsPopover.tsx b/site/src/pages/TemplateVersionEditorPage/ProvisionerTagsPopover.tsx index e9ed12b58deda..385b6486306e8 100644 --- a/site/src/pages/TemplateVersionEditorPage/ProvisionerTagsPopover.tsx +++ b/site/src/pages/TemplateVersionEditorPage/ProvisionerTagsPopover.tsx @@ -1,22 +1,21 @@ - -import { Stack } from 'components/Stack/Stack'; -import { TopbarButton } from 'components/FullPageLayout/Topbar'; +import { Stack } from "components/Stack/Stack"; +import { TopbarButton } from "components/FullPageLayout/Topbar"; import { Popover, PopoverContent, PopoverTrigger, } from "components/Popover/Popover"; -import { ProvisionerTag } from 'pages/HealthPage/ProvisionerDaemonsPage'; -import { type FC} from 'react'; -import useTheme from '@mui/system/useTheme'; -import { useFormik } from 'formik'; +import { ProvisionerTag } from "pages/HealthPage/ProvisionerDaemonsPage"; +import { type FC } from "react"; +import useTheme from "@mui/system/useTheme"; +import { useFormik } from "formik"; import * as Yup from "yup"; -import { getFormHelpers, onChangeTrimmed } from 'utils/formUtils'; -import { FormFields, FormSection, VerticalForm } from 'components/Form/Form'; -import TextField from '@mui/material/TextField'; -import Button from '@mui/material/Button'; -import ExpandMoreOutlined from '@mui/icons-material/ExpandMoreOutlined'; -import AddIcon from '@mui/icons-material/Add'; +import { getFormHelpers, onChangeTrimmed } from "utils/formUtils"; +import { FormFields, FormSection, VerticalForm } from "components/Form/Form"; +import TextField from "@mui/material/TextField"; +import Button from "@mui/material/Button"; +import ExpandMoreOutlined from "@mui/icons-material/ExpandMoreOutlined"; +import AddIcon from "@mui/icons-material/Add"; const initialValues = { key: "", @@ -24,23 +23,34 @@ const initialValues = { }; const validationSchema = Yup.object({ - key: Yup.string().required("Required").notOneOf(["owner"], "Cannot override owner tag"), - value: Yup.string().required("Required").when("key", ([key], schema) => { - if (key === "scope") { - return schema.oneOf(["organization", "scope"], "Scope value must be 'organization' or 'user'"); - } + key: Yup.string() + .required("Required") + .notOneOf(["owner"], "Cannot override owner tag"), + value: Yup.string() + .required("Required") + .when("key", ([key], schema) => { + if (key === "scope") { + return schema.oneOf( + ["organization", "scope"], + "Scope value must be 'organization' or 'user'", + ); + } - return schema; - }) + return schema; + }), }); interface ProviderTagsPopoverProps { - tags: Record ; + tags: Record; onSubmit: (values: typeof initialValues) => void; onDelete: (key: string) => void; } -export const ProviderTagsPopover: FC = ({ tags, onSubmit, onDelete }) => { +export const ProviderTagsPopover: FC = ({ + tags, + onSubmit, + onDelete, +}) => { const theme = useTheme(); const form = useFormik({ @@ -55,74 +65,83 @@ export const ProviderTagsPopover: FC = ({ tags, onSubm return ( - - - - - - -
+ + + + + - - - - {Object.keys(tags).length > 0 && ( - - {Object.keys(tags).filter((key) => { - // filter out owner since you cannot override it - return key !== "owner" - }).map((k) => - <> - {k === "scope" ? ( - - ) : ( - - )} - - )} - - )} - +
+ + + + {Object.keys(tags).length > 0 && ( + + {Object.keys(tags) + .filter((key) => { + // filter out owner since you cannot override it + return key !== "owner"; + }) + .map((k) => ( + <> + {k === "scope" ? ( + + ) : ( + + )} + + ))} + + )} - - - - - - - - - -
-
- + + + + + + + + + +
+
+
); }; diff --git a/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditorPage.tsx b/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditorPage.tsx index 58209090cc65f..27dc4330be6ab 100644 --- a/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditorPage.tsx +++ b/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditorPage.tsx @@ -103,7 +103,9 @@ export const TemplateVersionEditorPage: FC = () => { }; // Provisioner Tags - const [provisionerTags, setProvisionerTags] = useState>({}); + const [provisionerTags, setProvisionerTags] = useState< + Record + >({}); useEffect(() => { if (templateVersionQuery.data?.job.tags) { setProvisionerTags(templateVersionQuery.data.job.tags); diff --git a/site/src/utils/provisionertags.ts b/site/src/utils/provisionertags.ts deleted file mode 100644 index 14ea2f9350e86..0000000000000 --- a/site/src/utils/provisionertags.ts +++ /dev/null @@ -1,11 +0,0 @@ -export const additionalTags = (records: Record) => { - return Object.keys(records) - .filter((key) => key !== "scope" && key !== "owner") - .reduce( - (acc, key) => { - acc[key] = records[key]; - return acc; - }, - {} as Record, - ); -} From 95159b9d2ab0ec1c4d29b43a03a67c2dc4aa5453 Mon Sep 17 00:00:00 2001 From: Garrett Delfosse Date: Wed, 17 Jan 2024 19:33:37 +0000 Subject: [PATCH 06/11] remove file --- site/src/components/Form/Form.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/site/src/components/Form/Form.tsx b/site/src/components/Form/Form.tsx index d368a39e8c7a4..a7e902a3deb59 100644 --- a/site/src/components/Form/Form.tsx +++ b/site/src/components/Form/Form.tsx @@ -125,9 +125,7 @@ export const FormSection: FC< {alpha && } {deprecated && } - {description && ( -
{description}
- )} +
{description}
{children} From b62fd65352a5f94fc2ee03d1c6f93c3999111760 Mon Sep 17 00:00:00 2001 From: Garrett Delfosse Date: Wed, 17 Jan 2024 20:21:52 +0000 Subject: [PATCH 07/11] add storybook --- site/package.json | 1 + site/pnpm-lock.yaml | 88 +++++++++++++++---- .../ProvisionerTagsPopover.stories.tsx | 40 +++++++++ .../ProvisionerTagsPopover.tsx | 4 +- .../TemplateVersionEditor.tsx | 4 +- site/src/testHelpers/entities.ts | 9 +- 6 files changed, 124 insertions(+), 22 deletions(-) create mode 100644 site/src/pages/TemplateVersionEditorPage/ProvisionerTagsPopover.stories.tsx diff --git a/site/package.json b/site/package.json index 98543875a1055..67709c129b031 100644 --- a/site/package.json +++ b/site/package.json @@ -106,6 +106,7 @@ "@storybook/addon-links": "7.5.2", "@storybook/addon-mdx-gfm": "7.5.2", "@storybook/addon-themes": "7.6.4", + "@storybook/preview-api": "7.6.9", "@storybook/react": "7.5.2", "@storybook/react-vite": "7.5.2", "@swc/core": "1.3.38", diff --git a/site/pnpm-lock.yaml b/site/pnpm-lock.yaml index ff9b1c6a59983..b8641ff8f4f19 100644 --- a/site/pnpm-lock.yaml +++ b/site/pnpm-lock.yaml @@ -239,6 +239,9 @@ devDependencies: '@storybook/addon-themes': specifier: 7.6.4 version: 7.6.4 + '@storybook/preview-api': + specifier: 7.6.9 + version: 7.6.9 '@storybook/react': specifier: 7.5.2 version: 7.5.2(react-dom@18.2.0)(react@18.2.0)(typescript@5.2.2) @@ -406,7 +409,7 @@ devDependencies: version: 7.5.2 storybook-addon-react-router-v6: specifier: 2.0.0 - version: 2.0.0(@storybook/blocks@7.5.3)(@storybook/channels@7.5.3)(@storybook/components@7.5.3)(@storybook/core-events@7.5.3)(@storybook/manager-api@7.5.3)(@storybook/preview-api@7.5.3)(@storybook/theming@7.5.3)(react-dom@18.2.0)(react-router-dom@6.20.0)(react-router@6.20.0)(react@18.2.0) + version: 2.0.0(@storybook/blocks@7.5.3)(@storybook/channels@7.5.3)(@storybook/components@7.5.3)(@storybook/core-events@7.5.3)(@storybook/manager-api@7.5.3)(@storybook/preview-api@7.6.9)(@storybook/theming@7.5.3)(react-dom@18.2.0)(react-router-dom@6.20.0)(react-router@6.20.0)(react@18.2.0) storybook-react-context: specifier: 0.6.0 version: 0.6.0(react-dom@18.2.0) @@ -523,14 +526,14 @@ packages: resolution: {integrity: sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.23.0 + '@babel/types': 7.23.4 dev: true /@babel/helper-builder-binary-assignment-operator-visitor@7.22.15: resolution: {integrity: sha512-QkBXwGgaoC2GtGZRoma6kv7Szfv06khvhFav67ZExau2RaXzy8MpHSMO2PNoP2XtmQphJQRHFfg77Bq731Yizw==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.23.0 + '@babel/types': 7.23.4 dev: true /@babel/helper-compilation-targets@7.22.15: @@ -627,7 +630,7 @@ packages: resolution: {integrity: sha512-6gfrPwh7OuT6gZyJZvd6WbTfrqAo7vm4xCzAXOusKqq/vWdKXphTpj5klHKNmRUU6/QRGlBsyU9mAIPaWHlqJA==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.23.0 + '@babel/types': 7.23.4 dev: true /@babel/helper-module-imports@7.22.15: @@ -667,7 +670,7 @@ packages: resolution: {integrity: sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.23.0 + '@babel/types': 7.23.4 dev: true /@babel/helper-plugin-utils@7.22.5: @@ -708,7 +711,7 @@ packages: resolution: {integrity: sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.23.0 + '@babel/types': 7.23.4 dev: true /@babel/helper-split-export-declaration@7.22.6: @@ -740,7 +743,7 @@ packages: dependencies: '@babel/helper-function-name': 7.23.0 '@babel/template': 7.22.15 - '@babel/types': 7.23.0 + '@babel/types': 7.23.4 dev: true /@babel/helpers@7.23.2: @@ -4207,7 +4210,7 @@ packages: '@storybook/client-logger': 7.5.2 '@storybook/components': 7.5.2(@types/react-dom@18.2.4)(@types/react@18.2.6)(react-dom@18.2.0)(react@18.2.0) '@storybook/core-events': 7.5.2 - '@storybook/csf': 0.1.1 + '@storybook/csf': 0.1.2 '@storybook/docs-tools': 7.5.2 '@storybook/global': 5.0.0 '@storybook/manager-api': 7.5.2(react-dom@18.2.0)(react@18.2.0) @@ -4365,6 +4368,17 @@ packages: tiny-invariant: 1.3.1 dev: true + /@storybook/channels@7.6.9: + resolution: {integrity: sha512-goGGZPT294CS1QDF65Fs+PCauvM/nTMseU913ZVSZbFTk4uvqIXOaOraqhQze8A/C8a0yls4qu2Wp00tCnyaTA==} + dependencies: + '@storybook/client-logger': 7.6.9 + '@storybook/core-events': 7.6.9 + '@storybook/global': 5.0.0 + qs: 6.11.2 + telejson: 7.2.0 + tiny-invariant: 1.3.1 + dev: true + /@storybook/cli@7.5.2: resolution: {integrity: sha512-8JPvA/K66zBmRFpRRwsD0JLqZUODRrGmNuAWx+Bj1K8wqbg68MYnOflbkSIxIVxrfhd39OrffV0h8CwKNL9gAg==} hasBin: true @@ -4436,13 +4450,19 @@ packages: '@storybook/global': 5.0.0 dev: true + /@storybook/client-logger@7.6.9: + resolution: {integrity: sha512-Xm6fa6AR3cjxabauMldBv/66OOp5IhDiUEpp4D/a7hXfvCWqwmjVJ6EPz9WzkMhcPbMJr8vWJBaS3glkFqsRng==} + dependencies: + '@storybook/global': 5.0.0 + dev: true + /@storybook/codemod@7.5.2: resolution: {integrity: sha512-PxZg0w4OlmFB4dBzB+sCgwmHNke0n1N8vNooxtcuusrLKlbUfmssYRnQn6yRSJw0WfkUYgI10CWxGaamaOFekA==} dependencies: '@babel/core': 7.23.2 '@babel/preset-env': 7.23.2(@babel/core@7.23.2) '@babel/types': 7.23.0 - '@storybook/csf': 0.1.1 + '@storybook/csf': 0.1.2 '@storybook/csf-tools': 7.5.2 '@storybook/node-logger': 7.5.2 '@storybook/types': 7.5.2 @@ -4590,6 +4610,12 @@ packages: ts-dedent: 2.2.0 dev: true + /@storybook/core-events@7.6.9: + resolution: {integrity: sha512-YCds7AA6sbnnZ2qq5l+AIxhQqYlXB8eVTkjj6phgczsLjkqKapYFxAFc3ppRnE0FcsL2iji17ikHzZ8+eHYznA==} + dependencies: + ts-dedent: 2.2.0 + dev: true + /@storybook/core-server@7.5.2: resolution: {integrity: sha512-4oXpy1L/NyHiz/OXNUFnSeMLA/+lTgQAlVx86pRbEBDj6snt1/NSx2+yZyFtZ/XTnJ22BPpM8IIrgm95ZlQKmA==} dependencies: @@ -4599,7 +4625,7 @@ packages: '@storybook/channels': 7.5.2 '@storybook/core-common': 7.5.2 '@storybook/core-events': 7.5.2 - '@storybook/csf': 0.1.1 + '@storybook/csf': 0.1.2 '@storybook/csf-tools': 7.5.2 '@storybook/docs-mdx': 0.1.0 '@storybook/global': 5.0.0 @@ -4657,7 +4683,7 @@ packages: '@babel/parser': 7.23.0 '@babel/traverse': 7.23.2 '@babel/types': 7.23.0 - '@storybook/csf': 0.1.1 + '@storybook/csf': 0.1.2 '@storybook/types': 7.5.2 fs-extra: 11.1.1 recast: 0.23.4 @@ -4834,6 +4860,25 @@ packages: util-deprecate: 1.0.2 dev: true + /@storybook/preview-api@7.6.9: + resolution: {integrity: sha512-qVRylkOc70Ivz/oRE3cXaQA9r60qXSCXhY8xFjnBvZFjoYr0ImGx+tt0818YzSkhTf6LsNbx9HxwW4+x7JD6dw==} + dependencies: + '@storybook/channels': 7.6.9 + '@storybook/client-logger': 7.6.9 + '@storybook/core-events': 7.6.9 + '@storybook/csf': 0.1.2 + '@storybook/global': 5.0.0 + '@storybook/types': 7.6.9 + '@types/qs': 6.9.10 + dequal: 2.0.3 + lodash: 4.17.21 + memoizerific: 1.11.3 + qs: 6.11.2 + synchronous-promise: 2.0.17 + ts-dedent: 2.2.0 + util-deprecate: 1.0.2 + dev: true + /@storybook/preview@7.5.2: resolution: {integrity: sha512-dA5VpHp0D9nh9/wOzWP8At1wtz/SiaMBbwaiEOFTFUGcPerrkroEWadIlSSB7vgQJ9yWiD4l3KDaS8ANzHWtPQ==} dev: true @@ -5041,6 +5086,15 @@ packages: file-system-cache: 2.3.0 dev: true + /@storybook/types@7.6.9: + resolution: {integrity: sha512-Qnx7exS6bO1MrqasHl12h8/HeBuxrwg2oMXROO7t0qmprV6+DGb6OxztsVIgbKR+m6uqFFM1q+f/Q5soI1qJ6g==} + dependencies: + '@storybook/channels': 7.6.9 + '@types/babel__core': 7.20.5 + '@types/express': 4.17.17 + file-system-cache: 2.3.0 + dev: true + /@swc/core-darwin-arm64@1.3.38: resolution: {integrity: sha512-4ZTJJ/cR0EsXW5UxFCifZoGfzQ07a8s4ayt1nLvLQ5QoB1GTAf9zsACpvWG8e7cmCR0L76R5xt8uJuyr+noIXA==} engines: {node: '>=10'} @@ -6474,7 +6528,7 @@ packages: dependencies: '@babel/core': 7.23.2 '@jest/transform': 29.7.0 - '@types/babel__core': 7.20.3 + '@types/babel__core': 7.20.5 babel-plugin-istanbul: 6.1.1 babel-preset-jest: 29.5.0(@babel/core@7.23.2) chalk: 4.1.2 @@ -6502,9 +6556,9 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@babel/template': 7.22.15 - '@babel/types': 7.23.0 - '@types/babel__core': 7.20.3 - '@types/babel__traverse': 7.20.3 + '@babel/types': 7.23.4 + '@types/babel__core': 7.20.5 + '@types/babel__traverse': 7.20.4 dev: true /babel-plugin-macros@3.1.0: @@ -13162,7 +13216,7 @@ packages: resolution: {integrity: sha512-siT1RiqlfQnGqgT/YzXVUNsom9S0H1OX+dpdGN1xkyYATo4I6sep5NmsRD/40s3IIOvlCq6akxkqG82urIZW1w==} dev: true - /storybook-addon-react-router-v6@2.0.0(@storybook/blocks@7.5.3)(@storybook/channels@7.5.3)(@storybook/components@7.5.3)(@storybook/core-events@7.5.3)(@storybook/manager-api@7.5.3)(@storybook/preview-api@7.5.3)(@storybook/theming@7.5.3)(react-dom@18.2.0)(react-router-dom@6.20.0)(react-router@6.20.0)(react@18.2.0): + /storybook-addon-react-router-v6@2.0.0(@storybook/blocks@7.5.3)(@storybook/channels@7.5.3)(@storybook/components@7.5.3)(@storybook/core-events@7.5.3)(@storybook/manager-api@7.5.3)(@storybook/preview-api@7.6.9)(@storybook/theming@7.5.3)(react-dom@18.2.0)(react-router-dom@6.20.0)(react-router@6.20.0)(react@18.2.0): resolution: {integrity: sha512-M+PR7rdacFDwUCQZRBJVnzyEOqHrDVrTqN8ufqo+TuXxk33QZvb3QeZuo0d2UTYctgA1GY74EX9RJCEXZpv6VQ==} peerDependencies: '@storybook/blocks': ^7.0.0 @@ -13187,7 +13241,7 @@ packages: '@storybook/components': 7.5.3(@types/react-dom@18.2.4)(@types/react@18.2.6)(react-dom@18.2.0)(react@18.2.0) '@storybook/core-events': 7.5.3 '@storybook/manager-api': 7.5.3(react-dom@18.2.0)(react@18.2.0) - '@storybook/preview-api': 7.5.3 + '@storybook/preview-api': 7.6.9 '@storybook/theming': 7.5.3(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) diff --git a/site/src/pages/TemplateVersionEditorPage/ProvisionerTagsPopover.stories.tsx b/site/src/pages/TemplateVersionEditorPage/ProvisionerTagsPopover.stories.tsx new file mode 100644 index 0000000000000..d6f7f2b795789 --- /dev/null +++ b/site/src/pages/TemplateVersionEditorPage/ProvisionerTagsPopover.stories.tsx @@ -0,0 +1,40 @@ +import type { Meta, StoryObj } from "@storybook/react"; +import { chromatic } from "testHelpers/chromatic"; +import { MockTemplateVersion } from "testHelpers/entities"; +import { ProvisionerTagsPopover } from "./ProvisionerTagsPopover"; +import { useArgs } from "@storybook/preview-api"; + +const meta: Meta = { + title: "pages/ProvisionerTagsPopover", + parameters: { + chromatic, + layout: "centered", + }, + component: ProvisionerTagsPopover, + args: { + tags: MockTemplateVersion.job.tags, + }, + render: function Render(args) { + const [{ tags }, updateArgs] = useArgs(); + + return ( + { + updateArgs({ tags: { ...tags, [key]: value } }); + }} + onDelete={(key) => { + const newTags = { ...tags }; + delete newTags[key]; + updateArgs({ tags: newTags }); + }} + /> + ); + }, +}; + +export default meta; +type Story = StoryObj; + +export const Example: Story = {}; diff --git a/site/src/pages/TemplateVersionEditorPage/ProvisionerTagsPopover.tsx b/site/src/pages/TemplateVersionEditorPage/ProvisionerTagsPopover.tsx index 385b6486306e8..9825987732708 100644 --- a/site/src/pages/TemplateVersionEditorPage/ProvisionerTagsPopover.tsx +++ b/site/src/pages/TemplateVersionEditorPage/ProvisionerTagsPopover.tsx @@ -40,13 +40,13 @@ const validationSchema = Yup.object({ }), }); -interface ProviderTagsPopoverProps { +interface ProvisionerTagsPopoverProps { tags: Record; onSubmit: (values: typeof initialValues) => void; onDelete: (key: string) => void; } -export const ProviderTagsPopover: FC = ({ +export const ProvisionerTagsPopover: FC = ({ tags, onSubmit, onDelete, diff --git a/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditor.tsx b/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditor.tsx index c700f5b1d71ea..db07c575db99e 100644 --- a/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditor.tsx +++ b/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditor.tsx @@ -54,7 +54,7 @@ import { } from "components/FullPageLayout/Topbar"; import { Sidebar } from "components/FullPageLayout/Sidebar"; import ButtonGroup from "@mui/material/ButtonGroup"; -import { ProviderTagsPopover } from "./ProvisionerTagsPopover"; +import { ProvisionerTagsPopover } from "./ProvisionerTagsPopover"; type Tab = "logs" | "resources" | undefined; // Undefined is to hide the tab @@ -266,7 +266,7 @@ export const TemplateVersionEditor: FC = ({ > Build
- { onUpdateProvisionerTags({ diff --git a/site/src/testHelpers/entities.ts b/site/src/testHelpers/entities.ts index 1ccfec3395386..6d22ed072f567 100644 --- a/site/src/testHelpers/entities.ts +++ b/site/src/testHelpers/entities.ts @@ -355,7 +355,14 @@ export const MockProvisionerJob: TypesGen.ProvisionerJob = { status: "succeeded", file_id: MockOrganization.id, completed_at: "2022-05-17T17:39:01.382927298Z", - tags: {}, + tags: { + scope: "organization", + owner: "", + wowzers: "whatatag", + isCapable: "false", + department: "engineering", + dreaming: "true", + }, queue_position: 0, queue_size: 0, }; From c7d327d92a41332c4bdcba663234f5b58add6db6 Mon Sep 17 00:00:00 2001 From: Garrett Delfosse Date: Thu, 18 Jan 2024 18:00:33 +0000 Subject: [PATCH 08/11] Add jest tests --- .../HealthPage/ProvisionerDaemonsPage.tsx | 2 +- .../ProvisionerTagsPopover.test.tsx | 111 ++++++++++++++++++ .../ProvisionerTagsPopover.tsx | 3 +- 3 files changed, 113 insertions(+), 3 deletions(-) create mode 100644 site/src/pages/TemplateVersionEditorPage/ProvisionerTagsPopover.test.tsx diff --git a/site/src/pages/HealthPage/ProvisionerDaemonsPage.tsx b/site/src/pages/HealthPage/ProvisionerDaemonsPage.tsx index ae41ed63a46ba..b7e2b0f04d843 100644 --- a/site/src/pages/HealthPage/ProvisionerDaemonsPage.tsx +++ b/site/src/pages/HealthPage/ProvisionerDaemonsPage.tsx @@ -204,7 +204,7 @@ export const ProvisionerTag: FC = ({ k, v, onDelete }) => { <> {kv} { diff --git a/site/src/pages/TemplateVersionEditorPage/ProvisionerTagsPopover.test.tsx b/site/src/pages/TemplateVersionEditorPage/ProvisionerTagsPopover.test.tsx new file mode 100644 index 0000000000000..3f27f42be63f5 --- /dev/null +++ b/site/src/pages/TemplateVersionEditorPage/ProvisionerTagsPopover.test.tsx @@ -0,0 +1,111 @@ +import { renderComponent } from "testHelpers/renderHelpers"; +import { ProvisionerTagsPopover } from "./ProvisionerTagsPopover"; +import { fireEvent, screen } from "@testing-library/react"; +import { MockTemplateVersion } from "testHelpers/entities"; +import userEvent from "@testing-library/user-event"; + +let tags = MockTemplateVersion.job.tags; + +describe("ProvisionerTagsPopover", () => { + describe("click the button", () => { + it("can add a tag", async () => { + const onSubmit = jest.fn().mockImplementation(({key, value}) => { + tags = {...tags, [key]: value} + }); + const onDelete = jest.fn().mockImplementation((key) => { + const newTags = {...tags} + delete newTags[key] + tags = newTags + }); + const {rerender} = renderComponent( + + ); + + // Open Popover + const btn = await screen.findByRole("button"); + expect(btn).toBeEnabled(); + await userEvent.click(btn); + + // Check for existing tags + const el = await screen.findByText(/scope: organization/i); + expect(el).toBeInTheDocument(); + + // Add key and value + const el2 = await screen.findByLabelText("Key"); + expect(el2).toBeEnabled(); + fireEvent.change(el2, { target: { value: "foo" } }); + expect(el2).toHaveValue("foo"); + const el3 = await screen.findByLabelText("Value"); + expect(el3).toBeEnabled(); + fireEvent.change(el3, { target: { value: "bar" } }); + expect(el3).toHaveValue("bar"); + + // Submit + const btn2 = await screen.findByRole("button", { name: /add/i, hidden: true}); + expect(btn2).toBeEnabled(); + await userEvent.click(btn2); + expect(onSubmit).toHaveBeenCalledTimes(1); + + rerender( + + ); + + // Check for new tag + const el4 = await screen.findByText(/foo: bar/i); + expect(el4).toBeInTheDocument(); + }); + it("can remove a tag", async () => { + const onSubmit = jest.fn().mockImplementation(({key, value}) => { + tags = {...tags, [key]: value} + }); + const onDelete = jest.fn().mockImplementation((key) => { + delete tags[key] + tags = {...tags} + }); + const {rerender} = renderComponent( + + ); + + // Open Popover + const btn = await screen.findByRole("button"); + expect(btn).toBeEnabled(); + await userEvent.click(btn); + + // Check for existing tags + const el = await screen.findByText(/wowzers: whatatag/i); + expect(el).toBeInTheDocument(); + + // Find Delete button + const btn2 = await screen.findByRole("button", { name: /delete-wowzers/i, hidden: true}); + expect(btn2).toBeEnabled(); + + // Delete tag + await userEvent.click(btn2); + expect(onDelete).toHaveBeenCalledTimes(1); + + rerender( + + ); + + // Expect deleted tag to be gone + const el2 = screen.queryByText(/wowzers: whatatag/i); + expect(el2).not.toBeInTheDocument(); + }); + }); +}); diff --git a/site/src/pages/TemplateVersionEditorPage/ProvisionerTagsPopover.tsx b/site/src/pages/TemplateVersionEditorPage/ProvisionerTagsPopover.tsx index 9825987732708..d4fb3ff7e7079 100644 --- a/site/src/pages/TemplateVersionEditorPage/ProvisionerTagsPopover.tsx +++ b/site/src/pages/TemplateVersionEditorPage/ProvisionerTagsPopover.tsx @@ -90,7 +90,6 @@ export const ProvisionerTagsPopover: FC = ({ title="Provisioner Tags" description="Tags are a way to control which provisoner daemons process which build jobs. To learn more read the docs. " /> - {Object.keys(tags).length > 0 && ( {Object.keys(tags) .filter((key) => { @@ -112,7 +111,6 @@ export const ProvisionerTagsPopover: FC = ({ ))} - )} @@ -132,6 +130,7 @@ export const ProvisionerTagsPopover: FC = ({ variant="contained" color="secondary" type="submit" + aria-label='add' disabled={!form.dirty || !form.isValid} > From 6d0c615b72412c5d790399380ba7c7af6ff59c54 Mon Sep 17 00:00:00 2001 From: Garrett Delfosse Date: Thu, 18 Jan 2024 18:01:48 +0000 Subject: [PATCH 09/11] fmt --- .../ProvisionerTagsPopover.test.tsx | 40 ++++++++++------- .../ProvisionerTagsPopover.tsx | 44 +++++++++---------- 2 files changed, 45 insertions(+), 39 deletions(-) diff --git a/site/src/pages/TemplateVersionEditorPage/ProvisionerTagsPopover.test.tsx b/site/src/pages/TemplateVersionEditorPage/ProvisionerTagsPopover.test.tsx index 3f27f42be63f5..5db8a90f80bd2 100644 --- a/site/src/pages/TemplateVersionEditorPage/ProvisionerTagsPopover.test.tsx +++ b/site/src/pages/TemplateVersionEditorPage/ProvisionerTagsPopover.test.tsx @@ -9,20 +9,20 @@ let tags = MockTemplateVersion.job.tags; describe("ProvisionerTagsPopover", () => { describe("click the button", () => { it("can add a tag", async () => { - const onSubmit = jest.fn().mockImplementation(({key, value}) => { - tags = {...tags, [key]: value} + const onSubmit = jest.fn().mockImplementation(({ key, value }) => { + tags = { ...tags, [key]: value }; }); const onDelete = jest.fn().mockImplementation((key) => { - const newTags = {...tags} - delete newTags[key] - tags = newTags + const newTags = { ...tags }; + delete newTags[key]; + tags = newTags; }); - const {rerender} = renderComponent( + const { rerender } = renderComponent( + />, ); // Open Popover @@ -45,7 +45,10 @@ describe("ProvisionerTagsPopover", () => { expect(el3).toHaveValue("bar"); // Submit - const btn2 = await screen.findByRole("button", { name: /add/i, hidden: true}); + const btn2 = await screen.findByRole("button", { + name: /add/i, + hidden: true, + }); expect(btn2).toBeEnabled(); await userEvent.click(btn2); expect(onSubmit).toHaveBeenCalledTimes(1); @@ -55,7 +58,7 @@ describe("ProvisionerTagsPopover", () => { tags={tags} onSubmit={onSubmit} onDelete={onDelete} - /> + />, ); // Check for new tag @@ -63,19 +66,19 @@ describe("ProvisionerTagsPopover", () => { expect(el4).toBeInTheDocument(); }); it("can remove a tag", async () => { - const onSubmit = jest.fn().mockImplementation(({key, value}) => { - tags = {...tags, [key]: value} + const onSubmit = jest.fn().mockImplementation(({ key, value }) => { + tags = { ...tags, [key]: value }; }); const onDelete = jest.fn().mockImplementation((key) => { - delete tags[key] - tags = {...tags} + delete tags[key]; + tags = { ...tags }; }); - const {rerender} = renderComponent( + const { rerender } = renderComponent( + />, ); // Open Popover @@ -88,7 +91,10 @@ describe("ProvisionerTagsPopover", () => { expect(el).toBeInTheDocument(); // Find Delete button - const btn2 = await screen.findByRole("button", { name: /delete-wowzers/i, hidden: true}); + const btn2 = await screen.findByRole("button", { + name: /delete-wowzers/i, + hidden: true, + }); expect(btn2).toBeEnabled(); // Delete tag @@ -100,7 +106,7 @@ describe("ProvisionerTagsPopover", () => { tags={tags} onSubmit={onSubmit} onDelete={onDelete} - /> + />, ); // Expect deleted tag to be gone diff --git a/site/src/pages/TemplateVersionEditorPage/ProvisionerTagsPopover.tsx b/site/src/pages/TemplateVersionEditorPage/ProvisionerTagsPopover.tsx index d4fb3ff7e7079..adee5b7a95987 100644 --- a/site/src/pages/TemplateVersionEditorPage/ProvisionerTagsPopover.tsx +++ b/site/src/pages/TemplateVersionEditorPage/ProvisionerTagsPopover.tsx @@ -90,27 +90,27 @@ export const ProvisionerTagsPopover: FC = ({ title="Provisioner Tags" description="Tags are a way to control which provisoner daemons process which build jobs. To learn more read the docs. " /> - - {Object.keys(tags) - .filter((key) => { - // filter out owner since you cannot override it - return key !== "owner"; - }) - .map((k) => ( - <> - {k === "scope" ? ( - - ) : ( - - )} - - ))} - + + {Object.keys(tags) + .filter((key) => { + // filter out owner since you cannot override it + return key !== "owner"; + }) + .map((k) => ( + <> + {k === "scope" ? ( + + ) : ( + + )} + + ))} + @@ -130,7 +130,7 @@ export const ProvisionerTagsPopover: FC = ({ variant="contained" color="secondary" type="submit" - aria-label='add' + aria-label="add" disabled={!form.dirty || !form.isValid} > From 4b283597a93f1de30246a91f89277fffb4355852 Mon Sep 17 00:00:00 2001 From: Garrett Delfosse Date: Thu, 18 Jan 2024 18:17:01 +0000 Subject: [PATCH 10/11] fix comment --- .../ProvisionerTagsPopover.tsx | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/site/src/pages/TemplateVersionEditorPage/ProvisionerTagsPopover.tsx b/site/src/pages/TemplateVersionEditorPage/ProvisionerTagsPopover.tsx index adee5b7a95987..9d65021dc6b77 100644 --- a/site/src/pages/TemplateVersionEditorPage/ProvisionerTagsPopover.tsx +++ b/site/src/pages/TemplateVersionEditorPage/ProvisionerTagsPopover.tsx @@ -16,6 +16,8 @@ import TextField from "@mui/material/TextField"; import Button from "@mui/material/Button"; import ExpandMoreOutlined from "@mui/icons-material/ExpandMoreOutlined"; import AddIcon from "@mui/icons-material/Add"; +import Link from "@mui/material/Link"; +import { docs } from "utils/docs"; const initialValues = { key: "", @@ -88,7 +90,19 @@ export const ProvisionerTagsPopover: FC = ({ + Tags are a way to control which provisioner daemons complete + which build jobs.  + + Learn more... + + + } /> {Object.keys(tags) From cad35c737f3f23bb89a1347f0d812588fb65b769 Mon Sep 17 00:00:00 2001 From: Garrett Delfosse Date: Thu, 18 Jan 2024 20:30:59 +0000 Subject: [PATCH 11/11] rename --- .../ProvisionerTagsPopover.stories.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/src/pages/TemplateVersionEditorPage/ProvisionerTagsPopover.stories.tsx b/site/src/pages/TemplateVersionEditorPage/ProvisionerTagsPopover.stories.tsx index d6f7f2b795789..664fce53fe260 100644 --- a/site/src/pages/TemplateVersionEditorPage/ProvisionerTagsPopover.stories.tsx +++ b/site/src/pages/TemplateVersionEditorPage/ProvisionerTagsPopover.stories.tsx @@ -5,7 +5,7 @@ import { ProvisionerTagsPopover } from "./ProvisionerTagsPopover"; import { useArgs } from "@storybook/preview-api"; const meta: Meta = { - title: "pages/ProvisionerTagsPopover", + title: "component/ProvisionerTagsPopover", parameters: { chromatic, layout: "centered",