Skip to content

Commit 18edb0d

Browse files
committed
feat: add export policy button
1 parent 7d62a49 commit 18edb0d

File tree

3 files changed

+117
-19
lines changed

3 files changed

+117
-19
lines changed
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import DownloadOutlined from "@mui/icons-material/DownloadOutlined";
2+
import Button from "@mui/material/Button";
3+
import type { OrganizationSyncSettings } from "api/typesGenerated";
4+
import { displayError } from "components/GlobalSnackbar/utils";
5+
import { saveAs } from "file-saver";
6+
import { type FC, useMemo, useState } from "react";
7+
8+
interface ExportPolicyButtonProps {
9+
syncSettings: OrganizationSyncSettings | undefined;
10+
download?: (file: Blob, filename: string) => void;
11+
}
12+
13+
export const ExportPolicyButton: FC<ExportPolicyButtonProps> = ({
14+
syncSettings,
15+
download = saveAs,
16+
}) => {
17+
const [isDownloading, setIsDownloading] = useState(false);
18+
19+
const policyJSON = useMemo(() => {
20+
return syncSettings?.field && syncSettings.mapping
21+
? JSON.stringify(syncSettings, null, 2)
22+
: null;
23+
}, [syncSettings]);
24+
console.log({ syncSettings });
25+
return (
26+
<Button
27+
startIcon={<DownloadOutlined />}
28+
disabled={!policyJSON || isDownloading}
29+
onClick={async () => {
30+
if (policyJSON) {
31+
try {
32+
setIsDownloading(true);
33+
const file = new Blob([policyJSON], {
34+
type: "application/json",
35+
});
36+
download(file, "organizations_policy.json");
37+
} catch (e) {
38+
console.error(e);
39+
displayError("Failed to export organizations policy json");
40+
} finally {
41+
setIsDownloading(false);
42+
}
43+
}
44+
}}
45+
>
46+
Export Policy
47+
</Button>
48+
);
49+
};

site/src/pages/DeploymentSettingsPage/IdpOrgSyncPage/IdpOrgSyncPage.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ export const IdpOrgSyncPage: FC = () => {
3434
);
3535

