Skip to content

Commit bf9ab32

Browse files
committed
typescript is so irritating sometimes
1 parent 5246f8d commit bf9ab32

File tree

74 files changed

+906
-285
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

74 files changed

+906
-285
lines changed
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
import { deploymentConfig } from "api/queries/deployment";
2+
import type {
3+
AuthorizationResponse,
4+
DeploymentConfig,
5+
Organization,
6+
} from "api/typesGenerated";
7+
import { Loader } from "components/Loader/Loader";
8+
import { Margins } from "components/Margins/Margins";
9+
import { Stack } from "components/Stack/Stack";
10+
import { useAuthenticated } from "contexts/auth/RequireAuth";
11+
import { RequirePermission } from "contexts/auth/RequirePermission";
12+
import { useDashboard } from "modules/dashboard/useDashboard";
13+
import { createContext, type FC, Suspense, useContext } from "react";
14+
import { useQuery } from "react-query";
15+
import { Navigate, Outlet, useNavigate, useParams } from "react-router-dom";
16+
import { Sidebar } from "./Sidebar";
17+
import { ErrorAlert } from "components/Alert/ErrorAlert";
18+
19+
export const ManagementSettingsContext = createContext<
20+
{ deploymentValues: DeploymentConfig } | undefined
21+
>(undefined);
22+
23+
type ManagementSettingsValue = Readonly<{
24+
deploymentValues: DeploymentConfig;
25+
organizations: readonly Organization[];
26+
organization?: Organization;
27+
}>;
28+
29+
export const useManagementSettings = (): ManagementSettingsValue => {
30+
const context = useContext(ManagementSettingsContext);
31+
if (!context) {
32+
throw new Error(
33+
"useManagementSettings should be used inside of ManagementSettingsLayout",
34+
);
35+
}
36+
const { organizations } = useDashboard();
37+
const { organization: orgName } = useParams() as {
38+
organization?: string;
39+
};
40+
41+
const organization =
42+
organizations && orgName
43+
? organizations.find((org) => org.name === orgName)
44+
: undefined;
45+
46+
return {
47+
deploymentValues: context.deploymentValues,
48+
organizations,
49+
organization,
50+
};
51+
};
52+
53+
/**
54+
* Return true if the user can edit the organization settings or its members.
55+
*/
56+
export const canEditOrganization = (
57+
permissions: AuthorizationResponse | undefined,
58+
) => {
59+
return (
60+
permissions !== undefined &&
61+
(permissions.editOrganization ||
62+
permissions.editMembers ||
63+
permissions.editGroups)
64+
);
65+
};
66+
67+
/**
68+
* A multi-org capable settings page layout.
69+
*
70+
* If multi-org is not enabled or licensed, this is the wrong layout to use.
71+
* See DeploySettingsLayoutInner instead.
72+
*/
73+
export const ManagementSettingsLayout: FC = () => {
74+
const { permissions } = useAuthenticated();
75+
const deploymentConfigQuery = useQuery(deploymentConfig());
76+
77+
// The deployment settings page also contains users, audit logs, groups and
78+
// organizations, so this page must be visible if you can see any of these.
79+
const canViewDeploymentSettingsPage =
80+
permissions.viewDeploymentValues ||
81+
permissions.viewAllUsers ||
82+
permissions.editAnyOrganization ||
83+
permissions.viewAnyAuditLog;
84+
85+
if (deploymentConfigQuery.error) {
86+
return <ErrorAlert error={deploymentConfigQuery.error} />;
87+
}
88+
89+
if (!deploymentConfigQuery.data) {
90+
return <Loader />;
91+
}
92+
93+
return (
94+
<Margins>
95+
<Stack css={{ padding: "48px 0" }} direction="row" spacing={6}>
96+
<Sidebar />
97+
<main css={{ width: "100%" }}>
98+
<Suspense fallback={<Loader />}>
99+
<RequirePermission isFeatureVisible={canViewDeploymentSettingsPage}>
100+
<ManagementSettingsContext.Provider
101+
value={{ deploymentValues: deploymentConfigQuery.data }}
102+
>
103+
<Outlet />
104+
</ManagementSettingsContext.Provider>
105+
</RequirePermission>
106+
</Suspense>
107+
</main>
108+
</Stack>
109+
</Margins>
110+
);
111+
};
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import { organizationsPermissions } from "api/queries/organizations";
2+
import { useAuthenticated } from "contexts/auth/RequireAuth";
3+
import { useDashboard } from "modules/dashboard/useDashboard";
4+
import type { FC } from "react";
5+
import { useQuery } from "react-query";
6+
import { useLocation, useParams } from "react-router-dom";
7+
import {
8+
canEditOrganization,
9+
useManagementSettings,
10+
} from "modules/management/ManagementSettingsLayout";
11+
import { type OrganizationWithPermissions, SidebarView } from "./SidebarView";
12+
13+
/**
14+
* A combined deployment settings and organization menu.
15+
*
16+
* This should only be used with multi-org support. If multi-org support is
17+
* disabled or not licensed, this is the wrong sidebar to use. See
18+
* DeploySettingsPage/Sidebar instead.
19+
*/
20+
export const Sidebar: FC = () => {
21+
const location = useLocation();
22+
const { permissions } = useAuthenticated();
23+
const { experiments } = useDashboard();
24+
const { organizations } = useManagementSettings();
25+
const { organization: organizationName } = useParams() as {
26+
organization?: string;
27+
};
28+
29+
const orgPermissionsQuery = useQuery(
30+
organizationsPermissions(organizations?.map((o) => o.id)),
31+
);
32+
33+
// Sometimes a user can read an organization but cannot actually do anything
34+
// with it. For now, these are filtered out so you only see organizations you
35+
// can manage in some way.
36+
const editableOrgs = organizations
37+
?.map((org) => {
38+
return {
39+
...org,
40+
permissions: orgPermissionsQuery.data?.[org.id],
41+
};
42+
})
43+
// TypeScript is not able to infer whether permissions are defined on the
44+
// object even if we explicitly check org.permissions here, so add the `is`
45+
// here to help out (canEditOrganization does the actual check).
46+
.filter((org): org is OrganizationWithPermissions => {
47+
return canEditOrganization(org.permissions);
48+
});
49+
50+
return (
51+
<SidebarView
52+
// Both activeSettings and activeOrganizationName could be be falsey if
53+
// the user is on /organizations but has no editable organizations to
54+
// which we can redirect.
55+
activeSettings={location.pathname.startsWith("/deployment")}
56+
activeOrganizationName={organizationName}
57+
organizations={editableOrgs}
58+
permissions={permissions}
59+
experiments={experiments}
60+
/>
61+
);
62+
};

0 commit comments

Comments
 (0)