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", pFad - Phonifier reborn

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

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


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy