rest api 작동 구현

This commit is contained in:
dohyeons 2025-10-23 14:36:14 +09:00
parent 64658d5d5d
commit 84ce175d95
1 changed files with 118 additions and 40 deletions

View File

@ -48,9 +48,6 @@ export default function CustomMetricWidget({ element }: CustomMetricWidgetProps)
const [error, setError] = useState<string | null>(null);
useEffect(() => {
console.log("🎯 CustomMetricWidget mounted, element:", element);
console.log("📊 dataSource:", element?.dataSource);
console.log("📈 customMetricConfig:", element?.customMetricConfig);
loadData();
// 자동 새로고침 (30초마다)
@ -63,51 +60,127 @@ export default function CustomMetricWidget({ element }: CustomMetricWidgetProps)
setLoading(true);
setError(null);
// 쿼리나 설정이 없으면 초기 상태로 반환
if (!element?.dataSource?.query || !element?.customMetricConfig?.metrics) {
console.log("⚠️ 쿼리 또는 지표 설정이 없습니다");
console.log("- dataSource.query:", element?.dataSource?.query);
console.log("- customMetricConfig.metrics:", element?.customMetricConfig?.metrics);
// 데이터 소스 타입 확인
const dataSourceType = element?.dataSource?.type;
// 설정이 없으면 초기 상태로 반환
if (!element?.customMetricConfig?.metrics) {
setMetrics([]);
setLoading(false);
return;
}
console.log("✅ 쿼리 실행 시작:", element.dataSource.query);
// Database 타입
if (dataSourceType === "database") {
if (!element?.dataSource?.query) {
setMetrics([]);
setLoading(false);
return;
}
const token = localStorage.getItem("authToken");
const response = await fetch("/api/dashboards/execute-query", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({
query: element.dataSource.query,
connectionType: element.dataSource.connectionType || "current",
connectionId: element.dataSource.connectionId,
}),
});
if (!response.ok) throw new Error("데이터 로딩 실패");
const result = await response.json();
if (result.success && result.data?.rows) {
const rows = result.data.rows;
// 각 메트릭 계산
const calculatedMetrics = element.customMetricConfig.metrics.map((metric) => {
const value = calculateMetric(rows, metric.field, metric.aggregation);
return {
...metric,
calculatedValue: value,
};
const token = localStorage.getItem("authToken");
const response = await fetch("/api/dashboards/execute-query", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({
query: element.dataSource.query,
connectionType: element.dataSource.connectionType || "current",
connectionId: element.dataSource.connectionId,
}),
});
setMetrics(calculatedMetrics);
if (!response.ok) throw new Error("데이터 로딩 실패");
const result = await response.json();
if (result.success && result.data?.rows) {
const rows = result.data.rows;
const calculatedMetrics = element.customMetricConfig.metrics.map((metric) => {
const value = calculateMetric(rows, metric.field, metric.aggregation);
return {
...metric,
calculatedValue: value,
};
});
setMetrics(calculatedMetrics);
} else {
throw new Error(result.message || "데이터 로드 실패");
}
}
// API 타입
else if (dataSourceType === "api") {
if (!element?.dataSource?.endpoint) {
setMetrics([]);
setLoading(false);
return;
}
const token = localStorage.getItem("authToken");
const response = await fetch("/api/dashboards/fetch-external-api", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({
method: element.dataSource.method || "GET",
url: element.dataSource.endpoint,
headers: element.dataSource.headers || {},
body: element.dataSource.body,
authType: element.dataSource.authType,
authConfig: element.dataSource.authConfig,
}),
});
if (!response.ok) throw new Error("API 호출 실패");
const result = await response.json();
if (result.success && result.data) {
// API 응답 데이터 구조 확인 및 처리
let rows: any[] = [];
// result.data가 배열인 경우
if (Array.isArray(result.data)) {
rows = result.data;
}
// result.data.results가 배열인 경우 (일반적인 API 응답 구조)
else if (result.data.results && Array.isArray(result.data.results)) {
rows = result.data.results;
}
// result.data.items가 배열인 경우
else if (result.data.items && Array.isArray(result.data.items)) {
rows = result.data.items;
}
// result.data.data가 배열인 경우
else if (result.data.data && Array.isArray(result.data.data)) {
rows = result.data.data;
}
// 그 외의 경우 단일 객체를 배열로 래핑
else {
rows = [result.data];
}
const calculatedMetrics = element.customMetricConfig.metrics.map((metric) => {
const value = calculateMetric(rows, metric.field, metric.aggregation);
return {
...metric,
calculatedValue: value,
};
});
setMetrics(calculatedMetrics);
} else {
throw new Error("API 응답 형식 오류");
}
} else {
throw new Error(result.message || "데이터 로드 실패");
setMetrics([]);
setLoading(false);
}
} catch (err) {
console.error("메트릭 로드 실패:", err);
@ -144,7 +217,12 @@ export default function CustomMetricWidget({ element }: CustomMetricWidgetProps)
);
}
if (!element?.dataSource?.query || !element?.customMetricConfig?.metrics || metrics.length === 0) {
// 데이터 소스가 없거나 설정이 없는 경우
const hasDataSource =
(element?.dataSource?.type === "database" && element?.dataSource?.query) ||
(element?.dataSource?.type === "api" && element?.dataSource?.endpoint);
if (!hasDataSource || !element?.customMetricConfig?.metrics || metrics.length === 0) {
return (
<div className="flex h-full items-center justify-center bg-white p-4">
<div className="max-w-xs space-y-2 text-center">