feat(site): show version files diff based on active version (#11686)

This commit is contained in:
Bruno Quaresma
2024-01-18 16:08:17 -03:00
committed by GitHub
parent 6bb1a34a37
commit 156aaba335
9 changed files with 130 additions and 106 deletions
+33
View File
@@ -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;
@@ -18,7 +18,7 @@ const meta: Meta<typeof TemplateFiles> = {
component: TemplateFiles,
args: {
currentFiles: exampleFiles,
previousFiles: exampleFiles,
baseFiles: exampleFiles,
tab: { value: "0", set: action("change tab") },
},
};
@@ -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<AllowedExtension, string> = {
interface TemplateFilesProps {
currentFiles: TemplateVersionFiles;
previousFiles?: TemplateVersionFiles;
/**
* Files used to compare with current files
*/
baseFiles?: TemplateVersionFiles;
tab: UseTabResult;
}
export const TemplateFiles: FC<TemplateFilesProps> = ({
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 (
<div css={styles.files}>
@@ -61,9 +64,9 @@ export const TemplateFiles: FC<TemplateFilesProps> = ({
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 (
<button
@@ -93,6 +96,27 @@ export const TemplateFiles: FC<TemplateFilesProps> = ({
</div>
);
};
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",
@@ -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<typeof getTemplateVersionFiles>[] = [];
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,
};
};
@@ -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 = () => {
<title>{getTemplatePageTitle("Source Code", template)}</title>
</Helmet>
{templateFiles && tab.isLoaded ? (
{previousFiles && currentFiles && tab.isLoaded ? (
<TemplateFiles
currentFiles={templateFiles.currentFiles}
previousFiles={templateFiles.previousFiles}
currentFiles={currentFiles}
baseFiles={previousFiles}
tab={tab}
/>
) : (
@@ -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 = () => {
</Helmet>
<TemplateVersionPageView
error={templateVersionQuery.error || templateFilesError}
currentVersion={templateVersionQuery.data}
currentFiles={templateFiles?.currentFiles}
previousFiles={templateFiles?.previousFiles}
error={
templateQuery.error ||
selectedVersionQuery.error ||
selectedVersionFilesQuery.error ||
activeVersionQuery.error ||
activeVersionFilesQuery.error
}
currentVersion={selectedVersionQuery.data}
currentFiles={selectedVersionFilesQuery.data}
baseFiles={activeVersionFilesQuery.data}
versionName={versionName}
templateName={templateName}
tab={tab}
@@ -38,7 +38,7 @@ const defaultArgs: TemplateVersionPageViewProps = {
"some.tpl": `{{.Name}}`,
"some.sh": `echo "Hello world"`,
},
previousFiles: undefined,
baseFiles: undefined,
error: undefined,
};
@@ -28,7 +28,7 @@ export interface TemplateVersionPageViewProps {
error: unknown;
currentVersion: TemplateVersion | undefined;
currentFiles: TemplateVersionFiles | undefined;
previousFiles: TemplateVersionFiles | undefined;
baseFiles: TemplateVersionFiles | undefined;
}
export const TemplateVersionPageView: FC<TemplateVersionPageViewProps> = ({
@@ -38,7 +38,7 @@ export const TemplateVersionPageView: FC<TemplateVersionPageViewProps> = ({
createWorkspaceUrl,
currentVersion,
currentFiles,
previousFiles,
baseFiles,
error,
}) => {
return (
@@ -103,7 +103,7 @@ export const TemplateVersionPageView: FC<TemplateVersionPageViewProps> = ({
<TemplateFiles
tab={tab}
currentFiles={currentFiles}
previousFiles={previousFiles}
baseFiles={baseFiles}
/>
</>
)}
+1 -3
View File
@@ -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<string, string>;
export const getTemplateVersionFiles = async (
fileId: string,
tarFile: ArrayBuffer,
): Promise<TemplateVersionFiles> => {
const files: TemplateVersionFiles = {};
const tarFile = await API.getFile(fileId);
const tarReader = new TarReader();
await tarReader.readFile(tarFile);
for (const file of tarReader.fileInfo) {