chore: remove global organization id state (#14135)

This commit is contained in:
Kayla Washburn-Love
2024-08-05 10:33:58 -06:00
committed by GitHub
parent f9b660e573
commit efbd6257e4
29 changed files with 177 additions and 151 deletions

View File

@ -300,10 +300,20 @@ const BASE_CONTENT_TYPE_JSON = {
"Content-Type": "application/json", "Content-Type": "application/json",
} as const satisfies HeadersInit; } as const satisfies HeadersInit;
type TemplateOptions = Readonly<{ export type GetTemplatesOptions = Readonly<{
readonly deprecated?: boolean; readonly deprecated?: boolean;
}>; }>;
function normalizeGetTemplatesOptions(
options: GetTemplatesOptions = {},
): Record<string, string> {
const params: Record<string, string> = {};
if (options.deprecated !== undefined) {
params["deprecated"] = String(options.deprecated);
}
return params;
}
type SearchParamOptions = TypesGen.Pagination & { type SearchParamOptions = TypesGen.Pagination & {
q?: string; q?: string;
}; };
@ -625,21 +635,26 @@ class ApiMethods {
return response.data; return response.data;
}; };
getTemplates = async (
options?: GetTemplatesOptions,
): Promise<TypesGen.Template[]> => {
const params = normalizeGetTemplatesOptions(options);
const response = await this.axios.get<TypesGen.Template[]>(
`/api/v2/templates`,
{ params },
);
return response.data;
};
/** /**
* @param organization Can be the organization's ID or name * @param organization Can be the organization's ID or name
*/ */
getTemplates = async ( getTemplatesByOrganization = async (
organization: string, organization: string,
options?: TemplateOptions, options?: GetTemplatesOptions,
): Promise<TypesGen.Template[]> => { ): Promise<TypesGen.Template[]> => {
const params: Record<string, string> = {}; const params = normalizeGetTemplatesOptions(options);
if (options?.deprecated !== undefined) {
// Just want to check if it isn't undefined. If it has
// a boolean value, convert it to a string and include
// it as a param.
params["deprecated"] = String(options.deprecated);
}
const response = await this.axios.get<TypesGen.Template[]>( const response = await this.axios.get<TypesGen.Template[]>(
`/api/v2/organizations/${organization}/templates`, `/api/v2/organizations/${organization}/templates`,
{ params }, { params },

View File

@ -1,5 +1,5 @@
import type { MutationOptions, QueryClient, QueryOptions } from "react-query"; import type { MutationOptions, QueryClient, QueryOptions } from "react-query";
import { API } from "api/api"; import { API, type GetTemplatesOptions } from "api/api";
import type { import type {
CreateTemplateRequest, CreateTemplateRequest,
CreateTemplateVersionRequest, CreateTemplateVersionRequest,
@ -38,16 +38,30 @@ export const templateByName = (
}; };
}; };
const getTemplatesQueryKey = (organizationId: string, deprecated?: boolean) => [ const getTemplatesQueryKey = (options?: GetTemplatesOptions) => [
organizationId,
"templates", "templates",
deprecated, options?.deprecated,
]; ];
export const templates = (organizationId: string, deprecated?: boolean) => { export const templates = (options?: GetTemplatesOptions) => {
return { return {
queryKey: getTemplatesQueryKey(organizationId, deprecated), queryKey: getTemplatesQueryKey(options),
queryFn: () => API.getTemplates(organizationId, { deprecated }), queryFn: () => API.getTemplates(options),
};
};
const getTemplatesByOrganizationQueryKey = (
organization: string,
options?: GetTemplatesOptions,
) => [organization, "templates", options?.deprecated];
export const templatesByOrganization = (
organization: string,
options: GetTemplatesOptions = {},
) => {
return {
queryKey: getTemplatesByOrganizationQueryKey(organization, options),
queryFn: () => API.getTemplatesByOrganization(organization, options),
}; };
}; };
@ -100,7 +114,10 @@ export const setGroupRole = (
export const templateExamples = (organizationId: string) => { export const templateExamples = (organizationId: string) => {
return { return {
queryKey: [...getTemplatesQueryKey(organizationId), "examples"], queryKey: [
...getTemplatesByOrganizationQueryKey(organizationId),
"examples",
],
queryFn: () => API.getTemplateExamples(organizationId), queryFn: () => API.getTemplateExamples(organizationId),
}; };
}; };

View File

@ -3,23 +3,22 @@ import { useQuery } from "react-query";
import { appearance } from "api/queries/appearance"; import { appearance } from "api/queries/appearance";
import { entitlements } from "api/queries/entitlements"; import { entitlements } from "api/queries/entitlements";
import { experiments } from "api/queries/experiments"; import { experiments } from "api/queries/experiments";
import { organizations } from "api/queries/organizations";
import type { import type {
AppearanceConfig, AppearanceConfig,
Entitlements, Entitlements,
Experiments, Experiments,
Organization,
} from "api/typesGenerated"; } from "api/typesGenerated";
import { ErrorAlert } from "components/Alert/ErrorAlert";
import { Loader } from "components/Loader/Loader"; import { Loader } from "components/Loader/Loader";
import { useEmbeddedMetadata } from "hooks/useEmbeddedMetadata"; import { useEmbeddedMetadata } from "hooks/useEmbeddedMetadata";
export interface DashboardValue { export interface DashboardValue {
/**
* @deprecated Do not add new usage of this value. It is being removed as part
* of the multi-org work.
*/
organizationId: string;
entitlements: Entitlements; entitlements: Entitlements;
experiments: Experiments; experiments: Experiments;
appearance: AppearanceConfig; appearance: AppearanceConfig;
organizations: Organization[];
} }
export const DashboardContext = createContext<DashboardValue | undefined>( export const DashboardContext = createContext<DashboardValue | undefined>(
@ -31,9 +30,23 @@ export const DashboardProvider: FC<PropsWithChildren> = ({ children }) => {
const entitlementsQuery = useQuery(entitlements(metadata.entitlements)); const entitlementsQuery = useQuery(entitlements(metadata.entitlements));
const experimentsQuery = useQuery(experiments(metadata.experiments)); const experimentsQuery = useQuery(experiments(metadata.experiments));
const appearanceQuery = useQuery(appearance(metadata.appearance)); const appearanceQuery = useQuery(appearance(metadata.appearance));
const organizationsQuery = useQuery(organizations());
const error =
entitlementsQuery.error ||
appearanceQuery.error ||
experimentsQuery.error ||
organizationsQuery.error;
if (error) {
return <ErrorAlert error={error} />;
}
const isLoading = const isLoading =
!entitlementsQuery.data || !appearanceQuery.data || !experimentsQuery.data; !entitlementsQuery.data ||
!appearanceQuery.data ||
!experimentsQuery.data ||
!organizationsQuery.data;
if (isLoading) { if (isLoading) {
return <Loader fullscreen />; return <Loader fullscreen />;
@ -42,10 +55,10 @@ export const DashboardProvider: FC<PropsWithChildren> = ({ children }) => {
return ( return (
<DashboardContext.Provider <DashboardContext.Provider
value={{ value={{
organizationId: "00000000-0000-0000-0000-000000000000",
entitlements: entitlementsQuery.data, entitlements: entitlementsQuery.data,
experiments: experimentsQuery.data, experiments: experimentsQuery.data,
appearance: appearanceQuery.data, appearance: appearanceQuery.data,
organizations: organizationsQuery.data,
}} }}
> >
{children} {children}

View File

@ -27,8 +27,13 @@ export const linkToUsers = withFilter("/users", "status:active");
export const linkToTemplate = export const linkToTemplate =
(organizationName: string, templateName: string): LinkThunk => (organizationName: string, templateName: string): LinkThunk =>
(dashboard) => (dashboard) => {
dashboard.experiments.includes("multi-organization") && const hasMultipleOrganizations = dashboard.organizations.length > 1;
selectFeatureVisibility(dashboard.entitlements).multiple_organizations const organizationsEnabled =
dashboard.experiments.includes("multi-organization") &&
selectFeatureVisibility(dashboard.entitlements).multiple_organizations;
return hasMultipleOrganizations || organizationsEnabled
? `/templates/${organizationName}/${templateName}` ? `/templates/${organizationName}/${templateName}`
: `/templates/${templateName}`; : `/templates/${templateName}`;
};

View File

@ -233,13 +233,13 @@ export const CreateTemplateForm: FC<CreateTemplateFormProps> = (props) => {
{showOrganizationPicker && ( {showOrganizationPicker && (
<OrganizationAutocomplete <OrganizationAutocomplete
{...getFieldHelpers("organization_id")} {...getFieldHelpers("organization")}
required required
label="Belongs to" label="Belongs to"
value={selectedOrg} value={selectedOrg}
onChange={(newValue) => { onChange={(newValue) => {
setSelectedOrg(newValue); setSelectedOrg(newValue);
void form.setFieldValue("organization", newValue?.id || ""); void form.setFieldValue("organization", newValue?.name || "");
}} }}
size="medium" size="medium"
/> />

View File

@ -65,7 +65,6 @@ export interface CreateUserFormProps {
onCancel: () => void; onCancel: () => void;
error?: unknown; error?: unknown;
isLoading: boolean; isLoading: boolean;
organizationId: string;
authMethods?: TypesGen.AuthMethods; authMethods?: TypesGen.AuthMethods;
} }
@ -86,7 +85,7 @@ const validationSchema = Yup.object({
export const CreateUserForm: FC< export const CreateUserForm: FC<
React.PropsWithChildren<CreateUserFormProps> React.PropsWithChildren<CreateUserFormProps>
> = ({ onSubmit, onCancel, error, isLoading, organizationId, authMethods }) => { > = ({ onSubmit, onCancel, error, isLoading, authMethods }) => {
const form: FormikContextType<TypesGen.CreateUserRequest> = const form: FormikContextType<TypesGen.CreateUserRequest> =
useFormik<TypesGen.CreateUserRequest>({ useFormik<TypesGen.CreateUserRequest>({
initialValues: { initialValues: {
@ -94,7 +93,7 @@ export const CreateUserForm: FC<
password: "", password: "",
username: "", username: "",
name: "", name: "",
organization_id: organizationId, organization_id: "00000000-0000-0000-0000-000000000000",
disable_login: false, disable_login: false,
login_type: "", login_type: "",
}, },

View File

@ -5,7 +5,6 @@ import { useNavigate } from "react-router-dom";
import { authMethods, createUser } from "api/queries/users"; import { authMethods, createUser } from "api/queries/users";
import { displaySuccess } from "components/GlobalSnackbar/utils"; import { displaySuccess } from "components/GlobalSnackbar/utils";
import { Margins } from "components/Margins/Margins"; import { Margins } from "components/Margins/Margins";
import { useDashboard } from "modules/dashboard/useDashboard";
import { pageTitle } from "utils/page"; import { pageTitle } from "utils/page";
import { CreateUserForm } from "./CreateUserForm"; import { CreateUserForm } from "./CreateUserForm";
@ -14,7 +13,6 @@ export const Language = {
}; };
export const CreateUserPage: FC = () => { export const CreateUserPage: FC = () => {
const { organizationId } = useDashboard();
const navigate = useNavigate(); const navigate = useNavigate();
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const createUserMutation = useMutation(createUser(queryClient)); const createUserMutation = useMutation(createUser(queryClient));
@ -38,7 +36,6 @@ export const CreateUserPage: FC = () => {
navigate("..", { relative: "path" }); navigate("..", { relative: "path" });
}} }}
isLoading={createUserMutation.isLoading} isLoading={createUserMutation.isLoading}
organizationId={organizationId}
/> />
</Margins> </Margins>
); );

View File

@ -33,13 +33,12 @@ export type CreateWorkspaceMode = (typeof createWorkspaceModes)[number];
export type ExternalAuthPollingState = "idle" | "polling" | "abandoned"; export type ExternalAuthPollingState = "idle" | "polling" | "abandoned";
const CreateWorkspacePage: FC = () => { const CreateWorkspacePage: FC = () => {
const { template: templateName } = useParams() as { const { organization: organizationName = "default", template: templateName } =
template: string; useParams() as { organization?: string; template: string };
};
const { user: me } = useAuthenticated(); const { user: me } = useAuthenticated();
const navigate = useNavigate(); const navigate = useNavigate();
const [searchParams] = useSearchParams(); const [searchParams] = useSearchParams();
const { experiments, organizationId } = useDashboard(); const { experiments } = useDashboard();
const customVersionId = searchParams.get("version") ?? undefined; const customVersionId = searchParams.get("version") ?? undefined;
const defaultName = searchParams.get("name"); const defaultName = searchParams.get("name");
@ -54,15 +53,19 @@ const CreateWorkspacePage: FC = () => {
); );
const createWorkspaceMutation = useMutation(createWorkspace(queryClient)); const createWorkspaceMutation = useMutation(createWorkspace(queryClient));
const templateQuery = useQuery(templateByName(organizationId, templateName)); const templateQuery = useQuery(
templateByName(organizationName, templateName),
);
const permissionsQuery = useQuery( const permissionsQuery = useQuery(
checkAuthorization({ templateQuery.data
checks: createWorkspaceChecks(organizationId), ? checkAuthorization({
}), checks: createWorkspaceChecks(templateQuery.data.organization_id),
})
: { enabled: false },
); );
const realizedVersionId = const realizedVersionId =
customVersionId ?? templateQuery.data?.active_version_id; customVersionId ?? templateQuery.data?.active_version_id;
const organizationId = templateQuery.data?.organization_id;
const richParametersQuery = useQuery({ const richParametersQuery = useQuery({
...richParameters(realizedVersionId ?? ""), ...richParameters(realizedVersionId ?? ""),
enabled: realizedVersionId !== undefined, enabled: realizedVersionId !== undefined,
@ -110,7 +113,7 @@ const CreateWorkspacePage: FC = () => {
const autoCreationStartedRef = useRef(false); const autoCreationStartedRef = useRef(false);
const automateWorkspaceCreation = useEffectEvent(async () => { const automateWorkspaceCreation = useEffectEvent(async () => {
if (autoCreationStartedRef.current) { if (autoCreationStartedRef.current || !organizationId) {
return; return;
} }

View File

@ -5,17 +5,15 @@ import { getErrorMessage } from "api/errors";
import { groups } from "api/queries/groups"; import { groups } from "api/queries/groups";
import { displayError } from "components/GlobalSnackbar/utils"; import { displayError } from "components/GlobalSnackbar/utils";
import { useAuthenticated } from "contexts/auth/RequireAuth"; import { useAuthenticated } from "contexts/auth/RequireAuth";
import { useDashboard } from "modules/dashboard/useDashboard";
import { useFeatureVisibility } from "modules/dashboard/useFeatureVisibility"; import { useFeatureVisibility } from "modules/dashboard/useFeatureVisibility";
import { pageTitle } from "utils/page"; import { pageTitle } from "utils/page";
import GroupsPageView from "./GroupsPageView"; import GroupsPageView from "./GroupsPageView";
export const GroupsPage: FC = () => { export const GroupsPage: FC = () => {
const { permissions } = useAuthenticated(); const { permissions } = useAuthenticated();
const { organizationId } = useDashboard();
const { createGroup: canCreateGroup } = permissions; const { createGroup: canCreateGroup } = permissions;
const { template_rbac: isTemplateRBACEnabled } = useFeatureVisibility(); const { template_rbac: isTemplateRBACEnabled } = useFeatureVisibility();
const groupsQuery = useQuery(groups(organizationId)); const groupsQuery = useQuery(groups("default"));
useEffect(() => { useEffect(() => {
if (groupsQuery.error) { if (groupsQuery.error) {

View File

@ -51,7 +51,7 @@ import { pageTitle } from "utils/page";
export const GroupPage: FC = () => { export const GroupPage: FC = () => {
const { organization = "default", groupName } = useParams() as { const { organization = "default", groupName } = useParams() as {
organization: string; organization?: string;
groupName: string; groupName: string;
}; };
const queryClient = useQueryClient(); const queryClient = useQueryClient();

View File

@ -12,7 +12,7 @@ import GroupSettingsPageView from "./GroupSettingsPageView";
export const GroupSettingsPage: FC = () => { export const GroupSettingsPage: FC = () => {
const { organization = "default", groupName } = useParams() as { const { organization = "default", groupName } = useParams() as {
organization: string; organization?: string;
groupName: string; groupName: string;
}; };
const queryClient = useQueryClient(); const queryClient = useQueryClient();

View File

@ -30,7 +30,7 @@ export const GroupsPage: FC = () => {
} = useFeatureVisibility(); } = useFeatureVisibility();
const { experiments } = useDashboard(); const { experiments } = useDashboard();
const location = useLocation(); const location = useLocation();
const { organization = "default" } = useParams() as { organization: string }; const { organization = "default" } = useParams() as { organization?: string };
const groupsQuery = useQuery(groups(organization)); const groupsQuery = useQuery(groups(organization));
const { organizations } = useOrganizationSettings(); const { organizations } = useOrganizationSettings();

View File

@ -1,8 +1,7 @@
import { createContext, type FC, Suspense, useContext } from "react"; import { type FC, Suspense } from "react";
import { useQuery } from "react-query"; import { useQuery } from "react-query";
import { Outlet } from "react-router-dom"; import { Outlet } from "react-router-dom";
import { deploymentConfig } from "api/queries/deployment"; import { deploymentConfig } from "api/queries/deployment";
import { organizations } from "api/queries/organizations";
import type { Organization } from "api/typesGenerated"; import type { Organization } from "api/typesGenerated";
import { Loader } from "components/Loader/Loader"; import { Loader } from "components/Loader/Loader";
import { Margins } from "components/Margins/Margins"; import { Margins } from "components/Margins/Margins";
@ -14,29 +13,17 @@ import NotFoundPage from "pages/404Page/404Page";
import { DeploySettingsContext } from "../DeploySettingsPage/DeploySettingsLayout"; import { DeploySettingsContext } from "../DeploySettingsPage/DeploySettingsLayout";
import { Sidebar } from "./Sidebar"; import { Sidebar } from "./Sidebar";
type OrganizationSettingsContextValue = { type OrganizationSettingsValue = { organizations: Organization[] };
organizations: Organization[];
};
const OrganizationSettingsContext = createContext< export const useOrganizationSettings = (): OrganizationSettingsValue => {
OrganizationSettingsContextValue | undefined const { organizations } = useDashboard();
>(undefined); return { organizations };
export const useOrganizationSettings = (): OrganizationSettingsContextValue => {
const context = useContext(OrganizationSettingsContext);
if (!context) {
throw new Error(
"useOrganizationSettings should be used inside of OrganizationSettingsLayout",
);
}
return context;
}; };
export const ManagementSettingsLayout: FC = () => { export const ManagementSettingsLayout: FC = () => {
const { permissions } = useAuthenticated(); const { permissions } = useAuthenticated();
const { experiments } = useDashboard(); const { experiments } = useDashboard();
const deploymentConfigQuery = useQuery(deploymentConfig()); const deploymentConfigQuery = useQuery(deploymentConfig());
const organizationsQuery = useQuery(organizations());
const multiOrgExperimentEnabled = experiments.includes("multi-organization"); const multiOrgExperimentEnabled = experiments.includes("multi-organization");
@ -48,30 +35,20 @@ export const ManagementSettingsLayout: FC = () => {
<RequirePermission isFeatureVisible={permissions.viewDeploymentValues}> <RequirePermission isFeatureVisible={permissions.viewDeploymentValues}>
<Margins> <Margins>
<Stack css={{ padding: "48px 0" }} direction="row" spacing={6}> <Stack css={{ padding: "48px 0" }} direction="row" spacing={6}>
{organizationsQuery.data ? ( <Sidebar />
<OrganizationSettingsContext.Provider <main css={{ width: "100%" }}>
value={{ organizations: organizationsQuery.data }} {deploymentConfigQuery.data ? (
> <DeploySettingsContext.Provider
<Sidebar /> value={{ deploymentValues: deploymentConfigQuery.data }}
<main css={{ width: "100%" }}> >
{deploymentConfigQuery.data ? ( <Suspense fallback={<Loader />}>
<DeploySettingsContext.Provider <Outlet />
value={{ </Suspense>
deploymentValues: deploymentConfigQuery.data, </DeploySettingsContext.Provider>
}} ) : (
> <Loader />
<Suspense fallback={<Loader />}> )}
<Outlet /> </main>
</Suspense>
</DeploySettingsContext.Provider>
) : (
<Loader />
)}
</main>
</OrganizationSettingsContext.Provider>
) : (
<Loader />
)}
</Stack> </Stack>
</Margins> </Margins>
</RequirePermission> </RequirePermission>

View File

@ -9,15 +9,19 @@ import { useTemplateLayoutContext } from "pages/TemplatePage/TemplateLayout";
import { getTemplatePageTitle } from "../utils"; import { getTemplatePageTitle } from "../utils";
const TemplateFilesPage: FC = () => { const TemplateFilesPage: FC = () => {
const { organization: organizationId = "default" } = useParams() as { const { organization: organizationName = "default" } = useParams() as {
organization: string; organization?: string;
}; };
const { template, activeVersion } = useTemplateLayoutContext(); const { template, activeVersion } = useTemplateLayoutContext();
const { data: currentFiles } = useQuery( const { data: currentFiles } = useQuery(
templateFiles(activeVersion.job.file_id), templateFiles(activeVersion.job.file_id),
); );
const previousVersionQuery = useQuery( const previousVersionQuery = useQuery(
previousTemplateVersion(organizationId, template.name, activeVersion.name), previousTemplateVersion(
organizationName,
template.name,
activeVersion.name,
),
); );
const previousVersion = previousVersionQuery.data; const previousVersion = previousVersionQuery.data;
const hasPreviousVersion = const hasPreviousVersion =

View File

@ -71,14 +71,11 @@ export const TemplateLayout: FC<PropsWithChildren> = ({
children = <Outlet />, children = <Outlet />,
}) => { }) => {
const navigate = useNavigate(); const navigate = useNavigate();
const { template: templateName, organization: organizationId = "default" } = const { organization: organizationName = "default", template: templateName } =
useParams() as { useParams() as { organization?: string; template: string };
template: string;
organization: string;
};
const { data, error, isLoading } = useQuery({ const { data, error, isLoading } = useQuery({
queryKey: ["template", templateName], queryKey: ["template", templateName],
queryFn: () => fetchTemplate(organizationId, templateName), queryFn: () => fetchTemplate(organizationName, templateName),
}); });
const location = useLocation(); const location = useLocation();
const paths = location.pathname.split("/"); const paths = location.pathname.split("/");

View File

@ -7,19 +7,19 @@ import { templateByNameKey } from "api/queries/templates";
import type { UpdateTemplateMeta } from "api/typesGenerated"; import type { UpdateTemplateMeta } from "api/typesGenerated";
import { displaySuccess } from "components/GlobalSnackbar/utils"; import { displaySuccess } from "components/GlobalSnackbar/utils";
import { useDashboard } from "modules/dashboard/useDashboard"; import { useDashboard } from "modules/dashboard/useDashboard";
import { linkToTemplate, useLinks } from "modules/navigation";
import { pageTitle } from "utils/page"; import { pageTitle } from "utils/page";
import { useTemplateSettings } from "../TemplateSettingsLayout"; import { useTemplateSettings } from "../TemplateSettingsLayout";
import { TemplateSchedulePageView } from "./TemplateSchedulePageView"; import { TemplateSchedulePageView } from "./TemplateSchedulePageView";
const TemplateSchedulePage: FC = () => { const TemplateSchedulePage: FC = () => {
const { template: templateName } = useParams() as { template: string }; const getLink = useLinks();
const navigate = useNavigate(); const navigate = useNavigate();
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const { template } = useTemplateSettings(); const { template } = useTemplateSettings();
const { entitlements } = useDashboard(); const { entitlements } = useDashboard();
const { organization: organizationId = "default" } = useParams() as { const { organization: organizationName = "default", template: templateName } =
organization: string; useParams() as { organization?: string; template: string };
};
const allowAdvancedScheduling = const allowAdvancedScheduling =
entitlements.features["advanced_template_scheduling"].enabled; entitlements.features["advanced_template_scheduling"].enabled;
@ -32,7 +32,7 @@ const TemplateSchedulePage: FC = () => {
{ {
onSuccess: async () => { onSuccess: async () => {
await queryClient.invalidateQueries( await queryClient.invalidateQueries(
templateByNameKey(organizationId, templateName), templateByNameKey(organizationName, templateName),
); );
displaySuccess("Template updated successfully"); displaySuccess("Template updated successfully");
// clear browser storage of workspaces impending deletion // clear browser storage of workspaces impending deletion
@ -53,7 +53,7 @@ const TemplateSchedulePage: FC = () => {
template={template} template={template}
submitError={submitError} submitError={submitError}
onCancel={() => { onCancel={() => {
navigate(`/templates/${organizationId}/${templateName}`); navigate(getLink(linkToTemplate(organizationName, templateName)));
}} }}
onSubmit={(templateScheduleSettings) => { onSubmit={(templateScheduleSettings) => {
updateTemplate({ updateTemplate({

View File

@ -27,10 +27,7 @@ export function useTemplateSettings() {
export const TemplateSettingsLayout: FC = () => { export const TemplateSettingsLayout: FC = () => {
const { organization: organizationName = "default", template: templateName } = const { organization: organizationName = "default", template: templateName } =
useParams() as { useParams() as { organization?: string; template: string };
organization: string;
template: string;
};
const templateQuery = useQuery( const templateQuery = useQuery(
templateByName(organizationName, templateName), templateByName(organizationName, templateName),
); );

View File

@ -16,13 +16,15 @@ import type {
import { ErrorAlert } from "components/Alert/ErrorAlert"; import { ErrorAlert } from "components/Alert/ErrorAlert";
import { displaySuccess } from "components/GlobalSnackbar/utils"; import { displaySuccess } from "components/GlobalSnackbar/utils";
import { Loader } from "components/Loader/Loader"; import { Loader } from "components/Loader/Loader";
import { linkToTemplate, useLinks } from "modules/navigation";
import { pageTitle } from "utils/page"; import { pageTitle } from "utils/page";
import { useTemplateSettings } from "../TemplateSettingsLayout"; import { useTemplateSettings } from "../TemplateSettingsLayout";
import { TemplateVariablesPageView } from "./TemplateVariablesPageView"; import { TemplateVariablesPageView } from "./TemplateVariablesPageView";
export const TemplateVariablesPage: FC = () => { export const TemplateVariablesPage: FC = () => {
const getLink = useLinks();
const { organization = "default", template: templateName } = useParams() as { const { organization = "default", template: templateName } = useParams() as {
organization: string; organization?: string;
template: string; template: string;
}; };
const { template } = useTemplateSettings(); const { template } = useTemplateSettings();
@ -97,7 +99,7 @@ export const TemplateVariablesPage: FC = () => {
publishError, publishError,
}} }}
onCancel={() => { onCancel={() => {
navigate(`/templates/${organization}/${templateName}`); navigate(getLink(linkToTemplate(organization, templateName)));
}} }}
onSubmit={async (formData) => { onSubmit={async (formData) => {
const request = filterEmptySensitiveVariables(formData, variables); const request = filterEmptySensitiveVariables(formData, variables);

View File

@ -35,7 +35,7 @@ export const TemplateVersionEditorPage: FC = () => {
template: templateName, template: templateName,
version: versionName, version: versionName,
} = useParams() as { } = useParams() as {
organization: string; organization?: string;
template: string; template: string;
version: string; version: string;
}; };
@ -194,7 +194,9 @@ export const TemplateVersionEditorPage: FC = () => {
params.set("version", publishedVersion.id); params.set("version", publishedVersion.id);
} }
navigate( navigate(
`/templates/${organizationName}/${templateName}/workspace?${params.toString()}`, `${getLink(
linkToTemplate(organizationName, templateName),
)}/workspace?${params.toString()}`,
); );
}} }}
isBuilding={ isBuilding={

View File

@ -9,16 +9,18 @@ import {
templateVersionByName, templateVersionByName,
} from "api/queries/templates"; } from "api/queries/templates";
import { useAuthenticated } from "contexts/auth/RequireAuth"; import { useAuthenticated } from "contexts/auth/RequireAuth";
import { linkToTemplate, useLinks } from "modules/navigation";
import { pageTitle } from "utils/page"; import { pageTitle } from "utils/page";
import TemplateVersionPageView from "./TemplateVersionPageView"; import TemplateVersionPageView from "./TemplateVersionPageView";
export const TemplateVersionPage: FC = () => { export const TemplateVersionPage: FC = () => {
const getLink = useLinks();
const { const {
organization: organizationName = "default", organization: organizationName = "default",
template: templateName, template: templateName,
version: versionName, version: versionName,
} = useParams() as { } = useParams() as {
organization: string; organization?: string;
template: string; template: string;
version: string; version: string;
}; };
@ -51,10 +53,12 @@ export const TemplateVersionPage: FC = () => {
const params = new URLSearchParams(); const params = new URLSearchParams();
if (versionId) { if (versionId) {
params.set("version", versionId); params.set("version", versionId);
return `/templates/${organizationName}/${templateName}/workspace?${params.toString()}`; return `${getLink(
linkToTemplate(organizationName, templateName),
)}/workspace?${params.toString()}`;
} }
return undefined; return undefined;
}, [templateName, versionId, organizationName]); }, [getLink, templateName, versionId, organizationName]);
return ( return (
<> <>

View File

@ -3,15 +3,13 @@ import { Helmet } from "react-helmet-async";
import { useQuery } from "react-query"; import { useQuery } from "react-query";
import { templateExamples, templates } from "api/queries/templates"; import { templateExamples, templates } from "api/queries/templates";
import { useAuthenticated } from "contexts/auth/RequireAuth"; import { useAuthenticated } from "contexts/auth/RequireAuth";
import { useDashboard } from "modules/dashboard/useDashboard";
import { pageTitle } from "utils/page"; import { pageTitle } from "utils/page";
import { TemplatesPageView } from "./TemplatesPageView"; import { TemplatesPageView } from "./TemplatesPageView";
export const TemplatesPage: FC = () => { export const TemplatesPage: FC = () => {
const { permissions } = useAuthenticated(); const { permissions } = useAuthenticated();
const { organizationId } = useDashboard();
const templatesQuery = useQuery(templates(organizationId)); const templatesQuery = useQuery(templates());
const examplesQuery = useQuery({ const examplesQuery = useQuery({
...templateExamples("default"), ...templateExamples("default"),
enabled: permissions.createTemplates, enabled: permissions.createTemplates,

View File

@ -13,6 +13,7 @@ import {
MockAppearanceConfig, MockAppearanceConfig,
MockAuthMethodsAll, MockAuthMethodsAll,
MockBuildInfo, MockBuildInfo,
MockDefaultOrganization,
MockEntitlements, MockEntitlements,
MockExperiments, MockExperiments,
MockUser, MockUser,
@ -70,6 +71,7 @@ const meta = {
{ key: ["entitlements"], data: MockEntitlements }, { key: ["entitlements"], data: MockEntitlements },
{ key: ["experiments"], data: MockExperiments }, { key: ["experiments"], data: MockExperiments },
{ key: ["appearance"], data: MockAppearanceConfig }, { key: ["appearance"], data: MockAppearanceConfig },
{ key: ["organizations"], data: [MockDefaultOrganization] },
{ {
key: getAuthorizationKey({ checks: permissionsToCheck }), key: getAuthorizationKey({ checks: permissionsToCheck }),
data: { editWorkspaceProxies: true }, data: { editWorkspaceProxies: true },

View File

@ -13,11 +13,12 @@ export const AccountPage: FC = () => {
const { permissions, user: me } = useAuthenticated(); const { permissions, user: me } = useAuthenticated();
const { updateProfile, updateProfileError, isUpdatingProfile } = const { updateProfile, updateProfileError, isUpdatingProfile } =
useAuthContext(); useAuthContext();
const { entitlements, organizationId } = useDashboard(); const { entitlements } = useDashboard();
const hasGroupsFeature = entitlements.features.user_role_management.enabled; const hasGroupsFeature = entitlements.features.user_role_management.enabled;
const groupsQuery = useQuery({ const groupsQuery = useQuery({
...groupsForUser(organizationId, me.id), // TODO: This should probably list all groups, not just default org groups
...groupsForUser("default", me.id),
enabled: hasGroupsFeature, enabled: hasGroupsFeature,
}); });

View File

@ -40,11 +40,11 @@ const UsersPage: FC = () => {
const navigate = useNavigate(); const navigate = useNavigate();
const location = useLocation(); const location = useLocation();
const searchParamsResult = useSearchParams(); const searchParamsResult = useSearchParams();
const { entitlements, experiments, organizationId } = useDashboard(); const { entitlements, experiments } = useDashboard();
const [searchParams] = searchParamsResult; const [searchParams] = searchParamsResult;
const isMultiOrg = experiments.includes("multi-organization"); const isMultiOrg = experiments.includes("multi-organization");
const groupsByUserIdQuery = useQuery(groupsByUserId(organizationId)); const groupsByUserIdQuery = useQuery(groupsByUserId("default"));
const authMethodsQuery = useQuery(authMethods()); const authMethodsQuery = useQuery(authMethods());
const { permissions, user: me } = useAuthenticated(); const { permissions, user: me } = useAuthenticated();

View File

@ -3,7 +3,7 @@ import { useQuery, useQueryClient } from "react-query";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import { watchWorkspace } from "api/api"; import { watchWorkspace } from "api/api";
import { checkAuthorization } from "api/queries/authCheck"; import { checkAuthorization } from "api/queries/authCheck";
import { templateByName } from "api/queries/templates"; import { template as templateQueryOptions } from "api/queries/templates";
import { workspaceBuildsKey } from "api/queries/workspaceBuilds"; import { workspaceBuildsKey } from "api/queries/workspaceBuilds";
import { workspaceByOwnerAndName } from "api/queries/workspaces"; import { workspaceByOwnerAndName } from "api/queries/workspaces";
import type { Workspace } from "api/typesGenerated"; import type { Workspace } from "api/typesGenerated";
@ -13,7 +13,6 @@ import { Margins } from "components/Margins/Margins";
import { useEffectEvent } from "hooks/hookPolyfills"; import { useEffectEvent } from "hooks/hookPolyfills";
import { AnnouncementBanners } from "modules/dashboard/AnnouncementBanners/AnnouncementBanners"; import { AnnouncementBanners } from "modules/dashboard/AnnouncementBanners/AnnouncementBanners";
import { Navbar } from "modules/dashboard/Navbar/Navbar"; import { Navbar } from "modules/dashboard/Navbar/Navbar";
import { useDashboard } from "modules/dashboard/useDashboard";
import { workspaceChecks, type WorkspacePermissions } from "./permissions"; import { workspaceChecks, type WorkspacePermissions } from "./permissions";
import { WorkspaceReadyPage } from "./WorkspaceReadyPage"; import { WorkspaceReadyPage } from "./WorkspaceReadyPage";
@ -25,7 +24,6 @@ export const WorkspacePage: FC = () => {
}; };
const workspaceName = params.workspace; const workspaceName = params.workspace;
const username = params.username.replace("@", ""); const username = params.username.replace("@", "");
const { organizationId } = useDashboard();
// Workspace // Workspace
const workspaceQueryOptions = workspaceByOwnerAndName( const workspaceQueryOptions = workspaceByOwnerAndName(
@ -36,10 +34,11 @@ export const WorkspacePage: FC = () => {
const workspace = workspaceQuery.data; const workspace = workspaceQuery.data;
// Template // Template
const templateQuery = useQuery({ const templateQuery = useQuery(
...templateByName(organizationId, workspace?.template_name ?? ""), workspace
enabled: workspace !== undefined, ? templateQueryOptions(workspace.template_id)
}); : { enabled: false },
);
const template = templateQuery.data; const template = templateQuery.data;
// Permissions // Permissions

View File

@ -39,13 +39,12 @@ const WorkspacesPage: FC = () => {
const searchParamsResult = useSafeSearchParams(); const searchParamsResult = useSafeSearchParams();
const pagination = usePagination({ searchParamsResult }); const pagination = usePagination({ searchParamsResult });
const { permissions } = useAuthenticated(); const { permissions } = useAuthenticated();
const { entitlements, organizationId } = useDashboard(); const { entitlements } = useDashboard();
const templatesQuery = useQuery(templates(organizationId, false)); const templatesQuery = useQuery(templates());
const filterProps = useWorkspacesFilter({ const filterProps = useWorkspacesFilter({
searchParamsResult, searchParamsResult,
organizationId,
onFilterChange: () => pagination.goToPage(1), onFilterChange: () => pagination.goToPage(1),
}); });
@ -142,13 +141,11 @@ export default WorkspacesPage;
type UseWorkspacesFilterOptions = { type UseWorkspacesFilterOptions = {
searchParamsResult: ReturnType<typeof useSearchParams>; searchParamsResult: ReturnType<typeof useSearchParams>;
onFilterChange: () => void; onFilterChange: () => void;
organizationId: string;
}; };
const useWorkspacesFilter = ({ const useWorkspacesFilter = ({
searchParamsResult, searchParamsResult,
onFilterChange, onFilterChange,
organizationId,
}: UseWorkspacesFilterOptions) => { }: UseWorkspacesFilterOptions) => {
const filter = useFilter({ const filter = useFilter({
fallbackFilter: "owner:me", fallbackFilter: "owner:me",
@ -166,7 +163,6 @@ const useWorkspacesFilter = ({
}); });
const templateMenu = useTemplateFilterMenu({ const templateMenu = useTemplateFilterMenu({
organizationId,
value: filter.values.template, value: filter.values.template,
onChange: (option) => onChange: (option) =>
filter.update({ ...filter.values, template: option?.value }), filter.update({ ...filter.values, template: option?.value }),

View File

@ -16,18 +16,14 @@ import { getDisplayWorkspaceStatus } from "utils/workspace";
export const useTemplateFilterMenu = ({ export const useTemplateFilterMenu = ({
value, value,
onChange, onChange,
organizationId, }: Pick<UseFilterMenuOptions<SelectFilterOption>, "value" | "onChange">) => {
}: { organizationId: string } & Pick<
UseFilterMenuOptions<SelectFilterOption>,
"value" | "onChange"
>) => {
return useFilterMenu({ return useFilterMenu({
onChange, onChange,
value, value,
id: "template", id: "template",
getSelectedOption: async () => { getSelectedOption: async () => {
// Show all templates including deprecated // Show all templates including deprecated
const templates = await API.getTemplates(organizationId); const templates = await API.getTemplates();
const template = templates.find((template) => template.name === value); const template = templates.find((template) => template.name === value);
if (template) { if (template) {
return { return {
@ -40,7 +36,7 @@ export const useTemplateFilterMenu = ({
}, },
getOptions: async (query) => { getOptions: async (query) => {
// Show all templates including deprecated // Show all templates including deprecated
const templates = await API.getTemplates(organizationId); const templates = await API.getTemplates();
const filteredTemplates = templates.filter( const filteredTemplates = templates.filter(
(template) => (template) =>
template.name.toLowerCase().includes(query.toLowerCase()) || template.name.toLowerCase().includes(query.toLowerCase()) ||

View File

@ -42,7 +42,7 @@ export const handlers = [
// organizations // organizations
http.get("/api/v2/organizations", () => { http.get("/api/v2/organizations", () => {
return HttpResponse.json([M.MockDefaultOrganization, M.MockOrganization2]); return HttpResponse.json([M.MockDefaultOrganization]);
}), }),
http.get("/api/v2/organizations/:organizationId", () => { http.get("/api/v2/organizations/:organizationId", () => {
return HttpResponse.json(M.MockOrganization); return HttpResponse.json(M.MockOrganization);

View File

@ -3,7 +3,11 @@ import type { FC } from "react";
import { withDefaultFeatures } from "api/api"; import { withDefaultFeatures } from "api/api";
import type { Entitlements } from "api/typesGenerated"; import type { Entitlements } from "api/typesGenerated";
import { DashboardContext } from "modules/dashboard/DashboardProvider"; import { DashboardContext } from "modules/dashboard/DashboardProvider";
import { MockAppearanceConfig, MockEntitlements } from "./entities"; import {
MockAppearanceConfig,
MockDefaultOrganization,
MockEntitlements,
} from "./entities";
export const withDashboardProvider = ( export const withDashboardProvider = (
Story: FC, Story: FC,
@ -26,10 +30,10 @@ export const withDashboardProvider = (
return ( return (
<DashboardContext.Provider <DashboardContext.Provider
value={{ value={{
organizationId: "",
entitlements, entitlements,
experiments, experiments,
appearance: MockAppearanceConfig, appearance: MockAppearanceConfig,
organizations: [MockDefaultOrganization],
}} }}
> >
<Story /> <Story />