3636
const error = organizationIdpSyncSettingsQuery.error;
37-
37+
console.log({ organizationIdpSyncSettingsQuery });
3838
return (
3939
<>
4040
<Helmet>

site/src/pages/DeploymentSettingsPage/IdpOrgSyncPage/IdpOrgSyncPageView.tsx

Lines changed: 67 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ import { ChooseOne, Cond } from "components/Conditionals/ChooseOne";
1616
import { EmptyState } from "components/EmptyState/EmptyState";
1717
import { Loader } from "components/Loader/Loader";
1818
import { Stack } from "components/Stack/Stack";
19-
import { StatusIndicator } from "components/StatusIndicator/StatusIndicator";
2019
import {
2120
TableLoaderSkeleton,
2221
TableRowSkeleton,
@@ -34,7 +33,7 @@ import type { FC } from "react";
3433
import { MONOSPACE_FONT_FAMILY } from "theme/constants";
3534
import { docs } from "utils/docs";
3635
import { getFormHelpers, onChangeTrimmed } from "utils/formUtils";
37-
// import { ExportPolicyButton } from "./ExportPolicyButton";
36+
import { ExportPolicyButton } from "./ExportPolicyButton";
3837
import { IdpPillList } from "./IdpPillList";
3938

4039
interface IdpSyncPageViewProps {
@@ -62,11 +61,33 @@ export const IdpSyncPageView: FC<IdpSyncPageViewProps> = ({
6261
});
6362
const getFieldHelpers = getFormHelpers<OrganizationSyncSettings>(form, error);
6463
const [coderOrgs, setCoderOrgs] = useState<Option[]>([]);
65-
const [isChecked, setIsChecked] = useState(
66-
form.initialValues.organization_assign_default,
67-
);
68-
const organizationMappingCount = organizationSyncSettings?.mapping
69-
? Object.entries(organizationSyncSettings.mapping).length
64+
const [idpOrgName, setIdpOrgName] = useState("");
65+
const [syncSettings, setSyncSettings] = useState<
66+
OrganizationSyncSettings | undefined
67+
>(organizationSyncSettings);
68+
console.log({ organizationSyncSettings });
69+
// const newMapping: Record<string, string[]> =
70+
// organizationSyncSettings?.mapping !== undefined
71+
// ? Object.entries(organizationSyncSettings.mapping).reduce(
72+
// (acc, [key, value]) => {
73+
// acc[key] = value as string[];
74+
// return acc;
75+
// },
76+
// {} as Record<string, string[]>,
77+
// )
78+
// : {};
79+
// console.log({ newMapping });
80+
// const [mapping, setMapping] = useState<Record<string, readonly string[]>>(
81+
// organizationSyncSettings?.mapping || {},
82+
// );
83+
// const [isChecked, setIsChecked] = useState(
84+
// form.initialValues.organization_assign_default,
85+
// );
86+
// const organizationMappingCount = organizationSyncSettings?.mapping
87+
// ? Object.entries(organizationSyncSettings.mapping).length
88+
// : 0;
89+
const organizationMappingCount = syncSettings?.mapping
90+
? Object.entries(syncSettings.mapping).length
7091
: 0;
7192

7293
if (error) {
@@ -82,23 +103,40 @@ export const IdpSyncPageView: FC<IdpSyncPageViewProps> = ({
82103
value: org.id,
83104
}));
84105

106+
const getOrgNames = (orgIds: readonly string[]) => {
107+
return orgIds.map(
108+
(orgId) =>
109+
organizations.find((org) => org.id === orgId)?.display_name || orgId,
110+
);
111+
};
112+
85113
return (
86114
<>
87115
<Stack spacing={2}>
88116
<form onSubmit={form.handleSubmit}>
89117
<Stack direction="row" alignItems="center">
90118
<TextField
91119
{...getFieldHelpers("field")}
120+
value={syncSettings?.field}
92121
autoFocus
93122
fullWidth
94123
label="Organization Sync Field"
95124
className="w-72"
125+
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
126+
setSyncSettings({
127+
...(syncSettings as OrganizationSyncSettings),
128+
field: event.target.value,
129+
});
130+
}}
96131
/>
97132
<Switch
98133
id="organization-assign-default"
99-
checked={isChecked}
134+
checked={syncSettings?.organization_assign_default}
100135
onCheckedChange={async (checked) => {
101-
setIsChecked(checked);
136+
setSyncSettings({
137+
...(syncSettings as OrganizationSyncSettings),
138+
organization_assign_default: checked,
139+
});
102140
await form.setFieldValue(
103141
"organization_assign_default",
104142
checked,
@@ -115,15 +153,16 @@ export const IdpSyncPageView: FC<IdpSyncPageViewProps> = ({
115153
justifyContent="space-between"
116154
css={styles.tableInfo}
117155
>
118-
{/* <ExportPolicyButton
119-
syncSettings={groupSyncSettings}
120-
organization={organization}
121-
type="groups"
122-
/> */}
156+
<ExportPolicyButton syncSettings={syncSettings} />
123157
</Stack>
124158

125159
<div className="flex flex-row py-10 gap-2 justify-between">
126160
<TextField
161+
id="idp-organization-name"
162+
value={idpOrgName}
163+
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
164+
setIdpOrgName(event.target.value);
165+
}}
127166
autoFocus
128167
fullWidth
129168
label="Idp organization name"
@@ -144,8 +183,18 @@ export const IdpSyncPageView: FC<IdpSyncPageViewProps> = ({
144183
/>
145184
<Button
146185
className="mt-px"
147-
onClick={(event) => {
186+
onClick={() => {
148187
console.log("add Idp organization");
188+
189+
setSyncSettings({
190+
...(syncSettings as OrganizationSyncSettings),
191+
mapping: {
192+
...syncSettings?.mapping,
193+
[idpOrgName]: coderOrgs.map((org) => org.value),
194+
},
195+
});
196+
setIdpOrgName("");
197+
setCoderOrgs([]);
149198
}}
150199
>
151200
<Plus size={14} />
@@ -155,14 +204,14 @@ export const IdpSyncPageView: FC<IdpSyncPageViewProps> = ({
155204

156205
<Stack spacing={6}>
157206
<IdpMappingTable isEmpty={organizationMappingCount === 0}>
158-
{organizationSyncSettings?.mapping &&
159-
Object.entries(organizationSyncSettings.mapping)
207+
{syncSettings?.mapping &&
208+
Object.entries(syncSettings.mapping)
160209
.sort()
161210
.map(([idpOrg, organizations]) => (
162211
<OrganizationRow
163212
key={idpOrg}
164213
idpOrg={idpOrg}
165-
coderOrgs={organizations}
214+
coderOrgs={getOrgNames(organizations)}
166215
/>
167216
))}
168217
</IdpMappingTable>

0 commit comments

Comments
 (0)