forked from baron/baron-sso
Update dev workflow and org chart settings
This commit is contained in:
@@ -106,10 +106,20 @@ describe("buildOrgPickerTree", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it("excludes private tenants and their descendants from picker choices", () => {
|
||||
it("excludes internal and private tenants from picker choices by default", () => {
|
||||
const tenants = [
|
||||
tenant("hanmac-family-id", "COMPANY_GROUP", "한맥가족", "hanmac-family"),
|
||||
tenant("saman-id", "COMPANY", "삼안", "saman", "hanmac-family-id"),
|
||||
{
|
||||
...tenant(
|
||||
"internal-id",
|
||||
"ORGANIZATION",
|
||||
"내부 조직",
|
||||
"internal",
|
||||
"saman-id",
|
||||
),
|
||||
config: { visibility: "internal" },
|
||||
},
|
||||
{
|
||||
...tenant(
|
||||
"secret-id",
|
||||
@@ -138,4 +148,34 @@ describe("buildOrgPickerTree", () => {
|
||||
|
||||
expect(tree.roots[0]?.children.map((node) => node.id)).toEqual(["open-id"]);
|
||||
});
|
||||
|
||||
it("includes internal tenants when explicitly requested", () => {
|
||||
const tenants = [
|
||||
tenant("hanmac-family-id", "COMPANY_GROUP", "한맥가족", "hanmac-family"),
|
||||
tenant("saman-id", "COMPANY", "삼안", "saman", "hanmac-family-id"),
|
||||
{
|
||||
...tenant(
|
||||
"internal-id",
|
||||
"ORGANIZATION",
|
||||
"내부 조직",
|
||||
"internal",
|
||||
"saman-id",
|
||||
),
|
||||
config: { visibility: "internal" },
|
||||
},
|
||||
tenant("open-id", "ORGANIZATION", "공개 조직", "open", "saman-id"),
|
||||
];
|
||||
|
||||
const tree = buildOrgPickerTree({
|
||||
includeInternal: true,
|
||||
tenants,
|
||||
users: [] satisfies UserSummary[],
|
||||
tenantId: "saman",
|
||||
});
|
||||
|
||||
expect(tree.roots[0]?.children.map((node) => node.id)).toEqual([
|
||||
"internal-id",
|
||||
"open-id",
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -108,11 +108,13 @@ function findTenantNode(
|
||||
}
|
||||
|
||||
export function buildOrgPickerTree({
|
||||
includeInternal = false,
|
||||
tenants,
|
||||
users,
|
||||
rootTenantId,
|
||||
tenantId,
|
||||
}: {
|
||||
includeInternal?: boolean;
|
||||
tenants: TenantSummary[];
|
||||
users: UserSummary[];
|
||||
rootTenantId?: string;
|
||||
@@ -120,7 +122,7 @@ export function buildOrgPickerTree({
|
||||
}) {
|
||||
const visibleTenants = filterTenantsByVisibility(
|
||||
tenants.filter(isOrgFrontTenantType),
|
||||
"internal",
|
||||
includeInternal ? "internal" : "public",
|
||||
);
|
||||
const usersBySlug = new Map<string, UserSummary[]>();
|
||||
for (const user of users) {
|
||||
|
||||
@@ -8,6 +8,7 @@ describe("org picker embed options", () => {
|
||||
it("builds slug-based tenant scope urls", () => {
|
||||
expect(
|
||||
buildOrgPickerEmbedSrc({
|
||||
includeInternal: false,
|
||||
mode: "single",
|
||||
select: "tenant",
|
||||
includeDescendants: true,
|
||||
@@ -21,6 +22,27 @@ describe("org picker embed options", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("builds and parses internal visibility flag only when requested", () => {
|
||||
const src = buildOrgPickerEmbedSrc({
|
||||
includeInternal: true,
|
||||
mode: "single",
|
||||
select: "tenant",
|
||||
includeDescendants: true,
|
||||
showDescendantToggle: true,
|
||||
tenantId: "",
|
||||
width: 400,
|
||||
height: 600,
|
||||
});
|
||||
|
||||
expect(src).toBe(
|
||||
"/embed/picker?mode=single&select=tenant&width=400&height=600&includeInternal=true",
|
||||
);
|
||||
expect(parseOrgPickerEmbedOptions(src).includeInternal).toBe(true);
|
||||
expect(parseOrgPickerEmbedOptions("?mode=single").includeInternal).toBe(
|
||||
false,
|
||||
);
|
||||
});
|
||||
|
||||
it("parses tenantSlug first and keeps legacy tenantId compatibility", () => {
|
||||
expect(
|
||||
parseOrgPickerEmbedOptions(
|
||||
|
||||
@@ -16,6 +16,7 @@ export type OrgPickerResult = {
|
||||
};
|
||||
|
||||
export type OrgPickerEmbedOptions = {
|
||||
includeInternal: boolean;
|
||||
mode: OrgPickerMode;
|
||||
select: OrgPickerSelectableType;
|
||||
includeDescendants: boolean;
|
||||
@@ -68,6 +69,7 @@ export function parseOrgPickerEmbedOptions(search: string) {
|
||||
? ("single" as const)
|
||||
: ("multiple" as const),
|
||||
select: parseOrgPickerSelectableType(params.get("select")),
|
||||
includeInternal: params.get("includeInternal") === "true",
|
||||
includeDescendants: params.get("includeDescendants") !== "false",
|
||||
showDescendantToggle: params.get("showDescendantToggle") !== "false",
|
||||
tenantId:
|
||||
@@ -87,6 +89,9 @@ export function buildOrgPickerEmbedSrc(options: OrgPickerEmbedOptions) {
|
||||
width: String(options.width),
|
||||
height: String(options.height),
|
||||
});
|
||||
if (options.includeInternal) {
|
||||
params.set("includeInternal", "true");
|
||||
}
|
||||
|
||||
const tenantSlug = options.tenantId.trim();
|
||||
if (tenantSlug) {
|
||||
|
||||
@@ -1200,6 +1200,8 @@ export function TenantOrgChartPage() {
|
||||
const location = useLocation();
|
||||
const searchParams = new URLSearchParams(location.search);
|
||||
const shareToken = searchParams.get("token");
|
||||
const visibilityMode =
|
||||
searchParams.get("includeInternal") === "true" ? "internal" : "public";
|
||||
const [selectedTenantFilter, setSelectedTenantFilter] =
|
||||
React.useState(FAMILY_FILTER_ID);
|
||||
const [collapsedIds, setCollapsedIds] = React.useState<Set<string>>(
|
||||
@@ -1270,7 +1272,7 @@ export function TenantOrgChartPage() {
|
||||
}
|
||||
|
||||
const rootNodes = buildTenantFullTree(
|
||||
filterSystemGlobalTenants(tenantsQuery.data.items, "internal"),
|
||||
filterSystemGlobalTenants(tenantsQuery.data.items, visibilityMode),
|
||||
).subTree;
|
||||
|
||||
return {
|
||||
@@ -1280,7 +1282,13 @@ export function TenantOrgChartPage() {
|
||||
}),
|
||||
sharedWith: "",
|
||||
};
|
||||
}, [publicQuery.data, shareToken, tenantsQuery.data, usersQuery.data]);
|
||||
}, [
|
||||
publicQuery.data,
|
||||
shareToken,
|
||||
tenantsQuery.data,
|
||||
usersQuery.data,
|
||||
visibilityMode,
|
||||
]);
|
||||
|
||||
const familyRoot = React.useMemo(() => {
|
||||
return (
|
||||
|
||||
@@ -22,7 +22,7 @@ function PickerScenarioControls({
|
||||
onChange: (options: OrgPickerEmbedOptions) => void;
|
||||
}) {
|
||||
return (
|
||||
<div className="grid gap-3 rounded-md border border-border bg-card p-4 md:grid-cols-2 lg:grid-cols-[1fr,1fr,1fr,auto,auto,auto,auto] lg:items-end">
|
||||
<div className="grid gap-3 rounded-md border border-border bg-card p-4 md:grid-cols-2 lg:grid-cols-[1fr,1fr,1fr,auto,auto,auto,auto,auto] lg:items-end">
|
||||
<label className="space-y-1 text-sm font-medium">
|
||||
<span className="block text-muted-foreground">선택 모드</span>
|
||||
<select
|
||||
@@ -104,6 +104,20 @@ function PickerScenarioControls({
|
||||
<span>하위 선택 스위치 표시</span>
|
||||
</label>
|
||||
|
||||
<label className="flex h-10 items-center gap-2 rounded-md border border-border bg-background px-3 text-sm">
|
||||
<input
|
||||
checked={options.includeInternal}
|
||||
onChange={(event) =>
|
||||
onChange({
|
||||
...options,
|
||||
includeInternal: event.target.checked,
|
||||
})
|
||||
}
|
||||
type="checkbox"
|
||||
/>
|
||||
<span>internal 표시</span>
|
||||
</label>
|
||||
|
||||
<label className="space-y-1 text-sm font-medium">
|
||||
<span className="block text-muted-foreground">임베딩 너비</span>
|
||||
<input
|
||||
|
||||
@@ -333,6 +333,7 @@ export function OrgPickerEmbedPage() {
|
||||
const mode = parseOrgPickerMode(searchParams.get("mode"));
|
||||
const select = parseOrgPickerSelectableType(searchParams.get("select"));
|
||||
const rootTenantId = searchParams.get("rootTenantId") || undefined;
|
||||
const includeInternal = searchParams.get("includeInternal") === "true";
|
||||
const tenantId =
|
||||
searchParams.get("tenantSlug") ||
|
||||
searchParams.get("tenantId") ||
|
||||
@@ -363,12 +364,20 @@ export function OrgPickerEmbedPage() {
|
||||
|
||||
const tree = React.useMemo(() => {
|
||||
return buildOrgPickerTree({
|
||||
includeInternal,
|
||||
tenants: tenantsQuery.data?.items ?? [],
|
||||
users: select === "tenant" ? [] : (usersQuery.data?.items ?? []),
|
||||
rootTenantId,
|
||||
tenantId,
|
||||
});
|
||||
}, [rootTenantId, select, tenantId, tenantsQuery.data, usersQuery.data]);
|
||||
}, [
|
||||
includeInternal,
|
||||
rootTenantId,
|
||||
select,
|
||||
tenantId,
|
||||
tenantsQuery.data,
|
||||
usersQuery.data,
|
||||
]);
|
||||
|
||||
const selectedItems = React.useMemo(
|
||||
() =>
|
||||
@@ -579,7 +588,7 @@ export function OrgPickerPage() {
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<section className="grid gap-3 rounded-md border border-border bg-card p-4 md:grid-cols-2 lg:grid-cols-[1fr,1fr,1fr,auto,auto,auto,auto] lg:items-end">
|
||||
<section className="grid gap-3 rounded-md border border-border bg-card p-4 md:grid-cols-2 lg:grid-cols-[1fr,1fr,1fr,auto,auto,auto,auto,auto] lg:items-end">
|
||||
<label className="space-y-1 text-sm font-medium">
|
||||
<span className="block text-muted-foreground">선택 모드</span>
|
||||
<select
|
||||
@@ -661,6 +670,20 @@ export function OrgPickerPage() {
|
||||
<span>하위 선택 스위치 표시</span>
|
||||
</label>
|
||||
|
||||
<label className="flex h-10 items-center gap-2 rounded-md border border-border bg-background px-3 text-sm">
|
||||
<input
|
||||
checked={options.includeInternal}
|
||||
onChange={(event) =>
|
||||
setOptions((current) => ({
|
||||
...current,
|
||||
includeInternal: event.target.checked,
|
||||
}))
|
||||
}
|
||||
type="checkbox"
|
||||
/>
|
||||
<span>internal 표시</span>
|
||||
</label>
|
||||
|
||||
<label className="space-y-1 text-sm font-medium">
|
||||
<span className="block text-muted-foreground">임베딩 너비</span>
|
||||
<input
|
||||
|
||||
Reference in New Issue
Block a user