Skip to content

chore: consolidate ManageSettingsLayout code #14885

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 17 commits into from
Oct 3, 2024
Next Next commit
typescript is so irritating sometimes
  • Loading branch information
aslilac committed Sep 30, 2024
commit bf9ab326fa9417d03b9c24020ad31c1fce3bd7a5
111 changes: 111 additions & 0 deletions site/src/modules/management/ManagementSettingsLayout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import { deploymentConfig } from "api/queries/deployment";
import type {
AuthorizationResponse,
DeploymentConfig,
Organization,
} from "api/typesGenerated";
import { Loader } from "components/Loader/Loader";
import { Margins } from "components/Margins/Margins";
import { Stack } from "components/Stack/Stack";
import { useAuthenticated } from "contexts/auth/RequireAuth";
import { RequirePermission } from "contexts/auth/RequirePermission";
import { useDashboard } from "modules/dashboard/useDashboard";
import { createContext, type FC, Suspense, useContext } from "react";
import { useQuery } from "react-query";
import { Navigate, Outlet, useNavigate, useParams } from "react-router-dom";
import { Sidebar } from "./Sidebar";
import { ErrorAlert } from "components/Alert/ErrorAlert";

export const ManagementSettingsContext = createContext<
{ deploymentValues: DeploymentConfig } | undefined
>(undefined);

type ManagementSettingsValue = Readonly<{
deploymentValues: DeploymentConfig;
organizations: readonly Organization[];
organization?: Organization;
}>;

export const useManagementSettings = (): ManagementSettingsValue => {
const context = useContext(ManagementSettingsContext);
if (!context) {
throw new Error(
"useManagementSettings should be used inside of ManagementSettingsLayout",
);
}
const { organizations } = useDashboard();
const { organization: orgName } = useParams() as {
organization?: string;
};

const organization =
organizations && orgName
? organizations.find((org) => org.name === orgName)
: undefined;

return {
deploymentValues: context.deploymentValues,
organizations,
organization,
};
};

/**
* Return true if the user can edit the organization settings or its members.
*/
export const canEditOrganization = (
permissions: AuthorizationResponse | undefined,
) => {
return (
permissions !== undefined &&
(permissions.editOrganization ||
permissions.editMembers ||
permissions.editGroups)
);
};

/**
* A multi-org capable settings page layout.
*
* If multi-org is not enabled or licensed, this is the wrong layout to use.
* See DeploySettingsLayoutInner instead.
*/
export const ManagementSettingsLayout: FC = () => {
const { permissions } = useAuthenticated();
const deploymentConfigQuery = useQuery(deploymentConfig());

// The deployment settings page also contains users, audit logs, groups and
// organizations, so this page must be visible if you can see any of these.
const canViewDeploymentSettingsPage =
permissions.viewDeploymentValues ||
permissions.viewAllUsers ||
permissions.editAnyOrganization ||
permissions.viewAnyAuditLog;

if (deploymentConfigQuery.error) {
return <ErrorAlert error={deploymentConfigQuery.error} />;
}

if (!deploymentConfigQuery.data) {
return <Loader />;
}

return (
<Margins>
<Stack css={{ padding: "48px 0" }} direction="row" spacing={6}>
<Sidebar />
<main css={{ width: "100%" }}>
<Suspense fallback={<Loader />}>
<RequirePermission isFeatureVisible={canViewDeploymentSettingsPage}>
<ManagementSettingsContext.Provider
value={{ deploymentValues: deploymentConfigQuery.data }}
>
<Outlet />
</ManagementSettingsContext.Provider>
</RequirePermission>
</Suspense>
</main>
</Stack>
</Margins>
);
};
62 changes: 62 additions & 0 deletions site/src/modules/management/Sidebar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { organizationsPermissions } from "api/queries/organizations";
import { useAuthenticated } from "contexts/auth/RequireAuth";
import { useDashboard } from "modules/dashboard/useDashboard";
import type { FC } from "react";
import { useQuery } from "react-query";
import { useLocation, useParams } from "react-router-dom";
import {
canEditOrganization,
useManagementSettings,
} from "modules/management/ManagementSettingsLayout";
import { type OrganizationWithPermissions, SidebarView } from "./SidebarView";

/**
* A combined deployment settings and organization menu.
*
* This should only be used with multi-org support. If multi-org support is
* disabled or not licensed, this is the wrong sidebar to use. See
* DeploySettingsPage/Sidebar instead.
*/
export const Sidebar: FC = () => {
const location = useLocation();
const { permissions } = useAuthenticated();
const { experiments } = useDashboard();
const { organizations } = useManagementSettings();
const { organization: organizationName } = useParams() as {
organization?: string;
};

const orgPermissionsQuery = useQuery(
organizationsPermissions(organizations?.map((o) => o.id)),
);

// Sometimes a user can read an organization but cannot actually do anything
// with it. For now, these are filtered out so you only see organizations you
// can manage in some way.
const editableOrgs = organizations
?.map((org) => {
return {
...org,
permissions: orgPermissionsQuery.data?.[org.id],
};
})
// TypeScript is not able to infer whether permissions are defined on the
// object even if we explicitly check org.permissions here, so add the `is`
// here to help out (canEditOrganization does the actual check).
.filter((org): org is OrganizationWithPermissions => {
return canEditOrganization(org.permissions);
});

return (
<SidebarView
// Both activeSettings and activeOrganizationName could be be falsey if
// the user is on /organizations but has no editable organizations to
// which we can redirect.
activeSettings={location.pathname.startsWith("/deployment")}
activeOrganizationName={organizationName}
organizations={editableOrgs}
permissions={permissions}
experiments={experiments}
/>
);
};
Loading