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/HealthPage/Content.tsx b/site/src/pages/HealthPage/Content.tsx index 33548e5011909..a304205c58fe6 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: 8, gap: 8, cursor: "default", }} @@ -182,11 +182,7 @@ export const Pill = forwardRef((props, ref) => { ); }); -type BooleanPillProps = Omit< - ComponentProps, - "children" | "icon" | "value" -> & { - children: string; +type BooleanPillProps = Omit, "icon" | "value"> & { value: boolean; }; diff --git a/site/src/pages/HealthPage/ProvisionerDaemonsPage.tsx b/site/src/pages/HealthPage/ProvisionerDaemonsPage.tsx index 050450be9b360..b7e2b0f04d843 100644 --- a/site/src/pages/HealthPage/ProvisionerDaemonsPage.tsx +++ b/site/src/pages/HealthPage/ProvisionerDaemonsPage.tsx @@ -21,6 +21,9 @@ 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 CloseIcon from "@mui/icons-material/Close"; +import IconButton from "@mui/material/IconButton"; export const ProvisionerDaemonsPage = () => { const healthStatus = useOutletContext(); @@ -129,9 +132,9 @@ export const ProvisionerDaemonsPage = () => { - {Object.keys(extraTags).map((k) => - renderTag(k, extraTags[k]), - )} + {Object.keys(extraTags).map((k) => ( + + ))} @@ -188,13 +191,42 @@ const parseBool = (s: string): { valid: boolean; value: boolean } => { } }; -const renderTag = (k: string, v: string) => { +interface ProvisionerTagProps { + k: string; + v: string; + onDelete?: (key: string) => void; +} + +export const ProvisionerTag: FC = ({ k, v, onDelete }) => { const { valid, value: boolValue } = parseBool(v); const kv = `${k}: ${v}`; + const content = onDelete ? ( + <> + {kv} + { + onDelete(k); + }} + > + + + + ) : ( + kv + ); if (valid) { - return {kv}; + return {content}; } - return }>{kv}; + return }>{content}; }; export default ProvisionerDaemonsPage; diff --git a/site/src/pages/TemplateVersionEditorPage/ProvisionerTagsPopover.stories.tsx b/site/src/pages/TemplateVersionEditorPage/ProvisionerTagsPopover.stories.tsx new file mode 100644 index 0000000000000..664fce53fe260 --- /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: "component/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.test.tsx b/site/src/pages/TemplateVersionEditorPage/ProvisionerTagsPopover.test.tsx new file mode 100644 index 0000000000000..5db8a90f80bd2 --- /dev/null +++ b/site/src/pages/TemplateVersionEditorPage/ProvisionerTagsPopover.test.tsx @@ -0,0 +1,117 @@ +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 new file mode 100644 index 0000000000000..9d65021dc6b77 --- /dev/null +++ b/site/src/pages/TemplateVersionEditorPage/ProvisionerTagsPopover.tsx @@ -0,0 +1,160 @@ +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 * 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 Link from "@mui/material/Link"; +import { docs } from "utils/docs"; + +const initialValues = { + key: "", + value: "", +}; + +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'", + ); + } + + return schema; + }), +}); + +interface ProvisionerTagsPopoverProps { + tags: Record; + onSubmit: (values: typeof initialValues) => void; + onDelete: (key: string) => void; +} + +export const ProvisionerTagsPopover: FC = ({ + tags, + onSubmit, + onDelete, +}) => { + const theme = useTheme(); + + const form = useFormik({ + initialValues, + validationSchema, + onSubmit: (values) => { + onSubmit(values); + form.resetForm(); + }, + }); + const getFieldHelpers = getFormHelpers(form); + + return ( + + + + + + + +
+ + + + Tags are a way to control which provisioner daemons complete + which build jobs.  + + Learn more... + + + } + /> + + {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 11b93f1188f4e..db07c575db99e 100644 --- a/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditor.tsx +++ b/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditor.tsx @@ -53,6 +53,8 @@ import { TopbarIconButton, } from "components/FullPageLayout/Topbar"; import { Sidebar } from "components/FullPageLayout/Sidebar"; +import ButtonGroup from "@mui/material/ButtonGroup"; +import { ProvisionerTagsPopover } from "./ProvisionerTagsPopover"; type Tab = "logs" | "resources" | undefined; // Undefined is to hide the tab @@ -78,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 => { @@ -114,6 +118,8 @@ export const TemplateVersionEditor: FC = ({ onSubmitMissingVariableValues, onCancelSubmitMissingVariableValues, defaultTab, + provisionerTags, + onUpdateProvisionerTags, }) => { const theme = useTheme(); const [selectedTab, setSelectedTab] = useState(defaultTab); @@ -236,20 +242,45 @@ export const TemplateVersionEditor: FC = ({ )} - - } - title="Build template (Ctrl + Enter)" - disabled={disablePreview} - onClick={() => { - triggerPreview(); + button:hover + button": { + borderLeft: "1px solid #FFF", + }, }} + disabled={disablePreview} > - Build - + + } + title="Build template (Ctrl + Enter)" + disabled={disablePreview} + onClick={() => { + triggerPreview(); + }} + > + Build + + { + 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< + Record + >({}); + useEffect(() => { + if (templateVersionQuery.data?.job.tags) { + setProvisionerTags(templateVersionQuery.data.job.tags); + } + }, [templateVersionQuery.data?.job.tags]); + return ( <> @@ -127,7 +137,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 +220,10 @@ export const TemplateVersionEditorPage: FC = () => { onCancelSubmitMissingVariableValues={() => { setIsMissingVariablesDialogOpen(false); }} + provisionerTags={provisionerTags} + onUpdateProvisionerTags={(tags) => { + setProvisionerTags(tags); + }} /> ) : ( 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, }; 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