1
0
forked from baron/baron-sso

네이버 웍스 연동기능 개선

This commit is contained in:
2026-05-18 15:36:30 +09:00
parent c71ece84b8
commit e29d056b9e
61 changed files with 4137 additions and 710 deletions

View File

@@ -22,6 +22,17 @@ vi.mock("../../lib/adminApi", () => ({
})),
fetchAllTenants: vi.fn(async () => ({
items: [
{
id: "group-1",
type: "COMPANY_GROUP",
name: "한맥그룹",
slug: "hanmac-group",
description: "",
status: "active",
memberCount: 0,
createdAt: "2026-05-06T00:00:00Z",
updatedAt: "2026-05-06T00:00:00Z",
},
{
id: "company-1",
type: "COMPANY",
@@ -58,7 +69,7 @@ vi.mock("../../lib/adminApi", () => ({
],
limit: 1000,
offset: 0,
total: 3,
total: 4,
})),
fetchAdminRPUsageDaily: vi.fn(async () => ({
days: 14,
@@ -150,7 +161,7 @@ describe("admin overview and auth guard pages", () => {
renderWithProviders(<GlobalOverviewPage />);
expect(
await screen.findByText("회사별 앱별 로그인요청/기타 요청 현황"),
await screen.findByText("회사별 앱별 로그인 요청 현황"),
).toBeInTheDocument();
expect(
await screen.findByLabelText("일 단위 RP 요청 현황"),
@@ -168,7 +179,7 @@ describe("admin overview and auth guard pages", () => {
expect(
(await screen.findByText("전체 테넌트 수")).parentElement,
).toHaveTextContent("3");
).toHaveTextContent("4");
expect(screen.getByText("OIDC 클라이언트").parentElement).toHaveTextContent(
"3",
);
@@ -180,17 +191,30 @@ describe("admin overview and auth guard pages", () => {
);
});
it("changes the RP usage perspective and targets a permitted organization", async () => {
it("limits the overview graph choices to company tenants", async () => {
renderWithProviders(<GlobalOverviewPage />);
await screen.findByText("회사별 앱별 로그인요청/기타 요청 현황");
await screen.findByText("회사별 앱별 로그인 요청 현황");
expect(
await screen.findByRole("checkbox", { name: "한맥 (hanmac)" }),
).toBeInTheDocument();
expect(
screen.queryByText("한맥그룹 (hanmac-group)"),
).not.toBeInTheDocument();
expect(screen.queryByText("개발팀 (dev-team)")).not.toBeInTheDocument();
expect(screen.queryByText("개인 (personal)")).not.toBeInTheDocument();
});
it("changes the RP usage perspective and targets a permitted company", async () => {
renderWithProviders(<GlobalOverviewPage />);
await screen.findByText("회사별 앱별 로그인 요청 현황");
fireEvent.click(screen.getByRole("button", { name: "주" }));
expect(await screen.findAllByText("19(05월1주)")).not.toHaveLength(0);
expect(await screen.findAllByText("40(10월1주)")).not.toHaveLength(0);
fireEvent.click(screen.getByRole("button", { name: "월" }));
fireEvent.click(
screen.getByRole("checkbox", { name: "개발팀 (dev-team)" }),
);
fireEvent.click(screen.getByRole("checkbox", { name: "한맥 (hanmac)" }));
await waitFor(() => {
expect(fetchAdminRPUsageDaily).toHaveBeenLastCalledWith({
@@ -198,6 +222,10 @@ describe("admin overview and auth guard pages", () => {
period: "month",
});
});
expect(
screen.queryByText("한맥그룹 (hanmac-group)"),
).not.toBeInTheDocument();
expect(screen.queryByText("개발팀 (dev-team)")).not.toBeInTheDocument();
expect(screen.queryByText("개인 (personal)")).not.toBeInTheDocument();
expect(await screen.findAllByText("05월")).not.toHaveLength(0);
});
@@ -217,7 +245,7 @@ describe("admin overview and auth guard pages", () => {
renderWithProviders(<GlobalOverviewPage />);
await screen.findByText("회사별 앱별 로그인요청/기타 요청 현황");
await screen.findByText("회사별 앱별 로그인 요청 현황");
expect(screen.queryByText("정합성 최종 검증")).not.toBeInTheDocument();
expect(fetchDataIntegrityReport).not.toHaveBeenCalled();
});

View File

@@ -9,6 +9,11 @@ import {
Users,
} from "lucide-react";
import { type ReactNode, useMemo, useState } from "react";
import {
OverviewAxisNotes,
OverviewMetric,
OverviewSelectionChips,
} from "../../../../common/core/components/overview";
import { RoleGuard } from "../../components/auth/RoleGuard";
import {
type DataIntegrityStatus,
@@ -21,11 +26,6 @@ import {
fetchDataIntegrityReport,
} from "../../lib/adminApi";
import { t } from "../../lib/i18n";
import {
OverviewAxisNotes,
OverviewMetric,
OverviewSelectionChips,
} from "../../../../common/core/components/overview";
type DailyPoint = {
date: string;
@@ -72,7 +72,10 @@ function summarizeSeries(rows: RPUsageDailyMetric[]): SeriesSummary[] {
uniqueSubjects: 0,
} satisfies SeriesSummary);
current.loginRequests += row.loginRequests;
current.uniqueSubjects = Math.max(current.uniqueSubjects, row.uniqueSubjects);
current.uniqueSubjects = Math.max(
current.uniqueSubjects,
row.uniqueSubjects,
);
bySeries.set(key, current);
}
return Array.from(bySeries.values())
@@ -200,10 +203,7 @@ function IntegrityOverviewSummary() {
<AlertTriangle size={18} className="text-amber-600" />
)}
<h3 className="text-base font-semibold">
{t(
"ui.admin.integrity.summary.title",
"정합성 최종 검증",
)}
{t("ui.admin.integrity.summary.title", "정합성 최종 검증")}
</h3>
</div>
<div className="flex flex-wrap items-center gap-3 text-sm">
@@ -213,11 +213,9 @@ function IntegrityOverviewSummary() {
{integrityStatusText(data.status)}
</span>
<span className="tabular-nums">
{t(
"ui.admin.integrity.summary.failures_text",
"실패 {{count}}건",
{ count: data.summary.failures },
)}
{t("ui.admin.integrity.summary.failures_text", "실패 {{count}}건", {
count: data.summary.failures,
})}
</span>
<span className="text-muted-foreground">
{formatOverviewDateTime(data.checkedAt)}
@@ -303,7 +301,7 @@ function RPUsageMixedChart({
<p className="text-sm text-muted-foreground">
{t(
"ui.admin.overview.chart.description",
"전체 또는 선택한 조직 기준으로 그래프를 확인합니다.",
"전체 또는 선택한 회사 기준으로 그래프를 확인합니다.",
)}
</p>
</div>
@@ -397,17 +395,20 @@ function RPUsageMixedChart({
))}
</svg>
</div>
<OverviewAxisNotes
xAxisLabel={t("ui.common.chart.axis.x", "X축: 기간")}
yAxisLabel={t("ui.common.chart.axis.y", "Y축: 로그인 요청 수")}
/>
<OverviewAxisNotes
xAxisLabel={t("ui.common.chart.axis.x", "X축: 기간")}
yAxisLabel={t("ui.common.chart.axis.y", "Y축: 로그인 요청 수")}
/>
</div>
)}
{series.length > 0 && (
<div className="grid gap-x-6 gap-y-2 border-t border-border/60 pt-2 text-xs md:grid-cols-2 xl:grid-cols-3">
{series.map((item) => (
<div key={item.key} className="flex min-w-0 flex-wrap items-center gap-x-3 gap-y-1">
<div
key={item.key}
className="flex min-w-0 flex-wrap items-center gap-x-3 gap-y-1"
>
<span className="font-medium">{item.clientLabel}</span>
<span className="whitespace-nowrap tabular-nums text-muted-foreground">
{t(
@@ -423,7 +424,6 @@ function RPUsageMixedChart({
))}
</div>
)}
</section>
);
}
@@ -444,7 +444,7 @@ function GlobalOverviewPage() {
});
const tenantOptions = useMemo(() => {
return (tenantsQuery.data?.items ?? []).filter(
(tenant) => tenant.type === "COMPANY" || tenant.type === "ORGANIZATION",
(tenant) => tenant.type === "COMPANY",
);
}, [tenantsQuery.data?.items]);
const usageQuery = useQuery({
@@ -582,7 +582,7 @@ function GlobalOverviewPage() {
<p className="text-sm text-muted-foreground">
{t(
"ui.admin.overview.chart.description",
"전체 또는 선택한 조직 기준으로 그래프를 확인합니다.",
"전체 또는 선택한 회사 기준으로 그래프를 확인합니다.",
)}
</p>
</div>