8000 feat: integrate backend with idp sync page by jaaydenh · Pull Request #14755 · coder/coder · GitHub
[go: up one dir, main page]

Skip to content

feat: integrate backend with idp sync page #14755

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 13 commits into from
Sep 24, 2024
Prev Previous commit
Next Next commit
feat: implement export policy button
  • Loading branch information
jaaydenh committed Sep 22, 2024
commit 615c8d38709a8fd27470441f2f88bfa7f2993a4a
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,12 @@ import { Link as RouterLink, useNavigate } from "react-router-dom";
import { docs } from "utils/docs";
import { PermissionPillsList } from "./PermissionPillsList";

export type CustomRolesPageViewProps = {
interface CustomRolesPageViewProps {
Copy link
Member

Choose a reason for hiding this comment

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

Have we decided on whether we prefer interfaces over type aliases? Asking because I generally avoid interfaces so that I can't do accidental interface merging, but I've been seeing a lot of recent code using interfaces

Copy link
Contributor Author

Choose a reason for hiding this comment

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

My goal was just to be consistent and I saw more interfaces used for all the organization related pages. I don't personally have a strong preference other than being consistent.

Copy link
Member

Choose a reason for hiding this comment

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

I like interface because I think interface WibbleProps extends WobbleProps {} is a lot clearer than type WibbleProps = WobbleProps & {} in the places where we need it. and I think we just generally use it in more places rn.

roles: Role[] | undefined;
onDeleteRole: (role: Role) => void;
canAssignOrgRole: boolean;
isCustomRolesEnabled: boolean;
};
}

