Skip to content

fix: ensure user admins can always see users table #15226

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 33 commits into from
Oct 29, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
1ad09fa
refactor: update layout so that sidebar always shows
Parkreiner Oct 24, 2024
620bd3a
refactor: consolidate logic more
Parkreiner Oct 24, 2024
7ee3cf0
fix: add redirect for base users
Parkreiner Oct 24, 2024
e889621
fix: update routing logic to not block on disabled queries
Parkreiner Oct 24, 2024
36e0376
fix: update type misalignment
Parkreiner Oct 24, 2024
032a75c
chore: format and tidy up
Parkreiner Oct 24, 2024
eea8bbd
fix: make sure you can't navigate directly to bare /deployment page
Parkreiner Oct 24, 2024
e6d4201
fix: add more granularity to redirect logic
Parkreiner Oct 24, 2024
14fc68e
fix: prevent infinite redirect
Parkreiner Oct 24, 2024
a8fd9e6
fix: update redirects for /organizations routes
Parkreiner Oct 24, 2024
0a36cd1
fix: format files
Parkreiner Oct 24, 2024
e6d772b
fix: tighten up types for permissions
Parkreiner Oct 25, 2024
3752af5
Merge branch 'main' into mes/deploy-bug
Parkreiner Oct 25, 2024
2068a4c
fix: update route checks to account for subroutes
Parkreiner Oct 25, 2024
2fe3d53
fix: add e2e check to ensure that redirect hasn't happened
Parkreiner Oct 25, 2024
3c41de0
fix: update stories to account for new dashboard logic
Parkreiner Oct 25, 2024
162642e
fix: update out-of-date comment
Parkreiner Oct 25, 2024
64c6f29
fix: update default value logic for storybook setup
Parkreiner Oct 25, 2024
e12a4aa
fix: update individual stories
Parkreiner Oct 25, 2024
b9b33c0
fix: finish storybook injection changes
Parkreiner Oct 25, 2024
f7df5ff
fix: add back separate empty state to satisfy tests
Parkreiner Oct 25, 2024
6925294
fix: add tests and update code to account for missing cases
Parkreiner Oct 25, 2024
9fc0afa
fix: apply formatting
Parkreiner Oct 25, 2024
498e7ba
break out `DeploymentSettingsProvider`
aslilac Oct 28, 2024
234c606
fix: apply changes from previous PR
Parkreiner Oct 28, 2024
4496a75
fix: apply formatting
Parkreiner Oct 28, 2024
9fcf3e7
Merge branch 'main' into mes/deploy-bug
Parkreiner Oct 28, 2024
19fb6e5
Merge branch 'lilac/deployment-settings-provider' into mes/deploy-bug
Parkreiner Oct 28, 2024
804255e
fix: delete invalid tests
Parkreiner Oct 28, 2024
d341175
fix: revert changes not caught in merge
Parkreiner Oct 28, 2024
f3e6659
fix: update spellcheck
Parkreiner Oct 28, 2024
ce67a66
fix: revert nits
Parkreiner Oct 28, 2024
7e946a7
fix: apply feedback
Parkreiner Oct 29, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
fix: apply changes from previous PR
  • Loading branch information
