From d640312758b27bfeb900d71a66e40283f6711367 Mon Sep 17 00:00:00 2001 From: Jaayden Halko Date: Fri, 12 Jul 2024 03:57:08 +0000 Subject: [PATCH 01/32] feat: initial changes for multi org template creation --- .../OrganizationAutocomplete.tsx | 143 ++++++++++++++++++ .../CreateTemplatesGalleryPage.tsx} | 26 ++-- .../CreateTemplatesPageView.tsx | 110 ++++++++++++++ .../StarterTemplates.tsx | 113 ++++++++++++++ .../StarterTemplatesPage.test.tsx | 6 +- .../StarterTemplatesPageView.stories.tsx | 0 .../StarterTemplatesPageView.tsx | 39 +++++ .../StarterTemplatesPageView.tsx | 133 ---------------- .../pages/TemplatesPage/TemplatesPageView.tsx | 13 +- site/src/router.tsx | 6 +- 10 files changed, 440 insertions(+), 149 deletions(-) create mode 100644 site/src/components/OrganizationAutocomplete/OrganizationAutocomplete.tsx rename site/src/pages/{StarterTemplatesPage/StarterTemplatesPage.tsx => CreateTemplatesGalleryPage/CreateTemplatesGalleryPage.tsx} (58%) create mode 100644 site/src/pages/CreateTemplatesGalleryPage/CreateTemplatesPageView.tsx create mode 100644 site/src/pages/CreateTemplatesGalleryPage/StarterTemplates.tsx rename site/src/pages/{StarterTemplatesPage => CreateTemplatesGalleryPage}/StarterTemplatesPage.test.tsx (89%) rename site/src/pages/{StarterTemplatesPage => CreateTemplatesGalleryPage}/StarterTemplatesPageView.stories.tsx (100%) create mode 100644 site/src/pages/CreateTemplatesGalleryPage/StarterTemplatesPageView.tsx delete mode 100644 site/src/pages/StarterTemplatesPage/StarterTemplatesPageView.tsx diff --git a/site/src/components/OrganizationAutocomplete/OrganizationAutocomplete.tsx b/site/src/components/OrganizationAutocomplete/OrganizationAutocomplete.tsx new file mode 100644 index 0000000000000..629065abe2673 --- /dev/null +++ b/site/src/components/OrganizationAutocomplete/OrganizationAutocomplete.tsx @@ -0,0 +1,143 @@ +import { css } from "@emotion/css"; +import Autocomplete from "@mui/material/Autocomplete"; +import CircularProgress from "@mui/material/CircularProgress"; +import TextField from "@mui/material/TextField"; +import { + type ChangeEvent, + type ComponentProps, + type FC, + useState, +} from "react"; +import { useQuery } from "react-query"; +import { myOrganizations } from "api/queries/users"; +import type { Organization } from "api/typesGenerated"; +import { Avatar } from "components/Avatar/Avatar"; +import { AvatarData } from "components/AvatarData/AvatarData"; +import { useDebouncedFunction } from "hooks/debounce"; +// import { prepareQuery } from "utils/filters"; + +export type OrganizationAutocompleteProps = { + value: Organization | null; + onChange: (organization: Organization | null) => void; + label?: string; + className?: string; + size?: ComponentProps["size"]; +}; + +export const OrganizationAutocomplete: FC = ({ + value, + onChange, + label, + className, + size = "small", +}) => { + const [autoComplete, setAutoComplete] = useState<{ + value: string; + open: boolean; + }>({ + value: value?.name ?? "", + open: false, + }); + // const usersQuery = useQuery({ + // ...users({ + // q: prepareQuery(encodeURI(autoComplete.value)), + // limit: 25, + // }), + // enabled: autoComplete.open, + // keepPreviousData: true, + // }); + const organizationsQuery = useQuery(myOrganizations()); + + const { debounced: debouncedInputOnChange } = useDebouncedFunction( + (event: ChangeEvent) => { + setAutoComplete((state) => ({ + ...state, + value: event.target.value, + })); + }, + 750, + ); + + return ( + organization} + noOptionsText="No users found" + className={className} + options={organizationsQuery.data ?? []} + loading={organizationsQuery.isLoading} + value={value} + id="organization-autocomplete" + open={autoComplete.open} + onOpen={() => { + setAutoComplete((state) => ({ + ...state, + open: true, + })); + }} + onClose={() => { + setAutoComplete({ + value: value?.name ?? "", + open: false, + }); + }} + onChange={(_, newValue) => { + onChange(newValue); + }} + isOptionEqualToValue={(option: Organization, value: Organization) => + option.name === value.name + } + getOptionLabel={(option) => option.name} + renderOption={(props, option) => ( +
  • + +
  • + )} + renderInput={(params) => ( + + {value.name} + + ), + endAdornment: ( + <> + {organizationsQuery.isFetching && autoComplete.open ? ( + + ) : null} + {params.InputProps.endAdornment} + + ), + classes: { root }, + }} + InputLabelProps={{ + shrink: true, + }} + /> + )} + /> + ); +}; + +const root = css` + padding-left: 14px !important; // Same padding left as input + gap: 4px; +`; diff --git a/site/src/pages/StarterTemplatesPage/StarterTemplatesPage.tsx b/site/src/pages/CreateTemplatesGalleryPage/CreateTemplatesGalleryPage.tsx similarity index 58% rename from site/src/pages/StarterTemplatesPage/StarterTemplatesPage.tsx rename to site/src/pages/CreateTemplatesGalleryPage/CreateTemplatesGalleryPage.tsx index d52c92a12df82..8bc461e1919ad 100644 --- a/site/src/pages/StarterTemplatesPage/StarterTemplatesPage.tsx +++ b/site/src/pages/CreateTemplatesGalleryPage/CreateTemplatesGalleryPage.tsx @@ -6,26 +6,34 @@ import type { TemplateExample } from "api/typesGenerated"; import { useDashboard } from "modules/dashboard/useDashboard"; import { pageTitle } from "utils/page"; import { getTemplatesByTag } from "utils/starterTemplates"; +import { CreateTemplatesPageView } from "./CreateTemplatesPageView"; import { StarterTemplatesPageView } from "./StarterTemplatesPageView"; -const StarterTemplatesPage: FC = () => { - const { organizationId } = useDashboard(); +const CreateTemplatesGalleryPage: FC = () => { + const { organizationId, experiments } = useDashboard(); const templateExamplesQuery = useQuery(templateExamples(organizationId)); const starterTemplatesByTag = templateExamplesQuery.data ? // Currently, the scratch template should not be displayed on the starter templates page. getTemplatesByTag(removeScratchExample(templateExamplesQuery.data)) : undefined; + const multiOrgExperimentEnabled = experiments.includes("multi-organization"); return ( <> - Codestin Search App + Codestin Search App - - + {multiOrgExperimentEnabled ? ( + + ) : ( + + )} ); }; @@ -34,4 +42,4 @@ const removeScratchExample = (data: TemplateExample[]) => { return data.filter((example) => example.id !== "scratch"); }; -export default StarterTemplatesPage; +export default CreateTemplatesGalleryPage; diff --git a/site/src/pages/CreateTemplatesGalleryPage/CreateTemplatesPageView.tsx b/site/src/pages/CreateTemplatesGalleryPage/CreateTemplatesPageView.tsx new file mode 100644 index 0000000000000..c2c04d19a7ab3 --- /dev/null +++ b/site/src/pages/CreateTemplatesGalleryPage/CreateTemplatesPageView.tsx @@ -0,0 +1,110 @@ +import type { Interpolation, Theme } from "@emotion/react"; +import { useState, type FC } from "react"; +import { useQuery } from "react-query"; +import { Link, useSearchParams } from "react-router-dom"; +import { templateExamples } from "api/queries/templates"; +import type { Organization, TemplateExample } from "api/typesGenerated"; +import { ErrorAlert } from "components/Alert/ErrorAlert"; +import { Loader } from "components/Loader/Loader"; +import { Margins } from "components/Margins/Margins"; +import { OrganizationAutocomplete } from "components/OrganizationAutocomplete/OrganizationAutocomplete"; +import { PageHeader, PageHeaderTitle } from "components/PageHeader/PageHeader"; +import { Stack } from "components/Stack/Stack"; +import { useDashboard } from "modules/dashboard/useDashboard"; +import { TemplateExampleCard } from "modules/templates/TemplateExampleCard/TemplateExampleCard"; +import { + getTemplatesByTag, + type StarterTemplatesByTag, +} from "utils/starterTemplates"; +import { StarterTemplates } from "./StarterTemplates"; + +// const getTagLabel = (tag: string) => { +// const labelByTag: Record = { +// all: "All templates", +// digitalocean: "DigitalOcean", +// aws: "AWS", +// google: "Google Cloud", +// }; +// // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- this can be undefined +// return labelByTag[tag] ?? tag; +// }; + +// const selectTags = (starterTemplatesByTag: StarterTemplatesByTag) => { +// return starterTemplatesByTag +// ? Object.keys(starterTemplatesByTag).sort((a, b) => a.localeCompare(b)) +// : undefined; +// }; + +export interface CreateTemplatePageViewProps { + starterTemplatesByTag?: StarterTemplatesByTag; + error?: unknown; +} + +// const removeScratchExample = (data: TemplateExample[]) => { +// return data.filter((example) => example.id !== "scratch"); +// }; + +export const CreateTemplatesPageView: FC = ({ + starterTemplatesByTag, + error, +}) => { + const [selectedOrg, setSelectedOrg] = useState(null); + // const { organizationId } = useDashboard(); + // const templateExamplesQuery = useQuery(templateExamples(organizationId)); + // const starterTemplatesByTag = templateExamplesQuery.data + // ? // Currently, the scratch template should not be displayed on the starter templates page. + // getTemplatesByTag(removeScratchExample(templateExamplesQuery.data)) + // : undefined; + + return ( + + + Create a Template + + + { + setSelectedOrg(newValue); + }} + /> + + {Boolean(error) && } + + {Boolean(!starterTemplatesByTag) && } + + + + ); +}; + +const styles = { + autoComplete: { + width: 300, + }, + + filterCaption: (theme) => ({ + textTransform: "uppercase", + fontWeight: 600, + fontSize: 12, + color: theme.palette.text.secondary, + letterSpacing: "0.1em", + }), + + tagLink: (theme) => ({ + color: theme.palette.text.secondary, + textDecoration: "none", + fontSize: 14, + textTransform: "capitalize", + + "&:hover": { + color: theme.palette.text.primary, + }, + }), + + tagLinkActive: (theme) => ({ + color: theme.palette.text.primary, + fontWeight: 600, + }), +} satisfies Record>; diff --git a/site/src/pages/CreateTemplatesGalleryPage/StarterTemplates.tsx b/site/src/pages/CreateTemplatesGalleryPage/StarterTemplates.tsx new file mode 100644 index 0000000000000..919ea957b4051 --- /dev/null +++ b/site/src/pages/CreateTemplatesGalleryPage/StarterTemplates.tsx @@ -0,0 +1,113 @@ +import type { Interpolation, Theme } from "@emotion/react"; +import type { FC } from "react"; +import { Link, useSearchParams } from "react-router-dom"; +import { Stack } from "components/Stack/Stack"; +import { TemplateExampleCard } from "modules/templates/TemplateExampleCard/TemplateExampleCard"; +import type { StarterTemplatesByTag } from "utils/starterTemplates"; + +const getTagLabel = (tag: string) => { + const labelByTag: Record = { + all: "All templates", + digitalocean: "DigitalOcean", + aws: "AWS", + google: "Google Cloud", + }; + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- this can be undefined + return labelByTag[tag] ?? tag; +}; + +const selectTags = (starterTemplatesByTag: StarterTemplatesByTag) => { + return starterTemplatesByTag + ? Object.keys(starterTemplatesByTag).sort((a, b) => a.localeCompare(b)) + : undefined; +}; + +export interface StarterTemplatesProps { + starterTemplatesByTag?: StarterTemplatesByTag; +} + +export const StarterTemplates: FC = ({ + starterTemplatesByTag, +}) => { + const [urlParams] = useSearchParams(); + const tags = starterTemplatesByTag + ? selectTags(starterTemplatesByTag) + : undefined; + const activeTag = urlParams.get("tag") ?? "all"; + const visibleTemplates = starterTemplatesByTag + ? starterTemplatesByTag[activeTag] + : undefined; + + return ( + + {starterTemplatesByTag && tags && ( + +

    Choose a starter template

    + Filter + {tags.map((tag) => ( + + {getTagLabel(tag)} ({starterTemplatesByTag[tag].length}) + + ))} +
    + )} + +
    + {visibleTemplates && + visibleTemplates.map((example) => ( + ({ + backgroundColor: theme.palette.background.paper, + })} + example={example} + key={example.id} + activeTag={activeTag} + /> + ))} +
    +
    + ); +}; + +const styles = { + filterCaption: (theme) => ({ + textTransform: "uppercase", + fontWeight: 600, + fontSize: 12, + color: theme.palette.text.secondary, + letterSpacing: "0.1em", + }), + + tagLink: (theme) => ({ + color: theme.palette.text.secondary, + textDecoration: "none", + fontSize: 14, + textTransform: "capitalize", + + "&:hover": { + color: theme.palette.text.primary, + }, + }), + + tagLinkActive: (theme) => ({ + color: theme.palette.text.primary, + fontWeight: 600, + }), + + sectionTitle: (theme) => ({ + fontSize: 16, + fontWeight: 500, + margin: 0, + }), +} satisfies Record>; diff --git a/site/src/pages/StarterTemplatesPage/StarterTemplatesPage.test.tsx b/site/src/pages/CreateTemplatesGalleryPage/StarterTemplatesPage.test.tsx similarity index 89% rename from site/src/pages/StarterTemplatesPage/StarterTemplatesPage.test.tsx rename to site/src/pages/CreateTemplatesGalleryPage/StarterTemplatesPage.test.tsx index 9d03587adf005..00b5b1664718c 100644 --- a/site/src/pages/StarterTemplatesPage/StarterTemplatesPage.test.tsx +++ b/site/src/pages/CreateTemplatesGalleryPage/StarterTemplatesPage.test.tsx @@ -8,7 +8,7 @@ import { MockTemplateExample2, } from "testHelpers/entities"; import { server } from "testHelpers/server"; -import StarterTemplatesPage from "./StarterTemplatesPage"; +import StarterTemplatesPage from "./CreateTemplatesGalleryPage"; test("does not display the scratch template", async () => { server.use( @@ -35,13 +35,13 @@ test("does not display the scratch template", async () => { element: , children: [ { - path: "/starter-templates", + path: "/create-templates", element: , }, ], }, ], - { initialEntries: ["/starter-templates"] }, + { initialEntries: ["/create-templatess"] }, )} /> , diff --git a/site/src/pages/StarterTemplatesPage/StarterTemplatesPageView.stories.tsx b/site/src/pages/CreateTemplatesGalleryPage/StarterTemplatesPageView.stories.tsx similarity index 100% rename from site/src/pages/StarterTemplatesPage/StarterTemplatesPageView.stories.tsx rename to site/src/pages/CreateTemplatesGalleryPage/StarterTemplatesPageView.stories.tsx diff --git a/site/src/pages/CreateTemplatesGalleryPage/StarterTemplatesPageView.tsx b/site/src/pages/CreateTemplatesGalleryPage/StarterTemplatesPageView.tsx new file mode 100644 index 0000000000000..6bc661998429d --- /dev/null +++ b/site/src/pages/CreateTemplatesGalleryPage/StarterTemplatesPageView.tsx @@ -0,0 +1,39 @@ +import type { Interpolation, Theme } from "@emotion/react"; +import type { FC } from "react"; +import { ErrorAlert } from "components/Alert/ErrorAlert"; +import { Loader } from "components/Loader/Loader"; +import { Margins } from "components/Margins/Margins"; +import { + PageHeader, + PageHeaderSubtitle, + PageHeaderTitle, +} from "components/PageHeader/PageHeader"; +import type { StarterTemplatesByTag } from "utils/starterTemplates"; +import { StarterTemplates } from "./StarterTemplates"; + +export interface StarterTemplatesPageViewProps { + starterTemplatesByTag?: StarterTemplatesByTag; + error?: unknown; +} + +export const StarterTemplatesPageView: FC = ({ + starterTemplatesByTag, + error, +}) => { + return ( + + + Starter Templates + + Import a built-in template to start developing in the cloud + + + + {Boolean(error) && } + + {Boolean(!starterTemplatesByTag) && } + + + + ); +}; diff --git a/site/src/pages/StarterTemplatesPage/StarterTemplatesPageView.tsx b/site/src/pages/StarterTemplatesPage/StarterTemplatesPageView.tsx deleted file mode 100644 index e0a6c4b975747..0000000000000 --- a/site/src/pages/StarterTemplatesPage/StarterTemplatesPageView.tsx +++ /dev/null @@ -1,133 +0,0 @@ -import type { Interpolation, Theme } from "@emotion/react"; -import type { FC } from "react"; -import { Link, useSearchParams } from "react-router-dom"; -import { ErrorAlert } from "components/Alert/ErrorAlert"; -import { Loader } from "components/Loader/Loader"; -import { Margins } from "components/Margins/Margins"; -import { - PageHeader, - PageHeaderSubtitle, - PageHeaderTitle, -} from "components/PageHeader/PageHeader"; -import { Stack } from "components/Stack/Stack"; -import { TemplateExampleCard } from "modules/templates/TemplateExampleCard/TemplateExampleCard"; -import type { StarterTemplatesByTag } from "utils/starterTemplates"; - -const getTagLabel = (tag: string) => { - const labelByTag: Record = { - all: "All templates", - digitalocean: "DigitalOcean", - aws: "AWS", - google: "Google Cloud", - }; - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- this can be undefined - return labelByTag[tag] ?? tag; -}; - -const selectTags = (starterTemplatesByTag: StarterTemplatesByTag) => { - return starterTemplatesByTag - ? Object.keys(starterTemplatesByTag).sort((a, b) => a.localeCompare(b)) - : undefined; -}; -export interface StarterTemplatesPageViewProps { - starterTemplatesByTag?: StarterTemplatesByTag; - error?: unknown; -} - -export const StarterTemplatesPageView: FC = ({ - starterTemplatesByTag, - error, -}) => { - const [urlParams] = useSearchParams(); - const tags = starterTemplatesByTag - ? selectTags(starterTemplatesByTag) - : undefined; - const activeTag = urlParams.get("tag") ?? "all"; - const visibleTemplates = starterTemplatesByTag - ? starterTemplatesByTag[activeTag] - : undefined; - - return ( - - - Starter Templates - - Import a built-in template to start developing in the cloud - - - - {Boolean(error) && } - - {Boolean(!starterTemplatesByTag) && } - - - {starterTemplatesByTag && tags && ( - - Filter - {tags.map((tag) => ( - - {getTagLabel(tag)} ({starterTemplatesByTag[tag].length}) - - ))} - - )} - -
    - {visibleTemplates && - visibleTemplates.map((example) => ( - ({ - backgroundColor: theme.palette.background.paper, - })} - example={example} - key={example.id} - activeTag={activeTag} - /> - ))} -
    -
    -
    - ); -}; - -const styles = { - filterCaption: (theme) => ({ - textTransform: "uppercase", - fontWeight: 600, - fontSize: 12, - color: theme.palette.text.secondary, - letterSpacing: "0.1em", - }), - - tagLink: (theme) => ({ - color: theme.palette.text.secondary, - textDecoration: "none", - fontSize: 14, - textTransform: "capitalize", - - "&:hover": { - color: theme.palette.text.primary, - }, - }), - - tagLinkActive: (theme) => ({ - color: theme.palette.text.primary, - fontWeight: 600, - }), -} satisfies Record>; diff --git a/site/src/pages/TemplatesPage/TemplatesPageView.tsx b/site/src/pages/TemplatesPage/TemplatesPageView.tsx index fd7be676da6cb..6dfd51232661a 100644 --- a/site/src/pages/TemplatesPage/TemplatesPageView.tsx +++ b/site/src/pages/TemplatesPage/TemplatesPageView.tsx @@ -1,4 +1,5 @@ import type { Interpolation, Theme } from "@emotion/react"; +import AddIcon from "@mui/icons-material/AddOutlined"; import ArrowForwardOutlined from "@mui/icons-material/ArrowForwardOutlined"; import Button from "@mui/material/Button"; import Skeleton from "@mui/material/Skeleton"; @@ -171,7 +172,17 @@ export const TemplatesPageView: FC = ({ + canCreateTemplates && ( + + ) } > diff --git a/site/src/router.tsx b/site/src/router.tsx index c66a98b8a9a6e..83205bc56d44e 100644 --- a/site/src/router.tsx +++ b/site/src/router.tsx @@ -168,8 +168,8 @@ const TemplateVersionPage = lazy( const TemplateVersionEditorPage = lazy( () => import("./pages/TemplateVersionEditorPage/TemplateVersionEditorPage"), ); -const StarterTemplatesPage = lazy( - () => import("./pages/StarterTemplatesPage/StarterTemplatesPage"), +const CreateTemplatesGalleryPage = lazy( + () => import("./pages/CreateTemplatesGalleryPage/CreateTemplatesGalleryPage"), ); const StarterTemplatePage = lazy( () => import("pages/StarterTemplatePage/StarterTemplatePage"), @@ -294,7 +294,7 @@ export const router = createBrowserRouter( } /> - } /> + } /> } /> From 1c973aef9d27440b8ec672d8ad82640d98165eb2 Mon Sep 17 00:00:00 2001 From: Jaayden Halko Date: Thu, 18 Jul 2024 19:31:08 +0000 Subject: [PATCH 02/32] feat: styling and add template creation options --- .../CreateTemplatesPageView.tsx | 189 ++++++++++++------ .../StarterTemplates.tsx | 5 +- 2 files changed, 128 insertions(+), 66 deletions(-) diff --git a/site/src/pages/CreateTemplatesGalleryPage/CreateTemplatesPageView.tsx b/site/src/pages/CreateTemplatesGalleryPage/CreateTemplatesPageView.tsx index c2c04d19a7ab3..f402794727cf4 100644 --- a/site/src/pages/CreateTemplatesGalleryPage/CreateTemplatesPageView.tsx +++ b/site/src/pages/CreateTemplatesGalleryPage/CreateTemplatesPageView.tsx @@ -1,110 +1,171 @@ import type { Interpolation, Theme } from "@emotion/react"; +import Card from "@mui/material/Card"; +import CardActionArea from "@mui/material/CardActionArea"; +import CardContent from "@mui/material/CardContent"; +import Stack from "@mui/material/Stack"; import { useState, type FC } from "react"; -import { useQuery } from "react-query"; -import { Link, useSearchParams } from "react-router-dom"; -import { templateExamples } from "api/queries/templates"; -import type { Organization, TemplateExample } from "api/typesGenerated"; +import { Link as RouterLink } from "react-router-dom"; +import type { Organization } from "api/typesGenerated"; import { ErrorAlert } from "components/Alert/ErrorAlert"; +import { ExternalImage } from "components/ExternalImage/ExternalImage"; import { Loader } from "components/Loader/Loader"; import { Margins } from "components/Margins/Margins"; import { OrganizationAutocomplete } from "components/OrganizationAutocomplete/OrganizationAutocomplete"; import { PageHeader, PageHeaderTitle } from "components/PageHeader/PageHeader"; -import { Stack } from "components/Stack/Stack"; -import { useDashboard } from "modules/dashboard/useDashboard"; -import { TemplateExampleCard } from "modules/templates/TemplateExampleCard/TemplateExampleCard"; -import { - getTemplatesByTag, - type StarterTemplatesByTag, -} from "utils/starterTemplates"; +// import { useDashboard } from "modules/dashboard/useDashboard"; +import type { StarterTemplatesByTag } from "utils/starterTemplates"; import { StarterTemplates } from "./StarterTemplates"; -// const getTagLabel = (tag: string) => { -// const labelByTag: Record = { -// all: "All templates", -// digitalocean: "DigitalOcean", -// aws: "AWS", -// google: "Google Cloud", -// }; -// // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- this can be undefined -// return labelByTag[tag] ?? tag; -// }; - -// const selectTags = (starterTemplatesByTag: StarterTemplatesByTag) => { -// return starterTemplatesByTag -// ? Object.keys(starterTemplatesByTag).sort((a, b) => a.localeCompare(b)) -// : undefined; -// }; - export interface CreateTemplatePageViewProps { starterTemplatesByTag?: StarterTemplatesByTag; error?: unknown; } -// const removeScratchExample = (data: TemplateExample[]) => { -// return data.filter((example) => example.id !== "scratch"); -// }; - export const CreateTemplatesPageView: FC = ({ starterTemplatesByTag, error, }) => { const [selectedOrg, setSelectedOrg] = useState(null); // const { organizationId } = useDashboard(); - // const templateExamplesQuery = useQuery(templateExamples(organizationId)); - // const starterTemplatesByTag = templateExamplesQuery.data - // ? // Currently, the scratch template should not be displayed on the starter templates page. - // getTemplatesByTag(removeScratchExample(templateExamplesQuery.data)) - // : undefined; + // TODO: if there is only 1 organization, set the dropdown to the default organizationId return ( Create a Template + + +

    Choose an Organization

    + { + setSelectedOrg(newValue); + }} + /> +
    - { - setSelectedOrg(newValue); - }} - /> + +

    Choose a starting point

    +
    + + + + +
    + +
    +
    +

    Scratch Template

    + + Create a minimal starter template that you can customize + +
    +
    +
    +
    +
    + + + + +
    + +
    +
    +

    Upload Template

    + + Get started by uploading an existing template + +
    +
    +
    +
    +
    +
    +
    - {Boolean(error) && } + {Boolean(error) && } - {Boolean(!starterTemplatesByTag) && } + {Boolean(!starterTemplatesByTag) && } - + +
    ); }; const styles = { autoComplete: { - width: 300, + width: 415, }, - filterCaption: (theme) => ({ - textTransform: "uppercase", - fontWeight: 600, - fontSize: 12, - color: theme.palette.text.secondary, - letterSpacing: "0.1em", + sectionTitle: (theme) => ({ + color: theme.palette.text.primary, + fontSize: 16, + fontWeight: 400, + margin: 0, }), - tagLink: (theme) => ({ - color: theme.palette.text.secondary, - textDecoration: "none", + cardTitle: (theme) => ({ fontSize: 14, - textTransform: "capitalize", + fontWeight: 600, + margin: 0, + marginBottom: 4, + }), - "&:hover": { - color: theme.palette.text.primary, - }, + cardDescription: (theme) => ({ + fontSize: 13, + color: theme.palette.text.secondary, + lineHeight: "1.6", + display: "block", }), - tagLinkActive: (theme) => ({ - color: theme.palette.text.primary, - fontWeight: 600, + icon: { + flexShrink: 0, + width: 32, + height: 32, + }, + + menuItemIcon: (theme) => ({ + color: theme.palette.text.secondary, + width: 20, + height: 20, }), } satisfies Record>; diff --git a/site/src/pages/CreateTemplatesGalleryPage/StarterTemplates.tsx b/site/src/pages/CreateTemplatesGalleryPage/StarterTemplates.tsx index 919ea957b4051..a99d80d2db031 100644 --- a/site/src/pages/CreateTemplatesGalleryPage/StarterTemplates.tsx +++ b/site/src/pages/CreateTemplatesGalleryPage/StarterTemplates.tsx @@ -41,7 +41,7 @@ export const StarterTemplates: FC = ({ return ( {starterTemplatesByTag && tags && ( - +

    Choose a starter template

    Filter {tags.map((tag) => ( @@ -106,8 +106,9 @@ const styles = { }), sectionTitle: (theme) => ({ + color: theme.palette.text.primary, fontSize: 16, - fontWeight: 500, + fontWeight: 400, margin: 0, }), } satisfies Record>; From 6b5e7ec991f47d3a1f53e0be009babe0101beafb Mon Sep 17 00:00:00 2001 From: Jaayden Halko Date: Thu, 18 Jul 2024 19:45:59 +0000 Subject: [PATCH 03/32] chore: remove unused imports --- .../CreateTemplatesGalleryPage/StarterTemplatesPageView.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/site/src/pages/CreateTemplatesGalleryPage/StarterTemplatesPageView.tsx b/site/src/pages/CreateTemplatesGalleryPage/StarterTemplatesPageView.tsx index 6bc661998429d..485f53b39f313 100644 --- a/site/src/pages/CreateTemplatesGalleryPage/StarterTemplatesPageView.tsx +++ b/site/src/pages/CreateTemplatesGalleryPage/StarterTemplatesPageView.tsx @@ -1,4 +1,3 @@ -import type { Interpolation, Theme } from "@emotion/react"; import type { FC } from "react"; import { ErrorAlert } from "components/Alert/ErrorAlert"; import { Loader } from "components/Loader/Loader"; From fbd6ca333631f739cf8d1cde8297697627340631 Mon Sep 17 00:00:00 2001 From: Jaayden Halko Date: Thu, 18 Jul 2024 19:55:03 +0000 Subject: [PATCH 04/32] fix: use theme on cardTitle --- .../pages/CreateTemplatesGalleryPage/CreateTemplatesPageView.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/site/src/pages/CreateTemplatesGalleryPage/CreateTemplatesPageView.tsx b/site/src/pages/CreateTemplatesGalleryPage/CreateTemplatesPageView.tsx index f402794727cf4..f5cc5b0c43c4e 100644 --- a/site/src/pages/CreateTemplatesGalleryPage/CreateTemplatesPageView.tsx +++ b/site/src/pages/CreateTemplatesGalleryPage/CreateTemplatesPageView.tsx @@ -144,6 +144,7 @@ const styles = { }), cardTitle: (theme) => ({ + color: theme.palette.text.secondary, fontSize: 14, fontWeight: 600, margin: 0, From 013522b612dcf8d8ba6ffe5ff9d06d264a5db0d0 Mon Sep 17 00:00:00 2001 From: Jaayden Halko Date: Thu, 18 Jul 2024 19:55:13 +0000 Subject: [PATCH 05/32] fix: remove unused import --- site/src/pages/TemplatesPage/TemplatesPageView.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/site/src/pages/TemplatesPage/TemplatesPageView.tsx b/site/src/pages/TemplatesPage/TemplatesPageView.tsx index 6dfd51232661a..d77808106bf8b 100644 --- a/site/src/pages/TemplatesPage/TemplatesPageView.tsx +++ b/site/src/pages/TemplatesPage/TemplatesPageView.tsx @@ -44,7 +44,6 @@ import { formatTemplateBuildTime, formatTemplateActiveDevelopers, } from "utils/templates"; -import { CreateTemplateButton } from "./CreateTemplateButton"; import { EmptyTemplates } from "./EmptyTemplates"; export const Language = { From f547bfc4ecee4e9dd8fd76b2fb886dbbe44241f0 Mon Sep 17 00:00:00 2001 From: Jaayden Halko Date: Fri, 19 Jul 2024 02:38:46 +0000 Subject: [PATCH 06/32] fix: pass key directly to JSX without using spread --- .../OrganizationAutocomplete.tsx | 21 +++++++++++-------- .../UserAutocomplete/UserAutocomplete.tsx | 21 +++++++++++-------- 2 files changed, 24 insertions(+), 18 deletions(-) diff --git a/site/src/components/OrganizationAutocomplete/OrganizationAutocomplete.tsx b/site/src/components/OrganizationAutocomplete/OrganizationAutocomplete.tsx index 629065abe2673..a974f149de5fe 100644 --- a/site/src/components/OrganizationAutocomplete/OrganizationAutocomplete.tsx +++ b/site/src/components/OrganizationAutocomplete/OrganizationAutocomplete.tsx @@ -89,15 +89,18 @@ export const OrganizationAutocomplete: FC = ({ option.name === value.name } getOptionLabel={(option) => option.name} - renderOption={(props, option) => ( -
  • - -
  • - )} + renderOption={(props, option) => { + const { key, ...optionProps } = props; + return ( +
  • + +
  • + ); + }} renderInput={(params) => ( = ({ option.username === value.username } getOptionLabel={(option) => option.email} - renderOption={(props, option) => ( -
  • - -
  • - )} + renderOption={(props, option) => { + const { key, ...optionProps } = props; + return ( +
  • + +
  • + ); + }} renderInput={(params) => ( Date: Fri, 19 Jul 2024 02:39:04 +0000 Subject: [PATCH 07/32] fix: use correct path --- .../CreateTemplatesGalleryPage/StarterTemplatesPage.test.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/site/src/pages/CreateTemplatesGalleryPage/StarterTemplatesPage.test.tsx b/site/src/pages/CreateTemplatesGalleryPage/StarterTemplatesPage.test.tsx index 00b5b1664718c..2e9d6ad9daee1 100644 --- a/site/src/pages/CreateTemplatesGalleryPage/StarterTemplatesPage.test.tsx +++ b/site/src/pages/CreateTemplatesGalleryPage/StarterTemplatesPage.test.tsx @@ -35,13 +35,13 @@ test("does not display the scratch template", async () => { element: , children: [ { - path: "/create-templates", + path: "/starter-templates", element: , }, ], }, ], - { initialEntries: ["/create-templatess"] }, + { initialEntries: ["/starter-templates"] }, )} /> , From b4e3c67213c97de279b88a13f127daed0df3cb4a Mon Sep 17 00:00:00 2001 From: Jaayden Halko Date: Fri, 19 Jul 2024 02:52:20 +0000 Subject: [PATCH 08/32] fix: fix test --- site/src/pages/TemplatesPage/TemplatesPage.test.tsx | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/site/src/pages/TemplatesPage/TemplatesPage.test.tsx b/site/src/pages/TemplatesPage/TemplatesPage.test.tsx index 670362416338b..0fa365eb95bb9 100644 --- a/site/src/pages/TemplatesPage/TemplatesPage.test.tsx +++ b/site/src/pages/TemplatesPage/TemplatesPage.test.tsx @@ -5,7 +5,7 @@ import { AppProviders } from "App"; import { RequireAuth } from "contexts/auth/RequireAuth"; import TemplatesPage from "./TemplatesPage"; -test("create template from scratch", async () => { +test("navigate to starter templates page", async () => { const user = userEvent.setup(); const router = createMemoryRouter( [ @@ -16,10 +16,6 @@ test("create template from scratch", async () => { path: "/templates", element: , }, - { - path: "/templates/new", - element:
    , - }, ], }, ], @@ -34,9 +30,5 @@ test("create template from scratch", async () => { name: "Create Template", }); await user.click(createTemplateButton); - const fromScratchMenuItem = await screen.findByText("From scratch"); - await user.click(fromScratchMenuItem); - await screen.findByTestId("new-template-page"); - expect(router.state.location.pathname).toBe("/templates/new"); - expect(router.state.location.search).toBe("?exampleId=scratch"); + expect(router.state.location.pathname).toBe("/starter-templates"); }); From 2636753b65e9e85528f62c5cc6d63660706a6766 Mon Sep 17 00:00:00 2001 From: Jaayden Halko Date: Fri, 19 Jul 2024 18:38:40 +0000 Subject: [PATCH 09/32] chore: handle merge changes --- .../OrganizationAutocomplete/OrganizationAutocomplete.tsx | 4 ++-- .../CreateTemplatesGalleryPage/CreateTemplatesPageView.tsx | 2 +- .../src/pages/CreateTemplatesGalleryPage/StarterTemplates.tsx | 2 +- .../CreateTemplatesGalleryPage/StarterTemplatesPageView.tsx | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/site/src/components/OrganizationAutocomplete/OrganizationAutocomplete.tsx b/site/src/components/OrganizationAutocomplete/OrganizationAutocomplete.tsx index a974f149de5fe..be403bc2e2161 100644 --- a/site/src/components/OrganizationAutocomplete/OrganizationAutocomplete.tsx +++ b/site/src/components/OrganizationAutocomplete/OrganizationAutocomplete.tsx @@ -9,7 +9,7 @@ import { useState, } from "react"; import { useQuery } from "react-query"; -import { myOrganizations } from "api/queries/users"; +import { organizations } from "api/queries/organizations"; import type { Organization } from "api/typesGenerated"; import { Avatar } from "components/Avatar/Avatar"; import { AvatarData } from "components/AvatarData/AvatarData"; @@ -46,7 +46,7 @@ export const OrganizationAutocomplete: FC = ({ // enabled: autoComplete.open, // keepPreviousData: true, // }); - const organizationsQuery = useQuery(myOrganizations()); + const organizationsQuery = useQuery(organizations()); const { debounced: debouncedInputOnChange } = useDebouncedFunction( (event: ChangeEvent) => { diff --git a/site/src/pages/CreateTemplatesGalleryPage/CreateTemplatesPageView.tsx b/site/src/pages/CreateTemplatesGalleryPage/CreateTemplatesPageView.tsx index f5cc5b0c43c4e..4b4f3889f64b3 100644 --- a/site/src/pages/CreateTemplatesGalleryPage/CreateTemplatesPageView.tsx +++ b/site/src/pages/CreateTemplatesGalleryPage/CreateTemplatesPageView.tsx @@ -13,7 +13,7 @@ import { Margins } from "components/Margins/Margins"; import { OrganizationAutocomplete } from "components/OrganizationAutocomplete/OrganizationAutocomplete"; import { PageHeader, PageHeaderTitle } from "components/PageHeader/PageHeader"; // import { useDashboard } from "modules/dashboard/useDashboard"; -import type { StarterTemplatesByTag } from "utils/starterTemplates"; +import type { StarterTemplatesByTag } from "utils/templateAggregators"; import { StarterTemplates } from "./StarterTemplates"; export interface CreateTemplatePageViewProps { diff --git a/site/src/pages/CreateTemplatesGalleryPage/StarterTemplates.tsx b/site/src/pages/CreateTemplatesGalleryPage/StarterTemplates.tsx index a99d80d2db031..ac4164b42d42d 100644 --- a/site/src/pages/CreateTemplatesGalleryPage/StarterTemplates.tsx +++ b/site/src/pages/CreateTemplatesGalleryPage/StarterTemplates.tsx @@ -3,7 +3,7 @@ import type { FC } from "react"; import { Link, useSearchParams } from "react-router-dom"; import { Stack } from "components/Stack/Stack"; import { TemplateExampleCard } from "modules/templates/TemplateExampleCard/TemplateExampleCard"; -import type { StarterTemplatesByTag } from "utils/starterTemplates"; +import type { StarterTemplatesByTag } from "utils/templateAggregators"; const getTagLabel = (tag: string) => { const labelByTag: Record = { diff --git a/site/src/pages/CreateTemplatesGalleryPage/StarterTemplatesPageView.tsx b/site/src/pages/CreateTemplatesGalleryPage/StarterTemplatesPageView.tsx index 485f53b39f313..01db4071965cf 100644 --- a/site/src/pages/CreateTemplatesGalleryPage/StarterTemplatesPageView.tsx +++ b/site/src/pages/CreateTemplatesGalleryPage/StarterTemplatesPageView.tsx @@ -7,7 +7,7 @@ import { PageHeaderSubtitle, PageHeaderTitle, } from "components/PageHeader/PageHeader"; -import type { StarterTemplatesByTag } from "utils/starterTemplates"; +import type { StarterTemplatesByTag } from "utils/templateAggregators"; import { StarterTemplates } from "./StarterTemplates"; export interface StarterTemplatesPageViewProps { From c11397902f667f9ea2496e0907fcf0961a16c936 Mon Sep 17 00:00:00 2001 From: Jaayden Halko Date: Fri, 19 Jul 2024 19:17:48 +0000 Subject: [PATCH 10/32] fix: remove CreateTemplateButton --- site/src/pages/TemplatesPage/TemplatesPageView.tsx | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/site/src/pages/TemplatesPage/TemplatesPageView.tsx b/site/src/pages/TemplatesPage/TemplatesPageView.tsx index d77808106bf8b..fd7be676da6cb 100644 --- a/site/src/pages/TemplatesPage/TemplatesPageView.tsx +++ b/site/src/pages/TemplatesPage/TemplatesPageView.tsx @@ -1,5 +1,4 @@ import type { Interpolation, Theme } from "@emotion/react"; -import AddIcon from "@mui/icons-material/AddOutlined"; import ArrowForwardOutlined from "@mui/icons-material/ArrowForwardOutlined"; import Button from "@mui/material/Button"; import Skeleton from "@mui/material/Skeleton"; @@ -44,6 +43,7 @@ import { formatTemplateBuildTime, formatTemplateActiveDevelopers, } from "utils/templates"; +import { CreateTemplateButton } from "./CreateTemplateButton"; import { EmptyTemplates } from "./EmptyTemplates"; export const Language = { @@ -171,17 +171,7 @@ export const TemplatesPageView: FC = ({ } - variant="contained" - onClick={() => { - navigate("/starter-templates"); - }} - > - Create Template - - ) + canCreateTemplates && } > From cb5339600392a761c48166c3ed36a59950917f83 Mon Sep 17 00:00:00 2001 From: Jaayden Halko Date: Tue, 23 Jul 2024 02:25:43 +0000 Subject: [PATCH 11/32] fix: update template examples for multi-org --- site/src/api/api.ts | 6 ++---- site/src/api/queries/templates.ts | 6 +++--- .../CreateTemplatesGalleryPage.tsx | 4 ++-- site/src/pages/StarterTemplatePage/StarterTemplatePage.tsx | 4 +--- site/src/pages/TemplatesPage/TemplatesPage.tsx | 2 +- 5 files changed, 9 insertions(+), 13 deletions(-) diff --git a/site/src/api/api.ts b/site/src/api/api.ts index 3ee650f4f359e..2056a156f0964 100644 --- a/site/src/api/api.ts +++ b/site/src/api/api.ts @@ -1673,11 +1673,9 @@ class ApiMethods { return response.data; }; - getTemplateExamples = async ( - organizationId: string, - ): Promise => { + getTemplateExamples = async (): Promise => { const response = await this.axios.get( - `/api/v2/organizations/${organizationId}/templates/examples`, + `/api/v2/organizations/00000000-0000-0000-0000-000000000000/templates/examples`, ); return response.data; diff --git a/site/src/api/queries/templates.ts b/site/src/api/queries/templates.ts index 2d0485b8f347b..f572267913049 100644 --- a/site/src/api/queries/templates.ts +++ b/site/src/api/queries/templates.ts @@ -90,10 +90,10 @@ export const setGroupRole = ( }; }; -export const templateExamples = (organizationId: string) => { +export const templateExamples = () => { return { - queryKey: [...getTemplatesQueryKey(organizationId), "examples"], - queryFn: () => API.getTemplateExamples(organizationId), + queryKey: ["templates", "examples"], + queryFn: () => API.getTemplateExamples(), }; }; diff --git a/site/src/pages/CreateTemplatesGalleryPage/CreateTemplatesGalleryPage.tsx b/site/src/pages/CreateTemplatesGalleryPage/CreateTemplatesGalleryPage.tsx index 8bc461e1919ad..0f0a9457637ed 100644 --- a/site/src/pages/CreateTemplatesGalleryPage/CreateTemplatesGalleryPage.tsx +++ b/site/src/pages/CreateTemplatesGalleryPage/CreateTemplatesGalleryPage.tsx @@ -10,8 +10,8 @@ import { CreateTemplatesPageView } from "./CreateTemplatesPageView"; import { StarterTemplatesPageView } from "./StarterTemplatesPageView"; const CreateTemplatesGalleryPage: FC = () => { - const { organizationId, experiments } = useDashboard(); - const templateExamplesQuery = useQuery(templateExamples(organizationId)); + const { experiments } = useDashboard(); + const templateExamplesQuery = useQuery(templateExamples()); const starterTemplatesByTag = templateExamplesQuery.data ? // Currently, the scratch template should not be displayed on the starter templates page. getTemplatesByTag(removeScratchExample(templateExamplesQuery.data)) diff --git a/site/src/pages/StarterTemplatePage/StarterTemplatePage.tsx b/site/src/pages/StarterTemplatePage/StarterTemplatePage.tsx index ed7b5b1c9d92f..0c4c3b5c8b492 100644 --- a/site/src/pages/StarterTemplatePage/StarterTemplatePage.tsx +++ b/site/src/pages/StarterTemplatePage/StarterTemplatePage.tsx @@ -3,14 +3,12 @@ import { Helmet } from "react-helmet-async"; import { useQuery } from "react-query"; import { useParams } from "react-router-dom"; import { templateExamples } from "api/queries/templates"; -import { useDashboard } from "modules/dashboard/useDashboard"; import { pageTitle } from "utils/page"; import { StarterTemplatePageView } from "./StarterTemplatePageView"; const StarterTemplatePage: FC = () => { const { exampleId } = useParams() as { exampleId: string }; - const { organizationId } = useDashboard(); - const templateExamplesQuery = useQuery(templateExamples(organizationId)); + const templateExamplesQuery = useQuery(templateExamples()); const starterTemplate = templateExamplesQuery.data?.find( (example) => example.id === exampleId, ); diff --git a/site/src/pages/TemplatesPage/TemplatesPage.tsx b/site/src/pages/TemplatesPage/TemplatesPage.tsx index 75c98d5221320..288800e6bbc00 100644 --- a/site/src/pages/TemplatesPage/TemplatesPage.tsx +++ b/site/src/pages/TemplatesPage/TemplatesPage.tsx @@ -13,7 +13,7 @@ export const TemplatesPage: FC = () => { const templatesQuery = useQuery(templates(organizationId)); const examplesQuery = useQuery({ - ...templateExamples(organizationId), + ...templateExamples(), enabled: permissions.createTemplates, }); const error = templatesQuery.error || examplesQuery.error; From 789699a1234873971b9187a38229b8701e6d2b4d Mon Sep 17 00:00:00 2001 From: Jaayden Halko Date: Tue, 23 Jul 2024 02:27:17 +0000 Subject: [PATCH 12/32] feat: pass along organizationId when creating templates --- .../TemplateExampleCard/TemplateExampleCard.tsx | 4 +++- .../pages/CreateTemplatePage/CreateTemplatePage.tsx | 3 +++ .../CreateTemplatePage/DuplicateTemplateView.tsx | 3 ++- .../CreateTemplatePage/ImportStarterTemplateView.tsx | 5 +++-- .../pages/CreateTemplatePage/UploadTemplateView.tsx | 3 ++- site/src/pages/CreateTemplatePage/types.ts | 1 + .../CreateTemplatesPageView.tsx | 12 ++++++++---- .../CreateTemplatesGalleryPage/StarterTemplates.tsx | 3 +++ 8 files changed, 25 insertions(+), 9 deletions(-) diff --git a/site/src/modules/templates/TemplateExampleCard/TemplateExampleCard.tsx b/site/src/modules/templates/TemplateExampleCard/TemplateExampleCard.tsx index 0770931d0be26..077585402b157 100644 --- a/site/src/modules/templates/TemplateExampleCard/TemplateExampleCard.tsx +++ b/site/src/modules/templates/TemplateExampleCard/TemplateExampleCard.tsx @@ -10,11 +10,13 @@ import { Pill } from "components/Pill/Pill"; type TemplateExampleCardProps = HTMLAttributes & { example: TemplateExample; activeTag?: string; + organizationId?: string; }; export const TemplateExampleCard: FC = ({ example, activeTag, + organizationId = "00000000-0000-0000-0000-000000000000", ...divProps }) => { return ( @@ -58,7 +60,7 @@ export const TemplateExampleCard: FC = ({ diff --git a/site/src/pages/CreateTemplatePage/CreateTemplatePage.tsx b/site/src/pages/CreateTemplatePage/CreateTemplatePage.tsx index e427fa6b32ff8..fa8cc53880b17 100644 --- a/site/src/pages/CreateTemplatePage/CreateTemplatePage.tsx +++ b/site/src/pages/CreateTemplatePage/CreateTemplatePage.tsx @@ -38,6 +38,9 @@ const CreateTemplatePage: FC = () => { error: createTemplateMutation.error, isCreating: createTemplateMutation.isLoading, variablesSectionRef, + organizationId: + searchParams.get("organizationId") || + "00000000-0000-0000-0000-000000000000", }; return ( diff --git a/site/src/pages/CreateTemplatePage/DuplicateTemplateView.tsx b/site/src/pages/CreateTemplatePage/DuplicateTemplateView.tsx index d108d89ca1ba0..ac6bdd2c9c611 100644 --- a/site/src/pages/CreateTemplatePage/DuplicateTemplateView.tsx +++ b/site/src/pages/CreateTemplatePage/DuplicateTemplateView.tsx @@ -21,9 +21,10 @@ export const DuplicateTemplateView: FC = ({ variablesSectionRef, error, isCreating, + organizationId, }) => { const navigate = useNavigate(); - const { entitlements, organizationId } = useDashboard(); + const { entitlements } = useDashboard(); const [searchParams] = useSearchParams(); const templateByNameQuery = useQuery( templateByName(organizationId, searchParams.get("fromTemplate")!), diff --git a/site/src/pages/CreateTemplatePage/ImportStarterTemplateView.tsx b/site/src/pages/CreateTemplatePage/ImportStarterTemplateView.tsx index e62cda910f847..af7ba6e48114d 100644 --- a/site/src/pages/CreateTemplatePage/ImportStarterTemplateView.tsx +++ b/site/src/pages/CreateTemplatePage/ImportStarterTemplateView.tsx @@ -24,11 +24,12 @@ export const ImportStarterTemplateView: FC = ({ variablesSectionRef, error, isCreating, + organizationId, }) => { const navigate = useNavigate(); - const { entitlements, organizationId } = useDashboard(); + const { entitlements } = useDashboard(); const [searchParams] = useSearchParams(); - const templateExamplesQuery = useQuery(templateExamples(organizationId)); + const templateExamplesQuery = useQuery(templateExamples()); const templateExample = templateExamplesQuery.data?.find( (e) => e.id === searchParams.get("exampleId")!, ); diff --git a/site/src/pages/CreateTemplatePage/UploadTemplateView.tsx b/site/src/pages/CreateTemplatePage/UploadTemplateView.tsx index b9f49d4a46b94..70ae90b9a3669 100644 --- a/site/src/pages/CreateTemplatePage/UploadTemplateView.tsx +++ b/site/src/pages/CreateTemplatePage/UploadTemplateView.tsx @@ -18,10 +18,11 @@ export const UploadTemplateView: FC = ({ variablesSectionRef, isCreating, error, + organizationId, }) => { const navigate = useNavigate(); - const { entitlements, organizationId } = useDashboard(); + const { entitlements } = useDashboard(); const formPermissions = getFormPermissions(entitlements); const uploadFileMutation = useMutation(uploadFile()); diff --git a/site/src/pages/CreateTemplatePage/types.ts b/site/src/pages/CreateTemplatePage/types.ts index 74bf1399b7b73..dca2d17622ad8 100644 --- a/site/src/pages/CreateTemplatePage/types.ts +++ b/site/src/pages/CreateTemplatePage/types.ts @@ -6,4 +6,5 @@ export type CreateTemplatePageViewProps = { variablesSectionRef: React.RefObject; error: unknown; isCreating: boolean; + organizationId: string; }; diff --git a/site/src/pages/CreateTemplatesGalleryPage/CreateTemplatesPageView.tsx b/site/src/pages/CreateTemplatesGalleryPage/CreateTemplatesPageView.tsx index 4b4f3889f64b3..3ec9428c3cd6d 100644 --- a/site/src/pages/CreateTemplatesGalleryPage/CreateTemplatesPageView.tsx +++ b/site/src/pages/CreateTemplatesGalleryPage/CreateTemplatesPageView.tsx @@ -26,7 +26,8 @@ export const CreateTemplatesPageView: FC = ({ error, }) => { const [selectedOrg, setSelectedOrg] = useState(null); - // const { organizationId } = useDashboard(); + const organizationId = + selectedOrg?.id || "00000000-0000-0000-0000-000000000000"; // TODO: if there is only 1 organization, set the dropdown to the default organizationId return ( @@ -59,7 +60,7 @@ export const CreateTemplatesPageView: FC = ({ @@ -90,7 +91,7 @@ export const CreateTemplatesPageView: FC = ({ @@ -125,7 +126,10 @@ export const CreateTemplatesPageView: FC = ({ {Boolean(!starterTemplatesByTag) && } - + ); diff --git a/site/src/pages/CreateTemplatesGalleryPage/StarterTemplates.tsx b/site/src/pages/CreateTemplatesGalleryPage/StarterTemplates.tsx index ac4164b42d42d..c0bc3ff64e7df 100644 --- a/site/src/pages/CreateTemplatesGalleryPage/StarterTemplates.tsx +++ b/site/src/pages/CreateTemplatesGalleryPage/StarterTemplates.tsx @@ -24,10 +24,12 @@ const selectTags = (starterTemplatesByTag: StarterTemplatesByTag) => { export interface StarterTemplatesProps { starterTemplatesByTag?: StarterTemplatesByTag; + organizationId?: string; } export const StarterTemplates: FC = ({ starterTemplatesByTag, + organizationId, }) => { const [urlParams] = useSearchParams(); const tags = starterTemplatesByTag @@ -73,6 +75,7 @@ export const StarterTemplates: FC = ({ example={example} key={example.id} activeTag={activeTag} + organizationId={organizationId} /> ))}
    From 8b03b110725b0e0fff62868cb45085d3686f61f0 Mon Sep 17 00:00:00 2001 From: Jaayden Halko Date: Wed, 24 Jul 2024 01:53:32 +0000 Subject: [PATCH 13/32] fix: improve organization autocomplete --- .../OrganizationAutocomplete.tsx | 21 ++++++------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/site/src/components/OrganizationAutocomplete/OrganizationAutocomplete.tsx b/site/src/components/OrganizationAutocomplete/OrganizationAutocomplete.tsx index be403bc2e2161..c74fb2bd330b8 100644 --- a/site/src/components/OrganizationAutocomplete/OrganizationAutocomplete.tsx +++ b/site/src/components/OrganizationAutocomplete/OrganizationAutocomplete.tsx @@ -14,7 +14,6 @@ import type { Organization } from "api/typesGenerated"; import { Avatar } from "components/Avatar/Avatar"; import { AvatarData } from "components/AvatarData/AvatarData"; import { useDebouncedFunction } from "hooks/debounce"; -// import { prepareQuery } from "utils/filters"; export type OrganizationAutocompleteProps = { value: Organization | null; @@ -38,14 +37,6 @@ export const OrganizationAutocomplete: FC = ({ value: value?.name ?? "", open: false, }); - // const usersQuery = useQuery({ - // ...users({ - // q: prepareQuery(encodeURI(autoComplete.value)), - // limit: 25, - // }), - // enabled: autoComplete.open, - // keepPreviousData: true, - // }); const organizationsQuery = useQuery(organizations()); const { debounced: debouncedInputOnChange } = useDebouncedFunction( @@ -60,10 +51,8 @@ export const OrganizationAutocomplete: FC = ({ return ( organization} - noOptionsText="No users found" + noOptionsText="No organizations found" className={className} options={organizationsQuery.data ?? []} loading={organizationsQuery.isLoading} @@ -88,14 +77,14 @@ export const OrganizationAutocomplete: FC = ({ isOptionEqualToValue={(option: Organization, value: Organization) => option.name === value.name } - getOptionLabel={(option) => option.name} + getOptionLabel={(option) => option.display_name} renderOption={(props, option) => { const { key, ...optionProps } = props; return (
  • @@ -107,12 +96,14 @@ export const OrganizationAutocomplete: FC = ({ fullWidth size={size} label={label} + autoFocus placeholder="Organization name" css={{ "&:not(:has(label))": { margin: 0, }, }} + required InputProps={{ ...params.InputProps, onChange: debouncedInputOnChange, From b3aa04f6a1c1849d0c836662c4aa3387823f7e6a Mon Sep 17 00:00:00 2001 From: Jaayden Halko Date: Wed, 24 Jul 2024 01:59:41 +0000 Subject: [PATCH 14/32] Feat: move org dropdown and add orgId search param --- .../CreateTemplatePage/CreateTemplateForm.tsx | 19 ++++++++-- .../CreateTemplatePage/CreateTemplatePage.tsx | 7 ++-- .../DuplicateTemplateView.tsx | 5 +-- .../ImportStarterTemplateView.tsx | 3 +- .../CreateTemplatePage/UploadTemplateView.tsx | 3 +- site/src/pages/CreateTemplatePage/types.ts | 1 - .../CreateTemplatesPageView.tsx | 34 +++--------------- .../src/pages/TemplatePage/TemplateLayout.tsx | 36 ++++++++++++++----- .../pages/TemplatePage/TemplatePageHeader.tsx | 13 +++++-- .../pages/TemplateSettingsPage/Sidebar.tsx | 20 ++++++++--- .../TemplatePermissionsPage.tsx | 1 + .../TemplateSchedulePage.tsx | 7 ++-- .../TemplateSettingsLayout.tsx | 7 ++-- .../TemplateVariablesPage.tsx | 7 ++-- .../TemplateVersionEditorPage.tsx | 6 ++-- 15 files changed, 100 insertions(+), 69 deletions(-) diff --git a/site/src/pages/CreateTemplatePage/CreateTemplateForm.tsx b/site/src/pages/CreateTemplatePage/CreateTemplateForm.tsx index bbc7f45288385..ae291b24f8cb5 100644 --- a/site/src/pages/CreateTemplatePage/CreateTemplateForm.tsx +++ b/site/src/pages/CreateTemplatePage/CreateTemplateForm.tsx @@ -2,10 +2,11 @@ import TextField from "@mui/material/TextField"; import { useFormik } from "formik"; import camelCase from "lodash/camelCase"; import capitalize from "lodash/capitalize"; -import type { FC } from "react"; +import { useState, type FC } from "react"; import { useSearchParams } from "react-router-dom"; import * as Yup from "yup"; import type { + Organization, ProvisionerJobLog, ProvisionerType, Template, @@ -20,6 +21,7 @@ import { FormFooter, } from "components/Form/Form"; import { IconField } from "components/IconField/IconField"; +import { OrganizationAutocomplete } from "components/OrganizationAutocomplete/OrganizationAutocomplete"; import { SelectedTemplate } from "pages/CreateWorkspacePage/SelectedTemplate"; import { nameValidator, @@ -53,6 +55,7 @@ export interface CreateTemplateData { user_variable_values?: VariableValue[]; allow_everyone_group_access: boolean; provisioner_type: ProvisionerType; + organization_id: string; } const validationSchema = Yup.object({ @@ -85,6 +88,7 @@ const defaultInitialValues: CreateTemplateData = { allow_user_autostop: false, allow_everyone_group_access: true, provisioner_type: "terraform", + organization_id: "", }; type GetInitialValuesParams = { @@ -176,6 +180,7 @@ export type CreateTemplateFormProps = ( export const CreateTemplateForm: FC = (props) => { const [searchParams] = useSearchParams(); + const [selectedOrg, setSelectedOrg] = useState(null); const { onCancel, onSubmit, @@ -188,6 +193,7 @@ export const CreateTemplateForm: FC = (props) => { allowAdvancedScheduling, variablesSectionRef, } = props; + // TODO: if there is only 1 organization, set the dropdown to the default organizationId or hide it const form = useFormik({ initialValues: getInitialValues({ @@ -227,11 +233,20 @@ export const CreateTemplateForm: FC = (props) => { /> )} + { + setSelectedOrg(newValue); + return form.setFieldValue("organization_id", newValue?.id || ""); + }} + size="medium" + /> + { onCreateVersion: setTemplateVersion, onTemplateVersionChanges: setTemplateVersion, }); - navigate(`/templates/${template.name}/files`); + navigate( + `/templates/${template.name}/files?orgId=${options.organizationId}`, + ); }, onOpenBuildLogsDrawer: () => setIsBuildLogsOpen(true), error: createTemplateMutation.error, isCreating: createTemplateMutation.isLoading, variablesSectionRef, - organizationId: - searchParams.get("organizationId") || - "00000000-0000-0000-0000-000000000000", }; return ( diff --git a/site/src/pages/CreateTemplatePage/DuplicateTemplateView.tsx b/site/src/pages/CreateTemplatePage/DuplicateTemplateView.tsx index ac6bdd2c9c611..291624fcfbb20 100644 --- a/site/src/pages/CreateTemplatePage/DuplicateTemplateView.tsx +++ b/site/src/pages/CreateTemplatePage/DuplicateTemplateView.tsx @@ -21,11 +21,12 @@ export const DuplicateTemplateView: FC = ({ variablesSectionRef, error, isCreating, - organizationId, }) => { const navigate = useNavigate(); const { entitlements } = useDashboard(); const [searchParams] = useSearchParams(); + const organizationId = + searchParams.get("orgId") || "00000000-0000-0000-0000-000000000000"; const templateByNameQuery = useQuery( templateByName(organizationId, searchParams.get("fromTemplate")!), ); @@ -77,7 +78,7 @@ export const DuplicateTemplateView: FC = ({ logs={templateVersionLogsQuery.data} onSubmit={async (formData) => { await onCreateTemplate({ - organizationId, + organizationId: formData.organization_id, version: firstVersionFromFile( templateVersionQuery.data!.job.file_id, formData.user_variable_values, diff --git a/site/src/pages/CreateTemplatePage/ImportStarterTemplateView.tsx b/site/src/pages/CreateTemplatePage/ImportStarterTemplateView.tsx index af7ba6e48114d..290d59509c42b 100644 --- a/site/src/pages/CreateTemplatePage/ImportStarterTemplateView.tsx +++ b/site/src/pages/CreateTemplatePage/ImportStarterTemplateView.tsx @@ -24,7 +24,6 @@ export const ImportStarterTemplateView: FC = ({ variablesSectionRef, error, isCreating, - organizationId, }) => { const navigate = useNavigate(); const { entitlements } = useDashboard(); @@ -74,7 +73,7 @@ export const ImportStarterTemplateView: FC = ({ logs={templateVersionLogsQuery.data} onSubmit={async (formData) => { await onCreateTemplate({ - organizationId, + organizationId: formData.organization_id, version: firstVersionFromExample( templateExample!, formData.user_variable_values, diff --git a/site/src/pages/CreateTemplatePage/UploadTemplateView.tsx b/site/src/pages/CreateTemplatePage/UploadTemplateView.tsx index 70ae90b9a3669..1bb812c209f57 100644 --- a/site/src/pages/CreateTemplatePage/UploadTemplateView.tsx +++ b/site/src/pages/CreateTemplatePage/UploadTemplateView.tsx @@ -18,7 +18,6 @@ export const UploadTemplateView: FC = ({ variablesSectionRef, isCreating, error, - organizationId, }) => { const navigate = useNavigate(); @@ -59,7 +58,7 @@ export const UploadTemplateView: FC = ({ }} onSubmit={async (formData) => { await onCreateTemplate({ - organizationId, + organizationId: formData.organization_id, version: firstVersionFromFile( uploadedFile!.hash, formData.user_variable_values, diff --git a/site/src/pages/CreateTemplatePage/types.ts b/site/src/pages/CreateTemplatePage/types.ts index dca2d17622ad8..74bf1399b7b73 100644 --- a/site/src/pages/CreateTemplatePage/types.ts +++ b/site/src/pages/CreateTemplatePage/types.ts @@ -6,5 +6,4 @@ export type CreateTemplatePageViewProps = { variablesSectionRef: React.RefObject; error: unknown; isCreating: boolean; - organizationId: string; }; diff --git a/site/src/pages/CreateTemplatesGalleryPage/CreateTemplatesPageView.tsx b/site/src/pages/CreateTemplatesGalleryPage/CreateTemplatesPageView.tsx index 3ec9428c3cd6d..292f52f060ffc 100644 --- a/site/src/pages/CreateTemplatesGalleryPage/CreateTemplatesPageView.tsx +++ b/site/src/pages/CreateTemplatesGalleryPage/CreateTemplatesPageView.tsx @@ -3,16 +3,13 @@ import Card from "@mui/material/Card"; import CardActionArea from "@mui/material/CardActionArea"; import CardContent from "@mui/material/CardContent"; import Stack from "@mui/material/Stack"; -import { useState, type FC } from "react"; +import type { FC } from "react"; import { Link as RouterLink } from "react-router-dom"; -import type { Organization } from "api/typesGenerated"; import { ErrorAlert } from "components/Alert/ErrorAlert"; import { ExternalImage } from "components/ExternalImage/ExternalImage"; import { Loader } from "components/Loader/Loader"; import { Margins } from "components/Margins/Margins"; -import { OrganizationAutocomplete } from "components/OrganizationAutocomplete/OrganizationAutocomplete"; import { PageHeader, PageHeaderTitle } from "components/PageHeader/PageHeader"; -// import { useDashboard } from "modules/dashboard/useDashboard"; import type { StarterTemplatesByTag } from "utils/templateAggregators"; import { StarterTemplates } from "./StarterTemplates"; @@ -25,28 +22,12 @@ export const CreateTemplatesPageView: FC = ({ starterTemplatesByTag, error, }) => { - const [selectedOrg, setSelectedOrg] = useState(null); - const organizationId = - selectedOrg?.id || "00000000-0000-0000-0000-000000000000"; - // TODO: if there is only 1 organization, set the dropdown to the default organizationId - return ( Create a Template - -

    Choose an Organization

    - { - setSelectedOrg(newValue); - }} - /> -
    -

    Choose a starting point

    = ({ @@ -91,7 +72,7 @@ export const CreateTemplatesPageView: FC = ({ @@ -126,20 +107,13 @@ export const CreateTemplatesPageView: FC = ({ {Boolean(!starterTemplatesByTag) && } - + ); }; const styles = { - autoComplete: { - width: 415, - }, - sectionTitle: (theme) => ({ color: theme.palette.text.primary, fontSize: 16, diff --git a/site/src/pages/TemplatePage/TemplateLayout.tsx b/site/src/pages/TemplatePage/TemplateLayout.tsx index bd53a6dc39052..4de4d2959d207 100644 --- a/site/src/pages/TemplatePage/TemplateLayout.tsx +++ b/site/src/pages/TemplatePage/TemplateLayout.tsx @@ -6,7 +6,13 @@ import { useContext, } from "react"; import { useQuery } from "react-query"; -import { Outlet, useLocation, useNavigate, useParams } from "react-router-dom"; +import { + Outlet, + useLocation, + useNavigate, + useParams, + useSearchParams, +} from "react-router-dom"; import { API } from "api/api"; import type { AuthorizationRequest } from "api/typesGenerated"; import { ErrorAlert } from "components/Alert/ErrorAlert"; @@ -72,8 +78,10 @@ export const TemplateLayout: FC = ({ children = , }) => { const navigate = useNavigate(); - const { organizationId } = useDashboard(); const { template: templateName } = useParams() as { template: string }; + const [searchParams] = useSearchParams(); + const organizationId = + searchParams.get("orgId") || "00000000-0000-0000-0000-000000000000"; const { data, error, isLoading } = useQuery({ queryKey: ["template", templateName], queryFn: () => fetchTemplate(organizationId, templateName), @@ -115,29 +123,41 @@ export const TemplateLayout: FC = ({ > - + Summary - + Docs {data.permissions.canUpdateTemplate && ( - + Source Code )} Versions - + Embed {shouldShowInsights && ( Insights diff --git a/site/src/pages/TemplatePage/TemplatePageHeader.tsx b/site/src/pages/TemplatePage/TemplatePageHeader.tsx index 7b74d2924da69..4ce6f60326aad 100644 --- a/site/src/pages/TemplatePage/TemplatePageHeader.tsx +++ b/site/src/pages/TemplatePage/TemplatePageHeader.tsx @@ -39,6 +39,7 @@ type TemplateMenuProps = { templateName: string; templateVersion: string; templateId: string; + organizationId: string; onDelete: () => void; }; @@ -46,6 +47,7 @@ const TemplateMenu: FC = ({ templateName, templateVersion, templateId, + organizationId, onDelete, }) => { const dialogState = useDeletionDialogState(templateId, onDelete); @@ -66,7 +68,9 @@ const TemplateMenu: FC = ({ { - navigate(`/templates/${templateName}/settings`); + navigate( + `/templates/${templateName}/settings?orgId=${organizationId}`, + ); }} > @@ -76,7 +80,7 @@ const TemplateMenu: FC = ({ { navigate( - `/templates/${templateName}/versions/${templateVersion}/edit`, + `/templates/${templateName}/versions/${templateVersion}/edit?orgId=${organizationId}`, ); }} > @@ -86,7 +90,9 @@ const TemplateMenu: FC = ({ { - navigate(`/templates/new?fromTemplate=${templateName}`); + navigate( + `/templates/new?fromTemplate=${templateName}&orgId=${organizationId}`, + ); }} > @@ -188,6 +194,7 @@ export const TemplatePageHeader: FC = ({ templateVersion={activeVersion.name} templateName={template.name} templateId={template.id} + organizationId={template.organization_id} onDelete={onDeleteTemplate} /> )} diff --git a/site/src/pages/TemplateSettingsPage/Sidebar.tsx b/site/src/pages/TemplateSettingsPage/Sidebar.tsx index c2c22bb75a90e..ae35fc1630226 100644 --- a/site/src/pages/TemplateSettingsPage/Sidebar.tsx +++ b/site/src/pages/TemplateSettingsPage/Sidebar.tsx @@ -27,16 +27,28 @@ export const Sidebar: FC = ({ template }) => { subtitle={template.name} /> - + General - + Permissions - + Variables - + Schedule diff --git a/site/src/pages/TemplateSettingsPage/TemplatePermissionsPage/TemplatePermissionsPage.tsx b/site/src/pages/TemplateSettingsPage/TemplatePermissionsPage/TemplatePermissionsPage.tsx index 6af149bd20462..fa908cfe89de6 100644 --- a/site/src/pages/TemplateSettingsPage/TemplatePermissionsPage/TemplatePermissionsPage.tsx +++ b/site/src/pages/TemplateSettingsPage/TemplatePermissionsPage/TemplatePermissionsPage.tsx @@ -1,6 +1,7 @@ import type { FC } from "react"; import { Helmet } from "react-helmet-async"; import { useMutation, useQuery, useQueryClient } from "react-query"; +import { useSearchParams } from "react-router-dom"; import { setGroupRole, setUserRole, templateACL } from "api/queries/templates"; import { displaySuccess } from "components/GlobalSnackbar/utils"; import { Paywall } from "components/Paywall/Paywall"; diff --git a/site/src/pages/TemplateSettingsPage/TemplateSchedulePage/TemplateSchedulePage.tsx b/site/src/pages/TemplateSettingsPage/TemplateSchedulePage/TemplateSchedulePage.tsx index 45f87bdda5a5b..e1df15e5df2db 100644 --- a/site/src/pages/TemplateSettingsPage/TemplateSchedulePage/TemplateSchedulePage.tsx +++ b/site/src/pages/TemplateSettingsPage/TemplateSchedulePage/TemplateSchedulePage.tsx @@ -1,7 +1,7 @@ import type { FC } from "react"; import { Helmet } from "react-helmet-async"; import { useMutation, useQueryClient } from "react-query"; -import { useNavigate, useParams } from "react-router-dom"; +import { useNavigate, useParams, useSearchParams } from "react-router-dom"; import { API } from "api/api"; import { templateByNameKey } from "api/queries/templates"; import type { UpdateTemplateMeta } from "api/typesGenerated"; @@ -16,7 +16,10 @@ const TemplateSchedulePage: FC = () => { const navigate = useNavigate(); const queryClient = useQueryClient(); const { template } = useTemplateSettings(); - const { entitlements, organizationId } = useDashboard(); + const { entitlements } = useDashboard(); + const [searchParams] = useSearchParams(); + const organizationId = + searchParams.get("orgId") || "00000000-0000-0000-0000-000000000000"; const allowAdvancedScheduling = entitlements.features["advanced_template_scheduling"].enabled; diff --git a/site/src/pages/TemplateSettingsPage/TemplateSettingsLayout.tsx b/site/src/pages/TemplateSettingsPage/TemplateSettingsLayout.tsx index 8e157dac3bd95..08c61f1d1a7c5 100644 --- a/site/src/pages/TemplateSettingsPage/TemplateSettingsLayout.tsx +++ b/site/src/pages/TemplateSettingsPage/TemplateSettingsLayout.tsx @@ -1,7 +1,7 @@ import { createContext, type FC, Suspense, useContext } from "react"; import { Helmet } from "react-helmet-async"; import { useQuery } from "react-query"; -import { Outlet, useParams } from "react-router-dom"; +import { Outlet, useParams, useSearchParams } from "react-router-dom"; import { checkAuthorization } from "api/queries/authCheck"; import { templateByName } from "api/queries/templates"; import type { AuthorizationResponse, Template } from "api/typesGenerated"; @@ -9,7 +9,6 @@ import { ErrorAlert } from "components/Alert/ErrorAlert"; import { Loader } from "components/Loader/Loader"; import { Margins } from "components/Margins/Margins"; import { Stack } from "components/Stack/Stack"; -import { useDashboard } from "modules/dashboard/useDashboard"; import { pageTitle } from "utils/page"; import { Sidebar } from "./Sidebar"; @@ -27,7 +26,9 @@ export function useTemplateSettings() { } export const TemplateSettingsLayout: FC = () => { - const { organizationId } = useDashboard(); + const [searchParams] = useSearchParams(); + const organizationId = + searchParams.get("orgId") || "00000000-0000-0000-0000-000000000000"; const { template: templateName } = useParams() as { template: string }; const templateQuery = useQuery(templateByName(organizationId, templateName)); const permissionsQuery = useQuery({ diff --git a/site/src/pages/TemplateSettingsPage/TemplateVariablesPage/TemplateVariablesPage.tsx b/site/src/pages/TemplateSettingsPage/TemplateVariablesPage/TemplateVariablesPage.tsx index e717e24a2aab5..932d87b967510 100644 --- a/site/src/pages/TemplateSettingsPage/TemplateVariablesPage/TemplateVariablesPage.tsx +++ b/site/src/pages/TemplateSettingsPage/TemplateVariablesPage/TemplateVariablesPage.tsx @@ -1,7 +1,7 @@ import { useCallback, type FC } from "react"; import { Helmet } from "react-helmet-async"; import { useMutation, useQuery, useQueryClient } from "react-query"; -import { useNavigate, useParams } from "react-router-dom"; +import { useNavigate, useParams, useSearchParams } from "react-router-dom"; import { createAndBuildTemplateVersion, templateVersion, @@ -16,7 +16,6 @@ import type { import { ErrorAlert } from "components/Alert/ErrorAlert"; import { displaySuccess } from "components/GlobalSnackbar/utils"; import { Loader } from "components/Loader/Loader"; -import { useDashboard } from "modules/dashboard/useDashboard"; import { pageTitle } from "utils/page"; import { useTemplateSettings } from "../TemplateSettingsLayout"; import { TemplateVariablesPageView } from "./TemplateVariablesPageView"; @@ -26,7 +25,9 @@ export const TemplateVariablesPage: FC = () => { organization: string; template: string; }; - const { organizationId } = useDashboard(); + const [searchParams] = useSearchParams(); + const organizationId = + searchParams.get("orgId") || "00000000-0000-0000-0000-000000000000"; const { template } = useTemplateSettings(); const navigate = useNavigate(); const queryClient = useQueryClient(); diff --git a/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditorPage.tsx b/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditorPage.tsx index 2c3098aa82c10..0bf36c82dd68a 100644 --- a/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditorPage.tsx +++ b/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditorPage.tsx @@ -18,7 +18,6 @@ import type { } from "api/typesGenerated"; import { displayError } from "components/GlobalSnackbar/utils"; import { Loader } from "components/Loader/Loader"; -import { useDashboard } from "modules/dashboard/useDashboard"; import { useWatchVersionLogs } from "modules/templates/useWatchVersionLogs"; import { type FileTree, traverse } from "utils/filetree"; import { pageTitle } from "utils/page"; @@ -36,7 +35,9 @@ export const TemplateVersionEditorPage: FC = () => { const navigate = useNavigate(); const { version: versionName, template: templateName } = useParams() as Params; - const { organizationId } = useDashboard(); + const [searchParams, setSearchParams] = useSearchParams(); + const organizationId = + searchParams.get("orgId") || "00000000-0000-0000-0000-000000000000"; const templateQuery = useQuery(templateByName(organizationId, templateName)); const templateVersionOptions = templateVersionByName( organizationId, @@ -83,7 +84,6 @@ export const TemplateVersionEditorPage: FC = () => { useState(); // File navigation - const [searchParams, setSearchParams] = useSearchParams(); // It can be undefined when a selected file is deleted const activePath: string | undefined = searchParams.get("path") ?? findInitialFile(fileTree ?? {}); From 03768c4ccd0f7edd020a347708926d3b18e6c558 Mon Sep 17 00:00:00 2001 From: Jaayden Halko Date: Wed, 24 Jul 2024 17:48:59 +0000 Subject: [PATCH 15/32] chore: show org autocomplete if experiment is enabled --- .../CreateTemplatePage/CreateTemplateForm.tsx | 29 ++++++++++++------- .../src/pages/TemplatePage/TemplateLayout.tsx | 1 - 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/site/src/pages/CreateTemplatePage/CreateTemplateForm.tsx b/site/src/pages/CreateTemplatePage/CreateTemplateForm.tsx index ae291b24f8cb5..00d44f9c97cd1 100644 --- a/site/src/pages/CreateTemplatePage/CreateTemplateForm.tsx +++ b/site/src/pages/CreateTemplatePage/CreateTemplateForm.tsx @@ -22,6 +22,7 @@ import { } from "components/Form/Form"; import { IconField } from "components/IconField/IconField"; import { OrganizationAutocomplete } from "components/OrganizationAutocomplete/OrganizationAutocomplete"; +import { useDashboard } from "modules/dashboard/useDashboard"; import { SelectedTemplate } from "pages/CreateWorkspacePage/SelectedTemplate"; import { nameValidator, @@ -88,7 +89,7 @@ const defaultInitialValues: CreateTemplateData = { allow_user_autostop: false, allow_everyone_group_access: true, provisioner_type: "terraform", - organization_id: "", + organization_id: "00000000-0000-0000-0000-000000000000", }; type GetInitialValuesParams = { @@ -179,6 +180,7 @@ export type CreateTemplateFormProps = ( }; export const CreateTemplateForm: FC = (props) => { + const { experiments } = useDashboard(); const [searchParams] = useSearchParams(); const [selectedOrg, setSelectedOrg] = useState(null); const { @@ -193,6 +195,7 @@ export const CreateTemplateForm: FC = (props) => { allowAdvancedScheduling, variablesSectionRef, } = props; + const multiOrgExperimentEnabled = experiments.includes("multi-organization"); // TODO: if there is only 1 organization, set the dropdown to the default organizationId or hide it const form = useFormik({ @@ -232,16 +235,20 @@ export const CreateTemplateForm: FC = (props) => { }} /> )} - - { - setSelectedOrg(newValue); - return form.setFieldValue("organization_id", newValue?.id || ""); - }} - size="medium" - /> + {multiOrgExperimentEnabled && ( + { + setSelectedOrg(newValue); + return form.setFieldValue( + "organization_id", + newValue?.id || "", + ); + }} + size="medium" + /> + )} Date: Wed, 24 Jul 2024 20:35:42 +0000 Subject: [PATCH 16/32] fix: remove unnecessary query param --- site/src/pages/TemplateSettingsPage/Sidebar.tsx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/site/src/pages/TemplateSettingsPage/Sidebar.tsx b/site/src/pages/TemplateSettingsPage/Sidebar.tsx index ae35fc1630226..65d4d8c753176 100644 --- a/site/src/pages/TemplateSettingsPage/Sidebar.tsx +++ b/site/src/pages/TemplateSettingsPage/Sidebar.tsx @@ -33,10 +33,7 @@ export const Sidebar: FC = ({ template }) => { > General - + Permissions Date: Wed, 24 Jul 2024 20:41:09 +0000 Subject: [PATCH 17/32] fix: cleanup --- site/src/pages/TemplateSettingsPage/Sidebar.tsx | 5 ++++- .../TemplatePermissionsPage/TemplatePermissionsPage.tsx | 1 - 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/site/src/pages/TemplateSettingsPage/Sidebar.tsx b/site/src/pages/TemplateSettingsPage/Sidebar.tsx index 65d4d8c753176..ae35fc1630226 100644 --- a/site/src/pages/TemplateSettingsPage/Sidebar.tsx +++ b/site/src/pages/TemplateSettingsPage/Sidebar.tsx @@ -33,7 +33,10 @@ export const Sidebar: FC = ({ template }) => { > General - + Permissions Date: Wed, 24 Jul 2024 21:05:51 +0000 Subject: [PATCH 18/32] fix: fix tests --- site/e2e/helpers.ts | 2 +- site/src/pages/TemplatesPage/TemplatesPage.test.tsx | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/site/e2e/helpers.ts b/site/e2e/helpers.ts index 4d047b948e93b..36e420d625380 100644 --- a/site/e2e/helpers.ts +++ b/site/e2e/helpers.ts @@ -198,7 +198,7 @@ export const createTemplate = async ( const name = randomName(); await page.getByLabel("Name *").fill(name); await page.getByTestId("form-submit").click(); - await expectUrl(page).toHavePathName(`/templates/${name}/files`, { + await expectUrl(page).toHavePathName(`/templates/${name}/files?orgId=`, { timeout: 30000, }); return name; diff --git a/site/src/pages/TemplatesPage/TemplatesPage.test.tsx b/site/src/pages/TemplatesPage/TemplatesPage.test.tsx index 0fa365eb95bb9..ff8437c81257f 100644 --- a/site/src/pages/TemplatesPage/TemplatesPage.test.tsx +++ b/site/src/pages/TemplatesPage/TemplatesPage.test.tsx @@ -1,10 +1,20 @@ import { render, screen } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; +import { HttpResponse, http } from "msw"; import { RouterProvider, createMemoryRouter } from "react-router-dom"; import { AppProviders } from "App"; import { RequireAuth } from "contexts/auth/RequireAuth"; +import { server } from "testHelpers/server"; import TemplatesPage from "./TemplatesPage"; +beforeAll(() => { + server.use( + http.get("/api/v2/experiments", () => { + return HttpResponse.json(["multi-organization"]); + }), + ); +}); + test("navigate to starter templates page", async () => { const user = userEvent.setup(); const router = createMemoryRouter( From 2810cd3e16337f09d249c8472b378fc78a7b45c2 Mon Sep 17 00:00:00 2001 From: Jaayden Halko Date: Wed, 24 Jul 2024 21:24:16 +0000 Subject: [PATCH 19/32] fix: fix create template form stories --- .../src/pages/CreateTemplatePage/CreateTemplateForm.stories.tsx | 2 ++ site/src/pages/CreateTemplatePage/CreateTemplateForm.tsx | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/site/src/pages/CreateTemplatePage/CreateTemplateForm.stories.tsx b/site/src/pages/CreateTemplatePage/CreateTemplateForm.stories.tsx index 893de4d6bd688..89f7bd824d78e 100644 --- a/site/src/pages/CreateTemplatePage/CreateTemplateForm.stories.tsx +++ b/site/src/pages/CreateTemplatePage/CreateTemplateForm.stories.tsx @@ -9,6 +9,7 @@ import { MockTemplateVersionVariable4, MockTemplateVersionVariable5, } from "testHelpers/entities"; +import { withDashboardProvider } from "testHelpers/storybook"; import { CreateTemplateForm } from "./CreateTemplateForm"; const meta: Meta = { @@ -18,6 +19,7 @@ const meta: Meta = { isSubmitting: false, onCancel: action("onCancel"), }, + decorators: [withDashboardProvider], }; export default meta; diff --git a/site/src/pages/CreateTemplatePage/CreateTemplateForm.tsx b/site/src/pages/CreateTemplatePage/CreateTemplateForm.tsx index 00d44f9c97cd1..9a4c2faf403e7 100644 --- a/site/src/pages/CreateTemplatePage/CreateTemplateForm.tsx +++ b/site/src/pages/CreateTemplatePage/CreateTemplateForm.tsx @@ -196,7 +196,6 @@ export const CreateTemplateForm: FC = (props) => { variablesSectionRef, } = props; const multiOrgExperimentEnabled = experiments.includes("multi-organization"); - // TODO: if there is only 1 organization, set the dropdown to the default organizationId or hide it const form = useFormik({ initialValues: getInitialValues({ From 6499543e670806b3c9654315d71160000f4e151e Mon Sep 17 00:00:00 2001 From: Jaayden Halko Date: Wed, 24 Jul 2024 23:35:05 +0000 Subject: [PATCH 20/32] fix --- site/e2e/helpers.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/e2e/helpers.ts b/site/e2e/helpers.ts index 36e420d625380..4d047b948e93b 100644 --- a/site/e2e/helpers.ts +++ b/site/e2e/helpers.ts @@ -198,7 +198,7 @@ export const createTemplate = async ( const name = randomName(); await page.getByLabel("Name *").fill(name); await page.getByTestId("form-submit").click(); - await expectUrl(page).toHavePathName(`/templates/${name}/files?orgId=`, { + await expectUrl(page).toHavePathName(`/templates/${name}/files`, { timeout: 30000, }); return name; From c52c3dcd90cdea8beba4a7a86f6e7ab5cd299c2a Mon Sep 17 00:00:00 2001 From: Jaayden Halko Date: Thu, 25 Jul 2024 23:25:27 +0000 Subject: [PATCH 21/32] fix: merge issues --- .../CreateTemplatesPageView.tsx | 2 +- .../StarterTemplates.tsx | 2 +- .../StarterTemplatesPageView.tsx | 2 +- site/src/pages/TemplatesPage/TemplatesPageView.tsx | 14 ++++++++++++-- 4 files changed, 15 insertions(+), 5 deletions(-) diff --git a/site/src/pages/CreateTemplatesGalleryPage/CreateTemplatesPageView.tsx b/site/src/pages/CreateTemplatesGalleryPage/CreateTemplatesPageView.tsx index 292f52f060ffc..3b3d519b9c4ac 100644 --- a/site/src/pages/CreateTemplatesGalleryPage/CreateTemplatesPageView.tsx +++ b/site/src/pages/CreateTemplatesGalleryPage/CreateTemplatesPageView.tsx @@ -10,7 +10,7 @@ import { ExternalImage } from "components/ExternalImage/ExternalImage"; import { Loader } from "components/Loader/Loader"; import { Margins } from "components/Margins/Margins"; import { PageHeader, PageHeaderTitle } from "components/PageHeader/PageHeader"; -import type { StarterTemplatesByTag } from "utils/templateAggregators"; +import type { StarterTemplatesByTag } from "utils/starterTemplates"; import { StarterTemplates } from "./StarterTemplates"; export interface CreateTemplatePageViewProps { diff --git a/site/src/pages/CreateTemplatesGalleryPage/StarterTemplates.tsx b/site/src/pages/CreateTemplatesGalleryPage/StarterTemplates.tsx index c0bc3ff64e7df..0c5f4df318400 100644 --- a/site/src/pages/CreateTemplatesGalleryPage/StarterTemplates.tsx +++ b/site/src/pages/CreateTemplatesGalleryPage/StarterTemplates.tsx @@ -3,7 +3,7 @@ import type { FC } from "react"; import { Link, useSearchParams } from "react-router-dom"; import { Stack } from "components/Stack/Stack"; import { TemplateExampleCard } from "modules/templates/TemplateExampleCard/TemplateExampleCard"; -import type { StarterTemplatesByTag } from "utils/templateAggregators"; +import type { StarterTemplatesByTag } from "utils/starterTemplates"; const getTagLabel = (tag: string) => { const labelByTag: Record = { diff --git a/site/src/pages/CreateTemplatesGalleryPage/StarterTemplatesPageView.tsx b/site/src/pages/CreateTemplatesGalleryPage/StarterTemplatesPageView.tsx index 01db4071965cf..485f53b39f313 100644 --- a/site/src/pages/CreateTemplatesGalleryPage/StarterTemplatesPageView.tsx +++ b/site/src/pages/CreateTemplatesGalleryPage/StarterTemplatesPageView.tsx @@ -7,7 +7,7 @@ import { PageHeaderSubtitle, PageHeaderTitle, } from "components/PageHeader/PageHeader"; -import type { StarterTemplatesByTag } from "utils/templateAggregators"; +import type { StarterTemplatesByTag } from "utils/starterTemplates"; import { StarterTemplates } from "./StarterTemplates"; export interface StarterTemplatesPageViewProps { diff --git a/site/src/pages/TemplatesPage/TemplatesPageView.tsx b/site/src/pages/TemplatesPage/TemplatesPageView.tsx index fd7be676da6cb..d77808106bf8b 100644 --- a/site/src/pages/TemplatesPage/TemplatesPageView.tsx +++ b/site/src/pages/TemplatesPage/TemplatesPageView.tsx @@ -1,4 +1,5 @@ import type { Interpolation, Theme } from "@emotion/react"; +import AddIcon from "@mui/icons-material/AddOutlined"; import ArrowForwardOutlined from "@mui/icons-material/ArrowForwardOutlined"; import Button from "@mui/material/Button"; import Skeleton from "@mui/material/Skeleton"; @@ -43,7 +44,6 @@ import { formatTemplateBuildTime, formatTemplateActiveDevelopers, } from "utils/templates"; -import { CreateTemplateButton } from "./CreateTemplateButton"; import { EmptyTemplates } from "./EmptyTemplates"; export const Language = { @@ -171,7 +171,17 @@ export const TemplatesPageView: FC = ({ + canCreateTemplates && ( + + ) } > From 6205129594536f98e0fe3dab2c48eebd7379141c Mon Sep 17 00:00:00 2001 From: Jaayden Halko Date: Fri, 26 Jul 2024 00:44:25 +0000 Subject: [PATCH 22/32] chore: use default org for example templates --- site/src/api/api.ts | 6 ++++-- site/src/api/queries/templates.ts | 6 +++--- .../pages/CreateTemplatePage/ImportStarterTemplateView.tsx | 2 +- .../CreateTemplatesGalleryPage.tsx | 2 +- site/src/pages/StarterTemplatePage/StarterTemplatePage.tsx | 2 +- site/src/pages/TemplatesPage/TemplatesPage.tsx | 2 +- 6 files changed, 11 insertions(+), 9 deletions(-) diff --git a/site/src/api/api.ts b/site/src/api/api.ts index 2056a156f0964..3ee650f4f359e 100644 --- a/site/src/api/api.ts +++ b/site/src/api/api.ts @@ -1673,9 +1673,11 @@ class ApiMethods { return response.data; }; - getTemplateExamples = async (): Promise => { + getTemplateExamples = async ( + organizationId: string, + ): Promise => { const response = await this.axios.get( - `/api/v2/organizations/00000000-0000-0000-0000-000000000000/templates/examples`, + `/api/v2/organizations/${organizationId}/templates/examples`, ); return response.data; diff --git a/site/src/api/queries/templates.ts b/site/src/api/queries/templates.ts index f572267913049..2d0485b8f347b 100644 --- a/site/src/api/queries/templates.ts +++ b/site/src/api/queries/templates.ts @@ -90,10 +90,10 @@ export const setGroupRole = ( }; }; -export const templateExamples = () => { +export const templateExamples = (organizationId: string) => { return { - queryKey: ["templates", "examples"], - queryFn: () => API.getTemplateExamples(), + queryKey: [...getTemplatesQueryKey(organizationId), "examples"], + queryFn: () => API.getTemplateExamples(organizationId), }; }; diff --git a/site/src/pages/CreateTemplatePage/ImportStarterTemplateView.tsx b/site/src/pages/CreateTemplatePage/ImportStarterTemplateView.tsx index 290d59509c42b..00b85989b6cbe 100644 --- a/site/src/pages/CreateTemplatePage/ImportStarterTemplateView.tsx +++ b/site/src/pages/CreateTemplatePage/ImportStarterTemplateView.tsx @@ -28,7 +28,7 @@ export const ImportStarterTemplateView: FC = ({ const navigate = useNavigate(); const { entitlements } = useDashboard(); const [searchParams] = useSearchParams(); - const templateExamplesQuery = useQuery(templateExamples()); + const templateExamplesQuery = useQuery(templateExamples("default")); const templateExample = templateExamplesQuery.data?.find( (e) => e.id === searchParams.get("exampleId")!, ); diff --git a/site/src/pages/CreateTemplatesGalleryPage/CreateTemplatesGalleryPage.tsx b/site/src/pages/CreateTemplatesGalleryPage/CreateTemplatesGalleryPage.tsx index 0f0a9457637ed..e7cae2724486d 100644 --- a/site/src/pages/CreateTemplatesGalleryPage/CreateTemplatesGalleryPage.tsx +++ b/site/src/pages/CreateTemplatesGalleryPage/CreateTemplatesGalleryPage.tsx @@ -11,7 +11,7 @@ import { StarterTemplatesPageView } from "./StarterTemplatesPageView"; const CreateTemplatesGalleryPage: FC = () => { const { experiments } = useDashboard(); - const templateExamplesQuery = useQuery(templateExamples()); + const templateExamplesQuery = useQuery(templateExamples("default")); const starterTemplatesByTag = templateExamplesQuery.data ? // Currently, the scratch template should not be displayed on the starter templates page. getTemplatesByTag(removeScratchExample(templateExamplesQuery.data)) diff --git a/site/src/pages/StarterTemplatePage/StarterTemplatePage.tsx b/site/src/pages/StarterTemplatePage/StarterTemplatePage.tsx index 0c4c3b5c8b492..df042a7cca743 100644 --- a/site/src/pages/StarterTemplatePage/StarterTemplatePage.tsx +++ b/site/src/pages/StarterTemplatePage/StarterTemplatePage.tsx @@ -8,7 +8,7 @@ import { StarterTemplatePageView } from "./StarterTemplatePageView"; const StarterTemplatePage: FC = () => { const { exampleId } = useParams() as { exampleId: string }; - const templateExamplesQuery = useQuery(templateExamples()); + const templateExamplesQuery = useQuery(templateExamples("default")); const starterTemplate = templateExamplesQuery.data?.find( (example) => example.id === exampleId, ); diff --git a/site/src/pages/TemplatesPage/TemplatesPage.tsx b/site/src/pages/TemplatesPage/TemplatesPage.tsx index 288800e6bbc00..a5bb98dfa44d2 100644 --- a/site/src/pages/TemplatesPage/TemplatesPage.tsx +++ b/site/src/pages/TemplatesPage/TemplatesPage.tsx @@ -13,7 +13,7 @@ export const TemplatesPage: FC = () => { const templatesQuery = useQuery(templates(organizationId)); const examplesQuery = useQuery({ - ...templateExamples(), + ...templateExamples("default"), enabled: permissions.createTemplates, }); const error = templatesQuery.error || examplesQuery.error; From b721a43d8dafed55c8429eebfcdee6cc908eff9e Mon Sep 17 00:00:00 2001 From: Jaayden Halko Date: Fri, 26 Jul 2024 02:50:16 +0000 Subject: [PATCH 23/32] feat: add organizationId to the templates route --- .../templates/TemplateFiles/TemplateFiles.tsx | 4 +- .../WorkspaceOutdatedTooltip.tsx | 4 +- .../CreateTemplatePage/CreateTemplatePage.tsx | 4 +- .../useWorkspaceDuplication.ts | 2 +- .../TemplateEmbedPage/TemplateEmbedPage.tsx | 9 +++- .../TemplateFilesPage/TemplateFilesPage.tsx | 7 ++- .../src/pages/TemplatePage/TemplateLayout.tsx | 29 +++++------ .../pages/TemplatePage/TemplatePageHeader.tsx | 8 ++-- .../TemplateSummaryPageView.tsx | 4 +- .../pages/TemplateSettingsPage/Sidebar.tsx | 22 ++------- .../TemplateSettingsPage.tsx | 4 +- .../TemplateSchedulePage.tsx | 10 ++-- .../TemplateSettingsLayout.tsx | 8 ++-- .../TemplateVariablesPage.tsx | 10 ++-- .../TemplateVersionEditor.tsx | 4 +- .../TemplateVersionEditorPage.tsx | 9 ++-- .../TemplateVersionPage.tsx | 8 ++-- .../TemplateVersionPageView.stories.tsx | 1 + .../TemplateVersionPageView.tsx | 9 +++- .../pages/TemplatesPage/TemplatesPageView.tsx | 6 ++- .../pages/WorkspacePage/WorkspaceTopbar.tsx | 6 +-- .../pages/WorkspacesPage/WorkspacesButton.tsx | 2 +- .../pages/WorkspacesPage/WorkspacesEmpty.tsx | 2 +- .../pages/WorkspacesPage/WorkspacesTable.tsx | 1 + site/src/router.tsx | 48 ++++++++++--------- 25 files changed, 114 insertions(+), 107 deletions(-) diff --git a/site/src/modules/templates/TemplateFiles/TemplateFiles.tsx b/site/src/modules/templates/TemplateFiles/TemplateFiles.tsx index 415feae8932f5..1260d9806ca17 100644 --- a/site/src/modules/templates/TemplateFiles/TemplateFiles.tsx +++ b/site/src/modules/templates/TemplateFiles/TemplateFiles.tsx @@ -17,6 +17,7 @@ interface TemplateFilesProps { baseFiles?: TemplateVersionFiles; versionName: string; templateName: string; + organizationId: string; } export const TemplateFiles: FC = ({ @@ -24,6 +25,7 @@ export const TemplateFiles: FC = ({ baseFiles, versionName, templateName, + organizationId, }) => { const filenames = Object.keys(currentFiles); const theme = useTheme(); @@ -104,7 +106,7 @@ export const TemplateFiles: FC = ({
    void; templateName: string; + organizationId: string; latestVersionId: string; ariaLabel?: string; } @@ -52,6 +53,7 @@ export const WorkspaceOutdatedTooltipContent: FC = ({ ariaLabel, latestVersionId, templateName, + organizationId, }) => { const popover = usePopover(); const { data: activeVersion } = useQuery({ @@ -71,7 +73,7 @@ export const WorkspaceOutdatedTooltipContent: FC = ({
    {activeVersion ? ( diff --git a/site/src/pages/CreateTemplatePage/CreateTemplatePage.tsx b/site/src/pages/CreateTemplatePage/CreateTemplatePage.tsx index b58e3a4284c95..bf368da0bfd5b 100644 --- a/site/src/pages/CreateTemplatePage/CreateTemplatePage.tsx +++ b/site/src/pages/CreateTemplatePage/CreateTemplatePage.tsx @@ -32,9 +32,7 @@ const CreateTemplatePage: FC = () => { onCreateVersion: setTemplateVersion, onTemplateVersionChanges: setTemplateVersion, }); - navigate( - `/templates/${template.name}/files?orgId=${options.organizationId}`, - ); + navigate(`/templates/${options.organizationId}/${template.name}/files`); }, onOpenBuildLogsDrawer: () => setIsBuildLogsOpen(true), error: createTemplateMutation.error, diff --git a/site/src/pages/CreateWorkspacePage/useWorkspaceDuplication.ts b/site/src/pages/CreateWorkspacePage/useWorkspaceDuplication.ts index 042eb4f6ce45e..e8ea10a5b4a0f 100644 --- a/site/src/pages/CreateWorkspacePage/useWorkspaceDuplication.ts +++ b/site/src/pages/CreateWorkspacePage/useWorkspaceDuplication.ts @@ -55,7 +55,7 @@ export function useWorkspaceDuplication(workspace?: Workspace) { // code defensively and have some redundancy in case someone forgets void Promise.resolve().then(() => { navigate({ - pathname: `/templates/${workspace.template_name}/workspace`, + pathname: `/templates/${workspace.organization_id}/${workspace.template_name}/workspace`, search: newUrlParams.toString(), }); }); diff --git a/site/src/pages/TemplatePage/TemplateEmbedPage/TemplateEmbedPage.tsx b/site/src/pages/TemplatePage/TemplateEmbedPage/TemplateEmbedPage.tsx index 643f9c166fb7b..652a894952c9e 100644 --- a/site/src/pages/TemplatePage/TemplateEmbedPage/TemplateEmbedPage.tsx +++ b/site/src/pages/TemplatePage/TemplateEmbedPage/TemplateEmbedPage.tsx @@ -50,10 +50,11 @@ interface TemplateEmbedPageViewProps { function getClipboardCopyContent( templateName: string, + organizationId: string, buttonValues: ButtonValues | undefined, ): string { const deploymentUrl = `${window.location.protocol}//${window.location.host}`; - const createWorkspaceUrl = `${deploymentUrl}/templates/${templateName}/workspace`; + const createWorkspaceUrl = `${deploymentUrl}/templates/${organizationId}/${templateName}/workspace`; const createWorkspaceParams = new URLSearchParams(buttonValues); const buttonUrl = `${createWorkspaceUrl}?${createWorkspaceParams.toString()}`; @@ -66,7 +67,11 @@ export const TemplateEmbedPageView: FC = ({ }) => { const [buttonValues, setButtonValues] = useState(); const clipboard = useClipboard({ - textToCopy: getClipboardCopyContent(template.name, buttonValues), + textToCopy: getClipboardCopyContent( + template.name, + template.organization_id, + buttonValues, + ), }); // template parameters is async so we need to initialize the values after it diff --git a/site/src/pages/TemplatePage/TemplateFilesPage/TemplateFilesPage.tsx b/site/src/pages/TemplatePage/TemplateFilesPage/TemplateFilesPage.tsx index 915241780c3fa..bc31bba8f1e01 100644 --- a/site/src/pages/TemplatePage/TemplateFilesPage/TemplateFilesPage.tsx +++ b/site/src/pages/TemplatePage/TemplateFilesPage/TemplateFilesPage.tsx @@ -1,15 +1,17 @@ import type { FC } from "react"; import { Helmet } from "react-helmet-async"; import { useQuery } from "react-query"; +import { useParams } from "react-router-dom"; import { previousTemplateVersion, templateFiles } from "api/queries/templates"; import { Loader } from "components/Loader/Loader"; -import { useDashboard } from "modules/dashboard/useDashboard"; import { TemplateFiles } from "modules/templates/TemplateFiles/TemplateFiles"; import { useTemplateLayoutContext } from "pages/TemplatePage/TemplateLayout"; import { getTemplatePageTitle } from "../utils"; const TemplateFilesPage: FC = () => { - const { organizationId } = useDashboard(); + const { organization: organizationId } = useParams() as { + organization: string; + }; const { template, activeVersion } = useTemplateLayoutContext(); const { data: currentFiles } = useQuery( templateFiles(activeVersion.job.file_id), @@ -39,6 +41,7 @@ const TemplateFilesPage: FC = () => { baseFiles={previousFiles} versionName={activeVersion.name} templateName={template.name} + organizationId={organizationId} /> ) : ( diff --git a/site/src/pages/TemplatePage/TemplateLayout.tsx b/site/src/pages/TemplatePage/TemplateLayout.tsx index f80ca6a23d316..3ba2ef4ac33e7 100644 --- a/site/src/pages/TemplatePage/TemplateLayout.tsx +++ b/site/src/pages/TemplatePage/TemplateLayout.tsx @@ -6,13 +6,7 @@ import { useContext, } from "react"; import { useQuery } from "react-query"; -import { - Outlet, - useLocation, - useNavigate, - useParams, - useSearchParams, -} from "react-router-dom"; +import { Outlet, useLocation, useNavigate, useParams } from "react-router-dom"; import { API } from "api/api"; import type { AuthorizationRequest } from "api/typesGenerated"; import { ErrorAlert } from "components/Alert/ErrorAlert"; @@ -77,10 +71,11 @@ export const TemplateLayout: FC = ({ children = , }) => { const navigate = useNavigate(); - const { template: templateName } = useParams() as { template: string }; - const [searchParams] = useSearchParams(); - const organizationId = - searchParams.get("orgId") || "00000000-0000-0000-0000-000000000000"; + const { template: templateName, organization: organizationId } = + useParams() as { + template: string; + organization: string; + }; const { data, error, isLoading } = useQuery({ queryKey: ["template", templateName], queryFn: () => fetchTemplate(organizationId, templateName), @@ -123,40 +118,40 @@ export const TemplateLayout: FC = ({ Summary Docs {data.permissions.canUpdateTemplate && ( Source Code )} Versions Embed {shouldShowInsights && ( Insights diff --git a/site/src/pages/TemplatePage/TemplatePageHeader.tsx b/site/src/pages/TemplatePage/TemplatePageHeader.tsx index 4ce6f60326aad..2b6ed1bd655d8 100644 --- a/site/src/pages/TemplatePage/TemplatePageHeader.tsx +++ b/site/src/pages/TemplatePage/TemplatePageHeader.tsx @@ -68,9 +68,7 @@ const TemplateMenu: FC = ({ { - navigate( - `/templates/${templateName}/settings?orgId=${organizationId}`, - ); + navigate(`/templates/${organizationId}/${templateName}/settings`); }} > @@ -80,7 +78,7 @@ const TemplateMenu: FC = ({ { navigate( - `/templates/${templateName}/versions/${templateVersion}/edit?orgId=${organizationId}`, + `/templates/${organizationId}/${templateName}/versions/${templateVersion}/edit`, ); }} > @@ -183,7 +181,7 @@ export const TemplatePageHeader: FC = ({ variant="contained" startIcon={} component={RouterLink} - to={`/templates/${template.name}/workspace`} + to={`/templates/${template.organization_id}/${template.name}/workspace`} > Create Workspace diff --git a/site/src/pages/TemplatePage/TemplateSummaryPage/TemplateSummaryPageView.tsx b/site/src/pages/TemplatePage/TemplateSummaryPage/TemplateSummaryPageView.tsx index fcf662ea2dc48..9309487b96dbb 100644 --- a/site/src/pages/TemplatePage/TemplateSummaryPage/TemplateSummaryPageView.tsx +++ b/site/src/pages/TemplatePage/TemplateSummaryPage/TemplateSummaryPageView.tsx @@ -28,7 +28,9 @@ export const TemplateSummaryPageView: FC = ({ if (location.hash === "#readme") { // We moved the readme to the docs page, but we known that some users // have bookmarked the readme or linked it elsewhere. Redirect them to the docs page. - navigate(`/templates/${template.name}/docs`, { replace: true }); + navigate(`/templates/${template.organization_id}/${template.name}/docs`, { + replace: true, + }); } }, [template, navigate, location]); diff --git a/site/src/pages/TemplateSettingsPage/Sidebar.tsx b/site/src/pages/TemplateSettingsPage/Sidebar.tsx index ae35fc1630226..1d6058e154783 100644 --- a/site/src/pages/TemplateSettingsPage/Sidebar.tsx +++ b/site/src/pages/TemplateSettingsPage/Sidebar.tsx @@ -23,32 +23,20 @@ export const Sidebar: FC = ({ template }) => { } title={template.display_name || template.name} - linkTo={`/templates/${template.name}`} + linkTo={`/templates/${template.organization_id}/${template.name}`} subtitle={template.name} /> - + General - + Permissions - + Variables - + Schedule diff --git a/site/src/pages/TemplateSettingsPage/TemplateGeneralSettingsPage/TemplateSettingsPage.tsx b/site/src/pages/TemplateSettingsPage/TemplateGeneralSettingsPage/TemplateSettingsPage.tsx index 674505afd89e0..2571429404193 100644 --- a/site/src/pages/TemplateSettingsPage/TemplateGeneralSettingsPage/TemplateSettingsPage.tsx +++ b/site/src/pages/TemplateSettingsPage/TemplateGeneralSettingsPage/TemplateSettingsPage.tsx @@ -47,7 +47,7 @@ export const TemplateSettingsPage: FC = () => { ); } displaySuccess("Template updated successfully"); - navigate(`/templates/${data.name}`); + navigate(`/templates/${data.organization_id}/${data.name}`); }, }, ); @@ -62,7 +62,7 @@ export const TemplateSettingsPage: FC = () => { template={template} submitError={submitError} onCancel={() => { - navigate(`/templates/${templateName}`); + navigate(`/templates/${organizationId}/${templateName}`); }} onSubmit={(templateSettings) => { updateTemplate({ diff --git a/site/src/pages/TemplateSettingsPage/TemplateSchedulePage/TemplateSchedulePage.tsx b/site/src/pages/TemplateSettingsPage/TemplateSchedulePage/TemplateSchedulePage.tsx index e1df15e5df2db..2a07142960abf 100644 --- a/site/src/pages/TemplateSettingsPage/TemplateSchedulePage/TemplateSchedulePage.tsx +++ b/site/src/pages/TemplateSettingsPage/TemplateSchedulePage/TemplateSchedulePage.tsx @@ -1,7 +1,7 @@ import type { FC } from "react"; import { Helmet } from "react-helmet-async"; import { useMutation, useQueryClient } from "react-query"; -import { useNavigate, useParams, useSearchParams } from "react-router-dom"; +import { useNavigate, useParams } from "react-router-dom"; import { API } from "api/api"; import { templateByNameKey } from "api/queries/templates"; import type { UpdateTemplateMeta } from "api/typesGenerated"; @@ -17,9 +17,9 @@ const TemplateSchedulePage: FC = () => { const queryClient = useQueryClient(); const { template } = useTemplateSettings(); const { entitlements } = useDashboard(); - const [searchParams] = useSearchParams(); - const organizationId = - searchParams.get("orgId") || "00000000-0000-0000-0000-000000000000"; + const { organization: organizationId } = useParams() as { + organization: string; + }; const allowAdvancedScheduling = entitlements.features["advanced_template_scheduling"].enabled; @@ -53,7 +53,7 @@ const TemplateSchedulePage: FC = () => { template={template} submitError={submitError} onCancel={() => { - navigate(`/templates/${templateName}`); + navigate(`/templates/${organizationId}/${templateName}`); }} onSubmit={(templateScheduleSettings) => { updateTemplate({ diff --git a/site/src/pages/TemplateSettingsPage/TemplateSettingsLayout.tsx b/site/src/pages/TemplateSettingsPage/TemplateSettingsLayout.tsx index 08c61f1d1a7c5..21dc495491e65 100644 --- a/site/src/pages/TemplateSettingsPage/TemplateSettingsLayout.tsx +++ b/site/src/pages/TemplateSettingsPage/TemplateSettingsLayout.tsx @@ -1,7 +1,7 @@ import { createContext, type FC, Suspense, useContext } from "react"; import { Helmet } from "react-helmet-async"; import { useQuery } from "react-query"; -import { Outlet, useParams, useSearchParams } from "react-router-dom"; +import { Outlet, useParams } from "react-router-dom"; import { checkAuthorization } from "api/queries/authCheck"; import { templateByName } from "api/queries/templates"; import type { AuthorizationResponse, Template } from "api/typesGenerated"; @@ -26,9 +26,9 @@ export function useTemplateSettings() { } export const TemplateSettingsLayout: FC = () => { - const [searchParams] = useSearchParams(); - const organizationId = - searchParams.get("orgId") || "00000000-0000-0000-0000-000000000000"; + const { organization: organizationId } = useParams() as { + organization: string; + }; const { template: templateName } = useParams() as { template: string }; const templateQuery = useQuery(templateByName(organizationId, templateName)); const permissionsQuery = useQuery({ diff --git a/site/src/pages/TemplateSettingsPage/TemplateVariablesPage/TemplateVariablesPage.tsx b/site/src/pages/TemplateSettingsPage/TemplateVariablesPage/TemplateVariablesPage.tsx index 932d87b967510..e3ed8a385b8fc 100644 --- a/site/src/pages/TemplateSettingsPage/TemplateVariablesPage/TemplateVariablesPage.tsx +++ b/site/src/pages/TemplateSettingsPage/TemplateVariablesPage/TemplateVariablesPage.tsx @@ -1,7 +1,7 @@ import { useCallback, type FC } from "react"; import { Helmet } from "react-helmet-async"; import { useMutation, useQuery, useQueryClient } from "react-query"; -import { useNavigate, useParams, useSearchParams } from "react-router-dom"; +import { useNavigate, useParams } from "react-router-dom"; import { createAndBuildTemplateVersion, templateVersion, @@ -25,9 +25,9 @@ export const TemplateVariablesPage: FC = () => { organization: string; template: string; }; - const [searchParams] = useSearchParams(); - const organizationId = - searchParams.get("orgId") || "00000000-0000-0000-0000-000000000000"; + const { organization: organizationId } = useParams() as { + organization: string; + }; const { template } = useTemplateSettings(); const navigate = useNavigate(); const queryClient = useQueryClient(); @@ -100,7 +100,7 @@ export const TemplateVariablesPage: FC = () => { publishError, }} onCancel={() => { - navigate(`/templates/${templateName}`); + navigate(`/templates/${organizationId}/${templateName}`); }} onSubmit={async (formData) => { const request = filterEmptySensitiveVariables(formData, variables); diff --git a/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditor.tsx b/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditor.tsx index 955fde47d95f9..b7d782779bea4 100644 --- a/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditor.tsx +++ b/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditor.tsx @@ -200,7 +200,7 @@ export const TemplateVersionEditor: FC = ({ @@ -210,7 +210,7 @@ export const TemplateVersionEditor: FC = ({ { const navigate = useNavigate(); const { version: versionName, template: templateName } = useParams() as Params; + const { organization: organizationId } = useParams() as { + organization: string; + }; const [searchParams, setSearchParams] = useSearchParams(); - const organizationId = - searchParams.get("orgId") || "00000000-0000-0000-0000-000000000000"; const templateQuery = useQuery(templateByName(organizationId, templateName)); const templateVersionOptions = templateVersionByName( organizationId, @@ -98,7 +99,7 @@ export const TemplateVersionEditorPage: FC = () => { const navigateToVersion = (version: TemplateVersion) => { return navigate( - `/templates/${templateName}/versions/${version.name}/edit`, + `/templates/${organizationId}/${templateName}/versions/${version.name}/edit`, { replace: true }, ); }; @@ -188,7 +189,7 @@ export const TemplateVersionEditorPage: FC = () => { params.set("version", publishedVersion.id); } navigate( - `/templates/${templateName}/workspace?${params.toString()}`, + `/templates/${organizationId}/${templateName}/workspace?${params.toString()}`, ); }} isBuilding={ diff --git a/site/src/pages/TemplateVersionPage/TemplateVersionPage.tsx b/site/src/pages/TemplateVersionPage/TemplateVersionPage.tsx index dba108caa750b..cc68fa18245b7 100644 --- a/site/src/pages/TemplateVersionPage/TemplateVersionPage.tsx +++ b/site/src/pages/TemplateVersionPage/TemplateVersionPage.tsx @@ -9,7 +9,6 @@ import { templateVersionByName, } from "api/queries/templates"; import { useAuthenticated } from "contexts/auth/RequireAuth"; -import { useDashboard } from "modules/dashboard/useDashboard"; import { pageTitle } from "utils/page"; import TemplateVersionPageView from "./TemplateVersionPageView"; @@ -21,7 +20,9 @@ type Params = { export const TemplateVersionPage: FC = () => { const { version: versionName, template: templateName } = useParams() as Params; - const { organizationId } = useDashboard(); + const { organization: organizationId } = useParams() as { + organization: string; + }; /** * Template version files @@ -49,7 +50,7 @@ export const TemplateVersionPage: FC = () => { const params = new URLSearchParams(); if (versionId) { params.set("version", versionId); - return `/templates/${templateName}/workspace?${params.toString()}`; + return `/templates/${organizationId}/${templateName}/workspace?${params.toString()}`; } return undefined; }, [templateName, versionId]); @@ -73,6 +74,7 @@ export const TemplateVersionPage: FC = () => { baseFiles={activeVersionFilesQuery.data} versionName={versionName} templateName={templateName} + organizationId={organizationId} createWorkspaceUrl={ permissions.updateTemplates ? createWorkspaceUrl : undefined } diff --git a/site/src/pages/TemplateVersionPage/TemplateVersionPageView.stories.tsx b/site/src/pages/TemplateVersionPage/TemplateVersionPageView.stories.tsx index 1d04cf599182d..86764ca93f58e 100644 --- a/site/src/pages/TemplateVersionPage/TemplateVersionPageView.stories.tsx +++ b/site/src/pages/TemplateVersionPage/TemplateVersionPageView.stories.tsx @@ -23,6 +23,7 @@ You can add instructions here const defaultArgs: TemplateVersionPageViewProps = { templateName: MockTemplate.name, + organizationId: MockTemplate.organization_id, versionName: MockTemplateVersion.name, currentVersion: MockTemplateVersion, currentFiles: { diff --git a/site/src/pages/TemplateVersionPage/TemplateVersionPageView.tsx b/site/src/pages/TemplateVersionPage/TemplateVersionPageView.tsx index c9de8562d59f6..e9c8b56b3de51 100644 --- a/site/src/pages/TemplateVersionPage/TemplateVersionPageView.tsx +++ b/site/src/pages/TemplateVersionPage/TemplateVersionPageView.tsx @@ -22,6 +22,7 @@ import type { TemplateVersionFiles } from "utils/templateVersion"; export interface TemplateVersionPageViewProps { versionName: string; templateName: string; + organizationId: string; createWorkspaceUrl?: string; error: unknown; currentVersion: TemplateVersion | undefined; @@ -32,6 +33,7 @@ export interface TemplateVersionPageViewProps { export const TemplateVersionPageView: FC = ({ versionName, templateName, + organizationId, createWorkspaceUrl, currentVersion, currentFiles, @@ -56,7 +58,7 @@ export const TemplateVersionPageView: FC = ({ @@ -82,7 +84,9 @@ export const TemplateVersionPageView: FC = ({ + {templateName} } @@ -102,6 +106,7 @@ export const TemplateVersionPageView: FC = ({ baseFiles={baseFiles} templateName={templateName} versionName={versionName} + organizationId={organizationId} /> )} diff --git a/site/src/pages/TemplatesPage/TemplatesPageView.tsx b/site/src/pages/TemplatesPage/TemplatesPageView.tsx index d77808106bf8b..8304e83a6f836 100644 --- a/site/src/pages/TemplatesPage/TemplatesPageView.tsx +++ b/site/src/pages/TemplatesPage/TemplatesPageView.tsx @@ -84,7 +84,7 @@ interface TemplateRowProps { } const TemplateRow: FC = ({ template }) => { - const templatePageLink = `/templates/${template.name}`; + const templatePageLink = `/templates/${template.organization_id}/${template.name}`; const hasIcon = template.icon && template.icon !== ""; const navigate = useNavigate(); @@ -139,7 +139,9 @@ const TemplateRow: FC = ({ template }) => { title={`Create a workspace using the ${template.display_name} template`} onClick={(e) => { e.stopPropagation(); - navigate(`/templates/${template.name}/workspace`); + navigate( + `/templates/${template.organization_id}/${template.name}/workspace`, + ); }} > Create Workspace diff --git a/site/src/pages/WorkspacePage/WorkspaceTopbar.tsx b/site/src/pages/WorkspacePage/WorkspaceTopbar.tsx index d05f7c7c66453..4ca4de82fcc8e 100644 --- a/site/src/pages/WorkspacePage/WorkspaceTopbar.tsx +++ b/site/src/pages/WorkspacePage/WorkspaceTopbar.tsx @@ -169,7 +169,7 @@ export const WorkspaceTopbar: FC = ({ title={ {workspace.template_display_name.length > 0 @@ -180,7 +180,7 @@ export const WorkspaceTopbar: FC = ({ subtitle={ {workspace.latest_build.template_version_name} @@ -217,7 +217,7 @@ export const WorkspaceTopbar: FC = ({ diff --git a/site/src/pages/WorkspacesPage/WorkspacesButton.tsx b/site/src/pages/WorkspacesPage/WorkspacesButton.tsx index 95df46d32316f..c21d98891f7fd 100644 --- a/site/src/pages/WorkspacesPage/WorkspacesButton.tsx +++ b/site/src/pages/WorkspacesPage/WorkspacesButton.tsx @@ -127,7 +127,7 @@ interface WorkspaceResultsRowProps { const WorkspaceResultsRow: FC = ({ template }) => { return ( ( ({ width: "320px", padding: 16, diff --git a/site/src/pages/WorkspacesPage/WorkspacesTable.tsx b/site/src/pages/WorkspacesPage/WorkspacesTable.tsx index 4eb8c686f3bf7..344aed3d68fcd 100644 --- a/site/src/pages/WorkspacesPage/WorkspacesTable.tsx +++ b/site/src/pages/WorkspacesPage/WorkspacesTable.tsx @@ -170,6 +170,7 @@ export const WorkspacesTable: FC = ({ onUpdateVersion={() => { onUpdateWorkspace(workspace); }} + organizationId={workspace.organization_id} /> )} diff --git a/site/src/router.tsx b/site/src/router.tsx index 83205bc56d44e..09d4bce869503 100644 --- a/site/src/router.tsx +++ b/site/src/router.tsx @@ -301,31 +301,33 @@ export const router = createBrowserRouter( } /> } /> - - }> - } /> - } /> - } /> - } /> - } /> - } /> - + + + }> + } /> + } /> + } /> + } /> + } /> + } /> + - } /> + } /> - }> - } /> - } - /> - } /> - } /> - + }> + } /> + } + /> + } /> + } /> + - - - } /> + + + } /> + @@ -471,7 +473,7 @@ export const router = createBrowserRouter( {/* Pages that don't have the dashboard layout */} } /> } /> Date: Fri, 26 Jul 2024 03:23:07 +0000 Subject: [PATCH 24/32] fix: add missing useMemo dependency --- site/e2e/helpers.ts | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/site/e2e/helpers.ts b/site/e2e/helpers.ts index 4d047b948e93b..9ad24ddc27f1f 100644 --- a/site/e2e/helpers.ts +++ b/site/e2e/helpers.ts @@ -58,10 +58,12 @@ export const createWorkspace = async ( buildParameters: WorkspaceBuildParameter[] = [], useExternalAuthProvider: string | undefined = undefined, ): Promise => { - await page.goto(`/templates/${templateName}/workspace`, { + await page.goto(`/templates/default/${templateName}/workspace`, { waitUntil: "domcontentloaded", }); - await expectUrl(page).toHavePathName(`/templates/${templateName}/workspace`); + await expectUrl(page).toHavePathName( + `/templates/default/${templateName}/workspace`, + ); const name = randomName(); await page.getByLabel("name").fill(name); @@ -198,7 +200,7 @@ export const createTemplate = async ( const name = randomName(); await page.getByLabel("Name *").fill(name); await page.getByTestId("form-submit").click(); - await expectUrl(page).toHavePathName(`/templates/${name}/files`, { + await expectUrl(page).toHavePathName(`/templates/default/${name}/files`, { timeout: 30000, }); return name; @@ -830,10 +832,12 @@ export const updateTemplateSettings = async ( "name" | "display_name" | "description" | "deprecation_message" >, ) => { - await page.goto(`/templates/${templateName}/settings`, { + await page.goto(`/templates/default/${templateName}/settings`, { waitUntil: "domcontentloaded", }); - await expectUrl(page).toHavePathName(`/templates/${templateName}/settings`); + await expectUrl(page).toHavePathName( + `/templates/default/${templateName}/settings`, + ); for (const [key, value] of Object.entries(templateSettingValues)) { // Skip max_port_share_level for now since the frontend is not yet able to handle it @@ -847,7 +851,7 @@ export const updateTemplateSettings = async ( await page.getByTestId("form-submit").click(); const name = templateSettingValues.name ?? templateName; - await expectUrl(page).toHavePathName(`/templates/${name}`); + await expectUrl(page).toHavePathName(`/templates/default/${name}`); }; export const updateWorkspace = async ( From e23475b81a7e84903084236f9bcbc835c6067590 Mon Sep 17 00:00:00 2001 From: Jaayden Halko Date: Fri, 26 Jul 2024 03:25:50 +0000 Subject: [PATCH 25/32] fix: add organizationId dependency --- site/src/pages/TemplateVersionPage/TemplateVersionPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/src/pages/TemplateVersionPage/TemplateVersionPage.tsx b/site/src/pages/TemplateVersionPage/TemplateVersionPage.tsx index cc68fa18245b7..504220cb13390 100644 --- a/site/src/pages/TemplateVersionPage/TemplateVersionPage.tsx +++ b/site/src/pages/TemplateVersionPage/TemplateVersionPage.tsx @@ -53,7 +53,7 @@ export const TemplateVersionPage: FC = () => { return `/templates/${organizationId}/${templateName}/workspace?${params.toString()}`; } return undefined; - }, [templateName, versionId]); + }, [templateName, versionId, organizationId]); return ( <> From 53d4a3efa584f4a5477bcaea22b51ae6322d3d35 Mon Sep 17 00:00:00 2001 From: Jaayden Halko Date: Fri, 26 Jul 2024 03:29:18 +0000 Subject: [PATCH 26/32] feat: update tests for organization in templates route --- site/e2e/tests/updateTemplate.spec.ts | 12 +++++---- .../workspaces/autoCreateWorkspace.spec.ts | 6 ++--- .../CreateTemplatePage.test.tsx | 4 +-- .../CreateWorkspacePage.test.tsx | 26 ++++++++++--------- .../useWorkspaceDuplication.test.tsx | 6 +++-- .../TemplateEmbedPage.test.tsx | 6 ++--- .../TemplateVersionEditorPage.test.tsx | 8 +++--- .../TemplateVersionPage.test.tsx | 4 +-- 8 files changed, 39 insertions(+), 33 deletions(-) diff --git a/site/e2e/tests/updateTemplate.spec.ts b/site/e2e/tests/updateTemplate.spec.ts index c35ebcd6d1948..827d0a7a16f16 100644 --- a/site/e2e/tests/updateTemplate.spec.ts +++ b/site/e2e/tests/updateTemplate.spec.ts @@ -26,11 +26,11 @@ test("add and remove a group", async ({ page }) => { const templateName = await createTemplate(page); const groupName = await createGroup(page); - await page.goto(`/templates/${templateName}/settings/permissions`, { + await page.goto(`/templates/default/${templateName}/settings/permissions`, { waitUntil: "domcontentloaded", }); await expectUrl(page).toHavePathName( - `/templates/${templateName}/settings/permissions`, + `/templates/default/${templateName}/settings/permissions`, ); // Type the first half of the group name @@ -56,15 +56,17 @@ test("require latest version", async ({ page }) => { const templateName = await createTemplate(page); - await page.goto(`/templates/${templateName}/settings`, { + await page.goto(`/templates/default/${templateName}/settings`, { waitUntil: "domcontentloaded", }); - await expectUrl(page).toHavePathName(`/templates/${templateName}/settings`); + await expectUrl(page).toHavePathName( + `/templates/default/${templateName}/settings`, + ); let checkbox = await page.waitForSelector("#require_active_version"); await checkbox.click(); await page.getByTestId("form-submit").click(); - await page.goto(`/templates/${templateName}/settings`, { + await page.goto(`/templates/default/${templateName}/settings`, { waitUntil: "domcontentloaded", }); checkbox = await page.waitForSelector("#require_active_version"); diff --git a/site/e2e/tests/workspaces/autoCreateWorkspace.spec.ts b/site/e2e/tests/workspaces/autoCreateWorkspace.spec.ts index 3a9aaee2eeb3c..08cf77b1149b7 100644 --- a/site/e2e/tests/workspaces/autoCreateWorkspace.spec.ts +++ b/site/e2e/tests/workspaces/autoCreateWorkspace.spec.ts @@ -18,7 +18,7 @@ test("create workspace in auto mode", async ({ page }) => { ); const name = "test-workspace"; await page.goto( - `/templates/${template}/workspace?mode=auto¶m.repo=example&name=${name}`, + `/templates/default/${template}/workspace?mode=auto¶m.repo=example&name=${name}`, { waitUntil: "domcontentloaded", }, @@ -38,7 +38,7 @@ test("use an existing workspace that matches the `match` parameter instead of cr ); const prevWorkspace = await createWorkspace(page, template); await page.goto( - `/templates/${template}/workspace?mode=auto¶m.repo=example&name=new-name&match=name:${prevWorkspace}`, + `/templates/default/${template}/workspace?mode=auto¶m.repo=example&name=new-name&match=name:${prevWorkspace}`, { waitUntil: "domcontentloaded", }, @@ -56,7 +56,7 @@ test("show error if `match` parameter is invalid", async ({ page }) => { ); const prevWorkspace = await createWorkspace(page, template); await page.goto( - `/templates/${template}/workspace?mode=auto¶m.repo=example&name=new-name&match=not-valid-query:${prevWorkspace}`, + `/templates/default/${template}/workspace?mode=auto¶m.repo=example&name=new-name&match=not-valid-query:${prevWorkspace}`, { waitUntil: "domcontentloaded", }, diff --git a/site/src/pages/CreateTemplatePage/CreateTemplatePage.test.tsx b/site/src/pages/CreateTemplatePage/CreateTemplatePage.test.tsx index 956d385ccd75a..6ef46fef42756 100644 --- a/site/src/pages/CreateTemplatePage/CreateTemplatePage.test.tsx +++ b/site/src/pages/CreateTemplatePage/CreateTemplatePage.test.tsx @@ -93,7 +93,7 @@ test("Create template from starter template", async () => { ); await waitFor(() => expect(API.createTemplate).toBeCalledTimes(1)); expect(router.state.location.pathname).toEqual( - `/templates/${MockTemplate.name}/files`, + `/templates/00000000-0000-0000-0000-000000000000/${MockTemplate.name}/files`, ); expect(API.createTemplateVersion).toHaveBeenCalledWith( "00000000-0000-0000-0000-000000000000", @@ -146,7 +146,7 @@ test("Create template from duplicating a template", async () => { ); await waitFor(() => { expect(router.state.location.pathname).toEqual( - `/templates/${MockTemplate.name}/files`, + `/templates/${MockTemplate.organization_id}/${MockTemplate.name}/files`, ); }); }); diff --git a/site/src/pages/CreateWorkspacePage/CreateWorkspacePage.test.tsx b/site/src/pages/CreateWorkspacePage/CreateWorkspacePage.test.tsx index 4f0bcd5d71aea..eabf0585b0f59 100644 --- a/site/src/pages/CreateWorkspacePage/CreateWorkspacePage.test.tsx +++ b/site/src/pages/CreateWorkspacePage/CreateWorkspacePage.test.tsx @@ -27,8 +27,8 @@ const validationNumberNotInRangeText = "Value must be between 1 and 3."; const renderCreateWorkspacePage = () => { return renderWithAuth(, { - route: "/templates/" + MockTemplate.name + "/workspace", - path: "/templates/:template/workspace", + route: "/templates/default/" + MockTemplate.name + "/workspace", + path: "/templates/:organization/:template/workspace", }); }; @@ -77,10 +77,10 @@ describe("CreateWorkspacePage", () => { renderWithAuth(, { route: - "/templates/" + + "/templates/default/" + MockTemplate.name + `/workspace?param.${param}=${paramValue}`, - path: "/templates/:template/workspace", + path: "/templates/:organization/:template/workspace", }); await screen.findByDisplayValue(paramValue); @@ -279,10 +279,10 @@ describe("CreateWorkspacePage", () => { renderWithAuth(, { route: - "/templates/" + + "/templates/default/" + MockTemplate.name + `/workspace?param.${param}=${paramValue}&mode=auto`, - path: "/templates/:template/workspace", + path: "/templates/:organization/:template/workspace", }); await waitFor(() => { @@ -314,10 +314,10 @@ describe("CreateWorkspacePage", () => { renderWithAuth(, { route: - "/templates/" + + "/templates/default/" + MockTemplate.name + `/workspace?param.${param}=${paramValue}&mode=auto`, - path: "/templates/:template/workspace", + path: "/templates/:organization/:template/workspace", }); await waitForLoaderToBeRemoved(); @@ -339,10 +339,10 @@ describe("CreateWorkspacePage", () => { renderWithAuth(, { route: - "/templates/" + + "/templates/default/" + MockTemplate.name + `/workspace?param.${param}=${paramValue}&mode=auto&version=test-template-version`, - path: "/templates/:template/workspace", + path: "/templates/:organization/:template/workspace", }); await waitFor(() => { @@ -367,8 +367,10 @@ describe("CreateWorkspacePage", () => { }); renderWithAuth(, { - path: "/templates/:template/workspace", - route: `/templates/${MockWorkspace.name}/workspace?${params.toString()}`, + path: "/templates/:organization/:template/workspace", + route: `/templates/default/${ + MockWorkspace.name + }/workspace?${params.toString()}`, }); const warningMessage = await screen.findByTestId("duplication-warning"); diff --git a/site/src/pages/CreateWorkspacePage/useWorkspaceDuplication.test.tsx b/site/src/pages/CreateWorkspacePage/useWorkspaceDuplication.test.tsx index 6c3510a56d552..277654dca4994 100644 --- a/site/src/pages/CreateWorkspacePage/useWorkspaceDuplication.test.tsx +++ b/site/src/pages/CreateWorkspacePage/useWorkspaceDuplication.test.tsx @@ -17,7 +17,7 @@ function render(workspace?: Workspace) { routingOptions: { extraRoutes: [ { - path: "/templates/:template/workspace", + path: "/templates/:organization/:template/workspace", element: , }, ], @@ -39,7 +39,9 @@ async function performNavigation( const templateName = MockWorkspace.template_name; return waitFor(() => { const { pathname } = getLocationSnapshot(); - expect(pathname).toEqual(`/templates/${templateName}/workspace`); + expect(pathname).toEqual( + `/templates/${MockWorkspace.organization_id}/${templateName}/workspace`, + ); }); } diff --git a/site/src/pages/TemplatePage/TemplateEmbedPage/TemplateEmbedPage.test.tsx b/site/src/pages/TemplatePage/TemplateEmbedPage/TemplateEmbedPage.test.tsx index d2867a80085b6..438b161c05ee9 100644 --- a/site/src/pages/TemplatePage/TemplateEmbedPage/TemplateEmbedPage.test.tsx +++ b/site/src/pages/TemplatePage/TemplateEmbedPage/TemplateEmbedPage.test.tsx @@ -23,8 +23,8 @@ test("Users can fill the parameters and copy the open in coder url", async () => , { - route: `/templates/${MockTemplate.name}/embed`, - path: "/templates/:template/embed", + route: `/templates/${MockTemplate.organization_id}/${MockTemplate.name}/embed`, + path: "/templates/:organization/:template/embed", }, ); await waitForLoaderToBeRemoved(); @@ -47,6 +47,6 @@ test("Users can fill the parameters and copy the open in coder url", async () => const copyButton = screen.getByRole("button", { name: /copy/i }); await userEvent.click(copyButton); expect(window.navigator.clipboard.writeText).toBeCalledWith( - `[![Open in Coder](http://localhost/open-in-coder.svg)](http://localhost/templates/test-template/workspace?mode=manual¶m.first_parameter=firstParameterValue¶m.second_parameter=123456)`, + `[![Open in Coder](http://localhost/open-in-coder.svg)](http://localhost/templates/${MockTemplate.organization_id}/test-template/workspace?mode=manual¶m.first_parameter=firstParameterValue¶m.second_parameter=123456)`, ); }); diff --git a/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditorPage.test.tsx b/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditorPage.test.tsx index 8c63b7db428d1..3864d41a79d27 100644 --- a/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditorPage.test.tsx +++ b/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditorPage.test.tsx @@ -53,8 +53,8 @@ jest.mock("pages/TemplateVersionEditorPage/MonacoEditor", () => ({ const renderTemplateEditorPage = () => { renderWithAuth(, { - route: `/templates/${MockTemplate.name}/versions/${MockTemplateVersion.name}/edit`, - path: "/templates/:template/versions/:version/edit", + route: `/templates/default/${MockTemplate.name}/versions/${MockTemplateVersion.name}/edit`, + path: "/templates/:organization/:template/versions/:version/edit", extraRoutes: [ { path: "/templates/:templateId", @@ -369,14 +369,14 @@ function renderEditorPage(queryClient: QueryClient) { children: [ { element: , - path: "/templates/:template/versions/:version/edit", + path: "/templates/:organization/:template/versions/:version/edit", }, ], }, ], { initialEntries: [ - `/templates/${MockTemplate.name}/versions/${MockTemplateVersion.name}/edit`, + `/templates/default/${MockTemplate.name}/versions/${MockTemplateVersion.name}/edit`, ], }, )} diff --git a/site/src/pages/TemplateVersionPage/TemplateVersionPage.test.tsx b/site/src/pages/TemplateVersionPage/TemplateVersionPage.test.tsx index 9430866207634..936fcbf06684f 100644 --- a/site/src/pages/TemplateVersionPage/TemplateVersionPage.test.tsx +++ b/site/src/pages/TemplateVersionPage/TemplateVersionPage.test.tsx @@ -26,8 +26,8 @@ const setup = async () => { .mockImplementation(() => "a minute ago"); renderWithAuth(, { - route: `/templates/${TEMPLATE_NAME}/versions/${VERSION_NAME}`, - path: "/templates/:template/versions/:version", + route: `/templates/default/${TEMPLATE_NAME}/versions/${VERSION_NAME}`, + path: "/templates/:organization/:template/versions/:version", }); await waitForLoaderToBeRemoved(); }; From 7d92893404f95d334078fcc183e3f1f6c4a5eecd Mon Sep 17 00:00:00 2001 From: Jaayden Halko Date: Fri, 26 Jul 2024 18:04:32 +0000 Subject: [PATCH 27/32] feat: gate org dropdown to multiple_organizations feature --- site/src/pages/CreateTemplatePage/CreateTemplateForm.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/site/src/pages/CreateTemplatePage/CreateTemplateForm.tsx b/site/src/pages/CreateTemplatePage/CreateTemplateForm.tsx index 9a4c2faf403e7..0d671678cb9bb 100644 --- a/site/src/pages/CreateTemplatePage/CreateTemplateForm.tsx +++ b/site/src/pages/CreateTemplatePage/CreateTemplateForm.tsx @@ -23,6 +23,7 @@ import { import { IconField } from "components/IconField/IconField"; import { OrganizationAutocomplete } from "components/OrganizationAutocomplete/OrganizationAutocomplete"; import { useDashboard } from "modules/dashboard/useDashboard"; +import { useFeatureVisibility } from "modules/dashboard/useFeatureVisibility"; import { SelectedTemplate } from "pages/CreateWorkspacePage/SelectedTemplate"; import { nameValidator, @@ -89,7 +90,7 @@ const defaultInitialValues: CreateTemplateData = { allow_user_autostop: false, allow_everyone_group_access: true, provisioner_type: "terraform", - organization_id: "00000000-0000-0000-0000-000000000000", + organization_id: "default", }; type GetInitialValuesParams = { @@ -181,6 +182,8 @@ export type CreateTemplateFormProps = ( export const CreateTemplateForm: FC = (props) => { const { experiments } = useDashboard(); + const { multiple_organizations: organizationsEnabled } = + useFeatureVisibility(); const [searchParams] = useSearchParams(); const [selectedOrg, setSelectedOrg] = useState(null); const { @@ -234,7 +237,7 @@ export const CreateTemplateForm: FC = (props) => { }} /> )} - {multiOrgExperimentEnabled && ( + {multiOrgExperimentEnabled && organizationsEnabled && ( Date: Fri, 26 Jul 2024 18:05:06 +0000 Subject: [PATCH 28/32] chore: set default for organization route param --- .../pages/TemplatePage/TemplateFilesPage/TemplateFilesPage.tsx | 2 +- .../TemplateSchedulePage/TemplateSchedulePage.tsx | 2 +- site/src/pages/TemplateSettingsPage/TemplateSettingsLayout.tsx | 2 +- .../TemplateVariablesPage/TemplateVariablesPage.tsx | 2 +- .../TemplateVersionEditorPage/TemplateVersionEditorPage.tsx | 2 +- site/src/pages/TemplateVersionPage/TemplateVersionPage.tsx | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/site/src/pages/TemplatePage/TemplateFilesPage/TemplateFilesPage.tsx b/site/src/pages/TemplatePage/TemplateFilesPage/TemplateFilesPage.tsx index bc31bba8f1e01..f76f7ca6e938d 100644 --- a/site/src/pages/TemplatePage/TemplateFilesPage/TemplateFilesPage.tsx +++ b/site/src/pages/TemplatePage/TemplateFilesPage/TemplateFilesPage.tsx @@ -9,7 +9,7 @@ import { useTemplateLayoutContext } from "pages/TemplatePage/TemplateLayout"; import { getTemplatePageTitle } from "../utils"; const TemplateFilesPage: FC = () => { - const { organization: organizationId } = useParams() as { + const { organization: organizationId = "default" } = useParams() as { organization: string; }; const { template, activeVersion } = useTemplateLayoutContext(); diff --git a/site/src/pages/TemplateSettingsPage/TemplateSchedulePage/TemplateSchedulePage.tsx b/site/src/pages/TemplateSettingsPage/TemplateSchedulePage/TemplateSchedulePage.tsx index 2a07142960abf..71f1063bed317 100644 --- a/site/src/pages/TemplateSettingsPage/TemplateSchedulePage/TemplateSchedulePage.tsx +++ b/site/src/pages/TemplateSettingsPage/TemplateSchedulePage/TemplateSchedulePage.tsx @@ -17,7 +17,7 @@ const TemplateSchedulePage: FC = () => { const queryClient = useQueryClient(); const { template } = useTemplateSettings(); const { entitlements } = useDashboard(); - const { organization: organizationId } = useParams() as { + const { organization: organizationId = "default" } = useParams() as { organization: string; }; const allowAdvancedScheduling = diff --git a/site/src/pages/TemplateSettingsPage/TemplateSettingsLayout.tsx b/site/src/pages/TemplateSettingsPage/TemplateSettingsLayout.tsx index 21dc495491e65..bce90f8f93a32 100644 --- a/site/src/pages/TemplateSettingsPage/TemplateSettingsLayout.tsx +++ b/site/src/pages/TemplateSettingsPage/TemplateSettingsLayout.tsx @@ -26,7 +26,7 @@ export function useTemplateSettings() { } export const TemplateSettingsLayout: FC = () => { - const { organization: organizationId } = useParams() as { + const { organization: organizationId = "default" } = useParams() as { organization: string; }; const { template: templateName } = useParams() as { template: string }; diff --git a/site/src/pages/TemplateSettingsPage/TemplateVariablesPage/TemplateVariablesPage.tsx b/site/src/pages/TemplateSettingsPage/TemplateVariablesPage/TemplateVariablesPage.tsx index e3ed8a385b8fc..7f09c46dc301f 100644 --- a/site/src/pages/TemplateSettingsPage/TemplateVariablesPage/TemplateVariablesPage.tsx +++ b/site/src/pages/TemplateSettingsPage/TemplateVariablesPage/TemplateVariablesPage.tsx @@ -25,7 +25,7 @@ export const TemplateVariablesPage: FC = () => { organization: string; template: string; }; - const { organization: organizationId } = useParams() as { + const { organization: organizationId = "default" } = useParams() as { organization: string; }; const { template } = useTemplateSettings(); diff --git a/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditorPage.tsx b/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditorPage.tsx index df33e9e919afa..7229e394a46ef 100644 --- a/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditorPage.tsx +++ b/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditorPage.tsx @@ -35,7 +35,7 @@ export const TemplateVersionEditorPage: FC = () => { const navigate = useNavigate(); const { version: versionName, template: templateName } = useParams() as Params; - const { organization: organizationId } = useParams() as { + const { organization: organizationId = "default" } = useParams() as { organization: string; }; const [searchParams, setSearchParams] = useSearchParams(); diff --git a/site/src/pages/TemplateVersionPage/TemplateVersionPage.tsx b/site/src/pages/TemplateVersionPage/TemplateVersionPage.tsx index 504220cb13390..a3815384ae042 100644 --- a/site/src/pages/TemplateVersionPage/TemplateVersionPage.tsx +++ b/site/src/pages/TemplateVersionPage/TemplateVersionPage.tsx @@ -20,7 +20,7 @@ type Params = { export const TemplateVersionPage: FC = () => { const { version: versionName, template: templateName } = useParams() as Params; - const { organization: organizationId } = useParams() as { + const { organization: organizationId = "default" } = useParams() as { organization: string; }; From 9bb11398257bf270aba5d0ebe9165106dc4f92c4 Mon Sep 17 00:00:00 2001 From: Jaayden Halko Date: Fri, 26 Jul 2024 19:49:20 +0000 Subject: [PATCH 29/32] feat: update template routes for organizations --- site/src/router.tsx | 62 +++++++++++++++++++++++---------------------- 1 file changed, 32 insertions(+), 30 deletions(-) diff --git a/site/src/router.tsx b/site/src/router.tsx index 09d4bce869503..c5d6ecbe23b2a 100644 --- a/site/src/router.tsx +++ b/site/src/router.tsx @@ -275,6 +275,36 @@ const RoutesWithSuspense = () => { ); }; +const templateRouter = () => { + return ( + + }> + } /> + } /> + } /> + } /> + } /> + } /> + + + } /> + + }> + } /> + } /> + } /> + } /> + + + + + } /> + + + + ); +}; + export const router = createBrowserRouter( createRoutesFromChildren( }> @@ -301,36 +331,8 @@ export const router = createBrowserRouter( } /> } /> - - - }> - } /> - } /> - } /> - } /> - } /> - } /> - - - } /> - - }> - } /> - } - /> - } /> - } /> - - - - - } /> - - - - + {templateRouter()} + {templateRouter()} From 1375b305d3d79f79c57cf4cd13f6cb67a02f998f Mon Sep 17 00:00:00 2001 From: Jaayden Halko Date: Fri, 26 Jul 2024 19:49:50 +0000 Subject: [PATCH 30/32] feat: add templates route --- site/src/modules/navigation.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/site/src/modules/navigation.ts b/site/src/modules/navigation.ts index 74217a4ceaaac..59a07db8bc075 100644 --- a/site/src/modules/navigation.ts +++ b/site/src/modules/navigation.ts @@ -1,3 +1,5 @@ +import type { Experiments } from "api/typesGenerated"; + /** * @fileoverview TODO: centralize navigation code here! URL constants, URL formatting, all of it */ @@ -5,3 +7,19 @@ export const USERS_LINK = `/users?filter=${encodeURIComponent( "status:active", )}`; + +export const TEMPLATES_ROUTE = ( + organizationId: string, + templateName: string, + routeSuffix: string = "", + orgsEnabled: boolean = false, + experiments: Experiments = [], +) => { + const multiOrgExperimentEnabled = experiments.includes("multi-organization"); + + if (multiOrgExperimentEnabled && orgsEnabled) { + return `/templates/${organizationId}/${templateName}${routeSuffix}`; + } + + return `/templates/${templateName}${routeSuffix}`; +}; From aa452fee98781b8796e27a41e17c34090d93aea1 Mon Sep 17 00:00:00 2001 From: Jaayden Halko Date: Fri, 26 Jul 2024 19:57:08 +0000 Subject: [PATCH 31/32] feat: use templates_route --- .../src/pages/TemplatePage/TemplateLayout.tsx | 50 +++++++++---------- 1 file changed, 24 insertions(+), 26 deletions(-) diff --git a/site/src/pages/TemplatePage/TemplateLayout.tsx b/site/src/pages/TemplatePage/TemplateLayout.tsx index 3ba2ef4ac33e7..47e5e5d75ce45 100644 --- a/site/src/pages/TemplatePage/TemplateLayout.tsx +++ b/site/src/pages/TemplatePage/TemplateLayout.tsx @@ -13,6 +13,9 @@ import { ErrorAlert } from "components/Alert/ErrorAlert"; import { Loader } from "components/Loader/Loader"; import { Margins } from "components/Margins/Margins"; import { TAB_PADDING_Y, TabLink, Tabs, TabsList } from "components/Tabs/Tabs"; +import { useDashboard } from "modules/dashboard/useDashboard"; +import { useFeatureVisibility } from "modules/dashboard/useFeatureVisibility"; +import { TEMPLATES_ROUTE } from "modules/navigation"; import { TemplatePageHeader } from "./TemplatePageHeader"; const templatePermissions = ( @@ -71,7 +74,8 @@ export const TemplateLayout: FC = ({ children = , }) => { const navigate = useNavigate(); - const { template: templateName, organization: organizationId } = + const { experiments } = useDashboard(); + const { template: templateName, organization: organizationId = "default" } = useParams() as { template: string; organization: string; @@ -87,6 +91,18 @@ export const TemplateLayout: FC = ({ // have permission to update templates. Need both checks. const shouldShowInsights = data?.permissions?.canUpdateTemplate || data?.permissions?.canReadInsights; + const { multiple_organizations: organizationsEnabled } = + useFeatureVisibility(); + + const templatesRoute = (path = "") => { + return TEMPLATES_ROUTE( + organizationId, + templateName, + path, + organizationsEnabled, + experiments, + ); + }; if (error) { return ( @@ -107,7 +123,7 @@ export const TemplateLayout: FC = ({ activeVersion={data.activeVersion} permissions={data.permissions} onDeleteTemplate={() => { - navigate("/templates"); + navigate(templatesRoute()); }} /> @@ -117,43 +133,25 @@ export const TemplateLayout: FC = ({ > - + Summary - + Docs {data.permissions.canUpdateTemplate && ( - + Source Code )} - + Versions - + Embed {shouldShowInsights && ( - + Insights )} From b637620f8f66818cf2ea38b5ffcaaec0f59af4a9 Mon Sep 17 00:00:00 2001 From: McKayla Washburn Date: Fri, 26 Jul 2024 23:22:05 +0000 Subject: [PATCH 32/32] the whirly durly --- site/e2e/helpers.ts | 16 +++----- site/e2e/tests/updateTemplate.spec.ts | 12 +++--- .../workspaces/autoCreateWorkspace.spec.ts | 6 +-- site/src/api/queries/templates.ts | 10 ++++- .../OrganizationAutocomplete.tsx | 38 ++++++++---------- .../UserAutocomplete/UserAutocomplete.tsx | 39 ++++++++----------- .../TemplateExampleCard.tsx | 4 +- .../templates/TemplateFiles/TemplateFiles.tsx | 15 +++---- .../WorkspaceOutdatedTooltip.tsx | 13 ++++--- .../CreateTemplateForm.stories.tsx | 2 - .../DuplicateTemplateView.tsx | 22 +++++------ .../CreateTemplatesPageView.tsx | 4 +- .../StarterTemplates.tsx | 3 -- .../CreateWorkspacePage.test.tsx | 11 ++---- .../TemplateFilesPage/TemplateFilesPage.tsx | 6 +-- .../pages/TemplatePage/TemplatePageHeader.tsx | 20 +++++----- .../TemplateVersionEditorPage.tsx | 13 ++++--- .../TemplateVersionPage.test.tsx | 4 +- .../TemplateVersionPage.tsx | 12 +++--- .../TemplateVersionPageView.stories.tsx | 2 +- .../TemplateVersionPageView.tsx | 19 ++++----- .../pages/WorkspacesPage/WorkspacesTable.tsx | 2 +- 22 files changed, 128 insertions(+), 145 deletions(-) diff --git a/site/e2e/helpers.ts b/site/e2e/helpers.ts index 9ad24ddc27f1f..4d047b948e93b 100644 --- a/site/e2e/helpers.ts +++ b/site/e2e/helpers.ts @@ -58,12 +58,10 @@ export const createWorkspace = async ( buildParameters: WorkspaceBuildParameter[] = [], useExternalAuthProvider: string | undefined = undefined, ): Promise => { - await page.goto(`/templates/default/${templateName}/workspace`, { + await page.goto(`/templates/${templateName}/workspace`, { waitUntil: "domcontentloaded", }); - await expectUrl(page).toHavePathName( - `/templates/default/${templateName}/workspace`, - ); + await expectUrl(page).toHavePathName(`/templates/${templateName}/workspace`); const name = randomName(); await page.getByLabel("name").fill(name); @@ -200,7 +198,7 @@ export const createTemplate = async ( const name = randomName(); await page.getByLabel("Name *").fill(name); await page.getByTestId("form-submit").click(); - await expectUrl(page).toHavePathName(`/templates/default/${name}/files`, { + await expectUrl(page).toHavePathName(`/templates/${name}/files`, { timeout: 30000, }); return name; @@ -832,12 +830,10 @@ export const updateTemplateSettings = async ( "name" | "display_name" | "description" | "deprecation_message" >, ) => { - await page.goto(`/templates/default/${templateName}/settings`, { + await page.goto(`/templates/${templateName}/settings`, { waitUntil: "domcontentloaded", }); - await expectUrl(page).toHavePathName( - `/templates/default/${templateName}/settings`, - ); + await expectUrl(page).toHavePathName(`/templates/${templateName}/settings`); for (const [key, value] of Object.entries(templateSettingValues)) { // Skip max_port_share_level for now since the frontend is not yet able to handle it @@ -851,7 +847,7 @@ export const updateTemplateSettings = async ( await page.getByTestId("form-submit").click(); const name = templateSettingValues.name ?? templateName; - await expectUrl(page).toHavePathName(`/templates/default/${name}`); + await expectUrl(page).toHavePathName(`/templates/${name}`); }; export const updateWorkspace = async ( diff --git a/site/e2e/tests/updateTemplate.spec.ts b/site/e2e/tests/updateTemplate.spec.ts index 827d0a7a16f16..c35ebcd6d1948 100644 --- a/site/e2e/tests/updateTemplate.spec.ts +++ b/site/e2e/tests/updateTemplate.spec.ts @@ -26,11 +26,11 @@ test("add and remove a group", async ({ page }) => { const templateName = await createTemplate(page); const groupName = await createGroup(page); - await page.goto(`/templates/default/${templateName}/settings/permissions`, { + await page.goto(`/templates/${templateName}/settings/permissions`, { waitUntil: "domcontentloaded", }); await expectUrl(page).toHavePathName( - `/templates/default/${templateName}/settings/permissions`, + `/templates/${templateName}/settings/permissions`, ); // Type the first half of the group name @@ -56,17 +56,15 @@ test("require latest version", async ({ page }) => { const templateName = await createTemplate(page); - await page.goto(`/templates/default/${templateName}/settings`, { + await page.goto(`/templates/${templateName}/settings`, { waitUntil: "domcontentloaded", }); - await expectUrl(page).toHavePathName( - `/templates/default/${templateName}/settings`, - ); + await expectUrl(page).toHavePathName(`/templates/${templateName}/settings`); let checkbox = await page.waitForSelector("#require_active_version"); await checkbox.click(); await page.getByTestId("form-submit").click(); - await page.goto(`/templates/default/${templateName}/settings`, { + await page.goto(`/templates/${templateName}/settings`, { waitUntil: "domcontentloaded", }); checkbox = await page.waitForSelector("#require_active_version"); diff --git a/site/e2e/tests/workspaces/autoCreateWorkspace.spec.ts b/site/e2e/tests/workspaces/autoCreateWorkspace.spec.ts index 08cf77b1149b7..3a9aaee2eeb3c 100644 --- a/site/e2e/tests/workspaces/autoCreateWorkspace.spec.ts +++ b/site/e2e/tests/workspaces/autoCreateWorkspace.spec.ts @@ -18,7 +18,7 @@ test("create workspace in auto mode", async ({ page }) => { ); const name = "test-workspace"; await page.goto( - `/templates/default/${template}/workspace?mode=auto¶m.repo=example&name=${name}`, + `/templates/${template}/workspace?mode=auto¶m.repo=example&name=${name}`, { waitUntil: "domcontentloaded", }, @@ -38,7 +38,7 @@ test("use an existing workspace that matches the `match` parameter instead of cr ); const prevWorkspace = await createWorkspace(page, template); await page.goto( - `/templates/default/${template}/workspace?mode=auto¶m.repo=example&name=new-name&match=name:${prevWorkspace}`, + `/templates/${template}/workspace?mode=auto¶m.repo=example&name=new-name&match=name:${prevWorkspace}`, { waitUntil: "domcontentloaded", }, @@ -56,7 +56,7 @@ test("show error if `match` parameter is invalid", async ({ page }) => { ); const prevWorkspace = await createWorkspace(page, template); await page.goto( - `/templates/default/${template}/workspace?mode=auto¶m.repo=example&name=new-name&match=not-valid-query:${prevWorkspace}`, + `/templates/${template}/workspace?mode=auto¶m.repo=example&name=new-name&match=not-valid-query:${prevWorkspace}`, { waitUntil: "domcontentloaded", }, diff --git a/site/src/api/queries/templates.ts b/site/src/api/queries/templates.ts index 2d0485b8f347b..22c1962a3187b 100644 --- a/site/src/api/queries/templates.ts +++ b/site/src/api/queries/templates.ts @@ -13,11 +13,19 @@ import type { import { delay } from "utils/delay"; import { getTemplateVersionFiles } from "utils/templateVersion"; +export const templateKey = (templateId: string) => ["template", templateId]; + +export const template = (templateId: string): QueryOptions