diff --git a/frontend/components/dashboard/widgets/CustomMetricWidget.tsx b/frontend/components/dashboard/widgets/CustomMetricWidget.tsx index b98bd5cb..4dfc289e 100644 --- a/frontend/components/dashboard/widgets/CustomMetricWidget.tsx +++ b/frontend/components/dashboard/widgets/CustomMetricWidget.tsx @@ -48,9 +48,6 @@ export default function CustomMetricWidget({ element }: CustomMetricWidgetProps) const [error, setError] = useState(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 (