Parkreiner committed Oct 28, 2024
commit 234c6060035f9532788991502673b6f1f8c4d2b3
1 change: 1 addition & 0 deletions site/e2e/global.setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ test("setup deployment", async ({ page }) => {
expect(constants.license.split(".").length).toBe(3); // otherwise it's invalid

await page.goto("/deployment/licenses", { waitUntil: "domcontentloaded" });
await expect(page).toHaveTitle("License Settings - Coder");

await page.getByText("Add a license").click();
await page.getByRole("textbox").fill(constants.license);
Expand Down
8 changes: 3 additions & 5 deletions site/jest.setup.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import "@testing-library/jest-dom";
import "jest-location-mock";
import { cleanup } from "@testing-library/react";
import crypto from "crypto";
import crypto from "node:crypto";
import { useMemo } from "react";
import type { Region } from "api/typesGenerated";
import type { ProxyLatencyReport } from "contexts/useProxyLatency";
Expand Down Expand Up @@ -48,9 +48,7 @@ global.ResizeObserver = require("resize-observer-polyfill");
// Polyfill the getRandomValues that is used on utils/random.ts
Object.defineProperty(global.self, "crypto", {
value: {
getRandomValues: function (buffer: Buffer) {
return crypto.randomFillSync(buffer);
},
getRandomValues: crypto.randomFillSync,
},
});

Expand All @@ -72,5 +70,5 @@ afterEach(() => {
// Clean up after the tests are finished.
afterAll(() => server.close());

// This is needed because we are compiling under `--isolatedModules`
// biome-ignore lint/complexity/noUselessEmptyExport: This is needed because we are compiling under `--isolatedModules`
export {};
28 changes: 24 additions & 4 deletions site/src/contexts/auth/permissions.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import type { AuthorizationCheck } from "api/typesGenerated";

export const checks = {
viewAllUsers: "viewAllUsers",
updateUsers: "updateUsers",
Expand All @@ -11,13 +13,20 @@ export const checks = {
viewUpdateCheck: "viewUpdateCheck",
viewExternalAuthConfig: "viewExternalAuthConfig",
viewDeploymentStats: "viewDeploymentStats",
readWorkspaceProxies: "readWorkspaceProxies",
editWorkspaceProxies: "editWorkspaceProxies",
createOrganization: "createOrganization",
editAnyOrganization: "editAnyOrganization",
viewAnyGroup: "viewAnyGroup",
createGroup: "createGroup",
viewAllLicenses: "viewAllLicenses",
} as const;
viewNotificationTemplate: "viewNotificationTemplate",
} as const satisfies Record<string, string>;

// Type expression seems a little redundant (`keyof typeof checks` has the same
// result), just because each key-value pair is currently symmetrical; this may
// change down the line
type PermissionValue = (typeof checks)[keyof typeof checks];

export const permissionsToCheck = {
[checks.viewAllUsers]: {
Expand Down Expand Up @@ -94,6 +103,12 @@ export const permissionsToCheck = {
},
action: "read",
},
[checks.readWorkspaceProxies]: {
object: {
resource_type: "workspace_proxy",
},
action: "read",
},
[checks.editWorkspaceProxies]: {
object: {
resource_type: "workspace_proxy",
Expand All @@ -116,7 +131,6 @@ export const permissionsToCheck = {
[checks.viewAnyGroup]: {
object: {
resource_type: "group",
org_id: "any",
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ended up deleting this because the property wasn't compatible. It also didn't seem to be used anywhere

@Emyrk Not sure if we should update the types so that this can be added back

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you delete this unused check then?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, sorry – I meant that the org_id property specifically didn't seem to be used anywhere

},
action: "read",
},
Expand All @@ -132,6 +146,12 @@ export const permissionsToCheck = {
},
action: "read",
},
} as const;
[checks.viewNotificationTemplate]: {
object: {
resource_type: "notification_template",
},
action: "read",
},
} as const satisfies Record<PermissionValue, AuthorizationCheck>;

export type Permissions = Record<keyof typeof permissionsToCheck, boolean>;
export type Permissions = Record<PermissionValue, boolean>;
5 changes: 4 additions & 1 deletion site/src/modules/management/DeploymentSettingsProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export const useDeploymentSettings = (): DeploymentSettingsValue => {
const context = useContext(DeploymentSettingsContext);
if (!context) {
throw new Error(
"useDeploymentSettings should be used inside of DeploymentSettingsLayout",
`${useDeploymentSettings.name} should be used inside of ${DeploymentSettingsProvider.name}`,
);
}

Expand All @@ -39,6 +39,9 @@ const DeploymentSettingsProvider: FC = () => {
permissions.editAnyOrganization ||
permissions.viewAnyAuditLog;

// Not a huge problem to unload the content in the event of an error,
// because the sidebar rendering isn't tied to this. Even if the user hits
// a 403 error, they'll still have navigation options
if (deploymentConfigQuery.error) {
return <ErrorAlert error={deploymentConfigQuery.error} />;
}
Expand Down
2 changes: 1 addition & 1 deletion site/src/modules/management/ManagementSettingsLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ const ManagementSettingsLayout: FC = () => {
<Margins>
<Stack css={{ padding: "48px 0" }} direction="row" spacing={6}>
<Sidebar />
<main css={{ width: "100%" }}>
<main css={{ flexGrow: 1 }}>
<Suspense fallback={<Loader />}>
<Outlet />
</Suspense>
Expand Down
3 changes: 2 additions & 1 deletion site/src/modules/management/SidebarView.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { Meta, StoryObj } from "@storybook/react";
import {
MockNoPermissions,
MockOrganization,
MockOrganization2,
MockPermissions,
Expand Down Expand Up @@ -96,7 +97,7 @@ export const NoDeploymentValues: Story = {

export const NoPermissions: Story = {
args: {
permissions: {},
permissions: MockNoPermissions,
},
};

Expand Down
33 changes: 17 additions & 16 deletions site/src/modules/management/SidebarView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ import { Loader } from "components/Loader/Loader";
import { Sidebar as BaseSidebar } from "components/Sidebar/Sidebar";
import { Stack } from "components/Stack/Stack";
import { UserAvatar } from "components/UserAvatar/UserAvatar";
import type { Permissions } from "contexts/auth/permissions";
import { type ClassName, useClassName } from "hooks/useClassName";
import { useDashboard } from "modules/dashboard/useDashboard";
import { linkToUsers } from "modules/navigation";
import type { FC, ReactNode } from "react";
import { Link, NavLink } from "react-router-dom";

Expand All @@ -30,7 +30,7 @@ interface SidebarProps {
/** Organizations and their permissions or undefined if still fetching. */
organizations: OrganizationWithPermissions[] | undefined;
/** Site-wide permissions. */
permissions: AuthorizationResponse;
permissions: Permissions;
}

/**
Expand Down Expand Up @@ -72,7 +72,7 @@ interface DeploymentSettingsNavigationProps {
/** Whether a deployment setting page is being viewed. */
active: boolean;
/** Site-wide permissions. */
permissions: AuthorizationResponse;
permissions: Permissions;
}

/**
Expand Down Expand Up @@ -130,10 +130,11 @@ const DeploymentSettingsNavigation: FC<DeploymentSettingsNavigationProps> = ({
{permissions.viewDeploymentValues && (
<SidebarNavSubItem href="network">Network</SidebarNavSubItem>
)}
{/* All users can view workspace regions. */}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This actually wasn't true, so Steven and I had to put this behind a condition

<SidebarNavSubItem href="workspace-proxies">
Workspace Proxies
</SidebarNavSubItem>
{permissions.readWorkspaceProxies && (
<SidebarNavSubItem href="workspace-proxies">
Workspace Proxies
</SidebarNavSubItem>
)}
{permissions.viewDeploymentValues && (
<SidebarNavSubItem href="security">Security</SidebarNavSubItem>
)}
Expand All @@ -145,12 +146,14 @@ const DeploymentSettingsNavigation: FC<DeploymentSettingsNavigationProps> = ({
{permissions.viewAllUsers && (
<SidebarNavSubItem href="users">Users</SidebarNavSubItem>
)}
<SidebarNavSubItem href="notifications">
<Stack direction="row" alignItems="center" spacing={1}>
<span>Notifications</span>
<FeatureStageBadge contentType="beta" size="sm" />
</Stack>
</SidebarNavSubItem>
{permissions.viewNotificationTemplate && (
<SidebarNavSubItem href="notifications">
<Stack direction="row" alignItems="center" spacing={1}>
<span>Notifications</span>
<FeatureStageBadge contentType="beta" size="sm" />
</Stack>
</SidebarNavSubItem>
)}
</Stack>
)}
</div>
Expand All @@ -167,7 +170,7 @@ interface OrganizationsSettingsNavigationProps {
/** Organizations and their permissions or undefined if still fetching. */
organizations: OrganizationWithPermissions[] | undefined;
/** Site-wide permissions. */
permissions: AuthorizationResponse;
permissions: Permissions;
}

/**
Expand Down Expand Up @@ -241,8 +244,6 @@ interface OrganizationSettingsNavigationProps {
const OrganizationSettingsNavigation: FC<
OrganizationSettingsNavigationProps
> = ({ active, organization }) => {
const { experiments } = useDashboard();

return (
<>
<SidebarNavItem
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,7 @@ const ExternalAuthSettingsPage: FC = () => {
<Helmet>
<title>{pageTitle("External Authentication Settings")}</title>
</Helmet>

{deploymentConfig ? (
<ExternalAuthSettingsPageView config={deploymentConfig.config} />
) : (
<Loader />
)}
<ExternalAuthSettingsPageView config={deploymentConfig.config} />
</>
);
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { deploymentDAUs } from "api/queries/deployment";
import { entitlements } from "api/queries/entitlements";
import { availableExperiments, experiments } from "api/queries/experiments";
import { Loader } from "components/Loader/Loader";
import { useEmbeddedMetadata } from "hooks/useEmbeddedMetadata";
import type { FC } from "react";
import { Helmet } from "react-helmet-async";
Expand Down Expand Up @@ -30,18 +29,14 @@ const GeneralSettingsPage: FC = () => {
<Helmet>
<title>{pageTitle("General Settings")}</title>
</Helmet>
{deploymentConfig ? (
<GeneralSettingsPageView
deploymentOptions={deploymentConfig.options}
deploymentDAUs={deploymentDAUsQuery.data}
deploymentDAUsError={deploymentDAUsQuery.error}
entitlements={entitlementsQuery.data}
invalidExperiments={invalidExperiments}
safeExperiments={safeExperiments}
/>
) : (
<Loader />
)}
<GeneralSettingsPageView
deploymentOptions={deploymentConfig.options}
deploymentDAUs={deploymentDAUsQuery.data}
deploymentDAUsError={deploymentDAUsQuery.error}
entitlements={entitlementsQuery.data}
invalidExperiments={invalidExperiments}
safeExperiments={safeExperiments}
/>
</>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,7 @@ const NetworkSettingsPage: FC = () => {
<Helmet>
<title>{pageTitle("Network Settings")}</title>
</Helmet>

{deploymentConfig ? (
<NetworkSettingsPageView options={deploymentConfig.options} />
) : (
<Loader />
)}
<NetworkSettingsPageView options={deploymentConfig.options} />
</>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,14 @@ import { Section } from "pages/UserSettingsPage/Section";
import type { FC } from "react";
import { Helmet } from "react-helmet-async";
import { useQueries } from "react-query";
import { useSearchParams } from "react-router-dom";
import { deploymentGroupHasParent } from "utils/deployOptions";
import { pageTitle } from "utils/page";
import OptionsTable from "../OptionsTable";
import { NotificationEvents } from "./NotificationEvents";
import { useDeploymentSettings } from "modules/management/DeploymentSettingsProvider";
import { useSearchParamsKey } from "hooks/useSearchParamsKey";

export const NotificationsPage: FC = () => {
const [searchParams] = useSearchParams();
const { deploymentConfig } = useDeploymentSettings();
const [templatesByGroup, dispatchMethods] = useQueries({
queries: [
Expand All @@ -30,10 +29,12 @@ export const NotificationsPage: FC = () => {
notificationDispatchMethods(),
],
});
const ready =
templatesByGroup.data && dispatchMethods.data && deploymentConfig;
const tab = searchParams.get("tab") || "events";
const tabState = useSearchParamsKey({
key: "tab",
defaultValue: "events",
});

const ready = !!(templatesByGroup.data && dispatchMethods.data);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: prefer Boolean(…) over !!(…), it makes it much clearer that you want a boolean cast

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So do I, but for some reason, when I tried to change things, it wasn't doing anything with type narrowing

return (
<>
<Helmet>
Expand All @@ -45,7 +46,7 @@ export const NotificationsPage: FC = () => {
layout="fluid"
featureStage={"beta"}
>
<Tabs active={tab}>
<Tabs active={tabState.value}>
<TabsList>
<TabLink to="?tab=events" value="events">
Events
Expand All @@ -58,7 +59,7 @@ export const NotificationsPage: FC = () => {

<div css={styles.content}>
{ready ? (
tab === "events" ? (
tabState.value === "events" ? (
<NotificationEvents
templatesByGroup={templatesByGroup.data}
deploymentConfig={deploymentConfig.config}
Expand All @@ -71,7 +72,7 @@ export const NotificationsPage: FC = () => {
/>
) : (
<OptionsTable
options={deploymentConfig?.options.filter((o) =>
options={deploymentConfig.options.filter((o) =>
deploymentGroupHasParent(o.group, "Notifications"),
)}
/>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { Loader } from "components/Loader/Loader";
import { useDashboard } from "modules/dashboard/useDashboard";
import { useFeatureVisibility } from "modules/dashboard/useFeatureVisibility";
import type { FC } from "react";
Expand All @@ -17,16 +16,11 @@ const ObservabilitySettingsPage: FC = () => {
<Helmet>
<title>{pageTitle("Observability Settings")}</title>
</Helmet>

{deploymentConfig ? (
<ObservabilitySettingsPageView
options={deploymentConfig.options}
featureAuditLogEnabled={entitlements.features.audit_log.enabled}
isPremium={hasPremiumLicense}
/>
) : (
<Loader />
)}
<ObservabilitySettingsPageView
options={deploymentConfig.options}
featureAuditLogEnabled={entitlements.features.audit_log.enabled}
isPremium={hasPremiumLicense}
/>
</>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,10 @@ const SecuritySettingsPage: FC = () => {
<Helmet>
<title>{pageTitle("Security Settings")}</title>
</Helmet>

{deploymentConfig ? (
<SecuritySettingsPageView
options={deploymentConfig.options}
featureBrowserOnlyEnabled={entitlements.features.browser_only.enabled}
/>
) : (
<Loader />
)}
<SecuritySettingsPageView
options={deploymentConfig.options}
featureBrowserOnlyEnabled={entitlements.features.browser_only.enabled}
/>
</>
);
};
Expand Down
Loading
Loading