forked from baron/baron-sso
feat: improve Worksmobile tenant sync handling
This commit is contained in:
@@ -4,6 +4,7 @@ import {
|
||||
mergeTenantOrgConfig,
|
||||
ORG_UNIT_TYPE_OPTIONS,
|
||||
readTenantOrgConfig,
|
||||
removeTenantOrgConfig,
|
||||
shouldAllowHanmacOrgConfig,
|
||||
} from "./orgConfig";
|
||||
|
||||
@@ -49,17 +50,69 @@ describe("tenant org config", () => {
|
||||
it("reads and writes tenant visibility and org unit type", () => {
|
||||
expect(
|
||||
readTenantOrgConfig({ visibility: "private", orgUnitType: "팀" }),
|
||||
).toEqual({ orgUnitType: "팀", visibility: "private" });
|
||||
).toEqual({
|
||||
orgUnitType: "팀",
|
||||
visibility: "private",
|
||||
worksmobileExcluded: false,
|
||||
});
|
||||
expect(
|
||||
readTenantOrgConfig({ visibility: "internal", orgUnitType: "센터" }),
|
||||
).toEqual({ orgUnitType: "센터", visibility: "internal" });
|
||||
).toEqual({
|
||||
orgUnitType: "센터",
|
||||
visibility: "internal",
|
||||
worksmobileExcluded: false,
|
||||
});
|
||||
|
||||
expect(
|
||||
mergeTenantOrgConfig(
|
||||
{ userSchema: [], visibility: "private", orgUnitType: "팀" },
|
||||
{ orgUnitType: "", visibility: "internal" },
|
||||
{
|
||||
orgUnitType: "",
|
||||
visibility: "internal",
|
||||
worksmobileExcluded: false,
|
||||
},
|
||||
),
|
||||
).toEqual({ userSchema: [], visibility: "internal" });
|
||||
).toEqual({
|
||||
userSchema: [],
|
||||
visibility: "internal",
|
||||
worksmobileExcluded: false,
|
||||
});
|
||||
});
|
||||
|
||||
it("reads, writes, and removes the Worksmobile exclusion flag", () => {
|
||||
expect(
|
||||
readTenantOrgConfig({ worksmobileExcluded: true }),
|
||||
).toMatchObject({
|
||||
worksmobileExcluded: true,
|
||||
});
|
||||
expect(
|
||||
readTenantOrgConfig({ worksmobileExcluded: "true" }),
|
||||
).toMatchObject({
|
||||
worksmobileExcluded: true,
|
||||
});
|
||||
expect(
|
||||
mergeTenantOrgConfig(
|
||||
{ userSchema: [], worksmobileExcluded: false },
|
||||
{
|
||||
orgUnitType: "팀",
|
||||
visibility: "private",
|
||||
worksmobileExcluded: true,
|
||||
},
|
||||
),
|
||||
).toEqual({
|
||||
userSchema: [],
|
||||
orgUnitType: "팀",
|
||||
visibility: "private",
|
||||
worksmobileExcluded: true,
|
||||
});
|
||||
expect(
|
||||
removeTenantOrgConfig({
|
||||
userSchema: [],
|
||||
orgUnitType: "팀",
|
||||
visibility: "private",
|
||||
worksmobileExcluded: true,
|
||||
}),
|
||||
).toEqual({ userSchema: [] });
|
||||
});
|
||||
|
||||
it("includes task-force and executive-direct org unit types", () => {
|
||||
|
||||
@@ -14,6 +14,13 @@ export const ORG_UNIT_TYPE_OPTIONS = [
|
||||
"임원직속",
|
||||
] as const;
|
||||
|
||||
export const USER_GROUP_ORG_UNIT_TYPE_OPTIONS = [
|
||||
"팀",
|
||||
"TF",
|
||||
"TF팀",
|
||||
"셀",
|
||||
] as const;
|
||||
|
||||
export const TENANT_VISIBILITY_OPTIONS = [
|
||||
{ label: "공개", value: "public" },
|
||||
{ label: "내부", value: "internal" },
|
||||
@@ -26,6 +33,7 @@ export type TenantVisibility =
|
||||
export type TenantOrgConfig = {
|
||||
orgUnitType: string;
|
||||
visibility: TenantVisibility;
|
||||
worksmobileExcluded: boolean;
|
||||
};
|
||||
|
||||
const ORG_UNIT_TYPE_SET = new Set<string>(ORG_UNIT_TYPE_OPTIONS);
|
||||
@@ -55,17 +63,29 @@ export function shouldAllowHanmacOrgConfig(
|
||||
return false;
|
||||
}
|
||||
|
||||
export function getOrgUnitTypeOptionsForTenantType(type: string) {
|
||||
return type === "USER_GROUP"
|
||||
? USER_GROUP_ORG_UNIT_TYPE_OPTIONS
|
||||
: ORG_UNIT_TYPE_OPTIONS;
|
||||
}
|
||||
|
||||
export function readTenantOrgConfig(
|
||||
config: Record<string, unknown> | undefined,
|
||||
): TenantOrgConfig {
|
||||
const rawVisibility = String(config?.visibility ?? "public").toLowerCase();
|
||||
const rawOrgUnitType = String(config?.orgUnitType ?? "");
|
||||
const rawWorksmobileExcluded = config?.worksmobileExcluded;
|
||||
|
||||
return {
|
||||
orgUnitType: ORG_UNIT_TYPE_SET.has(rawOrgUnitType) ? rawOrgUnitType : "",
|
||||
visibility: TENANT_VISIBILITY_SET.has(rawVisibility)
|
||||
? (rawVisibility as TenantVisibility)
|
||||
: "public",
|
||||
worksmobileExcluded:
|
||||
rawWorksmobileExcluded === true ||
|
||||
String(rawWorksmobileExcluded ?? "")
|
||||
.trim()
|
||||
.toLowerCase() === "true",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -76,6 +96,7 @@ export function mergeTenantOrgConfig(
|
||||
const { orgUnitType: _orgUnitType, ...rest } = config ?? {};
|
||||
const merged = { ...rest };
|
||||
merged.visibility = next.visibility;
|
||||
merged.worksmobileExcluded = next.worksmobileExcluded;
|
||||
|
||||
if (next.orgUnitType) {
|
||||
merged.orgUnitType = next.orgUnitType;
|
||||
@@ -90,6 +111,7 @@ export function removeTenantOrgConfig(
|
||||
const {
|
||||
orgUnitType: _orgUnitType,
|
||||
visibility: _visibility,
|
||||
worksmobileExcluded: _worksmobileExcluded,
|
||||
...rest
|
||||
} = config ?? {};
|
||||
return rest;
|
||||
|
||||
@@ -66,7 +66,7 @@ describe("tenantCsvImport", () => {
|
||||
|
||||
it("parses tenant CSV rows with the supported import columns", () => {
|
||||
const rows = parseTenantCSV(
|
||||
"tenant_id,name,type,parent_tenant_id,slug,memo,email_domain,visibility,org_unit_type\n,Hanmac Tech,COMPANY,,hanmac-tech,Memo,hanmac-tech.example.com,internal,센터\n",
|
||||
"tenant_id,name,type,parent_tenant_id,slug,memo,email_domain,visibility,org_unit_type,worksmobile_sync\n,Hanmac Tech,COMPANY,,hanmac-tech,Memo,hanmac-tech.example.com,internal,센터,no\n",
|
||||
);
|
||||
|
||||
expect(rows).toEqual([
|
||||
@@ -82,6 +82,7 @@ describe("tenantCsvImport", () => {
|
||||
emailDomain: "hanmac-tech.example.com",
|
||||
visibility: "internal",
|
||||
orgUnitType: "센터",
|
||||
worksmobileSync: "no",
|
||||
},
|
||||
]);
|
||||
});
|
||||
@@ -111,7 +112,7 @@ describe("tenantCsvImport", () => {
|
||||
|
||||
it("serializes selected matches by filling tenant_id before upload", () => {
|
||||
const rows = parseTenantCSV(
|
||||
"tenant_id,name,type,parent_tenant_id,slug,memo,email_domain,visibility,org_unit_type\n,Hanmac Tech,COMPANY,,hanmac-tech,Memo,hanmac-tech.example.com,private,팀\n",
|
||||
"tenant_id,name,type,parent_tenant_id,slug,memo,email_domain,visibility,org_unit_type,worksmobile_sync\n,Hanmac Tech,COMPANY,,hanmac-tech,Memo,hanmac-tech.example.com,private,팀,no\n",
|
||||
);
|
||||
const preview = buildTenantImportPreview(rows, tenants);
|
||||
const csv = serializeTenantImportCSV(preview, {
|
||||
@@ -119,10 +120,10 @@ describe("tenantCsvImport", () => {
|
||||
});
|
||||
|
||||
expect(csv.split("\n")[0]).toBe(
|
||||
"tenant_id,name,type,parent_tenant_id,parent_tenant_slug,slug,memo,email_domain,visibility,org_unit_type",
|
||||
"tenant_id,name,type,parent_tenant_id,parent_tenant_slug,slug,memo,email_domain,visibility,org_unit_type,worksmobile_sync",
|
||||
);
|
||||
expect(csv).toContain(
|
||||
"tenant-1,Hanmac Tech,COMPANY,,,hanmac-tech,Memo,hanmac-tech.example.com,private,팀",
|
||||
"tenant-1,Hanmac Tech,COMPANY,,,hanmac-tech,Memo,hanmac-tech.example.com,private,팀,no",
|
||||
);
|
||||
});
|
||||
|
||||
@@ -253,10 +254,10 @@ ${exportedTenantId},Tenant With UUID,COMPANY,,,tenant-with-uuid,Memo,tenant-with
|
||||
});
|
||||
|
||||
expect(csv.split("\n")[0]).toBe(
|
||||
"tenant_id,name,type,parent_tenant_id,parent_tenant_slug,slug,memo,email_domain,visibility,org_unit_type",
|
||||
"tenant_id,name,type,parent_tenant_id,parent_tenant_slug,slug,memo,email_domain,visibility,org_unit_type,worksmobile_sync",
|
||||
);
|
||||
expect(csv).toContain(
|
||||
"staging-child-id,Child Tenant,ORGANIZATION,staging-parent-id,parent-slug,child-slug,,",
|
||||
"staging-child-id,Child Tenant,ORGANIZATION,staging-parent-id,parent-slug,child-slug,,,,,yes",
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ export type TenantCSVRow = {
|
||||
emailDomain: string;
|
||||
visibility: string;
|
||||
orgUnitType: string;
|
||||
worksmobileSync: string;
|
||||
};
|
||||
|
||||
export type TenantCSVParseOptions = {
|
||||
@@ -80,6 +81,7 @@ const importHeaders = [
|
||||
"email_domain",
|
||||
"visibility",
|
||||
"org_unit_type",
|
||||
"worksmobile_sync",
|
||||
];
|
||||
|
||||
const headerAliases: Record<string, TenantCSVSourceKey> = {
|
||||
@@ -116,6 +118,11 @@ const headerAliases: Record<string, TenantCSVSourceKey> = {
|
||||
organization_type: "orgUnitType",
|
||||
orgtype: "orgUnitType",
|
||||
org_type: "orgUnitType",
|
||||
worksmobile: "worksmobileSync",
|
||||
worksmobilesync: "worksmobileSync",
|
||||
worksmobile_sync: "worksmobileSync",
|
||||
works_sync: "worksmobileSync",
|
||||
works: "worksmobileSync",
|
||||
};
|
||||
|
||||
export function parseTenantCSV(
|
||||
@@ -175,6 +182,7 @@ export function parseTenantCSV(
|
||||
emailDomain: value("emailDomain"),
|
||||
visibility: value("visibility"),
|
||||
orgUnitType: value("orgUnitType"),
|
||||
worksmobileSync: normalizeWorksmobileSync(value("worksmobileSync")),
|
||||
};
|
||||
});
|
||||
}
|
||||
@@ -305,6 +313,7 @@ export function serializeTenantImportCSV(
|
||||
preview.row.emailDomain,
|
||||
preview.row.visibility,
|
||||
preview.row.orgUnitType,
|
||||
preview.row.worksmobileSync || "yes",
|
||||
]);
|
||||
}
|
||||
return `${lines.map(formatCSVRecord).join("\n")}\n`;
|
||||
@@ -528,6 +537,30 @@ function normalizeHeader(value: string) {
|
||||
return value.trim().toLowerCase().replaceAll(" ", "_");
|
||||
}
|
||||
|
||||
function normalizeWorksmobileSync(value: string) {
|
||||
const normalized = value.trim().toLowerCase();
|
||||
if (
|
||||
[
|
||||
"no",
|
||||
"n",
|
||||
"false",
|
||||
"0",
|
||||
"off",
|
||||
"none",
|
||||
"excluded",
|
||||
"exclude",
|
||||
"not_sync",
|
||||
"not-synced",
|
||||
"미연동",
|
||||
"연동안함",
|
||||
"제외",
|
||||
].includes(normalized)
|
||||
) {
|
||||
return "no";
|
||||
}
|
||||
return "yes";
|
||||
}
|
||||
|
||||
function slugFromMailingList(value: string) {
|
||||
if (!value) return "";
|
||||
return normalizeTenantSlug(value.split("@")[0] ?? value);
|
||||
|
||||
Reference in New Issue
Block a user