export const CustomRolesPageView: FC<CustomRolesPageViewProps> = ({
roles,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import DownloadOutlined from "@mui/icons-material/DownloadOutlined";
import Button from "@mui/material/Button";
import type { Organization } from "api/typesGenerated";
import { displayError } from "components/GlobalSnackbar/utils";
import { saveAs } from "file-saver";
import { type FC, useState } from "react";

interface DownloadPolicyButtonProps {
policy: string | null;
Copy link
Member

Choose a reason for hiding this comment

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

Is there any information on how policies are defined? I'm seeing that we're downloading just the policy itself when the user clicks the button

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The policy is meant to be a json representation of GroupSyncSettings and RoleSyncSettings so the goal was to make ExportPolicyButton work for both types

type: "groups" | "roles";
organization: Organization;
}

export const ExportPolicyButton: FC<DownloadPolicyButtonProps> = ({
policy,
type,
organization,
}) => {
const [isDownloading, setIsDownloading] = useState(false);

return (
<Button
startIcon={<DownloadOutlined />}
disabled={!policy || isDownloading}
onClick={async () => {
if (policy) {
try {
setIsDownloading(true);
const file = new Blob([policy], {
type: "application/json",
});
saveAs(file, `${organization.name}_${type}-policy.json`);
} catch (e) {
console.error(e);
displayError("Failed to export policy json");
} finally {
setIsDownloading(false);
}
}
}}
>
Export Policy
</Button>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ export const IdpSyncPage: FC = () => {
groupSyncSettings={groupIdpSyncSettingsQuery.data}
roleSyncSettings={roleIdpSyncSettingsQuery.data}
groups={groupsQuery.data}
organization={organization}
/>
</>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import type { Interpolation, Theme } from "@emotion/react";
import AddIcon from "@mui/icons-material/AddOutlined";
import LaunchOutlined from "@mui/icons-material/LaunchOutlined";
import Button from "@mui/material/Button";
import Skeleton from "@mui/material/Skeleton";
Expand All @@ -12,6 +11,7 @@ import TableRow from "@mui/material/TableRow";
import type {
Group,
GroupSyncSettings,
Organization,
RoleSyncSettings,
} from "api/typesGenerated";
import { ChooseOne, Cond } from "components/Conditionals/ChooseOne";
Expand All @@ -28,18 +28,21 @@ import type { FC } from "react";
import { useSearchParams } from "react-router-dom";
import { MONOSPACE_FONT_FAMILY } from "theme/constants";
import { docs } from "utils/docs";
import { ExportPolicyButton } from "./ExportPolicyButton";
import { PillList } from "./PillList";

export type IdpSyncPageViewProps = {
interface IdpSyncPageViewProps {
groupSyncSettings: GroupSyncSettings | undefined;
roleSyncSettings: RoleSyncSettings | undefined;
groups: Group[] | undefined;
};
organization: Organization;
}

export const IdpSyncPageView: FC<IdpSyncPageViewProps> = ({
Copy link
Member

Choose a reason for hiding this comment

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

Have to put the comment here because the code was already in place previously, and it's not selectable for review:

I think we need to add some CSS optical adjustments to the key-value pairs for the IdpField component. Since we're using two different typefaces, their baselines are slightly different. And when you place them right next to each other on the same line, there's enough variation that the text looks sloppy

You might have to open this in a new tab, but the baselines are ever so slightly off, and the cap-heights are, too:
Screenshot 2024-09-23 at 12 21 28 PM

Copy link
Member

Choose a reason for hiding this comment

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

Though a slightly easier and probably much more manageable alternative would be to set all the text in monospace

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The monospace font was supposed to be a temporary measure until this page becomes a form and these would become fields. I'll see if I can improve it for now.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

as a temporary fix, I can add some padding to make the baselines match. I really wanted to use the monospace font on only the field value to signify that it is the value of the configuration.

groupSyncSettings,
roleSyncSettings,
groups,
organization,
}) => {
const [searchParams] = useSearchParams();
const groupsMap = new Map<string, string>();
Expand All @@ -62,6 +65,15 @@ export const IdpSyncPageView: FC<IdpSyncPageViewProps> = ({
? Object.entries(roleSyncSettings.mapping).length
: 0;

const rolePolicy =
roleSyncSettings?.field && roleSyncSettings.mapping
? JSON.stringify(roleSyncSettings, null, 2)
: null;
const groupPolicy =
groupSyncSettings?.field && groupSyncSettings.mapping
? JSON.stringify(groupSyncSettings, null, 2)
: null;
Copy link
Member

Choose a reason for hiding this comment

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

This is dangerous – we need to be super, super careful with any JSON.stringify calls inside components:

  1. Stringifying on every render can be slow
  2. JSON.stringify can throw, so if it fails, we might blow up the entire app


return (
<>
<ChooseOne>
Expand All @@ -77,7 +89,7 @@ export const IdpSyncPageView: FC<IdpSyncPageViewProps> = ({
<Tabs
active={tab}
css={{
marginBottom: 24,
marginBottom: 20,
}}
>
<TabsList>
Expand Down Expand Up @@ -121,14 +133,11 @@ export const IdpSyncPageView: FC<IdpSyncPageViewProps> = ({
css={styles.tableInfo}
>
<TableRowCount count={groupMappingCount} type="groups" />
<Button
component="a"
startIcon={<AddIcon />}
// to="export"
href={docs("/admin/auth#group-sync-enterprise")}
>
Export Policy
</Button>
<ExportPolicyButton
policy={groupPolicy}
organization={organization}
type="groups"
/>
</Stack>
<Stack spacing={6}>
<IdpMappingTable
Expand Down Expand Up @@ -164,14 +173,11 @@ export const IdpSyncPageView: FC<IdpSyncPageViewProps> = ({
css={styles.tableInfo}
>
<TableRowCount count={roleMappingCount} type="roles" />
<Button
component="a"
startIcon={<AddIcon />}
// to="export"
href={docs("/admin/auth#group-sync-enterprise")}
>
Export Policy
</Button>
<ExportPolicyButton
policy={rolePolicy}
organization={organization}
type="roles"
/>
</Stack>
<IdpMappingTable
type="Role"
Expand Down Expand Up @@ -210,7 +216,7 @@ const IdpField: FC<IdpFieldProps> = ({
}) => {
return (
<span css={{ display: "flex", alignItems: "center", gap: "16px" }}>
<p>{name}</p>
<p css={styles.fieldLabel}>{name}</p>
{fieldText ? (
<p css={styles.fieldText}>{fieldText}</p>
) : (
Expand Down Expand Up @@ -365,12 +371,16 @@ const TableLoader = () => {

const styles = {
fieldText: (theme) => ({
color: theme.palette.text.secondary,
fontFamily: MONOSPACE_FONT_FAMILY,
whiteSpace: "nowrap",
}),
fieldLabel: (theme) => ({
color: theme.palette.text.secondary,
}),
fields: () => ({
marginBottom: 20,
marginBottom: 16,
marginLeft: 16,
fontSize: 14,
}),
tableInfo: () => ({
marginBottom: 16,
Expand Down
Loading
0