From 71ef496bcc221f4d0cf1d9d6e9571491e27c9558 Mon Sep 17 00:00:00 2001 From: hyeonsu Date: Fri, 19 Sep 2025 09:36:36 +0900 Subject: [PATCH 1/7] =?UTF-8?q?=EB=94=94=EC=9E=90=EC=9D=B8=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../connection/InsertFieldMappingPanel.tsx | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/frontend/components/dataflow/connection/InsertFieldMappingPanel.tsx b/frontend/components/dataflow/connection/InsertFieldMappingPanel.tsx index 8118147c..71892017 100644 --- a/frontend/components/dataflow/connection/InsertFieldMappingPanel.tsx +++ b/frontend/components/dataflow/connection/InsertFieldMappingPanel.tsx @@ -236,14 +236,10 @@ export const InsertFieldMappingPanel: React.FC = ( return (
{/* 헤더 섹션 */} - - -

- 양쪽 테이블의 컬럼을 클릭하여 매핑하거나, 대상 컬럼에 기본값을 입력하세요. 같은 데이터 타입의 컬럼만 매핑 - 가능합니다. 하나의 FROM 컬럼은 하나의 TO 컬럼에만 매핑 가능합니다. -

-
-
+

+ 양쪽 테이블의 컬럼을 클릭하여 매핑하거나, 대상 컬럼에 기본값을 입력하세요. 같은 데이터 타입의 컬럼만 매핑 + 가능합니다. 하나의 FROM 컬럼은 하나의 TO 컬럼에만 매핑 가능합니다. +

= (
{/* 빠른 필터 액션 */} - + 빠른 필터:
- {relationship.fromColumns.join(", ")} → {relationship.toColumns.join(", ")} + {relationship.fromTable} → {relationship.toTable}
From 210a4ec62d8e1cde2b207c49a68e10e1d6f59624 Mon Sep 17 00:00:00 2001 From: hyeonsu Date: Fri, 19 Sep 2025 10:26:57 +0900 Subject: [PATCH 3/7] =?UTF-8?q?=EC=A0=9C=EC=96=B4=EA=B4=80=EB=A6=AC=20?= =?UTF-8?q?=EC=BA=94=EB=B2=84=EC=8A=A4=20=ED=85=8C=EC=9D=B4=EB=B8=94=20?= =?UTF-8?q?=EB=B0=8F=20=EC=BB=AC=EB=9F=BC=20db=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=EA=B0=80=EC=A0=B8=EC=98=A4=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/dataflow/DataFlowDesigner.tsx | 8 +++- frontend/components/dataflow/TableNode.tsx | 23 +++++++---- frontend/lib/api/dataflow.ts | 40 +++++++++++++++++-- 3 files changed, 57 insertions(+), 14 deletions(-) diff --git a/frontend/components/dataflow/DataFlowDesigner.tsx b/frontend/components/dataflow/DataFlowDesigner.tsx index 7ebe19ab..f3a5372f 100644 --- a/frontend/components/dataflow/DataFlowDesigner.tsx +++ b/frontend/components/dataflow/DataFlowDesigner.tsx @@ -284,8 +284,12 @@ export const DataFlowDesigner: React.FC = ({ description: "", // 새로 추가된 노드는 description 없이 통일 columns: Array.isArray(table.columns) ? table.columns.map((col) => ({ - name: col.columnName || "unknown", - type: col.dataType || "varchar", // 기존과 동일한 기본값 사용 + columnName: col.columnName || "unknown", + name: col.columnName || "unknown", // 호환성을 위해 유지 + displayName: col.displayName, // 한국어 라벨 + columnLabel: col.columnLabel, // 한국어 라벨 + type: col.dataType || "varchar", + dataType: col.dataType || "varchar", description: col.description || "", })) : [], diff --git a/frontend/components/dataflow/TableNode.tsx b/frontend/components/dataflow/TableNode.tsx index bbe7f88b..67fdf8b0 100644 --- a/frontend/components/dataflow/TableNode.tsx +++ b/frontend/components/dataflow/TableNode.tsx @@ -4,9 +4,13 @@ import React from "react"; import { Handle, Position } from "@xyflow/react"; interface TableColumn { - name: string; - type: string; - description: string; + columnName: string; + name?: string; // 호환성을 위해 유지 + columnLabel?: string; + displayName?: string; + dataType?: string; + type?: string; // 호환성을 위해 유지 + description?: string; } interface Table { @@ -43,21 +47,24 @@ export const TableNode: React.FC<{ data: TableNodeData }> = ({ data }) => {
{table.columns.map((column) => { - const isSelected = selectedColumns.includes(column.name); + const columnKey = column.columnName || column.name || ""; + const columnDisplayName = column.displayName || column.columnLabel || column.name || column.columnName; + const columnType = column.dataType || column.type || ""; + const isSelected = selectedColumns.includes(columnKey); return (
onColumnClick(table.tableName, column.name)} + onClick={() => onColumnClick(table.tableName, columnKey)} > {/* 핸들 제거됨 - 컬럼 클릭으로만 연결 생성 */}
- {column.name} - {column.type} + {columnDisplayName} + {columnType}
{column.description &&
{column.description}
}
diff --git a/frontend/lib/api/dataflow.ts b/frontend/lib/api/dataflow.ts index c40f1a3b..2e5610a5 100644 --- a/frontend/lib/api/dataflow.ts +++ b/frontend/lib/api/dataflow.ts @@ -376,7 +376,13 @@ export class DataFlowAPI { } // 페이지네이션된 응답에서 columns 배열만 추출 - return response.data.data?.columns || []; + const columns = response.data.data?.columns || []; + + // 이미 displayName에 라벨이 포함되어 있으므로 추가 처리 불필요 + return columns.map((column) => ({ + ...column, + columnLabel: column.displayName || column.columnName, // displayName을 columnLabel로도 설정 + })); } catch (error) { console.error("컬럼 정보 조회 오류:", error); throw error; @@ -390,11 +396,37 @@ export class DataFlowAPI { try { const columns = await this.getTableColumns(tableName); + // 테이블 라벨 정보 조회 + let tableLabel = tableName; + let tableDescription = `${tableName} 테이블`; + + try { + const response = await apiClient.get(`/table-management/tables/${tableName}/labels`); + if (response.data.success && response.data.data) { + tableLabel = response.data.data.tableLabel || tableName; + tableDescription = response.data.data.description || `${tableName} 테이블`; + } + } catch { + // 라벨 정보가 없으면 기본값 사용 + console.log(`테이블 라벨 정보 없음: ${tableName}`); + } + + // TableNode가 기대하는 컬럼 구조로 변환 + const formattedColumns = columns.map((column) => ({ + columnName: column.columnName, + name: column.columnName, // TableNode에서 사용하는 필드 + displayName: column.displayName, // 한국어 라벨 + columnLabel: column.displayName, // 동일한 값으로 설정 + type: column.dataType, // TableNode에서 사용하는 필드 + dataType: column.dataType, + description: column.description || "", + })); + return { tableName, - displayName: tableName, - description: `${tableName} 테이블`, - columns, + displayName: tableLabel, + description: tableDescription, + columns: formattedColumns, }; } catch (error) { console.error("테이블 및 컬럼 정보 조회 오류:", error); From 8dec80fe2272b7b8bf7a09f44c756f338352f964 Mon Sep 17 00:00:00 2001 From: hyeonsu Date: Fri, 19 Sep 2025 10:45:09 +0900 Subject: [PATCH 4/7] =?UTF-8?q?=ED=95=84=EB=93=9C=20=EC=97=B0=EA=B2=B0=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=20=ED=85=8C=EC=9D=B4=EB=B8=94=20=EB=B0=8F=20?= =?UTF-8?q?=EC=BB=AC=EB=9F=BC=20db=EC=97=90=EC=84=9C=20=EA=B0=80=EC=A0=B8?= =?UTF-8?q?=EC=98=A4=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/components/dataflow/ConnectionSetupModal.tsx | 7 +++++-- .../components/dataflow/condition/ConditionRenderer.tsx | 2 +- .../dataflow/connection/ActionConditionRenderer.tsx | 4 ++-- .../dataflow/connection/ActionFieldMappings.tsx | 4 ++-- .../components/dataflow/connection/ActionSplitConfig.tsx | 4 ++-- .../components/dataflow/connection/ColumnTableSection.tsx | 8 ++++++-- .../components/dataflow/connection/SimpleKeySettings.tsx | 4 ++-- 7 files changed, 20 insertions(+), 13 deletions(-) diff --git a/frontend/components/dataflow/ConnectionSetupModal.tsx b/frontend/components/dataflow/ConnectionSetupModal.tsx index b0c27fe9..781d7c33 100644 --- a/frontend/components/dataflow/ConnectionSetupModal.tsx +++ b/frontend/components/dataflow/ConnectionSetupModal.tsx @@ -185,6 +185,9 @@ export const ConnectionSetupModal: React.FC = ({ // 모달이 열릴 때 기본값 설정 useEffect(() => { if (isOpen && connection) { + // 모달이 열릴 때마다 캐시 초기화 (라벨 업데이트 반영) + setTableColumnsCache({}); + const fromTableName = connection.fromNode.tableName; const toTableName = connection.toNode.tableName; const fromDisplayName = connection.fromNode.displayName; @@ -283,8 +286,8 @@ export const ConnectionSetupModal: React.FC = ({ }, [selectedFromColumns, selectedToColumns]); // 테이블 컬럼 로드 함수 (캐시 활용) - const loadTableColumns = async (tableName: string): Promise => { - if (tableColumnsCache[tableName]) { + const loadTableColumns = async (tableName: string, forceReload = false): Promise => { + if (tableColumnsCache[tableName] && !forceReload) { return tableColumnsCache[tableName]; } diff --git a/frontend/components/dataflow/condition/ConditionRenderer.tsx b/frontend/components/dataflow/condition/ConditionRenderer.tsx index 88a1dbff..17c38bf9 100644 --- a/frontend/components/dataflow/condition/ConditionRenderer.tsx +++ b/frontend/components/dataflow/condition/ConditionRenderer.tsx @@ -172,7 +172,7 @@ export const ConditionRenderer: React.FC = ({ {fromTableColumns.map((column) => ( - {column.columnName} + {column.displayName || column.columnLabel || column.columnName} ))} diff --git a/frontend/components/dataflow/connection/ActionConditionRenderer.tsx b/frontend/components/dataflow/connection/ActionConditionRenderer.tsx index fb542ebc..6c0cb933 100644 --- a/frontend/components/dataflow/connection/ActionConditionRenderer.tsx +++ b/frontend/components/dataflow/connection/ActionConditionRenderer.tsx @@ -214,13 +214,13 @@ export const ActionConditionRenderer: React.FC = ( {condition.tableType === "from" && fromTableColumns.map((column) => ( - {column.columnName} + {column.displayName || column.columnLabel || column.columnName} ))} {condition.tableType === "to" && toTableColumns.map((column) => ( - {column.columnName} + {column.displayName || column.columnLabel || column.columnName} ))} diff --git a/frontend/components/dataflow/connection/ActionFieldMappings.tsx b/frontend/components/dataflow/connection/ActionFieldMappings.tsx index dcd56cf6..008b6a32 100644 --- a/frontend/components/dataflow/connection/ActionFieldMappings.tsx +++ b/frontend/components/dataflow/connection/ActionFieldMappings.tsx @@ -154,7 +154,7 @@ export const ActionFieldMappings: React.FC = ({ tableColumnsCache[mapping.sourceTable]?.map((column) => (
- {column.columnName} + {column.displayName || column.columnLabel || column.columnName}
))} @@ -200,7 +200,7 @@ export const ActionFieldMappings: React.FC = ({ tableColumnsCache[mapping.targetTable]?.map((column) => (
- {column.columnName} + {column.displayName || column.columnLabel || column.columnName}
))} diff --git a/frontend/components/dataflow/connection/ActionSplitConfig.tsx b/frontend/components/dataflow/connection/ActionSplitConfig.tsx index 9538a50a..685c85a1 100644 --- a/frontend/components/dataflow/connection/ActionSplitConfig.tsx +++ b/frontend/components/dataflow/connection/ActionSplitConfig.tsx @@ -90,7 +90,7 @@ export const ActionSplitConfig: React.FC = ({ {fromTableColumns.map((column) => ( - {column.columnName} + {column.displayName || column.columnLabel || column.columnName} ))} @@ -117,7 +117,7 @@ export const ActionSplitConfig: React.FC = ({ {toTableColumns.map((column) => ( - {column.columnName} + {column.displayName || column.columnLabel || column.columnName} ))} diff --git a/frontend/components/dataflow/connection/ColumnTableSection.tsx b/frontend/components/dataflow/connection/ColumnTableSection.tsx index 0edbd0a6..eff8ba18 100644 --- a/frontend/components/dataflow/connection/ColumnTableSection.tsx +++ b/frontend/components/dataflow/connection/ColumnTableSection.tsx @@ -198,7 +198,9 @@ export const ColumnTableSection: React.FC = ({
- {column.columnName} + + {column.displayName || column.columnLabel || column.columnName} + {isSelected && } {isMapped && } {oppositeSelectedColumn && !isTypeCompatible && ( @@ -264,7 +266,9 @@ export const ColumnTableSection: React.FC = ({ >
- {column.columnName} + + {column.displayName || column.columnLabel || column.columnName} + {isSelected && } {oppositeSelectedColumn && !isTypeCompatible && ( diff --git a/frontend/components/dataflow/connection/SimpleKeySettings.tsx b/frontend/components/dataflow/connection/SimpleKeySettings.tsx index c5fc5abe..57b5dd91 100644 --- a/frontend/components/dataflow/connection/SimpleKeySettings.tsx +++ b/frontend/components/dataflow/connection/SimpleKeySettings.tsx @@ -83,7 +83,7 @@ export const SimpleKeySettings: React.FC = ({ }} className="rounded" /> - {column.columnName} + {column.displayName || column.columnLabel || column.columnName} ({column.dataType}) ))} @@ -112,7 +112,7 @@ export const SimpleKeySettings: React.FC = ({ }} className="rounded" /> - {column.columnName} + {column.displayName || column.columnLabel || column.columnName} ({column.dataType}) ))} From a8fc5cbd92b070e55f5a44255125d4d5c2900c54 Mon Sep 17 00:00:00 2001 From: hyeonsu Date: Fri, 19 Sep 2025 11:00:47 +0900 Subject: [PATCH 5/7] =?UTF-8?q?=EB=9D=BC=EB=B2=A8=EC=9D=B4=20=EC=97=86?= =?UTF-8?q?=EC=9D=84=20=EA=B2=BD=EC=9A=B0=20=EC=98=A4=EB=A5=98=20=ED=95=B4?= =?UTF-8?q?=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controllers/tableManagementController.ts | 28 ++++++++----------- frontend/lib/api/dataflow.ts | 9 ++++-- 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/backend-node/src/controllers/tableManagementController.ts b/backend-node/src/controllers/tableManagementController.ts index 10f76e72..c3192a6f 100644 --- a/backend-node/src/controllers/tableManagementController.ts +++ b/backend-node/src/controllers/tableManagementController.ts @@ -282,15 +282,13 @@ export async function getTableLabels( const tableLabels = await tableManagementService.getTableLabels(tableName); if (!tableLabels) { - const response: ApiResponse = { - success: false, - message: "테이블 라벨 정보를 찾을 수 없습니다.", - error: { - code: "TABLE_LABELS_NOT_FOUND", - details: `테이블 ${tableName}의 라벨 정보가 존재하지 않습니다.`, - }, + // 라벨이 없으면 빈 객체를 성공으로 반환 (404 에러 대신) + const response: ApiResponse<{}> = { + success: true, + message: "테이블 라벨 정보를 조회했습니다.", + data: {}, }; - res.status(404).json(response); + res.status(200).json(response); return; } @@ -350,15 +348,13 @@ export async function getColumnLabels( ); if (!columnLabels) { - const response: ApiResponse = { - success: false, - message: "컬럼 라벨 정보를 찾을 수 없습니다.", - error: { - code: "COLUMN_LABELS_NOT_FOUND", - details: `컬럼 ${tableName}.${columnName}의 라벨 정보가 존재하지 않습니다.`, - }, + // 라벨이 없으면 빈 객체를 성공으로 반환 (404 에러 대신) + const response: ApiResponse<{}> = { + success: true, + message: "컬럼 라벨 정보를 조회했습니다.", + data: {}, }; - res.status(404).json(response); + res.status(200).json(response); return; } diff --git a/frontend/lib/api/dataflow.ts b/frontend/lib/api/dataflow.ts index 2e5610a5..48a59529 100644 --- a/frontend/lib/api/dataflow.ts +++ b/frontend/lib/api/dataflow.ts @@ -406,9 +406,12 @@ export class DataFlowAPI { tableLabel = response.data.data.tableLabel || tableName; tableDescription = response.data.data.description || `${tableName} 테이블`; } - } catch { - // 라벨 정보가 없으면 기본값 사용 - console.log(`테이블 라벨 정보 없음: ${tableName}`); + } catch (error) { + // 라벨 정보가 없으면 기본값 사용 (404 등의 에러는 무시) + const axiosError = error as { response?: { status?: number } }; + if (axiosError?.response?.status !== 404) { + console.warn(`테이블 라벨 조회 중 예상치 못한 오류: ${tableName}`, error); + } } // TableNode가 기대하는 컬럼 구조로 변환 From ddbf1257673aefaf678daed5c1b4d959a63ca3cf Mon Sep 17 00:00:00 2001 From: hyeonsu Date: Fri, 19 Sep 2025 11:09:21 +0900 Subject: [PATCH 6/7] =?UTF-8?q?=EB=8B=A8=EC=88=9C=20=ED=82=A4=EA=B0=92=20?= =?UTF-8?q?=EC=97=B0=EA=B2=B0=20=EB=9D=BC=EB=B2=A8=EB=A1=9C=20=EB=B3=B4?= =?UTF-8?q?=EC=97=AC=EC=A3=BC=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dataflow/connection/SimpleKeySettings.tsx | 28 ++++++++++++------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/frontend/components/dataflow/connection/SimpleKeySettings.tsx b/frontend/components/dataflow/connection/SimpleKeySettings.tsx index 57b5dd91..a1c34f34 100644 --- a/frontend/components/dataflow/connection/SimpleKeySettings.tsx +++ b/frontend/components/dataflow/connection/SimpleKeySettings.tsx @@ -132,11 +132,15 @@ export const SimpleKeySettings: React.FC = ({
{selectedFromColumns.length > 0 ? ( - selectedFromColumns.map((column) => ( - - {column} - - )) + selectedFromColumns.map((column) => { + const columnInfo = fromTableColumns.find((col) => col.columnName === column); + const displayName = columnInfo?.displayName || columnInfo?.columnLabel || column; + return ( + + {displayName} + + ); + }) ) : ( 선택된 컬럼 없음 )} @@ -147,11 +151,15 @@ export const SimpleKeySettings: React.FC = ({
{selectedToColumns.length > 0 ? ( - selectedToColumns.map((column) => ( - - {column} - - )) + selectedToColumns.map((column) => { + const columnInfo = toTableColumns.find((col) => col.columnName === column); + const displayName = columnInfo?.displayName || columnInfo?.columnLabel || column; + return ( + + {displayName} + + ); + }) ) : ( 선택된 컬럼 없음 )} From a14ad5eefd572437f6a2b493996f5843159bd797 Mon Sep 17 00:00:00 2001 From: hyeonsu Date: Fri, 19 Sep 2025 11:27:16 +0900 Subject: [PATCH 7/7] =?UTF-8?q?=ED=95=84=EB=93=9C=20=EC=97=B0=EA=B2=B0=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=20=EB=AA=A8=EB=8B=AC=20=EB=A7=A4=ED=95=91=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dataflow/ConnectionSetupModal.tsx | 21 ++----------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/frontend/components/dataflow/ConnectionSetupModal.tsx b/frontend/components/dataflow/ConnectionSetupModal.tsx index 781d7c33..b5d5e89c 100644 --- a/frontend/components/dataflow/ConnectionSetupModal.tsx +++ b/frontend/components/dataflow/ConnectionSetupModal.tsx @@ -556,26 +556,9 @@ export const ConnectionSetupModal: React.FC = ({ return true; // DELETE는 필드 매핑 검증 생략 } - // INSERT 액션의 경우 모든 TO 테이블 컬럼이 매핑되거나 기본값이 있어야 함 + // INSERT 액션의 경우 최소 하나의 매핑이 있으면 됨 (모든 컬럼 매핑 필수 조건 제거) if (action.actionType === "insert") { - // TO 테이블의 모든 컬럼을 찾기 - const toTableName = action.fieldMappings[0]?.targetTable; - if (!toTableName) return false; - - const toTableColumns = tableColumnsCache[toTableName] || []; - if (toTableColumns.length === 0) return false; - - // 모든 TO 컬럼이 매핑되거나 기본값이 있는지 확인 - return toTableColumns.every((column) => { - const mapping = action.fieldMappings.find((m) => m.targetField === column.columnName); - if (!mapping) return false; - - // 소스 매핑 또는 기본값 중 하나는 있어야 함 - const hasSource = mapping.sourceTable && mapping.sourceField; - const hasDefault = mapping.defaultValue && mapping.defaultValue.trim(); - - return hasSource || hasDefault; - }); + return true; // 필드 매핑이 있으면 충분함 } return action.fieldMappings.every((mapping) => {