refactor: clean up workspace and template settings (#9654)

This commit is contained in:
Kayla Washburn
2023-09-13 11:55:27 -06:00
committed by GitHub
parent 6c409b8872
commit 53a985ff11
13 changed files with 153 additions and 123 deletions

View File

@ -1,4 +1,38 @@
import * as API from "api/api";
import { type Template, type AuthorizationResponse } from "api/typesGenerated";
import { type QueryOptions } from "@tanstack/react-query";
export const templateByNameKey = (orgId: string, name: string) => [
orgId,
"template",
name,
"settings",
];
export const templateByName = (
orgId: string,
name: string,
): QueryOptions<{ template: Template; permissions: AuthorizationResponse }> => {
return {
queryKey: templateByNameKey(orgId, name),
queryFn: async () => {
const template = await API.getTemplateByName(orgId, name);
const permissions = await API.checkAuthorization({
checks: {
canUpdateTemplate: {
object: {
resource_type: "template",
resource_id: template.id,
},
action: "update",
},
},
});
return { template, permissions };
},
};
};
const getTemplatesQueryKey = (orgId: string) => [orgId, "templates"];

View File

@ -0,0 +1,20 @@
import * as API from "api/api";
import { type Workspace } from "api/typesGenerated";
import { type QueryOptions } from "@tanstack/react-query";
export const workspaceByOwnerAndNameKey = (owner: string, name: string) => [
"workspace",
owner,
name,
"settings",
];
export const workspaceByOwnerAndName = (
owner: string,
name: string,
): QueryOptions<Workspace> => {
return {
queryKey: workspaceByOwnerAndNameKey(owner, name),
queryFn: () => API.getWorkspaceByOwnerAndName(owner, name),
};
};

View File

@ -6,16 +6,16 @@ import { FC } from "react";
import { Helmet } from "react-helmet-async";
import { useNavigate, useParams } from "react-router-dom";
import { pageTitle } from "utils/page";
import {
getTemplateQuery,
useTemplateSettingsContext,
} from "../TemplateSettingsLayout";
import { useTemplateSettings } from "../TemplateSettingsLayout";
import { TemplateSettingsPageView } from "./TemplateSettingsPageView";
import { templateByNameKey } from "api/queries/templates";
import { useOrganizationId } from "hooks";
export const TemplateSettingsPage: FC = () => {
const { template: templateName } = useParams() as { template: string };
const navigate = useNavigate();
const { template } = useTemplateSettingsContext();
const orgId = useOrganizationId();
const { template } = useTemplateSettings();
const queryClient = useQueryClient();
const {
mutate: updateTemplate,
@ -25,9 +25,9 @@ export const TemplateSettingsPage: FC = () => {
(data: UpdateTemplateMeta) => updateTemplateMeta(template.id, data),
{
onSuccess: async () => {
await queryClient.invalidateQueries({
queryKey: getTemplateQuery(templateName),
});
await queryClient.invalidateQueries(
templateByNameKey(orgId, templateName),
);
displaySuccess("Template updated successfully");
},
},

View File

@ -11,7 +11,7 @@ import { FC } from "react";
import { Helmet } from "react-helmet-async";
import { pageTitle } from "utils/page";
import { templateACLMachine } from "xServices/template/templateACLXService";
import { useTemplateSettingsContext } from "../TemplateSettingsLayout";
import { useTemplateSettings } from "../TemplateSettingsLayout";
import { TemplatePermissionsPageView } from "./TemplatePermissionsPageView";
import { docs } from "utils/docs";
@ -19,7 +19,7 @@ export const TemplatePermissionsPage: FC<
React.PropsWithChildren<unknown>
> = () => {
const organizationId = useOrganizationId();
const { template, permissions } = useTemplateSettingsContext();
const { template, permissions } = useTemplateSettings();
const { template_rbac: isTemplateRBACEnabled } = useFeatureVisibility();
const [state, send] = useMachine(templateACLMachine, {
context: { templateId: template.id },

View File

@ -138,6 +138,7 @@ export const TemplateScheduleForm: FC<TemplateScheduleForm> = ({
}
},
initialTouched,
enableReinitialize: true,
});
const getFieldHelpers = getFormHelpers<TemplateScheduleFormValues>(

View File

@ -1,4 +1,4 @@
import { useMutation } from "@tanstack/react-query";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { updateTemplateMeta } from "api/api";
import { UpdateTemplateMeta } from "api/typesGenerated";
import { useDashboard } from "components/Dashboard/DashboardProvider";
@ -7,14 +7,17 @@ import { FC } from "react";
import { Helmet } from "react-helmet-async";
import { useNavigate, useParams } from "react-router-dom";
import { pageTitle } from "utils/page";
import { useTemplateSettingsContext } from "../TemplateSettingsLayout";
import { useTemplateSettings } from "../TemplateSettingsLayout";
import { TemplateSchedulePageView } from "./TemplateSchedulePageView";
import { useLocalStorage } from "hooks";
import { useLocalStorage, useOrganizationId } from "hooks";
import { templateByNameKey } from "api/queries/templates";
const TemplateSchedulePage: FC = () => {
const { template: templateName } = useParams() as { template: string };
const navigate = useNavigate();
const { template } = useTemplateSettingsContext();
const queryClient = useQueryClient();
const orgId = useOrganizationId();
const { template } = useTemplateSettings();
const { entitlements, experiments } = useDashboard();
const allowAdvancedScheduling =
entitlements.features["advanced_template_scheduling"].enabled;
@ -33,7 +36,10 @@ const TemplateSchedulePage: FC = () => {
} = useMutation(
(data: UpdateTemplateMeta) => updateTemplateMeta(template.id, data),
{
onSuccess: () => {
onSuccess: async () => {
await queryClient.invalidateQueries(
templateByNameKey(orgId, templateName),
);
displaySuccess("Template updated successfully");
// clear browser storage of workspaces impending deletion
clearLocal("dismissedWorkspaceList"); // workspaces page

View File

@ -7,68 +7,36 @@ import { pageTitle } from "../../utils/page";
import { Loader } from "components/Loader/Loader";
import { Outlet, useParams } from "react-router-dom";
import { Margins } from "components/Margins/Margins";
import { checkAuthorization, getTemplateByName } from "api/api";
import { useQuery } from "@tanstack/react-query";
import { useOrganizationId } from "hooks/useOrganizationId";
import { templateByName } from "api/queries/templates";
import { type AuthorizationResponse, type Template } from "api/typesGenerated";
import { ErrorAlert } from "components/Alert/ErrorAlert";
const templatePermissions = (templateId: string) =>
({
canUpdateTemplate: {
object: {
resource_type: "template",
resource_id: templateId,
},
action: "update",
},
}) as const;
const fetchTemplateSettings = async (orgId: string, name: string) => {
const template = await getTemplateByName(orgId, name);
const permissions = await checkAuthorization({
checks: templatePermissions(template.id),
});
return {
template,
permissions,
};
};
export const getTemplateQuery = (name: string) => [
"template",
name,
"settings",
];
const useTemplate = (orgId: string, name: string) => {
return useQuery({
queryKey: getTemplateQuery(name),
queryFn: () => fetchTemplateSettings(orgId, name),
keepPreviousData: true,
});
};
const TemplateSettingsContext = createContext<
Awaited<ReturnType<typeof fetchTemplateSettings>> | undefined
const TemplateSettings = createContext<
{ template: Template; permissions: AuthorizationResponse } | undefined
>(undefined);
export const useTemplateSettingsContext = () => {
const context = useContext(TemplateSettingsContext);
if (!context) {
throw new Error(
"useTemplateSettingsContext must be used within a TemplateSettingsContext.Provider",
);
export function useTemplateSettings() {
const value = useContext(TemplateSettings);
if (!value) {
throw new Error("This hook can only be used from a template settings page");
}
return context;
};
return value;
}
export const TemplateSettingsLayout: FC = () => {
const styles = useStyles();
const orgId = useOrganizationId();
const { template: templateName } = useParams() as { template: string };
const { data: settings } = useTemplate(orgId, templateName);
const { data, error, isLoading, isError } = useQuery(
templateByName(orgId, templateName),
);
if (isLoading) {
return <Loader />;
}
return (
<>
@ -76,22 +44,22 @@ export const TemplateSettingsLayout: FC = () => {
<title>{pageTitle([templateName, "Settings"])}</title>
</Helmet>
{settings ? (
<TemplateSettingsContext.Provider value={settings}>
<Margins>
<Stack className={styles.wrapper} direction="row" spacing={10}>
<Sidebar template={settings.template} />
<Margins>
<Stack className={styles.wrapper} direction="row" spacing={10}>
{isError ? (
<ErrorAlert error={error} />
) : (
<TemplateSettings.Provider value={data}>
<Sidebar template={data.template} />
<Suspense fallback={<Loader />}>
<main className={styles.content}>
<Outlet />
</main>
</Suspense>
</Stack>
</Margins>
</TemplateSettingsContext.Provider>
) : (
<Loader />
)}
</TemplateSettings.Provider>
)}
</Stack>
</Margins>
</>
);
};

View File

@ -11,7 +11,7 @@ import { Helmet } from "react-helmet-async";
import { useNavigate, useParams } from "react-router-dom";
import { templateVariablesMachine } from "xServices/template/templateVariablesXService";
import { pageTitle } from "../../../utils/page";
import { useTemplateSettingsContext } from "../TemplateSettingsLayout";
import { useTemplateSettings } from "../TemplateSettingsLayout";
import { TemplateVariablesPageView } from "./TemplateVariablesPageView";
export const TemplateVariablesPage: FC = () => {
@ -20,7 +20,7 @@ export const TemplateVariablesPage: FC = () => {
template: string;
};
const organizationId = useOrganizationId();
const { template } = useTemplateSettingsContext();
const { template } = useTemplateSettings();
const navigate = useNavigate();
const [state, send] = useMachine(templateVariablesMachine, {
context: {

View File

@ -1,7 +1,7 @@
import { getWorkspaceParameters, postWorkspaceBuild } from "api/api";
import { Helmet } from "react-helmet-async";
import { pageTitle } from "utils/page";
import { useWorkspaceSettingsContext } from "../WorkspaceSettingsLayout";
import { useWorkspaceSettings } from "../WorkspaceSettingsLayout";
import { useMutation, useQuery } from "@tanstack/react-query";
import { Loader } from "components/Loader/Loader";
import {
@ -17,7 +17,7 @@ import { ErrorAlert } from "components/Alert/ErrorAlert";
import { WorkspaceBuildParameter } from "api/typesGenerated";
const WorkspaceParametersPage = () => {
const { workspace } = useWorkspaceSettingsContext();
const workspace = useWorkspaceSettings();
const parameters = useQuery({
queryKey: ["workspace", workspace.id, "parameters"],
queryFn: () => getWorkspaceParameters(workspace),

View File

@ -201,6 +201,7 @@ export const WorkspaceScheduleForm: FC<
onSubmit,
validationSchema,
initialTouched,
enableReinitialize: true,
});
const formHelpers = getFormHelpers<WorkspaceScheduleFormValues>(
form,

View File

@ -10,12 +10,13 @@ import {
scheduleChanged,
} from "pages/WorkspaceSettingsPage/WorkspaceSchedulePage/schedule";
import { ttlMsToAutostop } from "pages/WorkspaceSettingsPage/WorkspaceSchedulePage/ttl";
import { useWorkspaceSettingsContext } from "pages/WorkspaceSettingsPage/WorkspaceSettingsLayout";
import { useWorkspaceSettings } from "pages/WorkspaceSettingsPage/WorkspaceSettingsLayout";
import { FC } from "react";
import { Helmet } from "react-helmet-async";
import { Navigate, useNavigate, useParams } from "react-router-dom";
import { pageTitle } from "utils/page";
import * as TypesGen from "api/typesGenerated";
import { workspaceByOwnerAndNameKey } from "api/queries/workspace";
import { WorkspaceScheduleForm } from "./WorkspaceScheduleForm";
import { workspaceSchedule } from "xServices/workspaceSchedule/workspaceScheduleXService";
import {
@ -23,6 +24,7 @@ import {
formValuesToTTLRequest,
} from "./formToRequest";
import { ErrorAlert } from "components/Alert/ErrorAlert";
import { useQueryClient } from "@tanstack/react-query";
const getAutostart = (workspace: TypesGen.Workspace) =>
scheduleToAutostart(workspace.autostart_schedule);
@ -44,7 +46,8 @@ export const WorkspaceSchedulePage: FC = () => {
const navigate = useNavigate();
const username = params.username.replace("@", "");
const workspaceName = params.workspace;
const { workspace } = useWorkspaceSettingsContext();
const queryClient = useQueryClient();
const workspace = useWorkspaceSettings();
const [scheduleState, scheduleSend] = useMachine(workspaceSchedule, {
context: { workspace },
});
@ -97,7 +100,7 @@ export const WorkspaceSchedulePage: FC = () => {
onCancel={() => {
navigate(`/@${username}/${workspaceName}`);
}}
onSubmit={(values) => {
onSubmit={async (values) => {
scheduleSend({
type: "SUBMIT_SCHEDULE",
autostart: formValuesToAutostartRequest(values),
@ -111,6 +114,10 @@ export const WorkspaceSchedulePage: FC = () => {
values,
),
});
await queryClient.invalidateQueries(
workspaceByOwnerAndNameKey(params.username, params.workspace),
);
}}
/>
)}

View File

@ -7,39 +7,23 @@ import { pageTitle } from "../../utils/page";
import { Loader } from "components/Loader/Loader";
import { Outlet, useParams } from "react-router-dom";
import { Margins } from "components/Margins/Margins";
import { getWorkspaceByOwnerAndName } from "api/api";
import { workspaceByOwnerAndName } from "api/queries/workspace";
import { useQuery } from "@tanstack/react-query";
import { ErrorAlert } from "components/Alert/ErrorAlert";
import { type Workspace } from "api/typesGenerated";
const fetchWorkspaceSettings = async (owner: string, name: string) => {
const workspace = await getWorkspaceByOwnerAndName(owner, name);
const WorkspaceSettings = createContext<Workspace | undefined>(undefined);
return {
workspace,
};
};
const useWorkspace = (owner: string, name: string) => {
return useQuery({
queryKey: ["workspace", name, "settings"],
queryFn: () => fetchWorkspaceSettings(owner, name),
});
};
const WorkspaceSettingsContext = createContext<
Awaited<ReturnType<typeof fetchWorkspaceSettings>> | undefined
>(undefined);
export const useWorkspaceSettingsContext = () => {
const context = useContext(WorkspaceSettingsContext);
if (!context) {
export function useWorkspaceSettings() {
const value = useContext(WorkspaceSettings);
if (!value) {
throw new Error(
"useWorkspaceSettingsContext must be used within a WorkspaceSettingsContext.Provider",
"This hook can only be used from a workspace settings page",
);
}
return context;
};
return value;
}
export const WorkspaceSettingsLayout: FC = () => {
const styles = useStyles();
@ -49,7 +33,16 @@ export const WorkspaceSettingsLayout: FC = () => {
};
const workspaceName = params.workspace;
const username = params.username.replace("@", "");
const { data: settings } = useWorkspace(username, workspaceName);
const {
data: workspace,
error,
isLoading,
isError,
} = useQuery(workspaceByOwnerAndName(username, workspaceName));
if (isLoading) {
return <Loader />;
}
return (
<>
@ -57,22 +50,22 @@ export const WorkspaceSettingsLayout: FC = () => {
<title>{pageTitle([workspaceName, "Settings"])}</title>
</Helmet>
{settings ? (
<WorkspaceSettingsContext.Provider value={settings}>
<Margins>
<Stack className={styles.wrapper} direction="row" spacing={10}>
<Sidebar workspace={settings.workspace} username={username} />
<Margins>
<Stack className={styles.wrapper} direction="row" spacing={10}>
{isError ? (
<ErrorAlert error={error} />
) : (
<WorkspaceSettings.Provider value={workspace}>
<Sidebar workspace={workspace} username={username} />
<Suspense fallback={<Loader />}>
<main className={styles.content}>
<Outlet />
</main>
</Suspense>
</Stack>
</Margins>
</WorkspaceSettingsContext.Provider>
) : (
<Loader />
)}
</WorkspaceSettings.Provider>
)}
</Stack>
</Margins>
</>
);
};

View File

@ -1,7 +1,7 @@
import { Helmet } from "react-helmet-async";
import { useNavigate, useParams } from "react-router-dom";
import { pageTitle } from "utils/page";
import { useWorkspaceSettingsContext } from "./WorkspaceSettingsLayout";
import { useWorkspaceSettings } from "./WorkspaceSettingsLayout";
import { WorkspaceSettingsPageView } from "./WorkspaceSettingsPageView";
import { useMutation } from "@tanstack/react-query";
import { displaySuccess } from "components/GlobalSnackbar/utils";
@ -15,7 +15,7 @@ const WorkspaceSettingsPage = () => {
};
const workspaceName = params.workspace;
const username = params.username.replace("@", "");
const { workspace } = useWorkspaceSettingsContext();
const workspace = useWorkspaceSettings();
const navigate = useNavigate();
const mutation = useMutation({
mutationFn: (formValues: WorkspaceSettingsFormValues) =>