diff --git a/site/src/api/queries/templates.ts b/site/src/api/queries/templates.ts index 9fce3909c229c..3be881f0d5b03 100644 --- a/site/src/api/queries/templates.ts +++ b/site/src/api/queries/templates.ts @@ -15,6 +15,7 @@ import { type QueryOptions, } from "react-query"; import { delay } from "utils/delay"; +import { getTemplateVersionFiles } from "utils/templateVersion"; export const templateByNameKey = (orgId: string, name: string) => [ orgId, @@ -236,6 +237,38 @@ export const resources = (versionId: string) => { }; }; +export const templateFiles = (fileId: string) => { + return { + queryKey: ["templateFiles", fileId], + queryFn: async () => { + const tarFile = await API.getFile(fileId); + return getTemplateVersionFiles(tarFile); + }, + }; +}; + +export const previousTemplateVersion = ( + organizationId: string, + templateName: string, + versionName: string, +) => { + return { + queryKey: [ + "templateVersion", + organizationId, + templateName, + versionName, + "previous", + ], + queryFn: () => + API.getPreviousTemplateVersionByName( + organizationId, + templateName, + versionName, + ), + }; +}; + const waitBuildToBeFinished = async (version: TemplateVersion) => { let data: TemplateVersion; let jobStatus: ProvisionerJobStatus; diff --git a/site/src/components/TemplateFiles/TemplateFiles.stories.tsx b/site/src/components/TemplateFiles/TemplateFiles.stories.tsx index ce565ed3d0a48..ba484d62c74bb 100644 --- a/site/src/components/TemplateFiles/TemplateFiles.stories.tsx +++ b/site/src/components/TemplateFiles/TemplateFiles.stories.tsx @@ -18,7 +18,7 @@ const meta: Meta = { component: TemplateFiles, args: { currentFiles: exampleFiles, - previousFiles: exampleFiles, + baseFiles: exampleFiles, tab: { value: "0", set: action("change tab") }, }, }; diff --git a/site/src/components/TemplateFiles/TemplateFiles.tsx b/site/src/components/TemplateFiles/TemplateFiles.tsx index da1cc9bf07356..ba37caff6ec77 100644 --- a/site/src/components/TemplateFiles/TemplateFiles.tsx +++ b/site/src/components/TemplateFiles/TemplateFiles.tsx @@ -1,10 +1,10 @@ import { type Interpolation, type Theme } from "@emotion/react"; -import { type FC } from "react"; +import { useEffect, type FC } from "react"; import { DockerIcon } from "components/Icons/DockerIcon"; import { MarkdownIcon } from "components/Icons/MarkdownIcon"; import { TerraformIcon } from "components/Icons/TerraformIcon"; import { SyntaxHighlighter } from "components/SyntaxHighlighter/SyntaxHighlighter"; -import { UseTabResult } from "hooks/useTab"; +import { UseTabResult, useTab } from "hooks/useTab"; import { AllowedExtension, TemplateVersionFiles } from "utils/templateVersion"; import InsertDriveFileOutlined from "@mui/icons-material/InsertDriveFileOutlined"; @@ -39,19 +39,22 @@ const languageByExtension: Record = { interface TemplateFilesProps { currentFiles: TemplateVersionFiles; - previousFiles?: TemplateVersionFiles; + /** + * Files used to compare with current files + */ + baseFiles?: TemplateVersionFiles; tab: UseTabResult; } export const TemplateFiles: FC = ({ currentFiles, - previousFiles, + baseFiles, tab, }) => { const filenames = Object.keys(currentFiles); const selectedFilename = filenames[Number(tab.value)]; const currentFile = currentFiles[selectedFilename]; - const previousFile = previousFiles && previousFiles[selectedFilename]; + const previousFile = baseFiles && baseFiles[selectedFilename]; return (
@@ -61,9 +64,9 @@ export const TemplateFiles: FC = ({ const extension = getExtension(filename) as AllowedExtension; const icon = iconByExtension[extension]; const hasDiff = - previousFiles && - previousFiles[filename] && - currentFiles[filename] !== previousFiles[filename]; + baseFiles && + baseFiles[filename] && + currentFiles[filename] !== baseFiles[filename]; return (
); }; + +export const useFileTab = (templateFiles: TemplateVersionFiles | undefined) => { + // Tabs The default tab is the tab that has main.tf but until we loads the + // files and check if main.tf exists we don't know which tab is the default + // one so we just use empty string + const tab = useTab("file", ""); + const isLoaded = tab.value !== ""; + useEffect(() => { + if (templateFiles && !isLoaded) { + const terraformFileIndex = Object.keys(templateFiles).indexOf("main.tf"); + // If main.tf exists use the index if not just use the first tab + tab.set(terraformFileIndex !== -1 ? terraformFileIndex.toString() : "0"); + } + }, [isLoaded, tab, templateFiles]); + + return { + ...tab, + isLoaded, + }; +}; + const styles = { tabs: (theme) => ({ display: "flex", diff --git a/site/src/components/TemplateFiles/hooks.ts b/site/src/components/TemplateFiles/hooks.ts deleted file mode 100644 index 6d21538f8c3db..0000000000000 --- a/site/src/components/TemplateFiles/hooks.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { TemplateVersion } from "api/typesGenerated"; -import { useTab } from "hooks/useTab"; -import { useEffect } from "react"; -import { useQuery } from "react-query"; -import { - TemplateVersionFiles, - getTemplateVersionFiles, -} from "utils/templateVersion"; -import * as API from "api/api"; - -export const useFileTab = (templateFiles: TemplateVersionFiles | undefined) => { - // Tabs The default tab is the tab that has main.tf but until we loads the - // files and check if main.tf exists we don't know which tab is the default - // one so we just use empty string - const tab = useTab("file", ""); - const isLoaded = tab.value !== ""; - useEffect(() => { - if (templateFiles && !isLoaded) { - const terraformFileIndex = Object.keys(templateFiles).indexOf("main.tf"); - // If main.tf exists use the index if not just use the first tab - tab.set(terraformFileIndex !== -1 ? terraformFileIndex.toString() : "0"); - } - }, [isLoaded, tab, templateFiles]); - - return { - ...tab, - isLoaded, - }; -}; - -export const useTemplateFiles = ( - templateName: string, - version: TemplateVersion | undefined, -) => { - return useQuery({ - queryKey: ["templateFiles", templateName, version], - queryFn: () => { - if (!version) { - return; - } - return getTemplateFilesWithDiff(templateName, version); - }, - enabled: version !== undefined, - }); -}; - -const getTemplateFilesWithDiff = async ( - templateName: string, - version: TemplateVersion, -) => { - const previousVersion = await API.getPreviousTemplateVersionByName( - version.organization_id!, - templateName, - version.name, - ); - const loadFilesPromises: ReturnType[] = []; - loadFilesPromises.push(getTemplateVersionFiles(version.job.file_id)); - if (previousVersion) { - loadFilesPromises.push( - getTemplateVersionFiles(previousVersion.job.file_id), - ); - } - const [currentFiles, previousFiles] = await Promise.all(loadFilesPromises); - return { - currentFiles, - previousFiles, - }; -}; diff --git a/site/src/pages/TemplatePage/TemplateFilesPage/TemplateFilesPage.tsx b/site/src/pages/TemplatePage/TemplateFilesPage/TemplateFilesPage.tsx index 0c8b53c6d53f9..6ddeb3b69b753 100644 --- a/site/src/pages/TemplatePage/TemplateFilesPage/TemplateFilesPage.tsx +++ b/site/src/pages/TemplatePage/TemplateFilesPage/TemplateFilesPage.tsx @@ -1,18 +1,30 @@ import { type FC } from "react"; import { Helmet } from "react-helmet-async"; import { Loader } from "components/Loader/Loader"; -import { TemplateFiles } from "components/TemplateFiles/TemplateFiles"; -import { useFileTab, useTemplateFiles } from "components/TemplateFiles/hooks"; +import { + TemplateFiles, + useFileTab, +} from "components/TemplateFiles/TemplateFiles"; import { useTemplateLayoutContext } from "pages/TemplatePage/TemplateLayout"; import { getTemplatePageTitle } from "../utils"; +import { useQuery } from "react-query"; +import { previousTemplateVersion, templateFiles } from "api/queries/templates"; +import { useOrganizationId } from "hooks"; const TemplateFilesPage: FC = () => { + const orgId = useOrganizationId(); const { template, activeVersion } = useTemplateLayoutContext(); - const { data: templateFiles } = useTemplateFiles( - template.name, - activeVersion, + const { data: currentFiles } = useQuery( + templateFiles(activeVersion.job.file_id), ); - const tab = useFileTab(templateFiles?.currentFiles); + const { data: previousTemplate } = useQuery( + previousTemplateVersion(orgId, template.name, activeVersion.name), + ); + const { data: previousFiles } = useQuery({ + ...templateFiles(previousTemplate?.job.file_id ?? ""), + enabled: Boolean(previousTemplate), + }); + const tab = useFileTab(currentFiles); return ( <> @@ -20,10 +32,10 @@ const TemplateFilesPage: FC = () => { {getTemplatePageTitle("Source Code", template)} - {templateFiles && tab.isLoaded ? ( + {previousFiles && currentFiles && tab.isLoaded ? ( ) : ( diff --git a/site/src/pages/TemplateVersionPage/TemplateVersionPage.tsx b/site/src/pages/TemplateVersionPage/TemplateVersionPage.tsx index 491e57a576571..fdc9f20afd4e2 100644 --- a/site/src/pages/TemplateVersionPage/TemplateVersionPage.tsx +++ b/site/src/pages/TemplateVersionPage/TemplateVersionPage.tsx @@ -6,8 +6,13 @@ import { useParams } from "react-router-dom"; import { pageTitle } from "utils/page"; import TemplateVersionPageView from "./TemplateVersionPageView"; import { useQuery } from "react-query"; -import { templateVersionByName } from "api/queries/templates"; -import { useFileTab, useTemplateFiles } from "components/TemplateFiles/hooks"; +import { + templateByName, + templateFiles, + templateVersion, + templateVersionByName, +} from "api/queries/templates"; +import { useFileTab } from "components/TemplateFiles/TemplateFiles"; type Params = { version: string; @@ -18,16 +23,30 @@ export const TemplateVersionPage: FC = () => { const { version: versionName, template: templateName } = useParams() as Params; const orgId = useOrganizationId(); - const templateVersionQuery = useQuery( + + /** + * Template version files + */ + const templateQuery = useQuery(templateByName(orgId, templateName)); + const selectedVersionQuery = useQuery( templateVersionByName(orgId, templateName, versionName), ); - const { data: templateFiles, error: templateFilesError } = useTemplateFiles( - templateName, - templateVersionQuery.data, - ); - const tab = useFileTab(templateFiles?.currentFiles); + const selectedVersionFilesQuery = useQuery({ + ...templateFiles(selectedVersionQuery.data?.job.file_id ?? ""), + enabled: Boolean(selectedVersionQuery.data), + }); + const activeVersionQuery = useQuery({ + ...templateVersion(templateQuery.data?.active_version_id ?? ""), + enabled: Boolean(templateQuery.data), + }); + const activeVersionFilesQuery = useQuery({ + ...templateFiles(activeVersionQuery.data?.job.file_id ?? ""), + enabled: Boolean(activeVersionQuery.data), + }); + const tab = useFileTab(selectedVersionFilesQuery.data); + const permissions = usePermissions(); - const versionId = templateVersionQuery.data?.id; + const versionId = selectedVersionQuery.data?.id; const createWorkspaceUrl = useMemo(() => { const params = new URLSearchParams(); if (versionId) { @@ -44,10 +63,16 @@ export const TemplateVersionPage: FC = () => { = ({ @@ -38,7 +38,7 @@ export const TemplateVersionPageView: FC = ({ createWorkspaceUrl, currentVersion, currentFiles, - previousFiles, + baseFiles, error, }) => { return ( @@ -103,7 +103,7 @@ export const TemplateVersionPageView: FC = ({ )} diff --git a/site/src/utils/templateVersion.ts b/site/src/utils/templateVersion.ts index 00bb3c6562a4e..153f46b432d53 100644 --- a/site/src/utils/templateVersion.ts +++ b/site/src/utils/templateVersion.ts @@ -1,4 +1,3 @@ -import * as API from "api/api"; import { FileTree, createFile } from "./filetree"; import { TarReader } from "./tar"; @@ -6,10 +5,9 @@ import { TarReader } from "./tar"; export type TemplateVersionFiles = Record; export const getTemplateVersionFiles = async ( - fileId: string, + tarFile: ArrayBuffer, ): Promise => { const files: TemplateVersionFiles = {}; - const tarFile = await API.getFile(fileId); const tarReader = new TarReader(); await tarReader.readFile(tarFile); for (const file of tarReader.fileInfo) {