-
+
HTTP Method
-
setExternalCallSettings({ ...externalCallSettings, httpMethod: value })
- }
- >
-
-
-
-
+ }
+ >
+
+
+
+
GET
POST
PUT
DELETE
-
-
-
+
+
+
Headers
diff --git a/frontend/components/dataflow/DataFlowDesigner.tsx b/frontend/components/dataflow/DataFlowDesigner.tsx
index bb3b6264..0cfa551d 100644
--- a/frontend/components/dataflow/DataFlowDesigner.tsx
+++ b/frontend/components/dataflow/DataFlowDesigner.tsx
@@ -74,6 +74,56 @@ interface DataFlowDesignerProps {
// 내부에서 사용할 확장된 JsonRelationship 타입 (connectionType 포함)
interface ExtendedJsonRelationship extends JsonRelationship {
connectionType: "simple-key" | "data-save" | "external-call";
+ settings?: {
+ control?: {
+ triggerType?: "insert" | "update" | "delete";
+ conditionTree?: Array<{
+ id: string;
+ type: string;
+ field?: string;
+ operator_type?: string;
+ value?: unknown;
+ logicalOperator?: string;
+ groupId?: string;
+ groupLevel?: number;
+ }>;
+ };
+ actions?: Array<{
+ id: string;
+ name: string;
+ actionType: "insert" | "update" | "delete" | "upsert";
+ conditions?: Array<{
+ id: string;
+ type: string;
+ field?: string;
+ operator_type?: string;
+ value?: unknown;
+ logicalOperator?: string;
+ groupId?: string;
+ groupLevel?: number;
+ }>;
+ fieldMappings: Array<{
+ sourceTable?: string;
+ sourceField: string;
+ targetTable?: string;
+ targetField: string;
+ defaultValue?: string;
+ }>;
+ splitConfig?: {
+ sourceField: string;
+ delimiter: string;
+ targetField: string;
+ };
+ }>;
+ notes?: string;
+ apiCall?: {
+ url: string;
+ method: string;
+ headers: Array<{ key: string; value: string }>;
+ body: string;
+ successCriteria: string;
+ };
+ };
}
export const DataFlowDesigner: React.FC = ({
@@ -249,7 +299,7 @@ export const DataFlowDesigner: React.FC = ({
console.log("🔥 정규화된 관계들:", normalizedRelationships);
setTempRelationships(normalizedRelationships);
setCurrentDiagramId(currentDiagramId);
- setCurrentDiagramCategory(jsonDiagram.category || "simple-key"); // 관계도의 연결 종류 설정
+ setCurrentDiagramCategory("simple-key"); // 관계도의 연결 종류 설정 (기본값)
// 테이블 노드 생성을 위한 테이블 정보 로드
@@ -755,7 +805,7 @@ export const DataFlowDesigner: React.FC = ({
console.log("🔥 연결 타입:", newRelationship.connectionType);
// 첫 번째 관계가 추가되면 관계도의 category를 해당 connectionType으로 설정
- if (tempRelationships.length === 0) {
+ if ((tempRelationships || []).length === 0) {
setCurrentDiagramCategory(relationship.connection_type);
}
@@ -796,7 +846,7 @@ export const DataFlowDesigner: React.FC = ({
console.log("메모리에 관계 생성 완료:", newRelationship);
toast.success("관계가 생성되었습니다. 저장 버튼을 눌러 관계도를 저장하세요.");
},
- [pendingConnection, setEdges, editingRelationshipId, tempRelationships.length],
+ [pendingConnection, setEdges, editingRelationshipId, tempRelationships],
);
// 연결 설정 취소
@@ -841,12 +891,12 @@ export const DataFlowDesigner: React.FC = ({
console.log("🔍 저장할 노드 위치 정보:", nodePositions);
console.log("📊 현재 노드 개수:", nodes.length);
console.log("📋 연결된 테이블 목록:", connectedTables);
- console.log("🔗 관계 개수:", tempRelationships.length);
+ console.log("🔗 관계 개수:", (tempRelationships || []).length);
// 🔥 주요 연결 타입 변수 제거 (더 이상 사용하지 않음)
// 🔥 수정: relationships는 핵심 관계 정보만 포함, settings 전체 제거
- const cleanRelationships = tempRelationships.map((rel) => {
+ const cleanRelationships = (tempRelationships || []).map((rel) => {
// 🔥 settings 전체를 제거하고 핵심 정보만 유지
const cleanRel: JsonRelationship = {
id: rel.id,
@@ -871,12 +921,12 @@ export const DataFlowDesigner: React.FC = ({
},
node_positions: nodePositions,
// 🔥 수정: 각 관계별 category 정보를 배열로 저장
- category: tempRelationships.map((rel) => ({
+ category: (tempRelationships || []).map((rel) => ({
id: rel.id,
category: rel.connectionType,
})),
// 🔥 각 관계별 control 정보를 배열로 저장 (전체 실행 조건)
- control: tempRelationships
+ control: (tempRelationships || [])
.filter((rel) => rel.connectionType === "data-save")
.map((rel) => {
console.log("🔍 Control 데이터 추출 중:", {
@@ -889,57 +939,159 @@ export const DataFlowDesigner: React.FC = ({
const controlData = rel.settings?.control as {
triggerType?: "insert" | "update" | "delete";
conditionTree?: Array<{
- field: string;
- operator_type: "=" | "!=" | ">" | "<" | ">=" | "<=" | "LIKE";
- value: unknown;
+ id: string;
+ type: string;
+ field?: string;
+ operator_type?: "=" | "!=" | ">" | "<" | ">=" | "<=" | "LIKE";
+ value?: unknown;
logicalOperator?: "AND" | "OR";
+ groupId?: string;
+ groupLevel?: number;
}>;
};
console.log("🔍 추출된 controlData:", controlData);
console.log("🔍 conditionTree:", controlData?.conditionTree);
+ // 🔥 조건 필터링: 괄호 포함 모든 유효한 조건 유지
+ const validConditions = (controlData?.conditionTree || [])
+ .filter((cond) => {
+ // 괄호 조건은 항상 유지
+ if (cond.type === "group-start" || cond.type === "group-end") {
+ return true;
+ }
+ // 실제 조건은 field와 value가 있어야 함
+ return cond.type === "condition" && cond.field && cond.value !== undefined && cond.value !== "";
+ })
+ .map((cond, index) => {
+ // 괄호 조건 처리
+ if (cond.type === "group-start" || cond.type === "group-end") {
+ return {
+ id: cond.id,
+ type: cond.type,
+ ...(cond.groupId && { groupId: cond.groupId }),
+ ...(cond.groupLevel !== undefined && { groupLevel: cond.groupLevel }),
+ // 첫 번째가 아닐 때만 logicalOperator 포함
+ ...(index > 0 && cond.logicalOperator && { logicalOperator: cond.logicalOperator }),
+ };
+ }
+
+ // 일반 조건 처리
+ return {
+ id: cond.id,
+ type: cond.type,
+ field: cond.field,
+ operator: cond.operator_type,
+ value: cond.value,
+ dataType: "string", // 기본값
+ // 첫 번째 조건이 아닐 때만 logicalOperator 포함
+ ...(index > 0 && cond.logicalOperator && { logicalOperator: cond.logicalOperator }),
+ };
+ });
+
return {
id: rel.id, // relationships의 id와 동일
triggerType: (controlData?.triggerType as "insert" | "update" | "delete") || "insert",
- // 🔥 실제 저장된 conditionTree에서 조건 추출
- conditions: (controlData?.conditionTree || []).map((cond) => ({
- field: cond.field,
- operator: cond.operator_type,
- value: cond.value,
- logicalOperator: cond.logicalOperator || "AND",
- })),
+ conditions: validConditions,
};
}),
// 🔥 각 관계별 plan 정보를 배열로 저장 (저장 액션)
- plan: tempRelationships
+ plan: (tempRelationships || [])
.filter((rel) => rel.connectionType === "data-save")
.map((rel) => ({
id: rel.id, // relationships의 id와 동일
sourceTable: rel.fromTable,
// 🔥 실제 사용자가 설정한 액션들 사용
actions:
- (rel.settings?.actions as Array<{
- id: string;
- name: string;
- actionType: "insert" | "update" | "delete" | "upsert";
- fieldMappings: Array<{
- sourceTable?: string;
- sourceField: string;
- targetTable?: string;
- targetField: string;
- defaultValue?: string;
- transformFunction?: string;
- }>;
- conditions?: Array<{
+ (
+ rel.settings?.actions as Array<{
id: string;
- type: string;
- field: string;
- operator_type: string;
- value: unknown;
- logicalOperator?: string;
- }>;
- }>) || [],
+ name: string;
+ actionType: "insert" | "update" | "delete" | "upsert";
+ fieldMappings: Array<{
+ sourceTable?: string;
+ sourceField: string;
+ targetTable?: string;
+ targetField: string;
+ defaultValue?: string;
+ transformFunction?: string;
+ }>;
+ splitConfig?: {
+ sourceField: string;
+ delimiter: string;
+ targetField: string;
+ };
+ conditions?: Array<{
+ id: string;
+ type: string;
+ field?: string;
+ operator_type?: string;
+ value?: unknown;
+ logicalOperator?: string;
+ groupId?: string;
+ groupLevel?: number;
+ }>;
+ }>
+ )?.map((action) => ({
+ ...action,
+ // fieldMappings에서 불필요한 transformFunction 제거
+ fieldMappings: action.fieldMappings?.map((mapping) => ({
+ sourceTable: mapping.sourceTable,
+ sourceField: mapping.sourceField,
+ targetTable: mapping.targetTable,
+ targetField: mapping.targetField,
+ defaultValue: mapping.defaultValue,
+ // transformFunction 제거 - 불필요한 필드
+ })),
+ // splitConfig 처리 - 사용자가 설정하지 않은 경우 포함하지 않음
+ ...(action.splitConfig &&
+ (action.splitConfig.sourceField ||
+ action.splitConfig.delimiter ||
+ action.splitConfig.targetField) && {
+ splitConfig: {
+ sourceField: action.splitConfig.sourceField || "",
+ delimiter: action.splitConfig.delimiter || "",
+ targetField: action.splitConfig.targetField || "",
+ },
+ }),
+ // 🔥 조건 처리: 괄호 포함 모든 유효한 조건 유지
+ ...(action.conditions && {
+ conditions: action.conditions
+ .filter((cond) => {
+ // 괄호 조건은 항상 유지
+ if (cond.type === "group-start" || cond.type === "group-end") {
+ return true;
+ }
+ // 실제 조건은 field와 value가 있어야 함
+ return cond.type === "condition" && cond.field && cond.value !== undefined && cond.value !== "";
+ })
+ .map((cond, index) => {
+ // 괄호 조건 처리
+ if (cond.type === "group-start" || cond.type === "group-end") {
+ return {
+ id: cond.id,
+ type: cond.type,
+ ...(cond.groupId && { groupId: cond.groupId }),
+ ...(cond.groupLevel !== undefined && { groupLevel: cond.groupLevel }),
+ // 첫 번째가 아닐 때만 logicalOperator 포함
+ ...(index > 0 && cond.logicalOperator && { logicalOperator: cond.logicalOperator }),
+ };
+ }
+
+ // 일반 조건 처리
+ return {
+ id: cond.id,
+ type: cond.type,
+ field: cond.field,
+ value: cond.value,
+ dataType: "string", // 기본값
+ operator_type: cond.operator_type,
+ // 첫 번째 조건이 아닐 때만 logicalOperator 포함
+ ...(index > 0 && cond.logicalOperator && { logicalOperator: cond.logicalOperator }),
+ };
+ }),
+ }),
+ })) || [],
})),
};
@@ -955,11 +1107,13 @@ export const DataFlowDesigner: React.FC = ({
let savedDiagram;
- // 편집 모드 vs 신규 생성 모드 구분
- if (diagramId && diagramId > 0) {
+ // 편집 모드 vs 신규 생성 모드 구분 (currentDiagramId 우선 사용)
+ const effectiveDiagramId = currentDiagramId || diagramId;
+
+ if (effectiveDiagramId && effectiveDiagramId > 0) {
// 편집 모드: 기존 관계도 업데이트
savedDiagram = await DataFlowAPI.updateJsonDataFlowDiagram(
- diagramId,
+ effectiveDiagramId,
createRequest,
companyCode,
user?.userId || "SYSTEM",
@@ -981,7 +1135,7 @@ export const DataFlowDesigner: React.FC = ({
setCurrentDiagramId(savedDiagram.diagram_id);
// 관계도 이름 업데이트 (편집 모드일 때만)
- if (diagramId && diagramId > 0 && onDiagramNameUpdate) {
+ if (effectiveDiagramId && effectiveDiagramId > 0 && onDiagramNameUpdate) {
onDiagramNameUpdate(diagramName);
}
@@ -993,7 +1147,7 @@ export const DataFlowDesigner: React.FC = ({
setIsSaving(false);
}
},
- [tempRelationships, diagramId, companyCode, user?.userId, nodes, onDiagramNameUpdate],
+ [tempRelationships, diagramId, currentDiagramId, companyCode, user?.userId, nodes, onDiagramNameUpdate],
);
// 저장 모달 열기
@@ -1204,7 +1358,7 @@ export const DataFlowDesigner: React.FC = ({
hasUnsavedChanges ? "animate-pulse" : ""
}`}
>
- 💾 관계도 저장 {tempRelationships.length > 0 && `(${tempRelationships.length})`}
+ 💾 관계도 저장 {(tempRelationships || []).length > 0 && `(${(tempRelationships || []).length})`}
@@ -1222,7 +1376,7 @@ export const DataFlowDesigner: React.FC
= ({
관계도 ID:
@@ -1313,7 +1467,7 @@ export const DataFlowDesigner: React.FC
= ({
{/* 편집 버튼 */}
{
+ onClick={async (e) => {
e.stopPropagation();
// 관계 선택 시 수정 모드로 전환
setEditingRelationshipId(relationship.id);
@@ -1328,7 +1482,34 @@ export const DataFlowDesigner: React.FC = ({
}
setSelectedColumns(newSelectedColumns);
- // 🔥 수정: 연결 설정 모달 열기
+ // 🔥 수정: 데이터베이스에서 관계 설정 정보 로드
+ let relationshipSettings = {};
+ if (diagramId && diagramId > 0) {
+ try {
+ const jsonDiagram = await DataFlowAPI.getJsonDataFlowDiagramById(
+ diagramId,
+ companyCode,
+ );
+ if (jsonDiagram && relationship.connectionType === "data-save") {
+ const control = jsonDiagram.control?.find((c) => c.id === relationship.id);
+ const plan = jsonDiagram.plan?.find((p) => p.id === relationship.id);
+
+ relationshipSettings = {
+ control: control
+ ? {
+ triggerType: control.triggerType,
+ conditionTree: control.conditions || [],
+ }
+ : undefined,
+ actions: plan ? plan.actions || [] : [],
+ };
+ }
+ } catch (error) {
+ console.error("관계 설정 정보 로드 실패:", error);
+ }
+ }
+
+ // 연결 설정 모달 열기
const fromTable = nodes.find(
(node) => node.data?.table?.tableName === relationship.fromTable,
);
@@ -1361,7 +1542,7 @@ export const DataFlowDesigner: React.FC = ({
existingRelationship: {
relationshipName: relationship.relationshipName,
connectionType: relationship.connectionType,
- settings: relationship.settings || {},
+ settings: relationshipSettings,
},
});
}
@@ -1559,6 +1740,11 @@ export const DataFlowDesigner: React.FC = ({
{/* 연결 설정 모달 */}
;
+ control?: Array<{
+ id: string;
+ triggerType: "insert" | "update" | "delete";
+ conditions: Array<{
+ id?: string;
+ type?: string;
+ field?: string;
+ operator?: string;
+ value?: unknown;
+ logicalOperator?: string;
+ groupId?: string;
+ groupLevel?: number;
+ }>;
+ }>;
+ plan?: Array<{
+ id: string;
+ sourceTable: string;
+ actions: Array<{
+ id: string;
+ name: string;
+ actionType: "insert" | "update" | "delete" | "upsert";
+ conditions?: Array<{
+ id: string;
+ type: string;
+ field?: string;
+ operator_type?: string;
+ value?: unknown;
+ logicalOperator?: string;
+ groupId?: string;
+ groupLevel?: number;
+ }>;
+ fieldMappings: Array<{
+ sourceTable?: string;
+ sourceField: string;
+ targetTable?: string;
+ targetField: string;
+ defaultValue?: string;
+ transformFunction?: string;
+ }>;
+ splitConfig?: {
+ sourceField: string;
+ delimiter: string;
+ targetField: string;
+ };
+ }>;
+ }>;
company_code: string;
created_at?: string;
updated_at?: string;
@@ -230,11 +279,16 @@ export interface CreateDiagramRequest {
control?: Array<{
id: string; // relationships의 id와 동일
triggerType: "insert" | "update" | "delete";
- conditions?: Array<{
- field: string;
- operator: "=" | "!=" | ">" | "<" | ">=" | "<=" | "LIKE";
- value: unknown;
+ conditions: Array<{
+ id?: string;
+ type?: string;
+ field?: string;
+ operator?: string;
+ value?: unknown;
+ dataType?: string;
logicalOperator?: "AND" | "OR";
+ groupId?: string;
+ groupLevel?: number;
}>;
}>;
// 🔥 저장 액션 - relationships의 id와 동일한 id 사용
@@ -251,15 +305,22 @@ export interface CreateDiagramRequest {
targetTable?: string;
targetField: string;
defaultValue?: string;
- transformFunction?: string;
}>;
+ splitConfig?: {
+ sourceField: string;
+ delimiter: string;
+ targetField: string;
+ };
conditions?: Array<{
id: string;
type: string;
- field: string;
- operator_type: string;
- value: unknown;
+ field?: string;
+ operator_type?: string;
+ value?: unknown;
+ dataType?: string;
logicalOperator?: string;
+ groupId?: string;
+ groupLevel?: number;
}>;
}>;
}>;
@@ -629,7 +690,7 @@ export class DataFlowAPI {
to_table_name: rel.toTable,
from_column_name: rel.fromColumns.join(","),
to_column_name: rel.toColumns.join(","),
- connection_type: (jsonDiagram.category as "simple-key" | "data-save" | "external-call") || "simple-key", // 관계도의 category 사용
+ connection_type: rel.connectionType || "simple-key", // 각 관계의 connectionType 사용
company_code: companyCode, // 실제 사용자 회사 코드 사용
settings: rel.settings || {},
created_at: jsonDiagram.created_at,